use alloc::vec::Vec;
use crate::error::{Error, Result};
use crate::section::SpliceInfoSection;
use dvb_common::{Parse, Serialize};
pub const EVENT_TYPE_INLINE: u8 = 0;
pub const EVENT_TYPE_REFERENCE: u8 = 1;
pub const TIMELINE_TYPE_TEMI: u8 = 0x2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum TimelineType {
None,
VideoPts,
Temi,
Reserved(u8),
}
impl TimelineType {
#[must_use]
pub fn from_bits(v: u8) -> Self {
match v & 0x0F {
0x0 => Self::None,
0x1 => Self::VideoPts,
0x2 => Self::Temi,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn bits(self) -> u8 {
match self {
Self::None => 0x0,
Self::VideoPts => 0x1,
Self::Temi => 0x2,
Self::Reserved(v) => v & 0x0F,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::None => "no timeline used",
Self::VideoPts => "PTS references video PTS",
Self::Temi => "PTS references TEMI timeline",
Self::Reserved(_) => "reserved",
}
}
}
dvb_common::impl_spec_display!(TimelineType, Reserved);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[non_exhaustive]
pub enum Scte35Carriage<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
Inline(SpliceInfoSection<'a>),
CarouselObject(&'a [u8]),
}
impl Scte35Carriage<'_> {
#[must_use]
pub const fn event_type(&self) -> u8 {
match self {
Self::Inline(_) => EVENT_TYPE_INLINE,
Self::CarouselObject(_) => EVENT_TYPE_REFERENCE,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct StreamEventPayload<'a> {
pub timeline_type: TimelineType,
pub temi: Option<(u8, u8)>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub private_data: Option<PrivateData<'a>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub carriage: Scte35Carriage<'a>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PrivateData<'a> {
pub specifier: u32,
#[cfg_attr(feature = "serde", serde(borrow))]
pub bytes: &'a [u8],
}
impl<'a> StreamEventPayload<'a> {
fn dvb_data_len(&self) -> usize {
1 + if self.temi.is_some() { 2 } else { 0 }
}
fn private_data_field_len(&self) -> usize {
match &self.private_data {
Some(p) => 4 + p.bytes.len(),
None => 0,
}
}
}
impl<'a> Parse<'a> for StreamEventPayload<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
if bytes.len() < 2 {
return Err(Error::BufferTooShort {
need: 2,
have: bytes.len(),
what: "DSM-CC_stream_event_payload_binary header",
});
}
let dvb_data_length = bytes[0] as usize;
if bytes.len() < 1 + dvb_data_length {
return Err(Error::LengthOverflow {
declared: dvb_data_length,
available: bytes.len().saturating_sub(1),
what: "DSM-CC_stream_event_payload_binary DVB_data_length",
});
}
if dvb_data_length < 1 {
return Err(Error::BufferTooShort {
need: 1,
have: dvb_data_length,
what: "DSM-CC_stream_event_payload_binary flags byte",
});
}
let flags = bytes[1];
let event_type = (flags >> 4) & 0x01;
let timeline_type = TimelineType::from_bits(flags & 0x0F);
let temi = if timeline_type == TimelineType::Temi {
if dvb_data_length < 3 || bytes.len() < 4 {
return Err(Error::BufferTooShort {
need: 4,
have: bytes.len(),
what: "DSM-CC_stream_event_payload_binary temi fields",
});
}
Some((bytes[2], bytes[3]))
} else {
None
};
let mut pos = 1 + dvb_data_length;
if bytes.len() <= pos {
return Err(Error::BufferTooShort {
need: pos + 1,
have: bytes.len(),
what: "DSM-CC_stream_event_payload_binary private_data_length",
});
}
let private_data_length = bytes[pos] as usize;
pos += 1;
let private_data = if private_data_length > 0 {
if private_data_length < 4 {
return Err(Error::InvalidValue {
field: "DSM-CC_stream_event_payload_binary.private_data_length",
reason: "must be >= 4 (32-bit private_data_specifier) when present",
});
}
if bytes.len() < pos + private_data_length {
return Err(Error::LengthOverflow {
declared: private_data_length,
available: bytes.len().saturating_sub(pos),
what: "DSM-CC_stream_event_payload_binary private_data",
});
}
let specifier =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
let pd_bytes = &bytes[pos + 4..pos + private_data_length];
pos += private_data_length;
Some(PrivateData {
specifier,
bytes: pd_bytes,
})
} else {
None
};
let carriage = if event_type == EVENT_TYPE_REFERENCE {
if bytes.len() <= pos {
return Err(Error::BufferTooShort {
need: pos + 1,
have: bytes.len(),
what: "DSM-CC_stream_event_payload_binary carousel_object_name_length",
});
}
let name_len = bytes[pos] as usize;
pos += 1;
if bytes.len() < pos + name_len {
return Err(Error::LengthOverflow {
declared: name_len,
available: bytes.len().saturating_sub(pos),
what: "DSM-CC_stream_event_payload_binary carousel_object_name",
});
}
Scte35Carriage::CarouselObject(&bytes[pos..pos + name_len])
} else {
Scte35Carriage::Inline(SpliceInfoSection::parse(&bytes[pos..])?)
};
Ok(Self {
timeline_type,
temi,
private_data,
carriage,
})
}
}
impl Serialize for StreamEventPayload<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
let mut n = 1 + self.dvb_data_len();
n += 1; n += self.private_data_field_len();
match &self.carriage {
Scte35Carriage::Inline(s) => n += s.serialized_len(),
Scte35Carriage::CarouselObject(name) => n += 1 + name.len(),
}
n
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
let dvb_data_len = self.dvb_data_len();
if dvb_data_len > u8::MAX as usize {
return Err(Error::InvalidValue {
field: "DSM-CC_stream_event_payload_binary.DVB_data_length",
reason: "exceeds 8-bit field",
});
}
buf[0] = dvb_data_len as u8;
buf[1] = 0xE0 | (self.carriage.event_type() << 4) | self.timeline_type.bits();
let mut pos = 2;
if let Some((tag, id)) = self.temi {
buf[pos] = tag;
buf[pos + 1] = id;
pos += 2;
}
debug_assert_eq!(pos, 1 + dvb_data_len);
let pdl = self.private_data_field_len();
if pdl > u8::MAX as usize {
return Err(Error::InvalidValue {
field: "DSM-CC_stream_event_payload_binary.private_data_length",
reason: "exceeds 8-bit field",
});
}
buf[pos] = pdl as u8;
pos += 1;
if let Some(p) = &self.private_data {
buf[pos..pos + 4].copy_from_slice(&p.specifier.to_be_bytes());
pos += 4;
buf[pos..pos + p.bytes.len()].copy_from_slice(p.bytes);
pos += p.bytes.len();
}
match &self.carriage {
Scte35Carriage::Inline(s) => {
let w = s.serialize_into(&mut buf[pos..])?;
pos += w;
}
Scte35Carriage::CarouselObject(name) => {
if name.len() > u8::MAX as usize {
return Err(Error::InvalidValue {
field: "DSM-CC_stream_event_payload_binary.carousel_object_name_length",
reason: "exceeds 8-bit field",
});
}
buf[pos] = name.len() as u8;
pos += 1;
buf[pos..pos + name.len()].copy_from_slice(name);
pos += name.len();
}
}
debug_assert_eq!(pos, need);
Ok(need)
}
}
#[must_use]
pub fn base64_encode(data: &[u8]) -> Vec<u8> {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = Vec::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b0 = chunk[0];
let b1 = chunk.get(1).copied().unwrap_or(0);
let b2 = chunk.get(2).copied().unwrap_or(0);
out.push(ALPHABET[(b0 >> 2) as usize]);
out.push(ALPHABET[(((b0 & 0x03) << 4) | (b1 >> 4)) as usize]);
if chunk.len() > 1 {
out.push(ALPHABET[(((b1 & 0x0F) << 2) | (b2 >> 6)) as usize]);
} else {
out.push(b'=');
}
if chunk.len() > 2 {
out.push(ALPHABET[(b2 & 0x3F) as usize]);
} else {
out.push(b'=');
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::{AnyCommand, TimeSignal};
use crate::time::SpliceTime;
fn sample_section() -> Vec<u8> {
let ts = TimeSignal {
splice_time: SpliceTime::with_pts(0x0012_3456),
};
SpliceInfoSection::new_clear(AnyCommand::TimeSignal(ts), &[]).to_bytes()
}
#[test]
fn inline_round_trip() {
let section_bytes = sample_section();
let section = SpliceInfoSection::parse(§ion_bytes).unwrap();
let payload = StreamEventPayload {
timeline_type: TimelineType::VideoPts,
temi: None,
private_data: None,
carriage: Scte35Carriage::Inline(section),
};
let bytes = payload.to_bytes();
assert_eq!(bytes[0], 1);
assert_eq!(bytes[1], 0xE1);
assert_eq!(bytes[2], 0);
assert_eq!(bytes[3], 0xFC);
let back = StreamEventPayload::parse(&bytes).unwrap();
assert_eq!(payload, back);
assert_eq!(back.to_bytes(), bytes);
}
#[test]
fn temi_and_private_data_round_trip() {
let section_bytes = sample_section();
let section = SpliceInfoSection::parse(§ion_bytes).unwrap();
let pd = [0xAA, 0xBB];
let payload = StreamEventPayload {
timeline_type: TimelineType::Temi,
temi: Some((0x07, 0x10)),
private_data: Some(PrivateData {
specifier: 0x0000_0028, bytes: &pd,
}),
carriage: Scte35Carriage::Inline(section),
};
let bytes = payload.to_bytes();
assert_eq!(bytes[0], 3);
assert_eq!(bytes[1], 0xE2);
assert_eq!(bytes[2], 0x07); assert_eq!(bytes[3], 0x10); assert_eq!(bytes[4], 6);
assert_eq!(&bytes[5..9], &[0x00, 0x00, 0x00, 0x28]);
assert_eq!(&bytes[9..11], &pd);
let back = StreamEventPayload::parse(&bytes).unwrap();
assert_eq!(payload, back);
assert_eq!(back.to_bytes(), bytes);
}
#[test]
fn carousel_reference_round_trip() {
let name = b"dvb://1.2.3/MyCarouselObject";
let payload = StreamEventPayload {
timeline_type: TimelineType::None,
temi: None,
private_data: None,
carriage: Scte35Carriage::CarouselObject(name),
};
let bytes = payload.to_bytes();
assert_eq!(bytes[1], 0xF0);
assert_eq!(bytes[2], 0);
assert_eq!(bytes[3] as usize, name.len());
assert_eq!(&bytes[4..], &name[..]);
let back = StreamEventPayload::parse(&bytes).unwrap();
assert_eq!(payload, back);
assert_eq!(back.to_bytes(), bytes);
}
#[test]
fn field_mutation_bites() {
let name = b"dvb://1/x";
let a = StreamEventPayload {
timeline_type: TimelineType::None,
temi: None,
private_data: None,
carriage: Scte35Carriage::CarouselObject(name),
};
let b = StreamEventPayload {
timeline_type: TimelineType::VideoPts,
..a.clone()
};
assert_ne!(a.to_bytes(), b.to_bytes());
}
#[test]
fn base64_matches_rfc4648_vectors() {
assert_eq!(base64_encode(b""), b"");
assert_eq!(base64_encode(b"f"), b"Zg==");
assert_eq!(base64_encode(b"fo"), b"Zm8=");
assert_eq!(base64_encode(b"foo"), b"Zm9v");
assert_eq!(base64_encode(b"foob"), b"Zm9vYg==");
assert_eq!(base64_encode(b"fooba"), b"Zm9vYmE=");
assert_eq!(base64_encode(b"foobar"), b"Zm9vYmFy");
}
}