1use crate::error::{Error, Result};
41use crate::objects;
42use crate::tag::ApduTag;
43use alloc::vec::Vec;
44use dvb_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}
88dvb_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 [0x9F, 0x84, 0x09, 0x09, 0x05, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x44, 0x00]
602 );
603 assert_eq!(TuneTripletReq::parse(&bytes).unwrap(), t);
604 let mut other = t;
605 other.background_tune = false;
606 assert_eq!(other.to_bytes()[4], 0x01);
607 assert_ne!(bytes, other.to_bytes());
608 }
609
610 #[test]
611 fn tune_triplet_with_descriptor_extension() {
612 let t = TuneTripletReq {
613 background_tune: false,
614 tune_quietly: true,
615 keep_app_running: false,
616 original_network_id: 0x0001,
617 transport_stream_id: 0x0002,
618 service_id: 0x0003,
619 delivery_system_descriptor_tag: 0x7F,
620 descriptor_tag_extension: Some(0x79),
621 };
622 let bytes = t.to_bytes();
623 assert_eq!(
625 bytes,
626 [0x9F, 0x84, 0x09, 0x09, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x7F, 0x79]
627 );
628 assert_eq!(TuneTripletReq::parse(&bytes).unwrap(), t);
629 }
630
631 #[test]
632 fn tune_lcn_round_trips_and_bites() {
633 let t = TuneLcnReq {
634 background_tune: true,
635 tune_quietly: true,
636 keep_app_running: false,
637 logical_channel_number: 0x0123,
638 };
639 let bytes = t.to_bytes();
640 assert_eq!(bytes, [0x9F, 0x84, 0x07, 0x03, 0x01, 0x81, 0x23]);
643 assert_eq!(TuneLcnReq::parse(&bytes).unwrap(), t);
644 let mut other = t;
645 other.logical_channel_number = 0x3FFE;
646 assert_ne!(bytes, other.to_bytes());
647 assert_eq!(
649 TuneLcnReq::parse(&other.to_bytes())
650 .unwrap()
651 .logical_channel_number,
652 0x3FFE
653 );
654 }
655
656 #[test]
657 fn tune_ip_multistream_vs_basev3_distinct_bytes() {
658 let data: &[u8] = &[0xAA, 0xBB];
660 let ms = TuneIpReq {
661 mode: HostControlMode::MultiStream,
662 background_tune: true,
663 tune_quietly: true,
664 keep_app_running: false,
665 service_location_data: data,
666 };
667 let v3 = TuneIpReq {
668 mode: HostControlMode::BaseV3,
669 background_tune: false, tune_quietly: true,
671 keep_app_running: false,
672 service_location_data: data,
673 };
674 let ms_bytes = ms.to_bytes();
675 let v3_bytes = v3.to_bytes();
676 assert_eq!(ms_bytes, [0x9F, 0x84, 0x08, 0x04, 0x60, 0x02, 0xAA, 0xBB]);
678 assert_eq!(v3_bytes, [0x9F, 0x84, 0x08, 0x04, 0x20, 0x02, 0xAA, 0xBB]);
680 assert_ne!(ms_bytes, v3_bytes);
682 assert_eq!(
683 TuneIpReq::parse_mode(&ms_bytes, HostControlMode::MultiStream).unwrap(),
684 ms
685 );
686 assert_eq!(
687 TuneIpReq::parse_mode(&v3_bytes, HostControlMode::BaseV3).unwrap(),
688 v3
689 );
690 }
691
692 #[test]
693 fn tune_ip_multistream_background_bit_is_v3_reserved() {
694 let ms = TuneIpReq {
698 mode: HostControlMode::MultiStream,
699 background_tune: true,
700 tune_quietly: false,
701 keep_app_running: false,
702 service_location_data: &[],
703 };
704 let bytes = ms.to_bytes();
705 assert_eq!(bytes[4], 0x40);
707 let as_v3 = TuneIpReq::parse_mode(&bytes, HostControlMode::BaseV3).unwrap();
708 assert!(!as_v3.background_tune);
709 assert!(!as_v3.tune_quietly);
710 assert!(!as_v3.keep_app_running);
711 }
712
713 #[test]
714 fn tune_ip_empty_location() {
715 let t = TuneIpReq {
716 mode: HostControlMode::MultiStream,
717 background_tune: false,
718 tune_quietly: false,
719 keep_app_running: false,
720 service_location_data: &[],
721 };
722 let bytes = t.to_bytes();
723 assert_eq!(bytes, [0x9F, 0x84, 0x08, 0x02, 0x00, 0x00]);
724 assert_eq!(
725 TuneIpReq::parse_mode(&bytes, HostControlMode::MultiStream).unwrap(),
726 t
727 );
728 }
729
730 #[test]
731 fn tuner_status_req_round_trips() {
732 let bytes = TunerStatusReq.to_bytes();
733 assert_eq!(bytes, [0x9F, 0x84, 0x0A, 0x00]);
734 assert_eq!(TunerStatusReq::parse(&bytes).unwrap(), TunerStatusReq);
735 }
736
737 #[test]
738 fn tuner_status_reply_round_trips_with_two_dsds() {
739 let r = TunerStatusReply {
740 ip_tune_capable: true,
741 dsds: alloc::vec![
742 TunerStatusDsd {
743 connected: true,
744 delivery_system_descriptor_tag: 0x44, descriptor_tag_extension: None,
746 },
747 TunerStatusDsd {
748 connected: false,
749 delivery_system_descriptor_tag: 0x7F,
750 descriptor_tag_extension: Some(0x79),
751 },
752 ],
753 };
754 let bytes = r.to_bytes();
755 assert_eq!(
759 bytes,
760 [0x9F, 0x84, 0x0B, 0x07, 0x82, 0x01, 0x44, 0x00, 0x00, 0x7F, 0x79]
761 );
762 assert_eq!(TunerStatusReply::parse(&bytes).unwrap(), r);
763 let mut other = r.clone();
764 other.ip_tune_capable = false;
765 assert_eq!(other.to_bytes()[4], 0x02);
766 assert_ne!(bytes, other.to_bytes());
767 }
768
769 #[test]
770 fn tuner_status_reply_empty() {
771 let r = TunerStatusReply {
772 ip_tune_capable: false,
773 dsds: Vec::new(),
774 };
775 let bytes = r.to_bytes();
776 assert_eq!(bytes, [0x9F, 0x84, 0x0B, 0x01, 0x00]);
777 assert_eq!(TunerStatusReply::parse(&bytes).unwrap(), r);
778 }
779
780 #[test]
781 fn dispatch_routes_each_tag() {
782 let triplet = TuneTripletReq {
783 background_tune: false,
784 tune_quietly: false,
785 keep_app_running: false,
786 original_network_id: 1,
787 transport_stream_id: 2,
788 service_id: 3,
789 delivery_system_descriptor_tag: 0,
790 descriptor_tag_extension: None,
791 }
792 .to_bytes();
793 assert!(matches!(
794 MultistreamHostControlApdu::parse_mode(&triplet, HostControlMode::MultiStream).unwrap(),
795 MultistreamHostControlApdu::TuneTripletReq(_)
796 ));
797 let ip = TuneIpReq {
798 mode: HostControlMode::MultiStream,
799 background_tune: true,
800 tune_quietly: false,
801 keep_app_running: false,
802 service_location_data: &[0x01],
803 }
804 .to_bytes();
805 let parsed =
806 MultistreamHostControlApdu::parse_mode(&ip, HostControlMode::MultiStream).unwrap();
807 assert!(matches!(parsed, MultistreamHostControlApdu::TuneIpReq(_)));
808 assert_eq!(parsed.to_bytes(), ip);
809 assert!(matches!(
811 MultistreamHostControlApdu::parse_mode(
812 &[0x9F, 0x84, 0x00, 0x00],
813 HostControlMode::MultiStream
814 ),
815 Err(Error::UnexpectedApduTag { .. })
816 ));
817 }
818}