Files
deckcraft/crates/ogg/src/tests.rs
T
Nikita 6873a25635 Codecs: a hostile number is a format error, not an overflow
H.264 and HEVC quantiser offsets, Matroska block times, Opus granule timing and QuickTime PCM frame sizes are range-checked before the arithmetic.
2026-10-13 20:16:51 +03:00

199 lines
7.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Synthetic Ogg streams: a small page writer (RFC 3533) and Opus-like packets.
use super::*;
/// Write one page holding `segments` (lacing already computed by `lace`).
fn page(out: &mut Vec<u8>, flags: u8, granule: i64, serial: u32, seq: u32, lacing: &[u8], body: &[u8]) {
let start = out.len();
out.extend_from_slice(b"OggS");
out.push(0);
out.push(flags);
out.extend_from_slice(&granule.to_le_bytes());
out.extend_from_slice(&serial.to_le_bytes());
out.extend_from_slice(&seq.to_le_bytes());
out.extend_from_slice(&[0; 4]);
out.push(lacing.len() as u8);
out.extend_from_slice(lacing);
out.extend_from_slice(body);
let crc = crc32(&out[start..]);
out[start + 22..start + 26].copy_from_slice(&crc.to_le_bytes());
}
fn lace(packets: &[&[u8]], last_continues: bool) -> (Vec<u8>, Vec<u8>) {
let mut l = Vec::new();
let mut b = Vec::new();
for (i, p) in packets.iter().enumerate() {
let mut n = p.len();
while n >= 255 {
l.push(255);
n -= 255;
}
if !(last_continues && i + 1 == packets.len()) {
l.push(n as u8);
}
b.extend_from_slice(p);
}
(l, b)
}
fn opus_head(pre_skip: u16) -> Vec<u8> {
let mut h = b"OpusHead".to_vec();
h.push(1);
h.push(2);
h.extend_from_slice(&pre_skip.to_le_bytes());
h.extend_from_slice(&48_000u32.to_le_bytes());
h.extend_from_slice(&0i16.to_le_bytes());
h.push(0);
h
}
/// A CELT 20 ms packet (960 samples): TOC config 31, code 0.
fn pkt(i: usize, len: usize) -> Vec<u8> {
let mut p = vec![0xF8];
p.extend((0..len.saturating_sub(1)).map(|k| (i * 7 + k) as u8));
p
}
/// Header pages, then pages of 3 packets each; the last page's granule trims `trim` samples.
fn opus_file(npk: usize, pre_skip: u16, trim: i64) -> Vec<u8> {
let mut f = Vec::new();
let head = opus_head(pre_skip);
let (l, b) = lace(&[&head], false);
page(&mut f, 0x02, 0, 9, 0, &l, &b);
let tags = b"OpusTags\x00\x00\x00\x00\x00\x00\x00\x00".to_vec();
let (l, b) = lace(&[&tags], false);
page(&mut f, 0, 0, 9, 1, &l, &b);
let pkts: Vec<Vec<u8>> = (0..npk).map(|i| pkt(i, 40 + i % 5)).collect();
let mut done = 0;
for (seq, chunk) in (2..).zip(pkts.chunks(3)) {
done += chunk.len();
let last = done == npk;
let mut g = done as i64 * 960;
if last {
g -= trim;
}
let refs: Vec<&[u8]> = chunk.iter().map(Vec::as_slice).collect();
let (l, b) = lace(&refs, false);
page(&mut f, if last { 0x04 } else { 0 }, g, 9, seq, &l, &b);
}
f
}
#[test]
fn crc_matches_known_value() {
// CRC-32/CKSUM parameters without its final XOR (check value 0x765E7680 ^ 0xFFFFFFFF)
assert_eq!(crc32(b"123456789"), 0x89A1_897F);
assert_eq!(crc32(b""), 0);
assert_ne!(crc32(b"OggS"), crc32(b"OggT"));
}
#[test]
fn opus_pages_packets_and_timing() {
let f = opus_file(10, 312, 500);
assert!(sniff(&f));
let o = open(&f).unwrap();
assert!(o.warnings.is_empty(), "{:?}", o.warnings);
let s = o.stream_of(Codec::Opus).unwrap();
let st = &o.streams[s];
assert_eq!(st.headers.len(), 2);
assert_eq!(st.packets.len(), 10);
assert!(st.saw_eos);
assert_eq!(o.read_packet(&f, s, 4).unwrap(), pkt(4, 44));
let t = OpusTiming::of(st, 312).unwrap();
assert_eq!(t.durations, vec![960; 10]);
assert_eq!(t.starts[0], -312);
assert_eq!(t.starts[9], 9 * 960 - 312);
// end trimming: the last page granule is 500 short
assert_eq!(t.total, 10 * 960 - 500 - 312);
}
#[test]
fn packets_spanning_pages() {
let mut f = Vec::new();
let head = opus_head(0);
let (l, b) = lace(&[&head], false);
page(&mut f, 0x02, 0, 1, 0, &l, &b);
let (l, b) = lace(&[b"OpusTags"], false);
page(&mut f, 0, 0, 1, 1, &l, &b);
let big = pkt(0, 700);
// first 510 bytes on one page (two 255 lacing values, packet continues), rest on the next
let (l, b) = (vec![255, 255], big[..510].to_vec());
page(&mut f, 0, -1, 1, 2, &l, &b);
let rest = &big[510..];
let small = pkt(1, 30);
let (l, b) = lace(&[rest, &small], false);
page(&mut f, 0x01 | 0x04, 1920, 1, 3, &l, &b);
let o = open(&f).unwrap();
let st = &o.streams[0];
assert_eq!(st.packets.len(), 2);
assert_eq!(st.packets[0].parts.len(), 2);
assert_eq!(o.read_packet(&f, 0, 0).unwrap(), big);
assert_eq!(o.read_packet(&f, 0, 1).unwrap(), small);
let t = OpusTiming::of(st, 0).unwrap();
assert_eq!(t.starts, vec![0, 960]);
assert_eq!(t.total, 1920);
}
#[test]
fn corrupt_pages_are_skipped_and_truncation_is_safe() {
let f = opus_file(12, 312, 0);
// flip a byte inside the third data page's body: that page fails its CRC and is dropped
let mut g = f.clone();
let third = g.windows(4).enumerate().filter(|(_, w)| *w == b"OggS").map(|(i, _)| i).nth(4).unwrap();
g[third + 40] ^= 0xFF;
let o = open(&g).unwrap();
assert!(o.warnings.iter().any(|w| w.contains("CRC")), "{:?}", o.warnings);
assert_eq!(o.streams[0].packets.len(), 9);
let t = OpusTiming::of(&o.streams[0], 312).unwrap();
// the granules after the gap put the later packets at their true positions
assert_eq!(*t.starts.last().unwrap(), 11 * 960 - 312);
for cut in 0..f.len() {
if let Ok(o) = open(&f[..cut].to_vec()) {
for st in &o.streams {
let t = OpusTiming::of(st, 312).unwrap();
assert_eq!(t.starts.len(), st.packets.len());
}
}
}
let mut seed = 99u64;
for _ in 0..300 {
let mut g = f.clone();
seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
let at = (seed >> 33) as usize % g.len();
g[at] = (seed >> 20) as u8;
if let Ok(o) = open(&g) {
for st in &o.streams {
let _ = OpusTiming::of(st, 312);
}
}
}
assert!(matches!(open(&b"not ogg at all, no capture pattern".to_vec()), Err(Error::NotOgg)));
}
#[test]
fn opus_toc_durations() {
assert_eq!(opus_packet_samples([0x00, 0], 10), Some(480)); // SILK NB 10 ms
assert_eq!(opus_packet_samples([0x18, 0], 10), Some(2880)); // SILK NB 60 ms
assert_eq!(opus_packet_samples([0x78 | 1, 0], 10), Some(1920)); // hybrid 20 ms × 2
assert_eq!(opus_packet_samples([0x80 | 3, 6], 10), Some(720)); // CELT 2.5 ms × 6
assert_eq!(opus_packet_samples([0xF8 | 3, 7], 10), None); // 140 ms: invalid
assert_eq!(opus_packet_samples([0xF8, 0], 0), None);
}
#[test]
fn opus_timing_rejects_a_granule_that_overflows() {
let packet = |granule: i64| Stream {
serial: 1,
codec: Codec::Opus,
headers: vec![],
packets: vec![Packet { parts: vec![], size: 10, granule: Some(granule), page: 0, eos: true, prefix: [0x00, 0] }],
pages: 1,
last_granule: Some(granule),
saw_eos: true,
};
let ok = OpusTiming::of(&packet(480), 0).unwrap();
assert_eq!(ok.starts, vec![0]);
assert_eq!(ok.total, 480);
assert!(OpusTiming::of(&packet(i64::MIN), 0).is_err());
}