Skip to main content

dvb_ci/
tag.rs

1//! `apdu_tag` โ€” the 3-byte ASN.1 application-object tag โ€” ETSI EN 50221 ยง8.8.2,
2//! Table 58 + Figure 16 (PDF pp. 56-57).
3//!
4//! Every public `apdu_tag` is coded on three bytes beginning `0x9F`: the second
5//! byte has its MSB set, the third byte has its MSB clear. We carry the tag as a
6//! 24-bit value in the low three bytes of a `u32` and provide named constants
7//! for every Table 58 entry (the named-constant policy: no magic tag bytes).
8
9/// The fixed first byte of every public `apdu_tag` (Figure 16).
10pub const APDU_TAG_PREFIX: u8 = 0x9F;
11
12/// A 3-byte `apdu_tag`, held as a 24-bit value in the low three bytes of a `u32`.
13#[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    /// Build a tag from its three wire bytes (b1 must be `0x9F` for a valid
20    /// public tag, but any value is accepted so private tags round-trip).
21    #[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    /// Build a tag from a raw 24-bit value (the low three bytes of `v`).
27    #[must_use]
28    pub const fn from_u24(v: u32) -> Self {
29        Self(v & 0x00FF_FFFF)
30    }
31
32    /// The tag as a 24-bit integer (low three bytes of the returned `u32`).
33    #[must_use]
34    pub const fn as_u24(self) -> u32 {
35        self.0
36    }
37
38    /// The three wire bytes, MSB first.
39    #[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    /// Diagnostic name from Table 58, or `"unknown"` for an unallocated tag.
45    #[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
110// --- Resource Manager (Table 58) ---
111/// `Tprofile_enq` = `9F 80 10`.
112pub const PROFILE_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x10);
113/// `Tprofile` (profile reply) = `9F 80 11`.
114pub const PROFILE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x11);
115/// `Tprofile_change` = `9F 80 12`.
116pub const PROFILE_CHANGE: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x12);
117
118// --- Application Information ---
119/// `Tapplication_info_enq` = `9F 80 20`.
120pub const APPLICATION_INFO_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x20);
121/// `Tapplication_info` = `9F 80 21`.
122pub const APPLICATION_INFO: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x21);
123/// `Tenter_menu` = `9F 80 22`.
124pub const ENTER_MENU: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x22);
125
126// --- CA Support ---
127/// `Tca_info_enq` = `9F 80 30`.
128pub const CA_INFO_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x30);
129/// `Tca_info` = `9F 80 31`.
130pub const CA_INFO: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x31);
131/// `Tca_pmt` = `9F 80 32`.
132pub const CA_PMT: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x32);
133/// `Tca_pmt_reply` = `9F 80 33`.
134pub const CA_PMT_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x33);
135
136// --- Date-Time ---
137/// `Tdate_time_enq` = `9F 84 40`.
138pub const DATE_TIME_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x40);
139/// `Tdate_time` = `9F 84 41`.
140pub const DATE_TIME: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x41);
141
142// --- Host Control (Table 58) ---
143/// `Ttune` = `9F 84 00`.
144pub const TUNE: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x00);
145/// `Treplace` = `9F 84 01`.
146pub const REPLACE: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x01);
147/// `Tclear_replace` = `9F 84 02`.
148pub const CLEAR_REPLACE: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x02);
149/// `Task_release` = `9F 84 03`.
150pub const ASK_RELEASE: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x03);
151
152// --- MMI ---
153/// `Tclose_mmi` = `9F 88 00`.
154pub const CLOSE_MMI: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x00);
155/// `Tdisplay_control` = `9F 88 01`.
156pub const DISPLAY_CONTROL: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x01);
157/// `Tdisplay_reply` = `9F 88 02`.
158pub const DISPLAY_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x02);
159/// `Ttext-last` = `9F 88 03`.
160pub const TEXT_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x03);
161/// `Ttext-more` = `9F 88 04`.
162pub const TEXT_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x04);
163/// `Tkeypad_control` = `9F 88 05`.
164pub const KEYPAD_CONTROL: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x05);
165/// `Tkeypress` = `9F 88 06`.
166pub const KEYPRESS: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x06);
167/// `Tenq` = `9F 88 07`.
168pub const ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x07);
169/// `Tansw` = `9F 88 08`.
170pub const ANSW: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x08);
171/// `Tmenu_last` = `9F 88 09`.
172pub const MENU_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x09);
173/// `Tmenu_more` = `9F 88 0A`.
174pub const MENU_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0A);
175/// `Tmenu_answ` = `9F 88 0B`.
176pub const MENU_ANSW: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0B);
177/// `Tlist_last` = `9F 88 0C`.
178pub const LIST_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0C);
179/// `Tlist_more` = `9F 88 0D`.
180pub const LIST_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0D);
181/// `Tsubtitle_segment_last` = `9F 88 0E`.
182pub const SUBTITLE_SEGMENT_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0E);
183/// `Tsubtitle_segment_more` = `9F 88 0F`.
184pub const SUBTITLE_SEGMENT_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x0F);
185/// `Tdisplay_message` = `9F 88 10`.
186pub const DISPLAY_MESSAGE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x10);
187/// `Tscene_end_mark` = `9F 88 11`.
188pub const SCENE_END_MARK: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x11);
189/// `Tscene_done` = `9F 88 12`.
190pub const SCENE_DONE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x12);
191/// `Tscene_control` = `9F 88 13`.
192pub const SCENE_CONTROL: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x13);
193/// `Tsubtitle_download_last` = `9F 88 14`.
194pub const SUBTITLE_DOWNLOAD_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x14);
195/// `Tsubtitle_download_more` = `9F 88 15`.
196pub const SUBTITLE_DOWNLOAD_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x15);
197/// `Tflush_download` = `9F 88 16`.
198pub const FLUSH_DOWNLOAD: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x16);
199/// `Tdownload_reply` = `9F 88 17`.
200pub const DOWNLOAD_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x88, 0x17);
201
202// --- Low-Speed Communications (Table 58) ---
203/// `Tcomms_cmd` = `9F 8C 00`.
204pub const COMMS_CMD: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x00);
205/// `Tconnection_descriptor` = `9F 8C 01`.
206pub const CONNECTION_DESCRIPTOR: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x01);
207/// `Tcomms_reply` = `9F 8C 02`.
208pub const COMMS_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x02);
209/// `Tcomms_send_last` = `9F 8C 03`.
210pub const COMMS_SEND_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x03);
211/// `Tcomms_send_more` = `9F 8C 04`.
212pub const COMMS_SEND_MORE: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x04);
213/// `Tcomms_rcv_last` = `9F 8C 05`.
214pub const COMMS_RCV_LAST: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x05);
215/// `Tcomms_rcv_more` = `9F 8C 06`.
216pub 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}