1use crate::error::{Error, Result};
9use crate::traits::Descriptor;
10use dvb_common::{Parse, Serialize};
11
12pub const TAG: u8 = 0x76;
14const HEADER_LEN: usize = 2;
15const CRID_TYPE_MASK: u8 = 0xFC;
16const CRID_LOCATION_MASK: u8 = 0x03;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub enum CridLocation<'a> {
27 Inline(#[cfg_attr(feature = "serde", serde(borrow))] &'a [u8]),
29 Reference(u16),
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub struct CridEntry<'a> {
37 pub crid_type: u8,
41 #[cfg_attr(feature = "serde", serde(borrow))]
43 pub location: CridLocation<'a>,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
53pub struct ContentIdentifierDescriptor<'a> {
54 pub entries: Vec<CridEntry<'a>>,
56}
57
58impl<'a> Parse<'a> for ContentIdentifierDescriptor<'a> {
59 type Error = crate::error::Error;
60 fn parse(bytes: &'a [u8]) -> Result<Self> {
61 if bytes.len() < HEADER_LEN {
62 return Err(Error::BufferTooShort {
63 need: HEADER_LEN,
64 have: bytes.len(),
65 what: "ContentIdentifierDescriptor header",
66 });
67 }
68 if bytes[0] != TAG {
69 return Err(Error::InvalidDescriptor {
70 tag: bytes[0],
71 reason: "unexpected tag for ContentIdentifierDescriptor",
72 });
73 }
74 let length = bytes[1] as usize;
75 let end = HEADER_LEN + length;
76 if bytes.len() < end {
77 return Err(Error::BufferTooShort {
78 need: end,
79 have: bytes.len(),
80 what: "ContentIdentifierDescriptor body",
81 });
82 }
83 if length == 0 {
84 return Ok(Self {
85 entries: Vec::new(),
86 });
87 }
88 let body = &bytes[HEADER_LEN..end];
89 let mut entries = Vec::new();
90 let mut pos = 0;
91 while pos < body.len() {
92 let header_byte = body[pos];
93 pos += 1;
94 let crid_type = (header_byte & CRID_TYPE_MASK) >> 2;
95 let crid_location = header_byte & CRID_LOCATION_MASK;
96 let location = match crid_location {
97 0x00 => {
98 if pos >= body.len() {
99 return Err(Error::InvalidDescriptor {
100 tag: TAG,
101 reason: "inline CRID length byte missing",
102 });
103 }
104 let crid_length = body[pos] as usize;
105 pos += 1;
106 if pos + crid_length > body.len() {
107 return Err(Error::InvalidDescriptor {
108 tag: TAG,
109 reason: "inline CRID length exceeds descriptor body",
110 });
111 }
112 let crid_bytes = &body[pos..pos + crid_length];
113 pos += crid_length;
114 CridLocation::Inline(crid_bytes)
115 }
116 0x01 => {
117 if pos + 2 > body.len() {
118 return Err(Error::InvalidDescriptor {
119 tag: TAG,
120 reason: "CRID reference truncated",
121 });
122 }
123 let crid_ref = u16::from_be_bytes([body[pos], body[pos + 1]]);
125 pos += 2;
126 CridLocation::Reference(crid_ref)
127 }
128 _ => {
129 return Err(Error::InvalidDescriptor {
130 tag: TAG,
131 reason: "reserved crid_location value",
132 });
133 }
134 };
135 entries.push(CridEntry {
136 crid_type,
137 location,
138 });
139 }
140 Ok(Self { entries })
141 }
142}
143
144impl Serialize for ContentIdentifierDescriptor<'_> {
145 type Error = crate::error::Error;
146 fn serialized_len(&self) -> usize {
147 let body_len: usize = self
148 .entries
149 .iter()
150 .map(|e| match &e.location {
151 CridLocation::Inline(data) => 2 + data.len(),
152 CridLocation::Reference(_) => 3,
153 })
154 .sum();
155 HEADER_LEN + body_len
156 }
157
158 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
159 let len = self.serialized_len();
160 if buf.len() < len {
161 return Err(Error::OutputBufferTooSmall {
162 need: len,
163 have: buf.len(),
164 });
165 }
166 buf[0] = TAG;
167 buf[1] = (len - HEADER_LEN) as u8;
168 let mut pos = HEADER_LEN;
169 for entry in &self.entries {
170 let header = (entry.crid_type << 2) & CRID_TYPE_MASK;
171 match &entry.location {
172 CridLocation::Inline(data) => {
173 buf[pos] = header;
174 buf[pos + 1] = data.len() as u8;
175 buf[pos + 2..pos + 2 + data.len()].copy_from_slice(data);
176 pos += 2 + data.len();
177 }
178 CridLocation::Reference(val) => {
179 buf[pos] = header | 0x01;
180 let bytes = val.to_be_bytes();
181 buf[pos + 1] = bytes[0];
182 buf[pos + 2] = bytes[1];
183 pos += 3;
184 }
185 }
186 }
187 Ok(len)
189 }
190}
191
192impl<'a> Descriptor<'a> for ContentIdentifierDescriptor<'a> {
193 const TAG: u8 = TAG;
194 fn descriptor_length(&self) -> u8 {
195 (self.serialized_len() - HEADER_LEN) as u8
196 }
197}
198
199impl<'a> crate::traits::DescriptorDef<'a> for ContentIdentifierDescriptor<'a> {
200 const TAG: u8 = TAG;
201 const NAME: &'static str = "CONTENT_IDENTIFIER";
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn parse_single_inline_crid() {
210 let data = b"DVB/CRID/EPG123";
211 let mut buf = vec![TAG, (data.len() + 2) as u8, 0x01 << 2, data.len() as u8];
212 buf.extend_from_slice(data);
213 let d = ContentIdentifierDescriptor::parse(&buf).unwrap();
214 assert_eq!(d.entries.len(), 1);
215 assert_eq!(d.entries[0].crid_type, 0x01);
216 match &d.entries[0].location {
217 CridLocation::Inline(bytes) => assert_eq!(*bytes, data.as_slice()),
218 _ => panic!("expected Inline"),
219 }
220 }
221
222 #[test]
223 fn parse_single_reference_crid() {
224 let buf = [TAG, 0x03, (0x02 << 2) | 0x01, 0x00, 0x42];
225 let d = ContentIdentifierDescriptor::parse(&buf).unwrap();
226 assert_eq!(d.entries.len(), 1);
227 assert_eq!(d.entries[0].crid_type, 0x02);
228 match d.entries[0].location {
229 CridLocation::Reference(val) => assert_eq!(val, 0x0042),
230 _ => panic!("expected Reference"),
231 }
232 }
233
234 #[test]
235 fn parse_multiple_entries() {
236 let inline_data = b"EPG/EPG123";
237 let ref_val: u16 = 0x0100;
238 let mut buf = vec![
240 TAG,
241 0x00, 0x01 << 2,
243 inline_data.len() as u8,
244 ];
245 buf.extend_from_slice(inline_data);
246 buf.push((0x03 << 2) | 0x01);
248 buf.extend_from_slice(&ref_val.to_be_bytes());
249 let body_len = buf.len() - HEADER_LEN;
251 buf[1] = body_len as u8;
252
253 let d = ContentIdentifierDescriptor::parse(&buf).unwrap();
254 assert_eq!(d.entries.len(), 2);
255 assert_eq!(d.entries[0].crid_type, 0x01);
257 match &d.entries[0].location {
258 CridLocation::Inline(bytes) => assert_eq!(*bytes, inline_data.as_slice()),
259 _ => panic!("expected Inline for first entry"),
260 }
261 assert_eq!(d.entries[1].crid_type, 0x03);
263 match d.entries[1].location {
264 CridLocation::Reference(val) => assert_eq!(val, ref_val),
265 _ => panic!("expected Reference for second entry"),
266 }
267 }
268
269 #[test]
270 fn parse_rejects_wrong_tag() {
271 let buf = [0x7A, 0x03, 0x04, 0x00, 0x42];
272 assert!(matches!(
273 ContentIdentifierDescriptor::parse(&buf).unwrap_err(),
274 Error::InvalidDescriptor { tag: 0x7A, .. }
275 ));
276 }
277
278 #[test]
279 fn parse_rejects_inline_length_overrun() {
280 let buf = [TAG, 4, 0x01 << 2, 10, 0xAA, 0xBB];
283 assert!(matches!(
284 ContentIdentifierDescriptor::parse(&buf).unwrap_err(),
285 Error::InvalidDescriptor { tag: TAG, .. }
286 ));
287 }
288
289 #[test]
290 fn parse_rejects_reference_truncated() {
291 let buf = [TAG, 2, (0x02 << 2) | 0x01, 0xAA];
294 assert!(matches!(
295 ContentIdentifierDescriptor::parse(&buf).unwrap_err(),
296 Error::InvalidDescriptor { tag: TAG, .. }
297 ));
298 }
299
300 #[test]
301 fn parse_rejects_reserved_location() {
302 let buf = [TAG, 0x01, (0x01 << 2) | 0x02];
304 assert!(matches!(
305 ContentIdentifierDescriptor::parse(&buf).unwrap_err(),
306 Error::InvalidDescriptor { tag: TAG, .. }
307 ));
308 let buf = [TAG, 0x01, (0x01 << 2) | 0x03];
310 assert!(matches!(
311 ContentIdentifierDescriptor::parse(&buf).unwrap_err(),
312 Error::InvalidDescriptor { tag: TAG, .. }
313 ));
314 }
315
316 #[test]
317 fn empty_descriptor_valid() {
318 let buf = [TAG, 0x00];
319 let d = ContentIdentifierDescriptor::parse(&buf).unwrap();
320 assert_eq!(d.entries.len(), 0);
321 }
322
323 #[test]
324 fn serialize_round_trip_inline_and_reference() {
325 let inline_data = b"DVB/CRID/TEST456";
326 let ref_val: u16 = 789;
327 let desc = ContentIdentifierDescriptor {
328 entries: vec![
329 CridEntry {
330 crid_type: 0x01,
331 location: CridLocation::Inline(inline_data.as_slice()),
332 },
333 CridEntry {
334 crid_type: 0x03,
335 location: CridLocation::Reference(ref_val),
336 },
337 ],
338 };
339 let mut buf = vec![0u8; desc.serialized_len()];
340 desc.serialize_into(&mut buf).unwrap();
341 let parsed = ContentIdentifierDescriptor::parse(&buf).unwrap();
342 assert_eq!(parsed.entries.len(), desc.entries.len());
343 match &parsed.entries[0].location {
345 CridLocation::Inline(bytes) => assert_eq!(*bytes, inline_data.as_slice()),
346 _ => panic!("expected Inline"),
347 }
348 assert_eq!(parsed.entries[0].crid_type, 0x01);
349 match parsed.entries[1].location {
351 CridLocation::Reference(val) => assert_eq!(val, ref_val),
352 _ => panic!("expected Reference"),
353 }
354 assert_eq!(parsed.entries[1].crid_type, 0x03);
355 }
356}