Fix read-fault misclassification, DTS AMODE channel table, and untestable guards
- resolve_fmts_key_map: distinguish a genuinely-not-FMTS disc from a
transient live-drive read fault. read_filesystem now returns the new
Error::UdfNotFilesystem for a deterministic tag/format mismatch (no AVDP,
no partition descriptor, no FSD); resolve maps only UdfNotFilesystem (fs)
and UdfNotFound (.tbl absent) to Ok(None), and PROPAGATES DiscRead / other
I/O faults so a marginal AACS 2.1 disc fails loud instead of silently
dropping forensic content under a base-Unit-Key-only map.
- DTS_AMODE_CH (mp4/audio.rs): extend 10→16 entries
{1,2,2,2,2,3,3,4,4,5,6,6,6,7,8,8} (the spec per-AMODE channel table / ETSI TS 102 114) so
the spec-legal high AMODEs that now pass the decodability gate declare
their true channelcount (AMODE 13→7, 14/15→8) instead of a truncated 6.
- session.rs resolve_keys "called before scan" guard is now testable:
from_parts_for_test takes Option<Disc>; added a test that a disc-less
session returns a clean DeviceNotReady Err rather than panicking.
- mp4/read.rs: a track with samples but a missing/malformed stts (mandatory
per ISO/IEC 14496-12) is dropped rather than emitting all-zero timestamps,
matching the existing stco/stsc guards; all-tracks-dropped → Mp4Invalid.
- Remove the inert MuxInput::Iso.key_map field (the Iso path re-derives its
map inside build_iso_pipeline); the live path keeps Live.key_map.
All four fixes are mutation-verified.
This commit is contained in:
@@ -201,6 +201,15 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
||||
let durations = find_box(stbl, b"stts")
|
||||
.map(|b| parse_stts(b, n))
|
||||
.unwrap_or_default();
|
||||
if durations.is_empty() {
|
||||
// Samples exist but there is no decoding-time table: `stts` is
|
||||
// mandatory in a valid stbl (ISO/IEC 14496-12 §8.6.1). Without it
|
||||
// every sample would take dur=0 → all-zero, identical timestamps,
|
||||
// collapsing the whole track onto one instant. Drop the track
|
||||
// rather than emit degenerate timing (mirrors the stco/stsc
|
||||
// guards above); an all-tracks-dropped file fails Mp4Invalid below.
|
||||
continue;
|
||||
}
|
||||
let ctts = find_box(stbl, b"ctts")
|
||||
.map(|b| parse_ctts(b, n))
|
||||
.unwrap_or_default();
|
||||
@@ -1216,11 +1225,21 @@ mod tests {
|
||||
p.extend_from_slice(&0u32.to_be_bytes()); // sample_desc_idx
|
||||
mp4_box(b"stsc", &p)
|
||||
};
|
||||
let stts = {
|
||||
// Mandatory time-to-sample box: 1 entry → 1 sample × 1000 ticks.
|
||||
let mut p = Vec::new();
|
||||
p.extend_from_slice(&[0, 0, 0, 0]); // version+flags
|
||||
p.extend_from_slice(&1u32.to_be_bytes()); // entry_count
|
||||
p.extend_from_slice(&1u32.to_be_bytes()); // sample_count
|
||||
p.extend_from_slice(&1000u32.to_be_bytes()); // sample_delta
|
||||
mp4_box(b"stts", &p)
|
||||
};
|
||||
let mut stbl = Vec::new();
|
||||
stbl.extend_from_slice(&stsd);
|
||||
stbl.extend_from_slice(&stsz);
|
||||
stbl.extend_from_slice(&stco);
|
||||
stbl.extend_from_slice(&stsc);
|
||||
stbl.extend_from_slice(&stts);
|
||||
let minf = mp4_box(b"minf", &mp4_box(b"stbl", &stbl));
|
||||
let mut mdia = Vec::new();
|
||||
mdia.extend_from_slice(&mdhd);
|
||||
@@ -1319,11 +1338,20 @@ mod tests {
|
||||
p.extend_from_slice(&0u32.to_be_bytes()); // sample_desc_idx
|
||||
mp4_box(b"stsc", &p)
|
||||
};
|
||||
let stts = {
|
||||
let mut p = Vec::new();
|
||||
p.extend_from_slice(&[0, 0, 0, 0]); // version+flags
|
||||
p.extend_from_slice(&1u32.to_be_bytes()); // entry_count
|
||||
p.extend_from_slice(&1u32.to_be_bytes()); // sample_count
|
||||
p.extend_from_slice(&1000u32.to_be_bytes()); // sample_delta
|
||||
mp4_box(b"stts", &p)
|
||||
};
|
||||
let mut stbl = Vec::new();
|
||||
stbl.extend_from_slice(&stsd);
|
||||
stbl.extend_from_slice(&stsz);
|
||||
stbl.extend_from_slice(&stco);
|
||||
stbl.extend_from_slice(&stsc);
|
||||
stbl.extend_from_slice(&stts);
|
||||
let minf = mp4_box(b"minf", &mp4_box(b"stbl", &stbl));
|
||||
let mut mdia = Vec::new();
|
||||
mdia.extend_from_slice(&mdhd);
|
||||
@@ -1397,6 +1425,15 @@ mod tests {
|
||||
p.extend_from_slice(&0u32.to_be_bytes()); // sample_desc_idx
|
||||
mp4_box(b"stsc", &p)
|
||||
};
|
||||
let stts = {
|
||||
// Mandatory time-to-sample box: 3 entries' worth via one run (3×1000).
|
||||
let mut p = Vec::new();
|
||||
p.extend_from_slice(&[0, 0, 0, 0]); // version+flags
|
||||
p.extend_from_slice(&1u32.to_be_bytes()); // entry_count
|
||||
p.extend_from_slice(&3u32.to_be_bytes()); // sample_count
|
||||
p.extend_from_slice(&1000u32.to_be_bytes()); // sample_delta
|
||||
mp4_box(b"stts", &p)
|
||||
};
|
||||
let mut stbl = Vec::new();
|
||||
stbl.extend_from_slice(&stsd);
|
||||
stbl.extend_from_slice(&stsz);
|
||||
@@ -1406,6 +1443,9 @@ mod tests {
|
||||
if omit != b"stsc" {
|
||||
stbl.extend_from_slice(&stsc);
|
||||
}
|
||||
if omit != b"stts" {
|
||||
stbl.extend_from_slice(&stts);
|
||||
}
|
||||
let minf = mp4_box(b"minf", &mp4_box(b"stbl", &stbl));
|
||||
let mut mdia = Vec::new();
|
||||
mdia.extend_from_slice(&mdhd);
|
||||
@@ -1446,6 +1486,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// A track with samples (`stsz`) but no time-to-sample table (`stts`, mandatory
|
||||
/// per ISO/IEC 14496-12 §8.6.1) must be DROPPED — without it every sample takes
|
||||
/// dur=0, collapsing the whole track onto one instant (all-zero timestamps).
|
||||
/// With it the only track, the file fails `Mp4Invalid`.
|
||||
/// Mutation check: delete the `if durations.is_empty() { continue; }` guard and
|
||||
/// `from_reader` returns `Ok` (all-zero-timestamp samples), flipping this to FAIL.
|
||||
#[test]
|
||||
fn missing_stts_drops_track_all_dropped_is_invalid() {
|
||||
use std::io::Cursor;
|
||||
let moov = mp4_box(b"moov", &audio_trak_missing(b"stts"));
|
||||
let rd = Mp4Reader::from_reader(Cursor::new(moov), "no-stts".into());
|
||||
assert!(
|
||||
rd.is_err(),
|
||||
"a track with stsz but no stts must be dropped; all-dropped → Mp4Invalid"
|
||||
);
|
||||
}
|
||||
|
||||
/// Sanity companion: the SAME builder WITH both tables present yields a valid,
|
||||
/// indexed single-track file — proving the two Err results above come from the
|
||||
/// missing table, not from some unrelated defect in the fixture builder.
|
||||
|
||||
Reference in New Issue
Block a user