Bitmaps & Data Element Presence
Lesson, slides, and applied problem sets.
View SlidesLesson
Module 2: Bitmaps & Data Element Presence
Why Bitmaps?
You've just decoded your first MTI. 0100 - an authorization request. Now you need to read the actual transaction data: card number, amount, merchant info. But here's the problem: ISO8583 has 128 possible data elements (192 in the 2003 spec), and no message uses all of them.
How do you know which fields are present?
The naive approach: Prefix each field with an identifier.
Field02:5412345678901234,Field03:000000,Field04:000000010000,...
This works, but it's expensive. Every field needs 7+ bytes of overhead ("Field02:"). In a message with 20 fields, that's 140+ bytes just for identifiers. When you're processing 10,000 transactions per second over intercontinental links in 1987, every byte counts.
The ISO8583 solution: A bitmap.
Instead of labeling each field, you transmit a 64-bit (8-byte) bitmap where each bit represents a field. Bit 2 set? Field 2 is present. Bit 3 clear? Field 3 is absent. Parse the bitmap once, then read fields in order.
8 bytes to indicate presence of up to 64 fields. That's the elegance of bitmaps.
The Primary Bitmap
Every ISO8583 message has a primary bitmap immediately after the MTI. It's always exactly 8 bytes (64 bits), representing fields 1 through 64.
Message structure:
┌──────────┬─────────────────────────┬────────────────────────────────┐
│ MTI │ Primary Bitmap │ Data Elements │
│ 4 chars │ 8 bytes │ (variable length) │
│ 0100 │ 72 3A 44 81 08 E1 80 00│ [field data in order...] │
└──────────┴─────────────────────────┴────────────────────────────────┘
Bit Numbering: The Critical Detail
Here's where most bugs originate. ISO8583 uses 1-based bit numbering with big-endian byte order and MSB-first bit order.
What does that mean? Let's decode 72 3A 44 81:
Byte 0: 72 (hex) = 0111 0010 (binary)
││││ ││││
Bit numbers: 1234 5678
Byte 1: 3A (hex) = 0011 1010 (binary)
││││ ││││
Bit numbers: 9... ...16
Reading the bits:
- Bit 1 (leftmost bit of byte 0) = 0 → Field 1 absent (no secondary bitmap)
- Bit 2 = 1 → Field 2 (PAN) present
- Bit 3 = 1 → Field 3 (Processing Code) present
- Bit 4 = 1 → Field 4 (Amount) present
- Bit 5 = 0 → Field 5 absent
- Bit 6 = 0 → Field 6 absent
- Bit 7 = 1 → Field 7 (Transmission Date/Time) present
- Bit 8 = 0 → Field 8 absent
The indexing trap: Programmers naturally think of index 0 for the first element. ISO8583 uses index 1. Bit 1 is at byte 0, bit position 7 (the most significant bit). This off-by-one difference causes countless bugs.
To check if field N is present:
byteIndex = (N - 1) / 8
bitPosition = 7 - ((N - 1) % 8)
isPresent = (bitmap[byteIndex] >> bitPosition) & 1
Example: Is field 11 present?
byteIndex = (11 - 1) / 8 = 1
bitPosition = 7 - ((11 - 1) % 8) = 7 - 2 = 5
Check bit 5 of byte 1
## Off-by-one sanity checks
Use this tiny table to keep indexing correct:
| Field | `byteIndex` | `bitPosition` |
|---|---:|---:|
| 1 | 0 | 7 |
| 8 | 0 | 0 |
| 9 | 1 | 7 |
| 16 | 1 | 0 |
| 64 | 7 | 0 |
| 65 | 8 | 7 |
| 128 | 15 | 0 |
If field 1 is read as byte index 0 and bit position 0, everything else will drift.
Complete Bitmap Walkthrough
Let's fully decode a real bitmap: 72 3A 44 81 08 E1 80 00
Byte Hex Binary Bits present
──── ─── ──────── ─────────────
0 72 0111 0010 2, 3, 4, 7
1 3A 0011 1010 10, 11, 13, 14
2 44 0100 0100 10 (wait, that's wrong!)
Let me redo this correctly with proper bit numbering:
Byte 0: 72 = 0111 0010
Bits: 1 2 3 4 5 6 7 8
Val: 0 1 1 1 0 0 1 0
Present: 2, 3, 4, 7
Byte 1: 3A = 0011 1010
Bits: 9 10 11 12 13 14 15 16
Val: 0 0 1 1 1 0 1 0
Present: 11, 12, 13, 15
Byte 2: 44 = 0100 0100
Bits: 17 18 19 20 21 22 23 24
Val: 0 1 0 0 0 1 0 0
Present: 18, 22
Byte 3: 81 = 1000 0001
Bits: 25 26 27 28 29 30 31 32
Val: 1 0 0 0 0 0 0 1
Present: 25, 32
Byte 4: 08 = 0000 1000
Bits: 33 34 35 36 37 38 39 40
Val: 0 0 0 0 1 0 0 0
Present: 37
Byte 5: E1 = 1110 0001
Bits: 41 42 43 44 45 46 47 48
Val: 1 1 1 0 0 0 0 1
Present: 41, 42, 43, 48
Byte 6: 80 = 1000 0000
Bits: 49 50 51 52 53 54 55 56
Val: 1 0 0 0 0 0 0 0
Present: 49
Byte 7: 00 = 0000 0000
Bits: 57 58 59 60 61 62 63 64
Val: 0 0 0 0 0 0 0 0
Present: (none)
Fields present: 2, 3, 4, 7, 11, 12, 13, 15, 18, 22, 25, 32, 37, 41, 42, 43, 48, 49
This is a typical authorization request:
- DE2: PAN (card number)
- DE3: Processing code
- DE4: Transaction amount
- DE7: Transmission date/time
- DE11: STAN
- DE12: Local time
- DE13: Local date
- DE15: Settlement date
- DE18: Merchant type
- DE22: POS entry mode
- DE25: POS condition code
- DE32: Acquiring institution ID
- DE37: Retrieval reference number
- DE41: Terminal ID
- DE42: Merchant ID
- DE43: Merchant name/location
- DE48: Additional data (private)
- DE49: Currency code
The Secondary Bitmap: Field 1's Secret
What if you need fields above 64? Enter the secondary bitmap.
The clever design: Bit 1 of the primary bitmap indicates whether a secondary bitmap follows. When bit 1 = 1, the next 8 bytes are the secondary bitmap, covering fields 65-128.
Here's the insight that confuses many engineers: the secondary bitmap IS Field 1 (DE1). There's no separate "Field 1 data" - when bit 1 is set, Field 1's content is the secondary bitmap itself.
Bit 1 = 0: Primary bitmap only
┌──────────┬────────────────────┬────────────────────┐
│ MTI │ Primary Bitmap │ Data Elements │
│ 4 chars │ 8 bytes │ (fields 2-64) │
│ │ Bit 1 = 0 │ │
└──────────┴────────────────────┴────────────────────┘
Bit 1 = 1: Primary + Secondary bitmap
┌──────────┬────────────────────┬───────────────────────┬────────────────────┐
│ MTI │ Primary Bitmap │ Secondary Bitmap │ Data Elements │
│ 4 chars │ 8 bytes │ 8 bytes │ (fields 2-64, │
│ │ Bit 1 = 1 │ (= Field 1/DE1) │ 65-128) │
└──────────┴────────────────────┴───────────────────────┴────────────────────┘
When You Need a Secondary Bitmap
Common fields that require secondary bitmap coverage (fields 65-128):
- DE70: Network management information code
- DE90: Original data elements (for reversals)
- DE95: Replacement amounts
- DE100: Receiving institution ID
- DE102-103: Account identification
- DE123-128: Private use fields
Rules:
- Field 1 must be set when any secondary bitmap field is present.
- A field can be in the primary bitmap only if it is in 1..64.
- If you need 65..128, use a 16-byte bitmap and set field 1.
Secondary Bitmap Bit Numbering
The secondary bitmap follows the same pattern, but for fields 65-128:
Secondary Bitmap: 80 00 00 00 00 00 00 01
Byte 0: 80 = 1000 0000
Bits: 65 66 67 68 69 70 71 72
Val: 1 0 0 0 0 0 0 0
Present: 65
Byte 7: 01 = 0000 0001
Bits: 121 122 123 124 125 126 127 128
Val: 0 0 0 0 0 0 0 1
Present: 128
In the 1987 format, bit positions 1..64 of the secondary bitmap correspond to fields 65..128. In the 2003 format, bit 1 of the secondary bitmap may indicate a tertiary bitmap follows (field 65 = tertiary indicator). That is: if you support 2003 tertiary, field 65 becomes a control bit; otherwise it behaves as a normal presence bit for field 65.
Parsing Contract: How Many Bitmap Bytes?
ISO8583 does not have a single global rule for how bitmaps are encoded; each implementation or network decides between:
8 bytesfor binary bitmap (raw bytes)16 hex chars(ASCII representation of 8 bytes)
Most production outages I have seen are caused by assuming one and seeing the other.
A deterministic bitmap parser
Your parser should determine bytes in a fixed order:
- Read the primary bitmap using configured encoding preference (binary or hex-ascii).
- Check bit 1. If set, attempt to read secondary.
- If secondary bit 1 is set and your parser supports tertiary, read tertiary.
- Validate that each required bitmap segment was fully present before reading fields.
- Preserve remaining bytes exactly; never consume field bytes based on uncertain bitmap.
func parseBitmapBytes(input []byte, encoding string, allowTertiary bool) ([]byte, int, error)
This function should never interpret bytes after the declared bitmap length as bitmap bytes just because they look like a bit pattern.
Why this contract matters
If you parse with a guessed encoding:
- a short hex bitmap (
72 3A...in text form) can be mistaken for binary and under-read, - or binary bytes can be misread as hex text and double-read,
- both cases desynchronize the entire field cursor.
Treat bitmap length as protocol contract, not heuristic guesswork.
Defensive Bitmap Decoding Rules
Use these rules before decoding any field values:
- Length checks first
- Primary must exist (
8bytes in binary form or16chars in hex form). - Secondary requires primary bit 1 plus enough bytes.
- Tertiary requires secondary bit 1 plus enough bytes and support flag.
- Primary must exist (
- Bit-control checks
- If primary bit 1 is
0, never parse secondary. - If primary bit 1 is
1, secondary is required. - If secondary bit 1 is
1while 2003 mode is off, either reject or keep a protocol-compat path explicit.
- If primary bit 1 is
- Error surface
- On error, return:
- raw MTI
- parsed bitmap bytes (hex)
- expected/available byte counts
- field index cursor where parsing stopped
- On error, return:
That error payload is what makes “field drift” problems solvable in minutes instead of days.
Canonical Presence List and Round-Trip Assurance
For reliable interoperability, define two canonical functions:
fieldsFromBitmap(bitmap []byte) []intreturns a strictly increasing list of present fields.bitmapFromFields(fields []int, maxField int) []bytesets bit positions and minimal required byte length.
Then guarantee:
fieldsFromBitmap(bitmapFromFields(fields))should include all input fields.bitmapFromFields(fieldsFromBitmap(bitmap))should match canonicalizedbitmaprepresentation.
This simple invariant catches off-by-one and bit-order regressions before they reach field parsing.
The Tertiary Bitmap: 2003 Spec Extension
The ISO 8583:2003 specification added support for fields 129-192 through a tertiary bitmap.
The pattern continues:
- Bit 1 of primary bitmap → secondary bitmap present (Field 1)
- Bit 1 of secondary bitmap (Field 65) → tertiary bitmap present (Field 65 = tertiary bitmap)
2003 Spec with Tertiary Bitmap:
┌──────────┬─────────────┬─────────────────┬─────────────────┬──────────────┐
│ MTI │ Primary │ Secondary │ Tertiary │ Data │
│ │ (8 bytes) │ (8 bytes) │ (8 bytes) │ Elements │
│ │ Bit 1 = 1 │ Bit 1 = 1 │ (= Field 65) │ │
└──────────┴─────────────┴─────────────────┴─────────────────┴──────────────┘
In practice, you'll rarely see tertiary bitmaps. They're mostly used in:
- Extended EMV data fields
- Network-specific extensions
- Future-proofing large implementations
Bitmap Representations: Binary vs Hex-ASCII
Here's a source of many parsing failures. Bitmaps can be transmitted in two ways:
1. Binary (Raw Bytes)
8 bytes: 72 3A 44 81 08 E1 80 00
This is the compact, standard representation.
2. Hex-ASCII (Character Encoding)
16 characters: "723A448108E18000"
Which is 16 bytes: 37 32 33 41 34 34 38 31 30 38 45 31 38 30 30 30
Some networks and log files transmit bitmaps as hex-encoded ASCII strings. The bitmap 72 becomes the two characters "72" (0x37, 0x32).
How to detect which you have:
- Count the bytes. 8 bytes = binary, 16 bytes = hex-ASCII
- Check byte values. If all bytes are in 0x30-0x39 (digits) or 0x41-0x46/0x61-0x66 (A-F/a-f), it's likely hex-ASCII
Encoding validation checklist:
- Confirm total bytes length first.
- In hex-ASCII mode, length must be even and decode cleanly.
- In binary mode, treat non-printable bytes as normal payload.
- If parser mode is dynamic, decode both candidates first and validate with MTI/field structure before processing payload.
Production tip: When your parser suddenly breaks with a new integration, the first thing to check is bitmap encoding. I've seen teams spend days debugging before realizing the new endpoint sends hex-ASCII instead of binary.
Efficient Bitmap Manipulation in Go
Let's build up from simple to production-quality implementations.
Basic: Check Field Presence
// IsFieldPresent checks if a field is present in the bitmap
func IsFieldPresent(bitmap []byte, field int) bool {
if field < 1 || field > len(bitmap)*8 {
return false
}
byteIndex := (field - 1) / 8
bitPosition := 7 - ((field - 1) % 8)
return (bitmap[byteIndex] >> bitPosition) & 1 == 1
}
The magic formula:
(field - 1) / 8: Which byte contains this field's bit7 - ((field - 1) % 8): Which bit within that byte (counting from MSB)
Get All Present Fields
// GetPresentFields returns all field numbers with bit set to 1
func GetPresentFields(bitmap []byte) []int {
var fields []int
for field := 1; field <= len(bitmap)*8; field++ {
if IsFieldPresent(bitmap, field) {
fields = append(fields, field)
}
}
return fields
}
This is O(n) where n is the number of bits. For 64 bits, it's trivially fast.
Production Optimization: Using uint64
For primary bitmaps, you can use native 64-bit integers:
import "encoding/binary"
// ParsePrimaryBitmap converts 8 bytes to uint64 for fast operations
func ParsePrimaryBitmap(data []byte) uint64 {
if len(data) < 8 {
return 0
}
return binary.BigEndian.Uint64(data)
}
// IsFieldPresentFast uses bitwise AND on uint64
func IsFieldPresentFast(bitmap uint64, field int) bool {
if field < 1 || field > 64 {
return false
}
// Bit 1 is the MSB (bit 63 in zero-indexed uint64)
shift := 64 - field
return (bitmap >> shift) & 1 == 1
}
Why is this faster? Because bitmap >> shift & 1 compiles to a single CPU instruction on 64-bit processors. No array indexing, no division.
Building a Bitmap
// BuildBitmap creates a bitmap from a list of field numbers
func BuildBitmap(fields []int) []byte {
// Determine if we need secondary bitmap
maxField := 0
for _, f := range fields {
if f > maxField {
maxField = f
}
}
bitmapSize := 8 // Primary
if maxField > 64 {
bitmapSize = 16 // Primary + Secondary
}
if maxField > 128 {
bitmapSize = 24 // + Tertiary
}
bitmap := make([]byte, bitmapSize)
for _, field := range fields {
if field < 1 {
continue
}
byteIndex := (field - 1) / 8
bitPosition := 7 - ((field - 1) % 8)
bitmap[byteIndex] |= (1 << bitPosition)
}
// Set bit 1 if secondary bitmap exists
if bitmapSize >= 16 {
bitmap[0] |= 0x80 // Set bit 1 (MSB of first byte)
}
// Set bit 65 if tertiary bitmap exists
if bitmapSize >= 24 {
bitmap[8] |= 0x80 // Set bit 1 of secondary (= field 65)
}
return bitmap
}
Critical point: When building a bitmap with fields above 64, you must:
- Set bit 1 of the primary bitmap
- Include the 8 bytes for the secondary bitmap
- Properly set field presence bits in the secondary
Handling Hex-ASCII Bitmaps
import "encoding/hex"
// DecodeHexASCIIBitmap converts hex-ASCII string to binary
func DecodeHexASCIIBitmap(s string) ([]byte, error) {
return hex.DecodeString(s)
}
// Example: "723A448108E18000" → []byte{0x72, 0x3A, 0x44, 0x81, 0x08, 0xE1, 0x80, 0x00}
When parsing a full message, you might need auto-detection:
// DetectBitmapEncoding guesses the bitmap encoding from raw bytes
func DetectBitmapEncoding(data []byte) string {
if len(data) < 8 {
return "unknown"
}
// Check if first 16 bytes look like hex-ASCII
if len(data) >= 16 {
allHexChars := true
for i := 0; i < 16; i++ {
c := data[i]
isHexChar := (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') ||
(c >= 'a' && c <= 'f')
if !isHexChar {
allHexChars = false
break
}
}
if allHexChars {
return "hex-ascii"
}
}
return "binary"
}
Common Pitfalls
1. Off-by-One Errors
The classic bug:
// WRONG: Using zero-based indexing
byteIndex := field / 8
bitPosition := 7 - (field % 8)
// RIGHT: ISO8583 uses 1-based field numbers
byteIndex := (field - 1) / 8
bitPosition := 7 - ((field - 1) % 8)
If your bitmap parser shows field 64 when it should show field 65, or vice versa, this is almost certainly the bug.
2. Bit Order Confusion
Some engineers read bits right-to-left (LSB first) because that's how binary numbers work. ISO8583 reads bits left-to-right (MSB first).
Byte: 0x80 = 1000 0000
WRONG (LSB-first thinking):
Bit 0 is rightmost, so bit 0 = 0, bit 7 = 1
"Field 8 is present"
RIGHT (ISO8583 MSB-first):
Bit 1 is leftmost, so bit 1 = 1
"Field 1 is present" (secondary bitmap indicator)
3. Forgetting to Set Bit 1 When Building
When constructing a message with fields above 64:
// WRONG: Only set field presence bits
bitmap := make([]byte, 16)
setBit(bitmap, 70) // Set bit for field 70
// RIGHT: Also set bit 1 to indicate secondary bitmap exists
bitmap := make([]byte, 16)
bitmap[0] |= 0x80 // Set bit 1 first
setBit(bitmap, 70) // Then set field 70
If you forget bit 1, the parser will only read 8 bytes and miss your secondary bitmap entirely.
4. Hex-ASCII Length Miscalculation
// WRONG: Assuming binary
bitmap := message[4:12] // 8 bytes after MTI
// RIGHT: Detect encoding first
if isHexASCII(message[4:20]) {
bitmapHex := string(message[4:20]) // 16 characters
bitmap, _ = hex.DecodeString(bitmapHex)
} else {
bitmap = message[4:12]
}
5. Endianness Issues
ISO8583 bitmaps are big-endian. If you're on a little-endian system (most modern computers) and cast bytes directly to integers:
// WRONG: Direct cast on little-endian system
bitmap := *(*uint64)(unsafe.Pointer(&data[0]))
// RIGHT: Use encoding/binary for portability
bitmap := binary.BigEndian.Uint64(data)
Debugging Bitmap Issues
Print Helper
When debugging, having a visual bitmap representation is invaluable:
func PrintBitmap(bitmap []byte) {
for byteIdx, b := range bitmap {
for bitIdx := 7; bitIdx >= 0; bitIdx-- {
field := byteIdx*8 + (7 - bitIdx) + 1
if (b >> bitIdx) & 1 == 1 {
fmt.Printf("Field %d: present\n", field)
}
}
}
}
Production Triage Matrix
When a parser fails, use this matrix in order:
- Bitmap parsing failed immediately
- Verify expected encoding mode (binary vs hex-ASCII).
- Confirm bytes/length (8/16/24 as contract requires).
- If strict binary mode receives printable-hex bytes, convert before parse.
- Bit 65/tertiary confusion
- If primary bit 1 is set, ensure there are enough bytes for secondary parsing.
- If secondary bit 1 is set, check whether your environment explicitly supports tertiary.
- If not supported, reject with a protocol error instead of silently ignoring.
- Field list mismatch
- Build an expected field set from business requirements.
- Compare against bitmap-derived set.
- Any mismatch is either a malformed bitmap or malformed field body.
- Data alignment error
- Once header + bitmap are trusted, log the first parse boundary.
- Ensure all declared fixed fields and var-lens consume exactly what is claimed.
- One off-by-one in one field affects every following parse.
- Performance degradation
- Benchmark parse loop with synthetic messages at target TPS.
- Ensure the critical path avoids repeated slice reallocation.
- Cache bitmap checks when the same message shape repeats.
Hex Dump Comparison
When a message fails to parse, dump both the expected and actual bitmaps:
Expected bitmap: 72 3A 44 81 08 E1 80 00
Actual bitmap: 37 32 33 41 34 34 38 31
Aha! The actual starts with 0x37, 0x32... that's "72" in ASCII.
The sender is using hex-ASCII encoding.
Field List Comparison
expected := []int{2, 3, 4, 7, 11, 12, 13, 15, 18, 22, 25, 32, 37, 41, 42, 43, 48, 49}
actual := GetPresentFields(bitmap)
if !reflect.DeepEqual(expected, actual) {
fmt.Println("Missing:", difference(expected, actual))
fmt.Println("Extra:", difference(actual, expected))
}
Real-World Case Study: The Midnight Bitmap Bug
A payment processor noticed that transactions were failing every night at exactly midnight.
Investigation:
- Authorization requests (0100) during the day: working
- Same transactions at 00:00-00:05: failing with "invalid message format"
Root cause: The terminal software was setting field 13 (Local Transaction Date) as "0000" on day boundaries due to a date formatting bug. But that wasn't the bitmap issue.
The real problem: The terminal used a custom field 60 (Private Use) for batch sequence numbers. At midnight, the batch counter rolled over to 0, and the terminal's buggy bitmap builder interpreted "no batch number" as "don't include field 60" - but it was still writing the field 60 data without updating the bitmap.
Before midnight:
Bitmap indicates: 2, 3, 4, 11, 12, 13, 60, ...
Message contains: DE2, DE3, DE4, DE11, DE12, DE13, DE60, ...
✓ Parser reads everything correctly
After midnight:
Bitmap indicates: 2, 3, 4, 11, 12, 13, ... (no 60!)
Message contains: DE2, DE3, DE4, DE11, DE12, DE13, DE60, ...
✗ Parser stops at DE13, garbage follows
The fix: The terminal software was updated to always include field 60 in the bitmap if it was present in the message body.
Lesson: Bitmap and data must be synchronized. Any mismatch causes cascading parse failures because all subsequent field positions shift.
Performance Considerations
For high-throughput systems, bitmap parsing is in the critical path:
Benchmark: IsFieldPresent Methods
// Method 1: Array indexing
func IsFieldPresent_Array(bitmap []byte, field int) bool
// ~2ns per call
// Method 2: uint64 bitwise
func IsFieldPresent_Uint64(bitmap uint64, field int) bool
// ~0.5ns per call
// Method 3: Precomputed lookup table
var fieldMasks [65]uint64 // fieldMasks[n] = mask for field n
func IsFieldPresent_Lookup(bitmap uint64, field int) bool {
return bitmap & fieldMasks[field] != 0
}
// ~0.3ns per call
For most applications, the simple array method is fine. If you're building a network switch handling 100K+ TPS, the optimized methods matter.
Allocation Avoidance
// BAD: Allocates on every call
func GetPresentFields(bitmap []byte) []int {
var fields []int // slice allocation
// ...
return fields
}
// BETTER: Preallocate or reuse
func GetPresentFieldsInto(bitmap []byte, fields []int) []int {
fields = fields[:0] // Reset length, keep capacity
// ...
return fields
}
Summary
The bitmap is the table of contents for an ISO8583 message. It tells you exactly which data elements are present, allowing compact encoding without per-field identifiers.
Key concepts:
- Primary bitmap: 8 bytes (64 bits) immediately after MTI, covers fields 1-64
- Bit 1 indicator: When set, a secondary bitmap follows
- Secondary bitmap: 8 more bytes for fields 65-128 (IS Field 1/DE1)
- Bit numbering: 1-based, MSB-first, big-endian bytes
- The formula:
byteIndex = (field-1)/8,bitPosition = 7 - ((field-1)%8)
Common pitfalls:
- Off-by-one errors (1-based vs 0-based)
- Bit order confusion (MSB vs LSB)
- Forgetting to set bit 1 when using secondary bitmap
- Hex-ASCII vs binary encoding mismatch
Debugging tips:
- Print the bitmap in binary with field numbers
- Compare expected vs actual field lists
- Check for hex-ASCII encoding when bitmap seems wrong
- Verify bitmap and data element count match
What's Next
Now that you understand how bitmaps indicate field presence, you're ready to:
- bitmap-parser: Parse a bitmap and return all present field numbers
- bitmap-builder: Given field numbers, construct a valid bitmap
These foundational skills feed directly into Module 3 (Field Encoding), where you'll learn how to actually read the data elements that the bitmap points to.