use heapless::Vec;
use crate::limits::{
GETDATA_RAW_MAX, MAX_KEYSETS, MAX_KEYS_PER_SET, MAX_MODULES_PER_ELF, MAX_PRIVILEGE_BYTES,
MAX_REGISTRY_ENTRIES, MAX_SCP_VARIANTS,
};
use crate::model::{CardCapabilities, KeyInfo, KeyTemplateFormat, KeyType, Keyset, ScpVariant};
use crate::report::CardLifeCycle;
use crate::tlv::{self, Tlv, TlvError};
const TAG_CRD: u32 = 0x66;
const TAG_CRD_BODY: u32 = 0x73;
const TAG_SCP_ENTRY: u32 = 0x64;
const TAG_OID: u32 = 0x06;
const TAG_KEY_TEMPLATE: u32 = 0xE0;
const TAG_KEY_ENTRY: u32 = 0xC0;
const KEY_EXTENDED_MARKER: u8 = 0xB9;
const TAG_CCI: u32 = 0x67;
const TAG_CCI_CHANNELS: u32 = 0xA0;
const TAG_CCI_PRIVILEGES: u32 = 0xA3;
const TAG_GP_REGISTRY: u32 = 0xE3;
const TAG_LIFE_CYCLE: u32 = 0x9F70;
const TAG_AID: u32 = 0x4F;
const TAG_PRIVILEGES: u32 = 0xC5;
const TAG_ASSOC_SD_AID: u32 = 0xCC;
const TAG_ELF_AID: u32 = 0xC4;
const TAG_MODULE_AID: u32 = 0x84;
const KEY_TYPE_DES: u8 = 0x80;
const KEY_TYPE_AES: u8 = 0x88;
const KEY_TYPE_RSA_PUBLIC: u8 = 0xA1;
const KEY_TYPE_RSA_PRIVATE_CRT: u8 = 0xA2;
const KEY_TYPE_RSA_PRIVATE_EXP: u8 = 0xA3;
const KEY_TYPE_ECC_PUBLIC: u8 = 0xB0;
const KEY_TYPE_ECC_PRIVATE: u8 = 0xB1;
const KEY_TYPE_ECC_PARAMS_REF: u8 = 0xB2;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CardRecognition {
pub scp: Vec<ScpVariant, MAX_SCP_VARIANTS>,
}
pub fn parse_card_recognition(data: &[u8]) -> Result<CardRecognition, TlvError> {
let mut out = CardRecognition::default();
if data.is_empty() {
return Ok(out);
}
let top = tlv::parse(data)?;
let body = if let Some(v66) = find(&top, TAG_CRD) {
let wrapped = tlv::parse(v66)?;
find(&wrapped, TAG_CRD_BODY)
} else {
find(&top, TAG_CRD_BODY)
};
let Some(body) = body else {
return Ok(out); };
let entries = tlv::parse(body)?;
for entry in entries.iter().filter(|t| t.tag == TAG_SCP_ENTRY) {
let oid_tlvs = tlv::parse(entry.value)?;
let Some(oid) = find(&oid_tlvs, TAG_OID) else {
continue;
};
if let [.., scp_id, i] = oid {
if let Some(variant) = scp_variant(*scp_id, *i) {
out.scp.push(variant).map_err(|_| TlvError::TooMany)?;
}
}
}
Ok(out)
}
fn scp_variant(scp_id: u8, i_param: u8) -> Option<ScpVariant> {
match scp_id {
0x02 => Some(ScpVariant::Scp02 { i_param }),
0x03 => Some(ScpVariant::Scp03 { i_param }),
_ => None,
}
}
pub struct KeyInformation {
pub format: KeyTemplateFormat,
pub keysets: Vec<Keyset, MAX_KEYSETS>,
}
pub fn parse_key_information(data: &[u8]) -> Result<KeyInformation, TlvError> {
let mut format = KeyTemplateFormat::Basic;
let mut keysets: Vec<Keyset, MAX_KEYSETS> = Vec::new();
if data.is_empty() {
return Ok(KeyInformation { format, keysets });
}
let top = tlv::parse(data)?;
let body = find(&top, TAG_KEY_TEMPLATE).unwrap_or(data);
let entries = tlv::parse(body)?;
for entry in entries.iter().filter(|t| t.tag == TAG_KEY_ENTRY) {
let value = entry.value;
let (Some(&kid), Some(&kvn)) = (value.first(), value.get(1)) else {
continue;
};
let rest = &value[2..];
if rest.first() == Some(&KEY_EXTENDED_MARKER) {
format = KeyTemplateFormat::Extended;
push_key(
&mut keysets,
kvn,
KeyInfo {
kid,
key_type: KeyType::Other(KEY_EXTENDED_MARKER),
key_length: 0,
},
)?;
} else {
for pair in rest.chunks_exact(2) {
push_key(
&mut keysets,
kvn,
KeyInfo {
kid,
key_type: decode_key_type(pair[0]),
key_length: pair[1],
},
)?;
}
}
}
Ok(KeyInformation { format, keysets })
}
fn push_key(keysets: &mut Vec<Keyset, MAX_KEYSETS>, kvn: u8, key: KeyInfo) -> Result<(), TlvError> {
if let Some(set) = keysets.iter_mut().find(|s| s.kvn == kvn) {
return set.keys.push(key).map_err(|_| TlvError::TooMany);
}
let mut keys: Vec<KeyInfo, MAX_KEYS_PER_SET> = Vec::new();
keys.push(key).map_err(|_| TlvError::TooMany)?;
keysets
.push(Keyset { kvn, keys })
.map_err(|_| TlvError::TooMany)
}
fn decode_key_type(byte: u8) -> KeyType {
match byte {
KEY_TYPE_DES => KeyType::Des,
KEY_TYPE_AES => KeyType::Aes,
KEY_TYPE_RSA_PUBLIC => KeyType::RsaPublic,
KEY_TYPE_RSA_PRIVATE_CRT => KeyType::RsaPrivateCrt,
KEY_TYPE_RSA_PRIVATE_EXP => KeyType::RsaPrivateExponent,
KEY_TYPE_ECC_PUBLIC => KeyType::EccPublic,
KEY_TYPE_ECC_PRIVATE => KeyType::EccPrivate,
KEY_TYPE_ECC_PARAMS_REF => KeyType::EccParametersRef,
other => KeyType::Other(other),
}
}
pub fn parse_card_capabilities(data: &[u8]) -> Result<CardCapabilities, TlvError> {
let mut caps = CardCapabilities {
max_logical_channels: 1, ciphers_supported: Vec::new(),
privileges_supported: Vec::new(),
memory_total_bytes: None,
memory_free_bytes: None,
cci_raw: Vec::new(),
};
let raw_len = data.len().min(GETDATA_RAW_MAX);
let _ = caps.cci_raw.extend_from_slice(&data[..raw_len]);
if data.is_empty() {
return Ok(caps);
}
let top = tlv::parse(data)?;
let body = find(&top, TAG_CCI).unwrap_or(data);
let subs = tlv::parse(body)?;
if let Some(channels) = find(&subs, TAG_CCI_CHANNELS) {
if let Some(&n) = channels.first() {
caps.max_logical_channels = n;
}
}
if let Some(privileges) = find(&subs, TAG_CCI_PRIVILEGES) {
let n = privileges.len().min(MAX_PRIVILEGE_BYTES);
let _ = caps
.privileges_supported
.extend_from_slice(&privileges[..n]);
}
Ok(caps)
}
pub fn parse_status_e3(data: &[u8]) -> Result<Option<CardLifeCycle>, TlvError> {
if data.is_empty() {
return Ok(None);
}
let top = tlv::parse(data)?;
let Some(registry) = find(&top, TAG_GP_REGISTRY) else {
return Ok(None);
};
let fields = tlv::parse(registry)?;
let Some(life_cycle) = find(&fields, TAG_LIFE_CYCLE) else {
return Ok(None);
};
Ok(life_cycle.first().map(|&b| decode_life_cycle(b)))
}
fn decode_life_cycle(byte: u8) -> CardLifeCycle {
match byte {
0x01 => CardLifeCycle::OpReady,
0x07 => CardLifeCycle::Initialized,
0x0F => CardLifeCycle::Secured,
0x7F => CardLifeCycle::CardLocked,
0xFF => CardLifeCycle::Terminated,
other => CardLifeCycle::Unknown(other),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegistryEntry<'a> {
pub aid: &'a [u8],
pub life_cycle: u8,
pub privileges: [u8; 3],
pub associated_sd_aid: Option<&'a [u8]>,
pub elf_aid: Option<&'a [u8]>,
pub modules: Vec<&'a [u8], MAX_MODULES_PER_ELF>,
}
pub fn parse_status_registry(
data: &[u8],
) -> Result<Vec<RegistryEntry<'_>, MAX_REGISTRY_ENTRIES>, TlvError> {
let mut out: Vec<RegistryEntry, MAX_REGISTRY_ENTRIES> = Vec::new();
if data.is_empty() {
return Ok(out);
}
let top = tlv::parse(data)?;
for e3 in top.iter().filter(|t| t.tag == TAG_GP_REGISTRY) {
let fields = tlv::parse(e3.value)?;
let aid = find(&fields, TAG_AID).unwrap_or(&[]);
let life_cycle = find(&fields, TAG_LIFE_CYCLE)
.and_then(|v| v.first().copied())
.unwrap_or(0);
let privileges = privileges_to_3(find(&fields, TAG_PRIVILEGES));
let associated_sd_aid = find(&fields, TAG_ASSOC_SD_AID);
let elf_aid = find(&fields, TAG_ELF_AID);
let mut modules: Vec<&[u8], MAX_MODULES_PER_ELF> = Vec::new();
for m in fields.iter().filter(|t| t.tag == TAG_MODULE_AID) {
if modules.push(m.value).is_err() {
break; }
}
let entry = RegistryEntry {
aid,
life_cycle,
privileges,
associated_sd_aid,
elf_aid,
modules,
};
if out.push(entry).is_err() {
break; }
}
Ok(out)
}
fn privileges_to_3(value: Option<&[u8]>) -> [u8; 3] {
let mut p = [0u8; 3];
if let Some(b) = value {
let n = b.len().min(3);
p[..n].copy_from_slice(&b[..n]);
}
p
}
fn find<'a>(tlvs: &[Tlv<'a>], tag: u32) -> Option<&'a [u8]> {
tlvs.iter().find(|t| t.tag == tag).map(|t| t.value)
}
#[cfg(test)]
mod tests {
use super::*;
const CRD_SCP02_55: &[u8] = &[
0x66, 0x4C, 0x73, 0x4A, 0x06, 0x07, 0x2A, 0x86, 0x48, 0x86, 0xFC, 0x6B, 0x01, 0x60, 0x0C,
0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, 0xFC, 0x6B, 0x02, 0x02, 0x01, 0x01, 0x63, 0x09, 0x06,
0x07, 0x2A, 0x86, 0x48, 0x86, 0xFC, 0x6B, 0x03, 0x64, 0x0B, 0x06, 0x09, 0x2A, 0x86, 0x48,
0x86, 0xFC, 0x6B, 0x04, 0x02, 0x55, 0x65, 0x0B, 0x06, 0x09, 0x2B, 0x85, 0x10, 0x86, 0x48,
0x64, 0x02, 0x01, 0x03, 0x66, 0x0C, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x2A, 0x02,
0x6E, 0x01, 0x02,
];
#[test]
fn crd_decodes_scp02_i55() {
let crd = parse_card_recognition(CRD_SCP02_55).unwrap();
assert_eq!(crd.scp.len(), 1);
assert_eq!(crd.scp[0], ScpVariant::Scp02 { i_param: 0x55 });
}
#[test]
fn crd_decodes_scp03_i70() {
let crd = [
0x66, 0x0E, 0x73, 0x0C, 0x64, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xFC, 0x6B,
0x03, 0x70,
];
let r = parse_card_recognition(&crd).unwrap();
assert_eq!(r.scp[0], ScpVariant::Scp03 { i_param: 0x70 });
}
#[test]
fn crd_multiple_variants_preserve_order() {
let crd = [
0x73, 0x10, 0x64, 0x06, 0x06, 0x04, 0x00, 0x00, 0x02, 0x55, 0x64, 0x06, 0x06, 0x04,
0x00, 0x00, 0x03, 0x70,
];
let r = parse_card_recognition(&crd).unwrap();
assert_eq!(r.scp.len(), 2);
assert_eq!(r.scp[0], ScpVariant::Scp02 { i_param: 0x55 });
assert_eq!(r.scp[1], ScpVariant::Scp03 { i_param: 0x70 });
}
#[test]
fn crd_drops_scp01() {
let crd = [0x73, 0x08, 0x64, 0x06, 0x06, 0x04, 0x00, 0x00, 0x01, 0x05];
assert!(parse_card_recognition(&crd).unwrap().scp.is_empty());
}
#[test]
fn crd_empty_input_is_empty_not_error() {
assert!(parse_card_recognition(&[]).unwrap().scp.is_empty());
}
#[test]
fn crd_no_template_is_empty_not_error() {
assert!(parse_card_recognition(&[0x5C, 0x01, 0x9F])
.unwrap()
.scp
.is_empty());
}
#[test]
fn crd_entry_without_oid_is_skipped() {
let crd = [0x73, 0x06, 0x64, 0x04, 0x80, 0x02, 0x00, 0x00];
assert!(parse_card_recognition(&crd).unwrap().scp.is_empty());
}
#[test]
fn crd_oid_shorter_than_two_octets_is_skipped() {
let crd = [0x73, 0x05, 0x64, 0x03, 0x06, 0x01, 0x2A];
assert!(parse_card_recognition(&crd).unwrap().scp.is_empty());
}
#[test]
fn crd_malformed_tlv_is_rejected() {
assert_eq!(
parse_card_recognition(&[0x66, 0x05, 0x00]),
Err(TlvError::Truncated)
);
}
#[test]
fn key_info_basic_single_keyset_three_kids() {
let data = [
0xE0, 0x12, 0xC0, 0x04, 0x01, 0x01, 0x88, 0x10, 0xC0, 0x04, 0x02, 0x01, 0x88, 0x10,
0xC0, 0x04, 0x03, 0x01, 0x88, 0x10,
];
let info = parse_key_information(&data).unwrap();
assert!(matches!(info.format, KeyTemplateFormat::Basic));
assert_eq!(info.keysets.len(), 1);
assert_eq!(info.keysets[0].kvn, 1);
assert_eq!(info.keysets[0].keys.len(), 3);
assert_eq!(info.keysets[0].keys[0].kid, 1);
assert!(matches!(info.keysets[0].keys[0].key_type, KeyType::Aes));
assert_eq!(info.keysets[0].keys[2].key_length, 0x10);
}
#[test]
fn key_info_groups_by_kvn() {
let data = [
0xE0, 0x0E, 0xC0, 0x04, 0x01, 0x01, 0x80, 0x10, 0xC0, 0x04, 0x01, 0x02, 0x88, 0x10,
0xC0, 0x00, ];
let info = parse_key_information(&data).unwrap();
assert_eq!(info.keysets.len(), 2);
assert!(matches!(info.keysets[0].keys[0].key_type, KeyType::Des));
assert!(matches!(info.keysets[1].keys[0].key_type, KeyType::Aes));
}
#[test]
fn key_info_multi_component_pairs() {
let data = [0xE0, 0x08, 0xC0, 0x06, 0x10, 0x01, 0xA1, 0x80, 0xA2, 0x80];
let info = parse_key_information(&data).unwrap();
assert_eq!(info.keysets[0].keys.len(), 2);
assert!(matches!(
info.keysets[0].keys[0].key_type,
KeyType::RsaPublic
));
assert!(matches!(
info.keysets[0].keys[1].key_type,
KeyType::RsaPrivateCrt
));
}
#[test]
fn key_info_extended_format_sets_flag() {
let data = [0xE0, 0x06, 0xC0, 0x04, 0x01, 0x01, 0xB9, 0x00];
let info = parse_key_information(&data).unwrap();
assert!(matches!(info.format, KeyTemplateFormat::Extended));
assert_eq!(info.keysets[0].keys[0].kid, 1);
assert!(matches!(
info.keysets[0].keys[0].key_type,
KeyType::Other(0xB9)
));
}
#[test]
fn key_info_unknown_type_is_other() {
let data = [0xE0, 0x06, 0xC0, 0x04, 0x05, 0x09, 0x42, 0x08];
let info = parse_key_information(&data).unwrap();
assert!(matches!(
info.keysets[0].keys[0].key_type,
KeyType::Other(0x42)
));
}
#[test]
fn key_info_bare_c0_without_e0_wrapper() {
let data = [0xC0, 0x04, 0x01, 0x01, 0x88, 0x10];
let info = parse_key_information(&data).unwrap();
assert_eq!(info.keysets[0].keys[0].kid, 1);
}
#[test]
fn key_info_empty_is_empty_not_error() {
let info = parse_key_information(&[]).unwrap();
assert!(info.keysets.is_empty());
}
#[test]
fn key_info_malformed_tlv_is_rejected() {
assert!(matches!(
parse_key_information(&[0xE0, 0x05, 0xC0]),
Err(TlvError::Truncated)
));
}
#[test]
fn cci_decodes_channels_and_privileges_and_keeps_raw() {
let data = [0x67, 0x08, 0xA0, 0x01, 0x04, 0xA3, 0x03, 0x80, 0x00, 0x00];
let caps = parse_card_capabilities(&data).unwrap();
assert_eq!(caps.max_logical_channels, 4);
assert_eq!(caps.privileges_supported.len(), 3);
assert_eq!(caps.privileges_supported[0], 0x80);
assert_eq!(caps.cci_raw.len(), data.len());
assert!(caps.ciphers_supported.is_empty());
assert!(caps.memory_total_bytes.is_none());
}
#[test]
fn cci_defaults_to_one_channel_when_a0_absent() {
let data = [0x67, 0x05, 0xA3, 0x03, 0x80, 0x00, 0x00];
let caps = parse_card_capabilities(&data).unwrap();
assert_eq!(caps.max_logical_channels, 1);
}
#[test]
fn cci_empty_a0_keeps_default_channel() {
let data = [0x67, 0x02, 0xA0, 0x00];
let caps = parse_card_capabilities(&data).unwrap();
assert_eq!(caps.max_logical_channels, 1);
}
#[test]
fn cci_bare_subtags_without_67_wrapper() {
let data = [0xA0, 0x01, 0x02];
let caps = parse_card_capabilities(&data).unwrap();
assert_eq!(caps.max_logical_channels, 2);
}
#[test]
fn cci_empty_is_defaults_not_error() {
let caps = parse_card_capabilities(&[]).unwrap();
assert_eq!(caps.max_logical_channels, 1);
assert!(caps.cci_raw.is_empty());
}
#[test]
fn cci_malformed_tlv_is_rejected() {
assert!(matches!(
parse_card_capabilities(&[0x67, 0x05, 0xA0]),
Err(TlvError::Truncated)
));
}
#[test]
fn e3_decodes_each_known_lifecycle_byte() {
for (raw, expect) in [
(0x01u8, CardLifeCycle::OpReady),
(0x07, CardLifeCycle::Initialized),
(0x0F, CardLifeCycle::Secured),
(0x7F, CardLifeCycle::CardLocked),
(0xFF, CardLifeCycle::Terminated),
] {
let data = [
0xE3, 0x0D, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x9F, 0x70, 0x01,
raw,
];
assert_eq!(parse_status_e3(&data).unwrap(), Some(expect));
}
}
#[test]
fn e3_unknown_byte_maps_to_unknown() {
let data = [0xE3, 0x04, 0x9F, 0x70, 0x01, 0x42];
assert_eq!(
parse_status_e3(&data).unwrap(),
Some(CardLifeCycle::Unknown(0x42))
);
}
#[test]
fn e3_absent_template_is_none() {
assert_eq!(parse_status_e3(&[0x4F, 0x00]).unwrap(), None);
}
#[test]
fn e3_without_9f70_is_none() {
let data = [0xE3, 0x02, 0x4F, 0x00];
assert_eq!(parse_status_e3(&data).unwrap(), None);
}
#[test]
fn e3_empty_9f70_is_none() {
let data = [0xE3, 0x03, 0x9F, 0x70, 0x00];
assert_eq!(parse_status_e3(&data).unwrap(), None);
}
#[test]
fn e3_empty_input_is_none() {
assert_eq!(parse_status_e3(&[]).unwrap(), None);
}
#[test]
fn e3_malformed_tlv_is_rejected() {
assert_eq!(
parse_status_e3(&[0xE3, 0x05, 0x9F]),
Err(TlvError::Truncated)
);
}
fn e3_app(aid: &[u8], lc: u8, privs: &[u8]) -> std::vec::Vec<u8> {
let mut inner = std::vec::Vec::new();
inner.push(0x4F);
inner.push(u8::try_from(aid.len()).unwrap());
inner.extend_from_slice(aid);
inner.extend_from_slice(&[0x9F, 0x70, 0x01, lc]);
inner.push(0xC5);
inner.push(u8::try_from(privs.len()).unwrap());
inner.extend_from_slice(privs);
let mut v = std::vec::Vec::new();
v.push(0xE3);
v.push(u8::try_from(inner.len()).unwrap());
v.extend_from_slice(&inner);
v
}
#[test]
fn registry_decodes_isd_with_full_3byte_privileges() {
let aid = [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00];
let e = e3_app(&aid, 0x01, &[0x9E, 0xFE, 0x80]);
let r = parse_status_registry(&e).unwrap();
assert_eq!(r.len(), 1);
assert_eq!(r[0].aid, &aid);
assert_eq!(r[0].life_cycle, 0x01);
assert_eq!(r[0].privileges, [0x9E, 0xFE, 0x80]);
assert!(r[0].modules.is_empty());
}
#[test]
fn registry_pads_one_byte_privileges_into_byte_zero() {
let e = e3_app(&[0xA0, 0x00, 0x00, 0x00, 0x18], 0x07, &[0x80]);
let r = parse_status_registry(&e).unwrap();
assert_eq!(r[0].privileges, [0x80, 0x00, 0x00]);
}
#[test]
fn registry_decodes_two_entries_in_one_page() {
let a = e3_app(&[0xA0, 0x00, 0x00, 0x00, 0x11], 0x07, &[0x00, 0x00, 0x00]);
let b = e3_app(&[0xA0, 0x00, 0x00, 0x00, 0x22], 0x0F, &[0x80, 0x00, 0x00]);
let mut page = a;
page.extend_from_slice(&b);
let r = parse_status_registry(&page).unwrap();
assert_eq!(r.len(), 2);
assert_eq!(r[0].life_cycle, 0x07);
assert_eq!(r[1].privileges, [0x80, 0x00, 0x00]);
}
#[test]
fn registry_decodes_elf_with_associated_sd_and_modules() {
let elf = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x01];
let sd = [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00];
let m1 = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x01, 0x01];
let m2 = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x01, 0x02];
let mut inner = std::vec::Vec::new();
inner.push(0x4F);
inner.push(u8::try_from(elf.len()).unwrap());
inner.extend_from_slice(&elf);
inner.extend_from_slice(&[0x9F, 0x70, 0x01, 0x01]);
inner.push(0xCC);
inner.push(u8::try_from(sd.len()).unwrap());
inner.extend_from_slice(&sd);
for m in [&m1[..], &m2[..]] {
inner.push(0x84);
inner.push(u8::try_from(m.len()).unwrap());
inner.extend_from_slice(m);
}
let mut e = std::vec::Vec::new();
e.push(0xE3);
e.push(u8::try_from(inner.len()).unwrap());
e.extend_from_slice(&inner);
let r = parse_status_registry(&e).unwrap();
assert_eq!(r[0].aid, &elf);
assert_eq!(r[0].associated_sd_aid, Some(&sd[..]));
assert_eq!(r[0].modules.len(), 2);
assert_eq!(r[0].modules[0], &m1);
assert_eq!(r[0].modules[1], &m2);
}
#[test]
fn registry_decodes_application_elf_aid_tag_c4() {
let inst = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C];
let elf = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x03];
let mut inner = std::vec::Vec::new();
inner.push(0x4F);
inner.push(u8::try_from(inst.len()).unwrap());
inner.extend_from_slice(&inst);
inner.extend_from_slice(&[0x9F, 0x70, 0x01, 0x07]);
inner.extend_from_slice(&[0xC5, 0x01, 0x00]);
inner.push(0xC4);
inner.push(u8::try_from(elf.len()).unwrap());
inner.extend_from_slice(&elf);
let mut e = std::vec::Vec::new();
e.push(0xE3);
e.push(u8::try_from(inner.len()).unwrap());
e.extend_from_slice(&inner);
let r = parse_status_registry(&e).unwrap();
assert_eq!(r[0].elf_aid, Some(&elf[..]));
assert_eq!(r[0].associated_sd_aid, None);
}
#[test]
fn registry_missing_4f_yields_empty_aid_not_error() {
let e = [0xE3, 0x04, 0x9F, 0x70, 0x01, 0x0F];
let r = parse_status_registry(&e).unwrap();
assert_eq!(r.len(), 1);
assert!(r[0].aid.is_empty());
assert_eq!(r[0].life_cycle, 0x0F);
}
#[test]
fn registry_empty_page_is_empty_not_error() {
assert!(parse_status_registry(&[]).unwrap().is_empty());
}
#[test]
fn registry_no_e3_is_empty_not_error() {
assert!(parse_status_registry(&[0x4F, 0x00]).unwrap().is_empty());
}
#[test]
fn registry_malformed_tlv_is_rejected() {
assert_eq!(
parse_status_registry(&[0xE3, 0x05, 0x4F]),
Err(TlvError::Truncated)
);
}
#[test]
fn registry_truncates_excess_modules_without_panic() {
use crate::limits::MAX_MODULES_PER_ELF;
let elf = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x09];
let mut inner = std::vec::Vec::new();
inner.push(0x4F);
inner.push(u8::try_from(elf.len()).unwrap());
inner.extend_from_slice(&elf);
inner.extend_from_slice(&[0x9F, 0x70, 0x01, 0x01]);
for i in 0..(MAX_MODULES_PER_ELF + 4) {
inner.extend_from_slice(&[
0x84,
0x05,
0xA0,
0x00,
0x00,
0x01,
u8::try_from(i).unwrap(),
]);
}
let mut e = std::vec::Vec::new();
e.push(0xE3);
if inner.len() < 0x80 {
e.push(u8::try_from(inner.len()).unwrap());
} else {
e.push(0x81);
e.push(u8::try_from(inner.len()).unwrap());
}
e.extend_from_slice(&inner);
let r = parse_status_registry(&e).unwrap();
assert_eq!(r[0].modules.len(), MAX_MODULES_PER_ELF);
}
}