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