use alloc::vec;
use alloc::vec::Vec;
use broadcast_common::len::need_out;
use super::{
BINDING_NCONTEXT, BINDING_NOBJECT, BIOP_MAGIC, BIOP_VERSION_MAJOR, BIOP_VERSION_MINOR,
BYTE_ORDER_BIG_ENDIAN,
ior::{Ior, NameComponent, Tap},
};
pub use crate::descriptors::carousel::CompressedModuleDescriptor;
use crate::descriptors::carousel::{AnyCarouselDescriptor, CarouselDescriptorLoop};
use crate::error::{Error, Result};
use broadcast_common::{Parse, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum BindingType {
NObject,
NContext,
Reserved(u8),
}
impl BindingType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
BINDING_NOBJECT => Self::NObject,
BINDING_NCONTEXT => Self::NContext,
v => Self::Reserved(v),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::NObject => BINDING_NOBJECT,
Self::NContext => BINDING_NCONTEXT,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::NObject => "nobject",
Self::NContext => "ncontext",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(BindingType, Reserved);
const BIOP_HEADER_LEN: usize = 12;
const OBJECT_KEY_LEN_FIELD: usize = 1;
const OBJECT_KIND_LEN_FIELD: usize = 4;
const OBJECT_KIND_DATA_LEN: usize = 4;
const OBJECT_INFO_LEN_FIELD: usize = 2;
const SERVICE_CONTEXT_COUNT_FIELD: usize = 1;
const SERVICE_CONTEXT_FIXED: usize = 6;
const MESSAGE_BODY_LEN_FIELD: usize = 4;
const BINDINGS_COUNT_FIELD: usize = 2;
const BINDING_NAME_COUNT_FIELD: usize = 1;
const BINDING_TYPE_FIELD: usize = 1;
const BINDING_OBJ_INFO_LEN_FIELD: usize = 2;
const FILE_CONTENT_LEN_FIELD: usize = 4;
const FILE_CONTENT_SIZE_LEN: usize = 8;
const STREAM_ADESC_LEN_FIELD: usize = 1;
const STREAM_INFO_FIXED: usize = 9;
const STREAM_TAPS_COUNT_FIELD: usize = 1;
const STREAM_EVENT_NAMES_COUNT_FIELD: usize = 2;
const STREAM_EVENT_NAME_LEN_FIELD: usize = 1;
const STREAM_EVENT_IDS_COUNT_FIELD: usize = 1;
const STREAM_EVENT_ID_LEN: usize = 2;
const MODULE_INFO_FIXED: usize = 12;
const MODULE_TAPS_COUNT_FIELD: usize = 1;
const MODULE_USER_INFO_LEN_FIELD: usize = 1;
const SGI_DOWNLOAD_TAPS_COUNT_FIELD: usize = 1;
const SGI_USER_INFO_LEN_FIELD: usize = 2;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Binding<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: Vec<NameComponent<'a>>,
pub binding_type: BindingType,
pub ior: Ior<'a>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_info: &'a [u8],
}
impl<'a> Binding<'a> {
fn parse_from(bytes: &'a [u8], pos: usize, end: usize) -> Result<(Self, usize)> {
if pos + BINDING_NAME_COUNT_FIELD > end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: pos + BINDING_NAME_COUNT_FIELD,
have: end,
what: "Binding nameComponents_count",
},
));
}
let name_count = bytes[pos] as usize;
let mut cur = pos + BINDING_NAME_COUNT_FIELD;
let mut name = Vec::with_capacity(name_count.min(4));
for _ in 0..name_count {
let (nc, next) = NameComponent::parse_8bit(bytes, cur, end)?;
name.push(nc);
cur = next;
}
if cur + BINDING_TYPE_FIELD > end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + BINDING_TYPE_FIELD,
have: end,
what: "Binding bindingType",
},
));
}
let binding_type = BindingType::from_u8(bytes[cur]);
cur += BINDING_TYPE_FIELD;
let ior_slice = &bytes[cur..end];
let (ior, ior_len) = Ior::parse_at(ior_slice)?;
cur += ior_len;
let (boi, _) = bytes[cur..end]
.split_first_chunk::<2>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + BINDING_OBJ_INFO_LEN_FIELD,
have: end,
what: "Binding objectInfo_length",
},
))?;
let obj_info_len = u16::from_be_bytes(*boi) as usize;
cur += BINDING_OBJ_INFO_LEN_FIELD;
if cur + obj_info_len > end {
return Err(Error::SectionLengthOverflow {
declared: obj_info_len,
available: end - cur,
});
}
let object_info = &bytes[cur..cur + obj_info_len];
cur += obj_info_len;
Ok((
Binding {
name,
binding_type,
ior,
object_info,
},
cur,
))
}
fn serialized_len(&self) -> usize {
let name_len: usize = self.name.iter().map(|n| n.serialized_len_8bit()).sum();
BINDING_NAME_COUNT_FIELD
+ name_len
+ BINDING_TYPE_FIELD
+ self.ior.serialized_len()
+ BINDING_OBJ_INFO_LEN_FIELD
+ self.object_info.len()
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
need_out(buf, len)?;
if self.name.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.name.len(),
available: u8::MAX as usize,
});
}
buf[0] = broadcast_common::len::fit_u8(self.name.len(), "self.name")?;
let mut pos = BINDING_NAME_COUNT_FIELD;
for nc in &self.name {
let written = nc.serialize_8bit(&mut buf[pos..])?;
pos += written;
}
buf[pos] = self.binding_type.to_u8();
pos += BINDING_TYPE_FIELD;
let written = self.ior.serialize_into(&mut buf[pos..])?;
pos += written;
buf[pos..pos + 2].copy_from_slice(
&broadcast_common::len::fit_u16(self.object_info.len(), "self.object_info")?
.to_be_bytes(),
);
pos += BINDING_OBJ_INFO_LEN_FIELD;
buf[pos..pos + self.object_info.len()].copy_from_slice(self.object_info);
pos += self.object_info.len();
Ok(pos)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ServiceContext<'a> {
pub context_id: u32,
#[cfg_attr(feature = "serde", serde(borrow))]
pub data: &'a [u8],
}
fn parse_biop_header(bytes: &[u8]) -> Result<(&[u8], [u8; 4], usize, usize)> {
let total = bytes.len();
let (bhdr, _) = bytes
.split_first_chunk::<BIOP_HEADER_LEN>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: BIOP_HEADER_LEN,
have: total,
what: "BIOP message header",
},
))?;
let magic = u32::from_be_bytes([bhdr[0], bhdr[1], bhdr[2], bhdr[3]]);
if magic != BIOP_MAGIC {
return Err(Error::InvalidSync(broadcast_common::error::InvalidSync {
what: "BIOP magic",
expected: BIOP_MAGIC,
found: magic,
}));
}
if bhdr[4] != BIOP_VERSION_MAJOR || bhdr[5] != BIOP_VERSION_MINOR {
return Err(Error::InvalidValue(broadcast_common::error::InvalidValue {
field: "biop_version",
value: u64::from(u16::from_be_bytes([bhdr[4], bhdr[5]])),
reason: "must be 1.0 (0x0100)",
}));
}
if bhdr[6] != BYTE_ORDER_BIG_ENDIAN {
return Err(Error::InvalidValue(broadcast_common::error::InvalidValue {
field: "byte_order",
value: u64::from(bhdr[6]),
reason: "must be 0x00 (big-endian)",
}));
}
let message_size_wire = u32::from_be_bytes([bhdr[8], bhdr[9], bhdr[10], bhdr[11]]);
let end = super::span(BIOP_HEADER_LEN, message_size_wire, total)?.end;
let message_size = message_size_wire as usize;
let mut pos = BIOP_HEADER_LEN;
if pos + OBJECT_KEY_LEN_FIELD > end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: pos + OBJECT_KEY_LEN_FIELD,
have: end,
what: "BIOP objectKey_length",
},
));
}
let obj_key_len = bytes[pos] as usize;
pos += OBJECT_KEY_LEN_FIELD;
if pos + obj_key_len > end {
return Err(Error::SectionLengthOverflow {
declared: obj_key_len,
available: end - pos,
});
}
let object_key = &bytes[pos..pos + obj_key_len];
pos += obj_key_len;
let (bkl, _) = bytes[pos..end]
.split_first_chunk::<4>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: pos + OBJECT_KIND_LEN_FIELD,
have: end,
what: "BIOP objectKind_length",
},
))?;
let kind_len = u32::from_be_bytes(*bkl) as usize;
pos += OBJECT_KIND_LEN_FIELD;
if kind_len != OBJECT_KIND_DATA_LEN {
return Err(Error::ValueOutOfRange {
field: "objectKind_length",
reason: "DVB BIOP objectKind must be exactly 4 bytes",
});
}
if pos + OBJECT_KIND_DATA_LEN > end {
return Err(Error::SectionLengthOverflow {
declared: OBJECT_KIND_DATA_LEN,
available: end - pos,
});
}
let mut kind_bytes = [0u8; 4];
kind_bytes.copy_from_slice(&bytes[pos..pos + 4]);
pos += OBJECT_KIND_DATA_LEN;
Ok((object_key, kind_bytes, message_size, pos))
}
fn parse_service_context_list(
bytes: &[u8],
pos: usize,
end: usize,
) -> Result<(Vec<ServiceContext<'_>>, usize)> {
if pos + SERVICE_CONTEXT_COUNT_FIELD > end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: pos + SERVICE_CONTEXT_COUNT_FIELD,
have: end,
what: "serviceContextList_count",
},
));
}
let count = bytes[pos] as usize;
let mut cur = pos + SERVICE_CONTEXT_COUNT_FIELD;
let mut list = Vec::with_capacity(count.min(16));
for _ in 0..count {
let (sch, _) = bytes[cur..end]
.split_first_chunk::<SERVICE_CONTEXT_FIXED>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + SERVICE_CONTEXT_FIXED,
have: end,
what: "serviceContext entry",
},
))?;
let context_id = u32::from_be_bytes([sch[0], sch[1], sch[2], sch[3]]);
let ctx_data_len = u16::from_be_bytes([sch[4], sch[5]]) as usize;
cur += SERVICE_CONTEXT_FIXED;
if cur + ctx_data_len > end {
return Err(Error::SectionLengthOverflow {
declared: ctx_data_len,
available: end - cur,
});
}
let data = &bytes[cur..cur + ctx_data_len];
cur += ctx_data_len;
list.push(ServiceContext { context_id, data });
}
Ok((list, cur))
}
fn service_context_list_len(list: &[ServiceContext]) -> usize {
SERVICE_CONTEXT_COUNT_FIELD
+ list
.iter()
.map(|e| SERVICE_CONTEXT_FIXED + e.data.len())
.sum::<usize>()
}
fn write_service_context_list(buf: &mut [u8], list: &[ServiceContext]) -> Result<usize> {
if list.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: list.len(),
available: u8::MAX as usize,
});
}
buf[0] = broadcast_common::len::fit_u8(list.len(), "list")?;
let mut pos = SERVICE_CONTEXT_COUNT_FIELD;
for entry in list {
if entry.data.len() > u16::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: entry.data.len(),
available: u16::MAX as usize,
});
}
buf[pos..pos + 4].copy_from_slice(&entry.context_id.to_be_bytes());
buf[pos + 4..pos + 6].copy_from_slice(
&broadcast_common::len::fit_u16(entry.data.len(), "entry.data")?.to_be_bytes(),
);
pos += SERVICE_CONTEXT_FIXED;
buf[pos..pos + entry.data.len()].copy_from_slice(entry.data);
pos += entry.data.len();
}
Ok(pos)
}
fn write_biop_header(buf: &mut [u8], message_size: u32) {
buf[0..4].copy_from_slice(&BIOP_MAGIC.to_be_bytes());
buf[4] = BIOP_VERSION_MAJOR;
buf[5] = BIOP_VERSION_MINOR;
buf[6] = BYTE_ORDER_BIG_ENDIAN;
buf[7] = 0x00; buf[8..12].copy_from_slice(&message_size.to_be_bytes());
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DirectoryMessage<'a> {
pub object_kind: [u8; 4],
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_key: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_info: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow))]
pub service_context: Vec<ServiceContext<'a>>,
pub bindings: Vec<Binding<'a>>,
}
impl<'a> DirectoryMessage<'a> {
pub fn is_service_gateway(&self) -> bool {
&self.object_kind == b"srg\0"
}
fn parse_from(
bytes: &'a [u8],
object_key: &'a [u8],
object_kind: [u8; 4],
pos: usize,
end: usize,
) -> Result<Self> {
let mut cur = pos;
let (bdoi, _) = bytes[cur..end]
.split_first_chunk::<2>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + OBJECT_INFO_LEN_FIELD,
have: end,
what: "DirectoryMessage objectInfo_length",
},
))?;
let obj_info_len = u16::from_be_bytes(*bdoi) as usize;
cur += OBJECT_INFO_LEN_FIELD;
if cur + obj_info_len > end {
return Err(Error::SectionLengthOverflow {
declared: obj_info_len,
available: end - cur,
});
}
let object_info = &bytes[cur..cur + obj_info_len];
cur += obj_info_len;
let (service_context, next) = parse_service_context_list(bytes, cur, end)?;
cur = next;
let (bbl, _) = bytes[cur..end]
.split_first_chunk::<4>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + MESSAGE_BODY_LEN_FIELD,
have: end,
what: "DirectoryMessage messageBody_length",
},
))?;
let body_len_wire = u32::from_be_bytes(*bbl);
cur += MESSAGE_BODY_LEN_FIELD;
let body_end = super::span(cur, body_len_wire, end)?.end;
let (bbc, _) =
bytes[cur..body_end]
.split_first_chunk::<2>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + BINDINGS_COUNT_FIELD,
have: body_end,
what: "DirectoryMessage bindings_count",
},
))?;
let bindings_count = u16::from_be_bytes(*bbc) as usize;
cur += BINDINGS_COUNT_FIELD;
let mut bindings = Vec::with_capacity(bindings_count.min(256));
for _ in 0..bindings_count {
let (binding, next) = Binding::parse_from(bytes, cur, body_end)?;
bindings.push(binding);
cur = next;
}
Ok(DirectoryMessage {
object_kind,
object_key,
object_info,
service_context,
bindings,
})
}
fn body_len(&self) -> usize {
let bindings_len: usize = self.bindings.iter().map(|b| b.serialized_len()).sum();
BINDINGS_COUNT_FIELD + bindings_len
}
fn serialized_len_inner(&self) -> usize {
let key_part = OBJECT_KEY_LEN_FIELD
+ self.object_key.len()
+ OBJECT_KIND_LEN_FIELD
+ OBJECT_KIND_DATA_LEN;
let info_part = OBJECT_INFO_LEN_FIELD + self.object_info.len();
let svc_ctx_part = service_context_list_len(&self.service_context);
let body_part = MESSAGE_BODY_LEN_FIELD + self.body_len();
key_part + info_part + svc_ctx_part + body_part
}
pub fn serialized_len_total(&self) -> usize {
BIOP_HEADER_LEN + self.serialized_len_inner()
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let inner_len = self.serialized_len_inner();
let total = BIOP_HEADER_LEN + inner_len;
need_out(buf, total)?;
write_biop_header(
buf,
broadcast_common::len::fit_u32(inner_len, "messageSize")?,
);
let mut pos = BIOP_HEADER_LEN;
buf[pos] = broadcast_common::len::fit_u8(self.object_key.len(), "self.object_key")?;
pos += OBJECT_KEY_LEN_FIELD;
buf[pos..pos + self.object_key.len()].copy_from_slice(self.object_key);
pos += self.object_key.len();
buf[pos..pos + 4].copy_from_slice(&(OBJECT_KIND_DATA_LEN as u32).to_be_bytes());
pos += OBJECT_KIND_LEN_FIELD;
buf[pos..pos + 4].copy_from_slice(&self.object_kind);
pos += OBJECT_KIND_DATA_LEN;
if self.object_info.len() > u16::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.object_info.len(),
available: u16::MAX as usize,
});
}
buf[pos..pos + 2].copy_from_slice(
&broadcast_common::len::fit_u16(self.object_info.len(), "self.object_info")?
.to_be_bytes(),
);
pos += OBJECT_INFO_LEN_FIELD;
buf[pos..pos + self.object_info.len()].copy_from_slice(self.object_info);
pos += self.object_info.len();
pos += write_service_context_list(&mut buf[pos..], &self.service_context)?;
let body_len = self.body_len();
let body_len = broadcast_common::len::fit_u32(body_len, "messageBody_length")?;
buf[pos..pos + 4].copy_from_slice(&body_len.to_be_bytes());
pos += MESSAGE_BODY_LEN_FIELD;
buf[pos..pos + 2].copy_from_slice(
&broadcast_common::len::fit_u16(self.bindings.len(), "self.bindings")?.to_be_bytes(),
);
pos += BINDINGS_COUNT_FIELD;
for binding in &self.bindings {
let written = binding.serialize_into_buf(&mut buf[pos..])?;
pos += written;
}
Ok(total)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FileMessage<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_key: &'a [u8],
pub content_size: u64,
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_info_extra: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow))]
pub service_context: Vec<ServiceContext<'a>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub content: &'a [u8],
}
impl<'a> FileMessage<'a> {
fn parse_from(bytes: &'a [u8], object_key: &'a [u8], pos: usize, end: usize) -> Result<Self> {
let mut cur = pos;
let (bfoi, _) = bytes[cur..end]
.split_first_chunk::<2>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + OBJECT_INFO_LEN_FIELD,
have: end,
what: "FileMessage objectInfo_length",
},
))?;
let obj_info_len = u16::from_be_bytes(*bfoi) as usize;
cur += OBJECT_INFO_LEN_FIELD;
if obj_info_len < FILE_CONTENT_SIZE_LEN {
return Err(Error::ValueOutOfRange {
field: "FileMessage.objectInfo_length",
reason: "FileMessage objectInfo must be at least 8 bytes (ContentSize)",
});
}
if cur + obj_info_len > end {
return Err(Error::SectionLengthOverflow {
declared: obj_info_len,
available: end - cur,
});
}
let (bcs, _) =
bytes[cur..end]
.split_first_chunk::<8>()
.ok_or(Error::SectionLengthOverflow {
declared: obj_info_len,
available: end - cur,
})?;
let content_size = u64::from_be_bytes(*bcs);
let object_info_extra = &bytes[cur + FILE_CONTENT_SIZE_LEN..cur + obj_info_len];
cur += obj_info_len;
let (service_context, next) = parse_service_context_list(bytes, cur, end)?;
cur = next;
let (bfbl, _) = bytes[cur..end]
.split_first_chunk::<4>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + MESSAGE_BODY_LEN_FIELD,
have: end,
what: "FileMessage messageBody_length",
},
))?;
let body_len_wire = u32::from_be_bytes(*bfbl);
cur += MESSAGE_BODY_LEN_FIELD;
let body_end = super::span(cur, body_len_wire, end)?.end;
let (bfcl, _) =
bytes[cur..body_end]
.split_first_chunk::<4>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + FILE_CONTENT_LEN_FIELD,
have: body_end,
what: "FileMessage content_length",
},
))?;
let content_len_wire = u32::from_be_bytes(*bfcl);
cur += FILE_CONTENT_LEN_FIELD;
let content_range = super::span(cur, content_len_wire, body_end)?;
let content = &bytes[content_range];
Ok(FileMessage {
object_key,
content_size,
object_info_extra,
service_context,
content,
})
}
fn serialized_len_inner(&self) -> usize {
let obj_info_total = FILE_CONTENT_SIZE_LEN + self.object_info_extra.len();
OBJECT_KEY_LEN_FIELD
+ self.object_key.len()
+ OBJECT_KIND_LEN_FIELD
+ OBJECT_KIND_DATA_LEN
+ OBJECT_INFO_LEN_FIELD
+ obj_info_total
+ service_context_list_len(&self.service_context)
+ MESSAGE_BODY_LEN_FIELD
+ FILE_CONTENT_LEN_FIELD
+ self.content.len()
}
pub fn serialized_len_total(&self) -> usize {
BIOP_HEADER_LEN + self.serialized_len_inner()
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let inner_len = self.serialized_len_inner();
let total = BIOP_HEADER_LEN + inner_len;
need_out(buf, total)?;
write_biop_header(
buf,
broadcast_common::len::fit_u32(inner_len, "messageSize")?,
);
let mut pos = BIOP_HEADER_LEN;
buf[pos] = broadcast_common::len::fit_u8(self.object_key.len(), "objectKey_length")?;
pos += OBJECT_KEY_LEN_FIELD;
buf[pos..pos + self.object_key.len()].copy_from_slice(self.object_key);
pos += self.object_key.len();
buf[pos..pos + 4].copy_from_slice(&(OBJECT_KIND_DATA_LEN as u32).to_be_bytes());
pos += OBJECT_KIND_LEN_FIELD;
buf[pos..pos + 4].copy_from_slice(b"fil\0");
pos += OBJECT_KIND_DATA_LEN;
let obj_info_total = FILE_CONTENT_SIZE_LEN + self.object_info_extra.len();
let obj_info_total = broadcast_common::len::fit_u16(obj_info_total, "objectInfo_length")?;
buf[pos..pos + 2].copy_from_slice(&obj_info_total.to_be_bytes());
pos += OBJECT_INFO_LEN_FIELD;
buf[pos..pos + 8].copy_from_slice(&self.content_size.to_be_bytes());
pos += FILE_CONTENT_SIZE_LEN;
buf[pos..pos + self.object_info_extra.len()].copy_from_slice(self.object_info_extra);
pos += self.object_info_extra.len();
pos += write_service_context_list(&mut buf[pos..], &self.service_context)?;
let body_len = FILE_CONTENT_LEN_FIELD + self.content.len();
let body_len = broadcast_common::len::fit_u32(body_len, "messageBody_length")?;
buf[pos..pos + 4].copy_from_slice(&body_len.to_be_bytes());
pos += MESSAGE_BODY_LEN_FIELD;
let content_len =
broadcast_common::len::fit_u32(self.content.len(), "File_content_length")?;
buf[pos..pos + 4].copy_from_slice(&content_len.to_be_bytes());
pos += FILE_CONTENT_LEN_FIELD;
buf[pos..pos + self.content.len()].copy_from_slice(self.content);
Ok(total)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DsmStreamInfo<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub description: &'a [u8],
pub duration_seconds: i32,
pub duration_microseconds: u16,
pub audio: u8,
pub video: u8,
pub data: u8,
}
impl<'a> DsmStreamInfo<'a> {
fn serialized_len(&self) -> usize {
STREAM_ADESC_LEN_FIELD + self.description.len() + STREAM_INFO_FIXED
}
fn parse_from(bytes: &'a [u8], pos: usize, end: usize) -> Result<(Self, usize)> {
if pos + STREAM_ADESC_LEN_FIELD > end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: pos + STREAM_ADESC_LEN_FIELD,
have: end,
what: "DsmStreamInfo aDescription_length",
},
));
}
let desc_len = bytes[pos] as usize;
let mut cur = pos + STREAM_ADESC_LEN_FIELD;
if cur + desc_len > end {
return Err(Error::SectionLengthOverflow {
declared: desc_len,
available: end - cur,
});
}
let description = &bytes[cur..cur + desc_len];
cur += desc_len;
let (sif, _) = bytes[cur..end]
.split_first_chunk::<STREAM_INFO_FIXED>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + STREAM_INFO_FIXED,
have: end,
what: "DsmStreamInfo fixed fields",
},
))?;
let duration_seconds = i32::from_be_bytes([sif[0], sif[1], sif[2], sif[3]]);
let duration_microseconds = u16::from_be_bytes([sif[4], sif[5]]);
let audio = sif[6];
let video = sif[7];
let data = sif[8];
cur += STREAM_INFO_FIXED;
Ok((
DsmStreamInfo {
description,
duration_seconds,
duration_microseconds,
audio,
video,
data,
},
cur,
))
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
need_out(buf, len)?;
if self.description.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.description.len(),
available: u8::MAX as usize,
});
}
buf[0] = broadcast_common::len::fit_u8(self.description.len(), "self.description")?;
let mut pos = STREAM_ADESC_LEN_FIELD;
buf[pos..pos + self.description.len()].copy_from_slice(self.description);
pos += self.description.len();
buf[pos..pos + 4].copy_from_slice(&self.duration_seconds.to_be_bytes());
pos += 4;
buf[pos..pos + 2].copy_from_slice(&self.duration_microseconds.to_be_bytes());
pos += 2;
buf[pos] = self.audio;
pos += 1;
buf[pos] = self.video;
pos += 1;
buf[pos] = self.data;
pos += 1;
Ok(pos)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct StreamMessage<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_key: &'a [u8],
pub stream_info: DsmStreamInfo<'a>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_info_extra: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow))]
pub service_context: Vec<ServiceContext<'a>>,
pub taps: Vec<super::ior::Tap<'a>>,
}
impl<'a> StreamMessage<'a> {
fn parse_from(bytes: &'a [u8], object_key: &'a [u8], pos: usize, end: usize) -> Result<Self> {
let mut cur = pos;
let (bsmoi, _) = bytes[cur..end]
.split_first_chunk::<2>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + OBJECT_INFO_LEN_FIELD,
have: end,
what: "StreamMessage objectInfo_length",
},
))?;
let obj_info_len = u16::from_be_bytes(*bsmoi) as usize;
cur += OBJECT_INFO_LEN_FIELD;
if cur + obj_info_len > end {
return Err(Error::SectionLengthOverflow {
declared: obj_info_len,
available: end - cur,
});
}
let obj_info_start = cur;
let obj_info_end = cur + obj_info_len;
let (stream_info, _) = DsmStreamInfo::parse_from(bytes, cur, obj_info_end)?;
let info_len = stream_info.serialized_len();
if obj_info_len < info_len {
return Err(Error::ValueOutOfRange {
field: "StreamMessage.objectInfo_length",
reason: "objectInfo too short for DSM::Stream::Info_T",
});
}
let object_info_extra = &bytes[obj_info_start + info_len..obj_info_end];
cur = obj_info_end;
let (service_context, next) = parse_service_context_list(bytes, cur, end)?;
cur = next;
let (bsmbl, _) = bytes[cur..end]
.split_first_chunk::<4>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + MESSAGE_BODY_LEN_FIELD,
have: end,
what: "StreamMessage messageBody_length",
},
))?;
let body_len_wire = u32::from_be_bytes(*bsmbl);
cur += MESSAGE_BODY_LEN_FIELD;
let body_end = super::span(cur, body_len_wire, end)?.end;
if cur + STREAM_TAPS_COUNT_FIELD > body_end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + STREAM_TAPS_COUNT_FIELD,
have: body_end,
what: "StreamMessage taps_count",
},
));
}
let taps_count = bytes[cur] as usize;
cur += STREAM_TAPS_COUNT_FIELD;
let mut taps = Vec::with_capacity(taps_count.min(16));
for _ in 0..taps_count {
let (tap, next) = super::ior::Tap::parse_from(bytes, cur, body_end)?;
taps.push(tap);
cur = next;
}
Ok(StreamMessage {
object_key,
stream_info,
object_info_extra,
service_context,
taps,
})
}
fn body_len(&self) -> usize {
let taps_len: usize = self.taps.iter().map(|t| t.serialized_len()).sum();
STREAM_TAPS_COUNT_FIELD + taps_len
}
fn obj_info_len(&self) -> usize {
self.stream_info.serialized_len() + self.object_info_extra.len()
}
fn serialized_len_inner(&self) -> usize {
OBJECT_KEY_LEN_FIELD
+ self.object_key.len()
+ OBJECT_KIND_LEN_FIELD
+ OBJECT_KIND_DATA_LEN
+ OBJECT_INFO_LEN_FIELD
+ self.obj_info_len()
+ service_context_list_len(&self.service_context)
+ MESSAGE_BODY_LEN_FIELD
+ self.body_len()
}
pub fn serialized_len_total(&self) -> usize {
BIOP_HEADER_LEN + self.serialized_len_inner()
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let inner_len = self.serialized_len_inner();
let total = BIOP_HEADER_LEN + inner_len;
need_out(buf, total)?;
write_biop_header(
buf,
broadcast_common::len::fit_u32(inner_len, "messageSize")?,
);
let mut pos = BIOP_HEADER_LEN;
buf[pos] = broadcast_common::len::fit_u8(self.object_key.len(), "self.object_key")?;
pos += OBJECT_KEY_LEN_FIELD;
buf[pos..pos + self.object_key.len()].copy_from_slice(self.object_key);
pos += self.object_key.len();
buf[pos..pos + 4].copy_from_slice(&(OBJECT_KIND_DATA_LEN as u32).to_be_bytes());
pos += OBJECT_KIND_LEN_FIELD;
buf[pos..pos + 4].copy_from_slice(b"str\0");
pos += OBJECT_KIND_DATA_LEN;
let oi_len = broadcast_common::len::fit_u16(self.obj_info_len(), "objectInfo_length")?;
buf[pos..pos + 2].copy_from_slice(&oi_len.to_be_bytes());
pos += OBJECT_INFO_LEN_FIELD;
let written = self.stream_info.serialize_into_buf(&mut buf[pos..])?;
pos += written;
buf[pos..pos + self.object_info_extra.len()].copy_from_slice(self.object_info_extra);
pos += self.object_info_extra.len();
pos += write_service_context_list(&mut buf[pos..], &self.service_context)?;
let bl = broadcast_common::len::fit_u32(self.body_len(), "messageBody_length")?;
buf[pos..pos + 4].copy_from_slice(&bl.to_be_bytes());
pos += MESSAGE_BODY_LEN_FIELD;
if self.taps.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.taps.len(),
available: u8::MAX as usize,
});
}
buf[pos] = broadcast_common::len::fit_u8(self.taps.len(), "self.taps")?;
pos += STREAM_TAPS_COUNT_FIELD;
for tap in &self.taps {
let written = tap.serialize_into_buf(&mut buf[pos..])?;
pos += written;
}
Ok(total)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct StreamEventMessage<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_key: &'a [u8],
pub stream_info: DsmStreamInfo<'a>,
pub event_names: Vec<&'a [u8]>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_info_extra: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow))]
pub service_context: Vec<ServiceContext<'a>>,
pub taps: Vec<super::ior::Tap<'a>>,
pub event_ids: Vec<u16>,
}
impl<'a> StreamEventMessage<'a> {
fn parse_from(bytes: &'a [u8], object_key: &'a [u8], pos: usize, end: usize) -> Result<Self> {
let mut cur = pos;
let (bseoi, _) = bytes[cur..end]
.split_first_chunk::<2>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + OBJECT_INFO_LEN_FIELD,
have: end,
what: "StreamEventMessage objectInfo_length",
},
))?;
let obj_info_len = u16::from_be_bytes(*bseoi) as usize;
cur += OBJECT_INFO_LEN_FIELD;
if cur + obj_info_len > end {
return Err(Error::SectionLengthOverflow {
declared: obj_info_len,
available: end - cur,
});
}
let obj_info_end = cur + obj_info_len;
let (stream_info, next_cur) = DsmStreamInfo::parse_from(bytes, cur, obj_info_end)?;
cur = next_cur;
let (benc, _) =
bytes[cur..obj_info_end]
.split_first_chunk::<2>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + STREAM_EVENT_NAMES_COUNT_FIELD,
have: obj_info_end,
what: "StreamEventMessage eventNames_count",
},
))?;
let event_names_count = u16::from_be_bytes(*benc) as usize;
cur += STREAM_EVENT_NAMES_COUNT_FIELD;
let mut event_names = Vec::with_capacity(event_names_count.min(64));
for _ in 0..event_names_count {
if cur + STREAM_EVENT_NAME_LEN_FIELD > obj_info_end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + STREAM_EVENT_NAME_LEN_FIELD,
have: obj_info_end,
what: "StreamEventMessage eventName_length",
},
));
}
let name_len = bytes[cur] as usize;
cur += STREAM_EVENT_NAME_LEN_FIELD;
if cur + name_len > obj_info_end {
return Err(Error::SectionLengthOverflow {
declared: name_len,
available: obj_info_end - cur,
});
}
event_names.push(&bytes[cur..cur + name_len]);
cur += name_len;
}
let object_info_extra = &bytes[cur..obj_info_end];
cur = obj_info_end;
let (service_context, next) = parse_service_context_list(bytes, cur, end)?;
cur = next;
let (bsebl, _) = bytes[cur..end]
.split_first_chunk::<4>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + MESSAGE_BODY_LEN_FIELD,
have: end,
what: "StreamEventMessage messageBody_length",
},
))?;
let body_len_wire = u32::from_be_bytes(*bsebl);
cur += MESSAGE_BODY_LEN_FIELD;
let body_end = super::span(cur, body_len_wire, end)?.end;
if cur + STREAM_TAPS_COUNT_FIELD > body_end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + STREAM_TAPS_COUNT_FIELD,
have: body_end,
what: "StreamEventMessage taps_count",
},
));
}
let taps_count = bytes[cur] as usize;
cur += STREAM_TAPS_COUNT_FIELD;
let mut taps = Vec::with_capacity(taps_count.min(16));
for _ in 0..taps_count {
let (tap, next) = super::ior::Tap::parse_from(bytes, cur, body_end)?;
taps.push(tap);
cur = next;
}
if cur + STREAM_EVENT_IDS_COUNT_FIELD > body_end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + STREAM_EVENT_IDS_COUNT_FIELD,
have: body_end,
what: "StreamEventMessage eventIds_count",
},
));
}
let event_ids_count = bytes[cur] as usize;
cur += STREAM_EVENT_IDS_COUNT_FIELD;
if event_ids_count != event_names_count {
return Err(Error::ValueOutOfRange {
field: "StreamEventMessage.eventIds_count",
reason: "eventIds_count must equal eventNames_count",
});
}
let mut event_ids = Vec::with_capacity(event_ids_count.min(64));
for _ in 0..event_ids_count {
let (bei, _) =
bytes[cur..body_end]
.split_first_chunk::<2>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: cur + STREAM_EVENT_ID_LEN,
have: body_end,
what: "StreamEventMessage eventId",
},
))?;
event_ids.push(u16::from_be_bytes(*bei));
cur += STREAM_EVENT_ID_LEN;
}
let _ = cur; Ok(StreamEventMessage {
object_key,
stream_info,
event_names,
object_info_extra,
service_context,
taps,
event_ids,
})
}
fn event_list_wire_len(&self) -> usize {
let names_len: usize = self
.event_names
.iter()
.map(|n| STREAM_EVENT_NAME_LEN_FIELD + n.len())
.sum();
STREAM_EVENT_NAMES_COUNT_FIELD + names_len
}
fn body_len(&self) -> usize {
let taps_len: usize = self.taps.iter().map(|t| t.serialized_len()).sum();
STREAM_TAPS_COUNT_FIELD
+ taps_len
+ STREAM_EVENT_IDS_COUNT_FIELD
+ self.event_ids.len() * STREAM_EVENT_ID_LEN
}
fn obj_info_len(&self) -> usize {
self.stream_info.serialized_len()
+ self.event_list_wire_len()
+ self.object_info_extra.len()
}
fn serialized_len_inner(&self) -> usize {
OBJECT_KEY_LEN_FIELD
+ self.object_key.len()
+ OBJECT_KIND_LEN_FIELD
+ OBJECT_KIND_DATA_LEN
+ OBJECT_INFO_LEN_FIELD
+ self.obj_info_len()
+ service_context_list_len(&self.service_context)
+ MESSAGE_BODY_LEN_FIELD
+ self.body_len()
}
pub fn serialized_len_total(&self) -> usize {
BIOP_HEADER_LEN + self.serialized_len_inner()
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let inner_len = self.serialized_len_inner();
let total = BIOP_HEADER_LEN + inner_len;
need_out(buf, total)?;
write_biop_header(
buf,
broadcast_common::len::fit_u32(inner_len, "messageSize")?,
);
let mut pos = BIOP_HEADER_LEN;
buf[pos] = broadcast_common::len::fit_u8(self.object_key.len(), "self.object_key")?;
pos += OBJECT_KEY_LEN_FIELD;
buf[pos..pos + self.object_key.len()].copy_from_slice(self.object_key);
pos += self.object_key.len();
buf[pos..pos + 4].copy_from_slice(&(OBJECT_KIND_DATA_LEN as u32).to_be_bytes());
pos += OBJECT_KIND_LEN_FIELD;
buf[pos..pos + 4].copy_from_slice(b"ste\0");
pos += OBJECT_KIND_DATA_LEN;
let oi_len = broadcast_common::len::fit_u16(self.obj_info_len(), "objectInfo_length")?;
buf[pos..pos + 2].copy_from_slice(&oi_len.to_be_bytes());
pos += OBJECT_INFO_LEN_FIELD;
let written = self.stream_info.serialize_into_buf(&mut buf[pos..])?;
pos += written;
if self.event_names.len() > u16::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.event_names.len(),
available: u16::MAX as usize,
});
}
buf[pos..pos + 2].copy_from_slice(
&broadcast_common::len::fit_u16(self.event_names.len(), "self.event_names")?
.to_be_bytes(),
);
pos += STREAM_EVENT_NAMES_COUNT_FIELD;
for name in &self.event_names {
if name.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: name.len(),
available: u8::MAX as usize,
});
}
buf[pos] = broadcast_common::len::fit_u8(name.len(), "name")?;
pos += STREAM_EVENT_NAME_LEN_FIELD;
buf[pos..pos + name.len()].copy_from_slice(name);
pos += name.len();
}
buf[pos..pos + self.object_info_extra.len()].copy_from_slice(self.object_info_extra);
pos += self.object_info_extra.len();
pos += write_service_context_list(&mut buf[pos..], &self.service_context)?;
let bl = broadcast_common::len::fit_u32(self.body_len(), "messageBody_length")?;
buf[pos..pos + 4].copy_from_slice(&bl.to_be_bytes());
pos += MESSAGE_BODY_LEN_FIELD;
if self.taps.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.taps.len(),
available: u8::MAX as usize,
});
}
buf[pos] = broadcast_common::len::fit_u8(self.taps.len(), "self.taps")?;
pos += STREAM_TAPS_COUNT_FIELD;
for tap in &self.taps {
let written = tap.serialize_into_buf(&mut buf[pos..])?;
pos += written;
}
if self.event_ids.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.event_ids.len(),
available: u8::MAX as usize,
});
}
buf[pos] = broadcast_common::len::fit_u8(self.event_ids.len(), "self.event_ids")?;
pos += STREAM_EVENT_IDS_COUNT_FIELD;
for &id in &self.event_ids {
buf[pos..pos + 2].copy_from_slice(&id.to_be_bytes());
pos += STREAM_EVENT_ID_LEN;
}
Ok(total)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum BiopMessage<'a> {
Directory(DirectoryMessage<'a>),
File(FileMessage<'a>),
ServiceGateway(DirectoryMessage<'a>),
Stream(StreamMessage<'a>),
StreamEvent(StreamEventMessage<'a>),
}
impl<'a> BiopMessage<'a> {
pub fn parse_at(bytes: &'a [u8]) -> Result<(Self, usize)> {
let (object_key, kind_bytes, message_size, pos) = parse_biop_header(bytes)?;
let consumed = BIOP_HEADER_LEN + message_size;
let end = consumed;
let msg = match &kind_bytes {
b"dir\0" => {
let dm = DirectoryMessage::parse_from(bytes, object_key, kind_bytes, pos, end)?;
BiopMessage::Directory(dm)
}
b"srg\0" => {
let dm = DirectoryMessage::parse_from(bytes, object_key, kind_bytes, pos, end)?;
BiopMessage::ServiceGateway(dm)
}
b"fil\0" => {
let fm = FileMessage::parse_from(bytes, object_key, pos, end)?;
BiopMessage::File(fm)
}
b"str\0" => {
let sm = StreamMessage::parse_from(bytes, object_key, pos, end)?;
BiopMessage::Stream(sm)
}
b"ste\0" => {
let se = StreamEventMessage::parse_from(bytes, object_key, pos, end)?;
BiopMessage::StreamEvent(se)
}
_ => {
return Err(Error::ValueOutOfRange {
field: "BiopMessage.objectKind",
reason: "unknown BIOP objectKind",
});
}
};
Ok((msg, consumed))
}
fn serialized_len_total(&self) -> usize {
match self {
Self::Directory(d) | Self::ServiceGateway(d) => d.serialized_len_total(),
Self::File(f) => f.serialized_len_total(),
Self::Stream(s) => s.serialized_len_total(),
Self::StreamEvent(se) => se.serialized_len_total(),
}
}
}
impl Serialize for BiopMessage<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
self.serialized_len_total()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len_total();
need_out(buf, len)?;
match self {
Self::Directory(d) | Self::ServiceGateway(d) => {
d.serialize_into_buf(buf)?;
}
Self::File(f) => {
f.serialize_into_buf(buf)?;
}
Self::Stream(s) => {
s.serialize_into_buf(buf)?;
}
Self::StreamEvent(se) => {
se.serialize_into_buf(buf)?;
}
}
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ModuleInfo<'a> {
pub module_timeout: u32,
pub block_timeout: u32,
pub min_block_time: u32,
#[cfg_attr(feature = "serde", serde(borrow))]
pub taps: Vec<super::ior::Tap<'a>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub user_info: CarouselDescriptorLoop<'a>,
}
impl ModuleInfo<'_> {
pub fn descriptors(&self) -> impl Iterator<Item = (u8, &[u8])> {
self.user_info.raw_tags()
}
pub fn compressed_module_descriptor(&self) -> Option<Result<CompressedModuleDescriptor>> {
self.user_info.iter().find_map(|d| match d {
Ok(AnyCarouselDescriptor::CompressedModule(c)) => Some(Ok(c)),
Err(
e @ Error::InvalidDescriptor {
tag: crate::descriptors::carousel::compressed_module::TAG,
..
},
) => Some(Err(e)),
Ok(_) | Err(_) => None,
})
}
}
impl<'a> Parse<'a> for ModuleInfo<'a> {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let end = bytes.len();
let mi_fixed_len = MODULE_INFO_FIXED + MODULE_TAPS_COUNT_FIELD;
let (mi_hdr, _) = bytes
.split_first_chunk::<13>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: mi_fixed_len,
have: end,
what: "ModuleInfo fixed fields",
},
))?;
let module_timeout = u32::from_be_bytes([mi_hdr[0], mi_hdr[1], mi_hdr[2], mi_hdr[3]]);
let block_timeout = u32::from_be_bytes([mi_hdr[4], mi_hdr[5], mi_hdr[6], mi_hdr[7]]);
let min_block_time = u32::from_be_bytes([mi_hdr[8], mi_hdr[9], mi_hdr[10], mi_hdr[11]]);
let taps_count = mi_hdr[12] as usize;
let mut pos = MODULE_INFO_FIXED + MODULE_TAPS_COUNT_FIELD;
let mut taps = Vec::with_capacity(taps_count.min(8));
for _ in 0..taps_count {
let (tap, next) = super::ior::Tap::parse_from(bytes, pos, end)?;
taps.push(tap);
pos = next;
}
if pos + MODULE_USER_INFO_LEN_FIELD > end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: pos + MODULE_USER_INFO_LEN_FIELD,
have: end,
what: "ModuleInfo UserInfoLength",
},
));
}
let user_info_len = bytes[pos] as usize;
pos += MODULE_USER_INFO_LEN_FIELD;
if pos + user_info_len > end {
return Err(Error::SectionLengthOverflow {
declared: user_info_len,
available: end - pos,
});
}
let user_info = CarouselDescriptorLoop::new(&bytes[pos..pos + user_info_len]);
Ok(ModuleInfo {
module_timeout,
block_timeout,
min_block_time,
taps,
user_info,
})
}
}
impl Serialize for ModuleInfo<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
let taps_len: usize = self.taps.iter().map(|t| t.serialized_len()).sum();
MODULE_INFO_FIXED
+ MODULE_TAPS_COUNT_FIELD
+ taps_len
+ MODULE_USER_INFO_LEN_FIELD
+ self.user_info.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
need_out(buf, len)?;
buf[0..4].copy_from_slice(&self.module_timeout.to_be_bytes());
buf[4..8].copy_from_slice(&self.block_timeout.to_be_bytes());
buf[8..12].copy_from_slice(&self.min_block_time.to_be_bytes());
if self.taps.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.taps.len(),
available: u8::MAX as usize,
});
}
buf[12] = broadcast_common::len::fit_u8(self.taps.len(), "self.taps")?;
let mut pos = MODULE_INFO_FIXED + MODULE_TAPS_COUNT_FIELD;
for tap in &self.taps {
let written = tap.serialize_into_buf(&mut buf[pos..])?;
pos += written;
}
let user_info_len = broadcast_common::len::fit_u8(self.user_info.len(), "UserInfoLength")?;
buf[pos] = user_info_len;
pos += MODULE_USER_INFO_LEN_FIELD;
buf[pos..pos + self.user_info.len()].copy_from_slice(self.user_info.raw());
pos += self.user_info.len();
Ok(pos)
}
}
#[cfg(feature = "flate2")]
pub const MAX_DECOMPRESSED_MODULE_SIZE: usize = 64 * 1024 * 1024;
#[cfg(feature = "flate2")]
pub fn decompress_zlib_bounded(data: &[u8], max_len: usize) -> Result<Vec<u8>> {
use std::io::Read;
let limit = (max_len as u64).saturating_add(1);
let mut decoder = flate2::read::ZlibDecoder::new(data).take(limit);
let mut out = Vec::new();
decoder.read_to_end(&mut out).map_err(|e| {
Error::InvalidField(broadcast_common::error::InvalidField {
field: "compressed_module_descriptor body",
reason: if e.kind() == std::io::ErrorKind::InvalidData {
"zlib decompression failed: invalid data"
} else {
"zlib decompression failed"
},
})
})?;
if out.len() > max_len {
return Err(Error::InvalidField(broadcast_common::error::InvalidField {
field: "compressed_module_descriptor body",
reason: "zlib output exceeds the decompressed-size limit",
}));
}
Ok(out)
}
#[cfg(feature = "flate2")]
pub fn decompress_zlib(data: &[u8]) -> Result<Vec<u8>> {
decompress_zlib_bounded(data, MAX_DECOMPRESSED_MODULE_SIZE)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ServiceGatewayInfo<'a> {
pub ior: Ior<'a>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub download_taps: Vec<Tap<'a>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub service_context: Vec<ServiceContext<'a>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub user_info: &'a [u8],
}
impl<'a> ServiceGatewayInfo<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self> {
let end = bytes.len();
let (ior, mut pos) = Ior::parse_at(bytes)?;
if pos + SGI_DOWNLOAD_TAPS_COUNT_FIELD > end {
return Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: pos + SGI_DOWNLOAD_TAPS_COUNT_FIELD,
have: end,
what: "ServiceGatewayInfo downloadTaps_count",
},
));
}
let tap_count = bytes[pos] as usize;
pos += SGI_DOWNLOAD_TAPS_COUNT_FIELD;
let mut download_taps = Vec::with_capacity(tap_count);
for _ in 0..tap_count {
let (tap, next) = Tap::parse_from(bytes, pos, end)?;
download_taps.push(tap);
pos = next;
}
let (service_context, next) = parse_service_context_list(bytes, pos, end)?;
pos = next;
let (buil, _) = bytes[pos..end]
.split_first_chunk::<2>()
.ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: pos + SGI_USER_INFO_LEN_FIELD,
have: end,
what: "ServiceGatewayInfo userInfoLength",
},
))?;
let ui_len = u16::from_be_bytes(*buil) as usize;
pos += SGI_USER_INFO_LEN_FIELD;
if pos + ui_len > end {
return Err(Error::SectionLengthOverflow {
declared: ui_len,
available: end - pos,
});
}
let user_info = &bytes[pos..pos + ui_len];
Ok(ServiceGatewayInfo {
ior,
download_taps,
service_context,
user_info,
})
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut buf = vec![0u8; self.serialized_len()];
self.serialize_into(&mut buf)?;
Ok(buf)
}
}
impl<'a> Serialize for ServiceGatewayInfo<'a> {
type Error = Error;
fn serialized_len(&self) -> usize {
self.ior.serialized_len()
+ SGI_DOWNLOAD_TAPS_COUNT_FIELD
+ self
.download_taps
.iter()
.map(Tap::serialized_len)
.sum::<usize>()
+ service_context_list_len(&self.service_context)
+ SGI_USER_INFO_LEN_FIELD
+ self.user_info.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
need_out(buf, len)?;
let mut pos = self.ior.serialize_into(buf)?;
buf[pos] = broadcast_common::len::fit_u8(self.download_taps.len(), "downloadTaps_count")?;
pos += SGI_DOWNLOAD_TAPS_COUNT_FIELD;
for tap in &self.download_taps {
pos += tap.serialize_into_buf(&mut buf[pos..])?;
}
pos += write_service_context_list(&mut buf[pos..], &self.service_context)?;
let user_info_len = broadcast_common::len::fit_u16(
self.user_info.len(),
"ServiceGatewayInfo userInfo length",
)?;
buf[pos..pos + 2].copy_from_slice(&user_info_len.to_be_bytes());
pos += SGI_USER_INFO_LEN_FIELD;
buf[pos..pos + self.user_info.len()].copy_from_slice(self.user_info);
pos += self.user_info.len();
Ok(pos)
}
}
#[cfg(test)]
mod tests {
use super::*;
use broadcast_common::Parse;
fn sample_file_message(key: &'static [u8], content: &'static [u8]) -> BiopMessage<'static> {
BiopMessage::File(FileMessage {
object_key: key,
content_size: content.len() as u64,
object_info_extra: &[],
service_context: vec![],
content,
})
}
fn sample_dir_message() -> BiopMessage<'static> {
use crate::carousel::biop::ior::{
BiopProfileBody, ConnBinder, ObjectLocation, TaggedProfile,
};
let ior = crate::carousel::biop::ior::Ior {
type_id: b"fil\0",
profiles: vec![TaggedProfile::Biop(BiopProfileBody {
object_location: ObjectLocation {
carousel_id: 0xAB,
module_id: 2,
version_major: 1,
version_minor: 0,
object_key: &[0x02],
},
conn_binder: ConnBinder { taps: vec![] },
extra: vec![],
})],
};
BiopMessage::Directory(DirectoryMessage {
object_kind: *b"dir\0",
object_key: &[0x01],
object_info: &[],
service_context: vec![],
bindings: vec![Binding {
name: vec![NameComponent {
id: b"index.html",
kind: b"fil\0",
}],
binding_type: BindingType::NObject,
ior,
object_info: &[],
}],
})
}
#[test]
fn file_message_round_trip() {
let content: &[u8] = b"Hello, BIOP!";
let msg = sample_file_message(&[0x01], content);
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let (parsed, consumed) = BiopMessage::parse_at(&buf).unwrap();
assert_eq!(consumed, buf.len());
assert_eq!(parsed, msg);
let mut buf2 = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut buf2).unwrap();
assert_eq!(buf, buf2);
}
#[test]
fn directory_message_round_trip() {
let msg = sample_dir_message();
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let (parsed, consumed) = BiopMessage::parse_at(&buf).unwrap();
assert_eq!(consumed, buf.len());
assert_eq!(parsed, msg);
let mut buf2 = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut buf2).unwrap();
assert_eq!(buf, buf2, "Directory message byte-exact re-serialize");
}
#[test]
fn module_info_round_trip() {
use crate::carousel::biop::ior::Tap;
let info = ModuleInfo {
module_timeout: 0x00FFFFFF,
block_timeout: 0x00FFFFFF,
min_block_time: 0x00000064,
taps: vec![Tap {
id: 0,
use_: 0x0017,
association_tag: 0x0042,
selector: &[],
}],
user_info: CarouselDescriptorLoop::new(&[]),
};
let mut buf = vec![0u8; info.serialized_len()];
info.serialize_into(&mut buf).unwrap();
let parsed = ModuleInfo::parse(&buf).unwrap();
assert_eq!(parsed, info);
let mut buf2 = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut buf2).unwrap();
assert_eq!(buf, buf2, "ModuleInfo byte-exact re-serialize");
}
#[test]
fn module_info_byte_anchor() {
use crate::carousel::biop::ior::Tap;
#[rustfmt::skip]
let expected: &[u8] = &[
0x00, 0x0F, 0x42, 0x40, 0x00, 0x0F, 0x42, 0x40, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x47, 0x00, 0x00, ];
let info = ModuleInfo {
module_timeout: 0x000F4240,
block_timeout: 0x000F4240,
min_block_time: 0x00000064,
taps: vec![Tap {
id: 0,
use_: 0x0017,
association_tag: 0x0047,
selector: &[],
}],
user_info: CarouselDescriptorLoop::new(&[]),
};
let mut buf = vec![0u8; info.serialized_len()];
info.serialize_into(&mut buf).unwrap();
assert_eq!(buf.as_slice(), expected);
let parsed = ModuleInfo::parse(expected).unwrap();
assert_eq!(parsed, info);
}
#[test]
fn sgi_byte_anchor_m6() {
#[rustfmt::skip]
let raw: &[u8] = &[
0x00, 0x00, 0x00, 0x04, 0x73, 0x72, 0x67, 0x00, 0x00, 0x00, 0x00, 0x01, 0x49, 0x53, 0x4F, 0x06, 0x00, 0x00, 0x00, 0x28, 0x00, 0x02, 0x49, 0x53, 0x4F, 0x50, 0x0A, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x49, 0x53, 0x4F, 0x40, 0x12, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x47, 0x0A, 0x00, 0x01, 0x80, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, ];
assert_eq!(raw.len(), 64);
let sgi = ServiceGatewayInfo::parse(raw).unwrap();
assert_eq!(sgi.ior.type_id, b"srg\0");
assert_eq!(sgi.ior.profiles.len(), 1);
let bp = sgi.ior.biop_profile().unwrap();
assert_eq!(bp.object_location.carousel_id, 0xAB);
assert_eq!(bp.object_location.module_id, 1);
assert_eq!(bp.object_location.version_major, 1);
assert_eq!(bp.object_location.version_minor, 0);
assert_eq!(bp.object_location.object_key, &[0x01]);
assert_eq!(bp.conn_binder.taps.len(), 1);
let tap = &bp.conn_binder.taps[0];
assert_eq!(tap.use_, 0x0016);
assert_eq!(tap.association_tag, 0x47);
assert_eq!(tap.transaction_id(), Some(0x80000002));
assert_eq!(tap.timeout(), Some(0xFFFFFFFF));
let out = sgi.to_bytes().unwrap();
assert_eq!(out.len(), 64, "SGI serialized length");
assert_eq!(out.as_slice(), raw, "SGI byte-exact round-trip");
}
#[test]
fn service_gateway_info_to_bytes_errors_instead_of_panicking_on_oversized_service_context() {
let ior = crate::carousel::biop::ior::Ior {
type_id: b"srg\0",
profiles: vec![],
};
let service_context = (0..=u8::MAX as usize + 1)
.map(|i| ServiceContext {
context_id: i as u32,
data: &[],
})
.collect();
let sgi = ServiceGatewayInfo {
ior,
download_taps: Vec::new(),
service_context,
user_info: &[],
};
let err = sgi.to_bytes().unwrap_err();
assert!(matches!(err, Error::SectionLengthOverflow { .. }));
}
#[cfg(feature = "serde")]
#[test]
fn biop_serde_round_trip() {
let content: &[u8] = b"test content";
let msg = sample_file_message(&[0x01], content);
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("content_size"));
}
#[cfg(feature = "flate2")]
#[test]
fn zlib_round_trip() {
use flate2::{Compression, write::ZlibEncoder};
use std::io::Write;
let original = b"Hello, compressed BIOP world! ".repeat(10);
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = encoder.finish().unwrap();
let decompressed = decompress_zlib(&compressed).unwrap();
assert_eq!(decompressed.as_slice(), original.as_slice());
}
#[test]
fn module_info_compressed_module_descriptor_found_and_typed() {
#[rustfmt::skip]
let user_info: &[u8] = &[
0x71, 0x02, 0x02, 0xFF, crate::descriptors::carousel::compressed_module::TAG, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, ];
let info = ModuleInfo {
module_timeout: 0,
block_timeout: 0,
min_block_time: 0,
taps: vec![],
user_info: CarouselDescriptorLoop::new(user_info),
};
let cmd = info.compressed_module_descriptor().unwrap().unwrap();
assert_eq!(cmd.original_size, 256);
assert_eq!(cmd.compression_method, 0);
let bad = ModuleInfo {
user_info: CarouselDescriptorLoop::new(&[0x09, 0x03, 0x00, 0x00, 0x00]),
..info.clone()
};
assert!(matches!(
bad.compressed_module_descriptor().unwrap(),
Err(Error::InvalidDescriptor { .. })
));
let none = ModuleInfo {
user_info: CarouselDescriptorLoop::new(&[0x06, 0x01, 0x07]),
..info
};
assert!(none.compressed_module_descriptor().is_none());
}
#[cfg(feature = "flate2")]
#[test]
fn decompress_zlib_bounded_usize_max_is_unbounded() {
use flate2::{Compression, write::ZlibEncoder};
use std::io::Write;
let original = b"Hello, compressed BIOP world! ".repeat(10);
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = encoder.finish().unwrap();
let decompressed = decompress_zlib_bounded(&compressed, usize::MAX).unwrap();
assert_eq!(decompressed.as_slice(), original.as_slice());
}
#[cfg(feature = "flate2")]
#[test]
fn decompress_zlib_bounded_exact_boundary() {
use flate2::{Compression, write::ZlibEncoder};
use std::io::Write;
let original = vec![0x5Au8; 1000];
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = encoder.finish().unwrap();
let ok = decompress_zlib_bounded(&compressed, original.len()).unwrap();
assert_eq!(ok, original);
let err = decompress_zlib_bounded(&compressed, original.len() - 1);
assert_eq!(
err.err(),
Some(crate::error::Error::InvalidField(
broadcast_common::error::InvalidField {
field: "compressed_module_descriptor body",
reason: "zlib output exceeds the decompressed-size limit"
}
)),
"output exceeding max_len by exactly 1 byte must return Err"
);
}
#[test]
fn stream_message_round_trip() {
use crate::carousel::biop::ior::Tap;
let msg = BiopMessage::Stream(StreamMessage {
object_key: &[0x01, 0x02],
stream_info: DsmStreamInfo {
description: b"audio stream",
duration_seconds: -5,
duration_microseconds: 500,
audio: 1,
video: 0,
data: 0,
},
object_info_extra: b"\xDE\xAD",
service_context: vec![],
taps: vec![
Tap {
id: 0,
use_: 0x0018,
association_tag: 0x0010,
selector: &[],
},
Tap {
id: 0,
use_: 0x0019,
association_tag: 0x0011,
selector: &[],
},
],
});
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let (parsed, consumed) = BiopMessage::parse_at(&buf).unwrap();
assert_eq!(consumed, buf.len(), "consumed must equal total buf len");
assert_eq!(parsed, msg);
let mut buf2 = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut buf2).unwrap();
assert_eq!(buf, buf2, "StreamMessage byte-exact re-serialize");
}
#[test]
fn stream_event_message_round_trip() {
use crate::carousel::biop::ior::Tap;
let msg = BiopMessage::StreamEvent(StreamEventMessage {
object_key: &[0x03],
stream_info: DsmStreamInfo {
description: b"event stream",
duration_seconds: 3600,
duration_microseconds: 0,
audio: 0,
video: 1,
data: 0,
},
event_names: vec![b"play".as_ref(), b"pause".as_ref(), b"stop".as_ref()],
object_info_extra: &[],
service_context: vec![],
taps: vec![Tap {
id: 0,
use_: 0x000C,
association_tag: 0x0020,
selector: &[],
}],
event_ids: vec![0x0001, 0x0002, 0x0003],
});
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let (parsed, consumed) = BiopMessage::parse_at(&buf).unwrap();
assert_eq!(consumed, buf.len(), "consumed must equal total buf len");
assert_eq!(parsed, msg);
let mut buf2 = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut buf2).unwrap();
assert_eq!(buf, buf2, "StreamEventMessage byte-exact re-serialize");
}
#[test]
fn stream_message_byte_anchor() {
use crate::carousel::biop::ior::Tap;
#[rustfmt::skip]
let expected: &[u8] = &[
0x42, 0x49, 0x4F, 0x50, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x01, 0xAB, 0x00, 0x00, 0x00, 0x04, 0x73, 0x74, 0x72, 0x00, 0x00, 0x0D, 0x03, 0x76, 0x69, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x47, 0x00, ];
assert_eq!(expected.len(), 50);
let expected_msg = BiopMessage::Stream(StreamMessage {
object_key: &[0xAB],
stream_info: DsmStreamInfo {
description: b"vid",
duration_seconds: 0,
duration_microseconds: 0,
audio: 1,
video: 1,
data: 0,
},
object_info_extra: &[],
service_context: vec![],
taps: vec![Tap {
id: 0,
use_: 0x0018,
association_tag: 0x0047,
selector: &[],
}],
});
let mut buf = vec![0u8; expected_msg.serialized_len()];
expected_msg.serialize_into(&mut buf).unwrap();
assert_eq!(
buf.as_slice(),
expected,
"StreamMessage serialize must match byte anchor"
);
let (parsed, consumed) = BiopMessage::parse_at(expected).unwrap();
assert_eq!(consumed, expected.len());
assert_eq!(
parsed, expected_msg,
"StreamMessage parse must match byte anchor struct"
);
}
#[test]
fn stream_event_message_byte_anchor() {
#[rustfmt::skip]
let expected: &[u8] = &[
0x42, 0x49, 0x4F, 0x50, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x01, 0xCD, 0x00, 0x00, 0x00, 0x04, 0x73, 0x74, 0x65, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x66, 0x6F, 0x6F, 0x03, 0x62, 0x61, 0x72, 0x00,
0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, ];
assert_eq!(expected.len(), 55);
let expected_msg = BiopMessage::StreamEvent(StreamEventMessage {
object_key: &[0xCD],
stream_info: DsmStreamInfo {
description: &[],
duration_seconds: 0,
duration_microseconds: 0,
audio: 0,
video: 0,
data: 0,
},
event_names: vec![b"foo".as_ref(), b"bar".as_ref()],
object_info_extra: &[],
service_context: vec![],
taps: vec![],
event_ids: vec![1, 2],
});
let mut buf = vec![0u8; expected_msg.serialized_len()];
expected_msg.serialize_into(&mut buf).unwrap();
assert_eq!(
buf.as_slice(),
expected,
"StreamEventMessage serialize must match byte anchor"
);
let (parsed, consumed) = BiopMessage::parse_at(expected).unwrap();
assert_eq!(consumed, expected.len());
assert_eq!(
parsed, expected_msg,
"StreamEventMessage parse must match byte anchor struct"
);
}
#[test]
fn service_context_typed_round_trip() {
let msg = BiopMessage::File(FileMessage {
object_key: &[0x01],
content_size: 3,
object_info_extra: &[],
service_context: vec![
ServiceContext {
context_id: 0xDEADBEEF,
data: &[1, 2, 3],
},
ServiceContext {
context_id: 0x11223344,
data: &[],
},
],
content: b"abc",
});
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let (parsed, consumed) = BiopMessage::parse_at(&buf).unwrap();
assert_eq!(consumed, buf.len(), "consumed must equal total buf len");
assert_eq!(parsed, msg, "parsed must equal original");
let mut buf2 = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut buf2).unwrap();
assert_eq!(
buf, buf2,
"serviceContext typed round-trip must be byte-exact"
);
assert_eq!(buf[32], 2, "serviceContextList_count must be 2");
assert_eq!(&buf[33..37], &[0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(&buf[37..39], &[0x00, 0x03]);
assert_eq!(&buf[39..42], &[0x01, 0x02, 0x03]);
assert_eq!(&buf[42..46], &[0x11, 0x22, 0x33, 0x44]);
assert_eq!(&buf[46..48], &[0x00, 0x00]);
}
#[test]
fn binding_type_full_range_round_trip() {
for v in 0u8..=0xFF {
let bt = BindingType::from_u8(v);
assert_eq!(bt.to_u8(), v, "BindingType round-trip failed for 0x{v:02X}");
}
}
#[test]
fn binding_type_known_values() {
assert_eq!(BindingType::from_u8(0x01), BindingType::NObject);
assert_eq!(BindingType::from_u8(0x02), BindingType::NContext);
assert_eq!(BindingType::NObject.name(), "nobject");
assert_eq!(BindingType::NContext.name(), "ncontext");
assert_eq!(BindingType::Reserved(0x05).name(), "reserved");
}
#[test]
fn directory_message_binding_type_round_trip() {
let msg = sample_dir_message();
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let (parsed, _) = BiopMessage::parse_at(&buf).unwrap();
match parsed {
BiopMessage::Directory(d) => {
assert_eq!(d.bindings[0].binding_type, BindingType::NObject);
}
other => panic!("expected Directory, got {other:?}"),
}
}
#[test]
fn directory_message_oversized_body_length_errors() {
let msg = BiopMessage::Directory(DirectoryMessage {
object_kind: *b"dir\0",
object_key: &[0x01],
object_info: &[],
service_context: vec![],
bindings: vec![],
});
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let off = 12 + 1 + 1 + 4 + 4 + 2 + 1;
assert_eq!(
u32::from_be_bytes(buf[off..off + 4].try_into().unwrap()),
BINDINGS_COUNT_FIELD as u32,
"sanity: with zero bindings, messageBody_length must equal bindings_count field size"
);
buf[off..off + 4].copy_from_slice(&0xFFFF_FFFFu32.to_be_bytes());
assert_eq!(
BiopMessage::parse_at(&buf).err(),
Some(crate::error::Error::SectionLengthOverflow {
declared: 4294967295,
available: 2
}),
"messageBody_length=0xFFFFFFFF must return Err, not panic"
);
}
#[test]
fn file_message_oversized_body_length_errors() {
let content: &[u8] = b"Hello, BIOP!";
let msg = sample_file_message(&[0x01], content);
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let off = 12 + 1 + 1 + 4 + 4 + 2 + 8 + 1;
let expected_body_len = (FILE_CONTENT_LEN_FIELD + content.len()) as u32;
assert_eq!(
u32::from_be_bytes(buf[off..off + 4].try_into().unwrap()),
expected_body_len,
"sanity: computed messageBody_length offset must match the wire value"
);
buf[off..off + 4].copy_from_slice(&0xFFFF_FFFFu32.to_be_bytes());
assert_eq!(
BiopMessage::parse_at(&buf).err(),
Some(crate::error::Error::SectionLengthOverflow {
declared: 4294967295,
available: 16
}),
"messageBody_length=0xFFFFFFFF must return Err, not panic"
);
}
#[test]
fn file_message_oversized_content_length_errors() {
let content: &[u8] = b"Hello, BIOP!";
let msg = sample_file_message(&[0x01], content);
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let body_len_off = 12 + 1 + 1 + 4 + 4 + 2 + 8 + 1;
let off = body_len_off + MESSAGE_BODY_LEN_FIELD;
assert_eq!(
u32::from_be_bytes(buf[off..off + 4].try_into().unwrap()),
content.len() as u32,
"sanity: computed content_length offset must match the wire value"
);
buf[off..off + 4].copy_from_slice(&0xFFFF_FFFFu32.to_be_bytes());
assert_eq!(
BiopMessage::parse_at(&buf).err(),
Some(crate::error::Error::SectionLengthOverflow {
declared: 4294967295,
available: 12
}),
"content_length=0xFFFFFFFF must return Err, not panic"
);
}
#[test]
fn stream_message_oversized_body_length_errors() {
use crate::carousel::biop::ior::Tap;
let msg = BiopMessage::Stream(StreamMessage {
object_key: &[0xAB],
stream_info: DsmStreamInfo {
description: b"vid",
duration_seconds: 0,
duration_microseconds: 0,
audio: 1,
video: 1,
data: 0,
},
object_info_extra: &[],
service_context: vec![],
taps: vec![Tap {
id: 0,
use_: 0x0018,
association_tag: 0x0047,
selector: &[],
}],
});
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let off = 38;
assert_eq!(
&buf[off..off + 4],
&[0x00, 0x00, 0x00, 0x08],
"sanity: messageBody_length offset must match the byte anchor"
);
buf[off..off + 4].copy_from_slice(&0xFFFF_FFFFu32.to_be_bytes());
assert_eq!(
BiopMessage::parse_at(&buf).err(),
Some(crate::error::Error::SectionLengthOverflow {
declared: 4294967295,
available: 8
}),
"messageBody_length=0xFFFFFFFF must return Err, not panic"
);
}
#[test]
fn stream_event_message_oversized_body_length_errors() {
let msg = BiopMessage::StreamEvent(StreamEventMessage {
object_key: &[0xCD],
stream_info: DsmStreamInfo {
description: &[],
duration_seconds: 0,
duration_microseconds: 0,
audio: 0,
video: 0,
data: 0,
},
event_names: vec![b"foo".as_ref(), b"bar".as_ref()],
object_info_extra: &[],
service_context: vec![],
taps: vec![],
event_ids: vec![1, 2],
});
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
let off = 45;
assert_eq!(
&buf[off..off + 4],
&[0x00, 0x00, 0x00, 0x06],
"sanity: messageBody_length offset must match the byte anchor"
);
buf[off..off + 4].copy_from_slice(&0xFFFF_FFFFu32.to_be_bytes());
assert_eq!(
BiopMessage::parse_at(&buf).err(),
Some(crate::error::Error::SectionLengthOverflow {
declared: 4294967295,
available: 6
}),
"messageBody_length=0xFFFFFFFF must return Err, not panic"
);
}
#[test]
fn biop_header_oversized_message_size_errors() {
let content: &[u8] = b"Hello, BIOP!";
let msg = sample_file_message(&[0x01], content);
let mut buf = vec![0u8; msg.serialized_len()];
msg.serialize_into(&mut buf).unwrap();
buf[8..12].copy_from_slice(&0xFFFF_FFFFu32.to_be_bytes());
assert_eq!(
BiopMessage::parse_at(&buf).err(),
Some(crate::error::Error::SectionLengthOverflow {
declared: 4294967295,
available: 41
}),
"message_size=0xFFFFFFFF must return Err, not panic"
);
}
#[cfg(feature = "flate2")]
#[test]
fn decompress_zlib_bounded_rejects_oversized_output() {
use flate2::{Compression, write::ZlibEncoder};
use std::io::Write;
let original = vec![0u8; 1024 * 1024];
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = encoder.finish().unwrap();
let result = decompress_zlib_bounded(&compressed, 1024);
assert_eq!(
result.err(),
Some(crate::error::Error::InvalidField(
broadcast_common::error::InvalidField {
field: "compressed_module_descriptor body",
reason: "zlib output exceeds the decompressed-size limit"
}
)),
"decompressed output exceeding max_len must return Err"
);
}
}