craft-media: DeckCraft's two Ogg fixes (#810)

Packet byte ranges are checked against the source before a buffer is
sized for them (deckcraft 51d19baa), and packets after the last known
granule count towards the Opus duration (deckcraft 9d36b593). Each test
failed before its fix.
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-11 12:48:03 -05:00
1 parent ac5f998274
commit 27f2204603
2 files changed
+38

No files matched your search

+7
View File
@@ -708,6 +708,11 @@ fn find_capture(src: &(impl ByteSource + ?Sized), from: u64, len: u64, recovery:
/// The first `max` bytes of a packet's parts.
fn read_parts(src: &(impl ByteSource + ?Sized), parts: &[(u64, u32)], max: usize) -> Result<Vec<u8>> {
// Ranges come from a scan of some source; check them against this one before sizing a buffer.
let len = src.len();
if parts.iter().any(|&(o, n)| o.checked_add(u64::from(n)).is_none_or(|end| end > len)) {
return Err(Error::Invalid("packet range lies outside the source".into()));
}
let mut v = Vec::with_capacity(parts.iter().map(|p| p.1 as usize).sum::<usize>().min(max));
for &(o, n) in parts {
let a = v.len();
@@ -797,6 +802,8 @@ impl OpusTiming {
c = c.saturating_add(i64::from(durations[k]));
ends[k] = Some(c);
}
// the total counts these packets too
cursor = Some(c);
break;
}
let Some(g) = stream.packets[j].granule else { break };
+31
View File
@@ -696,3 +696,34 @@ fn crc_update_continues_across_any_split() {
assert_eq!(crc32_update(crc32_bitwise(0, a), b), whole, "cut {cut} (reference head)");
}
}
#[test]
fn read_packet_checks_its_ranges_against_the_source_before_allocating() {
let f = opus_file(10, 312, 500);
let o = open(&f[..]).unwrap();
let s = o.stream_of(Codec::Opus).unwrap();
// The last packet lies past the end of a source cut short: an error, not a read of missing bytes.
let short = &f[..f.len() / 2];
assert!(matches!(o.read_packet(short, s, 9), Err(Error::Invalid(_))));
}
#[test]
fn opus_timing_includes_packets_after_the_last_known_granule() {
let f = opus_file(6, 312, 0);
let o = open(&f[..]).unwrap();
let mut stream = o.streams[0].clone();
// Missing granules on complete-packet pages are non-conforming. The recovery fallback still
// assigns their packet positions; the total must include the same samples.
stream.packets.last_mut().unwrap().granule = None;
let timing = OpusTiming::of(&stream, 312);
assert_eq!(timing.starts, vec![-312, 648, 1608, 2568, 3528, 4488]);
assert_eq!(timing.total, 6 * 960 - 312);
// With no usable granule, every complete packet still contributes its TOC duration.
for packet in &mut stream.packets {
packet.granule = None;
}
let timing = OpusTiming::of(&stream, 312);
assert_eq!(timing.starts, vec![-312, 648, 1608, 2568, 3528, 4488]);
assert_eq!(timing.total, 6 * 960 - 312);
}