use crate::error::{Error, Result};
pub const TS_PACKET_SIZE: usize = 188;
pub const TS_SYNC_BYTE: u8 = 0x47;
const MAX_SECTION_SIZE: usize = 4098;
const SECTION_HEADER_LEN: usize = 3;
pub(crate) const SECTION_LENGTH_HI_MASK: u8 = 0x0F;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum ScramblingControl {
NotScrambled,
Reserved,
EvenKey,
OddKey,
}
impl ScramblingControl {
pub fn from_bits(bits: u8) -> Self {
match bits & 0b11 {
0b00 => Self::NotScrambled,
0b01 => Self::Reserved,
0b10 => Self::EvenKey,
0b11 => Self::OddKey,
_ => unreachable!(),
}
}
pub fn to_bits(self) -> u8 {
match self {
Self::NotScrambled => 0b00,
Self::Reserved => 0b01,
Self::EvenKey => 0b10,
Self::OddKey => 0b11,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::NotScrambled => "not_scrambled",
Self::Reserved => "reserved",
Self::EvenKey => "even_key",
Self::OddKey => "odd_key",
}
}
}
broadcast_common::impl_spec_display!(ScramblingControl);
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum AdaptationFieldControl {
Reserved,
PayloadOnly,
AdaptationOnly,
AdaptationAndPayload,
}
impl AdaptationFieldControl {
pub fn from_flags(has_adaptation: bool, has_payload: bool) -> Self {
match (has_adaptation, has_payload) {
(false, false) => Self::Reserved,
(false, true) => Self::PayloadOnly,
(true, false) => Self::AdaptationOnly,
(true, true) => Self::AdaptationAndPayload,
}
}
pub fn to_bits(self) -> u8 {
match self {
Self::Reserved => 0b00,
Self::PayloadOnly => 0b01,
Self::AdaptationOnly => 0b10,
Self::AdaptationAndPayload => 0b11,
}
}
pub fn to_flags(self) -> (bool, bool) {
match self {
Self::Reserved => (false, false),
Self::PayloadOnly => (false, true),
Self::AdaptationOnly => (true, false),
Self::AdaptationAndPayload => (true, true),
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Reserved => "reserved",
Self::PayloadOnly => "payload_only",
Self::AdaptationOnly => "adaptation_only",
Self::AdaptationAndPayload => "adaptation_and_payload",
}
}
}
broadcast_common::impl_spec_display!(AdaptationFieldControl);
const TEI_MASK: u8 = 0x80;
const PUSI_MASK: u8 = 0x40;
pub const PID_MASK_HI: u8 = 0x1F;
pub const SCRAMBLING_MASK: u8 = 0xC0;
pub const ADAPTATION_FLAG: u8 = 0x20;
pub const PAYLOAD_FLAG: u8 = 0x10;
pub const CC_MASK: u8 = 0x0F;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TsHeader {
pub tei: bool,
pub pusi: bool,
pub pid: u16,
pub scrambling: u8,
pub has_adaptation: bool,
pub has_payload: bool,
pub continuity_counter: u8,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TsPacket<'a> {
pub header: TsHeader,
pub payload: Option<&'a [u8]>,
#[cfg_attr(feature = "serde", serde(skip))]
adaptation: Option<&'a [u8]>,
#[cfg_attr(feature = "serde", serde(skip))]
pub raw: &'a [u8; TS_PACKET_SIZE],
}
impl TsHeader {
pub fn parse(raw4: &[u8]) -> Result<Self> {
if raw4.len() < 4 {
return Err(Error::BufferTooShort {
need: 4,
have: raw4.len(),
what: "TsHeader",
});
}
let b1 = raw4[1];
let b2 = raw4[2];
let b3 = raw4[3];
let tei = (b1 & TEI_MASK) != 0;
let pusi = (b1 & PUSI_MASK) != 0;
let pid = (((b1 & PID_MASK_HI) as u16) << 8) | (b2 as u16);
let scrambling = (b3 & SCRAMBLING_MASK) >> 6;
let has_adaptation = (b3 & ADAPTATION_FLAG) != 0;
let has_payload = (b3 & PAYLOAD_FLAG) != 0;
let continuity_counter = b3 & CC_MASK;
Ok(Self {
tei,
pusi,
pid,
scrambling,
has_adaptation,
has_payload,
continuity_counter,
})
}
pub const fn serialized_len() -> usize {
4
}
pub fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
if buf.len() < 4 {
return Err(Error::OutputBufferTooSmall {
need: 4,
have: buf.len(),
});
}
buf[0] = TS_SYNC_BYTE;
buf[1] = 0;
if self.tei {
buf[1] |= TEI_MASK;
}
if self.pusi {
buf[1] |= PUSI_MASK;
}
buf[1] |= ((self.pid >> 8) as u8) & PID_MASK_HI;
buf[2] = (self.pid & 0xFF) as u8;
buf[3] = (self.scrambling << 6) & SCRAMBLING_MASK;
if self.has_adaptation {
buf[3] |= ADAPTATION_FLAG;
}
if self.has_payload {
buf[3] |= PAYLOAD_FLAG;
}
buf[3] |= self.continuity_counter & CC_MASK;
Ok(4)
}
pub fn scrambling_control(&self) -> ScramblingControl {
ScramblingControl::from_bits(self.scrambling)
}
pub fn adaptation_field_control(&self) -> AdaptationFieldControl {
AdaptationFieldControl::from_flags(self.has_adaptation, self.has_payload)
}
}
impl<'a> broadcast_common::Parse<'a> for TsHeader {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
TsHeader::parse(bytes)
}
}
impl broadcast_common::Serialize for TsHeader {
type Error = Error;
fn serialized_len(&self) -> usize {
TsHeader::serialized_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
TsHeader::serialize_into(self, buf)
}
}
impl<'a> TsPacket<'a> {
pub fn parse(buf: &'a [u8]) -> Result<Self> {
if buf.len() < TS_PACKET_SIZE {
return Err(Error::BufferTooShort {
need: TS_PACKET_SIZE,
have: buf.len(),
what: "TsPacket",
});
}
if buf[0] != TS_SYNC_BYTE {
return Err(Error::InvalidSyncByte { found: buf[0] });
}
let raw: &[u8; TS_PACKET_SIZE] =
buf[..TS_PACKET_SIZE]
.try_into()
.map_err(|_| Error::BufferTooShort {
need: TS_PACKET_SIZE,
have: buf.len(),
what: "TsPacket::parse (array conversion)",
})?;
let header = TsHeader::parse(&raw[..4])?;
let mut cursor = 4usize;
let mut payload = None;
let mut adaptation = None;
if header.has_adaptation && cursor < TS_PACKET_SIZE {
let af_len = raw[cursor] as usize;
let af_start = cursor + 1;
if af_len > 0 && af_start < TS_PACKET_SIZE {
let af_end = (af_start + af_len).min(TS_PACKET_SIZE);
adaptation = Some(&raw[af_start..af_end]);
}
cursor += 1 + af_len;
}
if header.has_payload && cursor < TS_PACKET_SIZE {
payload = Some(&raw[cursor..]);
}
Ok(TsPacket {
header,
payload,
adaptation,
raw,
})
}
pub fn adaptation_field(&self) -> Option<crate::Result<AdaptationField<'a>>> {
self.adaptation.map(AdaptationField::parse)
}
}
pub(crate) const AF_DISCONTINUITY: u8 = 0x80;
pub(crate) const AF_RANDOM_ACCESS: u8 = 0x40;
pub(crate) const AF_ES_PRIORITY: u8 = 0x20;
pub const AF_PCR_FLAG: u8 = 0x10;
pub(crate) const AF_OPCR_FLAG: u8 = 0x08;
pub(crate) const AF_SPLICING_FLAG: u8 = 0x04;
pub(crate) const AF_TRANSPORT_PRIVATE_DATA_FLAG: u8 = 0x02;
pub(crate) const AF_EXTENSION_FLAG: u8 = 0x01;
pub(crate) const AF_STUFFING_BYTE: u8 = 0xFF;
pub(crate) const PCR_FIELD_LEN: usize = 6;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Pcr {
pub base: u64,
pub extension: u16,
}
impl Pcr {
#[must_use]
pub fn as_27mhz(self) -> u64 {
self.base * 300 + self.extension as u64
}
#[must_use]
pub fn from_27mhz(ticks: u64) -> Self {
const BASE_MASK: u64 = 0x1_FFFF_FFFF;
const EXT_MASK: u16 = 0x1FF;
Self {
base: (ticks / 300) & BASE_MASK,
extension: ((ticks % 300) as u16) & EXT_MASK,
}
}
#[must_use]
pub fn to_field_bytes(self) -> [u8; PCR_FIELD_LEN] {
let b = self.base;
let e = self.extension as u64;
[
((b >> 25) & 0xFF) as u8,
((b >> 17) & 0xFF) as u8,
((b >> 9) & 0xFF) as u8,
((b >> 1) & 0xFF) as u8,
(((b & 0x01) as u8) << 7) | 0x7E | ((e >> 8) as u8 & 0x01),
(e & 0xFF) as u8,
]
}
pub(crate) fn parse(af: &[u8], at: usize) -> Result<Self> {
let b: &[u8; PCR_FIELD_LEN] = af
.get(at..at + PCR_FIELD_LEN)
.and_then(|s| s.try_into().ok())
.ok_or(Error::BufferTooShort {
need: at + PCR_FIELD_LEN,
have: af.len(),
what: "adaptation_field PCR",
})?;
let base = ((b[0] as u64) << 25)
| ((b[1] as u64) << 17)
| ((b[2] as u64) << 9)
| ((b[3] as u64) << 1)
| ((b[4] as u64) >> 7);
let extension = (((b[4] & 0x01) as u16) << 8) | (b[5] as u16);
Ok(Self { base, extension })
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Ltw {
pub ltw_valid_flag: bool,
pub ltw_offset: u16,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SeamlessSplice {
pub splice_type: u8,
pub dts_next_au: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct AdaptationFieldExtension {
pub ltw: Option<Ltw>,
pub piecewise_rate: Option<u32>,
pub seamless_splice: Option<SeamlessSplice>,
}
impl AdaptationFieldExtension {
fn parse(data: &[u8]) -> Result<Self> {
if data.is_empty() {
return Err(Error::BufferTooShort {
need: 1,
have: 0,
what: "adaptation_field_extension_length",
});
}
let ext_len = data[0] as usize;
if data.len() < 1 + ext_len || ext_len < 1 {
return Err(Error::BufferTooShort {
need: 2.max(1 + ext_len),
have: data.len(),
what: "adaptation_field_extension body",
});
}
let ext = &data[1..1 + ext_len]; let flags = ext[0];
let mut cursor = 1usize;
let ltw = if flags & 0x80 != 0 {
if ext.len() < cursor + 2 {
return Err(Error::BufferTooShort {
need: cursor + 2,
have: ext.len(),
what: "ltw_offset",
});
}
let w0 = ext[cursor];
let w1 = ext[cursor + 1];
cursor += 2;
Some(Ltw {
ltw_valid_flag: (w0 & 0x80) != 0,
ltw_offset: (((w0 & 0x7F) as u16) << 8) | (w1 as u16),
})
} else {
None
};
let piecewise_rate = if flags & 0x40 != 0 {
if ext.len() < cursor + 3 {
return Err(Error::BufferTooShort {
need: cursor + 3,
have: ext.len(),
what: "piecewise_rate",
});
}
let r = (((ext[cursor] & 0x3F) as u32) << 16)
| ((ext[cursor + 1] as u32) << 8)
| (ext[cursor + 2] as u32);
cursor += 3;
Some(r)
} else {
None
};
let seamless_splice = if flags & 0x20 != 0 {
if ext.len() < cursor + 5 {
return Err(Error::BufferTooShort {
need: cursor + 5,
have: ext.len(),
what: "seamless_splice DTS_next_AU",
});
}
let b = &ext[cursor..cursor + 5];
let splice_type = (b[0] >> 4) & 0x0F;
let hi = u64::from((b[0] >> 1) & 0x07); let mid = (u64::from(b[1]) << 7) | u64::from(b[2] >> 1); let lo = (u64::from(b[3]) << 7) | u64::from(b[4] >> 1); let dts_next_au = (hi << 30) | (mid << 15) | lo;
cursor += 5;
Some(SeamlessSplice {
splice_type,
dts_next_au,
})
} else {
None
};
let _ = cursor;
Ok(AdaptationFieldExtension {
ltw,
piecewise_rate,
seamless_splice,
})
}
#[must_use]
pub fn serialized_len(&self) -> usize {
let body = 1 + self.ltw.map_or(0, |_| 2)
+ self.piecewise_rate.map_or(0, |_| 3)
+ self.seamless_splice.map_or(0, |_| 5);
1 + body }
pub 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 body_len = need - 1;
buf[0] = body_len as u8;
let mut flags = 0u8;
if self.ltw.is_some() {
flags |= 0x80;
}
if self.piecewise_rate.is_some() {
flags |= 0x40;
}
if self.seamless_splice.is_some() {
flags |= 0x20;
}
buf[1] = flags;
let mut cursor = 2usize;
if let Some(ltw) = self.ltw {
let ltw_valid = if ltw.ltw_valid_flag { 0x80u8 } else { 0x00 };
buf[cursor] = ltw_valid | ((ltw.ltw_offset >> 8) as u8 & 0x7F);
buf[cursor + 1] = (ltw.ltw_offset & 0xFF) as u8;
cursor += 2;
}
if let Some(rate) = self.piecewise_rate {
buf[cursor] = 0xC0 | ((rate >> 16) as u8 & 0x3F);
buf[cursor + 1] = (rate >> 8) as u8;
buf[cursor + 2] = rate as u8;
cursor += 3;
}
if let Some(ss) = self.seamless_splice {
let ts = ss.dts_next_au & 0x1_FFFF_FFFF;
let st = ss.splice_type & 0x0F;
buf[cursor] = (st << 4) | ((((ts >> 30) & 0x07) as u8) << 1) | 0x01;
buf[cursor + 1] = ((ts >> 22) & 0xFF) as u8;
buf[cursor + 2] = ((((ts >> 15) & 0x7F) as u8) << 1) | 0x01;
buf[cursor + 3] = ((ts >> 7) & 0xFF) as u8;
buf[cursor + 4] = (((ts & 0x7F) as u8) << 1) | 0x01;
cursor += 5;
}
Ok(cursor)
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct AdaptationField<'a> {
pub discontinuity_indicator: bool,
pub random_access_indicator: bool,
pub elementary_stream_priority_indicator: bool,
pub pcr: Option<Pcr>,
pub opcr: Option<Pcr>,
pub splice_countdown: Option<i8>,
pub transport_private_data: Option<&'a [u8]>,
pub extension: Option<AdaptationFieldExtension>,
pub stuffing_len: usize,
}
impl<'a> AdaptationField<'a> {
pub(crate) fn parse(af: &'a [u8]) -> Result<Self> {
let flags = *af.first().ok_or(Error::BufferTooShort {
need: 1,
have: 0,
what: "adaptation_field flags",
})?;
let mut cursor = 1usize;
let pcr = if flags & AF_PCR_FLAG != 0 {
let p = Pcr::parse(af, cursor)?;
cursor += PCR_FIELD_LEN;
Some(p)
} else {
None
};
let opcr = if flags & AF_OPCR_FLAG != 0 {
let p = Pcr::parse(af, cursor)?;
cursor += PCR_FIELD_LEN;
Some(p)
} else {
None
};
let splice_countdown = if flags & AF_SPLICING_FLAG != 0 {
let b = *af.get(cursor).ok_or(Error::BufferTooShort {
need: cursor + 1,
have: af.len(),
what: "adaptation_field splice_countdown",
})?;
cursor += 1;
Some(b as i8)
} else {
None
};
let transport_private_data = if flags & AF_TRANSPORT_PRIVATE_DATA_FLAG != 0 {
let tpd_len = *af.get(cursor).ok_or(Error::BufferTooShort {
need: cursor + 1,
have: af.len(),
what: "transport_private_data_length",
})? as usize;
cursor += 1;
let end = cursor + tpd_len;
let slice = af.get(cursor..end).ok_or(Error::BufferTooShort {
need: end,
have: af.len(),
what: "transport_private_data",
})?;
cursor = end;
Some(slice)
} else {
None
};
let extension = if flags & AF_EXTENSION_FLAG != 0 {
let ext_data = af.get(cursor..).ok_or(Error::BufferTooShort {
need: cursor + 1,
have: af.len(),
what: "adaptation_field_extension",
})?;
let ext = AdaptationFieldExtension::parse(ext_data)?;
if !ext_data.is_empty() {
let _ext_len = ext_data[0] as usize;
cursor += 1 + _ext_len;
}
Some(ext)
} else {
None
};
let stuffing_len = af.len().saturating_sub(cursor);
Ok(AdaptationField {
discontinuity_indicator: flags & AF_DISCONTINUITY != 0,
random_access_indicator: flags & AF_RANDOM_ACCESS != 0,
elementary_stream_priority_indicator: flags & AF_ES_PRIORITY != 0,
pcr,
opcr,
splice_countdown,
transport_private_data,
extension,
stuffing_len,
})
}
#[must_use]
pub fn serialized_len(&self) -> usize {
let mut n = 1usize; if self.pcr.is_some() {
n += PCR_FIELD_LEN;
}
if self.opcr.is_some() {
n += PCR_FIELD_LEN;
}
if self.splice_countdown.is_some() {
n += 1;
}
if let Some(tpd) = self.transport_private_data {
n += 1 + tpd.len(); }
if let Some(ref ext) = self.extension {
n += ext.serialized_len();
}
n += self.stuffing_len;
n
}
pub 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 mut flags = 0u8;
if self.discontinuity_indicator {
flags |= AF_DISCONTINUITY;
}
if self.random_access_indicator {
flags |= AF_RANDOM_ACCESS;
}
if self.elementary_stream_priority_indicator {
flags |= AF_ES_PRIORITY;
}
if self.pcr.is_some() {
flags |= AF_PCR_FLAG;
}
if self.opcr.is_some() {
flags |= AF_OPCR_FLAG;
}
if self.splice_countdown.is_some() {
flags |= AF_SPLICING_FLAG;
}
if self.transport_private_data.is_some() {
flags |= AF_TRANSPORT_PRIVATE_DATA_FLAG;
}
if self.extension.is_some() {
flags |= AF_EXTENSION_FLAG;
}
buf[0] = flags;
let mut cursor = 1usize;
if let Some(pcr) = self.pcr {
buf[cursor..cursor + PCR_FIELD_LEN].copy_from_slice(&pcr.to_field_bytes());
cursor += PCR_FIELD_LEN;
}
if let Some(opcr) = self.opcr {
buf[cursor..cursor + PCR_FIELD_LEN].copy_from_slice(&opcr.to_field_bytes());
cursor += PCR_FIELD_LEN;
}
if let Some(sc) = self.splice_countdown {
buf[cursor] = sc as u8;
cursor += 1;
}
if let Some(tpd) = self.transport_private_data {
buf[cursor] = tpd.len() as u8;
cursor += 1;
buf[cursor..cursor + tpd.len()].copy_from_slice(tpd);
cursor += tpd.len();
}
if let Some(ref ext) = self.extension {
let written = ext.serialize_into(&mut buf[cursor..])?;
cursor += written;
}
for b in buf[cursor..cursor + self.stuffing_len].iter_mut() {
*b = AF_STUFFING_BYTE;
}
cursor += self.stuffing_len;
Ok(cursor)
}
}
#[derive(Default)]
pub struct SectionReassembler {
buf: bytes::BytesMut,
ready: alloc::collections::VecDeque<bytes::Bytes>,
}
impl SectionReassembler {
pub fn feed(&mut self, payload: &[u8], pusi: bool) {
if pusi {
if payload.is_empty() {
self.buf.clear();
return;
}
let pointer = payload[0] as usize;
if !self.buf.is_empty() && pointer > 0 {
let avail = payload.len() - 1;
let tail_len = pointer.min(avail);
if self.buf.len() + tail_len > MAX_SECTION_SIZE {
self.buf.clear();
} else {
self.buf.extend_from_slice(&payload[1..1 + tail_len]);
self.drain_complete_sections();
}
}
self.buf.clear();
let start = 1 + pointer;
if start >= payload.len() {
return;
}
let new_data = &payload[start..];
if new_data.len() > MAX_SECTION_SIZE {
return;
}
self.buf.extend_from_slice(new_data);
} else {
if self.buf.is_empty() {
return;
}
let take = if self.buf.len() >= SECTION_HEADER_LEN {
let exp = SECTION_HEADER_LEN
+ (((self.buf[1] & SECTION_LENGTH_HI_MASK) as usize) << 8
| self.buf[2] as usize);
exp.saturating_sub(self.buf.len()).min(payload.len())
} else {
payload.len().min(MAX_SECTION_SIZE - self.buf.len())
};
self.buf.extend_from_slice(&payload[..take]);
}
self.drain_complete_sections();
}
fn drain_complete_sections(&mut self) {
loop {
if self.buf.len() < SECTION_HEADER_LEN {
break;
}
if self.buf[0] == 0xFF {
self.buf.clear();
break;
}
let exp = SECTION_HEADER_LEN
+ (((self.buf[1] & SECTION_LENGTH_HI_MASK) as usize) << 8 | self.buf[2] as usize);
if self.buf.len() >= exp {
let section = self.buf.split_to(exp).freeze();
self.ready.push_back(section);
} else {
break;
}
}
}
pub fn pop_section(&mut self) -> Option<bytes::Bytes> {
self.ready.pop_front()
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
}
pub fn iter_packets(buf: &[u8]) -> impl Iterator<Item = TsPacket<'_>> {
buf.chunks_exact(TS_PACKET_SIZE)
.filter_map(|chunk| TsPacket::parse(chunk).ok())
}
pub fn extract_ts_payload(pkt: &[u8]) -> Option<&[u8]> {
if pkt.len() < 4 {
return None;
}
let afc = (pkt[3] >> 4) & 0x3;
match afc {
0x1 => {
if pkt.len() > 4 {
Some(&pkt[4..])
} else {
None
}
}
0x3 => {
if pkt.len() < 5 {
return None;
}
let af_len = pkt[4] as usize;
let start = 5 + af_len;
if start < pkt.len() {
Some(&pkt[start..])
} else {
None
}
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
fn make_packet(b1: u8, b2: u8, b3: u8, payload_data: &[u8]) -> [u8; TS_PACKET_SIZE] {
let mut pkt = [0u8; TS_PACKET_SIZE];
pkt[0] = TS_SYNC_BYTE;
pkt[1] = b1;
pkt[2] = b2;
pkt[3] = b3;
let payload_start = 4;
let end = (payload_start + payload_data.len()).min(TS_PACKET_SIZE);
let len = (end - payload_start).min(payload_data.len());
pkt[payload_start..payload_start + len].copy_from_slice(&payload_data[..len]);
pkt
}
#[test]
fn parse_rejects_non_0x47_sync_byte() {
let mut pkt = [0u8; TS_PACKET_SIZE];
pkt[0] = 0x46; let err = TsPacket::parse(&pkt).unwrap_err();
match err {
Error::InvalidSyncByte { found } => assert_eq!(found, 0x46),
other => panic!("expected InvalidSyncByte, got {other:?}"),
}
}
#[test]
fn ts_header_round_trip() {
let cases = [
TsHeader {
tei: false,
pusi: true,
pid: 0x0000,
scrambling: 0,
has_adaptation: false,
has_payload: true,
continuity_counter: 0,
},
TsHeader {
tei: true,
pusi: false,
pid: 0x1FFF,
scrambling: 0b11,
has_adaptation: true,
has_payload: true,
continuity_counter: 0x0F,
},
TsHeader {
tei: false,
pusi: false,
pid: 0x0100,
scrambling: 0b10,
has_adaptation: true,
has_payload: false,
continuity_counter: 7,
},
];
for h in cases {
let mut buf = [0u8; 4];
assert_eq!(h.serialize_into(&mut buf).unwrap(), 4);
assert_eq!(TsHeader::parse(&buf).unwrap(), h, "round-trip mismatch");
}
}
#[test]
fn parse_extracts_pid_and_continuity_counter() {
let pkt = make_packet(0x12, 0x34, 0x05, &[]);
let pkt = TsPacket::parse(&pkt).unwrap();
assert_eq!(pkt.header.pid, 0x1234);
assert_eq!(pkt.header.continuity_counter, 5);
}
#[test]
fn payload_unit_start_indicator_flag_extracted() {
let pkt1 = make_packet(0x40, 0x00, 0x00, &[]);
let pkt1 = TsPacket::parse(&pkt1).unwrap();
assert!(pkt1.header.pusi);
let pkt2 = make_packet(0x00, 0x00, 0x00, &[]);
let pkt2 = TsPacket::parse(&pkt2).unwrap();
assert!(!pkt2.header.pusi);
}
fn build_pusi_payload(pointer_field: u8, previous_tail: &[u8], section: &[u8]) -> Vec<u8> {
assert_eq!(pointer_field as usize, previous_tail.len());
let mut v = Vec::with_capacity(1 + previous_tail.len() + section.len());
v.push(pointer_field);
v.extend_from_slice(previous_tail);
v.extend_from_slice(section);
v
}
fn build_section(table_id: u8, body_after_length: &[u8]) -> Vec<u8> {
let section_length = body_after_length.len() as u16;
let mut v = Vec::with_capacity(3 + section_length as usize);
v.push(table_id);
v.push(0xB0 | ((section_length >> 8) as u8 & 0x0F));
v.push((section_length & 0xFF) as u8);
v.extend_from_slice(body_after_length);
v
}
#[test]
fn reassembler_accumulates_multi_packet_section() {
let body = vec![0xAAu8; 197];
let section = build_section(0x02, &body);
assert_eq!(section.len(), 200);
let first_chunk = 100;
let payload1 = build_pusi_payload(0, &[], §ion[..first_chunk]);
let payload2 = section[first_chunk..].to_vec();
let mut reasm = SectionReassembler::default();
reasm.feed(&payload1, true);
reasm.feed(&payload2, false);
let out = reasm.pop_section().expect("section should be ready");
assert_eq!(out.len(), 200);
assert_eq!(out.as_ref(), §ion[..]);
}
#[test]
fn reassembler_yields_complete_section_once_length_satisfied() {
let section = build_section(0x42, &[0xAA]);
assert_eq!(section.len(), 4);
let payload = build_pusi_payload(0, &[], §ion);
let mut reasm = SectionReassembler::default();
reasm.feed(&payload, true);
let out = reasm
.pop_section()
.expect("single-packet section should pop");
assert_eq!(out.as_ref(), §ion[..]);
}
#[test]
fn reassembler_extracts_all_concatenated_sections_in_one_payload() {
let s1 = build_section(0x42, &[0x11, 0x22]); let s2 = build_section(0x46, &[0x33]); let s3 = build_section(0x4A, &[0x44, 0x55, 0x66]);
let mut concat = Vec::new();
concat.extend_from_slice(&s1);
concat.extend_from_slice(&s2);
concat.extend_from_slice(&s3);
let payload = build_pusi_payload(0, &[], &concat);
let mut reasm = SectionReassembler::default();
reasm.feed(&payload, true);
let got: Vec<_> = core::iter::from_fn(|| reasm.pop_section()).collect();
assert_eq!(got.len(), 3, "all three concatenated sections must pop");
assert_eq!(got[0].as_ref(), &s1[..]);
assert_eq!(got[1].as_ref(), &s2[..]);
assert_eq!(got[2].as_ref(), &s3[..]);
}
#[test]
fn reassembler_stops_at_stuffing_after_concatenated_sections() {
let s1 = build_section(0x42, &[0xAA]); let s2 = build_section(0x46, &[0xBB, 0xCC]); let mut concat = Vec::new();
concat.extend_from_slice(&s1);
concat.extend_from_slice(&s2);
concat.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); let payload = build_pusi_payload(0, &[], &concat);
let mut reasm = SectionReassembler::default();
reasm.feed(&payload, true);
let got: Vec<_> = core::iter::from_fn(|| reasm.pop_section()).collect();
assert_eq!(got.len(), 2);
assert_eq!(got[0].as_ref(), &s1[..]);
assert_eq!(got[1].as_ref(), &s2[..]);
assert!(
reasm.is_empty(),
"stuffing tail must be discarded, not buffered"
);
}
#[test]
fn reassembler_concatenated_then_spanning_tail() {
let s1 = build_section(0x42, &[0x01, 0x02]); let s2 = build_section(0x46, &[0x09u8; 60]); let split = 30;
let mut head = Vec::new();
head.extend_from_slice(&s1);
head.extend_from_slice(&s2[..split]);
let payload1 = build_pusi_payload(0, &[], &head);
let payload2 = s2[split..].to_vec();
let mut reasm = SectionReassembler::default();
reasm.feed(&payload1, true);
let first = reasm.pop_section().expect("first section pops at once");
assert_eq!(first.as_ref(), &s1[..]);
assert!(reasm.pop_section().is_none(), "second is still partial");
reasm.feed(&payload2, false);
let second = reasm.pop_section().expect("second pops after continuation");
assert_eq!(second.as_ref(), &s2[..]);
}
#[test]
fn reassembler_completes_section_spanning_into_pusi_packet() {
let spanning = build_section(0x42, &[0x5Au8; 62]); let head = 41;
let tail = &spanning[head..]; assert_eq!(tail.len(), 24);
let next = build_section(0x46, &[0x77, 0x88]);
let payload_a = build_pusi_payload(0, &[], &spanning[..head]);
let payload_b = build_pusi_payload(24, tail, &next);
let mut reasm = SectionReassembler::default();
reasm.feed(&payload_a, true);
assert!(reasm.pop_section().is_none(), "head alone is incomplete");
reasm.feed(&payload_b, true);
let got: Vec<_> = core::iter::from_fn(|| reasm.pop_section()).collect();
assert_eq!(got.len(), 2, "spanning section + new section must both pop");
assert_eq!(
got[0].as_ref(),
&spanning[..],
"spanning section completed from B's pointer tail"
);
assert_eq!(got[1].as_ref(), &next[..]);
}
#[test]
fn reassembler_pusi_pointer_spans_whole_payload() {
let spanning = build_section(0x42, &[0x33u8; 40]); let head = 20;
let payload_a = build_pusi_payload(0, &[], &spanning[..head]);
let tail = &spanning[head..];
let mut reasm = SectionReassembler::default();
reasm.feed(&payload_a, true);
reasm.feed(&build_pusi_payload_pointer_spanning_all(tail), true);
let out = reasm.pop_section().expect("spanning section completes");
assert_eq!(out.as_ref(), &spanning[..]);
assert!(reasm.pop_section().is_none());
}
fn build_pusi_payload_pointer_spanning_all(tail: &[u8]) -> Vec<u8> {
let mut v = Vec::with_capacity(1 + tail.len());
v.push(tail.len() as u8);
v.extend_from_slice(tail);
v
}
#[test]
fn reassembler_completes_max_length_section_and_stays_usable() {
let mut section = Vec::with_capacity(MAX_SECTION_SIZE);
section.push(0x00); section.push(0xB0 | ((4095u16 >> 8) as u8 & 0x0F));
section.push(0xFF); section.resize(MAX_SECTION_SIZE, 0u8);
assert_eq!(section.len(), MAX_SECTION_SIZE);
let mut reasm = SectionReassembler::default();
let mut first = vec![0x00u8]; first.extend_from_slice(§ion[..183]);
reasm.feed(&first, true);
assert!(
reasm.pop_section().is_none(),
"incomplete until the declared length arrives"
);
for chunk in section[183..].chunks(184) {
reasm.feed(chunk, false);
}
let out = reasm
.pop_section()
.expect("max-length section completes at its declared length");
assert_eq!(out.len(), MAX_SECTION_SIZE);
assert_eq!(out.as_ref(), §ion[..]);
assert!(reasm.is_empty());
reasm.feed(&[0u8; 184], false);
assert!(reasm.pop_section().is_none());
let valid_section = build_section(0x00, &[0xAA]);
let payload2 = build_pusi_payload(0, &[], &valid_section);
reasm.feed(&payload2, true);
let out = reasm
.pop_section()
.expect("fresh section should pop after reset");
assert_eq!(out.as_ref(), &valid_section[..]);
}
#[test]
fn reassembler_handles_pusi_with_nonzero_pointer_field() {
let prior_tail = vec![0x11, 0x22, 0x33];
let new_section = build_section(0x02, &[0xBB]);
assert_eq!(new_section.len(), 4);
let payload = build_pusi_payload(3, &prior_tail, &new_section);
let mut reasm = SectionReassembler::default();
reasm.feed(&payload, true);
let out = reasm
.pop_section()
.expect("section after pointer_field skip should pop");
assert_eq!(out.as_ref(), &new_section[..]);
}
#[test]
fn reassembler_ignores_continuation_before_pusi() {
let pkt = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xAA, 0xBB, 0xCC]);
let mut reasm = SectionReassembler::default();
reasm.feed(&pkt[4..], false);
assert!(
reasm.pop_section().is_none(),
"no section should appear without prior PUSI"
);
assert!(
reasm.pop_section().is_none(),
"second pop should also be none"
);
}
#[test]
fn reassembler_empty_pusi_payload_does_not_panic() {
let mut reasm = SectionReassembler::default();
reasm.feed(&[], true);
assert!(reasm.pop_section().is_none());
let payload = vec![0x00u8, 0x72, 0x70, 0x01, 0x00];
reasm.feed(&payload, true);
assert!(reasm.pop_section().is_some());
}
#[test]
fn reassembler_accepts_maximal_private_section() {
let mut section = vec![0x80u8, 0x7F, 0xFF]; section.resize(3 + 0xFFF, 0xAB);
let mut reasm = SectionReassembler::default();
let mut first = vec![0x00];
first.extend_from_slice(§ion[..183]);
reasm.feed(&first, true);
for chunk in section[183..].chunks(184) {
reasm.feed(chunk, false);
}
let out = reasm.pop_section().expect("4098-byte section should pop");
assert_eq!(out.len(), 4098);
assert_eq!(out.as_ref(), §ion[..]);
}
#[test]
fn reassembler_completes_large_section_with_trailing_stuffing() {
let body = vec![0x5Au8; 4096 - 3];
let section = build_section(0x50, &body); assert_eq!(section.len(), 4096);
let mut reasm = SectionReassembler::default();
let mut first = vec![0x00u8];
first.extend_from_slice(§ion[..183]);
reasm.feed(&first, true);
let mut pos = 183usize;
while pos < section.len() {
let take = (section.len() - pos).min(184);
let mut payload = section[pos..pos + take].to_vec();
if take < 184 {
payload.resize(184, 0xFF); }
reasm.feed(&payload, false);
pos += take;
}
let out = reasm
.pop_section()
.expect("4096-byte section must complete despite trailing stuffing (#148)");
assert_eq!(out.len(), 4096);
assert_eq!(out.as_ref(), §ion[..]);
assert!(reasm.is_empty(), "stuffing tail must be discarded");
}
#[test]
fn pcr_as_27mhz_known_value() {
assert_eq!(
Pcr {
base: 10_000,
extension: 0
}
.as_27mhz(),
3_000_000
);
assert_eq!(
Pcr {
base: 1,
extension: 100
}
.as_27mhz(),
400
);
}
#[test]
fn pcr_decode_from_bytes() {
let af = [0x10u8, 0x00, 0x00, 0x13, 0x88, 0x7E, 0x00];
let pcr = Pcr::parse(&af, 1).expect("6 bytes present");
assert_eq!(
pcr,
Pcr {
base: 10_000,
extension: 0
}
);
assert_eq!(pcr.as_27mhz(), 3_000_000);
}
#[test]
fn adaptation_field_flags_and_pcr() {
let mut raw = [0xAAu8; TS_PACKET_SIZE];
raw[0] = TS_SYNC_BYTE;
raw[1] = 0x01; raw[2] = 0x00;
raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
raw[4] = 7; raw[5] = AF_DISCONTINUITY | AF_PCR_FLAG;
raw[6..12].copy_from_slice(&[0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
let pkt = TsPacket::parse(&raw).expect("valid packet");
let af = pkt
.adaptation_field()
.expect("has adaptation field")
.expect("adaptation field parses");
assert!(af.discontinuity_indicator);
assert!(!af.random_access_indicator);
assert_eq!(
af.pcr,
Some(Pcr {
base: 10_000,
extension: 0
})
);
assert_eq!(af.pcr.unwrap().as_27mhz(), 3_000_000);
assert!(af.opcr.is_none());
assert!(af.splice_countdown.is_none());
let payload = pkt.payload.expect("payload present");
assert_eq!(payload.len(), TS_PACKET_SIZE - 12);
assert_eq!(payload[0], 0xAA);
}
#[test]
fn no_adaptation_returns_none() {
let mut raw = [0x00u8; TS_PACKET_SIZE];
raw[0] = TS_SYNC_BYTE;
raw[1] = 0x01;
raw[3] = PAYLOAD_FLAG; let pkt = TsPacket::parse(&raw).expect("valid");
assert!(pkt.adaptation_field().is_none());
assert!(pkt.adaptation.is_none());
}
#[test]
fn adaptation_field_splice_countdown_negative() {
let mut raw = [0xAAu8; TS_PACKET_SIZE];
raw[0] = TS_SYNC_BYTE;
raw[1] = 0x01;
raw[2] = 0x00;
raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
raw[4] = 2; raw[5] = AF_SPLICING_FLAG;
raw[6] = 0xFB; let pkt = TsPacket::parse(&raw).expect("valid");
let af = pkt.adaptation_field().unwrap().unwrap();
assert_eq!(af.splice_countdown, Some(-5));
assert!(af.pcr.is_none());
}
#[test]
fn scrambling_control_all_values() {
assert_eq!(
ScramblingControl::from_bits(0b00),
ScramblingControl::NotScrambled
);
assert_eq!(
ScramblingControl::from_bits(0b01),
ScramblingControl::Reserved
);
assert_eq!(
ScramblingControl::from_bits(0b10),
ScramblingControl::EvenKey
);
assert_eq!(
ScramblingControl::from_bits(0b11),
ScramblingControl::OddKey
);
assert_eq!(ScramblingControl::NotScrambled.name(), "not_scrambled");
assert_eq!(ScramblingControl::Reserved.name(), "reserved");
assert_eq!(ScramblingControl::EvenKey.name(), "even_key");
assert_eq!(ScramblingControl::OddKey.name(), "odd_key");
assert_eq!(ScramblingControl::NotScrambled.to_string(), "not_scrambled");
assert_eq!(ScramblingControl::OddKey.to_string(), "odd_key");
assert_eq!(
ScramblingControl::from_bits(0xFF),
ScramblingControl::OddKey
);
}
#[test]
fn adaptation_field_control_all_values() {
assert_eq!(
AdaptationFieldControl::from_flags(false, false),
AdaptationFieldControl::Reserved
);
assert_eq!(
AdaptationFieldControl::from_flags(false, true),
AdaptationFieldControl::PayloadOnly
);
assert_eq!(
AdaptationFieldControl::from_flags(true, false),
AdaptationFieldControl::AdaptationOnly
);
assert_eq!(
AdaptationFieldControl::from_flags(true, true),
AdaptationFieldControl::AdaptationAndPayload
);
assert_eq!(AdaptationFieldControl::Reserved.name(), "reserved");
assert_eq!(AdaptationFieldControl::PayloadOnly.name(), "payload_only");
assert_eq!(
AdaptationFieldControl::AdaptationOnly.name(),
"adaptation_only"
);
assert_eq!(
AdaptationFieldControl::AdaptationAndPayload.name(),
"adaptation_and_payload"
);
assert_eq!(
AdaptationFieldControl::PayloadOnly.to_string(),
"payload_only"
);
}
#[test]
fn ts_header_scrambling_control_accessor() {
let hdr = TsHeader {
tei: false,
pusi: false,
pid: 0x0100,
scrambling: 0b10,
has_adaptation: false,
has_payload: true,
continuity_counter: 0,
};
assert_eq!(hdr.scrambling_control(), ScramblingControl::EvenKey);
}
#[test]
fn ts_header_adaptation_field_control_accessor() {
let hdr_payload_only = TsHeader {
tei: false,
pusi: false,
pid: 0x0100,
scrambling: 0,
has_adaptation: false,
has_payload: true,
continuity_counter: 0,
};
assert_eq!(
hdr_payload_only.adaptation_field_control(),
AdaptationFieldControl::PayloadOnly
);
let hdr_both = TsHeader {
tei: false,
pusi: false,
pid: 0x0100,
scrambling: 0,
has_adaptation: true,
has_payload: true,
continuity_counter: 0,
};
assert_eq!(
hdr_both.adaptation_field_control(),
AdaptationFieldControl::AdaptationAndPayload
);
}
#[test]
fn iter_packets_yields_valid_and_skips_bad_sync() {
let pkt1 = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xAA; 10]);
let pkt2 = make_packet(0x40, 0x64, PAYLOAD_FLAG, &[0xBB; 10]);
let mut bad = [0u8; TS_PACKET_SIZE];
bad[0] = 0x00;
let mut buf = Vec::new();
buf.extend_from_slice(&pkt1);
buf.extend_from_slice(&pkt2);
buf.extend_from_slice(&bad);
let pkts: Vec<_> = super::iter_packets(&buf).collect();
assert_eq!(pkts.len(), 2, "bad sync packet must be skipped");
assert_eq!(pkts[0].header.pid, 0x0000);
assert_eq!(pkts[1].header.pid, 0x0064);
}
#[test]
fn extract_ts_payload_payload_only() {
let pkt = make_packet(0x00, 0x00, PAYLOAD_FLAG, &[0xABu8; 10]);
let p = super::extract_ts_payload(&pkt).expect("payload present");
assert_eq!(p[0], 0xAB);
assert_eq!(p.len(), TS_PACKET_SIZE - 4);
}
#[test]
fn extract_ts_payload_adaptation_only_returns_none() {
let pkt = make_packet(0x00, 0x00, ADAPTATION_FLAG, &[]);
assert!(super::extract_ts_payload(&pkt).is_none());
}
#[test]
fn pcr_from_27mhz_round_trips() {
for &ticks in &[0u64, 1, 300, 27_000_000, u64::from(u32::MAX), 8_589_934_591] {
let pcr = Pcr::from_27mhz(ticks);
assert_eq!(pcr.as_27mhz(), ticks, "ticks={ticks}");
}
}
#[test]
fn pcr_to_field_bytes_round_trips_parse() {
let cases = [
Pcr {
base: 0,
extension: 0,
},
Pcr {
base: 10_000,
extension: 0,
},
Pcr {
base: 1,
extension: 100,
},
Pcr {
base: 0x1_FFFF_FFFF,
extension: 0x1FF,
},
];
for pcr in cases {
let bytes = pcr.to_field_bytes();
let mut af = [0u8; 7];
af[1..7].copy_from_slice(&bytes);
let decoded = Pcr::parse(&af, 1).expect("parse round-trip");
assert_eq!(decoded, pcr, "round-trip failed for {pcr:?}");
}
}
#[test]
fn pcr_to_field_bytes_known_vector() {
let pcr = Pcr {
base: 10_000,
extension: 0,
};
let bytes = pcr.to_field_bytes();
assert_eq!(bytes, [0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
}
#[test]
fn scrambling_control_to_bits_inverse_of_from_bits() {
for bits in 0u8..=3 {
let sc = ScramblingControl::from_bits(bits);
assert_eq!(sc.to_bits(), bits, "to_bits() != from_bits() for {bits}");
}
}
#[test]
fn adaptation_field_control_to_bits_inverse_of_from_flags() {
let cases = [
(false, false, 0b00u8),
(false, true, 0b01),
(true, false, 0b10),
(true, true, 0b11),
];
for (has_af, has_pl, expected_bits) in cases {
let afc = AdaptationFieldControl::from_flags(has_af, has_pl);
assert_eq!(afc.to_bits(), expected_bits);
assert_eq!(afc.to_flags(), (has_af, has_pl));
}
}
#[test]
fn adaptation_field_serialize_round_trip_with_pcr() {
let original = AdaptationField {
discontinuity_indicator: true,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: Some(Pcr {
base: 10_000,
extension: 0,
}),
opcr: None,
splice_countdown: None,
transport_private_data: None,
extension: None,
stuffing_len: 0,
};
let len = original.serialized_len();
assert_eq!(len, 7); let mut buf = vec![0u8; len];
let written = original.serialize_into(&mut buf).expect("serialize");
assert_eq!(written, len);
let decoded = AdaptationField::parse(&buf).expect("parse round-trip");
assert_eq!(decoded, original);
}
#[test]
fn adaptation_field_serialize_produces_known_bytes() {
let af = AdaptationField {
discontinuity_indicator: true,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: Some(Pcr {
base: 10_000,
extension: 0,
}),
opcr: None,
splice_countdown: None,
transport_private_data: None,
extension: None,
stuffing_len: 0,
};
let mut buf = [0u8; 7];
af.serialize_into(&mut buf).unwrap();
assert_eq!(buf[0], AF_DISCONTINUITY | AF_PCR_FLAG);
assert_eq!(&buf[1..7], &[0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
}
#[test]
fn adaptation_field_serialize_round_trip_opcr_and_splice() {
let original = AdaptationField {
discontinuity_indicator: false,
random_access_indicator: true,
elementary_stream_priority_indicator: true,
pcr: Some(Pcr {
base: 1,
extension: 100,
}),
opcr: Some(Pcr {
base: 999,
extension: 5,
}),
splice_countdown: Some(-3),
transport_private_data: None,
extension: None,
stuffing_len: 0,
};
let len = original.serialized_len();
assert_eq!(len, 1 + 6 + 6 + 1); let mut buf = vec![0u8; len];
original.serialize_into(&mut buf).unwrap();
let decoded = AdaptationField::parse(&buf).expect("parse");
assert_eq!(decoded, original);
}
#[test]
fn adaptation_field_serialize_flags_only() {
let af = AdaptationField {
discontinuity_indicator: false,
random_access_indicator: true,
elementary_stream_priority_indicator: false,
pcr: None,
opcr: None,
splice_countdown: None,
transport_private_data: None,
extension: None,
stuffing_len: 0,
};
assert_eq!(af.serialized_len(), 1);
let mut buf = [0u8; 1];
af.serialize_into(&mut buf).unwrap();
assert_eq!(buf[0], AF_RANDOM_ACCESS);
let decoded = AdaptationField::parse(&buf).unwrap();
assert_eq!(decoded, af);
}
#[test]
fn adaptation_field_serialize_rejects_small_buffer() {
let af = AdaptationField {
discontinuity_indicator: false,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: Some(Pcr {
base: 0,
extension: 0,
}),
opcr: None,
splice_countdown: None,
transport_private_data: None,
extension: None,
stuffing_len: 0,
};
let mut buf = [0u8; 3]; assert!(matches!(
af.serialize_into(&mut buf),
Err(Error::OutputBufferTooSmall { .. })
));
}
#[test]
fn adaptation_field_extension_ltw_round_trip() {
let ext = AdaptationFieldExtension {
ltw: Some(Ltw {
ltw_valid_flag: true,
ltw_offset: 0x1234,
}),
piecewise_rate: None,
seamless_splice: None,
};
let mut buf = vec![0u8; ext.serialized_len()];
ext.serialize_into(&mut buf).unwrap();
let af = AdaptationField {
discontinuity_indicator: false,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: None,
opcr: None,
splice_countdown: None,
transport_private_data: None,
extension: Some(ext),
stuffing_len: 0,
};
let len = af.serialized_len();
let mut abuf = vec![0u8; len];
af.serialize_into(&mut abuf).unwrap();
let decoded = AdaptationField::parse(&abuf).unwrap();
assert_eq!(decoded.extension, Some(ext));
}
#[test]
fn adaptation_field_extension_piecewise_rate_round_trip() {
let af = AdaptationField {
discontinuity_indicator: false,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: None,
opcr: None,
splice_countdown: None,
transport_private_data: None,
extension: Some(AdaptationFieldExtension {
ltw: None,
piecewise_rate: Some(0x3FFFFF), seamless_splice: None,
}),
stuffing_len: 0,
};
let mut buf = vec![0u8; af.serialized_len()];
af.serialize_into(&mut buf).unwrap();
let decoded = AdaptationField::parse(&buf).unwrap();
assert_eq!(decoded.extension.unwrap().piecewise_rate, Some(0x3FFFFF));
}
#[test]
fn adaptation_field_extension_seamless_splice_round_trip() {
let af = AdaptationField {
discontinuity_indicator: false,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: None,
opcr: None,
splice_countdown: None,
transport_private_data: None,
extension: Some(AdaptationFieldExtension {
ltw: None,
piecewise_rate: None,
seamless_splice: Some(SeamlessSplice {
splice_type: 0xA,
dts_next_au: 0x1_2345_6789,
}),
}),
stuffing_len: 0,
};
let mut buf = vec![0u8; af.serialized_len()];
af.serialize_into(&mut buf).unwrap();
let decoded = AdaptationField::parse(&buf).unwrap();
let ss = decoded.extension.unwrap().seamless_splice.unwrap();
assert_eq!(ss.splice_type, 0xA);
assert_eq!(ss.dts_next_au, 0x1_2345_6789);
}
#[test]
fn adaptation_field_transport_private_data_round_trip() {
let tpd = [0xDE, 0xAD, 0xBE, 0xEF];
let af = AdaptationField {
discontinuity_indicator: false,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: None,
opcr: None,
splice_countdown: None,
transport_private_data: Some(&tpd),
extension: None,
stuffing_len: 0,
};
let mut buf = vec![0u8; af.serialized_len()];
af.serialize_into(&mut buf).unwrap();
let decoded = AdaptationField::parse(&buf).unwrap();
assert_eq!(decoded.transport_private_data, Some(tpd.as_slice()));
}
#[test]
fn adaptation_field_stuffing_round_trip() {
let af = AdaptationField {
discontinuity_indicator: false,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: Some(Pcr {
base: 12_345,
extension: 7,
}),
opcr: None,
splice_countdown: None,
transport_private_data: None,
extension: None,
stuffing_len: 20,
};
assert_eq!(af.serialized_len(), 27);
let mut buf = vec![0u8; af.serialized_len()];
af.serialize_into(&mut buf).unwrap();
assert!(buf[7..27].iter().all(|&b| b == AF_STUFFING_BYTE));
let decoded = AdaptationField::parse(&buf).unwrap();
assert_eq!(decoded.stuffing_len, 20);
assert_eq!(decoded, af);
let pure = AdaptationField {
discontinuity_indicator: false,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: None,
opcr: None,
splice_countdown: None,
transport_private_data: None,
extension: None,
stuffing_len: 5,
};
let mut pbuf = vec![0u8; pure.serialized_len()];
pure.serialize_into(&mut pbuf).unwrap();
assert_eq!(pbuf, vec![0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
assert_eq!(AdaptationField::parse(&pbuf).unwrap(), pure);
}
#[test]
fn adaptation_field_all_fields_round_trip() {
let tpd = [0x01u8, 0x02, 0x03];
let af = AdaptationField {
discontinuity_indicator: true,
random_access_indicator: false,
elementary_stream_priority_indicator: false,
pcr: Some(Pcr {
base: 90_000,
extension: 50,
}),
opcr: None,
splice_countdown: Some(10),
transport_private_data: Some(&tpd),
extension: Some(AdaptationFieldExtension {
ltw: Some(Ltw {
ltw_valid_flag: false,
ltw_offset: 500,
}),
piecewise_rate: Some(12345),
seamless_splice: None,
}),
stuffing_len: 0,
};
let len = af.serialized_len();
let mut buf = vec![0u8; len];
af.serialize_into(&mut buf).unwrap();
let decoded = AdaptationField::parse(&buf).unwrap();
assert_eq!(decoded.pcr, af.pcr);
assert_eq!(decoded.splice_countdown, af.splice_countdown);
assert_eq!(decoded.transport_private_data, af.transport_private_data);
assert_eq!(decoded.extension, af.extension);
assert!(decoded.discontinuity_indicator);
}
#[test]
fn adaptation_field_serialize_from_real_packet_bytes() {
let mut raw = [0xAAu8; TS_PACKET_SIZE];
raw[0] = TS_SYNC_BYTE;
raw[1] = 0x01;
raw[2] = 0x00;
raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
raw[4] = 7;
raw[5] = AF_DISCONTINUITY | AF_PCR_FLAG;
raw[6..12].copy_from_slice(&[0x00, 0x00, 0x13, 0x88, 0x7E, 0x00]);
let pkt = TsPacket::parse(&raw).unwrap();
let af = pkt.adaptation_field().unwrap().unwrap();
let mut ser = vec![0u8; af.serialized_len()];
af.serialize_into(&mut ser).unwrap();
let decoded = AdaptationField::parse(&ser).unwrap();
assert_eq!(
decoded.pcr,
Some(Pcr {
base: 10_000,
extension: 0
})
);
assert!(decoded.discontinuity_indicator);
}
}