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 cell_id = u16::from_be_bytes([body[pos], body[pos + 1]]);
100            let frequency =
101                u32::from_be_bytes([body[pos + 2], body[pos + 3], body[pos + 4], body[pos + 5]]);
102            let subcell_info_loop_length = body[pos + 6] as usize;
103            pos += OUTER_FIXED_LEN;
104            if subcell_info_loop_length % SUBCELL_LEN != 0 {
105                return Err(Error::InvalidDescriptor {
106                    tag: TAG,
107                    reason: "subcell_info_loop_length must be a multiple of 5",
108                });
109            }
110            if pos + subcell_info_loop_length > body.len() {
111                return Err(Error::InvalidDescriptor {
112                    tag: TAG,
113                    reason: "subcell_info_loop_length exceeds descriptor body",
114                });
115            }
116            let subcell_count = subcell_info_loop_length / SUBCELL_LEN;
117            let mut subcells = Vec::with_capacity(subcell_count);
118            for _ in 0..subcell_count {
119                let cell_id_extension = body[pos];
120                let transposer_frequency = u32::from_be_bytes([
121                    body[pos + 1],
122                    body[pos + 2],
123                    body[pos + 3],
124                    body[pos + 4],
125                ]);
126                subcells.push(CellFrequencyLinkSubcell {
127                    cell_id_extension,
128                    transposer_frequency,
129                });
130                pos += SUBCELL_LEN;
131            }
132            entries.push(CellFrequencyLinkEntry {
133                cell_id,
134                frequency,
135                subcells,
136            });
137        }
138        Ok(Self { entries })
139    }
140}
141
142impl CellFrequencyLinkDescriptor {
143    fn body_len(&self) -> usize {
144        self.entries
145            .iter()
146            .map(|e| OUTER_FIXED_LEN + e.subcells.len() * SUBCELL_LEN)
147            .sum()
148    }
149}
150
151impl Serialize for CellFrequencyLinkDescriptor {
152    type Error = crate::error::Error;
153    fn serialized_len(&self) -> usize {
154        HEADER_LEN + self.body_len()
155    }
156
157    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
158        let body_len = self.body_len();
159        if body_len > u8::MAX as usize {
160            return Err(Error::InvalidDescriptor {
161                tag: TAG,
162                reason: "cell_frequency_link_descriptor body exceeds 255 bytes",
163            });
164        }
165        for e in &self.entries {
166            if e.subcells.len() * SUBCELL_LEN > u8::MAX as usize {
167                return Err(Error::InvalidDescriptor {
168                    tag: TAG,
169                    reason: "subcell_info_loop_length exceeds 255 bytes",
170                });
171            }
172        }
173        let len = self.serialized_len();
174        if buf.len() < len {
175            return Err(Error::OutputBufferTooSmall {
176                need: len,
177                have: buf.len(),
178            });
179        }
180        buf[0] = TAG;
181        buf[1] = body_len as u8;
182        let mut pos = HEADER_LEN;
183        for e in &self.entries {
184            buf[pos..pos + 2].copy_from_slice(&e.cell_id.to_be_bytes());
185            buf[pos + 2..pos + 6].copy_from_slice(&e.frequency.to_be_bytes());
186            buf[pos + 6] = (e.subcells.len() * SUBCELL_LEN) as u8;
187            pos += OUTER_FIXED_LEN;
188            for sc in &e.subcells {
189                buf[pos] = sc.cell_id_extension;
190                buf[pos + 1..pos + 5].copy_from_slice(&sc.transposer_frequency.to_be_bytes());
191                pos += SUBCELL_LEN;
192            }
193        }
194        Ok(len)
195    }
196}
197impl<'a> crate::traits::DescriptorDef<'a> for CellFrequencyLinkDescriptor {
198    const TAG: u8 = TAG;
199    const NAME: &'static str = "CELL_FREQUENCY_LINK";
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn frequency_hz_computes_correctly() {
208        let entry = CellFrequencyLinkEntry {
209            cell_id: 0,
210            frequency: 100_000,
211            subcells: vec![],
212        };
213        assert_eq!(entry.frequency_hz(), 1_000_000);
214    }
215
216    #[test]
217    fn transposer_frequency_hz_computes_correctly() {
218        let sc = CellFrequencyLinkSubcell {
219            cell_id_extension: 0,
220            transposer_frequency: 500_000,
221        };
222        assert_eq!(sc.transposer_frequency_hz(), 5_000_000);
223    }
224
225    #[test]
226    fn parse_entry_with_subcells() {
227        let bytes = [
228            TAG, 17, // body length
229            // outer: cell_id=0x1234, freq=0x00112233, subcell_loop_len=10
230            0x12, 0x34, 0x00, 0x11, 0x22, 0x33, 10, // subcell 1: ext=0x01, freq=0x0AABBCCD
231            0x01, 0x0A, 0xAB, 0xBC, 0xCD, // subcell 2: ext=0x02, freq=0x0DDEEFF0
232            0x02, 0x0D, 0xDE, 0xEF, 0xF0,
233        ];
234        let d = CellFrequencyLinkDescriptor::parse(&bytes).unwrap();
235        assert_eq!(d.entries.len(), 1);
236        assert_eq!(d.entries[0].cell_id, 0x1234);
237        assert_eq!(d.entries[0].frequency, 0x0011_2233);
238        assert_eq!(d.entries[0].subcells.len(), 2);
239        assert_eq!(d.entries[0].subcells[0].cell_id_extension, 0x01);
240        assert_eq!(d.entries[0].subcells[0].transposer_frequency, 0x0AAB_BCCD);
241        assert_eq!(d.entries[0].subcells[1].cell_id_extension, 0x02);
242    }
243
244    #[test]
245    fn parse_entry_no_subcells() {
246        let bytes = [TAG, 7, 0x00, 0x05, 0x00, 0x00, 0x10, 0x00, 0x00];
247        let d = CellFrequencyLinkDescriptor::parse(&bytes).unwrap();
248        assert_eq!(d.entries.len(), 1);
249        assert_eq!(d.entries[0].cell_id, 0x0005);
250        assert!(d.entries[0].subcells.is_empty());
251    }
252
253    #[test]
254    fn empty_body_is_valid() {
255        let d = CellFrequencyLinkDescriptor::parse(&[TAG, 0]).unwrap();
256        assert!(d.entries.is_empty());
257    }
258
259    #[test]
260    fn parse_rejects_wrong_tag() {
261        assert!(matches!(
262            CellFrequencyLinkDescriptor::parse(&[0x6E, 0]).unwrap_err(),
263            Error::InvalidDescriptor { tag: 0x6E, .. }
264        ));
265    }
266
267    #[test]
268    fn parse_rejects_truncated_outer() {
269        // body length 5 < OUTER_FIXED_LEN(7)
270        let bytes = [TAG, 5, 0x00, 0x01, 0x00, 0x00, 0x10];
271        assert!(matches!(
272            CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
273            Error::InvalidDescriptor { tag: TAG, .. }
274        ));
275    }
276
277    #[test]
278    fn parse_rejects_subcell_loop_overrun() {
279        // subcell_loop_len=10 but no subcell bytes follow
280        let bytes = [TAG, 7, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 10];
281        assert!(matches!(
282            CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
283            Error::InvalidDescriptor { tag: TAG, .. }
284        ));
285    }
286
287    #[test]
288    fn parse_rejects_buffer_shorter_than_length() {
289        let bytes = [TAG, 7, 0x00, 0x01, 0x00];
290        assert!(matches!(
291            CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
292            Error::BufferTooShort { .. }
293        ));
294    }
295
296    #[test]
297    fn serialize_round_trip() {
298        let d = CellFrequencyLinkDescriptor {
299            entries: vec![
300                CellFrequencyLinkEntry {
301                    cell_id: 0x1234,
302                    frequency: 0x0011_2233,
303                    subcells: vec![
304                        CellFrequencyLinkSubcell {
305                            cell_id_extension: 0x01,
306                            transposer_frequency: 0x0AAB_BCCD,
307                        },
308                        CellFrequencyLinkSubcell {
309                            cell_id_extension: 0x02,
310                            transposer_frequency: 0x0DDE_EFF0,
311                        },
312                    ],
313                },
314                CellFrequencyLinkEntry {
315                    cell_id: 0x9999,
316                    frequency: 0x4455_6677,
317                    subcells: vec![],
318                },
319            ],
320        };
321        let mut buf = vec![0u8; d.serialized_len()];
322        d.serialize_into(&mut buf).unwrap();
323        assert_eq!(CellFrequencyLinkDescriptor::parse(&buf).unwrap(), d);
324    }
325
326    #[test]
327    fn serialize_rejects_too_small_buffer() {
328        let d = CellFrequencyLinkDescriptor {
329            entries: vec![CellFrequencyLinkEntry {
330                cell_id: 0,
331                frequency: 0,
332                subcells: vec![],
333            }],
334        };
335        let mut buf = vec![0u8; 3];
336        assert!(matches!(
337            d.serialize_into(&mut buf).unwrap_err(),
338            Error::OutputBufferTooSmall { .. }
339        ));
340    }
341
342    #[test]
343    fn serialize_rejects_over_range_body() {
344        // 37 empty entries × 7 bytes = 259 > 255.
345        let d = CellFrequencyLinkDescriptor {
346            entries: (0..37)
347                .map(|_| CellFrequencyLinkEntry {
348                    cell_id: 0,
349                    frequency: 0,
350                    subcells: vec![],
351                })
352                .collect(),
353        };
354        let mut buf = vec![0u8; d.serialized_len()];
355        assert!(matches!(
356            d.serialize_into(&mut buf).unwrap_err(),
357            Error::InvalidDescriptor { tag: TAG, .. }
358        ));
359    }
360
361    #[cfg(feature = "serde")]
362    #[test]
363    fn serde_round_trip() {
364        let d = CellFrequencyLinkDescriptor {
365            entries: vec![CellFrequencyLinkEntry {
366                cell_id: 0x1234,
367                frequency: 0x0011_2233,
368                subcells: vec![CellFrequencyLinkSubcell {
369                    cell_id_extension: 0x01,
370                    transposer_frequency: 0x0AAB_BCCD,
371                }],
372            }],
373        };
374        let json = serde_json::to_string(&d).unwrap();
375        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
376        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
377    }
378}