Files
filmcraft/crates/vp8/tests/robustness.rs
MrCloudy2andClaude Opus 5.5 53a0d959fe #432: VP8 video decodes (clean-room pure-Rust VP8 decoder)
VP8 WebM showed black video: there was no VP8 decoder. New L0 crate
filmcraft-vp8, implemented from RFC 6386 sections 1-19 (tables extracted
from the RFC text, nothing from the attached reference decoder): every
coding tool, versions 0-3, hidden altref frames, size changes. Bit-exact
with libvpx on 62 libvpx VP8 test vectors, real-world FATE samples and
generated fixtures; never panics on corrupted input.

Wired into codecs for Matroska / WebM (V_VP8, including the alpha layer
of transparent WebM) and MP4 (vp08). VP8 colour is BT.601 (RFC 6386 9.2).
A hidden altref frame is a WebM block of its own with the time of the
frame that follows it: the VP8 time lookup now moves on to that shown
frame instead of repeating the previous one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:13:07 +02:00

99 lines
3.6 KiB
Rust

//! Damaged, truncated, reordered and random input must never panic (errors are fine).
mod common;
use filmcraft_vp8::{Decoder, Error};
fn xorshift(seed: &mut u64) -> u64 {
*seed ^= *seed << 13;
*seed ^= *seed >> 7;
*seed ^= *seed << 17;
*seed
}
fn decode_all(frames: &[Vec<u8>]) {
let mut dec = Decoder::new();
for (i, f) in frames.iter().enumerate() {
let _ = dec.is_disposable(f);
let _ = dec.decode(f, i as i64);
let _ = filmcraft_vp8::keyframe_info(f);
}
}
#[test]
fn corrupted_streams_do_not_panic() {
for name in ["default", "altref_2pass", "partitions8", "realtime_segments", "version3_fullpixel", "odd_size"] {
let f = common::fixture(name);
let Some((ivf, _)) = common::ensure(f) else { return };
let data = std::fs::read(ivf).unwrap();
let frames: Vec<Vec<u8>> = common::ivf_frames(&data).into_iter().map(|f| f.to_vec()).collect();
let mut seed = 0x9e37_79b9_7f4a_7c15u64 ^ name.len() as u64;
for round in 0..12 {
let mut d = frames.clone();
for fr in d.iter_mut() {
if fr.is_empty() {
continue;
}
match round % 4 {
0 => {
for _ in 0..1 + round {
let i = (xorshift(&mut seed) as usize) % fr.len();
fr[i] ^= 1 << (xorshift(&mut seed) % 8);
}
}
1 => {
let n = (xorshift(&mut seed) as usize) % (fr.len() + 1);
fr.truncate(n);
}
2 => {
let i = (xorshift(&mut seed) as usize) % fr.len().min(24);
fr[i] = xorshift(&mut seed) as u8;
}
// frames left intact; their order is shuffled below
_ => {}
}
}
if round % 4 == 3 {
for i in (1..d.len()).rev() {
let j = (xorshift(&mut seed) as usize) % (i + 1);
d.swap(i, j);
}
}
decode_all(&d);
}
}
}
#[test]
fn random_bytes_and_tiny_frames_do_not_panic() {
let mut seed = 42u64;
let mut dec = Decoder::new();
for len in 0..200 {
let f: Vec<u8> = (0..len).map(|_| xorshift(&mut seed) as u8).collect();
let _ = dec.decode(&f, len as i64);
}
// a key frame header claiming a large size followed by garbage
let mut key = vec![0u8; 64];
key[0] = 0x10; // key frame, version 0, shown, first partition 0 bytes
key[3..6].copy_from_slice(&[0x9d, 0x01, 0x2a]);
key[6..8].copy_from_slice(&1920u16.to_le_bytes());
key[8..10].copy_from_slice(&1080u16.to_le_bytes());
let _ = dec.decode(&key, 0);
}
#[test]
fn inter_frame_without_key_frame_is_an_error() {
let f = common::fixture("default");
let Some((ivf, _)) = common::ensure(f) else { return };
let data = std::fs::read(ivf).unwrap();
let frames = common::ivf_frames(&data);
let mut dec = Decoder::new();
assert!(matches!(dec.decode(frames[1], 1), Err(Error::MissingReference(_))));
// and decoding works from the next key frame on; after a reset again only from a key frame
assert!(dec.decode(frames[0], 0).unwrap().is_some());
assert!(dec.decode(frames[1], 1).unwrap().is_some());
dec.reset();
assert!(matches!(dec.decode(frames[2], 2), Err(Error::MissingReference(_))));
assert!(filmcraft_vp8::is_keyframe(frames[0]) && !filmcraft_vp8::is_keyframe(frames[1]));
}