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