use super::*;
impl<'a> ExtensionBodyDef<'a> for VvcSubpictures<'a> {
const TAG_EXTENSION: u8 = 0x23;
const NAME: &'static str = "VVC_SUBPICTURES";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct VvcSubpicture {
pub component_tag: u8,
pub vvc_subpicture_id: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct VvcSubpictures<'a> {
pub default_service_mode: bool,
pub subpictures: Vec<VvcSubpicture>,
pub processing_mode: u8,
pub service_description: Option<DvbText<'a>>,
}
impl<'a> Parse<'a> for VvcSubpictures<'a> {
type Error = crate::error::Error;
fn parse(sel: &'a [u8]) -> Result<Self> {
if sel.is_empty() {
return Err(Error::BufferTooShort {
need: 1,
have: sel.len(),
what: "vvc_subpictures body",
});
}
let byte0 = sel[0];
let default_service_mode = (byte0 & 0x80) != 0;
let service_description_present = (byte0 & 0x40) != 0;
let n = (byte0 & 0x3F) as usize;
let subpicture_bytes = n * 2;
let min_len = 1 + subpicture_bytes + 1;
if sel.len() < min_len {
return Err(Error::BufferTooShort {
need: min_len,
have: sel.len(),
what: "vvc_subpictures body",
});
}
let mut pos = 1;
let mut subpictures = Vec::with_capacity(n);
for _ in 0..n {
let component_tag = sel[pos];
let vvc_subpicture_id = sel[pos + 1];
subpictures.push(VvcSubpicture {
component_tag,
vvc_subpicture_id,
});
pos += 2;
}
let processing_mode = sel[pos] & 0x07;
pos += 1;
let service_description = if service_description_present {
if sel.len() < pos + 1 {
return Err(Error::BufferTooShort {
need: pos + 1,
have: sel.len(),
what: "vvc_subpictures body",
});
}
let len = sel[pos] as usize;
pos += 1;
if sel.len() < pos + len {
return Err(Error::BufferTooShort {
need: pos + len,
have: sel.len(),
what: "vvc_subpictures body",
});
}
let text = DvbText::new(&sel[pos..pos + len]);
pos += len;
Some(text)
} else {
None
};
if pos != sel.len() {
return Err(invalid("vvc_subpictures: trailing data"));
}
Ok(VvcSubpictures {
default_service_mode,
subpictures,
processing_mode,
service_description,
})
}
}
impl Serialize for VvcSubpictures<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
1 + self.subpictures.len() * 2
+ 1 + self
.service_description
.as_ref()
.map_or(0, |t| 1 + t.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
let service_description_present = self.service_description.is_some();
buf[0] = (u8::from(self.default_service_mode) << 7)
| (u8::from(service_description_present) << 6)
| (self.subpictures.len() as u8 & 0x3F);
let mut p = 1;
for sp in &self.subpictures {
buf[p] = sp.component_tag;
buf[p + 1] = sp.vvc_subpicture_id;
p += 2;
}
buf[p] = self.processing_mode & 0x07;
p += 1;
if let Some(text) = &self.service_description {
buf[p] = text.len() as u8;
p += 1;
buf[p..p + text.len()].copy_from_slice(text.raw());
}
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::descriptors::extension::test_support::*;
#[cfg(feature = "serde")]
use crate::descriptors::extension::ExtensionTag;
use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
#[cfg(feature = "serde")]
use crate::text::DvbText;
#[cfg(feature = "serde")]
#[test]
fn parse_vvc_subpictures_with_description_round_trip() {
let sel = [0xC2, 0x10, 0x01, 0x11, 0x02, 0x05, 0x02, b'H', b'i'];
let bytes = wrap(0x23, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
assert_eq!(d.kind(), Some(ExtensionTag::VvcSubpictures));
match &d.body {
ExtensionBody::VvcSubpictures(b) => {
assert!(b.default_service_mode);
assert_eq!(b.subpictures.len(), 2);
assert_eq!(b.subpictures[0].component_tag, 0x10);
assert_eq!(b.subpictures[0].vvc_subpicture_id, 0x01);
assert_eq!(b.subpictures[1].component_tag, 0x11);
assert_eq!(b.subpictures[1].vvc_subpicture_id, 0x02);
assert_eq!(b.processing_mode, 5);
assert!(b.service_description.is_some());
let desc = b.service_description.as_ref().unwrap();
assert_eq!(desc.raw(), b"Hi");
assert_eq!(desc.decode(), "Hi");
}
other => panic!("expected VvcSubpictures, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn parse_vvc_subpictures_no_description_round_trip() {
let sel = [0x01, 0x20, 0x03, 0x01];
let bytes = wrap(0x23, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::VvcSubpictures(b) => {
assert!(!b.default_service_mode);
assert_eq!(b.subpictures.len(), 1);
assert_eq!(b.subpictures[0].component_tag, 0x20);
assert_eq!(b.subpictures[0].vvc_subpicture_id, 0x03);
assert_eq!(b.processing_mode, 1);
assert!(b.service_description.is_none());
}
other => panic!("expected VvcSubpictures, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn parse_vvc_subpictures_rejects_truncated() {
let sel = [0x00]; let bytes = wrap(0x23, &sel);
assert!(matches!(
ExtensionDescriptor::parse(&bytes).unwrap_err(),
crate::error::Error::BufferTooShort { .. }
));
}
#[test]
fn parse_vvc_subpictures_rejects_overrun() {
let sel = [0xC0]; let bytes = wrap(0x23, &sel);
assert!(matches!(
ExtensionDescriptor::parse(&bytes).unwrap_err(),
crate::error::Error::BufferTooShort { .. }
));
}
#[test]
fn parse_vvc_subpictures_serialize_round_trip_no_subpictures() {
let sel = [0x80, 0x03];
let bytes = wrap(0x23, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::VvcSubpictures(b) => {
assert!(b.default_service_mode);
assert!(b.subpictures.is_empty());
assert_eq!(b.processing_mode, 3);
assert!(b.service_description.is_none());
}
other => panic!("expected VvcSubpictures, got {other:?}"),
}
round_trip(&d);
}
#[cfg(feature = "serde")]
#[test]
fn serde_serialize_vvc_subpictures() {
let d = ExtensionDescriptor {
tag_extension: 0x23,
body: ExtensionBody::VvcSubpictures(VvcSubpictures {
default_service_mode: true,
subpictures: vec![VvcSubpicture {
component_tag: 0x10,
vvc_subpicture_id: 0x01,
}],
processing_mode: 5,
service_description: Some(DvbText::new(b"Hi")),
}),
};
let json = serde_json::to_string(&d).unwrap();
assert!(json.contains("\"tag_extension\":35"));
assert!(json.contains("\"vvcSubpictures\""));
assert!(json.contains("\"service_description\":\"Hi\""));
}
}