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 super::descriptor_body;
22use crate::error::{Error, Result};
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))]
41#[non_exhaustive]
42pub enum CellLinkage {
43    /// 0x00 — undefined (no payload).
44    Undefined,
45    /// 0x01 — bouquet related.
46    Bouquet {
47        /// Linked bouquet_id.
48        bouquet_id: u16,
49    },
50    /// 0x02 — service related.
51    Service {
52        /// Linked original_network_id.
53        original_network_id: u16,
54        /// Linked transport_stream_id.
55        transport_stream_id: u16,
56        /// Linked service_id.
57        service_id: u16,
58    },
59    /// 0x03 — other mosaic related.
60    OtherMosaic {
61        /// Linked original_network_id.
62        original_network_id: u16,
63        /// Linked transport_stream_id.
64        transport_stream_id: u16,
65        /// Linked service_id.
66        service_id: u16,
67    },
68    /// 0x04 — event related.
69    Event {
70        /// Linked original_network_id.
71        original_network_id: u16,
72        /// Linked transport_stream_id.
73        transport_stream_id: u16,
74        /// Linked service_id.
75        service_id: u16,
76        /// Linked event_id.
77        event_id: u16,
78    },
79    /// 0x05..=0xFF — reserved; the raw byte is preserved so unknown linkage
80    /// values round-trip (they carry no further payload per Table 75).
81    Reserved {
82        /// The raw cell_linkage_info byte.
83        value: u8,
84    },
85}
86
87impl CellLinkage {
88    /// The cell_linkage_info byte this variant serializes to.
89    fn info_byte(&self) -> u8 {
90        match self {
91            CellLinkage::Undefined => 0x00,
92            CellLinkage::Bouquet { .. } => 0x01,
93            CellLinkage::Service { .. } => 0x02,
94            CellLinkage::OtherMosaic { .. } => 0x03,
95            CellLinkage::Event { .. } => 0x04,
96            CellLinkage::Reserved { value } => *value,
97        }
98    }
99
100    /// Length of the conditional payload (excluding the info byte).
101    fn payload_len(&self) -> usize {
102        match self {
103            CellLinkage::Undefined | CellLinkage::Reserved { .. } => 0,
104            CellLinkage::Bouquet { .. } => 2,
105            CellLinkage::Service { .. } | CellLinkage::OtherMosaic { .. } => 6,
106            CellLinkage::Event { .. } => 8,
107        }
108    }
109}
110
111/// One logical cell within the mosaic.
112#[derive(Debug, Clone, PartialEq, Eq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114pub struct MosaicLogicalCell {
115    /// 6-bit logical_cell_id.
116    pub logical_cell_id: u8,
117    /// 3-bit logical_cell_presentation_info (Table 74): 1 = video,
118    /// 2 = still picture, 3 = graphics/text.
119    pub presentation_info: u8,
120    /// elementary_cell_id values (each 6 bits) composing this logical cell.
121    pub elementary_cell_ids: Vec<u8>,
122    /// Linkage of this logical cell to a bouquet / service / event.
123    pub linkage: CellLinkage,
124}
125
126/// Mosaic Descriptor.
127#[derive(Debug, Clone, PartialEq, Eq)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize))]
129pub struct MosaicDescriptor {
130    /// mosaic_entry_point: set when this mosaic is the top of a hierarchy.
131    pub mosaic_entry_point: bool,
132    /// 3-bit number_of_horizontal_elementary_cells (Table 72; 0 = one cell).
133    pub num_horizontal_cells: u8,
134    /// 3-bit number_of_vertical_elementary_cells (Table 73; 0 = one cell).
135    pub num_vertical_cells: u8,
136    /// Logical cells in wire order.
137    pub logical_cells: Vec<MosaicLogicalCell>,
138}
139
140fn read_u16(b: &[u8], at: usize) -> u16 {
141    u16::from_be_bytes([b[at], b[at + 1]])
142}
143
144impl<'a> Parse<'a> for MosaicDescriptor {
145    type Error = crate::error::Error;
146    fn parse(bytes: &'a [u8]) -> Result<Self> {
147        let body = descriptor_body(
148            bytes,
149            TAG,
150            "MosaicDescriptor",
151            "unexpected tag for mosaic_descriptor",
152        )?;
153        if body.len() < GRID_HEADER_LEN {
154            return Err(Error::InvalidDescriptor {
155                tag: TAG,
156                reason: "mosaic_descriptor missing grid header byte",
157            });
158        }
159        // grid header: reserved bit (bit 3) ignored on parse (§5.1).
160        let mosaic_entry_point = body[0] & ENTRY_POINT_MASK != 0;
161        let num_horizontal_cells = (body[0] >> 4) & 0x07;
162        let num_vertical_cells = body[0] & 0x07;
163
164        let mut logical_cells = Vec::new();
165        let mut pos = GRID_HEADER_LEN;
166        while pos < body.len() {
167            if pos + CELL_FIXED_LEN > body.len() {
168                return Err(Error::InvalidDescriptor {
169                    tag: TAG,
170                    reason: "truncated mosaic logical cell header",
171                });
172            }
173            // logical_cell_id(6) | reserved(7) | presentation_info(3)
174            let logical_cell_id = (body[pos] >> 2) & 0x3F;
175            let presentation_info = body[pos + 1] & 0x07;
176            let elem_field_len = body[pos + 2] as usize;
177            pos += CELL_FIXED_LEN;
178            if pos + elem_field_len > body.len() {
179                return Err(Error::InvalidDescriptor {
180                    tag: TAG,
181                    reason: "elementary_cell_field_length exceeds descriptor body",
182                });
183            }
184            let mut elementary_cell_ids = Vec::with_capacity(elem_field_len);
185            for &b in &body[pos..pos + elem_field_len] {
186                // reserved(2) ignored on parse; low 6 bits are the id.
187                elementary_cell_ids.push(b & ELEM_CELL_ID_MASK);
188            }
189            pos += elem_field_len;
190            if pos >= body.len() {
191                return Err(Error::InvalidDescriptor {
192                    tag: TAG,
193                    reason: "missing cell_linkage_info byte",
194                });
195            }
196            let info = body[pos];
197            pos += 1;
198            let linkage = match info {
199                0x00 => CellLinkage::Undefined,
200                0x01 => {
201                    if pos + 2 > body.len() {
202                        return Err(Error::InvalidDescriptor {
203                            tag: TAG,
204                            reason: "truncated bouquet linkage payload",
205                        });
206                    }
207                    let l = CellLinkage::Bouquet {
208                        bouquet_id: read_u16(body, pos),
209                    };
210                    pos += 2;
211                    l
212                }
213                0x02 | 0x03 => {
214                    if pos + 6 > body.len() {
215                        return Err(Error::InvalidDescriptor {
216                            tag: TAG,
217                            reason: "truncated service/mosaic linkage payload",
218                        });
219                    }
220                    let original_network_id = read_u16(body, pos);
221                    let transport_stream_id = read_u16(body, pos + 2);
222                    let service_id = read_u16(body, pos + 4);
223                    pos += 6;
224                    if info == 0x02 {
225                        CellLinkage::Service {
226                            original_network_id,
227                            transport_stream_id,
228                            service_id,
229                        }
230                    } else {
231                        CellLinkage::OtherMosaic {
232                            original_network_id,
233                            transport_stream_id,
234                            service_id,
235                        }
236                    }
237                }
238                0x04 => {
239                    if pos + 8 > body.len() {
240                        return Err(Error::InvalidDescriptor {
241                            tag: TAG,
242                            reason: "truncated event linkage payload",
243                        });
244                    }
245                    let l = CellLinkage::Event {
246                        original_network_id: read_u16(body, pos),
247                        transport_stream_id: read_u16(body, pos + 2),
248                        service_id: read_u16(body, pos + 4),
249                        event_id: read_u16(body, pos + 6),
250                    };
251                    pos += 8;
252                    l
253                }
254                // 0x05..=0xFF reserved: no defined payload (Table 75).
255                other => CellLinkage::Reserved { value: other },
256            };
257            logical_cells.push(MosaicLogicalCell {
258                logical_cell_id,
259                presentation_info,
260                elementary_cell_ids,
261                linkage,
262            });
263        }
264        Ok(Self {
265            mosaic_entry_point,
266            num_horizontal_cells,
267            num_vertical_cells,
268            logical_cells,
269        })
270    }
271}
272
273impl Serialize for MosaicDescriptor {
274    type Error = crate::error::Error;
275    fn serialized_len(&self) -> usize {
276        let cells: usize = self
277            .logical_cells
278            .iter()
279            .map(|c| CELL_FIXED_LEN + c.elementary_cell_ids.len() + 1 + c.linkage.payload_len())
280            .sum();
281        HEADER_LEN + GRID_HEADER_LEN + cells
282    }
283
284    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
285        let len = self.serialized_len();
286        if buf.len() < len {
287            return Err(Error::OutputBufferTooSmall {
288                need: len,
289                have: buf.len(),
290            });
291        }
292        let body_len = len - HEADER_LEN;
293        // 8-bit descriptor_length field: error rather than silently truncate.
294        if body_len > MAX_BODY_LEN {
295            return Err(Error::InvalidDescriptor {
296                tag: TAG,
297                reason: "mosaic_descriptor body exceeds 255 bytes",
298            });
299        }
300        buf[0] = TAG;
301        buf[1] = body_len as u8;
302        // grid header: reserved bit (bit 3) emitted as 1 (§5.1).
303        buf[2] = if self.mosaic_entry_point {
304            ENTRY_POINT_MASK
305        } else {
306            0
307        } | ((self.num_horizontal_cells & 0x07) << 4)
308            | 0x08
309            | (self.num_vertical_cells & 0x07);
310        let mut pos = HEADER_LEN + GRID_HEADER_LEN;
311        for cell in &self.logical_cells {
312            // 8-bit elementary_cell_field_length: error on over-range.
313            if cell.elementary_cell_ids.len() > MAX_ELEM_FIELD {
314                return Err(Error::InvalidDescriptor {
315                    tag: TAG,
316                    reason: "elementary_cell_field exceeds 255 entries (8-bit length field)",
317                });
318            }
319            // byte0: logical_cell_id(6) | top 2 reserved bits emitted 1s.
320            buf[pos] = ((cell.logical_cell_id & 0x3F) << 2) | 0x03;
321            // byte1: 5 reserved bits emitted 1s | presentation_info(3).
322            buf[pos + 1] = 0xF8 | (cell.presentation_info & 0x07);
323            buf[pos + 2] = cell.elementary_cell_ids.len() as u8;
324            pos += CELL_FIXED_LEN;
325            for &id in &cell.elementary_cell_ids {
326                // reserved(2) emitted 1s | elementary_cell_id(6).
327                buf[pos] = 0xC0 | (id & ELEM_CELL_ID_MASK);
328                pos += 1;
329            }
330            buf[pos] = cell.linkage.info_byte();
331            pos += 1;
332            match &cell.linkage {
333                CellLinkage::Undefined | CellLinkage::Reserved { .. } => {}
334                CellLinkage::Bouquet { bouquet_id } => {
335                    buf[pos..pos + 2].copy_from_slice(&bouquet_id.to_be_bytes());
336                    pos += 2;
337                }
338                CellLinkage::Service {
339                    original_network_id,
340                    transport_stream_id,
341                    service_id,
342                }
343                | CellLinkage::OtherMosaic {
344                    original_network_id,
345                    transport_stream_id,
346                    service_id,
347                } => {
348                    buf[pos..pos + 2].copy_from_slice(&original_network_id.to_be_bytes());
349                    buf[pos + 2..pos + 4].copy_from_slice(&transport_stream_id.to_be_bytes());
350                    buf[pos + 4..pos + 6].copy_from_slice(&service_id.to_be_bytes());
351                    pos += 6;
352                }
353                CellLinkage::Event {
354                    original_network_id,
355                    transport_stream_id,
356                    service_id,
357                    event_id,
358                } => {
359                    buf[pos..pos + 2].copy_from_slice(&original_network_id.to_be_bytes());
360                    buf[pos + 2..pos + 4].copy_from_slice(&transport_stream_id.to_be_bytes());
361                    buf[pos + 4..pos + 6].copy_from_slice(&service_id.to_be_bytes());
362                    buf[pos + 6..pos + 8].copy_from_slice(&event_id.to_be_bytes());
363                    pos += 8;
364                }
365            }
366        }
367        Ok(len)
368    }
369}
370impl<'a> crate::traits::DescriptorDef<'a> for MosaicDescriptor {
371    const TAG: u8 = TAG;
372    const NAME: &'static str = "MOSAIC";
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378
379    #[test]
380    fn parse_single_cell_undefined_linkage() {
381        // grid header 0x80|0x08 (entry point + reserved), 2x2 grid coded as 1,1
382        // logical cell: id=5, presentation=1, 2 elem cells [3,7], linkage 0x00
383        let bytes = [
384            TAG,
385            0x07,
386            0x80 | (1 << 4) | 0x08 | 1, // header
387            (5 << 2) | 0x03,            // logical_cell_id=5
388            0xF8 | 1,                   // presentation_info=1
389            0x02,                       // elementary_cell_field_length=2
390            0xC0 | 3,
391            0xC0 | 7,
392            0x00, // cell_linkage_info = undefined
393        ];
394        let d = MosaicDescriptor::parse(&bytes).unwrap();
395        assert!(d.mosaic_entry_point);
396        assert_eq!(d.num_horizontal_cells, 1);
397        assert_eq!(d.num_vertical_cells, 1);
398        assert_eq!(d.logical_cells.len(), 1);
399        let c = &d.logical_cells[0];
400        assert_eq!(c.logical_cell_id, 5);
401        assert_eq!(c.presentation_info, 1);
402        assert_eq!(c.elementary_cell_ids, vec![3, 7]);
403        assert_eq!(c.linkage, CellLinkage::Undefined);
404    }
405
406    #[test]
407    fn parse_service_linkage() {
408        let d = MosaicDescriptor {
409            mosaic_entry_point: false,
410            num_horizontal_cells: 0,
411            num_vertical_cells: 0,
412            logical_cells: vec![MosaicLogicalCell {
413                logical_cell_id: 1,
414                presentation_info: 2,
415                elementary_cell_ids: vec![0],
416                linkage: CellLinkage::Service {
417                    original_network_id: 0x1111,
418                    transport_stream_id: 0x2222,
419                    service_id: 0x3333,
420                },
421            }],
422        };
423        let mut buf = vec![0u8; d.serialized_len()];
424        d.serialize_into(&mut buf).unwrap();
425        let p = MosaicDescriptor::parse(&buf).unwrap();
426        assert_eq!(p, d);
427        assert!(matches!(
428            p.logical_cells[0].linkage,
429            CellLinkage::Service {
430                original_network_id: 0x1111,
431                transport_stream_id: 0x2222,
432                service_id: 0x3333,
433            }
434        ));
435    }
436
437    #[test]
438    fn parse_event_linkage_round_trip() {
439        let d = MosaicDescriptor {
440            mosaic_entry_point: true,
441            num_horizontal_cells: 3,
442            num_vertical_cells: 2,
443            logical_cells: vec![MosaicLogicalCell {
444                logical_cell_id: 10,
445                presentation_info: 3,
446                elementary_cell_ids: vec![1, 2, 3],
447                linkage: CellLinkage::Event {
448                    original_network_id: 0xAAAA,
449                    transport_stream_id: 0xBBBB,
450                    service_id: 0xCCCC,
451                    event_id: 0xDDDD,
452                },
453            }],
454        };
455        let mut buf = vec![0u8; d.serialized_len()];
456        d.serialize_into(&mut buf).unwrap();
457        assert_eq!(MosaicDescriptor::parse(&buf).unwrap(), d);
458    }
459
460    #[test]
461    fn parse_reserved_linkage_round_trips() {
462        let bytes = [
463            TAG,
464            0x05,
465            0x08, // grid header, no entry point, grid 0,0
466            (1 << 2) | 0x03,
467            0xF8,
468            0x00, // no elementary cells
469            0x42, // reserved linkage value
470        ];
471        let d = MosaicDescriptor::parse(&bytes).unwrap();
472        assert_eq!(
473            d.logical_cells[0].linkage,
474            CellLinkage::Reserved { value: 0x42 }
475        );
476        let mut buf = vec![0u8; d.serialized_len()];
477        d.serialize_into(&mut buf).unwrap();
478        assert_eq!(MosaicDescriptor::parse(&buf).unwrap(), d);
479    }
480
481    #[test]
482    fn parse_multiple_cells() {
483        let d = MosaicDescriptor {
484            mosaic_entry_point: false,
485            num_horizontal_cells: 1,
486            num_vertical_cells: 1,
487            logical_cells: vec![
488                MosaicLogicalCell {
489                    logical_cell_id: 0,
490                    presentation_info: 1,
491                    elementary_cell_ids: vec![0],
492                    linkage: CellLinkage::Bouquet { bouquet_id: 0x1234 },
493                },
494                MosaicLogicalCell {
495                    logical_cell_id: 1,
496                    presentation_info: 1,
497                    elementary_cell_ids: vec![1],
498                    linkage: CellLinkage::Undefined,
499                },
500            ],
501        };
502        let mut buf = vec![0u8; d.serialized_len()];
503        d.serialize_into(&mut buf).unwrap();
504        let p = MosaicDescriptor::parse(&buf).unwrap();
505        assert_eq!(p.logical_cells.len(), 2);
506        assert_eq!(p, d);
507    }
508
509    #[test]
510    fn parse_rejects_wrong_tag() {
511        assert!(matches!(
512            MosaicDescriptor::parse(&[0x52, 1, 0x08]).unwrap_err(),
513            Error::InvalidDescriptor { tag: 0x52, .. }
514        ));
515    }
516
517    #[test]
518    fn parse_rejects_short_buffer() {
519        // declares 5 body bytes, only 2 present
520        let bytes = [TAG, 5, 0x08, 0x00];
521        assert!(matches!(
522            MosaicDescriptor::parse(&bytes).unwrap_err(),
523            Error::BufferTooShort { .. }
524        ));
525    }
526
527    #[test]
528    fn parse_rejects_elem_field_overrun() {
529        // elementary_cell_field_length=10 but body has no room
530        let bytes = [TAG, 4, 0x08, (1 << 2) | 0x03, 0xF8, 0x0A];
531        assert!(matches!(
532            MosaicDescriptor::parse(&bytes).unwrap_err(),
533            Error::InvalidDescriptor { tag: TAG, .. }
534        ));
535    }
536
537    #[test]
538    fn parse_rejects_missing_linkage_byte() {
539        // cell header present, elem_field_len=0, but no cell_linkage_info byte
540        let bytes = [TAG, 4, 0x08, (1 << 2) | 0x03, 0xF8, 0x00];
541        // length=4 means body is [0x08, hdr0, hdr1, 0x00]; after the 3 fixed
542        // cell bytes pos == body.len() so the linkage byte is missing.
543        assert!(matches!(
544            MosaicDescriptor::parse(&bytes).unwrap_err(),
545            Error::InvalidDescriptor { tag: TAG, .. }
546        ));
547    }
548
549    #[test]
550    fn parse_rejects_zero_length() {
551        assert!(matches!(
552            MosaicDescriptor::parse(&[TAG, 0]).unwrap_err(),
553            Error::InvalidDescriptor { tag: TAG, .. }
554        ));
555    }
556
557    #[test]
558    fn serialize_emits_reserved_ones() {
559        let d = MosaicDescriptor {
560            mosaic_entry_point: false,
561            num_horizontal_cells: 0,
562            num_vertical_cells: 0,
563            logical_cells: vec![],
564        };
565        let mut buf = vec![0u8; d.serialized_len()];
566        d.serialize_into(&mut buf).unwrap();
567        // grid header reserved bit (bit 3) set
568        assert_eq!(buf[2] & 0x08, 0x08);
569    }
570
571    #[test]
572    fn serialize_round_trip_empty_grid() {
573        let d = MosaicDescriptor {
574            mosaic_entry_point: true,
575            num_horizontal_cells: 7,
576            num_vertical_cells: 7,
577            logical_cells: vec![],
578        };
579        let mut buf = vec![0u8; d.serialized_len()];
580        d.serialize_into(&mut buf).unwrap();
581        assert_eq!(MosaicDescriptor::parse(&buf).unwrap(), d);
582    }
583
584    #[cfg(feature = "serde")]
585    #[test]
586    fn serde_round_trip() {
587        let d = MosaicDescriptor {
588            mosaic_entry_point: true,
589            num_horizontal_cells: 1,
590            num_vertical_cells: 1,
591            logical_cells: vec![MosaicLogicalCell {
592                logical_cell_id: 5,
593                presentation_info: 1,
594                elementary_cell_ids: vec![3, 7],
595                linkage: CellLinkage::Bouquet { bouquet_id: 0x1234 },
596            }],
597        };
598        let json = serde_json::to_string(&d).unwrap();
599        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
600        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
601    }
602}