1use super::descriptor_body;
10use crate::descriptors::content_time_base_indicator::ContentTimeBaseIndicator;
11use crate::error::{Error, Result};
12use dvb_common::{Parse, Serialize};
13
14pub const TAG: u8 = 0x24;
16const HEADER_LEN: usize = 2;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize))]
21pub struct ContentTimeBase {
22 pub content_time_base_value: u64,
24 pub metadata_time_base_value: u64,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize))]
31pub struct ContentTimeBaseAssociation<'a> {
32 #[cfg_attr(feature = "serde", serde(borrow))]
34 pub data: &'a [u8],
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
41pub struct ContentLabelingDescriptor<'a> {
42 pub metadata_application_format: u16,
44 pub metadata_application_format_identifier: Option<u32>,
46 pub content_reference_id_record_flag: bool,
48 pub content_time_base_indicator: ContentTimeBaseIndicator,
50 #[cfg_attr(feature = "serde", serde(borrow))]
52 pub content_reference_id_record: Option<&'a [u8]>,
53 pub time_base: Option<ContentTimeBase>,
55 pub content_id: Option<u8>,
57 pub time_base_association: Option<ContentTimeBaseAssociation<'a>>,
59 #[cfg_attr(feature = "serde", serde(borrow))]
61 pub private_data: &'a [u8],
62}
63
64fn read_33bit(bytes: &[u8], pos: usize) -> u64 {
65 ((bytes[pos] as u64) << 25)
68 | ((bytes[pos + 1] as u64) << 17)
69 | ((bytes[pos + 2] as u64) << 9)
70 | ((bytes[pos + 3] as u64) << 1)
71 | ((bytes[pos + 4] >> 7) as u64)
72}
73
74fn write_33bit(buf: &mut [u8], pos: usize, value: u64) {
75 let v = value & 0x1_FFFF_FFFF;
76 buf[pos] = ((v >> 25) & 0xFF) as u8;
77 buf[pos + 1] = ((v >> 17) & 0xFF) as u8;
78 buf[pos + 2] = ((v >> 9) & 0xFF) as u8;
79 buf[pos + 3] = ((v >> 1) & 0xFF) as u8;
80 buf[pos + 4] = ((v & 0x01) as u8) << 7;
81}
82
83impl<'a> Parse<'a> for ContentLabelingDescriptor<'a> {
84 type Error = crate::error::Error;
85
86 fn parse(bytes: &'a [u8]) -> Result<Self> {
87 let body = descriptor_body(
88 bytes,
89 TAG,
90 "ContentLabelingDescriptor",
91 "unexpected tag for content_labeling_descriptor",
92 )?;
93
94 if body.len() < 3 {
96 return Err(Error::InvalidDescriptor {
97 tag: TAG,
98 reason: "content_labeling_descriptor too short (< 3 body bytes)",
99 });
100 }
101
102 let metadata_application_format = u16::from_be_bytes([body[0], body[1]]);
103 let mut pos = 2;
104
105 let metadata_application_format_identifier = if metadata_application_format == 0xFFFF {
106 if body.len() < pos + 4 {
107 return Err(Error::InvalidDescriptor {
108 tag: TAG,
109 reason: "content_labeling_descriptor too short for metadata_application_format_identifier",
110 });
111 }
112 let id = u32::from_be_bytes([body[pos], body[pos + 1], body[pos + 2], body[pos + 3]]);
113 pos += 4;
114 Some(id)
115 } else {
116 None
117 };
118
119 if body.len() < pos + 1 {
120 return Err(Error::InvalidDescriptor {
121 tag: TAG,
122 reason: "content_labeling_descriptor too short for flags byte",
123 });
124 }
125 let flags = body[pos];
126 let content_reference_id_record_flag = (flags & 0x80) != 0;
127 let indicator_raw = (flags >> 3) & 0x0F;
128 let content_time_base_indicator = ContentTimeBaseIndicator::from_u8(indicator_raw);
129 pos += 1;
130
131 let (content_reference_id_record, _) = if content_reference_id_record_flag {
132 if body.len() < pos + 1 {
133 return Err(Error::InvalidDescriptor {
134 tag: TAG,
135 reason: "content_labeling_descriptor too short for content_reference_id_record_length",
136 });
137 }
138 let rec_len = body[pos] as usize;
139 pos += 1;
140 if body.len() < pos + rec_len {
141 return Err(Error::InvalidDescriptor {
142 tag: TAG,
143 reason: "content_labeling_descriptor too short for content_reference_id_record",
144 });
145 }
146 let rec = &body[pos..pos + rec_len];
147 pos += rec_len;
148 (Some(rec), pos)
149 } else {
150 (None, pos)
151 };
152
153 let time_base = if indicator_raw == 1 || indicator_raw == 2 {
154 if body.len() < pos + 10 {
157 return Err(Error::InvalidDescriptor {
158 tag: TAG,
159 reason: "content_labeling_descriptor too short for time base block",
160 });
161 }
162 let ctv = read_33bit(&body[pos..], 0);
172 let mtv = read_33bit(&body[pos + 5..], 0);
173 pos += 10;
174 Some(ContentTimeBase {
175 content_time_base_value: ctv,
176 metadata_time_base_value: mtv,
177 })
178 } else {
179 None
180 };
181
182 let content_id = if indicator_raw == 2 {
183 if body.len() < pos + 1 {
184 return Err(Error::InvalidDescriptor {
185 tag: TAG,
186 reason: "content_labeling_descriptor too short for contentId byte",
187 });
188 }
189 let id = body[pos] & 0x7F;
190 pos += 1;
191 Some(id)
192 } else {
193 None
194 };
195
196 let time_base_association = if (3..=7).contains(&indicator_raw) {
197 if body.len() < pos + 1 {
198 return Err(Error::InvalidDescriptor {
199 tag: TAG,
200 reason: "content_labeling_descriptor too short for time_base_association_data_length",
201 });
202 }
203 let assoc_len = body[pos] as usize;
204 pos += 1;
205 if body.len() < pos + assoc_len {
206 return Err(Error::InvalidDescriptor {
207 tag: TAG,
208 reason: "content_labeling_descriptor too short for time_base_association_data",
209 });
210 }
211 let data = &body[pos..pos + assoc_len];
212 pos += assoc_len;
213 Some(ContentTimeBaseAssociation { data })
214 } else {
215 None
216 };
217
218 let private_data = &body[pos..];
219
220 Ok(Self {
221 metadata_application_format,
222 metadata_application_format_identifier,
223 content_reference_id_record_flag,
224 content_time_base_indicator,
225 content_reference_id_record,
226 time_base,
227 content_id,
228 time_base_association,
229 private_data,
230 })
231 }
232}
233
234impl Serialize for ContentLabelingDescriptor<'_> {
235 type Error = crate::error::Error;
236
237 fn serialized_len(&self) -> usize {
238 let mut len: usize = HEADER_LEN + 3; if self.metadata_application_format_identifier.is_some() {
240 len += 4;
241 }
242 if let Some(rec) = self.content_reference_id_record {
243 len += 1 + rec.len();
244 }
245 if self.time_base.is_some() {
246 len += 10;
247 }
248 if self.content_id.is_some() {
249 len += 1;
250 }
251 if let Some(ref assoc) = self.time_base_association {
252 len += 1 + assoc.data.len();
253 }
254 len += self.private_data.len();
255 len
256 }
257
258 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
259 let len = self.serialized_len();
260 if buf.len() < len {
261 return Err(Error::OutputBufferTooSmall {
262 need: len,
263 have: buf.len(),
264 });
265 }
266 buf[0] = TAG;
267 buf[1] = (len - HEADER_LEN) as u8;
268
269 buf[HEADER_LEN] = (self.metadata_application_format >> 8) as u8;
270 buf[HEADER_LEN + 1] = self.metadata_application_format as u8;
271 let mut pos = HEADER_LEN + 2;
272
273 if let Some(id) = self.metadata_application_format_identifier {
274 buf[pos..pos + 4].copy_from_slice(&id.to_be_bytes());
275 pos += 4;
276 }
277
278 let mut flags = (self.content_time_base_indicator.to_u8() & 0x0F) << 3;
279 if self.content_reference_id_record_flag {
280 flags |= 0x80;
281 }
282 buf[pos] = flags;
283 pos += 1;
284
285 if let Some(rec) = self.content_reference_id_record {
286 buf[pos] = rec.len() as u8;
287 pos += 1;
288 buf[pos..pos + rec.len()].copy_from_slice(rec);
289 pos += rec.len();
290 }
291
292 if let Some(ref tb) = self.time_base {
293 write_33bit(buf, pos, tb.content_time_base_value);
295 pos += 5;
296 write_33bit(buf, pos, tb.metadata_time_base_value);
298 pos += 5;
299 }
300
301 if let Some(cid) = self.content_id {
302 buf[pos] = cid & 0x7F;
303 pos += 1;
304 }
305
306 if let Some(ref assoc) = self.time_base_association {
307 buf[pos] = assoc.data.len() as u8;
308 pos += 1;
309 buf[pos..pos + assoc.data.len()].copy_from_slice(assoc.data);
310 pos += assoc.data.len();
311 }
312
313 buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
314 Ok(len)
315 }
316}
317
318impl<'a> crate::traits::DescriptorDef<'a> for ContentLabelingDescriptor<'a> {
319 const TAG: u8 = TAG;
320 const NAME: &'static str = "CONTENT_LABELING";
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 fn serialize_round_trip(d: &ContentLabelingDescriptor<'_>) {
329 let mut buf = vec![0u8; d.serialized_len()];
330 let written = d.serialize_into(&mut buf).unwrap();
331 assert_eq!(written, d.serialized_len());
332 let reparsed = ContentLabelingDescriptor::parse(&buf).unwrap();
333 assert_eq!(*d, reparsed, "round-trip mismatch");
334 }
335
336 #[test]
337 fn round_trip_indicator_0_minimal() {
338 let d = ContentLabelingDescriptor {
339 metadata_application_format: 0x0010,
340 metadata_application_format_identifier: None,
341 content_reference_id_record_flag: false,
342 content_time_base_indicator: ContentTimeBaseIndicator::None,
343 content_reference_id_record: None,
344 time_base: None,
345 content_id: None,
346 time_base_association: None,
347 private_data: &[],
348 };
349 serialize_round_trip(&d);
350 }
351
352 #[test]
353 fn round_trip_indicator_0_with_ref_id_and_private() {
354 let d = ContentLabelingDescriptor {
355 metadata_application_format: 0x0011,
356 metadata_application_format_identifier: None,
357 content_reference_id_record_flag: true,
358 content_time_base_indicator: ContentTimeBaseIndicator::None,
359 content_reference_id_record: Some(&[0xAA, 0xBB, 0xCC]),
360 time_base: None,
361 content_id: None,
362 time_base_association: None,
363 private_data: &[0xDD, 0xEE],
364 };
365 serialize_round_trip(&d);
366 }
367
368 #[test]
369 fn round_trip_indicator_1_stc_time_base() {
370 let d = ContentLabelingDescriptor {
371 metadata_application_format: 0x0100,
372 metadata_application_format_identifier: None,
373 content_reference_id_record_flag: false,
374 content_time_base_indicator: ContentTimeBaseIndicator::Stc,
375 content_reference_id_record: None,
376 time_base: Some(ContentTimeBase {
377 content_time_base_value: 0x123456789,
378 metadata_time_base_value: 0x1ABCDEF01,
379 }),
380 content_id: None,
381 time_base_association: None,
382 private_data: &[],
383 };
384 serialize_round_trip(&d);
385 }
386
387 #[test]
388 fn round_trip_indicator_2_npt_with_content_id() {
389 let d = ContentLabelingDescriptor {
390 metadata_application_format: 0x0100,
391 metadata_application_format_identifier: None,
392 content_reference_id_record_flag: false,
393 content_time_base_indicator: ContentTimeBaseIndicator::Npt,
394 content_reference_id_record: None,
395 time_base: Some(ContentTimeBase {
396 content_time_base_value: 0x1AABBCCDD,
397 metadata_time_base_value: 0x0,
398 }),
399 content_id: Some(42),
400 time_base_association: None,
401 private_data: &[],
402 };
403 serialize_round_trip(&d);
404 }
405
406 #[test]
407 fn round_trip_indicator_3_association() {
408 let d = ContentLabelingDescriptor {
409 metadata_application_format: 0x0100,
410 metadata_application_format_identifier: None,
411 content_reference_id_record_flag: false,
412 content_time_base_indicator: ContentTimeBaseIndicator::Reserved(3),
413 content_reference_id_record: None,
414 time_base: None,
415 content_id: None,
416 time_base_association: Some(ContentTimeBaseAssociation {
417 data: &[0x11, 0x22, 0x33, 0x44],
418 }),
419 private_data: &[],
420 };
421 serialize_round_trip(&d);
422 }
423
424 #[test]
425 fn round_trip_with_ffff_identifier() {
426 let d = ContentLabelingDescriptor {
427 metadata_application_format: 0xFFFF,
428 metadata_application_format_identifier: Some(0xDEADBEEF),
429 content_reference_id_record_flag: true,
430 content_time_base_indicator: ContentTimeBaseIndicator::None,
431 content_reference_id_record: Some(&[0x01, 0x02]),
432 time_base: None,
433 content_id: None,
434 time_base_association: None,
435 private_data: &[0xFF],
436 };
437 serialize_round_trip(&d);
438 }
439
440 #[test]
441 fn round_trip_indicator_private_8() {
442 let d = ContentLabelingDescriptor {
444 metadata_application_format: 0x0101,
445 metadata_application_format_identifier: None,
446 content_reference_id_record_flag: false,
447 content_time_base_indicator: ContentTimeBaseIndicator::Private(10),
448 content_reference_id_record: None,
449 time_base: None,
450 content_id: None,
451 time_base_association: None,
452 private_data: &[0x99],
453 };
454 serialize_round_trip(&d);
455 }
456
457 #[test]
458 fn parse_rejects_wrong_tag() {
459 let err = ContentLabelingDescriptor::parse(&[0x02, 3, 0, 0, 0]).unwrap_err();
460 assert!(matches!(err, Error::InvalidDescriptor { tag: 0x02, .. }));
461 }
462
463 #[test]
464 fn parse_rejects_too_short() {
465 let err = ContentLabelingDescriptor::parse(&[TAG, 0]).unwrap_err();
466 assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
467 }
468}