mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-30 10:42:29 +01:00
338 lines
13 KiB
Rust
338 lines
13 KiB
Rust
use super::{
|
|
demux::HlsPatSectionParser, HlsPatPacketFilter, HlsPmtEvidence, HlsPmtPacketFilter, HlsPmtSectionEvidence,
|
|
HlsPmtSectionParser, HlsPsiSectionConsumer, HlsTsDemuxContext, HlsTsMalformedReason, HlsTsProgramTopology,
|
|
PSI_SYNTAX_HEADER_BYTES, TS_PACKET_SIZE,
|
|
};
|
|
use mpeg2ts_reader::{
|
|
demultiplex,
|
|
demultiplex::PacketFilter,
|
|
packet::{Packet, Pid},
|
|
psi,
|
|
psi::{BufferSectionSyntaxParser, SectionPacketConsumer, SectionProcessor, SectionSyntaxSectionProcessor},
|
|
};
|
|
use std::collections::BTreeSet;
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub(super) enum HlsPsiTableKind {
|
|
Pat,
|
|
Pmt,
|
|
}
|
|
|
|
impl HlsPsiTableKind {
|
|
pub(super) const fn malformed_reason(self) -> HlsTsMalformedReason {
|
|
match self {
|
|
Self::Pat => HlsTsMalformedReason::InvalidPat,
|
|
Self::Pmt => HlsTsMalformedReason::InvalidPmt,
|
|
}
|
|
}
|
|
|
|
pub(super) const fn expected_table_id(self) -> u8 {
|
|
match self {
|
|
Self::Pat => 0x00,
|
|
Self::Pmt => 0x02,
|
|
}
|
|
}
|
|
|
|
pub(super) const fn minimum_section_length(self) -> usize {
|
|
match self {
|
|
Self::Pat => 9,
|
|
Self::Pmt => 13,
|
|
}
|
|
}
|
|
|
|
pub(super) fn validate_common_header(self, header: &[u8]) -> bool {
|
|
header.len() == psi::SectionCommonHeader::SIZE
|
|
&& header[0] == self.expected_table_id()
|
|
&& header[1] & 0x80 != 0
|
|
&& header[1] & 0x30 == 0x30
|
|
&& (self.minimum_section_length()..=1_021).contains(&psi_section_length(header))
|
|
}
|
|
}
|
|
|
|
fn psi_section_length(header: &[u8]) -> usize { (usize::from(header[1] & 0x0F) << 8) | usize::from(header[2]) }
|
|
|
|
impl<P> HlsPsiSectionConsumer<P>
|
|
where
|
|
P: SectionProcessor<Context = HlsTsDemuxContext>,
|
|
{
|
|
pub(super) fn new(consumer: SectionPacketConsumer<P>, table_kind: HlsPsiTableKind) -> Self {
|
|
Self { consumer, table_kind, pending_syntax_header: None }
|
|
}
|
|
|
|
pub(super) fn consume(&mut self, ctx: &mut HlsTsDemuxContext, packet: &Packet<'_>) {
|
|
if let Some(mut pending) = self.pending_syntax_header.take() {
|
|
let Some(payload) = packet.payload() else {
|
|
self.pending_syntax_header = Some(pending);
|
|
return;
|
|
};
|
|
if packet.payload_unit_start_indicator() {
|
|
let Some((&pointer, section_data)) = payload.split_first() else {
|
|
ctx.record_malformed(HlsTsMalformedReason::InvalidPsiPointer);
|
|
return;
|
|
};
|
|
let pointer = usize::from(pointer);
|
|
if pointer >= section_data.len() {
|
|
ctx.record_malformed(HlsTsMalformedReason::InvalidPsiPointer);
|
|
return;
|
|
}
|
|
let (previous_section, section_start) = section_data.split_at(pointer);
|
|
pending.extend_from_slice(previous_section);
|
|
if pending.len() < psi::SectionCommonHeader::SIZE
|
|
|| !self.table_kind.validate_common_header(&pending[..psi::SectionCommonHeader::SIZE])
|
|
{
|
|
ctx.record_malformed(self.table_kind.malformed_reason());
|
|
return;
|
|
}
|
|
let section_bytes = psi::SectionCommonHeader::SIZE
|
|
.saturating_add(psi_section_length(&pending[..psi::SectionCommonHeader::SIZE]));
|
|
if pending.len() < section_bytes {
|
|
ctx.record_malformed(self.table_kind.malformed_reason());
|
|
return;
|
|
}
|
|
self.consume_synthetic_payload(ctx, packet.pid(), &pending[..section_bytes], true);
|
|
if ctx.malformed.is_none() {
|
|
self.consume_new_section(ctx, packet.pid(), section_start);
|
|
}
|
|
return;
|
|
}
|
|
pending.extend_from_slice(payload);
|
|
if pending.len() < psi::SectionCommonHeader::SIZE {
|
|
self.pending_syntax_header = Some(pending);
|
|
return;
|
|
}
|
|
if !self.table_kind.validate_common_header(&pending[..psi::SectionCommonHeader::SIZE]) {
|
|
ctx.record_malformed(self.table_kind.malformed_reason());
|
|
return;
|
|
}
|
|
if pending.len() < PSI_SYNTAX_HEADER_BYTES {
|
|
self.pending_syntax_header = Some(pending);
|
|
return;
|
|
}
|
|
let section_bytes = psi::SectionCommonHeader::SIZE
|
|
.saturating_add(psi_section_length(&pending[..psi::SectionCommonHeader::SIZE]))
|
|
.min(pending.len());
|
|
self.consume_synthetic_payload(ctx, packet.pid(), &pending[..section_bytes], true);
|
|
return;
|
|
}
|
|
|
|
let Some(payload) = packet.payload() else {
|
|
self.consumer.consume(ctx, packet);
|
|
return;
|
|
};
|
|
if !packet.payload_unit_start_indicator() {
|
|
self.consumer.consume(ctx, packet);
|
|
return;
|
|
}
|
|
let Some((&pointer, section_data)) = payload.split_first() else {
|
|
ctx.record_malformed(HlsTsMalformedReason::InvalidPsiPointer);
|
|
return;
|
|
};
|
|
let pointer = usize::from(pointer);
|
|
if pointer >= section_data.len() {
|
|
ctx.record_malformed(HlsTsMalformedReason::InvalidPsiPointer);
|
|
return;
|
|
}
|
|
let section_start = §ion_data[pointer..];
|
|
if section_start.len() < PSI_SYNTAX_HEADER_BYTES {
|
|
if let Some(previous_section) = section_data.get(..pointer).filter(|bytes| !bytes.is_empty()) {
|
|
self.consume_synthetic_payload(ctx, packet.pid(), previous_section, false);
|
|
}
|
|
if ctx.malformed.is_none() {
|
|
self.consume_new_section(ctx, packet.pid(), section_start);
|
|
}
|
|
return;
|
|
}
|
|
if !self.table_kind.validate_common_header(§ion_start[..psi::SectionCommonHeader::SIZE]) {
|
|
ctx.record_malformed(self.table_kind.malformed_reason());
|
|
return;
|
|
}
|
|
self.consumer.consume(ctx, packet);
|
|
}
|
|
|
|
pub(super) fn consume_new_section(&mut self, ctx: &mut HlsTsDemuxContext, pid: Pid, section_start: &[u8]) {
|
|
if section_start.len() < PSI_SYNTAX_HEADER_BYTES {
|
|
if section_start.len() >= psi::SectionCommonHeader::SIZE
|
|
&& !self.table_kind.validate_common_header(§ion_start[..psi::SectionCommonHeader::SIZE])
|
|
{
|
|
ctx.record_malformed(self.table_kind.malformed_reason());
|
|
return;
|
|
}
|
|
self.pending_syntax_header = Some(section_start.to_vec());
|
|
return;
|
|
}
|
|
if !self.table_kind.validate_common_header(§ion_start[..psi::SectionCommonHeader::SIZE]) {
|
|
ctx.record_malformed(self.table_kind.malformed_reason());
|
|
return;
|
|
}
|
|
self.consume_synthetic_payload(ctx, pid, section_start, true);
|
|
}
|
|
|
|
pub(super) fn consume_synthetic_payload(
|
|
&mut self,
|
|
ctx: &mut HlsTsDemuxContext,
|
|
pid: Pid,
|
|
bytes: &[u8],
|
|
start: bool,
|
|
) {
|
|
let mut offset = 0usize;
|
|
let mut first = true;
|
|
while offset < bytes.len() {
|
|
let starts_section = start && first;
|
|
let capacity = if starts_section { 183 } else { 184 };
|
|
let copied = bytes.len().saturating_sub(offset).min(capacity);
|
|
let payload = &bytes[offset..offset.saturating_add(copied)];
|
|
let packet_bytes = synthetic_psi_packet(pid, starts_section, payload);
|
|
if let Some(packet) = Packet::try_new(&packet_bytes) {
|
|
self.consumer.consume(ctx, &packet);
|
|
}
|
|
offset = offset.saturating_add(copied);
|
|
first = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn synthetic_psi_packet(pid: Pid, starts_section: bool, payload: &[u8]) -> [u8; TS_PACKET_SIZE] {
|
|
let pointer_bytes = usize::from(starts_section);
|
|
let payload_length = payload.len().saturating_add(pointer_bytes).min(184);
|
|
let pid = u16::from(pid);
|
|
let mut packet = [0xFF_u8; TS_PACKET_SIZE];
|
|
packet[0] = crate::packet::TS_SYNC_BYTE;
|
|
packet[1] = (u8::try_from(pid >> 8).unwrap_or(0) & 0x1F) | if starts_section { 0x40 } else { 0 };
|
|
packet[2] = pid.to_be_bytes()[1];
|
|
let payload_offset = if payload_length == 184 {
|
|
packet[3] = 0x10;
|
|
4
|
|
} else {
|
|
packet[3] = 0x30;
|
|
let adaptation_length = 183usize.saturating_sub(payload_length);
|
|
packet[4] = u8::try_from(adaptation_length).unwrap_or(182);
|
|
if adaptation_length > 0 {
|
|
packet[5] = 0;
|
|
}
|
|
5usize.saturating_add(adaptation_length)
|
|
};
|
|
let data_offset = if starts_section {
|
|
packet[payload_offset] = 0;
|
|
payload_offset.saturating_add(1)
|
|
} else {
|
|
payload_offset
|
|
};
|
|
let copy_length = payload.len().min(TS_PACKET_SIZE.saturating_sub(data_offset));
|
|
packet[data_offset..data_offset.saturating_add(copy_length)].copy_from_slice(&payload[..copy_length]);
|
|
packet
|
|
}
|
|
|
|
impl Default for HlsPatPacketFilter {
|
|
fn default() -> Self {
|
|
Self {
|
|
consumer: HlsPsiSectionConsumer::new(
|
|
SectionPacketConsumer::new(SectionSyntaxSectionProcessor::new(BufferSectionSyntaxParser::new(
|
|
HlsPatSectionParser,
|
|
))),
|
|
HlsPsiTableKind::Pat,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PacketFilter for HlsPatPacketFilter {
|
|
type Ctx = HlsTsDemuxContext;
|
|
|
|
fn consume(&mut self, ctx: &mut Self::Ctx, packet: &Packet<'_>) { self.consumer.consume(ctx, packet); }
|
|
}
|
|
|
|
impl HlsPmtPacketFilter {
|
|
pub(super) fn new(pid: Pid, program_number: u16) -> Self {
|
|
Self {
|
|
consumer: HlsPsiSectionConsumer::new(
|
|
SectionPacketConsumer::new(SectionSyntaxSectionProcessor::new(BufferSectionSyntaxParser::new(
|
|
HlsPmtSectionParser { pid, program_number },
|
|
))),
|
|
HlsPsiTableKind::Pmt,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PacketFilter for HlsPmtPacketFilter {
|
|
type Ctx = HlsTsDemuxContext;
|
|
|
|
fn consume(&mut self, ctx: &mut Self::Ctx, packet: &Packet<'_>) { self.consumer.consume(ctx, packet); }
|
|
}
|
|
|
|
pub(super) enum HlsTsPacketFilter {
|
|
Pat(HlsPatPacketFilter),
|
|
Pmt(HlsPmtPacketFilter),
|
|
Ignore(demultiplex::NullPacketFilter<HlsTsDemuxContext>),
|
|
}
|
|
|
|
impl PacketFilter for HlsTsPacketFilter {
|
|
type Ctx = HlsTsDemuxContext;
|
|
|
|
fn consume(&mut self, ctx: &mut Self::Ctx, packet: &Packet<'_>) {
|
|
match self {
|
|
Self::Pat(filter) => filter.consume(ctx, packet),
|
|
Self::Pmt(filter) => filter.consume(ctx, packet),
|
|
Self::Ignore(filter) => filter.consume(ctx, packet),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl HlsPmtEvidence {
|
|
pub(super) fn complete(&self) -> bool {
|
|
self.last_section_number.is_some_and(|last| (0..=last).all(|section| self.sections.contains_key(§ion)))
|
|
}
|
|
|
|
pub(super) fn restart_version(&mut self, version: u8, last_section_number: u8) {
|
|
self.table_version = Some(version);
|
|
self.last_section_number = Some(last_section_number);
|
|
self.sections.clear();
|
|
self.has_pcr = false;
|
|
self.stream_types.clear();
|
|
}
|
|
|
|
pub(super) fn record_section(
|
|
&mut self,
|
|
section_number: u8,
|
|
section: HlsPmtSectionEvidence,
|
|
) -> Result<(), HlsTsMalformedReason> {
|
|
if self.sections.get(§ion_number).is_some_and(|current| current != §ion) {
|
|
return Err(HlsTsMalformedReason::InvalidPmt);
|
|
}
|
|
self.sections.insert(section_number, section);
|
|
self.has_pcr = self.sections.values().any(|section| section.pcr_pid != u16::from(mpeg2ts_reader::STUFFING_PID));
|
|
self.stream_types = self
|
|
.sections
|
|
.values()
|
|
.flat_map(|section| section.streams.iter().map(|stream| stream.stream_type))
|
|
.collect();
|
|
Ok(())
|
|
}
|
|
|
|
pub(super) fn topology(&self, transport_stream_id: u16, pmt_pid: u16) -> Option<HlsTsProgramTopology> {
|
|
if !self.complete() {
|
|
return None;
|
|
}
|
|
let mut sections = self.sections.values();
|
|
let pcr_pid = sections.next()?.pcr_pid;
|
|
if self.sections.values().any(|section| section.pcr_pid != pcr_pid) {
|
|
return None;
|
|
}
|
|
let streams = self.sections.values().flat_map(|section| section.streams.iter().copied()).collect::<Vec<_>>();
|
|
if streams.is_empty() {
|
|
return None;
|
|
}
|
|
let mut elementary_pids = BTreeSet::new();
|
|
if streams.iter().any(|stream| !elementary_pids.insert(stream.elementary_pid)) {
|
|
return None;
|
|
}
|
|
Some(HlsTsProgramTopology {
|
|
transport_stream_id,
|
|
program_number: self.program_number,
|
|
pmt_pid,
|
|
pcr_pid,
|
|
streams: streams.into(),
|
|
})
|
|
}
|
|
}
|