///|
/// Bitmap representation selected by a wire profile.
pub(all) enum BitmapEncoding {
BinaryBitmap
AsciiHexBitmap
} derive(Eq, Debug)
///|
/// MTI representation selected by a wire profile.
pub(all) enum MtiEncoding {
AsciiMti
BcdMti
} derive(Eq, Debug)
///|
/// Complete message wire-format choices.
pub(all) struct WireProfile {
name : String
mti_encoding : MtiEncoding
bitmap_encoding : BitmapEncoding
reject_trailing : Bool
} derive(Eq, Debug)
///|
/// Common binary-bitmap ASCII-MTI profile.
pub fn ascii_binary_profile() -> WireProfile {
{
name: "ascii-mti-binary-bitmap",
mti_encoding: AsciiMti,
bitmap_encoding: BinaryBitmap,
reject_trailing: true,
}
}
///|
/// Common all-display profile using ASCII hexadecimal bitmap text.
pub fn ascii_hex_profile() -> WireProfile {
{
name: "ascii-mti-ascii-hex-bitmap",
mti_encoding: AsciiMti,
bitmap_encoding: AsciiHexBitmap,
reject_trailing: true,
}
}
///|
/// Compact profile with a two-byte BCD MTI.
pub fn bcd_binary_profile() -> WireProfile {
{
name: "bcd-mti-binary-bitmap",
mti_encoding: BcdMti,
bitmap_encoding: BinaryBitmap,
reject_trailing: true,
}
}
///|
/// Return a copy that allows bytes after one decoded message.
pub fn WireProfile::allow_trailing(self : WireProfile) -> WireProfile {
{
name: self.name,
mti_encoding: self.mti_encoding,
bitmap_encoding: self.bitmap_encoding,
reject_trailing: false,
}
}
///|
/// Encode the four-digit message type indicator.
fn encode_mti(profile : WireProfile, mti : String) -> Result[Bytes, IsoError] {
if !valid_mti_text(mti) {
return Err(InvalidMti(mti))
}
match profile.mti_encoding {
AsciiMti => text_to_ascii(0, mti)
BcdMti => bcd_pack_digits(0, mti, true, 0)
}
}
///|
/// Decode an MTI at an offset and report consumed bytes.
fn decode_mti(
profile : WireProfile,
data : Bytes,
start : Int,
) -> Result[(String, Int), IsoError] {
let byte_length = match profile.mti_encoding {
AsciiMti => 4
BcdMti => 2
}
if start < 0 || start + byte_length > data.length() {
return Err(
Truncated("MTI", byte_length, Int::max(0, data.length() - start)),
)
}
let raw = bytes_slice(data, start, start + byte_length)
let text = match profile.mti_encoding {
AsciiMti =>
match ascii_to_text(0, raw) {
Ok(text) => text
Err(error) => return Err(error)
}
BcdMti =>
match bcd_unpack_digits(0, raw, 4, true, 0) {
Ok(text) => text
Err(error) => return Err(error)
}
}
if !valid_mti_text(text) {
Err(InvalidMti(text))
} else {
Ok((text, byte_length))
}
}
///|
/// Encode a bitmap according to the profile.
fn encode_bitmap(profile : WireProfile, bitmap : Bitmap) -> Bytes {
match profile.bitmap_encoding {
BinaryBitmap => bitmap.to_bytes()
AsciiHexBitmap => bitmap.to_ascii_bytes()
}
}
///|
/// Decode a bitmap according to the profile.
fn decode_bitmap(
profile : WireProfile,
data : Bytes,
start : Int,
) -> Result[(Bitmap, Int), IsoError] {
match profile.bitmap_encoding {
BinaryBitmap => bitmap_decode_binary(data, start)
AsciiHexBitmap => bitmap_decode_ascii(data, start)
}
}
///|
/// Pack a complete ISO 8583 message.
pub fn pack_message(
packager : Packager,
profile : WireProfile,
message : IsoMessage,
) -> Result[Bytes, IsoError] {
match parse_mti(message.mti) {
Err(error) => return Err(error)
Ok(_) => ()
}
let issues = validate_message_fields(message, packager)
if issues.length() > 0 {
return Err(TemplateViolation(issues[0].message))
}
let mti = match encode_mti(profile, message.mti) {
Ok(bytes) => bytes
Err(error) => return Err(error)
}
let bitmap = match message.bitmap() {
Ok(value) => value
Err(error) => return Err(error)
}
let bitmap_bytes = encode_bitmap(profile, bitmap)
let fields = match encode_selected_fields(packager, message) {
Ok(bytes) => bytes
Err(error) => return Err(error)
}
let output : Array[Byte] = []
append_bytes(output, mti)
append_bytes(output, bitmap_bytes)
append_bytes(output, fields)
Ok(Bytes::from_array(output))
}
///|
/// Decode a complete message and retain consumption metadata.
pub fn unpack_message_with_result(
packager : Packager,
profile : WireProfile,
data : Bytes,
) -> Result[DecodeResult, IsoError] {
let (mti, mti_bytes) = match decode_mti(profile, data, 0) {
Ok(value) => value
Err(error) => return Err(error)
}
let (bitmap, bitmap_bytes) = match decode_bitmap(profile, data, mti_bytes) {
Ok(value) => value
Err(error) => return Err(error)
}
let fields_start = mti_bytes + bitmap_bytes
let (fields, field_bytes) = match
decode_selected_fields(packager, bitmap, data, fields_start) {
Ok(value) => value
Err(error) => return Err(error)
}
let consumed = fields_start + field_bytes
if profile.reject_trailing && consumed != data.length() {
return Err(TrailingBytes(data.length() - consumed))
}
let message = { mti, fields, }
Ok({ message, consumed, bitmap_bytes, })
}
///|
/// Decode exactly one complete message.
pub fn unpack_message(
packager : Packager,
profile : WireProfile,
data : Bytes,
) -> Result[IsoMessage, IsoError] {
match unpack_message_with_result(packager, profile, data) {
Ok(result) => Ok(result.message)
Err(error) => Err(error)
}
}
///|
/// Pack directly to a hexadecimal diagnostic fixture.
pub fn pack_message_hex(
packager : Packager,
profile : WireProfile,
message : IsoMessage,
) -> Result[String, IsoError] {
match pack_message(packager, profile, message) {
Ok(bytes) => Ok(hex_encode(bytes))
Err(error) => Err(error)
}
}
///|
/// Unpack a hexadecimal diagnostic fixture.
pub fn unpack_message_hex(
packager : Packager,
profile : WireProfile,
fixture : String,
) -> Result[IsoMessage, IsoError] {
let bytes = match hex_decode(fixture) {
Ok(bytes) => bytes
Err(error) => return Err(error)
}
unpack_message(packager, profile, bytes)
}
///|
/// Explain byte-level layout without exposing sensitive values.
pub fn message_layout(
packager : Packager,
profile : WireProfile,
message : IsoMessage,
) -> Result[Array[String], IsoError] {
let lines : Array[String] = []
let mti_bytes = match encode_mti(profile, message.mti) {
Ok(bytes) => bytes
Err(error) => return Err(error)
}
let bitmap = match message.bitmap() {
Ok(value) => value
Err(error) => return Err(error)
}
let bitmap_bytes = encode_bitmap(profile, bitmap)
lines.push("MTI offset=0 bytes=\{mti_bytes.length()} value=\{message.mti}")
lines.push(
"bitmap offset=\{mti_bytes.length()} bytes=\{bitmap_bytes.length()} fields=\{bitmap.field_count()}",
)
let mut offset = mti_bytes.length() + bitmap_bytes.length()
for entry in message.fields {
let spec = match packager.spec(entry.number) {
Some(value) => value
None => return Err(UnknownField(entry.number))
}
let encoded = match encode_field(spec, entry.value) {
Ok(value) => value
Err(error) => return Err(error)
}
lines.push(
"DE\{entry.number} offset=\{offset} bytes=\{encoded.bytes.length()} logical=\{encoded.logical_length} name=\{spec.name}",
)
offset += encoded.bytes.length()
}
lines.push("total bytes=\{offset}")
Ok(lines)
}