1use crate::error::{Error, Result};
41use crate::objects;
42use crate::tag::ApduTag;
43use alloc::vec::Vec;
44use broadcast_common::{Parse, Serialize};
45
46pub mod tag {
50 use crate::tag::ApduTag;
51 pub const TUNE_LCN_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x07);
53 pub const TUNE_IP_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x08);
55 pub const TUNE_TRIPLET_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x09);
57 pub const TUNER_STATUS_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x0A);
59 pub const TUNER_STATUS_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x84, 0x0B);
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize))]
68#[non_exhaustive]
69pub enum HostControlMode {
70 MultiStream,
73 BaseV3,
76}
77
78impl HostControlMode {
79 #[must_use]
81 pub fn name(&self) -> &'static str {
82 match self {
83 Self::MultiStream => "multi_stream",
84 Self::BaseV3 => "base_v3",
85 }
86 }
87}
88broadcast_common::impl_spec_display!(HostControlMode);
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103pub struct TuneTripletReq {
104 pub background_tune: bool,
106 pub tune_quietly: bool,
108 pub keep_app_running: bool,
110 pub original_network_id: u16,
112 pub transport_stream_id: u16,
114 pub service_id: u16,
116 pub delivery_system_descriptor_tag: u8,
118 pub descriptor_tag_extension: Option<u8>,
122}
123
124const TRIPLET_BODY: usize = 1 + 2 + 2 + 2 + 1 + 1;
126const DSD_TAG_EXTENSION: u8 = 0x7F;
128
129const TRIPLET_BACKGROUND_BIT: u8 = 0x04;
131const TRIPLET_QUIETLY_BIT: u8 = 0x02;
132const TRIPLET_KEEP_BIT: u8 = 0x01;
133
134impl<'a> Parse<'a> for TuneTripletReq {
135 type Error = Error;
136 fn parse(bytes: &'a [u8]) -> Result<Self> {
137 let body = objects::parse_apdu_header(bytes, tag::TUNE_TRIPLET_REQ, "tune_triplet_req")?;
138 if body.len() < TRIPLET_BODY {
139 return Err(Error::BufferTooShort {
140 need: TRIPLET_BODY,
141 have: body.len(),
142 what: "tune_triplet_req",
143 });
144 }
145 let flags = body[0];
146 let dsd_tag = body[7];
147 let descriptor_tag_extension = if dsd_tag == DSD_TAG_EXTENSION {
148 Some(body[8])
149 } else {
150 None
151 };
152 Ok(Self {
153 background_tune: flags & TRIPLET_BACKGROUND_BIT != 0,
154 tune_quietly: flags & TRIPLET_QUIETLY_BIT != 0,
155 keep_app_running: flags & TRIPLET_KEEP_BIT != 0,
156 original_network_id: u16::from_be_bytes([body[1], body[2]]),
157 transport_stream_id: u16::from_be_bytes([body[3], body[4]]),
158 service_id: u16::from_be_bytes([body[5], body[6]]),
159 delivery_system_descriptor_tag: dsd_tag,
160 descriptor_tag_extension,
161 })
162 }
163}
164
165impl Serialize for TuneTripletReq {
166 type Error = Error;
167 fn serialized_len(&self) -> usize {
168 objects::apdu_len(TRIPLET_BODY)
169 }
170 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
171 let pos = objects::write_apdu_header(tag::TUNE_TRIPLET_REQ, TRIPLET_BODY, buf)?;
172 let mut flags = 0u8;
173 if self.background_tune {
174 flags |= TRIPLET_BACKGROUND_BIT;
175 }
176 if self.tune_quietly {
177 flags |= TRIPLET_QUIETLY_BIT;
178 }
179 if self.keep_app_running {
180 flags |= TRIPLET_KEEP_BIT;
181 }
182 buf[pos] = flags;
183 buf[pos + 1..pos + 3].copy_from_slice(&self.original_network_id.to_be_bytes());
184 buf[pos + 3..pos + 5].copy_from_slice(&self.transport_stream_id.to_be_bytes());
185 buf[pos + 5..pos + 7].copy_from_slice(&self.service_id.to_be_bytes());
186 buf[pos + 7] = self.delivery_system_descriptor_tag;
187 buf[pos + 8] = self.descriptor_tag_extension.unwrap_or(0);
189 Ok(pos + TRIPLET_BODY)
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize))]
197pub struct TuneLcnReq {
198 pub background_tune: bool,
200 pub tune_quietly: bool,
202 pub keep_app_running: bool,
204 pub logical_channel_number: u16,
206}
207
208const LCN_BODY: usize = 3;
210
211const LCN_BACKGROUND_BIT: u8 = 0x01;
214const LCN_QUIETLY_BIT: u8 = 0x80;
216const LCN_KEEP_BIT: u8 = 0x40;
217const LCN_MASK: u16 = 0x3FFF;
219
220impl<'a> Parse<'a> for TuneLcnReq {
221 type Error = Error;
222 fn parse(bytes: &'a [u8]) -> Result<Self> {
223 let body = objects::parse_apdu_header(bytes, tag::TUNE_LCN_REQ, "tune_lcn_req")?;
224 if body.len() < LCN_BODY {
225 return Err(Error::BufferTooShort {
226 need: LCN_BODY,
227 have: body.len(),
228 what: "tune_lcn_req",
229 });
230 }
231 let background_tune = body[0] & LCN_BACKGROUND_BIT != 0;
232 let tune_quietly = body[1] & LCN_QUIETLY_BIT != 0;
233 let keep_app_running = body[1] & LCN_KEEP_BIT != 0;
234 let logical_channel_number = u16::from_be_bytes([body[1], body[2]]) & LCN_MASK;
235 Ok(Self {
236 background_tune,
237 tune_quietly,
238 keep_app_running,
239 logical_channel_number,
240 })
241 }
242}
243
244impl Serialize for TuneLcnReq {
245 type Error = Error;
246 fn serialized_len(&self) -> usize {
247 objects::apdu_len(LCN_BODY)
248 }
249 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
250 let pos = objects::write_apdu_header(tag::TUNE_LCN_REQ, LCN_BODY, buf)?;
251 buf[pos] = if self.background_tune {
252 LCN_BACKGROUND_BIT
253 } else {
254 0
255 };
256 let mut hi = (self.logical_channel_number >> 8) as u8 & (LCN_MASK >> 8) as u8;
257 if self.tune_quietly {
258 hi |= LCN_QUIETLY_BIT;
259 }
260 if self.keep_app_running {
261 hi |= LCN_KEEP_BIT;
262 }
263 buf[pos + 1] = hi;
264 buf[pos + 2] = self.logical_channel_number as u8;
265 Ok(pos + LCN_BODY)
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
273#[cfg_attr(feature = "serde", derive(serde::Serialize))]
274pub struct TuneIpReq<'a> {
275 pub mode: HostControlMode,
277 pub background_tune: bool,
281 pub tune_quietly: bool,
283 pub keep_app_running: bool,
285 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
287 pub service_location_data: &'a [u8],
288}
289
290const TUNE_IP_PREFIX: usize = 2;
292const IP_MS_BACKGROUND_BIT: u8 = 0x40;
295const IP_MS_QUIETLY_BIT: u8 = 0x20;
296const IP_MS_KEEP_BIT: u8 = 0x10;
297const IP_V3_QUIETLY_BIT: u8 = 0x20;
300const IP_V3_KEEP_BIT: u8 = 0x10;
301const SLL_HI_MASK: u8 = 0x0F;
303
304impl<'a> TuneIpReq<'a> {
305 pub fn parse_mode(bytes: &'a [u8], mode: HostControlMode) -> Result<Self> {
307 let body = objects::parse_apdu_header(bytes, tag::TUNE_IP_REQ, "tune_ip_req")?;
308 if body.len() < TUNE_IP_PREFIX {
309 return Err(Error::BufferTooShort {
310 need: TUNE_IP_PREFIX,
311 have: body.len(),
312 what: "tune_ip_req",
313 });
314 }
315 let (background_tune, tune_quietly, keep_app_running) = match mode {
316 HostControlMode::MultiStream => (
317 body[0] & IP_MS_BACKGROUND_BIT != 0,
318 body[0] & IP_MS_QUIETLY_BIT != 0,
319 body[0] & IP_MS_KEEP_BIT != 0,
320 ),
321 HostControlMode::BaseV3 => (
322 false,
323 body[0] & IP_V3_QUIETLY_BIT != 0,
324 body[0] & IP_V3_KEEP_BIT != 0,
325 ),
326 };
327 let sll = (((body[0] & SLL_HI_MASK) as usize) << 8) | body[1] as usize;
328 let data = &body[TUNE_IP_PREFIX..];
329 if data.len() < sll {
330 return Err(Error::BufferTooShort {
331 need: sll,
332 have: data.len(),
333 what: "tune_ip_req service_location_data",
334 });
335 }
336 Ok(Self {
337 mode,
338 background_tune,
339 tune_quietly,
340 keep_app_running,
341 service_location_data: &data[..sll],
342 })
343 }
344}
345
346impl Serialize for TuneIpReq<'_> {
347 type Error = Error;
348 fn serialized_len(&self) -> usize {
349 objects::apdu_len(TUNE_IP_PREFIX + self.service_location_data.len())
350 }
351 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
352 let sll = self.service_location_data.len();
353 let body_len = TUNE_IP_PREFIX + sll;
354 let pos = objects::write_apdu_header(tag::TUNE_IP_REQ, body_len, buf)?;
355 let mut byte0 = (sll >> 8) as u8 & SLL_HI_MASK;
356 match self.mode {
357 HostControlMode::MultiStream => {
358 if self.background_tune {
359 byte0 |= IP_MS_BACKGROUND_BIT;
360 }
361 if self.tune_quietly {
362 byte0 |= IP_MS_QUIETLY_BIT;
363 }
364 if self.keep_app_running {
365 byte0 |= IP_MS_KEEP_BIT;
366 }
367 }
368 HostControlMode::BaseV3 => {
369 if self.tune_quietly {
370 byte0 |= IP_V3_QUIETLY_BIT;
371 }
372 if self.keep_app_running {
373 byte0 |= IP_V3_KEEP_BIT;
374 }
375 }
376 }
377 buf[pos] = byte0;
378 buf[pos + 1] = sll as u8;
379 buf[pos + 2..pos + 2 + sll].copy_from_slice(self.service_location_data);
380 Ok(pos + body_len)
381 }
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
386#[cfg_attr(feature = "serde", derive(serde::Serialize))]
387pub struct TunerStatusReq;
388
389impl<'a> Parse<'a> for TunerStatusReq {
390 type Error = Error;
391 fn parse(bytes: &'a [u8]) -> Result<Self> {
392 objects::parse_empty_apdu(bytes, tag::TUNER_STATUS_REQ, "tuner_status_req")?;
393 Ok(Self)
394 }
395}
396
397impl Serialize for TunerStatusReq {
398 type Error = Error;
399 fn serialized_len(&self) -> usize {
400 objects::empty_apdu_len()
401 }
402 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
403 objects::serialize_empty_apdu(tag::TUNER_STATUS_REQ, buf)
404 }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq)]
411#[cfg_attr(feature = "serde", derive(serde::Serialize))]
412pub struct TunerStatusDsd {
413 pub connected: bool,
416 pub delivery_system_descriptor_tag: u8,
418 pub descriptor_tag_extension: Option<u8>,
422}
423
424#[derive(Debug, Clone, PartialEq, Eq)]
426#[cfg_attr(feature = "serde", derive(serde::Serialize))]
427pub struct TunerStatusReply {
428 pub ip_tune_capable: bool,
430 pub dsds: Vec<TunerStatusDsd>,
432}
433
434const DSD_ENTRY_LEN: usize = 3;
436const IP_TUNE_CAPABLE_BIT: u8 = 0x80;
438const NUM_DSD_MASK: u8 = 0x7F;
439const DSD_CONNECTED_BIT: u8 = 0x01;
441
442impl<'a> Parse<'a> for TunerStatusReply {
443 type Error = Error;
444 fn parse(bytes: &'a [u8]) -> Result<Self> {
445 let body =
446 objects::parse_apdu_header(bytes, tag::TUNER_STATUS_REPLY, "tuner_status_reply")?;
447 if body.is_empty() {
448 return Err(Error::BufferTooShort {
449 need: 1,
450 have: 0,
451 what: "tuner_status_reply",
452 });
453 }
454 let ip_tune_capable = body[0] & IP_TUNE_CAPABLE_BIT != 0;
455 let num_dsd = (body[0] & NUM_DSD_MASK) as usize;
456 let mut rest = &body[1..];
457 let mut dsds = Vec::with_capacity(num_dsd);
458 for _ in 0..num_dsd {
459 if rest.len() < DSD_ENTRY_LEN {
460 return Err(Error::BufferTooShort {
461 need: DSD_ENTRY_LEN,
462 have: rest.len(),
463 what: "tuner_status_reply dsd",
464 });
465 }
466 let connected = rest[0] & DSD_CONNECTED_BIT != 0;
467 let dsd_tag = rest[1];
468 let descriptor_tag_extension = if dsd_tag == DSD_TAG_EXTENSION {
469 Some(rest[2])
470 } else {
471 None
472 };
473 dsds.push(TunerStatusDsd {
474 connected,
475 delivery_system_descriptor_tag: dsd_tag,
476 descriptor_tag_extension,
477 });
478 rest = &rest[DSD_ENTRY_LEN..];
479 }
480 Ok(Self {
481 ip_tune_capable,
482 dsds,
483 })
484 }
485}
486
487impl Serialize for TunerStatusReply {
488 type Error = Error;
489 fn serialized_len(&self) -> usize {
490 objects::apdu_len(1 + self.dsds.len() * DSD_ENTRY_LEN)
491 }
492 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
493 let body_len = 1 + self.dsds.len() * DSD_ENTRY_LEN;
494 let mut pos = objects::write_apdu_header(tag::TUNER_STATUS_REPLY, body_len, buf)?;
495 let mut byte0 = self.dsds.len() as u8 & NUM_DSD_MASK;
496 if self.ip_tune_capable {
497 byte0 |= IP_TUNE_CAPABLE_BIT;
498 }
499 buf[pos] = byte0;
500 pos += 1;
501 for dsd in &self.dsds {
502 buf[pos] = if dsd.connected { DSD_CONNECTED_BIT } else { 0 };
503 buf[pos + 1] = dsd.delivery_system_descriptor_tag;
504 buf[pos + 2] = dsd.descriptor_tag_extension.unwrap_or(0);
505 pos += DSD_ENTRY_LEN;
506 }
507 Ok(pos)
508 }
509}
510
511#[derive(Debug, Clone, PartialEq, Eq)]
516#[cfg_attr(feature = "serde", derive(serde::Serialize))]
517#[non_exhaustive]
518pub enum MultistreamHostControlApdu<'a> {
519 TuneTripletReq(TuneTripletReq),
521 TuneLcnReq(TuneLcnReq),
523 TuneIpReq(TuneIpReq<'a>),
525 TunerStatusReq(TunerStatusReq),
527 TunerStatusReply(TunerStatusReply),
529}
530
531impl<'a> MultistreamHostControlApdu<'a> {
532 pub fn parse_mode(body: &'a [u8], mode: HostControlMode) -> Result<Self> {
535 if body.len() < 3 {
536 return Err(Error::BufferTooShort {
537 need: 3,
538 have: body.len(),
539 what: "multistream_host_control apdu_tag",
540 });
541 }
542 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
543 match t {
544 tag::TUNE_TRIPLET_REQ => Ok(Self::TuneTripletReq(TuneTripletReq::parse(body)?)),
545 tag::TUNE_LCN_REQ => Ok(Self::TuneLcnReq(TuneLcnReq::parse(body)?)),
546 tag::TUNE_IP_REQ => Ok(Self::TuneIpReq(TuneIpReq::parse_mode(body, mode)?)),
547 tag::TUNER_STATUS_REQ => Ok(Self::TunerStatusReq(TunerStatusReq::parse(body)?)),
548 tag::TUNER_STATUS_REPLY => Ok(Self::TunerStatusReply(TunerStatusReply::parse(body)?)),
549 _ => Err(Error::UnexpectedApduTag {
550 got: t.as_u24(),
551 expected: tag::TUNE_TRIPLET_REQ.as_u24(),
552 what: "multistream_host_control",
553 }),
554 }
555 }
556}
557
558impl Serialize for MultistreamHostControlApdu<'_> {
559 type Error = Error;
560 fn serialized_len(&self) -> usize {
561 match self {
562 Self::TuneTripletReq(o) => o.serialized_len(),
563 Self::TuneLcnReq(o) => o.serialized_len(),
564 Self::TuneIpReq(o) => o.serialized_len(),
565 Self::TunerStatusReq(o) => o.serialized_len(),
566 Self::TunerStatusReply(o) => o.serialized_len(),
567 }
568 }
569 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
570 match self {
571 Self::TuneTripletReq(o) => o.serialize_into(buf),
572 Self::TuneLcnReq(o) => o.serialize_into(buf),
573 Self::TuneIpReq(o) => o.serialize_into(buf),
574 Self::TunerStatusReq(o) => o.serialize_into(buf),
575 Self::TunerStatusReply(o) => o.serialize_into(buf),
576 }
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583
584 #[test]
585 fn tune_triplet_round_trips_and_bites() {
586 let t = TuneTripletReq {
587 background_tune: true,
588 tune_quietly: false,
589 keep_app_running: true,
590 original_network_id: 0x1122,
591 transport_stream_id: 0x3344,
592 service_id: 0x5566,
593 delivery_system_descriptor_tag: 0x44,
594 descriptor_tag_extension: None,
595 };
596 let bytes = t.to_bytes();
597 assert_eq!(
600 bytes,
601 [
602 0x9F, 0x84, 0x09, 0x09, 0x05, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x44, 0x00
603 ]
604 );
605 assert_eq!(TuneTripletReq::parse(&bytes).unwrap(), t);
606 let mut other = t;
607 other.background_tune = false;
608 assert_eq!(other.to_bytes()[4], 0x01);
609 assert_ne!(bytes, other.to_bytes());
610 }
611
612 #[test]
613 fn tune_triplet_with_descriptor_extension() {
614 let t = TuneTripletReq {
615 background_tune: false,
616 tune_quietly: true,
617 keep_app_running: false,
618 original_network_id: 0x0001,
619 transport_stream_id: 0x0002,
620 service_id: 0x0003,
621 delivery_system_descriptor_tag: 0x7F,
622 descriptor_tag_extension: Some(0x79),
623 };
624 let bytes = t.to_bytes();
625 assert_eq!(
627 bytes,
628 [
629 0x9F, 0x84, 0x09, 0x09, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x7F, 0x79
630 ]
631 );
632 assert_eq!(TuneTripletReq::parse(&bytes).unwrap(), t);
633 }
634
635 #[test]
636 fn tune_lcn_round_trips_and_bites() {
637 let t = TuneLcnReq {
638 background_tune: true,
639 tune_quietly: true,
640 keep_app_running: false,
641 logical_channel_number: 0x0123,
642 };
643 let bytes = t.to_bytes();
644 assert_eq!(bytes, [0x9F, 0x84, 0x07, 0x03, 0x01, 0x81, 0x23]);
647 assert_eq!(TuneLcnReq::parse(&bytes).unwrap(), t);
648 let mut other = t;
649 other.logical_channel_number = 0x3FFE;
650 assert_ne!(bytes, other.to_bytes());
651 assert_eq!(
653 TuneLcnReq::parse(&other.to_bytes())
654 .unwrap()
655 .logical_channel_number,
656 0x3FFE
657 );
658 }
659
660 #[test]
661 fn tune_ip_multistream_vs_basev3_distinct_bytes() {
662 let data: &[u8] = &[0xAA, 0xBB];
664 let ms = TuneIpReq {
665 mode: HostControlMode::MultiStream,
666 background_tune: true,
667 tune_quietly: true,
668 keep_app_running: false,
669 service_location_data: data,
670 };
671 let v3 = TuneIpReq {
672 mode: HostControlMode::BaseV3,
673 background_tune: false, tune_quietly: true,
675 keep_app_running: false,
676 service_location_data: data,
677 };
678 let ms_bytes = ms.to_bytes();
679 let v3_bytes = v3.to_bytes();
680 assert_eq!(ms_bytes, [0x9F, 0x84, 0x08, 0x04, 0x60, 0x02, 0xAA, 0xBB]);
682 assert_eq!(v3_bytes, [0x9F, 0x84, 0x08, 0x04, 0x20, 0x02, 0xAA, 0xBB]);
684 assert_ne!(ms_bytes, v3_bytes);
686 assert_eq!(
687 TuneIpReq::parse_mode(&ms_bytes, HostControlMode::MultiStream).unwrap(),
688 ms
689 );
690 assert_eq!(
691 TuneIpReq::parse_mode(&v3_bytes, HostControlMode::BaseV3).unwrap(),
692 v3
693 );
694 }
695
696 #[test]
697 fn tune_ip_multistream_background_bit_is_v3_reserved() {
698 let ms = TuneIpReq {
702 mode: HostControlMode::MultiStream,
703 background_tune: true,
704 tune_quietly: false,
705 keep_app_running: false,
706 service_location_data: &[],
707 };
708 let bytes = ms.to_bytes();
709 assert_eq!(bytes[4], 0x40);
711 let as_v3 = TuneIpReq::parse_mode(&bytes, HostControlMode::BaseV3).unwrap();
712 assert!(!as_v3.background_tune);
713 assert!(!as_v3.tune_quietly);
714 assert!(!as_v3.keep_app_running);
715 }
716
717 #[test]
718 fn tune_ip_empty_location() {
719 let t = TuneIpReq {
720 mode: HostControlMode::MultiStream,
721 background_tune: false,
722 tune_quietly: false,
723 keep_app_running: false,
724 service_location_data: &[],
725 };
726 let bytes = t.to_bytes();
727 assert_eq!(bytes, [0x9F, 0x84, 0x08, 0x02, 0x00, 0x00]);
728 assert_eq!(
729 TuneIpReq::parse_mode(&bytes, HostControlMode::MultiStream).unwrap(),
730 t
731 );
732 }
733
734 #[test]
735 fn tuner_status_req_round_trips() {
736 let bytes = TunerStatusReq.to_bytes();
737 assert_eq!(bytes, [0x9F, 0x84, 0x0A, 0x00]);
738 assert_eq!(TunerStatusReq::parse(&bytes).unwrap(), TunerStatusReq);
739 }
740
741 #[test]
742 fn tuner_status_reply_round_trips_with_two_dsds() {
743 let r = TunerStatusReply {
744 ip_tune_capable: true,
745 dsds: alloc::vec![
746 TunerStatusDsd {
747 connected: true,
748 delivery_system_descriptor_tag: 0x44, descriptor_tag_extension: None,
750 },
751 TunerStatusDsd {
752 connected: false,
753 delivery_system_descriptor_tag: 0x7F,
754 descriptor_tag_extension: Some(0x79),
755 },
756 ],
757 };
758 let bytes = r.to_bytes();
759 assert_eq!(
763 bytes,
764 [
765 0x9F, 0x84, 0x0B, 0x07, 0x82, 0x01, 0x44, 0x00, 0x00, 0x7F, 0x79
766 ]
767 );
768 assert_eq!(TunerStatusReply::parse(&bytes).unwrap(), r);
769 let mut other = r.clone();
770 other.ip_tune_capable = false;
771 assert_eq!(other.to_bytes()[4], 0x02);
772 assert_ne!(bytes, other.to_bytes());
773 }
774
775 #[test]
776 fn tuner_status_reply_empty() {
777 let r = TunerStatusReply {
778 ip_tune_capable: false,
779 dsds: Vec::new(),
780 };
781 let bytes = r.to_bytes();
782 assert_eq!(bytes, [0x9F, 0x84, 0x0B, 0x01, 0x00]);
783 assert_eq!(TunerStatusReply::parse(&bytes).unwrap(), r);
784 }
785
786 #[test]
787 fn dispatch_routes_each_tag() {
788 let triplet = TuneTripletReq {
789 background_tune: false,
790 tune_quietly: false,
791 keep_app_running: false,
792 original_network_id: 1,
793 transport_stream_id: 2,
794 service_id: 3,
795 delivery_system_descriptor_tag: 0,
796 descriptor_tag_extension: None,
797 }
798 .to_bytes();
799 assert!(matches!(
800 MultistreamHostControlApdu::parse_mode(&triplet, HostControlMode::MultiStream).unwrap(),
801 MultistreamHostControlApdu::TuneTripletReq(_)
802 ));
803 let ip = TuneIpReq {
804 mode: HostControlMode::MultiStream,
805 background_tune: true,
806 tune_quietly: false,
807 keep_app_running: false,
808 service_location_data: &[0x01],
809 }
810 .to_bytes();
811 let parsed =
812 MultistreamHostControlApdu::parse_mode(&ip, HostControlMode::MultiStream).unwrap();
813 assert!(matches!(parsed, MultistreamHostControlApdu::TuneIpReq(_)));
814 assert_eq!(parsed.to_bytes(), ip);
815 assert!(matches!(
817 MultistreamHostControlApdu::parse_mode(
818 &[0x9F, 0x84, 0x00, 0x00],
819 HostControlMode::MultiStream
820 ),
821 Err(Error::UnexpectedApduTag { .. })
822 ));
823 }
824}