use super::descriptor_body;
use crate::error::{Error, Result};
use dvb_common::{Parse, Serialize};
pub const TAG: u8 = 0x66;
const HEADER_LEN: usize = 2;
const ID_LEN: usize = 2;
pub const DATA_BROADCAST_ID_SSU: u16 = 0x000A;
const SSU_OUI_DATA_LENGTH_LEN: usize = 1;
const SSU_OUI_FIXED_LEN: usize = 6;
const SSU_UPDATE_TYPE_MASK: u8 = 0x0F;
const SSU_UPDATE_VERSIONING_FLAG_MASK: u8 = 0x20;
const SSU_UPDATE_VERSION_MASK: u8 = 0x1F;
#[must_use]
pub fn data_broadcast_id_name(id: u16) -> Option<&'static str> {
match id {
0x0005 => Some("Multiprotocol Encapsulation (MPE)"),
0x0006 => Some("Data Carousel"),
0x0007 => Some("Object Carousel"),
DATA_BROADCAST_ID_SSU => Some("System Software Update (SSU)"),
0x000B => Some("IP/MAC Notification (INT)"),
0x00F0 => Some("MHP Object Carousel"),
0x0123 => Some("HbbTV"),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SsuOuiEntry<'a> {
pub oui: [u8; 3],
pub update_type: u8,
pub update_versioning_flag: bool,
pub update_version: u8,
#[cfg_attr(feature = "serde", serde(borrow))]
pub selector: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SsuIdSelector<'a> {
pub oui_entries: Vec<SsuOuiEntry<'a>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub private_data: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum IdSelector<'a> {
Ssu(SsuIdSelector<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
Raw(&'a [u8]),
}
impl<'a> IdSelector<'a> {
pub fn parse(data_broadcast_id: u16, bytes: &'a [u8]) -> Result<Self> {
if data_broadcast_id == DATA_BROADCAST_ID_SSU {
Ok(IdSelector::Ssu(SsuIdSelector::parse(bytes)?))
} else {
Ok(IdSelector::Raw(bytes))
}
}
pub fn serialized_len(&self) -> usize {
match self {
IdSelector::Ssu(s) => s.serialized_len(),
IdSelector::Raw(b) => b.len(),
}
}
pub fn serialize_into_at(&self, buf: &mut [u8], pos: usize) -> Result<usize> {
match self {
IdSelector::Ssu(s) => s.serialize_into(&mut buf[pos..]),
IdSelector::Raw(b) => {
buf[pos..pos + b.len()].copy_from_slice(b);
Ok(b.len())
}
}
}
}
impl<'a> Parse<'a> for SsuIdSelector<'a> {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
if bytes.len() < SSU_OUI_DATA_LENGTH_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "SSU id_selector too short for OUI_data_length",
});
}
let oui_data_length = bytes[0] as usize;
let oui_loop_end = SSU_OUI_DATA_LENGTH_LEN + oui_data_length;
if oui_loop_end > bytes.len() {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "SSU id_selector OUI_data_length exceeds available bytes",
});
}
let mut pos = SSU_OUI_DATA_LENGTH_LEN;
let mut oui_entries = Vec::new();
while pos < oui_loop_end {
if pos + SSU_OUI_FIXED_LEN > oui_loop_end {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "SSU id_selector OUI entry truncated",
});
}
let oui = [bytes[pos], bytes[pos + 1], bytes[pos + 2]];
let byte3 = bytes[pos + 3]; let byte4 = bytes[pos + 4]; let selector_length = bytes[pos + 5] as usize;
pos += SSU_OUI_FIXED_LEN;
if pos + selector_length > oui_loop_end {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "SSU id_selector OUI selector_length exceeds OUI loop",
});
}
let selector = &bytes[pos..pos + selector_length];
pos += selector_length;
oui_entries.push(SsuOuiEntry {
oui,
update_type: byte3 & SSU_UPDATE_TYPE_MASK,
update_versioning_flag: (byte4 & SSU_UPDATE_VERSIONING_FLAG_MASK) != 0,
update_version: byte4 & SSU_UPDATE_VERSION_MASK,
selector,
});
}
let private_data = &bytes[oui_loop_end..];
Ok(SsuIdSelector {
oui_entries,
private_data,
})
}
}
impl Serialize for SsuIdSelector<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
let oui_body: usize = self
.oui_entries
.iter()
.map(|e| SSU_OUI_FIXED_LEN + e.selector.len())
.sum();
SSU_OUI_DATA_LENGTH_LEN + oui_body + self.private_data.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 oui_body: usize = self
.oui_entries
.iter()
.map(|e| SSU_OUI_FIXED_LEN + e.selector.len())
.sum();
if oui_body > u8::MAX as usize {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "SSU OUI loop exceeds 255 bytes (OUI_data_length field overflow)",
});
}
buf[0] = oui_body as u8;
let mut pos = SSU_OUI_DATA_LENGTH_LEN;
for e in &self.oui_entries {
if e.selector.len() > u8::MAX as usize {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "SSU OUI entry selector exceeds 255 bytes",
});
}
buf[pos..pos + 3].copy_from_slice(&e.oui);
buf[pos + 3] = e.update_type & SSU_UPDATE_TYPE_MASK; let uvf_bit: u8 = if e.update_versioning_flag {
SSU_UPDATE_VERSIONING_FLAG_MASK
} else {
0
};
buf[pos + 4] = uvf_bit | (e.update_version & SSU_UPDATE_VERSION_MASK);
buf[pos + 5] = e.selector.len() as u8;
pos += SSU_OUI_FIXED_LEN;
buf[pos..pos + e.selector.len()].copy_from_slice(e.selector);
pos += e.selector.len();
}
buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct DataBroadcastIdDescriptor<'a> {
pub data_broadcast_id: u16,
#[cfg_attr(feature = "serde", serde(borrow))]
pub id_selector: &'a [u8],
}
impl<'a> DataBroadcastIdDescriptor<'a> {
pub fn id_selector_decoded(&self) -> Result<IdSelector<'a>> {
IdSelector::parse(self.data_broadcast_id, self.id_selector)
}
}
impl<'a> Parse<'a> for DataBroadcastIdDescriptor<'a> {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"DataBroadcastIdDescriptor",
"unexpected tag for data_broadcast_id_descriptor",
)?;
if body.len() < ID_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "data_broadcast_id_descriptor body shorter than 2 bytes",
});
}
let data_broadcast_id = u16::from_be_bytes([body[0], body[1]]);
let id_selector = &body[ID_LEN..];
Ok(Self {
data_broadcast_id,
id_selector,
})
}
}
impl Serialize for DataBroadcastIdDescriptor<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
HEADER_LEN + ID_LEN + self.id_selector.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
let body = ID_LEN + self.id_selector.len();
if body > u8::MAX as usize {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "data_broadcast_id_descriptor body exceeds 255 bytes",
});
}
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
buf[0] = TAG;
buf[1] = body as u8;
buf[HEADER_LEN..HEADER_LEN + ID_LEN].copy_from_slice(&self.data_broadcast_id.to_be_bytes());
buf[HEADER_LEN + ID_LEN..len].copy_from_slice(self.id_selector);
Ok(len)
}
}
impl<'a> crate::traits::DescriptorDef<'a> for DataBroadcastIdDescriptor<'a> {
const TAG: u8 = TAG;
const NAME: &'static str = "DATA_BROADCAST_ID";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_extracts_id_and_raw_selector() {
let bytes = [TAG, 0x05, 0x00, 0x0B, 0xAA, 0xBB, 0xCC];
let d = DataBroadcastIdDescriptor::parse(&bytes).unwrap();
assert_eq!(d.data_broadcast_id, 0x000B);
assert_eq!(d.id_selector, &[0xAA, 0xBB, 0xCC][..]);
assert_eq!(
d.id_selector_decoded().unwrap(),
IdSelector::Raw(&[0xAA, 0xBB, 0xCC])
);
}
#[test]
fn parse_accepts_empty_raw_selector() {
let bytes = [TAG, 0x02, 0x00, 0x0B];
let d = DataBroadcastIdDescriptor::parse(&bytes).unwrap();
assert_eq!(d.data_broadcast_id, 0x000B);
assert!(d.id_selector.is_empty());
assert_eq!(d.id_selector_decoded().unwrap(), IdSelector::Raw(&[]));
}
#[test]
fn data_broadcast_id_name_verified() {
assert_eq!(
data_broadcast_id_name(0x0005),
Some("Multiprotocol Encapsulation (MPE)")
);
assert_eq!(data_broadcast_id_name(0x0006), Some("Data Carousel"));
assert_eq!(data_broadcast_id_name(0x0007), Some("Object Carousel"));
assert_eq!(
data_broadcast_id_name(DATA_BROADCAST_ID_SSU),
Some("System Software Update (SSU)")
);
assert_eq!(
data_broadcast_id_name(0x000B),
Some("IP/MAC Notification (INT)")
);
assert_eq!(data_broadcast_id_name(0x00F0), Some("MHP Object Carousel"));
assert_eq!(data_broadcast_id_name(0x0123), Some("HbbTV"));
}
#[test]
fn data_broadcast_id_name_removed_entries_return_none() {
assert_eq!(data_broadcast_id_name(0x00F1), None);
assert_eq!(data_broadcast_id_name(0x00F2), None);
assert_eq!(data_broadcast_id_name(0x00F3), None);
assert_eq!(data_broadcast_id_name(0x00F4), None);
}
#[test]
fn data_broadcast_id_name_unknown() {
assert_eq!(data_broadcast_id_name(0x0000), None);
assert_eq!(data_broadcast_id_name(0xFFFF), None);
}
#[test]
fn parse_rejects_wrong_tag() {
let err = DataBroadcastIdDescriptor::parse(&[0x65, 0x02, 0x00, 0x0A]).unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { tag: 0x65, .. }));
}
#[test]
fn parse_rejects_short_buffer() {
let err = DataBroadcastIdDescriptor::parse(&[TAG]).unwrap_err();
assert!(matches!(err, Error::BufferTooShort { .. }));
}
#[test]
fn parse_rejects_body_too_short() {
let err = DataBroadcastIdDescriptor::parse(&[TAG, 0x01, 0x00]).unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { .. }));
}
#[test]
fn parse_rejects_length_overrun() {
let err = DataBroadcastIdDescriptor::parse(&[TAG, 0x05, 0x00, 0x0B, 0xAA]).unwrap_err();
assert!(matches!(err, Error::BufferTooShort { .. }));
}
#[test]
fn raw_round_trip() {
let d = DataBroadcastIdDescriptor {
data_broadcast_id: 0x0123,
id_selector: &[0xDE, 0xAD, 0xBE, 0xEF],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = DataBroadcastIdDescriptor::parse(&buf).unwrap();
assert_eq!(d, re);
}
#[test]
fn serialize_rejects_too_small_buffer() {
let d = DataBroadcastIdDescriptor {
data_broadcast_id: 0x0001,
id_selector: &[0x01],
};
let mut tiny = [0u8; 2];
let err = d.serialize_into(&mut tiny).unwrap_err();
assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
}
#[test]
fn serialize_rejects_over_range_body() {
let sel = vec![0u8; 254];
let d = DataBroadcastIdDescriptor {
data_broadcast_id: 0x0001,
id_selector: &sel,
};
let mut buf = vec![0u8; d.serialized_len()];
let err = d.serialize_into(&mut buf).unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
}
#[cfg(feature = "serde")]
#[test]
fn serde_serialize_is_stable() {
let d = DataBroadcastIdDescriptor {
data_broadcast_id: 0x000B,
id_selector: &[0x01, 0x02],
};
let json = serde_json::to_string(&d).unwrap();
assert!(json.contains("\"data_broadcast_id\""));
assert!(json.contains("\"id_selector\""));
assert!(json.contains("11"));
}
fn sample_ssu_selector() -> SsuIdSelector<'static> {
SsuIdSelector {
oui_entries: vec![SsuOuiEntry {
oui: [0x00, 0x15, 0x0A],
update_type: 0x02, update_versioning_flag: true,
update_version: 0x01,
selector: &[0xAA, 0xBB, 0xCC],
}],
private_data: &[],
}
}
#[test]
fn ssu_selector_hand_built_byte_anchor() {
#[rustfmt::skip]
let expected: &[u8] = &[
0x09, 0x00, 0x15, 0x0A, 0x02, 0x21, 0x03, 0xAA, 0xBB, 0xCC, ];
let s = sample_ssu_selector();
let mut buf = vec![0u8; s.serialized_len()];
s.serialize_into(&mut buf).unwrap();
assert_eq!(buf.as_slice(), expected);
let re = SsuIdSelector::parse(expected).unwrap();
assert_eq!(re, s);
}
#[test]
fn ssu_selector_round_trip() {
let s = sample_ssu_selector();
let mut buf = vec![0u8; s.serialized_len()];
s.serialize_into(&mut buf).unwrap();
let re = SsuIdSelector::parse(&buf).unwrap();
assert_eq!(re, s);
let mut buf2 = vec![0u8; re.serialized_len()];
re.serialize_into(&mut buf2).unwrap();
assert_eq!(buf, buf2, "SSU selector byte-exact re-serialize");
}
#[test]
fn ssu_selector_empty_oui_loop() {
let s = SsuIdSelector {
oui_entries: vec![],
private_data: &[0xDE, 0xAD],
};
let mut buf = vec![0u8; s.serialized_len()];
s.serialize_into(&mut buf).unwrap();
assert_eq!(buf[0], 0x00);
assert_eq!(&buf[1..], &[0xDE, 0xAD]);
let re = SsuIdSelector::parse(&buf).unwrap();
assert_eq!(re, s);
}
#[test]
fn ssu_selector_no_versioning_flag() {
let s = SsuIdSelector {
oui_entries: vec![SsuOuiEntry {
oui: [0x00, 0x00, 0x00],
update_type: 0x01,
update_versioning_flag: false,
update_version: 0x00,
selector: &[],
}],
private_data: &[],
};
let mut buf = vec![0u8; s.serialized_len()];
s.serialize_into(&mut buf).unwrap();
assert_eq!(buf[4], 0x01); assert_eq!(buf[5], 0x00); let re = SsuIdSelector::parse(&buf).unwrap();
assert_eq!(re, s);
}
#[test]
fn descriptor_ssu_round_trip() {
let sel = sample_ssu_selector();
let mut sel_bytes = vec![0u8; sel.serialized_len()];
sel.serialize_into(&mut sel_bytes).unwrap();
let d = DataBroadcastIdDescriptor {
data_broadcast_id: DATA_BROADCAST_ID_SSU,
id_selector: &sel_bytes,
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(buf[0], TAG);
assert_eq!(buf[1] as usize, ID_LEN + sel.serialized_len());
assert_eq!(&buf[2..4], &[0x00, 0x0A]); let re = DataBroadcastIdDescriptor::parse(&buf).unwrap();
assert_eq!(re, d);
assert_eq!(
re.id_selector_decoded().unwrap(),
IdSelector::Ssu(sample_ssu_selector())
);
let mut buf2 = vec![0u8; re.serialized_len()];
re.serialize_into(&mut buf2).unwrap();
assert_eq!(buf, buf2, "SSU descriptor byte-exact re-serialize");
}
#[test]
fn ssu_selector_parse_rejects_truncated_oui_loop() {
let bytes = &[0x06, 0x00, 0x15];
assert!(matches!(
SsuIdSelector::parse(bytes).unwrap_err(),
Error::InvalidDescriptor { .. }
));
}
#[test]
fn ssu_selector_parse_rejects_selector_overflows_oui_loop() {
#[rustfmt::skip]
let bytes = &[
0x06, 0x00, 0x15, 0x0A, 0x02, 0x21, 0x05, ];
assert!(matches!(
SsuIdSelector::parse(bytes).unwrap_err(),
Error::InvalidDescriptor { .. }
));
}
#[cfg(feature = "serde")]
#[test]
fn ssu_descriptor_serde_json() {
let sel = sample_ssu_selector();
let mut sel_bytes = vec![0u8; sel.serialized_len()];
sel.serialize_into(&mut sel_bytes).unwrap();
let d = DataBroadcastIdDescriptor {
data_broadcast_id: DATA_BROADCAST_ID_SSU,
id_selector: &sel_bytes,
};
let json = serde_json::to_string(&d).unwrap();
assert!(json.contains("\"data_broadcast_id\":10"));
let decoded = d.id_selector_decoded().unwrap();
let sj = serde_json::to_string(&decoded).unwrap();
assert!(sj.contains("\"update_type\":2"));
assert!(sj.contains("\"update_versioning_flag\":true"));
assert!(sj.contains("\"update_version\":1"));
}
}