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