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 #[cfg_attr(feature = "serde", serde(skip))]
108 pub length_form: crate::length::LengthForm,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114pub struct ScanStartReq {
115 #[cfg_attr(feature = "serde", serde(skip))]
119 pub length_form: crate::length::LengthForm,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
124#[cfg_attr(feature = "serde", derive(serde::Serialize))]
125pub struct ScanNextReq {
126 #[cfg_attr(feature = "serde", serde(skip))]
130 pub length_form: crate::length::LengthForm,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Default)]
136#[cfg_attr(feature = "serde", derive(serde::Serialize))]
137pub struct DeliverySystemInfoAck {
138 #[cfg_attr(feature = "serde", serde(skip))]
142 pub length_form: crate::length::LengthForm,
143
144 pub systems: Vec<SystemIdentifier>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize))]
151pub struct ScanAck<'a> {
152 #[cfg_attr(feature = "serde", serde(skip))]
156 pub length_form: crate::length::LengthForm,
157
158 pub ts_state: u8,
161 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
164 pub tuning_information_message: &'a [u8],
165 pub scan_progress: u8,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Default)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize))]
173pub struct TuneTSReq<'a> {
174 #[cfg_attr(feature = "serde", serde(skip))]
178 pub length_form: crate::length::LengthForm,
179
180 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
182 pub tuning_information_message: &'a [u8],
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
187#[cfg_attr(feature = "serde", derive(serde::Serialize))]
188pub struct TuneTSAck {
189 #[cfg_attr(feature = "serde", serde(skip))]
193 pub length_form: crate::length::LengthForm,
194
195 pub ts_state: u8,
198}
199
200crate::dispatch::declare_empty_apdu!(
203 DeliverySystemInfoReq,
204 tag::DELIVERY_SYSTEM_INFO_REQ,
205 "DeliverySystemInfoReq"
206);
207crate::dispatch::declare_empty_apdu!(ScanStartReq, tag::SCAN_START_REQ, "ScanStartReq");
208crate::dispatch::declare_empty_apdu!(ScanNextReq, tag::SCAN_NEXT_REQ, "ScanNextReq");
209
210impl<'a> Parse<'a> for DeliverySystemInfoAck {
213 type Error = Error;
214 fn parse(bytes: &'a [u8]) -> Result<Self> {
215 let (length_form, body) = objects::parse_apdu_header(
216 bytes,
217 tag::DELIVERY_SYSTEM_INFO_ACK,
218 "DeliverySystemInfoAck",
219 )?;
220 let mut systems = Vec::with_capacity(body.len());
221 for &b in body {
222 systems.push(SystemIdentifier::from_u8(b));
223 }
224 Ok(Self {
225 length_form,
226 systems,
227 })
228 }
229}
230impl Serialize for DeliverySystemInfoAck {
231 type Error = Error;
232 fn serialized_len(&self) -> usize {
233 objects::apdu_len(self.systems.len(), self.length_form)
234 }
235 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
236 let body_len = self.systems.len();
237 let mut pos = objects::write_apdu_header(
238 tag::DELIVERY_SYSTEM_INFO_ACK,
239 body_len,
240 self.length_form,
241 buf,
242 )?;
243 for s in &self.systems {
244 buf[pos] = s.to_u8();
245 pos += 1;
246 }
247 Ok(pos)
248 }
249}
250
251const SCAN_ACK_BODY: usize = 1 + TUNING_INFO_MESSAGE_LEN + 1;
255
256impl<'a> Parse<'a> for ScanAck<'a> {
257 type Error = Error;
258 fn parse(bytes: &'a [u8]) -> Result<Self> {
259 let (length_form, body) = objects::parse_apdu_header(bytes, tag::SCAN_ACK, "ScanAck")?;
260 if body.len() < SCAN_ACK_BODY {
261 return Err(Error::BufferTooShort {
262 need: SCAN_ACK_BODY,
263 have: body.len(),
264 what: "ScanAck",
265 });
266 }
267 crate::objects::reject_trailing_body(body, SCAN_ACK_BODY, "ScanAck")?;
268 Ok(Self {
269 length_form,
270 ts_state: body[0],
271 tuning_information_message: &body[1..1 + TUNING_INFO_MESSAGE_LEN],
272 scan_progress: body[1 + TUNING_INFO_MESSAGE_LEN],
273 })
274 }
275}
276impl Serialize for ScanAck<'_> {
277 type Error = Error;
278 fn serialized_len(&self) -> usize {
279 objects::apdu_len(SCAN_ACK_BODY, self.length_form)
280 }
281 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
282 if self.tuning_information_message.len() != TUNING_INFO_MESSAGE_LEN {
283 return Err(Error::InvalidObject {
284 what: "ScanAck",
285 reason: "TuningInformationMessage must be exactly 11 bytes",
286 });
287 }
288 let mut pos =
289 objects::write_apdu_header(tag::SCAN_ACK, SCAN_ACK_BODY, self.length_form, buf)?;
290 buf[pos] = self.ts_state;
291 pos += 1;
292 buf[pos..pos + TUNING_INFO_MESSAGE_LEN].copy_from_slice(self.tuning_information_message);
293 pos += TUNING_INFO_MESSAGE_LEN;
294 buf[pos] = self.scan_progress;
295 Ok(pos + 1)
296 }
297}
298
299impl<'a> Parse<'a> for TuneTSReq<'a> {
302 type Error = Error;
303 fn parse(bytes: &'a [u8]) -> Result<Self> {
304 let (length_form, body) = objects::parse_apdu_header(bytes, tag::TUNE_TS_REQ, "TuneTSReq")?;
305 if !body.is_empty() && body.len() != TUNING_INFO_MESSAGE_LEN {
307 return Err(Error::InvalidObject {
308 what: "TuneTSReq",
309 reason: "TuningInformationMessage must be absent or exactly 11 bytes",
310 });
311 }
312 Ok(Self {
313 length_form,
314 tuning_information_message: body,
315 })
316 }
317}
318impl Serialize for TuneTSReq<'_> {
319 type Error = Error;
320 fn serialized_len(&self) -> usize {
321 objects::apdu_len(self.tuning_information_message.len(), self.length_form)
322 }
323 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
324 if !self.tuning_information_message.is_empty()
325 && self.tuning_information_message.len() != TUNING_INFO_MESSAGE_LEN
326 {
327 return Err(Error::InvalidObject {
328 what: "TuneTSReq",
329 reason: "TuningInformationMessage must be absent or exactly 11 bytes",
330 });
331 }
332 let body_len = self.tuning_information_message.len();
333 let pos = objects::write_apdu_header(tag::TUNE_TS_REQ, body_len, self.length_form, buf)?;
334 buf[pos..pos + body_len].copy_from_slice(self.tuning_information_message);
335 Ok(pos + body_len)
336 }
337}
338
339const TUNE_TS_ACK_BODY: usize = 1;
343
344impl<'a> Parse<'a> for TuneTSAck {
345 type Error = Error;
346 fn parse(bytes: &'a [u8]) -> Result<Self> {
347 let (length_form, body) = objects::parse_apdu_header(bytes, tag::TUNE_TS_ACK, "TuneTSAck")?;
348 if body.len() < TUNE_TS_ACK_BODY {
349 return Err(Error::BufferTooShort {
350 need: TUNE_TS_ACK_BODY,
351 have: body.len(),
352 what: "TuneTSAck",
353 });
354 }
355 crate::objects::reject_trailing_body(body, TUNE_TS_ACK_BODY, "TuneTSAck")?;
356 Ok(Self {
357 length_form,
358 ts_state: body[0],
359 })
360 }
361}
362impl Serialize for TuneTSAck {
363 type Error = Error;
364 fn serialized_len(&self) -> usize {
365 objects::apdu_len(TUNE_TS_ACK_BODY, self.length_form)
366 }
367 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
368 let pos =
369 objects::write_apdu_header(tag::TUNE_TS_ACK, TUNE_TS_ACK_BODY, self.length_form, buf)?;
370 buf[pos] = self.ts_state;
371 Ok(pos + TUNE_TS_ACK_BODY)
372 }
373}
374
375crate::dispatch::declare_resource_apdus! {
376 #[derive(Debug, Clone, PartialEq, Eq)]
378 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
379 #[non_exhaustive]
380 pub enum StreamInputApdu<'a> ("stream_input") {
381 DeliverySystemInfoReq(DeliverySystemInfoReq) = tag::DELIVERY_SYSTEM_INFO_REQ,
383 DeliverySystemInfoAck(DeliverySystemInfoAck) = tag::DELIVERY_SYSTEM_INFO_ACK,
385 ScanStartReq(ScanStartReq) = tag::SCAN_START_REQ,
387 ScanNextReq(ScanNextReq) = tag::SCAN_NEXT_REQ,
389 ScanAck(ScanAck<'a>) = tag::SCAN_ACK,
391 TuneTSReq(TuneTSReq<'a>) = tag::TUNE_TS_REQ,
393 TuneTSAck(TuneTSAck) = tag::TUNE_TS_ACK,
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn header_only_objects_round_trip() {
404 assert_eq!(
405 DeliverySystemInfoReq::default().try_to_bytes().unwrap(),
406 [0x9F, 0x80, 0x00, 0x00]
407 );
408 assert_eq!(
409 ScanStartReq::default().try_to_bytes().unwrap(),
410 [0x9F, 0x80, 0x02, 0x00]
411 );
412 assert_eq!(
413 ScanNextReq::default().try_to_bytes().unwrap(),
414 [0x9F, 0x80, 0x03, 0x00]
415 );
416 assert_eq!(
417 DeliverySystemInfoReq::parse(&[0x9F, 0x80, 0x00, 0x00]).unwrap(),
418 DeliverySystemInfoReq::default()
419 );
420 assert_eq!(
421 ScanNextReq::parse(&[0x9F, 0x80, 0x03, 0x00]).unwrap(),
422 ScanNextReq::default()
423 );
424 }
425
426 #[test]
427 fn delivery_system_info_ack_multi_round_trips_and_bites() {
428 let ack = DeliverySystemInfoAck {
429 length_form: crate::length::LengthForm::Minimal,
430 systems: alloc::vec![
431 SystemIdentifier::DvbC,
432 SystemIdentifier::DvbS,
433 SystemIdentifier::DvbT,
434 ],
435 };
436 let bytes = ack.try_to_bytes().unwrap();
437 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x03, 0x01, 0x02, 0x03]);
439 assert_eq!(DeliverySystemInfoAck::parse(&bytes).unwrap(), ack);
440 assert_eq!(ack.systems[0].name(), "DVB-C");
441 let mut other = ack.clone();
442 other.systems[2] = SystemIdentifier::Abstract;
443 assert_ne!(bytes, other.try_to_bytes().unwrap());
444 }
445
446 #[test]
447 fn delivery_system_info_ack_reserved_value() {
448 let ack = DeliverySystemInfoAck {
449 length_form: crate::length::LengthForm::Minimal,
450 systems: alloc::vec![SystemIdentifier::from_u8(0x7F)],
451 };
452 assert_eq!(ack.systems[0], SystemIdentifier::Reserved(0x7F));
453 let bytes = ack.try_to_bytes().unwrap();
454 assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x01, 0x7F]);
455 assert_eq!(DeliverySystemInfoAck::parse(&bytes).unwrap(), ack);
456 }
457
458 #[test]
459 fn scan_ack_round_trips_and_bites() {
460 let tim = [
461 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB,
462 ];
463 let ack = ScanAck {
464 length_form: crate::length::LengthForm::Minimal,
465 ts_state: 0xC8,
466 tuning_information_message: &tim,
467 scan_progress: 0x40,
468 };
469 let bytes = ack.try_to_bytes().unwrap();
470 assert_eq!(
472 bytes,
473 [
474 0x9F, 0x80, 0x04, 0x0D, 0xC8, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
475 0xAA, 0xBB, 0x40
476 ]
477 );
478 assert_eq!(ScanAck::parse(&bytes).unwrap(), ack);
479 let mut other = ack.clone();
480 other.scan_progress = 0x41;
481 assert_ne!(bytes, other.try_to_bytes().unwrap());
482 }
483
484 #[test]
485 fn tune_ts_req_with_message_round_trips_and_bites() {
486 let tim = [
487 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
488 ];
489 let req = TuneTSReq {
490 length_form: crate::length::LengthForm::Minimal,
491 tuning_information_message: &tim,
492 };
493 let bytes = req.try_to_bytes().unwrap();
494 assert_eq!(
495 bytes,
496 [
497 0x9F, 0x80, 0x05, 0x0B, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
498 0x0B
499 ]
500 );
501 assert_eq!(TuneTSReq::parse(&bytes).unwrap(), req);
502 let mut tim2 = tim;
503 tim2[10] = 0xFF;
504 let other = TuneTSReq {
505 length_form: crate::length::LengthForm::Minimal,
506 tuning_information_message: &tim2,
507 };
508 assert_ne!(bytes, other.try_to_bytes().unwrap());
509 }
510
511 #[test]
512 fn tune_ts_req_disconnect_empty_message() {
513 let req = TuneTSReq {
514 length_form: crate::length::LengthForm::Minimal,
515 tuning_information_message: &[],
516 };
517 let bytes = req.try_to_bytes().unwrap();
518 assert_eq!(bytes, [0x9F, 0x80, 0x05, 0x00]);
519 assert_eq!(TuneTSReq::parse(&bytes).unwrap(), req);
520 }
521
522 #[test]
523 fn tune_ts_req_rejects_wrong_length() {
524 let bad = [0x9F, 0x80, 0x05, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
526 assert!(matches!(
527 TuneTSReq::parse(&bad),
528 Err(Error::InvalidObject { .. })
529 ));
530 }
531
532 #[test]
533 fn tune_ts_ack_round_trips_and_bites() {
534 let ack = TuneTSAck {
535 length_form: crate::length::LengthForm::Minimal,
536 ts_state: 0xFF,
537 };
538 let bytes = ack.try_to_bytes().unwrap();
539 assert_eq!(bytes, [0x9F, 0x80, 0x06, 0x01, 0xFF]);
540 assert_eq!(TuneTSAck::parse(&bytes).unwrap(), ack);
541 let other = TuneTSAck {
542 length_form: crate::length::LengthForm::Minimal,
543 ts_state: 0x00,
544 };
545 assert_ne!(bytes, other.try_to_bytes().unwrap());
546 }
547
548 #[test]
549 fn dispatch_routes_each_tag() {
550 let req = DeliverySystemInfoReq::default().try_to_bytes().unwrap();
551 assert!(matches!(
552 StreamInputApdu::parse(&req).unwrap(),
553 StreamInputApdu::DeliverySystemInfoReq(_)
554 ));
555 let ack = TuneTSAck {
556 length_form: crate::length::LengthForm::Minimal,
557 ts_state: 1,
558 }
559 .try_to_bytes()
560 .unwrap();
561 let parsed = StreamInputApdu::parse(&ack).unwrap();
562 assert!(matches!(parsed, StreamInputApdu::TuneTSAck(_)));
563 assert_eq!(parsed.try_to_bytes().unwrap(), ack);
564 let next = ScanNextReq::default().try_to_bytes().unwrap();
566 assert!(matches!(
567 StreamInputApdu::parse(&next).unwrap(),
568 StreamInputApdu::ScanNextReq(_)
569 ));
570 }
571}