1use crate::error::{Error, Result};
9use crate::tag::{self, ApduTag};
10use crate::traits::ApduDef;
11use broadcast_common::{Parse, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16#[non_exhaustive]
17pub enum ApplicationType {
18 ConditionalAccess,
20 ElectronicProgrammeGuide,
22 Reserved(u8),
24}
25
26impl ApplicationType {
27 #[must_use]
29 pub fn from_u8(v: u8) -> Self {
30 match v {
31 0x01 => Self::ConditionalAccess,
32 0x02 => Self::ElectronicProgrammeGuide,
33 other => Self::Reserved(other),
34 }
35 }
36 #[must_use]
38 pub const fn to_u8(self) -> u8 {
39 match self {
40 Self::ConditionalAccess => 0x01,
41 Self::ElectronicProgrammeGuide => 0x02,
42 Self::Reserved(v) => v,
43 }
44 }
45 #[must_use]
47 pub fn name(&self) -> &'static str {
48 match self {
49 Self::ConditionalAccess => "Conditional_Access",
50 Self::ElectronicProgrammeGuide => "Electronic_Programme_Guide",
51 Self::Reserved(_) => "reserved",
52 }
53 }
54}
55broadcast_common::impl_spec_display!(ApplicationType, Reserved);
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize))]
60pub struct ApplicationInfoEnq;
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize))]
65pub struct EnterMenu;
66
67#[derive(Debug, Clone, PartialEq, Eq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize))]
72pub struct ApplicationInfo<'a> {
73 pub application_type: ApplicationType,
75 pub application_manufacturer: u16,
77 pub manufacturer_code: u16,
79 #[cfg_attr(feature = "serde", serde(borrow, with = "super::bytes_serde"))]
82 pub menu_string: &'a [u8],
83}
84
85impl<'a> Parse<'a> for ApplicationInfoEnq {
88 type Error = Error;
89 fn parse(bytes: &'a [u8]) -> Result<Self> {
90 super::parse_empty_apdu(bytes, tag::APPLICATION_INFO_ENQ, "application_info_enq")?;
91 Ok(Self)
92 }
93}
94impl Serialize for ApplicationInfoEnq {
95 type Error = Error;
96 fn serialized_len(&self) -> usize {
97 super::empty_apdu_len()
98 }
99 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
100 super::serialize_empty_apdu(tag::APPLICATION_INFO_ENQ, buf)
101 }
102}
103impl ApduDef<'_> for ApplicationInfoEnq {
104 const TAG: ApduTag = tag::APPLICATION_INFO_ENQ;
105 const NAME: &'static str = "APPLICATION_INFO_ENQ";
106}
107
108impl<'a> Parse<'a> for EnterMenu {
111 type Error = Error;
112 fn parse(bytes: &'a [u8]) -> Result<Self> {
113 super::parse_empty_apdu(bytes, tag::ENTER_MENU, "enter_menu")?;
114 Ok(Self)
115 }
116}
117impl Serialize for EnterMenu {
118 type Error = Error;
119 fn serialized_len(&self) -> usize {
120 super::empty_apdu_len()
121 }
122 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
123 super::serialize_empty_apdu(tag::ENTER_MENU, buf)
124 }
125}
126impl ApduDef<'_> for EnterMenu {
127 const TAG: ApduTag = tag::ENTER_MENU;
128 const NAME: &'static str = "ENTER_MENU";
129}
130
131const APP_INFO_PREFIX: usize = 6;
136
137impl<'a> Parse<'a> for ApplicationInfo<'a> {
138 type Error = Error;
139 fn parse(bytes: &'a [u8]) -> Result<Self> {
140 let body = super::parse_apdu_header(bytes, tag::APPLICATION_INFO, "application_info")?;
141 if body.len() < APP_INFO_PREFIX {
142 return Err(Error::BufferTooShort {
143 need: APP_INFO_PREFIX,
144 have: body.len(),
145 what: "application_info",
146 });
147 }
148 let application_type = ApplicationType::from_u8(body[0]);
149 let application_manufacturer = u16::from_be_bytes([body[1], body[2]]);
150 let manufacturer_code = u16::from_be_bytes([body[3], body[4]]);
151 let menu_string_length = body[5] as usize;
152 let menu_end = APP_INFO_PREFIX + menu_string_length;
153 if body.len() < menu_end {
154 return Err(Error::LengthMismatch {
155 what: "application_info menu_string",
156 declared: menu_string_length,
157 actual: body.len() - APP_INFO_PREFIX,
158 });
159 }
160 Ok(Self {
161 application_type,
162 application_manufacturer,
163 manufacturer_code,
164 menu_string: &body[APP_INFO_PREFIX..menu_end],
165 })
166 }
167}
168
169impl Serialize for ApplicationInfo<'_> {
170 type Error = Error;
171 fn serialized_len(&self) -> usize {
172 super::apdu_len(APP_INFO_PREFIX + self.menu_string.len())
173 }
174 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
175 if self.menu_string.len() > u8::MAX as usize {
176 return Err(Error::InvalidObject {
177 what: "application_info",
178 reason: "menu_string longer than 255 bytes",
179 });
180 }
181 let body_len = APP_INFO_PREFIX + self.menu_string.len();
182 let mut pos = super::write_apdu_header(tag::APPLICATION_INFO, body_len, buf)?;
183 buf[pos] = self.application_type.to_u8();
184 buf[pos + 1..pos + 3].copy_from_slice(&self.application_manufacturer.to_be_bytes());
185 buf[pos + 3..pos + 5].copy_from_slice(&self.manufacturer_code.to_be_bytes());
186 buf[pos + 5] = self.menu_string.len() as u8;
187 pos += APP_INFO_PREFIX;
188 buf[pos..pos + self.menu_string.len()].copy_from_slice(self.menu_string);
189 Ok(pos + self.menu_string.len())
190 }
191}
192
193impl<'a> ApduDef<'a> for ApplicationInfo<'a> {
194 const TAG: ApduTag = tag::APPLICATION_INFO;
195 const NAME: &'static str = "APPLICATION_INFO";
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn enq_round_trip() {
204 let bytes = ApplicationInfoEnq.to_bytes();
205 assert_eq!(bytes, [0x9F, 0x80, 0x20, 0x00]);
206 assert_eq!(
207 ApplicationInfoEnq::parse(&bytes).unwrap(),
208 ApplicationInfoEnq
209 );
210 }
211
212 #[test]
213 fn enter_menu_round_trip() {
214 let bytes = EnterMenu.to_bytes();
215 assert_eq!(bytes, [0x9F, 0x80, 0x22, 0x00]);
216 assert_eq!(EnterMenu::parse(&bytes).unwrap(), EnterMenu);
217 }
218
219 #[test]
220 fn application_info_round_trip() {
221 let info = ApplicationInfo {
222 application_type: ApplicationType::ConditionalAccess,
223 application_manufacturer: 0x1234,
224 manufacturer_code: 0x5678,
225 menu_string: b"CA Module",
226 };
227 let bytes = info.to_bytes();
228 assert_eq!(bytes.len(), 19);
230 assert_eq!(&bytes[..4], &[0x9F, 0x80, 0x21, 0x0F]); let parsed = ApplicationInfo::parse(&bytes).unwrap();
232 assert_eq!(parsed, info);
233 assert_eq!(parsed.application_type.name(), "Conditional_Access");
234 }
235
236 #[test]
237 fn mutating_field_changes_bytes() {
238 let info = ApplicationInfo {
239 application_type: ApplicationType::ConditionalAccess,
240 application_manufacturer: 0x1234,
241 manufacturer_code: 0x5678,
242 menu_string: b"abc",
243 };
244 let a = info.to_bytes();
245 let mut other = info.clone();
246 other.application_manufacturer = 0x9999;
247 assert_ne!(a, other.to_bytes());
248 }
249
250 #[test]
251 fn empty_menu_string() {
252 let info = ApplicationInfo {
253 application_type: ApplicationType::ElectronicProgrammeGuide,
254 application_manufacturer: 0,
255 manufacturer_code: 0,
256 menu_string: b"",
257 };
258 let bytes = info.to_bytes();
259 assert_eq!(ApplicationInfo::parse(&bytes).unwrap(), info);
260 }
261}