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