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