use alloc::vec::Vec;
use crate::error::{Error, Result};
use crate::version::EmsgVersion;
pub const EMSG_BOX_TYPE: [u8; 4] = *b"emsg";
pub const FULLBOX_HEADER_LEN: usize = 12;
pub const EMSG_FLAGS: u32 = 0;
pub const STRING_TERMINATOR: u8 = 0x00;
const U32_LEN: usize = 4;
const U64_LEN: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum PresentationTime {
Delta(u32),
Absolute(u64),
}
impl PresentationTime {
pub fn version(self) -> EmsgVersion {
match self {
PresentationTime::Delta(_) => EmsgVersion::SegmentRelative,
PresentationTime::Absolute(_) => EmsgVersion::RepresentationRelative,
}
}
pub fn name(&self) -> &'static str {
match self {
PresentationTime::Delta(_) => "presentation_time_delta",
PresentationTime::Absolute(_) => "presentation_time",
}
}
}
dvb_common::impl_spec_display!(PresentationTime, Delta, Absolute);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EmsgBox<'a> {
pub scheme_id_uri: &'a str,
pub value: &'a str,
pub timescale: u32,
pub presentation_time: PresentationTime,
pub event_duration: u32,
pub id: u32,
#[cfg_attr(feature = "serde", serde(skip))]
pub message_data: &'a [u8],
}
impl<'a> EmsgBox<'a> {
pub fn version(&self) -> EmsgVersion {
self.presentation_time.version()
}
pub fn is_scte35(&self) -> bool {
self.scheme_id_uri.starts_with(SCTE35_SCHEME_PREFIX)
}
pub fn serialized_len(&self) -> usize {
FULLBOX_HEADER_LEN + self.body_len()
}
fn body_len(&self) -> usize {
let strings = self.scheme_id_uri.len()
+ 1 + self.value.len()
+ 1; let ints = match self.presentation_time {
PresentationTime::Delta(_) => U32_LEN * 4,
PresentationTime::Absolute(_) => U32_LEN * 3 + U64_LEN,
};
strings + ints + self.message_data.len()
}
pub fn parse(data: &'a [u8]) -> Result<Self> {
if data.len() < FULLBOX_HEADER_LEN {
return Err(Error::BufferTooShort {
need: FULLBOX_HEADER_LEN,
have: data.len(),
what: "emsg FullBox header",
});
}
let size = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let box_type = [data[4], data[5], data[6], data[7]];
if box_type != EMSG_BOX_TYPE {
return Err(Error::NotEmsg { found: box_type });
}
let version = data[8];
if (size as usize) < FULLBOX_HEADER_LEN {
return Err(Error::InvalidSize {
size,
reason: "size smaller than the FullBox header (0/1 large-size forms unsupported)",
});
}
if size as usize > data.len() {
return Err(Error::InvalidSize {
size,
reason: "size exceeds available bytes",
});
}
let version = EmsgVersion::from_u8(version).ok_or(Error::UnsupportedVersion { version })?;
let body = &data[FULLBOX_HEADER_LEN..size as usize];
match version {
EmsgVersion::SegmentRelative => Self::parse_v0(body),
EmsgVersion::RepresentationRelative => Self::parse_v1(body),
}
}
fn parse_v0(body: &'a [u8]) -> Result<Self> {
let (scheme_id_uri, rest) = parse_cstr(body, "scheme_id_uri")?;
let (value, rest) = parse_cstr(rest, "value")?;
let need = U32_LEN * 4;
if rest.len() < need {
return Err(Error::BufferTooShort {
need,
have: rest.len(),
what: "emsg v0 integer fields",
});
}
let timescale = read_u32(&rest[0..U32_LEN]);
let delta = read_u32(&rest[U32_LEN..U32_LEN * 2]);
let event_duration = read_u32(&rest[U32_LEN * 2..U32_LEN * 3]);
let id = read_u32(&rest[U32_LEN * 3..U32_LEN * 4]);
let message_data = &rest[need..];
Ok(EmsgBox {
scheme_id_uri,
value,
timescale,
presentation_time: PresentationTime::Delta(delta),
event_duration,
id,
message_data,
})
}
fn parse_v1(body: &'a [u8]) -> Result<Self> {
let need = U32_LEN + U64_LEN + U32_LEN + U32_LEN;
if body.len() < need {
return Err(Error::BufferTooShort {
need,
have: body.len(),
what: "emsg v1 integer fields",
});
}
let timescale = read_u32(&body[0..U32_LEN]);
let presentation_time = read_u64(&body[U32_LEN..U32_LEN + U64_LEN]);
let mut off = U32_LEN + U64_LEN;
let event_duration = read_u32(&body[off..off + U32_LEN]);
off += U32_LEN;
let id = read_u32(&body[off..off + U32_LEN]);
off += U32_LEN;
let (scheme_id_uri, rest) = parse_cstr(&body[off..], "scheme_id_uri")?;
let (value, message_data) = parse_cstr(rest, "value")?;
Ok(EmsgBox {
scheme_id_uri,
value,
timescale,
presentation_time: PresentationTime::Absolute(presentation_time),
event_duration,
id,
message_data,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if out.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: out.len(),
});
}
if total > u32::MAX as usize {
return Err(Error::FieldTooWide {
what: "size",
value: total as u64,
bits: 32,
});
}
out[0..U32_LEN].copy_from_slice(&(total as u32).to_be_bytes());
out[4..8].copy_from_slice(&EMSG_BOX_TYPE);
out[8] = self.version().to_u8();
out[9] = 0;
out[10] = 0;
out[11] = 0;
let mut off = FULLBOX_HEADER_LEN;
match self.presentation_time {
PresentationTime::Delta(delta) => {
off = write_cstr(out, off, self.scheme_id_uri);
off = write_cstr(out, off, self.value);
off = write_u32(out, off, self.timescale);
off = write_u32(out, off, delta);
off = write_u32(out, off, self.event_duration);
off = write_u32(out, off, self.id);
}
PresentationTime::Absolute(pt) => {
off = write_u32(out, off, self.timescale);
off = write_u64(out, off, pt);
off = write_u32(out, off, self.event_duration);
off = write_u32(out, off, self.id);
off = write_cstr(out, off, self.scheme_id_uri);
off = write_cstr(out, off, self.value);
}
}
out[off..off + self.message_data.len()].copy_from_slice(self.message_data);
off += self.message_data.len();
debug_assert_eq!(off, total);
Ok(off)
}
pub fn to_vec(&self) -> Result<Vec<u8>> {
let mut out = alloc::vec![0u8; self.serialized_len()];
self.serialize_into(&mut out)?;
Ok(out)
}
}
pub const SCTE35_SCHEME_PREFIX: &str = "urn:scte:scte35";
fn parse_cstr<'a>(data: &'a [u8], field: &'static str) -> Result<(&'a str, &'a [u8])> {
let term = data
.iter()
.position(|&b| b == STRING_TERMINATOR)
.ok_or(Error::InvalidString {
field,
reason: "missing null terminator",
})?;
let s = core::str::from_utf8(&data[..term]).map_err(|_| Error::InvalidString {
field,
reason: "invalid UTF-8",
})?;
Ok((s, &data[term + 1..]))
}
fn write_cstr(out: &mut [u8], off: usize, s: &str) -> usize {
let bytes = s.as_bytes();
out[off..off + bytes.len()].copy_from_slice(bytes);
let term = off + bytes.len();
out[term] = STRING_TERMINATOR;
term + 1
}
fn read_u32(b: &[u8]) -> u32 {
u32::from_be_bytes([b[0], b[1], b[2], b[3]])
}
fn read_u64(b: &[u8]) -> u64 {
u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
}
fn write_u32(out: &mut [u8], off: usize, v: u32) -> usize {
out[off..off + U32_LEN].copy_from_slice(&v.to_be_bytes());
off + U32_LEN
}
fn write_u64(out: &mut [u8], off: usize, v: u64) -> usize {
out[off..off + U64_LEN].copy_from_slice(&v.to_be_bytes());
off + U64_LEN
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn v0_exact_wire_bytes_round_trip() {
let msg = [0xDEu8, 0xAD, 0xBE, 0xEF];
let b = EmsgBox {
scheme_id_uri: "urn:example:scheme",
value: "",
timescale: 90_000,
presentation_time: PresentationTime::Delta(0x0001_0203),
event_duration: 0xFFFF_FFFF,
id: 0x0000_002A,
message_data: &msg,
};
assert_eq!(b.version(), EmsgVersion::SegmentRelative);
let out = b.to_vec().unwrap();
assert_eq!(&out[0..4], &(out.len() as u32).to_be_bytes());
assert_eq!(&out[4..8], b"emsg");
assert_eq!(out[8], 0); assert_eq!(&out[9..12], &[0, 0, 0]);
let scheme = b"urn:example:scheme\x00";
assert_eq!(&out[12..12 + scheme.len()], scheme);
let mut off = 12 + scheme.len();
assert_eq!(out[off], 0x00); off += 1;
assert_eq!(&out[off..off + 4], &90_000u32.to_be_bytes());
off += 4;
assert_eq!(&out[off..off + 4], &0x0001_0203u32.to_be_bytes()); off += 4;
assert_eq!(&out[off..off + 4], &0xFFFF_FFFFu32.to_be_bytes());
off += 4;
assert_eq!(&out[off..off + 4], &0x0000_002Au32.to_be_bytes());
off += 4;
assert_eq!(&out[off..], &msg);
assert_eq!(EmsgBox::parse(&out).unwrap(), b);
}
#[test]
fn v1_exact_wire_bytes_round_trip() {
let msg = [0x01u8, 0x02];
let b = EmsgBox {
scheme_id_uri: "urn:scte:scte35:2013:bin",
value: "1001",
timescale: 1000,
presentation_time: PresentationTime::Absolute(0x0102_0304_0506_0708),
event_duration: 250,
id: 7,
message_data: &msg,
};
assert_eq!(b.version(), EmsgVersion::RepresentationRelative);
assert!(b.is_scte35());
let out = b.to_vec().unwrap();
assert_eq!(out[8], 1); let mut off = 12;
assert_eq!(&out[off..off + 4], &1000u32.to_be_bytes());
off += 4;
assert_eq!(&out[off..off + 8], &0x0102_0304_0506_0708u64.to_be_bytes());
off += 8;
assert_eq!(&out[off..off + 4], &250u32.to_be_bytes());
off += 4;
assert_eq!(&out[off..off + 4], &7u32.to_be_bytes());
off += 4;
let scheme = b"urn:scte:scte35:2013:bin\x00";
assert_eq!(&out[off..off + scheme.len()], scheme);
off += scheme.len();
let value = b"1001\x00";
assert_eq!(&out[off..off + value.len()], value);
off += value.len();
assert_eq!(&out[off..], &msg);
assert_eq!(EmsgBox::parse(&out).unwrap(), b);
}
#[test]
fn v0_v1_field_order_differs_but_both_round_trip() {
let msg = [0xAAu8, 0xBB];
let v0 = EmsgBox {
scheme_id_uri: "s",
value: "v",
timescale: 0x1111_1111,
presentation_time: PresentationTime::Delta(0x2222_2222),
event_duration: 0x3333_3333,
id: 0x4444_4444,
message_data: &msg,
};
let v1 = EmsgBox {
scheme_id_uri: "s",
value: "v",
timescale: 0x1111_1111,
presentation_time: PresentationTime::Absolute(0x2222_2222),
event_duration: 0x3333_3333,
id: 0x4444_4444,
message_data: &msg,
};
let o0 = v0.to_vec().unwrap();
let o1 = v1.to_vec().unwrap();
assert_eq!(&o0[12..14], b"s\x00");
assert_eq!(&o1[12..16], &0x1111_1111u32.to_be_bytes());
assert_ne!(o0, o1);
assert_eq!(EmsgBox::parse(&o0).unwrap(), v0);
assert_eq!(EmsgBox::parse(&o1).unwrap(), v1);
}
#[test]
fn mutating_a_field_changes_wire_bytes() {
let mk = |id: u32| EmsgBox {
scheme_id_uri: "urn:x",
value: "",
timescale: 48_000,
presentation_time: PresentationTime::Delta(10),
event_duration: 20,
id,
message_data: &[],
};
let a = mk(1).to_vec().unwrap();
let b = mk(2).to_vec().unwrap();
assert_ne!(a, b);
assert_eq!(a.len(), b.len());
}
#[test]
fn rejects_non_emsg_type() {
let mut data = vec![0u8; FULLBOX_HEADER_LEN];
data[0..4].copy_from_slice(&(FULLBOX_HEADER_LEN as u32).to_be_bytes());
data[4..8].copy_from_slice(b"moof");
assert!(matches!(EmsgBox::parse(&data), Err(Error::NotEmsg { .. })));
}
#[test]
fn rejects_unsupported_version() {
let b = EmsgBox {
scheme_id_uri: "x",
value: "",
timescale: 1,
presentation_time: PresentationTime::Delta(0),
event_duration: 0,
id: 0,
message_data: &[],
};
let mut out = b.to_vec().unwrap();
out[8] = 9; assert!(matches!(
EmsgBox::parse(&out),
Err(Error::UnsupportedVersion { version: 9 })
));
}
#[test]
fn rejects_size_overrun() {
let mut data = vec![0u8; FULLBOX_HEADER_LEN];
data[0..4].copy_from_slice(&0xFFFF_FFFFu32.to_be_bytes()); data[4..8].copy_from_slice(b"emsg");
assert!(matches!(
EmsgBox::parse(&data),
Err(Error::InvalidSize { .. })
));
}
#[test]
fn rejects_missing_terminator() {
let mut data = vec![0u8; 16];
let size = data.len() as u32;
data[0..4].copy_from_slice(&size.to_be_bytes());
data[4..8].copy_from_slice(b"emsg");
data[8] = 0; data[12..16].copy_from_slice(b"abcd"); assert!(matches!(
EmsgBox::parse(&data),
Err(Error::InvalidString { .. })
));
}
#[test]
fn is_scte35_recognises_prefix() {
let b = EmsgBox {
scheme_id_uri: "urn:scte:scte35:2013:bin",
value: "",
timescale: 1,
presentation_time: PresentationTime::Delta(0),
event_duration: 0,
id: 0,
message_data: &[],
};
assert!(b.is_scte35());
let nb = EmsgBox {
scheme_id_uri: "urn:mpeg:dash:event:2012",
..b.clone()
};
assert!(!nb.is_scte35());
}
}