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