1use crate::error::{Error, Result};
20use crate::objects;
21use alloc::vec::Vec;
22use broadcast_common::{Parse, Serialize};
23
24pub mod tag {
26 use crate::tag::ApduTag;
27 pub const DELIVERY_SYSTEM_INFO_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
29 pub const DELIVERY_SYSTEM_INFO_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
31 pub const SCAN_START_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
33 pub const SCAN_NEXT_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
35 pub const SCAN_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x04);
37 pub const TUNE_TS_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x05);
39 pub const TUNE_TS_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x06);
41}
42
43pub const TUNING_INFO_MESSAGE_LEN: usize = 11;
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize))]
49#[non_exhaustive]
50pub enum SystemIdentifier {
51 Abstract,
53 DvbC,
55 DvbS,
57 DvbT,
59 Reserved(u8),
61}
62
63impl SystemIdentifier {
64 #[must_use]
66 pub fn from_u8(v: u8) -> Self {
67 match v {
68 0 => Self::Abstract,
69 1 => Self::DvbC,
70 2 => Self::DvbS,
71 3 => Self::DvbT,
72 other => Self::Reserved(other),
73 }
74 }
75 #[must_use]
77 pub const fn to_u8(self) -> u8 {
78 match self {
79 Self::Abstract => 0,
80 Self::DvbC => 1,
81 Self::DvbS => 2,
82 Self::DvbT => 3,
83 Self::Reserved(v) => v,
84 }
85 }
86 #[must_use]
88 pub fn name(&self) -> &'static str {
89 match self {
90 Self::Abstract => "Abstract",
91 Self::DvbC => "DVB-C",
92 Self::DvbS => "DVB-S",
93 Self::DvbT => "DVB-T",
94 Self::Reserved(_) => "reserved",
95 }
96 }
97}
98broadcast_common::impl_spec_display!(SystemIdentifier, Reserved);
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103pub struct DeliverySystemInfoReq;
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize))]
108pub struct ScanStartReq;
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize))]
113pub struct ScanNextReq;
114
115#[derive(Debug, Clone, PartialEq, Eq, Default)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize))]
119pub struct DeliverySystemInfoAck {
120 pub systems: Vec<SystemIdentifier>,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize))]
127pub struct ScanAck<'a> {
128 pub ts_state: u8,
131 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
134 pub tuning_information_message: &'a [u8],
135 pub scan_progress: u8,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Default)]
142#[cfg_attr(feature = "serde", derive(serde::Serialize))]
143pub struct TuneTSReq<'a> {
144 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
146 pub tuning_information_message: &'a [u8],
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize))]
152pub struct TuneTSAck {
153 pub ts_state: u8,
156}
157
158crate::dispatch::declare_empty_apdu!(
161 DeliverySystemInfoReq,
162 tag::DELIVERY_SYSTEM_INFO_REQ,
163 "DeliverySystemInfoReq"
164);
165crate::dispatch::declare_empty_apdu!(ScanStartReq, tag::SCAN_START_REQ, "ScanStartReq");
166crate::dispatch::declare_empty_apdu!(ScanNextReq, tag::SCAN_NEXT_REQ, "ScanNextReq");
167
168impl<'a> Parse<'a> for DeliverySystemInfoAck {
171 type Error = Error;
172 fn parse(bytes: &'a [u8]) -> Result<Self> {
173 let body = objects::parse_apdu_header(
174 bytes,
175 tag::DELIVERY_SYSTEM_INFO_ACK,
176 "DeliverySystemInfoAck",
177 )?;
178 let mut systems = Vec::with_capacity(body.len());
179 for &b in body {
180 systems.push(SystemIdentifier::from_u8(b));
181 }
182 Ok(Self { systems })
183 }
184}
185impl Serialize for DeliverySystemInfoAck {
186 type Error = Error;
187 fn serialized_len(&self) -> usize {
188 objects::apdu_len(self.systems.len())
189 }
190 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
191 let body_len = self.systems.len();
192 let mut pos = objects::write_apdu_header(tag::DELIVERY_SYSTEM_INFO_ACK, body_len, buf)?;
193 for s in &self.systems {
194 buf[pos] = s.to_u8();
195 pos += 1;
196 }
197 Ok(pos)
198 }
199}
200
201const SCAN_ACK_BODY: usize = 1 + TUNING_INFO_MESSAGE_LEN + 1;
205
206impl<'a> Parse<'a> for ScanAck<'a> {
207 type Error = Error;
208 fn parse(bytes: &'a [u8]) -> Result<Self> {
209 let body = objects::parse_apdu_header(bytes, tag::SCAN_ACK, "ScanAck")?;
210 if body.len() < SCAN_ACK_BODY {
211 return Err(Error::BufferTooShort {
212 need: SCAN_ACK_BODY,
213 have: body.len(),
214 what: "ScanAck",
215 });
216 }
217 crate::objects::reject_trailing_body(body, SCAN_ACK_BODY, "ScanAck")?;
218 Ok(Self {
219 ts_state: body[0],
220 tuning_information_message: &body[1..1 + TUNING_INFO_MESSAGE_LEN],
221 scan_progress: body[1 + TUNING_INFO_MESSAGE_LEN],
222 })
223 }
224}
225impl Serialize for ScanAck<'_> {
226 type Error = Error;
227 fn serialized_len(&self) -> usize {
228 objects::apdu_len(SCAN_ACK_BODY)
229 }
230 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
231 if self.tuning_information_message.len() != TUNING_INFO_MESSAGE_LEN {
232 return Err(Error::InvalidObject {
233 what: "ScanAck",
234 reason: "TuningInformationMessage must be exactly 11 bytes",
235 });
236 }
237 let mut pos = objects::write_apdu_header(tag::SCAN_ACK, SCAN_ACK_BODY, buf)?;
238 buf[pos] = self.ts_state;
239 pos += 1;
240 buf[pos..pos + TUNING_INFO_MESSAGE_LEN].copy_from_slice(self.tuning_information_message);
241 pos += TUNING_INFO_MESSAGE_LEN;
242 buf[pos] = self.scan_progress;
243 Ok(pos + 1)
244 }
245}
246
247impl<'a> Parse<'a> for TuneTSReq<'a> {
250 type Error = Error;
251 fn parse(bytes: &'a [u8]) -> Result<Self> {
252 let body = objects::parse_apdu_header(bytes, tag::TUNE_TS_REQ, "TuneTSReq")?;
253 if !body.is_empty() && body.len() != TUNING_INFO_MESSAGE_LEN {
255 return Err(Error::InvalidObject {
256 what: "TuneTSReq",
257 reason: "TuningInformationMessage must be absent or exactly 11 bytes",
258 });
259 }
260 Ok(Self {
261 tuning_information_message: body,
262 })
263 }
264}
265impl Serialize for TuneTSReq<'_> {
266 type Error = Error;
267 fn serialized_len(&self) -> usize {
268 objects::apdu_len(self.tuning_information_message.len())
269 }
270 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
271 if !self.tuning_information_message.is_empty()
272 && self.tuning_information_message.len() != TUNING_INFO_MESSAGE_LEN
273 {
274 return Err(Error::InvalidObject {
275 what: "TuneTSReq",
276 reason: "TuningInformationMessage must be absent or exactly 11 bytes",
277 });
278 }
279 let body_len = self.tuning_information_message.len();
280 let pos = objects::write_apdu_header(tag::TUNE_TS_REQ, body_len, buf)?;
281 buf[pos..pos + body_len].copy_from_slice(self.tuning_information_message);
282 Ok(pos + body_len)
283 }
284}
285
286const TUNE_TS_ACK_BODY: usize = 1;
290
291impl<'a> Parse<'a> for TuneTSAck {
292 type Error = Error;
293 fn parse(bytes: &'a [u8]) -> Result<Self> {
294 let body = objects::parse_apdu_header(bytes, tag::TUNE_TS_ACK, "TuneTSAck")?;
295 if body.len() < TUNE_TS_ACK_BODY {
296 return Err(Error::BufferTooShort {
297 need: TUNE_TS_ACK_BODY,
298 have: body.len(),
299 what: "TuneTSAck",
300 });
301 }
302 crate::objects::reject_trailing_body(body, TUNE_TS_ACK_BODY, "TuneTSAck")?;
303 Ok(Self { ts_state: body[0] })
304 }
305}
306impl Serialize for TuneTSAck {
307 type Error = Error;
308 fn serialized_len(&self) -> usize {
309 objects::apdu_len(TUNE_TS_ACK_BODY)
310 }
311 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
312 let pos = objects::write_apdu_header(tag::TUNE_TS_ACK, TUNE_TS_ACK_BODY, buf)?;
313 buf[pos] = self.ts_state;
314 Ok(pos + TUNE_TS_ACK_BODY)
315 }
316}
317
318crate::dispatch::declare_resource_apdus! {
319 #[derive(Debug, Clone, PartialEq, Eq)]
321 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
322 #[non_exhaustive]
323 pub enum StreamInputApdu<'a> ("stream_input") {
324 DeliverySystemInfoReq(DeliverySystemInfoReq) = tag::DELIVERY_SYSTEM_INFO_REQ,
326 DeliverySystemInfoAck(DeliverySystemInfoAck) = tag::DELIVERY_SYSTEM_INFO_ACK,
328 ScanStartReq(ScanStartReq) = tag::SCAN_START_REQ,
330 ScanNextReq(ScanNextReq) = tag::SCAN_NEXT_REQ,
332 ScanAck(ScanAck<'a>) = tag::SCAN_ACK,
334 TuneTSReq(TuneTSReq<'a>) = tag::TUNE_TS_REQ,
336 TuneTSAck(TuneTSAck) = tag::TUNE_TS_ACK,
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 fn header_only_objects_round_trip() {
347 assert_eq!(DeliverySystemInfoReq.to_bytes(), [0x9F, 0x80, 0x00, 0x00]);
348 assert_eq!(ScanStartReq.to_bytes(), [0x9F, 0x80, 0x02, 0x00]);
349 assert_eq!(ScanNextReq.to_bytes(), [0x9F, 0x80, 0x03, 0x00]);
350 assert_eq!(
351 DeliverySystemInfoReq::parse(&[0x9F, 0x80, 0x00, 0x00]).unwrap(),
352 DeliverySystemInfoReq
353 );
354 assert_eq!(
355 ScanNextReq::parse(&[0x9F, 0x80, 0x03, 0x00]).unwrap(),
356 ScanNextReq
357 );
358 }
359
360 #[test]
361 fn delivery_system_info_ack_multi_round_trips_and_bites() {
362 let ack = DeliverySystemInfoAck {
363 systems: alloc::vec![
364 SystemIdentifier::DvbC,
365 SystemIdentifier::DvbS,
366 SystemIdentifier::DvbT,
367 ],
368 };
369 let bytes = ack.to_bytes();
370 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x03, 0x01, 0x02, 0x03]);
372 assert_eq!(DeliverySystemInfoAck::parse(&bytes).unwrap(), ack);
373 assert_eq!(ack.systems[0].name(), "DVB-C");
374 let mut other = ack.clone();
375 other.systems[2] = SystemIdentifier::Abstract;
376 assert_ne!(bytes, other.to_bytes());
377 }
378
379 #[test]
380 fn delivery_system_info_ack_reserved_value() {
381 let ack = DeliverySystemInfoAck {
382 systems: alloc::vec![SystemIdentifier::from_u8(0x7F)],
383 };
384 assert_eq!(ack.systems[0], SystemIdentifier::Reserved(0x7F));
385 let bytes = ack.to_bytes();
386 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, 0x7F]);
387 assert_eq!(DeliverySystemInfoAck::parse(&bytes).unwrap(), ack);
388 }
389
390 #[test]
391 fn scan_ack_round_trips_and_bites() {
392 let tim = [
393 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
394 ];
395 let ack = ScanAck {
396 ts_state: 0xC8,
397 tuning_information_message: &tim,
398 scan_progress: 0x40,
399 };
400 let bytes = ack.to_bytes();
401 assert_eq!(
403 bytes,
404 [
405 0x9F, 0x80, 0x04, 0x0D, 0xC8, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
406 0xAA, 0xBB, 0x40
407 ]
408 );
409 assert_eq!(ScanAck::parse(&bytes).unwrap(), ack);
410 let mut other = ack.clone();
411 other.scan_progress = 0x41;
412 assert_ne!(bytes, other.to_bytes());
413 }
414
415 #[test]
416 fn tune_ts_req_with_message_round_trips_and_bites() {
417 let tim = [
418 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
419 ];
420 let req = TuneTSReq {
421 tuning_information_message: &tim,
422 };
423 let bytes = req.to_bytes();
424 assert_eq!(
425 bytes,
426 [
427 0x9F, 0x80, 0x05, 0x0B, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
428 0x0B
429 ]
430 );
431 assert_eq!(TuneTSReq::parse(&bytes).unwrap(), req);
432 let mut tim2 = tim;
433 tim2[10] = 0xFF;
434 let other = TuneTSReq {
435 tuning_information_message: &tim2,
436 };
437 assert_ne!(bytes, other.to_bytes());
438 }
439
440 #[test]
441 fn tune_ts_req_disconnect_empty_message() {
442 let req = TuneTSReq {
443 tuning_information_message: &[],
444 };
445 let bytes = req.to_bytes();
446 assert_eq!(bytes, [0x9F, 0x80, 0x05, 0x00]);
447 assert_eq!(TuneTSReq::parse(&bytes).unwrap(), req);
448 }
449
450 #[test]
451 fn tune_ts_req_rejects_wrong_length() {
452 let bad = [0x9F, 0x80, 0x05, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
454 assert!(matches!(
455 TuneTSReq::parse(&bad),
456 Err(Error::InvalidObject { .. })
457 ));
458 }
459
460 #[test]
461 fn tune_ts_ack_round_trips_and_bites() {
462 let ack = TuneTSAck { ts_state: 0xFF };
463 let bytes = ack.to_bytes();
464 assert_eq!(bytes, [0x9F, 0x80, 0x06, 0x01, 0xFF]);
465 assert_eq!(TuneTSAck::parse(&bytes).unwrap(), ack);
466 let other = TuneTSAck { ts_state: 0x00 };
467 assert_ne!(bytes, other.to_bytes());
468 }
469
470 #[test]
471 fn dispatch_routes_each_tag() {
472 let req = DeliverySystemInfoReq.to_bytes();
473 assert!(matches!(
474 StreamInputApdu::parse(&req).unwrap(),
475 StreamInputApdu::DeliverySystemInfoReq(_)
476 ));
477 let ack = TuneTSAck { ts_state: 1 }.to_bytes();
478 let parsed = StreamInputApdu::parse(&ack).unwrap();
479 assert!(matches!(parsed, StreamInputApdu::TuneTSAck(_)));
480 assert_eq!(parsed.to_bytes(), ack);
481 let next = ScanNextReq.to_bytes();
483 assert!(matches!(
484 StreamInputApdu::parse(&next).unwrap(),
485 StreamInputApdu::ScanNextReq(_)
486 ));
487 }
488}