Bitmap Parser
Bitmap Parser
You're building a transaction log analyzer. Captured ISO8583 messages need to be decoded, and the first step after extracting the MTI is parsing the bitmap to determine which fields are present.
The bitmap tells you exactly which data elements to expect in the message. Without parsing it correctly, you can't interpret any of the payload.
The Structure
An ISO8583 bitmap is an 8-byte (64-bit) field where each bit indicates whether a corresponding data element is present.
Bitmap: 72 3A 44 81 08 E1 80 00
Byte 0: 72 = 0111 0010 (binary)
││││ ││││
││││ │││└─ Bit 8 = 0 → Field 8 absent
││││ ││└── Bit 7 = 1 → Field 7 PRESENT
││││ │└─── Bit 6 = 0 → Field 6 absent
││││ └──── Bit 5 = 0 → Field 5 absent
│││└────── Bit 4 = 1 → Field 4 PRESENT
││└─────── Bit 3 = 1 → Field 3 PRESENT
│└──────── Bit 2 = 1 → Field 2 PRESENT
└───────── Bit 1 = 0 → Field 1 absent (no secondary bitmap)
Fields from byte 0: 2, 3, 4, 7
Key Rules
- 1-based numbering: Field numbers start at 1, not 0
- MSB-first: Bit 1 is the leftmost (most significant) bit of the first byte
- Big-endian byte order: First byte contains bits 1-8, second byte contains bits 9-16, etc.
- Bit 1 special meaning: When set, indicates a secondary bitmap follows (fields 65-128)
The Formula
To check if field N is present:
byteIndex = (N - 1) / 8
bitPosition = 7 - ((N - 1) % 8)
isPresent = (bitmap[byteIndex] >> bitPosition) & 1 == 1
What You're Building
// ParseBitmap takes a bitmap as raw bytes and returns a sorted slice of
// field numbers that are present (have their corresponding bit set to 1).
//
// The input bitmap can be 8 bytes (primary only) or 16 bytes (primary + secondary).
// For 8-byte input, only fields 1-64 are considered.
// For 16-byte input, fields 1-128 are considered.
//
// Returns the field numbers in ascending order.
func ParseBitmap(bitmap []byte) []int
Behavior
- Input: Raw bitmap bytes (8 or 16 bytes)
- Output: Sorted slice of present field numbers
- If bitmap is empty or nil, return empty slice
- If bitmap is neither 8 nor 16 bytes, return empty slice (invalid)
- Field 1 SHOULD be included in the output if bit 1 is set (it indicates secondary bitmap presence)
Examples
// Primary bitmap only
bitmap := []byte{0x72, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
ParseBitmap(bitmap) // → []int{2, 3, 4, 7, 11, 12, 13, 15}
// Just field 2 (PAN)
bitmap := []byte{0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
ParseBitmap(bitmap) // → []int{2}
// Secondary bitmap present (bit 1 set)
bitmap := []byte{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}
ParseBitmap(bitmap) // → []int{1, 128}
// Note: Field 1 is included because bit 1 is set (secondary bitmap indicator)
// Empty/invalid
ParseBitmap(nil) // → []int{}
ParseBitmap([]byte{}) // → []int{}
ParseBitmap([]byte{0x72, 0x3A, 0x00}) // → []int{} (not 8 or 16 bytes)
Real-World Context
Consider this production authorization request:
MTI: 0100
Bitmap: 72 3A 44 81 08 E1 80 00
Fields present: 2, 3, 4, 7, 11, 12, 13, 15, 18, 22, 25, 32, 37, 41, 42, 43, 48, 49
This tells you the message contains:
- DE2: PAN (Primary Account Number)
- DE3: Processing Code
- DE4: Transaction Amount
- DE7: Transmission Date/Time
- DE11: STAN (System Trace Audit Number)
- DE12: Local Transaction Time
- DE13: Local Transaction Date
- DE15: Settlement Date
- DE18: Merchant Category Code
- DE22: Point of Service Entry Mode
- DE25: Point of Service Condition Code
- DE32: Acquiring Institution ID
- DE37: Retrieval Reference Number
- DE41: Card Acceptor Terminal ID
- DE42: Card Acceptor ID Code
- DE43: Card Acceptor Name/Location
- DE48: Additional Data (Private)
- DE49: Currency Code
Without the bitmap parser, you'd have no idea which of these 64+ possible fields to expect.
Why This Matters
Every ISO8583 parser starts with bitmap decoding. Get this wrong, and:
- You'll miss fields that are present
- You'll try to read fields that don't exist
- Every field after the first error will be misaligned
- The entire message parse fails
The bitmap is your roadmap. Parse it first, parse it right.
Hints (only if stuck)
<details> <summary>Hint 1: Iterating through bits</summary> You can check all 64 (or 128) field positions in a simple loop. For each field number from 1 to max, calculate the byte index and bit position, then check if that bit is set. </details>
<details> <summary>Hint 2: The byte index formula</summary> For 1-based field numbers: byteIndex = (field - 1) / 8
Field 1 → byte 0 Field 8 → byte 0 Field 9 → byte 1 Field 64 → byte 7 Field 65 → byte 8 </details>
<details> <summary>Hint 3: The bit position formula</summary> Within each byte, bit positions go from 7 (MSB) to 0 (LSB): bitPosition = 7 - ((field - 1) % 8)
Field 1 → bit position 7 (leftmost) Field 2 → bit position 6 Field 8 → bit position 0 (rightmost) Field 9 → bit position 7 (of next byte) </details>
<details> <summary>Hint 4: Extracting a single bit</summary> To get the value of bit at position P: (byte >> P) & 1 This shifts the target bit to position 0, then masks off everything else. </details>