1use super::*;
3use alloc::vec::Vec;
4
5impl<'a> ExtensionBodyDef<'a> for ShDeliverySystem {
6 const TAG_EXTENSION: u8 = 0x05;
7 const NAME: &'static str = "SH_DELIVERY_SYSTEM";
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize))]
17#[non_exhaustive]
18pub enum ShDiversityMode {
19 NoDiversity,
21 PaTsOnly,
23 FecAtLink,
25 FecAtPhy,
27 FecAtPhyAndLink,
29 Reserved(u8),
31}
32
33impl ShDiversityMode {
34 #[must_use]
35 pub fn from_u8(v: u8) -> Self {
37 match v {
38 0x00 => ShDiversityMode::NoDiversity,
39 0x08 => ShDiversityMode::PaTsOnly,
40 0x0D => ShDiversityMode::FecAtLink,
41 0x0E => ShDiversityMode::FecAtPhy,
42 0x0F => ShDiversityMode::FecAtPhyAndLink,
43 other => ShDiversityMode::Reserved(other),
44 }
45 }
46
47 #[must_use]
48 pub fn to_u8(self) -> u8 {
50 match self {
51 ShDiversityMode::NoDiversity => 0x00,
52 ShDiversityMode::PaTsOnly => 0x08,
53 ShDiversityMode::FecAtLink => 0x0D,
54 ShDiversityMode::FecAtPhy => 0x0E,
55 ShDiversityMode::FecAtPhyAndLink => 0x0F,
56 ShDiversityMode::Reserved(v) => v,
57 }
58 }
59
60 #[must_use]
61 pub fn name(self) -> &'static str {
63 match self {
64 ShDiversityMode::NoDiversity => "no diversity",
65 ShDiversityMode::PaTsOnly => "paTS only",
66 ShDiversityMode::FecAtLink => "paTS + FEC diversity, FEC at link",
67 ShDiversityMode::FecAtPhy => "paTS + FEC diversity, FEC at PHY",
68 ShDiversityMode::FecAtPhyAndLink => "paTS + FEC diversity, FEC at PHY and link",
69 ShDiversityMode::Reserved(_) => "reserved",
70 }
71 }
72}
73dvb_common::impl_spec_display!(ShDiversityMode, Reserved);
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize))]
78#[non_exhaustive]
79pub enum ShPolarization {
80 LinearHorizontal,
82 LinearVertical,
84 CircularLeft,
86 CircularRight,
88 Reserved(u8),
90}
91
92impl ShPolarization {
93 #[must_use]
94 pub fn from_u8(v: u8) -> Self {
96 match v {
97 0 => Self::LinearHorizontal,
98 1 => Self::LinearVertical,
99 2 => Self::CircularLeft,
100 3 => Self::CircularRight,
101 other => Self::Reserved(other),
102 }
103 }
104
105 #[must_use]
106 pub fn to_u8(self) -> u8 {
108 match self {
109 Self::LinearHorizontal => 0,
110 Self::LinearVertical => 1,
111 Self::CircularLeft => 2,
112 Self::CircularRight => 3,
113 Self::Reserved(v) => v,
114 }
115 }
116
117 #[must_use]
118 pub fn name(self) -> &'static str {
120 match self {
121 Self::LinearHorizontal => "linear horizontal",
122 Self::LinearVertical => "linear vertical",
123 Self::CircularLeft => "circular left",
124 Self::CircularRight => "circular right",
125 Self::Reserved(_) => "reserved",
126 }
127 }
128}
129dvb_common::impl_spec_display!(ShPolarization, Reserved);
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize))]
134#[non_exhaustive]
135pub enum ShRollOff {
136 Alpha035,
138 Alpha025,
140 Alpha015,
142 Reserved(u8),
144}
145
146impl ShRollOff {
147 #[must_use]
148 pub fn from_u8(v: u8) -> Self {
150 match v {
151 0 => Self::Alpha035,
152 1 => Self::Alpha025,
153 2 => Self::Alpha015,
154 other => Self::Reserved(other),
155 }
156 }
157
158 #[must_use]
159 pub fn to_u8(self) -> u8 {
161 match self {
162 Self::Alpha035 => 0,
163 Self::Alpha025 => 1,
164 Self::Alpha015 => 2,
165 Self::Reserved(v) => v,
166 }
167 }
168
169 #[must_use]
170 pub fn name(self) -> &'static str {
172 match self {
173 Self::Alpha035 => "α = 0.35",
174 Self::Alpha025 => "α = 0.25",
175 Self::Alpha015 => "α = 0.15",
176 Self::Reserved(_) => "reserved",
177 }
178 }
179}
180dvb_common::impl_spec_display!(ShRollOff, Reserved);
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize))]
185#[non_exhaustive]
186pub enum ShModulationModeType {
187 Qpsk,
189 Psk8,
191 Apsk16,
193 Reserved(u8),
195}
196
197impl ShModulationModeType {
198 #[must_use]
199 pub fn from_u8(v: u8) -> Self {
201 match v {
202 0 => Self::Qpsk,
203 1 => Self::Psk8,
204 2 => Self::Apsk16,
205 other => Self::Reserved(other),
206 }
207 }
208
209 #[must_use]
210 pub fn to_u8(self) -> u8 {
212 match self {
213 Self::Qpsk => 0,
214 Self::Psk8 => 1,
215 Self::Apsk16 => 2,
216 Self::Reserved(v) => v,
217 }
218 }
219
220 #[must_use]
221 pub fn name(self) -> &'static str {
223 match self {
224 Self::Qpsk => "QPSK",
225 Self::Psk8 => "8PSK",
226 Self::Apsk16 => "16APSK",
227 Self::Reserved(_) => "reserved",
228 }
229 }
230}
231dvb_common::impl_spec_display!(ShModulationModeType, Reserved);
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235#[cfg_attr(feature = "serde", derive(serde::Serialize))]
236#[non_exhaustive]
237pub enum ShCodeRate {
238 Rate1_5Standard,
240 Rate2_9Standard,
242 Rate1_4Standard,
244 Rate2_7Standard,
246 Rate1_3Standard,
248 Rate1_3Complementary,
250 Rate2_5Standard,
252 Rate2_5Complementary,
254 Rate1_2Standard,
256 Rate1_2Complementary,
258 Rate2_3Standard,
260 Rate2_3Complementary,
262 Reserved(u8),
264}
265
266impl ShCodeRate {
267 #[must_use]
268 pub fn from_u8(v: u8) -> Self {
270 match v {
271 0x00 => ShCodeRate::Rate1_5Standard,
272 0x01 => ShCodeRate::Rate2_9Standard,
273 0x02 => ShCodeRate::Rate1_4Standard,
274 0x03 => ShCodeRate::Rate2_7Standard,
275 0x04 => ShCodeRate::Rate1_3Standard,
276 0x05 => ShCodeRate::Rate1_3Complementary,
277 0x06 => ShCodeRate::Rate2_5Standard,
278 0x07 => ShCodeRate::Rate2_5Complementary,
279 0x08 => ShCodeRate::Rate1_2Standard,
280 0x09 => ShCodeRate::Rate1_2Complementary,
281 0x0A => ShCodeRate::Rate2_3Standard,
282 0x0B => ShCodeRate::Rate2_3Complementary,
283 other => ShCodeRate::Reserved(other),
284 }
285 }
286
287 #[must_use]
288 pub fn to_u8(self) -> u8 {
290 match self {
291 ShCodeRate::Rate1_5Standard => 0x00,
292 ShCodeRate::Rate2_9Standard => 0x01,
293 ShCodeRate::Rate1_4Standard => 0x02,
294 ShCodeRate::Rate2_7Standard => 0x03,
295 ShCodeRate::Rate1_3Standard => 0x04,
296 ShCodeRate::Rate1_3Complementary => 0x05,
297 ShCodeRate::Rate2_5Standard => 0x06,
298 ShCodeRate::Rate2_5Complementary => 0x07,
299 ShCodeRate::Rate1_2Standard => 0x08,
300 ShCodeRate::Rate1_2Complementary => 0x09,
301 ShCodeRate::Rate2_3Standard => 0x0A,
302 ShCodeRate::Rate2_3Complementary => 0x0B,
303 ShCodeRate::Reserved(v) => v,
304 }
305 }
306
307 #[must_use]
308 pub fn name(self) -> &'static str {
310 match self {
311 ShCodeRate::Rate1_5Standard => "1/5 standard",
312 ShCodeRate::Rate2_9Standard => "2/9 standard",
313 ShCodeRate::Rate1_4Standard => "1/4 standard",
314 ShCodeRate::Rate2_7Standard => "2/7 standard",
315 ShCodeRate::Rate1_3Standard => "1/3 standard",
316 ShCodeRate::Rate1_3Complementary => "1/3 complementary",
317 ShCodeRate::Rate2_5Standard => "2/5 standard",
318 ShCodeRate::Rate2_5Complementary => "2/5 complementary",
319 ShCodeRate::Rate1_2Standard => "1/2 standard",
320 ShCodeRate::Rate1_2Complementary => "1/2 complementary",
321 ShCodeRate::Rate2_3Standard => "2/3 standard",
322 ShCodeRate::Rate2_3Complementary => "2/3 complementary",
323 ShCodeRate::Reserved(_) => "reserved",
324 }
325 }
326}
327dvb_common::impl_spec_display!(ShCodeRate, Reserved);
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
331#[cfg_attr(feature = "serde", derive(serde::Serialize))]
332#[non_exhaustive]
333pub enum ShBandwidth {
334 Mhz8,
336 Mhz7,
338 Mhz6,
340 Mhz5,
342 Mhz1_7,
344 Reserved(u8),
346}
347
348impl ShBandwidth {
349 #[must_use]
350 pub fn from_u8(v: u8) -> Self {
352 match v {
353 0 => Self::Mhz8,
354 1 => Self::Mhz7,
355 2 => Self::Mhz6,
356 3 => Self::Mhz5,
357 4 => Self::Mhz1_7,
358 other => Self::Reserved(other),
359 }
360 }
361
362 #[must_use]
363 pub fn to_u8(self) -> u8 {
365 match self {
366 Self::Mhz8 => 0,
367 Self::Mhz7 => 1,
368 Self::Mhz6 => 2,
369 Self::Mhz5 => 3,
370 Self::Mhz1_7 => 4,
371 Self::Reserved(v) => v,
372 }
373 }
374
375 #[must_use]
376 pub fn name(self) -> &'static str {
378 match self {
379 Self::Mhz8 => "8 MHz",
380 Self::Mhz7 => "7 MHz",
381 Self::Mhz6 => "6 MHz",
382 Self::Mhz5 => "5 MHz",
383 Self::Mhz1_7 => "1.7 MHz",
384 Self::Reserved(_) => "reserved",
385 }
386 }
387}
388dvb_common::impl_spec_display!(ShBandwidth, Reserved);
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392#[cfg_attr(feature = "serde", derive(serde::Serialize))]
393#[non_exhaustive]
394pub enum ShConstellationAndHierarchy {
395 Qpsk,
397 Qam16NonHierarchical,
399 Qam16Alpha1,
401 Qam16Alpha2,
403 Qam16Alpha3,
405 Reserved(u8),
407}
408
409impl ShConstellationAndHierarchy {
410 #[must_use]
411 pub fn from_u8(v: u8) -> Self {
413 match v {
414 0 => Self::Qpsk,
415 1 => Self::Qam16NonHierarchical,
416 2 => Self::Qam16Alpha1,
417 3 => Self::Qam16Alpha2,
418 4 => Self::Qam16Alpha3,
419 other => Self::Reserved(other),
420 }
421 }
422
423 #[must_use]
424 pub fn to_u8(self) -> u8 {
426 match self {
427 Self::Qpsk => 0,
428 Self::Qam16NonHierarchical => 1,
429 Self::Qam16Alpha1 => 2,
430 Self::Qam16Alpha2 => 3,
431 Self::Qam16Alpha3 => 4,
432 Self::Reserved(v) => v,
433 }
434 }
435
436 #[must_use]
437 pub fn name(self) -> &'static str {
439 match self {
440 Self::Qpsk => "QPSK",
441 Self::Qam16NonHierarchical => "16QAM, non-hierarchical",
442 Self::Qam16Alpha1 => "16QAM, α = 1",
443 Self::Qam16Alpha2 => "16QAM, α = 2",
444 Self::Qam16Alpha3 => "16QAM, α = 3",
445 Self::Reserved(_) => "reserved",
446 }
447 }
448}
449dvb_common::impl_spec_display!(ShConstellationAndHierarchy, Reserved);
450
451#[derive(Debug, Clone, Copy, PartialEq, Eq)]
453#[cfg_attr(feature = "serde", derive(serde::Serialize))]
454#[non_exhaustive]
455pub enum ShGuardInterval {
456 G1_32,
458 G1_16,
460 G1_8,
462 G1_4,
464 Reserved(u8),
466}
467
468impl ShGuardInterval {
469 #[must_use]
470 pub fn from_u8(v: u8) -> Self {
472 match v {
473 0 => Self::G1_32,
474 1 => Self::G1_16,
475 2 => Self::G1_8,
476 3 => Self::G1_4,
477 other => Self::Reserved(other),
478 }
479 }
480
481 #[must_use]
482 pub fn to_u8(self) -> u8 {
484 match self {
485 Self::G1_32 => 0,
486 Self::G1_16 => 1,
487 Self::G1_8 => 2,
488 Self::G1_4 => 3,
489 Self::Reserved(v) => v,
490 }
491 }
492
493 #[must_use]
494 pub fn name(self) -> &'static str {
496 match self {
497 Self::G1_32 => "1/32",
498 Self::G1_16 => "1/16",
499 Self::G1_8 => "1/8",
500 Self::G1_4 => "1/4",
501 Self::Reserved(_) => "reserved",
502 }
503 }
504}
505dvb_common::impl_spec_display!(ShGuardInterval, Reserved);
506
507#[derive(Debug, Clone, Copy, PartialEq, Eq)]
509#[cfg_attr(feature = "serde", derive(serde::Serialize))]
510#[non_exhaustive]
511pub enum ShTransmissionMode {
512 Mode1k,
514 Mode2k,
516 Mode4k,
518 Mode8k,
520 Reserved(u8),
522}
523
524impl ShTransmissionMode {
525 #[must_use]
526 pub fn from_u8(v: u8) -> Self {
528 match v {
529 0 => Self::Mode1k,
530 1 => Self::Mode2k,
531 2 => Self::Mode4k,
532 3 => Self::Mode8k,
533 other => Self::Reserved(other),
534 }
535 }
536
537 #[must_use]
538 pub fn to_u8(self) -> u8 {
540 match self {
541 Self::Mode1k => 0,
542 Self::Mode2k => 1,
543 Self::Mode4k => 2,
544 Self::Mode8k => 3,
545 Self::Reserved(v) => v,
546 }
547 }
548
549 #[must_use]
550 pub fn name(self) -> &'static str {
552 match self {
553 Self::Mode1k => "1k mode",
554 Self::Mode2k => "2k mode",
555 Self::Mode4k => "4k mode",
556 Self::Mode8k => "8k mode",
557 Self::Reserved(_) => "reserved",
558 }
559 }
560}
561dvb_common::impl_spec_display!(ShTransmissionMode, Reserved);
562
563#[derive(Debug, Clone, PartialEq, Eq)]
572#[cfg_attr(feature = "serde", derive(serde::Serialize))]
573pub struct ShDeliverySystem {
574 pub diversity_mode: ShDiversityMode,
576 pub modulations: Vec<ShModulation>,
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Eq)]
582#[cfg_attr(feature = "serde", derive(serde::Serialize))]
583pub struct ShModulation {
584 pub modulation: ShModulationMode,
586 pub interleaver: Option<ShInterleaver>,
589}
590
591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
593#[cfg_attr(feature = "serde", derive(serde::Serialize))]
594#[non_exhaustive]
595pub enum ShModulationMode {
596 Tdm {
598 polarization: ShPolarization,
600 roll_off: ShRollOff,
602 modulation_mode: ShModulationModeType,
604 code_rate: ShCodeRate,
606 symbol_rate: u8,
608 },
609 Ofdm {
611 bandwidth: ShBandwidth,
613 priority: bool,
615 constellation_and_hierarchy: ShConstellationAndHierarchy,
617 code_rate: ShCodeRate,
619 guard_interval: ShGuardInterval,
621 transmission_mode: ShTransmissionMode,
623 common_frequency: bool,
625 },
626}
627
628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
630#[cfg_attr(feature = "serde", derive(serde::Serialize))]
631#[non_exhaustive]
632pub enum ShInterleaver {
633 Type0 {
635 common_multiplier: u8,
637 nof_late_taps: u8,
639 nof_slices: u8,
641 slice_distance: u8,
643 non_late_increments: u8,
645 },
646 Type1 {
648 common_multiplier: u8,
650 },
651}
652
653impl<'a> Parse<'a> for ShDeliverySystem {
658 type Error = crate::error::Error;
659 fn parse(sel: &'a [u8]) -> Result<Self> {
660 if sel.is_empty() {
661 return Err(Error::BufferTooShort {
662 need: 1,
663 have: sel.len(),
664 what: "SH_delivery_system body",
665 });
666 }
667 let diversity_mode = ShDiversityMode::from_u8(sel[0] >> 4);
668 let mut pos = 1;
669 let mut modulations = Vec::new();
670 while pos < sel.len() {
671 if sel.len() - pos < 3 {
672 return Err(Error::BufferTooShort {
673 need: pos + 3,
674 have: sel.len(),
675 what: "SH_delivery_system body",
676 });
677 }
678 let flags = sel[pos];
679 let modulation_type = (flags >> 7) & 0x01;
680 let interleaver_presence = (flags >> 6) & 0x01;
681 let interleaver_type = (flags >> 5) & 0x01;
682 let mb0 = sel[pos + 1];
683 let mb1 = sel[pos + 2];
684 pos += 3;
685
686 let modulation = if modulation_type == 0 {
687 let polarization = ShPolarization::from_u8(mb0 >> 6);
688 let roll_off = ShRollOff::from_u8((mb0 >> 4) & 0x03);
689 let modulation_mode = ShModulationModeType::from_u8((mb0 >> 2) & 0x03);
690 let code_rate_raw = ((mb0 & 0x03) << 2) | (mb1 >> 6);
691 let code_rate = ShCodeRate::from_u8(code_rate_raw);
692 let symbol_rate = (mb1 >> 1) & 0x1F;
693 ShModulationMode::Tdm {
694 polarization,
695 roll_off,
696 modulation_mode,
697 code_rate,
698 symbol_rate,
699 }
700 } else {
701 let bandwidth = ShBandwidth::from_u8(mb0 >> 5);
702 let priority = ((mb0 >> 4) & 0x01) != 0;
703 let constellation_and_hierarchy =
704 ShConstellationAndHierarchy::from_u8((mb0 >> 1) & 0x07);
705 let code_rate_raw = ((mb0 & 0x01) << 3) | (mb1 >> 5);
706 let code_rate = ShCodeRate::from_u8(code_rate_raw);
707 let guard_interval = ShGuardInterval::from_u8((mb1 >> 3) & 0x03);
708 let transmission_mode = ShTransmissionMode::from_u8((mb1 >> 1) & 0x03);
709 let common_frequency = (mb1 & 0x01) != 0;
710 ShModulationMode::Ofdm {
711 bandwidth,
712 priority,
713 constellation_and_hierarchy,
714 code_rate,
715 guard_interval,
716 transmission_mode,
717 common_frequency,
718 }
719 };
720
721 let interleaver = if interleaver_presence == 1 {
722 if interleaver_type == 0 {
723 if sel.len() - pos < 4 {
724 return Err(Error::BufferTooShort {
725 need: pos + 4,
726 have: sel.len(),
727 what: "SH_delivery_system body",
728 });
729 }
730 let b0 = sel[pos];
731 let b1 = sel[pos + 1];
732 let b2 = sel[pos + 2];
733 let b3 = sel[pos + 3];
734 let common_multiplier = b0 >> 2;
735 let nof_late_taps = ((b0 & 0x03) << 4) | (b1 >> 4);
736 let nof_slices = ((b1 & 0x0F) << 2) | (b2 >> 6);
737 let slice_distance = ((b2 & 0x3F) << 2) | (b3 >> 6);
738 let non_late_increments = b3 & 0x3F;
739 pos += 4;
740 Some(ShInterleaver::Type0 {
741 common_multiplier,
742 nof_late_taps,
743 nof_slices,
744 slice_distance,
745 non_late_increments,
746 })
747 } else {
748 if sel.len() - pos < 1 {
749 return Err(Error::BufferTooShort {
750 need: pos + 1,
751 have: sel.len(),
752 what: "SH_delivery_system body",
753 });
754 }
755 let common_multiplier = sel[pos] >> 2;
756 pos += 1;
757 Some(ShInterleaver::Type1 { common_multiplier })
758 }
759 } else {
760 None
761 };
762
763 modulations.push(ShModulation {
764 modulation,
765 interleaver,
766 });
767 }
768 Ok(ShDeliverySystem {
769 diversity_mode,
770 modulations,
771 })
772 }
773}
774
775impl Serialize for ShDeliverySystem {
776 type Error = crate::error::Error;
777 fn serialized_len(&self) -> usize {
778 1 + self
779 .modulations
780 .iter()
781 .map(|m| {
782 3 + match &m.interleaver {
783 None => 0,
784 Some(ShInterleaver::Type0 { .. }) => 4,
785 Some(ShInterleaver::Type1 { .. }) => 1,
786 }
787 })
788 .sum::<usize>()
789 }
790 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
791 let len = self.serialized_len();
792 if buf.len() < len {
793 return Err(Error::OutputBufferTooSmall {
794 need: len,
795 have: buf.len(),
796 });
797 }
798 buf[0] = (self.diversity_mode.to_u8() << 4) | 0x0F;
799 let mut p = 1;
800 for m in &self.modulations {
801 let modulation_type_bit = matches!(m.modulation, ShModulationMode::Ofdm { .. }) as u8;
802 let interleaver_presence_bit = m.interleaver.is_some() as u8;
803 let interleaver_type_bit =
804 matches!(m.interleaver, Some(ShInterleaver::Type1 { .. })) as u8;
805 buf[p] = (modulation_type_bit << 7)
806 | (interleaver_presence_bit << 6)
807 | (interleaver_type_bit << 5)
808 | 0x1F;
809 p += 1;
810
811 match &m.modulation {
812 ShModulationMode::Tdm {
813 polarization,
814 roll_off,
815 modulation_mode,
816 code_rate,
817 symbol_rate,
818 } => {
819 let cr = code_rate.to_u8();
820 buf[p] = (polarization.to_u8() << 6)
821 | ((roll_off.to_u8() & 0x03) << 4)
822 | ((modulation_mode.to_u8() & 0x03) << 2)
823 | ((cr >> 2) & 0x03);
824 buf[p + 1] = ((cr & 0x03) << 6) | ((symbol_rate & 0x1F) << 1) | 0x01;
825 }
826 ShModulationMode::Ofdm {
827 bandwidth,
828 priority,
829 constellation_and_hierarchy,
830 code_rate,
831 guard_interval,
832 transmission_mode,
833 common_frequency,
834 } => {
835 let cr = code_rate.to_u8();
836 buf[p] = (bandwidth.to_u8() << 5)
837 | (u8::from(*priority) << 4)
838 | ((constellation_and_hierarchy.to_u8() & 0x07) << 1)
839 | ((cr >> 3) & 0x01);
840 buf[p + 1] = ((cr & 0x07) << 5)
841 | ((guard_interval.to_u8() & 0x03) << 3)
842 | ((transmission_mode.to_u8() & 0x03) << 1)
843 | u8::from(*common_frequency);
844 }
845 }
846 p += 2;
847
848 match &m.interleaver {
849 Some(ShInterleaver::Type0 {
850 common_multiplier,
851 nof_late_taps,
852 nof_slices,
853 slice_distance,
854 non_late_increments,
855 }) => {
856 let cm = common_multiplier & 0x3F;
857 let lt = nof_late_taps & 0x3F;
858 let ns = nof_slices & 0x3F;
859 let sd = slice_distance;
860 let nli = non_late_increments & 0x3F;
861 buf[p] = (cm << 2) | (lt >> 4);
862 buf[p + 1] = ((lt & 0x0F) << 4) | (ns >> 2);
863 buf[p + 2] = ((ns & 0x03) << 6) | (sd >> 2);
864 buf[p + 3] = ((sd & 0x03) << 6) | nli;
865 p += 4;
866 }
867 Some(ShInterleaver::Type1 { common_multiplier }) => {
868 buf[p] = ((common_multiplier & 0x3F) << 2) | 0x03;
869 p += 1;
870 }
871 None => {}
872 }
873 }
874 Ok(len)
875 }
876}
877
878#[cfg(test)]
879mod tests {
880 use super::*;
881 use crate::descriptors::extension::test_support::*;
882 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
883
884 #[test]
885 fn sh_diversity_mode_roundtrip() {
886 for b in 0..=0xFFu8 {
887 assert_eq!(ShDiversityMode::from_u8(b).to_u8(), b);
888 }
889 }
890
891 #[test]
892 fn sh_polarization_roundtrip() {
893 for b in 0..=0xFFu8 {
894 assert_eq!(ShPolarization::from_u8(b).to_u8(), b);
895 }
896 }
897
898 #[test]
899 fn sh_roll_off_roundtrip() {
900 for b in 0..=0xFFu8 {
901 assert_eq!(ShRollOff::from_u8(b).to_u8(), b);
902 }
903 }
904
905 #[test]
906 fn sh_modulation_mode_type_roundtrip() {
907 for b in 0..=0xFFu8 {
908 assert_eq!(ShModulationModeType::from_u8(b).to_u8(), b);
909 }
910 }
911
912 #[test]
913 fn sh_code_rate_roundtrip() {
914 for b in 0..=0xFFu8 {
915 assert_eq!(ShCodeRate::from_u8(b).to_u8(), b);
916 }
917 }
918
919 #[test]
920 fn sh_bandwidth_roundtrip() {
921 for b in 0..=0xFFu8 {
922 assert_eq!(ShBandwidth::from_u8(b).to_u8(), b);
923 }
924 }
925
926 #[test]
927 fn sh_constellation_and_hierarchy_roundtrip() {
928 for b in 0..=0xFFu8 {
929 assert_eq!(ShConstellationAndHierarchy::from_u8(b).to_u8(), b);
930 }
931 }
932
933 #[test]
934 fn sh_guard_interval_roundtrip() {
935 for b in 0..=0xFFu8 {
936 assert_eq!(ShGuardInterval::from_u8(b).to_u8(), b);
937 }
938 }
939
940 #[test]
941 fn sh_transmission_mode_roundtrip() {
942 for b in 0..=0xFFu8 {
943 assert_eq!(ShTransmissionMode::from_u8(b).to_u8(), b);
944 }
945 }
946
947 #[test]
948 fn sh_enum_names() {
949 assert_eq!(ShPolarization::LinearHorizontal.name(), "linear horizontal");
950 assert_eq!(ShPolarization::CircularRight.name(), "circular right");
951 assert_eq!(ShRollOff::Alpha035.name(), "α = 0.35");
952 assert_eq!(ShModulationModeType::Psk8.name(), "8PSK");
953 assert_eq!(ShBandwidth::Mhz1_7.name(), "1.7 MHz");
954 assert_eq!(
955 ShConstellationAndHierarchy::Qam16Alpha2.name(),
956 "16QAM, α = 2"
957 );
958 assert_eq!(ShGuardInterval::G1_8.name(), "1/8");
959 assert_eq!(ShTransmissionMode::Mode4k.name(), "4k mode");
960 assert_eq!(ShPolarization::Reserved(5).name(), "reserved");
961 }
962
963 #[test]
964 fn parse_sh_tdm_no_interleaver() {
965 let sel = [0xD0, 0x00, 0x9E, 0xAA];
966 let bytes = wrap(0x05, &sel);
967 let d = ExtensionDescriptor::parse(&bytes).unwrap();
968 assert_eq!(d.kind(), Some(ExtensionTag::ShDeliverySystem));
969 match &d.body {
970 ExtensionBody::ShDeliverySystem(b) => {
971 assert_eq!(b.diversity_mode, ShDiversityMode::FecAtLink);
972 assert_eq!(b.modulations.len(), 1);
973 let m = &b.modulations[0];
974 assert!(m.interleaver.is_none());
975 match &m.modulation {
976 ShModulationMode::Tdm {
977 polarization,
978 roll_off,
979 modulation_mode,
980 code_rate,
981 symbol_rate,
982 } => {
983 assert_eq!(*polarization, ShPolarization::CircularLeft);
984 assert_eq!(*roll_off, ShRollOff::Alpha025);
985 assert_eq!(*modulation_mode, ShModulationModeType::Reserved(3));
986 assert_eq!(*code_rate, ShCodeRate::Rate2_3Standard);
987 assert_eq!(*symbol_rate, 21);
988 }
989 other => panic!("expected Tdm, got {other:?}"),
990 }
991 }
992 other => panic!("expected ShDeliverySystem, got {other:?}"),
993 }
994 round_trip(&d);
995 }
996
997 #[test]
998 fn parse_sh_ofdm_interleaver_type1() {
999 let sel = [0x50, 0xE0, 0x35, 0x7D, 0x54];
1000 let bytes = wrap(0x05, &sel);
1001 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1002 match &d.body {
1003 ExtensionBody::ShDeliverySystem(b) => {
1004 assert_eq!(b.diversity_mode, ShDiversityMode::Reserved(5));
1005 assert_eq!(b.modulations.len(), 1);
1006 let m = &b.modulations[0];
1007 match &m.modulation {
1008 ShModulationMode::Ofdm {
1009 bandwidth,
1010 priority,
1011 constellation_and_hierarchy,
1012 code_rate,
1013 guard_interval,
1014 transmission_mode,
1015 common_frequency,
1016 } => {
1017 assert_eq!(*bandwidth, ShBandwidth::Mhz7);
1018 assert!(*priority);
1019 assert_eq!(
1020 *constellation_and_hierarchy,
1021 ShConstellationAndHierarchy::Qam16Alpha1
1022 );
1023 assert_eq!(*code_rate, ShCodeRate::Rate2_3Complementary);
1024 assert_eq!(*guard_interval, ShGuardInterval::G1_4);
1025 assert_eq!(*transmission_mode, ShTransmissionMode::Mode4k);
1026 assert!(*common_frequency);
1027 }
1028 other => panic!("expected Ofdm, got {other:?}"),
1029 }
1030 match &m.interleaver {
1031 Some(ShInterleaver::Type1 { common_multiplier }) => {
1032 assert_eq!(*common_multiplier, 21);
1033 }
1034 other => panic!("expected Type1 interleaver, got {other:?}"),
1035 }
1036 }
1037 other => panic!("expected ShDeliverySystem, got {other:?}"),
1038 }
1039 round_trip(&d);
1040 }
1041
1042 #[test]
1043 fn parse_sh_tdm_interleaver_type0() {
1044 let sel = [0x80, 0x40, 0x35, 0x54, 0x29, 0x47, 0x99, 0x28];
1045 let bytes = wrap(0x05, &sel);
1046 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1047 match &d.body {
1048 ExtensionBody::ShDeliverySystem(b) => {
1049 assert_eq!(b.diversity_mode, ShDiversityMode::PaTsOnly);
1050 assert_eq!(b.modulations.len(), 1);
1051 let m = &b.modulations[0];
1052 match &m.modulation {
1053 ShModulationMode::Tdm {
1054 polarization,
1055 roll_off,
1056 modulation_mode,
1057 code_rate,
1058 symbol_rate,
1059 } => {
1060 assert_eq!(*polarization, ShPolarization::LinearHorizontal);
1061 assert_eq!(*roll_off, ShRollOff::Reserved(3));
1062 assert_eq!(*modulation_mode, ShModulationModeType::Psk8);
1063 assert_eq!(*code_rate, ShCodeRate::Rate1_3Complementary);
1064 assert_eq!(*symbol_rate, 10);
1065 }
1066 other => panic!("expected Tdm, got {other:?}"),
1067 }
1068 match &m.interleaver {
1069 Some(ShInterleaver::Type0 {
1070 common_multiplier,
1071 nof_late_taps,
1072 nof_slices,
1073 slice_distance,
1074 non_late_increments,
1075 }) => {
1076 assert_eq!(*common_multiplier, 10);
1077 assert_eq!(*nof_late_taps, 20);
1078 assert_eq!(*nof_slices, 30);
1079 assert_eq!(*slice_distance, 100);
1080 assert_eq!(*non_late_increments, 40);
1081 }
1082 other => panic!("expected Type0 interleaver, got {other:?}"),
1083 }
1084 }
1085 other => panic!("expected ShDeliverySystem, got {other:?}"),
1086 }
1087 round_trip(&d);
1088 }
1089
1090 #[test]
1091 fn parse_sh_two_entries_mixed() {
1092 let sel = [
1093 0xD0, 0x00, 0x9E, 0xAA, 0xC0, 0x8B, 0x2A, 0x3D, 0x98, 0xCC, 0xB7,
1094 ];
1095 let bytes = wrap(0x05, &sel);
1096 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1097 match &d.body {
1098 ExtensionBody::ShDeliverySystem(b) => {
1099 assert_eq!(b.diversity_mode, ShDiversityMode::FecAtLink);
1100 assert_eq!(b.modulations.len(), 2);
1101 let m0 = &b.modulations[0];
1102 assert!(matches!(m0.modulation, ShModulationMode::Tdm { .. }));
1103 assert!(m0.interleaver.is_none());
1104 let m1 = &b.modulations[1];
1105 assert!(matches!(m1.modulation, ShModulationMode::Ofdm { .. }));
1106 match &m1.modulation {
1107 ShModulationMode::Ofdm {
1108 bandwidth,
1109 priority,
1110 constellation_and_hierarchy,
1111 code_rate,
1112 ..
1113 } => {
1114 assert_eq!(*bandwidth, ShBandwidth::Mhz1_7);
1115 assert!(!priority);
1116 assert_eq!(
1117 *constellation_and_hierarchy,
1118 ShConstellationAndHierarchy::Reserved(5)
1119 );
1120 assert_eq!(*code_rate, ShCodeRate::Rate1_2Complementary);
1121 }
1122 _ => unreachable!(),
1123 }
1124 assert!(matches!(m1.interleaver, Some(ShInterleaver::Type0 { .. })));
1125 }
1126 other => panic!("expected ShDeliverySystem, got {other:?}"),
1127 }
1128 round_trip(&d);
1129 }
1130
1131 #[test]
1132 fn parse_sh_rejects_partial_entry() {
1133 let sel = [0xD0, 0x00, 0x9E, 0xAA, 0x00];
1134 let bytes = wrap(0x05, &sel);
1135 assert!(matches!(
1136 ExtensionDescriptor::parse(&bytes).unwrap_err(),
1137 crate::error::Error::BufferTooShort { .. }
1138 ));
1139 }
1140
1141 #[test]
1142 fn parse_sh_single_diversity_byte() {
1143 let sel = [0xD0];
1144 let bytes = wrap(0x05, &sel);
1145 let d = ExtensionDescriptor::parse(&bytes).unwrap();
1146 match &d.body {
1147 ExtensionBody::ShDeliverySystem(b) => {
1148 assert_eq!(b.diversity_mode, ShDiversityMode::FecAtLink);
1149 assert!(b.modulations.is_empty());
1150 }
1151 other => panic!("expected ShDeliverySystem, got {other:?}"),
1152 }
1153 round_trip(&d);
1154 }
1155
1156 #[test]
1157 fn parse_sh_rejects_empty_selector() {
1158 let bytes = wrap(0x05, &[]);
1159 assert!(matches!(
1160 ExtensionDescriptor::parse(&bytes).unwrap_err(),
1161 crate::error::Error::BufferTooShort { .. }
1162 ));
1163 }
1164
1165 #[test]
1166 fn tsduck_sh_round_trips() {
1167 let vectors: [(&str, u8); 2] =
1168 [("7f02055f", 0x05), ("7f0d05afff94ac175f68831d8d99ad", 0x05)];
1169 for (hex, _ext) in vectors {
1170 let bytes = from_hex(hex);
1171 let d =
1172 ExtensionDescriptor::parse(&bytes).unwrap_or_else(|e| panic!("parse {hex}: {e:?}"));
1173 let mut out = vec![0u8; d.serialized_len()];
1174 let n = d.serialize_into(&mut out).unwrap();
1175 assert_eq!(out[..n], bytes[..], "byte-exact re-serialize for {hex}");
1176 }
1177 }
1178
1179 #[cfg(feature = "serde")]
1180 #[test]
1181 fn serde_serialize_sh_delivery_system() {
1182 let d = ExtensionDescriptor {
1183 tag_extension: 0x05,
1184 body: ExtensionBody::ShDeliverySystem(ShDeliverySystem {
1185 diversity_mode: ShDiversityMode::FecAtLink,
1186 modulations: vec![ShModulation {
1187 modulation: ShModulationMode::Ofdm {
1188 bandwidth: ShBandwidth::Mhz7,
1189 priority: true,
1190 constellation_and_hierarchy: ShConstellationAndHierarchy::Qam16Alpha1,
1191 code_rate: ShCodeRate::Reserved(11),
1192 guard_interval: ShGuardInterval::G1_4,
1193 transmission_mode: ShTransmissionMode::Mode4k,
1194 common_frequency: true,
1195 },
1196 interleaver: Some(ShInterleaver::Type1 {
1197 common_multiplier: 21,
1198 }),
1199 }],
1200 }),
1201 };
1202 let json = serde_json::to_string(&d).unwrap();
1203 assert!(json.contains("\"tag_extension\":5"));
1204 assert!(json.contains("\"shDeliverySystem\""));
1205 }
1206}