use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use mpeg_ts::owned::OwnedTsPacket;
use mpeg_ts::ts::{TS_PACKET_SIZE, TsHeader};
use crate::ops::{Op, StreamModel};
const AF_DISCONTINUITY: u8 = 0x80;
struct PidState {
expected: u8,
last_wire_cc: u8,
last_packet: Vec<u8>,
}
pub(crate) struct ContinuityOp {
per_pid: BTreeMap<u16, PidState>,
}
impl ContinuityOp {
pub(crate) fn new() -> Self {
Self {
per_pid: BTreeMap::new(),
}
}
#[inline]
#[allow(dead_code)]
fn afc(b3: u8) -> u8 {
(b3 >> 4) & 0x03
}
fn has_discontinuity(pkt: &[u8]) -> bool {
debug_assert!(pkt.len() == TS_PACKET_SIZE);
let af_len = pkt[4] as usize;
if af_len == 0 {
return false;
}
pkt[5] & AF_DISCONTINUITY != 0
}
}
impl Op for ContinuityOp {
fn process(&mut self, packet: &[u8], _model: &mut StreamModel, out: &mut dyn FnMut(&[u8])) {
if packet.len() != TS_PACKET_SIZE {
out(packet);
return;
}
let header = match TsHeader::parse(&packet[..4]) {
Ok(h) => h,
Err(_) => {
out(packet);
return;
}
};
let pid = header.pid;
let current_cc = header.continuity_counter;
let has_payload = header.has_payload;
if !has_payload {
out(packet);
return;
}
let is_discontinuity = header.has_adaptation && Self::has_discontinuity(packet);
if is_discontinuity {
self.per_pid.remove(&pid);
out(packet);
return;
}
let mut state_initialised = false;
let state = self.per_pid.entry(pid).or_insert_with(|| {
state_initialised = true;
PidState {
expected: current_cc,
last_wire_cc: current_cc,
last_packet: packet.to_vec(),
}
});
if state_initialised {
let next = (current_cc + 1) & 0x0F;
state.expected = next;
out(packet);
return;
}
if current_cc == state.last_wire_cc
&& broadcast_common::ts_dup::is_legal_duplicate_pair(&state.last_packet, packet)
{
state.last_wire_cc = current_cc;
state.last_packet = packet.to_vec();
out(packet);
return;
}
if current_cc == state.expected {
let next = (current_cc + 1) & 0x0F;
state.expected = next;
state.last_wire_cc = current_cc;
state.last_packet = packet.to_vec();
out(packet);
return;
}
let correct_cc = state.expected;
let mut buf = [0u8; TS_PACKET_SIZE];
buf.copy_from_slice(packet);
OwnedTsPacket::set_continuity_counter(&mut buf, correct_cc);
out(&buf[..]);
state.expected = (correct_cc + 1) & 0x0F;
state.last_wire_cc = correct_cc;
}
fn flush(&mut self, _model: &mut StreamModel, _out: &mut dyn FnMut(&[u8])) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use mpeg_ts::ts::TS_SYNC_BYTE;
fn make_payload_packet(pid: u16, cc: u8, has_adaptation: bool, payload: &[u8]) -> [u8; 188] {
let mut pkt = [0xFFu8; 188];
pkt[0] = TS_SYNC_BYTE;
pkt[1] = ((pid >> 8) as u8) & 0x1F;
pkt[2] = (pid & 0xFF) as u8;
let mut b3 = cc & 0x0F;
b3 |= 0x10; if has_adaptation {
b3 |= 0x20; }
pkt[3] = b3;
let mut cursor = 4usize;
if has_adaptation {
pkt[cursor] = 1; pkt[cursor + 1] = 0; cursor += 2;
}
let payload_len = payload.len().min(188 - cursor);
pkt[cursor..cursor + payload_len].copy_from_slice(&payload[..payload_len]);
pkt
}
fn make_discontinuity_packet(pid: u16, cc: u8, payload: &[u8]) -> [u8; 188] {
let mut pkt = [0xFFu8; 188];
pkt[0] = TS_SYNC_BYTE;
pkt[1] = ((pid >> 8) as u8) & 0x1F;
pkt[2] = (pid & 0xFF) as u8;
pkt[3] = cc & 0x0F;
pkt[3] |= 0x10 | 0x20;
pkt[4] = 1; pkt[5] = 0x80; let cursor = 6usize;
let payload_len = payload.len().min(188 - cursor);
pkt[cursor..cursor + payload_len].copy_from_slice(&payload[..payload_len]);
pkt
}
fn run_op(packets: &[[u8; 188]]) -> Vec<[u8; 188]> {
let mut op = ContinuityOp::new();
let mut model = StreamModel::default();
let mut output = Vec::new();
for pkt in packets {
op.process(pkt, &mut model, &mut |out| {
let mut buf = [0u8; 188];
buf.copy_from_slice(out);
output.push(buf);
});
}
op.flush(&mut model, &mut |_| {});
output
}
#[test]
fn non_payload_does_not_advance_cc() {
let pkt1 = make_payload_packet(0x0100, 0, false, &[0xAA]);
let mut pkt2 = make_payload_packet(0x0100, 0, false, &[0xBB]);
pkt2[3] &= 0xCF; pkt2[3] |= 0x20;
let mut pkt3 = make_payload_packet(0x0100, 0, false, &[0xCC]);
pkt3[3] &= 0xCF;
pkt3[3] |= 0x00;
let output = run_op(&[pkt1, pkt2, pkt3]);
assert_eq!(output.len(), 3);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 0);
assert_eq!(output[2][3] & 0x0F, 0);
}
#[test]
fn duplicate_packet_is_preserved() {
let payload = &[0xAB, 0xCD, 0xEF];
let pkt1 = make_payload_packet(0x0200, 0, false, payload);
let pkt2 = make_payload_packet(0x0200, 0, false, payload);
let output = run_op(&[pkt1, pkt2]);
assert_eq!(output.len(), 2);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 0);
}
#[test]
fn same_cc_with_different_payload_is_repaired() {
let pkt1 = make_payload_packet(0x0200, 0, false, &[0xAB]);
let pkt2 = make_payload_packet(0x0200, 0, false, &[0xCD]);
let output = run_op(&[pkt1, pkt2]);
assert_eq!(output.len(), 2);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 1);
}
#[test]
fn discontinuity_packet_is_preserved() {
let pkt1 = make_payload_packet(0x0300, 0, false, &[0xAA]);
let pkt2 = make_discontinuity_packet(0x0300, 0x0F, &[0xBB]);
let output = run_op(&[pkt1, pkt2]);
assert_eq!(output.len(), 2);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 0x0F);
}
#[test]
fn cc_after_discontinuity_resets() {
let pkt1 = make_payload_packet(0x0300, 0, false, &[0xAA]);
let pkt2 = make_discontinuity_packet(0x0300, 0x0F, &[0xBB]);
let pkt3 = make_payload_packet(0x0300, 3, false, &[0xCC]);
let output = run_op(&[pkt1, pkt2, pkt3]);
assert_eq!(output.len(), 3);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 0x0F);
assert_eq!(output[2][3] & 0x0F, 3);
}
#[test]
fn genuine_cc_gap_is_repaired() {
let pkt1 = make_payload_packet(0x0400, 0, false, &[0xAA]);
let pkt2 = make_payload_packet(0x0400, 3, false, &[0xBB]);
let output = run_op(&[pkt1, pkt2]);
assert_eq!(output.len(), 2);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 1); }
#[test]
fn normal_cc_sequence_passes_unmodified() {
let pkt1 = make_payload_packet(0x0500, 0, false, &[0xAA]);
let pkt2 = make_payload_packet(0x0500, 1, false, &[0xBB]);
let pkt3 = make_payload_packet(0x0500, 2, false, &[0xCC]);
let output = run_op(&[pkt1, pkt2, pkt3]);
assert_eq!(output.len(), 3);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 1);
assert_eq!(output[2][3] & 0x0F, 2);
}
#[test]
fn cc_wrap_around() {
let pkt1 = make_payload_packet(0x0600, 0x0F, false, &[0xAA]);
let pkt2 = make_payload_packet(0x0600, 0x00, false, &[0xBB]);
let output = run_op(&[pkt1, pkt2]);
assert_eq!(output.len(), 2);
assert_eq!(output[0][3] & 0x0F, 0x0F);
assert_eq!(output[1][3] & 0x0F, 0x00); }
#[test]
fn multiple_pids_independent() {
let pkt1 = make_payload_packet(0x0100, 0, false, &[0xAA]);
let pkt2 = make_payload_packet(0x0200, 0, false, &[0xBB]);
let pkt3 = make_payload_packet(0x0100, 1, false, &[0xCC]); let pkt4 = make_payload_packet(0x0200, 1, false, &[0xDD]); let pkt5 = make_payload_packet(0x0100, 2, false, &[0xEE]);
let output = run_op(&[pkt1, pkt2, pkt3, pkt4, pkt5]);
assert_eq!(output.len(), 5);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 0);
assert_eq!(output[2][3] & 0x0F, 1);
assert_eq!(output[3][3] & 0x0F, 1);
assert_eq!(output[4][3] & 0x0F, 2);
}
#[test]
fn afc_11_packet_with_adaptation_advances_cc() {
let pkt1 = make_payload_packet(0x0700, 0, true, &[0xAA]);
let pkt2 = make_payload_packet(0x0700, 1, true, &[0xBB]);
let output = run_op(&[pkt1, pkt2]);
assert_eq!(output.len(), 2);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 1);
}
#[test]
fn duplicate_with_pcr_difference_is_preserved() {
let mut pkt1 = [0xFFu8; 188];
pkt1[0] = TS_SYNC_BYTE;
pkt1[1] = 0x00;
pkt1[2] = 0x50; pkt1[3] = 0x10; pkt1[4..10].copy_from_slice(b"PAYLOA");
let mut pkt1b = [0xFFu8; 188];
pkt1b[0] = TS_SYNC_BYTE;
pkt1b[1] = 0x00;
pkt1b[2] = 0x50;
pkt1b[3] = 0x30 | 0x01; pkt1b[4] = 7; pkt1b[5] = 0x10; pkt1b[6] = 0x00;
pkt1b[7] = 0x00;
pkt1b[8] = 0x00;
pkt1b[9] = 0x00;
pkt1b[10] = 0x00;
pkt1b[11] = 0x00;
pkt1b[12..18].copy_from_slice(b"PAYLOA");
let mut pkt2 = [0xFFu8; 188];
pkt2[0] = TS_SYNC_BYTE;
pkt2[1] = 0x00;
pkt2[2] = 0x50;
pkt2[3] = 0x30 | 0x01; pkt2[4] = 7; pkt2[5] = 0x10; pkt2[6] = 0x12;
pkt2[7] = 0x34;
pkt2[8] = 0x56;
pkt2[9] = 0x78;
pkt2[10] = 0x9A;
pkt2[11] = 0xBC;
pkt2[12..18].copy_from_slice(b"PAYLOA");
let output = run_op(&[pkt1, pkt1b, pkt2]);
assert_eq!(output.len(), 3);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(output[1][3] & 0x0F, 1);
assert_eq!(output[2][3] & 0x0F, 1);
}
#[test]
fn splice_countdown_difference_is_not_a_legal_duplicate() {
let mut pkt1 = [0xFFu8; 188];
pkt1[0] = TS_SYNC_BYTE;
pkt1[1] = 0x00;
pkt1[2] = 0x60; pkt1[3] = 0x30; pkt1[4] = 8; pkt1[5] = 0x10 | 0x04; pkt1[6..12].copy_from_slice(&[0, 0, 0, 0, 0, 0]); pkt1[12] = 5; pkt1[13..19].copy_from_slice(b"PAYLOA");
let mut pkt2 = pkt1;
pkt2[12] = 4;
let output = run_op(&[pkt1, pkt2]);
assert_eq!(output.len(), 2);
assert_eq!(output[0][3] & 0x0F, 0);
assert_eq!(
output[1][3] & 0x0F,
1,
"splice_countdown-only difference must be repaired, not preserved as a duplicate"
);
}
}