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 crate::error::{Error, Result};
30use crate::traits::Descriptor;
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, serde::Deserialize))]
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, serde::Deserialize))]
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, serde::Deserialize))]
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        if bytes.len() < HEADER_LEN {
107            return Err(Error::BufferTooShort {
108                need: HEADER_LEN,
109                have: bytes.len(),
110                what: "CellListDescriptor header",
111            });
112        }
113        if bytes[0] != TAG {
114            return Err(Error::InvalidDescriptor {
115                tag: bytes[0],
116                reason: "unexpected tag for cell_list_descriptor",
117            });
118        }
119        let length = bytes[1] as usize;
120        let end = HEADER_LEN + length;
121        if bytes.len() < end {
122            return Err(Error::BufferTooShort {
123                need: end,
124                have: bytes.len(),
125                what: "CellListDescriptor body",
126            });
127        }
128        let body = &bytes[HEADER_LEN..end];
129        let mut entries = Vec::new();
130        let mut pos = 0;
131        while pos < body.len() {
132            if pos + OUTER_FIXED_LEN > body.len() {
133                return Err(Error::InvalidDescriptor {
134                    tag: TAG,
135                    reason: "cell_list outer entry truncated",
136                });
137            }
138            let cell_id = u16::from_be_bytes([body[pos], body[pos + 1]]);
139            let cell_latitude = u16::from_be_bytes([body[pos + 2], body[pos + 3]]);
140            let cell_longitude = u16::from_be_bytes([body[pos + 4], body[pos + 5]]);
141            let (cell_extent_of_latitude, cell_extent_of_longitude) =
142                read_extents(&body[pos + 6..pos + 9]);
143            let subcell_info_loop_length = body[pos + 9] as usize;
144            pos += OUTER_FIXED_LEN;
145            if subcell_info_loop_length % SUBCELL_LEN != 0 {
146                return Err(Error::InvalidDescriptor {
147                    tag: TAG,
148                    reason: "subcell_info_loop_length must be a multiple of 8",
149                });
150            }
151            if pos + subcell_info_loop_length > body.len() {
152                return Err(Error::InvalidDescriptor {
153                    tag: TAG,
154                    reason: "subcell_info_loop_length exceeds descriptor body",
155                });
156            }
157            let subcell_count = subcell_info_loop_length / SUBCELL_LEN;
158            let mut subcells = Vec::with_capacity(subcell_count);
159            for _ in 0..subcell_count {
160                let cell_id_extension = body[pos];
161                let subcell_latitude = u16::from_be_bytes([body[pos + 1], body[pos + 2]]);
162                let subcell_longitude = u16::from_be_bytes([body[pos + 3], body[pos + 4]]);
163                let (subcell_extent_of_latitude, subcell_extent_of_longitude) =
164                    read_extents(&body[pos + 5..pos + 8]);
165                subcells.push(CellListSubcell {
166                    cell_id_extension,
167                    subcell_latitude,
168                    subcell_longitude,
169                    subcell_extent_of_latitude,
170                    subcell_extent_of_longitude,
171                });
172                pos += SUBCELL_LEN;
173            }
174            entries.push(CellListEntry {
175                cell_id,
176                cell_latitude,
177                cell_longitude,
178                cell_extent_of_latitude,
179                cell_extent_of_longitude,
180                subcells,
181            });
182        }
183        Ok(Self { entries })
184    }
185}
186
187impl CellListDescriptor {
188    fn body_len(&self) -> usize {
189        self.entries
190            .iter()
191            .map(|e| OUTER_FIXED_LEN + e.subcells.len() * SUBCELL_LEN)
192            .sum()
193    }
194}
195
196impl Serialize for CellListDescriptor {
197    type Error = crate::error::Error;
198    fn serialized_len(&self) -> usize {
199        HEADER_LEN + self.body_len()
200    }
201
202    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
203        let body_len = self.body_len();
204        if body_len > u8::MAX as usize {
205            return Err(Error::InvalidDescriptor {
206                tag: TAG,
207                reason: "cell_list_descriptor body exceeds 255 bytes",
208            });
209        }
210        for e in &self.entries {
211            if e.subcells.len() * SUBCELL_LEN > u8::MAX as usize {
212                return Err(Error::InvalidDescriptor {
213                    tag: TAG,
214                    reason: "subcell_info_loop_length exceeds 255 bytes",
215                });
216            }
217        }
218        let len = self.serialized_len();
219        if buf.len() < len {
220            return Err(Error::OutputBufferTooSmall {
221                need: len,
222                have: buf.len(),
223            });
224        }
225        buf[0] = TAG;
226        buf[1] = body_len as u8;
227        let mut pos = HEADER_LEN;
228        for e in &self.entries {
229            buf[pos..pos + 2].copy_from_slice(&e.cell_id.to_be_bytes());
230            buf[pos + 2..pos + 4].copy_from_slice(&e.cell_latitude.to_be_bytes());
231            buf[pos + 4..pos + 6].copy_from_slice(&e.cell_longitude.to_be_bytes());
232            write_extents(
233                &mut buf[pos + 6..pos + 9],
234                e.cell_extent_of_latitude,
235                e.cell_extent_of_longitude,
236            );
237            buf[pos + 9] = (e.subcells.len() * SUBCELL_LEN) as u8;
238            pos += OUTER_FIXED_LEN;
239            for sc in &e.subcells {
240                buf[pos] = sc.cell_id_extension;
241                buf[pos + 1..pos + 3].copy_from_slice(&sc.subcell_latitude.to_be_bytes());
242                buf[pos + 3..pos + 5].copy_from_slice(&sc.subcell_longitude.to_be_bytes());
243                write_extents(
244                    &mut buf[pos + 5..pos + 8],
245                    sc.subcell_extent_of_latitude,
246                    sc.subcell_extent_of_longitude,
247                );
248                pos += SUBCELL_LEN;
249            }
250        }
251        Ok(len)
252    }
253}
254
255impl<'a> Descriptor<'a> for CellListDescriptor {
256    const TAG: u8 = TAG;
257    fn descriptor_length(&self) -> u8 {
258        self.body_len() as u8
259    }
260}
261
262impl<'a> crate::traits::DescriptorDef<'a> for CellListDescriptor {
263    const TAG: u8 = TAG;
264    const NAME: &'static str = "CELL_LIST";
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn extents_pack_round_trips() {
273        let mut b = [0u8; 3];
274        write_extents(&mut b, 0xABC, 0xDEF);
275        assert_eq!(read_extents(&b), (0xABC, 0xDEF));
276    }
277
278    #[test]
279    fn parse_entry_with_subcell() {
280        let bytes = [
281            TAG, 18, // body length: outer(10) + 1 subcell(8)
282            // outer: cell_id=0x1234, lat=0x5678, long=0x9ABC, extent_lat=0xDEF, extent_long=0x012, loop_len=8
283            0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x12, 8,
284            // subcell: ext=0x07, lat=0x1111, long=0x2222, extent_lat=0x333, extent_long=0x444
285            0x07, 0x11, 0x11, 0x22, 0x22, 0x33, 0x34, 0x44,
286        ];
287        let d = CellListDescriptor::parse(&bytes).unwrap();
288        assert_eq!(d.entries.len(), 1);
289        let e = &d.entries[0];
290        assert_eq!(e.cell_id, 0x1234);
291        assert_eq!(e.cell_latitude, 0x5678);
292        assert_eq!(e.cell_longitude, 0x9ABC);
293        assert_eq!(e.cell_extent_of_latitude, 0xDEF);
294        assert_eq!(e.cell_extent_of_longitude, 0x012);
295        assert_eq!(e.subcells.len(), 1);
296        let sc = &e.subcells[0];
297        assert_eq!(sc.cell_id_extension, 0x07);
298        assert_eq!(sc.subcell_latitude, 0x1111);
299        assert_eq!(sc.subcell_longitude, 0x2222);
300        assert_eq!(sc.subcell_extent_of_latitude, 0x333);
301        assert_eq!(sc.subcell_extent_of_longitude, 0x444);
302    }
303
304    #[test]
305    fn parse_entry_no_subcells() {
306        let bytes = [
307            TAG, 10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
308        ];
309        let d = CellListDescriptor::parse(&bytes).unwrap();
310        assert_eq!(d.entries.len(), 1);
311        assert!(d.entries[0].subcells.is_empty());
312    }
313
314    #[test]
315    fn empty_body_is_valid() {
316        let d = CellListDescriptor::parse(&[TAG, 0]).unwrap();
317        assert!(d.entries.is_empty());
318    }
319
320    #[test]
321    fn parse_rejects_wrong_tag() {
322        assert!(matches!(
323            CellListDescriptor::parse(&[0x6D, 0]).unwrap_err(),
324            Error::InvalidDescriptor { tag: 0x6D, .. }
325        ));
326    }
327
328    #[test]
329    fn parse_rejects_truncated_outer() {
330        let bytes = [TAG, 5, 0, 0, 0, 0, 0];
331        assert!(matches!(
332            CellListDescriptor::parse(&bytes).unwrap_err(),
333            Error::InvalidDescriptor { tag: TAG, .. }
334        ));
335    }
336
337    #[test]
338    fn parse_rejects_subcell_loop_overrun() {
339        // loop_len=8 but no subcell bytes follow
340        let bytes = [
341            TAG, 10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8,
342        ];
343        assert!(matches!(
344            CellListDescriptor::parse(&bytes).unwrap_err(),
345            Error::InvalidDescriptor { tag: TAG, .. }
346        ));
347    }
348
349    #[test]
350    fn parse_rejects_buffer_shorter_than_length() {
351        let bytes = [TAG, 10, 0x00, 0x01, 0x00];
352        assert!(matches!(
353            CellListDescriptor::parse(&bytes).unwrap_err(),
354            Error::BufferTooShort { .. }
355        ));
356    }
357
358    #[test]
359    fn serialize_round_trip() {
360        let d = CellListDescriptor {
361            entries: vec![
362                CellListEntry {
363                    cell_id: 0x1234,
364                    cell_latitude: 0x5678,
365                    cell_longitude: 0x9ABC,
366                    cell_extent_of_latitude: 0xDEF,
367                    cell_extent_of_longitude: 0x012,
368                    subcells: vec![CellListSubcell {
369                        cell_id_extension: 0x07,
370                        subcell_latitude: 0x1111,
371                        subcell_longitude: 0x2222,
372                        subcell_extent_of_latitude: 0x333,
373                        subcell_extent_of_longitude: 0x444,
374                    }],
375                },
376                CellListEntry {
377                    cell_id: 0xAAAA,
378                    cell_latitude: 0xBBBB,
379                    cell_longitude: 0xCCCC,
380                    cell_extent_of_latitude: 0x111,
381                    cell_extent_of_longitude: 0x222,
382                    subcells: vec![],
383                },
384            ],
385        };
386        let mut buf = vec![0u8; d.serialized_len()];
387        d.serialize_into(&mut buf).unwrap();
388        assert_eq!(CellListDescriptor::parse(&buf).unwrap(), d);
389    }
390
391    #[test]
392    fn serialize_rejects_too_small_buffer() {
393        let d = CellListDescriptor {
394            entries: vec![CellListEntry {
395                cell_id: 0,
396                cell_latitude: 0,
397                cell_longitude: 0,
398                cell_extent_of_latitude: 0,
399                cell_extent_of_longitude: 0,
400                subcells: vec![],
401            }],
402        };
403        let mut buf = vec![0u8; 3];
404        assert!(matches!(
405            d.serialize_into(&mut buf).unwrap_err(),
406            Error::OutputBufferTooSmall { .. }
407        ));
408    }
409
410    #[test]
411    fn serialize_rejects_over_range_body() {
412        // 26 empty entries × 10 = 260 > 255.
413        let d = CellListDescriptor {
414            entries: (0..26)
415                .map(|_| CellListEntry {
416                    cell_id: 0,
417                    cell_latitude: 0,
418                    cell_longitude: 0,
419                    cell_extent_of_latitude: 0,
420                    cell_extent_of_longitude: 0,
421                    subcells: vec![],
422                })
423                .collect(),
424        };
425        let mut buf = vec![0u8; d.serialized_len()];
426        assert!(matches!(
427            d.serialize_into(&mut buf).unwrap_err(),
428            Error::InvalidDescriptor { tag: TAG, .. }
429        ));
430    }
431
432    #[cfg(feature = "serde")]
433    #[test]
434    fn serde_round_trip() {
435        let d = CellListDescriptor {
436            entries: vec![CellListEntry {
437                cell_id: 0x1234,
438                cell_latitude: 0x5678,
439                cell_longitude: 0x9ABC,
440                cell_extent_of_latitude: 0xDEF,
441                cell_extent_of_longitude: 0x012,
442                subcells: vec![CellListSubcell {
443                    cell_id_extension: 0x07,
444                    subcell_latitude: 0x1111,
445                    subcell_longitude: 0x2222,
446                    subcell_extent_of_latitude: 0x333,
447                    subcell_extent_of_longitude: 0x444,
448                }],
449            }],
450        };
451        let json = serde_json::to_string(&d).unwrap();
452        let back: CellListDescriptor = serde_json::from_str(&json).unwrap();
453        assert_eq!(back, d);
454    }
455}