//! A re-pack from a parent (packaging §11.5): a pack that takes every piece's base and //! refinements from an earlier package instead of coding them. The golden //! `video-repack-01` takes `video-base-01`'s bases under two floors; its files are those of //! a pack that codes, but for its receipt's parent and `LISTING`; a parent that does not //! verify, is not this source's, or is coded in other rungs is refused by name; and a //! verifier holding the parent checks the re-pack against it. use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use wai_pack::files::FileSet; 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 package(name: &str) -> Files { held(&corpus().join("packs").join(name).join("package")) } fn policy() -> String { String::from_utf8(std::fs::read(corpus().join("packs/video-repack-01/policy.toml")).unwrap()).unwrap() } /// Pack `policy` over `source` with the re-pack golden's floor and key, taking the bases /// from `parent` when given. fn pack(policy: &str, source: Vec, parent: Option) -> Result { let d = corpus().join("packs/video-repack-01"); let key = ed25519_dalek::SigningKey::from_bytes(&wai_pack::unhex(std::fs::read_to_string(d.join("key.hex")).unwrap().trim()).unwrap().try_into().unwrap()); let id = wai::signer_id::did_key(&key.verifying_key().to_bytes()); let mut o = wai_pack::pack::PackOpts::new(key, id, held(&d.join("floor"))); o.base_from = parent.map(FileSet::Memory); let mut src = BTreeMap::new(); src.insert("v".to_string(), source); wai_pack::pack::pack(policy.as_bytes(), &src, &mut o) } fn fixture() -> Vec { wai_pack::fixture::generate("video-base-01").unwrap() } fn code(r: Result) -> String { r.map(|_| "ok".to_string()).unwrap_or_else(|e| e.code) } fn receipt(f: &Files) -> wai::package::PackageReceiptV2 { wai::package::PackageReceiptV2::from_json(std::str::from_utf8(&f["package.receipt.json"]).unwrap()).unwrap() } /// The re-pack takes the parent's objects: its files are a coding pack's of the same /// policy, but for the receipt, which names the parent, and `LISTING`; and it verifies /// against its parent, and against no other package. #[test] fn a_repack_is_a_coding_pack_with_its_parent_named() { let parent = package("video-base-01"); let re = pack(&policy(), fixture(), Some(parent.clone())).unwrap(); assert_eq!(re.files, package("video-repack-01"), "the golden"); let coded = pack(&policy(), fixture(), None).unwrap(); let differ: Vec<&String> = re.files.keys().filter(|k| coded.files.get(*k) != Some(&re.files[*k])).collect(); assert_eq!(differ, vec!["LISTING", "package.receipt.json"]); assert_eq!(re.files.keys().collect::>(), coded.files.keys().collect::>()); assert_eq!(re.receipt.parent, wai::package::Parent::RePack(receipt(&parent).receipt_hash())); assert_eq!(coded.receipt.parent, wai::package::Parent::None); wai_pack::verify::verify_repack(&re.files, &parent).unwrap(); let codes = |r: Result>| r.err().unwrap_or_default().into_iter().map(|x| x.code).collect::>(); assert_eq!(codes(wai_pack::verify::verify_repack(&re.files, &package("video-ladder-01"))), vec!["repack-parent-mismatch"], "a parent it does not name"); assert_eq!(codes(wai_pack::verify::verify_repack(&coded.files, &parent)), vec!["repack-parent-mismatch"], "a pack that names no parent"); } /// §11.5's refusals, each before anything is written: a parent that does not verify, one /// whose pieces are another source's, one coded under other base parameters, and a source /// one sample from the parent's. #[test] fn a_parent_that_is_not_this_source_s_is_refused() { let parent = package("video-base-01"); // An object of the parent altered, its LISTING written again: it does not verify. let mut corrupt = parent.clone(); let obj = corrupt.keys().find(|k| k.starts_with("objects/")).unwrap().clone(); let b = corrupt.get_mut(&obj).unwrap(); let last = b.len() - 1; b[last] ^= 1; corrupt.remove("LISTING"); let view: BTreeMap = corrupt.iter().map(|(k, v)| (k.clone(), v.as_slice())).collect(); let l = wai::pin::listing_from(&view).unwrap(); corrupt.insert("LISTING".into(), l); assert_eq!(code(pack(&policy(), fixture(), Some(corrupt))), "repack-parent-invalid"); // Another fixture's package: other pieces. assert_eq!(code(pack(&policy(), fixture(), Some(package("video-staged-01")))), "repack-parent-mismatch"); // This policy's base under other parameters than the parent's. let other = policy().replace("q_candidates = [16, 12, 8]", "q_candidates = [16, 12]"); assert_ne!(other, policy()); assert_eq!(code(pack(&other, fixture(), Some(parent.clone()))), "repack-parent-mismatch"); // One luma sample of frame 30 changed: the same pieces and kinds, another source. let mut src = fixture(); let head = src.iter().position(|&c| c == b'\n').unwrap() + 1; let frame = 64 * 36 + 2 * 32 * 18; let at = head + 30 * (6 + frame) + 6 + 64 * 10 + 20; src[at] = src[at].wrapping_add(40); assert_eq!(code(pack(&policy(), src, Some(parent))), "repack-source-mismatch"); } /// The command line: `pack --base-from` writes the re-pack, and `verify --parent` checks it /// against the parent it names. #[test] fn the_command_line_repacks_and_verifies_against_the_parent() { let dir = std::env::temp_dir().join(format!("wai-pack-repack-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let g = corpus().join("packs/video-repack-01"); std::fs::write(dir.join("v.y4m"), fixture()).unwrap(); let bin = env!("CARGO_BIN_EXE_wai-pack"); let out = std::process::Command::new(bin) .args(["pack", "--policy"]).arg(g.join("policy.toml")) .arg("--out").arg(dir.join("out")) .arg("--key").arg(g.join("key.hex")) .arg("--source").arg(format!("v={}", dir.join("v.y4m").display())) .arg("--floor").arg(g.join("floor")) .arg("--base-from").arg(corpus().join("packs/video-base-01/package")) .output() .unwrap(); assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); assert_eq!(held(&dir.join("out")), package("video-repack-01")); let verify = |parent: &str| { std::process::Command::new(bin).arg("verify").arg(dir.join("out")).arg("--parent").arg(corpus().join("packs").join(parent).join("package")).output().unwrap() }; assert!(verify("video-base-01").status.success()); let wrong = verify("video-ladder-01"); assert!(!wrong.status.success() && String::from_utf8_lossy(&wrong.stderr).contains("repack-parent-mismatch")); let _ = std::fs::remove_dir_all(&dir); }