002b96794made joule-moq's corpora the same text in every build. The crates beside it wrote theirs with serde_json's own writer over values built by `json!`, which keeps an object's members in key order in a default build and in insertion order once serde_json's feature `preserve_order` is on; Cargo turns that feature on for a whole build when any crate in it asks (wai-rs does; the relay took it at2add6cd10). joule-moq-mls's `corpus::to_bytes` said it wrote canonical JSON and wrote whatever order its value had: the e2ee corpus held only because `generate` sorted its own value first. Reproduced oneab10d917with `--features serde_json/preserve_order`. On the Framework (x86_64 Linux, load 78 to 97 on 32 cores), every crate of the workspace that writes JSON, each test target: joule-moq-ingest failed the_injection_corpus_regenerates_byte_for_byte and the_ertmp_corpus_regenerates_byte_for_byte in each of its four feature sets, st2110::session's the_sample_catalog_is_what_a_session_writes (`wai_tempo` written after `width`) and an_origin_claim_verifies_and_holds_synthesis_members (members in insertion order), and the link and telephony generators' --check ("differs: vectors.json"); mls (19), interop (6), the relay (375), contrib, jwp, the joule-code-* crates and the st2110 --check passed. Ten runs of those ten checks: 50 of 100 failed on the iMac Pro (x86_64 macOS, load 4 to 7 on 20 cores) and 50 of 100 on Thor (aarch64 Linux, load 118 to 164 on 14 cores), the same five checks every run. Load plays no part; the build's order does. The committed files are right (every object in key order); the writers were wrong. The essence and telephony gateways also signed their origin claims and digested their statements over that order, where essence-contribution §10.7 and telephony-bridge §11 ask for RFC 8785, and their catalogs, which live-ingest §4.3 has in RFC 8785 canonical JSON, came out with `wai_tempo` last. - joule-moq-mls: `to_bytes` writes through joule_moq::json_order::pretty; `generate` leaves the order to it; a test holds the writer to key order for members inserted out of it. - joule-moq-ingest: the injection and eRTMP corpora, the link and telephony generators, the catalogs (its own copy of the sort gives way to json_order::sorted), st2110::session::{catalog_bytes, statement_digest, origin_claim} and the telephony bridge's three of the same, through json_order. The tests that verify a claim verify it over that text; the claim's members are compared as a set. - joule-moq-interop: the publisher's catalog in key order, and tests/catalogs/wai-receipts.json compared byte for byte (it was compared as a value, its doc noting the order followed the build). - joule_moq::json_order::check_the_order_the_job_expects: the positive control each crate's tests call; under JOULE_MOQ_EXPECT_INSERTION_ORDER=1 it fails a build that keeps key order. - joule-code-conformance.yml: joule-moq-mls (tests and `verify`) and joule-moq-interop with serde_json/preserve_order in the conformance job; joule-moq-ingest with it and `st2110 telephony rist`, every target and the three generators' --check, in the ingest job; each with the variable set. No committed file changes. The crates whose own builds keep insertion order always (joule-moq-room, -http, -remote, -agent, through wai-rs) are not changed: their samples are in that order, and sorting would change committed bytes. After, the same runs: 0 of 100 on the iMac Pro, 0 of 100 on Thor (load 21 to 24), 0 of 100 on the Framework (load 134 to 141 on 32 cores); there the whole preserve_order sweep passes (ingest 151, 188, 157 and 155 in its four sets, mls 21, interop 7, relay 375, the three --check and the e2ee verify), and the new steps as written pass on x86_64 and aarch64 (mls 21, interop 7, ingest 196, the three --check). Planted and caught, each in the new steps and passed by the default build: json_order::sorted without its sort fails ingest's injection, eRTMP and essence-catalog tests, all three --check, mls's two corpus tests and its verify, and interop's sample (default: ingest 196, mls 21 pass); mls's to_bytes as it was fails its two corpus tests and verify; the essence claim signed over the build's order fails the claim test and uncompressed_essence_reaches_a_relay_subscriber_with_every_receipt; the essence catalog in the build's order fails the_sample_catalog_is_what_a_session_writes. The step's feature left out with the variable set, the positive control fails in mls, ingest and interop. Gates, toolchain 1.99.1, on the Framework, rebased on15e9c93a0(df95cfd06, on top, changes only the relay's delivery_gap test): joule-moq-ingest --all-targets in its default, rist, st2110 and telephony sets (151, 155, 188, 157) and the three --check; the new steps as written (198, the three --check, mls 21 and its verify, interop 7); joule-moq with "serde_json/preserve_order quic qlog secure-objects" and the variable (279); joule-moq-mls (21); joule-moq-interop --all-targets (7); clippy -D warnings --all-targets for joule-moq-ingest (default, "st2110 telephony rist", and with preserve_order), joule-moq-interop and joule-moq-mls. On32a071ccc(joule-moq is the same on both): joule-moq --features quic --all-targets (266), its secure_objects lib tests (8), clippy for joule-moq (default, quic, the preserve_order set) and joule-moq-mls with preserve_order, joule-moq for wasm32 without default features. The repository's checks and both name lists exit 0; wai breaking against15e9c93a0finds no unacknowledged break. Co-Authored-By: Claude Opus 5.5 <[email protected]>
77 lines
3.8 KiB
Rust
77 lines
3.8 KiB
Rust
//! WAI `live-conformance/e2ee/` (confidentiality §14): the committed `vectors.json`
|
|
//! regenerates byte for byte from its fixed seeds, the reference gives every vector its
|
|
//! stated outcome, and §13 of the extension lists exactly the reference's codes.
|
|
|
|
use joule_moq::secure_objects::Refusal;
|
|
use joule_moq_mls::corpus;
|
|
|
|
const DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../../wai/live-conformance/e2ee/");
|
|
const SPEC: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../../wai/extensions/confidentiality.md");
|
|
|
|
#[test]
|
|
fn the_committed_corpus_regenerates_byte_for_byte() {
|
|
let on_disk = std::fs::read(format!("{DIR}{}", corpus::FILE)).expect("vectors.json");
|
|
assert!(on_disk == corpus::to_bytes(&corpus::generate()), "vectors.json is not what the generator writes; run joule_moq_e2ee_vectors gen");
|
|
}
|
|
|
|
/// The writer's key order is its own: members inserted out of key order, at every depth,
|
|
/// come out in key order. In a build where serde_json keeps insertion order (the
|
|
/// conformance job runs this crate in one) a writer that left the order to the build would
|
|
/// fail here and in the test above.
|
|
#[test]
|
|
fn the_writer_puts_every_object_in_key_order() {
|
|
let mut inner = serde_json::Map::new();
|
|
inner.insert("z".into(), serde_json::json!(0));
|
|
inner.insert("y".into(), serde_json::json!(1));
|
|
let mut outer = serde_json::Map::new();
|
|
outer.insert("b".into(), serde_json::Value::Array(vec![serde_json::Value::Object(inner)]));
|
|
outer.insert("a".into(), serde_json::json!(2));
|
|
let text = String::from_utf8(corpus::to_bytes(&serde_json::Value::Object(outer))).unwrap();
|
|
assert_eq!(text, "{\n \"a\": 2,\n \"b\": [\n {\n \"y\": 1,\n \"z\": 0\n }\n ]\n}\n");
|
|
}
|
|
|
|
/// The positive control of the conformance job's step that runs these tests in a build with
|
|
/// serde_json's `preserve_order`: with `JOULE_MOQ_EXPECT_INSERTION_ORDER=1` the build must
|
|
/// keep members in the order they were inserted.
|
|
#[test]
|
|
fn the_build_keeps_the_order_its_job_says() {
|
|
joule_moq::json_order::check_the_order_the_job_expects();
|
|
}
|
|
|
|
#[test]
|
|
fn the_reference_gives_every_vector_its_outcome() {
|
|
let v: serde_json::Value = serde_json::from_slice(&std::fs::read(format!("{DIR}{}", corpus::FILE)).unwrap()).unwrap();
|
|
corpus::check(&v).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn a_mutated_corpus_fails() {
|
|
let v: serde_json::Value = serde_json::from_slice(&std::fs::read(format!("{DIR}{}", corpus::FILE)).unwrap()).unwrap();
|
|
for (path, value) in [
|
|
("/records/0/outcome", serde_json::json!("bad_signature")),
|
|
("/objects/0/derived/nonce", serde_json::json!("00")),
|
|
("/objects/6/outcome", serde_json::json!("opened")),
|
|
("/rooms/0/deliveries/0/deliveries/0/outcome", serde_json::json!("bad_tag")),
|
|
("/rooms/0/rosters/1/roster/1", serde_json::json!("00")),
|
|
("/rooms/1/outcome/refuse", serde_json::json!("commit_mismatch")),
|
|
("/key_schedule/0/moq_key", serde_json::json!("00")),
|
|
] {
|
|
let mut m = v.clone();
|
|
*m.pointer_mut(path).unwrap_or_else(|| panic!("{path}")) = value;
|
|
assert!(corpus::check(&m).is_err(), "{path} mutated and still checks");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn section_13_lists_exactly_the_references_codes() {
|
|
let spec = std::fs::read_to_string(SPEC).unwrap();
|
|
let start = spec.find("## 13. Reasons").expect("§13");
|
|
let end = spec[start..].find("\n## ").map(|e| start + e).unwrap_or(spec.len());
|
|
let mut listed = Vec::new();
|
|
for line in spec[start..end].lines().filter(|l| l.starts_with("| `")) {
|
|
let cell = line.split('|').nth(1).unwrap();
|
|
listed.extend(cell.split('`').skip(1).step_by(2).map(str::to_string));
|
|
}
|
|
let codes: Vec<String> = Refusal::ALL.iter().map(|r| r.code().to_string()).collect();
|
|
assert_eq!(listed, codes, "§13's table and Refusal::ALL, in order");
|
|
}
|