Skip to main content

dvb_si/
compatibility.rs

1//! Compatibility Descriptor — ETSI TS 102 006 §9.4.2.2 Table 15 / ISO/IEC 13818-6.
2//!
3//! The `compatibilityDescriptor()` structure describes the hardware/software
4//! compatibility requirements of a DSM-CC download group. It is carried in
5//! DSI/DII messages (ISO/IEC 13818-6 §7.3) and UNT platform entries
6//! (TS 102 006 Table 11).
7//!
8//! Wire layout (Table 15):
9//!
10//! ```text
11//! compatibilityDescriptor() {
12//!   compatibilityDescriptorLength  [15:0]  16 bits  — byte count of everything after this field
13//!   descriptorCount                [15:0]  16 bits
14//!   for (i < descriptorCount) {
15//!     descriptorType                [7:0]   8 bits   — Table 16
16//!     descriptorLength              [7:0]   8 bits   — byte count of the rest of THIS descriptor
17//!     specifierType                 [7:0]   8 bits   — 0x01 = IEEE OUI
18//!     specifierData                 [23:0]  24 bits  — 3-byte IEEE OUI
19//!     model                         [15:0]  16 bits
20//!     version                       [15:0]  16 bits
21//!     subDescriptorCount            [7:0]   8 bits
22//!     for (j < subDescriptorCount) {
23//!       subDescriptorType           [7:0]   8 bits
24//!       subDescriptorLength         [7:0]   8 bits
25//!       subDescriptorData           (subDescriptorLength bytes)
26//!     }
27//!   }
28//! }
29//! ```
30//!
31//! An empty descriptor (`descriptorCount == 0`) is encoded as
32//! `compatibilityDescriptorLength = 0` (2 bytes on wire: `0x00 0x00`),
33//! matching the DSM-CC convention used by existing DVB broadcasts.
34
35use crate::error::{Error, Result};
36use dvb_common::{Parse, Serialize};
37
38pub(crate) const COMPAT_DESC_LEN_FIELD: usize = 2;
39const DESC_COUNT_FIELD: usize = 2;
40const DESC_HEADER_LEN: usize = 2;
41const DESC_FIXED_LEN: usize = 9;
42const SUB_DESC_HEADER_LEN: usize = 2;
43
44/// Compatibility Descriptor — ETSI TS 102 006 §9.4.2.2 Table 15 / ISO/IEC
45/// 13818-6 `compatibilityDescriptor()`.
46///
47/// The wire form starts with a 16-bit `compatibilityDescriptorLength` field;
48/// [`Parse`] consumes the **full** block including this length prefix, and
49/// [`Serialize`] emits it. An empty descriptor (no entries) serialises as the
50/// 2-byte `0x00 0x00` length-only form, matching the DSM-CC convention for
51/// "no compatibility information".
52#[derive(Debug, Clone, PartialEq, Eq)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize))]
54pub struct CompatibilityDescriptor<'a> {
55    /// Descriptor entries (may be empty).
56    pub descriptors: Vec<CompatibilityDescriptorEntry<'a>>,
57}
58
59/// A single compatibility descriptor entry — TS 102 006 Table 15 / ISO/IEC
60/// 13818-6.
61///
62/// `descriptorType` values are defined in TS 102 006 Table 16
63/// (0x00 = pad, 0x01 = system hardware, 0x02 = system software,
64/// 0x03–0x3F ISO reserved, 0x40–0x7F reserved, 0x80–0xFF private).
65#[derive(Debug, Clone, PartialEq, Eq)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize))]
67pub struct CompatibilityDescriptorEntry<'a> {
68    /// `descriptorType` — TS 102 006 Table 16 / ISO/IEC 13818-6.
69    pub descriptor_type: u8,
70    /// `specifierType` — `0x01` = IEEE OUI (Table 15 remark).
71    pub specifier_type: u8,
72    /// `specifierData` — 3-byte IEEE OUI when `specifierType == 0x01`.
73    pub specifier_data: [u8; 3],
74    /// `model` — zero if transmitted in a manufacturer private location.
75    pub model: u16,
76    /// `version` — zero if transmitted in a manufacturer private location.
77    pub version: u16,
78    /// Sub-descriptor entries.
79    pub sub_descriptors: Vec<SubDescriptor<'a>>,
80}
81
82/// A sub-descriptor within a compatibility descriptor entry — ISO/IEC 13818-6.
83#[derive(Debug, Clone, PartialEq, Eq)]
84#[cfg_attr(feature = "serde", derive(serde::Serialize))]
85pub struct SubDescriptor<'a> {
86    /// `subDescriptorType`.
87    pub sub_descriptor_type: u8,
88    /// `subDescriptorData`.
89    #[cfg_attr(feature = "serde", serde(borrow))]
90    pub data: &'a [u8],
91}
92
93fn entry_serialized_len(entry: &CompatibilityDescriptorEntry) -> usize {
94    DESC_HEADER_LEN
95        + DESC_FIXED_LEN
96        + entry
97            .sub_descriptors
98            .iter()
99            .map(|sd| SUB_DESC_HEADER_LEN + sd.data.len())
100            .sum::<usize>()
101}
102
103impl<'a> Parse<'a> for CompatibilityDescriptor<'a> {
104    type Error = Error;
105
106    fn parse(bytes: &'a [u8]) -> Result<Self> {
107        if bytes.len() < COMPAT_DESC_LEN_FIELD {
108            return Err(Error::BufferTooShort {
109                need: COMPAT_DESC_LEN_FIELD,
110                have: bytes.len(),
111                what: "CompatibilityDescriptor length field",
112            });
113        }
114        let compat_desc_len = u16::from_be_bytes([bytes[0], bytes[1]]) as usize;
115        let body_end = COMPAT_DESC_LEN_FIELD + compat_desc_len;
116        if body_end > bytes.len() {
117            return Err(Error::SectionLengthOverflow {
118                declared: compat_desc_len,
119                available: bytes.len() - COMPAT_DESC_LEN_FIELD,
120            });
121        }
122        if compat_desc_len == 0 {
123            return Ok(CompatibilityDescriptor {
124                descriptors: Vec::new(),
125            });
126        }
127        if compat_desc_len < DESC_COUNT_FIELD {
128            return Err(Error::BufferTooShort {
129                need: COMPAT_DESC_LEN_FIELD + DESC_COUNT_FIELD,
130                have: bytes.len(),
131                what: "CompatibilityDescriptor descriptorCount",
132            });
133        }
134        let body = &bytes[COMPAT_DESC_LEN_FIELD..body_end];
135        let descriptor_count = u16::from_be_bytes([body[0], body[1]]) as usize;
136        let mut pos = DESC_COUNT_FIELD;
137        let max_entries = (body.len() - DESC_COUNT_FIELD) / (DESC_HEADER_LEN + DESC_FIXED_LEN);
138        let mut descriptors = Vec::with_capacity(descriptor_count.min(max_entries));
139        for _ in 0..descriptor_count {
140            if pos + DESC_HEADER_LEN > body.len() {
141                return Err(Error::BufferTooShort {
142                    need: COMPAT_DESC_LEN_FIELD + pos + DESC_HEADER_LEN,
143                    have: COMPAT_DESC_LEN_FIELD + body.len(),
144                    what: "CompatibilityDescriptor entry header",
145                });
146            }
147            let descriptor_type = body[pos];
148            let descriptor_length = body[pos + 1] as usize;
149            let entry_end = pos + DESC_HEADER_LEN + descriptor_length;
150            if entry_end > body.len() {
151                return Err(Error::SectionLengthOverflow {
152                    declared: descriptor_length,
153                    available: body.len() - pos - DESC_HEADER_LEN,
154                });
155            }
156            if descriptor_length < DESC_FIXED_LEN {
157                return Err(Error::InvalidDescriptor {
158                    tag: descriptor_type,
159                    reason: "descriptorLength shorter than fixed fields",
160                });
161            }
162            let specifier_type = body[pos + DESC_HEADER_LEN];
163            let specifier_data = [
164                body[pos + DESC_HEADER_LEN + 1],
165                body[pos + DESC_HEADER_LEN + 2],
166                body[pos + DESC_HEADER_LEN + 3],
167            ];
168            let model = u16::from_be_bytes([
169                body[pos + DESC_HEADER_LEN + 4],
170                body[pos + DESC_HEADER_LEN + 5],
171            ]);
172            let version = u16::from_be_bytes([
173                body[pos + DESC_HEADER_LEN + 6],
174                body[pos + DESC_HEADER_LEN + 7],
175            ]);
176            let sub_descriptor_count = body[pos + DESC_HEADER_LEN + 8] as usize;
177            let sub_desc_start = pos + DESC_HEADER_LEN + DESC_FIXED_LEN;
178            let sub_desc_end = entry_end;
179            let sub_desc_region_len = sub_desc_end.saturating_sub(sub_desc_start);
180            let mut sub_descriptors = Vec::with_capacity(
181                sub_descriptor_count.min(sub_desc_region_len / SUB_DESC_HEADER_LEN),
182            );
183            let mut sub_pos = sub_desc_start;
184            for _ in 0..sub_descriptor_count {
185                if sub_pos + SUB_DESC_HEADER_LEN > sub_desc_end {
186                    return Err(Error::BufferTooShort {
187                        need: COMPAT_DESC_LEN_FIELD + sub_pos + SUB_DESC_HEADER_LEN,
188                        have: COMPAT_DESC_LEN_FIELD + sub_desc_end,
189                        what: "CompatibilityDescriptor subDescriptor header",
190                    });
191                }
192                let sub_descriptor_type = body[sub_pos];
193                let sub_descriptor_length = body[sub_pos + 1] as usize;
194                sub_pos += SUB_DESC_HEADER_LEN;
195                if sub_pos + sub_descriptor_length > sub_desc_end {
196                    return Err(Error::SectionLengthOverflow {
197                        declared: sub_descriptor_length,
198                        available: sub_desc_end - sub_pos,
199                    });
200                }
201                sub_descriptors.push(SubDescriptor {
202                    sub_descriptor_type,
203                    data: &body[sub_pos..sub_pos + sub_descriptor_length],
204                });
205                sub_pos += sub_descriptor_length;
206            }
207            pos = entry_end;
208            descriptors.push(CompatibilityDescriptorEntry {
209                descriptor_type,
210                specifier_type,
211                specifier_data,
212                model,
213                version,
214                sub_descriptors,
215            });
216        }
217        // Reject slack inside compatibilityDescriptorLength — leftover bytes
218        // would be silently dropped and lost on re-serialize.
219        if pos != body.len() {
220            return Err(Error::InvalidDescriptor {
221                tag: 0,
222                reason: "trailing bytes after compatibility descriptor entries",
223            });
224        }
225        Ok(CompatibilityDescriptor { descriptors })
226    }
227}
228
229impl Serialize for CompatibilityDescriptor<'_> {
230    type Error = Error;
231
232    fn serialized_len(&self) -> usize {
233        if self.descriptors.is_empty() {
234            return COMPAT_DESC_LEN_FIELD;
235        }
236        COMPAT_DESC_LEN_FIELD
237            + DESC_COUNT_FIELD
238            + self
239                .descriptors
240                .iter()
241                .map(entry_serialized_len)
242                .sum::<usize>()
243    }
244
245    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
246        let len = self.serialized_len();
247        if buf.len() < len {
248            return Err(Error::OutputBufferTooSmall {
249                need: len,
250                have: buf.len(),
251            });
252        }
253        if self.descriptors.is_empty() {
254            buf[0] = 0x00;
255            buf[1] = 0x00;
256            return Ok(COMPAT_DESC_LEN_FIELD);
257        }
258        let body_len = len - COMPAT_DESC_LEN_FIELD;
259        if body_len > u16::MAX as usize {
260            return Err(Error::SectionLengthOverflow {
261                declared: body_len,
262                available: u16::MAX as usize,
263            });
264        }
265        if self.descriptors.len() > u16::MAX as usize {
266            return Err(Error::SectionLengthOverflow {
267                declared: self.descriptors.len(),
268                available: u16::MAX as usize,
269            });
270        }
271        buf[..COMPAT_DESC_LEN_FIELD].copy_from_slice(&(body_len as u16).to_be_bytes());
272        buf[COMPAT_DESC_LEN_FIELD..COMPAT_DESC_LEN_FIELD + DESC_COUNT_FIELD]
273            .copy_from_slice(&(self.descriptors.len() as u16).to_be_bytes());
274        let mut pos = COMPAT_DESC_LEN_FIELD + DESC_COUNT_FIELD;
275        for entry in &self.descriptors {
276            let entry_body_len = entry_serialized_len(entry) - DESC_HEADER_LEN;
277            if entry_body_len > u8::MAX as usize {
278                return Err(Error::SectionLengthOverflow {
279                    declared: entry_body_len,
280                    available: u8::MAX as usize,
281                });
282            }
283            buf[pos] = entry.descriptor_type;
284            buf[pos + 1] = entry_body_len as u8;
285            buf[pos + DESC_HEADER_LEN] = entry.specifier_type;
286            buf[pos + DESC_HEADER_LEN + 1..pos + DESC_HEADER_LEN + 4]
287                .copy_from_slice(&entry.specifier_data);
288            buf[pos + DESC_HEADER_LEN + 4..pos + DESC_HEADER_LEN + 6]
289                .copy_from_slice(&entry.model.to_be_bytes());
290            buf[pos + DESC_HEADER_LEN + 6..pos + DESC_HEADER_LEN + 8]
291                .copy_from_slice(&entry.version.to_be_bytes());
292            if entry.sub_descriptors.len() > u8::MAX as usize {
293                return Err(Error::SectionLengthOverflow {
294                    declared: entry.sub_descriptors.len(),
295                    available: u8::MAX as usize,
296                });
297            }
298            buf[pos + DESC_HEADER_LEN + 8] = entry.sub_descriptors.len() as u8;
299            pos += DESC_HEADER_LEN + DESC_FIXED_LEN;
300            for sd in &entry.sub_descriptors {
301                buf[pos] = sd.sub_descriptor_type;
302                if sd.data.len() > u8::MAX as usize {
303                    return Err(Error::SectionLengthOverflow {
304                        declared: sd.data.len(),
305                        available: u8::MAX as usize,
306                    });
307                }
308                buf[pos + 1] = sd.data.len() as u8;
309                pos += SUB_DESC_HEADER_LEN;
310                buf[pos..pos + sd.data.len()].copy_from_slice(sd.data);
311                pos += sd.data.len();
312            }
313        }
314        Ok(len)
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321
322    #[test]
323    fn empty_round_trip() {
324        let cd = CompatibilityDescriptor {
325            descriptors: vec![],
326        };
327        let mut buf = vec![0u8; cd.serialized_len()];
328        cd.serialize_into(&mut buf).unwrap();
329        assert_eq!(buf, &[0x00, 0x00]);
330        let re = CompatibilityDescriptor::parse(&buf).unwrap();
331        assert!(re.descriptors.is_empty());
332    }
333
334    #[test]
335    fn empty_with_count_parses_to_empty() {
336        let bytes: &[u8] = &[0x00, 0x02, 0x00, 0x00];
337        let cd = CompatibilityDescriptor::parse(bytes).unwrap();
338        assert!(cd.descriptors.is_empty());
339        let mut buf = vec![0u8; cd.serialized_len()];
340        cd.serialize_into(&mut buf).unwrap();
341        assert_eq!(buf, &[0x00, 0x00]);
342    }
343
344    /// Hand-built wire bytes (not serializer-derived) pinning every field
345    /// position against TS 102 006 Table 15 — catches a mirrored read/write
346    /// layout bug that serializer-built round-trips cannot.
347    #[test]
348    fn hand_built_byte_anchor() {
349        // len=0x11(17), count=1; entry: type=0x01 len=0x0D(13),
350        // specifierType=0x01, OUI 00:15:0A, model 0x1234, version 0x0001,
351        // subCount=1, sub: type=0x05 len=0x02 data AA BB.
352        let bytes: &[u8] = &[
353            0x00, 0x11, 0x00, 0x01, 0x01, 0x0D, 0x01, 0x00, 0x15, 0x0A, 0x12, 0x34, 0x00, 0x01,
354            0x01, 0x05, 0x02, 0xAA, 0xBB,
355        ];
356        let cd = CompatibilityDescriptor::parse(bytes).unwrap();
357        assert_eq!(cd.descriptors.len(), 1);
358        let e = &cd.descriptors[0];
359        assert_eq!(e.descriptor_type, 0x01);
360        assert_eq!(e.specifier_type, 0x01);
361        assert_eq!(e.specifier_data, [0x00, 0x15, 0x0A]);
362        assert_eq!(e.model, 0x1234);
363        assert_eq!(e.version, 0x0001);
364        assert_eq!(e.sub_descriptors.len(), 1);
365        assert_eq!(e.sub_descriptors[0].sub_descriptor_type, 0x05);
366        assert_eq!(e.sub_descriptors[0].data, &[0xAA, 0xBB]);
367        // Byte-identical re-serialize against the hand-built wire.
368        let mut buf = vec![0u8; cd.serialized_len()];
369        cd.serialize_into(&mut buf).unwrap();
370        assert_eq!(buf, bytes);
371    }
372
373    #[test]
374    fn rejects_trailing_bytes() {
375        // len=0x03: count=0 (2 bytes) + 1 slack byte inside the declared length.
376        let bytes: &[u8] = &[0x00, 0x03, 0x00, 0x00, 0xFF];
377        assert!(matches!(
378            CompatibilityDescriptor::parse(bytes).unwrap_err(),
379            Error::InvalidDescriptor { .. }
380        ));
381    }
382
383    #[test]
384    fn rejects_truncated_entry_header() {
385        // count=1 but only 1 byte of the 2-byte entry header present.
386        let bytes: &[u8] = &[0x00, 0x03, 0x00, 0x01, 0x01];
387        assert!(CompatibilityDescriptor::parse(bytes).is_err());
388    }
389
390    #[test]
391    fn rejects_truncated_sub_descriptor() {
392        // descriptorLength=9 (fixed fields only) but subCount=1 → no room for
393        // the sub-descriptor header inside the entry.
394        let bytes: &[u8] = &[
395            0x00, 0x0D, 0x00, 0x01, 0x01, 0x09, 0x01, 0x00, 0x15, 0x0A, 0x12, 0x34, 0x00, 0x01,
396            0x01,
397        ];
398        assert!(CompatibilityDescriptor::parse(bytes).is_err());
399    }
400
401    #[test]
402    fn one_descriptor_with_sub_round_trip() {
403        let cd = CompatibilityDescriptor {
404            descriptors: vec![CompatibilityDescriptorEntry {
405                descriptor_type: 0x01,
406                specifier_type: 0x01,
407                specifier_data: [0x00, 0x15, 0x0A],
408                model: 0x1234,
409                version: 0x0001,
410                sub_descriptors: vec![
411                    SubDescriptor {
412                        sub_descriptor_type: 0x01,
413                        data: &[0xAA, 0xBB],
414                    },
415                    SubDescriptor {
416                        sub_descriptor_type: 0x02,
417                        data: &[0xCC],
418                    },
419                ],
420            }],
421        };
422        let mut buf = vec![0u8; cd.serialized_len()];
423        cd.serialize_into(&mut buf).unwrap();
424        let re = CompatibilityDescriptor::parse(&buf).unwrap();
425        assert_eq!(re.descriptors.len(), 1);
426        let e = &re.descriptors[0];
427        assert_eq!(e.descriptor_type, 0x01);
428        assert_eq!(e.specifier_type, 0x01);
429        assert_eq!(e.specifier_data, [0x00, 0x15, 0x0A]);
430        assert_eq!(e.model, 0x1234);
431        assert_eq!(e.version, 0x0001);
432        assert_eq!(e.sub_descriptors.len(), 2);
433        assert_eq!(e.sub_descriptors[0].sub_descriptor_type, 0x01);
434        assert_eq!(e.sub_descriptors[0].data, &[0xAA, 0xBB]);
435        assert_eq!(e.sub_descriptors[1].sub_descriptor_type, 0x02);
436        assert_eq!(e.sub_descriptors[1].data, &[0xCC]);
437        let mut buf2 = vec![0u8; cd.serialized_len()];
438        cd.serialize_into(&mut buf2).unwrap();
439        assert_eq!(buf, buf2, "byte-exact re-serialize");
440        assert_eq!(re, cd);
441    }
442
443    #[test]
444    fn two_descriptors_round_trip() {
445        let cd = CompatibilityDescriptor {
446            descriptors: vec![
447                CompatibilityDescriptorEntry {
448                    descriptor_type: 0x01,
449                    specifier_type: 0x01,
450                    specifier_data: [0x00, 0x00, 0x00],
451                    model: 0x0000,
452                    version: 0x0000,
453                    sub_descriptors: vec![],
454                },
455                CompatibilityDescriptorEntry {
456                    descriptor_type: 0x02,
457                    specifier_type: 0x01,
458                    specifier_data: [0x00, 0x15, 0x5A],
459                    model: 0x0100,
460                    version: 0x0002,
461                    sub_descriptors: vec![SubDescriptor {
462                        sub_descriptor_type: 0x80,
463                        data: &[0xDE, 0xAD, 0xBE, 0xEF],
464                    }],
465                },
466            ],
467        };
468        let mut buf = vec![0u8; cd.serialized_len()];
469        cd.serialize_into(&mut buf).unwrap();
470        let re = CompatibilityDescriptor::parse(&buf).unwrap();
471        assert_eq!(re, cd);
472    }
473
474    #[test]
475    fn parse_rejects_short_buffer() {
476        assert!(matches!(
477            CompatibilityDescriptor::parse(&[0x00]).unwrap_err(),
478            Error::BufferTooShort { .. }
479        ));
480    }
481
482    #[test]
483    fn parse_rejects_truncated_body() {
484        assert!(matches!(
485            CompatibilityDescriptor::parse(&[0x00, 0x05, 0x00, 0x01]).unwrap_err(),
486            Error::SectionLengthOverflow { .. }
487        ));
488    }
489
490    #[test]
491    fn parse_rejects_descriptor_length_too_short() {
492        let bytes: &[u8] = &[
493            0x00, 0x06, // compatibilityDescriptorLength = 6
494            0x00, 0x01, // descriptorCount = 1
495            0x01, 0x02, // descriptorType=1, descriptorLength=2 (too short, need 9)
496            0xAA, 0xBB, // 2 bytes of entry body (to satisfy entry_end bounds)
497        ];
498        assert!(matches!(
499            CompatibilityDescriptor::parse(bytes).unwrap_err(),
500            Error::InvalidDescriptor { .. }
501        ));
502    }
503
504    #[test]
505    fn serialize_rejects_small_buffer() {
506        let cd = CompatibilityDescriptor {
507            descriptors: vec![],
508        };
509        assert!(matches!(
510            cd.serialize_into(&mut [0u8; 1]).unwrap_err(),
511            Error::OutputBufferTooSmall { .. }
512        ));
513    }
514}