1use crate::error::{Error, Result};
64use crate::traits::Descriptor;
65use dvb_common::{Parse, Serialize};
66
67pub const TAG: u8 = 0x7F;
69const HEADER_LEN: usize = 2;
70const TAG_EXTENSION_LEN: usize = 1;
72const MIN_BODY_LEN: usize = TAG_EXTENSION_LEN;
74const MAX_DESCRIPTOR_LENGTH: usize = 0xFF;
76
77const ISO_639_LEN: usize = 3;
79const T2_FIXED_PREFIX_LEN: usize = 3; const T2_FLAGS_BLOCK_LEN: usize = 2; const C2_LEN: usize = 7; const C2_BUNDLE_ENTRY_LEN: usize = 8; const SERVICE_RELOCATED_LEN: usize = 6; const S2X_PRIMARY_LEN: usize = 11;
87const S2X_SCRAMBLING_LEN: usize = 3;
88const TTML_FIXED_LEN: usize = ISO_639_LEN + 2; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97#[non_exhaustive]
98#[repr(u8)]
99pub enum ExtensionTag {
100 ImageIcon = 0x00,
102 T2DeliverySystem = 0x04,
104 SupplementaryAudio = 0x06,
106 NetworkChangeNotify = 0x07,
108 Message = 0x08,
110 TargetRegion = 0x09,
112 TargetRegionName = 0x0A,
114 ServiceRelocated = 0x0B,
116 C2DeliverySystem = 0x0D,
118 UriLinkage = 0x13,
120 Ac4 = 0x15,
122 C2BundleDeliverySystem = 0x16,
124 S2XSatelliteDeliverySystem = 0x17,
126 AudioPreselection = 0x19,
128 TtmlSubtitling = 0x20,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
139pub enum ExtensionBody<'a> {
140 T2DeliverySystem(#[cfg_attr(feature = "serde", serde(borrow))] T2DeliverySystem<'a>),
142 SupplementaryAudio(#[cfg_attr(feature = "serde", serde(borrow))] SupplementaryAudio<'a>),
144 NetworkChangeNotify(#[cfg_attr(feature = "serde", serde(borrow))] NetworkChangeNotify<'a>),
146 Message(#[cfg_attr(feature = "serde", serde(borrow))] Message<'a>),
148 TargetRegion(#[cfg_attr(feature = "serde", serde(borrow))] TargetRegion<'a>),
150 TargetRegionName(#[cfg_attr(feature = "serde", serde(borrow))] TargetRegionName<'a>),
152 ServiceRelocated(ServiceRelocated),
154 C2DeliverySystem(C2DeliverySystem),
156 UriLinkage(#[cfg_attr(feature = "serde", serde(borrow))] UriLinkage<'a>),
158 Ac4(#[cfg_attr(feature = "serde", serde(borrow))] Ac4<'a>),
160 C2BundleDeliverySystem(C2BundleDeliverySystem),
162 S2XSatelliteDeliverySystem(
164 #[cfg_attr(feature = "serde", serde(borrow))] S2XSatelliteDeliverySystem<'a>,
165 ),
166 AudioPreselection(#[cfg_attr(feature = "serde", serde(borrow))] AudioPreselection<'a>),
168 TtmlSubtitling(#[cfg_attr(feature = "serde", serde(borrow))] TtmlSubtitling<'a>),
170 Raw(#[cfg_attr(feature = "serde", serde(borrow))] &'a [u8]),
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
187pub struct T2DeliverySystem<'a> {
188 pub plp_id: u8,
190 pub t2_system_id: u16,
192 pub siso_miso: Option<u8>,
194 pub bandwidth: Option<u8>,
196 pub guard_interval: Option<u8>,
198 pub transmission_mode: Option<u8>,
200 pub other_frequency_flag: Option<bool>,
202 pub tfs_flag: Option<bool>,
204 #[cfg_attr(feature = "serde", serde(borrow))]
206 pub cell_loop: &'a [u8],
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
216pub struct SupplementaryAudio<'a> {
217 pub mix_type: bool,
219 pub editorial_classification: u8,
221 pub language_code_present: bool,
223 #[cfg_attr(feature = "serde", serde(borrow))]
225 pub iso_639_language_code: Option<&'a [u8]>,
226 #[cfg_attr(feature = "serde", serde(borrow))]
228 pub private_data: &'a [u8],
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
240#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
241pub struct NetworkChangeNotify<'a> {
242 #[cfg_attr(feature = "serde", serde(borrow))]
244 pub cell_loop: &'a [u8],
245}
246
247#[derive(Debug, Clone, PartialEq, Eq)]
252#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
253#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
254pub struct Message<'a> {
255 pub message_id: u8,
257 #[cfg_attr(feature = "serde", serde(borrow))]
259 pub iso_639_language_code: &'a [u8],
260 #[cfg_attr(feature = "serde", serde(borrow))]
262 pub text: &'a [u8],
263}
264
265#[derive(Debug, Clone, PartialEq, Eq)]
273#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
274#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
275pub struct TargetRegion<'a> {
276 #[cfg_attr(feature = "serde", serde(borrow))]
278 pub country_code: &'a [u8],
279 #[cfg_attr(feature = "serde", serde(borrow))]
281 pub region_loop: &'a [u8],
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
289#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
290#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
291pub struct TargetRegionName<'a> {
292 #[cfg_attr(feature = "serde", serde(borrow))]
294 pub country_code: &'a [u8],
295 #[cfg_attr(feature = "serde", serde(borrow))]
297 pub iso_639_language_code: &'a [u8],
298 #[cfg_attr(feature = "serde", serde(borrow))]
300 pub region_loop: &'a [u8],
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
308#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
309pub struct ServiceRelocated {
310 pub old_original_network_id: u16,
312 pub old_transport_stream_id: u16,
314 pub old_service_id: u16,
316}
317
318#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
324pub struct C2DeliverySystem {
325 pub plp_id: u8,
327 pub data_slice_id: u8,
329 pub c2_system_tuning_frequency: u32,
331 pub c2_system_tuning_frequency_type: u8,
333 pub active_ofdm_symbol_duration: u8,
335 pub guard_interval: u8,
337}
338
339#[derive(Debug, Clone, PartialEq, Eq)]
347#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
348#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
349pub struct UriLinkage<'a> {
350 pub uri_linkage_type: u8,
352 #[cfg_attr(feature = "serde", serde(borrow))]
354 pub uri: &'a [u8],
355 pub min_polling_interval: Option<u16>,
357 #[cfg_attr(feature = "serde", serde(borrow))]
359 pub private_data: &'a [u8],
360}
361
362#[derive(Debug, Clone, PartialEq, Eq)]
371#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
372#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
373pub struct Ac4<'a> {
374 pub ac4_config_flag: bool,
376 pub ac4_toc_flag: bool,
378 pub ac4_dialog_enhancement_enabled: Option<bool>,
380 pub ac4_channel_mode: Option<u8>,
382 #[cfg_attr(feature = "serde", serde(borrow))]
384 pub toc: Option<&'a [u8]>,
385 #[cfg_attr(feature = "serde", serde(borrow))]
387 pub additional_info: &'a [u8],
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
397#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
398pub struct C2BundleEntry {
399 pub plp_id: u8,
401 pub data_slice_id: u8,
403 pub c2_system_tuning_frequency: u32,
405 pub c2_system_tuning_frequency_type: u8,
407 pub active_ofdm_symbol_duration: u8,
409 pub guard_interval: u8,
411 pub primary_channel: bool,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq)]
417#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
418pub struct C2BundleDeliverySystem {
419 pub entries: Vec<C2BundleEntry>,
421}
422
423#[derive(Debug, Clone, PartialEq, Eq)]
433#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
434#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
435pub struct S2XSatelliteDeliverySystem<'a> {
436 pub receiver_profiles: u8,
438 pub s2x_mode: u8,
440 pub scrambling_sequence_selector: bool,
442 pub ts_gs_s2x_mode: u8,
444 pub scrambling_sequence_index: Option<u32>,
446 pub frequency: u32,
448 pub orbital_position: u16,
450 pub west_east_flag: bool,
452 pub polarization: u8,
454 pub multiple_input_stream_flag: bool,
456 pub roll_off: u8,
458 pub symbol_rate: u32,
460 pub input_stream_identifier: Option<u8>,
462 pub timeslice_number: Option<u8>,
464 #[cfg_attr(feature = "serde", serde(borrow))]
466 pub tail: &'a [u8],
467}
468
469#[derive(Debug, Clone, PartialEq, Eq)]
478#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
479#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
480pub struct AudioPreselection<'a> {
481 pub num_preselections: u8,
483 #[cfg_attr(feature = "serde", serde(borrow))]
485 pub preselection_loop: &'a [u8],
486}
487
488#[derive(Debug, Clone, PartialEq, Eq)]
498#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
499#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
500pub struct TtmlSubtitling<'a> {
501 #[cfg_attr(feature = "serde", serde(borrow))]
503 pub iso_639_language_code: &'a [u8],
504 pub subtitle_purpose: u8,
506 pub tts_suitability: u8,
508 pub essential_font_usage_flag: bool,
510 pub qualifier_present_flag: bool,
512 pub dvb_ttml_profile_count: u8,
514 #[cfg_attr(feature = "serde", serde(borrow))]
516 pub tail: &'a [u8],
517}
518
519#[derive(Debug, Clone, PartialEq, Eq)]
521#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
522#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
523pub struct ExtensionDescriptor<'a> {
524 pub tag_extension: u8,
526 #[cfg_attr(feature = "serde", serde(borrow))]
528 pub body: ExtensionBody<'a>,
529}
530
531impl ExtensionDescriptor<'_> {
532 #[must_use]
534 pub fn kind(&self) -> Option<ExtensionTag> {
535 Some(match self.tag_extension {
536 0x00 => ExtensionTag::ImageIcon,
537 0x04 => ExtensionTag::T2DeliverySystem,
538 0x06 => ExtensionTag::SupplementaryAudio,
539 0x07 => ExtensionTag::NetworkChangeNotify,
540 0x08 => ExtensionTag::Message,
541 0x09 => ExtensionTag::TargetRegion,
542 0x0A => ExtensionTag::TargetRegionName,
543 0x0B => ExtensionTag::ServiceRelocated,
544 0x0D => ExtensionTag::C2DeliverySystem,
545 0x13 => ExtensionTag::UriLinkage,
546 0x15 => ExtensionTag::Ac4,
547 0x16 => ExtensionTag::C2BundleDeliverySystem,
548 0x17 => ExtensionTag::S2XSatelliteDeliverySystem,
549 0x19 => ExtensionTag::AudioPreselection,
550 0x20 => ExtensionTag::TtmlSubtitling,
551 _ => return None,
552 })
553 }
554}
555
556fn invalid(reason: &'static str) -> Error {
561 Error::InvalidDescriptor { tag: TAG, reason }
562}
563
564fn parse_t2(sel: &[u8]) -> Result<T2DeliverySystem<'_>> {
565 if sel.len() < T2_FIXED_PREFIX_LEN {
566 return Err(invalid("T2_delivery_system: prefix truncated"));
567 }
568 let plp_id = sel[0];
569 let t2_system_id = u16::from_be_bytes([sel[1], sel[2]]);
570 let mut pos = T2_FIXED_PREFIX_LEN;
571 let (siso_miso, bandwidth, guard_interval, transmission_mode, other_frequency_flag, tfs_flag) =
574 if sel.len() > T2_FIXED_PREFIX_LEN {
575 if sel.len() < T2_FIXED_PREFIX_LEN + T2_FLAGS_BLOCK_LEN {
576 return Err(invalid("T2_delivery_system: flags block truncated"));
577 }
578 let b0 = sel[pos];
579 let b1 = sel[pos + 1];
580 pos += T2_FLAGS_BLOCK_LEN;
581 (
582 Some(b0 >> 6),
583 Some((b0 >> 2) & 0x0F),
584 Some(b1 >> 5),
585 Some((b1 >> 2) & 0x07),
586 Some((b1 & 0x02) != 0),
587 Some((b1 & 0x01) != 0),
588 )
589 } else {
590 (None, None, None, None, None, None)
591 };
592 Ok(T2DeliverySystem {
593 plp_id,
594 t2_system_id,
595 siso_miso,
596 bandwidth,
597 guard_interval,
598 transmission_mode,
599 other_frequency_flag,
600 tfs_flag,
601 cell_loop: &sel[pos..],
602 })
603}
604
605fn parse_supplementary_audio(sel: &[u8]) -> Result<SupplementaryAudio<'_>> {
606 if sel.is_empty() {
607 return Err(invalid("supplementary_audio: flags byte missing"));
608 }
609 let flags = sel[0];
610 let mix_type = (flags & 0x80) != 0;
611 let editorial_classification = (flags >> 2) & 0x1F;
612 let language_code_present = (flags & 0x01) != 0;
613 let mut pos = 1;
614 let iso_639_language_code = if language_code_present {
615 if sel.len() < pos + ISO_639_LEN {
616 return Err(invalid("supplementary_audio: language code truncated"));
617 }
618 let lc = &sel[pos..pos + ISO_639_LEN];
619 pos += ISO_639_LEN;
620 Some(lc)
621 } else {
622 None
623 };
624 Ok(SupplementaryAudio {
625 mix_type,
626 editorial_classification,
627 language_code_present,
628 iso_639_language_code,
629 private_data: &sel[pos..],
630 })
631}
632
633fn parse_message(sel: &[u8]) -> Result<Message<'_>> {
634 if sel.len() < 1 + ISO_639_LEN {
635 return Err(invalid("message: header truncated"));
636 }
637 Ok(Message {
638 message_id: sel[0],
639 iso_639_language_code: &sel[1..1 + ISO_639_LEN],
640 text: &sel[1 + ISO_639_LEN..],
641 })
642}
643
644fn parse_target_region(sel: &[u8]) -> Result<TargetRegion<'_>> {
645 if sel.len() < ISO_639_LEN {
646 return Err(invalid("target_region: country_code truncated"));
647 }
648 Ok(TargetRegion {
649 country_code: &sel[..ISO_639_LEN],
650 region_loop: &sel[ISO_639_LEN..],
651 })
652}
653
654fn parse_target_region_name(sel: &[u8]) -> Result<TargetRegionName<'_>> {
655 if sel.len() < 2 * ISO_639_LEN {
656 return Err(invalid("target_region_name: header truncated"));
657 }
658 Ok(TargetRegionName {
659 country_code: &sel[..ISO_639_LEN],
660 iso_639_language_code: &sel[ISO_639_LEN..2 * ISO_639_LEN],
661 region_loop: &sel[2 * ISO_639_LEN..],
662 })
663}
664
665fn parse_service_relocated(sel: &[u8]) -> Result<ServiceRelocated> {
666 if sel.len() < SERVICE_RELOCATED_LEN {
667 return Err(invalid("service_relocated: truncated"));
668 }
669 Ok(ServiceRelocated {
670 old_original_network_id: u16::from_be_bytes([sel[0], sel[1]]),
671 old_transport_stream_id: u16::from_be_bytes([sel[2], sel[3]]),
672 old_service_id: u16::from_be_bytes([sel[4], sel[5]]),
673 })
674}
675
676fn parse_c2(sel: &[u8]) -> Result<C2DeliverySystem> {
677 if sel.len() < C2_LEN {
678 return Err(invalid("C2_delivery_system: truncated"));
679 }
680 let packed = sel[6];
681 Ok(C2DeliverySystem {
682 plp_id: sel[0],
683 data_slice_id: sel[1],
684 c2_system_tuning_frequency: u32::from_be_bytes([sel[2], sel[3], sel[4], sel[5]]),
685 c2_system_tuning_frequency_type: packed >> 6,
686 active_ofdm_symbol_duration: (packed >> 3) & 0x07,
687 guard_interval: packed & 0x07,
688 })
689}
690
691fn parse_uri_linkage(sel: &[u8]) -> Result<UriLinkage<'_>> {
692 if sel.len() < 2 {
693 return Err(invalid("URI_linkage: header truncated"));
694 }
695 let uri_linkage_type = sel[0];
696 let uri_length = sel[1] as usize;
697 let mut pos = 2;
698 if sel.len() < pos + uri_length {
699 return Err(invalid("URI_linkage: uri overruns body"));
700 }
701 let uri = &sel[pos..pos + uri_length];
702 pos += uri_length;
703 let min_polling_interval = if uri_linkage_type == 0x00 || uri_linkage_type == 0x01 {
704 if sel.len() < pos + 2 {
705 return Err(invalid("URI_linkage: min_polling_interval truncated"));
706 }
707 let v = u16::from_be_bytes([sel[pos], sel[pos + 1]]);
708 pos += 2;
709 Some(v)
710 } else {
711 None
712 };
713 Ok(UriLinkage {
714 uri_linkage_type,
715 uri,
716 min_polling_interval,
717 private_data: &sel[pos..],
718 })
719}
720
721fn parse_ac4(sel: &[u8]) -> Result<Ac4<'_>> {
722 if sel.is_empty() {
723 return Err(invalid("AC-4: flags byte missing"));
724 }
725 let flags = sel[0];
726 let ac4_config_flag = (flags & 0x80) != 0;
727 let ac4_toc_flag = (flags & 0x40) != 0;
728 let mut pos = 1;
729 let (ac4_dialog_enhancement_enabled, ac4_channel_mode) = if ac4_config_flag {
730 if sel.len() < pos + 1 {
731 return Err(invalid("AC-4: config byte truncated"));
732 }
733 let c = sel[pos];
734 pos += 1;
735 (Some((c & 0x80) != 0), Some((c >> 5) & 0x03))
736 } else {
737 (None, None)
738 };
739 let toc = if ac4_toc_flag {
740 if sel.len() < pos + 1 {
741 return Err(invalid("AC-4: toc length truncated"));
742 }
743 let toc_len = sel[pos] as usize;
744 pos += 1;
745 if sel.len() < pos + toc_len {
746 return Err(invalid("AC-4: toc overruns body"));
747 }
748 let t = &sel[pos..pos + toc_len];
749 pos += toc_len;
750 Some(t)
751 } else {
752 None
753 };
754 Ok(Ac4 {
755 ac4_config_flag,
756 ac4_toc_flag,
757 ac4_dialog_enhancement_enabled,
758 ac4_channel_mode,
759 toc,
760 additional_info: &sel[pos..],
761 })
762}
763
764fn parse_c2_bundle(sel: &[u8]) -> Result<C2BundleDeliverySystem> {
765 if sel.len() % C2_BUNDLE_ENTRY_LEN != 0 {
766 return Err(invalid(
767 "C2_bundle_delivery_system: not a whole number of entries",
768 ));
769 }
770 let mut entries = Vec::with_capacity(sel.len() / C2_BUNDLE_ENTRY_LEN);
771 for chunk in sel.chunks_exact(C2_BUNDLE_ENTRY_LEN) {
772 let packed = chunk[6];
773 entries.push(C2BundleEntry {
774 plp_id: chunk[0],
775 data_slice_id: chunk[1],
776 c2_system_tuning_frequency: u32::from_be_bytes([
777 chunk[2], chunk[3], chunk[4], chunk[5],
778 ]),
779 c2_system_tuning_frequency_type: packed >> 6,
780 active_ofdm_symbol_duration: (packed >> 3) & 0x07,
781 guard_interval: packed & 0x07,
782 primary_channel: (chunk[7] & 0x80) != 0,
783 });
784 }
785 Ok(C2BundleDeliverySystem { entries })
786}
787
788fn parse_s2x(sel: &[u8]) -> Result<S2XSatelliteDeliverySystem<'_>> {
789 if sel.len() < 2 {
791 return Err(invalid("S2X: flags truncated"));
792 }
793 let receiver_profiles = sel[0] >> 3;
794 let b1 = sel[1];
795 let s2x_mode = (b1 >> 6) & 0x03;
798 let scrambling_sequence_selector = (b1 & 0x20) != 0;
799 let ts_gs_s2x_mode = b1 & 0x03;
800 let mut pos = 2;
801 let scrambling_sequence_index = if scrambling_sequence_selector {
802 if sel.len() < pos + S2X_SCRAMBLING_LEN {
803 return Err(invalid("S2X: scrambling_sequence_index truncated"));
804 }
805 let idx = (u32::from(sel[pos] & 0x03) << 16)
806 | (u32::from(sel[pos + 1]) << 8)
807 | u32::from(sel[pos + 2]);
808 pos += S2X_SCRAMBLING_LEN;
809 Some(idx)
810 } else {
811 None
812 };
813 if sel.len() < pos + S2X_PRIMARY_LEN {
817 return Err(invalid("S2X: primary channel truncated"));
818 }
819 let frequency = u32::from_be_bytes([sel[pos], sel[pos + 1], sel[pos + 2], sel[pos + 3]]);
820 let orbital_position = u16::from_be_bytes([sel[pos + 4], sel[pos + 5]]);
821 let pb = sel[pos + 6];
822 let west_east_flag = (pb & 0x80) != 0;
823 let polarization = (pb >> 5) & 0x03;
824 let multiple_input_stream_flag = (pb & 0x10) != 0;
825 let roll_off = pb & 0x07;
826 let symbol_rate = (u32::from(sel[pos + 7] & 0x0F) << 24)
827 | (u32::from(sel[pos + 8]) << 16)
828 | (u32::from(sel[pos + 9]) << 8)
829 | u32::from(sel[pos + 10]);
830 pos += S2X_PRIMARY_LEN;
831 let input_stream_identifier = if multiple_input_stream_flag {
832 if sel.len() < pos + 1 {
833 return Err(invalid("S2X: input_stream_identifier truncated"));
834 }
835 let isi = sel[pos];
836 pos += 1;
837 Some(isi)
838 } else {
839 None
840 };
841 let timeslice_number = if s2x_mode == 2 {
842 if sel.len() < pos + 1 {
843 return Err(invalid("S2X: timeslice_number truncated"));
844 }
845 let ts = sel[pos];
846 pos += 1;
847 Some(ts)
848 } else {
849 None
850 };
851 Ok(S2XSatelliteDeliverySystem {
852 receiver_profiles,
853 s2x_mode,
854 scrambling_sequence_selector,
855 ts_gs_s2x_mode,
856 scrambling_sequence_index,
857 frequency,
858 orbital_position,
859 west_east_flag,
860 polarization,
861 multiple_input_stream_flag,
862 roll_off,
863 symbol_rate,
864 input_stream_identifier,
865 timeslice_number,
866 tail: &sel[pos..],
867 })
868}
869
870fn parse_audio_preselection(sel: &[u8]) -> Result<AudioPreselection<'_>> {
871 if sel.is_empty() {
872 return Err(invalid("audio_preselection: count byte missing"));
873 }
874 Ok(AudioPreselection {
875 num_preselections: sel[0] >> 3,
876 preselection_loop: &sel[1..],
877 })
878}
879
880fn parse_ttml(sel: &[u8]) -> Result<TtmlSubtitling<'_>> {
881 if sel.len() < TTML_FIXED_LEN {
882 return Err(invalid("TTML_subtitling: header truncated"));
883 }
884 let b3 = sel[ISO_639_LEN];
885 let b4 = sel[ISO_639_LEN + 1];
886 Ok(TtmlSubtitling {
887 iso_639_language_code: &sel[..ISO_639_LEN],
888 subtitle_purpose: b3 >> 2,
889 tts_suitability: b3 & 0x03,
890 essential_font_usage_flag: (b4 & 0x80) != 0,
891 qualifier_present_flag: (b4 & 0x40) != 0,
892 dvb_ttml_profile_count: b4 & 0x0F,
893 tail: &sel[TTML_FIXED_LEN..],
894 })
895}
896
897fn parse_body(tag_extension: u8, sel: &[u8]) -> Result<ExtensionBody<'_>> {
898 Ok(match tag_extension {
899 0x04 => ExtensionBody::T2DeliverySystem(parse_t2(sel)?),
900 0x06 => ExtensionBody::SupplementaryAudio(parse_supplementary_audio(sel)?),
901 0x07 => ExtensionBody::NetworkChangeNotify(NetworkChangeNotify { cell_loop: sel }),
902 0x08 => ExtensionBody::Message(parse_message(sel)?),
903 0x09 => ExtensionBody::TargetRegion(parse_target_region(sel)?),
904 0x0A => ExtensionBody::TargetRegionName(parse_target_region_name(sel)?),
905 0x0B => ExtensionBody::ServiceRelocated(parse_service_relocated(sel)?),
906 0x0D => ExtensionBody::C2DeliverySystem(parse_c2(sel)?),
907 0x13 => ExtensionBody::UriLinkage(parse_uri_linkage(sel)?),
908 0x15 => ExtensionBody::Ac4(parse_ac4(sel)?),
909 0x16 => ExtensionBody::C2BundleDeliverySystem(parse_c2_bundle(sel)?),
910 0x17 => ExtensionBody::S2XSatelliteDeliverySystem(parse_s2x(sel)?),
911 0x19 => ExtensionBody::AudioPreselection(parse_audio_preselection(sel)?),
912 0x20 => ExtensionBody::TtmlSubtitling(parse_ttml(sel)?),
913 _ => ExtensionBody::Raw(sel),
914 })
915}
916
917impl<'a> Parse<'a> for ExtensionDescriptor<'a> {
918 type Error = crate::error::Error;
919 fn parse(bytes: &'a [u8]) -> Result<Self> {
920 if bytes.len() < HEADER_LEN {
921 return Err(Error::BufferTooShort {
922 need: HEADER_LEN,
923 have: bytes.len(),
924 what: "ExtensionDescriptor header",
925 });
926 }
927 if bytes[0] != TAG {
928 return Err(Error::InvalidDescriptor {
929 tag: bytes[0],
930 reason: "unexpected tag for extension_descriptor",
931 });
932 }
933 let length = bytes[1] as usize;
934 let end = HEADER_LEN + length;
935 if bytes.len() < end {
936 return Err(Error::BufferTooShort {
937 need: end,
938 have: bytes.len(),
939 what: "ExtensionDescriptor body",
940 });
941 }
942 if length < MIN_BODY_LEN {
943 return Err(Error::InvalidDescriptor {
944 tag: TAG,
945 reason: "body must contain at least the descriptor_tag_extension byte",
946 });
947 }
948 let tag_extension = bytes[HEADER_LEN];
949 let sel = &bytes[HEADER_LEN + TAG_EXTENSION_LEN..end];
950 let body = parse_body(tag_extension, sel)?;
951 Ok(Self {
952 tag_extension,
953 body,
954 })
955 }
956}
957
958impl ExtensionBody<'_> {
963 fn selector_len(&self) -> usize {
965 match self {
966 ExtensionBody::T2DeliverySystem(b) => {
967 T2_FIXED_PREFIX_LEN
968 + if b.siso_miso.is_some() {
969 T2_FLAGS_BLOCK_LEN
970 } else {
971 0
972 }
973 + b.cell_loop.len()
974 }
975 ExtensionBody::SupplementaryAudio(b) => {
976 1 + b.iso_639_language_code.map_or(0, <[u8]>::len) + b.private_data.len()
977 }
978 ExtensionBody::NetworkChangeNotify(b) => b.cell_loop.len(),
979 ExtensionBody::Message(b) => 1 + ISO_639_LEN + b.text.len(),
980 ExtensionBody::TargetRegion(b) => ISO_639_LEN + b.region_loop.len(),
981 ExtensionBody::TargetRegionName(b) => 2 * ISO_639_LEN + b.region_loop.len(),
982 ExtensionBody::ServiceRelocated(_) => SERVICE_RELOCATED_LEN,
983 ExtensionBody::C2DeliverySystem(_) => C2_LEN,
984 ExtensionBody::UriLinkage(b) => {
985 2 + b.uri.len()
986 + if b.min_polling_interval.is_some() {
987 2
988 } else {
989 0
990 }
991 + b.private_data.len()
992 }
993 ExtensionBody::Ac4(b) => {
994 1 + usize::from(b.ac4_config_flag)
995 + b.toc.map_or(0, |t| 1 + t.len())
996 + b.additional_info.len()
997 }
998 ExtensionBody::C2BundleDeliverySystem(b) => b.entries.len() * C2_BUNDLE_ENTRY_LEN,
999 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
1000 2 + if b.scrambling_sequence_selector {
1001 S2X_SCRAMBLING_LEN
1002 } else {
1003 0
1004 } + S2X_PRIMARY_LEN
1005 + usize::from(b.input_stream_identifier.is_some())
1006 + usize::from(b.timeslice_number.is_some())
1007 + b.tail.len()
1008 }
1009 ExtensionBody::AudioPreselection(b) => 1 + b.preselection_loop.len(),
1010 ExtensionBody::TtmlSubtitling(b) => TTML_FIXED_LEN + b.tail.len(),
1011 ExtensionBody::Raw(s) => s.len(),
1012 }
1013 }
1014
1015 fn write_selector(&self, out: &mut [u8]) {
1017 match self {
1018 ExtensionBody::T2DeliverySystem(b) => {
1019 out[0] = b.plp_id;
1020 out[1..3].copy_from_slice(&b.t2_system_id.to_be_bytes());
1021 let mut p = T2_FIXED_PREFIX_LEN;
1022 if let (Some(sm), Some(bw), Some(gi), Some(tm), Some(off), Some(tfs)) = (
1023 b.siso_miso,
1024 b.bandwidth,
1025 b.guard_interval,
1026 b.transmission_mode,
1027 b.other_frequency_flag,
1028 b.tfs_flag,
1029 ) {
1030 out[p] = (sm << 6) | ((bw & 0x0F) << 2);
1031 out[p + 1] =
1032 (gi << 5) | ((tm & 0x07) << 2) | (u8::from(off) << 1) | u8::from(tfs);
1033 p += T2_FLAGS_BLOCK_LEN;
1034 }
1035 out[p..p + b.cell_loop.len()].copy_from_slice(b.cell_loop);
1036 }
1037 ExtensionBody::SupplementaryAudio(b) => {
1038 out[0] = (u8::from(b.mix_type) << 7)
1040 | ((b.editorial_classification & 0x1F) << 2)
1041 | 0x02
1042 | u8::from(b.language_code_present);
1043 let mut p = 1;
1044 if let Some(lc) = b.iso_639_language_code {
1045 out[p..p + lc.len()].copy_from_slice(lc);
1046 p += lc.len();
1047 }
1048 out[p..p + b.private_data.len()].copy_from_slice(b.private_data);
1049 }
1050 ExtensionBody::NetworkChangeNotify(b) => {
1051 out[..b.cell_loop.len()].copy_from_slice(b.cell_loop);
1052 }
1053 ExtensionBody::Message(b) => {
1054 out[0] = b.message_id;
1055 out[1..1 + ISO_639_LEN].copy_from_slice(b.iso_639_language_code);
1056 out[1 + ISO_639_LEN..1 + ISO_639_LEN + b.text.len()].copy_from_slice(b.text);
1057 }
1058 ExtensionBody::TargetRegion(b) => {
1059 out[..ISO_639_LEN].copy_from_slice(b.country_code);
1060 out[ISO_639_LEN..ISO_639_LEN + b.region_loop.len()].copy_from_slice(b.region_loop);
1061 }
1062 ExtensionBody::TargetRegionName(b) => {
1063 out[..ISO_639_LEN].copy_from_slice(b.country_code);
1064 out[ISO_639_LEN..2 * ISO_639_LEN].copy_from_slice(b.iso_639_language_code);
1065 out[2 * ISO_639_LEN..2 * ISO_639_LEN + b.region_loop.len()]
1066 .copy_from_slice(b.region_loop);
1067 }
1068 ExtensionBody::ServiceRelocated(b) => {
1069 out[0..2].copy_from_slice(&b.old_original_network_id.to_be_bytes());
1070 out[2..4].copy_from_slice(&b.old_transport_stream_id.to_be_bytes());
1071 out[4..6].copy_from_slice(&b.old_service_id.to_be_bytes());
1072 }
1073 ExtensionBody::C2DeliverySystem(b) => {
1074 out[0] = b.plp_id;
1075 out[1] = b.data_slice_id;
1076 out[2..6].copy_from_slice(&b.c2_system_tuning_frequency.to_be_bytes());
1077 out[6] = (b.c2_system_tuning_frequency_type << 6)
1078 | ((b.active_ofdm_symbol_duration & 0x07) << 3)
1079 | (b.guard_interval & 0x07);
1080 }
1081 ExtensionBody::UriLinkage(b) => {
1082 out[0] = b.uri_linkage_type;
1083 out[1] = b.uri.len() as u8;
1084 let mut p = 2;
1085 out[p..p + b.uri.len()].copy_from_slice(b.uri);
1086 p += b.uri.len();
1087 if let Some(mpi) = b.min_polling_interval {
1088 out[p..p + 2].copy_from_slice(&mpi.to_be_bytes());
1089 p += 2;
1090 }
1091 out[p..p + b.private_data.len()].copy_from_slice(b.private_data);
1092 }
1093 ExtensionBody::Ac4(b) => {
1094 out[0] = (u8::from(b.ac4_config_flag) << 7) | (u8::from(b.ac4_toc_flag) << 6);
1095 let mut p = 1;
1096 if b.ac4_config_flag {
1097 let de = b.ac4_dialog_enhancement_enabled.unwrap_or(false);
1098 let cm = b.ac4_channel_mode.unwrap_or(0) & 0x03;
1099 out[p] = (u8::from(de) << 7) | (cm << 5);
1100 p += 1;
1101 }
1102 if let Some(t) = b.toc {
1103 out[p] = t.len() as u8;
1104 p += 1;
1105 out[p..p + t.len()].copy_from_slice(t);
1106 p += t.len();
1107 }
1108 out[p..p + b.additional_info.len()].copy_from_slice(b.additional_info);
1109 }
1110 ExtensionBody::C2BundleDeliverySystem(b) => {
1111 let mut p = 0;
1112 for e in &b.entries {
1113 out[p] = e.plp_id;
1114 out[p + 1] = e.data_slice_id;
1115 out[p + 2..p + 6].copy_from_slice(&e.c2_system_tuning_frequency.to_be_bytes());
1116 out[p + 6] = (e.c2_system_tuning_frequency_type << 6)
1117 | ((e.active_ofdm_symbol_duration & 0x07) << 3)
1118 | (e.guard_interval & 0x07);
1119 out[p + 7] = u8::from(e.primary_channel) << 7;
1120 p += C2_BUNDLE_ENTRY_LEN;
1121 }
1122 }
1123 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
1124 out[0] = b.receiver_profiles << 3;
1125 out[1] = ((b.s2x_mode & 0x03) << 6)
1126 | (u8::from(b.scrambling_sequence_selector) << 5)
1127 | (b.ts_gs_s2x_mode & 0x03);
1128 let mut p = 2;
1129 if b.scrambling_sequence_selector {
1130 let idx = b.scrambling_sequence_index.unwrap_or(0) & 0x3FFFF;
1131 out[p] = (idx >> 16) as u8 & 0x03;
1132 out[p + 1] = (idx >> 8) as u8;
1133 out[p + 2] = idx as u8;
1134 p += S2X_SCRAMBLING_LEN;
1135 }
1136 out[p..p + 4].copy_from_slice(&b.frequency.to_be_bytes());
1137 out[p + 4..p + 6].copy_from_slice(&b.orbital_position.to_be_bytes());
1138 out[p + 6] = (u8::from(b.west_east_flag) << 7)
1139 | ((b.polarization & 0x03) << 5)
1140 | (u8::from(b.multiple_input_stream_flag) << 4)
1141 | (b.roll_off & 0x07);
1142 let sr = b.symbol_rate & 0x0FFF_FFFF;
1143 out[p + 7] = (sr >> 24) as u8 & 0x0F;
1144 out[p + 8] = (sr >> 16) as u8;
1145 out[p + 9] = (sr >> 8) as u8;
1146 out[p + 10] = sr as u8;
1147 p += S2X_PRIMARY_LEN;
1148 if let Some(isi) = b.input_stream_identifier {
1149 out[p] = isi;
1150 p += 1;
1151 }
1152 if let Some(ts) = b.timeslice_number {
1153 out[p] = ts;
1154 p += 1;
1155 }
1156 out[p..p + b.tail.len()].copy_from_slice(b.tail);
1157 }
1158 ExtensionBody::AudioPreselection(b) => {
1159 out[0] = b.num_preselections << 3;
1160 out[1..1 + b.preselection_loop.len()].copy_from_slice(b.preselection_loop);
1161 }
1162 ExtensionBody::TtmlSubtitling(b) => {
1163 out[..ISO_639_LEN].copy_from_slice(b.iso_639_language_code);
1164 out[ISO_639_LEN] = (b.subtitle_purpose << 2) | (b.tts_suitability & 0x03);
1165 out[ISO_639_LEN + 1] = (u8::from(b.essential_font_usage_flag) << 7)
1166 | (u8::from(b.qualifier_present_flag) << 6)
1167 | (b.dvb_ttml_profile_count & 0x0F);
1168 out[TTML_FIXED_LEN..TTML_FIXED_LEN + b.tail.len()].copy_from_slice(b.tail);
1169 }
1170 ExtensionBody::Raw(s) => out[..s.len()].copy_from_slice(s),
1171 }
1172 }
1173}
1174
1175impl Serialize for ExtensionDescriptor<'_> {
1176 type Error = crate::error::Error;
1177 fn serialized_len(&self) -> usize {
1178 HEADER_LEN + TAG_EXTENSION_LEN + self.body.selector_len()
1179 }
1180
1181 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
1182 let len = self.serialized_len();
1183 if buf.len() < len {
1184 return Err(Error::OutputBufferTooSmall {
1185 need: len,
1186 have: buf.len(),
1187 });
1188 }
1189 let body_len = len - HEADER_LEN;
1190 if body_len > MAX_DESCRIPTOR_LENGTH {
1191 return Err(Error::InvalidDescriptor {
1192 tag: TAG,
1193 reason: "descriptor_length exceeds 255 bytes",
1194 });
1195 }
1196 buf[0] = TAG;
1197 buf[1] = body_len as u8;
1198 buf[HEADER_LEN] = self.tag_extension;
1199 self.body
1200 .write_selector(&mut buf[HEADER_LEN + TAG_EXTENSION_LEN..len]);
1201 Ok(len)
1202 }
1203}
1204
1205impl<'a> Descriptor<'a> for ExtensionDescriptor<'a> {
1206 const TAG: u8 = TAG;
1207 fn descriptor_length(&self) -> u8 {
1208 (self.serialized_len() - HEADER_LEN) as u8
1209 }
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214 use super::*;
1215
1216 fn wrap(tag_ext: u8, sel: &[u8]) -> Vec<u8> {
1218 let mut v = vec![TAG, (sel.len() + 1) as u8, tag_ext];
1219 v.extend_from_slice(sel);
1220 v
1221 }
1222
1223 fn round_trip(d: &ExtensionDescriptor) {
1224 let mut buf = vec![0u8; d.serialized_len()];
1225 d.serialize_into(&mut buf).unwrap();
1226 let re = ExtensionDescriptor::parse(&buf).unwrap();
1227 assert_eq!(*d, re);
1228 }
1229
1230 #[test]
1231 fn parse_rejects_wrong_tag() {
1232 let raw = [0x43, 1, 0x04];
1233 assert!(matches!(
1234 ExtensionDescriptor::parse(&raw).unwrap_err(),
1235 Error::InvalidDescriptor { tag: 0x43, .. }
1236 ));
1237 }
1238
1239 #[test]
1240 fn parse_rejects_empty_body() {
1241 let raw = [TAG, 0];
1242 assert!(matches!(
1243 ExtensionDescriptor::parse(&raw).unwrap_err(),
1244 Error::InvalidDescriptor { tag: TAG, .. }
1245 ));
1246 }
1247
1248 #[test]
1249 fn parse_rejects_truncated_body() {
1250 let raw = [TAG, 3, 0x08];
1252 assert!(matches!(
1253 ExtensionDescriptor::parse(&raw).unwrap_err(),
1254 Error::BufferTooShort { .. }
1255 ));
1256 }
1257
1258 #[test]
1259 fn unknown_tag_round_trips_as_raw() {
1260 let sel = [0xDE, 0xAD, 0xBE, 0xEF];
1262 let bytes = wrap(0x42, &sel);
1263 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1264 assert_eq!(d.tag_extension, 0x42);
1265 assert_eq!(d.kind(), None);
1266 assert!(matches!(d.body, ExtensionBody::Raw(b) if b == sel));
1267 round_trip(&d);
1268 }
1269
1270 #[test]
1271 fn user_defined_tag_preserved() {
1272 let bytes = wrap(0x90, &[0x01, 0x02]);
1273 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1274 assert_eq!(d.tag_extension, 0x90);
1275 assert!(matches!(d.body, ExtensionBody::Raw(_)));
1276 round_trip(&d);
1277 }
1278
1279 #[test]
1280 fn parse_service_relocated() {
1281 let sel = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC];
1282 let bytes = wrap(0x0B, &sel);
1283 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1284 assert_eq!(d.kind(), Some(ExtensionTag::ServiceRelocated));
1285 match &d.body {
1286 ExtensionBody::ServiceRelocated(b) => {
1287 assert_eq!(b.old_original_network_id, 0x1234);
1288 assert_eq!(b.old_transport_stream_id, 0x5678);
1289 assert_eq!(b.old_service_id, 0x9ABC);
1290 }
1291 other => panic!("expected ServiceRelocated, got {other:?}"),
1292 }
1293 round_trip(&d);
1294 }
1295
1296 #[test]
1297 fn parse_message() {
1298 let sel = [0x07, b'e', b'n', b'g', b'H', b'i'];
1299 let bytes = wrap(0x08, &sel);
1300 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1301 match &d.body {
1302 ExtensionBody::Message(b) => {
1303 assert_eq!(b.message_id, 0x07);
1304 assert_eq!(b.iso_639_language_code, b"eng");
1305 assert_eq!(b.text, b"Hi");
1306 }
1307 other => panic!("expected Message, got {other:?}"),
1308 }
1309 round_trip(&d);
1310 }
1311
1312 #[test]
1313 fn parse_supplementary_audio_with_language() {
1314 let flags = 0x80 | (0x17 << 2) | 0x02 | 0x01;
1317 let sel = [flags, b'f', b'r', b'e', 0xAA];
1318 let bytes = wrap(0x06, &sel);
1319 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1320 match &d.body {
1321 ExtensionBody::SupplementaryAudio(b) => {
1322 assert!(b.mix_type);
1323 assert_eq!(b.editorial_classification, 0x17);
1324 assert!(b.language_code_present);
1325 assert_eq!(b.iso_639_language_code, Some(b"fre".as_slice()));
1326 assert_eq!(b.private_data, &[0xAA]);
1327 }
1328 other => panic!("expected SupplementaryAudio, got {other:?}"),
1329 }
1330 round_trip(&d);
1331 }
1332
1333 #[test]
1334 fn parse_supplementary_audio_no_language() {
1335 let flags = ((0x01 << 2) & 0x7C) | 0x02; let sel = [flags];
1337 let bytes = wrap(0x06, &sel);
1338 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1339 match &d.body {
1340 ExtensionBody::SupplementaryAudio(b) => {
1341 assert!(!b.language_code_present);
1342 assert_eq!(b.iso_639_language_code, None);
1343 assert!(b.private_data.is_empty());
1344 }
1345 other => panic!("expected SupplementaryAudio, got {other:?}"),
1346 }
1347 round_trip(&d);
1348 }
1349
1350 #[test]
1351 fn parse_c2_delivery_system() {
1352 let packed = (0x02 << 6) | (0x01 << 3) | 0x01;
1353 let sel = [0x05, 0x09, 0x12, 0x34, 0x56, 0x78, packed];
1354 let bytes = wrap(0x0D, &sel);
1355 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1356 match &d.body {
1357 ExtensionBody::C2DeliverySystem(b) => {
1358 assert_eq!(b.plp_id, 0x05);
1359 assert_eq!(b.data_slice_id, 0x09);
1360 assert_eq!(b.c2_system_tuning_frequency, 0x1234_5678);
1361 assert_eq!(b.c2_system_tuning_frequency_type, 0x02);
1362 assert_eq!(b.active_ofdm_symbol_duration, 0x01);
1363 assert_eq!(b.guard_interval, 0x01);
1364 }
1365 other => panic!("expected C2DeliverySystem, got {other:?}"),
1366 }
1367 round_trip(&d);
1368 }
1369
1370 #[test]
1371 fn parse_c2_bundle_two_entries() {
1372 let entry = |off: u8| {
1373 let packed = (0x01u8 << 6) | 0x01; [off, off + 1, 0x00, 0x00, 0x10, 0x00, packed, 0x80]
1376 };
1377 let mut sel = Vec::new();
1378 sel.extend_from_slice(&entry(0x01));
1379 sel.extend_from_slice(&entry(0x05));
1380 let bytes = wrap(0x16, &sel);
1381 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1382 match &d.body {
1383 ExtensionBody::C2BundleDeliverySystem(b) => {
1384 assert_eq!(b.entries.len(), 2);
1385 assert_eq!(b.entries[0].plp_id, 0x01);
1386 assert!(b.entries[0].primary_channel);
1387 assert_eq!(b.entries[1].plp_id, 0x05);
1388 assert_eq!(b.entries[1].guard_interval, 0x01);
1389 }
1390 other => panic!("expected C2BundleDeliverySystem, got {other:?}"),
1391 }
1392 round_trip(&d);
1393 }
1394
1395 #[test]
1396 fn parse_c2_bundle_rejects_partial_entry() {
1397 let sel = [0x01, 0x02, 0x03]; let bytes = wrap(0x16, &sel);
1399 assert!(matches!(
1400 ExtensionDescriptor::parse(&bytes).unwrap_err(),
1401 Error::InvalidDescriptor { tag: TAG, .. }
1402 ));
1403 }
1404
1405 #[test]
1406 fn parse_uri_linkage_with_polling() {
1407 let uri = b"http://x";
1408 let mut sel = vec![0x00, uri.len() as u8];
1409 sel.extend_from_slice(uri);
1410 sel.extend_from_slice(&0x1234u16.to_be_bytes());
1411 sel.push(0xFE); let bytes = wrap(0x13, &sel);
1413 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1414 match &d.body {
1415 ExtensionBody::UriLinkage(b) => {
1416 assert_eq!(b.uri_linkage_type, 0x00);
1417 assert_eq!(b.uri, uri);
1418 assert_eq!(b.min_polling_interval, Some(0x1234));
1419 assert_eq!(b.private_data, &[0xFE]);
1420 }
1421 other => panic!("expected UriLinkage, got {other:?}"),
1422 }
1423 round_trip(&d);
1424 }
1425
1426 #[test]
1427 fn parse_uri_linkage_no_polling() {
1428 let uri = b"dvb:";
1430 let mut sel = vec![0x02, uri.len() as u8];
1431 sel.extend_from_slice(uri);
1432 let bytes = wrap(0x13, &sel);
1433 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1434 match &d.body {
1435 ExtensionBody::UriLinkage(b) => {
1436 assert_eq!(b.min_polling_interval, None);
1437 assert!(b.private_data.is_empty());
1438 }
1439 other => panic!("expected UriLinkage, got {other:?}"),
1440 }
1441 round_trip(&d);
1442 }
1443
1444 #[test]
1445 fn parse_uri_linkage_rejects_overrun() {
1446 let sel = [0x02, 0x10, 0xAA]; let bytes = wrap(0x13, &sel);
1448 assert!(matches!(
1449 ExtensionDescriptor::parse(&bytes).unwrap_err(),
1450 Error::InvalidDescriptor { tag: TAG, .. }
1451 ));
1452 }
1453
1454 #[test]
1455 fn parse_ac4_full() {
1456 let sel = [0xC0, 0x80 | (0x02 << 5), 0x02, 0x11, 0x22, 0x33];
1458 let bytes = wrap(0x15, &sel);
1459 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1460 match &d.body {
1461 ExtensionBody::Ac4(b) => {
1462 assert!(b.ac4_config_flag);
1463 assert!(b.ac4_toc_flag);
1464 assert_eq!(b.ac4_dialog_enhancement_enabled, Some(true));
1465 assert_eq!(b.ac4_channel_mode, Some(0x02));
1466 assert_eq!(b.toc, Some([0x11u8, 0x22].as_slice()));
1467 assert_eq!(b.additional_info, &[0x33]);
1468 }
1469 other => panic!("expected Ac4, got {other:?}"),
1470 }
1471 round_trip(&d);
1472 }
1473
1474 #[test]
1475 fn parse_ac4_minimal() {
1476 let sel = [0x00]; let bytes = wrap(0x15, &sel);
1478 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1479 match &d.body {
1480 ExtensionBody::Ac4(b) => {
1481 assert!(!b.ac4_config_flag);
1482 assert!(!b.ac4_toc_flag);
1483 assert_eq!(b.toc, None);
1484 assert!(b.additional_info.is_empty());
1485 }
1486 other => panic!("expected Ac4, got {other:?}"),
1487 }
1488 round_trip(&d);
1489 }
1490
1491 #[test]
1492 fn parse_t2_minimal() {
1493 let sel = [0x07, 0x12, 0x34];
1495 let bytes = wrap(0x04, &sel);
1496 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1497 match &d.body {
1498 ExtensionBody::T2DeliverySystem(b) => {
1499 assert_eq!(b.plp_id, 0x07);
1500 assert_eq!(b.t2_system_id, 0x1234);
1501 assert_eq!(b.siso_miso, None);
1502 assert!(b.cell_loop.is_empty());
1503 }
1504 other => panic!("expected T2DeliverySystem, got {other:?}"),
1505 }
1506 round_trip(&d);
1507 }
1508
1509 #[test]
1510 fn parse_t2_with_flags_and_cells() {
1511 let b0 = (0x01 << 6) | (0x02 << 2);
1513 let b1 = (0x03 << 5) | (0x04 << 2) | 0x02; let sel = [0x07, 0x12, 0x34, b0, b1, 0xCA, 0xFE];
1515 let bytes = wrap(0x04, &sel);
1516 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1517 match &d.body {
1518 ExtensionBody::T2DeliverySystem(b) => {
1519 assert_eq!(b.siso_miso, Some(0x01));
1520 assert_eq!(b.bandwidth, Some(0x02));
1521 assert_eq!(b.guard_interval, Some(0x03));
1522 assert_eq!(b.transmission_mode, Some(0x04));
1523 assert_eq!(b.other_frequency_flag, Some(true));
1524 assert_eq!(b.tfs_flag, Some(false));
1525 assert_eq!(b.cell_loop, &[0xCA, 0xFE]);
1526 }
1527 other => panic!("expected T2DeliverySystem, got {other:?}"),
1528 }
1529 round_trip(&d);
1530 }
1531
1532 #[test]
1533 fn parse_s2x_primary_with_isi_and_timeslice() {
1534 let b0 = 0x05 << 3;
1536 let b1 = (0x02 << 6) | 0x01; let mut sel = vec![b0, b1];
1538 sel.extend_from_slice(&0x0102_0304u32.to_be_bytes()); sel.extend_from_slice(&0x00C8u16.to_be_bytes()); sel.push((1 << 7) | (0x02 << 5) | (1 << 4) | 0x03); let sr: u32 = 0x0AB_CDEF; sel.push((sr >> 24) as u8 & 0x0F);
1543 sel.push((sr >> 16) as u8);
1544 sel.push((sr >> 8) as u8);
1545 sel.push(sr as u8);
1546 sel.push(0x42); sel.push(0x09); let bytes = wrap(0x17, &sel);
1549 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1550 match &d.body {
1551 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
1552 assert_eq!(b.receiver_profiles, 0x05);
1553 assert_eq!(b.s2x_mode, 2);
1554 assert!(!b.scrambling_sequence_selector);
1555 assert_eq!(b.ts_gs_s2x_mode, 1);
1556 assert_eq!(b.frequency, 0x0102_0304);
1557 assert_eq!(b.orbital_position, 0x00C8);
1558 assert!(b.west_east_flag);
1559 assert_eq!(b.polarization, 2);
1560 assert!(b.multiple_input_stream_flag);
1561 assert_eq!(b.roll_off, 3);
1562 assert_eq!(b.symbol_rate, 0x0AB_CDEF);
1563 assert_eq!(b.input_stream_identifier, Some(0x42));
1564 assert_eq!(b.timeslice_number, Some(0x09));
1565 assert!(b.tail.is_empty());
1566 }
1567 other => panic!("expected S2X, got {other:?}"),
1568 }
1569 round_trip(&d);
1570 }
1571
1572 #[test]
1573 fn parse_s2x_with_scrambling_index() {
1574 let b0 = 0x01 << 3;
1575 let b1 = (0x01 << 6) | 0x20; let mut sel = vec![b0, b1];
1577 sel.push(0x02);
1579 sel.push(0xAB);
1580 sel.push(0xCD);
1581 sel.extend_from_slice(&0u32.to_be_bytes()); sel.extend_from_slice(&0u16.to_be_bytes()); sel.push(0x00); sel.extend_from_slice(&[0, 0, 0, 0]); let bytes = wrap(0x17, &sel);
1586 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1587 match &d.body {
1588 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
1589 assert!(b.scrambling_sequence_selector);
1590 assert_eq!(b.scrambling_sequence_index, Some(0x2ABCD));
1591 assert_eq!(b.input_stream_identifier, None);
1592 assert_eq!(b.timeslice_number, None);
1593 }
1594 other => panic!("expected S2X, got {other:?}"),
1595 }
1596 round_trip(&d);
1597 }
1598
1599 #[test]
1600 fn parse_s2x_mode3_tail_preserved() {
1601 let b0 = 0x01 << 3;
1603 let b1 = 0x03 << 6; let mut sel = vec![b0, b1];
1605 sel.extend_from_slice(&0u32.to_be_bytes());
1606 sel.extend_from_slice(&0u16.to_be_bytes());
1607 sel.push(0x00); sel.extend_from_slice(&[0, 0, 0, 0]); sel.extend_from_slice(&[0xAA, 0xBB, 0xCC]); let bytes = wrap(0x17, &sel);
1611 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1612 match &d.body {
1613 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
1614 assert_eq!(b.s2x_mode, 3);
1615 assert_eq!(b.timeslice_number, None);
1616 assert_eq!(b.tail, &[0xAA, 0xBB, 0xCC]);
1617 }
1618 other => panic!("expected S2X, got {other:?}"),
1619 }
1620 round_trip(&d);
1621 }
1622
1623 #[test]
1624 fn parse_audio_preselection_keeps_loop_raw() {
1625 let sel = [0x03 << 3, 0xAA, 0xBB, 0xCC];
1627 let bytes = wrap(0x19, &sel);
1628 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1629 match &d.body {
1630 ExtensionBody::AudioPreselection(b) => {
1631 assert_eq!(b.num_preselections, 3);
1632 assert_eq!(b.preselection_loop, &[0xAA, 0xBB, 0xCC]);
1633 }
1634 other => panic!("expected AudioPreselection, got {other:?}"),
1635 }
1636 round_trip(&d);
1637 }
1638
1639 #[test]
1640 fn parse_ttml_subtitling() {
1641 let b3 = (0x10 << 2) | 0x01;
1643 let b4 = 0x01; let sel = [b'e', b'n', b'g', b3, b4, 0x00, 0x02, b'h', b'i'];
1645 let bytes = wrap(0x20, &sel);
1646 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1647 match &d.body {
1648 ExtensionBody::TtmlSubtitling(b) => {
1649 assert_eq!(b.iso_639_language_code, b"eng");
1650 assert_eq!(b.subtitle_purpose, 0x10);
1651 assert_eq!(b.tts_suitability, 0x01);
1652 assert!(!b.essential_font_usage_flag);
1653 assert!(!b.qualifier_present_flag);
1654 assert_eq!(b.dvb_ttml_profile_count, 1);
1655 assert_eq!(b.tail, &[0x00, 0x02, b'h', b'i']);
1656 }
1657 other => panic!("expected TtmlSubtitling, got {other:?}"),
1658 }
1659 round_trip(&d);
1660 }
1661
1662 #[test]
1663 fn parse_target_region_loop_raw() {
1664 let sel = [b'g', b'b', b'r', 0x01, 0x02, 0x03];
1665 let bytes = wrap(0x09, &sel);
1666 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1667 match &d.body {
1668 ExtensionBody::TargetRegion(b) => {
1669 assert_eq!(b.country_code, b"gbr");
1670 assert_eq!(b.region_loop, &[0x01, 0x02, 0x03]);
1671 }
1672 other => panic!("expected TargetRegion, got {other:?}"),
1673 }
1674 round_trip(&d);
1675 }
1676
1677 #[test]
1678 fn parse_target_region_name_loop_raw() {
1679 let sel = [b'g', b'b', b'r', b'e', b'n', b'g', 0x44, 0x55];
1680 let bytes = wrap(0x0A, &sel);
1681 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1682 match &d.body {
1683 ExtensionBody::TargetRegionName(b) => {
1684 assert_eq!(b.country_code, b"gbr");
1685 assert_eq!(b.iso_639_language_code, b"eng");
1686 assert_eq!(b.region_loop, &[0x44, 0x55]);
1687 }
1688 other => panic!("expected TargetRegionName, got {other:?}"),
1689 }
1690 round_trip(&d);
1691 }
1692
1693 #[test]
1694 fn parse_network_change_notify_loop_raw() {
1695 let sel = [0x00, 0x01, 0x05, 0xAA, 0xBB];
1696 let bytes = wrap(0x07, &sel);
1697 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1698 match &d.body {
1699 ExtensionBody::NetworkChangeNotify(b) => {
1700 assert_eq!(b.cell_loop, &sel);
1701 }
1702 other => panic!("expected NetworkChangeNotify, got {other:?}"),
1703 }
1704 round_trip(&d);
1705 }
1706
1707 #[test]
1708 fn serialize_rejects_too_small_buffer() {
1709 let d = ExtensionDescriptor {
1710 tag_extension: 0x0B,
1711 body: ExtensionBody::ServiceRelocated(ServiceRelocated {
1712 old_original_network_id: 1,
1713 old_transport_stream_id: 2,
1714 old_service_id: 3,
1715 }),
1716 };
1717 let mut tiny = [0u8; 2];
1718 assert!(matches!(
1719 d.serialize_into(&mut tiny).unwrap_err(),
1720 Error::OutputBufferTooSmall { .. }
1721 ));
1722 }
1723
1724 #[test]
1725 fn descriptor_length_matches_body() {
1726 let d = ExtensionDescriptor {
1727 tag_extension: 0x08,
1728 body: ExtensionBody::Message(Message {
1729 message_id: 1,
1730 iso_639_language_code: b"eng",
1731 text: b"hello",
1732 }),
1733 };
1734 assert_eq!(d.descriptor_length(), 10);
1736 }
1737
1738 #[cfg(feature = "serde")]
1745 #[test]
1746 fn serde_round_trip_owned_body() {
1747 let typed = ExtensionDescriptor {
1748 tag_extension: 0x0D,
1749 body: ExtensionBody::C2DeliverySystem(C2DeliverySystem {
1750 plp_id: 1,
1751 data_slice_id: 2,
1752 c2_system_tuning_frequency: 0xDEAD_BEEF,
1753 c2_system_tuning_frequency_type: 1,
1754 active_ofdm_symbol_duration: 2,
1755 guard_interval: 3,
1756 }),
1757 };
1758 let json = serde_json::to_string(&typed).unwrap();
1759 let back: ExtensionDescriptor = serde_json::from_str(&json).unwrap();
1760 assert_eq!(typed, back);
1761 }
1762
1763 #[cfg(feature = "serde")]
1766 #[test]
1767 fn serde_serializes_borrowed_body() {
1768 let raw = ExtensionDescriptor {
1769 tag_extension: 0x42,
1770 body: ExtensionBody::Raw(&[0x01, 0x02, 0x03]),
1771 };
1772 let json = serde_json::to_string(&raw).unwrap();
1773 assert!(json.contains("\"tag_extension\":66"));
1774 assert!(json.contains("\"Raw\""));
1775
1776 let msg = ExtensionDescriptor {
1777 tag_extension: 0x08,
1778 body: ExtensionBody::Message(Message {
1779 message_id: 7,
1780 iso_639_language_code: b"eng",
1781 text: b"hi",
1782 }),
1783 };
1784 let json = serde_json::to_string(&msg).unwrap();
1785 assert!(json.contains("\"message_id\":7"));
1786 }
1787}