Skip to main content

dvb_si/descriptors/extension/
sh_delivery_system.rs

1//! SH Delivery System Descriptor — ETSI EN 300 468 §6.4.6.2 (tag_extension 0x05).
2use 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// ---------------------------------------------------------------------------
10//  SH-specific enums (Tables 120, 123-132)
11// ---------------------------------------------------------------------------
12
13/// Diversity mode — ETSI EN 300 468 Table 120.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16#[non_exhaustive]
17pub enum ShDiversityMode {
18    /// No diversity.
19    NoDiversity,
20    /// paTS only (0b1000).
21    PaTsOnly,
22    /// paTS + FEC diversity, FEC at link (0b1101).
23    FecAtLink,
24    /// paTS + FEC diversity, FEC at PHY (0b1110).
25    FecAtPhy,
26    /// paTS + FEC diversity, FEC at PHY and link (0b1111).
27    FecAtPhyAndLink,
28    /// Reserved / future use.
29    Reserved(u8),
30}
31
32impl ShDiversityMode {
33    #[must_use]
34    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
35    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
48    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    /// Human-readable spec name per the governing Table.
61    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/// Polarization for SH — ETSI EN 300 468 Table 123.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize))]
76#[non_exhaustive]
77pub enum ShPolarization {
78    /// Linear horizontal.
79    LinearHorizontal,
80    /// Linear vertical.
81    LinearVertical,
82    /// Circular left.
83    CircularLeft,
84    /// Circular right.
85    CircularRight,
86    /// Reserved / future use.
87    Reserved(u8),
88}
89
90impl ShPolarization {
91    #[must_use]
92    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
93    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
105    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    /// Human-readable spec name per the governing Table.
117    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/// Roll-off factor for SH — ETSI EN 300 468 Table 124.
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130#[cfg_attr(feature = "serde", derive(serde::Serialize))]
131#[non_exhaustive]
132pub enum ShRollOff {
133    /// α = 0.35.
134    Alpha035,
135    /// α = 0.25.
136    Alpha025,
137    /// α = 0.15.
138    Alpha015,
139    /// Reserved / future use.
140    Reserved(u8),
141}
142
143impl ShRollOff {
144    #[must_use]
145    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
146    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
157    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    /// Human-readable spec name per the governing Table.
168    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/// Modulation mode for TDM — ETSI EN 300 468 Table 125.
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize))]
181#[non_exhaustive]
182pub enum ShModulationModeType {
183    /// QPSK.
184    Qpsk,
185    /// 8PSK.
186    Psk8,
187    /// 16APSK.
188    Apsk16,
189    /// Reserved / future use.
190    Reserved(u8),
191}
192
193impl ShModulationModeType {
194    #[must_use]
195    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
196    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
207    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    /// Human-readable spec name per the governing Table.
218    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/// Code rate for SH — ETSI EN 300 468 Table 126 (4 bits).
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230#[cfg_attr(feature = "serde", derive(serde::Serialize))]
231#[non_exhaustive]
232pub enum ShCodeRate {
233    /// 1/5 standard.
234    Rate1_5Standard,
235    /// 2/9 standard.
236    Rate2_9Standard,
237    /// 1/4 standard.
238    Rate1_4Standard,
239    /// 2/7 standard.
240    Rate2_7Standard,
241    /// 1/3 standard.
242    Rate1_3Standard,
243    /// 1/3 complementary.
244    Rate1_3Complementary,
245    /// 2/5 standard.
246    Rate2_5Standard,
247    /// 2/5 complementary.
248    Rate2_5Complementary,
249    /// 1/2 standard.
250    Rate1_2Standard,
251    /// 1/2 complementary.
252    Rate1_2Complementary,
253    /// 2/3 standard.
254    Rate2_3Standard,
255    /// 2/3 complementary.
256    Rate2_3Complementary,
257    /// Reserved / future use.
258    Reserved(u8),
259}
260
261impl ShCodeRate {
262    #[must_use]
263    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
264    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
284    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    /// Human-readable spec name per the governing Table.
304    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/// Bandwidth for OFDM — ETSI EN 300 468 Table 128 (3 bits).
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325#[cfg_attr(feature = "serde", derive(serde::Serialize))]
326#[non_exhaustive]
327pub enum ShBandwidth {
328    /// 8 MHz.
329    Mhz8,
330    /// 7 MHz.
331    Mhz7,
332    /// 6 MHz.
333    Mhz6,
334    /// 5 MHz.
335    Mhz5,
336    /// 1.7 MHz.
337    Mhz1_7,
338    /// Reserved / future use.
339    Reserved(u8),
340}
341
342impl ShBandwidth {
343    #[must_use]
344    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
345    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
358    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    /// Human-readable spec name per the governing Table.
371    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/// Constellation and hierarchy — ETSI EN 300 468 Table 130 (3 bits).
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385#[cfg_attr(feature = "serde", derive(serde::Serialize))]
386#[non_exhaustive]
387pub enum ShConstellationAndHierarchy {
388    /// QPSK.
389    Qpsk,
390    /// 16QAM, non-hierarchical.
391    Qam16NonHierarchical,
392    /// 16QAM, hierarchical, α = 1.
393    Qam16Alpha1,
394    /// 16QAM, hierarchical, α = 2.
395    Qam16Alpha2,
396    /// 16QAM, hierarchical, α = 3.
397    Qam16Alpha3,
398    /// Reserved / future use.
399    Reserved(u8),
400}
401
402impl ShConstellationAndHierarchy {
403    #[must_use]
404    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
405    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
418    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    /// Human-readable spec name per the governing Table.
431    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/// Guard interval for OFDM — ETSI EN 300 468 Table 131 (2 bits).
444#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445#[cfg_attr(feature = "serde", derive(serde::Serialize))]
446#[non_exhaustive]
447pub enum ShGuardInterval {
448    /// 1/32.
449    G1_32,
450    /// 1/16.
451    G1_16,
452    /// 1/8.
453    G1_8,
454    /// 1/4.
455    G1_4,
456    /// Reserved / future use.
457    Reserved(u8),
458}
459
460impl ShGuardInterval {
461    #[must_use]
462    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
463    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
475    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    /// Human-readable spec name per the governing Table.
487    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/// Transmission mode for OFDM — ETSI EN 300 468 Table 132 (2 bits).
499#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500#[cfg_attr(feature = "serde", derive(serde::Serialize))]
501#[non_exhaustive]
502pub enum ShTransmissionMode {
503    /// 1k mode.
504    Mode1k,
505    /// 2k mode.
506    Mode2k,
507    /// 4k mode.
508    Mode4k,
509    /// 8k mode.
510    Mode8k,
511    /// Reserved / future use.
512    Reserved(u8),
513}
514
515impl ShTransmissionMode {
516    #[must_use]
517    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
518    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
530    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    /// Human-readable spec name per the governing Table.
542    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// ---------------------------------------------------------------------------
554//  Structs
555// ---------------------------------------------------------------------------
556
557/// SH_delivery_system body (Table 119, §6.4.6.2). The modulation loop is
558/// unfolded; `modulation_type` (Table 121) selects Tdm/Ofdm,
559/// `interleaver_presence` (Table 122) gates the interleaver, and
560/// `interleaver_type` selects its layout. Diversity mode: Table 120.
561#[derive(Debug, Clone, PartialEq, Eq)]
562#[cfg_attr(feature = "serde", derive(serde::Serialize))]
563pub struct ShDeliverySystem {
564    /// `diversity_mode` — Table 120.
565    pub diversity_mode: ShDiversityMode,
566    /// Modulation entries (the loop to end of body).
567    pub modulations: Vec<ShModulation>,
568}
569
570/// One modulation entry in the SH_delivery_system loop.
571#[derive(Debug, Clone, Copy, PartialEq, Eq)]
572#[cfg_attr(feature = "serde", derive(serde::Serialize))]
573pub struct ShModulation {
574    /// Modulation parameters; the variant encodes `modulation_type` (Table 121).
575    pub modulation: ShModulationMode,
576    /// Interleaver block; `Some` encodes `interleaver_presence==1`, the variant
577    /// encodes `interleaver_type`.
578    pub interleaver: Option<ShInterleaver>,
579}
580
581/// Modulation mode for an SH delivery system entry (Table 121).
582#[derive(Debug, Clone, Copy, PartialEq, Eq)]
583#[cfg_attr(feature = "serde", derive(serde::Serialize))]
584pub enum ShModulationMode {
585    /// `modulation_type == 0` — Time-Domain Multiplex.
586    Tdm {
587        /// polarization (2 bits) — Table 123.
588        polarization: ShPolarization,
589        /// roll_off (2 bits) — Table 124.
590        roll_off: ShRollOff,
591        /// modulation_mode (2 bits) — Table 125.
592        modulation_mode: ShModulationModeType,
593        /// code_rate (4 bits) — Table 126.
594        code_rate: ShCodeRate,
595        /// symbol_rate (5 bits) — Table 127 (raw).
596        symbol_rate: u8,
597    },
598    /// `modulation_type == 1` — OFDM.
599    Ofdm {
600        /// bandwidth (3 bits) — Table 128.
601        bandwidth: ShBandwidth,
602        /// priority (1 bit) — Table 129.
603        priority: bool,
604        /// constellation_and_hierarchy (3 bits) — Table 130.
605        constellation_and_hierarchy: ShConstellationAndHierarchy,
606        /// code_rate (4 bits) — Table 126.
607        code_rate: ShCodeRate,
608        /// guard_interval (2 bits) — Table 131.
609        guard_interval: ShGuardInterval,
610        /// transmission_mode (2 bits) — Table 132.
611        transmission_mode: ShTransmissionMode,
612        /// common_frequency (1 bit).
613        common_frequency: bool,
614    },
615}
616
617/// Interleaver block for an SH modulation entry (Table 122).
618#[derive(Debug, Clone, Copy, PartialEq, Eq)]
619#[cfg_attr(feature = "serde", derive(serde::Serialize))]
620pub enum ShInterleaver {
621    /// `interleaver_type == 0` — full interleaver parameters.
622    Type0 {
623        /// common_multiplier (6 bits).
624        common_multiplier: u8,
625        /// nof_late_taps (6 bits).
626        nof_late_taps: u8,
627        /// nof_slices (6 bits).
628        nof_slices: u8,
629        /// slice_distance (8 bits).
630        slice_distance: u8,
631        /// non_late_increments (6 bits).
632        non_late_increments: u8,
633    },
634    /// `interleaver_type == 1` — common_multiplier only.
635    Type1 {
636        /// common_multiplier (6 bits).
637        common_multiplier: u8,
638    },
639}
640
641// ---------------------------------------------------------------------------
642//  Parse / Serialize
643// ---------------------------------------------------------------------------
644
645impl<'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}