use super::descriptor_body;
use crate::error::{Error, Result};
use dvb_common::{Parse, Serialize};
pub const TAG: u8 = 0x13;
const HEADER_LEN: usize = 2;
const CAROUSEL_ID_LEN: usize = 4;
const FORMAT_ID_LEN: usize = 1;
const BODY_PREFIX_LEN: usize = CAROUSEL_ID_LEN + FORMAT_ID_LEN;
const FS1_MODULE_VERSION_LEN: usize = 1;
const FS1_MODULE_ID_LEN: usize = 2;
const FS1_BLOCK_SIZE_LEN: usize = 2;
const FS1_MODULE_SIZE_LEN: usize = 4;
const FS1_COMPRESSION_METHOD_LEN: usize = 1;
const FS1_ORIGINAL_SIZE_LEN: usize = 4;
const FS1_TIMEOUT_LEN: usize = 1;
const FS1_OBJECT_KEY_LENGTH_LEN: usize = 1;
const FS1_FIXED_LEN: usize = FS1_MODULE_VERSION_LEN
+ FS1_MODULE_ID_LEN
+ FS1_BLOCK_SIZE_LEN
+ FS1_MODULE_SIZE_LEN
+ FS1_COMPRESSION_METHOD_LEN
+ FS1_ORIGINAL_SIZE_LEN
+ FS1_TIMEOUT_LEN
+ FS1_OBJECT_KEY_LENGTH_LEN;
const FORMAT_ID_NONE: u8 = 0x00;
const FORMAT_ID_AGGREGATED: u8 = 0x01;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum FormatSpecifier<'a> {
Absent,
Aggregated {
module_version: u8,
module_id: u16,
block_size: u16,
module_size: u32,
compression_method: u8,
original_size: u32,
timeout: u8,
#[cfg_attr(feature = "serde", serde(borrow))]
object_key: &'a [u8],
},
Other {
format_id: u8,
#[cfg_attr(feature = "serde", serde(borrow))]
bytes: &'a [u8],
},
}
impl<'a> FormatSpecifier<'a> {
#[must_use]
pub fn format_id(&self) -> u8 {
match self {
FormatSpecifier::Absent => FORMAT_ID_NONE,
FormatSpecifier::Aggregated { .. } => FORMAT_ID_AGGREGATED,
FormatSpecifier::Other { format_id, .. } => *format_id,
}
}
fn serialized_len(&self) -> usize {
match self {
FormatSpecifier::Absent => 0,
FormatSpecifier::Aggregated { object_key, .. } => FS1_FIXED_LEN + object_key.len(),
FormatSpecifier::Other { bytes, .. } => bytes.len(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct CarouselIdentifierDescriptor<'a> {
pub carousel_id: u32,
#[cfg_attr(feature = "serde", serde(borrow))]
pub format: FormatSpecifier<'a>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub private_data: &'a [u8],
}
impl<'a> Parse<'a> for CarouselIdentifierDescriptor<'a> {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"CarouselIdentifierDescriptor",
"unexpected tag for carousel_identifier_descriptor",
)?;
if body.len() < BODY_PREFIX_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "carousel_identifier_descriptor body shorter than 5 bytes",
});
}
let carousel_id = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
let format_id = body[CAROUSEL_ID_LEN];
let after_prefix = &body[BODY_PREFIX_LEN..];
let (format, private_data) = match format_id {
FORMAT_ID_NONE => (FormatSpecifier::Absent, after_prefix),
FORMAT_ID_AGGREGATED => {
if after_prefix.len() < FS1_FIXED_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "FormatId=0x01 specifier truncated (insufficient fixed fields)",
});
}
let mut pos = 0usize;
let module_version = after_prefix[pos];
pos += FS1_MODULE_VERSION_LEN;
let module_id = u16::from_be_bytes([after_prefix[pos], after_prefix[pos + 1]]);
pos += FS1_MODULE_ID_LEN;
let block_size = u16::from_be_bytes([after_prefix[pos], after_prefix[pos + 1]]);
pos += FS1_BLOCK_SIZE_LEN;
let module_size = u32::from_be_bytes([
after_prefix[pos],
after_prefix[pos + 1],
after_prefix[pos + 2],
after_prefix[pos + 3],
]);
pos += FS1_MODULE_SIZE_LEN;
let compression_method = after_prefix[pos];
pos += FS1_COMPRESSION_METHOD_LEN;
let original_size = u32::from_be_bytes([
after_prefix[pos],
after_prefix[pos + 1],
after_prefix[pos + 2],
after_prefix[pos + 3],
]);
pos += FS1_ORIGINAL_SIZE_LEN;
let timeout = after_prefix[pos];
pos += FS1_TIMEOUT_LEN;
let object_key_length = after_prefix[pos] as usize;
pos += FS1_OBJECT_KEY_LENGTH_LEN;
let specifier_end = pos + object_key_length;
if specifier_end > after_prefix.len() {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "FormatId=0x01 ObjectKeyData exceeds descriptor body",
});
}
let object_key = &after_prefix[pos..specifier_end];
let private_data = &after_prefix[specifier_end..];
(
FormatSpecifier::Aggregated {
module_version,
module_id,
block_size,
module_size,
compression_method,
original_size,
timeout,
object_key,
},
private_data,
)
}
other => {
(
FormatSpecifier::Other {
format_id: other,
bytes: after_prefix,
},
&[][..],
)
}
};
Ok(Self {
carousel_id,
format,
private_data,
})
}
}
impl Serialize for CarouselIdentifierDescriptor<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
let body = BODY_PREFIX_LEN + self.format.serialized_len() + self.private_data.len();
HEADER_LEN + body
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
let body_len = BODY_PREFIX_LEN + self.format.serialized_len() + self.private_data.len();
if body_len > u8::MAX as usize {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "carousel_identifier_descriptor body exceeds 255 bytes",
});
}
if buf.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: buf.len(),
});
}
buf[0] = TAG;
buf[1] = body_len as u8;
buf[HEADER_LEN..HEADER_LEN + CAROUSEL_ID_LEN]
.copy_from_slice(&self.carousel_id.to_be_bytes());
buf[HEADER_LEN + CAROUSEL_ID_LEN] = self.format.format_id();
let mut pos = HEADER_LEN + BODY_PREFIX_LEN;
match &self.format {
FormatSpecifier::Absent => {}
FormatSpecifier::Aggregated {
module_version,
module_id,
block_size,
module_size,
compression_method,
original_size,
timeout,
object_key,
} => {
if object_key.len() > u8::MAX as usize {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "FormatId=0x01 ObjectKeyData exceeds 255 bytes",
});
}
buf[pos] = *module_version;
pos += FS1_MODULE_VERSION_LEN;
buf[pos..pos + FS1_MODULE_ID_LEN].copy_from_slice(&module_id.to_be_bytes());
pos += FS1_MODULE_ID_LEN;
buf[pos..pos + FS1_BLOCK_SIZE_LEN].copy_from_slice(&block_size.to_be_bytes());
pos += FS1_BLOCK_SIZE_LEN;
buf[pos..pos + FS1_MODULE_SIZE_LEN].copy_from_slice(&module_size.to_be_bytes());
pos += FS1_MODULE_SIZE_LEN;
buf[pos] = *compression_method;
pos += FS1_COMPRESSION_METHOD_LEN;
buf[pos..pos + FS1_ORIGINAL_SIZE_LEN].copy_from_slice(&original_size.to_be_bytes());
pos += FS1_ORIGINAL_SIZE_LEN;
buf[pos] = *timeout;
pos += FS1_TIMEOUT_LEN;
buf[pos] = object_key.len() as u8;
pos += FS1_OBJECT_KEY_LENGTH_LEN;
buf[pos..pos + object_key.len()].copy_from_slice(object_key);
pos += object_key.len();
}
FormatSpecifier::Other { bytes, .. } => {
buf[pos..pos + bytes.len()].copy_from_slice(bytes);
pos += bytes.len();
}
}
buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
Ok(total)
}
}
impl<'a> crate::traits::DescriptorDef<'a> for CarouselIdentifierDescriptor<'a> {
const TAG: u8 = TAG;
const NAME: &'static str = "CAROUSEL_IDENTIFIER";
}
#[cfg(test)]
mod tests {
use super::*;
#[rustfmt::skip]
const ANCHOR_BYTES: &[u8] = &[
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, ];
fn anchor_descriptor() -> CarouselIdentifierDescriptor<'static> {
CarouselIdentifierDescriptor {
carousel_id: 1,
format: FormatSpecifier::Aggregated {
module_version: 3,
module_id: 5,
block_size: 1024,
module_size: 4096,
compression_method: 0,
original_size: 4096,
timeout: 30,
object_key: &[0xAB],
},
private_data: &[0xDE, 0xAD],
}
}
#[test]
fn byte_anchor_serialize_matches_hand_built() {
let d = anchor_descriptor();
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(
buf.as_slice(),
ANCHOR_BYTES,
"serialized bytes differ from anchor"
);
}
#[test]
fn byte_anchor_parse_extracts_correct_fields() {
let d = CarouselIdentifierDescriptor::parse(ANCHOR_BYTES).unwrap();
assert_eq!(d.carousel_id, 1);
assert_eq!(d.private_data, &[0xDE, 0xAD]);
match &d.format {
FormatSpecifier::Aggregated {
module_version,
module_id,
block_size,
module_size,
compression_method,
original_size,
timeout,
object_key,
} => {
assert_eq!(*module_version, 3);
assert_eq!(*module_id, 5);
assert_eq!(*block_size, 1024);
assert_eq!(*module_size, 4096);
assert_eq!(*compression_method, 0);
assert_eq!(*original_size, 4096);
assert_eq!(*timeout, 30);
assert_eq!(*object_key, &[0xAB]);
}
other => panic!("expected Aggregated, got {other:?}"),
}
}
#[test]
fn byte_anchor_round_trip_byte_identical() {
let d = CarouselIdentifierDescriptor::parse(ANCHOR_BYTES).unwrap();
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(
buf.as_slice(),
ANCHOR_BYTES,
"round-trip not byte-identical"
);
let d2 = anchor_descriptor();
let mut buf2 = vec![0u8; d2.serialized_len()];
d2.serialize_into(&mut buf2).unwrap();
let d3 = CarouselIdentifierDescriptor::parse(&buf2).unwrap();
let mut buf3 = vec![0u8; d3.serialized_len()];
d3.serialize_into(&mut buf3).unwrap();
assert_eq!(buf2, buf3, "struct literal round-trip not byte-identical");
}
#[test]
fn parse_format_absent_extracts_fields() {
let bytes = [TAG, 0x08, 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xAA, 0xBB, 0xCC];
let d = CarouselIdentifierDescriptor::parse(&bytes).unwrap();
assert_eq!(d.carousel_id, 0xDEAD_BEEF);
assert_eq!(d.format, FormatSpecifier::Absent);
assert_eq!(d.format.format_id(), 0x00);
assert_eq!(d.private_data, &[0xAA, 0xBB, 0xCC]);
}
#[test]
fn round_trip_format_absent() {
let d = CarouselIdentifierDescriptor {
carousel_id: 0x0000_0042,
format: FormatSpecifier::Absent,
private_data: &[0x01, 0x02, 0x03],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = CarouselIdentifierDescriptor::parse(&buf).unwrap();
assert_eq!(re, d);
}
#[test]
fn format_absent_no_private_data() {
let bytes = [TAG, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00];
let d = CarouselIdentifierDescriptor::parse(&bytes).unwrap();
assert_eq!(d.carousel_id, 0);
assert_eq!(d.format, FormatSpecifier::Absent);
assert!(d.private_data.is_empty());
}
#[test]
fn parse_format_other_carries_raw_bytes() {
let bytes = [TAG, 0x08, 0x00, 0x00, 0x00, 0x7F, 0x42, 0xAA, 0xBB, 0xCC];
let d = CarouselIdentifierDescriptor::parse(&bytes).unwrap();
assert_eq!(d.carousel_id, 0x7F);
assert_eq!(d.format.format_id(), 0x42);
match &d.format {
FormatSpecifier::Other { format_id, bytes } => {
assert_eq!(*format_id, 0x42);
assert_eq!(*bytes, &[0xAA, 0xBB, 0xCC]);
}
other => panic!("expected Other, got {other:?}"),
}
assert!(d.private_data.is_empty());
}
#[test]
fn round_trip_format_other() {
let d = CarouselIdentifierDescriptor {
carousel_id: 0x0000_00FF,
format: FormatSpecifier::Other {
format_id: 0x05,
bytes: &[0x11, 0x22],
},
private_data: &[],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = CarouselIdentifierDescriptor::parse(&buf).unwrap();
assert_eq!(re, d);
}
#[test]
fn parse_rejects_wrong_tag() {
let err = CarouselIdentifierDescriptor::parse(&[0x14, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00])
.unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { tag: 0x14, .. }));
}
#[test]
fn parse_rejects_short_buffer() {
let err = CarouselIdentifierDescriptor::parse(&[TAG]).unwrap_err();
assert!(matches!(err, Error::BufferTooShort { .. }));
}
#[test]
fn parse_rejects_body_too_short() {
let err =
CarouselIdentifierDescriptor::parse(&[TAG, 0x04, 0x00, 0x00, 0x00, 0x01]).unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { .. }));
}
#[test]
fn parse_rejects_format1_fixed_truncated() {
let mut bytes = vec![TAG, 0x08, 0x00, 0x00, 0x00, 0x01, 0x01, 0xAA, 0xBB, 0xCC];
bytes[1] = (bytes.len() - 2) as u8;
let err = CarouselIdentifierDescriptor::parse(&bytes).unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { .. }));
}
#[test]
fn parse_rejects_format1_objectkey_overflow() {
let body = vec![
0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x05, 0x0A, ];
let mut full = vec![TAG, body.len() as u8];
full.extend_from_slice(&body);
full.push(0xEE);
full[1] = (full.len() - 2) as u8;
let _ = body; let err = CarouselIdentifierDescriptor::parse(&full).unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { .. }));
}
#[test]
fn parse_rejects_length_overrun() {
let err =
CarouselIdentifierDescriptor::parse(&[TAG, 0x0A, 0x00, 0x00, 0x00, 0x01]).unwrap_err();
assert!(matches!(err, Error::BufferTooShort { .. }));
}
#[test]
fn serialize_rejects_too_small_buffer() {
let d = anchor_descriptor();
let mut tiny = [0u8; 2];
let err = d.serialize_into(&mut tiny).unwrap_err();
assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
}
#[cfg(feature = "serde")]
#[test]
fn serde_serialize_aggregated_fields_present() {
let d = anchor_descriptor();
let json = serde_json::to_string(&d).unwrap();
assert!(json.contains("\"carousel_id\""));
assert!(json.contains("\"format\""));
assert!(json.contains("Aggregated") || json.contains("aggregated"));
assert!(json.contains("\"module_version\""));
assert!(json.contains("\"timeout\""));
}
#[cfg(feature = "serde")]
#[test]
fn serde_serialize_absent_format() {
let d = CarouselIdentifierDescriptor {
carousel_id: 0xABCD,
format: FormatSpecifier::Absent,
private_data: &[],
};
let json = serde_json::to_string(&d).unwrap();
assert!(json.contains("\"carousel_id\""));
assert!(json.contains("Absent") || json.contains("absent"));
}
}