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