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 crate::error::{Error, Result};
22use crate::traits::Descriptor;
23use dvb_common::{Parse, Serialize};
24
25/// Descriptor tag for cell_frequency_link_descriptor.
26pub const TAG: u8 = 0x6D;
27/// Length of the header (tag byte + length byte).
28pub const HEADER_LEN: usize = 2;
29/// Fixed bytes per outer entry before the subcell loop: cell_id(2)+frequency(4)+loop_len(1).
30pub const OUTER_FIXED_LEN: usize = 7;
31/// Bytes per subcell entry: cell_id_extension(1)+transposer_frequency(4).
32pub const SUBCELL_LEN: usize = 5;
33
34/// One transposer sub-cell.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize))]
37pub struct CellFrequencyLinkSubcell {
38    /// 8-bit cell_id_extension.
39    pub cell_id_extension: u8,
40    /// 32-bit transposer_frequency (10 Hz units).
41    pub transposer_frequency: u32,
42}
43
44/// One cell-frequency association with its sub-cell list.
45#[derive(Debug, Clone, PartialEq, Eq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize))]
47pub struct CellFrequencyLinkEntry {
48    /// 16-bit cell_id.
49    pub cell_id: u16,
50    /// 32-bit frequency (10 Hz units).
51    pub frequency: u32,
52    /// Transposer sub-cells for this cell.
53    pub subcells: Vec<CellFrequencyLinkSubcell>,
54}
55
56/// Cell Frequency Link Descriptor.
57#[derive(Debug, Clone, PartialEq, Eq)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize))]
59pub struct CellFrequencyLinkDescriptor {
60    /// Outer cell entries in wire order.
61    pub entries: Vec<CellFrequencyLinkEntry>,
62}
63
64impl<'a> Parse<'a> for CellFrequencyLinkDescriptor {
65    type Error = crate::error::Error;
66    fn parse(bytes: &'a [u8]) -> Result<Self> {
67        if bytes.len() < HEADER_LEN {
68            return Err(Error::BufferTooShort {
69                need: HEADER_LEN,
70                have: bytes.len(),
71                what: "CellFrequencyLinkDescriptor header",
72            });
73        }
74        if bytes[0] != TAG {
75            return Err(Error::InvalidDescriptor {
76                tag: bytes[0],
77                reason: "unexpected tag for cell_frequency_link_descriptor",
78            });
79        }
80        let length = bytes[1] as usize;
81        let end = HEADER_LEN + length;
82        if bytes.len() < end {
83            return Err(Error::BufferTooShort {
84                need: end,
85                have: bytes.len(),
86                what: "CellFrequencyLinkDescriptor body",
87            });
88        }
89        let body = &bytes[HEADER_LEN..end];
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}
197
198impl<'a> Descriptor<'a> for CellFrequencyLinkDescriptor {
199    const TAG: u8 = TAG;
200    fn descriptor_length(&self) -> u8 {
201        self.body_len() as u8
202    }
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 parse_entry_with_subcells() {
216        let bytes = [
217            TAG, 17, // body length
218            // outer: cell_id=0x1234, freq=0x00112233, subcell_loop_len=10
219            0x12, 0x34, 0x00, 0x11, 0x22, 0x33, 10, // subcell 1: ext=0x01, freq=0x0AABBCCD
220            0x01, 0x0A, 0xAB, 0xBC, 0xCD, // subcell 2: ext=0x02, freq=0x0DDEEFF0
221            0x02, 0x0D, 0xDE, 0xEF, 0xF0,
222        ];
223        let d = CellFrequencyLinkDescriptor::parse(&bytes).unwrap();
224        assert_eq!(d.entries.len(), 1);
225        assert_eq!(d.entries[0].cell_id, 0x1234);
226        assert_eq!(d.entries[0].frequency, 0x0011_2233);
227        assert_eq!(d.entries[0].subcells.len(), 2);
228        assert_eq!(d.entries[0].subcells[0].cell_id_extension, 0x01);
229        assert_eq!(d.entries[0].subcells[0].transposer_frequency, 0x0AAB_BCCD);
230        assert_eq!(d.entries[0].subcells[1].cell_id_extension, 0x02);
231    }
232
233    #[test]
234    fn parse_entry_no_subcells() {
235        let bytes = [TAG, 7, 0x00, 0x05, 0x00, 0x00, 0x10, 0x00, 0x00];
236        let d = CellFrequencyLinkDescriptor::parse(&bytes).unwrap();
237        assert_eq!(d.entries.len(), 1);
238        assert_eq!(d.entries[0].cell_id, 0x0005);
239        assert!(d.entries[0].subcells.is_empty());
240    }
241
242    #[test]
243    fn empty_body_is_valid() {
244        let d = CellFrequencyLinkDescriptor::parse(&[TAG, 0]).unwrap();
245        assert!(d.entries.is_empty());
246    }
247
248    #[test]
249    fn parse_rejects_wrong_tag() {
250        assert!(matches!(
251            CellFrequencyLinkDescriptor::parse(&[0x6E, 0]).unwrap_err(),
252            Error::InvalidDescriptor { tag: 0x6E, .. }
253        ));
254    }
255
256    #[test]
257    fn parse_rejects_truncated_outer() {
258        // body length 5 < OUTER_FIXED_LEN(7)
259        let bytes = [TAG, 5, 0x00, 0x01, 0x00, 0x00, 0x10];
260        assert!(matches!(
261            CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
262            Error::InvalidDescriptor { tag: TAG, .. }
263        ));
264    }
265
266    #[test]
267    fn parse_rejects_subcell_loop_overrun() {
268        // subcell_loop_len=10 but no subcell bytes follow
269        let bytes = [TAG, 7, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 10];
270        assert!(matches!(
271            CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
272            Error::InvalidDescriptor { tag: TAG, .. }
273        ));
274    }
275
276    #[test]
277    fn parse_rejects_buffer_shorter_than_length() {
278        let bytes = [TAG, 7, 0x00, 0x01, 0x00];
279        assert!(matches!(
280            CellFrequencyLinkDescriptor::parse(&bytes).unwrap_err(),
281            Error::BufferTooShort { .. }
282        ));
283    }
284
285    #[test]
286    fn serialize_round_trip() {
287        let d = CellFrequencyLinkDescriptor {
288            entries: vec![
289                CellFrequencyLinkEntry {
290                    cell_id: 0x1234,
291                    frequency: 0x0011_2233,
292                    subcells: vec![
293                        CellFrequencyLinkSubcell {
294                            cell_id_extension: 0x01,
295                            transposer_frequency: 0x0AAB_BCCD,
296                        },
297                        CellFrequencyLinkSubcell {
298                            cell_id_extension: 0x02,
299                            transposer_frequency: 0x0DDE_EFF0,
300                        },
301                    ],
302                },
303                CellFrequencyLinkEntry {
304                    cell_id: 0x9999,
305                    frequency: 0x4455_6677,
306                    subcells: vec![],
307                },
308            ],
309        };
310        let mut buf = vec![0u8; d.serialized_len()];
311        d.serialize_into(&mut buf).unwrap();
312        assert_eq!(CellFrequencyLinkDescriptor::parse(&buf).unwrap(), d);
313    }
314
315    #[test]
316    fn serialize_rejects_too_small_buffer() {
317        let d = CellFrequencyLinkDescriptor {
318            entries: vec![CellFrequencyLinkEntry {
319                cell_id: 0,
320                frequency: 0,
321                subcells: vec![],
322            }],
323        };
324        let mut buf = vec![0u8; 3];
325        assert!(matches!(
326            d.serialize_into(&mut buf).unwrap_err(),
327            Error::OutputBufferTooSmall { .. }
328        ));
329    }
330
331    #[test]
332    fn serialize_rejects_over_range_body() {
333        // 37 empty entries × 7 bytes = 259 > 255.
334        let d = CellFrequencyLinkDescriptor {
335            entries: (0..37)
336                .map(|_| CellFrequencyLinkEntry {
337                    cell_id: 0,
338                    frequency: 0,
339                    subcells: vec![],
340                })
341                .collect(),
342        };
343        let mut buf = vec![0u8; d.serialized_len()];
344        assert!(matches!(
345            d.serialize_into(&mut buf).unwrap_err(),
346            Error::InvalidDescriptor { tag: TAG, .. }
347        ));
348    }
349
350    #[cfg(feature = "serde")]
351    #[test]
352    fn serde_round_trip() {
353        let d = CellFrequencyLinkDescriptor {
354            entries: vec![CellFrequencyLinkEntry {
355                cell_id: 0x1234,
356                frequency: 0x0011_2233,
357                subcells: vec![CellFrequencyLinkSubcell {
358                    cell_id_extension: 0x01,
359                    transposer_frequency: 0x0AAB_BCCD,
360                }],
361            }],
362        };
363        let json = serde_json::to_string(&d).unwrap();
364        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
365        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
366    }
367}