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