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