///|
/// One BER-TLV node as used by EMV data in ISO 8583 field 55.
pub(all) struct BerTlv {
tag : String
value : Bytes
constructed : Bool
children : Array[BerTlv]
} derive(Debug)
///|
/// Internal result returned after reading one BER tag.
priv struct BerTagResult {
text : String
constructed : Bool
next : Int
}
///|
/// Internal result returned after reading a BER definite length.
priv struct BerLengthResult {
length : Int
next : Int
}
///|
/// Parse hexadecimal DE55 text into a BER-TLV tree.
pub fn parse_de55(value : String) -> Result[Array[BerTlv], IsoError] {
let data = match hex_decode(value) {
Ok(bytes) => bytes
Err(_) =>
return Err(InvalidTlv("DE55 must be even-length hexadecimal text"))
}
decode_ber_tlv(data)
}
///|
/// Decode a complete byte string containing one or more BER-TLV nodes.
pub fn decode_ber_tlv(data : Bytes) -> Result[Array[BerTlv], IsoError] {
if data.length() == 0 {
return Err(InvalidTlv("TLV sequence is empty"))
}
parse_tlv_range(data, 0, data.length(), 0)
}
///|
/// Parse all nodes in one exact byte range.
fn parse_tlv_range(
data : Bytes,
start : Int,
end : Int,
depth : Int,
) -> Result[Array[BerTlv], IsoError] {
if depth > 16 {
return Err(InvalidTlv("constructed TLV nesting exceeds 16 levels"))
}
let nodes : Array[BerTlv] = []
let mut offset = start
while offset < end {
let tag = match parse_ber_tag(data, offset, end) {
Ok(result) => result
Err(error) => return Err(error)
}
let length = match parse_ber_length(data, tag.next, end) {
Ok(result) => result
Err(error) => return Err(error)
}
let value_end = length.next + length.length
if value_end > end {
return Err(
InvalidTlv(
"tag \{tag.text} declares \{length.length} bytes but only \{end - length.next} remain",
),
)
}
let value = tlv_slice(data, length.next, value_end)
let children = if tag.constructed {
if value.length() == 0 {
return Err(InvalidTlv("constructed tag \{tag.text} has an empty value"))
}
match parse_tlv_range(data, length.next, value_end, depth + 1) {
Ok(result) => result
Err(error) => return Err(error)
}
} else {
[]
}
nodes.push({ tag: tag.text, value, constructed: tag.constructed, children, })
offset = value_end
}
if offset != end {
return Err(InvalidTlv("TLV range was not consumed exactly"))
}
Ok(nodes)
}
///|
/// Read and canonicalize one BER tag.
fn parse_ber_tag(
data : Bytes,
start : Int,
end : Int,
) -> Result[BerTagResult, IsoError] {
if start >= end {
return Err(InvalidTlv("missing tag byte"))
}
let first = data[start].to_int()
if first == 0 || first == 255 {
return Err(InvalidTlv("00 and FF padding bytes are not valid tags"))
}
let constructed = (first & 0x20) != 0
let mut next = start + 1
if (first & 0x1F) == 0x1F {
if next >= end {
return Err(InvalidTlv("truncated high-tag-number form"))
}
let first_continuation = data[next].to_int()
if (first_continuation & 0x7F) == 0 {
return Err(InvalidTlv("non-minimal high-tag-number form"))
}
let mut terminated = false
let mut count = 1
while next < end && count < 4 {
let current = data[next].to_int()
next += 1
count += 1
if (current & 0x80) == 0 {
terminated = true
break
}
}
if !terminated {
if next >= end {
return Err(InvalidTlv("unterminated high-tag-number form"))
}
return Err(InvalidTlv("BER tag exceeds four bytes"))
}
}
let bytes = tlv_slice(data, start, next)
Ok({ text: hex_encode(bytes), constructed, next, })
}
///|
/// Read a BER short or long definite length.
fn parse_ber_length(
data : Bytes,
start : Int,
end : Int,
) -> Result[BerLengthResult, IsoError] {
if start >= end {
return Err(InvalidTlv("missing length byte"))
}
let first = data[start].to_int()
if first < 128 {
return Ok({ length: first, next: start + 1, })
}
if first == 128 {
return Err(InvalidTlv("indefinite length is not supported"))
}
let count = first & 0x7F
if count == 0 || count > 4 {
return Err(InvalidTlv("long-form length must use one to four bytes"))
}
if start + 1 + count > end {
return Err(InvalidTlv("truncated long-form length"))
}
if data[start + 1].to_int() == 0 {
return Err(InvalidTlv("long-form length has a leading zero"))
}
let mut length = 0
for i = 0; i < count; i = i + 1 {
let byte = data[start + 1 + i].to_int()
if length > 0x7FFFFF {
return Err(InvalidTlv("declared length exceeds supported range"))
}
length = (length << 8) | byte
}
if length < 128 {
return Err(InvalidTlv("long-form length is not minimally encoded"))
}
Ok({ length, next: start + 1 + count, })
}
///|
/// Construct a primitive TLV after validating the tag form.
pub fn primitive_tlv(tag : String, value : Bytes) -> Result[BerTlv, IsoError] {
let tag_bytes = match hex_decode(tag) {
Ok(bytes) => bytes
Err(_) => return Err(InvalidTlv("tag must be hexadecimal"))
}
let parsed = match parse_ber_tag(tag_bytes, 0, tag_bytes.length()) {
Ok(result) => result
Err(error) => return Err(error)
}
if parsed.next != tag_bytes.length() {
return Err(InvalidTlv("tag text contains more than one tag"))
}
if parsed.constructed {
return Err(InvalidTlv("constructed tag requires child TLVs"))
}
Ok({ tag: parsed.text, value, constructed: false, children: [], })
}
///|
/// Construct a primitive TLV from hexadecimal value text.
pub fn primitive_tlv_hex(
tag : String,
value : String,
) -> Result[BerTlv, IsoError] {
let bytes = match hex_decode(value) {
Ok(result) => result
Err(_) => return Err(InvalidTlv("TLV value must be hexadecimal"))
}
primitive_tlv(tag, bytes)
}
///|
/// Construct a BER constructed node and derive its encoded value.
pub fn constructed_tlv(
tag : String,
children : Array[BerTlv],
) -> Result[BerTlv, IsoError] {
if children.length() == 0 {
return Err(InvalidTlv("constructed TLV needs at least one child"))
}
let tag_bytes = match hex_decode(tag) {
Ok(bytes) => bytes
Err(_) => return Err(InvalidTlv("tag must be hexadecimal"))
}
let parsed = match parse_ber_tag(tag_bytes, 0, tag_bytes.length()) {
Ok(result) => result
Err(error) => return Err(error)
}
if parsed.next != tag_bytes.length() {
return Err(InvalidTlv("tag text contains more than one tag"))
}
if !parsed.constructed {
return Err(InvalidTlv("primitive tag cannot contain child TLVs"))
}
let value = match encode_ber_tlv(children) {
Ok(result) => result
Err(error) => return Err(error)
}
Ok({ tag: parsed.text, value, constructed: true, children, })
}
///|
/// Encode a complete BER-TLV sequence.
pub fn encode_ber_tlv(nodes : Array[BerTlv]) -> Result[Bytes, IsoError] {
if nodes.length() == 0 {
return Err(InvalidTlv("TLV sequence is empty"))
}
let output : Array[Byte] = []
for node in nodes {
match encode_tlv_node(node, output, 0) {
Err(error) => return Err(error)
Ok(_) => ()
}
}
Ok(Bytes::from_array(output))
}
///|
/// Encode one node recursively into a caller-owned output buffer.
fn encode_tlv_node(
node : BerTlv,
output : Array[Byte],
depth : Int,
) -> Result[Unit, IsoError] {
if depth > 16 {
return Err(InvalidTlv("constructed TLV nesting exceeds 16 levels"))
}
let tag = match hex_decode(node.tag) {
Ok(bytes) => bytes
Err(_) => return Err(InvalidTlv("node tag must be hexadecimal"))
}
let parsed = match parse_ber_tag(tag, 0, tag.length()) {
Ok(result) => result
Err(error) => return Err(error)
}
if parsed.next != tag.length() {
return Err(InvalidTlv("node tag contains trailing bytes"))
}
if parsed.constructed != node.constructed {
return Err(InvalidTlv("tag constructed bit disagrees with node type"))
}
let value = if node.constructed {
if node.children.length() == 0 {
return Err(InvalidTlv("constructed node has no children"))
}
let nested : Array[Byte] = []
for child in node.children {
match encode_tlv_node(child, nested, depth + 1) {
Err(error) => return Err(error)
Ok(_) => ()
}
}
Bytes::from_array(nested)
} else {
if node.children.length() != 0 {
return Err(InvalidTlv("primitive node cannot have children"))
}
node.value
}
append_tlv_bytes(output, tag)
match append_ber_length(output, value.length()) {
Err(error) => return Err(error)
Ok(_) => ()
}
append_tlv_bytes(output, value)
Ok(())
}
///|
/// Append a canonical BER definite length.
fn append_ber_length(
output : Array[Byte],
length : Int,
) -> Result[Unit, IsoError] {
if length < 0 {
return Err(InvalidTlv("negative length"))
}
if length < 128 {
output.push(length.to_byte())
return Ok(())
}
let encoded : Array[Byte] = []
let mut remaining = length
while remaining > 0 {
encoded.push((remaining & 255).to_byte())
remaining = remaining >> 8
}
if encoded.length() > 4 {
return Err(InvalidTlv("value exceeds four-byte BER length range"))
}
output.push((128 | encoded.length()).to_byte())
let mut i = encoded.length()
while i > 0 {
i -= 1
output.push(encoded[i])
}
Ok(())
}
///|
/// Encode a TLV tree as uppercase DE55 hexadecimal text.
pub fn format_de55(nodes : Array[BerTlv]) -> Result[String, IsoError] {
match encode_ber_tlv(nodes) {
Ok(bytes) => Ok(hex_encode(bytes))
Err(error) => Err(error)
}
}
///|
/// Return the first node with a canonical hexadecimal tag, depth-first.
pub fn find_tlv(nodes : Array[BerTlv], tag : String) -> BerTlv? {
let wanted = tag.to_upper()
for node in nodes {
if node.tag == wanted {
return Some(node)
}
if node.constructed {
match find_tlv(node.children, wanted) {
Some(found) => return Some(found)
None => ()
}
}
}
None
}
///|
/// Return every matching node in depth-first wire order.
pub fn find_all_tlv(nodes : Array[BerTlv], tag : String) -> Array[BerTlv] {
let output : Array[BerTlv] = []
collect_tlv(nodes, tag.to_upper(), output)
output
}
///|
fn collect_tlv(
nodes : Array[BerTlv],
tag : String,
output : Array[BerTlv],
) -> Unit {
for node in nodes {
if node.tag == tag {
output.push(node)
}
if node.constructed {
collect_tlv(node.children, tag, output)
}
}
}
///|
/// Count all primitive and constructed nodes recursively.
pub fn count_tlv_nodes(nodes : Array[BerTlv]) -> Int {
let mut count = 0
for node in nodes {
count += 1
if node.constructed {
count += count_tlv_nodes(node.children)
}
}
count
}
///|
/// Return hexadecimal values for all primitive nodes in wire order.
pub fn flatten_primitive_tlv(nodes : Array[BerTlv]) -> Array[(String, String)] {
let output : Array[(String, String)] = []
flatten_tlv_into(nodes, output)
output
}
///|
fn flatten_tlv_into(
nodes : Array[BerTlv],
output : Array[(String, String)],
) -> Unit {
for node in nodes {
if node.constructed {
flatten_tlv_into(node.children, output)
} else {
output.push((node.tag, hex_encode(node.value)))
}
}
}
///|
/// Copy a byte range for TLV values and tag parsing.
fn tlv_slice(data : Bytes, start : Int, end : Int) -> Bytes {
Bytes::makei(end - start, fn(i) { data[start + i] })
}
///|
/// Append immutable bytes to a mutable byte buffer.
fn append_tlv_bytes(target : Array[Byte], source : Bytes) -> Unit {
for byte in source {
target.push(byte)
}
}