Skip to main content

dvb_si/descriptors/
cell_list.rs

1//! Cell List Descriptor — ETSI EN 300 468 §6.2.7 (tag 0x6C, Table 24, PDF p. 58).
2//!
3//! Carried inside the NIT. Describes the geographic cells of a terrestrial
4//! network and their sub-cells. Body layout (Table 24):
5//!
6//! ```text
7//! for (i=0;i<N;i++) {
8//!   cell_id                  16
9//!   cell_latitude            16
10//!   cell_longitude           16
11//!   cell_extent_of_latitude  12
12//!   cell_extent_of_longitude 12
13//!   subcell_info_loop_length  8
14//!   for (j=0;j<N;j++) {
15//!     cell_id_extension       8
16//!     subcell_latitude        16
17//!     subcell_longitude       16
18//!     subcell_extent_of_latitude  12
19//!     subcell_extent_of_longitude 12
20//!   }
21//! }
22//! ```
23//!
24//! Both loops are typed. The two 12-bit extents pack into 3 bytes (24 bits);
25//! they are exposed as `u16` with the high 4 bits unused. latitude/longitude
26//! are kept as the raw 16-bit wire value (spec: 16-bit two's-complement
27//! fractions of 90°/180°; we preserve the bits verbatim, no scaling).
28
29use super::descriptor_body;
30use crate::error::{Error, Result};
31use dvb_common::{Parse, Serialize};
32
33/// Descriptor tag for cell_list_descriptor.
34pub const TAG: u8 = 0x6C;
35/// Length of the header (tag byte + length byte).
36pub const HEADER_LEN: usize = 2;
37/// Bytes per outer entry before the subcell loop:
38/// cell_id(2)+lat(2)+long(2)+extents(3)+loop_len(1) = 10.
39pub const OUTER_FIXED_LEN: usize = 10;
40/// Bytes per subcell entry: ext(1)+lat(2)+long(2)+extents(3) = 8.
41pub const SUBCELL_LEN: usize = 8;
42/// Mask for a 12-bit extent value.
43pub const EXTENT_MASK: u16 = 0x0FFF;
44
45/// One sub-cell within a cell.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize))]
48pub struct CellListSubcell {
49    /// 8-bit cell_id_extension.
50    pub cell_id_extension: u8,
51    /// 16-bit subcell_latitude (raw wire value).
52    pub subcell_latitude: u16,
53    /// 16-bit subcell_longitude (raw wire value).
54    pub subcell_longitude: u16,
55    /// 12-bit subcell_extent_of_latitude.
56    pub subcell_extent_of_latitude: u16,
57    /// 12-bit subcell_extent_of_longitude.
58    pub subcell_extent_of_longitude: u16,
59}
60
61/// One cell with its sub-cell list.
62#[derive(Debug, Clone, PartialEq, Eq)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize))]
64pub struct CellListEntry {
65    /// 16-bit cell_id.
66    pub cell_id: u16,
67    /// 16-bit cell_latitude (raw wire value).
68    pub cell_latitude: u16,
69    /// 16-bit cell_longitude (raw wire value).
70    pub cell_longitude: u16,
71    /// 12-bit cell_extent_of_latitude.
72    pub cell_extent_of_latitude: u16,
73    /// 12-bit cell_extent_of_longitude.
74    pub cell_extent_of_longitude: u16,
75    /// Sub-cells for this cell.
76    pub subcells: Vec<CellListSubcell>,
77}
78
79/// Cell List Descriptor.
80#[derive(Debug, Clone, PartialEq, Eq)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize))]
82pub struct CellListDescriptor {
83    /// Outer cell entries in wire order.
84    pub entries: Vec<CellListEntry>,
85}
86
87/// Decode the packed 24-bit extent pair `lat(12) || long(12)` from 3 bytes.
88fn read_extents(b: &[u8]) -> (u16, u16) {
89    let lat = (u16::from(b[0]) << 4) | (u16::from(b[1]) >> 4);
90    let long = ((u16::from(b[1]) & 0x0F) << 8) | u16::from(b[2]);
91    (lat, long)
92}
93
94/// Encode a packed 24-bit extent pair into 3 bytes.
95fn write_extents(buf: &mut [u8], lat: u16, long: u16) {
96    let lat = lat & EXTENT_MASK;
97    let long = long & EXTENT_MASK;
98    buf[0] = (lat >> 4) as u8;
99    buf[1] = (((lat & 0x0F) << 4) | (long >> 8)) as u8;
100    buf[2] = long as u8;
101}
102
103impl<'a> Parse<'a> for CellListDescriptor {
104    type Error = crate::error::Error;
105    fn parse(bytes: &'a [u8]) -> Result<Self> {
106        let body = descriptor_body(
107            bytes,
108            TAG,
109            "CellListDescriptor",
110            "unexpected tag for cell_list_descriptor",
111        )?;
112        let mut entries = Vec::new();
113        let mut pos = 0;
114        while pos < body.len() {
115            if pos + OUTER_FIXED_LEN > body.len() {
116                return Err(Error::InvalidDescriptor {
117                    tag: TAG,
118                    reason: "cell_list outer entry truncated",
119                });
120            }
121            let cell_id = u16::from_be_bytes([body[pos], body[pos + 1]]);
122            let cell_latitude = u16::from_be_bytes([body[pos + 2], body[pos + 3]]);
123            let cell_longitude = u16::from_be_bytes([body[pos + 4], body[pos + 5]]);
124            let (cell_extent_of_latitude, cell_extent_of_longitude) =
125                read_extents(&body[pos + 6..pos + 9]);
126            let subcell_info_loop_length = body[pos + 9] as usize;
127            pos += OUTER_FIXED_LEN;
128            if subcell_info_loop_length % SUBCELL_LEN != 0 {
129                return Err(Error::InvalidDescriptor {
130                    tag: TAG,
131                    reason: "subcell_info_loop_length must be a multiple of 8",
132                });
133            }
134            if pos + subcell_info_loop_length > body.len() {
135                return Err(Error::InvalidDescriptor {
136                    tag: TAG,
137                    reason: "subcell_info_loop_length exceeds descriptor body",
138                });
139            }
140            let subcell_count = subcell_info_loop_length / SUBCELL_LEN;
141            let mut subcells = Vec::with_capacity(subcell_count);
142            for _ in 0..subcell_count {
143                let cell_id_extension = body[pos];
144                let subcell_latitude = u16::from_be_bytes([body[pos + 1], body[pos + 2]]);
145                let subcell_longitude = u16::from_be_bytes([body[pos + 3], body[pos + 4]]);
146                let (subcell_extent_of_latitude, subcell_extent_of_longitude) =
147                    read_extents(&body[pos + 5..pos + 8]);
148                subcells.push(CellListSubcell {
149                    cell_id_extension,
150                    subcell_latitude,
151                    subcell_longitude,
152                    subcell_extent_of_latitude,
153                    subcell_extent_of_longitude,
154                });
155                pos += SUBCELL_LEN;
156            }
157            entries.push(CellListEntry {
158                cell_id,
159                cell_latitude,
160                cell_longitude,
161                cell_extent_of_latitude,
162                cell_extent_of_longitude,
163                subcells,
164            });
165        }
166        Ok(Self { entries })
167    }
168}
169
170impl CellListDescriptor {
171    fn body_len(&self) -> usize {
172        self.entries
173            .iter()
174            .map(|e| OUTER_FIXED_LEN + e.subcells.len() * SUBCELL_LEN)
175            .sum()
176    }
177}
178
179impl Serialize for CellListDescriptor {
180    type Error = crate::error::Error;
181    fn serialized_len(&self) -> usize {
182        HEADER_LEN + self.body_len()
183    }
184
185    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
186        let body_len = self.body_len();
187        if body_len > u8::MAX as usize {
188            return Err(Error::InvalidDescriptor {
189                tag: TAG,
190                reason: "cell_list_descriptor body exceeds 255 bytes",
191            });
192        }
193        for e in &self.entries {
194            if e.subcells.len() * SUBCELL_LEN > u8::MAX as usize {
195                return Err(Error::InvalidDescriptor {
196                    tag: TAG,
197                    reason: "subcell_info_loop_length exceeds 255 bytes",
198                });
199            }
200        }
201        let len = self.serialized_len();
202        if buf.len() < len {
203            return Err(Error::OutputBufferTooSmall {
204                need: len,
205                have: buf.len(),
206            });
207        }
208        buf[0] = TAG;
209        buf[1] = body_len as u8;
210        let mut pos = HEADER_LEN;
211        for e in &self.entries {
212            buf[pos..pos + 2].copy_from_slice(&e.cell_id.to_be_bytes());
213            buf[pos + 2..pos + 4].copy_from_slice(&e.cell_latitude.to_be_bytes());
214            buf[pos + 4..pos + 6].copy_from_slice(&e.cell_longitude.to_be_bytes());
215            write_extents(
216                &mut buf[pos + 6..pos + 9],
217                e.cell_extent_of_latitude,
218                e.cell_extent_of_longitude,
219            );
220            buf[pos + 9] = (e.subcells.len() * SUBCELL_LEN) as u8;
221            pos += OUTER_FIXED_LEN;
222            for sc in &e.subcells {
223                buf[pos] = sc.cell_id_extension;
224                buf[pos + 1..pos + 3].copy_from_slice(&sc.subcell_latitude.to_be_bytes());
225                buf[pos + 3..pos + 5].copy_from_slice(&sc.subcell_longitude.to_be_bytes());
226                write_extents(
227                    &mut buf[pos + 5..pos + 8],
228                    sc.subcell_extent_of_latitude,
229                    sc.subcell_extent_of_longitude,
230                );
231                pos += SUBCELL_LEN;
232            }
233        }
234        Ok(len)
235    }
236}
237impl<'a> crate::traits::DescriptorDef<'a> for CellListDescriptor {
238    const TAG: u8 = TAG;
239    const NAME: &'static str = "CELL_LIST";
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn extents_pack_round_trips() {
248        let mut b = [0u8; 3];
249        write_extents(&mut b, 0xABC, 0xDEF);
250        assert_eq!(read_extents(&b), (0xABC, 0xDEF));
251    }
252
253    #[test]
254    fn parse_entry_with_subcell() {
255        let bytes = [
256            TAG, 18, // body length: outer(10) + 1 subcell(8)
257            // outer: cell_id=0x1234, lat=0x5678, long=0x9ABC, extent_lat=0xDEF, extent_long=0x012, loop_len=8
258            0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x12, 8,
259            // subcell: ext=0x07, lat=0x1111, long=0x2222, extent_lat=0x333, extent_long=0x444
260            0x07, 0x11, 0x11, 0x22, 0x22, 0x33, 0x34, 0x44,
261        ];
262        let d = CellListDescriptor::parse(&bytes).unwrap();
263        assert_eq!(d.entries.len(), 1);
264        let e = &d.entries[0];
265        assert_eq!(e.cell_id, 0x1234);
266        assert_eq!(e.cell_latitude, 0x5678);
267        assert_eq!(e.cell_longitude, 0x9ABC);
268        assert_eq!(e.cell_extent_of_latitude, 0xDEF);
269        assert_eq!(e.cell_extent_of_longitude, 0x012);
270        assert_eq!(e.subcells.len(), 1);
271        let sc = &e.subcells[0];
272        assert_eq!(sc.cell_id_extension, 0x07);
273        assert_eq!(sc.subcell_latitude, 0x1111);
274        assert_eq!(sc.subcell_longitude, 0x2222);
275        assert_eq!(sc.subcell_extent_of_latitude, 0x333);
276        assert_eq!(sc.subcell_extent_of_longitude, 0x444);
277    }
278
279    #[test]
280    fn parse_entry_no_subcells() {
281        let bytes = [
282            TAG, 10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
283        ];
284        let d = CellListDescriptor::parse(&bytes).unwrap();
285        assert_eq!(d.entries.len(), 1);
286        assert!(d.entries[0].subcells.is_empty());
287    }
288
289    #[test]
290    fn empty_body_is_valid() {
291        let d = CellListDescriptor::parse(&[TAG, 0]).unwrap();
292        assert!(d.entries.is_empty());
293    }
294
295    #[test]
296    fn parse_rejects_wrong_tag() {
297        assert!(matches!(
298            CellListDescriptor::parse(&[0x6D, 0]).unwrap_err(),
299            Error::InvalidDescriptor { tag: 0x6D, .. }
300        ));
301    }
302
303    #[test]
304    fn parse_rejects_truncated_outer() {
305        let bytes = [TAG, 5, 0, 0, 0, 0, 0];
306        assert!(matches!(
307            CellListDescriptor::parse(&bytes).unwrap_err(),
308            Error::InvalidDescriptor { tag: TAG, .. }
309        ));
310    }
311
312    #[test]
313    fn parse_rejects_subcell_loop_overrun() {
314        // loop_len=8 but no subcell bytes follow
315        let bytes = [
316            TAG, 10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8,
317        ];
318        assert!(matches!(
319            CellListDescriptor::parse(&bytes).unwrap_err(),
320            Error::InvalidDescriptor { tag: TAG, .. }
321        ));
322    }
323
324    #[test]
325    fn parse_rejects_buffer_shorter_than_length() {
326        let bytes = [TAG, 10, 0x00, 0x01, 0x00];
327        assert!(matches!(
328            CellListDescriptor::parse(&bytes).unwrap_err(),
329            Error::BufferTooShort { .. }
330        ));
331    }
332
333    #[test]
334    fn serialize_round_trip() {
335        let d = CellListDescriptor {
336            entries: vec![
337                CellListEntry {
338                    cell_id: 0x1234,
339                    cell_latitude: 0x5678,
340                    cell_longitude: 0x9ABC,
341                    cell_extent_of_latitude: 0xDEF,
342                    cell_extent_of_longitude: 0x012,
343                    subcells: vec![CellListSubcell {
344                        cell_id_extension: 0x07,
345                        subcell_latitude: 0x1111,
346                        subcell_longitude: 0x2222,
347                        subcell_extent_of_latitude: 0x333,
348                        subcell_extent_of_longitude: 0x444,
349                    }],
350                },
351                CellListEntry {
352                    cell_id: 0xAAAA,
353                    cell_latitude: 0xBBBB,
354                    cell_longitude: 0xCCCC,
355                    cell_extent_of_latitude: 0x111,
356                    cell_extent_of_longitude: 0x222,
357                    subcells: vec![],
358                },
359            ],
360        };
361        let mut buf = vec![0u8; d.serialized_len()];
362        d.serialize_into(&mut buf).unwrap();
363        assert_eq!(CellListDescriptor::parse(&buf).unwrap(), d);
364    }
365
366    #[test]
367    fn serialize_rejects_too_small_buffer() {
368        let d = CellListDescriptor {
369            entries: vec![CellListEntry {
370                cell_id: 0,
371                cell_latitude: 0,
372                cell_longitude: 0,
373                cell_extent_of_latitude: 0,
374                cell_extent_of_longitude: 0,
375                subcells: vec![],
376            }],
377        };
378        let mut buf = vec![0u8; 3];
379        assert!(matches!(
380            d.serialize_into(&mut buf).unwrap_err(),
381            Error::OutputBufferTooSmall { .. }
382        ));
383    }
384
385    #[test]
386    fn serialize_rejects_over_range_body() {
387        // 26 empty entries × 10 = 260 > 255.
388        let d = CellListDescriptor {
389            entries: (0..26)
390                .map(|_| CellListEntry {
391                    cell_id: 0,
392                    cell_latitude: 0,
393                    cell_longitude: 0,
394                    cell_extent_of_latitude: 0,
395                    cell_extent_of_longitude: 0,
396                    subcells: vec![],
397                })
398                .collect(),
399        };
400        let mut buf = vec![0u8; d.serialized_len()];
401        assert!(matches!(
402            d.serialize_into(&mut buf).unwrap_err(),
403            Error::InvalidDescriptor { tag: TAG, .. }
404        ));
405    }
406
407    #[cfg(feature = "serde")]
408    #[test]
409    fn serde_round_trip() {
410        let d = CellListDescriptor {
411            entries: vec![CellListEntry {
412                cell_id: 0x1234,
413                cell_latitude: 0x5678,
414                cell_longitude: 0x9ABC,
415                cell_extent_of_latitude: 0xDEF,
416                cell_extent_of_longitude: 0x012,
417                subcells: vec![CellListSubcell {
418                    cell_id_extension: 0x07,
419                    subcell_latitude: 0x1111,
420                    subcell_longitude: 0x2222,
421                    subcell_extent_of_latitude: 0x333,
422                    subcell_extent_of_longitude: 0x444,
423                }],
424            }],
425        };
426        let json = serde_json::to_string(&d).unwrap();
427        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
428        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
429    }
430}