use crate::{Confidence, Detail, Evidence, Outcome};
const START_CODE_3: [u8; 3] = [0x00, 0x00, 0x01];
const START_CODE_4: [u8; 4] = [0x00, 0x00, 0x00, 0x01];
const NAL_FORBIDDEN_ZERO: u8 = 0x80;
const NAL_TYPE_MASK: u8 = 0x1F;
const NAL_TYPE_MIN: u8 = 1;
const NAL_TYPE_MAX: u8 = 0x1F;
const ANNEXB_MIN_NALS_WEAK: usize = 4;
const ANNEXB_MIN_NALS_STRONG: usize = 16;
const ANNEXB_MIN_LEN: usize = START_CODE_3.len() + 1;
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() < ANNEXB_MIN_LEN {
return Outcome::Insufficient(ANNEXB_MIN_LEN);
}
let (chain, truncated) = annexb_nal_chain(region);
if chain >= ANNEXB_MIN_NALS_STRONG {
return Outcome::Match(Evidence {
confidence: Confidence::LATTICE_STRONG,
detail: Detail::None,
});
}
if chain >= ANNEXB_MIN_NALS_WEAK {
return Outcome::Match(Evidence {
confidence: Confidence::LATTICE_WEAK,
detail: Detail::None,
});
}
if chain == 0 {
if truncated {
return Outcome::Insufficient(need_at_least());
}
return Outcome::None;
}
if truncated {
Outcome::Insufficient(need_at_least())
} else {
Outcome::None
}
}
fn need_at_least() -> usize {
ANNEXB_MIN_NALS_WEAK * ANNEXB_MIN_LEN
}
fn start_code_len(data: &[u8], i: usize) -> usize {
if start_code4_at(data, i) {
START_CODE_4.len()
} else if start_code3_at(data, i) {
START_CODE_3.len()
} else {
0
}
}
fn start_code4_at(data: &[u8], i: usize) -> bool {
i + START_CODE_4.len() <= data.len()
&& data.get(i..i + START_CODE_4.len()) == Some(&START_CODE_4[..])
}
fn start_code3_at(data: &[u8], i: usize) -> bool {
i + START_CODE_3.len() <= data.len()
&& data.get(i..i + START_CODE_3.len()) == Some(&START_CODE_3[..])
}
fn valid_nal(data: &[u8], i: usize) -> bool {
let sc = start_code_len(data, i);
if sc == 0 {
return false;
}
let Some(&header) = data.get(i + sc) else {
return false;
};
if header & NAL_FORBIDDEN_ZERO != 0 {
return false;
}
let nal_type = header & NAL_TYPE_MASK;
(NAL_TYPE_MIN..=NAL_TYPE_MAX).contains(&nal_type)
}
fn annexb_nal_chain(data: &[u8]) -> (usize, bool) {
let n = data.len();
if start_code_len(data, 0) == 0 {
return (0, false);
}
let mut cnt = 0usize;
let mut i = 0usize;
loop {
let sc = start_code_len(data, i);
if sc == 0 {
return (cnt, true);
}
if i + sc >= n {
return (cnt, true);
}
if !valid_nal(data, i) {
return (cnt, false);
}
cnt += 1;
if cnt >= ANNEXB_MIN_NALS_STRONG {
return (cnt, false);
}
let mut next = i + sc + 1;
while next + 2 <= n && start_code_len(data, next) == 0 {
next += 1;
}
if next + 2 > n {
return (cnt, true);
}
i = next;
}
}
#[cfg(test)]
mod tests {
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, 8, 16, 24] {
let buf = {
let mut b = std::vec![0x00, 0x00, 0x00, 0x01, 0x67];
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"
);
}
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 forbidden_zero_bit_keeps_ps_out_of_annexb() {
let data = fixture_bytes("fixtures/ps/h264_ac3.ps");
match probe(&data, data.len()) {
Outcome::None => {}
other => panic!("h264_ac3.ps must NOT match AnnexB (forbidden bit), got {other:?}"),
}
}
#[test]
fn short_prefix_is_insufficient() {
let data = fixture_bytes("fixtures/container-probe/h264.annexb");
let region = &data[..ANNEXB_MIN_LEN - 1];
match probe(region, region.len()) {
Outcome::Insufficient(need) => assert_eq!(need, ANNEXB_MIN_LEN),
other => panic!("3-byte Annex B prefix must be Insufficient(4), got {other:?}"),
}
}
#[test]
fn start_code_with_no_nal_header_is_truncation_not_rejection() {
assert_eq!(annexb_nal_chain(&[0x00, 0x00, 0x01]), (0, true));
assert_eq!(annexb_nal_chain(&[0x00, 0x00, 0x00, 0x01]), (0, true));
}
#[test]
fn bare_start_code_probes_insufficient() {
match probe(&[0x00, 0x00, 0x00, 0x01], 4) {
Outcome::Insufficient(_) => {}
other => panic!("bare start code must be Insufficient, got {other:?}"),
}
}
}