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