Skip to main content

dvb_si/descriptors/
frequency_list.rs

1//! Frequency List Descriptor — ETSI EN 300 468 §6.2.17 (tag 0x62).
2//!
3//! Carried inside the NIT's transport_stream_loop second descriptor loop.
4//! Enumerates alternative centre frequencies on which the TS can be found,
5//! for handover when coverage changes.
6
7use super::descriptor_body;
8use crate::error::{Error, Result};
9use dvb_common::{Parse, Serialize};
10
11/// Descriptor tag for frequency_list_descriptor.
12pub const TAG: u8 = 0x62;
13/// Length of the header (tag byte + length byte).
14pub const HEADER_LEN: usize = 2;
15/// Length of the coding_type byte inside the descriptor body.
16pub const CODING_BYTE_LEN: usize = 1;
17/// Length of a single frequency entry in bytes.
18pub const ENTRY_LEN: usize = 4;
19/// Mask for the coding_type bits (bottom 2 bits); the top 6 bits are reserved.
20pub const CODING_TYPE_MASK: u8 = 0x03;
21/// Reserved bits (top 6 of the coding byte). Ignored on parse, set to 1 on serialize.
22pub const RESERVED_BITS_MASK: u8 = 0xFC;
23
24/// Coding type selects the interpretation of each 4-byte BCD frequency.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27pub enum CodingType {
28    /// Not defined (coding_type = 0b00).
29    Undefined,
30    /// Satellite — 8 BCD digits in 1/100 MHz (GHz.MMMM).
31    Satellite,
32    /// Cable — 8 BCD digits in 100 Hz units.
33    Cable,
34    /// Terrestrial — 8 BCD digits in 100 Hz units.
35    Terrestrial,
36}
37
38/// Frequency List Descriptor.
39#[derive(Debug, Clone, PartialEq, Eq)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize))]
41pub struct FrequencyListDescriptor {
42    /// Interpretation of every `centre_frequencies_bcd` entry.
43    pub coding_type: CodingType,
44    /// Raw 4-byte BCD centre_frequency entries in wire order.
45    pub centre_frequencies_bcd: Vec<[u8; 4]>,
46}
47
48impl FrequencyListDescriptor {
49    /// Hz per BCD-decoded unit for the current `coding_type`, or `None` for
50    /// `Undefined`. Satellite entries decode like
51    /// [`SatelliteDeliverySystemDescriptor`](super::satellite_delivery_system::SatelliteDeliverySystemDescriptor)
52    /// (1 kHz units); cable/terrestrial entries are 100 Hz units.
53    fn hz_per_unit(&self) -> Option<u64> {
54        match self.coding_type {
55            CodingType::Satellite => Some(1_000),
56            CodingType::Cable | CodingType::Terrestrial => Some(100),
57            CodingType::Undefined => None,
58        }
59    }
60
61    /// Decode every `centre_frequencies_bcd` entry to Hz, interpreted per
62    /// `coding_type`. Each element is `None` if its BCD is invalid or
63    /// `coding_type` is `Undefined`.
64    #[must_use]
65    pub fn centre_frequencies_hz(&self) -> Vec<Option<u64>> {
66        let scale = self.hz_per_unit();
67        self.centre_frequencies_bcd
68            .iter()
69            .map(|b| {
70                let value = dvb_common::bcd::bcd_to_decimal(u64::from(u32::from_be_bytes(*b)), 8)?;
71                Some(value * scale?)
72            })
73            .collect()
74    }
75
76    /// Replace the entries by encoding each Hz value to a 4-byte BCD entry per
77    /// `coding_type` (values truncate to the field's resolution).
78    ///
79    /// # Errors
80    /// [`ValueOutOfRange`](crate::Error::ValueOutOfRange) if `coding_type`
81    /// is `Undefined` or a value exceeds the 8-digit BCD entry.
82    pub fn set_centre_frequencies_hz(&mut self, frequencies_hz: &[u64]) -> crate::Result<()> {
83        let scale = self.hz_per_unit().ok_or(crate::Error::ValueOutOfRange {
84            field: "FrequencyListDescriptor::centre_frequency",
85            reason: "coding_type is Undefined; cannot encode frequencies",
86        })?;
87        let mut out = Vec::with_capacity(frequencies_hz.len());
88        for &hz in frequencies_hz {
89            let bcd = super::encode_bcd_field(
90                hz / scale,
91                8,
92                "FrequencyListDescriptor::centre_frequency",
93            )?;
94            out.push((bcd as u32).to_be_bytes());
95        }
96        self.centre_frequencies_bcd = out;
97        Ok(())
98    }
99}
100
101impl<'a> Parse<'a> for FrequencyListDescriptor {
102    type Error = crate::error::Error;
103    fn parse(bytes: &'a [u8]) -> Result<Self> {
104        let body = descriptor_body(bytes, TAG, "FrequencyListDescriptor", "expected tag 0x62")?;
105
106        if body.len() < CODING_BYTE_LEN {
107            return Err(Error::InvalidDescriptor {
108                tag: TAG,
109                reason: "body too short (need at least coding_type byte)",
110            });
111        }
112
113        if (body.len() - CODING_BYTE_LEN) % ENTRY_LEN != 0 {
114            return Err(Error::InvalidDescriptor {
115                tag: TAG,
116                reason: "body length minus coding byte must be multiple of 4",
117            });
118        }
119
120        let coding_byte = body[0];
121        // Top 6 bits are reserved_future_use — ignored on parse
122        // (EN 300 468 §5.1: decoders shall ignore reserved bits).
123        let coding_type_value = coding_byte & CODING_TYPE_MASK;
124        let coding_type = match coding_type_value {
125            0b00 => CodingType::Undefined,
126            0b01 => CodingType::Satellite,
127            0b10 => CodingType::Cable,
128            _ => CodingType::Terrestrial,
129        };
130
131        let entry_count = (body.len() - CODING_BYTE_LEN) / ENTRY_LEN;
132        let mut centre_frequencies_bcd = Vec::with_capacity(entry_count);
133
134        let mut offset = CODING_BYTE_LEN;
135        for _ in 0..entry_count {
136            let mut entry = [0u8; ENTRY_LEN];
137            entry.copy_from_slice(&body[offset..offset + ENTRY_LEN]);
138            centre_frequencies_bcd.push(entry);
139            offset += ENTRY_LEN;
140        }
141
142        Ok(FrequencyListDescriptor {
143            coding_type,
144            centre_frequencies_bcd,
145        })
146    }
147}
148
149impl Serialize for FrequencyListDescriptor {
150    type Error = crate::error::Error;
151    fn serialized_len(&self) -> usize {
152        HEADER_LEN + CODING_BYTE_LEN + self.centre_frequencies_bcd.len() * ENTRY_LEN
153    }
154
155    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
156        let need = self.serialized_len();
157        if buf.len() < need {
158            return Err(Error::OutputBufferTooSmall {
159                need,
160                have: buf.len(),
161            });
162        }
163
164        let coding_type_bits = match self.coding_type {
165            CodingType::Undefined => 0b00,
166            CodingType::Satellite => 0b01,
167            CodingType::Cable => 0b10,
168            CodingType::Terrestrial => 0b11,
169        };
170
171        let body_length = CODING_BYTE_LEN + self.centre_frequencies_bcd.len() * ENTRY_LEN;
172
173        buf[0] = TAG;
174        buf[1] = body_length as u8;
175        buf[HEADER_LEN] = RESERVED_BITS_MASK | coding_type_bits;
176
177        let mut offset = HEADER_LEN + CODING_BYTE_LEN;
178        for entry in &self.centre_frequencies_bcd {
179            buf[offset..offset + ENTRY_LEN].copy_from_slice(entry);
180            offset += ENTRY_LEN;
181        }
182
183        Ok(need)
184    }
185}
186impl<'a> crate::traits::DescriptorDef<'a> for FrequencyListDescriptor {
187    const TAG: u8 = TAG;
188    const NAME: &'static str = "FREQUENCY_LIST";
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    /// [TAG, 1, 0xFC] → zero entries, coding=Undefined
196    #[test]
197    fn parse_empty_entries_is_valid() {
198        let raw: Vec<u8> = vec![TAG, 0x01, 0xFC];
199        let desc = FrequencyListDescriptor::parse(&raw).unwrap();
200        assert!(desc.centre_frequencies_bcd.is_empty());
201        assert!(matches!(desc.coding_type, CodingType::Undefined));
202    }
203
204    /// coding_type byte 0xFD → Satellite (0xFC | 0b01)
205    #[test]
206    fn parse_extracts_coding_type_satellite() {
207        let raw: Vec<u8> = vec![TAG, 0x01, 0xFD];
208        let desc = FrequencyListDescriptor::parse(&raw).unwrap();
209        assert!(matches!(desc.coding_type, CodingType::Satellite));
210    }
211
212    /// coding_type byte 0xFE → Cable (0xFC | 0b10)
213    #[test]
214    fn parse_extracts_coding_type_cable() {
215        let raw: Vec<u8> = vec![TAG, 0x01, 0xFE];
216        let desc = FrequencyListDescriptor::parse(&raw).unwrap();
217        assert!(matches!(desc.coding_type, CodingType::Cable));
218    }
219
220    /// coding_type byte 0xFF → Terrestrial (0xFC | 0b11)
221    #[test]
222    fn parse_extracts_coding_type_terrestrial() {
223        let raw: Vec<u8> = vec![TAG, 0x01, 0xFF];
224        let desc = FrequencyListDescriptor::parse(&raw).unwrap();
225        assert!(matches!(desc.coding_type, CodingType::Terrestrial));
226    }
227
228    /// Multiple 4-byte entries parsed correctly.
229    #[test]
230    fn parse_extracts_multiple_frequency_entries() {
231        let raw: Vec<u8> = vec![
232            TAG, 0x09, // body length = 9 (1 coding byte + 2 entries × 4)
233            0xFD, // satellite
234            0x02, 0x75, 0x00, 0x00, // 27.50000 GHz
235            0x03, 0x00, 0x00, 0x00, // 30.00000 GHz
236        ];
237        let desc = FrequencyListDescriptor::parse(&raw).unwrap();
238        assert_eq!(desc.centre_frequencies_bcd.len(), 2);
239        assert_eq!(desc.centre_frequencies_bcd[0], [0x02, 0x75, 0x00, 0x00]);
240        assert_eq!(desc.centre_frequencies_bcd[1], [0x03, 0x00, 0x00, 0x00]);
241    }
242
243    /// Wrong tag byte should return InvalidDescriptor.
244    #[test]
245    fn parse_rejects_wrong_tag() {
246        let raw: Vec<u8> = vec![0x63, 0x01, 0xFC];
247        let err = FrequencyListDescriptor::parse(&raw).unwrap_err();
248        assert!(
249            matches!(err, Error::InvalidDescriptor { tag: 0x63, .. }),
250            "expected InvalidDescriptor(tag=0x63), got {err:?}"
251        );
252    }
253
254    /// Reserved bits set to zero must be ignored, not rejected (EN 300 468 §5.1).
255    #[test]
256    fn parse_ignores_reserved_bits() {
257        // coding byte 0x03: top 6 reserved bits = 0, coding_type = 0b11 (terrestrial).
258        let raw: Vec<u8> = vec![TAG, 0x01, 0x03];
259        let d = FrequencyListDescriptor::parse(&raw).unwrap();
260        assert_eq!(d.coding_type, CodingType::Terrestrial);
261        assert!(d.centre_frequencies_bcd.is_empty());
262    }
263
264    /// Body length not 1 + multiple of 4 → InvalidDescriptor
265    #[test]
266    fn parse_rejects_length_not_1_plus_multiple_of_4() {
267        let raw: Vec<u8> = vec![TAG, 0x03, 0xFC, 0x01, 0x02]; // body=3, need 1+4K
268        let err = FrequencyListDescriptor::parse(&raw).unwrap_err();
269        assert!(matches!(err, Error::InvalidDescriptor { .. }));
270    }
271
272    /// Buffer shorter than the 2-byte header → BufferTooShort
273    #[test]
274    fn parse_rejects_truncated_buffer() {
275        let raw: &[u8] = &[TAG];
276        let err = FrequencyListDescriptor::parse(raw).unwrap_err();
277        assert!(matches!(err, Error::BufferTooShort { need: 2, .. }));
278    }
279
280    /// Serialize a descriptor with zero entries, re-parse, compare.
281    #[test]
282    fn serialize_round_trip_empty() {
283        let desc = FrequencyListDescriptor {
284            coding_type: CodingType::Satellite,
285            centre_frequencies_bcd: vec![],
286        };
287        let raw: Vec<u8> = vec![TAG, 0x01, 0xFD];
288        let mut buf = vec![0u8; desc.serialized_len()];
289        let written = desc.serialize_into(&mut buf).unwrap();
290        assert_eq!(written, raw.len());
291        assert_eq!(buf, raw);
292
293        let reparsed = FrequencyListDescriptor::parse(&buf).unwrap();
294        assert_eq!(desc.coding_type, reparsed.coding_type);
295        assert_eq!(desc.centre_frequencies_bcd, reparsed.centre_frequencies_bcd);
296    }
297
298    /// Serialize a descriptor with many entries, re-parse, compare.
299    #[test]
300    fn serialize_round_trip_many_entries() {
301        let desc = FrequencyListDescriptor {
302            coding_type: CodingType::Cable,
303            centre_frequencies_bcd: vec![
304                [0x02, 0x75, 0x00, 0x00],
305                [0x03, 0x00, 0x00, 0x00],
306                [0x01, 0x15, 0x50, 0x00],
307                [0x04, 0x90, 0x25, 0x00],
308            ],
309        };
310        let mut buf = vec![0u8; desc.serialized_len()];
311        desc.serialize_into(&mut buf).unwrap();
312        let reparsed = FrequencyListDescriptor::parse(&buf).unwrap();
313        assert_eq!(desc.coding_type, reparsed.coding_type);
314        assert_eq!(desc.centre_frequencies_bcd, reparsed.centre_frequencies_bcd);
315    }
316}