1pub const APDU_TAG_PREFIX: u8 = 0x9F;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize))]
15#[cfg_attr(feature = "serde", serde(transparent))]
16pub struct ApduTag(u32);
17
18impl ApduTag {
19 #[must_use]
22 pub const fn from_bytes(b1: u8, b2: u8, b3: u8) -> Self {
23 Self(((b1 as u32) << 16) | ((b2 as u32) << 8) | b3 as u32)
24 }
25
26 #[must_use]
28 pub const fn from_u24(v: u32) -> Self {
29 Self(v & 0x00FF_FFFF)
30 }
31
32 #[must_use]
34 pub const fn as_u24(self) -> u32 {
35 self.0
36 }
37
38 #[must_use]
40 pub const fn to_bytes(self) -> [u8; 3] {
41 [(self.0 >> 16) as u8, (self.0 >> 8) as u8, self.0 as u8]
42 }
43
44 #[must_use]
46 pub fn name(self) -> &'static str {
47 match self {
48 PROFILE_ENQ => "profile_enq",
49 PROFILE => "profile",
50 PROFILE_CHANGE => "profile_change",
51 APPLICATION_INFO_ENQ => "application_info_enq",
52 APPLICATION_INFO => "application_info",
53 ENTER_MENU => "enter_menu",
54 CA_INFO_ENQ => "ca_info_enq",
55 CA_INFO => "ca_info",
56 CA_PMT => "ca_pmt",
57 CA_PMT_REPLY => "ca_pmt_reply",
58 DATE_TIME_ENQ => "date_time_enq",
59 DATE_TIME => "date_time",
60 CLOSE_MMI => "close_mmi",
61 TUNE => "tune",
62 REPLACE => "replace",
63 CLEAR_REPLACE => "clear_replace",
64 ASK_RELEASE => "ask_release",
65 DISPLAY_CONTROL => "display_control",
66 DISPLAY_REPLY => "display_reply",
67 TEXT_LAST => "text_last",
68 TEXT_MORE => "text_more",
69 KEYPAD_CONTROL => "keypad_control",
70 KEYPRESS => "keypress",
71 ENQ => "enq",
72 ANSW => "answ",
73 MENU_LAST => "menu_last",
74 MENU_MORE => "menu_more",
75 MENU_ANSW => "menu_answ",
76 LIST_LAST => "list_last",
77 LIST_MORE => "list_more",
78 SUBTITLE_SEGMENT_LAST => "subtitle_segment_last",
79 SUBTITLE_SEGMENT_MORE => "subtitle_segment_more",
80 DISPLAY_MESSAGE => "display_message",
81 SCENE_END_MARK => "scene_end_mark",
82 SCENE_DONE => "scene_done",
83 SCENE_CONTROL => "scene_control",
84 SUBTITLE_DOWNLOAD_LAST => "subtitle_download_last",
85 SUBTITLE_DOWNLOAD_MORE => "subtitle_download_more",
86 FLUSH_DOWNLOAD => "flush_download",
87 DOWNLOAD_REPLY => "download_reply",
88 COMMS_CMD => "comms_cmd",
89 CONNECTION_DESCRIPTOR => "connection_descriptor",
90 COMMS_REPLY => "comms_reply",
91 COMMS_SEND_LAST => "comms_send_last",
92 COMMS_SEND_MORE => "comms_send_more",
93 COMMS_RCV_LAST => "comms_rcv_last",
94 COMMS_RCV_MORE => "comms_rcv_more",
95 _ => "unknown",
96 }
97 }
98}
99
100impl core::fmt::Display for ApduTag {
101 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102 let [a, b, c] = self.to_bytes();
103 match self.name() {
104 "unknown" => write!(f, "apdu_tag(0x{a:02X}{b:02X}{c:02X})"),
105 n => write!(f, "{n}(0x{a:02X}{b:02X}{c:02X})"),
106 }
107 }
108}
109
110pub const PROFILE_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x10);
113pub const PROFILE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x11);
115pub const PROFILE_CHANGE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x12);
117
118pub const APPLICATION_INFO_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x20);
121pub const APPLICATION_INFO: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x21);
123pub const ENTER_MENU: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x22);
125
126pub const CA_INFO_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x30);
129pub const CA_INFO: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x31);
131pub const CA_PMT: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x32);
133pub const CA_PMT_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x33);
135
136pub const DATE_TIME_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x40);
139pub const DATE_TIME: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x41);
141
142pub const TUNE: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x00);
145pub const REPLACE: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x01);
147pub const CLEAR_REPLACE: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x02);
149pub const ASK_RELEASE: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x03);
151
152pub const CLOSE_MMI: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x00);
155pub const DISPLAY_CONTROL: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x01);
157pub const DISPLAY_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x02);
159pub const TEXT_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x03);
161pub const TEXT_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x04);
163pub const KEYPAD_CONTROL: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x05);
165pub const KEYPRESS: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x06);
167pub const ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x07);
169pub const ANSW: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x08);
171pub const MENU_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x09);
173pub const MENU_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0A);
175pub const MENU_ANSW: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0B);
177pub const LIST_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0C);
179pub const LIST_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0D);
181pub const SUBTITLE_SEGMENT_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0E);
183pub const SUBTITLE_SEGMENT_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0F);
185pub const DISPLAY_MESSAGE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x10);
187pub const SCENE_END_MARK: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x11);
189pub const SCENE_DONE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x12);
191pub const SCENE_CONTROL: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x13);
193pub const SUBTITLE_DOWNLOAD_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x14);
195pub const SUBTITLE_DOWNLOAD_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x15);
197pub const FLUSH_DOWNLOAD: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x16);
199pub const DOWNLOAD_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x17);
201
202pub const COMMS_CMD: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x00);
205pub const CONNECTION_DESCRIPTOR: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x01);
207pub const COMMS_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x02);
209pub const COMMS_SEND_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x03);
211pub const COMMS_SEND_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x04);
213pub const COMMS_RCV_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x05);
215pub const COMMS_RCV_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x06);
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use alloc::format;
222
223 #[test]
224 fn byte_round_trip() {
225 let t = ApduTag::from_bytes(0x9F, 0x80, 0x32);
226 assert_eq!(t.to_bytes(), [0x9F, 0x80, 0x32]);
227 assert_eq!(t.as_u24(), 0x009F_8032);
228 assert_eq!(ApduTag::from_u24(0x9F_8032), t);
229 assert_eq!(t, CA_PMT);
230 }
231
232 #[test]
233 fn names_match_table_58() {
234 assert_eq!(CA_PMT.name(), "ca_pmt");
235 assert_eq!(CA_PMT_REPLY.name(), "ca_pmt_reply");
236 assert_eq!(PROFILE_ENQ.name(), "profile_enq");
237 assert_eq!(DATE_TIME.name(), "date_time");
238 assert_eq!(ApduTag::from_bytes(0x9F, 0x99, 0x99).name(), "unknown");
239 }
240
241 #[test]
242 fn display_is_lossless() {
243 assert_eq!(format!("{CA_PMT}"), "ca_pmt(0x9F8032)");
244 assert_eq!(
245 format!("{}", ApduTag::from_bytes(0x9F, 0x99, 0x99)),
246 "apdu_tag(0x9F9999)"
247 );
248 }
249}