Skip to main content

dvb_ci/ci_plus/
descriptors.rs

1//! CI Plus Sample-Mode descriptors (IV / key-identifier) — ETSI TS 103 205
2//! V1.4.1 §7.5.5.4, Tables 45-47 (PDF pp. 79-80). See
3//! `docs/ts_103_205/ci-plus-descriptors.md`.
4//!
5//! Both follow the standard TLV form `descriptor_tag` (8) + `descriptor_length`
6//! (8) + `descriptor_length` opaque body bytes (§7.5.5.4.1). They may be carried
7//! in CI Plus Sample-Mode messages (Sample Start TS Packet) and the
8//! media-interface fragment-header descriptor loop.
9//!
10//! - `ciplus_initialization_vector_descriptor` (`0xD0`, Table 46) — the IV bytes.
11//! - `ciplus_key_identifier_descriptor` (`0xD1`, Table 47) — the key-id bytes.
12//!
13//! The body is an **opaque** crypto blob in both cases; only the variable length
14//! and the byte sequence are structural.
15
16use crate::error::{Error, Result};
17use dvb_common::{Parse, Serialize};
18
19/// `descriptor_tag` of the `ciplus_initialization_vector_descriptor` (Table 46).
20pub const IV_DESCRIPTOR_TAG: u8 = 0xD0;
21/// `descriptor_tag` of the `ciplus_key_identifier_descriptor` (Table 47).
22pub const KEY_IDENTIFIER_DESCRIPTOR_TAG: u8 = 0xD1;
23
24// descriptor_tag(1) + descriptor_length(1).
25const DESC_HEADER: usize = 2;
26
27/// `ciplus_initialization_vector_descriptor()` (Table 46): the IV associated with
28/// the following Sample. The IV bytes are an opaque crypto blob.
29#[derive(Debug, Clone, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize))]
31pub struct CiplusInitializationVectorDescriptor<'a> {
32    /// `IV_data_byte` sequence (length = `descriptor_length`).
33    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
34    pub iv_data: &'a [u8],
35}
36
37impl<'a> Parse<'a> for CiplusInitializationVectorDescriptor<'a> {
38    type Error = Error;
39    fn parse(bytes: &'a [u8]) -> Result<Self> {
40        let body = parse_tlv(
41            bytes,
42            IV_DESCRIPTOR_TAG,
43            "ciplus_initialization_vector_descriptor",
44        )?;
45        Ok(Self { iv_data: body })
46    }
47}
48impl Serialize for CiplusInitializationVectorDescriptor<'_> {
49    type Error = Error;
50    fn serialized_len(&self) -> usize {
51        DESC_HEADER + self.iv_data.len()
52    }
53    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
54        write_tlv(IV_DESCRIPTOR_TAG, self.iv_data, buf)
55    }
56}
57
58/// `ciplus_key_identifier_descriptor()` (Table 47): the content key identifier
59/// associated with the following Sample. The key-id bytes are an opaque blob.
60#[derive(Debug, Clone, PartialEq, Eq)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize))]
62pub struct CiplusKeyIdentifierDescriptor<'a> {
63    /// `key_id_data_byte` sequence (length = `descriptor_length`).
64    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
65    pub key_id_data: &'a [u8],
66}
67
68impl<'a> Parse<'a> for CiplusKeyIdentifierDescriptor<'a> {
69    type Error = Error;
70    fn parse(bytes: &'a [u8]) -> Result<Self> {
71        let body = parse_tlv(
72            bytes,
73            KEY_IDENTIFIER_DESCRIPTOR_TAG,
74            "ciplus_key_identifier_descriptor",
75        )?;
76        Ok(Self { key_id_data: body })
77    }
78}
79impl Serialize for CiplusKeyIdentifierDescriptor<'_> {
80    type Error = Error;
81    fn serialized_len(&self) -> usize {
82        DESC_HEADER + self.key_id_data.len()
83    }
84    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
85        write_tlv(KEY_IDENTIFIER_DESCRIPTOR_TAG, self.key_id_data, buf)
86    }
87}
88
89/// Parse a `descriptor_tag(8) + descriptor_length(8) + body` TLV, verifying the
90/// tag and returning the exactly-`descriptor_length` body slice.
91fn parse_tlv<'a>(bytes: &'a [u8], expected_tag: u8, what: &'static str) -> Result<&'a [u8]> {
92    if bytes.len() < DESC_HEADER {
93        return Err(Error::BufferTooShort {
94            need: DESC_HEADER,
95            have: bytes.len(),
96            what,
97        });
98    }
99    if bytes[0] != expected_tag {
100        return Err(Error::InvalidObject {
101            what,
102            reason: "descriptor_tag mismatch",
103        });
104    }
105    let len = bytes[1] as usize;
106    let end = DESC_HEADER + len;
107    if bytes.len() < end {
108        return Err(Error::LengthMismatch {
109            what,
110            declared: len,
111            actual: bytes.len().saturating_sub(DESC_HEADER),
112        });
113    }
114    Ok(&bytes[DESC_HEADER..end])
115}
116
117/// Serialize a TLV descriptor (`descriptor_length` is computed from `body`).
118fn write_tlv(tag: u8, body: &[u8], buf: &mut [u8]) -> Result<usize> {
119    if body.len() > u8::MAX as usize {
120        return Err(Error::LengthTooLarge(body.len()));
121    }
122    let total = DESC_HEADER + body.len();
123    if buf.len() < total {
124        return Err(Error::OutputBufferTooSmall {
125            need: total,
126            have: buf.len(),
127        });
128    }
129    buf[0] = tag;
130    buf[1] = body.len() as u8;
131    buf[DESC_HEADER..total].copy_from_slice(body);
132    Ok(total)
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn iv_descriptor_round_trips_and_bites() {
141        let d = CiplusInitializationVectorDescriptor {
142            iv_data: &[0x00, 0x11, 0x22, 0x33],
143        };
144        let bytes = d.to_bytes();
145        assert_eq!(bytes, [0xD0, 0x04, 0x00, 0x11, 0x22, 0x33]);
146        assert_eq!(
147            CiplusInitializationVectorDescriptor::parse(&bytes).unwrap(),
148            d
149        );
150        let other = CiplusInitializationVectorDescriptor {
151            iv_data: &[0x00, 0x11, 0x22, 0x34],
152        };
153        assert_ne!(bytes, other.to_bytes());
154    }
155
156    #[test]
157    fn iv_descriptor_zero_byte_body() {
158        let d = CiplusInitializationVectorDescriptor { iv_data: &[] };
159        let bytes = d.to_bytes();
160        assert_eq!(bytes, [0xD0, 0x00]);
161        let parsed = CiplusInitializationVectorDescriptor::parse(&bytes).unwrap();
162        assert_eq!(parsed, d);
163        assert!(parsed.iv_data.is_empty());
164    }
165
166    #[test]
167    fn key_id_descriptor_round_trips() {
168        let d = CiplusKeyIdentifierDescriptor {
169            key_id_data: &[0xDE, 0xAD],
170        };
171        let bytes = d.to_bytes();
172        assert_eq!(bytes, [0xD1, 0x02, 0xDE, 0xAD]);
173        assert_eq!(CiplusKeyIdentifierDescriptor::parse(&bytes).unwrap(), d);
174    }
175
176    #[test]
177    fn key_id_descriptor_zero_byte_body() {
178        let d = CiplusKeyIdentifierDescriptor { key_id_data: &[] };
179        let bytes = d.to_bytes();
180        assert_eq!(bytes, [0xD1, 0x00]);
181        assert_eq!(CiplusKeyIdentifierDescriptor::parse(&bytes).unwrap(), d);
182    }
183
184    #[test]
185    fn wrong_tag_rejected() {
186        let bytes = [0xD1, 0x00];
187        assert!(matches!(
188            CiplusInitializationVectorDescriptor::parse(&bytes),
189            Err(Error::InvalidObject { .. })
190        ));
191    }
192}