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/// SH_delivery_system body (Table 119, §6.4.6.2). The modulation loop is
10/// unfolded; `modulation_type` (Table 121) selects Tdm/Ofdm,
11/// `interleaver_presence` (Table 122) gates the interleaver, and
12/// `interleaver_type` selects its layout. Diversity mode: Table 120.
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize))]
15pub struct ShDeliverySystem {
16    /// `diversity_mode` (4 bits) — Table 120.
17    pub diversity_mode: u8,
18    /// Modulation entries (the loop to end of body).
19    pub modulations: Vec<ShModulation>,
20}
21
22/// One modulation entry in the SH_delivery_system loop.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize))]
25pub struct ShModulation {
26    /// Modulation parameters; the variant encodes `modulation_type` (Table 121).
27    pub modulation: ShModulationMode,
28    /// Interleaver block; `Some` encodes `interleaver_presence==1`, the variant
29    /// encodes `interleaver_type`.
30    pub interleaver: Option<ShInterleaver>,
31}
32
33/// Modulation mode for an SH delivery system entry (Table 121).
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36pub enum ShModulationMode {
37    /// `modulation_type == 0` — Time-Domain Multiplex.
38    Tdm {
39        /// polarization (2 bits) — Table 123.
40        polarization: u8,
41        /// roll_off (2 bits) — Table 124.
42        roll_off: u8,
43        /// modulation_mode (2 bits) — Table 125.
44        modulation_mode: u8,
45        /// code_rate (4 bits) — Table 126.
46        code_rate: u8,
47        /// symbol_rate (5 bits) — Table 127.
48        symbol_rate: u8,
49    },
50    /// `modulation_type == 1` — OFDM.
51    Ofdm {
52        /// bandwidth (3 bits) — Table 128.
53        bandwidth: u8,
54        /// priority (1 bit) — Table 129.
55        priority: bool,
56        /// constellation_and_hierarchy (3 bits) — Table 130.
57        constellation_and_hierarchy: u8,
58        /// code_rate (4 bits) — Table 126.
59        code_rate: u8,
60        /// guard_interval (2 bits) — Table 131.
61        guard_interval: u8,
62        /// transmission_mode (2 bits) — Table 132.
63        transmission_mode: u8,
64        /// common_frequency (1 bit).
65        common_frequency: bool,
66    },
67}
68
69/// Interleaver block for an SH modulation entry (Table 122).
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize))]
72pub enum ShInterleaver {
73    /// `interleaver_type == 0` — full interleaver parameters.
74    Type0 {
75        /// common_multiplier (6 bits).
76        common_multiplier: u8,
77        /// nof_late_taps (6 bits).
78        nof_late_taps: u8,
79        /// nof_slices (6 bits).
80        nof_slices: u8,
81        /// slice_distance (8 bits).
82        slice_distance: u8,
83        /// non_late_increments (6 bits).
84        non_late_increments: u8,
85    },
86    /// `interleaver_type == 1` — common_multiplier only.
87    Type1 {
88        /// common_multiplier (6 bits).
89        common_multiplier: u8,
90    },
91}
92
93impl<'a> Parse<'a> for ShDeliverySystem {
94    type Error = crate::error::Error;
95    fn parse(sel: &'a [u8]) -> Result<Self> {
96        if sel.is_empty() {
97            return Err(Error::BufferTooShort {
98                need: 1,
99                have: sel.len(),
100                what: "SH_delivery_system body",
101            });
102        }
103        let diversity_mode = sel[0] >> 4;
104        let mut pos = 1;
105        let mut modulations = Vec::new();
106        while pos < sel.len() {
107            // Need flags byte + 2 modulation bytes
108            if sel.len() - pos < 3 {
109                return Err(Error::BufferTooShort {
110                    need: pos + 3,
111                    have: sel.len(),
112                    what: "SH_delivery_system body",
113                });
114            }
115            let flags = sel[pos];
116            let modulation_type = (flags >> 7) & 0x01;
117            let interleaver_presence = (flags >> 6) & 0x01;
118            let interleaver_type = (flags >> 5) & 0x01;
119            let mb0 = sel[pos + 1];
120            let mb1 = sel[pos + 2];
121            pos += 3;
122
123            let modulation = if modulation_type == 0 {
124                // TDM
125                let polarization = mb0 >> 6;
126                let roll_off = (mb0 >> 4) & 0x03;
127                let modulation_mode = (mb0 >> 2) & 0x03;
128                let code_rate = ((mb0 & 0x03) << 2) | (mb1 >> 6);
129                let symbol_rate = (mb1 >> 1) & 0x1F;
130                ShModulationMode::Tdm {
131                    polarization,
132                    roll_off,
133                    modulation_mode,
134                    code_rate,
135                    symbol_rate,
136                }
137            } else {
138                // OFDM
139                let bandwidth = mb0 >> 5;
140                let priority = ((mb0 >> 4) & 0x01) != 0;
141                let constellation_and_hierarchy = (mb0 >> 1) & 0x07;
142                let code_rate = ((mb0 & 0x01) << 3) | (mb1 >> 5);
143                let guard_interval = (mb1 >> 3) & 0x03;
144                let transmission_mode = (mb1 >> 1) & 0x03;
145                let common_frequency = (mb1 & 0x01) != 0;
146                ShModulationMode::Ofdm {
147                    bandwidth,
148                    priority,
149                    constellation_and_hierarchy,
150                    code_rate,
151                    guard_interval,
152                    transmission_mode,
153                    common_frequency,
154                }
155            };
156
157            let interleaver = if interleaver_presence == 1 {
158                if interleaver_type == 0 {
159                    // 4-byte Type0 interleaver block
160                    if sel.len() - pos < 4 {
161                        return Err(Error::BufferTooShort {
162                            need: pos + 4,
163                            have: sel.len(),
164                            what: "SH_delivery_system body",
165                        });
166                    }
167                    let b0 = sel[pos];
168                    let b1 = sel[pos + 1];
169                    let b2 = sel[pos + 2];
170                    let b3 = sel[pos + 3];
171                    let common_multiplier = b0 >> 2;
172                    let nof_late_taps = ((b0 & 0x03) << 4) | (b1 >> 4);
173                    let nof_slices = ((b1 & 0x0F) << 2) | (b2 >> 6);
174                    let slice_distance = ((b2 & 0x3F) << 2) | (b3 >> 6);
175                    let non_late_increments = b3 & 0x3F;
176                    pos += 4;
177                    Some(ShInterleaver::Type0 {
178                        common_multiplier,
179                        nof_late_taps,
180                        nof_slices,
181                        slice_distance,
182                        non_late_increments,
183                    })
184                } else {
185                    // 1-byte Type1 interleaver block
186                    if sel.len() - pos < 1 {
187                        return Err(Error::BufferTooShort {
188                            need: pos + 1,
189                            have: sel.len(),
190                            what: "SH_delivery_system body",
191                        });
192                    }
193                    let common_multiplier = sel[pos] >> 2;
194                    pos += 1;
195                    Some(ShInterleaver::Type1 { common_multiplier })
196                }
197            } else {
198                None
199            };
200
201            modulations.push(ShModulation {
202                modulation,
203                interleaver,
204            });
205        }
206        Ok(ShDeliverySystem {
207            diversity_mode,
208            modulations,
209        })
210    }
211}
212
213impl Serialize for ShDeliverySystem {
214    type Error = crate::error::Error;
215    fn serialized_len(&self) -> usize {
216        1 + self
217            .modulations
218            .iter()
219            .map(|m| {
220                3 + match &m.interleaver {
221                    None => 0,
222                    Some(ShInterleaver::Type0 { .. }) => 4,
223                    Some(ShInterleaver::Type1 { .. }) => 1,
224                }
225            })
226            .sum::<usize>()
227    }
228    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
229        let len = self.serialized_len();
230        if buf.len() < len {
231            return Err(Error::OutputBufferTooSmall {
232                need: len,
233                have: buf.len(),
234            });
235        }
236        // diversity_mode(4) | reserved_future_use(4)=1
237        buf[0] = (self.diversity_mode << 4) | 0x0F;
238        let mut p = 1;
239        for m in &self.modulations {
240            let modulation_type_bit = matches!(m.modulation, ShModulationMode::Ofdm { .. }) as u8;
241            let interleaver_presence_bit = m.interleaver.is_some() as u8;
242            let interleaver_type_bit =
243                matches!(m.interleaver, Some(ShInterleaver::Type1 { .. })) as u8;
244            // modulation_type(1) | interleaver_presence(1) | interleaver_type(1)
245            //   | reserved_future_use(5)=1
246            buf[p] = (modulation_type_bit << 7)
247                | (interleaver_presence_bit << 6)
248                | (interleaver_type_bit << 5)
249                | 0x1F;
250            p += 1;
251
252            match &m.modulation {
253                ShModulationMode::Tdm {
254                    polarization,
255                    roll_off,
256                    modulation_mode,
257                    code_rate,
258                    symbol_rate,
259                } => {
260                    buf[p] = (polarization << 6)
261                        | ((roll_off & 0x03) << 4)
262                        | ((modulation_mode & 0x03) << 2)
263                        | ((code_rate >> 2) & 0x03);
264                    // code_rate low 2 | symbol_rate(5) | reserved_future_use(1)=1
265                    buf[p + 1] = ((code_rate & 0x03) << 6) | ((symbol_rate & 0x1F) << 1) | 0x01;
266                }
267                ShModulationMode::Ofdm {
268                    bandwidth,
269                    priority,
270                    constellation_and_hierarchy,
271                    code_rate,
272                    guard_interval,
273                    transmission_mode,
274                    common_frequency,
275                } => {
276                    buf[p] = (bandwidth << 5)
277                        | (u8::from(*priority) << 4)
278                        | ((constellation_and_hierarchy & 0x07) << 1)
279                        | ((code_rate >> 3) & 0x01);
280                    buf[p + 1] = ((code_rate & 0x07) << 5)
281                        | ((guard_interval & 0x03) << 3)
282                        | ((transmission_mode & 0x03) << 1)
283                        | u8::from(*common_frequency);
284                }
285            }
286            p += 2;
287
288            match &m.interleaver {
289                Some(ShInterleaver::Type0 {
290                    common_multiplier,
291                    nof_late_taps,
292                    nof_slices,
293                    slice_distance,
294                    non_late_increments,
295                }) => {
296                    let cm = common_multiplier & 0x3F;
297                    let lt = nof_late_taps & 0x3F;
298                    let ns = nof_slices & 0x3F;
299                    let sd = slice_distance;
300                    let nli = non_late_increments & 0x3F;
301                    buf[p] = (cm << 2) | (lt >> 4);
302                    buf[p + 1] = ((lt & 0x0F) << 4) | (ns >> 2);
303                    buf[p + 2] = ((ns & 0x03) << 6) | (sd >> 2);
304                    buf[p + 3] = ((sd & 0x03) << 6) | nli;
305                    p += 4;
306                }
307                Some(ShInterleaver::Type1 { common_multiplier }) => {
308                    // common_multiplier(6) | reserved_future_use(2)=1
309                    buf[p] = ((common_multiplier & 0x3F) << 2) | 0x03;
310                    p += 1;
311                }
312                None => {}
313            }
314        }
315        Ok(len)
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322    use crate::descriptors::extension::test_support::*;
323    use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
324
325    #[test]
326    fn parse_sh_tdm_no_interleaver() {
327        // diversity_mode=0x0D (1101), one TDM entry, no interleaver.
328        // TDM: polarization=2, roll_off=1, modulation_mode=3,
329        //      code_rate=10 (1010), symbol_rate=21 (10101).
330        // flags: mod_type=0, inter_pres=0, inter_type=0 -> 0x00
331        // mb0 = (2<<6)|(1<<4)|(3<<2)|((10>>2)&3) = 0x80|0x10|0x0C|0x02 = 0x9E
332        // mb1 = ((10&3)<<6)|(21<<1) = (2<<6)|42 = 0x80|0x2A = 0xAA
333        let sel = [0xD0, 0x00, 0x9E, 0xAA];
334        let bytes = wrap(0x05, &sel);
335        let d = ExtensionDescriptor::parse(&bytes).unwrap();
336        assert_eq!(d.kind(), Some(ExtensionTag::ShDeliverySystem));
337        match &d.body {
338            ExtensionBody::ShDeliverySystem(b) => {
339                assert_eq!(b.diversity_mode, 0x0D);
340                assert_eq!(b.modulations.len(), 1);
341                let m = &b.modulations[0];
342                assert!(m.interleaver.is_none());
343                match &m.modulation {
344                    ShModulationMode::Tdm {
345                        polarization,
346                        roll_off,
347                        modulation_mode,
348                        code_rate,
349                        symbol_rate,
350                    } => {
351                        assert_eq!(*polarization, 2);
352                        assert_eq!(*roll_off, 1);
353                        assert_eq!(*modulation_mode, 3);
354                        assert_eq!(*code_rate, 10);
355                        assert_eq!(*symbol_rate, 21);
356                    }
357                    other => panic!("expected Tdm, got {other:?}"),
358                }
359            }
360            other => panic!("expected ShDeliverySystem, got {other:?}"),
361        }
362        round_trip(&d);
363    }
364
365    #[test]
366    fn parse_sh_ofdm_interleaver_type1() {
367        // diversity_mode=0x05, one OFDM entry, interleaver Type1.
368        // OFDM: bw=1, pri=true, cah=2, cr=11(0x0B), gi=3, tm=2, cf=true
369        // Interleaver Type1: cm=21(0x15)
370        // flags: mod_type=1, inter_pres=1, inter_type=1 -> 0xE0
371        // mb0 = (1<<5)|(1<<4)|(2<<1)|((11>>3)&1) = 0x20|0x10|0x04|0x01 = 0x35
372        // mb1 = ((11&7)<<5)|(3<<3)|(2<<1)|1 = 0x60|0x18|0x04|0x01 = 0x7D
373        // Type1 byte: (21<<2) = 0x54
374        let sel = [0x50, 0xE0, 0x35, 0x7D, 0x54];
375        let bytes = wrap(0x05, &sel);
376        let d = ExtensionDescriptor::parse(&bytes).unwrap();
377        match &d.body {
378            ExtensionBody::ShDeliverySystem(b) => {
379                assert_eq!(b.diversity_mode, 0x05);
380                assert_eq!(b.modulations.len(), 1);
381                let m = &b.modulations[0];
382                match &m.modulation {
383                    ShModulationMode::Ofdm {
384                        bandwidth,
385                        priority,
386                        constellation_and_hierarchy,
387                        code_rate,
388                        guard_interval,
389                        transmission_mode,
390                        common_frequency,
391                    } => {
392                        assert_eq!(*bandwidth, 1);
393                        assert!(*priority);
394                        assert_eq!(*constellation_and_hierarchy, 2);
395                        assert_eq!(*code_rate, 11);
396                        assert_eq!(*guard_interval, 3);
397                        assert_eq!(*transmission_mode, 2);
398                        assert!(*common_frequency);
399                    }
400                    other => panic!("expected Ofdm, got {other:?}"),
401                }
402                match &m.interleaver {
403                    Some(ShInterleaver::Type1 { common_multiplier }) => {
404                        assert_eq!(*common_multiplier, 21);
405                    }
406                    other => panic!("expected Type1 interleaver, got {other:?}"),
407                }
408            }
409            other => panic!("expected ShDeliverySystem, got {other:?}"),
410        }
411        round_trip(&d);
412    }
413
414    #[test]
415    fn parse_sh_tdm_interleaver_type0() {
416        // diversity_mode=0x08, one TDM entry, interleaver Type0.
417        // TDM: pol=0, ro=3, mm=1, cr=5, sr=10
418        // Type0: cm=10, lt=20, ns=30, sd=100, nli=40
419        // flags: mod_type=0, inter_pres=1, inter_type=0 -> 0x40
420        // mb0 = (0<<6)|(3<<4)|(1<<2)|((5>>2)&3) = 0x30|0x04|0x01 = 0x35
421        // mb1 = ((5&3)<<6)|(10<<1) = (1<<6)|20 = 0x40|0x14 = 0x54
422        // Type0 byte0: (10<<2)|(20>>4) = 40|1 = 0x29
423        // Type0 byte1: ((20&15)<<4)|(30>>2) = (4<<4)|7 = 0x47
424        // Type0 byte2: ((30&3)<<6)|(100>>2) = (2<<6)|25 = 0x99
425        // Type0 byte3: ((100&3)<<6)|40 = 0x28
426        let sel = [0x80, 0x40, 0x35, 0x54, 0x29, 0x47, 0x99, 0x28];
427        let bytes = wrap(0x05, &sel);
428        let d = ExtensionDescriptor::parse(&bytes).unwrap();
429        match &d.body {
430            ExtensionBody::ShDeliverySystem(b) => {
431                assert_eq!(b.diversity_mode, 0x08);
432                assert_eq!(b.modulations.len(), 1);
433                let m = &b.modulations[0];
434                match &m.modulation {
435                    ShModulationMode::Tdm {
436                        polarization,
437                        roll_off,
438                        modulation_mode,
439                        code_rate,
440                        symbol_rate,
441                    } => {
442                        assert_eq!(*polarization, 0);
443                        assert_eq!(*roll_off, 3);
444                        assert_eq!(*modulation_mode, 1);
445                        assert_eq!(*code_rate, 5);
446                        assert_eq!(*symbol_rate, 10);
447                    }
448                    other => panic!("expected Tdm, got {other:?}"),
449                }
450                match &m.interleaver {
451                    Some(ShInterleaver::Type0 {
452                        common_multiplier,
453                        nof_late_taps,
454                        nof_slices,
455                        slice_distance,
456                        non_late_increments,
457                    }) => {
458                        assert_eq!(*common_multiplier, 10);
459                        assert_eq!(*nof_late_taps, 20);
460                        assert_eq!(*nof_slices, 30);
461                        assert_eq!(*slice_distance, 100);
462                        assert_eq!(*non_late_increments, 40);
463                    }
464                    other => panic!("expected Type0 interleaver, got {other:?}"),
465                }
466            }
467            other => panic!("expected ShDeliverySystem, got {other:?}"),
468        }
469        round_trip(&d);
470    }
471
472    #[test]
473    fn parse_sh_two_entries_mixed() {
474        // diversity_mode=0x0D
475        // Entry 1: TDM (same as test 1), no interleaver.
476        // Entry 2: OFDM bw=4 pri=false cah=5 cr=9 gi=1 tm=1 cf=false,
477        //          Type0 interleaver cm=15 lt=25 ns=35 sd=50 nli=55
478        // Entry1: flags=0x00, mb0=0x9E, mb1=0xAA
479        // Entry2 flags: 0xC0 (mod=1, pres=1, type=0)
480        // OFDM mb0: (4<<5)|(0<<4)|(5<<1)|((9>>3)&1) = 0x80|0x0A|0x01 = 0x8B
481        // OFDM mb1: ((9&7)<<5)|(1<<3)|(1<<1)|0 = 0x20|0x08|0x02 = 0x2A
482        // Type0 byte0: (15<<2)|(25>>4) = 60|1 = 0x3D
483        // Type0 byte1: ((25&15)<<4)|(35>>2) = (9<<4)|8 = 0x98
484        // Type0 byte2: ((35&3)<<6)|(50>>2) = (3<<6)|12 = 0xCC
485        // Type0 byte3: ((50&3)<<6)|55 = (2<<6)|55 = 0xB7
486        let sel = [
487            0xD0, 0x00, 0x9E, 0xAA, 0xC0, 0x8B, 0x2A, 0x3D, 0x98, 0xCC, 0xB7,
488        ];
489        let bytes = wrap(0x05, &sel);
490        let d = ExtensionDescriptor::parse(&bytes).unwrap();
491        match &d.body {
492            ExtensionBody::ShDeliverySystem(b) => {
493                assert_eq!(b.diversity_mode, 0x0D);
494                assert_eq!(b.modulations.len(), 2);
495                // Entry 1
496                let m0 = &b.modulations[0];
497                assert!(matches!(m0.modulation, ShModulationMode::Tdm { .. }));
498                assert!(m0.interleaver.is_none());
499                // Entry 2
500                let m1 = &b.modulations[1];
501                assert!(matches!(m1.modulation, ShModulationMode::Ofdm { .. }));
502                match &m1.modulation {
503                    ShModulationMode::Ofdm {
504                        bandwidth,
505                        priority,
506                        constellation_and_hierarchy,
507                        code_rate,
508                        ..
509                    } => {
510                        assert_eq!(*bandwidth, 4);
511                        assert!(!priority);
512                        assert_eq!(*constellation_and_hierarchy, 5);
513                        assert_eq!(*code_rate, 9);
514                    }
515                    _ => unreachable!(),
516                }
517                assert!(matches!(m1.interleaver, Some(ShInterleaver::Type0 { .. })));
518            }
519            other => panic!("expected ShDeliverySystem, got {other:?}"),
520        }
521        round_trip(&d);
522    }
523
524    #[test]
525    fn parse_sh_rejects_partial_entry() {
526        // Complete entry followed by a lone flags byte with no modulation block
527        let sel = [0xD0, 0x00, 0x9E, 0xAA, 0x00];
528        let bytes = wrap(0x05, &sel);
529        assert!(matches!(
530            ExtensionDescriptor::parse(&bytes).unwrap_err(),
531            crate::error::Error::BufferTooShort { .. }
532        ));
533    }
534
535    #[test]
536    fn parse_sh_single_diversity_byte() {
537        // Only diversity_mode byte, no modulations.
538        let sel = [0xD0];
539        let bytes = wrap(0x05, &sel);
540        let d = ExtensionDescriptor::parse(&bytes).unwrap();
541        match &d.body {
542            ExtensionBody::ShDeliverySystem(b) => {
543                assert_eq!(b.diversity_mode, 0x0D);
544                assert!(b.modulations.is_empty());
545            }
546            other => panic!("expected ShDeliverySystem, got {other:?}"),
547        }
548        round_trip(&d);
549    }
550
551    #[test]
552    fn parse_sh_rejects_empty_selector() {
553        let bytes = wrap(0x05, &[]);
554        assert!(matches!(
555            ExtensionDescriptor::parse(&bytes).unwrap_err(),
556            crate::error::Error::BufferTooShort { .. }
557        ));
558    }
559
560    #[cfg(feature = "serde")]
561    #[test]
562    fn serde_serialize_sh_delivery_system() {
563        let d = ExtensionDescriptor {
564            tag_extension: 0x05,
565            body: ExtensionBody::ShDeliverySystem(ShDeliverySystem {
566                diversity_mode: 0x0D,
567                modulations: vec![ShModulation {
568                    modulation: ShModulationMode::Ofdm {
569                        bandwidth: 1,
570                        priority: true,
571                        constellation_and_hierarchy: 2,
572                        code_rate: 11,
573                        guard_interval: 3,
574                        transmission_mode: 2,
575                        common_frequency: true,
576                    },
577                    interleaver: Some(ShInterleaver::Type1 {
578                        common_multiplier: 21,
579                    }),
580                }],
581            }),
582        };
583        let json = serde_json::to_string(&d).unwrap();
584        assert!(json.contains("\"tag_extension\":5"));
585        assert!(json.contains("\"shDeliverySystem\""));
586    }
587}