use alloc::vec::Vec;
use broadcast_common::Parse;
use crate::box_types::{BOX_HEADER_MIN_SIZE, BoxHeader, SIZE_TO_EOF};
use crate::error::{Error, Result};
use crate::movie_fragment::{MovieFragmentBox, TFHD_DEFAULT_BASE_IS_MOOF};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FragmentSampleRange {
pub start: usize,
pub end: usize,
pub duration: u32,
pub flags: u32,
pub composition_offset: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MdatRange {
start: usize,
end: usize,
}
#[cfg(test)]
thread_local! {
pub(crate) static MDAT_SCANS: core::cell::Cell<usize> = const { core::cell::Cell::new(0) };
}
pub fn mdat_ranges(file: &[u8]) -> Vec<MdatRange> {
#[cfg(test)]
MDAT_SCANS.with(|c| c.set(c.get() + 1));
let mut out = Vec::new();
let mut offset = 0usize;
while offset + BOX_HEADER_MIN_SIZE <= file.len() {
let rest = &file[offset..];
let Ok(header) = BoxHeader::parse(rest) else {
break;
};
let hdr_sz = header.header_size();
let total = if header.size == SIZE_TO_EOF as u64 {
rest.len()
} else {
match usize::try_from(header.size) {
Ok(t) if t >= hdr_sz => t,
Ok(_) => break,
Err(_) => rest.len(),
}
};
if hdr_sz > rest.len() {
break;
}
if header.box_type.is(b"mdat") {
out.push(MdatRange {
start: offset + hdr_sz,
end: offset + total.min(rest.len()),
});
}
offset += total.min(rest.len());
}
out
}
fn inside_mdat(mdats: &[MdatRange], start: usize, end: usize) -> bool {
mdats.iter().any(|m| start >= m.start && end <= m.end)
}
pub fn sample_ranges(
file: &[u8],
moof_off: usize,
moof: &MovieFragmentBox,
track_id: u32,
) -> Result<Vec<FragmentSampleRange>> {
sample_ranges_in(file, &mdat_ranges(file), moof_off, moof, track_id)
}
pub fn sample_ranges_in(
file: &[u8],
mdats: &[MdatRange],
moof_off: usize,
moof: &MovieFragmentBox,
track_id: u32,
) -> Result<Vec<FragmentSampleRange>> {
let mut out = Vec::new();
let mut data_end: i64 = moof_off as i64;
let mut total: usize = 0;
for traf in &moof.traf {
let tfhd = &traf.tfhd;
let explicit_base = match tfhd.base_data_offset {
Some(bdo) => Some(
i64::try_from(bdo)
.map_err(|_| Error::InvalidInput("tfhd.base_data_offset overflows i64"))?,
),
None if tfhd.flags & TFHD_DEFAULT_BASE_IS_MOOF != 0 => Some(moof_off as i64),
None => None,
};
let traf_base: i64 = explicit_base.unwrap_or(data_end);
let mut run_cursor = traf_base;
for trun in &traf.trun {
let run_base = match trun.data_offset {
Some(off) => traf_base
.checked_add(off as i64)
.ok_or(Error::InvalidInput("trun.data_offset overflow"))?,
None => run_cursor,
};
let mut cursor = run_base;
for (i, ts) in trun.samples.iter().enumerate() {
let size = ts
.sample_size
.or(tfhd.default_sample_size)
.ok_or(Error::InvalidInput(
"trun sample has no size (no trun.sample_size, no tfhd default_sample_size)",
))? as usize;
let duration = ts
.sample_duration
.or(tfhd.default_sample_duration)
.unwrap_or(0);
let flags = ts
.sample_flags
.or(if i == 0 {
trun.first_sample_flags
} else {
None
})
.or(tfhd.default_sample_flags)
.unwrap_or(0);
let composition_offset = ts.sample_composition_time_offset.unwrap_or(0);
let start = usize::try_from(cursor)
.map_err(|_| Error::InvalidInput("negative sample data offset"))?;
let end = start
.checked_add(size)
.ok_or(Error::InvalidInput("sample offset + size overflow"))?;
if tfhd.track_id == track_id {
total = total
.checked_add(size)
.ok_or(Error::InvalidInput("fragment sample bytes overflow"))?;
if total > file.len() {
return Err(Error::InvalidInput(
"fragment sample data exceeds the file length (overlapping runs?)",
));
}
if end > file.len() {
return Err(Error::BufferTooShort {
need: end,
have: file.len(),
what: "fragment sample data",
});
}
if !inside_mdat(mdats, start, end) {
return Err(Error::InvalidInput(
"fragment sample range is outside every mdat payload",
));
}
out.push(FragmentSampleRange {
start,
end,
duration,
flags,
composition_offset,
});
}
cursor = cursor
.checked_add(size as i64)
.ok_or(Error::InvalidInput("fragment data size overflow"))?;
}
run_cursor = cursor;
}
data_end = run_cursor.max(data_end);
}
Ok(out)
}
pub fn tfdt_as_i64(raw: u64) -> Result<i64> {
i64::try_from(raw).map_err(|_| {
Error::InvalidInput("tfdt base_media_decode_time overflows the signed decode-time range")
})
}
pub fn add_offset(dts: i64, offset: i64) -> Result<i64> {
dts.checked_add(offset)
.ok_or(Error::InvalidInput("sample pts overflow"))
}
pub fn add_duration(dts: i64, duration: u32) -> Result<i64> {
dts.checked_add(i64::from(duration))
.ok_or(Error::InvalidInput("sample dts overflow"))
}
#[cfg(test)]
mod tests {
use super::*;
fn box_bytes(ty: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(8 + payload.len());
out.extend_from_slice(&((8 + payload.len()) as u32).to_be_bytes());
out.extend_from_slice(ty);
out.extend_from_slice(payload);
out
}
fn container(ty: &[u8; 4], kids: &[Vec<u8>]) -> Vec<u8> {
let mut p = Vec::new();
for k in kids {
p.extend_from_slice(k);
}
box_bytes(ty, &p)
}
fn tfhd(flags: u32, track_id: u32, base: Option<u64>) -> Vec<u8> {
let mut p = Vec::new();
p.extend_from_slice(&flags.to_be_bytes());
p.extend_from_slice(&track_id.to_be_bytes());
if let Some(b) = base {
p.extend_from_slice(&b.to_be_bytes());
}
box_bytes(b"tfhd", &p)
}
const TRUN_DATA_OFFSET: u32 = 0x000001;
const TRUN_SAMPLE_SIZE: u32 = 0x000200;
fn trun(sizes: &[u32], data_offset: Option<i32>) -> Vec<u8> {
let mut flags = TRUN_SAMPLE_SIZE;
if data_offset.is_some() {
flags |= TRUN_DATA_OFFSET;
}
let mut p = Vec::new();
p.extend_from_slice(&flags.to_be_bytes());
p.extend_from_slice(&(sizes.len() as u32).to_be_bytes());
if let Some(o) = data_offset {
p.extend_from_slice(&o.to_be_bytes());
}
for s in sizes {
p.extend_from_slice(&s.to_be_bytes());
}
box_bytes(b"trun", &p)
}
fn mfhd() -> Vec<u8> {
let mut p = alloc::vec![0u8; 4];
p.extend_from_slice(&1u32.to_be_bytes());
box_bytes(b"mfhd", &p)
}
fn file_with(trafs: &[Vec<u8>], mdat_body: &[u8]) -> (Vec<u8>, usize) {
let mut kids = alloc::vec![mfhd()];
kids.extend_from_slice(trafs);
let moof = container(b"moof", &kids);
let mdat = box_bytes(b"mdat", mdat_body);
let mut file = box_bytes(b"free", &alloc::vec![0u8; 16]);
let moof_off = file.len();
file.extend_from_slice(&moof);
file.extend_from_slice(&mdat);
(file, moof_off)
}
fn parse_moof(file: &[u8], moof_off: usize) -> MovieFragmentBox {
let sz = u32::from_be_bytes([
file[moof_off],
file[moof_off + 1],
file[moof_off + 2],
file[moof_off + 3],
]) as usize;
MovieFragmentBox::parse_body(&file[moof_off + 8..moof_off + sz]).expect("moof")
}
fn mdat_rel(file: &[u8], moof_off: usize, mdat_len: usize) -> i32 {
let mdat_payload = file.len() - mdat_len;
(mdat_payload - moof_off) as i32
}
#[test]
fn second_run_data_offset_is_relative_to_traf_base() {
let mdat_body: Vec<u8> = (0u8..8).collect();
let probe = container(
b"traf",
&[
tfhd(TFHD_DEFAULT_BASE_IS_MOOF, 1, None),
trun(&[2, 2], Some(0)),
trun(&[2, 2], Some(0)),
],
);
let (pf, moof_off) = file_with(&[probe], &mdat_body);
let off = mdat_rel(&pf, moof_off, mdat_body.len());
let mdat_payload = pf.len() - mdat_body.len();
let traf = container(
b"traf",
&[
tfhd(TFHD_DEFAULT_BASE_IS_MOOF, 1, None),
trun(&[2, 2], Some(off)),
trun(&[2, 2], Some(off)),
],
);
let (file, moof_off) = file_with(&[traf], &mdat_body);
let moof = parse_moof(&file, moof_off);
let r = sample_ranges(&file, moof_off, &moof, 1).expect("ranges");
assert_eq!(r.len(), 4);
assert_eq!(r[0].start, mdat_payload);
assert_eq!(
r[2].start, mdat_payload,
"run 2 restarts at base + its offset"
);
assert_ne!(
r[2].start, r[1].end,
"a data_offset'd run must not continue after the previous run"
);
}
#[test]
fn implicit_run_continues_after_previous() {
let mdat_body: Vec<u8> = (0u8..8).collect();
let probe = container(
b"traf",
&[
tfhd(TFHD_DEFAULT_BASE_IS_MOOF, 1, None),
trun(&[2, 2], Some(0)),
trun(&[2, 2], None),
],
);
let (pf, moof_off) = file_with(&[probe], &mdat_body);
let off = mdat_rel(&pf, moof_off, mdat_body.len());
let traf = container(
b"traf",
&[
tfhd(TFHD_DEFAULT_BASE_IS_MOOF, 1, None),
trun(&[2, 2], Some(off)),
trun(&[2, 2], None),
],
);
let (file, moof_off) = file_with(&[traf], &mdat_body);
let moof = parse_moof(&file, moof_off);
let r = sample_ranges(&file, moof_off, &moof, 1).expect("ranges");
assert_eq!(r[2].start, r[1].end);
}
#[test]
fn run_pointing_into_moof_is_rejected() {
let mdat_body: Vec<u8> = (0u8..8).collect();
let traf = container(
b"traf",
&[
tfhd(TFHD_DEFAULT_BASE_IS_MOOF, 1, None),
trun(&[2], Some(0)),
],
);
let (file, moof_off) = file_with(&[traf], &mdat_body);
let moof = parse_moof(&file, moof_off);
assert!(sample_ranges(&file, moof_off, &moof, 1).is_err());
}
#[test]
fn many_overlapping_runs_are_capped() {
let mdat_body: Vec<u8> = alloc::vec![0xAA; 8];
let mut kids = alloc::vec![tfhd(TFHD_DEFAULT_BASE_IS_MOOF, 1, None)];
for _ in 0..10_000 {
kids.push(trun(&[8], Some(40)));
}
let traf = container(b"traf", &kids);
let (file, moof_off) = file_with(&[traf], &mdat_body);
let moof = parse_moof(&file, moof_off);
let err = sample_ranges(&file, moof_off, &moof, 1).unwrap_err();
assert!(
matches!(err, Error::InvalidInput(_)),
"10k overlapping runs must be capped, got {err:?}"
);
}
#[test]
fn hostile_tfdt_is_rejected() {
assert!(tfdt_as_i64(u64::MAX).is_err());
assert_eq!(tfdt_as_i64(5).unwrap(), 5);
}
#[test]
fn dts_arithmetic_is_checked() {
assert!(add_duration(i64::MAX, 1).is_err());
assert!(add_offset(i64::MAX, 1).is_err());
assert!(add_offset(i64::MIN, -1).is_err());
assert_eq!(add_duration(10, 3).unwrap(), 13);
assert_eq!(add_offset(10, -3).unwrap(), 7);
}
fn scans() -> usize {
MDAT_SCANS.with(|c| c.get())
}
#[test]
fn truncated_final_mdat_is_clamped_not_dropped() {
let mdat_body: Vec<u8> = (0u8..8).collect();
let probe = container(
b"traf",
&[
tfhd(TFHD_DEFAULT_BASE_IS_MOOF, 1, None),
trun(&[2, 2], Some(0)),
],
);
let (pf, moof_off) = file_with(&[probe], &mdat_body);
let off = mdat_rel(&pf, moof_off, mdat_body.len());
let traf = container(
b"traf",
&[
tfhd(TFHD_DEFAULT_BASE_IS_MOOF, 1, None),
trun(&[2, 2], Some(off)),
],
);
let (full, moof_off) = file_with(&[traf], &mdat_body);
let moof = parse_moof(&full, moof_off);
let payload = full.len() - mdat_body.len();
let cut = &full[..payload + 5];
let ranges = mdat_ranges(cut);
assert_eq!(
ranges,
alloc::vec![MdatRange {
start: payload,
end: cut.len()
}]
);
let r = sample_ranges(cut, moof_off, &moof, 1).expect("early samples resolve");
assert_eq!((r[0].start, r[0].end), (payload, payload + 2));
assert_eq!((r[1].start, r[1].end), (payload + 2, payload + 4));
let traf = container(
b"traf",
&[
tfhd(TFHD_DEFAULT_BASE_IS_MOOF, 1, None),
trun(&[2, 2, 2], Some(off)),
],
);
let (full3, moof_off3) = file_with(&[traf], &mdat_body);
let moof3 = parse_moof(&full3, moof_off3);
assert!(sample_ranges(&full3[..payload + 5], moof_off3, &moof3, 1).is_err());
}
#[test]
fn demux_scans_mdats_once_per_file() {
use broadcast_common::Unpackage;
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/cenc_frag_layouts/clear_multitrun_omit_implicit.mp4"
);
let bytes = std::fs::read(path).expect("fixture");
let before = scans();
let media = crate::media::Fmp4Demux::new()
.unpackage(bytes.as_slice())
.expect("demux");
assert!(media.tracks.iter().map(|t| t.samples.len()).sum::<usize>() > 1);
assert_eq!(scans() - before, 1, "one mdat scan per file");
}
}