Skip to main content

dvb_si/descriptors/extension/
t2_delivery_system.rs

1//! T2 Delivery System Descriptor — ETSI EN 300 468 §6.4.6.3 (tag_extension 0x04).
2use super::*;
3
4impl<'a> ExtensionBodyDef<'a> for T2DeliverySystem {
5    const TAG_EXTENSION: u8 = 0x04;
6    const NAME: &'static str = "T2_DELIVERY_SYSTEM";
7}
8
9// ---------------------------------------------------------------------------
10//  T2-specific enums (Tables 134-137)
11// ---------------------------------------------------------------------------
12
13/// SISO/MISO mode — ETSI EN 300 468 Table 134.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16#[non_exhaustive]
17pub enum T2SisoMiso {
18    /// Single Input, Single Output (SISO).
19    Siso,
20    /// Multiple Input, Single Output (MISO).
21    Miso,
22    /// Reserved / future use.
23    Reserved(u8),
24}
25
26impl T2SisoMiso {
27    #[must_use]
28    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
29    pub fn from_u8(v: u8) -> Self {
30        match v {
31            0 => T2SisoMiso::Siso,
32            1 => T2SisoMiso::Miso,
33            other => T2SisoMiso::Reserved(other),
34        }
35    }
36
37    #[must_use]
38    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
39    pub fn to_u8(self) -> u8 {
40        match self {
41            T2SisoMiso::Siso => 0,
42            T2SisoMiso::Miso => 1,
43            T2SisoMiso::Reserved(v) => v,
44        }
45    }
46
47    #[must_use]
48    /// Human-readable spec name per the governing Table.
49    pub fn name(self) -> &'static str {
50        match self {
51            T2SisoMiso::Siso => "SISO",
52            T2SisoMiso::Miso => "MISO",
53            T2SisoMiso::Reserved(_) => "reserved",
54        }
55    }
56}
57
58/// Bandwidth — ETSI EN 300 468 Table 135.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize))]
61#[non_exhaustive]
62pub enum T2Bandwidth {
63    /// 8 MHz.
64    Mhz8,
65    /// 7 MHz.
66    Mhz7,
67    /// 6 MHz.
68    Mhz6,
69    /// 5 MHz.
70    Mhz5,
71    /// 10 MHz.
72    Mhz10,
73    /// 1.712 MHz.
74    Mhz1_712,
75    /// Reserved / future use.
76    Reserved(u8),
77}
78
79impl T2Bandwidth {
80    #[must_use]
81    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
82    pub fn from_u8(v: u8) -> Self {
83        match v {
84            0 => T2Bandwidth::Mhz8,
85            1 => T2Bandwidth::Mhz7,
86            2 => T2Bandwidth::Mhz6,
87            3 => T2Bandwidth::Mhz5,
88            4 => T2Bandwidth::Mhz10,
89            5 => T2Bandwidth::Mhz1_712,
90            other => T2Bandwidth::Reserved(other),
91        }
92    }
93
94    #[must_use]
95    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
96    pub fn to_u8(self) -> u8 {
97        match self {
98            T2Bandwidth::Mhz8 => 0,
99            T2Bandwidth::Mhz7 => 1,
100            T2Bandwidth::Mhz6 => 2,
101            T2Bandwidth::Mhz5 => 3,
102            T2Bandwidth::Mhz10 => 4,
103            T2Bandwidth::Mhz1_712 => 5,
104            T2Bandwidth::Reserved(v) => v,
105        }
106    }
107
108    #[must_use]
109    /// Human-readable spec name per the governing Table.
110    pub fn name(self) -> &'static str {
111        match self {
112            T2Bandwidth::Mhz8 => "8 MHz",
113            T2Bandwidth::Mhz7 => "7 MHz",
114            T2Bandwidth::Mhz6 => "6 MHz",
115            T2Bandwidth::Mhz5 => "5 MHz",
116            T2Bandwidth::Mhz10 => "10 MHz",
117            T2Bandwidth::Mhz1_712 => "1.712 MHz",
118            T2Bandwidth::Reserved(_) => "reserved",
119        }
120    }
121}
122
123/// Guard interval — ETSI EN 300 468 Table 136.
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize))]
126#[non_exhaustive]
127pub enum T2GuardInterval {
128    /// 1/32.
129    G1_32,
130    /// 1/16.
131    G1_16,
132    /// 1/8.
133    G1_8,
134    /// 1/4.
135    G1_4,
136    /// 1/128.
137    G1_128,
138    /// 19/128.
139    G19_128,
140    /// 19/256.
141    G19_256,
142    /// Reserved / future use.
143    Reserved(u8),
144}
145
146impl T2GuardInterval {
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 => T2GuardInterval::G1_32,
152            1 => T2GuardInterval::G1_16,
153            2 => T2GuardInterval::G1_8,
154            3 => T2GuardInterval::G1_4,
155            4 => T2GuardInterval::G1_128,
156            5 => T2GuardInterval::G19_128,
157            6 => T2GuardInterval::G19_256,
158            other => T2GuardInterval::Reserved(other),
159        }
160    }
161
162    #[must_use]
163    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
164    pub fn to_u8(self) -> u8 {
165        match self {
166            T2GuardInterval::G1_32 => 0,
167            T2GuardInterval::G1_16 => 1,
168            T2GuardInterval::G1_8 => 2,
169            T2GuardInterval::G1_4 => 3,
170            T2GuardInterval::G1_128 => 4,
171            T2GuardInterval::G19_128 => 5,
172            T2GuardInterval::G19_256 => 6,
173            T2GuardInterval::Reserved(v) => v,
174        }
175    }
176
177    #[must_use]
178    /// Human-readable spec name per the governing Table.
179    pub fn name(self) -> &'static str {
180        match self {
181            T2GuardInterval::G1_32 => "1/32",
182            T2GuardInterval::G1_16 => "1/16",
183            T2GuardInterval::G1_8 => "1/8",
184            T2GuardInterval::G1_4 => "1/4",
185            T2GuardInterval::G1_128 => "1/128",
186            T2GuardInterval::G19_128 => "19/128",
187            T2GuardInterval::G19_256 => "19/256",
188            T2GuardInterval::Reserved(_) => "reserved",
189        }
190    }
191}
192
193/// Transmission mode — ETSI EN 300 468 Table 137.
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize))]
196#[non_exhaustive]
197pub enum T2TransmissionMode {
198    /// 2k mode.
199    Mode2k,
200    /// 8k mode.
201    Mode8k,
202    /// 4k mode.
203    Mode4k,
204    /// 1k mode.
205    Mode1k,
206    /// 16k mode.
207    Mode16k,
208    /// 32k mode.
209    Mode32k,
210    /// Reserved / future use.
211    Reserved(u8),
212}
213
214impl T2TransmissionMode {
215    #[must_use]
216    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
217    pub fn from_u8(v: u8) -> Self {
218        match v {
219            0 => T2TransmissionMode::Mode2k,
220            1 => T2TransmissionMode::Mode8k,
221            2 => T2TransmissionMode::Mode4k,
222            3 => T2TransmissionMode::Mode1k,
223            4 => T2TransmissionMode::Mode16k,
224            5 => T2TransmissionMode::Mode32k,
225            other => T2TransmissionMode::Reserved(other),
226        }
227    }
228
229    #[must_use]
230    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
231    pub fn to_u8(self) -> u8 {
232        match self {
233            T2TransmissionMode::Mode2k => 0,
234            T2TransmissionMode::Mode8k => 1,
235            T2TransmissionMode::Mode4k => 2,
236            T2TransmissionMode::Mode1k => 3,
237            T2TransmissionMode::Mode16k => 4,
238            T2TransmissionMode::Mode32k => 5,
239            T2TransmissionMode::Reserved(v) => v,
240        }
241    }
242
243    #[must_use]
244    /// Human-readable spec name per the governing Table.
245    pub fn name(self) -> &'static str {
246        match self {
247            T2TransmissionMode::Mode2k => "2k",
248            T2TransmissionMode::Mode8k => "8k",
249            T2TransmissionMode::Mode4k => "4k",
250            T2TransmissionMode::Mode1k => "1k",
251            T2TransmissionMode::Mode16k => "16k",
252            T2TransmissionMode::Mode32k => "32k",
253            T2TransmissionMode::Reserved(_) => "reserved",
254        }
255    }
256}
257
258// ---------------------------------------------------------------------------
259//  Structs
260// ---------------------------------------------------------------------------
261
262/// One T2 cell (Table 133 inner `for`).
263#[derive(Debug, Clone, PartialEq, Eq)]
264#[cfg_attr(feature = "serde", derive(serde::Serialize))]
265pub struct T2Cell {
266    /// cell_id(16).
267    pub cell_id: u16,
268    /// centre_frequency list. When tfs_flag, the length-prefixed loop;
269    /// otherwise exactly one frequency.
270    pub centre_frequencies: Vec<u32>,
271    /// subcell entries.
272    pub subcells: Vec<T2Subcell>,
273}
274
275impl T2Cell {
276    /// Decode all centre_frequencies to Hz (×10 Hz field resolution).
277    #[must_use]
278    pub fn centre_frequencies_hz(&self) -> Vec<u64> {
279        self.centre_frequencies
280            .iter()
281            .map(|&f| u64::from(f) * 10)
282            .collect()
283    }
284}
285
286/// One T2 subcell (Table 133 innermost `for`).
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288#[cfg_attr(feature = "serde", derive(serde::Serialize))]
289pub struct T2Subcell {
290    /// cell_id_extension(8).
291    pub cell_id_extension: u8,
292    /// transposer_frequency(32) — ×10 Hz units.
293    pub transposer_frequency: u32,
294}
295
296impl T2Subcell {
297    /// Decode transposer_frequency to Hz (×10 Hz field resolution).
298    #[must_use]
299    pub fn transposer_frequency_hz(&self) -> u64 {
300        u64::from(self.transposer_frequency) * 10
301    }
302}
303
304/// T2_delivery_system body (Table 133). The cell loop is unfolded.
305#[derive(Debug, Clone, PartialEq, Eq)]
306#[cfg_attr(feature = "serde", derive(serde::Serialize))]
307pub struct T2DeliverySystem {
308    /// PLP identifier.
309    pub plp_id: u8,
310    /// T2 system identifier.
311    pub t2_system_id: u16,
312    /// SISO_MISO, present iff `descriptor_length > 4` (flags block present).
313    pub siso_miso: Option<T2SisoMiso>,
314    /// bandwidth, present with `siso_miso`.
315    pub bandwidth: Option<T2Bandwidth>,
316    /// guard_interval, present with `siso_miso`.
317    pub guard_interval: Option<T2GuardInterval>,
318    /// transmission_mode, present with `siso_miso`.
319    pub transmission_mode: Option<T2TransmissionMode>,
320    /// other_frequency_flag, present with `siso_miso`.
321    pub other_frequency_flag: Option<bool>,
322    /// tfs_flag, present with `siso_miso`.
323    pub tfs_flag: Option<bool>,
324    /// Cell loop entries (present only when flags block is present).
325    pub cells: Vec<T2Cell>,
326}
327
328impl<'a> Parse<'a> for T2DeliverySystem {
329    type Error = crate::error::Error;
330    fn parse(sel: &'a [u8]) -> Result<Self> {
331        if sel.len() < T2_FIXED_PREFIX_LEN {
332            return Err(Error::BufferTooShort {
333                need: T2_FIXED_PREFIX_LEN,
334                have: sel.len(),
335                what: "T2_delivery_system body",
336            });
337        }
338        let plp_id = sel[0];
339        let t2_system_id = u16::from_be_bytes([sel[1], sel[2]]);
340        let mut pos = T2_FIXED_PREFIX_LEN;
341        let (
342            siso_miso,
343            bandwidth,
344            guard_interval,
345            transmission_mode,
346            other_frequency_flag,
347            tfs_flag,
348        ) = if sel.len() > T2_FIXED_PREFIX_LEN {
349            if sel.len() < T2_FIXED_PREFIX_LEN + T2_FLAGS_BLOCK_LEN {
350                return Err(Error::BufferTooShort {
351                    need: T2_FIXED_PREFIX_LEN + T2_FLAGS_BLOCK_LEN,
352                    have: sel.len(),
353                    what: "T2_delivery_system body",
354                });
355            }
356            let b0 = sel[pos];
357            let b1 = sel[pos + 1];
358            pos += T2_FLAGS_BLOCK_LEN;
359            (
360                Some(T2SisoMiso::from_u8(b0 >> 6)),
361                Some(T2Bandwidth::from_u8((b0 >> 2) & 0x0F)),
362                Some(T2GuardInterval::from_u8(b1 >> 5)),
363                Some(T2TransmissionMode::from_u8((b1 >> 2) & 0x07)),
364                Some((b1 & 0x02) != 0),
365                Some((b1 & 0x01) != 0),
366            )
367        } else {
368            (None, None, None, None, None, None)
369        };
370        let cells = if siso_miso.is_some() {
371            let tfs = tfs_flag.unwrap();
372            let mut cells = Vec::new();
373            while pos < sel.len() {
374                if pos + 2 > sel.len() {
375                    return Err(Error::BufferTooShort {
376                        need: pos + 2,
377                        have: sel.len(),
378                        what: "T2_delivery_system body",
379                    });
380                }
381                let cell_id = u16::from_be_bytes([sel[pos], sel[pos + 1]]);
382                pos += 2;
383                let centre_frequencies = if tfs {
384                    if pos >= sel.len() {
385                        return Err(Error::BufferTooShort {
386                            need: pos + 1,
387                            have: sel.len(),
388                            what: "T2_delivery_system body",
389                        });
390                    }
391                    let freq_loop_len = sel[pos] as usize;
392                    pos += 1;
393                    if freq_loop_len % 4 != 0 {
394                        return Err(invalid(
395                            "T2_delivery_system: frequency_loop_length not a multiple of 4",
396                        ));
397                    }
398                    if pos + freq_loop_len > sel.len() {
399                        return Err(Error::BufferTooShort {
400                            need: pos + freq_loop_len,
401                            have: sel.len(),
402                            what: "T2_delivery_system body",
403                        });
404                    }
405                    let end = pos + freq_loop_len;
406                    let mut freqs = Vec::with_capacity(freq_loop_len / 4);
407                    while pos < end {
408                        freqs.push(u32::from_be_bytes([
409                            sel[pos],
410                            sel[pos + 1],
411                            sel[pos + 2],
412                            sel[pos + 3],
413                        ]));
414                        pos += 4;
415                    }
416                    freqs
417                } else {
418                    if pos + 4 > sel.len() {
419                        return Err(Error::BufferTooShort {
420                            need: pos + 4,
421                            have: sel.len(),
422                            what: "T2_delivery_system body",
423                        });
424                    }
425                    let freq =
426                        u32::from_be_bytes([sel[pos], sel[pos + 1], sel[pos + 2], sel[pos + 3]]);
427                    pos += 4;
428                    vec![freq]
429                };
430                if pos >= sel.len() {
431                    return Err(Error::BufferTooShort {
432                        need: pos + 1,
433                        have: sel.len(),
434                        what: "T2_delivery_system body",
435                    });
436                }
437                let subcell_loop_len = sel[pos] as usize;
438                pos += 1;
439                if subcell_loop_len % 5 != 0 {
440                    return Err(invalid(
441                        "T2_delivery_system: subcell_info_loop_length not a multiple of 5",
442                    ));
443                }
444                if pos + subcell_loop_len > sel.len() {
445                    return Err(Error::BufferTooShort {
446                        need: pos + subcell_loop_len,
447                        have: sel.len(),
448                        what: "T2_delivery_system body",
449                    });
450                }
451                let end = pos + subcell_loop_len;
452                let mut subcells = Vec::with_capacity(subcell_loop_len / 5);
453                while pos < end {
454                    subcells.push(T2Subcell {
455                        cell_id_extension: sel[pos],
456                        transposer_frequency: u32::from_be_bytes([
457                            sel[pos + 1],
458                            sel[pos + 2],
459                            sel[pos + 3],
460                            sel[pos + 4],
461                        ]),
462                    });
463                    pos += 5;
464                }
465                cells.push(T2Cell {
466                    cell_id,
467                    centre_frequencies,
468                    subcells,
469                });
470            }
471            cells
472        } else {
473            Vec::new()
474        };
475        Ok(T2DeliverySystem {
476            plp_id,
477            t2_system_id,
478            siso_miso,
479            bandwidth,
480            guard_interval,
481            transmission_mode,
482            other_frequency_flag,
483            tfs_flag,
484            cells,
485        })
486    }
487}
488
489impl Serialize for T2DeliverySystem {
490    type Error = crate::error::Error;
491    fn serialized_len(&self) -> usize {
492        let mut len = T2_FIXED_PREFIX_LEN;
493        if self.siso_miso.is_some() {
494            len += T2_FLAGS_BLOCK_LEN;
495            let tfs = self.tfs_flag.unwrap_or(false);
496            for cell in &self.cells {
497                len += 2; // cell_id
498                if tfs {
499                    len += 1 + cell.centre_frequencies.len() * 4;
500                } else {
501                    len += 4;
502                }
503                len += 1 + cell.subcells.len() * 5;
504            }
505        }
506        len
507    }
508    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
509        let len = self.serialized_len();
510        if buf.len() < len {
511            return Err(Error::OutputBufferTooSmall {
512                need: len,
513                have: buf.len(),
514            });
515        }
516        buf[0] = self.plp_id;
517        buf[1..3].copy_from_slice(&self.t2_system_id.to_be_bytes());
518        let mut p = T2_FIXED_PREFIX_LEN;
519        if let (Some(sm), Some(bw), Some(gi), Some(tm), Some(off), Some(tfs)) = (
520            self.siso_miso,
521            self.bandwidth,
522            self.guard_interval,
523            self.transmission_mode,
524            self.other_frequency_flag,
525            self.tfs_flag,
526        ) {
527            buf[p] = (sm.to_u8() << 6) | ((bw.to_u8() & 0x0F) << 2) | 0x03;
528            buf[p + 1] = (gi.to_u8() << 5)
529                | ((tm.to_u8() & 0x07) << 2)
530                | (u8::from(off) << 1)
531                | u8::from(tfs);
532            p += T2_FLAGS_BLOCK_LEN;
533            for cell in &self.cells {
534                buf[p..p + 2].copy_from_slice(&cell.cell_id.to_be_bytes());
535                p += 2;
536                if tfs {
537                    let freq_len = (cell.centre_frequencies.len() * 4) as u8;
538                    buf[p] = freq_len;
539                    p += 1;
540                    for &freq in &cell.centre_frequencies {
541                        buf[p..p + 4].copy_from_slice(&freq.to_be_bytes());
542                        p += 4;
543                    }
544                } else {
545                    let freq = cell.centre_frequencies.first().copied().unwrap_or(0);
546                    buf[p..p + 4].copy_from_slice(&freq.to_be_bytes());
547                    p += 4;
548                }
549                let subcell_len = (cell.subcells.len() * 5) as u8;
550                buf[p] = subcell_len;
551                p += 1;
552                for sc in &cell.subcells {
553                    buf[p] = sc.cell_id_extension;
554                    buf[p + 1..p + 5].copy_from_slice(&sc.transposer_frequency.to_be_bytes());
555                    p += 5;
556                }
557            }
558        }
559        Ok(len)
560    }
561}
562
563#[cfg(test)]
564mod tests {
565    use super::*;
566    use crate::descriptors::extension::test_support::*;
567    use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
568
569    #[test]
570    fn t2_bandwidth_roundtrip() {
571        for b in 0..=0xFFu8 {
572            assert_eq!(T2Bandwidth::from_u8(b).to_u8(), b);
573        }
574    }
575
576    #[test]
577    fn t2_guard_interval_roundtrip() {
578        for b in 0..=0xFFu8 {
579            assert_eq!(T2GuardInterval::from_u8(b).to_u8(), b);
580        }
581    }
582
583    #[test]
584    fn t2_transmission_mode_roundtrip() {
585        for b in 0..=0xFFu8 {
586            assert_eq!(T2TransmissionMode::from_u8(b).to_u8(), b);
587        }
588    }
589
590    #[test]
591    fn parse_t2_minimal() {
592        // body = plp + system_id = 3 bytes => no flags block
593        let sel = [0x07, 0x12, 0x34];
594        let bytes = wrap(0x04, &sel);
595        let d = ExtensionDescriptor::parse(&bytes).unwrap();
596        match &d.body {
597            ExtensionBody::T2DeliverySystem(b) => {
598                assert_eq!(b.plp_id, 0x07);
599                assert_eq!(b.t2_system_id, 0x1234);
600                assert_eq!(b.siso_miso, None);
601                assert!(b.cells.is_empty());
602            }
603            other => panic!("expected T2DeliverySystem, got {other:?}"),
604        }
605        round_trip(&d);
606    }
607
608    #[test]
609    fn parse_t2_structured_flags_and_cells() {
610        // prefix + flags block (siso=0, bw=4(10MHz), gi=6(19/256), tm=3(1k), off=0, tfs=1)
611        // + 2 cells: one empty, one with 3 freqs + 2 subcells
612        let b0: u8 = ((0x04 & 0x0F) << 2) | 0x03; // siso_miso=0, bandwidth=4, reserved=11
613        let b1: u8 = (0x06 << 5) | ((0x03 & 0x07) << 2) | (u8::from(false) << 1) | u8::from(true);
614        // cell 1: cell_id=0x1234, freq_len=0, subcell_len=0
615        let cell1 = [0x12, 0x34, 0x00, 0x00];
616        // cell 2: cell_id=0x5678, freq_len=12 (3 freqs), three freqs, subcell_len=10 (2 subcells), two subcells
617        let f1 = 0x01020304u32;
618        let f2 = 0x05060708u32;
619        let f3 = 0x090A0B0Cu32;
620        let sc1_id = 0x10u8;
621        let sc1_freq = 0x11121314u32;
622        let sc2_id = 0x20u8;
623        let sc2_freq = 0x21222324u32;
624        let mut cell2 = Vec::new();
625        cell2.extend_from_slice(&0x5678u16.to_be_bytes());
626        cell2.push(12);
627        cell2.extend_from_slice(&f1.to_be_bytes());
628        cell2.extend_from_slice(&f2.to_be_bytes());
629        cell2.extend_from_slice(&f3.to_be_bytes());
630        cell2.push(10);
631        cell2.push(sc1_id);
632        cell2.extend_from_slice(&sc1_freq.to_be_bytes());
633        cell2.push(sc2_id);
634        cell2.extend_from_slice(&sc2_freq.to_be_bytes());
635        let mut sel = vec![0x07, 0x12, 0x34, b0, b1];
636        sel.extend_from_slice(&cell1);
637        sel.extend_from_slice(&cell2);
638        let bytes = wrap(0x04, &sel);
639        let d = ExtensionDescriptor::parse(&bytes).unwrap();
640        match &d.body {
641            ExtensionBody::T2DeliverySystem(b) => {
642                assert_eq!(b.plp_id, 0x07);
643                assert_eq!(b.t2_system_id, 0x1234);
644                assert_eq!(b.siso_miso, Some(T2SisoMiso::Siso));
645                assert_eq!(b.bandwidth, Some(T2Bandwidth::Mhz10));
646                assert_eq!(b.guard_interval, Some(T2GuardInterval::G19_256));
647                assert_eq!(b.transmission_mode, Some(T2TransmissionMode::Mode1k));
648                assert_eq!(b.other_frequency_flag, Some(false));
649                assert_eq!(b.tfs_flag, Some(true));
650                assert_eq!(b.cells.len(), 2);
651                // cell 0: empty
652                assert_eq!(b.cells[0].cell_id, 0x1234);
653                assert!(b.cells[0].centre_frequencies.is_empty());
654                assert!(b.cells[0].subcells.is_empty());
655                // cell 1: 3 freqs + 2 subcells
656                assert_eq!(b.cells[1].cell_id, 0x5678);
657                assert_eq!(b.cells[1].centre_frequencies, vec![f1, f2, f3]);
658                assert_eq!(b.cells[1].subcells.len(), 2);
659                assert_eq!(b.cells[1].subcells[0].cell_id_extension, sc1_id);
660                assert_eq!(b.cells[1].subcells[0].transposer_frequency, sc1_freq);
661                assert_eq!(b.cells[1].subcells[1].cell_id_extension, sc2_id);
662                assert_eq!(b.cells[1].subcells[1].transposer_frequency, sc2_freq);
663
664                // Accessor tests
665                assert_eq!(
666                    b.cells[1].subcells[0].transposer_frequency_hz(),
667                    u64::from(sc1_freq) * 10
668                );
669                assert_eq!(
670                    b.cells[1].centre_frequencies_hz(),
671                    vec![u64::from(f1) * 10, u64::from(f2) * 10, u64::from(f3) * 10]
672                );
673            }
674            other => panic!("expected T2DeliverySystem, got {other:?}"),
675        }
676        round_trip(&d);
677    }
678
679    #[test]
680    fn tsduck_t2_reference() {
681        let bytes = from_hex(
682            "7f240456789a13cd12340000678a0c075bcd1505e30a780fd22c320a1217ea6406fa0aa9fc59",
683        );
684        let d = ExtensionDescriptor::parse(&bytes).unwrap();
685        match &d.body {
686            ExtensionBody::T2DeliverySystem(b) => {
687                assert_eq!(b.plp_id, 0x56);
688                assert_eq!(b.t2_system_id, 0x789A);
689                assert_eq!(b.siso_miso, Some(T2SisoMiso::Siso));
690                assert_eq!(b.bandwidth, Some(T2Bandwidth::Mhz10));
691                assert_eq!(b.guard_interval, Some(T2GuardInterval::G19_256));
692                assert_eq!(b.transmission_mode, Some(T2TransmissionMode::Mode1k));
693                assert_eq!(b.other_frequency_flag, Some(false));
694                assert_eq!(b.tfs_flag, Some(true));
695                assert_eq!(b.cells.len(), 2);
696
697                assert_eq!(b.cells[0].cell_id, 0x1234);
698                assert!(b.cells[0].centre_frequencies.is_empty());
699                assert!(b.cells[0].subcells.is_empty());
700
701                assert_eq!(b.cells[1].cell_id, 0x678A);
702                assert_eq!(
703                    b.cells[1].centre_frequencies,
704                    vec![0x075BCD15, 0x05E30A78, 0x0FD22C32]
705                );
706                assert_eq!(b.cells[1].subcells.len(), 2);
707                assert_eq!(b.cells[1].subcells[0].cell_id_extension, 0x12);
708                assert_eq!(b.cells[1].subcells[0].transposer_frequency, 0x17EA6406);
709                assert_eq!(b.cells[1].subcells[1].cell_id_extension, 0xFA);
710                assert_eq!(b.cells[1].subcells[1].transposer_frequency, 0x0AA9FC59);
711            }
712            other => panic!("expected T2DeliverySystem, got {other:?}"),
713        }
714        let mut out = vec![0u8; d.serialized_len()];
715        let n = d.serialize_into(&mut out).unwrap();
716        assert_eq!(
717            out[..n],
718            bytes[..],
719            "byte-exact re-serialize for tsduck T2 reference"
720        );
721    }
722}