// BER-TLV parsing and encoding for EMV data objects.
// This is a data-object codec, not an ASN.1 schema compiler.
pub(all) suberror TlvError {
Truncated
BadTag
BadLength
TooDeep
}
pub(all) struct Tlv {
tag : Int
constructed : Bool
value : Array[Int]
children : Array[Tlv]
}
fn slice(bytes : Array[Int], start : Int, end : Int) -> Array[Int] {
let out : Array[Int] = Array::new()
for i in start.. Int {
if tag <= 0xff {
1
} else if tag <= 0xffff {
2
} else if tag <= 0xffffff {
3
} else {
4
}
}
pub fn tag_to_hex(tag : Int) -> String {
let digits = "0123456789ABCDEF"
let width = tag_width(tag) * 2
let out = StringBuilder()
for i in 0..> shift) & 0xf) {
Some(c) => out.write_char(c)
None => out.write_char('?')
}
}
out.to_string()
}
fn parse_one(
bytes : Array[Int],
pos : Int,
depth : Int,
) -> (Tlv, Int) raise TlvError {
if depth > 16 {
raise TooDeep
}
if pos >= bytes.length() {
raise Truncated
}
let first = bytes[pos]
let mut cursor = pos + 1
let mut tag = first
if (first & 0x1f) == 0x1f {
let mut count = 0
while true {
if cursor >= bytes.length() {
raise Truncated
}
let b = bytes[cursor]
cursor = cursor + 1
tag = (tag << 8) | b
count = count + 1
if (b & 0x80) == 0 {
break
}
if count > 3 {
raise BadTag
}
}
}
if cursor >= bytes.length() {
raise Truncated
}
let length_byte = bytes[cursor]
cursor = cursor + 1
let mut len = 0
if length_byte < 0x80 {
len = length_byte
} else {
let n = length_byte & 0x7f
if n == 0 || n > 4 {
raise BadLength
}
for _ in 0..= bytes.length() {
raise Truncated
}
len = (len << 8) | bytes[cursor]
cursor = cursor + 1
}
}
if cursor + len > bytes.length() {
raise Truncated
}
let value = slice(bytes, cursor, cursor + len)
let constructed = (first & 0x20) != 0
let children : Array[Tlv] = Array::new()
if constructed {
let mut child_pos = 0
while child_pos < value.length() {
if value[child_pos] == 0 {
child_pos = child_pos + 1
} else {
let (child, next) = parse_one(value, child_pos, depth + 1)
children.push(child)
child_pos = next
}
}
}
({ tag, constructed, value, children }, cursor + len)
}
pub fn parse_tlv(bytes : Array[Int]) -> Array[Tlv] raise TlvError {
let out : Array[Tlv] = Array::new()
let mut pos = 0
while pos < bytes.length() {
if bytes[pos] == 0 {
pos = pos + 1
} else {
let (item, next) = parse_one(bytes, pos, 0)
out.push(item)
pos = next
}
}
out
}
pub fn find_tag(items : Array[Tlv], tag : Int) -> Tlv? {
for item in items {
if item.tag == tag {
return Some(item)
}
match find_tag(item.children, tag) {
Some(found) => return Some(found)
None => ()
}
}
None
}
pub fn collect_tags(items : Array[Tlv]) -> Array[Int] {
let out : Array[Int] = Array::new()
for item in items {
out.push(item.tag)
for child_tag in collect_tags(item.children) {
out.push(child_tag)
}
}
out
}
pub fn tag_name(tag : Int) -> String {
if tag == 0x4f {
"ADF Name"
} else if tag == 0x50 {
"Application Label"
} else if tag == 0x57 {
"Track 2 Equivalent Data"
} else if tag == 0x5a {
"Primary Account Number"
} else if tag == 0x5f20 {
"Cardholder Name"
} else if tag == 0x5f24 {
"Application Expiration Date"
} else if tag == 0x5f34 {
"PAN Sequence Number"
} else if tag == 0x6f {
"FCI Template"
} else if tag == 0x70 {
"Record Template"
} else if tag == 0x77 {
"Response Message Template"
} else if tag == 0x80 {
"Response Message Template (old)"
} else if tag == 0x82 {
"Application Interchange Profile"
} else if tag == 0x83 {
"Command Template"
} else if tag == 0x84 {
"Dedicated File Name"
} else if tag == 0x8c {
"CDOL1"
} else if tag == 0x8d {
"CDOL2"
} else if tag == 0x90 {
"Issuer Public Key Certificate"
} else if tag == 0x94 {
"Application File Locator"
} else if tag == 0x95 {
"Terminal Verification Results"
} else if tag == 0x9a {
"Transaction Date"
} else if tag == 0x9c {
"Transaction Type"
} else if tag == 0x9f02 {
"Amount, Authorized"
} else if tag == 0x9f03 {
"Amount, Other"
} else if tag == 0x9f10 {
"Issuer Application Data"
} else if tag == 0x9f1a {
"Terminal Country Code"
} else if tag == 0x9f26 {
"Application Cryptogram"
} else if tag == 0x9f27 {
"Cryptogram Information Data"
} else if tag == 0x9f33 {
"Terminal Capabilities"
} else if tag == 0x9f35 {
"Terminal Type"
} else if tag == 0x9f36 {
"Application Transaction Counter"
} else if tag == 0x9f37 {
"Unpredictable Number"
} else if tag == 0x9f39 {
"POS Entry Mode"
} else if tag == 0x9f41 {
"Transaction Sequence Counter"
} else if tag == 0x9f4e {
"Merchant Name and Location"
} else if tag == 0x9f53 {
"Transaction Category Code"
} else if tag == 0xa5 {
"FCI Proprietary Template"
} else if tag == 0xbf0c {
"FCI Issuer Discretionary Data"
} else if tag == 0x5f28 {
"Issuer Country Code"
} else if tag == 0x5f2d {
"Language Preference"
} else if tag == 0x5f30 {
"Service Code"
} else if tag == 0x5f36 {
"Transaction Currency Code"
} else if tag == 0x5f50 {
"Issuer URL"
} else if tag == 0x5f53 {
"IBAN"
} else if tag == 0x5f54 {
"Bank Identifier Code"
} else if tag == 0x5f56 {
"Issuer Country Code (2)"
} else if tag == 0x61 {
"Application Template"
} else if tag == 0x9f07 {
"Application Usage Control"
} else if tag == 0x9f08 {
"Application Version Number"
} else if tag == 0x9f0d {
"Issuer Action Code - Default"
} else if tag == 0x9f0e {
"Issuer Action Code - Denial"
} else if tag == 0x9f0f {
"Issuer Action Code - Online"
} else if tag == 0x9f42 {
"Application Currency Code"
} else if tag == 0x9f44 {
"Application Currency Exponent"
} else if tag == 0x9f45 {
"Data Authentication Code"
} else if tag == 0x9f46 {
"ICC Public Key Certificate"
} else if tag == 0x9f47 {
"ICC Public Key Exponent"
} else if tag == 0x9f48 {
"ICC Public Key Remainder"
} else if tag == 0x9f49 {
"Dynamic Data Authentication Data Object List"
} else if tag == 0x9f4a {
"Static Data Authentication Tag List"
} else if tag == 0x9f4b {
"Signed Dynamic Application Data"
} else if tag == 0x9f4c {
"ICC Dynamic Number"
} else if tag == 0x9f4d {
"Log Entry"
} else if tag == 0x9f4f {
"Log Format"
} else if tag == 0x9f51 {
"Issuer Application Data (2)"
} else if tag == 0x9f52 {
"Application Default Action"
} else if tag == 0x9f54 {
"Transaction Category Code (2)"
} else if tag == 0x9f5b {
"DS Summary 1"
} else if tag == 0x9f5d {
"DS Summary 2"
} else if tag == 0x9f62 {
"PCVC3"
} else if tag == 0x9f63 {
"PUNATC"
} else if tag == 0x9f66 {
"Terminal Transaction Qualifiers"
} else {
"Unknown"
}
}
fn encode_length(len : Int) -> Array[Int] {
if len < 0x80 {
[len]
} else if len <= 0xff {
[0x81, len]
} else {
[0x82, (len >> 8) & 0xff, len & 0xff]
}
}
fn encode_tag(tag : Int) -> Array[Int] {
let width = tag_width(tag)
let out : Array[Int] = Array::new()
for i in 0..> ((width - 1 - i) * 8)) & 0xff)
}
out
}
pub fn encode_tlv(item : Tlv) -> Array[Int] {
let body = if item.constructed {
let out : Array[Int] = Array::new()
for child in item.children {
for b in encode_tlv(child) {
out.push(b)
}
}
out
} else {
item.value
}
let out = encode_tag(item.tag)
for b in encode_length(body.length()) {
out.push(b)
}
for b in body {
out.push(b)
}
out
}
pub fn encode_tlvs(items : Array[Tlv]) -> Array[Int] {
let out : Array[Int] = Array::new()
for item in items {
for b in encode_tlv(item) {
out.push(b)
}
}
out
}
pub fn flatten_tlvs(items : Array[Tlv]) -> Array[Tlv] {
let out : Array[Tlv] = Array::new()
for item in items {
if item.constructed {
for child in flatten_tlvs(item.children) {
out.push(child)
}
} else {
out.push(item)
}
}
out
}
pub fn find_path(items : Array[Tlv], path : Array[Int]) -> Tlv? {
if path.length() == 0 {
return None
}
let mut current = items
for depth in 0..
if depth == path.length() - 1 {
return Some(item)
} else {
current = item.children
}
None => return None
}
}
None
}