1use crate::error::{Error, Result};
22use crate::traits::Descriptor;
23use dvb_common::{Parse, Serialize};
24
25pub const TAG: u8 = 0x51;
27const HEADER_LEN: usize = 2;
28const GRID_HEADER_LEN: usize = 1;
29const CELL_FIXED_LEN: usize = 3; const MAX_BODY_LEN: usize = u8::MAX as usize;
32const MAX_ELEM_FIELD: usize = u8::MAX as usize;
34
35const ENTRY_POINT_MASK: u8 = 0x80; const ELEM_CELL_ID_MASK: u8 = 0x3F; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub enum CellLinkage {
42 Undefined,
44 Bouquet {
46 bouquet_id: u16,
48 },
49 Service {
51 original_network_id: u16,
53 transport_stream_id: u16,
55 service_id: u16,
57 },
58 OtherMosaic {
60 original_network_id: u16,
62 transport_stream_id: u16,
64 service_id: u16,
66 },
67 Event {
69 original_network_id: u16,
71 transport_stream_id: u16,
73 service_id: u16,
75 event_id: u16,
77 },
78 Reserved {
81 value: u8,
83 },
84}
85
86impl CellLinkage {
87 fn info_byte(&self) -> u8 {
89 match self {
90 CellLinkage::Undefined => 0x00,
91 CellLinkage::Bouquet { .. } => 0x01,
92 CellLinkage::Service { .. } => 0x02,
93 CellLinkage::OtherMosaic { .. } => 0x03,
94 CellLinkage::Event { .. } => 0x04,
95 CellLinkage::Reserved { value } => *value,
96 }
97 }
98
99 fn payload_len(&self) -> usize {
101 match self {
102 CellLinkage::Undefined | CellLinkage::Reserved { .. } => 0,
103 CellLinkage::Bouquet { .. } => 2,
104 CellLinkage::Service { .. } | CellLinkage::OtherMosaic { .. } => 6,
105 CellLinkage::Event { .. } => 8,
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113pub struct MosaicLogicalCell {
114 pub logical_cell_id: u8,
116 pub presentation_info: u8,
119 pub elementary_cell_ids: Vec<u8>,
121 pub linkage: CellLinkage,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct MosaicDescriptor {
129 pub mosaic_entry_point: bool,
131 pub num_horizontal_cells: u8,
133 pub num_vertical_cells: u8,
135 pub logical_cells: Vec<MosaicLogicalCell>,
137}
138
139fn read_u16(b: &[u8], at: usize) -> u16 {
140 u16::from_be_bytes([b[at], b[at + 1]])
141}
142
143impl<'a> Parse<'a> for MosaicDescriptor {
144 type Error = crate::error::Error;
145 fn parse(bytes: &'a [u8]) -> Result<Self> {
146 if bytes.len() < HEADER_LEN {
147 return Err(Error::BufferTooShort {
148 need: HEADER_LEN,
149 have: bytes.len(),
150 what: "MosaicDescriptor header",
151 });
152 }
153 if bytes[0] != TAG {
154 return Err(Error::InvalidDescriptor {
155 tag: bytes[0],
156 reason: "unexpected tag for mosaic_descriptor",
157 });
158 }
159 let length = bytes[1] as usize;
160 if length < GRID_HEADER_LEN {
161 return Err(Error::InvalidDescriptor {
162 tag: TAG,
163 reason: "mosaic_descriptor missing grid header byte",
164 });
165 }
166 let end = HEADER_LEN + length;
167 if bytes.len() < end {
168 return Err(Error::BufferTooShort {
169 need: end,
170 have: bytes.len(),
171 what: "MosaicDescriptor body",
172 });
173 }
174 let body = &bytes[HEADER_LEN..end];
175 let mosaic_entry_point = body[0] & ENTRY_POINT_MASK != 0;
177 let num_horizontal_cells = (body[0] >> 4) & 0x07;
178 let num_vertical_cells = body[0] & 0x07;
179
180 let mut logical_cells = Vec::new();
181 let mut pos = GRID_HEADER_LEN;
182 while pos < body.len() {
183 if pos + CELL_FIXED_LEN > body.len() {
184 return Err(Error::InvalidDescriptor {
185 tag: TAG,
186 reason: "truncated mosaic logical cell header",
187 });
188 }
189 let logical_cell_id = (body[pos] >> 2) & 0x3F;
191 let presentation_info = body[pos + 1] & 0x07;
192 let elem_field_len = body[pos + 2] as usize;
193 pos += CELL_FIXED_LEN;
194 if pos + elem_field_len > body.len() {
195 return Err(Error::InvalidDescriptor {
196 tag: TAG,
197 reason: "elementary_cell_field_length exceeds descriptor body",
198 });
199 }
200 let mut elementary_cell_ids = Vec::with_capacity(elem_field_len);
201 for &b in &body[pos..pos + elem_field_len] {
202 elementary_cell_ids.push(b & ELEM_CELL_ID_MASK);
204 }
205 pos += elem_field_len;
206 if pos >= body.len() {
207 return Err(Error::InvalidDescriptor {
208 tag: TAG,
209 reason: "missing cell_linkage_info byte",
210 });
211 }
212 let info = body[pos];
213 pos += 1;
214 let linkage = match info {
215 0x00 => CellLinkage::Undefined,
216 0x01 => {
217 if pos + 2 > body.len() {
218 return Err(Error::InvalidDescriptor {
219 tag: TAG,
220 reason: "truncated bouquet linkage payload",
221 });
222 }
223 let l = CellLinkage::Bouquet {
224 bouquet_id: read_u16(body, pos),
225 };
226 pos += 2;
227 l
228 }
229 0x02 | 0x03 => {
230 if pos + 6 > body.len() {
231 return Err(Error::InvalidDescriptor {
232 tag: TAG,
233 reason: "truncated service/mosaic linkage payload",
234 });
235 }
236 let original_network_id = read_u16(body, pos);
237 let transport_stream_id = read_u16(body, pos + 2);
238 let service_id = read_u16(body, pos + 4);
239 pos += 6;
240 if info == 0x02 {
241 CellLinkage::Service {
242 original_network_id,
243 transport_stream_id,
244 service_id,
245 }
246 } else {
247 CellLinkage::OtherMosaic {
248 original_network_id,
249 transport_stream_id,
250 service_id,
251 }
252 }
253 }
254 0x04 => {
255 if pos + 8 > body.len() {
256 return Err(Error::InvalidDescriptor {
257 tag: TAG,
258 reason: "truncated event linkage payload",
259 });
260 }
261 let l = CellLinkage::Event {
262 original_network_id: read_u16(body, pos),
263 transport_stream_id: read_u16(body, pos + 2),
264 service_id: read_u16(body, pos + 4),
265 event_id: read_u16(body, pos + 6),
266 };
267 pos += 8;
268 l
269 }
270 other => CellLinkage::Reserved { value: other },
272 };
273 logical_cells.push(MosaicLogicalCell {
274 logical_cell_id,
275 presentation_info,
276 elementary_cell_ids,
277 linkage,
278 });
279 }
280 Ok(Self {
281 mosaic_entry_point,
282 num_horizontal_cells,
283 num_vertical_cells,
284 logical_cells,
285 })
286 }
287}
288
289impl Serialize for MosaicDescriptor {
290 type Error = crate::error::Error;
291 fn serialized_len(&self) -> usize {
292 let cells: usize = self
293 .logical_cells
294 .iter()
295 .map(|c| CELL_FIXED_LEN + c.elementary_cell_ids.len() + 1 + c.linkage.payload_len())
296 .sum();
297 HEADER_LEN + GRID_HEADER_LEN + cells
298 }
299
300 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
301 let len = self.serialized_len();
302 if buf.len() < len {
303 return Err(Error::OutputBufferTooSmall {
304 need: len,
305 have: buf.len(),
306 });
307 }
308 let body_len = len - HEADER_LEN;
309 if body_len > MAX_BODY_LEN {
311 return Err(Error::SectionLengthOverflow {
312 declared: body_len,
313 available: MAX_BODY_LEN,
314 });
315 }
316 buf[0] = TAG;
317 buf[1] = body_len as u8;
318 buf[2] = if self.mosaic_entry_point {
320 ENTRY_POINT_MASK
321 } else {
322 0
323 } | ((self.num_horizontal_cells & 0x07) << 4)
324 | 0x08
325 | (self.num_vertical_cells & 0x07);
326 let mut pos = HEADER_LEN + GRID_HEADER_LEN;
327 for cell in &self.logical_cells {
328 if cell.elementary_cell_ids.len() > MAX_ELEM_FIELD {
330 return Err(Error::InvalidDescriptor {
331 tag: TAG,
332 reason: "elementary_cell_field exceeds 255 entries (8-bit length field)",
333 });
334 }
335 buf[pos] = ((cell.logical_cell_id & 0x3F) << 2) | 0x03;
337 buf[pos + 1] = 0xF8 | (cell.presentation_info & 0x07);
339 buf[pos + 2] = cell.elementary_cell_ids.len() as u8;
340 pos += CELL_FIXED_LEN;
341 for &id in &cell.elementary_cell_ids {
342 buf[pos] = 0xC0 | (id & ELEM_CELL_ID_MASK);
344 pos += 1;
345 }
346 buf[pos] = cell.linkage.info_byte();
347 pos += 1;
348 match &cell.linkage {
349 CellLinkage::Undefined | CellLinkage::Reserved { .. } => {}
350 CellLinkage::Bouquet { bouquet_id } => {
351 buf[pos..pos + 2].copy_from_slice(&bouquet_id.to_be_bytes());
352 pos += 2;
353 }
354 CellLinkage::Service {
355 original_network_id,
356 transport_stream_id,
357 service_id,
358 }
359 | CellLinkage::OtherMosaic {
360 original_network_id,
361 transport_stream_id,
362 service_id,
363 } => {
364 buf[pos..pos + 2].copy_from_slice(&original_network_id.to_be_bytes());
365 buf[pos + 2..pos + 4].copy_from_slice(&transport_stream_id.to_be_bytes());
366 buf[pos + 4..pos + 6].copy_from_slice(&service_id.to_be_bytes());
367 pos += 6;
368 }
369 CellLinkage::Event {
370 original_network_id,
371 transport_stream_id,
372 service_id,
373 event_id,
374 } => {
375 buf[pos..pos + 2].copy_from_slice(&original_network_id.to_be_bytes());
376 buf[pos + 2..pos + 4].copy_from_slice(&transport_stream_id.to_be_bytes());
377 buf[pos + 4..pos + 6].copy_from_slice(&service_id.to_be_bytes());
378 buf[pos + 6..pos + 8].copy_from_slice(&event_id.to_be_bytes());
379 pos += 8;
380 }
381 }
382 }
383 Ok(len)
384 }
385}
386
387impl<'a> Descriptor<'a> for MosaicDescriptor {
388 const TAG: u8 = TAG;
389 fn descriptor_length(&self) -> u8 {
390 (self.serialized_len() - HEADER_LEN) as u8
391 }
392}
393
394impl<'a> crate::traits::DescriptorDef<'a> for MosaicDescriptor {
395 const TAG: u8 = TAG;
396 const NAME: &'static str = "MOSAIC";
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn parse_single_cell_undefined_linkage() {
405 let bytes = [
408 TAG,
409 0x07,
410 0x80 | (1 << 4) | 0x08 | 1, (5 << 2) | 0x03, 0xF8 | 1, 0x02, 0xC0 | 3,
415 0xC0 | 7,
416 0x00, ];
418 let d = MosaicDescriptor::parse(&bytes).unwrap();
419 assert!(d.mosaic_entry_point);
420 assert_eq!(d.num_horizontal_cells, 1);
421 assert_eq!(d.num_vertical_cells, 1);
422 assert_eq!(d.logical_cells.len(), 1);
423 let c = &d.logical_cells[0];
424 assert_eq!(c.logical_cell_id, 5);
425 assert_eq!(c.presentation_info, 1);
426 assert_eq!(c.elementary_cell_ids, vec![3, 7]);
427 assert_eq!(c.linkage, CellLinkage::Undefined);
428 }
429
430 #[test]
431 fn parse_service_linkage() {
432 let d = MosaicDescriptor {
433 mosaic_entry_point: false,
434 num_horizontal_cells: 0,
435 num_vertical_cells: 0,
436 logical_cells: vec![MosaicLogicalCell {
437 logical_cell_id: 1,
438 presentation_info: 2,
439 elementary_cell_ids: vec![0],
440 linkage: CellLinkage::Service {
441 original_network_id: 0x1111,
442 transport_stream_id: 0x2222,
443 service_id: 0x3333,
444 },
445 }],
446 };
447 let mut buf = vec![0u8; d.serialized_len()];
448 d.serialize_into(&mut buf).unwrap();
449 let p = MosaicDescriptor::parse(&buf).unwrap();
450 assert_eq!(p, d);
451 assert!(matches!(
452 p.logical_cells[0].linkage,
453 CellLinkage::Service {
454 original_network_id: 0x1111,
455 transport_stream_id: 0x2222,
456 service_id: 0x3333,
457 }
458 ));
459 }
460
461 #[test]
462 fn parse_event_linkage_round_trip() {
463 let d = MosaicDescriptor {
464 mosaic_entry_point: true,
465 num_horizontal_cells: 3,
466 num_vertical_cells: 2,
467 logical_cells: vec![MosaicLogicalCell {
468 logical_cell_id: 10,
469 presentation_info: 3,
470 elementary_cell_ids: vec![1, 2, 3],
471 linkage: CellLinkage::Event {
472 original_network_id: 0xAAAA,
473 transport_stream_id: 0xBBBB,
474 service_id: 0xCCCC,
475 event_id: 0xDDDD,
476 },
477 }],
478 };
479 let mut buf = vec![0u8; d.serialized_len()];
480 d.serialize_into(&mut buf).unwrap();
481 assert_eq!(MosaicDescriptor::parse(&buf).unwrap(), d);
482 }
483
484 #[test]
485 fn parse_reserved_linkage_round_trips() {
486 let bytes = [
487 TAG,
488 0x05,
489 0x08, (1 << 2) | 0x03,
491 0xF8,
492 0x00, 0x42, ];
495 let d = MosaicDescriptor::parse(&bytes).unwrap();
496 assert_eq!(
497 d.logical_cells[0].linkage,
498 CellLinkage::Reserved { value: 0x42 }
499 );
500 let mut buf = vec![0u8; d.serialized_len()];
501 d.serialize_into(&mut buf).unwrap();
502 assert_eq!(MosaicDescriptor::parse(&buf).unwrap(), d);
503 }
504
505 #[test]
506 fn parse_multiple_cells() {
507 let d = MosaicDescriptor {
508 mosaic_entry_point: false,
509 num_horizontal_cells: 1,
510 num_vertical_cells: 1,
511 logical_cells: vec![
512 MosaicLogicalCell {
513 logical_cell_id: 0,
514 presentation_info: 1,
515 elementary_cell_ids: vec![0],
516 linkage: CellLinkage::Bouquet { bouquet_id: 0x1234 },
517 },
518 MosaicLogicalCell {
519 logical_cell_id: 1,
520 presentation_info: 1,
521 elementary_cell_ids: vec![1],
522 linkage: CellLinkage::Undefined,
523 },
524 ],
525 };
526 let mut buf = vec![0u8; d.serialized_len()];
527 d.serialize_into(&mut buf).unwrap();
528 let p = MosaicDescriptor::parse(&buf).unwrap();
529 assert_eq!(p.logical_cells.len(), 2);
530 assert_eq!(p, d);
531 }
532
533 #[test]
534 fn parse_rejects_wrong_tag() {
535 assert!(matches!(
536 MosaicDescriptor::parse(&[0x52, 1, 0x08]).unwrap_err(),
537 Error::InvalidDescriptor { tag: 0x52, .. }
538 ));
539 }
540
541 #[test]
542 fn parse_rejects_short_buffer() {
543 let bytes = [TAG, 5, 0x08, 0x00];
545 assert!(matches!(
546 MosaicDescriptor::parse(&bytes).unwrap_err(),
547 Error::BufferTooShort { .. }
548 ));
549 }
550
551 #[test]
552 fn parse_rejects_elem_field_overrun() {
553 let bytes = [TAG, 4, 0x08, (1 << 2) | 0x03, 0xF8, 0x0A];
555 assert!(matches!(
556 MosaicDescriptor::parse(&bytes).unwrap_err(),
557 Error::InvalidDescriptor { tag: TAG, .. }
558 ));
559 }
560
561 #[test]
562 fn parse_rejects_missing_linkage_byte() {
563 let bytes = [TAG, 4, 0x08, (1 << 2) | 0x03, 0xF8, 0x00];
565 assert!(matches!(
568 MosaicDescriptor::parse(&bytes).unwrap_err(),
569 Error::InvalidDescriptor { tag: TAG, .. }
570 ));
571 }
572
573 #[test]
574 fn parse_rejects_zero_length() {
575 assert!(matches!(
576 MosaicDescriptor::parse(&[TAG, 0]).unwrap_err(),
577 Error::InvalidDescriptor { tag: TAG, .. }
578 ));
579 }
580
581 #[test]
582 fn serialize_emits_reserved_ones() {
583 let d = MosaicDescriptor {
584 mosaic_entry_point: false,
585 num_horizontal_cells: 0,
586 num_vertical_cells: 0,
587 logical_cells: vec![],
588 };
589 let mut buf = vec![0u8; d.serialized_len()];
590 d.serialize_into(&mut buf).unwrap();
591 assert_eq!(buf[2] & 0x08, 0x08);
593 }
594
595 #[test]
596 fn serialize_round_trip_empty_grid() {
597 let d = MosaicDescriptor {
598 mosaic_entry_point: true,
599 num_horizontal_cells: 7,
600 num_vertical_cells: 7,
601 logical_cells: vec![],
602 };
603 let mut buf = vec![0u8; d.serialized_len()];
604 d.serialize_into(&mut buf).unwrap();
605 assert_eq!(MosaicDescriptor::parse(&buf).unwrap(), d);
606 }
607
608 #[cfg(feature = "serde")]
609 #[test]
610 fn serde_round_trip() {
611 let d = MosaicDescriptor {
612 mosaic_entry_point: true,
613 num_horizontal_cells: 1,
614 num_vertical_cells: 1,
615 logical_cells: vec![MosaicLogicalCell {
616 logical_cell_id: 5,
617 presentation_info: 1,
618 elementary_cell_ids: vec![3, 7],
619 linkage: CellLinkage::Bouquet { bouquet_id: 0x1234 },
620 }],
621 };
622 let json = serde_json::to_string(&d).unwrap();
623 let back: MosaicDescriptor = serde_json::from_str(&json).unwrap();
624 assert_eq!(d, back);
625 }
626}