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, serde::Deserialize))]
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, serde::Deserialize))]
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, serde::Deserialize))]
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, serde::Deserialize))]
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, serde::Deserialize))]
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, serde::Deserialize))]
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, serde::Deserialize))]
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
169fn parse_bandwidth(raw: u8) -> Bandwidth {
170    match raw {
171        0 => Bandwidth::Mhz8,
172        1 => Bandwidth::Mhz7,
173        2 => Bandwidth::Mhz6,
174        3 => Bandwidth::Mhz5,
175        other => Bandwidth::Reserved(other),
176    }
177}
178
179fn parse_constellation(raw: u8) -> Constellation {
180    match raw {
181        0 => Constellation::Qpsk,
182        1 => Constellation::Qam16,
183        2 => Constellation::Qam64,
184        other => Constellation::Reserved(other),
185    }
186}
187
188fn parse_hierarchy(raw: u8) -> Hierarchy {
189    match raw {
190        0 => Hierarchy::NonHierarchicalNative,
191        1 => Hierarchy::Alpha1Native,
192        2 => Hierarchy::Alpha2Native,
193        3 => Hierarchy::Alpha4Native,
194        4 => Hierarchy::NonHierarchicalInDepth,
195        5 => Hierarchy::Alpha1InDepth,
196        6 => Hierarchy::Alpha2InDepth,
197        7 => Hierarchy::Alpha4InDepth,
198        other => Hierarchy::Reserved(other),
199    }
200}
201
202fn parse_code_rate(raw: u8) -> CodeRate {
203    match raw {
204        0 => CodeRate::Rate1_2,
205        1 => CodeRate::Rate2_3,
206        2 => CodeRate::Rate3_4,
207        3 => CodeRate::Rate5_6,
208        4 => CodeRate::Rate7_8,
209        other => CodeRate::Reserved(other),
210    }
211}
212
213fn parse_guard_interval(raw: u8) -> GuardInterval {
214    match raw {
215        0 => GuardInterval::G1_32,
216        1 => GuardInterval::G1_16,
217        2 => GuardInterval::G1_8,
218        3 => GuardInterval::G1_4,
219        _ => GuardInterval::G1_32,
220    }
221}
222
223fn parse_transmission_mode(raw: u8) -> TransmissionMode {
224    match raw {
225        0 => TransmissionMode::Mode2k,
226        1 => TransmissionMode::Mode8k,
227        2 => TransmissionMode::Mode4k,
228        other => TransmissionMode::Reserved(other),
229    }
230}
231
232fn serialize_bandwidth(bw: Bandwidth) -> u8 {
233    match bw {
234        Bandwidth::Mhz8 => 0,
235        Bandwidth::Mhz7 => 1,
236        Bandwidth::Mhz6 => 2,
237        Bandwidth::Mhz5 => 3,
238        Bandwidth::Reserved(v) => v,
239    }
240}
241
242fn serialize_constellation(c: Constellation) -> u8 {
243    match c {
244        Constellation::Qpsk => 0,
245        Constellation::Qam16 => 1,
246        Constellation::Qam64 => 2,
247        Constellation::Reserved(v) => v,
248    }
249}
250
251fn serialize_hierarchy(h: Hierarchy) -> u8 {
252    match h {
253        Hierarchy::NonHierarchicalNative => 0,
254        Hierarchy::Alpha1Native => 1,
255        Hierarchy::Alpha2Native => 2,
256        Hierarchy::Alpha4Native => 3,
257        Hierarchy::NonHierarchicalInDepth => 4,
258        Hierarchy::Alpha1InDepth => 5,
259        Hierarchy::Alpha2InDepth => 6,
260        Hierarchy::Alpha4InDepth => 7,
261        Hierarchy::Reserved(v) => v,
262    }
263}
264
265fn serialize_code_rate(cr: CodeRate) -> u8 {
266    match cr {
267        CodeRate::Rate1_2 => 0,
268        CodeRate::Rate2_3 => 1,
269        CodeRate::Rate3_4 => 2,
270        CodeRate::Rate5_6 => 3,
271        CodeRate::Rate7_8 => 4,
272        CodeRate::Reserved(v) => v,
273    }
274}
275
276fn serialize_guard_interval(gi: GuardInterval) -> u8 {
277    match gi {
278        GuardInterval::G1_32 => 0,
279        GuardInterval::G1_16 => 1,
280        GuardInterval::G1_8 => 2,
281        GuardInterval::G1_4 => 3,
282    }
283}
284
285fn serialize_transmission_mode(tm: TransmissionMode) -> u8 {
286    match tm {
287        TransmissionMode::Mode2k => 0,
288        TransmissionMode::Mode8k => 1,
289        TransmissionMode::Mode4k => 2,
290        TransmissionMode::Reserved(v) => v,
291    }
292}
293
294impl<'a> Parse<'a> for TerrestrialDeliverySystemDescriptor {
295    type Error = crate::error::Error;
296    fn parse(bytes: &'a [u8]) -> Result<Self> {
297        if bytes.len() < HEADER_LEN + BODY_LEN as usize {
298            return Err(Error::BufferTooShort {
299                need: HEADER_LEN + BODY_LEN as usize,
300                have: bytes.len(),
301                what: "TerrestrialDeliverySystemDescriptor",
302            });
303        }
304        if bytes[0] != TAG {
305            return Err(Error::InvalidDescriptor {
306                tag: bytes[0],
307                reason: "unexpected tag for terrestrial_delivery_system_descriptor",
308            });
309        }
310        let length = bytes[1];
311        if length != BODY_LEN {
312            return Err(Error::InvalidDescriptor {
313                tag: TAG,
314                reason: "body length must equal 11",
315            });
316        }
317
318        let centre_frequency_10hz = u32::from_be_bytes(bytes[2..6].try_into().unwrap());
319
320        let byte6 = bytes[6];
321        let bw_raw = (byte6 & BW_MASK) >> BW_SHIFT;
322        let priority = (byte6 & PRIORITY_MASK) != 0;
323        let time_slicing_used = (byte6 & TIME_SLICING_MASK) == 0;
324        let mpe_fec_used = (byte6 & MPE_FEC_MASK) == 0;
325
326        let byte7 = bytes[7];
327        let constellation_raw = (byte7 & CONSTELLATION_MASK) >> CONSTELLATION_SHIFT;
328        let hierarchy_raw = (byte7 & HIERARCHY_MASK) >> HIERARCHY_SHIFT;
329        let code_rate_hp_raw = byte7 & CODE_RATE_HP_MASK;
330
331        let byte8 = bytes[8];
332        let code_rate_lp_raw = (byte8 & CODE_RATE_LP_MASK) >> CODE_RATE_LP_SHIFT;
333        let guard_interval_raw = (byte8 & GUARD_INTERVAL_MASK) >> GUARD_INTERVAL_SHIFT;
334        let transmission_mode_raw = (byte8 & TRANSMISSION_MODE_MASK) >> TRANSMISSION_MODE_SHIFT;
335        let other_frequency_flag = (byte8 & OTHER_FREQ_FLAG_MASK) != 0;
336
337        Ok(Self {
338            centre_frequency_10hz,
339            bandwidth: parse_bandwidth(bw_raw),
340            priority,
341            time_slicing_used,
342            mpe_fec_used,
343            constellation: parse_constellation(constellation_raw),
344            hierarchy: parse_hierarchy(hierarchy_raw),
345            code_rate_hp: parse_code_rate(code_rate_hp_raw),
346            code_rate_lp: parse_code_rate(code_rate_lp_raw),
347            guard_interval: parse_guard_interval(guard_interval_raw),
348            transmission_mode: parse_transmission_mode(transmission_mode_raw),
349            other_frequency_flag,
350        })
351    }
352}
353
354impl Serialize for TerrestrialDeliverySystemDescriptor {
355    type Error = crate::error::Error;
356    fn serialized_len(&self) -> usize {
357        HEADER_LEN + BODY_LEN as usize
358    }
359
360    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
361        let len = self.serialized_len();
362        if buf.len() < len {
363            return Err(Error::OutputBufferTooSmall {
364                need: len,
365                have: buf.len(),
366            });
367        }
368        buf[0] = TAG;
369        buf[1] = BODY_LEN;
370
371        buf[2..6].copy_from_slice(&self.centre_frequency_10hz.to_be_bytes());
372
373        let byte6 = (serialize_bandwidth(self.bandwidth) << BW_SHIFT)
374            | if self.priority { PRIORITY_MASK } else { 0 }
375            | if !self.time_slicing_used {
376                TIME_SLICING_MASK
377            } else {
378                0
379            }
380            | if !self.mpe_fec_used { MPE_FEC_MASK } else { 0 }
381            | RESERVED_FU_MASK;
382        buf[6] = byte6;
383
384        let byte7 = (serialize_constellation(self.constellation) << CONSTELLATION_SHIFT)
385            | (serialize_hierarchy(self.hierarchy) << HIERARCHY_SHIFT)
386            | serialize_code_rate(self.code_rate_hp);
387        buf[7] = byte7;
388
389        let byte8 = (serialize_code_rate(self.code_rate_lp) << CODE_RATE_LP_SHIFT)
390            | (serialize_guard_interval(self.guard_interval) << GUARD_INTERVAL_SHIFT)
391            | (serialize_transmission_mode(self.transmission_mode) << TRANSMISSION_MODE_SHIFT)
392            | if self.other_frequency_flag {
393                OTHER_FREQ_FLAG_MASK
394            } else {
395                0
396            };
397        buf[8] = byte8;
398
399        buf[9..13].copy_from_slice(&TRAILING_RESERVED.to_be_bytes());
400
401        Ok(len)
402    }
403}
404
405impl<'a> Descriptor<'a> for TerrestrialDeliverySystemDescriptor {
406    const TAG: u8 = TAG;
407
408    fn descriptor_length(&self) -> u8 {
409        BODY_LEN
410    }
411}
412
413impl<'a> crate::traits::DescriptorDef<'a> for TerrestrialDeliverySystemDescriptor {
414    const TAG: u8 = TAG;
415    const NAME: &'static str = "TERRESTRIAL_DELIVERY_SYSTEM";
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421
422    #[test]
423    fn parse_extracts_centre_frequency_10hz() {
424        let raw = [
425            TAG, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
426        ];
427        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
428        assert_eq!(d.centre_frequency_10hz, 0x04A858F0);
429    }
430
431    #[test]
432    fn parse_extracts_bandwidth_8mhz() {
433        let raw = [
434            TAG, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
435        ];
436        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
437        assert_eq!(d.bandwidth, Bandwidth::Mhz8);
438    }
439
440    #[test]
441    fn parse_extracts_bandwidth_7mhz() {
442        let raw = [
443            TAG,
444            BODY_LEN,
445            0x04,
446            0xA8,
447            0x58,
448            0xF0,
449            (0b001 << BW_SHIFT),
450            0x00,
451            0x00,
452            0x00,
453            0xFF,
454            0xFF,
455            0xFF,
456            0xFF,
457        ];
458        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
459        assert_eq!(d.bandwidth, Bandwidth::Mhz7);
460    }
461
462    #[test]
463    fn parse_extracts_constellation_qam64() {
464        let raw = [
465            TAG,
466            BODY_LEN,
467            0x04,
468            0xA8,
469            0x58,
470            0xF0,
471            0x00,
472            (0b10 << CONSTELLATION_SHIFT),
473            0x00,
474            0xFF,
475            0xFF,
476            0xFF,
477            0xFF,
478        ];
479        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
480        assert_eq!(d.constellation, Constellation::Qam64);
481    }
482
483    #[test]
484    fn parse_extracts_code_rate_hp_and_lp() {
485        let raw = [
486            TAG,
487            BODY_LEN,
488            0x04,
489            0xA8,
490            0x58,
491            0xF0,
492            0x00,
493            0b10 << CONSTELLATION_SHIFT,
494            0b100 << CODE_RATE_LP_SHIFT,
495            0xFF,
496            0xFF,
497            0xFF,
498            0xFF,
499        ];
500        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
501        assert_eq!(d.code_rate_hp, CodeRate::Rate1_2);
502        assert_eq!(d.code_rate_lp, CodeRate::Rate7_8);
503    }
504
505    #[test]
506    fn parse_extracts_guard_interval_1_4() {
507        let raw = [
508            TAG,
509            BODY_LEN,
510            0x04,
511            0xA8,
512            0x58,
513            0xF0,
514            0x00,
515            0x00,
516            0b11 << GUARD_INTERVAL_SHIFT,
517            0xFF,
518            0xFF,
519            0xFF,
520            0xFF,
521        ];
522        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
523        assert_eq!(d.guard_interval, GuardInterval::G1_4);
524    }
525
526    #[test]
527    fn parse_extracts_transmission_mode_8k() {
528        let raw = [
529            TAG,
530            BODY_LEN,
531            0x04,
532            0xA8,
533            0x58,
534            0xF0,
535            0x00,
536            0x00,
537            0b01 << TRANSMISSION_MODE_SHIFT,
538            0xFF,
539            0xFF,
540            0xFF,
541            0xFF,
542        ];
543        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
544        assert_eq!(d.transmission_mode, TransmissionMode::Mode8k);
545    }
546
547    #[test]
548    fn parse_extracts_other_frequency_flag() {
549        let raw = [
550            TAG,
551            BODY_LEN,
552            0x04,
553            0xA8,
554            0x58,
555            0xF0,
556            0x00,
557            0x00,
558            OTHER_FREQ_FLAG_MASK,
559            0xFF,
560            0xFF,
561            0xFF,
562            0xFF,
563        ];
564        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
565        assert!(d.other_frequency_flag);
566    }
567
568    #[test]
569    fn parse_preserves_reserved_bandwidth_in_reserve_variant() {
570        let raw = [
571            TAG,
572            BODY_LEN,
573            0x04,
574            0xA8,
575            0x58,
576            0xF0,
577            (0b111 << BW_SHIFT),
578            0x00,
579            0x00,
580            0x00,
581            0xFF,
582            0xFF,
583            0xFF,
584            0xFF,
585        ];
586        let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
587        assert_eq!(d.bandwidth, Bandwidth::Reserved(0b111));
588    }
589
590    #[test]
591    fn parse_rejects_wrong_tag() {
592        let raw = [
593            0x5B, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
594        ];
595        assert!(matches!(
596            TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap_err(),
597            Error::InvalidDescriptor { tag: 0x5B, .. }
598        ));
599    }
600
601    #[test]
602    fn parse_rejects_wrong_length() {
603        let raw = [
604            TAG, 12, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
605        ];
606        assert!(matches!(
607            TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap_err(),
608            Error::InvalidDescriptor { tag: TAG, .. }
609        ));
610    }
611
612    #[test]
613    fn serialize_round_trip_full_set() {
614        let d = TerrestrialDeliverySystemDescriptor {
615            centre_frequency_10hz: 0x04A858F0,
616            bandwidth: Bandwidth::Mhz8,
617            priority: true,
618            time_slicing_used: false,
619            mpe_fec_used: true,
620            constellation: Constellation::Qam64,
621            hierarchy: Hierarchy::Alpha2Native,
622            code_rate_hp: CodeRate::Rate3_4,
623            code_rate_lp: CodeRate::Rate7_8,
624            guard_interval: GuardInterval::G1_4,
625            transmission_mode: TransmissionMode::Mode8k,
626            other_frequency_flag: true,
627        };
628        let mut buf = vec![0u8; d.serialized_len()];
629        d.serialize_into(&mut buf).unwrap();
630        let parsed = TerrestrialDeliverySystemDescriptor::parse(&buf).unwrap();
631        assert_eq!(parsed, d);
632    }
633}