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