use crate::TsError;
pub const PAT_TABLE_ID: u8 = 0x00;
pub const PMT_TABLE_ID: u8 = 0x02;
const SECTION_HEADER_LEN: usize = 8;
const SECTION_CRC_LEN: usize = 4;
#[derive(Debug, Default, Clone)]
pub struct ProgramAssociationTable {
pub transport_stream_id: u16,
pub version_number: u8,
pub current_next_indicator: bool,
pub section_number: u8,
pub last_section_number: u8,
pub programs: Vec<(u16, u16)>,
}
impl ProgramAssociationTable {
pub fn parse(section: &[u8]) -> Result<Self, TsError> {
let (hdr, body) = parse_section_header(section, PAT_TABLE_ID)?;
let mut programs = Vec::new();
let mut i = 0;
while i + 4 <= body.len() {
let program_number = u16::from_be_bytes([body[i], body[i + 1]]);
let pid = ((((body[i + 2] & 0b0001_1111) as u16) << 8) | (body[i + 3] as u16)) & 0x1FFF;
programs.push((program_number, pid));
i += 4;
}
Ok(Self {
transport_stream_id: hdr.table_id_extension,
version_number: hdr.version_number,
current_next_indicator: hdr.current_next_indicator,
section_number: hdr.section_number,
last_section_number: hdr.last_section_number,
programs,
})
}
}
#[derive(Debug, Clone)]
pub struct PmtStream {
pub stream_type: u8,
pub elementary_pid: u16,
pub descriptors: Vec<u8>,
}
#[derive(Debug, Default, Clone)]
pub struct ProgramMapTable {
pub program_number: u16,
pub version_number: u8,
pub current_next_indicator: bool,
pub pcr_pid: u16,
pub program_info: Vec<u8>,
pub streams: Vec<PmtStream>,
}
impl ProgramMapTable {
pub fn parse(section: &[u8]) -> Result<Self, TsError> {
let (hdr, body) = parse_section_header(section, PMT_TABLE_ID)?;
if body.len() < 4 {
return Err(TsError::Truncated {
what: "PMT body",
have: body.len(),
need: 4,
});
}
let pcr_pid = u16::from_be_bytes([body[0] & 0b0001_1111, body[1]]);
let program_info_length = (u16::from_be_bytes([body[2] & 0b0000_1111, body[3]])) as usize;
let after_pcr: usize = 4;
let pi_end =
after_pcr
.checked_add(program_info_length)
.ok_or(TsError::SectionLengthOverrun {
claimed: program_info_length,
have: body.len() - after_pcr,
})?;
if pi_end > body.len() {
return Err(TsError::SectionLengthOverrun {
claimed: program_info_length,
have: body.len() - after_pcr,
});
}
let program_info = body[after_pcr..pi_end].to_vec();
let mut streams = Vec::new();
let mut i = pi_end;
while i + 5 <= body.len() {
let stream_type = body[i];
let elementary_pid = u16::from_be_bytes([body[i + 1] & 0b0001_1111, body[i + 2]]);
let es_info_length =
(u16::from_be_bytes([body[i + 3] & 0b0000_1111, body[i + 4]])) as usize;
let descr_start = i + 5;
let descr_end =
descr_start
.checked_add(es_info_length)
.ok_or(TsError::SectionLengthOverrun {
claimed: es_info_length,
have: body.len() - descr_start,
})?;
if descr_end > body.len() {
return Err(TsError::SectionLengthOverrun {
claimed: es_info_length,
have: body.len() - descr_start,
});
}
let descriptors = body[descr_start..descr_end].to_vec();
streams.push(PmtStream {
stream_type,
elementary_pid,
descriptors,
});
i = descr_end;
}
Ok(Self {
program_number: hdr.table_id_extension,
version_number: hdr.version_number,
current_next_indicator: hdr.current_next_indicator,
pcr_pid,
program_info,
streams,
})
}
}
struct SectionHeader {
table_id_extension: u16,
version_number: u8,
current_next_indicator: bool,
section_number: u8,
last_section_number: u8,
}
fn parse_section_header(
section: &[u8],
expected_table_id: u8,
) -> Result<(SectionHeader, &[u8]), TsError> {
if section.len() < SECTION_HEADER_LEN + SECTION_CRC_LEN {
return Err(TsError::Truncated {
what: "PSI section header",
have: section.len(),
need: SECTION_HEADER_LEN + SECTION_CRC_LEN,
});
}
let table_id = section[0];
if table_id != expected_table_id {
return Err(TsError::Unsupported(
"PSI table_id does not match expected value",
));
}
let b1 = section[1];
let b2 = section[2];
let section_length = ((((b1 & 0b0000_1111) as usize) << 8) | (b2 as usize)) & 0x0FFF;
let total = 3 + section_length;
if total > section.len() {
return Err(TsError::SectionLengthOverrun {
claimed: section_length,
have: section.len() - 3,
});
}
let section = §ion[..total];
let crc_pos = total - SECTION_CRC_LEN;
let computed = mpeg2_crc32(§ion[..crc_pos]);
let header_crc = u32::from_be_bytes([
section[crc_pos],
section[crc_pos + 1],
section[crc_pos + 2],
section[crc_pos + 3],
]);
if computed != header_crc {
return Err(TsError::PsiCrcMismatch {
header: header_crc,
computed,
});
}
let table_id_extension = u16::from_be_bytes([section[3], section[4]]);
let b5 = section[5];
let version_number = (b5 >> 1) & 0b0001_1111;
let current_next_indicator = (b5 & 0b0000_0001) != 0;
let section_number = section[6];
let last_section_number = section[7];
let body = §ion[SECTION_HEADER_LEN..crc_pos];
Ok((
SectionHeader {
table_id_extension,
version_number,
current_next_indicator,
section_number,
last_section_number,
},
body,
))
}
pub fn mpeg2_crc32(bytes: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFF_FFFF;
for &b in bytes {
crc ^= (b as u32) << 24;
for _ in 0..8 {
if (crc & 0x8000_0000) != 0 {
crc = (crc << 1) ^ 0x04C1_1DB7;
} else {
crc <<= 1;
}
}
}
crc
}
pub fn iter_sections(ts_payload: &[u8]) -> SectionIter<'_> {
if ts_payload.is_empty() {
return SectionIter { rest: &[][..] };
}
let ptr = ts_payload[0] as usize;
let start = 1 + ptr;
if start > ts_payload.len() {
return SectionIter { rest: &[][..] };
}
SectionIter {
rest: &ts_payload[start..],
}
}
#[derive(Debug)]
pub struct SectionIter<'a> {
rest: &'a [u8],
}
impl<'a> Iterator for SectionIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.rest.len() < 3 {
return None;
}
if self.rest[0] == 0xFF {
return None;
}
let section_length =
((((self.rest[1] & 0b0000_1111) as usize) << 8) | (self.rest[2] as usize)) & 0x0FFF;
let total = 3 + section_length;
if total > self.rest.len() {
return None;
}
let (head, tail) = self.rest.split_at(total);
self.rest = tail;
Some(head)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_pat_section(tsid: u16, version: u8, programs: &[(u16, u16)]) -> Vec<u8> {
let body_len = programs.len() * 4;
let section_length = 5 + body_len + 4;
let mut s = Vec::with_capacity(3 + section_length);
s.push(PAT_TABLE_ID);
let len_hi = 0b1011_0000 | ((section_length >> 8) & 0x0F) as u8;
s.push(len_hi);
s.push((section_length & 0xFF) as u8);
s.extend_from_slice(&tsid.to_be_bytes());
s.push(0b1100_0001 | ((version & 0b1_1111) << 1));
s.push(0); s.push(0); for (prog, pid) in programs {
s.extend_from_slice(&prog.to_be_bytes());
s.push(0b1110_0000 | ((pid >> 8) & 0x1F) as u8);
s.push((pid & 0xFF) as u8);
}
let crc = mpeg2_crc32(&s);
s.extend_from_slice(&crc.to_be_bytes());
s
}
fn build_pmt_section(
program_number: u16,
version: u8,
pcr_pid: u16,
program_info: &[u8],
streams: &[(u8, u16, &[u8])],
) -> Vec<u8> {
let body_len: usize =
4 + program_info.len() + streams.iter().map(|(_, _, d)| 5 + d.len()).sum::<usize>();
let section_length = 5 + body_len + 4;
let mut s = Vec::with_capacity(3 + section_length);
s.push(PMT_TABLE_ID);
let len_hi = 0b1011_0000 | ((section_length >> 8) & 0x0F) as u8;
s.push(len_hi);
s.push((section_length & 0xFF) as u8);
s.extend_from_slice(&program_number.to_be_bytes());
s.push(0b1100_0001 | ((version & 0b1_1111) << 1));
s.push(0);
s.push(0);
s.push(0b1110_0000 | ((pcr_pid >> 8) & 0x1F) as u8);
s.push((pcr_pid & 0xFF) as u8);
let pil = program_info.len() as u16;
s.push(0b1111_0000 | ((pil >> 8) & 0x0F) as u8);
s.push((pil & 0xFF) as u8);
s.extend_from_slice(program_info);
for (stype, epid, descr) in streams {
s.push(*stype);
s.push(0b1110_0000 | ((*epid >> 8) & 0x1F) as u8);
s.push((*epid & 0xFF) as u8);
let el = descr.len() as u16;
s.push(0b1111_0000 | ((el >> 8) & 0x0F) as u8);
s.push((el & 0xFF) as u8);
s.extend_from_slice(descr);
}
let crc = mpeg2_crc32(&s);
s.extend_from_slice(&crc.to_be_bytes());
s
}
#[test]
fn mpeg2_crc32_known_vector() {
let crc = mpeg2_crc32(b"123456789");
assert_eq!(crc, 0x0376_E6E7);
}
#[test]
fn pat_one_program_round_trip() {
let section = build_pat_section(1, 3, &[(1, 0x100)]);
let pat = ProgramAssociationTable::parse(§ion).unwrap();
assert_eq!(pat.transport_stream_id, 1);
assert_eq!(pat.version_number, 3);
assert!(pat.current_next_indicator);
assert_eq!(pat.programs, vec![(1, 0x100)]);
}
#[test]
fn pmt_avc_ac3_pgs_round_trip() {
let avc_descr: &[u8] = &[0x52, 0x01, 0x00]; let ac3_descr: &[u8] = &[0x6A, 0x01, 0x80];
let pgs_descr: &[u8] = &[];
let section = build_pmt_section(
1,
5,
0x100,
&[],
&[
(0x1B, 0x1011, avc_descr),
(0x81, 0x1100, ac3_descr),
(0x90, 0x1200, pgs_descr),
],
);
let pmt = ProgramMapTable::parse(§ion).unwrap();
assert_eq!(pmt.program_number, 1);
assert_eq!(pmt.version_number, 5);
assert!(pmt.current_next_indicator);
assert_eq!(pmt.pcr_pid, 0x100);
assert!(pmt.program_info.is_empty());
assert_eq!(pmt.streams.len(), 3);
assert_eq!(pmt.streams[0].stream_type, 0x1B);
assert_eq!(pmt.streams[0].elementary_pid, 0x1011);
assert_eq!(pmt.streams[0].descriptors, avc_descr);
assert_eq!(pmt.streams[1].stream_type, 0x81);
assert_eq!(pmt.streams[1].elementary_pid, 0x1100);
assert_eq!(pmt.streams[1].descriptors, ac3_descr);
assert_eq!(pmt.streams[2].stream_type, 0x90);
assert_eq!(pmt.streams[2].elementary_pid, 0x1200);
assert!(pmt.streams[2].descriptors.is_empty());
}
#[test]
fn psi_crc_corruption_is_rejected() {
let mut section = build_pat_section(7, 0, &[(1, 0x100)]);
section[3] ^= 0x01;
let err = ProgramAssociationTable::parse(§ion).unwrap_err();
match err {
TsError::PsiCrcMismatch { .. } => {}
other => panic!("expected PsiCrcMismatch, got {other:?}"),
}
}
#[test]
fn iter_sections_skips_pointer_field_and_stuffing() {
let section = build_pat_section(1, 0, &[(1, 0x100)]);
let mut payload = Vec::new();
payload.push(0u8); payload.extend_from_slice(§ion);
payload.extend(std::iter::repeat(0xFF).take(10));
let mut it = iter_sections(&payload);
let s = it.next().expect("section");
assert_eq!(s, section);
assert!(it.next().is_none());
}
#[test]
fn iter_sections_with_nonzero_pointer_field() {
let section = build_pat_section(1, 0, &[(1, 0x100)]);
let mut payload = Vec::new();
payload.push(3u8); payload.extend_from_slice(&[0xAA, 0xBB, 0xCC]); payload.extend_from_slice(§ion);
let s = iter_sections(&payload).next().expect("section");
assert_eq!(s, section);
}
#[test]
fn pat_network_pid_program_zero() {
let section = build_pat_section(2, 0, &[(0, 0x10), (1, 0x100)]);
let pat = ProgramAssociationTable::parse(§ion).unwrap();
assert_eq!(pat.programs[0], (0, 0x10));
assert_eq!(pat.programs[1], (1, 0x100));
}
}