1use crate::error::{Error, Result};
20use crate::objects;
21use alloc::vec::Vec;
22use broadcast_common::len::need_in;
23use broadcast_common::{Parse, Serialize};
24
25pub mod tag {
27 use crate::tag::ApduTag;
28 pub const DELIVERY_SYSTEM_INFO_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
30 pub const DELIVERY_SYSTEM_INFO_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
32 pub const SCAN_START_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
34 pub const SCAN_NEXT_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
36 pub const SCAN_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x04);
38 pub const TUNE_TS_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x05);
40 pub const TUNE_TS_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x06);
42}
43
44pub const TUNING_INFO_MESSAGE_LEN: usize = 11;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize))]
50#[non_exhaustive]
51pub enum SystemIdentifier {
52 Abstract,
54 DvbC,
56 DvbS,
58 DvbT,
60 Reserved(u8),
62}
63
64impl SystemIdentifier {
65 #[must_use]
67 pub fn from_u8(v: u8) -> Self {
68 match v {
69 0 => Self::Abstract,
70 1 => Self::DvbC,
71 2 => Self::DvbS,
72 3 => Self::DvbT,
73 other => Self::Reserved(other),
74 }
75 }
76 #[must_use]
78 pub const fn to_u8(self) -> u8 {
79 match self {
80 Self::Abstract => 0,
81 Self::DvbC => 1,
82 Self::DvbS => 2,
83 Self::DvbT => 3,
84 Self::Reserved(v) => v,
85 }
86 }
87 #[must_use]
89 pub fn name(&self) -> &'static str {
90 match self {
91 Self::Abstract => "Abstract",
92 Self::DvbC => "DVB-C",
93 Self::DvbS => "DVB-S",
94 Self::DvbT => "DVB-T",
95 Self::Reserved(_) => "reserved",
96 }
97 }
98}
99broadcast_common::impl_spec_display!(SystemIdentifier, Reserved);
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize))]
104pub struct DeliverySystemInfoReq {
105 #[cfg_attr(feature = "serde", serde(skip))]
109 pub length_form: crate::length::LengthForm,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize))]
115pub struct ScanStartReq {
116 #[cfg_attr(feature = "serde", serde(skip))]
120 pub length_form: crate::length::LengthForm,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize))]
126pub struct ScanNextReq {
127 #[cfg_attr(feature = "serde", serde(skip))]
131 pub length_form: crate::length::LengthForm,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Default)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize))]
138pub struct DeliverySystemInfoAck {
139 #[cfg_attr(feature = "serde", serde(skip))]
143 pub length_form: crate::length::LengthForm,
144
145 pub systems: Vec<SystemIdentifier>,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize))]
152pub struct ScanAck<'a> {
153 #[cfg_attr(feature = "serde", serde(skip))]
157 pub length_form: crate::length::LengthForm,
158
159 pub ts_state: u8,
162 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
165 pub tuning_information_message: &'a [u8],
166 pub scan_progress: u8,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Default)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize))]
174pub struct TuneTSReq<'a> {
175 #[cfg_attr(feature = "serde", serde(skip))]
179 pub length_form: crate::length::LengthForm,
180
181 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
183 pub tuning_information_message: &'a [u8],
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
188#[cfg_attr(feature = "serde", derive(serde::Serialize))]
189pub struct TuneTSAck {
190 #[cfg_attr(feature = "serde", serde(skip))]
194 pub length_form: crate::length::LengthForm,
195
196 pub ts_state: u8,
199}
200
201crate::dispatch::declare_empty_apdu!(
204 DeliverySystemInfoReq,
205 tag::DELIVERY_SYSTEM_INFO_REQ,
206 "DeliverySystemInfoReq"
207);
208crate::dispatch::declare_empty_apdu!(ScanStartReq, tag::SCAN_START_REQ, "ScanStartReq");
209crate::dispatch::declare_empty_apdu!(ScanNextReq, tag::SCAN_NEXT_REQ, "ScanNextReq");
210
211impl<'a> Parse<'a> for DeliverySystemInfoAck {
214 type Error = Error;
215 fn parse(bytes: &'a [u8]) -> Result<Self> {
216 let (length_form, body) = objects::parse_apdu_header(
217 bytes,
218 tag::DELIVERY_SYSTEM_INFO_ACK,
219 "DeliverySystemInfoAck",
220 )?;
221 let mut systems = Vec::with_capacity(body.len());
222 for &b in body {
223 systems.push(SystemIdentifier::from_u8(b));
224 }
225 Ok(Self {
226 length_form,
227 systems,
228 })
229 }
230}
231impl Serialize for DeliverySystemInfoAck {
232 type Error = Error;
233 fn serialized_len(&self) -> usize {
234 objects::apdu_len(self.systems.len(), self.length_form)
235 }
236 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
237 let body_len = self.systems.len();
238 let mut pos = objects::write_apdu_header(
239 tag::DELIVERY_SYSTEM_INFO_ACK,
240 body_len,
241 self.length_form,
242 buf,
243 )?;
244 for s in &self.systems {
245 buf[pos] = s.to_u8();
246 pos += 1;
247 }
248 Ok(pos)
249 }
250}
251
252const SCAN_ACK_BODY: usize = 1 + TUNING_INFO_MESSAGE_LEN + 1;
256
257impl<'a> Parse<'a> for ScanAck<'a> {
258 type Error = Error;
259 fn parse(bytes: &'a [u8]) -> Result<Self> {
260 let (length_form, body) = objects::parse_apdu_header(bytes, tag::SCAN_ACK, "ScanAck")?;
261 need_in(body, SCAN_ACK_BODY, "ScanAck")?;
262 crate::objects::reject_trailing_body(body, SCAN_ACK_BODY, "ScanAck")?;
263 Ok(Self {
264 length_form,
265 ts_state: body[0],
266 tuning_information_message: &body[1..1 + TUNING_INFO_MESSAGE_LEN],
267 scan_progress: body[1 + TUNING_INFO_MESSAGE_LEN],
268 })
269 }
270}
271impl Serialize for ScanAck<'_> {
272 type Error = Error;
273 fn serialized_len(&self) -> usize {
274 objects::apdu_len(SCAN_ACK_BODY, self.length_form)
275 }
276 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
277 if self.tuning_information_message.len() != TUNING_INFO_MESSAGE_LEN {
278 return Err(Error::InvalidObject {
279 what: "ScanAck",
280 reason: "TuningInformationMessage must be exactly 11 bytes",
281 });
282 }
283 let mut pos =
284 objects::write_apdu_header(tag::SCAN_ACK, SCAN_ACK_BODY, self.length_form, buf)?;
285 buf[pos] = self.ts_state;
286 pos += 1;
287 buf[pos..pos + TUNING_INFO_MESSAGE_LEN].copy_from_slice(self.tuning_information_message);
288 pos += TUNING_INFO_MESSAGE_LEN;
289 buf[pos] = self.scan_progress;
290 Ok(pos + 1)
291 }
292}
293
294impl<'a> Parse<'a> for TuneTSReq<'a> {
297 type Error = Error;
298 fn parse(bytes: &'a [u8]) -> Result<Self> {
299 let (length_form, body) = objects::parse_apdu_header(bytes, tag::TUNE_TS_REQ, "TuneTSReq")?;
300 if !body.is_empty() && body.len() != TUNING_INFO_MESSAGE_LEN {
302 return Err(Error::InvalidObject {
303 what: "TuneTSReq",
304 reason: "TuningInformationMessage must be absent or exactly 11 bytes",
305 });
306 }
307 Ok(Self {
308 length_form,
309 tuning_information_message: body,
310 })
311 }
312}
313impl Serialize for TuneTSReq<'_> {
314 type Error = Error;
315 fn serialized_len(&self) -> usize {
316 objects::apdu_len(self.tuning_information_message.len(), self.length_form)
317 }
318 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
319 if !self.tuning_information_message.is_empty()
320 && self.tuning_information_message.len() != TUNING_INFO_MESSAGE_LEN
321 {
322 return Err(Error::InvalidObject {
323 what: "TuneTSReq",
324 reason: "TuningInformationMessage must be absent or exactly 11 bytes",
325 });
326 }
327 let body_len = self.tuning_information_message.len();
328 let pos = objects::write_apdu_header(tag::TUNE_TS_REQ, body_len, self.length_form, buf)?;
329 buf[pos..pos + body_len].copy_from_slice(self.tuning_information_message);
330 Ok(pos + body_len)
331 }
332}
333
334const TUNE_TS_ACK_BODY: usize = 1;
338
339impl<'a> Parse<'a> for TuneTSAck {
340 type Error = Error;
341 fn parse(bytes: &'a [u8]) -> Result<Self> {
342 let (length_form, body) = objects::parse_apdu_header(bytes, tag::TUNE_TS_ACK, "TuneTSAck")?;
343 need_in(body, TUNE_TS_ACK_BODY, "TuneTSAck")?;
344 crate::objects::reject_trailing_body(body, TUNE_TS_ACK_BODY, "TuneTSAck")?;
345 Ok(Self {
346 length_form,
347 ts_state: body[0],
348 })
349 }
350}
351impl Serialize for TuneTSAck {
352 type Error = Error;
353 fn serialized_len(&self) -> usize {
354 objects::apdu_len(TUNE_TS_ACK_BODY, self.length_form)
355 }
356 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
357 let pos =
358 objects::write_apdu_header(tag::TUNE_TS_ACK, TUNE_TS_ACK_BODY, self.length_form, buf)?;
359 buf[pos] = self.ts_state;
360 Ok(pos + TUNE_TS_ACK_BODY)
361 }
362}
363
364crate::dispatch::declare_resource_apdus! {
365 #[derive(Debug, Clone, PartialEq, Eq)]
367 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
368 #[non_exhaustive]
369 pub enum StreamInputApdu<'a> ("stream_input") {
370 DeliverySystemInfoReq(DeliverySystemInfoReq) = tag::DELIVERY_SYSTEM_INFO_REQ,
372 DeliverySystemInfoAck(DeliverySystemInfoAck) = tag::DELIVERY_SYSTEM_INFO_ACK,
374 ScanStartReq(ScanStartReq) = tag::SCAN_START_REQ,
376 ScanNextReq(ScanNextReq) = tag::SCAN_NEXT_REQ,
378 ScanAck(ScanAck<'a>) = tag::SCAN_ACK,
380 TuneTSReq(TuneTSReq<'a>) = tag::TUNE_TS_REQ,
382 TuneTSAck(TuneTSAck) = tag::TUNE_TS_ACK,
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn header_only_objects_round_trip() {
393 assert_eq!(
394 DeliverySystemInfoReq::default().try_to_bytes().unwrap(),
395 [0x9F, 0x80, 0x00, 0x00]
396 );
397 assert_eq!(
398 ScanStartReq::default().try_to_bytes().unwrap(),
399 [0x9F, 0x80, 0x02, 0x00]
400 );
401 assert_eq!(
402 ScanNextReq::default().try_to_bytes().unwrap(),
403 [0x9F, 0x80, 0x03, 0x00]
404 );
405 assert_eq!(
406 DeliverySystemInfoReq::parse(&[0x9F, 0x80, 0x00, 0x00]).unwrap(),
407 DeliverySystemInfoReq::default()
408 );
409 assert_eq!(
410 ScanNextReq::parse(&[0x9F, 0x80, 0x03, 0x00]).unwrap(),
411 ScanNextReq::default()
412 );
413 }
414
415 #[test]
416 fn delivery_system_info_ack_multi_round_trips_and_bites() {
417 let ack = DeliverySystemInfoAck {
418 length_form: crate::length::LengthForm::Minimal,
419 systems: alloc::vec![
420 SystemIdentifier::DvbC,
421 SystemIdentifier::DvbS,
422 SystemIdentifier::DvbT,
423 ],
424 };
425 let bytes = ack.try_to_bytes().unwrap();
426 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x03, 0x01, 0x02, 0x03]);
428 assert_eq!(DeliverySystemInfoAck::parse(&bytes).unwrap(), ack);
429 assert_eq!(ack.systems[0].name(), "DVB-C");
430 let mut other = ack.clone();
431 other.systems[2] = SystemIdentifier::Abstract;
432 assert_ne!(bytes, other.try_to_bytes().unwrap());
433 }
434
435 #[test]
436 fn delivery_system_info_ack_reserved_value() {
437 let ack = DeliverySystemInfoAck {
438 length_form: crate::length::LengthForm::Minimal,
439 systems: alloc::vec![SystemIdentifier::from_u8(0x7F)],
440 };
441 assert_eq!(ack.systems[0], SystemIdentifier::Reserved(0x7F));
442 let bytes = ack.try_to_bytes().unwrap();
443 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, 0x7F]);
444 assert_eq!(DeliverySystemInfoAck::parse(&bytes).unwrap(), ack);
445 }
446
447 #[test]
448 fn scan_ack_round_trips_and_bites() {
449 let tim = [
450 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
451 ];
452 let ack = ScanAck {
453 length_form: crate::length::LengthForm::Minimal,
454 ts_state: 0xC8,
455 tuning_information_message: &tim,
456 scan_progress: 0x40,
457 };
458 let bytes = ack.try_to_bytes().unwrap();
459 assert_eq!(
461 bytes,
462 [
463 0x9F, 0x80, 0x04, 0x0D, 0xC8, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
464 0xAA, 0xBB, 0x40
465 ]
466 );
467 assert_eq!(ScanAck::parse(&bytes).unwrap(), ack);
468 let mut other = ack.clone();
469 other.scan_progress = 0x41;
470 assert_ne!(bytes, other.try_to_bytes().unwrap());
471 }
472
473 #[test]
474 fn tune_ts_req_with_message_round_trips_and_bites() {
475 let tim = [
476 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
477 ];
478 let req = TuneTSReq {
479 length_form: crate::length::LengthForm::Minimal,
480 tuning_information_message: &tim,
481 };
482 let bytes = req.try_to_bytes().unwrap();
483 assert_eq!(
484 bytes,
485 [
486 0x9F, 0x80, 0x05, 0x0B, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
487 0x0B
488 ]
489 );
490 assert_eq!(TuneTSReq::parse(&bytes).unwrap(), req);
491 let mut tim2 = tim;
492 tim2[10] = 0xFF;
493 let other = TuneTSReq {
494 length_form: crate::length::LengthForm::Minimal,
495 tuning_information_message: &tim2,
496 };
497 assert_ne!(bytes, other.try_to_bytes().unwrap());
498 }
499
500 #[test]
501 fn tune_ts_req_disconnect_empty_message() {
502 let req = TuneTSReq {
503 length_form: crate::length::LengthForm::Minimal,
504 tuning_information_message: &[],
505 };
506 let bytes = req.try_to_bytes().unwrap();
507 assert_eq!(bytes, [0x9F, 0x80, 0x05, 0x00]);
508 assert_eq!(TuneTSReq::parse(&bytes).unwrap(), req);
509 }
510
511 #[test]
512 fn tune_ts_req_rejects_wrong_length() {
513 let bad = [0x9F, 0x80, 0x05, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
515 assert!(matches!(
516 TuneTSReq::parse(&bad),
517 Err(Error::InvalidObject { .. })
518 ));
519 }
520
521 #[test]
522 fn tune_ts_ack_round_trips_and_bites() {
523 let ack = TuneTSAck {
524 length_form: crate::length::LengthForm::Minimal,
525 ts_state: 0xFF,
526 };
527 let bytes = ack.try_to_bytes().unwrap();
528 assert_eq!(bytes, [0x9F, 0x80, 0x06, 0x01, 0xFF]);
529 assert_eq!(TuneTSAck::parse(&bytes).unwrap(), ack);
530 let other = TuneTSAck {
531 length_form: crate::length::LengthForm::Minimal,
532 ts_state: 0x00,
533 };
534 assert_ne!(bytes, other.try_to_bytes().unwrap());
535 }
536
537 #[test]
538 fn dispatch_routes_each_tag() {
539 let req = DeliverySystemInfoReq::default().try_to_bytes().unwrap();
540 assert!(matches!(
541 StreamInputApdu::parse(&req).unwrap(),
542 StreamInputApdu::DeliverySystemInfoReq(_)
543 ));
544 let ack = TuneTSAck {
545 length_form: crate::length::LengthForm::Minimal,
546 ts_state: 1,
547 }
548 .try_to_bytes()
549 .unwrap();
550 let parsed = StreamInputApdu::parse(&ack).unwrap();
551 assert!(matches!(parsed, StreamInputApdu::TuneTSAck(_)));
552 assert_eq!(parsed.try_to_bytes().unwrap(), ack);
553 let next = ScanNextReq::default().try_to_bytes().unwrap();
555 assert!(matches!(
556 StreamInputApdu::parse(&next).unwrap(),
557 StreamInputApdu::ScanNextReq(_)
558 ));
559 }
560}