use crate::{Confidence, Detail, Evidence, Outcome};
const ADTS_LAYER_MASK: u8 = 0xF6;
const ADTS_SYNC: [u8; 2] = [0xFF, 0xF0];
const ADTS_FRAME_LENGTH_SHIFT: u8 = 11;
const ADTS_MIN_FRAME_LEN: usize = 7;
const ADTS_HEADER_LEN: usize = 6;
const ADTS_MIN_CHAIN_WEAK: usize = 4;
const ADTS_MIN_CHAIN_STRONG: usize = 16;
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() < ADTS_HEADER_LEN {
return Outcome::Insufficient(ADTS_HEADER_LEN);
}
let (longest, truncated, anchor, frame_len) = longest_adts_chain(region);
if longest >= ADTS_MIN_CHAIN_STRONG {
return Outcome::Match(Evidence {
confidence: Confidence::LATTICE_STRONG,
detail: Detail::None,
});
}
if longest >= ADTS_MIN_CHAIN_WEAK {
return Outcome::Match(Evidence {
confidence: Confidence::LATTICE_WEAK,
detail: Detail::None,
});
}
if longest == 0 {
return Outcome::None;
}
if truncated {
Outcome::Insufficient(need_at_least(anchor, frame_len))
} else {
Outcome::None
}
}
fn need_at_least(anchor: usize, frame_len: usize) -> usize {
anchor.saturating_add(ADTS_MIN_CHAIN_WEAK.saturating_mul(frame_len))
}
fn adts_frame_len(data: &[u8], i: usize) -> Option<usize> {
if i + 6 > data.len() {
return None;
}
if data[i] != ADTS_SYNC[0] || data[i + 1] & ADTS_LAYER_MASK != ADTS_SYNC[1] {
return None;
}
let frame_len = ((usize::from(data[i + 3]) & 0x03) << ADTS_FRAME_LENGTH_SHIFT)
| (usize::from(data[i + 4]) << 3)
| (usize::from(data[i + 5]) >> 5);
if frame_len < ADTS_MIN_FRAME_LEN {
return None;
}
Some(frame_len)
}
fn longest_adts_chain(data: &[u8]) -> (usize, bool, usize, usize) {
let mut best = 0usize;
let mut best_truncated = false;
let mut best_anchor = 0usize;
let mut best_frame_len = 0usize;
let n = data.len();
let mut i = 0usize;
while i < n {
if let Some(first_len) = adts_frame_len(data, i) {
let mut p = i;
let mut run = 0usize;
let mut truncated = false;
while let Some(l) = adts_frame_len(data, p) {
run += 1;
if run >= ADTS_MIN_CHAIN_STRONG {
return (run, false, i, first_len); }
if p + l > n {
truncated = true;
break;
}
p += l;
}
if run > best || (run == best && truncated) {
best = run;
best_truncated = truncated;
best_anchor = i;
best_frame_len = first_len;
}
i += 1;
} else {
i += 1;
}
}
(best, best_truncated, best_anchor, best_frame_len)
}
#[cfg(test)]
fn synthetic_ts_carrying_adts(frame_count: usize) -> alloc::vec::Vec<u8> {
let aac_len = frame_count * 274usize;
let packets = core::cmp::max(12, aac_len.div_ceil(188) * 2);
let mut v = alloc::vec::Vec::new();
for _ in 0..packets {
let mut pkt = alloc::vec![0u8; 188];
pkt[0] = 0x47;
v.extend_from_slice(&pkt);
}
let frame_len: u16 = 274;
for _ in 0..frame_count {
let mut f = alloc::vec![0u8; frame_len as usize];
f[0] = 0xFF;
f[1] = 0xF1; f[3] = ((frame_len >> 11) & 0x03) as u8;
f[4] = ((frame_len >> 3) & 0xFF) as u8;
f[5] = ((frame_len & 0x07) as u8) << 5;
v.extend_from_slice(&f);
}
v
}
#[cfg(test)]
pub(crate) fn adts_frame_count(data: &[u8]) -> usize {
let mut c = 0usize;
let mut i = 0usize;
while i + 6 <= data.len() {
if adts_frame_len(data, i).is_some() {
c += 1;
i += 1;
} else {
i += 1;
}
}
c
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture_bytes(rel: &str) -> std::vec::Vec<u8> {
std::fs::read(std::format!("{}/../{}", env!("CARGO_MANIFEST_DIR"), rel))
.unwrap_or_else(|e| panic!("failed to read {rel}: {e}"))
}
#[test]
fn adts_prober_fires_on_ts_carrying_adts() {
let data = synthetic_ts_carrying_adts(32);
let ts = crate::probe_with_budget(&data, data.len());
assert!(matches!(
ts,
crate::Probe::Identified {
format: crate::Format::MpegTs,
..
}
));
assert!(adts_frame_count(&data) > 0);
match probe(&data, data.len()) {
Outcome::Match(ev) => {
assert_eq!(ev.confidence, Confidence::LATTICE_STRONG);
}
other => panic!("adts probe must fire on the payload, got {other:?}"),
}
}
#[test]
fn chain_threshold_keeps_a_container_out() {
let data = fixture_bytes("fixtures/ts/h264_aac.ts");
assert!(
adts_frame_count(&data) > 0,
"container must carry ADTS frames"
);
match probe(&data, data.len()) {
Outcome::None => {}
other => panic!("h264_aac.ts must NOT match ADTS at the real threshold, got {other:?}"),
}
}
#[test]
fn synthetic_ts_carrying_adts_layout_is_pinned() {
let data = synthetic_ts_carrying_adts(32);
let base = 94 * 188;
assert_eq!(data.len(), base + 32 * 274);
for p in 0..94 {
assert_eq!(
data[p * 188],
0x47,
"packet {p} must start with the sync byte"
);
}
assert_eq!(data[base], 0xFF);
assert_eq!(data[base + 1], 0xF1);
assert_eq!(data[base + 3], 0);
assert_eq!(data[base + 4], (274u16 >> 3) as u8);
assert_eq!(data[base + 5], ((274u16 & 0x07) as u8) << 5);
let last = base + 31 * 274;
assert_eq!(data[last], 0xFF);
assert_eq!(data[last + 4], (274u16 >> 3) as u8);
}
#[test]
fn the_truncated_chain_need_is_the_structural_value() {
const F: usize = 64;
let encode = |frame_len: u16| -> [u8; 6] {
[
0xFF,
0xF1,
0x00,
((frame_len >> 11) & 0x03) as u8,
((frame_len >> 3) & 0xFF) as u8,
(((frame_len & 0x07) as u8) << 5),
]
};
let mut full = alloc::vec::Vec::new();
for _ in 0..5 {
full.extend_from_slice(&encode(F as u16));
full.resize(full.len() + (F - ADTS_HEADER_LEN), 0x00);
}
let mut seen = alloc::vec::Vec::new();
for len in [F + 8, 2 * F, 2 * F + 40, 3 * F - 8] {
let buf = &full[..len];
if let Outcome::Insufficient(need) = probe(buf, buf.len()) {
seen.push((len, need));
}
}
assert!(
seen.len() >= 3,
"the seed must reach the truncated-chain Insufficient at three or more \
lengths, else this proves nothing (got {seen:?})"
);
for (len, need) in &seen {
assert_eq!(
*need,
ADTS_MIN_CHAIN_WEAK * F,
"at {len} bytes the need must be the anchor plus \
{ADTS_MIN_CHAIN_WEAK} frames of {F}"
);
}
}
#[test]
fn large_frame_length_round_trips() {
let encode = |frame_len: u16| -> [u8; 6] {
[
0xFF, 0xF1, 0x00, ((frame_len >> 11) & 0x03) as u8,
((frame_len >> 3) & 0xFF) as u8,
(((frame_len & 0x07) as u8) << 5),
]
};
for frame_len in [0x800u16, 2050, 0x1FFF] {
let hdr = encode(frame_len);
let decoded = adts_frame_len(&hdr, 0).expect("valid header must decode");
assert_eq!(
usize::from(frame_len),
decoded,
"13-bit frame_length {frame_len} must round-trip exactly"
);
}
}
#[test]
fn short_prefix_is_insufficient() {
let data = fixture_bytes("fixtures/container-probe/aac.adts");
let region = &data[..ADTS_HEADER_LEN - 1];
match probe(region, region.len()) {
Outcome::Insufficient(need) => assert_eq!(need, ADTS_HEADER_LEN),
other => panic!("5-byte ADTS prefix must be Insufficient(6), got {other:?}"),
}
}
}