//! What the fuzz targets and the seed writer share: the integer-payload entries, read //! from the committed corpus, and the media decoders and extension containers by //! selector byte. use std::collections::BTreeMap; use std::path::PathBuf; /// The `wai/` directory. pub fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..") } /// One capability with one parameter set, as `int-payload-conformance/vectors.json` /// pairs them, and the payloads committed for that pair. pub struct IntEntry { pub cap: String, pub set: BTreeMap>, pub payloads: Vec>, } /// The distinct (capability, parameter set) pairs of the committed corpus, in the /// order the index first names them. A fuzz input's first byte picks one. pub fn int_entries() -> Vec { let dir = wai_dir(); let read = |rel: &str| std::fs::read(dir.join(rel)).unwrap_or_else(|e| panic!("read {rel}: {e}")); let idx: serde_json::Value = serde_json::from_slice(&read("int-payload-conformance/vectors.json")).expect("the index parses"); let mut out: Vec = Vec::new(); let mut keys: Vec<(String, Vec<(String, String)>)> = Vec::new(); for list in ["vectors", "refusals"] { for e in idx[list].as_array().into_iter().flatten() { let cap = e["capability"].as_str().expect("a capability").to_string(); let set: Vec<(String, String)> = e["set"] .as_object() .into_iter() .flatten() .map(|(k, v)| (k.clone(), v.as_str().expect("a path").to_string())) .collect(); let key = (cap.clone(), set.clone()); let i = match keys.iter().position(|k| *k == key) { Some(i) => i, None => { keys.push(key); out.push(IntEntry { cap, set: set.iter().map(|(k, p)| (k.clone(), read(p))).collect(), payloads: Vec::new(), }); out.len() - 1 } }; if let Some(p) = e["payload"].as_str() { out[i].payloads.push(read(p)); } } } assert!(!out.is_empty() && out.len() <= 256, "{} entries: one selector byte must address them", out.len()); out } /// The media decoders, in selector order. pub const MEDIA: [&str; 9] = ["png", "jpeg", "avif", "jxl", "flac", "opus", "av1", "zstd", "xz"]; /// Decodes `p` with the decoder `pick` selects, discarding the result: every input must /// come back as a value or a refusal, never as a crash, a hang or unbounded memory. pub fn media_decode(pick: u8, p: &[u8]) { use wai::codecs::{audio, image, text, video}; match pick as usize % MEDIA.len() { 0 => drop(image::png_decode(p)), 1 => drop(image::jpeg_decode(p)), 2 => drop(image::avif_decode(p)), 3 => drop(image::jxl_decode(p)), 4 => drop(audio::flac_decode(p)), 5 => drop(audio::opus_decode(p)), // One frame at a time: the whole-output form would only add an allocation. 6 => drop(video::av1_decode_each(p, |_, _, _| Ok(()))), 7 => drop(text::zstd_decode(p)), _ => drop(text::xz_decode(p)), } } /// The extension containers, in selector order, with the magic each begins with. pub const CONTAINERS: [(&str, &[u8; 4]); 8] = [ ("world", b"WWLD"), ("causal op", b"WWLD"), ("film", b"WFLM"), ("reel", b"WREL"), ("score", b"WSCR"), ("haptic", b"WHAP"), ("splat4d", b"W4DS"), ("audio scene", b"WSCN"), ]; /// Reads `p` as the container `pick` selects, discarding the result. pub fn container_read(pick: u8, p: &[u8]) { match pick as usize % CONTAINERS.len() { 0 => drop(wai::world::WorldContainer::from_bytes(p)), 1 => drop(wai::world::op::CausalOp::from_bytes(p)), 2 => drop(wai::film::Film::from_bytes(p)), 3 => drop(wai::film::Reel::from_bytes(p)), 4 => drop(wai::score::Score::from_bytes(p)), 5 => drop(wai::haptic::HapticSignal::from_bytes(p)), 6 => drop(wai::splat4d::Splat4dClipData::from_bytes(p)), _ => drop(wai::audio_scene::SpatialScene::from_bytes(p)), } }