All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 23s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 21s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 22s
wai-app and Create / wai-app · does this change touch it (push) Successful in 31s
WAI operation catalogue / does this change touch it (push) Successful in 20s
wai-audit collector and player integration / does this change touch it (push) Successful in 30s
wai-sink-swift native sink package / does this change touch it (push) Successful in 18s
wai-quantum studio and desktop / does this change touch it (push) Successful in 14s
wai-ledger service / does this change touch it (push) Successful in 45s
wai-fleet service / does this change touch it (push) Successful in 1m1s
wai-sink-android native sink library / does this change touch it (push) Successful in 19s
wai-quantum for JavaScript / does this change touch it (push) Successful in 51s
WAI Studio / does this change touch it (push) Successful in 52s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 2m14s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 5m12s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 5m30s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 7m13s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 27s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 41s
wai-audit collector and player integration / collector · linux (push) Successful in 2m30s
wai-ledger service / service · linux (push) Successful in 1m53s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m5s
wai-quantum studio and desktop / studio · browser (push) Successful in 4m38s
wai-quantum for JavaScript / javascript · linux (push) Successful in 3m51s
wai-fleet service / service · linux (push) Successful in 4m2s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 8m54s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 11m31s
WAI Studio / Studio in WebKit · linux (push) Successful in 13m53s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 9m58s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 1m1s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m17s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 8m46s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 5m29s
wai-ledger service / service · macos (push) Successful in 1m5s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 17m5s
wai-fleet service / service · macos (push) Successful in 44s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 8m34s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 18m3s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 12m15s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 1m46s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 9m10s
format-probes §14 to §17 add probes of basis `standard`, each read from its published text: - AV2 (`wai.video.av2`, AV2 v1.0.0): the length-delimited bitstream of Annex B, the OBU headers, the temporal delimiter a payload begins with, and the first sequence header OBU through its cropping window: profile, level, tier, chroma format, bit depth, maximum and cropped size, layer counts, with Annex A's reserved profiles and levels and each profile's chroma formats refused. No codec string: AOMedia's ISO BMFF binding is a working draft. - AC-4 (`wai.audio.ac4`, ETSI TS 103 190-1 V1.4.1 and -2 V1.3.1): sync frames with their CRC checked and the first table of contents to the first presentation's md_compat; the codec string of TS 103 190-2 E.13. - IAMF (`wai.audio.iamf`, IAMF v2.0.0): a standalone IA Sequence, its profiles, the order of its Descriptors, the Opus, FLAC and LPCM configurations as §3.13 constrains them, the start of each audio element; the codec string of §6.4. - IVAS (`wai.audio.ivas`, 3GPP TS 26.253 V19.3.0 Annex A): one IVAS payload's E and ToC bytes, frame sizes, processing information, padding and the first IVAS frame's format; `sivs` (TS 26.244 A.2.2). §18 says why EVC has no probe: its framing, NAL unit types and SPS are in ISO/IEC 23094-1, which is not openly readable, and RFC 9584 gives only the NAL unit header. §2.6 and platform-decode §2.5: the probes' `codec`, which browser-sink §4.2's gate refuses, so no user agent is asked; a native sink reads the string from the payload. SPEC §5 pins the payload forms of AC-4, IAMF and IVAS to what their probes read. Nothing is added to the floor or the recommended set. format-probe-conformance: hand-built vectors with known field values under gen/av2, gen/ac4, gen/iamf and gen/ivas, the texts' codec-string examples among them, and negative controls; format_probe_verify.py reads them with its own readers and requires the new planted faults (stated profiles, levels, versions, bit rates and codec strings, header bytes under restated digests) to fail, as tests/format_probe.rs requires of corpus::verify. Co-Authored-By: Claude Opus 5.5 <[email protected]>
136 lines
6.5 KiB
Rust
136 lines
6.5 KiB
Rust
//! Header probes (extensions/format-probes.md): the committed corpus `format-probe-conformance/`
|
|
//! regenerates byte for byte from its external vectors and holds no other generated file,
|
|
//! every vector, binding and fact gets its stated outcome, the extension's §2.4 and §12 tables
|
|
//! are the reference's probes and codes, and projection.md §4 lists only codes of §12.
|
|
#![cfg(feature = "format_probe")]
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::path::{Path, PathBuf};
|
|
use wai::format_probe::{corpus, Refusal, PROBES};
|
|
|
|
fn wai_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
|
}
|
|
|
|
fn walk(root: &Path, dir: &Path, out: &mut BTreeMap<String, Vec<u8>>) {
|
|
for e in std::fs::read_dir(dir).expect("a corpus directory").flatten() {
|
|
let p = e.path();
|
|
if p.is_dir() {
|
|
walk(root, &p, out);
|
|
} else {
|
|
let rel = p.strip_prefix(root).unwrap().to_string_lossy().replace('\\', "/");
|
|
out.insert(rel, std::fs::read(&p).unwrap());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn committed() -> BTreeMap<String, Vec<u8>> {
|
|
let root = wai_dir().join("format-probe-conformance");
|
|
let mut out = BTreeMap::new();
|
|
for sub in ["ext", "gen"] {
|
|
walk(&root, &root.join(sub), &mut out);
|
|
}
|
|
out.insert("vectors.json".into(), std::fs::read(root.join("vectors.json")).expect("vectors.json"));
|
|
out
|
|
}
|
|
|
|
#[test]
|
|
fn the_corpus_regenerates_and_every_outcome_holds() {
|
|
let files = committed();
|
|
assert!(files.keys().filter(|k| k.starts_with("ext/")).count() >= 9, "the external vectors");
|
|
let fails = corpus::verify(&files);
|
|
assert!(fails.is_empty(), "{}: run `wai_format_probe gen`", fails.join("\n"));
|
|
}
|
|
|
|
fn section(text: &str, heading: &str) -> String {
|
|
let at = text.find(&format!("\n{heading}")).unwrap_or_else(|| panic!("{heading}"));
|
|
let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e);
|
|
text[at..end].to_string()
|
|
}
|
|
|
|
/// §2.4 lists exactly the probes and bases of `PROBES`, in order; §12 exactly the codes of
|
|
/// `Refusal::ALL`, in order.
|
|
#[test]
|
|
fn the_extension_lists_the_probes_and_the_codes() {
|
|
let text = std::fs::read_to_string(wai_dir().join("extensions/format-probes.md")).expect("the extension");
|
|
let rows: Vec<(String, String)> = section(&text, "## 2. Common rules")
|
|
.lines()
|
|
.filter(|l| l.starts_with("| `wai."))
|
|
.map(|l| {
|
|
let cells: Vec<&str> = l.split('|').map(str::trim).collect();
|
|
(cells[1].trim_matches('`').to_string(), cells[2].to_string())
|
|
})
|
|
.collect();
|
|
let want: Vec<(String, String)> = PROBES.iter().map(|(c, b)| (c.to_string(), b.label().to_string())).collect();
|
|
assert_eq!(rows, want, "§2.4's table and PROBES differ");
|
|
let codes: Vec<String> = section(&text, "## 12. Refusals")
|
|
.lines()
|
|
.filter(|l| l.starts_with("| `"))
|
|
.filter_map(|l| l.split('`').nth(1).map(String::from))
|
|
.collect();
|
|
let want: Vec<String> = Refusal::ALL.iter().map(|r| r.code().to_string()).collect();
|
|
assert_eq!(codes, want, "§12's table and Refusal::ALL differ");
|
|
// projection.md states the codes its reader and its binding check give.
|
|
let proj = std::fs::read_to_string(wai_dir().join("extensions/projection.md")).expect("projection.md");
|
|
let checks = section(&proj, "## 4. Checks");
|
|
for r in [Refusal::NoSignature, Refusal::LengthMismatch, Refusal::ReservedValue, Refusal::DescriptorInvalid] {
|
|
assert!(checks.contains(&format!("`{}`", r.code())), "projection.md §4 does not state {}", r.code());
|
|
}
|
|
assert!(section(&proj, "## 5. Binding to the rendition").contains("`layer_absent`"));
|
|
}
|
|
|
|
fn sha256_hex(b: &[u8]) -> String {
|
|
use sha2::{Digest, Sha256};
|
|
Sha256::digest(b).iter().map(|x| format!("{x:02x}")).collect()
|
|
}
|
|
|
|
/// Planted faults in the AV2, AC-4, IAMF and IVAS vectors (format-probes §14 to §17): a
|
|
/// stated profile, level, version, bit rate or codec string changed, or a header byte changed
|
|
/// with the file's digest restated so that only the outcome can catch it. `corpus::verify`
|
|
/// must report each.
|
|
#[test]
|
|
fn planted_faults_in_the_av2_ac4_iamf_and_ivas_vectors_are_caught() {
|
|
use serde_json::{json, Value};
|
|
let files = committed();
|
|
let doc: Value = serde_json::from_slice(&files["vectors.json"]).unwrap();
|
|
let find = |doc: &Value, name: &str| doc["vectors"].as_array().unwrap().iter().position(|v| v["name"] == name).unwrap_or_else(|| panic!("{name}"));
|
|
let stated = [
|
|
("main420_10_1080p", "/outcome/accept/profile", json!(1)),
|
|
("main420_10_1080p", "/outcome/accept/level", json!("4.1")),
|
|
("main420_10_1080p", "/outcome/accept/codec", json!("av02.0.08M.10")),
|
|
("v2_crc_24fps", "/outcome/accept/codec", json!("ac-4.02.01.04")),
|
|
("v2_crc_24fps", "/outcome/accept/presentation/version", json!(2)),
|
|
("opus_simple", "/outcome/accept/codec", json!("iamf.000.000.opus")),
|
|
("opus_simple", "/outcome/accept/primary_profile", json!(1)),
|
|
("stereo_32k_two_frames_cmr", "/outcome/accept/codec", json!("sevs")),
|
|
("stereo_32k_two_frames_cmr", "/outcome/accept/frames/0/bitrate", json!(24400)),
|
|
];
|
|
let mut caught = 0;
|
|
for (name, ptr, value) in stated {
|
|
let mut d = doc.clone();
|
|
let i = find(&d, name);
|
|
*d["vectors"][i].pointer_mut(ptr).unwrap_or_else(|| panic!("{name}{ptr}")) = value;
|
|
let mut f = files.clone();
|
|
f.insert("vectors.json".into(), serde_json::to_vec_pretty(&d).unwrap());
|
|
assert!(!corpus::verify(&f).is_empty(), "{name}{ptr} restated, and nothing fails");
|
|
caught += 1;
|
|
}
|
|
// Header bytes: the AV2 profile's last bit, the AC-4 frame rate index, the IAMF primary
|
|
// profile, the IVAS ToC byte's bit rate.
|
|
let bytes = [("main420_10_1080p", 4, 0x04u8, false), ("v0_single_44k", 5, 0x01, false), ("opus_simple", 6, 0x01, true), ("stereo_32k_two_frames_cmr", 1, 0x07, false)];
|
|
for (name, at, x, set) in bytes {
|
|
let mut d = doc.clone();
|
|
let i = find(&d, name);
|
|
let file = d["vectors"][i]["file"].as_str().unwrap().to_string();
|
|
let mut f = files.clone();
|
|
let mut b = f[&file].clone();
|
|
b[at] = if set { x } else { b[at] ^ x };
|
|
d["vectors"][i]["sha256"] = json!(sha256_hex(&b));
|
|
f.insert(file, b);
|
|
f.insert("vectors.json".into(), serde_json::to_vec_pretty(&d).unwrap());
|
|
let fails = corpus::verify(&f);
|
|
assert!(fails.iter().any(|m| m.starts_with(&format!("{name}: stated"))), "{name} byte {at}: {fails:?}");
|
|
caught += 1;
|
|
}
|
|
assert_eq!(caught, 13);
|
|
}
|