Skip to main content

dvb_si/descriptors/
mosaic.rs

1//! Mosaic Descriptor — ETSI EN 300 468 §6.2.21 (tag 0x51).
2//!
3//! Table 71 (PDF p. 91, verified against the vendored PDF). Describes a mosaic
4//! service: a grid of elementary cells, each logical cell grouping one or more
5//! elementary cells and optionally linking to a bouquet / service / event.
6//!
7//! Wire layout:
8//!   header byte: mosaic_entry_point(1) | num_horizontal_cells(3)
9//!                | reserved(1) | num_vertical_cells(3)
10//!   for each logical cell:
11//!     2 bytes: logical_cell_id(6) | reserved(7) | logical_cell_presentation_info(3)
12//!     elementary_cell_field_length(8)
13//!     elementary_cell_field_length × { reserved(2) | elementary_cell_id(6) }
14//!     cell_linkage_info(8)
15//!     conditional linkage payload (see CellLinkage)
16//!
17//! The first loop level (logical cells) is typed. Within each cell, the
18//! elementary_cell_id list is exposed as `Vec<u8>` of 6-bit ids and the
19//! linkage payload as the typed `CellLinkage` enum.
20
21use crate::error::{Error, Result};
22use crate::traits::Descriptor;
23use dvb_common::{Parse, Serialize};
24
25/// Descriptor tag for mosaic_descriptor.
26pub const TAG: u8 = 0x51;
27const HEADER_LEN: usize = 2;
28const GRID_HEADER_LEN: usize = 1;
29const CELL_FIXED_LEN: usize = 3; // 2 id/presentation bytes + elementary_cell_field_length
30/// Maximum body length expressible in the 8-bit `descriptor_length` field.
31const MAX_BODY_LEN: usize = u8::MAX as usize;
32/// Maximum elementary_cell_field_length (8-bit field).
33const MAX_ELEM_FIELD: usize = u8::MAX as usize;
34
35const ENTRY_POINT_MASK: u8 = 0x80; // header bit 7
36const ELEM_CELL_ID_MASK: u8 = 0x3F; // low 6 bits
37
38/// Conditional linkage payload selected by `cell_linkage_info` (Table 75).
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize))]
41pub enum CellLinkage {
42    /// 0x00 — undefined (no payload).
43    Undefined,
44    /// 0x01 — bouquet related.
45    Bouquet {
46        /// Linked bouquet_id.
47        bouquet_id: u16,
48    },
49    /// 0x02 — service related.
50    Service {
51        /// Linked original_network_id.
52        original_network_id: u16,
53        /// Linked transport_stream_id.
54        transport_stream_id: u16,
55        /// Linked service_id.
56        service_id: u16,
57    },
58    /// 0x03 — other mosaic related.
59    OtherMosaic {
60        /// Linked original_network_id.
61        original_network_id: u16,
62        /// Linked transport_stream_id.
63        transport_stream_id: u16,
64        /// Linked service_id.
65        service_id: u16,
66    },
67    /// 0x04 — event related.
68    Event {
69        /// Linked original_network_id.
70        original_network_id: u16,
71        /// Linked transport_stream_id.
72        transport_stream_id: u16,
73        /// Linked service_id.
74        service_id: u16,
75        /// Linked event_id.
76        event_id: u16,
77    },
78    /// 0x05..=0xFF — reserved; the raw byte is preserved so unknown linkage
79    /// values round-trip (they carry no further payload per Table 75).
80    Reserved {
81        /// The raw cell_linkage_info byte.
82        value: u8,
83    },
84}
85
86impl CellLinkage {
87    /// The cell_linkage_info byte this variant serializes to.
88    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    /// Length of the conditional payload (excluding the info byte).
100    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/// One logical cell within the mosaic.
111#[derive(Debug, Clone, PartialEq, Eq)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize))]
113pub struct MosaicLogicalCell {
114    /// 6-bit logical_cell_id.
115    pub logical_cell_id: u8,
116    /// 3-bit logical_cell_presentation_info (Table 74): 1 = video,
117    /// 2 = still picture, 3 = graphics/text.
118    pub presentation_info: u8,
119    /// elementary_cell_id values (each 6 bits) composing this logical cell.
120    pub elementary_cell_ids: Vec<u8>,
121    /// Linkage of this logical cell to a bouquet / service / event.
122    pub linkage: CellLinkage,
123}
124
125/// Mosaic Descriptor.
126#[derive(Debug, Clone, PartialEq, Eq)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize))]
128pub struct MosaicDescriptor {
129    /// mosaic_entry_point: set when this mosaic is the top of a hierarchy.
130    pub mosaic_entry_point: bool,
131    /// 3-bit number_of_horizontal_elementary_cells (Table 72; 0 = one cell).
132    pub num_horizontal_cells: u8,
133    /// 3-bit number_of_vertical_elementary_cells (Table 73; 0 = one cell).
134    pub num_vertical_cells: u8,
135    /// Logical cells in wire order.
136    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        // grid header: reserved bit (bit 3) ignored on parse (§5.1).
176        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            // logical_cell_id(6) | reserved(7) | presentation_info(3)
190            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                // reserved(2) ignored on parse; low 6 bits are the id.
203                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                // 0x05..=0xFF reserved: no defined payload (Table 75).
271                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        // 8-bit descriptor_length field: error rather than silently truncate.
310        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        // grid header: reserved bit (bit 3) emitted as 1 (§5.1).
319        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            // 8-bit elementary_cell_field_length: error on over-range.
329            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            // byte0: logical_cell_id(6) | top 2 reserved bits emitted 1s.
336            buf[pos] = ((cell.logical_cell_id & 0x3F) << 2) | 0x03;
337            // byte1: 5 reserved bits emitted 1s | presentation_info(3).
338            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                // reserved(2) emitted 1s | elementary_cell_id(6).
343                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        // grid header 0x80|0x08 (entry point + reserved), 2x2 grid coded as 1,1
406        // logical cell: id=5, presentation=1, 2 elem cells [3,7], linkage 0x00
407        let bytes = [
408            TAG,
409            0x07,
410            0x80 | (1 << 4) | 0x08 | 1, // header
411            (5 << 2) | 0x03,            // logical_cell_id=5
412            0xF8 | 1,                   // presentation_info=1
413            0x02,                       // elementary_cell_field_length=2
414            0xC0 | 3,
415            0xC0 | 7,
416            0x00, // cell_linkage_info = undefined
417        ];
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, // grid header, no entry point, grid 0,0
490            (1 << 2) | 0x03,
491            0xF8,
492            0x00, // no elementary cells
493            0x42, // reserved linkage value
494        ];
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        // declares 5 body bytes, only 2 present
544        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        // elementary_cell_field_length=10 but body has no room
554        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        // cell header present, elem_field_len=0, but no cell_linkage_info byte
564        let bytes = [TAG, 4, 0x08, (1 << 2) | 0x03, 0xF8, 0x00];
565        // length=4 means body is [0x08, hdr0, hdr1, 0x00]; after the 3 fixed
566        // cell bytes pos == body.len() so the linkage byte is missing.
567        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        // grid header reserved bit (bit 3) set
592        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        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
624        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
625    }
626}