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}
72dvb_common::impl_spec_display!(ShDiversityMode, Reserved);
73
74/// Polarization for SH — ETSI EN 300 468 Table 123.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize))]
77#[non_exhaustive]
78pub enum ShPolarization {
79    /// Linear horizontal.
80    LinearHorizontal,
81    /// Linear vertical.
82    LinearVertical,
83    /// Circular left.
84    CircularLeft,
85    /// Circular right.
86    CircularRight,
87    /// Reserved / future use.
88    Reserved(u8),
89}
90
91impl ShPolarization {
92    #[must_use]
93    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
94    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
106    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    /// Human-readable spec name per the governing Table.
118    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/// Roll-off factor for SH — ETSI EN 300 468 Table 124.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize))]
133#[non_exhaustive]
134pub enum ShRollOff {
135    /// α = 0.35.
136    Alpha035,
137    /// α = 0.25.
138    Alpha025,
139    /// α = 0.15.
140    Alpha015,
141    /// Reserved / future use.
142    Reserved(u8),
143}
144
145impl ShRollOff {
146    #[must_use]
147    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
148    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
159    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    /// Human-readable spec name per the governing Table.
170    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/// Modulation mode for TDM — ETSI EN 300 468 Table 125.
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize))]
184#[non_exhaustive]
185pub enum ShModulationModeType {
186    /// QPSK.
187    Qpsk,
188    /// 8PSK.
189    Psk8,
190    /// 16APSK.
191    Apsk16,
192    /// Reserved / future use.
193    Reserved(u8),
194}
195
196impl ShModulationModeType {
197    #[must_use]
198    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
199    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
210    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    /// Human-readable spec name per the governing Table.
221    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/// Code rate for SH — ETSI EN 300 468 Table 126 (4 bits).
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234#[cfg_attr(feature = "serde", derive(serde::Serialize))]
235#[non_exhaustive]
236pub enum ShCodeRate {
237    /// 1/5 standard.
238    Rate1_5Standard,
239    /// 2/9 standard.
240    Rate2_9Standard,
241    /// 1/4 standard.
242    Rate1_4Standard,
243    /// 2/7 standard.
244    Rate2_7Standard,
245    /// 1/3 standard.
246    Rate1_3Standard,
247    /// 1/3 complementary.
248    Rate1_3Complementary,
249    /// 2/5 standard.
250    Rate2_5Standard,
251    /// 2/5 complementary.
252    Rate2_5Complementary,
253    /// 1/2 standard.
254    Rate1_2Standard,
255    /// 1/2 complementary.
256    Rate1_2Complementary,
257    /// 2/3 standard.
258    Rate2_3Standard,
259    /// 2/3 complementary.
260    Rate2_3Complementary,
261    /// Reserved / future use.
262    Reserved(u8),
263}
264
265impl ShCodeRate {
266    #[must_use]
267    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
268    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
288    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    /// Human-readable spec name per the governing Table.
308    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/// Bandwidth for OFDM — ETSI EN 300 468 Table 128 (3 bits).
329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330#[cfg_attr(feature = "serde", derive(serde::Serialize))]
331#[non_exhaustive]
332pub enum ShBandwidth {
333    /// 8 MHz.
334    Mhz8,
335    /// 7 MHz.
336    Mhz7,
337    /// 6 MHz.
338    Mhz6,
339    /// 5 MHz.
340    Mhz5,
341    /// 1.7 MHz.
342    Mhz1_7,
343    /// Reserved / future use.
344    Reserved(u8),
345}
346
347impl ShBandwidth {
348    #[must_use]
349    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
350    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
363    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    /// Human-readable spec name per the governing Table.
376    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/// Constellation and hierarchy — ETSI EN 300 468 Table 130 (3 bits).
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391#[cfg_attr(feature = "serde", derive(serde::Serialize))]
392#[non_exhaustive]
393pub enum ShConstellationAndHierarchy {
394    /// QPSK.
395    Qpsk,
396    /// 16QAM, non-hierarchical.
397    Qam16NonHierarchical,
398    /// 16QAM, hierarchical, α = 1.
399    Qam16Alpha1,
400    /// 16QAM, hierarchical, α = 2.
401    Qam16Alpha2,
402    /// 16QAM, hierarchical, α = 3.
403    Qam16Alpha3,
404    /// Reserved / future use.
405    Reserved(u8),
406}
407
408impl ShConstellationAndHierarchy {
409    #[must_use]
410    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
411    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
424    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    /// Human-readable spec name per the governing Table.
437    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/// Guard interval for OFDM — ETSI EN 300 468 Table 131 (2 bits).
451#[derive(Debug, Clone, Copy, PartialEq, Eq)]
452#[cfg_attr(feature = "serde", derive(serde::Serialize))]
453#[non_exhaustive]
454pub enum ShGuardInterval {
455    /// 1/32.
456    G1_32,
457    /// 1/16.
458    G1_16,
459    /// 1/8.
460    G1_8,
461    /// 1/4.
462    G1_4,
463    /// Reserved / future use.
464    Reserved(u8),
465}
466
467impl ShGuardInterval {
468    #[must_use]
469    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
470    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
482    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    /// Human-readable spec name per the governing Table.
494    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/// Transmission mode for OFDM — ETSI EN 300 468 Table 132 (2 bits).
507#[derive(Debug, Clone, Copy, PartialEq, Eq)]
508#[cfg_attr(feature = "serde", derive(serde::Serialize))]
509#[non_exhaustive]
510pub enum ShTransmissionMode {
511    /// 1k mode.
512    Mode1k,
513    /// 2k mode.
514    Mode2k,
515    /// 4k mode.
516    Mode4k,
517    /// 8k mode.
518    Mode8k,
519    /// Reserved / future use.
520    Reserved(u8),
521}
522
523impl ShTransmissionMode {
524    #[must_use]
525    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
526    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    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
538    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    /// Human-readable spec name per the governing Table.
550    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// ---------------------------------------------------------------------------
563//  Structs
564// ---------------------------------------------------------------------------
565
566/// SH_delivery_system body (Table 119, §6.4.6.2). The modulation loop is
567/// unfolded; `modulation_type` (Table 121) selects Tdm/Ofdm,
568/// `interleaver_presence` (Table 122) gates the interleaver, and
569/// `interleaver_type` selects its layout. Diversity mode: Table 120.
570#[derive(Debug, Clone, PartialEq, Eq)]
571#[cfg_attr(feature = "serde", derive(serde::Serialize))]
572pub struct ShDeliverySystem {
573    /// `diversity_mode` — Table 120.
574    pub diversity_mode: ShDiversityMode,
575    /// Modulation entries (the loop to end of body).
576    pub modulations: Vec<ShModulation>,
577}
578
579/// One modulation entry in the SH_delivery_system loop.
580#[derive(Debug, Clone, Copy, PartialEq, Eq)]
581#[cfg_attr(feature = "serde", derive(serde::Serialize))]
582pub struct ShModulation {
583    /// Modulation parameters; the variant encodes `modulation_type` (Table 121).
584    pub modulation: ShModulationMode,
585    /// Interleaver block; `Some` encodes `interleaver_presence==1`, the variant
586    /// encodes `interleaver_type`.
587    pub interleaver: Option<ShInterleaver>,
588}
589
590/// Modulation mode for an SH delivery system entry (Table 121).
591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
592#[cfg_attr(feature = "serde", derive(serde::Serialize))]
593pub enum ShModulationMode {
594    /// `modulation_type == 0` — Time-Domain Multiplex.
595    Tdm {
596        /// polarization (2 bits) — Table 123.
597        polarization: ShPolarization,
598        /// roll_off (2 bits) — Table 124.
599        roll_off: ShRollOff,
600        /// modulation_mode (2 bits) — Table 125.
601        modulation_mode: ShModulationModeType,
602        /// code_rate (4 bits) — Table 126.
603        code_rate: ShCodeRate,
604        /// symbol_rate (5 bits) — Table 127 (raw).
605        symbol_rate: u8,
606    },
607    /// `modulation_type == 1` — OFDM.
608    Ofdm {
609        /// bandwidth (3 bits) — Table 128.
610        bandwidth: ShBandwidth,
611        /// priority (1 bit) — Table 129.
612        priority: bool,
613        /// constellation_and_hierarchy (3 bits) — Table 130.
614        constellation_and_hierarchy: ShConstellationAndHierarchy,
615        /// code_rate (4 bits) — Table 126.
616        code_rate: ShCodeRate,
617        /// guard_interval (2 bits) — Table 131.
618        guard_interval: ShGuardInterval,
619        /// transmission_mode (2 bits) — Table 132.
620        transmission_mode: ShTransmissionMode,
621        /// common_frequency (1 bit).
622        common_frequency: bool,
623    },
624}
625
626/// Interleaver block for an SH modulation entry (Table 122).
627#[derive(Debug, Clone, Copy, PartialEq, Eq)]
628#[cfg_attr(feature = "serde", derive(serde::Serialize))]
629pub enum ShInterleaver {
630    /// `interleaver_type == 0` — full interleaver parameters.
631    Type0 {
632        /// common_multiplier (6 bits).
633        common_multiplier: u8,
634        /// nof_late_taps (6 bits).
635        nof_late_taps: u8,
636        /// nof_slices (6 bits).
637        nof_slices: u8,
638        /// slice_distance (8 bits).
639        slice_distance: u8,
640        /// non_late_increments (6 bits).
641        non_late_increments: u8,
642    },
643    /// `interleaver_type == 1` — common_multiplier only.
644    Type1 {
645        /// common_multiplier (6 bits).
646        common_multiplier: u8,
647    },
648}
649
650// ---------------------------------------------------------------------------
651//  Parse / Serialize
652// ---------------------------------------------------------------------------
653
654impl<'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}