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//! latitude/longitude are 16-bit two's-complement fractions: 90°/2^15 for
25//! latitude, 180°/2^15 for longitude (§6.2.7). The 12-bit extents use the
26//! same scale.
27
28use super::descriptor_body;
29use crate::error::{Error, Result};
30use alloc::vec::Vec;
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/// Scale factor: 90°/2^15 for latitude (§6.2.7).
46const LAT_SCALE: f64 = 90.0 / 32768.0;
47/// Scale factor: 180°/2^15 for longitude (§6.2.7).
48const LONG_SCALE: f64 = 180.0 / 32768.0;
49
50/// Interpret a 16-bit big-endian value as a signed i16 (two's-complement).
51fn u16_to_i16(raw: u16) -> i16 {
52    raw as i16
53}
54
55/// One sub-cell within a cell.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize))]
58pub struct CellListSubcell {
59    /// 8-bit cell_id_extension.
60    pub cell_id_extension: u8,
61    /// 16-bit subcell_latitude (raw wire value, two's-complement).
62    pub subcell_latitude: u16,
63    /// 16-bit subcell_longitude (raw wire value, two's-complement).
64    pub subcell_longitude: u16,
65    /// 12-bit subcell_extent_of_latitude.
66    pub subcell_extent_of_latitude: u16,
67    /// 12-bit subcell_extent_of_longitude.
68    pub subcell_extent_of_longitude: u16,
69}
70
71impl CellListSubcell {
72    /// Decode subcell_latitude to degrees (90°/2^15 scaling).
73    #[must_use]
74    pub fn subcell_latitude_deg(&self) -> f64 {
75        u16_to_i16(self.subcell_latitude) as f64 * LAT_SCALE
76    }
77
78    /// Decode subcell_longitude to degrees (180°/2^15 scaling).
79    #[must_use]
80    pub fn subcell_longitude_deg(&self) -> f64 {
81        u16_to_i16(self.subcell_longitude) as f64 * LONG_SCALE
82    }
83
84    /// Decode subcell_extent_of_latitude to degrees (90°/2^15 scaling).
85    #[must_use]
86    pub fn subcell_extent_of_latitude_deg(&self) -> f64 {
87        (self.subcell_extent_of_latitude & EXTENT_MASK) as f64 * LAT_SCALE
88    }
89
90    /// Decode subcell_extent_of_longitude to degrees (180°/2^15 scaling).
91    #[must_use]
92    pub fn subcell_extent_of_longitude_deg(&self) -> f64 {
93        (self.subcell_extent_of_longitude & EXTENT_MASK) as f64 * LONG_SCALE
94    }
95}
96
97/// One cell with its sub-cell list.
98#[derive(Debug, Clone, PartialEq, Eq)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize))]
100pub struct CellListEntry {
101    /// 16-bit cell_id.
102    pub cell_id: u16,
103    /// 16-bit cell_latitude (raw wire value, two's-complement).
104    pub cell_latitude: u16,
105    /// 16-bit cell_longitude (raw wire value, two's-complement).
106    pub cell_longitude: u16,
107    /// 12-bit cell_extent_of_latitude.
108    pub cell_extent_of_latitude: u16,
109    /// 12-bit cell_extent_of_longitude.
110    pub cell_extent_of_longitude: u16,
111    /// Sub-cells for this cell.
112    pub subcells: Vec<CellListSubcell>,
113}
114
115impl CellListEntry {
116    /// Decode cell_latitude to degrees (90°/2^15 scaling).
117    #[must_use]
118    pub fn cell_latitude_deg(&self) -> f64 {
119        u16_to_i16(self.cell_latitude) as f64 * LAT_SCALE
120    }
121
122    /// Decode cell_longitude to degrees (180°/2^15 scaling).
123    #[must_use]
124    pub fn cell_longitude_deg(&self) -> f64 {
125        u16_to_i16(self.cell_longitude) as f64 * LONG_SCALE
126    }
127
128    /// Decode cell_extent_of_latitude to degrees (90°/2^15 scaling).
129    #[must_use]
130    pub fn cell_extent_of_latitude_deg(&self) -> f64 {
131        (self.cell_extent_of_latitude & EXTENT_MASK) as f64 * LAT_SCALE
132    }
133
134    /// Decode cell_extent_of_longitude to degrees (180°/2^15 scaling).
135    #[must_use]
136    pub fn cell_extent_of_longitude_deg(&self) -> f64 {
137        (self.cell_extent_of_longitude & EXTENT_MASK) as f64 * LONG_SCALE
138    }
139}
140
141/// Cell List Descriptor.
142#[derive(Debug, Clone, PartialEq, Eq)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize))]
144pub struct CellListDescriptor {
145    /// Outer cell entries in wire order.
146    pub entries: Vec<CellListEntry>,
147}
148
149/// Decode the packed 24-bit extent pair `lat(12) || long(12)` from 3 bytes.
150fn read_extents(b: &[u8]) -> (u16, u16) {
151    let lat = (u16::from(b[0]) << 4) | (u16::from(b[1]) >> 4);
152    let long = ((u16::from(b[1]) & 0x0F) << 8) | u16::from(b[2]);
153    (lat, long)
154}
155
156/// Encode a packed 24-bit extent pair into 3 bytes.
157fn write_extents(buf: &mut [u8], lat: u16, long: u16) {
158    let lat = lat & EXTENT_MASK;
159    let long = long & EXTENT_MASK;
160    buf[0] = (lat >> 4) as u8;
161    buf[1] = (((lat & 0x0F) << 4) | (long >> 8)) as u8;
162    buf[2] = long as u8;
163}
164
165impl<'a> Parse<'a> for CellListDescriptor {
166    type Error = crate::error::Error;
167    fn parse(bytes: &'a [u8]) -> Result<Self> {
168        let body = descriptor_body(
169            bytes,
170            TAG,
171            "CellListDescriptor",
172            "unexpected tag for cell_list_descriptor",
173        )?;
174        let mut entries = Vec::new();
175        let mut pos = 0;
176        while pos < body.len() {
177            if pos + OUTER_FIXED_LEN > body.len() {
178                return Err(Error::InvalidDescriptor {
179                    tag: TAG,
180                    reason: "cell_list outer entry truncated",
181                });
182            }
183            let (outer, _) = body[pos..].split_first_chunk::<OUTER_FIXED_LEN>().ok_or(
184                Error::InvalidDescriptor {
185                    tag: TAG,
186                    reason: "cell_list outer entry truncated",
187                },
188            )?;
189            let cell_id = u16::from_be_bytes([outer[0], outer[1]]);
190            let cell_latitude = u16::from_be_bytes([outer[2], outer[3]]);
191            let cell_longitude = u16::from_be_bytes([outer[4], outer[5]]);
192            let (cell_extent_of_latitude, cell_extent_of_longitude) = read_extents(&outer[6..9]);
193            let subcell_info_loop_length = outer[9] as usize;
194            pos += OUTER_FIXED_LEN;
195            if subcell_info_loop_length % SUBCELL_LEN != 0 {
196                return Err(Error::InvalidDescriptor {
197                    tag: TAG,
198                    reason: "subcell_info_loop_length must be a multiple of 8",
199                });
200            }
201            if pos + subcell_info_loop_length > body.len() {
202                return Err(Error::InvalidDescriptor {
203                    tag: TAG,
204                    reason: "subcell_info_loop_length exceeds descriptor body",
205                });
206            }
207            let subcell_count = subcell_info_loop_length / SUBCELL_LEN;
208            let mut subcells = Vec::with_capacity(subcell_count);
209            for _ in 0..subcell_count {
210                let (sub, _) = body[pos..].split_first_chunk::<SUBCELL_LEN>().ok_or(
211                    Error::InvalidDescriptor {
212                        tag: TAG,
213                        reason: "subcell_info_loop_length exceeds descriptor body",
214                    },
215                )?;
216                let cell_id_extension = sub[0];
217                let subcell_latitude = u16::from_be_bytes([sub[1], sub[2]]);
218                let subcell_longitude = u16::from_be_bytes([sub[3], sub[4]]);
219                let (subcell_extent_of_latitude, subcell_extent_of_longitude) =
220                    read_extents(&sub[5..8]);
221                subcells.push(CellListSubcell {
222                    cell_id_extension,
223                    subcell_latitude,
224                    subcell_longitude,
225                    subcell_extent_of_latitude,
226                    subcell_extent_of_longitude,
227                });
228                pos += SUBCELL_LEN;
229            }
230            entries.push(CellListEntry {
231                cell_id,
232                cell_latitude,
233                cell_longitude,
234                cell_extent_of_latitude,
235                cell_extent_of_longitude,
236                subcells,
237            });
238        }
239        Ok(Self { entries })
240    }
241}
242
243impl CellListDescriptor {
244    fn body_len(&self) -> usize {
245        self.entries
246            .iter()
247            .map(|e| OUTER_FIXED_LEN + e.subcells.len() * SUBCELL_LEN)
248            .sum()
249    }
250}
251
252impl Serialize for CellListDescriptor {
253    type Error = crate::error::Error;
254    fn serialized_len(&self) -> usize {
255        HEADER_LEN + self.body_len()
256    }
257
258    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
259        let body_len = self.body_len();
260        if body_len > u8::MAX as usize {
261            return Err(Error::InvalidDescriptor {
262                tag: TAG,
263                reason: "cell_list_descriptor body exceeds 255 bytes",
264            });
265        }
266        for e in &self.entries {
267            if e.subcells.len() * SUBCELL_LEN > u8::MAX as usize {
268                return Err(Error::InvalidDescriptor {
269                    tag: TAG,
270                    reason: "subcell_info_loop_length exceeds 255 bytes",
271                });
272            }
273        }
274        let len = self.serialized_len();
275        if buf.len() < len {
276            return Err(Error::OutputBufferTooSmall {
277                need: len,
278                have: buf.len(),
279            });
280        }
281        buf[0] = TAG;
282        buf[1] = body_len as u8;
283        let mut pos = HEADER_LEN;
284        for e in &self.entries {
285            buf[pos..pos + 2].copy_from_slice(&e.cell_id.to_be_bytes());
286            buf[pos + 2..pos + 4].copy_from_slice(&e.cell_latitude.to_be_bytes());
287            buf[pos + 4..pos + 6].copy_from_slice(&e.cell_longitude.to_be_bytes());
288            write_extents(
289                &mut buf[pos + 6..pos + 9],
290                e.cell_extent_of_latitude,
291                e.cell_extent_of_longitude,
292            );
293            buf[pos + 9] = (e.subcells.len() * SUBCELL_LEN) as u8;
294            pos += OUTER_FIXED_LEN;
295            for sc in &e.subcells {
296                buf[pos] = sc.cell_id_extension;
297                buf[pos + 1..pos + 3].copy_from_slice(&sc.subcell_latitude.to_be_bytes());
298                buf[pos + 3..pos + 5].copy_from_slice(&sc.subcell_longitude.to_be_bytes());
299                write_extents(
300                    &mut buf[pos + 5..pos + 8],
301                    sc.subcell_extent_of_latitude,
302                    sc.subcell_extent_of_longitude,
303                );
304                pos += SUBCELL_LEN;
305            }
306        }
307        Ok(len)
308    }
309}
310impl<'a> crate::traits::DescriptorDef<'a> for CellListDescriptor {
311    const TAG: u8 = TAG;
312    const NAME: &'static str = "CELL_LIST";
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn extents_pack_round_trips() {
321        let mut b = [0u8; 3];
322        write_extents(&mut b, 0xABC, 0xDEF);
323        assert_eq!(read_extents(&b), (0xABC, 0xDEF));
324    }
325
326    #[test]
327    fn cell_latitude_deg_zero() {
328        let e = CellListEntry {
329            cell_id: 0,
330            cell_latitude: 0,
331            cell_longitude: 0,
332            cell_extent_of_latitude: 0,
333            cell_extent_of_longitude: 0,
334            subcells: vec![],
335        };
336        assert_eq!(e.cell_latitude_deg(), 0.0);
337        assert_eq!(e.cell_longitude_deg(), 0.0);
338    }
339
340    #[test]
341    fn cell_latitude_deg_max() {
342        // Max positive = 32767/32768 * 90 ≈ 89.997
343        let e = CellListEntry {
344            cell_id: 0,
345            cell_latitude: 0x7FFF,
346            cell_longitude: 0,
347            cell_extent_of_latitude: 0,
348            cell_extent_of_longitude: 0,
349            subcells: vec![],
350        };
351        let deg = e.cell_latitude_deg();
352        assert!(deg > 89.9 && deg <= 90.0, "got {deg}");
353    }
354
355    #[test]
356    fn cell_longitude_deg_max() {
357        let e = CellListEntry {
358            cell_id: 0,
359            cell_latitude: 0,
360            cell_longitude: 0x7FFF,
361            cell_extent_of_latitude: 0,
362            cell_extent_of_longitude: 0,
363            subcells: vec![],
364        };
365        let deg = e.cell_longitude_deg();
366        assert!(deg > 179.9 && deg <= 180.0, "got {deg}");
367    }
368
369    #[test]
370    fn cell_extent_deg() {
371        // 4095/32768 * 90 ≈ 11.25 degrees
372        let e = CellListEntry {
373            cell_id: 0,
374            cell_latitude: 0,
375            cell_longitude: 0,
376            cell_extent_of_latitude: 4095,
377            cell_extent_of_longitude: 2048,
378            subcells: vec![],
379        };
380        assert!(e.cell_extent_of_latitude_deg() > 11.2);
381        assert!(e.cell_extent_of_longitude_deg() > 11.2);
382    }
383
384    #[test]
385    fn parse_entry_with_subcell() {
386        let bytes = [
387            TAG, 18, // body length: outer(10) + 1 subcell(8)
388            // outer: cell_id=0x1234, lat=0x5678, long=0x9ABC, extent_lat=0xDEF, extent_long=0x012, loop_len=8
389            0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x12, 8,
390            // subcell: ext=0x07, lat=0x1111, long=0x2222, extent_lat=0x333, extent_long=0x444
391            0x07, 0x11, 0x11, 0x22, 0x22, 0x33, 0x34, 0x44,
392        ];
393        let d = CellListDescriptor::parse(&bytes).unwrap();
394        assert_eq!(d.entries.len(), 1);
395        let e = &d.entries[0];
396        assert_eq!(e.cell_id, 0x1234);
397        assert_eq!(e.cell_latitude, 0x5678);
398        assert_eq!(e.cell_longitude, 0x9ABC);
399        assert_eq!(e.cell_extent_of_latitude, 0xDEF);
400        assert_eq!(e.cell_extent_of_longitude, 0x012);
401        assert_eq!(e.subcells.len(), 1);
402        let sc = &e.subcells[0];
403        assert_eq!(sc.cell_id_extension, 0x07);
404        assert_eq!(sc.subcell_latitude, 0x1111);
405        assert_eq!(sc.subcell_longitude, 0x2222);
406        assert_eq!(sc.subcell_extent_of_latitude, 0x333);
407        assert_eq!(sc.subcell_extent_of_longitude, 0x444);
408
409        // Accessor tests
410        let lat_deg = e.cell_latitude_deg();
411        let long_deg = e.cell_longitude_deg();
412        assert!(lat_deg.abs() > 0.0);
413        assert!(long_deg.abs() > 0.0);
414        let sc_lat = sc.subcell_latitude_deg();
415        let sc_long = sc.subcell_longitude_deg();
416        assert!(sc_lat.abs() > 0.0);
417        assert!(sc_long.abs() > 0.0);
418    }
419
420    #[test]
421    fn parse_entry_no_subcells() {
422        let bytes = [
423            TAG, 10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
424        ];
425        let d = CellListDescriptor::parse(&bytes).unwrap();
426        assert_eq!(d.entries.len(), 1);
427        assert!(d.entries[0].subcells.is_empty());
428    }
429
430    #[test]
431    fn empty_body_is_valid() {
432        let d = CellListDescriptor::parse(&[TAG, 0]).unwrap();
433        assert!(d.entries.is_empty());
434    }
435
436    #[test]
437    fn parse_rejects_wrong_tag() {
438        assert!(matches!(
439            CellListDescriptor::parse(&[0x6D, 0]).unwrap_err(),
440            Error::InvalidDescriptor { tag: 0x6D, .. }
441        ));
442    }
443
444    #[test]
445    fn parse_rejects_truncated_outer() {
446        let bytes = [TAG, 5, 0, 0, 0, 0, 0];
447        assert!(matches!(
448            CellListDescriptor::parse(&bytes).unwrap_err(),
449            Error::InvalidDescriptor { tag: TAG, .. }
450        ));
451    }
452
453    #[test]
454    fn parse_rejects_subcell_loop_overrun() {
455        // loop_len=8 but no subcell bytes follow
456        let bytes = [
457            TAG, 10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8,
458        ];
459        assert!(matches!(
460            CellListDescriptor::parse(&bytes).unwrap_err(),
461            Error::InvalidDescriptor { tag: TAG, .. }
462        ));
463    }
464
465    #[test]
466    fn parse_rejects_buffer_shorter_than_length() {
467        let bytes = [TAG, 10, 0x00, 0x01, 0x00];
468        assert!(matches!(
469            CellListDescriptor::parse(&bytes).unwrap_err(),
470            Error::BufferTooShort { .. }
471        ));
472    }
473
474    #[test]
475    fn serialize_round_trip() {
476        let d = CellListDescriptor {
477            entries: vec![
478                CellListEntry {
479                    cell_id: 0x1234,
480                    cell_latitude: 0x5678,
481                    cell_longitude: 0x9ABC,
482                    cell_extent_of_latitude: 0xDEF,
483                    cell_extent_of_longitude: 0x012,
484                    subcells: vec![CellListSubcell {
485                        cell_id_extension: 0x07,
486                        subcell_latitude: 0x1111,
487                        subcell_longitude: 0x2222,
488                        subcell_extent_of_latitude: 0x333,
489                        subcell_extent_of_longitude: 0x444,
490                    }],
491                },
492                CellListEntry {
493                    cell_id: 0xAAAA,
494                    cell_latitude: 0xBBBB,
495                    cell_longitude: 0xCCCC,
496                    cell_extent_of_latitude: 0x111,
497                    cell_extent_of_longitude: 0x222,
498                    subcells: vec![],
499                },
500            ],
501        };
502        let mut buf = vec![0u8; d.serialized_len()];
503        d.serialize_into(&mut buf).unwrap();
504        assert_eq!(CellListDescriptor::parse(&buf).unwrap(), d);
505    }
506
507    #[test]
508    fn serialize_rejects_too_small_buffer() {
509        let d = CellListDescriptor {
510            entries: vec![CellListEntry {
511                cell_id: 0,
512                cell_latitude: 0,
513                cell_longitude: 0,
514                cell_extent_of_latitude: 0,
515                cell_extent_of_longitude: 0,
516                subcells: vec![],
517            }],
518        };
519        let mut buf = vec![0u8; 3];
520        assert!(matches!(
521            d.serialize_into(&mut buf).unwrap_err(),
522            Error::OutputBufferTooSmall { .. }
523        ));
524    }
525
526    #[test]
527    fn serialize_rejects_over_range_body() {
528        // 26 empty entries × 10 = 260 > 255.
529        let d = CellListDescriptor {
530            entries: (0..26)
531                .map(|_| CellListEntry {
532                    cell_id: 0,
533                    cell_latitude: 0,
534                    cell_longitude: 0,
535                    cell_extent_of_latitude: 0,
536                    cell_extent_of_longitude: 0,
537                    subcells: vec![],
538                })
539                .collect(),
540        };
541        let mut buf = vec![0u8; d.serialized_len()];
542        assert!(matches!(
543            d.serialize_into(&mut buf).unwrap_err(),
544            Error::InvalidDescriptor { tag: TAG, .. }
545        ));
546    }
547
548    #[cfg(feature = "serde")]
549    #[test]
550    fn serde_round_trip() {
551        let d = CellListDescriptor {
552            entries: vec![CellListEntry {
553                cell_id: 0x1234,
554                cell_latitude: 0x5678,
555                cell_longitude: 0x9ABC,
556                cell_extent_of_latitude: 0xDEF,
557                cell_extent_of_longitude: 0x012,
558                subcells: vec![CellListSubcell {
559                    cell_id_extension: 0x07,
560                    subcell_latitude: 0x1111,
561                    subcell_longitude: 0x2222,
562                    subcell_extent_of_latitude: 0x333,
563                    subcell_extent_of_longitude: 0x444,
564                }],
565            }],
566        };
567        let json = serde_json::to_string(&d).unwrap();
568        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
569        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
570    }
571}