The extension live-provenance.md registers wai.live.provenance: the per-segment C2PA Manifest Box method and the Verifiable Segment Info method, written as C2PA 2.4 §19 defines them for a CMAF track carried under draft-ietf-moq-cmsf-01, where a segment less its C2PA boxes is its MoQ group's objects, so the segment's hard binding and the group receipt's Merkle root commit to the same chunks. Session keys rotate in a segment's manifest and also sign the group receipts. wai-rs feature live_provenance writes and verifies it with no C2PA library: BMFF, JUMBF, COSE_Sign1 with EdDSA, the claim signer's X.509 chain, and the receipts through jwp_delivery. The corpus live-conformance/c2pa-live/ has five good streams and a negative for each of the thirty reasons. Co-Authored-By: Claude Opus 5.5 <[email protected]>
78 lines
3.4 KiB
Rust
78 lines
3.4 KiB
Rust
//! `wai.live.provenance` (extensions/live-provenance.md): the committed corpus
|
|
//! `live-conformance/c2pa-live/` regenerates byte for byte from its fixed seeds and the
|
|
//! packaging corpus's floor, holds no other file, every vector in it gets the outcome it
|
|
//! states, every reason of §12 refuses a vector, and §12 lists exactly the reasons the
|
|
//! reference reports.
|
|
#![cfg(feature = "live_provenance")]
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::path::{Path, PathBuf};
|
|
use wai::live_provenance::corpus::{self, Media};
|
|
use wai::live_provenance::Reason;
|
|
|
|
fn wai_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
|
}
|
|
|
|
fn read_tree(root: &Path, dir: &Path, out: &mut BTreeMap<String, Vec<u8>>) {
|
|
for e in std::fs::read_dir(dir).expect("the committed corpus").flatten() {
|
|
let p = e.path();
|
|
if p.is_dir() {
|
|
read_tree(root, &p, out);
|
|
} else {
|
|
let rel = p.strip_prefix(root).expect("under the root").to_string_lossy().replace('\\', "/");
|
|
if rel != "README.md" {
|
|
out.insert(rel, std::fs::read(&p).expect("a file"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn committed() -> BTreeMap<String, Vec<u8>> {
|
|
let dir = wai_dir().join("live-conformance/c2pa-live");
|
|
let mut out = BTreeMap::new();
|
|
read_tree(&dir, &dir, &mut out);
|
|
out
|
|
}
|
|
|
|
#[test]
|
|
fn the_committed_corpus_regenerates_from_its_seeds() {
|
|
let files = committed();
|
|
let media = Media::load(&wai_dir()).expect("the packaging corpus's floor");
|
|
let generated: BTreeMap<String, Vec<u8>> = corpus::generate(&media).into_iter().collect();
|
|
let extra: Vec<&String> = files.keys().filter(|k| !generated.contains_key(*k)).collect();
|
|
let missing: Vec<&String> = generated.keys().filter(|k| !files.contains_key(*k)).collect();
|
|
let differ: Vec<&String> = generated.iter().filter(|(k, v)| files.get(*k).is_some_and(|f| f != *v)).map(|(k, _)| k).collect();
|
|
assert!(
|
|
extra.is_empty() && missing.is_empty() && differ.is_empty(),
|
|
"extra {extra:?}, missing {missing:?}, differ {differ:?}: run `wai_live_provenance_vectors gen`"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn every_vector_gets_its_stated_outcome_and_every_reason_refuses_one() {
|
|
let fails = corpus::verify_files(&committed());
|
|
assert!(fails.is_empty(), "{}", fails.join("\n"));
|
|
}
|
|
|
|
/// §12 of the extension lists exactly the codes `Reason::ALL` has, each once, in order, with
|
|
/// the two C2PA codes the reference reports.
|
|
#[test]
|
|
fn the_extension_lists_the_reasons() {
|
|
let text = std::fs::read_to_string(wai_dir().join("extensions/live-provenance.md")).expect("the extension");
|
|
let at = text.find("\n## 12. Reasons").expect("a §12");
|
|
let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e);
|
|
let rows: Vec<Vec<String>> = text[at..end]
|
|
.lines()
|
|
.filter(|l| l.starts_with("| `"))
|
|
.map(|l| l.split('|').map(|c| c.trim().trim_matches('`').to_string()).collect())
|
|
.collect();
|
|
let listed: Vec<&str> = rows.iter().map(|r| r[1].as_str()).collect();
|
|
let want: Vec<&str> = Reason::ALL.iter().map(|r| r.code()).collect();
|
|
assert_eq!(listed, want, "§12's table and Reason::ALL differ");
|
|
for (r, row) in Reason::ALL.iter().zip(&rows) {
|
|
let code = |c: &str| if c == "—" { None } else { Some(c.to_string()) };
|
|
assert_eq!(code(&row[3]), r.c2pa().map(String::from), "{}: the §15 code", r.code());
|
|
assert_eq!(code(&row[4]), r.livevideo().map(String::from), "{}: the §19.7 code", r.code());
|
|
}
|
|
}
|