use crate::{Confidence, Detail::Isobmff, Evidence, IsobmffLayout, Outcome};
const BOX_HEADER_MIN_SIZE: usize = 8;
const BOX_HEADER_LARGESIZE_SIZE: usize = 16;
const BRAND_OFFSET: usize = 8;
const SIZE_TO_EOF: u32 = 0;
const SIZE_INDICATES_LARGESIZE: u32 = 1;
const TYPE_FTYP: [u8; 4] = *b"ftyp";
const TYPE_STYP: [u8; 4] = *b"styp";
const TYPE_MOOV: [u8; 4] = *b"moov";
const TYPE_MOOF: [u8; 4] = *b"moof";
const TYPE_SKIP: [u8; 4] = *b"skip";
const TYPE_FREE: [u8; 4] = *b"free";
const TYPE_MDAT: [u8; 4] = *b"mdat";
const BRAND_LEN: usize = 4;
const LEADING_BOX_TYPES: [[u8; 4]; 7] = [
TYPE_FTYP, TYPE_STYP, TYPE_MOOV, TYPE_MOOF, TYPE_SKIP, TYPE_FREE, TYPE_MDAT,
];
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() < BOX_HEADER_MIN_SIZE {
return Outcome::Insufficient(BOX_HEADER_MIN_SIZE);
}
let leading_type = [region[4], region[5], region[6], region[7]];
if !LEADING_BOX_TYPES.contains(&leading_type) {
return Outcome::None;
}
let leading_is_ftyp = leading_type == TYPE_FTYP || leading_type == TYPE_STYP;
let mut offset = 0usize;
let mut boxes = 0u64;
let mut brand: Option<[u8; 4]> = None;
let mut saw_moof = false;
let mut saw_moov = false;
let mut clean = true;
let mut ran_out = false;
let mut ran_out_need = 0usize;
loop {
if offset >= region.len() {
break;
}
let rem = ®ion[offset..];
let (size_u32, eff) = match decode_header(rem) {
Some(x) => x,
None => {
clean = false;
ran_out = true;
ran_out_need = offset.saturating_add(header_len_required(rem));
break;
}
};
let min_header = if size_u32 == SIZE_INDICATES_LARGESIZE {
BOX_HEADER_LARGESIZE_SIZE
} else {
BOX_HEADER_MIN_SIZE
};
if size_u32 == SIZE_TO_EOF || eff >= min_header {
if offset == 0 && leading_is_ftyp && rem.len() >= BRAND_OFFSET + BRAND_LEN {
brand = Some([rem[8], rem[9], rem[10], rem[11]]);
}
let this_type = [rem[4], rem[5], rem[6], rem[7]];
if this_type == TYPE_MOOF {
saw_moof = true;
} else if this_type == TYPE_MOOV {
saw_moov = true;
}
boxes += 1;
if size_u32 == SIZE_TO_EOF || eff > rem.len() {
break;
}
offset += eff;
} else {
clean = false;
break;
}
}
if boxes == 0 {
return crate::ran_out_or_ruled_out(ran_out, ran_out_need);
}
let walked_whole_input = clean && offset >= region.len() && limit == data.len();
let detail = Isobmff {
major_brand: brand,
boxes_walked: boxes.min(u8::MAX as u64) as u8,
layout: if saw_moof {
IsobmffLayout::Fragmented
} else if saw_moov && walked_whole_input {
IsobmffLayout::Progressive
} else {
IsobmffLayout::Unknown
},
};
let confidence = if clean && boxes >= 2 {
Confidence::STRUCTURAL
} else if clean && boxes == 1 {
Confidence::HEURISTIC
} else {
return crate::ran_out_or_ruled_out(ran_out, ran_out_need);
};
Outcome::Match(Evidence { confidence, detail })
}
fn header_len_required(rem: &[u8]) -> usize {
if rem.len() >= 4 {
let size32 = u32::from_be_bytes([rem[0], rem[1], rem[2], rem[3]]);
if size32 == SIZE_INDICATES_LARGESIZE {
return BOX_HEADER_LARGESIZE_SIZE;
}
}
BOX_HEADER_MIN_SIZE
}
fn largesize_to_len(ls: u64, pointer_bits: u32) -> Option<usize> {
if pointer_bits < u64::BITS && ls >= (1u64 << pointer_bits) {
return None;
}
usize::try_from(ls).ok()
}
fn decode_header(rem: &[u8]) -> Option<(u32, usize)> {
if rem.len() < BOX_HEADER_MIN_SIZE {
return None;
}
let size32 = u32::from_be_bytes([rem[0], rem[1], rem[2], rem[3]]);
if size32 == SIZE_INDICATES_LARGESIZE {
if rem.len() < BOX_HEADER_LARGESIZE_SIZE {
return None;
}
let ls = u64::from_be_bytes([
rem[8], rem[9], rem[10], rem[11], rem[12], rem[13], rem[14], rem[15],
]);
Some((size32, largesize_to_len(ls, usize::BITS)?))
} else if size32 == SIZE_TO_EOF {
Some((size32, 0))
} else {
Some((size32, size32 as usize))
}
}
#[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 short_prefix_is_insufficient() {
let data = fixture_bytes("fixtures/mp4/h264_high.mp4");
let region = &data[..BOX_HEADER_MIN_SIZE - 1];
match probe(region, region.len()) {
Outcome::Insufficient(need) => assert_eq!(need, BOX_HEADER_MIN_SIZE),
other => panic!("7-byte ISOBMFF prefix must be Insufficient(8), got {other:?}"),
}
}
#[test]
fn the_verdict_path_need_does_not_track_the_buffer_length() {
let mut seen = std::vec::Vec::new();
for len in 9..BOX_HEADER_MIN_SIZE * 2 {
let mut buf = std::vec![0u8; len];
buf[3] = BOX_HEADER_MIN_SIZE as u8; buf[4..8].copy_from_slice(&TYPE_FTYP);
if let Outcome::Insufficient(need) = probe(&buf, buf.len()) {
seen.push(need);
}
}
assert!(
seen.len() >= 2,
"seed must reach Insufficient at 2+ lengths, got {seen:?}"
);
assert!(
seen.windows(2).all(|w| w[0] == w[1]),
"the verdict-path need must not grow with the buffer, got {seen:?}"
);
}
#[test]
fn the_ran_out_need_is_structural_not_length_relative() {
for len in BOX_HEADER_MIN_SIZE..BOX_HEADER_LARGESIZE_SIZE {
let mut buf = std::vec![0u8; len];
buf[3] = SIZE_INDICATES_LARGESIZE as u8;
buf[4..8].copy_from_slice(&TYPE_FTYP);
match probe(&buf, buf.len()) {
Outcome::Insufficient(need) => assert_eq!(
need, BOX_HEADER_LARGESIZE_SIZE,
"at {len} bytes the need must be the 16 the largesize header \
requires, not a figure that tracks the {len} supplied"
),
other => panic!(
"a cut largesize header at {len} bytes must be Insufficient, got {other:?}"
),
}
}
}
#[test]
fn a_largesize_beyond_the_target_pointer_width_is_rejected() {
const OVERSIZED: u64 = 0x1_0000_0008;
assert_eq!(
largesize_to_len(OVERSIZED, 32),
None,
"a largesize of 2^32+8 is unaddressable on a 32-bit target and must \
be rejected, never truncated to 8"
);
assert_eq!(
largesize_to_len(OVERSIZED, 64),
Some(OVERSIZED as usize),
"the same largesize fits a 64-bit usize and must be decoded exactly"
);
assert_eq!(
largesize_to_len(u32::MAX as u64, 32),
Some(u32::MAX as usize)
);
assert_eq!(largesize_to_len(1u64 << 32, 32), None);
assert_eq!(largesize_to_len(u64::MAX, 32), None);
}
#[test]
fn largesize_is_decoded_faithfully_through_decode_header() {
let mut header = [0u8; 16];
header[3] = SIZE_INDICATES_LARGESIZE as u8;
header[8..].copy_from_slice(&0x1_0000_0008u64.to_be_bytes());
assert_eq!(
decode_header(&header),
Some((SIZE_INDICATES_LARGESIZE, 0x1_0000_0008usize)),
"a largesize of 2^32+8 must reach the caller exactly, not clobbered"
);
}
#[test]
fn largesize_smaller_than_its_own_header_is_rejected() {
let data: [u8; 16] = [
0x00, 0x00, 0x00, 0x01, 0x66, 0x72, 0x65, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, ];
match probe(&data, data.len()) {
Outcome::None => {}
other => panic!(
"a largesize smaller than its own 16-byte header must be None, got {other:?}"
),
}
}
}