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