ISO Parser CLI

hard · iso8583, payments

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

  • NewParser stores the supplied profile; Parse returns 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 45 decode to 12345.
  • 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

  1. Derive a fresh complete cursor table before implementing the field loop.
  2. Remove DE3 from a profile for a message containing DE2,3,4. Prove DE4 is not published, because its starting offset is unknown.
  3. Check signed prefixes, truncated fields, odd BCD, trailing bytes and nil specs.
  4. 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.

Run tests to see results
No issues detected
    Join Discord