use alloc::vec::Vec;
use crate::dvb_ta::das_descriptor::EquivalentSegmentationType;
use crate::error::{Error, Result};
use broadcast_common::{Parse, Serialize};
pub const MESSAGE_TYPE_TIME_SIGNAL: u8 = 0x00;
pub const MESSAGE_TYPE_SPLICE_INSERT: u8 = 0x01;
const MASK_33: u64 = (1 << 33) - 1;
fn read_33(first_low_bit: u8, next4: &[u8]) -> u64 {
(u64::from(first_low_bit & 0x01) << 32)
| (u64::from(next4[0]) << 24)
| (u64::from(next4[1]) << 16)
| (u64::from(next4[2]) << 8)
| u64::from(next4[3])
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[non_exhaustive]
pub enum CompactScte35 {
TimeSignal(CompactTimeSignal),
SpliceInsert(CompactSpliceInsert),
}
impl CompactScte35 {
#[must_use]
pub const fn message_type(&self) -> u8 {
match self {
Self::TimeSignal(_) => MESSAGE_TYPE_TIME_SIGNAL,
Self::SpliceInsert(_) => MESSAGE_TYPE_SPLICE_INSERT,
}
}
}
impl<'a> Parse<'a> for CompactScte35 {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let &message_type = bytes.first().ok_or(Error::BufferTooShort {
need: 1,
have: 0,
what: "compact_SCTE_35 message_type",
})?;
let body = &bytes[1..];
match message_type {
MESSAGE_TYPE_TIME_SIGNAL => Ok(Self::TimeSignal(CompactTimeSignal::parse(body)?)),
MESSAGE_TYPE_SPLICE_INSERT => Ok(Self::SpliceInsert(CompactSpliceInsert::parse(body)?)),
_ => Err(Error::InvalidValue {
field: "compact_SCTE_35.message_type",
reason: "unknown / reserved message_type",
}),
}
}
}
impl Serialize for CompactScte35 {
type Error = Error;
fn serialized_len(&self) -> usize {
1 + match self {
Self::TimeSignal(t) => t.serialized_len(),
Self::SpliceInsert(s) => s.serialized_len(),
}
}
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(),
});
}
buf[0] = self.message_type();
let written = match self {
Self::TimeSignal(t) => t.serialize_into(&mut buf[1..])?,
Self::SpliceInsert(s) => s.serialize_into(&mut buf[1..])?,
};
Ok(1 + written)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CompactTimeSignal {
pub encrypted_packet: bool,
pub encryption_algorithm: u8,
pub cw_index: u8,
pub pts_time: u64,
pub segmentation_event_id: u32,
pub segmentation_duration: u64,
pub segmentation_type_id: u8,
pub segmentation_upid: Vec<u8>,
pub segments_num: u8,
pub segments_expected: u8,
pub e_crc_32: Option<u32>,
}
const MASK_40: u64 = (1 << 40) - 1;
impl<'a> Parse<'a> for CompactTimeSignal {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
const FIXED_PREFIX: usize = 18;
if bytes.len() < FIXED_PREFIX {
return Err(Error::BufferTooShort {
need: FIXED_PREFIX,
have: bytes.len(),
what: "compact_time_signal fixed prefix",
});
}
let encrypted_packet = bytes[0] & 0x80 != 0;
let encryption_algorithm = (bytes[0] >> 1) & 0x3F;
let cw_index = bytes[1];
let pts_time = read_33(bytes[2], &bytes[3..7]);
let segmentation_event_id = u32::from_be_bytes([bytes[7], bytes[8], bytes[9], bytes[10]]);
let segmentation_duration = (u64::from(bytes[11]) << 32)
| (u64::from(bytes[12]) << 24)
| (u64::from(bytes[13]) << 16)
| (u64::from(bytes[14]) << 8)
| u64::from(bytes[15]);
let segmentation_type_id = bytes[16];
let upid_len = bytes[17] as usize;
let upid_end = FIXED_PREFIX + upid_len;
if bytes.len() < upid_end + 2 {
return Err(Error::BufferTooShort {
need: upid_end + 2,
have: bytes.len(),
what: "compact_time_signal upid + segments",
});
}
let segmentation_upid = bytes[FIXED_PREFIX..upid_end].to_vec();
let segments_num = bytes[upid_end];
let segments_expected = bytes[upid_end + 1];
let mut pos = upid_end + 2;
let e_crc_32 = if encrypted_packet {
if bytes.len() < pos + 4 {
return Err(Error::BufferTooShort {
need: pos + 4,
have: bytes.len(),
what: "compact_time_signal E_CRC_32",
});
}
let v =
u32::from_be_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]);
pos += 4;
let _ = pos;
Some(v)
} else {
None
};
Ok(Self {
encrypted_packet,
encryption_algorithm,
cw_index,
pts_time: pts_time & MASK_33,
segmentation_event_id,
segmentation_duration: segmentation_duration & MASK_40,
segmentation_type_id,
segmentation_upid,
segments_num,
segments_expected,
e_crc_32,
})
}
}
impl Serialize for CompactTimeSignal {
type Error = Error;
fn serialized_len(&self) -> usize {
18 + self.segmentation_upid.len() + 2 + if self.encrypted_packet { 4 } else { 0 }
}
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(),
});
}
if self.segmentation_upid.len() > u8::MAX as usize {
return Err(Error::InvalidValue {
field: "compact_time_signal.segmentation_upid",
reason: "length exceeds 8-bit segmentation_upid_length",
});
}
buf[0] = (u8::from(self.encrypted_packet) << 7)
| ((self.encryption_algorithm & 0x3F) << 1)
| 0x01;
buf[1] = self.cw_index;
let pts = self.pts_time & MASK_33;
buf[2] = 0xFE | ((pts >> 32) as u8 & 0x01);
buf[3] = (pts >> 24) as u8;
buf[4] = (pts >> 16) as u8;
buf[5] = (pts >> 8) as u8;
buf[6] = pts as u8;
buf[7..11].copy_from_slice(&self.segmentation_event_id.to_be_bytes());
let dur = self.segmentation_duration & MASK_40;
buf[11] = (dur >> 32) as u8;
buf[12] = (dur >> 24) as u8;
buf[13] = (dur >> 16) as u8;
buf[14] = (dur >> 8) as u8;
buf[15] = dur as u8;
buf[16] = self.segmentation_type_id;
buf[17] = self.segmentation_upid.len() as u8;
let upid_end = 18 + self.segmentation_upid.len();
buf[18..upid_end].copy_from_slice(&self.segmentation_upid);
buf[upid_end] = self.segments_num;
buf[upid_end + 1] = self.segments_expected;
let mut pos = upid_end + 2;
if self.encrypted_packet {
let crc = self.e_crc_32.unwrap_or(0);
buf[pos..pos + 4].copy_from_slice(&crc.to_be_bytes());
pos += 4;
}
debug_assert_eq!(pos, need);
Ok(need)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CompactSpliceInsert {
pub encrypted_packet: bool,
pub encryption_algorithm: u8,
pub cw_index: u8,
pub pts_time: u64,
pub splice_event_id: u32,
pub duration: u64,
pub unique_program_id: u16,
pub avail_num: u8,
pub avails_expected: u8,
pub das: Option<CompactDas>,
pub e_crc_32: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CompactDas {
pub break_num: u8,
pub breaks_expected: u8,
pub equivalent_segmentation_type: EquivalentSegmentationType,
pub upid: Vec<u8>,
}
impl<'a> Parse<'a> for CompactSpliceInsert {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
const FIXED: usize = 21;
if bytes.len() < FIXED {
return Err(Error::BufferTooShort {
need: FIXED,
have: bytes.len(),
what: "compact_splice_insert fixed fields",
});
}
let encrypted_packet = bytes[0] & 0x80 != 0;
let encryption_algorithm = (bytes[0] >> 1) & 0x3F;
let cw_index = bytes[1];
let pts_time = read_33(bytes[2], &bytes[3..7]) & MASK_33;
let splice_event_id = u32::from_be_bytes([bytes[7], bytes[8], bytes[9], bytes[10]]);
let duration = read_33(bytes[11], &bytes[12..16]) & MASK_33;
let unique_program_id = u16::from_be_bytes([bytes[16], bytes[17]]);
let avail_num = bytes[18];
let avails_expected = bytes[19];
let das_flag = bytes[20] & 0x80 != 0;
let mut pos = FIXED;
let das = if das_flag {
if bytes.len() < pos + 4 {
return Err(Error::BufferTooShort {
need: pos + 4,
have: bytes.len(),
what: "compact_splice_insert DAS body",
});
}
let desc_len = bytes[pos] as usize; pos += 1;
if desc_len < 3 {
return Err(Error::InvalidValue {
field: "compact_splice_insert.descriptor_length",
reason: "DAS body length must be at least 3 (break_num, breaks_expected, type)",
});
}
if bytes.len() < pos + desc_len {
return Err(Error::LengthOverflow {
declared: desc_len,
available: bytes.len().saturating_sub(pos),
what: "compact_splice_insert DAS body",
});
}
let break_num = bytes[pos];
let breaks_expected = bytes[pos + 1];
let equivalent_segmentation_type =
EquivalentSegmentationType::from_bits(bytes[pos + 2] & 0x0F);
let upid = bytes[pos + 3..pos + desc_len].to_vec();
pos += desc_len;
Some(CompactDas {
break_num,
breaks_expected,
equivalent_segmentation_type,
upid,
})
} else {
None
};
let e_crc_32 = if encrypted_packet {
if bytes.len() < pos + 4 {
return Err(Error::BufferTooShort {
need: pos + 4,
have: bytes.len(),
what: "compact_splice_insert E_CRC_32",
});
}
Some(u32::from_be_bytes([
bytes[pos],
bytes[pos + 1],
bytes[pos + 2],
bytes[pos + 3],
]))
} else {
None
};
Ok(Self {
encrypted_packet,
encryption_algorithm,
cw_index,
pts_time,
splice_event_id,
duration,
unique_program_id,
avail_num,
avails_expected,
das,
e_crc_32,
})
}
}
impl Serialize for CompactSpliceInsert {
type Error = Error;
fn serialized_len(&self) -> usize {
let mut n = 21;
if let Some(das) = &self.das {
n += 1 + 3 + das.upid.len();
}
if self.encrypted_packet {
n += 4;
}
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(),
});
}
if let Some(das) = &self.das {
if das.upid.len() + 3 > u8::MAX as usize {
return Err(Error::InvalidValue {
field: "compact_splice_insert.das.upid",
reason: "DAS body length exceeds 8-bit descriptor_length",
});
}
}
buf[0] = (u8::from(self.encrypted_packet) << 7)
| ((self.encryption_algorithm & 0x3F) << 1)
| 0x01;
buf[1] = self.cw_index;
let pts = self.pts_time & MASK_33;
buf[2] = 0xFE | ((pts >> 32) as u8 & 0x01);
buf[3] = (pts >> 24) as u8;
buf[4] = (pts >> 16) as u8;
buf[5] = (pts >> 8) as u8;
buf[6] = pts as u8;
buf[7..11].copy_from_slice(&self.splice_event_id.to_be_bytes());
let dur = self.duration & MASK_33;
buf[11] = 0xFE | ((dur >> 32) as u8 & 0x01);
buf[12] = (dur >> 24) as u8;
buf[13] = (dur >> 16) as u8;
buf[14] = (dur >> 8) as u8;
buf[15] = dur as u8;
buf[16..18].copy_from_slice(&self.unique_program_id.to_be_bytes());
buf[18] = self.avail_num;
buf[19] = self.avails_expected;
buf[20] = (u8::from(self.das.is_some()) << 7) | 0x7F;
let mut pos = 21;
if let Some(das) = &self.das {
let desc_len = 3 + das.upid.len();
buf[pos] = desc_len as u8;
buf[pos + 1] = das.break_num;
buf[pos + 2] = das.breaks_expected;
buf[pos + 3] = 0xF0 | das.equivalent_segmentation_type.bits();
buf[pos + 4..pos + 4 + das.upid.len()].copy_from_slice(&das.upid);
pos += 1 + desc_len;
}
if self.encrypted_packet {
let crc = self.e_crc_32.unwrap_or(0);
buf[pos..pos + 4].copy_from_slice(&crc.to_be_bytes());
pos += 4;
}
debug_assert_eq!(pos, need);
Ok(need)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_time_signal() -> CompactTimeSignal {
CompactTimeSignal {
encrypted_packet: false,
encryption_algorithm: 0,
cw_index: 0,
pts_time: 0x1_2345_6789 & MASK_33,
segmentation_event_id: 0xDEAD_BEEF,
segmentation_duration: 0x00_0123_4567,
segmentation_type_id: 0x34,
segmentation_upid: b"urn:com.broadcaster:112210F47DE98115".to_vec(),
segments_num: 1,
segments_expected: 2,
e_crc_32: None,
}
}
#[test]
fn time_signal_round_trip_and_dispatch() {
let ts = sample_time_signal();
let compact = CompactScte35::TimeSignal(ts.clone());
let bytes = compact.to_bytes();
assert_eq!(bytes[0], MESSAGE_TYPE_TIME_SIGNAL);
let back = CompactScte35::parse(&bytes).unwrap();
assert_eq!(compact, back);
assert_eq!(back.to_bytes(), bytes);
}
#[test]
fn time_signal_hand_computed_prefix() {
let mut ts = sample_time_signal();
ts.segmentation_upid = b"AB".to_vec(); let bytes = ts.to_bytes();
assert_eq!(bytes[0], 0x01);
assert_eq!(bytes[2], 0xFE | 0x01);
assert_eq!(&bytes[3..7], &[0x23, 0x45, 0x67, 0x89]);
assert_eq!(&bytes[7..11], &[0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(&bytes[11..16], &[0x00, 0x01, 0x23, 0x45, 0x67]);
assert_eq!(bytes[16], 0x34);
assert_eq!(bytes[17], 2); assert_eq!(&bytes[18..20], b"AB");
assert_eq!(bytes[20], 1); assert_eq!(bytes[21], 2); assert_eq!(bytes.len(), 22);
}
#[test]
fn time_signal_encrypted_carries_e_crc() {
let mut ts = sample_time_signal();
ts.encrypted_packet = true;
ts.e_crc_32 = Some(0x1122_3344);
let bytes = ts.to_bytes();
assert_eq!(bytes[0] & 0x80, 0x80);
let back = CompactTimeSignal::parse(&bytes).unwrap();
assert_eq!(ts, back);
assert_eq!(back.e_crc_32, Some(0x1122_3344));
}
#[test]
fn time_signal_field_mutation_bites() {
let a = sample_time_signal();
let mut b = a.clone();
b.segmentation_type_id = 0x36;
assert_ne!(a.to_bytes(), b.to_bytes());
let mut c = a.clone();
c.pts_time = a.pts_time ^ 1;
assert_ne!(a.to_bytes(), c.to_bytes());
}
fn sample_splice_insert(das: Option<CompactDas>) -> CompactSpliceInsert {
CompactSpliceInsert {
encrypted_packet: false,
encryption_algorithm: 0,
cw_index: 0,
pts_time: 0x0_1234_5678,
splice_event_id: 0x0102_0304,
duration: 0x0_0009_0000,
unique_program_id: 0xABCD,
avail_num: 1,
avails_expected: 3,
das,
e_crc_32: None,
}
}
#[test]
fn splice_insert_round_trip_no_das() {
let si = sample_splice_insert(None);
let compact = CompactScte35::SpliceInsert(si.clone());
let bytes = compact.to_bytes();
assert_eq!(bytes[0], MESSAGE_TYPE_SPLICE_INSERT);
assert_eq!(bytes[21] & 0x80, 0x00);
let back = CompactScte35::parse(&bytes).unwrap();
assert_eq!(compact, back);
assert_eq!(back.to_bytes(), bytes);
}
#[test]
fn splice_insert_round_trip_with_das() {
let das = CompactDas {
break_num: 2,
breaks_expected: 5,
equivalent_segmentation_type:
EquivalentSegmentationType::DistributorPlacementOpportunity,
upid: b"urn:tv.acme:B637643".to_vec(),
};
let si = sample_splice_insert(Some(das));
let bytes = si.to_bytes();
assert_eq!(bytes[20] & 0x80, 0x80);
assert_eq!(bytes[21] as usize, 3 + b"urn:tv.acme:B637643".len());
assert_eq!(bytes[24], 0xF1);
let back = CompactSpliceInsert::parse(&bytes).unwrap();
assert_eq!(si, back);
assert_eq!(back.to_bytes(), bytes);
}
#[test]
fn splice_insert_field_mutation_bites() {
let a = sample_splice_insert(None);
let mut b = a.clone();
b.unique_program_id = 0x0001;
assert_ne!(a.to_bytes(), b.to_bytes());
let c = sample_splice_insert(Some(CompactDas {
break_num: 0,
breaks_expected: 0,
equivalent_segmentation_type: EquivalentSegmentationType::NoEquivalent,
upid: Vec::new(),
}));
assert_ne!(a.to_bytes(), c.to_bytes());
}
#[test]
fn rejects_unknown_message_type() {
let bytes = [0x7F, 0, 0, 0, 0];
assert!(matches!(
CompactScte35::parse(&bytes).unwrap_err(),
Error::InvalidValue { .. }
));
}
#[test]
fn time_signal_upid_overflow_guard_fires() {
let mut ts = sample_time_signal();
ts.segmentation_upid = vec![0u8; 256]; let err = ts
.serialize_into(&mut vec![0u8; ts.serialized_len()])
.unwrap_err();
assert!(
matches!(
err,
Error::InvalidValue {
field: "compact_time_signal.segmentation_upid",
..
}
),
"expected InvalidValue for segmentation_upid overflow, got {err:?}"
);
}
}