use crate::commands::AnyCommand;
use crate::descriptors::{parse_loop, SpliceDescriptorIter};
use crate::error::{Error, Result};
use crate::time::PTS_MAX;
use broadcast_common::{Parse, Serialize};
pub const TABLE_ID: u8 = 0xFC;
const FIXED_HEADER_LEN: usize = 14;
const CRC_LEN: usize = 4;
pub const TIER_IGNORE: u16 = 0x0FFF;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ClearPayload<'a> {
pub command: AnyCommand<'a>,
pub descriptor_loop: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SpliceInfoSection<'a> {
pub sap_type: u8,
pub protocol_version: u8,
pub encrypted_packet: bool,
pub encryption_algorithm: u8,
pub pts_adjustment: u64,
pub cw_index: u8,
pub tier: u16,
pub clear: Option<ClearPayload<'a>>,
pub encrypted_payload: Option<&'a [u8]>,
}
impl<'a> SpliceInfoSection<'a> {
#[must_use]
pub fn new_clear(command: AnyCommand<'a>, descriptor_loop: &'a [u8]) -> Self {
Self {
sap_type: 0x3,
protocol_version: 0,
encrypted_packet: false,
encryption_algorithm: 0,
pts_adjustment: 0,
cw_index: 0,
tier: 0,
clear: Some(ClearPayload {
command,
descriptor_loop,
}),
encrypted_payload: None,
}
}
#[must_use]
pub fn descriptors(&self) -> SpliceDescriptorIter<'a> {
match &self.clear {
Some(c) => parse_loop(c.descriptor_loop),
None => parse_loop(&[]),
}
}
#[must_use]
pub fn pts_adjustment_duration(&self) -> core::time::Duration {
crate::time::ticks_to_duration(self.pts_adjustment)
}
pub fn set_pts_adjustment_duration(&mut self, d: core::time::Duration) -> Result<()> {
self.pts_adjustment =
crate::time::duration_to_ticks(d, PTS_MAX).ok_or(Error::InvalidValue {
field: "splice_info_section.pts_adjustment",
reason: "duration exceeds 33-bit 90 kHz range",
})?;
Ok(())
}
#[must_use]
pub fn tier_is_ignored(&self) -> bool {
self.tier == TIER_IGNORE
}
fn payload_len(&self) -> usize {
match (&self.clear, self.encrypted_payload) {
(Some(c), _) => 1 + c.command.body_len() + 2 + c.descriptor_loop.len(),
(None, Some(enc)) => enc.len(),
(None, None) => 0,
}
}
}
impl<'a> Parse<'a> for SpliceInfoSection<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
if bytes.len() < FIXED_HEADER_LEN + CRC_LEN {
return Err(Error::BufferTooShort {
need: FIXED_HEADER_LEN + CRC_LEN,
have: bytes.len(),
what: "splice_info_section header",
});
}
if bytes[0] != TABLE_ID {
return Err(Error::UnexpectedTableId { table_id: bytes[0] });
}
let sap_type = (bytes[1] >> 4) & 0x03;
let section_length = (u16::from(bytes[1] & 0x0F) << 8) | u16::from(bytes[2]);
let total = 3 + section_length as usize;
if bytes.len() < total {
return Err(Error::LengthOverflow {
declared: section_length as usize,
available: bytes.len().saturating_sub(3),
what: "splice_info_section section_length",
});
}
if total < FIXED_HEADER_LEN + CRC_LEN {
return Err(Error::BufferTooShort {
need: FIXED_HEADER_LEN + CRC_LEN,
have: total,
what: "splice_info_section body + CRC",
});
}
let crc_pos = total - CRC_LEN;
let (crc_region, crc_bytes) =
bytes[..total]
.split_last_chunk::<CRC_LEN>()
.ok_or(Error::BufferTooShort {
need: FIXED_HEADER_LEN + CRC_LEN,
have: bytes.len(),
what: "splice_info_section header",
})?;
let expected = u32::from_be_bytes(*crc_bytes);
let computed = broadcast_common::crc32_mpeg2::compute(crc_region);
if computed != expected {
return Err(Error::CrcMismatch { computed, expected });
}
let protocol_version = bytes[3];
let encrypted_packet = bytes[4] & 0x80 != 0;
let encryption_algorithm = (bytes[4] >> 1) & 0x3F;
let pts_adjustment = (u64::from(bytes[4] & 0x01) << 32)
| (u64::from(bytes[5]) << 24)
| (u64::from(bytes[6]) << 16)
| (u64::from(bytes[7]) << 8)
| u64::from(bytes[8]);
let cw_index = bytes[9];
let tier = (u16::from(bytes[10]) << 4) | (u16::from(bytes[11]) >> 4);
let splice_command_length = (u16::from(bytes[11] & 0x0F) << 8) | u16::from(bytes[12]);
let splice_command_type = bytes[13];
let mut section = SpliceInfoSection {
sap_type,
protocol_version,
encrypted_packet,
encryption_algorithm,
pts_adjustment,
cw_index,
tier,
clear: None,
encrypted_payload: None,
};
let payload = &bytes[13..crc_pos];
if encrypted_packet {
section.encrypted_payload = Some(payload);
return Ok(section);
}
let scl = splice_command_length as usize;
if splice_command_length == 0x0FFF {
return Err(Error::InvalidValue {
field: "splice_info_section.splice_command_length",
reason:
"0xFFF backwards-compat sentinel is not supported; provide the actual length",
});
}
if payload.len() < 1 + scl + 2 {
return Err(Error::BufferTooShort {
need: 1 + scl + 2,
have: payload.len(),
what: "splice_info_section command + descriptor_loop_length",
});
}
let command_body = &payload[1..1 + scl];
let command = AnyCommand::dispatch(splice_command_type, command_body)?;
let dll_pos = 1 + scl;
let (dll_bytes, _) = payload
.get(dll_pos..)
.and_then(|s| s.split_first_chunk::<2>())
.ok_or(Error::BufferTooShort {
need: 1 + scl + 2,
have: payload.len(),
what: "splice_info_section command + descriptor_loop_length",
})?;
let descriptor_loop_length = usize::from(u16::from_be_bytes(*dll_bytes));
let loop_start = dll_pos + 2;
if payload.len() < loop_start + descriptor_loop_length {
return Err(Error::LengthOverflow {
declared: descriptor_loop_length,
available: payload.len().saturating_sub(loop_start),
what: "splice_info_section descriptor_loop_length",
});
}
let descriptor_loop = &payload[loop_start..loop_start + descriptor_loop_length];
section.clear = Some(ClearPayload {
command,
descriptor_loop,
});
Ok(section)
}
}
impl Serialize for SpliceInfoSection<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
13 + self.payload_len() + CRC_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(),
});
}
if self.pts_adjustment > PTS_MAX {
return Err(Error::InvalidValue {
field: "splice_info_section.pts_adjustment",
reason: "exceeds 33-bit range",
});
}
if self.tier > 0x0FFF {
return Err(Error::InvalidValue {
field: "splice_info_section.tier",
reason: "exceeds 12-bit range",
});
}
let section_length = (need - 3) as u16;
if section_length > 4093 {
return Err(Error::InvalidValue {
field: "splice_info_section.section_length",
reason: "exceeds 4093",
});
}
buf[0] = TABLE_ID;
buf[1] = ((self.sap_type & 0x03) << 4) | ((section_length >> 8) as u8 & 0x0F);
buf[2] = (section_length & 0xFF) as u8;
buf[3] = self.protocol_version;
buf[4] = (u8::from(self.encrypted_packet) << 7)
| ((self.encryption_algorithm & 0x3F) << 1)
| ((self.pts_adjustment >> 32) as u8 & 0x01);
buf[5] = (self.pts_adjustment >> 24) as u8;
buf[6] = (self.pts_adjustment >> 16) as u8;
buf[7] = (self.pts_adjustment >> 8) as u8;
buf[8] = self.pts_adjustment as u8;
buf[9] = self.cw_index;
buf[10] = (self.tier >> 4) as u8;
match (&self.clear, self.encrypted_payload) {
(Some(c), _) => {
let scl = c.command.body_len() as u16;
buf[11] = (((self.tier & 0x0F) as u8) << 4) | ((scl >> 8) as u8 & 0x0F);
buf[12] = (scl & 0xFF) as u8;
buf[13] = c.command.command_type();
let mut pos = 14;
pos += c.command.serialize_body_into(&mut buf[pos..])?;
let dll = c.descriptor_loop.len() as u16;
buf[pos] = (dll >> 8) as u8;
buf[pos + 1] = (dll & 0xFF) as u8;
pos += 2;
buf[pos..pos + c.descriptor_loop.len()].copy_from_slice(c.descriptor_loop);
pos += c.descriptor_loop.len();
debug_assert_eq!(pos, need - CRC_LEN);
}
(None, Some(enc)) => {
buf[11] = (((self.tier & 0x0F) as u8) << 4) | 0x0F;
buf[12] = 0xFF;
buf[13..13 + enc.len()].copy_from_slice(enc);
}
(None, None) => {
buf[11] = ((self.tier & 0x0F) as u8) << 4;
buf[12] = 0;
return Err(Error::InvalidValue {
field: "splice_info_section",
reason: "neither a clear command nor an encrypted payload is present",
});
}
}
let crc_pos = need - CRC_LEN;
let crc = broadcast_common::crc32_mpeg2::compute(&buf[..crc_pos]);
buf[crc_pos..need].copy_from_slice(&crc.to_be_bytes());
Ok(need)
}
}