//! A supplied floor in the CMAF form (packaging §7.7): the golden `video-cmaf-01` takes //! `video-ladder-01`'s two AV1 streams as a media muxer wrote them into fMP4 segments, and //! its floor's CMAF files are those segments, byte for byte, bound to the receipt by the //! supplied floor's listing, which the package carries. Each fault of a supplied CMAF floor //! is refused by name, and a package whose segments are not the supplied ones is refused. use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use wai::stage_index::{Role, StageIndex}; type Files = BTreeMap>; fn corpus() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../pack-conformance") } fn held(p: &Path) -> Files { wai::pin::read_dir_set(p).unwrap() } fn golden() -> Files { held(&corpus().join("packs/video-cmaf-01/package")) } fn supplied() -> Files { held(&corpus().join("packs/video-cmaf-01/floor")) } fn policy() -> String { String::from_utf8(std::fs::read(corpus().join("packs/video-cmaf-01/policy.toml")).unwrap()).unwrap() } /// Pack the golden's fixture under `policy` over the supplied floor `floor` (its listing /// pinned in the policy). fn pack(policy: &str, floor: Files) -> Result { let listing = wai_pack::floor::listing_sha256(&floor).unwrap(); let at = policy.find("supplied_listing = \"").unwrap() + "supplied_listing = \"".len(); let policy = format!("{}{listing}{}", &policy[..at], &policy[at + 64..]); let mut o = wai_pack::pack::PackOpts::new(ed25519_dalek::SigningKey::from_bytes(&[0x5a; 32]), "did:key:test", floor); let mut src = BTreeMap::new(); src.insert("v".to_string(), wai_pack::fixture::generate("video-base-01").unwrap()); wai_pack::pack::pack(policy.as_bytes(), &src, &mut o) } fn code(r: Result) -> String { r.map(|_| "ok".to_string()).unwrap_or_else(|e| e.code) } /// Where box `kind` begins in `b` (its size field). fn box_at(b: &[u8], kind: &[u8; 4]) -> usize { b.windows(4).position(|w| w == kind).unwrap() - 4 } /// The floor's CMAF files are the supplied ones, byte for byte, and `floor.listing` is the /// supplied listing; each floor object holds its piece's samples as temporal units, which /// are exactly the OBU form's units of the same streams (`video-ladder-01`'s). #[test] fn a_cmaf_floor_is_the_ladder_s_own_bytes() { let pkg = golden(); wai_pack::verify::verify_files(&pkg).unwrap(); let sup = supplied(); let view: BTreeMap = sup.iter().map(|(k, v)| (k.clone(), v.as_slice())).collect(); assert_eq!(pkg["floor.listing"], wai::pin::listing_from(&view).unwrap()); let ix = StageIndex::parse(&pkg["index-v.wsix"]).unwrap(); let ladder = held(&corpus().join("packs/video-ladder-01/floor")); for (k, name) in ["hi", "lo"].iter().enumerate() { assert_eq!(pkg[&format!("floor/{name}/init.mp4")], sup[&format!("{name}/init.mp4")]); let segs: Vec<&String> = sup.keys().filter(|p| p.starts_with(&format!("{name}/seg_"))).collect(); assert_eq!(segs.len(), ix.pieces.len()); let tus: Vec> = wai_pack::av1::temporal_units(&ladder[&format!("{name}.obu")]).unwrap().iter().map(|t| t.bytes()).collect(); for (p, seg) in segs.iter().enumerate() { let b = &sup[*seg]; assert_eq!(&pkg[&format!("floor/{name}/{}.m4s", wai_pack::sha256_hex(b))], b, "{seg}, as supplied"); let fe = ix.piece_entries(p).iter().filter(|e| e.role == Role::Floor).nth(k).unwrap(); let w = wai::container::Wai::from_bytes(&pkg[&wai_pack::objects::object_path(&fe.content_hash)]).unwrap(); let (units, _) = wai_pack::floor::av1_units(&w.payload).unwrap(); let (s, n) = (ix.pieces[p].start as usize, ix.pieces[p].duration as usize); assert_eq!(units, tus[s..s + n].to_vec(), "piece {p} of {name}: the stream's own units"); } } let master = String::from_utf8(pkg["hls/classic/master.m3u8"].clone()).unwrap(); assert!(master.contains("../classic/floor-hi.m3u8") && master.contains("../classic/floor-lo.m3u8")); } /// §7.7's refusals, each before anything is written. #[test] fn a_supplied_cmaf_floor_s_faults_are_refused() { let p = policy(); let sup = supplied(); assert_eq!(code(pack(&p, sup.clone())), "ok"); let with = |edit: &dyn Fn(&mut Files)| { let mut f = sup.clone(); edit(&mut f); code(pack(&p, f)) }; assert_eq!(with(&|f| { f.remove("lo/init.mp4"); }), "floor-stream-missing"); assert_eq!(with(&|f| { f.insert("hi/index.m3u8".into(), b"#EXTM3U\n".to_vec()); }), "floor-cmaf-malformed"); assert_eq!(with(&|f| { f.insert("hi/x/seg.m4s".into(), Vec::new()); }), "floor-cmaf-malformed", "a segment in a directory below"); assert_eq!(with(&|f| { let b = f.get_mut("hi/seg_0002.m4s").unwrap(); b[..4].copy_from_slice(&0u32.to_be_bytes()); }), "floor-cmaf-malformed", "a box of size 0"); assert_eq!(with(&|f| { let b = f.get_mut("hi/seg_0002.m4s").unwrap(); let t = box_at(b, b"trun"); b[t + 24..t + 28].copy_from_slice(&0x0101_0000u32.to_be_bytes()); }), "floor-cmaf-malformed", "a segment that does not open on a sync sample"); assert_eq!(with(&|f| { let b = f.get_mut("hi/seg_0002.m4s").unwrap(); let t = box_at(b, b"tfhd"); let d = u32::from_be_bytes(b[t + 16..t + 20].try_into().unwrap()); b[t + 16..t + 20].copy_from_slice(&(d + 1).to_be_bytes()); }), "floor-cmaf-timing", "a sample longer than one frame"); // Every sample a sync sample by tfhd's default flags: the non-key frames after each // segment's first are flagged sync (#426 review S3). assert_eq!(with(&|f| { let b = f.get_mut("hi/seg_0002.m4s").unwrap(); let t = box_at(b, b"tfhd"); assert_eq!(u32::from_be_bytes(b[t + 8..t + 12].try_into().unwrap()) & 0xff_ffff, 0x02_0038, "default duration, size and flags"); b[t + 24..t + 28].copy_from_slice(&0x0200_0000u32.to_be_bytes()); }), "floor-cmaf-malformed", "a non-key frame flagged as a sync sample"); // The init's av1C and sample entry against the stream's sequence header (#426 review S2): // its level one higher, and its width one wider. assert_eq!(with(&|f| { let b = f.get_mut("hi/init.mp4").unwrap(); let c = box_at(b, b"av1C"); b[c + 9] += 1; }), "floor-cmaf-malformed", "an av1C whose level is not the sequence header's"); assert_eq!(with(&|f| { let b = f.get_mut("hi/init.mp4").unwrap(); // The sample entry, after stsd: `ftyp` names `av01` among its brands first. let st = box_at(b, b"stsd"); let e = st + b[st..].windows(4).position(|w| w == b"av01").unwrap() - 4; let w = u16::from_be_bytes([b[e + 32], b[e + 33]]); b[e + 32..e + 34].copy_from_slice(&(w + 1).to_be_bytes()); }), "floor-cmaf-malformed", "a sample entry wider than the sequence header's frames"); assert_eq!(with(&|f| { f.remove("lo/seg_0004.m4s"); }), "floor-frame-count"); // Pieces of at most two seconds: the first segment holds two pieces. let short = p.replace("max_ms = 4000", "max_ms = 2000").replace("target_ms = 2000", "target_ms = 1000"); assert_eq!(code(pack(&short, sup.clone())), "floor-cmaf-pieces"); // Without supplied_form, the floor is read in the OBU form, which holds no stream. assert_eq!(code(pack(&p.replace("supplied_form = \"cmaf\"\n", ""), sup)), "floor-stream-missing"); } /// A package whose segment is not the supplied one is refused, though it is a segment of /// the same samples: the receipt binds the supplied bytes through `floor.listing`. #[test] fn the_package_binds_the_supplied_bytes() { let mut f = golden(); let seg = f.keys().find(|k| k.starts_with("floor/hi/") && k.ends_with(".m4s")).unwrap().clone(); let mut b = vec![0, 0, 0, 8, b'f', b'r', b'e', b'e']; b.extend_from_slice(&f[&seg]); f.remove(&seg); let replaced = format!("floor/hi/{}.m4s", wai_pack::sha256_hex(&b)); f.insert(replaced.clone(), b); let key = wai_pack::unhex(std::fs::read_to_string(corpus().join("packs/video-cmaf-01/key.hex")).unwrap().trim()).unwrap(); // Resealed with catalogs naming the replacement, so the one fault is its binding. let renames = BTreeMap::from([(seg.clone(), replaced)]); wai_pack::golden::reseal_renamed(&mut f, &ed25519_dalek::SigningKey::from_bytes(&key.try_into().unwrap()), None, &renames).unwrap(); let got: Vec = wai_pack::verify::verify_files(&f).err().unwrap_or_default().into_iter().map(|r| r.code).collect(); assert_eq!(got.first().map(String::as_str), Some("floor-cmaf-mismatch"), "{got:?}"); } /// Packed into a directory with `link_supplied`, the floor's CMAF files are the supplied /// files themselves (hard links where the file system allows): the package verifies, and /// on a file system of inodes each shares the supplied file's. #[test] fn a_linked_floor_is_the_supplied_files() { let dir = std::env::temp_dir().join(format!("wai-pack-cmaf-link-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); let policy = std::fs::read(corpus().join("packs/video-cmaf-01/policy.toml")).unwrap(); let key = wai_pack::unhex(std::fs::read_to_string(corpus().join("packs/video-cmaf-01/key.hex")).unwrap().trim()).unwrap(); let key = ed25519_dalek::SigningKey::from_bytes(&key.try_into().unwrap()); let id = wai::signer_id::did_key(&key.verifying_key().to_bytes()); // The supplied floor copied beside the output, so a link is on one file system. let sup = dir.join("floor"); for (p, b) in supplied() { std::fs::create_dir_all(sup.join(&p).parent().unwrap()).unwrap(); std::fs::write(sup.join(&p), b).unwrap(); } let mut o = wai_pack::pack::PackOpts::with_floor(key, id, wai_pack::files::FileSet::Dir(sup.clone())); o.link_supplied = true; let fixture = wai_pack::fixture::generate("video-base-01").unwrap(); let mut src = BTreeMap::new(); src.insert("v".to_string(), wai_pack::files::Input::Bytes(&fixture)); let out = dir.join("out"); let mut sink = wai_pack::files::DirSink::new(&out).unwrap(); wai_pack::pack::pack_into(&policy, &src, &mut o, &mut sink).unwrap(); sink.keep(); assert_eq!(held(&out), golden(), "the golden, byte for byte"); #[cfg(unix)] { use std::os::unix::fs::MetadataExt as _; let ino = |p: &Path| std::fs::metadata(p).unwrap().ino(); assert_eq!(ino(&out.join("floor/hi/init.mp4")), ino(&sup.join("hi/init.mp4"))); let s0 = std::fs::read(sup.join("lo/seg_0000.m4s")).unwrap(); assert_eq!(ino(&out.join(format!("floor/lo/{}.m4s", wai_pack::sha256_hex(&s0)))), ino(&sup.join("lo/seg_0000.m4s"))); } let _ = std::fs::remove_dir_all(&dir); }