Skip to main content

dvb_si/descriptors/
cell_frequency_link.rs

1//! Cell Frequency Link Descriptor — ETSI EN 300 468 §6.2.7 (tag 0x6D, Table 23, PDF p. 58).
2//!
3//! Carried inside the NIT. Associates terrestrial cells with their transmit
4//! frequency and any transposer (gap-filler) sub-cells. Body layout (Table 23):
5//!
6//! ```text
7//! for (i=0;i<N;i++) {
8//!   cell_id                 16
9//!   frequency               32
10//!   subcell_info_loop_length 8
11//!   for (j=0;j<N;j++) {
12//!     cell_id_extension      8
13//!     transposer_frequency  32
14//!   }
15//! }
16//! ```
17//!
18//! Both loops are typed. `frequency`/`transposer_frequency` are raw u32 units
19//! (4 bytes, 10 Hz units per §6.2.7); we preserve the numeric value verbatim.
20
21use super::descriptor_body;
22use crate::error::{Error, Result};
23use alloc::vec::Vec;
24use dvb_common::{Parse, Serialize};
25
26/// Descriptor tag for cell_frequency_link_descriptor.
27pub const TAG: u8 = 0x6D;
28/// Length of the header (tag byte + length byte).
29pub const HEADER_LEN: usize = 2;
30/// Fixed bytes per outer entry before the subcell loop: cell_id(2)+frequency(4)+loop_len(1).
31pub const OUTER_FIXED_LEN: usize = 7;
32/// Bytes per subcell entry: cell_id_extension(1)+transposer_frequency(4).
33pub const SUBCELL_LEN: usize = 5;
34
35/// One transposer sub-cell.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize))]
38pub struct CellFrequencyLinkSubcell {
39    /// 8-bit cell_id_extension.
40    pub cell_id_extension: u8,
41    /// 32-bit transposer_frequency (10 Hz units).
42    pub transposer_frequency: u32,
43}
44
45impl CellFrequencyLinkSubcell {
46    /// Decode transposer_frequency to Hz (10 Hz field resolution).
47    #[must_use]
48    pub fn transposer_frequency_hz(&self) -> u64 {
49        u64::from(self.transposer_frequency) * 10
50    }
51}
52
53/// One cell-frequency association with its sub-cell list.
54#[derive(Debug, Clone, PartialEq, Eq)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize))]
56pub struct CellFrequencyLinkEntry {
57    /// 16-bit cell_id.
58    pub cell_id: u16,
59    /// 32-bit frequency (10 Hz units).
60    pub frequency: u32,
61    /// Transposer sub-cells for this cell.
62    pub subcells: Vec<CellFrequencyLinkSubcell>,
63}
64
65impl CellFrequencyLinkEntry {
66    /// Decode frequency to Hz (10 Hz field resolution).
67    #[must_use]
68    pub fn frequency_hz(&self) -> u64 {
69        u64::from(self.frequency) * 10
70    }
71}
72
73/// Cell Frequency Link Descriptor.
74#[derive(Debug, Clone, PartialEq, Eq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize))]
76pub struct CellFrequencyLinkDescriptor {
77    /// Outer cell entries in wire order.
78    pub entries: Vec<CellFrequencyLinkEntry>,
79}
80
81impl<'a> Parse<'a> for CellFrequencyLinkDescriptor {
82    type Error = crate::error::Error;
83    fn parse(bytes: &'a [u8]) -> Result<Self> {
84        let body = descriptor_body(
85            bytes,
86            TAG,
87            "CellFrequencyLinkDescriptor",
88            "unexpected tag for cell_frequency_link_descriptor",
89        )?;
90        let mut entries = Vec::new();
91        let mut pos = 0;
92        while pos < body.len() {
93            if pos + OUTER_FIXED_LEN > body.len() {
94                return Err(Error::InvalidDescriptor {
95                    tag: TAG,
96                    reason: "cell_frequency_link outer entry truncated",
97                });
98            }
99            let (outer, _) = body
100                .get(pos..)
101                .and_then(|s| s.split_first_chunk::<OUTER_FIXED_LEN>())
102                .ok_or(Error::InvalidDescriptor {
103                    tag: TAG,
104                    reason: "cell_frequency_link outer entry truncated",
105                })?;
106            let cell_id = u16::from_be_bytes([outer[0], outer[1]]);
107            let frequency = u32::from_be_bytes([outer[2], outer[3], outer[4], outer[5]]);
108            let subcell_info_loop_length = outer[6] as usize;
109            pos += OUTER_FIXED_LEN;
110            if subcell_info_loop_length % SUBCELL_LEN != 0 {
111                return Err(Error::InvalidDescriptor {
112                    tag: TAG,
113                    reason: "subcell_info_loop_length must be a multiple of 5",
114                });
115            }
116            if pos + subcell_info_loop_length > body.len() {
117                return Err(Error::InvalidDescriptor {
118                    tag: TAG,
119                    reason: "subcell_info_loop_length exceeds descriptor body",
120                });
121            }
122            let subcell_count = subcell_info_loop_length / SUBCELL_LEN;
123            let mut subcells = Vec::with_capacity(subcell_count);
124            for _ in 0..subcell_count {
125                let (sub, _) = body
126                    .get(pos..)
127                    .and_then(|s| s.split_first_chunk::<SUBCELL_LEN>())
128                    .ok_or(Error::InvalidDescriptor {
129                        tag: TAG,
130                        reason: "subcell_info_loop_length exceeds descriptor body",
131                    })?;
132                let cell_id_extension = sub[0];
133                let transposer_frequency = u32::from_be_bytes([sub[1], sub[2], sub[3], sub[4]]);
134                subcells.push(CellFrequencyLinkSubcell {
135                    cell_id_extension,
136                    transposer_frequency,
137                });
138                pos += SUBCELL_LEN;
139            }
140            entries.push(CellFrequencyLinkEntry {
141                cell_id,
142                frequency,
143                subcells,
144            });
145        }
146        Ok(Self { entries })
147    }
148}
149
150impl CellFrequencyLinkDescriptor {
151    fn body_len(&self) -> usize {
152        self.entries
153            .iter()
154            .map(|e| OUTER_FIXED_LEN + e.subcells.len() * SUBCELL_LEN)
155            .sum()
156    }
157}
158
159impl Serialize for CellFrequencyLinkDescriptor {
160    type Error = crate::error::Error;
161    fn serialized_len(&self) -> usize {
162        HEADER_LEN + self.body_len()
163    }
164
165    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
166        let body_len = self.body_len();
167        if body_len > u8::MAX as usize {
168            return Err(Error::InvalidDescriptor {
169                tag: TAG,
170                reason: "cell_frequency_link_descriptor body exceeds 255 bytes",
171            });
172        }
173        for e in &self.entries {
174            if e.subcells.len() * SUBCELL_LEN > u8::MAX as usize {
175                return Err(Error::InvalidDescriptor {
176                    tag: TAG,
177                    reason: "subcell_info_loop_length exceeds 255 bytes",
178                });
179            }
180        }
181        let len = self.serialized_len();
182        if buf.len() < len {
183            return Err(Error::OutputBufferTooSmall {
184                need: len,
185                have: buf.len(),
186            });
187        }
188        buf[0] = TAG;
189        buf[1] = body_len as u8;
190        let mut pos = HEADER_LEN;
191        for e in &self.entries {
192            buf[pos..pos + 2].copy_from_slice(&e.cell_id.to_be_bytes());
193            buf[pos + 2..pos + 6].copy_from_slice(&e.frequency.to_be_bytes());
194            buf[pos + 6] = (e.subcells.len() * SUBCELL_LEN) as u8;
195            pos += OUTER_FIXED_LEN;
196            for sc in &e.subcells {
197                buf[pos] = sc.cell_id_extension;
198                buf[pos + 1..pos + 5].copy_from_slice(&sc.transposer_frequency.to_be_bytes());
199                pos += SUBCELL_LEN;
200            }
201        }
202        Ok(len)
203    }
204}
205impl<'a> crate::traits::DescriptorDef<'a> for CellFrequencyLinkDescriptor {
206    const TAG: u8 = TAG;
207    const NAME: &'static str = "CELL_FREQUENCY_LINK";
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn frequency_hz_computes_correctly() {
216        let entry = CellFrequencyLinkEntry {
217            cell_id: 0,
218            frequency: 100_000,
219            subcells: vec![],
220        };
221        assert_eq!(entry.frequency_hz(), 1_000_000);
222    }
223
224    #[test]
225    fn transposer_frequency_hz_computes_correctly() {
226        let sc = CellFrequencyLinkSubcell {
227            cell_id_extension: 0,
228            transposer_frequency: 500_000,
229        };
230        assert_eq!(sc.transposer_frequency_hz(), 5_000_000);
231    }
232
233    #[test]
234    fn parse_entry_with_subcells() {
235        let bytes = [
236            TAG, 17, // body length
237            // outer: cell_id=0x1234, freq=0x00112233, subcell_loop_len=10
238            0x12, 0x34, 0x00, 0x11, 0x22, 0x33, 10, // subcell 1: ext=0x01, freq=0x0AABBCCD
239            0x01, 0x0A, 0xAB, 0xBC, 0xCD, // subcell 2: ext=0x02, freq=0x0DDEEFF0
240            0x02, 0x0D, 0xDE, 0xEF, 0xF0,
241        ];
242        let d = CellFrequencyLinkDescriptor::parse(&bytes).unwrap();
243        assert_eq!(d.entries.len(), 1);
244        assert_eq!(d.entries[0].cell_id, 0x1234);
245        assert_eq!(d.entries[0].frequency, 0x0011_2233);
246        assert_eq!(d.entries[0].subcells.len(), 2);
247        assert_eq!(d.entries[0].subcells[0].cell_id_extension, 0x01);
248        assert_eq!(d.entries[0].subcells[0].transposer_frequency, 0x0AAB_BCCD);
249        assert_eq!(d.entries[0].subcells[1].cell_id_extension, 0x02);
250    }
251
252    #[test]
253    fn parse_entry_no_subcells() {
254        let bytes = [TAG, 7, 0x00, 0x05, 0x00, 0x00, 0x10, 0x00, 0x00];
255        let d = CellFrequencyLinkDescriptor::parse(&bytes).unwrap();
256        assert_eq!(d.entries.len(), 1);
257        assert_eq!(d.entries[0].cell_id, 0x0005);
258        assert!(d.entries[0].subcells.is_empty());
259    }
260
261    #[test]
262    fn empty_body_is_valid() {
263        let d = CellFrequencyLinkDescriptor::parse(&[TAG, 0]).unwrap();
264        assert!(d.entries.is_empty());
265    }
266
267    #[test]
268    fn parse_rejects_wrong_tag() {
269        assert!(matches!(
270            CellFrequencyLinkDescriptor::parse(&[0x6E, 0]).unwrap_err(),
271            Error::InvalidDescriptor { tag: 0x6E, .. }
272        ));
273    }
274
275    #[test]
276    fn parse_rejects_truncated_outer() {
277        // body length 5 < OUTER_FIXED_LEN(7)
278        let bytes = [TAG, 5, 0x00, 0x01, 0x00, 0x00, 0x10];
279        assert!(matches!(
280            CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
281            Error::InvalidDescriptor { tag: TAG, .. }
282        ));
283    }
284
285    #[test]
286    fn parse_rejects_subcell_loop_overrun() {
287        // subcell_loop_len=10 but no subcell bytes follow
288        let bytes = [TAG, 7, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 10];
289        assert!(matches!(
290            CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
291            Error::InvalidDescriptor { tag: TAG, .. }
292        ));
293    }
294
295    #[test]
296    fn parse_rejects_buffer_shorter_than_length() {
297        let bytes = [TAG, 7, 0x00, 0x01, 0x00];
298        assert!(matches!(
299            CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
300            Error::BufferTooShort { .. }
301        ));
302    }
303
304    #[test]
305    fn serialize_round_trip() {
306        let d = CellFrequencyLinkDescriptor {
307            entries: vec![
308                CellFrequencyLinkEntry {
309                    cell_id: 0x1234,
310                    frequency: 0x0011_2233,
311                    subcells: vec![
312                        CellFrequencyLinkSubcell {
313                            cell_id_extension: 0x01,
314                            transposer_frequency: 0x0AAB_BCCD,
315                        },
316                        CellFrequencyLinkSubcell {
317                            cell_id_extension: 0x02,
318                            transposer_frequency: 0x0DDE_EFF0,
319                        },
320                    ],
321                },
322                CellFrequencyLinkEntry {
323                    cell_id: 0x9999,
324                    frequency: 0x4455_6677,
325                    subcells: vec![],
326                },
327            ],
328        };
329        let mut buf = vec![0u8; d.serialized_len()];
330        d.serialize_into(&mut buf).unwrap();
331        assert_eq!(CellFrequencyLinkDescriptor::parse(&buf).unwrap(), d);
332    }
333
334    #[test]
335    fn serialize_rejects_too_small_buffer() {
336        let d = CellFrequencyLinkDescriptor {
337            entries: vec![CellFrequencyLinkEntry {
338                cell_id: 0,
339                frequency: 0,
340                subcells: vec![],
341            }],
342        };
343        let mut buf = vec![0u8; 3];
344        assert!(matches!(
345            d.serialize_into(&mut buf).unwrap_err(),
346            Error::OutputBufferTooSmall { .. }
347        ));
348    }
349
350    #[test]
351    fn serialize_rejects_over_range_body() {
352        // 37 empty entries × 7 bytes = 259 > 255.
353        let d = CellFrequencyLinkDescriptor {
354            entries: (0..37)
355                .map(|_| CellFrequencyLinkEntry {
356                    cell_id: 0,
357                    frequency: 0,
358                    subcells: vec![],
359                })
360                .collect(),
361        };
362        let mut buf = vec![0u8; d.serialized_len()];
363        assert!(matches!(
364            d.serialize_into(&mut buf).unwrap_err(),
365            Error::InvalidDescriptor { tag: TAG, .. }
366        ));
367    }
368
369    #[cfg(feature = "serde")]
370    #[test]
371    fn serde_round_trip() {
372        let d = CellFrequencyLinkDescriptor {
373            entries: vec![CellFrequencyLinkEntry {
374                cell_id: 0x1234,
375                frequency: 0x0011_2233,
376                subcells: vec![CellFrequencyLinkSubcell {
377                    cell_id_extension: 0x01,
378                    transposer_frequency: 0x0AAB_BCCD,
379                }],
380            }],
381        };
382        let json = serde_json::to_string(&d).unwrap();
383        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
384        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
385    }
386}