use super::{
BIOP_DELIVERY_PARA_USE, BYTE_ORDER_BIG_ENDIAN, TAG_BIOP, TAG_CONN_BINDER, TAG_LITE_OPTIONS,
TAG_OBJECT_LOCATION, TAG_SERVICE_LOCATION,
};
use crate::error::{Error, Result};
use dvb_common::{Parse, Serialize};
const IOR_FIXED_LEN: usize = 8;
const PROFILE_HEADER_LEN: usize = 8;
const BIOP_BODY_FIXED_LEN: usize = 2;
const COMPONENT_HEADER_LEN: usize = 5;
const OBJECT_LOCATION_FIXED_LEN: usize = 9;
const CONN_BINDER_FIXED_LEN: usize = 1;
const TAP_FIXED_LEN: usize = 7;
const LITE_OPTIONS_BODY_FIXED_LEN: usize = 2;
const SERVICE_LOCATION_COMP_HEADER_LEN: usize = 8;
const SERVICE_DOMAIN_LEN_FIELD: usize = 1;
const NSAP_ADDRESS_LEN: usize = 20;
const _NSAP_FIELDS_LEN: usize = NSAP_ADDRESS_LEN; const NAMING_COUNT_LEN: usize = 4;
const NAMING_FIELD_LEN: usize = 4;
const INITIAL_CONTEXT_LEN_FIELD: usize = 4;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ObjectKind {
Directory,
File,
Stream,
ServiceGateway,
StreamEvent,
Unknown([u8; 4]),
}
impl ObjectKind {
pub fn from_bytes(b: [u8; 4]) -> Self {
match &b {
b"dir\0" => Self::Directory,
b"fil\0" => Self::File,
b"str\0" => Self::Stream,
b"srg\0" => Self::ServiceGateway,
b"ste\0" => Self::StreamEvent,
_ => Self::Unknown(b),
}
}
pub fn to_bytes(&self) -> [u8; 4] {
match self {
Self::Directory => *b"dir\0",
Self::File => *b"fil\0",
Self::Stream => *b"str\0",
Self::ServiceGateway => *b"srg\0",
Self::StreamEvent => *b"ste\0",
Self::Unknown(b) => *b,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Tap<'a> {
pub id: u16,
pub use_: u16,
pub association_tag: u16,
#[cfg_attr(feature = "serde", serde(borrow))]
pub selector: &'a [u8],
}
impl<'a> Tap<'a> {
pub fn transaction_id(&self) -> Option<u32> {
if self.use_ == BIOP_DELIVERY_PARA_USE && self.selector.len() >= 10 {
Some(u32::from_be_bytes([
self.selector[2],
self.selector[3],
self.selector[4],
self.selector[5],
]))
} else {
None
}
}
pub fn timeout(&self) -> Option<u32> {
if self.use_ == BIOP_DELIVERY_PARA_USE && self.selector.len() >= 10 {
Some(u32::from_be_bytes([
self.selector[6],
self.selector[7],
self.selector[8],
self.selector[9],
]))
} else {
None
}
}
pub(crate) fn serialized_len(&self) -> usize {
TAP_FIXED_LEN + self.selector.len()
}
pub(crate) fn parse_from(bytes: &'a [u8], pos: usize, end: usize) -> Result<(Self, usize)> {
if pos + TAP_FIXED_LEN > end {
return Err(Error::BufferTooShort {
need: pos + TAP_FIXED_LEN,
have: end,
what: "BIOP Tap fixed fields",
});
}
let id = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]);
let use_ = u16::from_be_bytes([bytes[pos + 2], bytes[pos + 3]]);
let association_tag = u16::from_be_bytes([bytes[pos + 4], bytes[pos + 5]]);
let selector_length = bytes[pos + 6] as usize;
let data_start = pos + TAP_FIXED_LEN;
if data_start + selector_length > end {
return Err(Error::SectionLengthOverflow {
declared: selector_length,
available: end - data_start,
});
}
let selector = &bytes[data_start..data_start + selector_length];
Ok((
Tap {
id,
use_,
association_tag,
selector,
},
data_start + selector_length,
))
}
pub(crate) fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
if self.selector.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.selector.len(),
available: u8::MAX as usize,
});
}
buf[0..2].copy_from_slice(&self.id.to_be_bytes());
buf[2..4].copy_from_slice(&self.use_.to_be_bytes());
buf[4..6].copy_from_slice(&self.association_tag.to_be_bytes());
buf[6] = self.selector.len() as u8;
buf[7..7 + self.selector.len()].copy_from_slice(self.selector);
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ObjectLocation<'a> {
pub carousel_id: u32,
pub module_id: u16,
pub version_major: u8,
pub version_minor: u8,
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_key: &'a [u8],
}
impl<'a> ObjectLocation<'a> {
fn serialized_len(&self) -> usize {
OBJECT_LOCATION_FIXED_LEN + self.object_key.len()
}
fn parse_from(bytes: &'a [u8], pos: usize, end: usize) -> Result<(Self, usize)> {
if pos + OBJECT_LOCATION_FIXED_LEN > end {
return Err(Error::BufferTooShort {
need: pos + OBJECT_LOCATION_FIXED_LEN,
have: end,
what: "BIOP ObjectLocation fixed fields",
});
}
let carousel_id =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
let module_id = u16::from_be_bytes([bytes[pos + 4], bytes[pos + 5]]);
let version_major = bytes[pos + 6];
let version_minor = bytes[pos + 7];
let object_key_length = bytes[pos + 8] as usize;
let data_start = pos + OBJECT_LOCATION_FIXED_LEN;
if data_start + object_key_length > end {
return Err(Error::SectionLengthOverflow {
declared: object_key_length,
available: end - data_start,
});
}
Ok((
ObjectLocation {
carousel_id,
module_id,
version_major,
version_minor,
object_key: &bytes[data_start..data_start + object_key_length],
},
data_start + object_key_length,
))
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
if self.object_key.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.object_key.len(),
available: u8::MAX as usize,
});
}
buf[0..4].copy_from_slice(&self.carousel_id.to_be_bytes());
buf[4..6].copy_from_slice(&self.module_id.to_be_bytes());
buf[6] = self.version_major;
buf[7] = self.version_minor;
buf[8] = self.object_key.len() as u8;
buf[9..9 + self.object_key.len()].copy_from_slice(self.object_key);
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ConnBinder<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub taps: Vec<Tap<'a>>,
}
impl<'a> ConnBinder<'a> {
fn serialized_len(&self) -> usize {
CONN_BINDER_FIXED_LEN + self.taps.iter().map(|t| t.serialized_len()).sum::<usize>()
}
fn parse_from(bytes: &'a [u8], pos: usize, end: usize) -> Result<(Self, usize)> {
if pos + CONN_BINDER_FIXED_LEN > end {
return Err(Error::BufferTooShort {
need: pos + CONN_BINDER_FIXED_LEN,
have: end,
what: "BIOP ConnBinder taps_count",
});
}
let taps_count = bytes[pos] as usize;
let mut cur = pos + CONN_BINDER_FIXED_LEN;
let mut taps = Vec::with_capacity(taps_count.min(16));
for _ in 0..taps_count {
let (tap, next) = Tap::parse_from(bytes, cur, end)?;
taps.push(tap);
cur = next;
}
Ok((ConnBinder { taps }, cur))
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
if self.taps.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.taps.len(),
available: u8::MAX as usize,
});
}
buf[0] = self.taps.len() as u8;
let mut pos = CONN_BINDER_FIXED_LEN;
for tap in &self.taps {
let written = tap.serialize_into_buf(&mut buf[pos..])?;
pos += written;
}
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LiteComponent<'a> {
pub tag: u32,
#[cfg_attr(feature = "serde", serde(borrow))]
pub data: &'a [u8],
}
impl<'a> LiteComponent<'a> {
fn serialized_len(&self) -> usize {
COMPONENT_HEADER_LEN + self.data.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct BiopProfileBody<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub object_location: ObjectLocation<'a>,
pub conn_binder: ConnBinder<'a>,
pub extra: Vec<LiteComponent<'a>>,
}
impl<'a> BiopProfileBody<'a> {
fn parse_from(bytes: &'a [u8]) -> Result<Self> {
let end = bytes.len();
if end < BIOP_BODY_FIXED_LEN {
return Err(Error::BufferTooShort {
need: BIOP_BODY_FIXED_LEN,
have: end,
what: "BIOP Profile Body fixed fields",
});
}
let byte_order = bytes[0];
if byte_order != BYTE_ORDER_BIG_ENDIAN {
return Err(Error::ReservedBitsViolation {
field: "profile_data_byte_order",
reason:
"must be 0x00 (big-endian) per DVB mandatory constraint (TR 101 202 §4.7.3.2)",
});
}
let lite_components_count = bytes[1] as usize;
if lite_components_count < 2 {
return Err(Error::ValueOutOfRange {
field: "liteComponents_count",
reason: "BIOP Profile Body must have at least 2 components (ObjectLocation + ConnBinder)",
});
}
let mut pos = BIOP_BODY_FIXED_LEN;
if pos + COMPONENT_HEADER_LEN > end {
return Err(Error::BufferTooShort {
need: pos + COMPONENT_HEADER_LEN,
have: end,
what: "BIOP ObjectLocation component header",
});
}
let comp0_tag =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
if comp0_tag != TAG_OBJECT_LOCATION {
return Err(Error::ReservedBitsViolation {
field: "componentId_tag[0]",
reason: "first liteComponent must be TAG_ObjectLocation (0x49534F50)",
});
}
let comp0_len = bytes[pos + 4] as usize;
pos += COMPONENT_HEADER_LEN;
let comp0_end = pos + comp0_len;
if comp0_end > end {
return Err(Error::SectionLengthOverflow {
declared: comp0_len,
available: end - pos,
});
}
let (object_location, _) = ObjectLocation::parse_from(bytes, pos, comp0_end)?;
pos = comp0_end;
if pos + COMPONENT_HEADER_LEN > end {
return Err(Error::BufferTooShort {
need: pos + COMPONENT_HEADER_LEN,
have: end,
what: "BIOP ConnBinder component header",
});
}
let comp1_tag =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
if comp1_tag != TAG_CONN_BINDER {
return Err(Error::ReservedBitsViolation {
field: "componentId_tag[1]",
reason: "second liteComponent must be TAG_ConnBinder (0x49534F40)",
});
}
let comp1_len = bytes[pos + 4] as usize;
pos += COMPONENT_HEADER_LEN;
let comp1_end = pos + comp1_len;
if comp1_end > end {
return Err(Error::SectionLengthOverflow {
declared: comp1_len,
available: end - pos,
});
}
let (conn_binder, _) = ConnBinder::parse_from(bytes, pos, comp1_end)?;
pos = comp1_end;
let extra_count = lite_components_count - 2;
let mut extra = Vec::with_capacity(extra_count.min(8));
for _ in 0..extra_count {
if pos + COMPONENT_HEADER_LEN > end {
return Err(Error::BufferTooShort {
need: pos + COMPONENT_HEADER_LEN,
have: end,
what: "BIOP extra liteComponent header",
});
}
let tag =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
let data_len = bytes[pos + 4] as usize;
pos += COMPONENT_HEADER_LEN;
if pos + data_len > end {
return Err(Error::SectionLengthOverflow {
declared: data_len,
available: end - pos,
});
}
extra.push(LiteComponent {
tag,
data: &bytes[pos..pos + data_len],
});
pos += data_len;
}
Ok(BiopProfileBody {
object_location,
conn_binder,
extra,
})
}
fn serialized_len(&self) -> usize {
let ol_len = self.object_location.serialized_len();
let cb_len = self.conn_binder.serialized_len();
let extra_len: usize = self.extra.iter().map(|c| c.serialized_len()).sum();
BIOP_BODY_FIXED_LEN
+ COMPONENT_HEADER_LEN
+ ol_len
+ COMPONENT_HEADER_LEN
+ cb_len
+ extra_len
}
fn serialize_into_buf(&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 total_components = 2 + self.extra.len();
if total_components > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: total_components,
available: u8::MAX as usize,
});
}
buf[0] = BYTE_ORDER_BIG_ENDIAN;
buf[1] = total_components as u8;
let mut pos = BIOP_BODY_FIXED_LEN;
let ol_data_len = self.object_location.serialized_len();
if ol_data_len > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: ol_data_len,
available: u8::MAX as usize,
});
}
buf[pos..pos + 4].copy_from_slice(&TAG_OBJECT_LOCATION.to_be_bytes());
buf[pos + 4] = ol_data_len as u8;
pos += COMPONENT_HEADER_LEN;
let written = self.object_location.serialize_into_buf(&mut buf[pos..])?;
pos += written;
let cb_data_len = self.conn_binder.serialized_len();
if cb_data_len > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: cb_data_len,
available: u8::MAX as usize,
});
}
buf[pos..pos + 4].copy_from_slice(&TAG_CONN_BINDER.to_be_bytes());
buf[pos + 4] = cb_data_len as u8;
pos += COMPONENT_HEADER_LEN;
let written = self.conn_binder.serialize_into_buf(&mut buf[pos..])?;
pos += written;
for comp in &self.extra {
let data_len = comp.data.len();
if data_len > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: data_len,
available: u8::MAX as usize,
});
}
buf[pos..pos + 4].copy_from_slice(&comp.tag.to_be_bytes());
buf[pos + 4] = data_len as u8;
pos += COMPONENT_HEADER_LEN;
buf[pos..pos + data_len].copy_from_slice(comp.data);
pos += data_len;
}
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NsapAddress {
pub carousel_id: u32,
pub specifier_data: [u8; 3],
pub transport_stream_id: u16,
pub original_network_id: u16,
pub service_id: u16,
}
impl NsapAddress {
const AFI: u8 = 0x00;
const NSAP_TYPE: u8 = 0x00;
const SPECIFIER_TYPE: u8 = 0x01;
const RESERVED: u32 = 0xFFFF_FFFF;
fn parse_from(bytes: &[u8], pos: usize) -> Result<Self> {
let end = pos + NSAP_ADDRESS_LEN;
if bytes.len() < end {
return Err(Error::BufferTooShort {
need: end,
have: bytes.len(),
what: "NSAP address (20 bytes)",
});
}
let carousel_id = u32::from_be_bytes([
bytes[pos + 2],
bytes[pos + 3],
bytes[pos + 4],
bytes[pos + 5],
]);
let specifier_data = [bytes[pos + 7], bytes[pos + 8], bytes[pos + 9]];
let transport_stream_id = u16::from_be_bytes([bytes[pos + 10], bytes[pos + 11]]);
let original_network_id = u16::from_be_bytes([bytes[pos + 12], bytes[pos + 13]]);
let service_id = u16::from_be_bytes([bytes[pos + 14], bytes[pos + 15]]);
Ok(NsapAddress {
carousel_id,
specifier_data,
transport_stream_id,
original_network_id,
service_id,
})
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
if buf.len() < NSAP_ADDRESS_LEN {
return Err(Error::OutputBufferTooSmall {
need: NSAP_ADDRESS_LEN,
have: buf.len(),
});
}
buf[0] = Self::AFI;
buf[1] = Self::NSAP_TYPE;
buf[2..6].copy_from_slice(&self.carousel_id.to_be_bytes());
buf[6] = Self::SPECIFIER_TYPE;
buf[7..10].copy_from_slice(&self.specifier_data);
buf[10..12].copy_from_slice(&self.transport_stream_id.to_be_bytes());
buf[12..14].copy_from_slice(&self.original_network_id.to_be_bytes());
buf[14..16].copy_from_slice(&self.service_id.to_be_bytes());
buf[16..20].copy_from_slice(&Self::RESERVED.to_be_bytes());
Ok(NSAP_ADDRESS_LEN)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct NameComponent<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub id: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow))]
pub kind: &'a [u8],
}
impl<'a> NameComponent<'a> {
pub(crate) fn serialized_len_32bit(&self) -> usize {
NAMING_FIELD_LEN + self.id.len() + NAMING_FIELD_LEN + self.kind.len()
}
pub(crate) fn parse_32bit(bytes: &'a [u8], pos: usize, end: usize) -> Result<(Self, usize)> {
if pos + NAMING_FIELD_LEN > end {
return Err(Error::BufferTooShort {
need: pos + NAMING_FIELD_LEN,
have: end,
what: "CosNaming id_length",
});
}
let id_len =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]])
as usize;
let id_start = pos + NAMING_FIELD_LEN;
if id_start + id_len > end {
return Err(Error::SectionLengthOverflow {
declared: id_len,
available: end - id_start,
});
}
let id = &bytes[id_start..id_start + id_len];
let kind_pos = id_start + id_len;
if kind_pos + NAMING_FIELD_LEN > end {
return Err(Error::BufferTooShort {
need: kind_pos + NAMING_FIELD_LEN,
have: end,
what: "CosNaming kind_length",
});
}
let kind_len = u32::from_be_bytes([
bytes[kind_pos],
bytes[kind_pos + 1],
bytes[kind_pos + 2],
bytes[kind_pos + 3],
]) as usize;
let kind_start = kind_pos + NAMING_FIELD_LEN;
if kind_start + kind_len > end {
return Err(Error::SectionLengthOverflow {
declared: kind_len,
available: end - kind_start,
});
}
let kind = &bytes[kind_start..kind_start + kind_len];
Ok((NameComponent { id, kind }, kind_start + kind_len))
}
pub(crate) fn serialize_32bit(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len_32bit();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
buf[0..4].copy_from_slice(&(self.id.len() as u32).to_be_bytes());
buf[4..4 + self.id.len()].copy_from_slice(self.id);
let kind_pos = 4 + self.id.len();
buf[kind_pos..kind_pos + 4].copy_from_slice(&(self.kind.len() as u32).to_be_bytes());
buf[kind_pos + 4..kind_pos + 4 + self.kind.len()].copy_from_slice(self.kind);
Ok(len)
}
pub(crate) fn serialized_len_8bit(&self) -> usize {
1 + self.id.len() + 1 + self.kind.len()
}
pub(crate) fn parse_8bit(bytes: &'a [u8], pos: usize, end: usize) -> Result<(Self, usize)> {
if pos + 1 > end {
return Err(Error::BufferTooShort {
need: pos + 1,
have: end,
what: "BIOP NameComponent id_length (8-bit)",
});
}
let id_len = bytes[pos] as usize;
let id_start = pos + 1;
if id_start + id_len > end {
return Err(Error::SectionLengthOverflow {
declared: id_len,
available: end - id_start,
});
}
let id = &bytes[id_start..id_start + id_len];
let kind_pos = id_start + id_len;
if kind_pos + 1 > end {
return Err(Error::BufferTooShort {
need: kind_pos + 1,
have: end,
what: "BIOP NameComponent kind_length (8-bit)",
});
}
let kind_len = bytes[kind_pos] as usize;
let kind_start = kind_pos + 1;
if kind_start + kind_len > end {
return Err(Error::SectionLengthOverflow {
declared: kind_len,
available: end - kind_start,
});
}
let kind = &bytes[kind_start..kind_start + kind_len];
Ok((NameComponent { id, kind }, kind_start + kind_len))
}
pub(crate) fn serialize_8bit(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len_8bit();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
if self.id.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.id.len(),
available: u8::MAX as usize,
});
}
if self.kind.len() > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: self.kind.len(),
available: u8::MAX as usize,
});
}
buf[0] = self.id.len() as u8;
buf[1..1 + self.id.len()].copy_from_slice(self.id);
let kind_pos = 1 + self.id.len();
buf[kind_pos] = self.kind.len() as u8;
buf[kind_pos + 1..kind_pos + 1 + self.kind.len()].copy_from_slice(self.kind);
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ServiceLocation<'a> {
pub service_domain: NsapAddress,
#[cfg_attr(feature = "serde", serde(borrow))]
pub path: Vec<NameComponent<'a>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub initial_context: &'a [u8],
}
impl<'a> ServiceLocation<'a> {
fn serialized_len(&self) -> usize {
SERVICE_DOMAIN_LEN_FIELD
+ NSAP_ADDRESS_LEN
+ NAMING_COUNT_LEN
+ self
.path
.iter()
.map(|c| c.serialized_len_32bit())
.sum::<usize>()
+ INITIAL_CONTEXT_LEN_FIELD
+ self.initial_context.len()
}
fn parse_from(bytes: &'a [u8], pos: usize, end: usize) -> Result<(Self, usize)> {
if pos + SERVICE_DOMAIN_LEN_FIELD > end {
return Err(Error::BufferTooShort {
need: pos + SERVICE_DOMAIN_LEN_FIELD,
have: end,
what: "ServiceLocation serviceDomain_length",
});
}
let sd_len = bytes[pos] as usize;
if sd_len != NSAP_ADDRESS_LEN {
return Err(Error::ValueOutOfRange {
field: "serviceDomain_length",
reason: "DVB Carousel NSAP address must be exactly 20 bytes (0x14)",
});
}
let sd_start = pos + SERVICE_DOMAIN_LEN_FIELD;
if sd_start + NSAP_ADDRESS_LEN > end {
return Err(Error::BufferTooShort {
need: sd_start + NSAP_ADDRESS_LEN,
have: end,
what: "ServiceLocation serviceDomain_data",
});
}
let service_domain = NsapAddress::parse_from(bytes, sd_start)?;
let mut cur = sd_start + NSAP_ADDRESS_LEN;
if cur + NAMING_COUNT_LEN > end {
return Err(Error::BufferTooShort {
need: cur + NAMING_COUNT_LEN,
have: end,
what: "ServiceLocation nameComponents_count",
});
}
let name_count =
u32::from_be_bytes([bytes[cur], bytes[cur + 1], bytes[cur + 2], bytes[cur + 3]])
as usize;
cur += NAMING_COUNT_LEN;
let mut path = Vec::with_capacity(name_count.min(16));
for _ in 0..name_count {
let (nc, next) = NameComponent::parse_32bit(bytes, cur, end)?;
path.push(nc);
cur = next;
}
if cur + INITIAL_CONTEXT_LEN_FIELD > end {
return Err(Error::BufferTooShort {
need: cur + INITIAL_CONTEXT_LEN_FIELD,
have: end,
what: "ServiceLocation initialContext_length",
});
}
let ic_len =
u32::from_be_bytes([bytes[cur], bytes[cur + 1], bytes[cur + 2], bytes[cur + 3]])
as usize;
cur += INITIAL_CONTEXT_LEN_FIELD;
if cur + ic_len > end {
return Err(Error::SectionLengthOverflow {
declared: ic_len,
available: end - cur,
});
}
let initial_context = &bytes[cur..cur + ic_len];
cur += ic_len;
Ok((
ServiceLocation {
service_domain,
path,
initial_context,
},
cur,
))
}
fn serialize_into_buf(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
buf[0] = NSAP_ADDRESS_LEN as u8; self.service_domain
.serialize_into_buf(&mut buf[1..1 + NSAP_ADDRESS_LEN])?;
let mut pos = SERVICE_DOMAIN_LEN_FIELD + NSAP_ADDRESS_LEN;
buf[pos..pos + 4].copy_from_slice(&(self.path.len() as u32).to_be_bytes());
pos += NAMING_COUNT_LEN;
for nc in &self.path {
let written = nc.serialize_32bit(&mut buf[pos..])?;
pos += written;
}
let ic_len = self.initial_context.len();
buf[pos..pos + 4].copy_from_slice(&(ic_len as u32).to_be_bytes());
pos += INITIAL_CONTEXT_LEN_FIELD;
buf[pos..pos + ic_len].copy_from_slice(self.initial_context);
pos += ic_len;
Ok(pos)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LiteOptionsProfileBody<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub service_location: ServiceLocation<'a>,
pub extra: Vec<LiteComponent<'a>>,
}
impl<'a> LiteOptionsProfileBody<'a> {
fn parse_from(bytes: &'a [u8]) -> Result<Self> {
let end = bytes.len();
if end < LITE_OPTIONS_BODY_FIXED_LEN {
return Err(Error::BufferTooShort {
need: LITE_OPTIONS_BODY_FIXED_LEN,
have: end,
what: "LiteOptions Profile Body fixed fields",
});
}
let byte_order = bytes[0];
if byte_order != BYTE_ORDER_BIG_ENDIAN {
return Err(Error::ReservedBitsViolation {
field: "profile_data_byte_order (LiteOptions)",
reason: "must be 0x00 (big-endian) per DVB mandatory constraint",
});
}
let component_count = bytes[1] as usize;
if component_count < 1 {
return Err(Error::ValueOutOfRange {
field: "component_count (LiteOptions)",
reason: "must have at least 1 component (ServiceLocation)",
});
}
let mut pos = LITE_OPTIONS_BODY_FIXED_LEN;
if pos + SERVICE_LOCATION_COMP_HEADER_LEN > end {
return Err(Error::BufferTooShort {
need: pos + SERVICE_LOCATION_COMP_HEADER_LEN,
have: end,
what: "LiteOptions ServiceLocation component header",
});
}
let comp0_tag =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
if comp0_tag != TAG_SERVICE_LOCATION {
return Err(Error::ReservedBitsViolation {
field: "componentId_tag[0] (LiteOptions)",
reason: "first component must be TAG_ServiceLocation (0x49534F46)",
});
}
let comp0_len = u32::from_be_bytes([
bytes[pos + 4],
bytes[pos + 5],
bytes[pos + 6],
bytes[pos + 7],
]) as usize;
pos += SERVICE_LOCATION_COMP_HEADER_LEN;
let comp0_end = pos + comp0_len;
if comp0_end > end {
return Err(Error::SectionLengthOverflow {
declared: comp0_len,
available: end - pos,
});
}
let (service_location, _) = ServiceLocation::parse_from(bytes, pos, comp0_end)?;
pos = comp0_end;
let extra_count = component_count - 1;
let mut extra = Vec::with_capacity(extra_count.min(8));
for _ in 0..extra_count {
if pos + COMPONENT_HEADER_LEN > end {
return Err(Error::BufferTooShort {
need: pos + COMPONENT_HEADER_LEN,
have: end,
what: "LiteOptions extra component header",
});
}
let tag =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
let data_len = bytes[pos + 4] as usize;
pos += COMPONENT_HEADER_LEN;
if pos + data_len > end {
return Err(Error::SectionLengthOverflow {
declared: data_len,
available: end - pos,
});
}
extra.push(LiteComponent {
tag,
data: &bytes[pos..pos + data_len],
});
pos += data_len;
}
Ok(LiteOptionsProfileBody {
service_location,
extra,
})
}
fn serialized_len(&self) -> usize {
let sl_data_len = self.service_location.serialized_len();
let extra_len: usize = self.extra.iter().map(|c| c.serialized_len()).sum();
LITE_OPTIONS_BODY_FIXED_LEN + SERVICE_LOCATION_COMP_HEADER_LEN + sl_data_len + extra_len
}
fn serialize_into_buf(&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 total_components = 1 + self.extra.len();
if total_components > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: total_components,
available: u8::MAX as usize,
});
}
buf[0] = BYTE_ORDER_BIG_ENDIAN;
buf[1] = total_components as u8;
let mut pos = LITE_OPTIONS_BODY_FIXED_LEN;
let sl_data_len = self.service_location.serialized_len();
buf[pos..pos + 4].copy_from_slice(&TAG_SERVICE_LOCATION.to_be_bytes());
buf[pos + 4..pos + 8].copy_from_slice(&(sl_data_len as u32).to_be_bytes());
pos += SERVICE_LOCATION_COMP_HEADER_LEN;
let written = self.service_location.serialize_into_buf(&mut buf[pos..])?;
pos += written;
for comp in &self.extra {
let data_len = comp.data.len();
if data_len > u8::MAX as usize {
return Err(Error::SectionLengthOverflow {
declared: data_len,
available: u8::MAX as usize,
});
}
buf[pos..pos + 4].copy_from_slice(&comp.tag.to_be_bytes());
buf[pos + 4] = data_len as u8;
pos += COMPONENT_HEADER_LEN;
buf[pos..pos + data_len].copy_from_slice(comp.data);
pos += data_len;
}
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum TaggedProfile<'a> {
Biop(BiopProfileBody<'a>),
LiteOptions(LiteOptionsProfileBody<'a>),
Unknown {
tag: u32,
#[cfg_attr(feature = "serde", serde(borrow))]
data: &'a [u8],
},
}
impl<'a> TaggedProfile<'a> {
fn serialized_len(&self) -> usize {
let data_len = match self {
Self::Biop(b) => b.serialized_len(),
Self::LiteOptions(l) => l.serialized_len(),
Self::Unknown { data, .. } => data.len(),
};
PROFILE_HEADER_LEN + data_len
}
fn serialize_into_buf(&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 (tag, data_len) = match self {
Self::Biop(b) => (TAG_BIOP, b.serialized_len()),
Self::LiteOptions(l) => (TAG_LITE_OPTIONS, l.serialized_len()),
Self::Unknown { tag, data } => (*tag, data.len()),
};
buf[0..4].copy_from_slice(&tag.to_be_bytes());
buf[4..8].copy_from_slice(&(data_len as u32).to_be_bytes());
let pos = PROFILE_HEADER_LEN;
match self {
Self::Biop(b) => {
b.serialize_into_buf(&mut buf[pos..])?;
}
Self::LiteOptions(l) => {
l.serialize_into_buf(&mut buf[pos..])?;
}
Self::Unknown { data, .. } => {
buf[pos..pos + data.len()].copy_from_slice(data);
}
}
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Ior<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub type_id: &'a [u8],
pub profiles: Vec<TaggedProfile<'a>>,
}
impl<'a> Ior<'a> {
pub fn object_kind(&self) -> ObjectKind {
if self.type_id.len() == 4 {
let mut arr = [0u8; 4];
arr.copy_from_slice(self.type_id);
ObjectKind::from_bytes(arr)
} else {
ObjectKind::Unknown([0; 4])
}
}
pub fn biop_profile(&self) -> Option<&BiopProfileBody<'a>> {
for p in &self.profiles {
if let TaggedProfile::Biop(b) = p {
return Some(b);
}
}
None
}
}
impl<'a> Parse<'a> for Ior<'a> {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let end = bytes.len();
if end < IOR_FIXED_LEN {
return Err(Error::BufferTooShort {
need: IOR_FIXED_LEN,
have: end,
what: "IOP::IOR fixed fields",
});
}
let type_id_length = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
if type_id_length % 4 != 0 {
return Err(Error::ValueOutOfRange {
field: "IOR.type_id_length",
reason: "type_id_length must be a multiple of 4 (DVB alias type_ids only — \
non-aligned type_ids are not supported per TR 101 202 §4.7.3.1)",
});
}
let mut pos = 4;
if pos + type_id_length > end {
return Err(Error::SectionLengthOverflow {
declared: type_id_length,
available: end - pos,
});
}
let type_id = &bytes[pos..pos + type_id_length];
pos += type_id_length;
if pos + 4 > end {
return Err(Error::BufferTooShort {
need: pos + 4,
have: end,
what: "IOR taggedProfiles_count",
});
}
let profiles_count =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]])
as usize;
pos += 4;
let mut profiles = Vec::with_capacity(profiles_count.min(8));
for _ in 0..profiles_count {
if pos + PROFILE_HEADER_LEN > end {
return Err(Error::BufferTooShort {
need: pos + PROFILE_HEADER_LEN,
have: end,
what: "TaggedProfile header",
});
}
let tag =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
let data_len = u32::from_be_bytes([
bytes[pos + 4],
bytes[pos + 5],
bytes[pos + 6],
bytes[pos + 7],
]) as usize;
pos += PROFILE_HEADER_LEN;
if pos + data_len > end {
return Err(Error::SectionLengthOverflow {
declared: data_len,
available: end - pos,
});
}
let profile_data = &bytes[pos..pos + data_len];
let profile = match tag {
TAG_BIOP => TaggedProfile::Biop(BiopProfileBody::parse_from(profile_data)?),
TAG_LITE_OPTIONS => {
TaggedProfile::LiteOptions(LiteOptionsProfileBody::parse_from(profile_data)?)
}
_ => TaggedProfile::Unknown {
tag,
data: profile_data,
},
};
profiles.push(profile);
pos += data_len;
}
Ok(Ior { type_id, profiles })
}
}
impl Serialize for Ior<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
let type_id_len = self.type_id.len();
let profiles_len: usize = self.profiles.iter().map(|p| p.serialized_len()).sum();
4 + type_id_len
+ 4 + profiles_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(),
});
}
if self.type_id.len() % 4 != 0 {
return Err(Error::ValueOutOfRange {
field: "IOR.type_id_length",
reason: "type_id_length must be a multiple of 4 (DVB alias type_ids only)",
});
}
buf[0..4].copy_from_slice(&(self.type_id.len() as u32).to_be_bytes());
buf[4..4 + self.type_id.len()].copy_from_slice(self.type_id);
let mut pos = 4 + self.type_id.len();
buf[pos..pos + 4].copy_from_slice(&(self.profiles.len() as u32).to_be_bytes());
pos += 4;
for profile in &self.profiles {
let written = profile.serialize_into_buf(&mut buf[pos..])?;
pos += written;
}
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
use dvb_common::Parse;
fn sample_ior() -> Vec<u8> {
#[rustfmt::skip]
let bytes: &[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,
];
bytes.to_vec()
}
#[test]
fn ior_round_trip() {
let raw = sample_ior();
let ior = Ior::parse(&raw).unwrap();
let mut out = vec![0u8; ior.serialized_len()];
ior.serialize_into(&mut out).unwrap();
assert_eq!(out, raw, "IOR round-trip byte-exact");
}
#[test]
fn ior_byte_anchor_m6_sgw() {
let raw = sample_ior();
let ior = Ior::parse(&raw).unwrap();
assert_eq!(ior.type_id, b"srg\0");
assert_eq!(ior.object_kind(), ObjectKind::ServiceGateway);
assert_eq!(ior.profiles.len(), 1);
let bp = 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.id, 0);
assert_eq!(tap.use_, 0x0016);
assert_eq!(tap.association_tag, 0x47);
assert_eq!(
tap.selector,
&[0x00, 0x01, 0x80, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF]
);
assert_eq!(tap.transaction_id(), Some(0x80000002));
assert_eq!(tap.timeout(), Some(0xFFFFFFFF));
}
#[test]
fn ior_rejects_non_aligned_type_id() {
let bytes: &[u8] = &[
0x00, 0x00, 0x00, 0x03, 0x64, 0x69, 0x72, 0x00, 0x00, 0x00, 0x00,
];
assert!(matches!(
Ior::parse(bytes).unwrap_err(),
crate::error::Error::ValueOutOfRange {
field: "IOR.type_id_length",
..
}
));
}
#[test]
fn object_kind_roundtrip() {
let kinds = [
ObjectKind::Directory,
ObjectKind::File,
ObjectKind::Stream,
ObjectKind::ServiceGateway,
ObjectKind::StreamEvent,
ObjectKind::Unknown([0x01, 0x02, 0x03, 0x04]),
];
for k in &kinds {
let b = k.to_bytes();
assert_eq!(ObjectKind::from_bytes(b), *k);
}
}
#[cfg(feature = "serde")]
#[test]
fn ior_serde_round_trip() {
let raw = sample_ior();
let ior = Ior::parse(&raw).unwrap();
let json = serde_json::to_string(&ior).unwrap();
assert!(
json.contains("carousel_id"),
"JSON must contain carousel_id field"
);
assert!(
json.contains("\"Biop\""),
"JSON must contain Biop profile variant"
);
}
}