//! The mandatory floor (SPEC §5: PNG, FLAC, zstd) in a build with the `floor` feature and no //! other: the library decodes each, and the `wai` binary encodes PNG and zstd and decodes all //! three, with no system codec library linked. A codec outside the floor is refused, saying //! which build has it. #![cfg(feature = "floor")] use std::path::{Path, PathBuf}; use std::process::Command; use wai::codecs::{audio, image, text}; fn tmp(name: &str) -> PathBuf { let d = std::env::temp_dir().join(format!("wai-floor-{name}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&d); std::fs::create_dir_all(&d).unwrap(); d } fn wai(args: &[&str]) -> std::process::Output { Command::new(env!("CARGO_BIN_EXE_wai")).args(args).output().expect("wai runs") } /// CRC-8 (polynomial 0x07) and CRC-16 (0x8005) of RFC 9639 §9.1.8 and §9.3. fn crc8(b: &[u8]) -> u8 { b.iter().fold(0u8, |mut c, &x| { c ^= x; for _ in 0..8 { c = if c & 0x80 != 0 { (c << 1) ^ 0x07 } else { c << 1 }; } c }) } fn crc16(b: &[u8]) -> u16 { b.iter().fold(0u16, |mut c, &x| { c ^= (x as u16) << 8; for _ in 0..8 { c = if c & 0x8000 != 0 { (c << 1) ^ 0x8005 } else { c << 1 }; } c }) } /// A FLAC stream (RFC 9639) of one frame of sixteen 16-bit mono samples at 8 kHz, in a /// verbatim subframe: written field by field, so the test needs no encoder. fn flac(samples: &[i16; 16]) -> Vec { let mut b = b"fLaC".to_vec(); b.extend_from_slice(&[0x80, 0, 0, 34]); // the last metadata block: STREAMINFO, 34 bytes b.extend_from_slice(&16u16.to_be_bytes()); // min and max block size b.extend_from_slice(&16u16.to_be_bytes()); b.extend_from_slice(&[0, 0, 0, 0, 0, 0]); // frame sizes unknown // 20 bits sample rate (8000), 3 bits channels-1 (0), 5 bits bps-1 (15), 36 bits samples (16). let v: u64 = (8000u64 << 44) | (15u64 << 36) | 16; b.extend_from_slice(&v.to_be_bytes()); b.extend_from_slice(&[0; 16]); // MD5 unknown let start = b.len(); // Sync, fixed blocking; block size from an 8-bit field, rate from STREAMINFO; mono, 16 bits; // frame 0; block size - 1. b.extend_from_slice(&[0xff, 0xf8, 0x60, 0x08, 0x00, 15]); let c = crc8(&b[start..]); b.push(c); b.push(0x02); // a verbatim subframe, no wasted bits for s in samples { b.extend_from_slice(&s.to_be_bytes()); } let c = crc16(&b[start..]); b.extend_from_slice(&c.to_be_bytes()); b } #[test] fn the_library_decodes_the_floor() { let rgb: Vec = (0..4 * 3 * 3).map(|i| (i * 7) as u8).collect(); let png = image::png_encode(&rgb, 3, 4).unwrap(); assert_eq!(image::png_decode(&png).unwrap(), (rgb, 3, 4)); let s: [i16; 16] = std::array::from_fn(|i| (i as i16 - 8) * 1000); let (pcm, rate) = audio::flac_decode(&flac(&s)).unwrap(); assert_eq!(rate, 8000); assert_eq!(pcm, s.iter().map(|&x| x as f32 / 32768.0).collect::>()); let t = b"the mandatory floor".repeat(20); assert_eq!(text::zstd_decode(&text::zstd_encode(&t, 3).unwrap()).unwrap(), t); assert_eq!(wai::floor_capabilities(), &["wai.image.png", "wai.audio.flac", "wai.text.zstd"]); } fn ok(o: &std::process::Output) { assert!(o.status.success(), "{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr)); } fn s(p: &Path) -> &str { p.to_str().unwrap() } #[test] fn the_cli_encodes_png_and_zstd_and_decodes_the_floor() { let d = tmp("cli"); // PNG: a 4x3 picture in, the same pixels out. let rgb: Vec = (0..36).map(|i| (i * 5) as u8).collect(); std::fs::write(d.join("pic.rgb"), &rgb).unwrap(); std::fs::write(d.join("pic.rgb.meta"), "3 4\n").unwrap(); ok(&wai(&["encode", "png", s(&d.join("pic.rgb")), "-o", s(&d.join("pic.wai"))])); ok(&wai(&["decode", s(&d.join("pic.wai")), "-o", s(&d.join("pic.out"))])); assert_eq!(std::fs::read(d.join("pic.out")).unwrap(), rgb); // zstd. let t = b"carried, compressed and rebuilt".repeat(9); std::fs::write(d.join("t.txt"), &t).unwrap(); ok(&wai(&["encode", "zstd", s(&d.join("t.txt")), "-o", s(&d.join("t.wai"))])); ok(&wai(&["decode", s(&d.join("t.wai")), "-o", s(&d.join("t.out"))])); assert_eq!(std::fs::read(d.join("t.out")).unwrap(), t); // FLAC: wrapped as it comes, decoded by the floor. let smp: [i16; 16] = std::array::from_fn(|i| (i as i16) * 300); std::fs::write(d.join("a.flac"), flac(&smp)).unwrap(); ok(&wai(&["wrap", "wai.audio.flac", s(&d.join("a.flac")), "-o", s(&d.join("a.wai"))])); ok(&wai(&["decode", s(&d.join("a.wai")), "-o", s(&d.join("a.f32"))])); let f: Vec = smp.iter().flat_map(|&x| (x as f32 / 32768.0).to_le_bytes()).collect(); assert_eq!(std::fs::read(d.join("a.f32")).unwrap(), f); assert_eq!(std::fs::read_to_string(d.join("a.f32.meta")).unwrap().trim(), "8000"); let _ = std::fs::remove_dir_all(&d); } #[cfg(not(feature = "codecs"))] #[test] fn a_codec_outside_the_floor_is_refused_naming_the_build_that_has_it() { let d = tmp("outside"); std::fs::write(d.join("x.txt"), b"x").unwrap(); let o = wai(&["encode", "xz", s(&d.join("x.txt")), "-o", s(&d.join("x.wai"))]); assert!(!o.status.success()); assert!(String::from_utf8_lossy(&o.stderr).contains("`codecs` feature"), "{}", String::from_utf8_lossy(&o.stderr)); // A payload the floor does not decode is inert here, never guessed. std::fs::write(d.join("y.jxl"), b"\xff\x0a").unwrap(); ok(&wai(&["wrap", "wai.image.jxl", s(&d.join("y.jxl")), "-o", s(&d.join("y.wai"))])); let o = wai(&["decode", s(&d.join("y.wai")), "-o", s(&d.join("y.out"))]); assert!(!o.status.success() && String::from_utf8_lossy(&o.stderr).contains("inert"), "{}", String::from_utf8_lossy(&o.stderr)); let _ = std::fs::remove_dir_all(&d); }