1use super::descriptor_body;
24use crate::error::{Error, Result};
25use dvb_common::{Parse, Serialize};
26
27pub const TAG: u8 = 0x13;
29
30const HEADER_LEN: usize = 2;
33const CAROUSEL_ID_LEN: usize = 4;
35const FORMAT_ID_LEN: usize = 1;
37const BODY_PREFIX_LEN: usize = CAROUSEL_ID_LEN + FORMAT_ID_LEN;
39
40const FS1_MODULE_VERSION_LEN: usize = 1;
43const FS1_MODULE_ID_LEN: usize = 2;
45const FS1_BLOCK_SIZE_LEN: usize = 2;
47const FS1_MODULE_SIZE_LEN: usize = 4;
49const FS1_COMPRESSION_METHOD_LEN: usize = 1;
51const FS1_ORIGINAL_SIZE_LEN: usize = 4;
53const FS1_TIMEOUT_LEN: usize = 1;
56const FS1_OBJECT_KEY_LENGTH_LEN: usize = 1;
58const FS1_FIXED_LEN: usize = FS1_MODULE_VERSION_LEN
60 + FS1_MODULE_ID_LEN
61 + FS1_BLOCK_SIZE_LEN
62 + FS1_MODULE_SIZE_LEN
63 + FS1_COMPRESSION_METHOD_LEN
64 + FS1_ORIGINAL_SIZE_LEN
65 + FS1_TIMEOUT_LEN
66 + FS1_OBJECT_KEY_LENGTH_LEN; const FORMAT_ID_NONE: u8 = 0x00;
71const FORMAT_ID_AGGREGATED: u8 = 0x01;
73
74#[derive(Debug, Clone, PartialEq, Eq)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize))]
89#[non_exhaustive]
90pub enum FormatSpecifier<'a> {
91 Absent,
93 Aggregated {
109 module_version: u8,
111 module_id: u16,
113 block_size: u16,
115 module_size: u32,
117 compression_method: u8,
119 original_size: u32,
122 timeout: u8,
124 #[cfg_attr(feature = "serde", serde(borrow))]
126 object_key: &'a [u8],
127 },
128 Other {
130 format_id: u8,
132 #[cfg_attr(feature = "serde", serde(borrow))]
139 bytes: &'a [u8],
140 },
141}
142
143impl<'a> FormatSpecifier<'a> {
144 #[must_use]
146 pub fn format_id(&self) -> u8 {
147 match self {
148 FormatSpecifier::Absent => FORMAT_ID_NONE,
149 FormatSpecifier::Aggregated { .. } => FORMAT_ID_AGGREGATED,
150 FormatSpecifier::Other { format_id, .. } => *format_id,
151 }
152 }
153
154 fn serialized_len(&self) -> usize {
157 match self {
158 FormatSpecifier::Absent => 0,
159 FormatSpecifier::Aggregated { object_key, .. } => FS1_FIXED_LEN + object_key.len(),
160 FormatSpecifier::Other { bytes, .. } => bytes.len(),
161 }
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize))]
173#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
174pub struct CarouselIdentifierDescriptor<'a> {
175 pub carousel_id: u32,
177 #[cfg_attr(feature = "serde", serde(borrow))]
179 pub format: FormatSpecifier<'a>,
180 #[cfg_attr(feature = "serde", serde(borrow))]
185 pub private_data: &'a [u8],
186}
187
188impl<'a> Parse<'a> for CarouselIdentifierDescriptor<'a> {
189 type Error = crate::error::Error;
190
191 fn parse(bytes: &'a [u8]) -> Result<Self> {
192 let body = descriptor_body(
193 bytes,
194 TAG,
195 "CarouselIdentifierDescriptor",
196 "unexpected tag for carousel_identifier_descriptor",
197 )?;
198 if body.len() < BODY_PREFIX_LEN {
199 return Err(Error::InvalidDescriptor {
200 tag: TAG,
201 reason: "carousel_identifier_descriptor body shorter than 5 bytes",
202 });
203 }
204 let carousel_id = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
205 let format_id = body[CAROUSEL_ID_LEN];
206 let after_prefix = &body[BODY_PREFIX_LEN..];
207
208 let (format, private_data) = match format_id {
209 FORMAT_ID_NONE => (FormatSpecifier::Absent, after_prefix),
210 FORMAT_ID_AGGREGATED => {
211 if after_prefix.len() < FS1_FIXED_LEN {
212 return Err(Error::InvalidDescriptor {
213 tag: TAG,
214 reason: "FormatId=0x01 specifier truncated (insufficient fixed fields)",
215 });
216 }
217 let mut pos = 0usize;
218 let module_version = after_prefix[pos];
219 pos += FS1_MODULE_VERSION_LEN;
220 let module_id = u16::from_be_bytes([after_prefix[pos], after_prefix[pos + 1]]);
221 pos += FS1_MODULE_ID_LEN;
222 let block_size = u16::from_be_bytes([after_prefix[pos], after_prefix[pos + 1]]);
223 pos += FS1_BLOCK_SIZE_LEN;
224 let module_size = u32::from_be_bytes([
225 after_prefix[pos],
226 after_prefix[pos + 1],
227 after_prefix[pos + 2],
228 after_prefix[pos + 3],
229 ]);
230 pos += FS1_MODULE_SIZE_LEN;
231 let compression_method = after_prefix[pos];
232 pos += FS1_COMPRESSION_METHOD_LEN;
233 let original_size = u32::from_be_bytes([
234 after_prefix[pos],
235 after_prefix[pos + 1],
236 after_prefix[pos + 2],
237 after_prefix[pos + 3],
238 ]);
239 pos += FS1_ORIGINAL_SIZE_LEN;
240 let timeout = after_prefix[pos];
241 pos += FS1_TIMEOUT_LEN;
242 let object_key_length = after_prefix[pos] as usize;
243 pos += FS1_OBJECT_KEY_LENGTH_LEN;
244 let specifier_end = pos + object_key_length;
246 if specifier_end > after_prefix.len() {
247 return Err(Error::InvalidDescriptor {
248 tag: TAG,
249 reason: "FormatId=0x01 ObjectKeyData exceeds descriptor body",
250 });
251 }
252 let object_key = &after_prefix[pos..specifier_end];
253 let private_data = &after_prefix[specifier_end..];
254 (
255 FormatSpecifier::Aggregated {
256 module_version,
257 module_id,
258 block_size,
259 module_size,
260 compression_method,
261 original_size,
262 timeout,
263 object_key,
264 },
265 private_data,
266 )
267 }
268 other => {
269 (
273 FormatSpecifier::Other {
274 format_id: other,
275 bytes: after_prefix,
276 },
277 &[][..],
278 )
279 }
280 };
281 Ok(Self {
282 carousel_id,
283 format,
284 private_data,
285 })
286 }
287}
288
289impl Serialize for CarouselIdentifierDescriptor<'_> {
290 type Error = crate::error::Error;
291
292 fn serialized_len(&self) -> usize {
293 let body = BODY_PREFIX_LEN + self.format.serialized_len() + self.private_data.len();
294 HEADER_LEN + body
295 }
296
297 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
298 let total = self.serialized_len();
299 let body_len = BODY_PREFIX_LEN + self.format.serialized_len() + self.private_data.len();
300 if body_len > u8::MAX as usize {
301 return Err(Error::InvalidDescriptor {
302 tag: TAG,
303 reason: "carousel_identifier_descriptor body exceeds 255 bytes",
304 });
305 }
306 if buf.len() < total {
307 return Err(Error::OutputBufferTooSmall {
308 need: total,
309 have: buf.len(),
310 });
311 }
312
313 buf[0] = TAG;
314 buf[1] = body_len as u8;
315 buf[HEADER_LEN..HEADER_LEN + CAROUSEL_ID_LEN]
316 .copy_from_slice(&self.carousel_id.to_be_bytes());
317 buf[HEADER_LEN + CAROUSEL_ID_LEN] = self.format.format_id();
318
319 let mut pos = HEADER_LEN + BODY_PREFIX_LEN;
320 match &self.format {
321 FormatSpecifier::Absent => {}
322 FormatSpecifier::Aggregated {
323 module_version,
324 module_id,
325 block_size,
326 module_size,
327 compression_method,
328 original_size,
329 timeout,
330 object_key,
331 } => {
332 if object_key.len() > u8::MAX as usize {
333 return Err(Error::InvalidDescriptor {
334 tag: TAG,
335 reason: "FormatId=0x01 ObjectKeyData exceeds 255 bytes",
336 });
337 }
338 buf[pos] = *module_version;
339 pos += FS1_MODULE_VERSION_LEN;
340 buf[pos..pos + FS1_MODULE_ID_LEN].copy_from_slice(&module_id.to_be_bytes());
341 pos += FS1_MODULE_ID_LEN;
342 buf[pos..pos + FS1_BLOCK_SIZE_LEN].copy_from_slice(&block_size.to_be_bytes());
343 pos += FS1_BLOCK_SIZE_LEN;
344 buf[pos..pos + FS1_MODULE_SIZE_LEN].copy_from_slice(&module_size.to_be_bytes());
345 pos += FS1_MODULE_SIZE_LEN;
346 buf[pos] = *compression_method;
347 pos += FS1_COMPRESSION_METHOD_LEN;
348 buf[pos..pos + FS1_ORIGINAL_SIZE_LEN].copy_from_slice(&original_size.to_be_bytes());
349 pos += FS1_ORIGINAL_SIZE_LEN;
350 buf[pos] = *timeout;
351 pos += FS1_TIMEOUT_LEN;
352 buf[pos] = object_key.len() as u8;
353 pos += FS1_OBJECT_KEY_LENGTH_LEN;
354 buf[pos..pos + object_key.len()].copy_from_slice(object_key);
355 pos += object_key.len();
356 }
357 FormatSpecifier::Other { bytes, .. } => {
358 buf[pos..pos + bytes.len()].copy_from_slice(bytes);
359 pos += bytes.len();
360 }
361 }
362 buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
363 Ok(total)
364 }
365}
366
367impl<'a> crate::traits::DescriptorDef<'a> for CarouselIdentifierDescriptor<'a> {
368 const TAG: u8 = TAG;
369 const NAME: &'static str = "CAROUSEL_IDENTIFIER";
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[rustfmt::skip]
411 const ANCHOR_BYTES: &[u8] = &[
412 0x13, 0x18, 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x05, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x1E, 0x01, 0xAB, 0xDE, 0xAD, ];
426
427 fn anchor_descriptor() -> CarouselIdentifierDescriptor<'static> {
428 CarouselIdentifierDescriptor {
429 carousel_id: 1,
430 format: FormatSpecifier::Aggregated {
431 module_version: 3,
432 module_id: 5,
433 block_size: 1024,
434 module_size: 4096,
435 compression_method: 0,
436 original_size: 4096,
437 timeout: 30,
438 object_key: &[0xAB],
439 },
440 private_data: &[0xDE, 0xAD],
441 }
442 }
443
444 #[test]
447 fn byte_anchor_serialize_matches_hand_built() {
448 let d = anchor_descriptor();
449 let mut buf = vec![0u8; d.serialized_len()];
450 d.serialize_into(&mut buf).unwrap();
451 assert_eq!(
452 buf.as_slice(),
453 ANCHOR_BYTES,
454 "serialized bytes differ from anchor"
455 );
456 }
457
458 #[test]
459 fn byte_anchor_parse_extracts_correct_fields() {
460 let d = CarouselIdentifierDescriptor::parse(ANCHOR_BYTES).unwrap();
461 assert_eq!(d.carousel_id, 1);
462 assert_eq!(d.private_data, &[0xDE, 0xAD]);
463 match &d.format {
464 FormatSpecifier::Aggregated {
465 module_version,
466 module_id,
467 block_size,
468 module_size,
469 compression_method,
470 original_size,
471 timeout,
472 object_key,
473 } => {
474 assert_eq!(*module_version, 3);
475 assert_eq!(*module_id, 5);
476 assert_eq!(*block_size, 1024);
477 assert_eq!(*module_size, 4096);
478 assert_eq!(*compression_method, 0);
479 assert_eq!(*original_size, 4096);
480 assert_eq!(*timeout, 30);
481 assert_eq!(*object_key, &[0xAB]);
482 }
483 other => panic!("expected Aggregated, got {other:?}"),
484 }
485 }
486
487 #[test]
488 fn byte_anchor_round_trip_byte_identical() {
489 let d = CarouselIdentifierDescriptor::parse(ANCHOR_BYTES).unwrap();
491 let mut buf = vec![0u8; d.serialized_len()];
492 d.serialize_into(&mut buf).unwrap();
493 assert_eq!(
494 buf.as_slice(),
495 ANCHOR_BYTES,
496 "round-trip not byte-identical"
497 );
498 let d2 = anchor_descriptor();
500 let mut buf2 = vec![0u8; d2.serialized_len()];
501 d2.serialize_into(&mut buf2).unwrap();
502 let d3 = CarouselIdentifierDescriptor::parse(&buf2).unwrap();
503 let mut buf3 = vec![0u8; d3.serialized_len()];
504 d3.serialize_into(&mut buf3).unwrap();
505 assert_eq!(buf2, buf3, "struct literal round-trip not byte-identical");
506 }
507
508 #[test]
511 fn parse_format_absent_extracts_fields() {
512 let bytes = [TAG, 0x08, 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xAA, 0xBB, 0xCC];
514 let d = CarouselIdentifierDescriptor::parse(&bytes).unwrap();
515 assert_eq!(d.carousel_id, 0xDEAD_BEEF);
516 assert_eq!(d.format, FormatSpecifier::Absent);
517 assert_eq!(d.format.format_id(), 0x00);
518 assert_eq!(d.private_data, &[0xAA, 0xBB, 0xCC]);
519 }
520
521 #[test]
522 fn round_trip_format_absent() {
523 let d = CarouselIdentifierDescriptor {
524 carousel_id: 0x0000_0042,
525 format: FormatSpecifier::Absent,
526 private_data: &[0x01, 0x02, 0x03],
527 };
528 let mut buf = vec![0u8; d.serialized_len()];
529 d.serialize_into(&mut buf).unwrap();
530 let re = CarouselIdentifierDescriptor::parse(&buf).unwrap();
531 assert_eq!(re, d);
532 }
533
534 #[test]
535 fn format_absent_no_private_data() {
536 let bytes = [TAG, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00];
538 let d = CarouselIdentifierDescriptor::parse(&bytes).unwrap();
539 assert_eq!(d.carousel_id, 0);
540 assert_eq!(d.format, FormatSpecifier::Absent);
541 assert!(d.private_data.is_empty());
542 }
543
544 #[test]
547 fn parse_format_other_carries_raw_bytes() {
548 let bytes = [TAG, 0x08, 0x00, 0x00, 0x00, 0x7F, 0x42, 0xAA, 0xBB, 0xCC];
550 let d = CarouselIdentifierDescriptor::parse(&bytes).unwrap();
551 assert_eq!(d.carousel_id, 0x7F);
552 assert_eq!(d.format.format_id(), 0x42);
553 match &d.format {
554 FormatSpecifier::Other { format_id, bytes } => {
555 assert_eq!(*format_id, 0x42);
556 assert_eq!(*bytes, &[0xAA, 0xBB, 0xCC]);
557 }
558 other => panic!("expected Other, got {other:?}"),
559 }
560 assert!(d.private_data.is_empty());
562 }
563
564 #[test]
565 fn round_trip_format_other() {
566 let d = CarouselIdentifierDescriptor {
567 carousel_id: 0x0000_00FF,
568 format: FormatSpecifier::Other {
569 format_id: 0x05,
570 bytes: &[0x11, 0x22],
571 },
572 private_data: &[],
573 };
574 let mut buf = vec![0u8; d.serialized_len()];
575 d.serialize_into(&mut buf).unwrap();
576 let re = CarouselIdentifierDescriptor::parse(&buf).unwrap();
577 assert_eq!(re, d);
578 }
579
580 #[test]
583 fn parse_rejects_wrong_tag() {
584 let err = CarouselIdentifierDescriptor::parse(&[0x14, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00])
585 .unwrap_err();
586 assert!(matches!(err, Error::InvalidDescriptor { tag: 0x14, .. }));
587 }
588
589 #[test]
590 fn parse_rejects_short_buffer() {
591 let err = CarouselIdentifierDescriptor::parse(&[TAG]).unwrap_err();
593 assert!(matches!(err, Error::BufferTooShort { .. }));
594 }
595
596 #[test]
597 fn parse_rejects_body_too_short() {
598 let err =
600 CarouselIdentifierDescriptor::parse(&[TAG, 0x04, 0x00, 0x00, 0x00, 0x01]).unwrap_err();
601 assert!(matches!(err, Error::InvalidDescriptor { .. }));
602 }
603
604 #[test]
605 fn parse_rejects_format1_fixed_truncated() {
606 let mut bytes = vec![TAG, 0x08, 0x00, 0x00, 0x00, 0x01, 0x01, 0xAA, 0xBB, 0xCC];
608 bytes[1] = (bytes.len() - 2) as u8;
609 let err = CarouselIdentifierDescriptor::parse(&bytes).unwrap_err();
610 assert!(matches!(err, Error::InvalidDescriptor { .. }));
611 }
612
613 #[test]
614 fn parse_rejects_format1_objectkey_overflow() {
615 let body = vec![
618 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x05, 0x0A, ];
629 let mut full = vec![TAG, body.len() as u8];
630 full.extend_from_slice(&body);
631 full.push(0xEE);
633 full[1] = (full.len() - 2) as u8;
634 let _ = body; let err = CarouselIdentifierDescriptor::parse(&full).unwrap_err();
639 assert!(matches!(err, Error::InvalidDescriptor { .. }));
640 }
641
642 #[test]
643 fn parse_rejects_length_overrun() {
644 let err =
646 CarouselIdentifierDescriptor::parse(&[TAG, 0x0A, 0x00, 0x00, 0x00, 0x01]).unwrap_err();
647 assert!(matches!(err, Error::BufferTooShort { .. }));
648 }
649
650 #[test]
651 fn serialize_rejects_too_small_buffer() {
652 let d = anchor_descriptor();
653 let mut tiny = [0u8; 2];
654 let err = d.serialize_into(&mut tiny).unwrap_err();
655 assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
656 }
657
658 #[cfg(feature = "serde")]
661 #[test]
662 fn serde_serialize_aggregated_fields_present() {
663 let d = anchor_descriptor();
664 let json = serde_json::to_string(&d).unwrap();
665 assert!(json.contains("\"carousel_id\""));
666 assert!(json.contains("\"format\""));
667 assert!(json.contains("Aggregated") || json.contains("aggregated"));
669 assert!(json.contains("\"module_version\""));
670 assert!(json.contains("\"timeout\""));
671 }
672
673 #[cfg(feature = "serde")]
674 #[test]
675 fn serde_serialize_absent_format() {
676 let d = CarouselIdentifierDescriptor {
677 carousel_id: 0xABCD,
678 format: FormatSpecifier::Absent,
679 private_data: &[],
680 };
681 let json = serde_json::to_string(&d).unwrap();
682 assert!(json.contains("\"carousel_id\""));
683 assert!(json.contains("Absent") || json.contains("absent"));
684 }
685}