use crate::{Confidence, Detail::Ts, Evidence, Outcome};
const TS_SYNC_BYTE: u8 = 0x47;
const TS_PACKET_SIZE: usize = 188;
const TS_PACKET_SIZE_192: usize = 192;
const TS_PACKET_SIZE_204: usize = 204;
const TS_PACKET_SIZE_208: usize = 208;
const TS_CONFIRM_FOR_STRONG: usize = 8;
const TS_CONFIRM_FOR_WEAK: usize = 3;
const TS_MIN_COVERAGE_PCT: u64 = 50;
const TS_STRIDES: [usize; 4] = [
TS_PACKET_SIZE,
TS_PACKET_SIZE_192,
TS_PACKET_SIZE_204,
TS_PACKET_SIZE_208,
];
pub(crate) fn probe(data: &[u8], limit: usize) -> Outcome {
debug_assert!(limit <= data.len(), "harness caps limit at data.len()");
let region = &data[..limit];
if region.len() >= TS_PACKET_SIZE && region.iter().all(|&b| b == TS_SYNC_BYTE) {
return Outcome::None;
}
let mut best_stride: Option<usize> = None;
let mut best_phase = 0usize;
let mut best_run = 0usize;
let mut best_observed_run = 0usize;
let mut best_observed_stride = 0usize;
let mut best_observed_phase = 0usize;
for &stride in &TS_STRIDES {
let win = core::cmp::min(stride, region.len());
if !region[..win].contains(&TS_SYNC_BYTE) {
continue;
}
for phase in 0..stride {
let mut run = 0usize;
let mut longest = 0usize;
let mut total_syncs = 0usize;
let mut pos = phase;
while pos < region.len() {
if region[pos] == TS_SYNC_BYTE {
run += 1;
total_syncs += 1;
if run > longest {
longest = run;
}
} else {
run = 0;
}
pos += stride;
}
if longest > best_observed_run {
best_observed_run = longest;
best_observed_stride = stride;
best_observed_phase = phase;
}
let possible = region.len().saturating_sub(phase).div_ceil(stride);
let coverage = if possible == 0 {
0
} else {
(total_syncs as u64 * 100) / possible as u64
};
if longest >= TS_CONFIRM_FOR_WEAK && coverage >= TS_MIN_COVERAGE_PCT {
let better = match best_stride {
None => true,
Some(s) => longest > best_run || (longest == best_run && stride < s),
};
if better {
best_stride = Some(stride);
best_phase = phase;
best_run = longest;
}
}
}
}
let stride = match best_stride {
Some(s) => s,
None => {
if best_observed_run == 0 {
return crate::ran_out_or_ruled_out(region.len() < TS_PACKET_SIZE, need_at_least());
}
let could_prove =
best_observed_phase + TS_CONFIRM_FOR_STRONG * best_observed_stride <= region.len();
if could_prove {
return Outcome::None;
}
return Outcome::Insufficient(need_at_least());
}
};
let detail = Ts {
stride: stride as u16,
phase: best_phase as u16,
};
let confidence = if best_run >= TS_CONFIRM_FOR_STRONG {
Confidence::LATTICE_STRONG
} else {
Confidence::LATTICE_WEAK
};
Outcome::Match(Evidence { confidence, detail })
}
fn need_at_least() -> usize {
TS_PACKET_SIZE * TS_CONFIRM_FOR_STRONG
}
#[cfg(test)]
mod all_sync {
use super::*;
#[test]
fn the_ran_out_need_does_not_track_the_buffer_length() {
let mut seen: std::vec::Vec<usize> = std::vec::Vec::new();
for len in [4usize, 64, 200, 700, 1400] {
let buf = {
let mut b = std::vec![0x47u8];
b.resize(len, 0x00);
b
};
if let Outcome::Insufficient(need) = probe(&buf, buf.len()) {
seen.push(need);
}
}
assert!(
seen.len() >= 2,
"the seed must reach Insufficient at two or more lengths, else this \
guard proves nothing (got {seen:?})"
);
assert!(
seen.windows(2).all(|w| w[0] == w[1]),
"need_at_least must be identical at every length short of the \
structure it names, got {seen:?} -- a value that grows with the \
buffer makes the caller crawl"
);
}
#[test]
fn all_sync_sub_packet_region_is_insufficient() {
match probe(&[TS_SYNC_BYTE; TS_PACKET_SIZE - 1], TS_PACKET_SIZE - 1) {
Outcome::Insufficient(_) => {}
other => panic!("all-0x47 sub-packet region must be Insufficient, got {other:?}"),
}
}
#[test]
fn non_sync_sub_packet_region_is_insufficient() {
match probe(&[b'A'; TS_PACKET_SIZE - 1], TS_PACKET_SIZE - 1) {
Outcome::Insufficient(_) => {}
other => panic!("all-'A' sub-packet region must be Insufficient, got {other:?}"),
}
}
#[test]
fn all_sync_full_region_is_still_rejected() {
match probe(&[TS_SYNC_BYTE; 4096], 4096) {
Outcome::None => {}
other => panic!("4096-byte all-0x47 region must be None, got {other:?}"),
}
}
}
#[cfg(test)]
mod drift {
#[test]
fn sync_byte_matches_mpeg_ts() {
assert_eq!(
super::TS_SYNC_BYTE,
mpeg_ts::ts::TS_SYNC_BYTE,
"container-probe's TS_SYNC_BYTE has drifted from mpeg-ts's"
);
}
#[test]
fn packet_size_matches_mpeg_ts() {
assert_eq!(
super::TS_PACKET_SIZE,
mpeg_ts::ts::TS_PACKET_SIZE,
"container-probe's TS_PACKET_SIZE has drifted from mpeg-ts's"
);
}
#[test]
fn wrapped_strides_stay_derived_from_the_packet_size() {
const TP_EXTRA_HEADER: usize = 4;
const RS_PARITY: usize = 16;
assert_eq!(
super::TS_PACKET_SIZE_192,
super::TS_PACKET_SIZE + TP_EXTRA_HEADER
);
assert_eq!(super::TS_PACKET_SIZE_204, super::TS_PACKET_SIZE + RS_PARITY);
assert_eq!(
super::TS_PACKET_SIZE_208,
super::TS_PACKET_SIZE + RS_PARITY + TP_EXTRA_HEADER
);
}
}