Skip to main content

dvb_si/descriptors/
terrestrial_delivery_system.rs

1//! Terrestrial Delivery System Descriptor — ETSI EN 300 468 §6.2.13.4 (tag 0x5A).
2//!
3//! Carried inside the NIT's transport\_stream\_loop second descriptor loop for
4//! DVB-T transponders. Expresses the full DVB-T PHY configuration needed to
5//! tune the carrier.
6
7use super::descriptor_body;
8use crate::error::{Error, Result};
9use dvb_common::{Parse, Serialize};
10
11/// Descriptor tag for terrestrial\_delivery\_system\_descriptor.
12pub const TAG: u8 = 0x5A;
13const HEADER_LEN: usize = 2;
14const BODY_LEN: u8 = 11;
15
16const BW_SHIFT: u8 = 5;
17const PRIORITY_MASK: u8 = 0b0001_0000;
18const TIME_SLICING_MASK: u8 = 0b0000_1000;
19const MPE_FEC_MASK: u8 = 0b0000_0100;
20const RESERVED_FU_MASK: u8 = 0b0000_0011;
21const BW_MASK: u8 = 0b1110_0000;
22
23const CONSTELLATION_SHIFT: u8 = 6;
24const HIERARCHY_SHIFT: u8 = 3;
25const CONSTELLATION_MASK: u8 = 0b1100_0000;
26const HIERARCHY_MASK: u8 = 0b0011_1000;
27const CODE_RATE_HP_MASK: u8 = 0b0000_0111;
28
29const CODE_RATE_LP_SHIFT: u8 = 5;
30const GUARD_INTERVAL_SHIFT: u8 = 3;
31const TRANSMISSION_MODE_SHIFT: u8 = 1;
32const CODE_RATE_LP_MASK: u8 = 0b1110_0000;
33const GUARD_INTERVAL_MASK: u8 = 0b0001_1000;
34const TRANSMISSION_MODE_MASK: u8 = 0b0000_0110;
35const OTHER_FREQ_FLAG_MASK: u8 = 0b0000_0001;
36
37const TRAILING_RESERVED: u32 = 0xFFFF_FFFF;
38
39/// Channel bandwidth (§6.2.13.4 Table 52).
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize))]
42#[non_exhaustive]
43pub enum Bandwidth {
44    /// 8 MHz.
45    Mhz8,
46    /// 7 MHz.
47    Mhz7,
48    /// 6 MHz.
49    Mhz6,
50    /// 5 MHz.
51    Mhz5,
52    /// Unspecified / reserved value.
53    Reserved(u8),
54}
55
56/// Constellation.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize))]
59#[non_exhaustive]
60pub enum Constellation {
61    /// QPSK.
62    Qpsk,
63    /// 16-QAM.
64    Qam16,
65    /// 64-QAM.
66    Qam64,
67    /// Unspecified / reserved value.
68    Reserved(u8),
69}
70
71/// Hierarchy mode — combines native/in-depth interleaver and α.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74#[non_exhaustive]
75pub enum Hierarchy {
76    /// Non-hierarchical + native.
77    NonHierarchicalNative,
78    /// α=1 + native.
79    Alpha1Native,
80    /// α=2 + native.
81    Alpha2Native,
82    /// α=4 + native.
83    Alpha4Native,
84    /// Non-hierarchical + in-depth.
85    NonHierarchicalInDepth,
86    /// α=1 + in-depth.
87    Alpha1InDepth,
88    /// α=2 + in-depth.
89    Alpha2InDepth,
90    /// α=4 + in-depth.
91    Alpha4InDepth,
92    /// Unspecified / reserved value.
93    Reserved(u8),
94}
95
96/// Convolutional code rate.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98#[cfg_attr(feature = "serde", derive(serde::Serialize))]
99#[non_exhaustive]
100pub enum CodeRate {
101    /// 1/2.
102    Rate1_2,
103    /// 2/3.
104    Rate2_3,
105    /// 3/4.
106    Rate3_4,
107    /// 5/6.
108    Rate5_6,
109    /// 7/8.
110    Rate7_8,
111    /// Unspecified / reserved value.
112    Reserved(u8),
113}
114
115/// Guard interval fraction.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117#[cfg_attr(feature = "serde", derive(serde::Serialize))]
118pub enum GuardInterval {
119    /// 1/32.
120    G1_32,
121    /// 1/16.
122    G1_16,
123    /// 1/8.
124    G1_8,
125    /// 1/4.
126    G1_4,
127}
128
129/// Transmission mode.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize))]
132#[non_exhaustive]
133pub enum TransmissionMode {
134    /// 2k mode.
135    Mode2k,
136    /// 8k mode.
137    Mode8k,
138    /// 4k mode.
139    Mode4k,
140    /// Unspecified / reserved value.
141    Reserved(u8),
142}
143
144/// Terrestrial Delivery System Descriptor.
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize))]
147pub struct TerrestrialDeliverySystemDescriptor {
148    /// Centre frequency in units of 10 Hz.
149    pub centre_frequency_10hz: u32,
150    /// Channel bandwidth.
151    pub bandwidth: Bandwidth,
152    /// High-priority stream indicator.
153    pub priority: bool,
154    /// Time slicing used (spec field polarity: 0 = used → store as bool "used").
155    pub time_slicing_used: bool,
156    /// MPE-FEC used (spec field polarity: 0 = used → store as bool "used").
157    pub mpe_fec_used: bool,
158    /// Constellation.
159    pub constellation: Constellation,
160    /// Hierarchy mode.
161    pub hierarchy: Hierarchy,
162    /// High-priority stream FEC code rate.
163    pub code_rate_hp: CodeRate,
164    /// Low-priority stream FEC code rate (ignored for non-hierarchical).
165    pub code_rate_lp: CodeRate,
166    /// Guard interval fraction.
167    pub guard_interval: GuardInterval,
168    /// Transmission mode (FFT size).
169    pub transmission_mode: TransmissionMode,
170    /// Set when alternative frequencies listed in a frequency_list_descriptor.
171    pub other_frequency_flag: bool,
172}
173
174impl TerrestrialDeliverySystemDescriptor {
175    /// Centre frequency in Hz. The `centre_frequency_10hz` field stores units of
176    /// 10 Hz (EN 300 468 §6.2.13.4), so this conversion is exact.
177    #[must_use]
178    pub fn centre_frequency_hz(&self) -> u64 {
179        u64::from(self.centre_frequency_10hz) * 10
180    }
181
182    /// Set the centre frequency from Hz, encoding to the field's 10 Hz
183    /// resolution (finer precision is truncated).
184    ///
185    /// # Errors
186    /// [`ValueOutOfRange`](crate::Error::ValueOutOfRange) if the value
187    /// exceeds the 32-bit (×10 Hz) field.
188    pub fn set_centre_frequency_hz(&mut self, hz: u64) -> crate::Result<()> {
189        let units = hz / 10;
190        if units > u64::from(u32::MAX) {
191            return Err(crate::Error::ValueOutOfRange {
192                field: "TerrestrialDeliverySystemDescriptor::centre_frequency",
193                reason: "frequency exceeds the 32-bit (10 Hz) field",
194            });
195        }
196        self.centre_frequency_10hz = units as u32;
197        Ok(())
198    }
199}
200
201fn parse_bandwidth(raw: u8) -> Bandwidth {
202    match raw {
203        0 => Bandwidth::Mhz8,
204        1 => Bandwidth::Mhz7,
205        2 => Bandwidth::Mhz6,
206        3 => Bandwidth::Mhz5,
207        other => Bandwidth::Reserved(other),
208    }
209}
210
211fn parse_constellation(raw: u8) -> Constellation {
212    match raw {
213        0 => Constellation::Qpsk,
214        1 => Constellation::Qam16,
215        2 => Constellation::Qam64,
216        other => Constellation::Reserved(other),
217    }
218}
219
220fn parse_hierarchy(raw: u8) -> Hierarchy {
221    match raw {
222        0 => Hierarchy::NonHierarchicalNative,
223        1 => Hierarchy::Alpha1Native,
224        2 => Hierarchy::Alpha2Native,
225        3 => Hierarchy::Alpha4Native,
226        4 => Hierarchy::NonHierarchicalInDepth,
227        5 => Hierarchy::Alpha1InDepth,
228        6 => Hierarchy::Alpha2InDepth,
229        7 => Hierarchy::Alpha4InDepth,
230        other => Hierarchy::Reserved(other),
231    }
232}
233
234fn parse_code_rate(raw: u8) -> CodeRate {
235    match raw {
236        0 => CodeRate::Rate1_2,
237        1 => CodeRate::Rate2_3,
238        2 => CodeRate::Rate3_4,
239        3 => CodeRate::Rate5_6,
240        4 => CodeRate::Rate7_8,
241        other => CodeRate::Reserved(other),
242    }
243}
244
245fn parse_guard_interval(raw: u8) -> GuardInterval {
246    match raw {
247        0 => GuardInterval::G1_32,
248        1 => GuardInterval::G1_16,
249        2 => GuardInterval::G1_8,
250        3 => GuardInterval::G1_4,
251        _ => GuardInterval::G1_32,
252    }
253}
254
255fn parse_transmission_mode(raw: u8) -> TransmissionMode {
256    match raw {
257        0 => TransmissionMode::Mode2k,
258        1 => TransmissionMode::Mode8k,
259        2 => TransmissionMode::Mode4k,
260        other => TransmissionMode::Reserved(other),
261    }
262}
263
264fn serialize_bandwidth(bw: Bandwidth) -> u8 {
265    match bw {
266        Bandwidth::Mhz8 => 0,
267        Bandwidth::Mhz7 => 1,
268        Bandwidth::Mhz6 => 2,
269        Bandwidth::Mhz5 => 3,
270        Bandwidth::Reserved(v) => v,
271    }
272}
273
274fn serialize_constellation(c: Constellation) -> u8 {
275    match c {
276        Constellation::Qpsk => 0,
277        Constellation::Qam16 => 1,
278        Constellation::Qam64 => 2,
279        Constellation::Reserved(v) => v,
280    }
281}
282
283fn serialize_hierarchy(h: Hierarchy) -> u8 {
284    match h {
285        Hierarchy::NonHierarchicalNative => 0,
286        Hierarchy::Alpha1Native => 1,
287        Hierarchy::Alpha2Native => 2,
288        Hierarchy::Alpha4Native => 3,
289        Hierarchy::NonHierarchicalInDepth => 4,
290        Hierarchy::Alpha1InDepth => 5,
291        Hierarchy::Alpha2InDepth => 6,
292        Hierarchy::Alpha4InDepth => 7,
293        Hierarchy::Reserved(v) => v,
294    }
295}
296
297fn serialize_code_rate(cr: CodeRate) -> u8 {
298    match cr {
299        CodeRate::Rate1_2 => 0,
300        CodeRate::Rate2_3 => 1,
301        CodeRate::Rate3_4 => 2,
302        CodeRate::Rate5_6 => 3,
303        CodeRate::Rate7_8 => 4,
304        CodeRate::Reserved(v) => v,
305    }
306}
307
308fn serialize_guard_interval(gi: GuardInterval) -> u8 {
309    match gi {
310        GuardInterval::G1_32 => 0,
311        GuardInterval::G1_16 => 1,
312        GuardInterval::G1_8 => 2,
313        GuardInterval::G1_4 => 3,
314    }
315}
316
317fn serialize_transmission_mode(tm: TransmissionMode) -> u8 {
318    match tm {
319        TransmissionMode::Mode2k => 0,
320        TransmissionMode::Mode8k => 1,
321        TransmissionMode::Mode4k => 2,
322        TransmissionMode::Reserved(v) => v,
323    }
324}
325
326impl<'a> Parse<'a> for TerrestrialDeliverySystemDescriptor {
327    type Error = crate::error::Error;
328    fn parse(bytes: &'a [u8]) -> Result<Self> {
329        let body = descriptor_body(
330            bytes,
331            TAG,
332            "TerrestrialDeliverySystemDescriptor",
333            "unexpected tag for terrestrial_delivery_system_descriptor",
334        )?;
335        if body.len() != BODY_LEN as usize {
336            return Err(Error::InvalidDescriptor {
337                tag: TAG,
338                reason: "body length must equal 11",
339            });
340        }
341
342        let centre_frequency_10hz = u32::from_be_bytes(body[0..4].try_into().unwrap());
343
344        let byte4 = body[4];
345        let bw_raw = (byte4 & BW_MASK) >> BW_SHIFT;
346        let priority = (byte4 & PRIORITY_MASK) != 0;
347        let time_slicing_used = (byte4 & TIME_SLICING_MASK) == 0;
348        let mpe_fec_used = (byte4 & MPE_FEC_MASK) == 0;
349
350        let byte5 = body[5];
351        let constellation_raw = (byte5 & CONSTELLATION_MASK) >> CONSTELLATION_SHIFT;
352        let hierarchy_raw = (byte5 & HIERARCHY_MASK) >> HIERARCHY_SHIFT;
353        let code_rate_hp_raw = byte5 & CODE_RATE_HP_MASK;
354
355        let byte6 = body[6];
356        let code_rate_lp_raw = (byte6 & CODE_RATE_LP_MASK) >> CODE_RATE_LP_SHIFT;
357        let guard_interval_raw = (byte6 & GUARD_INTERVAL_MASK) >> GUARD_INTERVAL_SHIFT;
358        let transmission_mode_raw = (byte6 & TRANSMISSION_MODE_MASK) >> TRANSMISSION_MODE_SHIFT;
359        let other_frequency_flag = (byte6 & OTHER_FREQ_FLAG_MASK) != 0;
360
361        Ok(Self {
362            centre_frequency_10hz,
363            bandwidth: parse_bandwidth(bw_raw),
364            priority,
365            time_slicing_used,
366            mpe_fec_used,
367            constellation: parse_constellation(constellation_raw),
368            hierarchy: parse_hierarchy(hierarchy_raw),
369            code_rate_hp: parse_code_rate(code_rate_hp_raw),
370            code_rate_lp: parse_code_rate(code_rate_lp_raw),
371            guard_interval: parse_guard_interval(guard_interval_raw),
372            transmission_mode: parse_transmission_mode(transmission_mode_raw),
373            other_frequency_flag,
374        })
375    }
376}
377
378impl Serialize for TerrestrialDeliverySystemDescriptor {
379    type Error = crate::error::Error;
380    fn serialized_len(&self) -> usize {
381        HEADER_LEN + BODY_LEN as usize
382    }
383
384    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
385        let len = self.serialized_len();
386        if buf.len() < len {
387            return Err(Error::OutputBufferTooSmall {
388                need: len,
389                have: buf.len(),
390            });
391        }
392        buf[0] = TAG;
393        buf[1] = BODY_LEN;
394
395        buf[2..6].copy_from_slice(&self.centre_frequency_10hz.to_be_bytes());
396
397        let byte6 = (serialize_bandwidth(self.bandwidth) << BW_SHIFT)
398            | if self.priority { PRIORITY_MASK } else { 0 }
399            | if !self.time_slicing_used {
400                TIME_SLICING_MASK
401            } else {
402                0
403            }
404            | if !self.mpe_fec_used { MPE_FEC_MASK } else { 0 }
405            | RESERVED_FU_MASK;
406        buf[6] = byte6;
407
408        let byte7 = (serialize_constellation(self.constellation) << CONSTELLATION_SHIFT)
409            | (serialize_hierarchy(self.hierarchy) << HIERARCHY_SHIFT)
410            | serialize_code_rate(self.code_rate_hp);
411        buf[7] = byte7;
412
413        let byte8 = (serialize_code_rate(self.code_rate_lp) << CODE_RATE_LP_SHIFT)
414            | (serialize_guard_interval(self.guard_interval) << GUARD_INTERVAL_SHIFT)
415            | (serialize_transmission_mode(self.transmission_mode) << TRANSMISSION_MODE_SHIFT)
416            | if self.other_frequency_flag {
417                OTHER_FREQ_FLAG_MASK
418            } else {
419                0
420            };
421        buf[8] = byte8;
422
423        buf[9..13].copy_from_slice(&TRAILING_RESERVED.to_be_bytes());
424
425        Ok(len)
426    }
427}
428impl<'a> crate::traits::DescriptorDef<'a> for TerrestrialDeliverySystemDescriptor {
429    const TAG: u8 = TAG;
430    const NAME: &'static str = "TERRESTRIAL_DELIVERY_SYSTEM";
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436
437    #[test]
438    fn parse_extracts_centre_frequency_10hz() {
439        let raw = [
440            TAG, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
441        ];
442        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
443        assert_eq!(d.centre_frequency_10hz, 0x04A858F0);
444    }
445
446    #[test]
447    fn parse_extracts_bandwidth_8mhz() {
448        let raw = [
449            TAG, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
450        ];
451        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
452        assert_eq!(d.bandwidth, Bandwidth::Mhz8);
453    }
454
455    #[test]
456    fn parse_extracts_bandwidth_7mhz() {
457        let raw = [
458            TAG,
459            BODY_LEN,
460            0x04,
461            0xA8,
462            0x58,
463            0xF0,
464            (0b001 << BW_SHIFT),
465            0x00,
466            0x00,
467            0x00,
468            0xFF,
469            0xFF,
470            0xFF,
471            0xFF,
472        ];
473        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
474        assert_eq!(d.bandwidth, Bandwidth::Mhz7);
475    }
476
477    #[test]
478    fn parse_extracts_constellation_qam64() {
479        let raw = [
480            TAG,
481            BODY_LEN,
482            0x04,
483            0xA8,
484            0x58,
485            0xF0,
486            0x00,
487            (0b10 << CONSTELLATION_SHIFT),
488            0x00,
489            0xFF,
490            0xFF,
491            0xFF,
492            0xFF,
493        ];
494        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
495        assert_eq!(d.constellation, Constellation::Qam64);
496    }
497
498    #[test]
499    fn parse_extracts_code_rate_hp_and_lp() {
500        let raw = [
501            TAG,
502            BODY_LEN,
503            0x04,
504            0xA8,
505            0x58,
506            0xF0,
507            0x00,
508            0b10 << CONSTELLATION_SHIFT,
509            0b100 << CODE_RATE_LP_SHIFT,
510            0xFF,
511            0xFF,
512            0xFF,
513            0xFF,
514        ];
515        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
516        assert_eq!(d.code_rate_hp, CodeRate::Rate1_2);
517        assert_eq!(d.code_rate_lp, CodeRate::Rate7_8);
518    }
519
520    #[test]
521    fn parse_extracts_guard_interval_1_4() {
522        let raw = [
523            TAG,
524            BODY_LEN,
525            0x04,
526            0xA8,
527            0x58,
528            0xF0,
529            0x00,
530            0x00,
531            0b11 << GUARD_INTERVAL_SHIFT,
532            0xFF,
533            0xFF,
534            0xFF,
535            0xFF,
536        ];
537        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
538        assert_eq!(d.guard_interval, GuardInterval::G1_4);
539    }
540
541    #[test]
542    fn parse_extracts_transmission_mode_8k() {
543        let raw = [
544            TAG,
545            BODY_LEN,
546            0x04,
547            0xA8,
548            0x58,
549            0xF0,
550            0x00,
551            0x00,
552            0b01 << TRANSMISSION_MODE_SHIFT,
553            0xFF,
554            0xFF,
555            0xFF,
556            0xFF,
557        ];
558        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
559        assert_eq!(d.transmission_mode, TransmissionMode::Mode8k);
560    }
561
562    #[test]
563    fn parse_extracts_other_frequency_flag() {
564        let raw = [
565            TAG,
566            BODY_LEN,
567            0x04,
568            0xA8,
569            0x58,
570            0xF0,
571            0x00,
572            0x00,
573            OTHER_FREQ_FLAG_MASK,
574            0xFF,
575            0xFF,
576            0xFF,
577            0xFF,
578        ];
579        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
580        assert!(d.other_frequency_flag);
581    }
582
583    #[test]
584    fn parse_preserves_reserved_bandwidth_in_reserve_variant() {
585        let raw = [
586            TAG,
587            BODY_LEN,
588            0x04,
589            0xA8,
590            0x58,
591            0xF0,
592            (0b111 << BW_SHIFT),
593            0x00,
594            0x00,
595            0x00,
596            0xFF,
597            0xFF,
598            0xFF,
599            0xFF,
600        ];
601        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
602        assert_eq!(d.bandwidth, Bandwidth::Reserved(0b111));
603    }
604
605    #[test]
606    fn parse_rejects_wrong_tag() {
607        let raw = [
608            0x5B, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
609        ];
610        assert!(matches!(
611            TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap_err(),
612            Error::InvalidDescriptor { tag: 0x5B, .. }
613        ));
614    }
615
616    #[test]
617    fn parse_rejects_wrong_length() {
618        let raw = [
619            TAG, 12, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
620        ];
621        assert!(matches!(
622            TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap_err(),
623            Error::InvalidDescriptor { tag: TAG, .. }
624        ));
625    }
626
627    #[test]
628    fn serialize_round_trip_full_set() {
629        let d = TerrestrialDeliverySystemDescriptor {
630            centre_frequency_10hz: 0x04A858F0,
631            bandwidth: Bandwidth::Mhz8,
632            priority: true,
633            time_slicing_used: false,
634            mpe_fec_used: true,
635            constellation: Constellation::Qam64,
636            hierarchy: Hierarchy::Alpha2Native,
637            code_rate_hp: CodeRate::Rate3_4,
638            code_rate_lp: CodeRate::Rate7_8,
639            guard_interval: GuardInterval::G1_4,
640            transmission_mode: TransmissionMode::Mode8k,
641            other_frequency_flag: true,
642        };
643        let mut buf = vec![0u8; d.serialized_len()];
644        d.serialize_into(&mut buf).unwrap();
645        let parsed = TerrestrialDeliverySystemDescriptor::parse(&buf).unwrap();
646        assert_eq!(parsed, d);
647    }
648}