Skip to main content

dvb_si/descriptors/extension/
c2_delivery_system.rs

1//! C2 Delivery System Descriptor — ETSI EN 300 468 §6.4.6.1 (tag_extension 0x0D).
2use super::*;
3
4impl<'a> ExtensionBodyDef<'a> for C2DeliverySystem {
5    const TAG_EXTENSION: u8 = 0x0D;
6    const NAME: &'static str = "C2_DELIVERY_SYSTEM";
7}
8
9/// C2 system tuning frequency type — ETSI EN 300 468 Table 116.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12#[non_exhaustive]
13pub enum C2TuningFrequencyType {
14    /// 0b00 — Data Slice tuning frequency.
15    DataSliceTuningFrequency,
16    /// 0b01 — C2 system centre frequency.
17    C2SystemCentreFrequency,
18    /// 0b10 — Initial tuning position for a (dependent) Static Data Slice.
19    InitialTuningPositionStaticDataSlice,
20    /// Reserved / future use.
21    Reserved(u8),
22}
23
24impl C2TuningFrequencyType {
25    #[must_use]
26    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
27    pub fn from_u8(v: u8) -> Self {
28        match v {
29            0 => C2TuningFrequencyType::DataSliceTuningFrequency,
30            1 => C2TuningFrequencyType::C2SystemCentreFrequency,
31            2 => C2TuningFrequencyType::InitialTuningPositionStaticDataSlice,
32            other => C2TuningFrequencyType::Reserved(other),
33        }
34    }
35
36    #[must_use]
37    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
38    pub fn to_u8(self) -> u8 {
39        match self {
40            C2TuningFrequencyType::DataSliceTuningFrequency => 0,
41            C2TuningFrequencyType::C2SystemCentreFrequency => 1,
42            C2TuningFrequencyType::InitialTuningPositionStaticDataSlice => 2,
43            C2TuningFrequencyType::Reserved(v) => v,
44        }
45    }
46
47    #[must_use]
48    /// Human-readable spec name per Table 116.
49    pub fn name(self) -> &'static str {
50        match self {
51            C2TuningFrequencyType::DataSliceTuningFrequency => "Data Slice tuning frequency",
52            C2TuningFrequencyType::C2SystemCentreFrequency => "C2 system centre frequency",
53            C2TuningFrequencyType::InitialTuningPositionStaticDataSlice => {
54                "initial tuning position for a Static Data Slice"
55            }
56            C2TuningFrequencyType::Reserved(_) => "reserved",
57        }
58    }
59}
60dvb_common::impl_spec_display!(C2TuningFrequencyType, Reserved);
61
62/// Active OFDM symbol duration — ETSI EN 300 468 Table 117.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize))]
65#[non_exhaustive]
66pub enum ActiveOfdmSymbolDuration {
67    /// 448 µs (4k FFT mode for 8 MHz CATV systems).
68    Us448,
69    /// 597.33 µs (4k FFT mode for 6 MHz CATV systems).
70    Us597_33,
71    /// Reserved / future use.
72    Reserved(u8),
73}
74
75impl ActiveOfdmSymbolDuration {
76    #[must_use]
77    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
78    pub fn from_u8(v: u8) -> Self {
79        match v {
80            0 => ActiveOfdmSymbolDuration::Us448,
81            1 => ActiveOfdmSymbolDuration::Us597_33,
82            other => ActiveOfdmSymbolDuration::Reserved(other),
83        }
84    }
85
86    #[must_use]
87    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
88    pub fn to_u8(self) -> u8 {
89        match self {
90            ActiveOfdmSymbolDuration::Us448 => 0,
91            ActiveOfdmSymbolDuration::Us597_33 => 1,
92            ActiveOfdmSymbolDuration::Reserved(v) => v,
93        }
94    }
95
96    #[must_use]
97    /// Human-readable spec name per the governing Table.
98    pub fn name(self) -> &'static str {
99        match self {
100            ActiveOfdmSymbolDuration::Us448 => "448 µs (4k FFT, 8 MHz)",
101            ActiveOfdmSymbolDuration::Us597_33 => "597.33 µs (4k FFT, 6 MHz)",
102            ActiveOfdmSymbolDuration::Reserved(_) => "reserved",
103        }
104    }
105}
106dvb_common::impl_spec_display!(ActiveOfdmSymbolDuration, Reserved);
107
108/// C2 guard interval — ETSI EN 300 468 Table 118.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize))]
111#[non_exhaustive]
112pub enum C2GuardInterval {
113    /// 1/128.
114    G1_128,
115    /// 1/64.
116    G1_64,
117    /// Reserved / future use.
118    Reserved(u8),
119}
120
121impl C2GuardInterval {
122    #[must_use]
123    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
124    pub fn from_u8(v: u8) -> Self {
125        match v {
126            0 => C2GuardInterval::G1_128,
127            1 => C2GuardInterval::G1_64,
128            other => C2GuardInterval::Reserved(other),
129        }
130    }
131
132    #[must_use]
133    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
134    pub fn to_u8(self) -> u8 {
135        match self {
136            C2GuardInterval::G1_128 => 0,
137            C2GuardInterval::G1_64 => 1,
138            C2GuardInterval::Reserved(v) => v,
139        }
140    }
141
142    #[must_use]
143    /// Human-readable spec name per the governing Table.
144    pub fn name(self) -> &'static str {
145        match self {
146            C2GuardInterval::G1_128 => "1/128",
147            C2GuardInterval::G1_64 => "1/64",
148            C2GuardInterval::Reserved(_) => "reserved",
149        }
150    }
151}
152dvb_common::impl_spec_display!(C2GuardInterval, Reserved);
153
154/// C2_delivery_system body (Table 115) — fully typed, fixed 7 bytes.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156#[cfg_attr(feature = "serde", derive(serde::Serialize))]
157pub struct C2DeliverySystem {
158    /// plp_id(8).
159    pub plp_id: u8,
160    /// data_slice_id(8).
161    pub data_slice_id: u8,
162    /// C2_System_tuning_frequency(32).
163    pub c2_system_tuning_frequency: u32,
164    /// C2_System_tuning_frequency_type(2) — Table 116.
165    pub c2_system_tuning_frequency_type: C2TuningFrequencyType,
166    /// active_OFDM_symbol_duration(3) — Table 117.
167    pub active_ofdm_symbol_duration: ActiveOfdmSymbolDuration,
168    /// guard_interval(3) — Table 118.
169    pub guard_interval: C2GuardInterval,
170}
171
172impl<'a> Parse<'a> for C2DeliverySystem {
173    type Error = crate::error::Error;
174    fn parse(sel: &'a [u8]) -> Result<Self> {
175        if sel.len() < C2_LEN {
176            return Err(Error::BufferTooShort {
177                need: C2_LEN,
178                have: sel.len(),
179                what: "C2_delivery_system body",
180            });
181        }
182        let packed = sel[6];
183        Ok(C2DeliverySystem {
184            plp_id: sel[0],
185            data_slice_id: sel[1],
186            c2_system_tuning_frequency: u32::from_be_bytes([sel[2], sel[3], sel[4], sel[5]]),
187            c2_system_tuning_frequency_type: C2TuningFrequencyType::from_u8(packed >> 6),
188            active_ofdm_symbol_duration: ActiveOfdmSymbolDuration::from_u8((packed >> 3) & 0x07),
189            guard_interval: C2GuardInterval::from_u8(packed & 0x07),
190        })
191    }
192}
193
194impl Serialize for C2DeliverySystem {
195    type Error = crate::error::Error;
196    fn serialized_len(&self) -> usize {
197        C2_LEN
198    }
199    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
200        let len = self.serialized_len();
201        if buf.len() < len {
202            return Err(Error::OutputBufferTooSmall {
203                need: len,
204                have: buf.len(),
205            });
206        }
207        buf[0] = self.plp_id;
208        buf[1] = self.data_slice_id;
209        buf[2..6].copy_from_slice(&self.c2_system_tuning_frequency.to_be_bytes());
210        buf[6] = (self.c2_system_tuning_frequency_type.to_u8() << 6)
211            | ((self.active_ofdm_symbol_duration.to_u8() & 0x07) << 3)
212            | (self.guard_interval.to_u8() & 0x07);
213        Ok(len)
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use crate::descriptors::extension::test_support::*;
221    use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
222
223    #[test]
224    fn c2_tuning_frequency_type_roundtrip() {
225        for b in 0..=0xFFu8 {
226            assert_eq!(C2TuningFrequencyType::from_u8(b).to_u8(), b);
227        }
228    }
229
230    #[test]
231    fn c2_tuning_frequency_type_name() {
232        assert_eq!(
233            C2TuningFrequencyType::DataSliceTuningFrequency.name(),
234            "Data Slice tuning frequency"
235        );
236        assert_eq!(
237            C2TuningFrequencyType::C2SystemCentreFrequency.name(),
238            "C2 system centre frequency"
239        );
240        assert_eq!(
241            C2TuningFrequencyType::InitialTuningPositionStaticDataSlice.name(),
242            "initial tuning position for a Static Data Slice"
243        );
244        assert_eq!(C2TuningFrequencyType::Reserved(3).name(), "reserved");
245    }
246
247    #[test]
248    fn active_ofdm_symbol_duration_roundtrip() {
249        for b in 0..=0xFFu8 {
250            assert_eq!(ActiveOfdmSymbolDuration::from_u8(b).to_u8(), b);
251        }
252    }
253
254    #[test]
255    fn c2_guard_interval_roundtrip() {
256        for b in 0..=0xFFu8 {
257            assert_eq!(C2GuardInterval::from_u8(b).to_u8(), b);
258        }
259    }
260
261    #[test]
262    fn parse_c2_delivery_system() {
263        let packed = (0x01u8 << 3) | 0x01;
264        let sel = [0x05, 0x09, 0x12, 0x34, 0x56, 0x78, packed];
265        let bytes = wrap(0x0D, &sel);
266        let d = ExtensionDescriptor::parse(&bytes).unwrap();
267        match &d.body {
268            ExtensionBody::C2DeliverySystem(b) => {
269                assert_eq!(b.plp_id, 0x05);
270                assert_eq!(b.data_slice_id, 0x09);
271                assert_eq!(b.c2_system_tuning_frequency, 0x1234_5678);
272                assert_eq!(
273                    b.c2_system_tuning_frequency_type,
274                    C2TuningFrequencyType::DataSliceTuningFrequency
275                );
276                assert_eq!(
277                    b.active_ofdm_symbol_duration,
278                    ActiveOfdmSymbolDuration::Us597_33
279                );
280                assert_eq!(b.guard_interval, C2GuardInterval::G1_64);
281            }
282            other => panic!("expected C2DeliverySystem, got {other:?}"),
283        }
284        round_trip(&d);
285    }
286
287    #[test]
288    fn parse_c2_delivery_system_centre_frequency() {
289        let packed = 0x01u8 << 6;
290        let sel = [0x05, 0x09, 0x12, 0x34, 0x56, 0x78, packed];
291        let bytes = wrap(0x0D, &sel);
292        let d = ExtensionDescriptor::parse(&bytes).unwrap();
293        match &d.body {
294            ExtensionBody::C2DeliverySystem(b) => {
295                assert_eq!(
296                    b.c2_system_tuning_frequency_type,
297                    C2TuningFrequencyType::C2SystemCentreFrequency
298                );
299            }
300            other => panic!("expected C2DeliverySystem, got {other:?}"),
301        }
302        round_trip(&d);
303    }
304
305    #[test]
306    fn parse_c2_delivery_system_initial_tuning() {
307        let packed = 0x02u8 << 6;
308        let sel = [0x05, 0x09, 0x12, 0x34, 0x56, 0x78, packed];
309        let bytes = wrap(0x0D, &sel);
310        let d = ExtensionDescriptor::parse(&bytes).unwrap();
311        match &d.body {
312            ExtensionBody::C2DeliverySystem(b) => {
313                assert_eq!(
314                    b.c2_system_tuning_frequency_type,
315                    C2TuningFrequencyType::InitialTuningPositionStaticDataSlice
316                );
317            }
318            other => panic!("expected C2DeliverySystem, got {other:?}"),
319        }
320        round_trip(&d);
321    }
322
323    #[test]
324    fn parse_c2_delivery_system_reserved_values() {
325        let packed = (0x03u8 << 6) | (0x07u8 << 3) | 0x07;
326        let sel = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, packed];
327        let bytes = wrap(0x0D, &sel);
328        let d = ExtensionDescriptor::parse(&bytes).unwrap();
329        match &d.body {
330            ExtensionBody::C2DeliverySystem(b) => {
331                assert_eq!(
332                    b.c2_system_tuning_frequency_type,
333                    C2TuningFrequencyType::Reserved(3)
334                );
335                assert_eq!(
336                    b.active_ofdm_symbol_duration,
337                    ActiveOfdmSymbolDuration::Reserved(7)
338                );
339                assert_eq!(b.guard_interval, C2GuardInterval::Reserved(7));
340            }
341            other => panic!("expected C2DeliverySystem, got {other:?}"),
342        }
343        round_trip(&d);
344    }
345}