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