pub const APDU_TAG_PREFIX: u8 = 0x9F;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct ApduTag(u32);
impl ApduTag {
#[must_use]
pub const fn from_bytes(b1: u8, b2: u8, b3: u8) -> Self {
Self(((b1 as u32) << 16) | ((b2 as u32) << 8) | b3 as u32)
}
#[must_use]
pub const fn from_u24(v: u32) -> Self {
Self(v & 0x00FF_FFFF)
}
#[must_use]
pub const fn as_u24(self) -> u32 {
self.0
}
#[must_use]
pub const fn to_bytes(self) -> [u8; 3] {
[(self.0 >> 16) as u8, (self.0 >> 8) as u8, self.0 as u8]
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
PROFILE_ENQ => "profile_enq",
PROFILE => "profile",
PROFILE_CHANGE => "profile_change",
APPLICATION_INFO_ENQ => "application_info_enq",
APPLICATION_INFO => "application_info",
ENTER_MENU => "enter_menu",
CA_INFO_ENQ => "ca_info_enq",
CA_INFO => "ca_info",
CA_PMT => "ca_pmt",
CA_PMT_REPLY => "ca_pmt_reply",
DATE_TIME_ENQ => "date_time_enq",
DATE_TIME => "date_time",
CLOSE_MMI => "close_mmi",
_ => "unknown",
}
}
}
impl core::fmt::Display for ApduTag {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let [a, b, c] = self.to_bytes();
match self.name() {
"unknown" => write!(f, "apdu_tag(0x{a:02X}{b:02X}{c:02X})"),
n => write!(f, "{n}(0x{a:02X}{b:02X}{c:02X})"),
}
}
}
pub const PROFILE_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x10);
pub const PROFILE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x11);
pub const PROFILE_CHANGE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x12);
pub const APPLICATION_INFO_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x20);
pub const APPLICATION_INFO: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x21);
pub const ENTER_MENU: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x22);
pub const CA_INFO_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x30);
pub const CA_INFO: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x31);
pub const CA_PMT: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x32);
pub const CA_PMT_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x33);
pub const DATE_TIME_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x40);
pub const DATE_TIME: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x41);
pub const CLOSE_MMI: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x00);
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn byte_round_trip() {
let t = ApduTag::from_bytes(0x9F, 0x80, 0x32);
assert_eq!(t.to_bytes(), [0x9F, 0x80, 0x32]);
assert_eq!(t.as_u24(), 0x009F_8032);
assert_eq!(ApduTag::from_u24(0x9F_8032), t);
assert_eq!(t, CA_PMT);
}
#[test]
fn names_match_table_58() {
assert_eq!(CA_PMT.name(), "ca_pmt");
assert_eq!(CA_PMT_REPLY.name(), "ca_pmt_reply");
assert_eq!(PROFILE_ENQ.name(), "profile_enq");
assert_eq!(DATE_TIME.name(), "date_time");
assert_eq!(ApduTag::from_bytes(0x9F, 0x99, 0x99).name(), "unknown");
}
#[test]
fn display_is_lossless() {
assert_eq!(format!("{CA_PMT}"), "ca_pmt(0x9F8032)");
assert_eq!(
format!("{}", ApduTag::from_bytes(0x9F, 0x99, 0x99)),
"apdu_tag(0x9F9999)"
);
}
}