A Rust port of the go8583 library:
an ISO 8583 message packer/unpacker with JSON-driven packagers, jPOS-style
class names (IFA_*, IFB_*, IFE_*, IF_*, IFMC_*, TLV …), and
BER-TLV support for DE55 (EMV) workflows.
The full knowledge base — architecture, field catalog, strategies, TLV rules, cookbook recipes, glossary — lives under
docs/.
IsoMsgwith jPOS-styleset/get/has_fieldplus dotted-path subfield access (msg.set_path("48.1", "AB")).BasePackagerthat orchestrates MTI, primary + secondary bitmap, and per-field packagers; also usable as a sub-packager for nested private containers (e.g. DE48/DE62).- JSON-driven configuration (
new_generic_packager/…_from_reader/…_from_config) that matches jPOS XML semantics but uses JSON. - A
PackagerRegistryyou can extend at runtime with custom classes. Interpreter/Prefixer/Padderstrategy primitives (ASCII, BCD, EBCDIC CP037, Binary).TlvandTlvList(with fluent builder) for BER-TLV encode/decode, including long-form length and multi-byte tags. Indefinite length is explicitly unsupported.- Bitmap helpers supporting 64-bit and 128-bit primary/secondary layouts, plus
Base1_BITMAP126for fixed BASE I/II private bitmaps. - Zero runtime dependencies beyond
serde+serde_json+thiserror+hex.
[dependencies]
rust8583 = "0.1"Requires Rust 2024 edition (rustc ≥ 1.90).
use rust8583::{new_generic_packager_from_reader, IsoMsg};
fn main() -> rust8583::Result<()> {
let cfg = r#"{
"fields": [
{"id": 0, "length": 4, "name": "MTI", "class": "IFA_NUMERIC"},
{"id": 1, "length": 16, "name": "BITMAP", "class": "IFA_BITMAP"},
{"id": 2, "length": 19, "name": "PAN", "class": "IFA_LLCHAR"},
{"id": 3, "length": 6, "name": "PROC", "class": "IFA_NUMERIC"},
{"id": 4, "length": 12, "name": "AMOUNT", "class": "IFA_AMOUNT"},
{"id": 11, "length": 6, "name": "STAN", "class": "IFA_NUMERIC"}
]
}"#;
let packager = new_generic_packager_from_reader(cfg.as_bytes())?;
let mut msg = IsoMsg::new();
msg.set_mti("0200");
msg.set(2, "4567890123456789");
msg.set(3, "000000");
msg.set(4, "1000");
msg.set(11, "112233");
let raw = packager.pack(&msg)?;
let mut parsed = IsoMsg::new();
packager.unpack(&mut parsed, &raw)?;
assert_eq!(parsed.get_string(2), "4567890123456789");
Ok(())
}The JSON config nests a packager block on the outer field. The outer class
contributes the length prefix (IFA_LLLCHAR → 3-digit ASCII), the inner
packager owns the sub-bitmap and subfields.
{"id": 48, "length": 999, "name": "ADDITIONAL DATA", "class": "IFA_LLLCHAR",
"packager": {
"fields": [
{"id": 0, "length": 16, "name": "SUB-BITMAP", "class": "IFA_BITMAP"},
{"id": 1, "length": 2, "name": "SUB1", "class": "IF_CHAR"},
{"id": 22, "length": 9, "name": "SUB22", "class": "IF_CHAR"}
]
}
}Populate via dotted paths:
msg.set_path("48.1", "AB")?;
msg.set_path("48.22", "AAAA00000")?;use rust8583::TlvList;
let mut tl = TlvList::new();
tl.append_hex(0x9F02, "000000000100")?; // Amount
tl.append_hex(0x9F26, "ABCDEF1234567890")?; // ARQC
tl.append_hex(0x82, "3900")?; // AIP
let de55 = tl.pack()?;
msg.set(55, de55); // packager declares DE55 as IFA_LLLTLV or IFB_LLHTLVOn unpack, DE55 comes back as FieldValue::Tlvs(Vec<Tlv>):
if let Some(rust8583::IsoValue::Field(rust8583::FieldValue::Tlvs(list))) = parsed.get(55) {
for t in list {
println!("0x{:X} = {}", t.tag(), t.get_string_value());
}
}use rust8583::{
AsciiInterpreter, AsciiPrefixer, IsoStringFieldPackager, LeftPadder,
PackagerRegistry,
};
PackagerRegistry::register("IFA_LNUM", |length| {
Box::new(IsoStringFieldPackager {
length,
description: String::new(),
interpreter: Box::new(AsciiInterpreter),
prefixer: Some(Box::new(AsciiPrefixer { length: 1 })),
padder: Some(Box::new(LeftPadder { pad_char: '0' })),
fixed_bcd: false,
binary_mode: false,
})
});After registration, any JSON config referencing "class": "IFA_LNUM" will
use it.
rust8583/
├── Cargo.toml
├── docs/ ← full knowledge base (read these first)
├── src/
│ ├── lib.rs public API re-exports
│ ├── error.rs Iso8583Error + Result alias
│ ├── msg.rs IsoMsg, IsoValue, FieldValue (dotted-path)
│ ├── packager.rs BasePackager + SubPackager
│ ├── field_packager.rs IsoFieldPackager trait + IsoStringFieldPackager
│ ├── field_packagers.rs constructors for every jPOS-style class
│ ├── strategies.rs Interpreter / Prefixer / Padder
│ ├── bitmap.rs IfaBitmap / IfbBitmap / Base1Bitmap126
│ ├── generic_packager.rs JSON loader + PackagerRegistry
│ ├── tlv.rs BER-TLV primitives
│ ├── tlv_list.rs jPOS-style TlvList + builder
│ ├── tlv_field_packager.rs IFA_LLLTLV / IFB_LLHTLV / TLV
│ ├── iso_util.rs hex / BCD / EBCDIC / padding helpers
│ └── main.rs demo binary
├── testdata/packagers/ sample packager configs
│ ├── minimal_packager.json
│ ├── mastercard_like.json
│ └── cup_like.json
└── tests/
└── integration.rs end-to-end round-trip tests
cargo build # build the library + demo binary
cargo run # run the cookbook demo
cargo test # unit + integration tests
cargo clippy --all-targets| Go | Rust |
|---|---|
ISOMsg |
IsoMsg |
ISOField |
IsoValue::Field(FieldValue) |
ISOComponent interface |
IsoValue enum |
ISOPackager |
BasePackager + IsoFieldPackager trait |
ISOFieldPackager |
IsoFieldPackager trait |
GenericPackager |
new_generic_packager_* functions |
PackagerRegistry |
PackagerRegistry (global, thread-safe) |
TLV / TLVList |
Tlv / TlvList |
ISOUtil |
free functions in iso_util |
Core ISO 8583 pack/unpack, dotted-path sub-fields, secondary-bitmap support,
BER-TLV (single + list), and the full jPOS-compatible field class catalog are
implemented and round-trip tested. See tests/integration.rs for worked
examples.
Not yet implemented:
- Tertiary bitmap (fields 129–192).
- jPOS XML packager files — use the JSON config format described in
docs/04-packagers.md.
MIT. See crate metadata.