//! The browser sink (extensions/browser-sink.md): the committed corpus //! `live-conformance/browser/` regenerates byte for byte, every case in it gets the outcome //! it states, every reason of §11 appears in it, §11 lists exactly the reference's codes, and //! the chain's group receipts and objects, which the corpus generator signs by hand, verify //! under `jwp_delivery` (jwp-receipts §3) with each binding naming the object's content hash //! and its group receipt's link hash. #![cfg(all(feature = "browser_sink", feature = "receipt_claims"))] use std::collections::BTreeSet; use std::path::PathBuf; use serde_json::Value; use wai::browser_sink::{corpus, Reason}; use wai::jwp_delivery::{GroupReceipt, ObjectReceipt}; fn hex_of(b: &[u8]) -> String { b.iter().map(|x| format!("{x:02x}")).collect() } fn unhex(s: &str) -> Vec { (0..s.len() / 2).map(|i| u8::from_str_radix(&s[2 * i..2 * i + 2], 16).unwrap()).collect() } fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn read(p: &str) -> Vec { std::fs::read(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}")) } fn vectors() -> Value { serde_json::from_slice(&read("live-conformance/browser/vectors.json")).unwrap() } #[test] fn every_committed_file_regenerates_and_no_other_is_there() { let dir = wai_dir().join("live-conformance/browser"); let want: BTreeSet = corpus::files().iter().map(|(n, _)| n.to_string()).chain(["README.md".to_string()]).collect(); let have: BTreeSet = std::fs::read_dir(&dir).unwrap().map(|e| e.unwrap().file_name().into_string().unwrap()).collect(); assert_eq!(have, want, "the corpus directory holds exactly the generated files and its README"); for (name, bytes) in corpus::files() { assert!(read(&format!("live-conformance/browser/{name}")) == bytes, "{name} differs from `wai_browser_sink_vectors gen`"); } } #[test] fn every_case_gets_its_stated_outcome() { let doc = String::from_utf8(read("live-conformance/browser/vectors.json")).unwrap(); let fails = corpus::verify(&doc); assert!(fails.is_empty(), "{}", fails.join("\n")); } /// Every code of §11 is given somewhere in the corpus: as a report's, an entry's, a leg's, an /// energy entry's, a binding's or a side table's refusal. #[test] fn the_corpus_gives_every_reason() { let v = vectors(); let mut seen = BTreeSet::new(); let mut take = |x: &Value| { if let Some(s) = x.as_str() { seen.insert(s.to_string()); } }; for c in v["routes"].as_array().unwrap() { take(&c["reason"]); for e in c["table"].as_array().into_iter().flatten() { take(&e["reason"]); take(&e["energy"]["reason"]); for l in ["ua", "host", "wasm"] { take(&e["legs"][l]); } } } for c in v["choices"].as_array().unwrap() { take(&c["reason"]); for g in c["choices"].as_array().into_iter().flatten() { take(&g["reason"]); } } for c in v["constraints"].as_array().unwrap() { take(&c["reason"]); } for c in v["bindings"].as_array().unwrap() { take(&c["reason"]); } for c in v["tables"].as_array().unwrap() { for r in c["result"]["refusals"].as_array().unwrap() { take(&r["reason"]); } } // binding_unverified_reference is a page's refusal over a decode it ran (§7.2): the corpus // states it for the chain's changed object, which a page refuses to bind. for g in v["chain"]["groups"].as_array().unwrap() { for o in g["objects"].as_array().unwrap() { if o["outcome"] == "refused" { seen.insert("binding_unverified_reference".into()); } } } for r in Reason::all() { assert!(seen.contains(r.code()), "nothing in the corpus gives {r}"); } } /// §11 of the extension lists exactly the codes `Reason::all()` has, each once, in order. #[test] fn the_extension_lists_the_reasons() { let text = String::from_utf8(read("extensions/browser-sink.md")).unwrap(); let at = text.find("\n## 11. Reasons").expect("a §11"); let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); let listed: Vec = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); let want: Vec = Reason::all().iter().map(|r| r.code().to_string()).collect(); assert_eq!(listed, want, "§11's table and Reason::all() differ"); } /// The chain the generator signs by hand verifies under jwp_delivery, and every binding names /// its object's content hash and its group receipt's link hash; the changed object does not /// verify. #[test] fn the_chain_verifies_under_jwp_delivery() { let v = vectors(); let mut previous: Option = None; let mut objects = 0; for g in v["chain"]["groups"].as_array().unwrap() { let group = GroupReceipt::from_json(&g["receipt"]).expect("the group reads"); group.verify().expect("the group's signature verifies"); group.check_parent(previous.as_ref()).expect("the group links to the one before"); assert_eq!(hex_of(&group.link_hash()), g["link_hash"].as_str().unwrap()); assert_eq!(hex_of(&group.canonical_bytes()), g["canonical"].as_str().unwrap()); for o in g["objects"].as_array().unwrap() { let receipt = ObjectReceipt::from_json(&o["receipt"]).expect("the object receipt reads"); let envelope = match &o["envelope"] { Value::String(h) => unhex(h), f => read(&format!("live-conformance/browser/{}", f["file"].as_str().unwrap())), }; let accepted = receipt.accept(&group, &envelope); if o["outcome"] == "verified" { accepted.unwrap_or_else(|e| panic!("{}: {e:?}", o["name"])); let b: Value = serde_json::from_str(o["binding"].as_str().unwrap()).unwrap(); assert_eq!(b["digest"], hex_of(&wai::jwp_hash::content_hash(&envelope))); assert_eq!(b["receipt"], g["link_hash"]); assert_eq!(b["tier"], "decode-equivalence"); objects += 1; } else { assert!(accepted.is_err(), "{}: a changed object verifies", o["name"]); assert!(o["binding"].is_null()); } } previous = Some(group); } assert_eq!(objects, 3); } /// The envelope the browser decodes is the WIV1 payload whose frame digests `sha256.json` /// publishes, wrapped once. #[test] fn the_int_motion_envelope_wraps_the_published_payload() { let e = read("live-conformance/browser/int_motion.wai"); let wiv1 = read("wai-rs/tests/vectors/int_video/int_video_bitstream.wiv1"); assert!(e.ends_with(&wiv1)); let digests: Value = serde_json::from_slice(&read(corpus::WIV1_DIGESTS)).unwrap(); assert_eq!(digests["int_video"]["frames_sha256"].as_array().unwrap().len(), 5); } /// The register the page evaluates sets over is capability-constraints §3.1's table, name for /// name and in order. #[test] fn the_register_is_capability_constraints_table() { let text = String::from_utf8(read("extensions/capability-constraints.md")).unwrap(); let at = text.find("\n### 3.1 ").expect("a §3.1"); let end = at + text[at..].find("\n## 4.").expect("a §4"); let listed: Vec<&str> = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1)).collect(); assert_eq!(listed, wai::browser_sink::REGISTER, "capability-constraints §3.1 and browser_sink::REGISTER differ"); }