1use crate::error::{Error, Result};
15use crate::objects;
16use crate::tag::ApduTag;
17use broadcast_common::{Parse, Serialize};
18
19pub mod tag {
21 use crate::tag::ApduTag;
22 pub const APPLICATION_INFO_ENQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x20);
24 pub const APPLICATION_INFO: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x21);
26 pub const ENTER_MENU: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x22);
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize))]
33#[non_exhaustive]
34pub enum ApplicationTypeV2 {
35 ConditionalAccess,
37 ElectronicProgrammeGuide,
39 SoftwareUpgrade,
41 NetworkInterface,
43 AccessibilityAids,
45 Unclassified,
47 Reserved(u8),
50}
51
52impl ApplicationTypeV2 {
53 #[must_use]
55 pub fn from_u8(v: u8) -> Self {
56 match v {
57 0x01 => Self::ConditionalAccess,
58 0x02 => Self::ElectronicProgrammeGuide,
59 0x03 => Self::SoftwareUpgrade,
60 0x04 => Self::NetworkInterface,
61 0x05 => Self::AccessibilityAids,
62 0x06 => Self::Unclassified,
63 other => Self::Reserved(other),
64 }
65 }
66 #[must_use]
68 pub const fn to_u8(self) -> u8 {
69 match self {
70 Self::ConditionalAccess => 0x01,
71 Self::ElectronicProgrammeGuide => 0x02,
72 Self::SoftwareUpgrade => 0x03,
73 Self::NetworkInterface => 0x04,
74 Self::AccessibilityAids => 0x05,
75 Self::Unclassified => 0x06,
76 Self::Reserved(v) => v,
77 }
78 }
79 #[must_use]
81 pub fn name(&self) -> &'static str {
82 match self {
83 Self::ConditionalAccess => "Conditional_Access",
84 Self::ElectronicProgrammeGuide => "Electronic_Programme_Guide",
85 Self::SoftwareUpgrade => "Software_upgrade",
86 Self::NetworkInterface => "Network_interface",
87 Self::AccessibilityAids => "Accessibility_aids",
88 Self::Unclassified => "Unclassified",
89 Self::Reserved(_) => "reserved",
90 }
91 }
92 #[must_use]
95 pub fn effective(self) -> Self {
96 match self {
97 Self::Reserved(_) => Self::Unclassified,
98 known => known,
99 }
100 }
101}
102broadcast_common::impl_spec_display!(ApplicationTypeV2, Reserved);
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize))]
107pub struct ApplicationInfoEnq;
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
111#[cfg_attr(feature = "serde", derive(serde::Serialize))]
112pub struct EnterMenu;
113
114#[derive(Debug, Clone, PartialEq, Eq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize))]
119pub struct ApplicationInfo<'a> {
120 pub application_type: ApplicationTypeV2,
122 pub application_manufacturer: u16,
124 pub manufacturer_code: u16,
126 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
128 pub menu_string: &'a [u8],
129}
130
131impl<'a> Parse<'a> for ApplicationInfoEnq {
134 type Error = Error;
135 fn parse(bytes: &'a [u8]) -> Result<Self> {
136 objects::parse_empty_apdu(bytes, tag::APPLICATION_INFO_ENQ, "application_info_enq")?;
137 Ok(Self)
138 }
139}
140impl Serialize for ApplicationInfoEnq {
141 type Error = Error;
142 fn serialized_len(&self) -> usize {
143 objects::empty_apdu_len()
144 }
145 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
146 objects::serialize_empty_apdu(tag::APPLICATION_INFO_ENQ, buf)
147 }
148}
149
150impl<'a> Parse<'a> for EnterMenu {
151 type Error = Error;
152 fn parse(bytes: &'a [u8]) -> Result<Self> {
153 objects::parse_empty_apdu(bytes, tag::ENTER_MENU, "enter_menu")?;
154 Ok(Self)
155 }
156}
157impl Serialize for EnterMenu {
158 type Error = Error;
159 fn serialized_len(&self) -> usize {
160 objects::empty_apdu_len()
161 }
162 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
163 objects::serialize_empty_apdu(tag::ENTER_MENU, buf)
164 }
165}
166
167const APP_INFO_PREFIX: usize = 6;
171
172impl<'a> Parse<'a> for ApplicationInfo<'a> {
173 type Error = Error;
174 fn parse(bytes: &'a [u8]) -> Result<Self> {
175 let body = objects::parse_apdu_header(bytes, tag::APPLICATION_INFO, "application_info")?;
176 if body.len() < APP_INFO_PREFIX {
177 return Err(Error::BufferTooShort {
178 need: APP_INFO_PREFIX,
179 have: body.len(),
180 what: "application_info",
181 });
182 }
183 let menu_len = body[5] as usize;
184 let menu_end = APP_INFO_PREFIX + menu_len;
185 if body.len() < menu_end {
186 return Err(Error::LengthMismatch {
187 what: "application_info menu_string",
188 declared: menu_len,
189 actual: body.len() - APP_INFO_PREFIX,
190 });
191 }
192 Ok(Self {
193 application_type: ApplicationTypeV2::from_u8(body[0]),
194 application_manufacturer: u16::from_be_bytes([body[1], body[2]]),
195 manufacturer_code: u16::from_be_bytes([body[3], body[4]]),
196 menu_string: &body[APP_INFO_PREFIX..menu_end],
197 })
198 }
199}
200impl Serialize for ApplicationInfo<'_> {
201 type Error = Error;
202 fn serialized_len(&self) -> usize {
203 objects::apdu_len(APP_INFO_PREFIX + self.menu_string.len())
204 }
205 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
206 if self.menu_string.len() > u8::MAX as usize {
207 return Err(Error::InvalidObject {
208 what: "application_info",
209 reason: "menu_string longer than 255 bytes",
210 });
211 }
212 let body_len = APP_INFO_PREFIX + self.menu_string.len();
213 let mut pos = objects::write_apdu_header(tag::APPLICATION_INFO, body_len, buf)?;
214 buf[pos] = self.application_type.to_u8();
215 buf[pos + 1..pos + 3].copy_from_slice(&self.application_manufacturer.to_be_bytes());
216 buf[pos + 3..pos + 5].copy_from_slice(&self.manufacturer_code.to_be_bytes());
217 buf[pos + 5] = self.menu_string.len() as u8;
218 pos += APP_INFO_PREFIX;
219 buf[pos..pos + self.menu_string.len()].copy_from_slice(self.menu_string);
220 Ok(pos + self.menu_string.len())
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
226#[cfg_attr(feature = "serde", derive(serde::Serialize))]
227#[non_exhaustive]
228pub enum ApplicationInfoV2Apdu<'a> {
229 ApplicationInfoEnq(ApplicationInfoEnq),
231 ApplicationInfo(ApplicationInfo<'a>),
233 EnterMenu(EnterMenu),
235}
236
237impl<'a> ApplicationInfoV2Apdu<'a> {
238 pub fn parse(body: &'a [u8]) -> Result<Self> {
240 if body.len() < 3 {
241 return Err(Error::BufferTooShort {
242 need: 3,
243 have: body.len(),
244 what: "application_info_v2 apdu_tag",
245 });
246 }
247 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
248 match t {
249 tag::APPLICATION_INFO_ENQ => {
250 Ok(Self::ApplicationInfoEnq(ApplicationInfoEnq::parse(body)?))
251 }
252 tag::APPLICATION_INFO => Ok(Self::ApplicationInfo(ApplicationInfo::parse(body)?)),
253 tag::ENTER_MENU => Ok(Self::EnterMenu(EnterMenu::parse(body)?)),
254 _ => Err(Error::UnexpectedApduTag {
255 got: t.as_u24(),
256 expected: tag::APPLICATION_INFO.as_u24(),
257 what: "application_info_v2",
258 }),
259 }
260 }
261}
262
263impl Serialize for ApplicationInfoV2Apdu<'_> {
264 type Error = Error;
265 fn serialized_len(&self) -> usize {
266 match self {
267 Self::ApplicationInfoEnq(o) => o.serialized_len(),
268 Self::ApplicationInfo(o) => o.serialized_len(),
269 Self::EnterMenu(o) => o.serialized_len(),
270 }
271 }
272 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
273 match self {
274 Self::ApplicationInfoEnq(o) => o.serialize_into(buf),
275 Self::ApplicationInfo(o) => o.serialize_into(buf),
276 Self::EnterMenu(o) => o.serialize_into(buf),
277 }
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn enq_and_enter_menu_round_trip() {
287 assert_eq!(ApplicationInfoEnq.to_bytes(), [0x9F, 0x80, 0x20, 0x00]);
288 assert_eq!(EnterMenu.to_bytes(), [0x9F, 0x80, 0x22, 0x00]);
289 assert_eq!(
290 ApplicationInfoEnq::parse(&[0x9F, 0x80, 0x20, 0x00]).unwrap(),
291 ApplicationInfoEnq
292 );
293 }
294
295 #[test]
296 fn application_info_round_trips_and_bites() {
297 let info = ApplicationInfo {
298 application_type: ApplicationTypeV2::AccessibilityAids,
299 application_manufacturer: 0x1234,
300 manufacturer_code: 0x5678,
301 menu_string: b"Audio",
302 };
303 let bytes = info.to_bytes();
304 assert_eq!(
306 bytes,
307 [
308 0x9F, 0x80, 0x21, 0x0B, 0x05, 0x12, 0x34, 0x56, 0x78, 0x05, b'A', b'u', b'd', b'i',
309 b'o'
310 ]
311 );
312 assert_eq!(ApplicationInfo::parse(&bytes).unwrap(), info);
313 assert_eq!(info.application_type.name(), "Accessibility_aids");
314 let mut other = info.clone();
315 other.application_type = ApplicationTypeV2::Unclassified;
316 assert_ne!(bytes, other.to_bytes());
317 }
318
319 #[test]
320 fn unrecognized_type_treated_as_unclassified() {
321 let t = ApplicationTypeV2::from_u8(0x7F);
322 assert_eq!(t, ApplicationTypeV2::Reserved(0x7F));
323 assert_eq!(t.effective(), ApplicationTypeV2::Unclassified);
324 assert_eq!(t.to_u8(), 0x7F);
326 }
327
328 #[test]
329 fn dispatch_routes_each_tag() {
330 let info = ApplicationInfo {
331 application_type: ApplicationTypeV2::SoftwareUpgrade,
332 application_manufacturer: 0,
333 manufacturer_code: 0,
334 menu_string: b"",
335 }
336 .to_bytes();
337 assert!(matches!(
338 ApplicationInfoV2Apdu::parse(&info).unwrap(),
339 ApplicationInfoV2Apdu::ApplicationInfo(_)
340 ));
341 }
342}