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
184                .get(pos..)
185                .and_then(|s| s.split_first_chunk::<OUTER_FIXED_LEN>())
186                .ok_or(Error::InvalidDescriptor {
187                    tag: TAG,
188                    reason: "cell_list outer entry truncated",
189                })?;
190            let cell_id = u16::from_be_bytes([outer[0], outer[1]]);
191            let cell_latitude = u16::from_be_bytes([outer[2], outer[3]]);
192            let cell_longitude = u16::from_be_bytes([outer[4], outer[5]]);
193            let (cell_extent_of_latitude, cell_extent_of_longitude) = read_extents(&outer[6..9]);
194            let subcell_info_loop_length = outer[9] as usize;
195            pos += OUTER_FIXED_LEN;
196            if subcell_info_loop_length % SUBCELL_LEN != 0 {
197                return Err(Error::InvalidDescriptor {
198                    tag: TAG,
199                    reason: "subcell_info_loop_length must be a multiple of 8",
200                });
201            }
202            if pos + subcell_info_loop_length > body.len() {
203                return Err(Error::InvalidDescriptor {
204                    tag: TAG,
205                    reason: "subcell_info_loop_length exceeds descriptor body",
206                });
207            }
208            let subcell_count = subcell_info_loop_length / SUBCELL_LEN;
209            let mut subcells = Vec::with_capacity(subcell_count);
210            for _ in 0..subcell_count {
211                let (sub, _) = body
212                    .get(pos..)
213                    .and_then(|s| s.split_first_chunk::<SUBCELL_LEN>())
214                    .ok_or(Error::InvalidDescriptor {
215                        tag: TAG,
216                        reason: "subcell_info_loop_length exceeds descriptor body",
217                    })?;
218                let cell_id_extension = sub[0];
219                let subcell_latitude = u16::from_be_bytes([sub[1], sub[2]]);
220                let subcell_longitude = u16::from_be_bytes([sub[3], sub[4]]);
221                let (subcell_extent_of_latitude, subcell_extent_of_longitude) =
222                    read_extents(&sub[5..8]);
223                subcells.push(CellListSubcell {
224                    cell_id_extension,
225                    subcell_latitude,
226                    subcell_longitude,
227                    subcell_extent_of_latitude,
228                    subcell_extent_of_longitude,
229                });
230                pos += SUBCELL_LEN;
231            }
232            entries.push(CellListEntry {
233                cell_id,
234                cell_latitude,
235                cell_longitude,
236                cell_extent_of_latitude,
237                cell_extent_of_longitude,
238                subcells,
239            });
240        }
241        Ok(Self { entries })
242    }
243}
244
245impl CellListDescriptor {
246    fn body_len(&self) -> usize {
247        self.entries
248            .iter()
249            .map(|e| OUTER_FIXED_LEN + e.subcells.len() * SUBCELL_LEN)
250            .sum()
251    }
252}
253
254impl Serialize for CellListDescriptor {
255    type Error = crate::error::Error;
256    fn serialized_len(&self) -> usize {
257        HEADER_LEN + self.body_len()
258    }
259
260    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
261        let body_len = self.body_len();
262        if body_len > u8::MAX as usize {
263            return Err(Error::InvalidDescriptor {
264                tag: TAG,
265                reason: "cell_list_descriptor body exceeds 255 bytes",
266            });
267        }
268        for e in &self.entries {
269            if e.subcells.len() * SUBCELL_LEN > u8::MAX as usize {
270                return Err(Error::InvalidDescriptor {
271                    tag: TAG,
272                    reason: "subcell_info_loop_length exceeds 255 bytes",
273                });
274            }
275        }
276        let len = self.serialized_len();
277        if buf.len() < len {
278            return Err(Error::OutputBufferTooSmall {
279                need: len,
280                have: buf.len(),
281            });
282        }
283        buf[0] = TAG;
284        buf[1] = body_len as u8;
285        let mut pos = HEADER_LEN;
286        for e in &self.entries {
287            buf[pos..pos + 2].copy_from_slice(&e.cell_id.to_be_bytes());
288            buf[pos + 2..pos + 4].copy_from_slice(&e.cell_latitude.to_be_bytes());
289            buf[pos + 4..pos + 6].copy_from_slice(&e.cell_longitude.to_be_bytes());
290            write_extents(
291                &mut buf[pos + 6..pos + 9],
292                e.cell_extent_of_latitude,
293                e.cell_extent_of_longitude,
294            );
295            buf[pos + 9] = (e.subcells.len() * SUBCELL_LEN) as u8;
296            pos += OUTER_FIXED_LEN;
297            for sc in &e.subcells {
298                buf[pos] = sc.cell_id_extension;
299                buf[pos + 1..pos + 3].copy_from_slice(&sc.subcell_latitude.to_be_bytes());
300                buf[pos + 3..pos + 5].copy_from_slice(&sc.subcell_longitude.to_be_bytes());
301                write_extents(
302                    &mut buf[pos + 5..pos + 8],
303                    sc.subcell_extent_of_latitude,
304                    sc.subcell_extent_of_longitude,
305                );
306                pos += SUBCELL_LEN;
307            }
308        }
309        Ok(len)
310    }
311}
312impl<'a> crate::traits::DescriptorDef<'a> for CellListDescriptor {
313    const TAG: u8 = TAG;
314    const NAME: &'static str = "CELL_LIST";
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320
321    #[test]
322    fn extents_pack_round_trips() {
323        let mut b = [0u8; 3];
324        write_extents(&mut b, 0xABC, 0xDEF);
325        assert_eq!(read_extents(&b), (0xABC, 0xDEF));
326    }
327
328    #[test]
329    fn cell_latitude_deg_zero() {
330        let e = CellListEntry {
331            cell_id: 0,
332            cell_latitude: 0,
333            cell_longitude: 0,
334            cell_extent_of_latitude: 0,
335            cell_extent_of_longitude: 0,
336            subcells: vec![],
337        };
338        assert_eq!(e.cell_latitude_deg(), 0.0);
339        assert_eq!(e.cell_longitude_deg(), 0.0);
340    }
341
342    #[test]
343    fn cell_latitude_deg_max() {
344        // Max positive = 32767/32768 * 90 ≈ 89.997
345        let e = CellListEntry {
346            cell_id: 0,
347            cell_latitude: 0x7FFF,
348            cell_longitude: 0,
349            cell_extent_of_latitude: 0,
350            cell_extent_of_longitude: 0,
351            subcells: vec![],
352        };
353        let deg = e.cell_latitude_deg();
354        assert!(deg > 89.9 && deg <= 90.0, "got {deg}");
355    }
356
357    #[test]
358    fn cell_longitude_deg_max() {
359        let e = CellListEntry {
360            cell_id: 0,
361            cell_latitude: 0,
362            cell_longitude: 0x7FFF,
363            cell_extent_of_latitude: 0,
364            cell_extent_of_longitude: 0,
365            subcells: vec![],
366        };
367        let deg = e.cell_longitude_deg();
368        assert!(deg > 179.9 && deg <= 180.0, "got {deg}");
369    }
370
371    #[test]
372    fn cell_extent_deg() {
373        // 4095/32768 * 90 ≈ 11.25 degrees
374        let e = CellListEntry {
375            cell_id: 0,
376            cell_latitude: 0,
377            cell_longitude: 0,
378            cell_extent_of_latitude: 4095,
379            cell_extent_of_longitude: 2048,
380            subcells: vec![],
381        };
382        assert!(e.cell_extent_of_latitude_deg() > 11.2);
383        assert!(e.cell_extent_of_longitude_deg() > 11.2);
384    }
385
386    #[test]
387    fn parse_entry_with_subcell() {
388        let bytes = [
389            TAG, 18, // body length: outer(10) + 1 subcell(8)
390            // outer: cell_id=0x1234, lat=0x5678, long=0x9ABC, extent_lat=0xDEF, extent_long=0x012, loop_len=8
391            0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x12, 8,
392            // subcell: ext=0x07, lat=0x1111, long=0x2222, extent_lat=0x333, extent_long=0x444
393            0x07, 0x11, 0x11, 0x22, 0x22, 0x33, 0x34, 0x44,
394        ];
395        let d = CellListDescriptor::parse(&bytes).unwrap();
396        assert_eq!(d.entries.len(), 1);
397        let e = &d.entries[0];
398        assert_eq!(e.cell_id, 0x1234);
399        assert_eq!(e.cell_latitude, 0x5678);
400        assert_eq!(e.cell_longitude, 0x9ABC);
401        assert_eq!(e.cell_extent_of_latitude, 0xDEF);
402        assert_eq!(e.cell_extent_of_longitude, 0x012);
403        assert_eq!(e.subcells.len(), 1);
404        let sc = &e.subcells[0];
405        assert_eq!(sc.cell_id_extension, 0x07);
406        assert_eq!(sc.subcell_latitude, 0x1111);
407        assert_eq!(sc.subcell_longitude, 0x2222);
408        assert_eq!(sc.subcell_extent_of_latitude, 0x333);
409        assert_eq!(sc.subcell_extent_of_longitude, 0x444);
410
411        // Accessor tests
412        let lat_deg = e.cell_latitude_deg();
413        let long_deg = e.cell_longitude_deg();
414        assert!(lat_deg.abs() > 0.0);
415        assert!(long_deg.abs() > 0.0);
416        let sc_lat = sc.subcell_latitude_deg();
417        let sc_long = sc.subcell_longitude_deg();
418        assert!(sc_lat.abs() > 0.0);
419        assert!(sc_long.abs() > 0.0);
420    }
421
422    #[test]
423    fn parse_entry_no_subcells() {
424        let bytes = [
425            TAG, 10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
426        ];
427        let d = CellListDescriptor::parse(&bytes).unwrap();
428        assert_eq!(d.entries.len(), 1);
429        assert!(d.entries[0].subcells.is_empty());
430    }
431
432    #[test]
433    fn empty_body_is_valid() {
434        let d = CellListDescriptor::parse(&[TAG, 0]).unwrap();
435        assert!(d.entries.is_empty());
436    }
437
438    #[test]
439    fn parse_rejects_wrong_tag() {
440        assert!(matches!(
441            CellListDescriptor::parse(&[0x6D, 0]).unwrap_err(),
442            Error::InvalidDescriptor { tag: 0x6D, .. }
443        ));
444    }
445
446    #[test]
447    fn parse_rejects_truncated_outer() {
448        let bytes = [TAG, 5, 0, 0, 0, 0, 0];
449        assert!(matches!(
450            CellListDescriptor::parse(&bytes).unwrap_err(),
451            Error::InvalidDescriptor { tag: TAG, .. }
452        ));
453    }
454
455    #[test]
456    fn parse_rejects_subcell_loop_overrun() {
457        // loop_len=8 but no subcell bytes follow
458        let bytes = [
459            TAG, 10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8,
460        ];
461        assert!(matches!(
462            CellListDescriptor::parse(&bytes).unwrap_err(),
463            Error::InvalidDescriptor { tag: TAG, .. }
464        ));
465    }
466
467    #[test]
468    fn parse_rejects_buffer_shorter_than_length() {
469        let bytes = [TAG, 10, 0x00, 0x01, 0x00];
470        assert!(matches!(
471            CellListDescriptor::parse(&bytes).unwrap_err(),
472            Error::BufferTooShort { .. }
473        ));
474    }
475
476    #[test]
477    fn serialize_round_trip() {
478        let d = CellListDescriptor {
479            entries: vec![
480                CellListEntry {
481                    cell_id: 0x1234,
482                    cell_latitude: 0x5678,
483                    cell_longitude: 0x9ABC,
484                    cell_extent_of_latitude: 0xDEF,
485                    cell_extent_of_longitude: 0x012,
486                    subcells: vec![CellListSubcell {
487                        cell_id_extension: 0x07,
488                        subcell_latitude: 0x1111,
489                        subcell_longitude: 0x2222,
490                        subcell_extent_of_latitude: 0x333,
491                        subcell_extent_of_longitude: 0x444,
492                    }],
493                },
494                CellListEntry {
495                    cell_id: 0xAAAA,
496                    cell_latitude: 0xBBBB,
497                    cell_longitude: 0xCCCC,
498                    cell_extent_of_latitude: 0x111,
499                    cell_extent_of_longitude: 0x222,
500                    subcells: vec![],
501                },
502            ],
503        };
504        let mut buf = vec![0u8; d.serialized_len()];
505        d.serialize_into(&mut buf).unwrap();
506        assert_eq!(CellListDescriptor::parse(&buf).unwrap(), d);
507    }
508
509    #[test]
510    fn serialize_rejects_too_small_buffer() {
511        let d = CellListDescriptor {
512            entries: vec![CellListEntry {
513                cell_id: 0,
514                cell_latitude: 0,
515                cell_longitude: 0,
516                cell_extent_of_latitude: 0,
517                cell_extent_of_longitude: 0,
518                subcells: vec![],
519            }],
520        };
521        let mut buf = vec![0u8; 3];
522        assert!(matches!(
523            d.serialize_into(&mut buf).unwrap_err(),
524            Error::OutputBufferTooSmall { .. }
525        ));
526    }
527
528    #[test]
529    fn serialize_rejects_over_range_body() {
530        // 26 empty entries × 10 = 260 > 255.
531        let d = CellListDescriptor {
532            entries: (0..26)
533                .map(|_| CellListEntry {
534                    cell_id: 0,
535                    cell_latitude: 0,
536                    cell_longitude: 0,
537                    cell_extent_of_latitude: 0,
538                    cell_extent_of_longitude: 0,
539                    subcells: vec![],
540                })
541                .collect(),
542        };
543        let mut buf = vec![0u8; d.serialized_len()];
544        assert!(matches!(
545            d.serialize_into(&mut buf).unwrap_err(),
546            Error::InvalidDescriptor { tag: TAG, .. }
547        ));
548    }
549
550    #[cfg(feature = "serde")]
551    #[test]
552    fn serde_round_trip() {
553        let d = CellListDescriptor {
554            entries: vec![CellListEntry {
555                cell_id: 0x1234,
556                cell_latitude: 0x5678,
557                cell_longitude: 0x9ABC,
558                cell_extent_of_latitude: 0xDEF,
559                cell_extent_of_longitude: 0x012,
560                subcells: vec![CellListSubcell {
561                    cell_id_extension: 0x07,
562                    subcell_latitude: 0x1111,
563                    subcell_longitude: 0x2222,
564                    subcell_extent_of_latitude: 0x333,
565                    subcell_extent_of_longitude: 0x444,
566                }],
567            }],
568        };
569        let json = serde_json::to_string(&d).unwrap();
570        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
571        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
572    }
573}