open-standards/wai/wai-rs/tests/browser_sink.rs
Transaction Science 9cc849ae19 browser sink for the page: wai-live-web's routing, constraint, binding and frame-state functions, a bound wasm decoder, and the verifier's hashes
wai-live-web exports the browser_sink functions (routeReport, evaluateConstraints,
readBinding, makeBinding, frameTier, frameState) and, behind its feature int_video,
decodeBound: a wai.video.int_motion envelope verified against its group and object
receipts, decoded by the integer decoder and every frame bound; an object that does not
verify is not decoded, and one decoded without receipts is unbound. joule-moq-web's
verifier now returns a verified object's content hash and its group receipt's link hash,
which a frame decoded from it names. Reason's list is a function, so nothing of the module
reaches include/wai.h.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 15:14:57 -04:00

158 lines
6.7 KiB
Rust

//! 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<u8> {
(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<u8> {
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<String> = corpus::files().iter().map(|(n, _)| n.to_string()).chain(["README.md".to_string()]).collect();
let have: BTreeSet<String> = 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["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<String> = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect();
let want: Vec<String> = 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<GroupReceipt> = 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);
}