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