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