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