///|
/// Encoded data element with length accounting.
pub(all) struct EncodedField {
bytes : Bytes
logical_length : Int
prefix_length : Int
content_length : Int
} derive(Eq, Debug)
///|
/// Decoded data element with byte-consumption accounting.
pub(all) struct DecodedField {
value : String
consumed : Int
logical_length : Int
prefix_length : Int
} derive(Eq, Debug)
///|
/// Number of decimal digits used by a variable-length prefix.
fn length_prefix_digits(kind : LengthKind) -> Int {
match kind {
Fixed => 0
Llvar => 2
Lllvar => 3
}
}
///|
/// Encode a logical length according to the field definition.
fn encode_length_prefix(
spec : FieldSpec,
length : Int,
) -> Result[Bytes, IsoError] {
let digits = length_prefix_digits(spec.length_kind)
if digits == 0 {
return Ok(Bytes::make(0, (0).to_byte()))
}
match spec.length_encoding {
AsciiLength => {
let text = match decimal_width(length, digits) {
Ok(value) => value
Err(error) => return Err(error)
}
text_to_ascii(spec.number, text)
}
BcdLength => bcd_pack_length(length, digits)
}
}
///|
/// Decode a logical length and report prefix bytes consumed.
fn decode_length_prefix(
spec : FieldSpec,
data : Bytes,
start : Int,
) -> Result[(Int, Int), IsoError] {
let digits = length_prefix_digits(spec.length_kind)
if digits == 0 {
return Ok((field_max_logical_length(spec), 0))
}
match spec.length_encoding {
AsciiLength => {
let value = match parse_decimal_bytes(data, start, digits) {
Ok(value) => value
Err(error) => return Err(error)
}
Ok((value, digits))
}
BcdLength => bcd_unpack_length(data, start, digits)
}
}
///|
/// Compute bytes occupied by content of the given logical length.
pub fn encoded_content_length(spec : FieldSpec, logical_length : Int) -> Int {
match spec.content_encoding {
AsciiContent => logical_length
BinaryContent => logical_length
BcdContent => (logical_length + 1) / 2
}
}
///|
/// Encode canonical field content without a length prefix.
fn encode_field_content(
spec : FieldSpec,
value : String,
) -> Result[Bytes, IsoError] {
match spec.content_encoding {
AsciiContent => text_to_ascii(spec.number, value)
BinaryContent => hex_decode(value)
BcdContent => bcd_pack_digits(spec.number, value, true, 0)
}
}
///|
/// Decode field content bytes into canonical API text.
fn decode_field_content(
spec : FieldSpec,
content : Bytes,
logical_length : Int,
) -> Result[String, IsoError] {
match spec.content_encoding {
AsciiContent => ascii_to_text(spec.number, content)
BinaryContent => Ok(hex_encode(content))
BcdContent =>
bcd_unpack_digits(spec.number, content, logical_length, true, 0)
}
}
///|
/// Encode one data element including its optional length prefix.
pub fn encode_field(
spec : FieldSpec,
value : String,
) -> Result[EncodedField, IsoError] {
let canonical = match canonical_field_value(spec, value) {
Ok(value) => value
Err(error) => return Err(error)
}
let logical_length = field_logical_length(spec, canonical)
let prefix = match encode_length_prefix(spec, logical_length) {
Ok(bytes) => bytes
Err(error) => return Err(error)
}
let content = match encode_field_content(spec, canonical) {
Ok(bytes) => bytes
Err(error) => return Err(error)
}
let combined = concat_bytes(prefix, content)
Ok({
bytes: combined,
logical_length,
prefix_length: prefix.length(),
content_length: content.length(),
})
}
///|
/// Decode one data element from an offset.
pub fn decode_field(
spec : FieldSpec,
data : Bytes,
start : Int,
) -> Result[DecodedField, IsoError] {
if start < 0 || start > data.length() {
return Err(Truncated("field \{spec.number}", 0, 0))
}
let (logical_length, prefix_length) = match
decode_length_prefix(spec, data, start) {
Ok(value) => value
Err(error) => return Err(error)
}
let minimum = field_min_logical_length(spec)
let maximum = field_max_logical_length(spec)
if logical_length < minimum {
return Err(FieldTooShort(spec.number, minimum, logical_length))
}
if logical_length > maximum {
return Err(FieldTooLong(spec.number, maximum, logical_length))
}
let content_length = encoded_content_length(spec, logical_length)
let content_start = start + prefix_length
if content_start + content_length > data.length() {
return Err(
Truncated(
"field \{spec.number} content",
content_length,
Int::max(0, data.length() - content_start),
),
)
}
let content = bytes_slice(data, content_start, content_start + content_length)
let value = match decode_field_content(spec, content, logical_length) {
Ok(value) => value
Err(error) => return Err(error)
}
match validate_data_kind(spec.number, spec.data_kind, value) {
Err(error) => return Err(error)
Ok(_) => ()
}
Ok({
value,
consumed: prefix_length + content_length,
logical_length,
prefix_length,
})
}
///|
/// Encode a field directly to uppercase hexadecimal for fixtures.
pub fn encode_field_hex(
spec : FieldSpec,
value : String,
) -> Result[String, IsoError] {
match encode_field(spec, value) {
Ok(encoded) => Ok(hex_encode(encoded.bytes))
Err(error) => Err(error)
}
}
///|
/// Decode a field from hexadecimal fixture text.
pub fn decode_field_hex(
spec : FieldSpec,
wire : String,
) -> Result[DecodedField, IsoError] {
let data = match hex_decode(wire) {
Ok(bytes) => bytes
Err(error) => return Err(error)
}
decode_field(spec, data, 0)
}
///|
/// Construct a fixed BCD numeric field definition.
pub fn fixed_bcd_numeric(
number : Int,
name : String,
digits : Int,
) -> FieldSpec {
{
number,
name,
length_kind: Fixed,
max_length: digits,
min_length: digits,
length_encoding: BcdLength,
content_encoding: BcdContent,
data_kind: Numeric,
pad_direction: PadLeft,
pad_char: '0',
}
}
///|
/// Construct a BCD-content LLVAR numeric field.
pub fn llvar_bcd_numeric(
number : Int,
name : String,
digits : Int,
) -> FieldSpec {
{
number,
name,
length_kind: Llvar,
max_length: digits,
min_length: 0,
length_encoding: BcdLength,
content_encoding: BcdContent,
data_kind: Numeric,
pad_direction: NoPadding,
pad_char: '0',
}
}
///|
/// Construct a binary LLLVAR field with BCD length prefix.
pub fn lllvar_bcd_binary(
number : Int,
name : String,
maximum_bytes : Int,
) -> FieldSpec {
{
number,
name,
length_kind: Lllvar,
max_length: maximum_bytes * 2,
min_length: 0,
length_encoding: BcdLength,
content_encoding: BinaryContent,
data_kind: BinaryHex,
pad_direction: NoPadding,
pad_char: '0',
}
}
///|
/// Decode a sequence of fields selected by a bitmap.
pub fn decode_selected_fields(
packager : Packager,
bitmap : Bitmap,
data : Bytes,
start : Int,
) -> Result[(Array[FieldEntry], Int), IsoError] {
let fields : Array[FieldEntry] = []
let mut offset = start
for number in bitmap.fields() {
let spec = match packager.spec(number) {
Some(spec) => spec
None => return Err(UnknownField(number))
}
let decoded = match decode_field(spec, data, offset) {
Ok(value) => value
Err(error) => return Err(error)
}
fields.push({ number, value: decoded.value, })
offset += decoded.consumed
}
Ok((fields, offset - start))
}
///|
/// Encode message fields in ascending data-element order.
pub fn encode_selected_fields(
packager : Packager,
message : IsoMessage,
) -> Result[Bytes, IsoError] {
let output : Array[Byte] = []
for entry in message.fields {
let spec = match packager.spec(entry.number) {
Some(spec) => spec
None => return Err(UnknownField(entry.number))
}
let encoded = match encode_field(spec, entry.value) {
Ok(value) => value
Err(error) => return Err(error)
}
append_bytes(output, encoded.bytes)
}
Ok(Bytes::from_array(output))
}