ISO Parser CLI
ISO8583 Parser Capstone
Build the supplied parser library for an explicit synthetic message profile. The historical slug says CLI; a terminal interface is not graded. The profiles are teaching examples, not complete network specifications. Keep all supplied types and function signatures. No encoding auto-detection is required.
Core Data Structures
// MessageSpec defines how to parse messages for a specific network
type MessageSpec struct {
Name string // e.g., "ISO8583:1987 Base"
BitmapEncoding string // "BINARY" or "HEX_ASCII"
Fields map[int]*FieldSpec // Field definitions
}
// FieldSpec defines how to parse a single data element
type FieldSpec struct {
Number int // Field number (2-128)
Name string // Human-readable name
LengthType string // "FIXED", "LLVAR", "LLLVAR"
MaxLength int // Maximum length
DataType string // "N", "AN", "ANS", "B"
Encoding string // "ASCII", "BCD", "BINARY"
}
// ParsedMessage is the result of parsing
type ParsedMessage struct {
MTI MTI // Parsed message type indicator
Bitmap Bitmap // Primary and optional secondary bitmap
Fields map[int]*ParsedField // Parsed data elements
Warnings []string // Non-fatal issues encountered
}
// MTI contains the parsed message type indicator
type MTI struct {
Raw string // "0100"
Version int // 0=1987, 1=1993, 2=2003
Class int // 1=auth, 2=financial, 4=reversal, 8=network
Function int // 0=request, 1=response, 2=advice, etc.
Origin int // 0=acquirer, 1=acquirer repeat, etc.
}
// Bitmap tracks which fields are present
type Bitmap struct {
Primary []byte // 8 bytes
Secondary []byte // 8 bytes if bit 1 is set
Fields []int // List of present field numbers
}
// ParsedField holds a single parsed data element
type ParsedField struct {
Number int // Field number
RawHex string // Raw bytes as hex string
Decoded string // Human-readable decoded value
}
Functions to Implement
// NewParser creates a parser with the given specification
func NewParser(spec *MessageSpec) *Parser
// Parse parses a hex string into a structured message
// The input is hex of the entire wire message, beginning "30313030" for ASCII 0100.
func (p *Parser) Parse(hexStr string) (*ParsedMessage, error)
// PrettyPrint returns a human-readable string representation
func (msg *ParsedMessage) PrettyPrint() string
// ToJSON returns a JSON representation
func (msg *ParsedMessage) ToJSON() ([]byte, error)
// DefaultSpec returns the course binary-bitmap profile
func DefaultSpec() *MessageSpec
// VisaSpec returns the synthetic course hex-ASCII profile (legacy name)
func VisaSpec() *MessageSpec
Complete-message parsing contract
NewParserstores the supplied profile;Parsereturns an error for a nil profile or a bitmap encoding other than BINARY or HEX_ASCII.- Trim outer whitespace and allow internal ASCII spaces in the hex input. Hex decode the entire input once. The wire MTI is exactly four ASCII digits.
- Read 8 binary or 16 hex-ASCII bytes per bitmap, including a secondary bitmap if bit 1 is set. Report set bits in increasing order, including bit 1 as an indicator, but do not parse it as a data field. This model stops at field 128.
- Parse present fields in increasing order. Fixed length is MaxLength. Variable prefixes are exactly 2, 3, or 4 ASCII decimal digits for LLVAR, LLLVAR, LLLLVAR. Signs and whitespace are not decimal digits. Reject lengths over MaxLength.
- Length units are bytes for ASCII/BINARY and decimal digits for BCD. BCD uses
ceil(digits/2) bytes, decimal nibbles only, and a leading zero pad for odd
lengths. Thus five digits encoded as
01 23 45decode to12345. - Reject negative MaxLength, mismatched FieldSpec.Number, missing/nil field definitions and unsupported length/data encodings. DataType is descriptive; the task does not implement business validation for every field.
- RawHex is lowercase hex of payload bytes only, excluding the length prefix. ASCII Decoded is text; BINARY Decoded is uppercase hex; BCD Decoded is digits.
- Stop at the first field error; do not guess a later field's start. Return the partial message containing only complete earlier fields AND a non-nil error identifying field number and wire offset. Also append this diagnostic to Warnings. Header errors may return a nil message.
- Reject trailing bytes after the last field with a non-nil error. Nil error means exactly one complete message was parsed. No malformed input may panic.
Byte-exact example
wire := append([]byte("0100"), []byte{0x70, 0, 0, 0, 0, 0, 0, 0}...)
wire = append(wire, []byte("16" + "1234567890123456" + "000000" + "000000001234")...)
msg, err := NewParser(DefaultSpec()).Parse(hex.EncodeToString(wire))
This is 48 wire bytes and 96 hex characters. It contains fields 2,3,4; their
payloads begin at offsets 14,30,36. Replacing the bitmap with the sixteen ASCII
characters 7000000000000000 requires VisaSpec and adds eight wire bytes.
Its name is retained only for compatibility.
Diagnostic display contract
PrettyPrint includes MTI, bitmap, sorted fields and warnings. ToJSON keeps the
existing mti, bitmap, fields, optional warnings structure; every field
view has name, raw, decoded. Both formats use the same policy:
- DE2: decoded shows first six/last four digits only for length >10, with
*between; otherwise mask every digit. Raw is[REDACTED]. - DE52: decoded is
[ENCRYPTED PIN DATA]; raw is[REDACTED]. - Allow displayed raw/value data only for fields 3,4,5,6,7,11,12,13,14,22,23, 25,26,32,37,38,39,41,42,43,49,50,51,90. Display raw as uppercase hex.
- All other fields (including track, ICC/private, MAC and unknown fields) show
[REDACTED]in both raw and decoded views. - Amounts 4,5,6 keep their original digits with a minor-unit label. Do not infer a decimal scale without a currency exponent. Retain response-code and currency descriptions for the supplied course lookup tables.
- Formatting must not mutate ParsedField.RawHex or Decoded. These views are intentionally lossy and cannot be used for retransmission or MAC calculation.
Use synthetic fixtures. Passing these tests establishes the course parsing and display contract, not a production certification or transaction approval.
Field Specification Reference
The DefaultSpec() should include at minimum these critical fields:
| Field | Name | Length Type | Max | Data Type |
|---|---|---|---|---|
| 2 | Primary Account Number | LLVAR | 19 | N |
| 3 | Processing Code | FIXED | 6 | N |
| 4 | Amount, Transaction | FIXED | 12 | N |
| 7 | Transmission Date/Time | FIXED | 10 | N |
| 11 | System Trace Audit Number | FIXED | 6 | N |
| 12 | Time, Local Transaction | FIXED | 6 | N |
| 13 | Date, Local Transaction | FIXED | 4 | N |
| 14 | Date, Expiration | FIXED | 4 | N |
| 22 | POS Entry Mode | FIXED | 3 | N |
| 23 | Card Sequence Number | FIXED | 3 | N |
| 25 | POS Condition Code | FIXED | 2 | N |
| 26 | POS PIN Capture Code | FIXED | 2 | N |
| 32 | Acquiring Institution ID | LLVAR | 11 | N |
| 35 | Track 2 Data | LLVAR | 37 | Z |
| 37 | Retrieval Reference Number | FIXED | 12 | AN |
| 38 | Authorization ID Response | FIXED | 6 | AN |
| 39 | Response Code | FIXED | 2 | AN |
| 41 | Card Acceptor Terminal ID | FIXED | 8 | ANS |
| 42 | Card Acceptor ID | FIXED | 15 | ANS |
| 43 | Card Acceptor Name/Location | FIXED | 40 | ANS |
| 49 | Currency Code, Transaction | FIXED | 3 | N |
| 52 | PIN Data | FIXED | 8 | B |
| 55 | ICC Related Data | LLLVAR | 999 | B |
| 64 | MAC | FIXED | 8 | B |
The default map also includes DE90 (42 ASCII bytes) and DE128 (8 binary bytes). VisaSpec uses the same field definitions and HEX_ASCII bitmap encoding.
Independent checkpoints
- Derive a fresh complete cursor table before implementing the field loop.
- Remove DE3 from a profile for a message containing DE2,3,4. Prove DE4 is not published, because its starting offset is unknown.
- Check signed prefixes, truncated fields, odd BCD, trailing bytes and nil specs.
- Search both display formats for sensitive text AND its hex representation.
The lesson provides a worked trace and progressive hints; complete the functions independently and use the checkpoint to diagnose a wrong boundary.