wai: HTTP delivery — a checked head, verified byte-range fetch of one rendition, and cache conformance #170
180 changed files with 11860 additions and 74 deletions
69
.github/workflows/wai-conformance.yml
vendored
69
.github/workflows/wai-conformance.yml
vendored
|
|
@ -56,6 +56,11 @@ on:
|
|||
# A library test (staged::tests::staged_delivery_text_lists_the_codes) checks
|
||||
# that this extension's §4.2 and A.6 list the codes the sink reports.
|
||||
- 'wai/extensions/staged-delivery.md'
|
||||
# A library test (codecs::registry::tests::wire_identifiers_are_in_their_documents)
|
||||
# checks every wire identifier against the section of these texts it names.
|
||||
- 'wai/extensions/http-delivery.md'
|
||||
- 'wai/extensions/moq-streaming-format.md'
|
||||
- 'wai/extensions/jwp-receipts.md'
|
||||
- 'wai/wai-web/**'
|
||||
- '.github/workflows/wai-conformance.yml'
|
||||
pull_request:
|
||||
|
|
@ -81,6 +86,11 @@ on:
|
|||
# A library test (staged::tests::staged_delivery_text_lists_the_codes) checks
|
||||
# that this extension's §4.2 and A.6 list the codes the sink reports.
|
||||
- 'wai/extensions/staged-delivery.md'
|
||||
# A library test (codecs::registry::tests::wire_identifiers_are_in_their_documents)
|
||||
# checks every wire identifier against the section of these texts it names.
|
||||
- 'wai/extensions/http-delivery.md'
|
||||
- 'wai/extensions/moq-streaming-format.md'
|
||||
- 'wai/extensions/jwp-receipts.md'
|
||||
- 'wai/wai-web/**'
|
||||
- '.github/workflows/wai-conformance.yml'
|
||||
|
||||
|
|
@ -143,12 +153,16 @@ jobs:
|
|||
# of this build on every leg of this matrix, without a word. staged and the integer
|
||||
# media features build the staged-delivery core, its corpus tool and `wai stage`;
|
||||
# stage_measure builds the staged-measurement core and wai_stage_measure; telemetry
|
||||
# the delivery-telemetry mapping and wai_telemetry.
|
||||
run: cargo build --no-default-features --features world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,receipt_claims,staged,video_int,audio_int,splat_int,stage_measure,telemetry
|
||||
# the delivery-telemetry mapping and wai_telemetry; http the HTTP delivery binding,
|
||||
# its corpus tool and `wai http`.
|
||||
run: cargo build --no-default-features --features world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,receipt_claims,staged,video_int,audio_int,splat_int,stage_measure,telemetry,http
|
||||
# video_package runs the package receipt's dedup tests, which content_dedup feeds.
|
||||
# The dev profile keeps overflow checks on, so an arithmetic overflow in the HTTP
|
||||
# binding (http_binding::tests, among them a seeded test of every parser of untrusted
|
||||
# input) fails here rather than wrapping.
|
||||
- name: Library unit tests (the fixed-point determinism floors)
|
||||
working-directory: wai/wai-rs
|
||||
run: cargo test --lib --no-default-features --features world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,oer,receipt_claims,key_discovery,staged,video_int,audio_int,splat_int,stage_measure,telemetry,video_package
|
||||
run: cargo test --lib --no-default-features --features world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,oer,receipt_claims,key_discovery,staged,video_int,audio_int,splat_int,stage_measure,telemetry,video_package,http
|
||||
# The JPEG AI exact decoder reproduces across platforms only if no platform math
|
||||
# library is on its path. Two guards, each failing the step: clippy's
|
||||
# disallowed-methods (wai-rs/clippy.toml), which each module on the decode path
|
||||
|
|
@ -225,20 +239,21 @@ jobs:
|
|||
# The integer decode path builds under each of its features alone, so a sink can take
|
||||
# one medium without the others, and for wasm32 (a browser sink); `int_payload` arms a
|
||||
# medium only under its feature, and none needs `neural`. So does the staged-measurement
|
||||
# core (`stage_measure`), which pulls no ONNX and reads no file on wasm32.
|
||||
# core (`stage_measure`), which pulls no ONNX and reads no file on wasm32. The HTTP
|
||||
# binding (`http`) performs no I/O, so it builds alone and for wasm32 too.
|
||||
- name: Each integer feature alone, and wasm32 (unix)
|
||||
if: matrix.os != 'windows'
|
||||
shell: bash
|
||||
working-directory: wai/wai-rs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for f in neural_int video_int audio_int splat_int staged stage_measure telemetry; do
|
||||
for f in neural_int video_int audio_int splat_int staged stage_measure telemetry http; do
|
||||
cargo check --lib --no-default-features --features "$f"
|
||||
done
|
||||
# The decode bench prints its machine line from bench_env; no other step builds it.
|
||||
cargo build --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int --bin wai_decode_bench
|
||||
rustup target add --toolchain 1.98.0 wasm32-unknown-unknown
|
||||
cargo check --lib --target wasm32-unknown-unknown --no-default-features --features video_int,audio_int,splat_int,staged
|
||||
cargo check --lib --target wasm32-unknown-unknown --no-default-features --features video_int,audio_int,splat_int,staged,http
|
||||
# The staged-measurement core is wasm32-clean: a browser computes the same figures.
|
||||
# Built, not only checked, so codegen and linking of the library run for wasm32.
|
||||
cargo build --lib --target wasm32-unknown-unknown --no-default-features --features stage_measure
|
||||
|
|
@ -254,6 +269,14 @@ jobs:
|
|||
- name: Carriage conformance and the wai CLI (wrap, registry export)
|
||||
working-directory: wai/wai-rs
|
||||
run: cargo test --no-default-features --test carriage --test manifest_builders --test cli_wrap_intent --test cli_registry
|
||||
# tests/cli_http.rs runs `wai http publish`, `codecs`, `origin` and `bench` as a user
|
||||
# does: objects named by their content hash with their head proofs, a sequence's union
|
||||
# and floor, the origin's headers, ranges and If-Range on loopback, and a fetch by
|
||||
# the byte-range procedure whose record states energy as not measured. Cargo.toml
|
||||
# declares it with required-features = ["http"].
|
||||
- name: The wai http commands
|
||||
working-directory: wai/wai-rs
|
||||
run: cargo test --no-default-features --features http --test cli_http
|
||||
# The machine-readable registry (wai/registry/, wai/vocab/v2.*, the table in
|
||||
# wai/REGISTRY.md) is generated from wai-rs/src/codecs/registry.rs. Regenerate it in
|
||||
# memory and fail on any difference; the message says how to regenerate. One leg: the
|
||||
|
|
@ -314,7 +337,7 @@ jobs:
|
|||
working-directory: wai/wai-rs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
F=world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,quantum,quantum_ops,oer,receipt_claims,key_discovery,video_int,audio_int,splat_int,staged,stage_measure,telemetry
|
||||
F=world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,quantum,quantum_ops,oer,receipt_claims,key_discovery,video_int,audio_int,splat_int,staged,stage_measure,telemetry,http
|
||||
v() { echo "::group::$1"; cargo run -q --no-default-features --features "$F" --bin "$2" -- verify "$3"; echo "::endgroup::"; }
|
||||
v worlds wai_world_vectors ../world-conformance
|
||||
v spatial-audio wai_audio_vectors ../audio-conformance
|
||||
|
|
@ -334,6 +357,7 @@ jobs:
|
|||
v signers wai_signer_vectors ../signer-conformance
|
||||
v int-payload wai_int_payload_vectors ../int-payload-conformance
|
||||
v staged wai_staged_vectors ../staged-conformance
|
||||
v http wai_http_vectors ../http-conformance
|
||||
v staged-measure wai_stage_measure ../staged-measure-conformance
|
||||
v telemetry wai_telemetry ../telemetry-conformance
|
||||
# The pin corpus's second implementation: Python standard library only, sharing no
|
||||
|
|
@ -364,7 +388,7 @@ jobs:
|
|||
shell: pwsh
|
||||
working-directory: wai/wai-rs
|
||||
run: |
|
||||
$F = "world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,quantum,quantum_ops,oer,receipt_claims,key_discovery,video_int,audio_int,splat_int,staged,stage_measure,telemetry"
|
||||
$F = "world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,quantum,quantum_ops,oer,receipt_claims,key_discovery,video_int,audio_int,splat_int,staged,stage_measure,telemetry,http"
|
||||
function v($grp, $bin, $path) {
|
||||
Write-Output "::group::$grp"
|
||||
cargo run -q --no-default-features --features $F --bin $bin -- verify $path
|
||||
|
|
@ -389,6 +413,7 @@ jobs:
|
|||
v signers wai_signer_vectors ../signer-conformance
|
||||
v int-payload wai_int_payload_vectors ../int-payload-conformance
|
||||
v staged wai_staged_vectors ../staged-conformance
|
||||
v http wai_http_vectors ../http-conformance
|
||||
v staged-measure wai_stage_measure ../staged-measure-conformance
|
||||
v telemetry wai_telemetry ../telemetry-conformance
|
||||
|
||||
|
|
@ -422,7 +447,8 @@ jobs:
|
|||
# signer_verify.py (every key-discovery vector and the canonical fixture, with its
|
||||
# verdicts required to equal wai_signer_vectors' line for line), and
|
||||
# int_payload_verify.py (the integer payloads and their refusal fixtures), and
|
||||
# staged_verify.py (the staged-delivery corpus).
|
||||
# staged_verify.py (the staged-delivery corpus), and http_verify.py (the HTTP delivery
|
||||
# corpus).
|
||||
# Each exits non-zero on any failure or any difference, and the step fails with it.
|
||||
# One platform: what they check is the reading of the text, not the architecture.
|
||||
independent-verifiers:
|
||||
|
|
@ -476,6 +502,31 @@ jobs:
|
|||
working-directory: wai
|
||||
shell: bash
|
||||
run: python3 staged-conformance/staged_verify.py staged-conformance
|
||||
# The HTTP delivery corpus's second implementation (http-conformance/README): the head,
|
||||
# the head proof (every committed proof rebuilt from the text), response checks, plans,
|
||||
# selection, the byte-range procedure and its fallback, and the codecs union and floor,
|
||||
# checked first against published BLAKE3 vectors. Standard library only. Its
|
||||
# mutations and the differential against wai-rs run in wai-http.yml's falsify job.
|
||||
- name: http_verify.py
|
||||
working-directory: wai
|
||||
shell: bash
|
||||
run: python3 http-conformance/http_verify.py http-conformance
|
||||
# Outside the corpus: wai_http_vectors fuzz writes seeded cases (Content-Range values,
|
||||
# codecs values, #WAI-LAYER lines, manifests and their member types, encryption,
|
||||
# refines, layers declared against their bases, layers over WAI2 components, objects
|
||||
# past the first request through a tampering origin) with wai-rs's outcomes, and
|
||||
# http_verify.py must give every one, a refusal by the same code. It prints each
|
||||
# family's count of cases and of refusals. Two fixed seeds, so a run is reproducible.
|
||||
- name: The HTTP differential outside the corpus (wai-rs against http_verify.py)
|
||||
working-directory: wai
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
(cd wai-rs && cargo build --no-default-features --features http --bin wai_http_vectors)
|
||||
for seed in 11 12; do
|
||||
wai-rs/target/debug/wai_http_vectors fuzz http-conformance --seed "$seed" --count 400 --out "$RUNNER_TEMP/http-fuzz-$seed.json"
|
||||
python3 http-conformance/http_verify.py cases "$RUNNER_TEMP/http-fuzz-$seed.json" http-conformance
|
||||
done
|
||||
# The staged-measurement corpus's second implementation (staged-measure-conformance/README):
|
||||
# it counts every object, applies every layer from its base's stage 0, computes the quality
|
||||
# integers, the output tree, the prefixes, the overhead points and the dedup totals itself,
|
||||
|
|
|
|||
157
.github/workflows/wai-http.yml
vendored
Normal file
157
.github/workflows/wai-http.yml
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
name: WAI HTTP delivery
|
||||
|
||||
# extensions/http-delivery.md: two jobs beside the corpus checks in wai-conformance.yml.
|
||||
#
|
||||
# falsify — the HTTP corpus tool and its independent verifier must catch defects. Each
|
||||
# patch in wai/http-conformance/falsify/ breaks one function of wai-rs (the content
|
||||
# hash's domain, the root flag, the left-subtree rule, the chunk counter, the component
|
||||
# digest check, the Content-Range and content-coding checks, ASCII-only numbers, the
|
||||
# union, layers' place in it and their dispatch, the wai prefix's case, duplicate
|
||||
# capabilities, a record beside a fallback, the trailing-byte rule, the missing-digest
|
||||
# plan, the class merge, the base-only threshold, refused refinement responses, stale
|
||||
# bytes after a mismatch, an unanswered base, the selection digest test); it
|
||||
# is applied to a fresh worktree, the build must succeed, and `wai_http_vectors verify`
|
||||
# must fail naming the cases on the patch's first line. Then each `http_verify.py
|
||||
# --mutate` must fail, and the differential: wai-rs writes random envelopes' covered
|
||||
# prefixes and head proofs, the Python verifier accepts every one, and refuses exactly
|
||||
# the ten it changes one chaining-value byte in.
|
||||
#
|
||||
# cache-conformance — the cache rules (§9). wai/cache-conformance/run.sh stages the
|
||||
# corpus's objects behind the reference origin (`wai http origin`) and runs driver.py
|
||||
# against the origin, which must pass every MUST test, and against four negative-control
|
||||
# proxies, which must each fail the tests their defect reaches. Labelled caches run only
|
||||
# where the environment provides them (/opt/wai-cache); this repository holds none, and
|
||||
# the job summary says when none ran. The reports are uploaded as an artifact.
|
||||
#
|
||||
# No workflow_dispatch: it does not start runs on this forge. A weekly schedule keeps the
|
||||
# jobs exercised between changes.
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'wai/wai-rs/src/http_binding/**'
|
||||
- 'wai/wai-rs/src/container.rs'
|
||||
- 'wai/wai-rs/src/jwp_hash.rs'
|
||||
- 'wai/wai-rs/src/bin/wai.rs'
|
||||
- 'wai/wai-rs/src/bin/wai_http_vectors.rs'
|
||||
- 'wai/wai-rs/src/bin/wai_cli/http.rs'
|
||||
- 'wai/wai-rs/src/bin/wai_cli/http_vectors.rs'
|
||||
- 'wai/wai-rs/Cargo.toml'
|
||||
- 'wai/http-conformance/**'
|
||||
- 'wai/cache-conformance/**'
|
||||
- 'wai/claim-conformance/_blake3.py'
|
||||
# http_verify.py reads staged_verify.py (and through it int_payload_verify.py), and
|
||||
# the corpus carries the staged ladder's base and layers.
|
||||
- 'wai/staged-conformance/staged_verify.py'
|
||||
- 'wai/staged-conformance/cases/audio_flac_rate_ladder/**'
|
||||
- 'wai/int-payload-conformance/int_payload_verify.py'
|
||||
- 'wai/registry/**'
|
||||
- '.github/workflows/wai-http.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'wai/wai-rs/src/http_binding/**'
|
||||
- 'wai/wai-rs/src/container.rs'
|
||||
- 'wai/wai-rs/src/jwp_hash.rs'
|
||||
- 'wai/wai-rs/src/bin/wai.rs'
|
||||
- 'wai/wai-rs/src/bin/wai_http_vectors.rs'
|
||||
- 'wai/wai-rs/src/bin/wai_cli/http.rs'
|
||||
- 'wai/wai-rs/src/bin/wai_cli/http_vectors.rs'
|
||||
- 'wai/wai-rs/Cargo.toml'
|
||||
- 'wai/http-conformance/**'
|
||||
- 'wai/cache-conformance/**'
|
||||
- 'wai/claim-conformance/_blake3.py'
|
||||
# http_verify.py reads staged_verify.py (and through it int_payload_verify.py), and
|
||||
# the corpus carries the staged ladder's base and layers.
|
||||
- 'wai/staged-conformance/staged_verify.py'
|
||||
- 'wai/staged-conformance/cases/audio_flac_rate_ladder/**'
|
||||
- 'wai/int-payload-conformance/int_payload_verify.py'
|
||||
- 'wai/registry/**'
|
||||
- '.github/workflows/wai-http.yml'
|
||||
schedule:
|
||||
- cron: '17 4 * * 1'
|
||||
|
||||
jobs:
|
||||
falsify:
|
||||
name: HTTP corpus · falsification and differential
|
||||
runs-on: linux
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust 1.98.0
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
command -v rustup >/dev/null 2>&1 || \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
|
||||
rustup toolchain install 1.98.0 --profile minimal
|
||||
rustup default 1.98.0
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
# One target directory for every patched build, so only the wai crate rebuilds.
|
||||
- name: Every patch is caught; every mutation is caught; the differential holds
|
||||
shell: bash
|
||||
env:
|
||||
CARGO_TARGET_DIR: ${{ runner.temp }}/falsify-target
|
||||
run: |
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
bash wai/http-conformance/falsify/run.sh "$RUNNER_TEMP/falsify"
|
||||
|
||||
cache-conformance:
|
||||
name: cache rules · controls and labelled caches
|
||||
runs-on: linux
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust 1.98.0
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
command -v rustup >/dev/null 2>&1 || \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
|
||||
rustup toolchain install 1.98.0 --profile minimal
|
||||
rustup default 1.98.0
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Build the reference origin
|
||||
working-directory: wai/wai-rs
|
||||
shell: bash
|
||||
run: cargo build --release --no-default-features --features http --bin wai
|
||||
|
||||
# The verdict: run.sh exits non-zero when the origin fails a MUST test, a control is
|
||||
# not caught, or a labelled cache fails one.
|
||||
- name: The origin passes, the controls are caught, labelled caches report
|
||||
shell: bash
|
||||
run: WAI="$GITHUB_WORKSPACE/wai/wai-rs/target/release/wai" bash wai/cache-conformance/run.sh "$RUNNER_TEMP/cache"
|
||||
|
||||
# The evidence in the log, whatever becomes of the upload: each report's path and
|
||||
# SHA-256, and the summary.
|
||||
- name: The reports, in the log
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
cd "$RUNNER_TEMP/cache/reports" 2>/dev/null || { echo "no reports were written"; exit 0; }
|
||||
for f in *; do printf '%s %s\n' "$(sha256sum "$f" | cut -d' ' -f1)" "$RUNNER_TEMP/cache/reports/$f"; done
|
||||
cat summary.md 2>/dev/null || true
|
||||
|
||||
# An upload this forge cannot take must not decide the job: the verdict is the step
|
||||
# above it.
|
||||
- name: Upload the reports
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cache-conformance-reports
|
||||
path: ${{ runner.temp }}/cache/reports
|
||||
100
wai/CHANGELOG.md
100
wai/CHANGELOG.md
|
|
@ -6,6 +6,106 @@ wire format may change and such changes are recorded here.
|
|||
|
||||
## v0.4.0 — current
|
||||
|
||||
### HTTP delivery (Draft)
|
||||
|
||||
- **The extension** [http-delivery](extensions/http-delivery.md) binds envelopes to
|
||||
HTTP, with no envelope byte and no manifest member changed:
|
||||
- the proposed media type `application/wai`, not registered;
|
||||
- `codecs` elements `wai1.`/`wai2.` that list the exhaustive union of the
|
||||
capabilities a variant may dispatch to or apply, with the floor every object of it
|
||||
offers carried separately (`X-WAI-FLOOR`, a DASH `SupplementalProperty`);
|
||||
- objects named by their receipt content hash (`<cid>.wai`), immutable, with no
|
||||
content coding and no `Vary`, and exactly one envelope each;
|
||||
- the `WHP1` head proof: the BLAKE3 chaining values that prove an envelope's head
|
||||
against its content hash without the rest of the object;
|
||||
- verified range fetch of one `WAI2` component that picks as whole-envelope
|
||||
selection does, and falls back to the whole object when a pick has no digest, a
|
||||
digest fails, or the plan needs most of the object;
|
||||
- receipt, key, grant, parameter-set and playlist sidecars; cache rules; priorities,
|
||||
deadlines and the carriage of enhancement companions, refinement components and
|
||||
layer objects ([staged-delivery](extensions/staged-delivery.md));
|
||||
- HLS and DASH adjacency for on-demand, live and low-latency live; a capability
|
||||
probe that never asks a user agent about a WAI element.
|
||||
- **jwp-receipts**: §3 step 4 accepts the content hash a verified head proof
|
||||
establishes for an object fetched in part, and checks a receipt about a component's
|
||||
exploded envelope over the envelope the verifier builds. New §4.4 (objects not
|
||||
delivered) and §4.5 (an HTTP cache is not a relay). §1 and §7.9 point to the HTTP
|
||||
locations. moq-streaming-format §6 names the HTTP binding.
|
||||
- **SPEC**: §2 and §7.1 name the HTTP object and the `WAI2` head; the
|
||||
`wai.video.manifest` row admits `render` and `extras` on a playlist receipt.
|
||||
- **The registry's wire identifiers.** `codecs::registry::WIRE_IDENTIFIERS` lists the
|
||||
names the container, the two delivery bindings and key discovery put on the wire
|
||||
outside a manifest, each with its kind, defining section and status; `wai registry
|
||||
export` writes them to `registry/capabilities.json` (`wire_identifiers`) and the table
|
||||
in `REGISTRY.md`, and a library test checks each against the section it names.
|
||||
- **The reference binding** (`wai-rs`, feature `http`): `http_binding::{head, head_proof,
|
||||
plan, session, codecs, origin}` — the head and §5.1, the `WHP1` proof built and checked
|
||||
with BLAKE3's tree, the root checked before any covered byte is read as a head; plans by
|
||||
priority class; a sans-IO fetch session with §7.1's response checks, component digests,
|
||||
the whole-object fallback and refinement deadlines; the `codecs` grammar, union, floor
|
||||
and support; an origin's headers and Range answers. `container::select_entries` is SPEC
|
||||
§7.1's walk with the digest test passed in, which `WaiMulti::pick` and a client holding
|
||||
only the head both run, and `companion_indices` / `refinement_indices` bind by entry.
|
||||
The `wai` tool gains `wai http publish|codecs|origin|vectors|bench`.
|
||||
- **The corpus** [`http-conformance/`](http-conformance/): objects with their head proofs
|
||||
and ETag sidecars (a pinned learned image beside AVIF, JPEG XL and PNG of the one
|
||||
picture; AV1 at two rates with HDR10+ bound by id; a missing and a wrong digest; an AVC
|
||||
base with an LCEVC enhancement; a segment sequence; Appendix D's envelope; a head of
|
||||
several chunks) and cases for §4–§7. `wai_http_vectors verify` and the independent
|
||||
`http_verify.py` (standard library, written from the text, checked first against
|
||||
published BLAKE3 vectors) agree on every case. Falsification patches and Python
|
||||
mutations each must be caught, and a differential checks wai-rs's proofs of random
|
||||
envelopes in Python (`.github/workflows/wai-http.yml`).
|
||||
- **Cache conformance** [`cache-conformance/`](cache-conformance/): a driver for §9's
|
||||
rules, run against the reference origin (which must pass), against four
|
||||
negative-control proxies (which must each be caught) and against labelled caches where
|
||||
an environment provides them; reports name a cache by label, version and configuration
|
||||
digest only, and CI uploads them.
|
||||
- **What review found, fixed** (http-delivery §3.1, §4, §7, §7.1, §10, §13, Appendix D):
|
||||
- a layer object (a `WAI1` whose capability is refinement-only) is never dispatched:
|
||||
it is fetched whole at refinement urgency, verified against its cid and handed to
|
||||
staged delivery, and joins a union after `+` through `refines.base`
|
||||
(`not_a_layer`, `codecs_layer_unbound`, `layer_tag_malformed`). A `WAI1` whose
|
||||
capability is a payload is an ordinary object even when it carries `refines`: it is
|
||||
dispatched, described by its capability, and `not_a_layer` when listed as a layer.
|
||||
A layer is declared only in the `alt` of its own base's capability;
|
||||
- a refinement never moves its base: only base-class bytes count toward the 90 %
|
||||
rule, a refinement is always requested on its own, a refinement with no digest or a
|
||||
failed one is dropped alone on the range path and on the whole path alike, and a
|
||||
refused refinement response abandons only it;
|
||||
- a client presents a base as soon as it verifies and applies its refinements as they
|
||||
arrive; a refinement's deadline runs from that moment on the caller's clock
|
||||
(`FetchSession::base_ready`, `tick`), and `wai http bench` reports the time to the
|
||||
verified base apart from the time to the complete fetch;
|
||||
- after a component mismatch every byte past `covered_len` is discarded before the
|
||||
rest is fetched again; a base request never answered is `base_unanswered`;
|
||||
- `Content-Range` numbers are ASCII digits only; an element beginning `wai` in any
|
||||
case must be a well-formed WAI element; an empty element or a capability named
|
||||
twice is malformed; a `WAI1` naming a record beside a payload fallback is described
|
||||
by its fallback; a `codecs` value with non-ASCII text, and a head proof whose lengths
|
||||
reach the end of 64 bits, are refused rather than panicking;
|
||||
- the reference origin caps what it reads of a request head (16 KiB); gives the whole
|
||||
head a deadline from the connection's accept (default 10 s, `--read-timeout-ms`),
|
||||
answered by 408 however slowly its bytes trickle in; bounds each connection's whole
|
||||
life from its accept, writing included (default 60 s, `--max-connection-ms`), so a
|
||||
peer reading a large response slowly is cut off then; serves at most 64 connections
|
||||
at once (`--max-connections`), answering 503 to one more, with a cap per peer
|
||||
address only when `--max-per-peer` is given (it listens on 127.0.0.1, where every
|
||||
client has one address, and a default cap would let a few reconnecting clients shut
|
||||
the rest out); decodes `/__hits` as bytes; and refuses `\`, `:` and NUL in a path.
|
||||
The wire-identifier test reads each name in its section's body.
|
||||
- a verified base is decided: a component that verified is never read again, and of a
|
||||
refinement's response only the range requested is kept, so a refinement answered
|
||||
`200` with the whole object, a byte changed, never reaches the base. Every response
|
||||
carries the caller's clock (`FetchSession::on_response(…, now_ms)`), the base is
|
||||
stamped with the time of the response that verified it, and the deadline's
|
||||
arithmetic saturates. The corpus's `fetch_timed` cases run the session on a clock;
|
||||
`http_verify.py` agrees on them.
|
||||
The corpus gains a case for each, and `http_verify.py` agrees with wai-rs on them and
|
||||
on a seeded differential outside the corpus, in which a refusal must match by its
|
||||
code and each family reports how many cases it generated and how many were refused.
|
||||
- No envelope byte or receipt byte changes for existing material.
|
||||
|
||||
### Packaging: a declared policy, its digest, and pieces cut at scene changes
|
||||
|
||||
- **The extension** [packaging](extensions/packaging.md), first sections: a package is
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ together with the class functions of [`wai-rs/src/codecs/mod.rs`](wai-rs/src/cod
|
|||
|
||||
| file | what |
|
||||
|---|---|
|
||||
| [`registry/capabilities.json`](registry/capabilities.json) | every registered string with its kind, status, media class, role, fidelity tier, licence class, replicate class, set membership, registering section, edition and registration date; the named identifiers that are not capabilities; the unregistered labels |
|
||||
| [`registry/capabilities.json`](registry/capabilities.json) | every registered string with its kind, status, media class, role, fidelity tier, licence class, replicate class, set membership, registering section, edition and registration date; the named identifiers that are not capabilities; the unregistered labels; the wire identifiers |
|
||||
| [`registry/capabilities.schema.json`](registry/capabilities.schema.json) | JSON Schema (2020-12) for that file; its enums are the label sets below |
|
||||
| [`vocab/v2.ttl`](vocab/v2.ttl), [`vocab/v2.jsonld`](vocab/v2.jsonld), [`vocab/v2.shapes.ttl`](vocab/v2.shapes.ttl) | the same registry as RDF, a JSON-LD context for manifests, and a SHACL shape that checks a manifest's capability and fallback against it, only the form of its media, and its parameter-set pins (`vocab/README.md`); a violation for a string the registry does not hold means the string is not registered in this revision, not that the envelope fails to conform or cannot be dispatched (SPEC §4, §8) |
|
||||
| the table at the end of this file | the same registry, for reading |
|
||||
|
|
@ -54,6 +54,16 @@ Two further tables hold strings that look like capabilities and are not:
|
|||
- **unregistered labels** are strings the reference implementation uses as labels
|
||||
that no revision registers, each with the reason.
|
||||
|
||||
A third holds the **wire identifiers**: the names the container, its delivery
|
||||
bindings ([http-delivery](extensions/http-delivery.md),
|
||||
[moq-streaming-format](extensions/moq-streaming-format.md)) and the key discovery
|
||||
they serve ([jwp-receipts](extensions/jwp-receipts.md) §7) put on the wire outside a
|
||||
manifest — magic numbers, a media type, `codecs` prefixes, paths, playlist tags and
|
||||
attributes, document kinds and catalog fields — each with its kind, the section that
|
||||
defines it, and whether it is defined, provisional (test material only until
|
||||
registered) or proposed (not registered). A library test checks that each is written
|
||||
in the section it names.
|
||||
|
||||
## Status
|
||||
|
||||
- **registered** — the payload format (for a record or derivation, the
|
||||
|
|
@ -250,4 +260,38 @@ and a role are fixed once registered.
|
|||
| `wai.quantum.compile` | a receipt-kind label the wai-quantum crate seals; no revision of SPEC or of an extension registers it, so it is not a registered record |
|
||||
| `wai.quantum.rearrange` | a receipt-kind label the wai-quantum crate seals; no revision of SPEC or of an extension registers it, so it is not a registered record |
|
||||
| `wai.quantum.qbom` | a receipt-kind label the wai-quantum crate seals; no revision of SPEC or of an extension registers it, so it is not a registered record |
|
||||
|
||||
| wire identifier (not a capability) | kind | status | defined in |
|
||||
|---|---|---|---|
|
||||
| `#WAI-LAYER` | playlist-tag | defined | http-delivery §10.4 |
|
||||
| `.wai` | path | defined | http-delivery §5.2 |
|
||||
| `.wai.hp` | path | defined | http-delivery §6.2 |
|
||||
| `.wai.receipts` | path | defined | http-delivery §8.2 |
|
||||
| `/.well-known/wai-grants/` | well-known | defined | http-delivery §8.4 |
|
||||
| `/.well-known/wai-key-status` | well-known | defined | http-delivery §8.4 |
|
||||
| `/.well-known/wai-keys` | well-known | defined | jwp-receipts §7.1 |
|
||||
| `WAI1` | magic | defined | SPEC §2 |
|
||||
| `WAI2` | magic | defined | SPEC §7.1 |
|
||||
| `WAI_RECEIPT` | catalog | defined | moq-streaming-format §5 |
|
||||
| `WHP1` | magic | defined | http-delivery §6.2 |
|
||||
| `X-WAI-FLOOR` | playlist-attribute | defined | http-delivery §11.1 |
|
||||
| `application/wai` | media-type | proposed | http-delivery §3 |
|
||||
| `mr/` | path | defined | http-delivery §8.6 |
|
||||
| `rcpt/` | path | defined | http-delivery §8.1 |
|
||||
| `science.transaction.wai.multivariant` | data-id | defined | http-delivery §11.1 |
|
||||
| `tag:transaction.science,2026:wai/floor` | scheme | defined | http-delivery §11.1 |
|
||||
| `tag:transaction.science,2026:wai/http-delivery` | scheme | defined | http-delivery §11.1 |
|
||||
| `wai` | catalog | defined | moq-streaming-format §3 |
|
||||
| `wai-key-status` | document-kind | defined | jwp-receipts §7.9 |
|
||||
| `wai-module-grant` | document-kind | defined | jwp-receipts §7.9 |
|
||||
| `wai-module-grants` | document-kind | defined | http-delivery §8.4 |
|
||||
| `wai-receipt-index` | document-kind | defined | http-delivery §8.2 |
|
||||
| `wai1` | codecs-prefix | defined | http-delivery §4.1 |
|
||||
| `wai1` | fourcc | provisional | http-delivery Appendix B |
|
||||
| `wai2` | codecs-prefix | defined | http-delivery §4.1 |
|
||||
| `wai2` | fourcc | provisional | http-delivery Appendix B |
|
||||
| `waiC` | fourcc | provisional | http-delivery Appendix B |
|
||||
| `wai_capabilities` | catalog | defined | moq-streaming-format §4 |
|
||||
| `wai_determinism` | catalog | defined | moq-streaming-format §4 |
|
||||
| `wai_receipts` | catalog | defined | moq-streaming-format §4 |
|
||||
<!-- end generated -->
|
||||
|
|
|
|||
11
wai/SPEC.md
11
wai/SPEC.md
|
|
@ -86,6 +86,11 @@ OPTIONAL; §5 says which are RECOMMENDED.
|
|||
|
||||
All WAI multi-byte integers are little-endian unless stated otherwise.
|
||||
|
||||
An envelope delivered over HTTP is one object of the proposed media type
|
||||
`application/wai`, named by its receipt content hash, and fetched whole or, for a
|
||||
`WAI2`, one verified component at a time
|
||||
([http-delivery](extensions/http-delivery.md)).
|
||||
|
||||
---
|
||||
|
||||
## 3. Manifest schema
|
||||
|
|
@ -614,7 +619,7 @@ registration and makes no conforming envelope non-conforming (§8).
|
|||
| `wai.neural.dcvc_fm` | DCVC-FM (feature modulation), wide single-model quality range (RGB + YUV) | a wide single-model quality range; shares the inter-frame route | research code, `neural/video_dcvc.rs` |
|
||||
| `wai.video.int_motion` | DETERMINISTIC INTEGER inter-frame — I-frame + integer block motion-compensated P-frames + integer-quantized residual over rANS, no float/ONNX; motion is whole-pel or **half-pel** (`mv_precision`), the latter predicting at the half-grid by integer bilinear interpolation (`(a+b+1)>>1`, `(a+b+c+d+2)>>2`) so real footage — which pans a non-integer number of pixels per frame — costs fewer residual bits while staying byte-exact | byte-identical frame-by-frame on every machine (*frame-state-equivalence*, a per-frame portable hash); the temporal extension of the integer contract, where a float inter-frame decode desyncs down the GOP | payload `WIV1`, keyframe 0; `int_video.rs` (the encode tools write a JSON form a converter reads) |
|
||||
| `wai.video.int_hyper` | NEURAL-KEYFRAME inter-frame — the I-frame is the LEARNED integer codec `int_hyper` decoded byte-exact, the P-frames are integer block-MC residuals on top | a learned keyframe you can put in a receipt + integer inter-frames; whole decode integer, byte-identical everywhere | payload `WIV1`, keyframe 1 (a nested `WIH1`); `int_video::decode_pframes_typed` ∘ `int_hyper_synth::decode_conditional_checked` |
|
||||
| `wai.video.manifest` | ATTESTED PER-SESSION MANIFEST — the OTT delivery layer, not a codec: a personalised HLS manifest (ads stitched at `EXT-X-DISCONTINUITY`, blackouts + geo enforced) rendered as a **pure function of the session *class*** `(content, entitlement, geo, blackout set, ad pod, window)` rather than the viewer, and sealed in an Ed25519 receipt that binds the JCP grant (capability, joule ceiling, `funds_ceiling` = ad budget), the composition, the stitched ads and the policy applied | **manifest-equivalence**: `manifest_hash == hash(render(segments))` and `session_class_hash` are recomputable by anyone holding the bytes, so the ads, blackouts and compose-work stop being dashboard claims; class-determinism means one generation serves every viewer of the class, and `uj_per_viewer` is the amortised energy that a 1:1-unique generator cannot report below its per-viewer cost. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_manifest.rs` |
|
||||
| `wai.video.manifest` | ATTESTED PER-SESSION MANIFEST — the OTT delivery layer, not a codec: a personalised HLS manifest (ads stitched at `EXT-X-DISCONTINUITY`, blackouts + geo enforced) rendered as a **pure function of the session *class*** `(content, entitlement, geo, blackout set, ad pod, window)` rather than the viewer, and sealed in an Ed25519 receipt that binds the JCP grant (capability, joule ceiling, `funds_ceiling` = ad budget), the composition, the stitched ads and the policy applied. A receipt over a WAI playlist MAY carry `render` and `extras`, the rendering parameters and per-segment lines that playlist adds, both bound through `manifest_hash` ([http-delivery](extensions/http-delivery.md) §8.6) | **manifest-equivalence**: `manifest_hash == hash(render(segments))` and `session_class_hash` are recomputable by anyone holding the bytes, so the ads, blackouts and compose-work stop being dashboard claims; class-determinism means one generation serves every viewer of the class, and `uj_per_viewer` is the amortised energy that a 1:1-unique generator cannot report below its per-viewer cost. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_manifest.rs` |
|
||||
| `wai.video.ssai` | ATTESTED SERVER-SIDE AD INSERTION — a **deterministic** ad decision (greedy by price within the avail duration and the funds remaining) plus a per-impression Ed25519 receipt binding the candidate pod, the manifest it belongs to, the served `creative_hash`es and the VAST quartile each reached | **spend-equivalence**: `budget_spent_micros` is recomputed from the *billable* impressions and rejected if it exceeds the grant's `funds_ceiling` — an overspent receipt fails verification; every creative and quartile is checkable, so a buyer and seller reconcile impressions from the same bytes instead of two logs that only a dashboard joins. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_ssai.rs` |
|
||||
| `wai.video.keys` | ATTESTED CONTENT-KEY RELEASE — the DRM licensing decision as a record: every key release, and every refusal, is an Ed25519 receipt binding the KID, the DRM system, the entitlement demanded and the window enforced, with a BLAKE3 `key_commit` proving *which* key was released without carrying it | **decision-equivalence**: `verify()` re-runs the release policy over the receipt's own `(grant capability, entitlement, issued, expiry)` and rejects any misreported outcome, and enforces granted-iff-committed — so "keys went only to entitled, unexpired sessions" is checkable against the signed log, not asserted. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_keys.rs` |
|
||||
| `wai.video.package` | DETERMINISTIC PACKAGING + ATTESTED SEGMENTMAP — a source sliced into content-hashed segments on GOP boundaries; the ordered `(index → content_hash)` map is a signed Merkle commitment, and identical content (ads, bumpers, replays, simulcast overlap) dedups to one stored/encoded copy | **map-equivalence + verifiable dedup**: `segment_map_root` lets a `wai.video.manifest` reference be checked against the package that produced it, and `verify()` recomputes `unique_segments`/`stored_bytes` from the segments so the storage/encode saving cannot be overstated; `uj_per_unique_segment` is the encode-once energy. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_package.rs` |
|
||||
|
|
@ -1727,6 +1732,10 @@ by which capability happens to be installed.
|
|||
i-th entry corresponds to `renditions[i]` in the manifest.
|
||||
- Payloads block: the rendition byte ranges, packed contiguously.
|
||||
- `n_renditions` MUST equal the manifest's `renditions.length`.
|
||||
- The bytes from offset 0 to the end of the rendition table are the envelope's
|
||||
**head**. They give every component's byte range and, with each entry's `sha256`,
|
||||
its digest, so a sink can fetch and verify one component without the others
|
||||
([http-delivery](extensions/http-delivery.md) §7).
|
||||
|
||||
### Selection algorithm
|
||||
|
||||
|
|
|
|||
47
wai/cache-conformance/README.md
Normal file
47
wai/cache-conformance/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# cache-conformance — HTTP caches under http-delivery §9
|
||||
|
||||
Checks the cache rules of [http-delivery](../extensions/http-delivery.md) §9 against
|
||||
an HTTP cache that fronts an origin serving the objects of
|
||||
[`http-conformance/`](../http-conformance/), and reports, per cache label and version,
|
||||
whether the configuration meets each rule.
|
||||
|
||||
| file | what |
|
||||
|---|---|
|
||||
| `tests.json` | the tests: id, rule, level (MUST or informative) and what each checks |
|
||||
| `driver.py` | stages the origin's files and runs the tests against a cache; standard library only, with `../claim-conformance/_blake3.py` to check each object against its content hash |
|
||||
| `mutators.py` | four proxies that each break §9 in one way: the negative controls |
|
||||
| `run.sh` | stages, starts `wai http origin`, and runs the driver against the origin, each control and each labelled cache |
|
||||
|
||||
## What runs
|
||||
|
||||
1. **The origin, directly** (`origin-direct`): the reference origin
|
||||
(`wai http origin`) must pass every MUST test. It answers byte ranges and `If-Range`,
|
||||
serves a live-edge alias with a shorter `max-age`, sends §8.4's `Cache-Control` for
|
||||
the key log, and counts the requests it receives (`--count-hits`) so the informative
|
||||
tests can tell whether a cache answered from its store.
|
||||
2. **The negative controls** (`control-flip`, `control-gzip`, `control-truncate`,
|
||||
`control-vary`): the driver must fail, and must name the tests each defect reaches —
|
||||
`flip` C1, C3 and C11; `gzip` C7; `truncate` C1 and C2; `vary` C13. A control the
|
||||
driver passes is a defect in the driver.
|
||||
3. **Labelled caches.** An environment that provides them lists their labels in
|
||||
`/opt/wai-cache/labels`; each `/opt/wai-cache/<label>/` holds `start.sh <port>
|
||||
<origin URL>`, which starts the cache in front of the origin, a `version` file and the
|
||||
`config` it runs with. This repository holds no cache configuration: the labels are
|
||||
opaque, and a report names a cache only by its label, its package version and the
|
||||
SHA-256 of its configuration. Where the directory is absent, only 1 and 2 run, and the
|
||||
summary says so.
|
||||
|
||||
## Reports
|
||||
|
||||
`run.sh <out-dir>` writes, under `<out-dir>/reports/`, `report-<label>.json`
|
||||
(`{cache_label, package_version, config_sha256, results}`), `requirements-<label>.md`
|
||||
(the same as a table, with whether the configuration meets §9), the driver's log per run,
|
||||
and `summary.md`, which CI also writes to the job summary. The CI job uploads the
|
||||
reports as an artifact.
|
||||
|
||||
## Running it
|
||||
|
||||
cd wai/wai-rs && cargo build --release --no-default-features --features http --bin wai
|
||||
cd ../.. && WAI=wai/wai-rs/target/release/wai bash wai/cache-conformance/run.sh /tmp/cache-run
|
||||
|
||||
Python 3 with the standard library is all the driver and the controls need.
|
||||
339
wai/cache-conformance/driver.py
Executable file
339
wai/cache-conformance/driver.py
Executable file
|
|
@ -0,0 +1,339 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Cache conformance for HTTP delivery (extensions/http-delivery.md §9).
|
||||
|
||||
Runs tests.json against an HTTP cache that fronts an origin serving the objects of
|
||||
wai/http-conformance/, and reports, per test, whether the cache met it. Standard library
|
||||
only, and ../claim-conformance/_blake3.py to check each object's bytes against its
|
||||
content hash; it shares no code with wai-rs.
|
||||
|
||||
python3 driver.py stage <http-conformance dir> <root>
|
||||
lay out the files an origin serves for the tests under <root>
|
||||
python3 driver.py run --base URL --root <root> [--origin URL] [--label L]
|
||||
[--version V] [--config FILE] [--report report.json]
|
||||
run the tests against the cache at URL (the origin itself, for a control)
|
||||
|
||||
`run` exits 1 when a MUST test fails, and prints `FAIL <id>: <detail>` for each. With
|
||||
--origin, the origin's hit counter (`wai http origin --count-hits`) lets the informative
|
||||
tests see whether a request reached the origin. A report names the cache by its label
|
||||
and version only.
|
||||
"""
|
||||
import hashlib, http.client, json, os, shutil, sys, time, urllib.parse
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance"))
|
||||
from _blake3 import blake3 # noqa: E402
|
||||
|
||||
DOMAIN = b"moq-jwp:object\x01"
|
||||
|
||||
|
||||
def cid_of(b):
|
||||
return blake3(DOMAIN + b).hex()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- staging
|
||||
|
||||
def stage(corpus, root):
|
||||
"""The origin's files: the corpus objects and head proofs, an alias, a live-edge
|
||||
part, a receipt index, a receipt file, a playlist receipt, a key log and a
|
||||
parameter set named by its digest."""
|
||||
if os.path.exists(root):
|
||||
shutil.rmtree(root)
|
||||
os.makedirs(os.path.join(root, "objects"))
|
||||
listing = json.load(open(os.path.join(corpus, "objects.json")))
|
||||
for oid, o in listing.items():
|
||||
for suffix in ("", ".hp"):
|
||||
shutil.copyfile(os.path.join(corpus, o["path"] + suffix), os.path.join(root, o["path"] + suffix))
|
||||
def put(rel, data):
|
||||
p = os.path.join(root, rel)
|
||||
os.makedirs(os.path.dirname(p), exist_ok=True)
|
||||
open(p, "wb").write(data)
|
||||
o8 = open(os.path.join(corpus, listing["O8"]["path"]), "rb").read()
|
||||
o9b = open(os.path.join(corpus, listing["O9b"]["path"]), "rb").read()
|
||||
put("alias/segment-8.wai", o8)
|
||||
put("live/part-1.wai", o9b)
|
||||
index = json.dumps({"kind": "wai-receipt-index", "object": listing["O3"]["cid"], "delivery": [], "claims": []}, sort_keys=True).encode()
|
||||
put(listing["O3"]["path"] + ".receipts", index)
|
||||
fixture = json.dumps({"kind": "cache-conformance-fixture", "of": "a receipt file"}, sort_keys=True).encode()
|
||||
put("objects/rcpt/%s.json" % blake3(fixture).hex(), fixture)
|
||||
playlist = json.dumps({"kind": "cache-conformance-fixture", "of": "a playlist receipt"}, sort_keys=True).encode()
|
||||
put("objects/mr/%s.json" % blake3(playlist).hex(), playlist)
|
||||
put(".well-known/wai-keys", json.dumps({"kind": "cache-conformance-fixture", "of": "a key log"}).encode())
|
||||
pset = open(os.path.join(corpus, "..", "wai-rs", "tests", "vectors", "int_mlicpp", "model.wmb"), "rb").read()
|
||||
put("sets/" + hashlib.sha256(pset).hexdigest(), pset)
|
||||
manifest = {
|
||||
"objects": {oid: {"path": o["path"], "cid": o["cid"], "n": o["n"]} for oid, o in listing.items()},
|
||||
"alias": {"path": "alias/segment-8.wai", "of": "O8"},
|
||||
"live": {"path": "live/part-1.wai", "of": "O9b"},
|
||||
"index": listing["O3"]["path"] + ".receipts",
|
||||
"receipt": "objects/rcpt/%s.json" % blake3(fixture).hex(),
|
||||
"playlist_receipt": "objects/mr/%s.json" % blake3(playlist).hex(),
|
||||
"key_log": ".well-known/wai-keys",
|
||||
"set": "sets/" + hashlib.sha256(pset).hexdigest(),
|
||||
}
|
||||
open(os.path.join(root, "stage.json"), "w").write(json.dumps(manifest, indent=1) + "\n")
|
||||
print("staged %d objects and their sidecars under %s" % (len(listing), root))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- requests
|
||||
|
||||
class Response:
|
||||
def __init__(self, status, headers, body):
|
||||
self.status, self.body = status, body
|
||||
self.headers = {}
|
||||
for k, v in headers:
|
||||
self.headers.setdefault(k.lower(), []).append(v)
|
||||
|
||||
def h(self, name):
|
||||
v = self.headers.get(name.lower())
|
||||
return None if v is None else ", ".join(v)
|
||||
|
||||
|
||||
def request(base, path, headers=None, method="GET"):
|
||||
u = urllib.parse.urlsplit(base)
|
||||
conn = http.client.HTTPConnection(u.hostname, u.port or 80, timeout=30)
|
||||
prefix = u.path.rstrip("/")
|
||||
h = {"Connection": "close"}
|
||||
h.update(headers or {})
|
||||
conn.request(method, prefix + "/" + path.lstrip("/"), headers=h)
|
||||
r = conn.getresponse()
|
||||
body = r.read()
|
||||
out = Response(r.status, r.getheaders(), body)
|
||||
conn.close()
|
||||
return out
|
||||
|
||||
|
||||
def hits(origin, path):
|
||||
if origin is None:
|
||||
return None
|
||||
r = request(origin, "/__hits?path=" + urllib.parse.quote("/" + path.lstrip("/")))
|
||||
return int(r.body) if r.status == 200 else None
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- tests
|
||||
|
||||
class Fail(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def need(cond, why):
|
||||
if not cond:
|
||||
raise Fail(why)
|
||||
|
||||
|
||||
def run_tests(base, root, origin):
|
||||
st = json.load(open(os.path.join(root, "stage.json")))
|
||||
obj = {k: open(os.path.join(root, v["path"]), "rb").read() for k, v in st["objects"].items()}
|
||||
for k, v in st["objects"].items():
|
||||
if cid_of(obj[k]) != v["cid"]:
|
||||
raise SystemExit("the staged object %s does not hash to its cid" % k)
|
||||
path = lambda k: st["objects"][k]["path"]
|
||||
etag = lambda k: '"%s"' % st["objects"][k]["cid"]
|
||||
results = {}
|
||||
|
||||
def whole_ok(r, k, what):
|
||||
need(r.status == 200, "%s: status %d" % (what, r.status))
|
||||
need(r.h("content-encoding") in (None, "identity"), "%s: Content-Encoding %s" % (what, r.h("content-encoding")))
|
||||
need(r.body == obj[k], "%s: %d bytes that are not the object's %d" % (what, len(r.body), len(obj[k])))
|
||||
|
||||
def range_ok(r, k, a, b, what, allow_200=False):
|
||||
n = len(obj[k])
|
||||
if allow_200 and r.status == 200:
|
||||
whole_ok(r, k, what)
|
||||
return
|
||||
need(r.status == 206, "%s: status %d" % (what, r.status))
|
||||
need(r.h("content-encoding") in (None, "identity"), "%s: Content-Encoding %s" % (what, r.h("content-encoding")))
|
||||
need(r.h("content-range") == "bytes %d-%d/%d" % (a, b, n), "%s: Content-Range %s" % (what, r.h("content-range")))
|
||||
need(r.body == obj[k][a:b + 1], "%s: the bytes are not the object's [%d, %d]" % (what, a, b))
|
||||
|
||||
def c1():
|
||||
whole_ok(request(base, path("O3")), "O3", "cold")
|
||||
whole_ok(request(base, path("O3")), "O3", "again")
|
||||
|
||||
def c2():
|
||||
r = request(base, path("O2"))
|
||||
whole_ok(r, "O2", "GET")
|
||||
need(r.h("etag") == etag("O2"), "ETag %s" % r.h("etag"))
|
||||
need((r.h("content-type") or "").startswith("application/wai"), "Content-Type %s" % r.h("content-type"))
|
||||
need(r.h("cache-control") == "public, max-age=31536000, immutable, no-transform", "Cache-Control %s" % r.h("cache-control"))
|
||||
need(r.h("content-length") == str(len(obj["O2"])), "Content-Length %s" % r.h("content-length"))
|
||||
|
||||
def c3():
|
||||
request(base, path("O4"))
|
||||
range_ok(request(base, path("O4"), {"Range": "bytes=1009-3000"}), "O4", 1009, 3000, "range")
|
||||
|
||||
def c4():
|
||||
n = len(obj["O7"])
|
||||
range_ok(request(base, path("O7"), {"Range": "bytes=1009-2999"}), "O7", 1009, 2999, "cold range", allow_200=True)
|
||||
range_ok(request(base, path("O7"), {"Range": "bytes=0-1008"}), "O7", 0, 1008, "head range", allow_200=True)
|
||||
need(n > 0, "empty")
|
||||
|
||||
def c5():
|
||||
n = len(obj["O9c"])
|
||||
request(base, path("O9c"))
|
||||
range_ok(request(base, path("O9c"), {"Range": "bytes=-100"}), "O9c", n - 100, n - 1, "suffix")
|
||||
range_ok(request(base, path("O9c"), {"Range": "bytes=%d-" % (n - 50)}), "O9c", n - 50, n - 1, "open")
|
||||
|
||||
def c6():
|
||||
n = len(obj["O9c"])
|
||||
r = request(base, path("O9c"), {"Range": "bytes=%d-" % n})
|
||||
need(r.status == 416, "status %d" % r.status)
|
||||
need(r.h("content-range") == "bytes */%d" % n, "Content-Range %s" % r.h("content-range"))
|
||||
|
||||
def c7():
|
||||
for enc in ("gzip, deflate, br", "br", "zstd, gzip"):
|
||||
whole_ok(request(base, path("O9a"), {"Accept-Encoding": enc}), "O9a", "Accept-Encoding %s" % enc)
|
||||
|
||||
def c8():
|
||||
request(base, path("OD"))
|
||||
range_ok(request(base, path("OD"), {"Range": "bytes=1009-3593", "If-Range": etag("OD")}), "OD", 1009, 3593, "matching If-Range")
|
||||
whole_ok(request(base, path("OD"), {"Range": "bytes=1009-3593", "If-Range": "W/" + etag("OD")}), "OD", "weak If-Range")
|
||||
whole_ok(request(base, path("OD"), {"Range": "bytes=1009-3593", "If-Range": '"%s"' % ("0" * 64)}), "OD", "other If-Range")
|
||||
|
||||
def c9():
|
||||
a = request(base, path("O5"), {"User-Agent": "conformance-a", "Accept-Language": "en", "Accept": "*/*"})
|
||||
b = request(base, path("O5"), {"User-Agent": "conformance-b", "Accept-Language": "fr", "Accept": "application/wai"})
|
||||
whole_ok(a, "O5", "first")
|
||||
whole_ok(b, "O5", "second")
|
||||
need(a.h("vary") is None and b.h("vary") is None, "Vary %s / %s" % (a.h("vary"), b.h("vary")))
|
||||
|
||||
def c10():
|
||||
p = path("O3") + ".hp"
|
||||
r = request(base, p)
|
||||
need(r.status == 200 and r.body == open(os.path.join(root, p), "rb").read(), "the head proof differs (status %d)" % r.status)
|
||||
need(r.h("content-encoding") in (None, "identity"), "Content-Encoding %s" % r.h("content-encoding"))
|
||||
need(r.h("cache-control") == "public, max-age=31536000, immutable, no-transform", "Cache-Control %s" % r.h("cache-control"))
|
||||
|
||||
def c11():
|
||||
r = request(base, st["alias"]["path"])
|
||||
whole_ok(r, st["alias"]["of"], "alias")
|
||||
need(r.h("etag") == etag(st["alias"]["of"]), "ETag %s" % r.h("etag"))
|
||||
|
||||
def c12():
|
||||
k, p = st["live"]["of"], st["live"]["path"]
|
||||
r = request(base, p)
|
||||
whole_ok(r, k, "live part")
|
||||
cc = r.h("cache-control") or ""
|
||||
need("max-age=31536000" not in cc and "max-age=" in cc, "Cache-Control %s" % cc)
|
||||
range_ok(request(base, p, {"Range": "bytes=10-99"}), k, 10, 99, "live range", allow_200=True)
|
||||
|
||||
def c13():
|
||||
r = request(base, path("O1"), {"Origin": "https://reader.example"})
|
||||
whole_ok(r, "O1", "with Origin")
|
||||
need(r.h("access-control-allow-origin") == "*", "Access-Control-Allow-Origin %s" % r.h("access-control-allow-origin"))
|
||||
need("origin" not in (r.h("vary") or "").lower(), "Vary %s" % r.h("vary"))
|
||||
|
||||
def r1():
|
||||
p = st["index"]
|
||||
first = request(base, p)
|
||||
need(first.status == 200, "status %d" % first.status)
|
||||
changed = json.dumps({"kind": "wai-receipt-index", "changed_at": time.time()}).encode()
|
||||
full = os.path.join(root, p)
|
||||
old = open(full, "rb").read()
|
||||
open(full, "wb").write(changed)
|
||||
try:
|
||||
second = request(base, p)
|
||||
finally:
|
||||
open(full, "wb").write(old)
|
||||
need(second.status == 200 and second.body == changed, "the index came back unchanged")
|
||||
|
||||
def fetch_kept(p, cc=None):
|
||||
r = request(base, p)
|
||||
need(r.status == 200 and r.body == open(os.path.join(root, p), "rb").read(), "%s differs (status %d)" % (p, r.status))
|
||||
need(r.h("content-encoding") in (None, "identity"), "Content-Encoding %s" % r.h("content-encoding"))
|
||||
if cc is not None:
|
||||
need(r.h("cache-control") == cc, "Cache-Control %s" % r.h("cache-control"))
|
||||
return r
|
||||
|
||||
def kept(p, cc=None):
|
||||
fetch_kept(p, cc)
|
||||
|
||||
imm = "public, max-age=31536000, immutable, no-transform"
|
||||
|
||||
def r5():
|
||||
r = fetch_kept(st["set"], imm)
|
||||
need(hashlib.sha256(r.body).hexdigest() == st["set"].rsplit("/", 1)[1], "the set does not hash to its name")
|
||||
|
||||
def s1():
|
||||
if origin is None:
|
||||
return "not run: no hit counter"
|
||||
p = path("O8")
|
||||
request(base, p)
|
||||
before = hits(origin, p)
|
||||
request(base, p)
|
||||
return "stored" if hits(origin, p) == before else "not stored"
|
||||
|
||||
def s2():
|
||||
if origin is None:
|
||||
return "not run: no hit counter"
|
||||
p = path("O9b")
|
||||
request(base, p)
|
||||
before = hits(origin, p)
|
||||
request(base, p, {"Range": "bytes=100-199"})
|
||||
return "answered from store" if hits(origin, p) == before else "forwarded"
|
||||
|
||||
table = {"C1": c1, "C2": c2, "C3": c3, "C4": c4, "C5": c5, "C6": c6, "C7": c7, "C8": c8, "C9": c9,
|
||||
"C10": c10, "C11": c11, "C12": c12, "C13": c13, "R1": r1,
|
||||
"R2": lambda: kept(st["receipt"], imm), "R3": lambda: kept(st["playlist_receipt"], imm),
|
||||
"R4": lambda: kept(st["key_log"], "max-age=300, no-transform"), "R5": r5, "S1": s1, "S2": s2}
|
||||
tests = json.load(open(os.path.join(HERE, "tests.json")))["tests"]
|
||||
out = []
|
||||
for t in tests:
|
||||
try:
|
||||
detail = table[t["id"]]()
|
||||
out.append({"id": t["id"], "name": t["name"], "level": t["level"], "pass": True, "detail": detail})
|
||||
except Fail as e:
|
||||
out.append({"id": t["id"], "name": t["name"], "level": t["level"], "pass": False, "detail": str(e)})
|
||||
except (OSError, http.client.HTTPException) as e:
|
||||
out.append({"id": t["id"], "name": t["name"], "level": t["level"], "pass": False, "detail": "transport: %s" % e})
|
||||
return out
|
||||
|
||||
|
||||
def requirements_md(report):
|
||||
lines = ["# Cache configuration: %s" % report["cache_label"], "",
|
||||
"Package version: %s. Configuration SHA-256: %s." % (report["package_version"], report["config_sha256"] or "none"), "",
|
||||
"| test | level | result | detail |", "|---|---|---|---|"]
|
||||
for r in report["results"]:
|
||||
res = ("pass" if r["pass"] else "FAIL") if r["level"] == "MUST" else (r["detail"] or "")
|
||||
lines.append("| %s %s | %s | %s | %s |" % (r["id"], r["name"], r["level"], res, "" if r["pass"] else r["detail"]))
|
||||
musts = [r for r in report["results"] if r["level"] == "MUST"]
|
||||
ok = all(r["pass"] for r in musts)
|
||||
lines += ["", "This configuration %s http-delivery §9 for every MUST test." % ("meets" if ok else "does not meet")]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) == 3 and argv[0] == "stage":
|
||||
stage(argv[1], argv[2])
|
||||
return 0
|
||||
if not argv or argv[0] != "run":
|
||||
print(__doc__)
|
||||
return 2
|
||||
opt = lambda f, d=None: argv[argv.index(f) + 1] if f in argv else d
|
||||
base, root = opt("--base"), opt("--root")
|
||||
if base is None or root is None:
|
||||
print(__doc__)
|
||||
return 2
|
||||
results = run_tests(base, root, opt("--origin"))
|
||||
cfg = opt("--config")
|
||||
report = {
|
||||
"cache_label": opt("--label", "unlabelled"),
|
||||
"package_version": opt("--version", "n/a"),
|
||||
"config_sha256": hashlib.sha256(open(cfg, "rb").read()).hexdigest() if cfg else None,
|
||||
"results": results,
|
||||
}
|
||||
if opt("--report"):
|
||||
open(opt("--report"), "w").write(json.dumps(report, indent=1) + "\n")
|
||||
open(os.path.join(os.path.dirname(opt("--report")) or ".", "requirements-%s.md" % report["cache_label"]), "w").write(requirements_md(report))
|
||||
failed = [r for r in results if r["level"] == "MUST" and not r["pass"]]
|
||||
for r in results:
|
||||
if not r["pass"]:
|
||||
print("FAIL %s: %s" % (r["id"], r["detail"]))
|
||||
elif r["level"] != "MUST":
|
||||
print("info %s: %s" % (r["id"], r["detail"]))
|
||||
print("%s: %d/%d MUST tests pass" % (report["cache_label"], sum(1 for r in results if r["level"] == "MUST" and r["pass"]),
|
||||
sum(1 for r in results if r["level"] == "MUST")))
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
82
wai/cache-conformance/mutators.py
Executable file
82
wai/cache-conformance/mutators.py
Executable file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Negative controls for driver.py: proxies that break http-delivery §9 in one way each,
|
||||
so a run against them must fail the tests that rule reaches. If the driver passes one of
|
||||
them, the driver is what is broken.
|
||||
|
||||
python3 mutators.py --mode flip|gzip|truncate|vary --listen PORT --upstream URL
|
||||
|
||||
- flip: changes one byte in the middle of every object body (§9 rule 1);
|
||||
- gzip: applies a gzip content coding to whole objects when the request offers it
|
||||
(§9 rule 1);
|
||||
- truncate: drops the last byte of every whole object, and says so in Content-Length
|
||||
(§9 rules 1 and 3);
|
||||
- vary: adds `Vary: Origin` and drops `Access-Control-Allow-Origin` (§9 rule 4,
|
||||
§5.3).
|
||||
|
||||
Standard library only.
|
||||
"""
|
||||
import gzip as gz, http.client, sys, urllib.parse
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
MODE, UPSTREAM = None, None
|
||||
HOP = {"connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "te", "trailer", "host"}
|
||||
|
||||
|
||||
class Proxy(BaseHTTPRequestHandler):
|
||||
protocol_version = "HTTP/1.1"
|
||||
|
||||
def log_message(self, *a):
|
||||
pass
|
||||
|
||||
def forward(self, method):
|
||||
u = urllib.parse.urlsplit(UPSTREAM)
|
||||
conn = http.client.HTTPConnection(u.hostname, u.port or 80, timeout=30)
|
||||
headers = {k: v for k, v in self.headers.items() if k.lower() not in HOP}
|
||||
conn.request(method, self.path, headers=headers)
|
||||
r = conn.getresponse()
|
||||
body = r.read()
|
||||
out = [(k, v) for k, v in r.getheaders() if k.lower() not in HOP and k.lower() != "content-length"]
|
||||
conn.close()
|
||||
is_object = self.path.split("?")[0].endswith(".wai")
|
||||
if MODE == "flip" and is_object and len(body) > 1:
|
||||
body = bytearray(body)
|
||||
body[len(body) // 2] ^= 0x01
|
||||
body = bytes(body)
|
||||
elif MODE == "gzip" and is_object and r.status == 200 and "gzip" in (self.headers.get("Accept-Encoding") or ""):
|
||||
body = gz.compress(body, mtime=0)
|
||||
out = [(k, v) for k, v in out if k.lower() != "content-encoding"] + [("Content-Encoding", "gzip")]
|
||||
elif MODE == "truncate" and is_object and r.status == 200 and body:
|
||||
body = body[:-1]
|
||||
elif MODE == "vary":
|
||||
out = [(k, v) for k, v in out if k.lower() != "access-control-allow-origin"] + [("Vary", "Origin")]
|
||||
self.send_response(r.status)
|
||||
for k, v in out:
|
||||
self.send_header(k, v)
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
if method != "HEAD":
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self):
|
||||
self.forward("GET")
|
||||
|
||||
def do_HEAD(self):
|
||||
self.forward("HEAD")
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.forward("OPTIONS")
|
||||
|
||||
|
||||
def main(argv):
|
||||
global MODE, UPSTREAM
|
||||
opt = lambda f: argv[argv.index(f) + 1]
|
||||
MODE, UPSTREAM, port = opt("--mode"), opt("--upstream"), int(opt("--listen"))
|
||||
if MODE not in ("flip", "gzip", "truncate", "vary"):
|
||||
print(__doc__)
|
||||
return 2
|
||||
ThreadingHTTPServer(("127.0.0.1", port), Proxy).serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
93
wai/cache-conformance/run.sh
Executable file
93
wai/cache-conformance/run.sh
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env bash
|
||||
# Cache conformance (extensions/http-delivery.md §9): stage the corpus's objects, start
|
||||
# the reference origin, and run driver.py
|
||||
#
|
||||
# 1. against the origin itself, which must pass every MUST test (the positive control);
|
||||
# 2. against each negative-control proxy in mutators.py, which must fail the tests its
|
||||
# defect reaches (the driver's own falsification);
|
||||
# 3. against each labelled cache an environment provides: /opt/wai-cache/labels lists
|
||||
# the labels, and /opt/wai-cache/<label>/start.sh <port> <origin URL> starts one in
|
||||
# front of the origin (with version and config files beside it). The public
|
||||
# repository holds no cache configuration; without that directory only 1 and 2 run,
|
||||
# and the summary says so.
|
||||
#
|
||||
# WAI=<path to a `wai` built with http> bash wai/cache-conformance/run.sh <out-dir>
|
||||
#
|
||||
# Reports go to <out-dir>/reports: report-<label>.json and requirements-<label>.md.
|
||||
# Exit status 1 when a control does not behave as stated or a labelled cache fails a
|
||||
# MUST test.
|
||||
set -uo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
OUT="${1:?usage: run.sh <out-dir>}"
|
||||
WAI="${WAI:?set WAI to a wai binary built with the http feature}"
|
||||
mkdir -p "$OUT/reports"
|
||||
ROOT="$OUT/root"
|
||||
python3 "$HERE/driver.py" stage "$HERE/../http-conformance" "$ROOT" || exit 1
|
||||
|
||||
pids=()
|
||||
cleanup() { for p in "${pids[@]}"; do kill "$p" 2>/dev/null; done; }
|
||||
trap cleanup EXIT
|
||||
|
||||
free_port() { python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1]); s.close()'; }
|
||||
wait_port() { for _ in $(seq 1 100); do python3 -c "import socket,sys; socket.create_connection(('127.0.0.1',$1),1)" 2>/dev/null && return 0; sleep 0.1; done; return 1; }
|
||||
|
||||
OP="$(free_port)"
|
||||
"$WAI" http origin --root "$ROOT" --port "$OP" --count-hits --live-prefix live/ --live-max-age 30 > "$OUT/origin.log" 2>&1 &
|
||||
pids+=($!)
|
||||
wait_port "$OP" || { echo "::error::the origin did not start"; cat "$OUT/origin.log"; exit 1; }
|
||||
ORIGIN="http://127.0.0.1:$OP"
|
||||
fail=0
|
||||
summary="| run | expectation | MUST passed | result |\n|---|---|---|---|\n"
|
||||
|
||||
run_driver() { # label base expect-pass [expected failing ids]
|
||||
local label="$1" base="$2" expect="$3"; shift 3
|
||||
python3 "$HERE/driver.py" run --base "$base" --root "$ROOT" --origin "$ORIGIN" --label "$label" \
|
||||
--report "$OUT/reports/report-$label.json" > "$OUT/reports/$label.log" 2>&1
|
||||
local rc=$?
|
||||
local passed; passed="$(tail -1 "$OUT/reports/$label.log" | sed 's/.*: //')"
|
||||
if [ "$expect" = pass ]; then
|
||||
if [ $rc -eq 0 ]; then summary+="| $label | every MUST passes | $passed | ok |\n"
|
||||
else summary+="| $label | every MUST passes | $passed | **failed** |\n"; echo "::error::$label failed a MUST test"; cat "$OUT/reports/$label.log"; fail=1; fi
|
||||
else
|
||||
local missing=""
|
||||
for id in "$@"; do grep -q "^FAIL $id:" "$OUT/reports/$label.log" || missing="$missing $id"; done
|
||||
if [ $rc -ne 0 ] && [ -z "$missing" ]; then summary+="| $label | fails $* | $passed | caught |\n"
|
||||
else summary+="| $label | fails $* | $passed | **not caught:$missing** |\n"; echo "::error::$label: the driver did not catch$missing (exit $rc)"; cat "$OUT/reports/$label.log"; fail=1; fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 1. The origin, directly.
|
||||
run_driver origin-direct "$ORIGIN" pass
|
||||
|
||||
# 2. The negative controls.
|
||||
expect_of() { case "$1" in flip) echo "C1 C3 C11" ;; gzip) echo "C7" ;; truncate) echo "C1 C2" ;; vary) echo "C13" ;; esac; }
|
||||
for m in flip gzip truncate vary; do
|
||||
MP="$(free_port)"
|
||||
python3 "$HERE/mutators.py" --mode "$m" --listen "$MP" --upstream "$ORIGIN" > "$OUT/mutator-$m.log" 2>&1 &
|
||||
pids+=($!)
|
||||
wait_port "$MP" || { echo "::error::mutator $m did not start"; fail=1; continue; }
|
||||
# shellcheck disable=SC2086
|
||||
run_driver "control-$m" "http://127.0.0.1:$MP" fail $(expect_of "$m")
|
||||
done
|
||||
|
||||
# 3. Labelled caches, where the environment provides them.
|
||||
if [ -f /opt/wai-cache/labels ]; then
|
||||
for label in $(cat /opt/wai-cache/labels); do
|
||||
CP="$(free_port)"
|
||||
/opt/wai-cache/"$label"/start.sh "$CP" "$ORIGIN" > "$OUT/cache-$label.log" 2>&1 &
|
||||
pids+=($!)
|
||||
wait_port "$CP" || { echo "::error::cache $label did not start"; fail=1; continue; }
|
||||
python3 "$HERE/driver.py" run --base "http://127.0.0.1:$CP" --root "$ROOT" --origin "$ORIGIN" --label "$label" \
|
||||
--version "$(cat /opt/wai-cache/"$label"/version 2>/dev/null || echo unknown)" \
|
||||
--config /opt/wai-cache/"$label"/config --report "$OUT/reports/report-$label.json" > "$OUT/reports/$label.log" 2>&1
|
||||
rc=$?
|
||||
summary+="| $label | every MUST passes | $(tail -1 "$OUT/reports/$label.log" | sed 's/.*: //') | $([ $rc -eq 0 ] && echo ok || echo '**failed**') |\n"
|
||||
[ $rc -eq 0 ] || fail=1
|
||||
done
|
||||
else
|
||||
summary+="| labelled caches | — | — | none in this environment (/opt/wai-cache/labels absent): controls only |\n"
|
||||
fi
|
||||
|
||||
printf "%b" "$summary" | tee "$OUT/reports/summary.md"
|
||||
[ -n "${GITHUB_STEP_SUMMARY:-}" ] && printf "%b" "$summary" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit $fail
|
||||
25
wai/cache-conformance/tests.json
Normal file
25
wai/cache-conformance/tests.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"note": "The tests driver.py runs against an HTTP cache that fronts an origin serving wai/http-conformance's objects (extensions/http-delivery.md §9). A MUST test failing means the cache, as configured, does not meet §9; an informative test reports what the cache did.",
|
||||
"tests": [
|
||||
{ "id": "C1", "name": "whole_identity", "level": "MUST", "rule": "§9 rule 1", "what": "a GET of an object, cold and then again, returns exactly the object's bytes" },
|
||||
{ "id": "C2", "name": "headers_kept", "level": "MUST", "rule": "§9 rule 3", "what": "a 200 keeps the origin's ETag, Content-Type and Cache-Control, and a Content-Length of the object's length" },
|
||||
{ "id": "C3", "name": "range_from_stored", "level": "MUST", "rule": "§9 rule 2", "what": "after a whole GET, a single range is answered 206 with exactly those bytes and Content-Range bytes a-b/n" },
|
||||
{ "id": "C4", "name": "range_cold", "level": "MUST", "rule": "§9 rule 2, §5.3", "what": "a single range on an object not yet fetched is answered 206 with exactly those bytes, or 200 with the whole object" },
|
||||
{ "id": "C5", "name": "suffix_and_open_ranges", "level": "MUST", "rule": "§9 rule 2", "what": "bytes=-N and bytes=A- are answered with exactly those bytes" },
|
||||
{ "id": "C6", "name": "unsatisfiable", "level": "MUST", "rule": "§9 rule 2", "what": "a range starting at the object's length is answered 416 with Content-Range bytes */n" },
|
||||
{ "id": "C7", "name": "no_coding", "level": "MUST", "rule": "§9 rule 1", "what": "with Accept-Encoding offering codings, the object comes back with no Content-Encoding and its exact bytes" },
|
||||
{ "id": "C8", "name": "if_range_strong", "level": "MUST", "rule": "§9 rule 5", "what": "If-Range with the object's ETag gets 206; with a weak form of it, or another ETag, the whole object" },
|
||||
{ "id": "C9", "name": "no_vary_by_header", "level": "MUST", "rule": "§9 rule 4", "what": "requests that differ in User-Agent, Accept-Language and Accept get the same bytes, with no Vary" },
|
||||
{ "id": "C10", "name": "head_proof_kept", "level": "MUST", "rule": "§9 rule 6, §6.2", "what": "a head proof is returned byte for byte with its immutable Cache-Control" },
|
||||
{ "id": "C11", "name": "alias", "level": "MUST", "rule": "§5.2, §9 rule 1", "what": "an alias returns the object's exact bytes and its ETag" },
|
||||
{ "id": "C12", "name": "live_edge_max_age", "level": "MUST", "rule": "§5.3, §9 rule 3", "what": "an object listed at a live edge keeps its shorter max-age and its bytes, whole and by range" },
|
||||
{ "id": "C13", "name": "no_vary_cors", "level": "MUST", "rule": "§5.3, §9 rule 4", "what": "a request with Origin gets Access-Control-Allow-Origin: * and no Vary: Origin" },
|
||||
{ "id": "R1", "name": "index_revalidated", "level": "MUST", "rule": "§9 rule 6, §8.2", "what": "a receipt index changed at the origin is returned changed on the next request" },
|
||||
{ "id": "R2", "name": "receipt_file_kept", "level": "MUST", "rule": "§9 rule 6, §8.1", "what": "a receipt file is returned byte for byte with its immutable Cache-Control" },
|
||||
{ "id": "R3", "name": "playlist_receipt_kept", "level": "MUST", "rule": "§9 rule 6, §8.6", "what": "a playlist receipt is returned byte for byte" },
|
||||
{ "id": "R4", "name": "key_log_max_age", "level": "MUST", "rule": "§9 rule 6, §8.4", "what": "the key log is returned byte for byte with its Cache-Control" },
|
||||
{ "id": "R5", "name": "parameter_set_kept", "level": "MUST", "rule": "§9 rule 6, §8.5", "what": "a parameter set named by its digest is returned byte for byte and hashes to its name" },
|
||||
{ "id": "S1", "name": "stored", "level": "informative", "rule": "§9", "what": "a second GET of an object is answered without reaching the origin" },
|
||||
{ "id": "S2", "name": "range_stored", "level": "informative", "rule": "§9 rule 2", "what": "a range after a whole GET is answered without reaching the origin" }
|
||||
]
|
||||
}
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
"""BLAKE3 in hash mode, written from its specification in pure Python.
|
||||
|
||||
Shared by the claim-conformance verifier (`claim_verify.py`) and the staged-delivery
|
||||
verifier (`../staged-conformance/staged_verify.py`), which import `blake3` from here. It
|
||||
shares no code with wai-rs, which computes the same hashes with the `blake3` crate.
|
||||
Shared by the claim-conformance verifier (`claim_verify.py`), the staged-delivery
|
||||
verifier (`../staged-conformance/staged_verify.py`) and the HTTP delivery verifier
|
||||
(`../http-conformance/http_verify.py`), which import from here. It shares no code with
|
||||
wai-rs, which computes the same hashes with the `blake3` crate.
|
||||
|
||||
`subtree_cv` and `parent_cv` expose the tree (the BLAKE3 specification, §2.1): the
|
||||
non-root chaining value of a subtree that starts at a given byte offset of the whole
|
||||
input, and a parent node with or without the ROOT flag. The HTTP head proof is built of
|
||||
these (http-delivery §6).
|
||||
"""
|
||||
|
||||
_IV = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19]
|
||||
|
|
@ -73,3 +79,35 @@ def blake3(data: bytes) -> bytes:
|
|||
out = (_IV, stack.pop() + _cv(out), 0, 64, _PARENT)
|
||||
cv, w, _, bl, f = out
|
||||
return b"".join(x.to_bytes(4, "little") for x in _compress(cv, w, 0, bl, f | _ROOT)[:8])
|
||||
|
||||
|
||||
def _bytes(words):
|
||||
return b"".join(x.to_bytes(4, "little") for x in words)
|
||||
|
||||
|
||||
def _left_chunks(n):
|
||||
"""The left subtree of `n` chunks (n >= 2): the largest power of two below n."""
|
||||
return 1 << ((n - 1).bit_length() - 1)
|
||||
|
||||
|
||||
def subtree_cv(data: bytes, start: int) -> bytes:
|
||||
"""The non-root chaining value of `data` as a whole subtree of the input whose first
|
||||
chunk is at byte offset `start` (a multiple of 1024): chunk counters run from
|
||||
start // 1024."""
|
||||
assert start % 1024 == 0
|
||||
chunks = [data[i:i + 1024] for i in range(0, len(data), 1024)] or [b""]
|
||||
|
||||
def node(cs, c0):
|
||||
if len(cs) == 1:
|
||||
return _cv(_chunk_output(cs[0], c0))
|
||||
k = _left_chunks(len(cs))
|
||||
return _cv((_IV, node(cs[:k], c0) + node(cs[k:], c0 + k), 0, 64, _PARENT))
|
||||
|
||||
return _bytes(node(chunks, start // 1024))
|
||||
|
||||
|
||||
def parent_cv(left: bytes, right: bytes, root: bool = False) -> bytes:
|
||||
"""A parent node over two chaining values: its chaining value, or with `root` the
|
||||
hash it outputs as the root."""
|
||||
m = [int.from_bytes((left + right)[i:i + 4], "little") for i in range(0, 64, 4)]
|
||||
return _bytes(_compress(_IV, m, 0, 64, _PARENT | (_ROOT if root else 0))[:8])
|
||||
|
|
|
|||
903
wai/extensions/http-delivery.md
Normal file
903
wai/extensions/http-delivery.md
Normal file
|
|
@ -0,0 +1,903 @@
|
|||
# WAI Extension: HTTP Delivery
|
||||
|
||||
> Status: Draft. Binds WAI envelopes to HTTP: a proposed media type, a `codecs`
|
||||
> grammar, content-addressed immutable objects, verified byte-range fetch of one
|
||||
> component of a `WAI2`, receipt, key and playlist sidecars, cache rules, delivery
|
||||
> order and deadlines, and HLS / DASH adjacency for on-demand, live and low-latency
|
||||
> live. No envelope byte and no manifest member changes. The reference implementation
|
||||
> is in `wai-rs`: `http_binding` (feature `http`) for §3–§7 and the origin side of §5
|
||||
> and §9, and the `wai http` commands of the `wai` tool. Keywords **MUST**,
|
||||
> **MUST NOT**, **SHOULD**, **MAY** are RFC 2119 / RFC 8174.
|
||||
|
||||
## 1. Scope
|
||||
|
||||
This extension says how a WAI envelope is named, served, cached and fetched over HTTP
|
||||
(RFC 9110, RFC 9111), and how WAI objects sit beside a classical HLS or DASH
|
||||
presentation without changing it. It is the HTTP sibling of
|
||||
[moq-streaming-format](moq-streaming-format.md); Appendix C maps the two. It defines
|
||||
no codec, no player and no re-packing: a node that serves bytes other than those it
|
||||
received is a producer under [jwp-receipts](jwp-receipts.md) §2.6 and §3.2, not a
|
||||
cache under §9. What a refinement does to its base is
|
||||
[staged-delivery](staged-delivery.md)'s and [SPEC](../SPEC.md) §7.1's; §10 defines
|
||||
only how one is carried over HTTP and when a client gives up on it.
|
||||
|
||||
## 2. Terms
|
||||
|
||||
- **Object**: one WAI envelope ([SPEC](../SPEC.md) §2 or §7.1) served as one HTTP
|
||||
representation.
|
||||
- **Content hash**, **cid**: the object's [jwp-receipts](jwp-receipts.md) §2.1 content
|
||||
hash, `BLAKE3("moq-jwp:object\x01" ‖ envelope)`, written as 64 lowercase hexadecimal
|
||||
digits. `envelope_len` is the envelope's length in bytes.
|
||||
- **Head**: for `WAI1`, the first `12 + man_len` bytes — magic, `man_len`, the manifest
|
||||
and `payload_len`; for `WAI2`, the first `10 + man_len + 8·n` bytes — magic, `man_len`,
|
||||
the manifest, `n_renditions` and the rendition table. Its length is `head_len`.
|
||||
- **Component**: one entry of a `WAI2` rendition table and its payload bytes
|
||||
([SPEC](../SPEC.md) §7.1). Its **range** is the `length` bytes from `head_len + offset`.
|
||||
- **Covered length**: `covered_len = min(envelope_len, 1024·⌈(15 + head_len)/1024⌉ − 15)`.
|
||||
- **Alias**: any URI other than `<cid>.wai` under which an object is served.
|
||||
- **Base**: the object, or the component, a sink presents on its own.
|
||||
- **Refinement**: bytes a sink MAY add to a base it can already present. It is one
|
||||
of three things:
|
||||
- a companion whose registered role is `enhancement` ([SPEC](../SPEC.md) §5, §7.1);
|
||||
- a component with `role: "refinement"` (SPEC §7.1, *Refinement components*);
|
||||
- a **layer object**: a `WAI1` envelope whose capability is refinement-only, which
|
||||
carries `refines` ([staged-delivery](staged-delivery.md) §1, §3). A `WAI1` whose
|
||||
capability is not refinement-only is not a layer object, whatever else it carries
|
||||
(§10.4).
|
||||
|
||||
## 3. Media type `application/wai` (proposed; not registered)
|
||||
|
||||
An object is served with media type `application/wai`. The name is **proposed**:
|
||||
RFC 6838 §3.1 admits a standards-tree registration only through an IETF document or a
|
||||
recognized standards organization, and none has been made. Until one is, the name
|
||||
identifies WAI objects between parties that implement this extension, and an
|
||||
implementation MUST NOT present it as registered.
|
||||
|
||||
The type's properties, in the fields of RFC 6838 §5.6:
|
||||
|
||||
| field | value |
|
||||
|---|---|
|
||||
| Type name / Subtype name | application / wai |
|
||||
| Required parameters | none |
|
||||
| Optional parameters | `codecs` (§4) |
|
||||
| Encoding considerations | binary |
|
||||
| Security considerations | §15 |
|
||||
| Interoperability considerations | `WAI1` and `WAI2` containers; a reader refuses an unknown MAJOR ([SPEC](../SPEC.md) §8) |
|
||||
| Published specification | WAI SPEC.md; this extension |
|
||||
| Fragment identifier considerations | §3.1 |
|
||||
| Magic number(s) | `57 41 49 31` ("WAI1") or `57 41 49 32` ("WAI2") at offset 0 |
|
||||
| File extension(s) | `.wai` |
|
||||
|
||||
### 3.1 Fragment identifiers
|
||||
|
||||
A fragment on an object's URI is a `&`-separated list of `key=value` pairs. It is never
|
||||
sent to a server and never changes the object. Unknown keys are ignored.
|
||||
|
||||
- `cid=<64 hex>`: the object's content hash, on an alias (§5.2).
|
||||
- `n=<decimal>`: the object's `envelope_len`.
|
||||
- `h=<decimal>`: the object's `covered_len`.
|
||||
- `c=<component id>`: a preference for the component whose `id` ([SPEC](../SPEC.md)
|
||||
§7.1) is that value, which a sink MAY honour as deployer policy within §7.
|
||||
|
||||
`n` and `h` are hints, never checks: every length that matters is checked against the
|
||||
head proof (§6.3) or, for an object fetched whole, the cid. A wrong `h` costs a request.
|
||||
A wrong `n` below `1009` costs the whole object: the client fetches it whole (§5.4),
|
||||
whatever its real length.
|
||||
|
||||
## 4. The `codecs` parameter
|
||||
|
||||
### 4.1 Grammar
|
||||
|
||||
The `codecs` parameter of `application/wai`, the HLS `CODECS` attribute and the DASH
|
||||
`@codecs` attribute carry WAI elements in this grammar (ABNF, RFC 5234):
|
||||
|
||||
```
|
||||
wai-element = wai-prefix "." alt *( "|" alt )
|
||||
wai-prefix = "wai1" / "wai2"
|
||||
alt = cap *( "+" cap )
|
||||
cap = 1*lcalnum 1*( "." 1*( lcalnum / "_" ) )
|
||||
lcalnum = %x61-7A / %x30-39
|
||||
```
|
||||
|
||||
- An element is one RFC 6381 `id-simple`; elements are separated by `,` as RFC 6381
|
||||
§3.2 separates codecs, and `.` is its hierarchy delimiter. Spaces and tabs around an
|
||||
element are not part of it, and an empty element is malformed. Classical elements
|
||||
(for example `mp4a.40.2`) MAY appear beside WAI elements. An element that begins with
|
||||
`wai`, in any ASCII case, is a WAI element: it MUST match the grammar, and is never
|
||||
read as classical.
|
||||
- `wai1` states that every object the element describes is a `WAI1` envelope; `wai2`
|
||||
states that at least one is a `WAI2` envelope. Both name WAI MAJOR 1.
|
||||
- A `cap` is a capability string ([SPEC](../SPEC.md) §5) without its leading `wai.`.
|
||||
Every capability this revision registers begins `wai.` and matches the rule once
|
||||
that is removed, so the mapping is one-to-one.
|
||||
- In an `alt`, the first `cap` is a **selectable** capability, and each `+cap` is a
|
||||
capability whose payload is applied with it: a companion ([SPEC](../SPEC.md) §7.1)
|
||||
bound to a rendition of it, or a refinement whose base is a rendition of it
|
||||
(§2).
|
||||
- An element MUST NOT name a capability whose registered kind is `derivation` or
|
||||
`record` (`codecs_not_media`), or a candidate (`codecs_candidate`). A first `cap`
|
||||
MUST NOT be companion-only or refinement-only, and a `+cap` MUST NOT be registered as
|
||||
standalone media (`codecs_malformed`); a capability this revision does not register
|
||||
may stand in either place, as SPEC §7.1 lets it be a rendition or a companion. A
|
||||
`cap` MUST NOT appear twice among an element's `alt`s, nor twice in one `alt`. A
|
||||
string that breaks any of these is `codecs_malformed`.
|
||||
|
||||
### 4.2 Meaning: the union and the floor
|
||||
|
||||
- **Union.** The element describing one object or a sequence of objects (an HLS
|
||||
variant, a DASH Representation, a MoQ track) lists every capability any of them may
|
||||
dispatch to or apply. For a `WAI1`, its `capability` and its `fallback`, each where
|
||||
SPEC §4 dispatches it, as an `alt`: a record, a derivation, a candidate, a
|
||||
companion-only or a refinement-only capability is never listed, so a `WAI1` naming a
|
||||
record beside a payload fallback is described by the fallback. For a `WAI2`, one
|
||||
`alt` per distinct selectable capability ([SPEC](../SPEC.md) §7.1), each with `+` and
|
||||
every companion capability bound to a rendition of it and every refinement capability
|
||||
whose base is a rendition of it. An object that lists no capability is not media
|
||||
(`codecs_not_media`). A `WAI1` whose capability is not refinement-only is described
|
||||
by its capability and fallback as above even when it carries `refines`, which a
|
||||
client does not read for it (§10.4). A layer object (§10.4) has no element of its own: its
|
||||
capability follows `+` in the `alt` of the base its `refines.base` names among the
|
||||
sequence's objects — a `WAI1`, through its `capability` (staged delivery never takes
|
||||
a base through its fallback), or a component of a `WAI2`, by the content hash of the
|
||||
component's exploded envelope ([SPEC](../SPEC.md) §7.1), through the component's
|
||||
capability. A sequence with a layer whose base it does not hold cannot be described
|
||||
(`codecs_layer_unbound`). `alt`s are ordered by first appearance, and the
|
||||
capabilities after `+` by first appearance in table order, then a sequence's layers
|
||||
in order. The union is exhaustive: a sink that finds, in a head it has verified, a
|
||||
selectable, companion or refinement capability the union does not list refuses that
|
||||
object (`codecs_undeclared`). A layer is declared only when its capability follows `+`
|
||||
in the `alt` of its own base's capability — the base its `refines.base` names, as
|
||||
above, which the sink holds — and not by following `+` in any other `alt`; a sink
|
||||
that does not hold that base cannot find the layer declared.
|
||||
- **Floor.** The floor of a sequence is the `alt`s whose first `cap` is selectable in
|
||||
**every** object that is not a layer, each with the capabilities after `+` that are
|
||||
bound to it, or joined to it by a layer, in every such object. For one object, the
|
||||
floor is its union. A sequence whose floor is
|
||||
empty MUST NOT be published as one variant (`codecs_no_common_capability`). The
|
||||
floor is written as a `wai-element` with the union's prefix, in HLS in the
|
||||
`X-WAI-FLOOR` attribute (§11.1) and in DASH in a `SupplementalProperty` (§11.1).
|
||||
- **Support.** A sink **supports** an element when it reads the element's container
|
||||
(`wai1`: `WAI1`; `wai2`: both) and supports the first `cap` of at least one `alt`
|
||||
of the **floor**. The capabilities after `+` never affect support: a sink that
|
||||
cannot apply one presents the rendition ([SPEC](../SPEC.md) §7.1). A sink supports
|
||||
a `codecs` value when it supports every element.
|
||||
- An element states capabilities, never parameter sets: a sink that supports a
|
||||
capability may still refuse an object's pin ([SPEC](../SPEC.md) §3.1) and select
|
||||
another rendition.
|
||||
|
||||
### 4.3 Not a question for the browser's decoders
|
||||
|
||||
An interface that answers for the user agent's own decoders (Media Capabilities,
|
||||
`MediaSource.isTypeSupported`, WebCodecs `isConfigSupported`) cannot answer for a WAI
|
||||
element. A sink MUST NOT pass a WAI element to such an interface, and MUST NOT read its
|
||||
answer about a classical codec string as support for a WAI capability, except for a
|
||||
capability whose payload the sink hands to that user-agent decoder (§12).
|
||||
|
||||
## 5. Objects
|
||||
|
||||
### 5.1 One envelope, nothing after it
|
||||
|
||||
An object MUST be exactly one envelope: for `WAI1`, `envelope_len = 12 + man_len +
|
||||
payload_len`; for `WAI2`, `envelope_len = head_len + Σ lengths`. A reader that finds
|
||||
another length refuses the object (`object_length_mismatch`). This rule binds HTTP
|
||||
objects; it does not change which envelopes conform to [SPEC](../SPEC.md).
|
||||
|
||||
### 5.2 Names and aliases
|
||||
|
||||
An object SHOULD be published at a URI whose last path segment is `<cid>.wai`. Such a
|
||||
URI names those bytes forever: a publisher MUST NOT serve other bytes under it.
|
||||
|
||||
An object MAY also be served under an alias. An alias MUST serve exactly the object's
|
||||
bytes with its `ETag`, and MUST NOT serve other bytes at any time. A client learns no
|
||||
content hash from a response, so before it presents anything fetched by alias it MUST
|
||||
verify the bytes against a cid it holds from a listing (a `cid` fragment or a
|
||||
`<cid>.wai` URI in a playlist) or from a verified receipt.
|
||||
|
||||
### 5.3 Response headers
|
||||
|
||||
For a `GET` or `HEAD` of an object an origin MUST send:
|
||||
|
||||
```
|
||||
Content-Type: application/wai (MAY carry ; codecs="…" of §4 for that object)
|
||||
Cache-Control: public, max-age=31536000, immutable, no-transform
|
||||
ETag: "<cid>"
|
||||
Accept-Ranges: bytes
|
||||
Access-Control-Allow-Origin: *
|
||||
Access-Control-Expose-Headers: Content-Range, Content-Length, ETag
|
||||
Timing-Allow-Origin: *
|
||||
```
|
||||
|
||||
It MUST NOT send `Content-Encoding` other than none, MUST NOT send `Vary`, and MUST
|
||||
NOT select bytes by any request header. An object listed only at a live edge (§11.4)
|
||||
MAY carry a smaller `max-age`, not less than the time it stays listed. An origin MAY
|
||||
send `Repr-Digest: sha-256=:<base64>:` (RFC 9530 §3) over the whole envelope.
|
||||
|
||||
An origin MUST answer a preflight for an object with
|
||||
`Access-Control-Allow-Headers: Range, Priority` and `Access-Control-Max-Age`.
|
||||
|
||||
A server MUST answer a single byte-range request on an object with `206` and a
|
||||
`Content-Range` whose complete length is `envelope_len` (RFC 9110 §14.4, §15.3.7), or
|
||||
with `200` and the whole object. It MAY answer a multi-range request with `200`.
|
||||
|
||||
### 5.4 Sizes
|
||||
|
||||
A publisher SHOULD give each listed object's `n` and, for a `WAI2`, its `h` (§3.1). A
|
||||
client that holds `n` below `1009` fetches the object whole: no head proof can cover
|
||||
less than one chunk.
|
||||
|
||||
### 5.5 Every media class (informative)
|
||||
|
||||
The binding is the same for every capability. What a range fetch saves, and where a
|
||||
refinement sits, depend on the media class.
|
||||
|
||||
| registry media | typical object | range fetch saves | refinement, carried as |
|
||||
|---|---|---|---|
|
||||
| image | `WAI2`: a pinned integer learned rendition beside standard image renditions | every rendition but the pick | a refinement component, or layer objects |
|
||||
| audio | a `WAI1` per segment; or a `WAI2` with an integer or learned codec beside a standard one | the renditions not picked | a refinement component, or layer objects |
|
||||
| video | one object per segment or part, aligned with a classical ladder (§11) | the same, per segment | an enhancement companion such as `wai.video.lcevc` (§10.3), a refinement component, or layer objects |
|
||||
| metadata (companions) | bound to a rendition in a `WAI2` | fetched only with its rendition | — |
|
||||
| interactive (worlds, feeds, avatars) | snapshot and op-delta objects | head first: decide, then fetch | later op-deltas are separate objects |
|
||||
| volumetric | `WAI2`: an integer splat codec beside a standard mesh, splat or point-cloud payload | the rendition not picked | a refinement component, or layer objects |
|
||||
| haptic, text | small objects | nothing: below 1009 bytes they are fetched whole (§5.4) | — |
|
||||
| binary (parameter sets, circuits, ternary) | a parameter set served as an immutable resource, verified by its pin (§8.5) | — | — |
|
||||
| records and derivations | carried as objects, never named in `codecs` (`codecs_not_media`) | — | — |
|
||||
|
||||
## 6. The head proof
|
||||
|
||||
### 6.1 What it proves
|
||||
|
||||
A head proof lets a client verify the first `covered_len` envelope bytes against the cid
|
||||
without the rest of the object. The content hash is a BLAKE3 root over 1024-byte chunks
|
||||
of `X = "moq-jwp:object\x01" ‖ envelope`; the proof is the chaining values of the
|
||||
subtrees of `X` that lie wholly at or after offset `c = 15 + covered_len`. It is a
|
||||
pure function of the object: anyone holding the object can produce it, and a wrong one
|
||||
fails verification.
|
||||
|
||||
### 6.2 Format (`<cid>.wai.hp`)
|
||||
|
||||
All integers are little-endian ([SPEC](../SPEC.md) §2).
|
||||
|
||||
| offset | size | field |
|
||||
|---|---|---|
|
||||
| 0 | 4 | magic `57 48 50 31` ("WHP1") |
|
||||
| 4 | 32 | cid, raw bytes |
|
||||
| 36 | 8 | `envelope_len` (u64) |
|
||||
| 44 | 8 | `covered_len` (u64), equal to §2's formula |
|
||||
| 52 | 1 | `n_cv` (u8), at most 64 |
|
||||
| 53 | 32·n_cv | chaining values, in verification order |
|
||||
|
||||
The file is exactly `53 + 32·n_cv` bytes. When `covered_len = envelope_len`, `n_cv` is
|
||||
0 and the client verifies the whole object: its content hash is computed over the bytes
|
||||
it holds. A head proof is served under §5.3's cache headers with
|
||||
`Content-Type: application/octet-stream`.
|
||||
|
||||
### 6.3 Verification
|
||||
|
||||
With `L = 15 + envelope_len`, `c = 15 + covered_len`, and
|
||||
`left(n) = 1024 · 2^⌊log₂((n−1)/1024)⌋`, when `covered_len < envelope_len`:
|
||||
|
||||
```
|
||||
cv(start, len):
|
||||
if start ≥ c: take the next chaining value from the proof
|
||||
elif start + len ≤ c: the non-root chaining value of the len bytes of X at start,
|
||||
a subtree whose first chunk has counter start/1024
|
||||
else: parent_cv(cv(start, left(len)), cv(start+left(len), len−left(len)))
|
||||
root = parent_root(cv(0, left(L)), cv(left(L), L − left(L)))
|
||||
```
|
||||
|
||||
`parent_cv` and `parent_root` are BLAKE3's parent node in hash mode, without and with
|
||||
the ROOT flag. `c` is a multiple of 1024 whenever `covered_len < envelope_len`, so
|
||||
every subtree the procedure meets lies wholly before `c` or wholly at or after it once
|
||||
it is split far enough. A client checks, in order:
|
||||
|
||||
1. the file has the format above; its cid is the object's; its `envelope_len` equals
|
||||
the complete length of every `Content-Range` and the length of every `200` body it
|
||||
received; and its `covered_len` is `envelope_len`, with `n_cv` 0, or is less than
|
||||
it with `15 + covered_len` a multiple of 1024 (`head_proof_malformed`);
|
||||
2. the chaining values are consumed exactly, and `root` equals the cid
|
||||
(`head_proof_mismatch`);
|
||||
3. `envelope_len` is the length the verified head gives (§5.1), and `covered_len` is
|
||||
§2's formula for that head (`head_proof_malformed`). The root does not encode the
|
||||
object's length, so a wrong `envelope_len` that leaves the tree's shape unchanged
|
||||
passes check 2 and is refused here.
|
||||
|
||||
No covered byte is read as a head before check 2 passes. A producer writes the
|
||||
chaining values in the order the procedure consumes them.
|
||||
|
||||
## 7. Byte-range fetch of a `WAI2` component
|
||||
|
||||
A client that fetches part of an object MUST verify, before it presents or decodes
|
||||
anything, every byte it uses: the head against the cid (§6), and each component it
|
||||
uses against the `sha256` of its entry in that verified head ([SPEC](../SPEC.md) §7.1).
|
||||
The procedure:
|
||||
|
||||
1. In parallel, request `Range: bytes=0-(E−1)`, with `E` the `h` fragment if given and
|
||||
16369 otherwise, and request `<cid>.wai.hp`.
|
||||
2. Check each response (§7.1). If `covered_len` exceeds the bytes received, request the
|
||||
rest of the first `covered_len` bytes.
|
||||
3. Verify the head (§6.3). Parse it (`head_malformed`); apply §5.1; refuse a
|
||||
capability the variant's union does not list (`codecs_undeclared`).
|
||||
4. Select from the head exactly as [SPEC](../SPEC.md) §7.1 selects, deferring only
|
||||
the component-digest test to the bytes. A deployer policy MAY reorder and restrict
|
||||
the entries considered, as SPEC §7.1 allows. The result is the picked entry, the
|
||||
companions SPEC §7.1 binds to it, and the refinement components whose
|
||||
`refines.base` is its `id`.
|
||||
5. If the picked entry, or a bound companion the sink will apply that is not a
|
||||
refinement (§2), states no `sha256`, go to step 7 (`component_digest_absent`). A
|
||||
refinement that states none is dropped: the client does not request it, and the
|
||||
base's path does not change.
|
||||
6. Request the ranges of the picked component and of the companions and refinements it
|
||||
will apply that the client does not already hold: one request per range, merging
|
||||
ranges that touch only when they are in the same priority class (§10.1), so a
|
||||
refinement is always requested on its own. Verify each against its entry's
|
||||
`sha256`. On a mismatch of the picked component or of a companion that is not a
|
||||
refinement (`component_digest_mismatch`), discard every byte held past
|
||||
`covered_len` and go to step 7. A refinement that mismatches is dropped alone.
|
||||
7. Request the rest, `bytes=covered_len-` (or `GET` the whole object), verify the whole
|
||||
envelope against the cid (`object_digest_mismatch`), and select as [SPEC](../SPEC.md)
|
||||
§7.1 selects on the whole envelope.
|
||||
|
||||
A client SHOULD also go to step 7 when the base-class bytes step 6 would request — the
|
||||
picked component's and those of the companions that are not refinements — are at least
|
||||
90 % of the bytes after `covered_len`. A refinement's bytes never count, so a refinement
|
||||
never moves its base to step 7; when the base goes there, the rest of the object carries
|
||||
the refinement's bytes with it. A client MUST NOT send a multi-range request and rely
|
||||
on a multipart answer. A `WAI1` has one component, its payload: a client decides from
|
||||
its verified head whether it can dispatch it ([SPEC](../SPEC.md) §4), and only then
|
||||
fetches the rest after the head. A layer object (§10.4) is never dispatched: the
|
||||
client fetches it whole, at refinement urgency, verifies it against its cid, and hands
|
||||
it to [staged-delivery](staged-delivery.md) §4.
|
||||
|
||||
### 7.1 Response checks
|
||||
|
||||
For each request a client checks, in order:
|
||||
|
||||
- the status is `206`, or `200` with a body that is the whole object
|
||||
(`http_status_unexpected`);
|
||||
- no `Content-Encoding` other than none is present (`content_coding_present`);
|
||||
- on a `206`, `Content-Range` names exactly the requested range, its last position
|
||||
reduced to `envelope_len − 1` where the request reached past the end
|
||||
(`content_range_mismatch`), and its complete length equals the head proof's
|
||||
`envelope_len` (`complete_length_mismatch`). Each of its numbers is one or more ASCII
|
||||
digits, RFC 9110 §14.4's `1*DIGIT`: a sign, a separator, a space inside the field or
|
||||
another script's digit is refused the same way;
|
||||
- on a `200`, the body length equals the head proof's `envelope_len`
|
||||
(`complete_length_mismatch`).
|
||||
|
||||
A refused response's bytes are discarded and never presented. A refused base-class
|
||||
response ends the fetch; a refused refinement-class response abandons that refinement
|
||||
(§10.2), and the base goes on. A client MAY retry once with `Cache-Control: no-cache`.
|
||||
|
||||
### 7.2 One pick on every path
|
||||
|
||||
On bytes that verify, §7 picks the same entry that [SPEC](../SPEC.md) §7.1 picks on
|
||||
the whole envelope: the digest test runs on the bytes either way, and every case in
|
||||
which a range client cannot run it (no `sha256`, a mismatch) is decided on the whole
|
||||
envelope. A range client never picks an entry the whole-envelope selection would skip,
|
||||
and never skips one it would pick. When the head alone shows that no entry can be
|
||||
picked, the client stops there and reports the reasons the head gives; an entry whose
|
||||
pin was refused may also fail its digest, which only its bytes would show.
|
||||
|
||||
## 8. Receipts, keys, grants and parameter sets over HTTP
|
||||
|
||||
### 8.1 Receipt files
|
||||
|
||||
`rcpt/<h>.json`, resolved against the object's URI, holds exactly one receipt: a group
|
||||
receipt whose link hash ([jwp-receipts](jwp-receipts.md) §3 step 3) is `h`, in its
|
||||
canonical form, or a claim whose `link_hash` (§2.4.3) is `h`, in its JCS form. It is
|
||||
immutable and served under §5.3's cache headers with `Content-Type: application/json`.
|
||||
A client refuses a receipt file whose computed hash is not its name
|
||||
(`receipt_name_mismatch`). A group receipt's `parent_receipt_hash` is the name of its
|
||||
parent's file, so a client resolves [jwp-receipts](jwp-receipts.md) §3 step 3 by
|
||||
fetching `rcpt/<parent_receipt_hash>.json`, and reports `parent_not_held` when it
|
||||
cannot, never skipping the step.
|
||||
|
||||
### 8.2 Receipt index
|
||||
|
||||
`<cid>.wai.receipts` is the sidecar carriage of the `WAI_RECEIPT` property
|
||||
([moq-streaming-format](moq-streaming-format.md) §5):
|
||||
|
||||
```json
|
||||
{ "kind": "wai-receipt-index", "object": "<cid>",
|
||||
"delivery": [ { "property": { …WAI_RECEIPT… }, "object_receipt": { …jwp-receipts §2.4.2… } } ],
|
||||
"claims": [ "<link hash>" ] }
|
||||
```
|
||||
|
||||
It is mutable and is served with `Cache-Control: no-cache, no-transform` and an `ETag`
|
||||
of its own. It is a locator, never evidence. A client checks each item on its own and
|
||||
ignores, item by item, one that fails:
|
||||
|
||||
- a delivery item: the property's `content_hash` and the object receipt's
|
||||
`content_hash` equal the cid (`receipt_not_about_object`); the group receipt is
|
||||
fetched as `rcpt/<property.group_receipt>.json` and verified under
|
||||
[jwp-receipts](jwp-receipts.md) §3 steps 1–5, step 4 as §8.3 states; the property
|
||||
is checked against the object receipt as moq-streaming-format §5 states;
|
||||
- a claim: verified under [jwp-receipts](jwp-receipts.md) §3.1; its `subject` is the
|
||||
cid or the link hash of a group listed in the index. A soft-binding claim
|
||||
([jwp-receipts](jwp-receipts.md) §2.6.2) is reported under the ancestor rule (§3.2):
|
||||
as attesting the ancestor, never the object in hand.
|
||||
|
||||
### 8.3 A range-fetched object
|
||||
|
||||
A client that holds only the head and some components cannot recompute the content
|
||||
hash over the envelope. Under [jwp-receipts](jwp-receipts.md) §3 step 4 it takes the
|
||||
cid its head proof verified, and the receipt then attests the object the cid names; the
|
||||
components it holds are attested through the verified head's `sha256` values.
|
||||
|
||||
A component's exploded envelope ([SPEC](../SPEC.md) §7.1, *Exploding a component*) is
|
||||
a function of the verified head and the component's bytes, so a client that holds them
|
||||
builds it. A receipt about that exploded envelope is checked under step 4 over the
|
||||
envelope the client built, as for any object: this is how a refinement component
|
||||
fetched by range meets [staged-delivery](staged-delivery.md) §10, which applies a layer
|
||||
only on a receipt checked over its envelope bytes.
|
||||
|
||||
### 8.4 Keys, statuses and grants
|
||||
|
||||
| resource | location | `Cache-Control` |
|
||||
|---|---|---|
|
||||
| key log ([jwp-receipts](jwp-receipts.md) §7.2) | `/.well-known/wai-keys` | `max-age=300, no-transform` |
|
||||
| key status (§7.9 there) | `/.well-known/wai-key-status` | `max-age` at most a quarter of the head's `status_max_age_s`, `no-transform` |
|
||||
| module grants (§7.9 there) | `/.well-known/wai-grants/<module digest>.json`: `{"kind":"wai-module-grants","module":"<digest>","grants":[…]}` | `max-age=300, no-transform` |
|
||||
|
||||
All are `application/json`. A verifier that **anchors** an origin fetches its key log
|
||||
from the origin over HTTPS without following a redirect, as [jwp-receipts](jwp-receipts.md)
|
||||
§7.3 states, and with `Cache-Control: no-cache`. Later revisions MAY come through any
|
||||
cache: each is checked against its parent, and a rolled-back copy is reported
|
||||
(`key_log_rolled_back`). The grants file is a locator: each grant is checked on its
|
||||
own.
|
||||
|
||||
### 8.5 Parameter sets
|
||||
|
||||
A parameter set MAY be served as an immutable resource named by its pin's digest
|
||||
(`<base>/<sha256>`), under §5.3's cache headers with `application/octet-stream`. A
|
||||
sink verifies it by its pin ([SPEC](../SPEC.md) §3.1) and decodes against it only if
|
||||
the checks of [jwp-receipts](jwp-receipts.md) §7.9 pass. A set they refuse is not
|
||||
held, and dispatch continues as [SPEC](../SPEC.md) §4 and §7.1 state.
|
||||
|
||||
### 8.6 Playlist receipts
|
||||
|
||||
A WAI playlist (§11) is authenticated by a manifest receipt (`wai.video.manifest`,
|
||||
[SPEC](../SPEC.md) §5) whose `manifest_hash` is the BLAKE3 of the playlist bytes,
|
||||
served immutable at `mr/<manifest_hash>.json` resolved against the playlist URI. Each
|
||||
revision of a live playlist has its own receipt, whose `parent_receipt_hash` names the
|
||||
previous one. A playlist receipt MAY carry `render`, the rendering parameters of the
|
||||
playlist it signs (version, media sequence, discontinuity sequence, part target,
|
||||
whether it ends), and `extras`, the per-segment lines that rendering adds
|
||||
(discontinuities, program date-time, parts, layer lines). When both are absent, the
|
||||
playlist is rendered as before. `manifest_hash` covers the rendered bytes, so both are
|
||||
bound by the signature through it.
|
||||
|
||||
## 9. Cache rules
|
||||
|
||||
An HTTP cache that stores and serves objects under this extension is a transparent
|
||||
byte store, not a relay under [jwp-receipts](jwp-receipts.md) §4.1 (see §4.5 there).
|
||||
It is bound by §4.3 there, and it MUST:
|
||||
|
||||
1. store and serve an object byte for byte, and not transform it (RFC 9111 §5.2.2.6)
|
||||
or apply a content coding to it;
|
||||
2. answer a single byte-range request from a stored complete object with `206` and the
|
||||
exact bytes (RFC 9110 §14), and with `416` and `Content-Range: bytes */<len>` for
|
||||
an unsatisfiable range. On a miss it MAY forward the range, or fetch the whole object
|
||||
and answer from it;
|
||||
3. keep `ETag`, `Content-Type`, `Cache-Control` and, on a `200`, `Content-Length` as the
|
||||
origin sent them;
|
||||
4. not vary a stored object by any request header;
|
||||
5. evaluate `If-Range` with a strong comparison (RFC 9110 §13.1.5, §8.8.3.2);
|
||||
6. store head proofs, receipt files, playlist receipts and parameter sets under the same
|
||||
rules, and revalidate receipt indexes on every use and the other §8.4 resources per
|
||||
their `Cache-Control`;
|
||||
7. where a configuration cannot meet 1–6 for a request, pass the request through
|
||||
uncached and relay the origin's bytes unchanged.
|
||||
|
||||
A cache SHOULD NOT revalidate an object while it is fresh (RFC 8246 §2). A node that
|
||||
serves other bytes than an object's — recompressed, re-packed or split — derives a new
|
||||
object: it publishes the new bytes under their own cid, with its own receipts and a
|
||||
soft-binding claim to the ancestor ([jwp-receipts](jwp-receipts.md) §2.6.2), listed in
|
||||
the new object's index, and MUST NOT serve them under the original name or an alias of
|
||||
it. Over HTTP an edge selects only by serving such derived objects; a client selects by
|
||||
range.
|
||||
|
||||
## 10. Delivery order, deadlines and refinements
|
||||
|
||||
### 10.1 Priority
|
||||
|
||||
A base and each refinement are separate requests. A client MUST NOT let a request whose
|
||||
bytes it needs for a base wait on a refinement's bytes, and a cache MUST NOT hold a
|
||||
base response for a refinement request. A client presents a base as soon as it has
|
||||
verified it (§7), and applies each refinement when that refinement arrives and
|
||||
verifies; it never holds a verified base back for its refinements. A base, once
|
||||
verified, is decided: no later response changes its bytes or its outcome. Of a
|
||||
response to a refinement request, a client keeps only the bytes of the range it
|
||||
requested, even from a `200` that carries the whole object (§7.1), so a refinement's
|
||||
response never reaches its base's bytes, before or after the base verified. A client SHOULD signal urgency 2 for a base
|
||||
and urgency 5 with incremental for a refinement (RFC 9218 §4.1, §4.2): by the
|
||||
`Priority` header (§5 there), or in a browser by the fetch `priority` option. A
|
||||
deadline never applies to a base: a base request that is never answered ends the fetch
|
||||
(`base_unanswered`).
|
||||
|
||||
### 10.2 Deadlines
|
||||
|
||||
A client presenting live sets a deadline on every refinement request: the latest time
|
||||
at which the refinement, once received and verified, can still be applied, which for a
|
||||
layer is its `lead_ms` before the presentation time of the first unit it covers
|
||||
([staged-delivery](staged-delivery.md) §8). When the deadline passes, the client
|
||||
abandons the request and presents what it has. This is the HTTP counterpart of the
|
||||
delivery timeout after which a MoQ relay drops an object; on either binding a
|
||||
refinement never stalls its base, and a layer that arrives late is not applied to the
|
||||
units that are already due (`deadline-missed`, staged-delivery §8).
|
||||
|
||||
A deadline runs on the client's clock from the moment the base verified, the moment
|
||||
§10.1 presents it: the time the response that completed the base was received. A
|
||||
refinement that has not arrived and verified within it is abandoned, and the base,
|
||||
already presented, is unaffected. What the clock read before the base verified never
|
||||
moves that start. A clock that steps back has not passed a deadline until it again
|
||||
reaches it, and a deadline past the clock's range is at its end. The reference
|
||||
client's session reads no clock of its own: every response it is given carries the
|
||||
caller's time, and the caller tells it the time between responses.
|
||||
|
||||
### 10.3 Enhancement companions and refinement components
|
||||
|
||||
A companion whose registered role is `enhancement` (for example `wai.video.lcevc`,
|
||||
[SPEC](../SPEC.md) §5) and a component with `role: "refinement"` are refinements inside
|
||||
the object: each range is requested in its own request, at refinement priority, after
|
||||
or beside the base component's. A client that abandons an enhancement companion
|
||||
presents the base, as [SPEC](../SPEC.md) §7.1 lets a sink that does not apply a
|
||||
companion present the rendition; one that abandons a refinement component presents the
|
||||
stage it has ([staged-delivery](staged-delivery.md) §7). A refinement that states no
|
||||
`sha256`, or does not match it, is dropped (§7 steps 5 and 6), and its base is
|
||||
unaffected.
|
||||
|
||||
### 10.4 Layer objects
|
||||
|
||||
A layer object is a `WAI1` whose capability is refinement-only ([SPEC](../SPEC.md) §5,
|
||||
[staged-delivery](staged-delivery.md) §3). Whether an object is one is decided by its
|
||||
capability alone: a `WAI1` whose capability is a payload is an ordinary object even
|
||||
when it carries `refines` (which staged delivery §3 forbids its producer to write), so
|
||||
a client dispatches it under SPEC §4 and does not read `refines`. A client never
|
||||
dispatches a layer object under SPEC §4: it
|
||||
fetches it whole, at refinement urgency (§10.1) and under a deadline (§10.2), verifies
|
||||
it against its cid, and hands it to [staged-delivery](staged-delivery.md) §4, which
|
||||
decides what it does to its base. A client that finds one by its head (§7) does the
|
||||
same with the rest of it. Against a declared union (§4.2), a layer is declared only in
|
||||
the `alt` of its own base's capability. An object a playlist lists as a layer that is
|
||||
not one, a payload carrying `refines` among them, is refused (`not_a_layer`); a layer's
|
||||
request abandoned at its deadline, or whose response
|
||||
§7.1 refuses, leaves the layer not delivered (§10.5).
|
||||
|
||||
A WAI media playlist lists the layer objects of a segment or part after its `EXTINF`
|
||||
or `EXT-X-PART` line, in the order a sink applies them:
|
||||
|
||||
```
|
||||
#WAI-LAYER:N=<1..255>,URI="<cid>.wai#n=…"
|
||||
```
|
||||
|
||||
Its attributes are an HLS attribute list (RFC 8216 §4.2): `N` a decimal integer from 1
|
||||
to 255, `URI` a quoted string, neither named twice; an attribute this revision does not
|
||||
define is ignored. A line that breaks these is `layer_tag_malformed`.
|
||||
|
||||
`N` is a locator: it lets a client choose the depth it fetches before it fetches any
|
||||
layer. The layer an envelope brings its units to is its `refines.layer`, inside the
|
||||
envelope and so inside its content hash; a sink reads it from there and ignores `N`, as
|
||||
[staged-delivery](staged-delivery.md) §13 reads a MoQ layer header. The base is layer
|
||||
0, the segment or part URI. A client fetches to the depth its policy chooses, each
|
||||
layer under §10.1 and §10.2.
|
||||
|
||||
### 10.5 Objects not delivered
|
||||
|
||||
A refinement a client abandoned is not delivered to it. Its receipts stay listed in
|
||||
the index; the client neither presents it nor reports its delivery, and the group
|
||||
receipt stays valid for the objects it did receive ([jwp-receipts](jwp-receipts.md)
|
||||
§4.4).
|
||||
|
||||
## 11. HLS and DASH adjacency
|
||||
|
||||
### 11.1 Mode N (native objects) — the default
|
||||
|
||||
- The classical multivariant playlist and MPD are unchanged. A publisher MAY add one
|
||||
line to the multivariant playlist:
|
||||
`#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="<URI of wai.m3u8>"`,
|
||||
resolved against the playlist's URI. No other line changes.
|
||||
- `wai.m3u8` uses HLS playlist syntax. Its media segments are WAI objects, which are
|
||||
not a format of RFC 8216 §3.1, so it is not an HLS presentation and MUST NOT be
|
||||
offered to a client that does not implement this extension. Each `EXT-X-STREAM-INF`
|
||||
carries `CODECS` (the union, §4.2), `X-WAI-FLOOR` (the floor), and `BANDWIDTH` and
|
||||
`AVERAGE-BANDWIDTH` computed from the objects' `envelope_len` over their durations
|
||||
(peak and mean of `8·envelope_len/duration`), never stated by hand.
|
||||
- `wai.mpd` is a separate MPD with one `AdaptationSet` per WAI stream,
|
||||
`mimeType="application/wai"`, `@codecs` (the union), an
|
||||
`EssentialProperty schemeIdUri="tag:transaction.science,2026:wai/http-delivery"`
|
||||
so a client that does not know it ignores the set, and a
|
||||
`SupplementalProperty schemeIdUri="tag:transaction.science,2026:wai/floor"` whose
|
||||
`value` is the floor.
|
||||
|
||||
### 11.2 Alignment
|
||||
|
||||
A WAI media playlist is **adjacent** to a classical media playlist when, segment for
|
||||
segment, they have the same `EXT-X-MEDIA-SEQUENCE`, `EXT-X-DISCONTINUITY-SEQUENCE`,
|
||||
`EXTINF` durations to the millisecond, discontinuities and `EXT-X-PROGRAM-DATE-TIME`
|
||||
values, and, where both carry parts, the same `EXT-X-PART-INF:PART-TARGET` and the same
|
||||
part durations to the millisecond in each segment. In DASH: the same `Period@id`s and
|
||||
`@start`s, and a `SegmentTimeline` with the same timescale, `t` and `d`. The WAI
|
||||
segment (or part) with a sequence number covers exactly the presentation interval of the
|
||||
classical one, so a sink can move between them at any boundary. A packager MUST NOT
|
||||
publish a WAI playlist that breaks this (`adjacency_misaligned`). `#WAI-LAYER` lines
|
||||
do not affect alignment.
|
||||
|
||||
### 11.3 Live at segment granularity (Live-S)
|
||||
|
||||
Each segment is one object, listed as `<cid>.wai#n=…&h=…` once it is complete. A client
|
||||
presents an object only after its bytes verify against the listed cid. The latency
|
||||
from the end of a segment's media interval to presentation is at least
|
||||
|
||||
`S_floor = e + p + r_pl + r_obj + v`
|
||||
|
||||
where `e` is encode and packaging time, `p` the time to publish and list, `r_pl` the
|
||||
wait for a playlist that lists the object (with blocking reload, one request time),
|
||||
`r_obj` the object request time (one round trip and transfer), and `v` the verification
|
||||
time.
|
||||
|
||||
### 11.4 Low-latency live (Live-P)
|
||||
|
||||
Each part is its own object, served under an alias, and listed with its cid:
|
||||
|
||||
```
|
||||
#EXT-X-PART-INF:PART-TARGET=0.333
|
||||
#EXT-X-PART:DURATION=0.333,INDEPENDENT=YES,URI="live/720/1200.3.wai#cid=<hex>&n=…"
|
||||
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="live/720/1200.4.wai"
|
||||
```
|
||||
|
||||
- The alias of a part names those bytes for its lifetime (§5.2). A preload hint names
|
||||
the alias before the part exists; the origin holds the request until it does.
|
||||
- A client presents a part only after its bytes hash to the cid that a later playlist
|
||||
revision lists for that alias. Issued together, the hint request and a blocking
|
||||
reload (`_HLS_msn`, `_HLS_part`) complete when the part is complete, so the floor is
|
||||
|
||||
`P_floor = e + max(r_obj, r_pl) + v`
|
||||
|
||||
against `e + r_obj` for a part presented without verification: the added latency is
|
||||
`max(0, r_pl − r_obj) + v`, with `v` the BLAKE3 time of one part.
|
||||
- The segment line names a separate segment object, published when the segment closes;
|
||||
it serves every client behind the live edge. Parts stop being listed as the HLS
|
||||
specification permits, and an origin MAY then delete them.
|
||||
- An `INDEPENDENT=YES` part is one whose envelope decodes without another object.
|
||||
|
||||
### 11.5 Live to on demand
|
||||
|
||||
Every object is immutable from the moment it is listed, so a live playlist becomes
|
||||
on-demand by appending `EXT-X-ENDLIST`; no object is rewritten.
|
||||
|
||||
### 11.6 Playlist authentication
|
||||
|
||||
A WAI playlist SHOULD be authenticated by its receipt (§8.6). A client that verifies it
|
||||
holds the cids, durations and order its signer published. Without it, which objects a
|
||||
playlist names is only as trustworthy as the connection to the origin that served it;
|
||||
each object is still bound to the cid the playlist names. A client reports which held:
|
||||
**listed** (bytes match a cid in a playlist fetched over HTTPS from the origin the
|
||||
multivariant playlist names), **signed** (that playlist's receipt, or the object's
|
||||
receipt, verified), and the confirmation level of [jwp-receipts](jwp-receipts.md) §7.4.
|
||||
In Live-P a client MAY present at **listed** and verify the playlist receipt after
|
||||
presenting; it MUST NOT report the part as signed until it has.
|
||||
|
||||
A WAI segment whose payload is a classical segment's media relates to the classical
|
||||
package's segment map (`wai.video.package`) by sequence number: the package's
|
||||
`content_hash` identifies the media and the cid identifies the envelope that carries it.
|
||||
They are hashes of different bytes, and neither replaces the other.
|
||||
|
||||
### 11.7 Mode F (fragmented MP4) — provisional
|
||||
|
||||
Appendix B defines ISOBMFF sample entries that carry one WAI envelope per sample. Such
|
||||
segments are fragmented MP4 (RFC 8216 §3.3), so a WAI track could be a variant of the
|
||||
classical multivariant playlist. The 4CCs are not registered: until they are, Mode F
|
||||
MUST NOT be published outside test material.
|
||||
|
||||
### 11.8 CTA-5005-B
|
||||
|
||||
In Mode N the classical presentation's conformance to CTA-5005-B is preserved by byte
|
||||
equality of its manifests. The `EXT-X-SESSION-DATA` option and Mode F are to be checked
|
||||
against CTA-5005-B's clauses before use.
|
||||
|
||||
## 12. Capability probe
|
||||
|
||||
### 12.1 The Sink Capability Descriptor
|
||||
|
||||
```json
|
||||
{ "wai_sink": 1,
|
||||
"containers": ["WAI1", "WAI2"],
|
||||
"caps": { "<capability>": { "via": "wasm" | "ua" | "host",
|
||||
"asked": { "<ua codec string>": true | false },
|
||||
"provisional": true | false } },
|
||||
"held_sets": { "<capability>": [ "<pin sha256>" ] } }
|
||||
```
|
||||
|
||||
- `wasm`: decoded by the sink's own compiled decoder. `ua`: by a user-agent decoder,
|
||||
under the codec strings in `asked`. `host`: by a native host. A capability the sink
|
||||
cannot decode is absent.
|
||||
- `provisional` is true for a `ua` capability: the decoder answered for the profiles
|
||||
asked, not for a given payload.
|
||||
- `held_sets` lists, per capability, the digests of the parameter sets that passed
|
||||
[jwp-receipts](jwp-receipts.md) §7.9 when the descriptor was built. A revoked set is
|
||||
never listed.
|
||||
|
||||
### 12.2 Asking the user agent
|
||||
|
||||
A capability is `ua` only if the sink asked the user-agent decoder it will hand the
|
||||
payload to, under that decoder's own name, and the answer was supported: WebCodecs
|
||||
`isConfigSupported` for a video or audio elementary stream, or an image decode of a
|
||||
known sample. Media Capabilities is asked only as a further question about a decoder
|
||||
already found. Before decoding a payload through a `ua` capability, a sink MUST ask
|
||||
again with the configuration that payload's own header gives; a refusal makes the
|
||||
capability unsupported for that object, and selection continues.
|
||||
|
||||
### 12.3 Support
|
||||
|
||||
`supports(codecs, descriptor)` is §4.2's rule with "reads the container" read as "is in
|
||||
`containers`" and "supports `cap`" read as "`wai.<cap>` is in `caps`".
|
||||
|
||||
### 12.4 Capable share (informative)
|
||||
|
||||
For a WAI variant V and a set C of sessions observed in a stated window:
|
||||
|
||||
capable_share(V, C) = |{ s ∈ C : supports(codecs(V), descriptor(s)) ∧ probe_ok(V, s) }| / |C|
|
||||
|
||||
`probe_ok(V, s)` holds when the session decoded V's published probe object and its
|
||||
output had the published digest (an exact tier), or decoded without error (any other
|
||||
tier). A report states |C|, the window, how C was sampled and the descriptor version. It
|
||||
is a share of sessions, not of devices or people.
|
||||
|
||||
## 13. Reasons
|
||||
|
||||
| reason | raised at |
|
||||
|---|---|
|
||||
| `object_length_mismatch` | §5.1 |
|
||||
| `head_malformed` | §7 step 3 |
|
||||
| `head_proof_malformed`, `head_proof_mismatch` | §6.3 |
|
||||
| `http_status_unexpected`, `content_coding_present`, `content_range_mismatch`, `complete_length_mismatch` | §7.1 |
|
||||
| `component_digest_absent` | §7 step 5 (a reason to fetch whole, never a skip) |
|
||||
| `component_digest_mismatch` | §7 step 6 ([SPEC](../SPEC.md) §7.1's reason) |
|
||||
| `object_digest_mismatch` | §7 step 7, or any whole fetch |
|
||||
| `receipt_name_mismatch`, `receipt_not_about_object` | §8.1, §8.2 |
|
||||
| `codecs_malformed`, `codecs_not_media`, `codecs_candidate`, `codecs_no_common_capability`, `codecs_undeclared`, `codecs_layer_unbound` | §4 |
|
||||
| `base_unanswered` | §10.1 |
|
||||
| `not_a_layer`, `layer_tag_malformed` | §10.4 |
|
||||
| `adjacency_misaligned` | §11.2 |
|
||||
|
||||
Selection, pin, receipt, key and module reasons are those of [SPEC](../SPEC.md) §3.1
|
||||
and §7.1 and [jwp-receipts](jwp-receipts.md) §3, §7.7 and §7.9, and the layer reasons
|
||||
those of [staged-delivery](staged-delivery.md) §4.2, unchanged. An object at which
|
||||
selection picks nothing is inert at the sink, with each entry's reason, as SPEC §7.1
|
||||
step 5 states.
|
||||
|
||||
## 14. Conformance
|
||||
|
||||
- [`http-conformance/`](../http-conformance/) holds objects, head proofs and cases for
|
||||
§4–§7 and §10 and their reasons, with positive cases. `wai_http_vectors verify`
|
||||
(also `wai http vectors verify`) and the independent `http_verify.py`, which shares no
|
||||
code with `wai-rs`, report the same outcome for every case, and for every case of a
|
||||
seeded differential over inputs outside the corpus (`wai_http_vectors fuzz`): every
|
||||
`Content-Range`, `codecs` value, `#WAI-LAYER` line, manifest, member type, encryption,
|
||||
`refines` and layer sequence it generates.
|
||||
- [`cache-conformance/`](../cache-conformance/) checks §9 against HTTP caches and
|
||||
reports, per cache label and version, the configuration that meets it. Products are
|
||||
not named in published reports.
|
||||
|
||||
## 15. Security and privacy considerations
|
||||
|
||||
- **What a range fetch establishes.** A head proof binds the head to the cid; the head's
|
||||
`sha256` values bind each component to the head; a receipt binds the cid to a signer;
|
||||
a playlist receipt binds the list of cids to a signer. Each link is a hash or a
|
||||
signature.
|
||||
- **Changed bytes.** A cache that changes bytes changes a hash and is refused. A content
|
||||
coding is refused before any byte is used (§7.1), so no decompression runs on the
|
||||
range path.
|
||||
- **Lengths.** `Content-Range`, `Content-Length`, `n` and `h` are untrusted; every
|
||||
length that matters is checked against the head proof.
|
||||
- **Playlists.** An unsigned playlist can name other objects; §11.6 says what a client
|
||||
then holds.
|
||||
- **Privacy.** The ranges a sink fetches reveal which rendition it picked, and so part
|
||||
of its capabilities, to every cache and the origin. A sink that does not want this
|
||||
fetches whole objects.
|
||||
- **Unsigned files.** Receipt indexes, grants files and aliases are locators, never
|
||||
evidence.
|
||||
|
||||
## Appendix A — CDNI configuration
|
||||
|
||||
Path patterns `/objects/*.wai`, `*.wai.hp`, `rcpt/*` and `mr/*` take the RFC 8006
|
||||
metadata `MI.Cache` with `include-query-strings: []`, and `MI.SourceMetadata`; a
|
||||
receipt index (`*.wai.receipts`) takes the cache rule with no storage beyond
|
||||
revalidation. RFC 8006 has no metadata for range handling, transformation or a time-to-
|
||||
live override: a configuration interface that has them states, for these paths, the
|
||||
behaviour §9 requires.
|
||||
|
||||
## Appendix B — ISOBMFF sample entries (provisional; 4CCs not registered)
|
||||
|
||||
```
|
||||
aligned(8) class WAIConfigurationBox extends FullBox('waiC', 0, 0) {
|
||||
unsigned int(8) wai_major; // 1
|
||||
unsigned int(8) max_minor; // highest manifest MINOR in the track
|
||||
unsigned int(16) element_len;
|
||||
unsigned int(8) element[element_len]; // the track's union element, UTF-8
|
||||
unsigned int(16) floor_len;
|
||||
unsigned int(8) floor[floor_len]; // the track's floor element, UTF-8
|
||||
}
|
||||
class WAIVisualSampleEntry(type) extends VisualSampleEntry(type) { WAIConfigurationBox config; } // 'vide'
|
||||
class WAIAudioSampleEntry(type) extends AudioSampleEntry(type) { WAIConfigurationBox config; } // 'soun'
|
||||
class WAIMetaSampleEntry(type) extends MetaDataSampleEntry(type) { WAIConfigurationBox config; } // 'meta'
|
||||
// type = 'wai1' when every sample is a WAI1 envelope, else 'wai2'
|
||||
```
|
||||
|
||||
- Each sample is one envelope, byte-identical, so its cid is unchanged.
|
||||
- A sample is a sync sample when it decodes alone.
|
||||
- Composition time equals decode time.
|
||||
- There is no `encv`/`enca` wrapping; confidentiality is [SPEC](../SPEC.md) §6.
|
||||
|
||||
## Appendix C — Mapping to moq-streaming-format
|
||||
|
||||
| MoQ ([moq-streaming-format](moq-streaming-format.md)) | HTTP (this extension) |
|
||||
|---|---|
|
||||
| object = one envelope (§3 there) | object `<cid>.wai` (§5.1) |
|
||||
| `packaging: "wai"` | media type `application/wai` |
|
||||
| catalog `wai_capabilities`, exhaustive (§4 there) | `codecs` union (§4.2); `codecs_undeclared` is the same protocol error |
|
||||
| (no floor) | `X-WAI-FLOOR` / DASH `SupplementalProperty` |
|
||||
| `WAI_RECEIPT` property (§5 there) | index item `property` (§8.2) |
|
||||
| group receipt per group | `rcpt/<link hash>.json`; a group per segment (on demand) or per segment with its parts (Live-P) |
|
||||
| a layer object on its own track | a layer object listed by `#WAI-LAYER` (§10.4); its layer is its `refines.layer` either way |
|
||||
| a subscriber's layer cap | the client's depth policy; nothing is sent to the edge |
|
||||
| a relay drops layers above the cap, receipts intact | the client does not request them; caches never drop; receipts stay listed (§10.5) |
|
||||
| the delivery timeout after which a relay drops an object | the client's refinement deadline (§10.2) |
|
||||
| publisher priority | RFC 9218 urgency (§10.1) |
|
||||
|
||||
## Appendix D — Worked example
|
||||
|
||||
**The envelope.** A `WAI2` with a 560-byte manifest:
|
||||
|
||||
```
|
||||
{"wai":"1.1","media":"video","intent":"replicate","renditions":[{"capability":"wai.video.av1","kind":"av1","id":"low","sha256":"f541874101876255b4baf3a739778d04cb9cba25ffa38b30bc1fb8b0701f2a45"},{"capability":"wai.video.av1","kind":"av1","id":"high","sha256":"b76bf31be170d9df8eafbd020941b5fd4b7ab017af6a1e5c1c03e909aa8f9fb7"},{"capability":"wai.meta.hdr10plus","kind":"descriptor","id":"hdr","sha256":"74739b7242c81aa5ec2a1601fd04a5f81bed0302970e6a51f3a7dc9e92ea8520","role":"companion","companion_of":"wai.video.av1","companion_of_id":"high"}],"target":null}
|
||||
```
|
||||
|
||||
Payloads, synthetic (they show the layout, and decode to nothing):
|
||||
- `low`: 3,000 B, byte i = (7i+3) mod 256;
|
||||
- `high`: 50,000 B, byte i = (13i+5) mod 256;
|
||||
- `hdr`: 200 B, byte i = (31i+1) mod 256.
|
||||
|
||||
**Layout.**
|
||||
- `head_len` = 594. Table: `0300 00000000 b80b0000 b80b0000 50c30000 08cf0000 c8000000`.
|
||||
- `envelope_len` = 53,794.
|
||||
- cid = `eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228`.
|
||||
- `covered_len` = 1009, `n_cv` = 6, so the proof file is 245 B.
|
||||
- Component ranges: `low` [594, 3593], `high` [3594, 53593], `hdr` [53594, 53793].
|
||||
- `codecs` = `wai2.video.av1+meta.hdr10plus`, and the floor is the same.
|
||||
|
||||
The plans below assume the object is listed with `#h=1009` (and `n`), so the first
|
||||
request ends at `covered_len`. Without `h`, the first request is 16,369 bytes.
|
||||
|
||||
**Plan 1: the sink picks `low`** (the table's order, or a policy under low throughput).
|
||||
- Requests: `bytes=0-1008` (1,009 B) and the proof (245 B), then `bytes=1009-3593`
|
||||
(2,585 B).
|
||||
- Body bytes: 3,839, with `envelope_len` 53,794.
|
||||
- Without `h`: the first request, `bytes=0-16368`, holds `low` whole; 2 requests and
|
||||
16,614 body bytes.
|
||||
|
||||
**Plan 2: a policy reorders to `high`, which binds `hdr`.**
|
||||
- [3594, 53793] is 50,200 B of base-class bytes: 951 ‰ of the 52,785 B after
|
||||
`covered_len`, which is at least 900 ‰.
|
||||
- So the client requests `bytes=1009-53793` and verifies the whole envelope.
|
||||
- Body bytes: 53,794 and the proof's 245.
|
||||
- Without `h`: the first request holds [0, 16368], and the base-class bytes still
|
||||
needed, [16369, 53793], are 37,425 B, 709 ‰ of the rest; so a range fetch of 3
|
||||
requests and 54,039 body bytes.
|
||||
|
||||
## Appendix E — References and their verification status
|
||||
|
||||
| reference | status |
|
||||
|---|---|
|
||||
| RFC 9110 §7.7, §8.4, §8.8.3, §13.1.5, §14.1–§14.6, §15.3.7, §15.5.17 | read |
|
||||
| RFC 9111 §5.2.2.6 · RFC 8246 §2 · RFC 6381 §3.2 · RFC 6838 §3.1, §3.2, §5.6 | read |
|
||||
| RFC 9530 §2, §3 · RFC 9218 §4.1, §4.2, §5 | read |
|
||||
| RFC 8216 §3.1, §3.3, §4.3.4.2, §4.3.4.4 · the HLS second-edition draft (its date only) | read |
|
||||
| RFC 8006 `MI.*` objects | read |
|
||||
| The HLS second-edition draft's `EXT-X-PART`, `EXT-X-PRELOAD-HINT` and blocking-reload clauses | not re-read here: §11.4 is to be checked against them |
|
||||
| CTA-5005-B | title and date only |
|
||||
| The BLAKE3 specification | the tree rule is checked by the reference implementation against whole-input hashing |
|
||||
| RFC 8615, RFC 8785, RFC 4151, RFC 5234, ISO/IEC 14496-12 and -15, ISO/IEC 23000-19, ISO/IEC 23009-1, the Fetch Standard (CORS-safelisted `Range`, `priority`), WebCodecs, Media Capabilities | not re-read here |
|
||||
|
||||
Every row not read is read before this extension leaves Draft.
|
||||
|
|
@ -67,6 +67,10 @@ Out of scope:
|
|||
the wire; receipts ride alongside WAI envelopes in the carrier
|
||||
protocol (e.g. a MoQT object header field, an HTTP trailer, a
|
||||
sidecar file).
|
||||
[http-delivery](http-delivery.md) §8 defines the HTTP carriage:
|
||||
receipt files named by link hash, a per-object index of `WAI_RECEIPT`
|
||||
properties, playlist receipts, and the locations of key logs,
|
||||
statuses and grants.
|
||||
- A codec. The receipt chain is opaque to the WAI capability dispatch
|
||||
in WAI SPEC.md §4. A receipt attests bytes; it does not interpret
|
||||
them.
|
||||
|
|
@ -1093,6 +1097,15 @@ match the algorithm style of WAI SPEC.md §4 (numbered, terminating).
|
|||
(`content_hash_mismatch`: the envelope was mutated in transit). An
|
||||
object receipt missing a member of §2.4.2, or carrying one neither
|
||||
§2.4.2 nor its label defines, is `malformed`.
|
||||
A verifier that received only part of an object over HTTP takes as
|
||||
the recomputed content hash the cid that a verified head proof
|
||||
establishes ([http-delivery](http-delivery.md) §6), provided every
|
||||
component it uses was checked against its entry's digest
|
||||
(http-delivery §7). The object receipt then attests the object that
|
||||
cid names. A receipt about a component's own object, its exploded
|
||||
envelope ([SPEC](../SPEC.md) §7.1), is checked over that envelope,
|
||||
which the verifier builds from the verified head and the component's
|
||||
bytes (http-delivery §8.3).
|
||||
|
||||
5. **Verify the Merkle inclusion proof.** In this order:
|
||||
- If the object receipt carries a class label (§2.4.2), check it as
|
||||
|
|
@ -1489,6 +1502,24 @@ chain across hops, a conforming relay:
|
|||
- Truncate the receipt to fewer fields than §2.4 requires, even when
|
||||
the carrier framing has size pressure.
|
||||
|
||||
### 4.4. Objects not delivered
|
||||
|
||||
A relay MAY drop an object whose delivery timeout has passed, and an
|
||||
HTTP client MAY abandon one ([http-delivery](http-delivery.md) §10.2).
|
||||
An object a subscriber did not receive is not delivered to it: it
|
||||
neither presents the object nor reports its delivery, and the group
|
||||
receipt stays valid for every object it did receive, each by its own
|
||||
inclusion proof (§3 step 5). A relay that drops an object MUST still
|
||||
forward the group receipt.
|
||||
|
||||
### 4.5. HTTP caches
|
||||
|
||||
An HTTP cache that stores and serves objects and receipt files byte for
|
||||
byte under [http-delivery](http-delivery.md) §9 is not a relay in the
|
||||
sense of §4.1 and §4.2: it fans out nothing, verifies nothing, and
|
||||
forwards what was requested. It is bound by §4.3. A node that verifies,
|
||||
filters or selects receipts is a relay.
|
||||
|
||||
---
|
||||
|
||||
## 5. Test vectors
|
||||
|
|
@ -2480,6 +2511,9 @@ revision the sink holds that the head descends from by `prev` — with its
|
|||
`seq`, under whose `root` the signature verifies. A status that is not
|
||||
held is ignored, as an injected revision is (§7.2).
|
||||
|
||||
Over HTTP, a status and an origin's grants are served where
|
||||
[http-delivery](http-delivery.md) §8.4 states.
|
||||
|
||||
**Module origins.** A sink holds a module only from an origin it accepts
|
||||
module grants from for the envelope and capability at hand — its
|
||||
**module origins**, each recorded with where it came from:
|
||||
|
|
|
|||
|
|
@ -164,6 +164,9 @@ Stated so it is not re-argued:
|
|||
should use CMSF; this packaging is for tracks whose objects are WAI envelopes.
|
||||
- It does **not** make MoQ WAI's transport. WAI remains carrier-agnostic; this
|
||||
is one binding among the several `jwp-receipts` §1 anticipates.
|
||||
- It is not the only binding. [http-delivery](http-delivery.md) binds the same
|
||||
objects to HTTP, admits `WAI2` envelopes with verified component ranges, carries
|
||||
`WAI_RECEIPT` in a sidecar index, and maps this extension in its Appendix C.
|
||||
- It does **not** pin a MOQT draft revision. MOQT is pre-Last-Call; an extension
|
||||
that pinned a moving target would be wrong within a meeting cycle. Where MOQT
|
||||
and this text disagree, MOQT wins and this text is the thing that must change.
|
||||
|
|
|
|||
2
wai/http-conformance/.gitattributes
vendored
Normal file
2
wai/http-conformance/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Hashed files: a line-ending conversion on checkout would change every digest.
|
||||
* -text
|
||||
157
wai/http-conformance/README.md
Normal file
157
wai/http-conformance/README.md
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# http-conformance — HTTP delivery
|
||||
|
||||
The conformance corpus of [http-delivery](../extensions/http-delivery.md) (§14): objects
|
||||
served as `<cid>.wai` with their `WHP1` head proofs (§6) and ETag sidecars, and cases
|
||||
for §4–§7 — the head, the head proof, the response checks, the plans, selection on the
|
||||
head and on the whole envelope, the byte-range procedure with its fallback, the
|
||||
`codecs` grammar, union, floor and support. Two implementations check every case and
|
||||
must agree:
|
||||
|
||||
- `wai_http_vectors verify .` (also `wai http vectors verify .`), in `wai-rs`, built
|
||||
with the feature `http`;
|
||||
- `python3 http_verify.py .`, written from the extension text and SPEC §7.1, sharing no
|
||||
code with `wai-rs`. It needs only the Python standard library, and reads
|
||||
`../claim-conformance/_blake3.py` (BLAKE3 from its specification) and
|
||||
`../registry/capabilities.json` (the published registry export).
|
||||
|
||||
## Files
|
||||
|
||||
| path | what |
|
||||
|---|---|
|
||||
| `objects/<cid>.wai` | each object, named by its content hash (§5.2) |
|
||||
| `objects/<cid>.wai.hp` | its head proof (§6.2) |
|
||||
| `objects/<cid>.wai.etag` | its `ETag` value, `"<cid>"` (§5.3) |
|
||||
| `objects.json` | each object's id, cid, path, `n`, `h`, `codecs` element and what it covers |
|
||||
| `bad/O6.wai` | O5 with one byte after it: not one envelope (§5.1) |
|
||||
| `cases/*.hp` | head proofs that are wrong in one way each |
|
||||
| `cases.json` | one case per line: `{name, op, input, expect}` |
|
||||
| `src/` | the frozen sources, with their SHA-256 in `src/SOURCES.json` |
|
||||
| `blake3_vectors.json` | a subset of the published BLAKE3 test vectors (CC0), which `http_verify.py` checks itself against first |
|
||||
| `http_verify.py` | the independent verifier |
|
||||
| `falsify/*.patch` | defects that `wai_http_vectors verify` must catch (below) |
|
||||
|
||||
## Objects
|
||||
|
||||
| id | object | covers |
|
||||
|---|---|---|
|
||||
| O1 | `WAI1`, the pinned `wai.neural.int_mlicpp` vector of `wai-rs/tests/vectors/int_mlicpp/` | a `WAI1` head; a proof that covers the whole object |
|
||||
| O2 | `WAI2` image: that pinned rendition, AVIF, JPEG XL and PNG of the one picture it decodes to | pins: held, not held |
|
||||
| O3 | `WAI2` video: AV1 at two rates, HDR10+ metadata bound to the higher by `companion_of_id` | range plans; a companion; tampering |
|
||||
| O4 | `WAI2` whose first selectable entry states no `sha256` | `component_digest_absent`: the whole object, the same pick |
|
||||
| O5 | a small `WAI1` | fetched whole (§5.4) |
|
||||
| O6 | O5 with a trailing byte (`bad/`) | `object_length_mismatch` |
|
||||
| O7 | `WAI2` whose first entry states a wrong `sha256` | the skip on both paths |
|
||||
| O8 | `WAI2`: an AVC base, an LCEVC enhancement bound to it, an AV1 alternative | classes; the refinement deadline (§10) |
|
||||
| O9a–O9c | three segments carrying {AV1, integer learned video}, {AV1}, {AV1} | union and floor (§4.2) |
|
||||
| OD | Appendix D's envelope | Appendix D's numbers, with `h` and without |
|
||||
| OH | a `WAI2` whose head spans several chunks | covered subtrees past the first chunk |
|
||||
| O10, O10L1, O10L2 | the staged-delivery FLAC base of `../staged-conformance/cases/audio_flac_rate_ladder/` and its two layers | layer objects (§10.4): never dispatched, fetched whole at refinement class, described after `+` through `refines.base` |
|
||||
| OS, ON, OW | an AVC base with an LCEVC enhancement nearly four times its size; the enhancement's digest right, absent, wrong | a refinement never moves its base (§7, §10.3); on a clock (`timed_*`), the base ready before its refinement, the deadline from the response that verified it, and a refinement's whole-object `200` that never reaches the base (§10.1, §10.2) |
|
||||
| OT | two renditions the first request holds whole | bytes changed in the first response are not reused |
|
||||
| OF | a `WAI1` naming a record beside a payload fallback | the union lists what SPEC §4 dispatches |
|
||||
| OE1, OE2s, OE2t | an encrypted `WAI1`; `WAI2`s whose `security` is a string, `true` | SPEC §6.4 on both containers |
|
||||
| OR | a refinement component whose `refines` is malformed | it refines nothing |
|
||||
| OPf, OPe, OPw | a `WAI1` naming `wai.text.zstd` whose `refines` is `false`, `{}`, and well formed | a payload carrying `refines` is an ordinary object: dispatched, described by its capability, `not_a_layer` when listed as a layer (§10.4) |
|
||||
| ORN | an AV1 base most of the object and a refinement component stating no `sha256` | the refinement dropped (`component_digest_absent`) on the whole path and the range path alike |
|
||||
| `bad/OK.wai`, `bad/OKN.wai` | a string key id; a numeric `kind` | the manifest's member types |
|
||||
|
||||
The HDR10+ block, the AVC bases and the LCEVC enhancements are synthetic: they carry
|
||||
the right start codes and decode to nothing, which transport does not need. OD's and
|
||||
OT's payloads are byte patterns, as are OE2s', OE2t's, OR's and ORN's. Every other payload is real: the integer renditions are
|
||||
the committed vectors; the images and the AV1 streams are in `src/`, encoded once
|
||||
outside this repository, and the AV1 streams were checked to decode.
|
||||
|
||||
## Operations
|
||||
|
||||
| op | input | expect |
|
||||
|---|---|---|
|
||||
| `parse_head` | an object (or `file`), optionally a `prefix` length, an `envelope_len` and a `patch_object` | `need`, or the head's version, lengths, `covered_len` and component ranges, or `error` |
|
||||
| `head_proof` | an object, optionally a `proof` file and a `patch_object` | `covered_len`, `n_cv`, `envelope_len`, or `error` |
|
||||
| `plan` | an object, the `wanted` entries with their classes, the `held` ranges, a `threshold_permille` | `requests` with their classes, or `whole_rest` and why |
|
||||
| `response` | the request, status, `Content-Range`, `Content-Encoding`, body length and object length | `ok` with the bytes held, or `error` |
|
||||
| `select` | an object and a sink: `advertises`, `held` (parameter-set files), `order` | `pick` and `skipped`, or `inert` |
|
||||
| `fetch` | an object, a `fragment`, a sink (with `applies`, `declared` and `layer_base`), a `tamper` | the pick, how it was verified, why the whole object if it was, body bytes, requests, companions, refinements, abandoned refinements, skips and the component's SHA-256, or `error` |
|
||||
| `fetch_timed` | an object, a sink, a `deadline_ms`, and a `script` of steps each at a time on the caller's clock: `base` (answer every base-class request, and those that follow), `base_once` (those outstanding now), `refinement` (every outstanding refinement request, by its range or by a `200` carrying the whole object with the byte at `flip` changed), `tick` | the step after which the base was first ready and its time, the base then and at the end, what each tick abandoned, and the outcome (null while there is none) |
|
||||
| `same_pick` | as `fetch` | the pick (or `inert`), which `select` and `fetch` must both give (§7.2) |
|
||||
| `codecs` | a `codecs` value | each element rendered, `null` for a classical one, or `error` |
|
||||
| `union_floor` | a sequence of objects | the union and the floor, or `error` |
|
||||
| `supports` | a floor, the sink's containers and capabilities | `supports` |
|
||||
| `layer_tag` | a `#WAI-LAYER` line | `n` and `uri`, or `error` |
|
||||
|
||||
An input names an object by id, a `file`, or an `envelope_hex` written inline;
|
||||
`union_floor` also takes `envelopes_hex`. A sink's `declared` is the union it was given
|
||||
(§4.2), parsed before anything is fetched; `layer_base` (an object id) or
|
||||
`layer_base_hex` is the base it holds, against which a layer is declared only in the
|
||||
`alt` of that base's capability. A `declared` that does not parse, or a base that is
|
||||
not an object, refuses the case with its code.
|
||||
|
||||
`fetch` runs the reference client's policy: a first request of 16369 bytes unless `h`
|
||||
is given, never past `n`; the rest of the object when the planned base-class ranges
|
||||
reach 900 ‰ of the bytes after `covered_len` (`threshold_permille` changes it); and,
|
||||
with `listed_as_layer`, one whole request at refinement class. The origin answers
|
||||
single ranges with `206`. A `tamper` changes what it serves: `patch_object` its bytes;
|
||||
`kind` `gzip` (every object response labelled gzip), `no_refinement` (no
|
||||
refinement-class request answered) or `no_proof` (the head proof's request not
|
||||
answered); `first_range_flip` one byte of the first range response only; `refinement`
|
||||
every refinement-class range response one way (`gzip`, `bad_range`, `416`,
|
||||
`complete_length`, `plus_sign`, `short_200`). Body bytes count the head proof; a
|
||||
delivery states its role (`base` or `layer`), the refinements abandoned and those
|
||||
dropped with why.
|
||||
|
||||
## Regenerating
|
||||
|
||||
cargo run --no-default-features --features http --bin wai_http_vectors -- gen ../http-conformance
|
||||
|
||||
from `wai/wai-rs`. Every file is a function of `src/`, the two committed vectors the
|
||||
objects carry, and the code, so a second run writes the same bytes; `verify`
|
||||
regenerates in memory and requires them. The generator also holds each negative case
|
||||
to the reason it exists for, and OD to Appendix D's cid, length and proof size.
|
||||
|
||||
## Falsification
|
||||
|
||||
Each patch in `falsify/` names the function it breaks and, on its first line, the
|
||||
cases that must fail. The `falsify` job of `.github/workflows/wai-http.yml` applies
|
||||
each to a fresh worktree, requires the build to succeed, and requires
|
||||
`wai_http_vectors verify` to exit non-zero naming those cases. `http_verify.py
|
||||
--mutate NAME` does the same on the Python side (`left-half`, `no-root`,
|
||||
`counter-zero` fail the proof cases; `floor-as-codecs` fails `union_O9`; `blake3-iv`
|
||||
fails the self-test; `base-at-done`, `tick-noop` and `refinement-unclipped` fail the
|
||||
`timed_*` cases), and the job checks a differential: `wai_http_vectors diff`
|
||||
writes random envelopes' covered prefixes and proofs, `http_verify.py diff` verifies
|
||||
every one, and, with one chaining-value byte changed in ten of them, refuses exactly
|
||||
those ten.
|
||||
|
||||
## Outside the corpus
|
||||
|
||||
wai_http_vectors fuzz . --seed S --count N --out cases.json
|
||||
python3 http_verify.py cases cases.json .
|
||||
|
||||
`fuzz` writes seeded cases, in the corpus's format and with wai-rs's outcomes, for the
|
||||
inputs where two readers most easily part, in families:
|
||||
|
||||
- `fuzz_response`: `Content-Range` values, half of them the request's own range with
|
||||
one number given a sign, a separator, a space or another script's digits;
|
||||
- `fuzz_codecs`: `codecs` values (the `wai` prefix in any case, duplicates, empty
|
||||
elements, non-ASCII); `fuzz_layer_tag`: `#WAI-LAYER` lines;
|
||||
- `fuzz_wai1_head`, `fuzz_wai1_fetch`, `fuzz_wai2_head`, `fuzz_wai2_select`,
|
||||
`fuzz_wai2_fetch`, `fuzz_union`: `WAI1` and `WAI2` manifests (member types,
|
||||
`security` in every shape, `refines` well and badly formed, members named twice,
|
||||
refinement components with a right, a wrong and no `sha256`);
|
||||
- `fuzz_wai1_refines_fetch`, `fuzz_wai1_refines_union`: well-formed `WAI1`s whose
|
||||
`refines` varies over payload and refinement-only capabilities, fetched as objects
|
||||
and as listed layers;
|
||||
- `fuzz_layer_union`, `fuzz_layer_declared`: a base with a layer that names it or does
|
||||
not, and the layer against a declared union and a base — its own base's `alt`, that
|
||||
`alt` without it, another `alt` with it, the other container, no base, another base;
|
||||
- `fuzz_component_layer_union`, `fuzz_component_layer_declared`: a layer whose base is
|
||||
a `WAI2` component, named by the hash of the component's exploded envelope;
|
||||
- `fuzz_large_fetch`: `WAI2` objects past the first request, with a base, a refinement
|
||||
or companion and an alternative each stating a right, a wrong or no digest, fetched
|
||||
through an origin tampered one way, at thresholds either side of the plan;
|
||||
`fuzz_whole_refine`: the whole path, its base's digest absent or the threshold low,
|
||||
with a refinement whose digest is right, wrong or absent.
|
||||
|
||||
A refusal is an outcome like any other: `http_verify.py cases` must give the same
|
||||
code. A case wai-rs gives no outcome for stops the run, and `fuzz` prints, per family,
|
||||
the cases it generated and how many were refused, so a family that only ever refuses
|
||||
(or never does) shows. `wai-conformance.yml` runs it at two seeds.
|
||||
BIN
wai/http-conformance/bad/O6.wai
Normal file
BIN
wai/http-conformance/bad/O6.wai
Normal file
Binary file not shown.
BIN
wai/http-conformance/bad/OK.wai
Normal file
BIN
wai/http-conformance/bad/OK.wai
Normal file
Binary file not shown.
BIN
wai/http-conformance/bad/OKN.wai
Normal file
BIN
wai/http-conformance/bad/OKN.wai
Normal file
Binary file not shown.
73
wai/http-conformance/blake3_vectors.json
Normal file
73
wai/http-conformance/blake3_vectors.json
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"note": "A subset of the published BLAKE3 test vectors (CC0): hash mode, the first 32 bytes of each output. Each input is input_len bytes, byte i being i mod 251.",
|
||||
"cases": [
|
||||
{
|
||||
"input_len": 0,
|
||||
"hash": "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
|
||||
},
|
||||
{
|
||||
"input_len": 1,
|
||||
"hash": "2d3adedff11b61f14c886e35afa036736dcd87a74d27b5c1510225d0f592e213"
|
||||
},
|
||||
{
|
||||
"input_len": 63,
|
||||
"hash": "e9bc37a594daad83be9470df7f7b3798297c3d834ce80ba85d6e207627b7db7b"
|
||||
},
|
||||
{
|
||||
"input_len": 64,
|
||||
"hash": "4eed7141ea4a5cd4b788606bd23f46e212af9cacebacdc7d1f4c6dc7f2511b98"
|
||||
},
|
||||
{
|
||||
"input_len": 65,
|
||||
"hash": "de1e5fa0be70df6d2be8fffd0e99ceaa8eb6e8c93a63f2d8d1c30ecb6b263dee"
|
||||
},
|
||||
{
|
||||
"input_len": 1023,
|
||||
"hash": "10108970eeda3eb932baac1428c7a2163b0e924c9a9e25b35bba72b28f70bd11"
|
||||
},
|
||||
{
|
||||
"input_len": 1024,
|
||||
"hash": "42214739f095a406f3fc83deb889744ac00df831c10daa55189b5d121c855af7"
|
||||
},
|
||||
{
|
||||
"input_len": 1025,
|
||||
"hash": "d00278ae47eb27b34faecf67b4fe263f82d5412916c1ffd97c8cb7fb814b8444"
|
||||
},
|
||||
{
|
||||
"input_len": 2048,
|
||||
"hash": "e776b6028c7cd22a4d0ba182a8bf62205d2ef576467e838ed6f2529b85fba24a"
|
||||
},
|
||||
{
|
||||
"input_len": 2049,
|
||||
"hash": "5f4d72f40d7a5f82b15ca2b2e44b1de3c2ef86c426c95c1af0b6879522563030"
|
||||
},
|
||||
{
|
||||
"input_len": 3072,
|
||||
"hash": "b98cb0ff3623be03326b373de6b9095218513e64f1ee2edd2525c7ad1e5cffd2"
|
||||
},
|
||||
{
|
||||
"input_len": 3073,
|
||||
"hash": "7124b49501012f81cc7f11ca069ec9226cecb8a2c850cfe644e327d22d3e1cd3"
|
||||
},
|
||||
{
|
||||
"input_len": 4096,
|
||||
"hash": "015094013f57a5277b59d8475c0501042c0b642e531b0a1c8f58d2163229e969"
|
||||
},
|
||||
{
|
||||
"input_len": 4097,
|
||||
"hash": "9b4052b38f1c5fc8b1f9ff7ac7b27cd242487b3d890d15c96a1c25b8aa0fb995"
|
||||
},
|
||||
{
|
||||
"input_len": 8193,
|
||||
"hash": "bab6c09cb8ce8cf459261398d2e7aef35700bf488116ceb94a36d0f5f1b7bc3b"
|
||||
},
|
||||
{
|
||||
"input_len": 16384,
|
||||
"hash": "f875d6646de28985646f34ee13be9a576fd515f76b5b0a26bb324735041ddde4"
|
||||
},
|
||||
{
|
||||
"input_len": 31744,
|
||||
"hash": "62b6960e1a44bcc1eb1a611a8d6235b6b4b78f32e7abc4fb4c6cdcce94895c47"
|
||||
}
|
||||
]
|
||||
}
|
||||
322
wai/http-conformance/cases.json
Normal file
322
wai/http-conformance/cases.json
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
[
|
||||
{"name":"parse_head_O1","op":"parse_head","input":{"object":"O1"},"expect":{"version":"WAI1","head_len":351,"envelope_len":859,"covered_len":859,"components":[[351,508]]}},
|
||||
{"name":"parse_head_O3","op":"parse_head","input":{"object":"O3"},"expect":{"version":"WAI2","head_len":607,"envelope_len":98613,"covered_len":1009,"components":[[607,20766],[21373,77040],[98413,200]]}},
|
||||
{"name":"parse_head_O5","op":"parse_head","input":{"object":"O5"},"expect":{"version":"WAI1","head_len":157,"envelope_len":251,"covered_len":251,"components":[[157,94]]}},
|
||||
{"name":"parse_head_O9b","op":"parse_head","input":{"object":"O9b"},"expect":{"version":"WAI1","head_len":170,"envelope_len":18004,"covered_len":1009,"components":[[170,17834]]}},
|
||||
{"name":"parse_head_OD","op":"parse_head","input":{"object":"OD"},"expect":{"version":"WAI2","head_len":594,"envelope_len":53794,"covered_len":1009,"components":[[594,3000],[3594,50000],[53594,200]]}},
|
||||
{"name":"parse_head_OH","op":"parse_head","input":{"object":"OH"},"expect":{"version":"WAI2","head_len":5308,"envelope_len":23519,"covered_len":6129,"components":[[5308,18211]]}},
|
||||
{"name":"parse_head_need_OD_300","op":"parse_head","input":{"object":"OD","prefix":300},"expect":{"need":570}},
|
||||
{"name":"parse_head_need_OD_580","op":"parse_head","input":{"object":"OD","prefix":580},"expect":{"need":594}},
|
||||
{"name":"parse_head_need_O1_6","op":"parse_head","input":{"object":"O1","prefix":6},"expect":{"need":8}},
|
||||
{"name":"object_length_trailing","op":"parse_head","input":{"file":"bad/O6.wai"},"expect":{"error":"object_length_mismatch"}},
|
||||
{"name":"object_length_short","op":"parse_head","input":{"object":"O3","envelope_len":98614},"expect":{"error":"object_length_mismatch"}},
|
||||
{"name":"head_malformed_magic","op":"parse_head","input":{"object":"O5","patch_object":[[3,"33"]]},"expect":{"error":"head_malformed"}},
|
||||
{"name":"head_malformed_offset","op":"parse_head","input":{"object":"OD","patch_object":[[574,"01"]]},"expect":{"error":"head_malformed"}},
|
||||
{"name":"head_proof_ok_O1","op":"head_proof","input":{"object":"O1"},"expect":{"covered_len":859,"n_cv":0,"envelope_len":859}},
|
||||
{"name":"head_proof_ok_O10","op":"head_proof","input":{"object":"O10"},"expect":{"covered_len":1009,"n_cv":1,"envelope_len":1825}},
|
||||
{"name":"head_proof_ok_O10L1","op":"head_proof","input":{"object":"O10L1"},"expect":{"covered_len":1009,"n_cv":4,"envelope_len":12711}},
|
||||
{"name":"head_proof_ok_O10L2","op":"head_proof","input":{"object":"O10L2"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":18367}},
|
||||
{"name":"head_proof_ok_O2","op":"head_proof","input":{"object":"O2"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":20250}},
|
||||
{"name":"head_proof_ok_O3","op":"head_proof","input":{"object":"O3"},"expect":{"covered_len":1009,"n_cv":7,"envelope_len":98613}},
|
||||
{"name":"head_proof_ok_O4","op":"head_proof","input":{"object":"O4"},"expect":{"covered_len":1009,"n_cv":6,"envelope_len":39361}},
|
||||
{"name":"head_proof_ok_O5","op":"head_proof","input":{"object":"O5"},"expect":{"covered_len":251,"n_cv":0,"envelope_len":251}},
|
||||
{"name":"head_proof_ok_O7","op":"head_proof","input":{"object":"O7"},"expect":{"covered_len":1009,"n_cv":6,"envelope_len":39437}},
|
||||
{"name":"head_proof_ok_O8","op":"head_proof","input":{"object":"O8"},"expect":{"covered_len":1009,"n_cv":6,"envelope_len":46433}},
|
||||
{"name":"head_proof_ok_O9a","op":"head_proof","input":{"object":"O9a"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":21034}},
|
||||
{"name":"head_proof_ok_O9b","op":"head_proof","input":{"object":"O9b"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":18004}},
|
||||
{"name":"head_proof_ok_O9c","op":"head_proof","input":{"object":"O9c"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":18381}},
|
||||
{"name":"head_proof_ok_OD","op":"head_proof","input":{"object":"OD"},"expect":{"covered_len":1009,"n_cv":6,"envelope_len":53794}},
|
||||
{"name":"head_proof_ok_OE1","op":"head_proof","input":{"object":"OE1"},"expect":{"covered_len":299,"n_cv":0,"envelope_len":299}},
|
||||
{"name":"head_proof_ok_OE2s","op":"head_proof","input":{"object":"OE2s"},"expect":{"covered_len":1009,"n_cv":3,"envelope_len":5251}},
|
||||
{"name":"head_proof_ok_OE2t","op":"head_proof","input":{"object":"OE2t"},"expect":{"covered_len":1009,"n_cv":3,"envelope_len":5242}},
|
||||
{"name":"head_proof_ok_OF","op":"head_proof","input":{"object":"OF"},"expect":{"covered_len":283,"n_cv":0,"envelope_len":283}},
|
||||
{"name":"head_proof_ok_OH","op":"head_proof","input":{"object":"OH"},"expect":{"covered_len":6129,"n_cv":3,"envelope_len":23519}},
|
||||
{"name":"head_proof_ok_ON","op":"head_proof","input":{"object":"ON"},"expect":{"covered_len":1009,"n_cv":6,"envelope_len":38384}},
|
||||
{"name":"head_proof_ok_OPe","op":"head_proof","input":{"object":"OPe"},"expect":{"covered_len":264,"n_cv":0,"envelope_len":264}},
|
||||
{"name":"head_proof_ok_OPf","op":"head_proof","input":{"object":"OPf"},"expect":{"covered_len":267,"n_cv":0,"envelope_len":267}},
|
||||
{"name":"head_proof_ok_OPw","op":"head_proof","input":{"object":"OPw"},"expect":{"covered_len":421,"n_cv":0,"envelope_len":421}},
|
||||
{"name":"head_proof_ok_OR","op":"head_proof","input":{"object":"OR"},"expect":{"covered_len":1009,"n_cv":4,"envelope_len":9434}},
|
||||
{"name":"head_proof_ok_ORN","op":"head_proof","input":{"object":"ORN"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":32659}},
|
||||
{"name":"head_proof_ok_OS","op":"head_proof","input":{"object":"OS"},"expect":{"covered_len":1009,"n_cv":6,"envelope_len":38460}},
|
||||
{"name":"head_proof_ok_OT","op":"head_proof","input":{"object":"OT"},"expect":{"covered_len":1009,"n_cv":4,"envelope_len":12368}},
|
||||
{"name":"head_proof_ok_OW","op":"head_proof","input":{"object":"OW"},"expect":{"covered_len":1009,"n_cv":6,"envelope_len":38460}},
|
||||
{"name":"head_proof_flip_prefix_O3","op":"head_proof","input":{"object":"O3","patch_object":[[1008,"d0"]]},"expect":{"error":"head_proof_mismatch"}},
|
||||
{"name":"head_proof_flip_cv_O3","op":"head_proof","input":{"object":"O3","proof":"cases/head_proof_flip_cv_O3.hp"},"expect":{"error":"head_proof_mismatch"}},
|
||||
{"name":"head_proof_drop_cv_O3","op":"head_proof","input":{"object":"O3","proof":"cases/head_proof_drop_cv_O3.hp"},"expect":{"error":"head_proof_mismatch"}},
|
||||
{"name":"head_proof_extra_cv_O3","op":"head_proof","input":{"object":"O3","proof":"cases/head_proof_extra_cv_O3.hp"},"expect":{"error":"head_proof_mismatch"}},
|
||||
{"name":"head_proof_unaligned_O3","op":"head_proof","input":{"object":"O3","proof":"cases/head_proof_unaligned_O3.hp"},"expect":{"error":"head_proof_malformed"}},
|
||||
{"name":"head_proof_envelope_len_O3","op":"head_proof","input":{"object":"O3","proof":"cases/head_proof_envelope_len_O3.hp"},"expect":{"error":"head_proof_malformed"}},
|
||||
{"name":"head_proof_magic","op":"head_proof","input":{"object":"O3","proof":"cases/head_proof_magic.hp"},"expect":{"error":"head_proof_malformed"}},
|
||||
{"name":"head_proof_other_object","op":"head_proof","input":{"object":"O3","proof":"cases/head_proof_of_O4.hp"},"expect":{"error":"head_proof_malformed"}},
|
||||
{"name":"head_proof_not_formula_OH","op":"head_proof","input":{"object":"OH","proof":"cases/head_proof_not_formula_OH.hp"},"expect":{"error":"head_proof_malformed"}},
|
||||
{"name":"plan_OD_1","op":"plan","input":{"object":"OD","wanted":[[0,"base"]],"held":[[0,1008]]},"expect":{"requests":[[1009,3593,"base"]],"dropped":[]}},
|
||||
{"name":"plan_OD_2","op":"plan","input":{"object":"OD","wanted":[[1,"base"],[2,"base"]],"held":[[0,1008]]},"expect":{"whole_rest":[1009,53793],"why":"range_threshold"}},
|
||||
{"name":"plan_classes_O8","op":"plan","input":{"object":"O8","wanted":[[0,"base"],[1,"refinement"]],"held":[[0,1008]]},"expect":{"requests":[[1009,8598,"base"],[8599,28598,"refinement"]],"dropped":[]}},
|
||||
{"name":"plan_one_class_O8","op":"plan","input":{"object":"O8","wanted":[[0,"base"],[1,"base"]],"held":[[0,1008]]},"expect":{"requests":[[1009,28598,"base"]],"dropped":[]}},
|
||||
{"name":"plan_no_digest_O4","op":"plan","input":{"object":"O4","wanted":[[0,"base"]],"held":[[0,1008]]},"expect":{"whole_rest":[1009,39360],"why":"component_digest_absent"}},
|
||||
{"name":"plan_O3_high","op":"plan","input":{"object":"O3","wanted":[[1,"base"],[2,"base"]],"held":[[0,1008]]},"expect":{"requests":[[21373,98612,"base"]],"dropped":[]}},
|
||||
{"name":"plan_held_O3","op":"plan","input":{"object":"O3","wanted":[[0,"base"]],"held":[[0,1008],[2000,2999]]},"expect":{"requests":[[1009,1999,"base"],[3000,21372,"base"]],"dropped":[]}},
|
||||
{"name":"response_ok","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-3593/53794","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"ok":[1009,3593]}},
|
||||
{"name":"response_wrong_range","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-3592/53794","content_encoding":null,"body_len":2584,"envelope_len":53794},"expect":{"error":"content_range_mismatch"}},
|
||||
{"name":"response_short_body","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-3593/53794","content_encoding":null,"body_len":2000,"envelope_len":53794},"expect":{"error":"content_range_mismatch"}},
|
||||
{"name":"response_gzip","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-3593/53794","content_encoding":"gzip","body_len":2585,"envelope_len":53794},"expect":{"error":"content_coding_present"}},
|
||||
{"name":"response_complete_length","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-3593/53795","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"error":"complete_length_mismatch"}},
|
||||
{"name":"response_complete_unknown","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-3593/*","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"error":"complete_length_mismatch"}},
|
||||
{"name":"response_identity","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-3593/53794","content_encoding":"identity","body_len":2585,"envelope_len":53794},"expect":{"ok":[1009,3593]}},
|
||||
{"name":"response_304","op":"response","input":{"request":[1009,3593],"status":304,"body_len":0,"envelope_len":53794},"expect":{"error":"http_status_unexpected"}},
|
||||
{"name":"response_200_short","op":"response","input":{"request":[1009,3593],"status":200,"body_len":1000,"envelope_len":53794},"expect":{"error":"complete_length_mismatch"}},
|
||||
{"name":"response_200_whole","op":"response","input":{"request":[1009,3593],"status":200,"body_len":53794,"envelope_len":53794},"expect":{"ok":[0,53793]}},
|
||||
{"name":"response_past_end","op":"response","input":{"request":[0,16368],"status":206,"content_range":"bytes 0-899/900","body_len":900,"envelope_len":900},"expect":{"ok":[0,899]}},
|
||||
{"name":"response_206_unasked","op":"response","input":{"request":null,"status":206,"content_range":"bytes 0-9/900","body_len":10,"envelope_len":900},"expect":{"error":"http_status_unexpected"}},
|
||||
{"name":"select_O2_held","op":"select","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"]},"expect":{"pick":0,"skipped":[]}},
|
||||
{"name":"select_O2_not_held","op":"select","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":[]},"expect":{"pick":1,"skipped":[[0,"prior_not_held"]]}},
|
||||
{"name":"select_O2_png_only","op":"select","input":{"object":"O2","advertises":["wai.image.png"],"held":[]},"expect":{"pick":3,"skipped":[[0,"missing_capability"],[1,"missing_capability"],[2,"missing_capability"]]}},
|
||||
{"name":"select_O3","op":"select","input":{"object":"O3","advertises":["wai.video.av1"]},"expect":{"pick":0,"skipped":[]}},
|
||||
{"name":"select_O7","op":"select","input":{"object":"O7","advertises":["wai.video.av1"]},"expect":{"pick":1,"skipped":[[0,"component_digest_mismatch"]]}},
|
||||
{"name":"select_O3_nothing","op":"select","input":{"object":"O3","advertises":[]},"expect":{"inert":[[0,"missing_capability"],[1,"missing_capability"],[2,"not_selectable"]]}},
|
||||
{"name":"select_O9a_int_hyper","op":"select","input":{"object":"O9a","advertises":["wai.video.int_hyper"],"held":[]},"expect":{"inert":[[0,"missing_capability"],[1,"prior_not_held"]]}},
|
||||
{"name":"fetch_O3_low","op":"fetch","input":{"object":"O3","fragment":"#n=98613&h=1009","advertises":["wai.video.av1"],"applies":["wai.meta.hdr10plus"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":21650,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"24003cee2a7bf7fa45caa412ab4ea9f5f08b6bf44842a73d5a11c2bcf36df464"}},
|
||||
{"name":"fetch_O3_low_default_head","op":"fetch","input":{"object":"O3","advertises":["wai.video.av1"],"applies":["wai.meta.hdr10plus"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":21650,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"24003cee2a7bf7fa45caa412ab4ea9f5f08b6bf44842a73d5a11c2bcf36df464"}},
|
||||
{"name":"fetch_O3_high","op":"fetch","input":{"object":"O3","fragment":"#n=98613&h=1009","advertises":["wai.video.av1"],"applies":["wai.meta.hdr10plus"],"order":[1,0,2]},"expect":{"role":"base","pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":78526,"requests":3,"companions":[2],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"68a51be787c439226eb7bb1cf434e913fc1f0e221870da87f70615d667a7ab12"}},
|
||||
{"name":"fetch_OD_1","op":"fetch","input":{"object":"OD","fragment":"#n=53794&h=1009","advertises":["wai.video.av1"],"applies":["wai.meta.hdr10plus"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":3839,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"f541874101876255b4baf3a739778d04cb9cba25ffa38b30bc1fb8b0701f2a45"}},
|
||||
{"name":"fetch_OD_2","op":"fetch","input":{"object":"OD","fragment":"#n=53794&h=1009","advertises":["wai.video.av1"],"applies":["wai.meta.hdr10plus"],"order":[1,0,2]},"expect":{"role":"base","pick":1,"verified_by":"whole-object","why_whole":"range_threshold","body_bytes":54039,"requests":3,"companions":[2],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"b76bf31be170d9df8eafbd020941b5fd4b7ab017af6a1e5c1c03e909aa8f9fb7"}},
|
||||
{"name":"fetch_OD_1_no_h","op":"fetch","input":{"object":"OD","advertises":["wai.video.av1"],"applies":["wai.meta.hdr10plus"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":16614,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"f541874101876255b4baf3a739778d04cb9cba25ffa38b30bc1fb8b0701f2a45"}},
|
||||
{"name":"fetch_OD_2_no_h","op":"fetch","input":{"object":"OD","advertises":["wai.video.av1"],"applies":["wai.meta.hdr10plus"],"order":[1,0,2]},"expect":{"role":"base","pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":54039,"requests":3,"companions":[2],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"b76bf31be170d9df8eafbd020941b5fd4b7ab017af6a1e5c1c03e909aa8f9fb7"}},
|
||||
{"name":"fetch_OD_2_no_threshold","op":"fetch","input":{"object":"OD","fragment":"#n=53794&h=1009","advertises":["wai.video.av1"],"applies":["wai.meta.hdr10plus"],"order":[1,0,2],"threshold_permille":1001},"expect":{"role":"base","pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":51454,"requests":3,"companions":[2],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"b76bf31be170d9df8eafbd020941b5fd4b7ab017af6a1e5c1c03e909aa8f9fb7"}},
|
||||
{"name":"fetch_O4","op":"fetch","input":{"object":"O4","fragment":"#n=39361&h=1009","advertises":["wai.video.av1"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"component_digest_absent","body_bytes":39606,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"24003cee2a7bf7fa45caa412ab4ea9f5f08b6bf44842a73d5a11c2bcf36df464"}},
|
||||
{"name":"fetch_O7","op":"fetch","input":{"object":"O7","fragment":"#n=39437&h=1009","advertises":["wai.video.av1"]},"expect":{"role":"base","pick":1,"verified_by":"whole-object","why_whole":"component_digest_mismatch","body_bytes":59817,"requests":4,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[[0,"component_digest_mismatch"]],"component_sha256":"0c03a22bebcdb29e26fc1e6dbf040bd86751e8e0be66a2b2ba6709f786a260e8"}},
|
||||
{"name":"component_tamper_O3","op":"fetch","input":{"object":"O3","fragment":"#n=98613&h=1009","advertises":["wai.video.av1"],"tamper":{"patch_object":[[1509,"32"]]}},"expect":{"error":"object_digest_mismatch"}},
|
||||
{"name":"head_tamper_O3","op":"fetch","input":{"object":"O3","advertises":["wai.video.av1"],"tamper":{"patch_object":[[40,"75"]]}},"expect":{"error":"head_proof_mismatch"}},
|
||||
{"name":"fetch_gzip_O3","op":"fetch","input":{"object":"O3","advertises":["wai.video.av1"],"tamper":{"kind":"gzip"}},"expect":{"error":"content_coding_present"}},
|
||||
{"name":"fetch_O8_deadline","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"tamper":{"kind":"no_refinement"}},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8844,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O8_full","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":28844,"requests":4,"companions":[1],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O8_no_enhancement","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":[]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8844,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O2_held","op":"fetch","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":16582,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"5b2bf2dbd36a683d4a62292d1a179e695c511c4971be6859cec41cd980e4f971"}},
|
||||
{"name":"fetch_O2_not_held","op":"fetch","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":[]},"expect":{"role":"base","pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":16582,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[[0,"prior_not_held"]],"component_sha256":"c56f9ab9a3b1df0bd650c3177bd85427f4a4be8fde82547219bcea4ec254162c"}},
|
||||
{"name":"fetch_O1","op":"fetch","input":{"object":"O1","advertises":["wai.neural.int_mlicpp"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":912,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"5b2bf2dbd36a683d4a62292d1a179e695c511c4971be6859cec41cd980e4f971"}},
|
||||
{"name":"fetch_O1_not_held","op":"fetch","input":{"object":"O1","advertises":["wai.neural.int_mlicpp"],"held":[]},"expect":{"error":"inert","skipped":[[0,"prior_not_held"]]}},
|
||||
{"name":"fetch_O5_small","op":"fetch","input":{"object":"O5","fragment":"#n=251","advertises":["wai.text.zstd"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"below_one_chunk","body_bytes":251,"requests":1,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}},
|
||||
{"name":"fetch_O9b_wai1","op":"fetch","input":{"object":"O9b","fragment":"#n=18004&h=1009","advertises":["wai.video.av1"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":18217,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"edd595431a8992d78fd6f691c6b066efa102ac8c1ffb822505639e0d640174a4"}},
|
||||
{"name":"fetch_OH","op":"fetch","input":{"object":"OH","advertises":["wai.video.av1"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":23668,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"dbf116e87034120670d459dbc4812644799b1dc3ae29cc070659103948ca59bc"}},
|
||||
{"name":"fetch_OH_short_head","op":"fetch","input":{"object":"OH","fragment":"#n=23519&h=1009","advertises":["wai.video.av1"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"range_threshold","body_bytes":23668,"requests":4,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"dbf116e87034120670d459dbc4812644799b1dc3ae29cc070659103948ca59bc"}},
|
||||
{"name":"fetch_O9a_undeclared","op":"fetch","input":{"object":"O9a","advertises":["wai.video.av1"],"declared":"wai2.video.av1"},"expect":{"error":"codecs_undeclared"}},
|
||||
{"name":"fetch_O9a_declared","op":"fetch","input":{"object":"O9a","advertises":["wai.video.av1"],"declared":"wai2.video.av1|video.int_hyper"},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":19099,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"0c03a22bebcdb29e26fc1e6dbf040bd86751e8e0be66a2b2ba6709f786a260e8"}},
|
||||
{"name":"same_pick_O2_video_0","op":"same_pick","input":{"object":"O2","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2,3],"fragment":"#n=20250&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O2_video_1","op":"same_pick","input":{"object":"O2","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[3,2,1,0],"fragment":"#n=20250&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O2_video_2","op":"same_pick","input":{"object":"O2","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,3,0],"fragment":"#n=20250&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O2_image_0","op":"same_pick","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2,3],"fragment":"#n=20250&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_O2_image_1","op":"same_pick","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[3,2,1,0],"fragment":"#n=20250&h=1009"},"expect":{"pick":3}},
|
||||
{"name":"same_pick_O2_image_2","op":"same_pick","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,3,0],"fragment":"#n=20250&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_O2_other_0","op":"same_pick","input":{"object":"O2","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2,3],"fragment":"#n=20250&h=1009"},"expect":{"pick":3}},
|
||||
{"name":"same_pick_O2_other_1","op":"same_pick","input":{"object":"O2","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[3,2,1,0],"fragment":"#n=20250&h=1009"},"expect":{"pick":3}},
|
||||
{"name":"same_pick_O2_other_2","op":"same_pick","input":{"object":"O2","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,3,0],"fragment":"#n=20250&h=1009"},"expect":{"pick":3}},
|
||||
{"name":"same_pick_O2_none_0","op":"same_pick","input":{"object":"O2","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2,3],"fragment":"#n=20250&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O2_none_1","op":"same_pick","input":{"object":"O2","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[3,2,1,0],"fragment":"#n=20250&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O2_none_2","op":"same_pick","input":{"object":"O2","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,3,0],"fragment":"#n=20250&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O3_video_0","op":"same_pick","input":{"object":"O3","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=98613&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_O3_video_1","op":"same_pick","input":{"object":"O3","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=98613&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_O3_video_2","op":"same_pick","input":{"object":"O3","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=98613&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_O3_image_0","op":"same_pick","input":{"object":"O3","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=98613&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O3_image_1","op":"same_pick","input":{"object":"O3","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=98613&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O3_image_2","op":"same_pick","input":{"object":"O3","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=98613&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O3_other_0","op":"same_pick","input":{"object":"O3","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=98613&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O3_other_1","op":"same_pick","input":{"object":"O3","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=98613&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O3_other_2","op":"same_pick","input":{"object":"O3","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=98613&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O3_none_0","op":"same_pick","input":{"object":"O3","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=98613&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O3_none_1","op":"same_pick","input":{"object":"O3","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=98613&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O3_none_2","op":"same_pick","input":{"object":"O3","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=98613&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O4_video_0","op":"same_pick","input":{"object":"O4","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=39361&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_O4_video_1","op":"same_pick","input":{"object":"O4","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39361&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_O4_video_2","op":"same_pick","input":{"object":"O4","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39361&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_O4_image_0","op":"same_pick","input":{"object":"O4","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=39361&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O4_image_1","op":"same_pick","input":{"object":"O4","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39361&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O4_image_2","op":"same_pick","input":{"object":"O4","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39361&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O4_other_0","op":"same_pick","input":{"object":"O4","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=39361&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O4_other_1","op":"same_pick","input":{"object":"O4","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39361&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O4_other_2","op":"same_pick","input":{"object":"O4","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39361&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O4_none_0","op":"same_pick","input":{"object":"O4","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=39361&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O4_none_1","op":"same_pick","input":{"object":"O4","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39361&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O4_none_2","op":"same_pick","input":{"object":"O4","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39361&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O7_video_0","op":"same_pick","input":{"object":"O7","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=39437&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_O7_video_1","op":"same_pick","input":{"object":"O7","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39437&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_O7_video_2","op":"same_pick","input":{"object":"O7","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39437&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_O7_image_0","op":"same_pick","input":{"object":"O7","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=39437&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O7_image_1","op":"same_pick","input":{"object":"O7","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39437&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O7_image_2","op":"same_pick","input":{"object":"O7","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39437&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O7_other_0","op":"same_pick","input":{"object":"O7","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=39437&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O7_other_1","op":"same_pick","input":{"object":"O7","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39437&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O7_other_2","op":"same_pick","input":{"object":"O7","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39437&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O7_none_0","op":"same_pick","input":{"object":"O7","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=39437&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O7_none_1","op":"same_pick","input":{"object":"O7","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39437&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O7_none_2","op":"same_pick","input":{"object":"O7","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=39437&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O8_video_0","op":"same_pick","input":{"object":"O8","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=46433&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_O8_video_1","op":"same_pick","input":{"object":"O8","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=46433&h=1009"},"expect":{"pick":2}},
|
||||
{"name":"same_pick_O8_video_2","op":"same_pick","input":{"object":"O8","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=46433&h=1009"},"expect":{"pick":2}},
|
||||
{"name":"same_pick_O8_image_0","op":"same_pick","input":{"object":"O8","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=46433&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O8_image_1","op":"same_pick","input":{"object":"O8","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=46433&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O8_image_2","op":"same_pick","input":{"object":"O8","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=46433&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O8_other_0","op":"same_pick","input":{"object":"O8","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=46433&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O8_other_1","op":"same_pick","input":{"object":"O8","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=46433&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O8_other_2","op":"same_pick","input":{"object":"O8","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=46433&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O8_none_0","op":"same_pick","input":{"object":"O8","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=46433&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O8_none_1","op":"same_pick","input":{"object":"O8","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=46433&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O8_none_2","op":"same_pick","input":{"object":"O8","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=46433&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O9a_video_0","op":"same_pick","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=21034&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_O9a_video_1","op":"same_pick","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=21034&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_O9a_video_2","op":"same_pick","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=21034&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_O9a_image_0","op":"same_pick","input":{"object":"O9a","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=21034&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O9a_image_1","op":"same_pick","input":{"object":"O9a","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=21034&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O9a_image_2","op":"same_pick","input":{"object":"O9a","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=21034&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O9a_other_0","op":"same_pick","input":{"object":"O9a","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=21034&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O9a_other_1","op":"same_pick","input":{"object":"O9a","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=21034&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O9a_other_2","op":"same_pick","input":{"object":"O9a","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=21034&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O9a_none_0","op":"same_pick","input":{"object":"O9a","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1],"fragment":"#n=21034&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O9a_none_1","op":"same_pick","input":{"object":"O9a","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=21034&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_O9a_none_2","op":"same_pick","input":{"object":"O9a","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,0],"fragment":"#n=21034&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OD_video_0","op":"same_pick","input":{"object":"OD","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=53794&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_OD_video_1","op":"same_pick","input":{"object":"OD","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=53794&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_OD_video_2","op":"same_pick","input":{"object":"OD","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=53794&h=1009"},"expect":{"pick":1}},
|
||||
{"name":"same_pick_OD_image_0","op":"same_pick","input":{"object":"OD","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=53794&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OD_image_1","op":"same_pick","input":{"object":"OD","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=53794&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OD_image_2","op":"same_pick","input":{"object":"OD","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=53794&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OD_other_0","op":"same_pick","input":{"object":"OD","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=53794&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OD_other_1","op":"same_pick","input":{"object":"OD","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=53794&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OD_other_2","op":"same_pick","input":{"object":"OD","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=53794&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OD_none_0","op":"same_pick","input":{"object":"OD","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2],"fragment":"#n=53794&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OD_none_1","op":"same_pick","input":{"object":"OD","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[2,1,0],"fragment":"#n=53794&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OD_none_2","op":"same_pick","input":{"object":"OD","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[1,2,0],"fragment":"#n=53794&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OH_video_0","op":"same_pick","input":{"object":"OH","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_OH_video_1","op":"same_pick","input":{"object":"OH","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_OH_video_2","op":"same_pick","input":{"object":"OH","advertises":["wai.video.av1","wai.video.avc"],"applies":["wai.meta.hdr10plus","wai.video.lcevc"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"pick":0}},
|
||||
{"name":"same_pick_OH_image_0","op":"same_pick","input":{"object":"OH","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OH_image_1","op":"same_pick","input":{"object":"OH","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OH_image_2","op":"same_pick","input":{"object":"OH","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OH_other_0","op":"same_pick","input":{"object":"OH","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OH_other_1","op":"same_pick","input":{"object":"OH","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OH_other_2","op":"same_pick","input":{"object":"OH","advertises":["wai.video.int_hyper","wai.image.png","wai.text.zstd"],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OH_none_0","op":"same_pick","input":{"object":"OH","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OH_none_1","op":"same_pick","input":{"object":"OH","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"same_pick_OH_none_2","op":"same_pick","input":{"object":"OH","advertises":[],"applies":[],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0],"fragment":"#n=23519&h=1009"},"expect":{"inert":true}},
|
||||
{"name":"codecs_ok_0","op":"codecs","input":{"value":"wai2.video.av1+meta.hdr10plus|video.int_hyper"},"expect":{"elements":["wai2.video.av1+meta.hdr10plus|video.int_hyper"]}},
|
||||
{"name":"codecs_ok_1","op":"codecs","input":{"value":"wai1.video.av1"},"expect":{"elements":["wai1.video.av1"]}},
|
||||
{"name":"codecs_ok_2","op":"codecs","input":{"value":"mp4a.40.2,wai1.video.av1, avc1.42E01E"},"expect":{"elements":[null,"wai1.video.av1",null]}},
|
||||
{"name":"codecs_ok_3","op":"codecs","input":{"value":"wai2.video.avc+video.lcevc"},"expect":{"elements":["wai2.video.avc+video.lcevc"]}},
|
||||
{"name":"codecs_ok_4","op":"codecs","input":{"value":"wai2.video.int_motion+video.int_refine"},"expect":{"elements":["wai2.video.int_motion+video.int_refine"]}},
|
||||
{"name":"codecs_ok_5","op":"codecs","input":{"value":"wai2.neural.int_mlicpp|image.avif"},"expect":{"elements":["wai2.neural.int_mlicpp|image.avif"]}},
|
||||
{"name":"codecs_uppercase","op":"codecs","input":{"value":"wai1.Video.av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_empty_segment","op":"codecs","input":{"value":"wai1.video..av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_wai3","op":"codecs","input":{"value":"wai3.video.av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_plus_standalone","op":"codecs","input":{"value":"wai1.video.av1+video.vp9"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_companion_first","op":"codecs","input":{"value":"wai1.meta.hdr10plus"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_refinement_first","op":"codecs","input":{"value":"wai2.video.int_refine"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_derivation","op":"codecs","input":{"value":"wai1.derive.crop"},"expect":{"error":"codecs_not_media"}},
|
||||
{"name":"codecs_record","op":"codecs","input":{"value":"wai1.video.manifest"},"expect":{"error":"codecs_not_media"}},
|
||||
{"name":"codecs_candidate","op":"codecs","input":{"value":"wai1.splat.sog"},"expect":{"error":"codecs_candidate"}},
|
||||
{"name":"codecs_one_segment","op":"codecs","input":{"value":"wai1.video"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"union_O9","op":"union_floor","input":{"objects":["O9a","O9b","O9c"]},"expect":{"union":"wai2.video.av1|video.int_hyper","floor":"wai2.video.av1"}},
|
||||
{"name":"union_O3","op":"union_floor","input":{"objects":["O3"]},"expect":{"union":"wai2.video.av1+meta.hdr10plus","floor":"wai2.video.av1+meta.hdr10plus"}},
|
||||
{"name":"union_O8","op":"union_floor","input":{"objects":["O8"]},"expect":{"union":"wai2.video.avc+video.lcevc|video.av1","floor":"wai2.video.avc+video.lcevc|video.av1"}},
|
||||
{"name":"union_O2","op":"union_floor","input":{"objects":["O2"]},"expect":{"union":"wai2.neural.int_mlicpp|image.avif|image.jxl|image.png","floor":"wai2.neural.int_mlicpp|image.avif|image.jxl|image.png"}},
|
||||
{"name":"union_wai1_only","op":"union_floor","input":{"objects":["O9b","O9c"]},"expect":{"union":"wai1.video.av1","floor":"wai1.video.av1"}},
|
||||
{"name":"floor_none","op":"union_floor","input":{"objects":["O9b","O2"]},"expect":{"error":"codecs_no_common_capability"}},
|
||||
{"name":"codecs_non_ascii_prefix","op":"codecs","input":{"value":"waié.video.av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_non_ascii_digit","op":"codecs","input":{"value":"wai١.video.av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_non_ascii_cap","op":"codecs","input":{"value":"wai2.videó.av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_non_ascii_classical","op":"codecs","input":{"value":"vidéo.av1"},"expect":{"elements":[null]}},
|
||||
{"name":"codecs_upper_prefix","op":"codecs","input":{"value":"WAI2.video.av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_mixed_prefix","op":"codecs","input":{"value":"Wai1.video.av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_wai_word","op":"codecs","input":{"value":"waiting.for.godot"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_dup_alt","op":"codecs","input":{"value":"wai2.video.av1|video.av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_dup_plus","op":"codecs","input":{"value":"wai2.video.av1+meta.hdr10plus+meta.hdr10plus"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_plus_is_alt","op":"codecs","input":{"value":"wai2.video.av1+video.av1"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_empty_element","op":"codecs","input":{"value":"wai2.video.av1,,mp4a.40.2"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_trailing_comma","op":"codecs","input":{"value":"wai2.video.av1,"},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_empty_value","op":"codecs","input":{"value":""},"expect":{"error":"codecs_malformed"}},
|
||||
{"name":"codecs_companion_in_two_alts","op":"codecs","input":{"value":"wai2.video.av1+meta.hdr10plus|video.vp9+meta.hdr10plus"},"expect":{"elements":["wai2.video.av1+meta.hdr10plus|video.vp9+meta.hdr10plus"]}},
|
||||
{"name":"response_plus_first","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes +1009-3593/53794","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"error":"content_range_mismatch"}},
|
||||
{"name":"response_plus_last","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-+3593/53794","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"error":"content_range_mismatch"}},
|
||||
{"name":"response_underscore","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-3_593/53794","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"error":"content_range_mismatch"}},
|
||||
{"name":"response_inner_space","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009- 3593/53794","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"error":"content_range_mismatch"}},
|
||||
{"name":"response_non_ascii_digit","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes ١٠٠٩-3593/53794","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"error":"content_range_mismatch"}},
|
||||
{"name":"response_plus_complete","op":"response","input":{"request":[1009,3593],"status":206,"content_range":"bytes 1009-3593/+53794","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"error":"complete_length_mismatch"}},
|
||||
{"name":"response_outer_whitespace","op":"response","input":{"request":[1009,3593],"status":206,"content_range":" bytes 1009-3593/53794\t","content_encoding":null,"body_len":2585,"envelope_len":53794},"expect":{"ok":[1009,3593]}},
|
||||
{"name":"fetch_OE1_encrypted","op":"fetch","input":{"object":"OE1","fragment":"#n=299","advertises":["wai.text.zstd"]},"expect":{"error":"inert","skipped":[[0,"unsupported_encryption"]]}},
|
||||
{"name":"select_OE2s_security_string","op":"select","input":{"object":"OE2s","advertises":["wai.text.zstd"]},"expect":{"inert":[[0,"unsupported_encryption"]]}},
|
||||
{"name":"select_OE2t_security_true","op":"select","input":{"object":"OE2t","advertises":["wai.text.zstd"]},"expect":{"inert":[[0,"unsupported_encryption"]]}},
|
||||
{"name":"fetch_OE2s_security_string","op":"fetch","input":{"object":"OE2s","advertises":["wai.text.zstd"]},"expect":{"error":"inert","skipped":[[0,"unsupported_encryption"]]}},
|
||||
{"name":"fetch_OR_malformed_refines","op":"fetch","input":{"object":"OR","advertises":["wai.video.int_motion"],"applies":["wai.video.int_refine"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":9615,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"6b1bcc071f58c5fb62613d029be744e824494acf931a1c46d89641a548b4aa91"}},
|
||||
{"name":"union_OR_malformed_refines","op":"union_floor","input":{"objects":["OR"]},"expect":{"union":"wai2.video.int_motion","floor":"wai2.video.int_motion"}},
|
||||
{"name":"parse_head_kid_string","op":"parse_head","input":{"file":"bad/OK.wai"},"expect":{"error":"head_malformed"}},
|
||||
{"name":"parse_head_kind_number","op":"parse_head","input":{"file":"bad/OKN.wai"},"expect":{"error":"head_malformed"}},
|
||||
{"name":"union_O10_layers","op":"union_floor","input":{"objects":["O10","O10L1","O10L2"]},"expect":{"union":"wai1.audio.flac+audio.int_refine","floor":"wai1.audio.flac+audio.int_refine"}},
|
||||
{"name":"union_O10_layers_unbound","op":"union_floor","input":{"objects":["O10L1","O10L2"]},"expect":{"error":"codecs_layer_unbound"}},
|
||||
{"name":"fetch_O10_base","op":"fetch","input":{"object":"O10","advertises":["wai.audio.flac"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":1910,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"67ddfc4700c518d0f0028fd03920901057f419d9fc84349e1c9cf7a75314fefc"}},
|
||||
{"name":"fetch_O10L1_discovered","op":"fetch","input":{"object":"O10L1","fragment":"#n=12711&h=1009","advertises":[]},"expect":{"role":"layer","pick":0,"verified_by":"whole-object","why_whole":"layer","body_bytes":12892,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"96c9e16b31256d323efe54d996bfab6223f4eeb017da5d55ece7fd74fee750e2"}},
|
||||
{"name":"fetch_O10L2_listed","op":"fetch","input":{"object":"O10L2","listed_as_layer":true,"advertises":[]},"expect":{"role":"layer","pick":0,"verified_by":"whole-object","why_whole":"layer","body_bytes":18367,"requests":1,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"305486882f8861a5a3bfae0f72baeb9300ce7c2f87db4e084ef7f826ab333a56"}},
|
||||
{"name":"fetch_O10L2_listed_abandoned","op":"fetch","input":{"object":"O10L2","listed_as_layer":true,"advertises":[],"tamper":{"kind":"no_refinement"}},"expect":{"role":"layer","pick":0,"verified_by":"whole-object","why_whole":"layer","body_bytes":0,"requests":1,"companions":[],"refinements":[],"abandoned":[0],"dropped":[],"skipped":[],"component_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}},
|
||||
{"name":"fetch_O10L1_listed_refused","op":"fetch","input":{"object":"O10L1","listed_as_layer":true,"advertises":[],"tamper":{"kind":"gzip"}},"expect":{"role":"layer","pick":0,"verified_by":"whole-object","why_whole":"layer","body_bytes":12711,"requests":1,"companions":[],"refinements":[],"abandoned":[0],"dropped":[],"skipped":[],"component_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}},
|
||||
{"name":"fetch_O10_listed_not_layer","op":"fetch","input":{"object":"O10","listed_as_layer":true,"advertises":["wai.audio.flac"]},"expect":{"error":"not_a_layer"}},
|
||||
{"name":"fetch_O10L1_declared","op":"fetch","input":{"object":"O10L1","advertises":[],"declared":"wai1.audio.flac+audio.int_refine","layer_base":"O10"},"expect":{"role":"layer","pick":0,"verified_by":"whole-object","why_whole":"layer","body_bytes":12892,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"96c9e16b31256d323efe54d996bfab6223f4eeb017da5d55ece7fd74fee750e2"}},
|
||||
{"name":"fetch_O10L1_undeclared","op":"fetch","input":{"object":"O10L1","advertises":[],"declared":"wai1.audio.flac","layer_base":"O10"},"expect":{"error":"codecs_undeclared"}},
|
||||
{"name":"fetch_O10L1_declared_other_alt","op":"fetch","input":{"object":"O10L1","advertises":[],"declared":"wai1.audio.flac|audio.int_codec+audio.int_refine","layer_base":"O10"},"expect":{"error":"codecs_undeclared"}},
|
||||
{"name":"fetch_O10L1_declared_no_base","op":"fetch","input":{"object":"O10L1","advertises":[],"declared":"wai1.audio.flac+audio.int_refine"},"expect":{"error":"codecs_undeclared"}},
|
||||
{"name":"fetch_OPf_dispatched","op":"fetch","input":{"object":"OPf","advertises":["wai.text.zstd"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":320,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}},
|
||||
{"name":"fetch_OPf_listed_not_layer","op":"fetch","input":{"object":"OPf","listed_as_layer":true,"advertises":["wai.text.zstd"]},"expect":{"error":"not_a_layer"}},
|
||||
{"name":"union_OPf","op":"union_floor","input":{"objects":["OPf"]},"expect":{"union":"wai1.text.zstd","floor":"wai1.text.zstd"}},
|
||||
{"name":"fetch_OPf_declared","op":"fetch","input":{"object":"OPf","advertises":["wai.text.zstd"],"declared":"wai1.text.zstd"},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":320,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}},
|
||||
{"name":"fetch_OPe_dispatched","op":"fetch","input":{"object":"OPe","advertises":["wai.text.zstd"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":317,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}},
|
||||
{"name":"fetch_OPe_listed_not_layer","op":"fetch","input":{"object":"OPe","listed_as_layer":true,"advertises":["wai.text.zstd"]},"expect":{"error":"not_a_layer"}},
|
||||
{"name":"union_OPe","op":"union_floor","input":{"objects":["OPe"]},"expect":{"union":"wai1.text.zstd","floor":"wai1.text.zstd"}},
|
||||
{"name":"fetch_OPe_declared","op":"fetch","input":{"object":"OPe","advertises":["wai.text.zstd"],"declared":"wai1.text.zstd"},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":317,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}},
|
||||
{"name":"fetch_OPw_dispatched","op":"fetch","input":{"object":"OPw","advertises":["wai.text.zstd"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":474,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}},
|
||||
{"name":"fetch_OPw_listed_not_layer","op":"fetch","input":{"object":"OPw","listed_as_layer":true,"advertises":["wai.text.zstd"]},"expect":{"error":"not_a_layer"}},
|
||||
{"name":"union_OPw","op":"union_floor","input":{"objects":["OPw"]},"expect":{"union":"wai1.text.zstd","floor":"wai1.text.zstd"}},
|
||||
{"name":"fetch_OPw_declared","op":"fetch","input":{"object":"OPw","advertises":["wai.text.zstd"],"declared":"wai1.text.zstd"},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":474,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}},
|
||||
{"name":"fetch_ORN_whole","op":"fetch","input":{"object":"ORN","fragment":"#n=32659&h=1009","advertises":["wai.video.av1"],"applies":["wai.video.int_refine"],"threshold_permille":900},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"range_threshold","body_bytes":32872,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[[1,"component_digest_absent"]],"skipped":[],"component_sha256":"7898322c774e80346b83405764069cbf1d06484da8824fab90e18e2ce0188f8c"}},
|
||||
{"name":"fetch_ORN_ranged","op":"fetch","input":{"object":"ORN","fragment":"#n=32659&h=1009","advertises":["wai.video.av1"],"applies":["wai.video.int_refine"],"threshold_permille":1001},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":30572,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[[1,"component_digest_absent"]],"skipped":[],"component_sha256":"7898322c774e80346b83405764069cbf1d06484da8824fab90e18e2ce0188f8c"}},
|
||||
{"name":"fetch_OS_threshold_stall","op":"fetch","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","tamper":{"kind":"no_refinement"}},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8705,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_OS_threshold_stall_1001","op":"fetch","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","tamper":{"kind":"no_refinement"},"threshold_permille":1001},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8705,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_OS_full","op":"fetch","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS"},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":38705,"requests":4,"companions":[1],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_ON_refinement_no_digest","op":"fetch","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"ON"},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8629,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[[1,"component_digest_absent"]],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_OW_refinement_wrong_digest","op":"fetch","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OW"},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":38705,"requests":4,"companions":[],"refinements":[],"abandoned":[],"dropped":[[1,"component_digest_mismatch"]],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"plan_OS_base_only_threshold","op":"plan","input":{"object":"OS","wanted":[[0,"base"],[1,"refinement"]],"held":[[0,1008]]},"expect":{"requests":[[1009,8459,"base"],[8460,38459,"refinement"]],"dropped":[]}},
|
||||
{"name":"plan_ON_refinement_dropped","op":"plan","input":{"object":"ON","wanted":[[0,"base"],[1,"refinement"]],"held":[[0,1008]]},"expect":{"requests":[[1009,8383,"base"]],"dropped":[[1,"component_digest_absent"]]}},
|
||||
{"name":"timed_OS_base_ready_first","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["base",1000],["tick",1049],["tick",1050]],"deadline_ms":50},"expect":{"ready_step":0,"ready_at":1000,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[[1049,[]],[1050,[3]]],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8705,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"timed_OS_refinement_after_base","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["base",10],["refinement",20,"range"]]},"expect":{"ready_step":0,"ready_at":10,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":38705,"requests":4,"companions":[1],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"timed_OS_clock_never_ticked","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["base",1759680000000],["tick",1759680000000],["tick",1759680001999],["tick",1759680002000]],"deadline_ms":2000},"expect":{"ready_step":0,"ready_at":1759680000000,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[[1759680000000,[]],[1759680001999,[]],[1759680002000,[3]]],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8705,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"timed_OS_clock_ticked_0","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["tick",0],["base",5000],["tick",6999],["tick",7000]],"deadline_ms":2000},"expect":{"ready_step":1,"ready_at":5000,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[[0,[]],[6999,[]],[7000,[3]]],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8705,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"timed_OS_clock_stepped_back","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["tick",10000],["tick",5],["base",6],["tick",2005],["tick",2006]],"deadline_ms":2000},"expect":{"ready_step":2,"ready_at":6,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[[10000,[]],[5,[]],[2005,[]],[2006,[3]]],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8705,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"timed_OS_clock_back_after","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["base",5000],["tick",4000],["tick",0],["tick",6999],["tick",7000]],"deadline_ms":2000},"expect":{"ready_step":0,"ready_at":5000,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[[4000,[]],[0,[]],[6999,[]],[7000,[3]]],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8705,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"timed_OS_clock_saturates","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["base",18446744073709551605],["tick",18446744073709551614],["tick",18446744073709551615]],"deadline_ms":2000},"expect":{"ready_step":0,"ready_at":18446744073709551605,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[[18446744073709551614,[]],[18446744073709551615,[3]]],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8705,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"timed_OS_ready_then_200_base_flipped","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["base",10],["refinement",20,"whole_200"]],"flip":560},"expect":{"ready_step":0,"ready_at":10,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":47165,"requests":4,"companions":[1],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"timed_OS_ready_then_200_enh_flipped","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["base",10],["refinement",20,"whole_200"]],"flip":8560},"expect":{"ready_step":0,"ready_at":10,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":47165,"requests":4,"companions":[],"refinements":[],"abandoned":[],"dropped":[[1,"component_digest_mismatch"]],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"timed_OS_200_before_base","op":"fetch_timed","input":{"advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"fragment":"#n=38460&h=1009","object":"OS","script":[["base_once",1],["refinement",2,"whole_200"],["base",3]],"flip":560},"expect":{"ready_step":2,"ready_at":3,"base":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"base_end":{"pick":0,"verified_by":"head-proof","component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18","companions":[],"refinements":[]},"ticks":[],"outcome":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":47165,"requests":4,"companions":[1],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}}},
|
||||
{"name":"fetch_O8_refinement_gzip","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"tamper":{"refinement":"gzip"}},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":28844,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O8_refinement_bad_range","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"tamper":{"refinement":"bad_range"}},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":28844,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O8_refinement_416","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"tamper":{"refinement":"416"}},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8844,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O8_refinement_complete_length","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"tamper":{"refinement":"complete_length"}},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":28844,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O8_refinement_plus_sign","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"tamper":{"refinement":"plus_sign"}},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":28844,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O8_refinement_short_200","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"tamper":{"refinement":"short_200"}},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":28844,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_OD_stale","op":"fetch","input":{"object":"OD","advertises":["wai.video.av1"],"tamper":{"first_range_flip":2000}},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"component_digest_mismatch","body_bytes":69399,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"f541874101876255b4baf3a739778d04cb9cba25ffa38b30bc1fb8b0701f2a45"}},
|
||||
{"name":"fetch_OT_stale_small","op":"fetch","input":{"object":"OT","advertises":["wai.video.av1"],"tamper":{"first_range_flip":2000}},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"component_digest_mismatch","body_bytes":23908,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"f541874101876255b4baf3a739778d04cb9cba25ffa38b30bc1fb8b0701f2a45"}},
|
||||
{"name":"head_proof_huge_O3","op":"head_proof","input":{"object":"O3","proof":"cases/head_proof_huge_O3.hp"},"expect":{"error":"head_proof_malformed"}},
|
||||
{"name":"union_OF_record_fallback","op":"union_floor","input":{"objects":["OF"]},"expect":{"union":"wai1.text.zstd","floor":"wai1.text.zstd"}},
|
||||
{"name":"fetch_OF_record_fallback","op":"fetch","input":{"object":"OF","fragment":"#n=283","advertises":["wai.text.zstd"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"below_one_chunk","body_bytes":283,"requests":1,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}},
|
||||
{"name":"fetch_O3_proof_unanswered","op":"fetch","input":{"object":"O3","advertises":["wai.video.av1"],"tamper":{"kind":"no_proof"}},"expect":{"error":"base_unanswered"}},
|
||||
{"name":"layer_tag_ok","op":"layer_tag","input":{"line":"#WAI-LAYER:N=1,URI=\"objects/a.wai#n=5,6\""},"expect":{"n":1,"uri":"objects/a.wai#n=5,6"}},
|
||||
{"name":"layer_tag_unknown_attribute","op":"layer_tag","input":{"line":"#WAI-LAYER:URI=\"x.wai\",N=255,X-FUTURE=ENUM"},"expect":{"n":255,"uri":"x.wai"}},
|
||||
{"name":"layer_tag_zero","op":"layer_tag","input":{"line":"#WAI-LAYER:N=0,URI=\"x\""},"expect":{"error":"layer_tag_malformed"}},
|
||||
{"name":"layer_tag_256","op":"layer_tag","input":{"line":"#WAI-LAYER:N=256,URI=\"x\""},"expect":{"error":"layer_tag_malformed"}},
|
||||
{"name":"layer_tag_no_uri","op":"layer_tag","input":{"line":"#WAI-LAYER:N=1"},"expect":{"error":"layer_tag_malformed"}},
|
||||
{"name":"layer_tag_unquoted_uri","op":"layer_tag","input":{"line":"#WAI-LAYER:N=1,URI=x"},"expect":{"error":"layer_tag_malformed"}},
|
||||
{"name":"layer_tag_twice","op":"layer_tag","input":{"line":"#WAI-LAYER:N=1,N=2,URI=\"x\""},"expect":{"error":"layer_tag_malformed"}},
|
||||
{"name":"layer_tag_plus","op":"layer_tag","input":{"line":"#WAI-LAYER:N=+1,URI=\"x\""},"expect":{"error":"layer_tag_malformed"}},
|
||||
{"name":"layer_tag_non_ascii_digit","op":"layer_tag","input":{"line":"#WAI-LAYER:N=١,URI=\"x\""},"expect":{"error":"layer_tag_malformed"}},
|
||||
{"name":"layer_tag_lower_name","op":"layer_tag","input":{"line":"#WAI-LAYER:n=1,URI=\"x\""},"expect":{"error":"layer_tag_malformed"}},
|
||||
{"name":"layer_tag_unterminated","op":"layer_tag","input":{"line":"#WAI-LAYER:N=1,URI=\"x"},"expect":{"error":"layer_tag_malformed"}},
|
||||
{"name":"supports_floor","op":"supports","input":{"floor":"wai2.video.av1","containers":["WAI1","WAI2"],"caps":["wai.video.av1"]},"expect":{"supports":true}},
|
||||
{"name":"supports_union_only","op":"supports","input":{"floor":"wai2.video.av1","containers":["WAI1","WAI2"],"caps":["wai.video.int_hyper"]},"expect":{"supports":false}},
|
||||
{"name":"supports_container_gate","op":"supports","input":{"floor":"wai2.video.av1","containers":["WAI1"],"caps":["wai.video.av1"]},"expect":{"supports":false}},
|
||||
{"name":"supports_wai1","op":"supports","input":{"floor":"wai1.video.av1","containers":["WAI1"],"caps":["wai.video.av1"]},"expect":{"supports":true}},
|
||||
{"name":"supports_companion_ignored","op":"supports","input":{"floor":"wai2.video.av1+meta.hdr10plus","containers":["WAI1","WAI2"],"caps":["wai.video.av1"]},"expect":{"supports":true}}
|
||||
]
|
||||
BIN
wai/http-conformance/cases/head_proof_drop_cv_O3.hp
Normal file
BIN
wai/http-conformance/cases/head_proof_drop_cv_O3.hp
Normal file
Binary file not shown.
BIN
wai/http-conformance/cases/head_proof_envelope_len_O3.hp
Normal file
BIN
wai/http-conformance/cases/head_proof_envelope_len_O3.hp
Normal file
Binary file not shown.
BIN
wai/http-conformance/cases/head_proof_extra_cv_O3.hp
Normal file
BIN
wai/http-conformance/cases/head_proof_extra_cv_O3.hp
Normal file
Binary file not shown.
BIN
wai/http-conformance/cases/head_proof_flip_cv_O3.hp
Normal file
BIN
wai/http-conformance/cases/head_proof_flip_cv_O3.hp
Normal file
Binary file not shown.
BIN
wai/http-conformance/cases/head_proof_huge_O3.hp
Normal file
BIN
wai/http-conformance/cases/head_proof_huge_O3.hp
Normal file
Binary file not shown.
BIN
wai/http-conformance/cases/head_proof_magic.hp
Normal file
BIN
wai/http-conformance/cases/head_proof_magic.hp
Normal file
Binary file not shown.
BIN
wai/http-conformance/cases/head_proof_not_formula_OH.hp
Normal file
BIN
wai/http-conformance/cases/head_proof_not_formula_OH.hp
Normal file
Binary file not shown.
BIN
wai/http-conformance/cases/head_proof_of_O4.hp
Normal file
BIN
wai/http-conformance/cases/head_proof_of_O4.hp
Normal file
Binary file not shown.
BIN
wai/http-conformance/cases/head_proof_unaligned_O3.hp
Normal file
BIN
wai/http-conformance/cases/head_proof_unaligned_O3.hp
Normal file
Binary file not shown.
15
wai/http-conformance/falsify/accept-gzip.patch
Normal file
15
wai/http-conformance/falsify/accept-gzip.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: response_gzip fetch_gzip_O3
|
||||
# a content coding is accepted (session::check_response)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index 4730c192..da765beb 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -206,7 +206,7 @@ pub fn check_response(req: Option<ByteRange>, r: &ResponseMeta, body_len: u64, e
|
||||
-> Result<ByteRange, RangeRefusal> {
|
||||
let coded = |r: &ResponseMeta| r.content_encoding.as_deref()
|
||||
.map(str::trim)
|
||||
- .filter(|c| !c.is_empty() && !c.eq_ignore_ascii_case("identity"))
|
||||
+ .filter(|_| false)
|
||||
.map(str::to_string);
|
||||
match r.status {
|
||||
206 => {
|
||||
15
wai/http-conformance/falsify/allow-trailing.patch
Normal file
15
wai/http-conformance/falsify/allow-trailing.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: object_length_trailing
|
||||
# bytes after the envelope are accepted (head::parse_head)
|
||||
diff --git a/wai/wai-rs/src/http_binding/head.rs b/wai/wai-rs/src/http_binding/head.rs
|
||||
index 1470133d..b861cb16 100644
|
||||
--- a/wai/wai-rs/src/http_binding/head.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/head.rs
|
||||
@@ -101,7 +101,7 @@ pub fn parse_head(prefix: &[u8], envelope_len: u64) -> Result<HeadParse, RangeRe
|
||||
}
|
||||
};
|
||||
let total = head_len + components.iter().map(|c| c.len).sum::<u64>();
|
||||
- if total != envelope_len {
|
||||
+ if total > envelope_len {
|
||||
return Err(RangeRefusal::ObjectLengthMismatch { expected: total, got: envelope_len });
|
||||
}
|
||||
let bytes = prefix[..head_len as usize].to_vec();
|
||||
15
wai/http-conformance/falsify/base-ready-at-done.patch
Normal file
15
wai/http-conformance/falsify/base-ready-at-done.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: timed_OS_base_ready_first timed_OS_clock_ticked_0 timed_OS_ready_then_200_base_flipped
|
||||
# the base is ready only when its refinements are done (session::FetchSession::advance_components)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index ec31518e..e8478b40 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -766,7 +766,7 @@ impl FetchSession {
|
||||
requests: s.requests,
|
||||
}
|
||||
};
|
||||
- if !base_pending && self.base.is_none() {
|
||||
+ if !pending && self.base.is_none() {
|
||||
// §10.1: the base is presentable now, whatever its refinements do.
|
||||
self.base = Some((assemble(&parts, true, self), self.now_ms));
|
||||
}
|
||||
15
wai/http-conformance/falsify/base-unanswered.patch
Normal file
15
wai/http-conformance/falsify/base-unanswered.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: fetch_O3_proof_unanswered
|
||||
# an unanswered base is reported as a malformed head (session::drive)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index 4730c192..9ba8287a 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -802,7 +802,7 @@ pub fn drive(
|
||||
}
|
||||
if session.queued.is_empty() {
|
||||
if session.outstanding() > 0 {
|
||||
- return Err(RangeRefusal::BaseUnanswered);
|
||||
+ return Err(RangeRefusal::HeadMalformed("unanswered".into()));
|
||||
}
|
||||
return Err(RangeRefusal::HeadMalformed("the session wants nothing and has no outcome".into()));
|
||||
}
|
||||
15
wai/http-conformance/falsify/counter.patch
Normal file
15
wai/http-conformance/falsify/counter.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: head_proof_ok_OH
|
||||
# covered subtrees are hashed with chunk counter 0 (head_proof::node_cv)
|
||||
diff --git a/wai/wai-rs/src/http_binding/head_proof.rs b/wai/wai-rs/src/http_binding/head_proof.rs
|
||||
index 40b6f1fa..d13bcdac 100644
|
||||
--- a/wai/wai-rs/src/http_binding/head_proof.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/head_proof.rs
|
||||
@@ -66,7 +66,7 @@ fn node_cv(
|
||||
if start >= c {
|
||||
it.next().ok_or(RangeRefusal::HeadProofMismatch)
|
||||
} else if start + len <= c {
|
||||
- Ok(subtree_cv(&x_prefix[start as usize..(start + len) as usize], start))
|
||||
+ Ok(subtree_cv(&x_prefix[start as usize..(start + len) as usize], 0))
|
||||
} else {
|
||||
let l = left_len(len);
|
||||
let left = node_cv(x_prefix, start, l, c, it)?;
|
||||
15
wai/http-conformance/falsify/deadline-wraps.patch
Normal file
15
wai/http-conformance/falsify/deadline-wraps.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: timed_OS_clock_saturates
|
||||
# the deadline wraps past the end of u64 (session::FetchSession::tick)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index ec31518e..d5f96691 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -398,7 +398,7 @@ impl FetchSession {
|
||||
pub fn tick(&mut self, now_ms: u64) -> Vec<u32> {
|
||||
self.now_ms = now_ms;
|
||||
let (Some(at), Some(d)) = (self.base_ready_at(), self.policy.refinement_deadline_ms) else { return Vec::new() };
|
||||
- if self.result.is_some() || now_ms < at.saturating_add(u64::from(d)) {
|
||||
+ if self.result.is_some() || now_ms < at.wrapping_add(u64::from(d)) {
|
||||
return Vec::new();
|
||||
}
|
||||
let ids = self.outstanding_of(Class::Refinement);
|
||||
15
wai/http-conformance/falsify/dispatch-layer.patch
Normal file
15
wai/http-conformance/falsify/dispatch-layer.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: fetch_O10L1_discovered
|
||||
# a layer is dispatched under SPEC §4 (session::advance_head)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index 4730c192..44fa50f2 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -575,7 +575,7 @@ impl FetchSession {
|
||||
}
|
||||
self.head = Some(head.clone());
|
||||
match head.version {
|
||||
- Version::Wai1 if head.is_layer() => {
|
||||
+ Version::Wai1 if false && head.is_layer() => {
|
||||
// A layer goes to staged delivery, never to SPEC §4 (§10.4): the rest of
|
||||
// it at refinement class, then the whole envelope against the cid.
|
||||
self.to_rest(&head, WhyWhole::Layer, Class::Refinement);
|
||||
14
wai/http-conformance/falsify/drop-domain.patch
Normal file
14
wai/http-conformance/falsify/drop-domain.patch
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# must fail: head_proof_ok_O3 fetch_O3_low
|
||||
# the content hash loses its domain (jwp_hash::content_hash)
|
||||
diff --git a/wai/wai-rs/src/jwp_hash.rs b/wai/wai-rs/src/jwp_hash.rs
|
||||
index d20bc358..f773f1fd 100644
|
||||
--- a/wai/wai-rs/src/jwp_hash.rs
|
||||
+++ b/wai/wai-rs/src/jwp_hash.rs
|
||||
@@ -13,7 +13,6 @@ pub const DOMAIN_OBJECT: &[u8] = b"moq-jwp:object\x01";
|
||||
/// followed by the envelope bytes.
|
||||
pub fn content_hash(envelope_bytes: &[u8]) -> [u8; 32] {
|
||||
let mut h = blake3::Hasher::new();
|
||||
- h.update(DOMAIN_OBJECT);
|
||||
h.update(envelope_bytes);
|
||||
*h.finalize().as_bytes()
|
||||
}
|
||||
15
wai/http-conformance/falsify/dup-caps.patch
Normal file
15
wai/http-conformance/falsify/dup-caps.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: codecs_dup_alt codecs_dup_plus
|
||||
# a capability may be named twice (codecs::parse_element)
|
||||
diff --git a/wai/wai-rs/src/http_binding/codecs.rs b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
index 530ae822..99f6eba8 100644
|
||||
--- a/wai/wai-rs/src/http_binding/codecs.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
@@ -144,7 +144,7 @@ pub fn parse_element(s: &str) -> Result<Element, CodecsError> {
|
||||
let alt = Alt { cap, plus: caps.map(str::to_string).collect() };
|
||||
check_alt(&alt)?;
|
||||
let repeated_plus = alt.plus.iter().enumerate().any(|(i, p)| alt.plus[..i].contains(p) || *p == alt.cap);
|
||||
- if repeated_plus || alts.iter().any(|x| x.cap == alt.cap) {
|
||||
+ if false && (repeated_plus || alts.iter().any(|x| x.cap == alt.cap)) {
|
||||
return Err(CodecsError::Malformed(format!("{s}: a capability named twice")));
|
||||
}
|
||||
alts.push(alt);
|
||||
17
wai/http-conformance/falsify/intersection.patch
Normal file
17
wai/http-conformance/falsify/intersection.patch
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# must fail: union_O9
|
||||
# the union is the floor, the intersection (codecs::union_of)
|
||||
diff --git a/wai/wai-rs/src/http_binding/codecs.rs b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
index 530ae822..97e32ca4 100644
|
||||
--- a/wai/wai-rs/src/http_binding/codecs.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
@@ -338,8 +338,9 @@ pub fn union_of(objects: &[SeqObject]) -> Result<Element, CodecsError> {
|
||||
let each = described(objects)?;
|
||||
let prefix = objects.iter().map(|o| if o.head.version == Version::Wai2 { Prefix::Wai2 } else { Prefix::Wai1 }).max().unwrap_or(Prefix::Wai1);
|
||||
let mut alts: Vec<Alt> = Vec::new();
|
||||
+ let all = each.clone();
|
||||
for e in each {
|
||||
- for a in e.alts {
|
||||
+ for a in e.alts.into_iter().filter(|a| all.iter().all(|o| o.alts.iter().any(|x| x.cap == a.cap))) {
|
||||
match alts.iter_mut().find(|x| x.cap == a.cap) {
|
||||
Some(x) => {
|
||||
for p in a.plus {
|
||||
15
wai/http-conformance/falsify/layer-any-alt.patch
Normal file
15
wai/http-conformance/falsify/layer-any-alt.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: fetch_O10L1_declared_other_alt
|
||||
# a layer is declared after + in any alt, not only its base's (codecs::check_declared)
|
||||
diff --git a/wai/wai-rs/src/http_binding/codecs.rs b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
index 568a3c5a..b3ea95e5 100644
|
||||
--- a/wai/wai-rs/src/http_binding/codecs.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
@@ -393,7 +393,7 @@ pub fn check_declared(head: &Head, union: &Element, base: Option<&SeqObject>) ->
|
||||
.and_then(|r| super::unhex32(&r.base))
|
||||
.ok_or_else(undeclared)?;
|
||||
let base_cap = base.and_then(|b| base_capability(b, &refines)).ok_or_else(undeclared)?;
|
||||
- return if union.alts.iter().any(|a| a.cap == base_cap && a.plus.contains(&cap)) { Ok(()) } else { Err(undeclared()) };
|
||||
+ return if union.alts.iter().any(|a| { let _ = &base_cap; a.plus.contains(&cap) }) { Ok(()) } else { Err(undeclared()) };
|
||||
}
|
||||
let e = element_of(head)?;
|
||||
if e.prefix > union.prefix {
|
||||
15
wai/http-conformance/falsify/layer-without-base.patch
Normal file
15
wai/http-conformance/falsify/layer-without-base.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: fetch_O10L1_declared_no_base
|
||||
# a layer is declared with no base held (codecs::check_declared)
|
||||
diff --git a/wai/wai-rs/src/http_binding/codecs.rs b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
index 568a3c5a..40a04044 100644
|
||||
--- a/wai/wai-rs/src/http_binding/codecs.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
@@ -392,7 +392,7 @@ pub fn check_declared(head: &Head, union: &Element, base: Option<&SeqObject>) ->
|
||||
.and_then(|v| Refines::from_value(v, RefForm::ContentHash).ok())
|
||||
.and_then(|r| super::unhex32(&r.base))
|
||||
.ok_or_else(undeclared)?;
|
||||
- let base_cap = base.and_then(|b| base_capability(b, &refines)).ok_or_else(undeclared)?;
|
||||
+ let Some(base_cap) = base.and_then(|b| base_capability(b, &refines)) else { return Ok(()) };
|
||||
return if union.alts.iter().any(|a| a.cap == base_cap && a.plus.contains(&cap)) { Ok(()) } else { Err(undeclared()) };
|
||||
}
|
||||
let e = element_of(head)?;
|
||||
15
wai/http-conformance/falsify/left-len.patch
Normal file
15
wai/http-conformance/falsify/left-len.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: head_proof_ok_O3
|
||||
# the left subtree is half the length, rounded down to a chunk (head_proof::left_len)
|
||||
diff --git a/wai/wai-rs/src/http_binding/head_proof.rs b/wai/wai-rs/src/http_binding/head_proof.rs
|
||||
index 40b6f1fa..ea665c22 100644
|
||||
--- a/wai/wai-rs/src/http_binding/head_proof.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/head_proof.rs
|
||||
@@ -35,7 +35,7 @@ pub struct HeadProof {
|
||||
/// leaves at least one byte to the right.
|
||||
fn left_len(n: u64) -> u64 {
|
||||
let q = (n - 1) / CHUNK;
|
||||
- CHUNK << (63 - q.leading_zeros())
|
||||
+ ((n / 2) / CHUNK).max(1) * CHUNK
|
||||
}
|
||||
|
||||
/// The root of a tree from its two children: BLAKE3's parent node with the ROOT flag.
|
||||
15
wai/http-conformance/falsify/merge-classes.patch
Normal file
15
wai/http-conformance/falsify/merge-classes.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: plan_classes_O8
|
||||
# ranges of different classes are merged (plan::plan_components)
|
||||
diff --git a/wai/wai-rs/src/http_binding/plan.rs b/wai/wai-rs/src/http_binding/plan.rs
|
||||
index 1d2902f7..fc58b047 100644
|
||||
--- a/wai/wai-rs/src/http_binding/plan.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/plan.rs
|
||||
@@ -151,7 +151,7 @@ pub fn plan_components(
|
||||
let mut merged: Vec<(Class, u64, u64)> = Vec::new();
|
||||
for (class, a, b) in pieces {
|
||||
match merged.last_mut() {
|
||||
- Some((c, _, e)) if *c == class && *e >= a => *e = (*e).max(b),
|
||||
+ Some((_, _, e)) if *e >= a => *e = (*e).max(b),
|
||||
_ => merged.push((class, a, b)),
|
||||
}
|
||||
}
|
||||
15
wai/http-conformance/falsify/no-root-flag.patch
Normal file
15
wai/http-conformance/falsify/no-root-flag.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: head_proof_ok_O3
|
||||
# the root merge drops the ROOT flag (head_proof::merge_root)
|
||||
diff --git a/wai/wai-rs/src/http_binding/head_proof.rs b/wai/wai-rs/src/http_binding/head_proof.rs
|
||||
index 40b6f1fa..69ba8a98 100644
|
||||
--- a/wai/wai-rs/src/http_binding/head_proof.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/head_proof.rs
|
||||
@@ -40,7 +40,7 @@ fn left_len(n: u64) -> u64 {
|
||||
|
||||
/// The root of a tree from its two children: BLAKE3's parent node with the ROOT flag.
|
||||
fn merge_root(l: &ChainingValue, r: &ChainingValue) -> [u8; 32] {
|
||||
- *merge_subtrees_root(l, r, Mode::Hash).as_bytes()
|
||||
+ merge_subtrees_non_root(l, r, Mode::Hash)
|
||||
}
|
||||
|
||||
/// The non-root chaining value of the subtree `x[..len]` whose first chunk is at
|
||||
15
wai/http-conformance/falsify/plus-sign.patch
Normal file
15
wai/http-conformance/falsify/plus-sign.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: response_plus_first
|
||||
# a Content-Range number may carry a sign (session::digits)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index 4730c192..8b8f7e67 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -194,7 +194,7 @@ impl Selector {
|
||||
/// Whether `s` is one or more ASCII digits: the `1*DIGIT` of RFC 9110 §14.4. No sign,
|
||||
/// separator, space or other script's digit.
|
||||
fn digits(s: &str) -> Option<u64> {
|
||||
- if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
|
||||
+ if s.is_empty() {
|
||||
return None;
|
||||
}
|
||||
s.parse().ok()
|
||||
15
wai/http-conformance/falsify/record-alt.patch
Normal file
15
wai/http-conformance/falsify/record-alt.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: union_OF_record_fallback
|
||||
# a WAI1 lists a capability SPEC §4 never dispatches (codecs::element_of)
|
||||
diff --git a/wai/wai-rs/src/http_binding/codecs.rs b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
index 530ae822..ff531eb8 100644
|
||||
--- a/wai/wai-rs/src/http_binding/codecs.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
@@ -241,7 +241,7 @@ pub fn element_of(head: &Head) -> Result<Element, CodecsError> {
|
||||
let m = head.manifest_v1().map_err(bad)?;
|
||||
let mut alts: Vec<Alt> = Vec::new();
|
||||
for c in [Some(m.model_requirement.capability.as_str()), m.model_requirement.fallback.as_deref()].into_iter().flatten() {
|
||||
- if is_dispatchable(c) && !alts.iter().any(|a| a.cap == short(c)) {
|
||||
+ if !alts.iter().any(|a| a.cap == short(c)) {
|
||||
alts.push(Alt { cap: short(c), plus: Vec::new() });
|
||||
}
|
||||
}
|
||||
15
wai/http-conformance/falsify/refinement-200-whole.patch
Normal file
15
wai/http-conformance/falsify/refinement-200-whole.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: timed_OS_200_before_base
|
||||
# a refinement 200 keeps the whole object it carries (session::FetchSession::take)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index ec31518e..6300eea5 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -533,7 +533,7 @@ impl FetchSession {
|
||||
return Ok(());
|
||||
};
|
||||
match check_response(Some(r), meta, body.len() as u64, len) {
|
||||
- Ok(got) if class == Class::Refinement => {
|
||||
+ Ok(got) if false && class == Class::Refinement => {
|
||||
// A refinement's response adds the bytes it was asked for and no
|
||||
// others: a `200` carrying the whole object is cut to the range.
|
||||
let (first, end) = (got.first.max(r.first), got.end().min(r.end()));
|
||||
14
wai/http-conformance/falsify/refines-makes-layer.patch
Normal file
14
wai/http-conformance/falsify/refines-makes-layer.patch
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# must fail: fetch_OPf_dispatched fetch_OPe_dispatched fetch_OPw_dispatched fetch_OPf_listed_not_layer fetch_OPw_listed_not_layer union_OPw
|
||||
# a payload carrying refines is taken for a layer (head::Head::is_layer)
|
||||
diff --git a/wai/wai-rs/src/http_binding/head.rs b/wai/wai-rs/src/http_binding/head.rs
|
||||
index 97ff1fac..d5f6339e 100644
|
||||
--- a/wai/wai-rs/src/http_binding/head.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/head.rs
|
||||
@@ -164,6 +164,6 @@ impl Head {
|
||||
/// ordinary object: SPEC §4 dispatches it, and `refines` is not read there.
|
||||
pub fn is_layer(&self) -> bool {
|
||||
self.version == Version::Wai1
|
||||
- && self.manifest_v1().is_ok_and(|m| crate::codecs::is_refinement_only(&m.model_requirement.capability))
|
||||
+ && self.manifest_v1().is_ok_and(|m| m.refines.is_some() || crate::codecs::is_refinement_only(&m.model_requirement.capability))
|
||||
}
|
||||
}
|
||||
18
wai/http-conformance/falsify/refusal-ends.patch
Normal file
18
wai/http-conformance/falsify/refusal-ends.patch
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# must fail: fetch_O8_refinement_gzip fetch_O8_refinement_bad_range
|
||||
# a refused refinement response ends the fetch (session::take)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index 4730c192..70f19622 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -481,10 +481,7 @@ impl FetchSession {
|
||||
self.held.push((got, body.to_vec()));
|
||||
Ok(())
|
||||
}
|
||||
- Err(_) if class == Class::Refinement => {
|
||||
- self.abandon(&want);
|
||||
- Ok(())
|
||||
- }
|
||||
+ Err(e) if class == Class::Refinement => Err(e),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
15
wai/http-conformance/falsify/response-no-clock.patch
Normal file
15
wai/http-conformance/falsify/response-no-clock.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: timed_OS_clock_never_ticked timed_OS_clock_ticked_0 timed_OS_clock_stepped_back
|
||||
# a response does not carry the clock, so the base is stamped with the last tick (session::FetchSession::on_response)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index ec31518e..8b35b343 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -463,7 +463,7 @@ impl FetchSession {
|
||||
/// range requested are kept, whatever else a `200` carries: no refinement's response
|
||||
/// touches its base's bytes.
|
||||
pub fn on_response(&mut self, id: u32, meta: &ResponseMeta, body: &[u8], now_ms: u64) -> Result<(), RangeRefusal> {
|
||||
- self.now_ms = now_ms;
|
||||
+ let _ = now_ms;
|
||||
if let Some(Err(e)) = &self.result {
|
||||
return Err(e.clone());
|
||||
}
|
||||
15
wai/http-conformance/falsify/reuse-stale.patch
Normal file
15
wai/http-conformance/falsify/reuse-stale.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: fetch_OD_stale fetch_OT_stale_small
|
||||
# bytes past covered_len are reused after a mismatch (session::advance_components)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index ec31518e..d7874048 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -728,7 +728,7 @@ impl FetchSession {
|
||||
self.abandoned.clear();
|
||||
self.dropped.clear();
|
||||
self.verified.clear();
|
||||
- self.drop_from(head.covered_len());
|
||||
+ let _ = head.covered_len();
|
||||
self.to_rest(&head, WhyWhole::DigestMismatch { index: i }, Class::Base);
|
||||
return self.advance_rest();
|
||||
}
|
||||
65
wai/http-conformance/falsify/run.sh
Executable file
65
wai/http-conformance/falsify/run.sh
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env bash
|
||||
# Falsify the HTTP corpus tool: each patch here breaks one function of wai-rs. For each,
|
||||
# in a fresh worktree of HEAD: apply it, require the build to succeed, and require
|
||||
# `wai_http_vectors verify` to exit non-zero and to name every case the patch's first
|
||||
# line lists. Then the Python side: each `http_verify.py --mutate` must fail, and the
|
||||
# differential must verify every proof and refuse exactly the flipped ones.
|
||||
#
|
||||
# bash wai/http-conformance/falsify/run.sh [work-dir [patch-name…]]
|
||||
#
|
||||
# With patch names, only those patches run (and nothing else).
|
||||
# Run from the repository root. CARGO_TARGET_DIR, if set, is shared by the builds.
|
||||
set -uo pipefail
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
WORK="${1:-${RUNNER_TEMP:-/tmp}/wai-http-falsify}"
|
||||
shift || true
|
||||
ONLY="$*"
|
||||
mkdir -p "$WORK"
|
||||
fail=0
|
||||
for patch in "$ROOT"/wai/http-conformance/falsify/*.patch; do
|
||||
name="$(basename "$patch" .patch)"
|
||||
if [ -n "$ONLY" ] && ! printf ' %s ' $ONLY | grep -q " $name "; then continue; fi
|
||||
wt="$WORK/f-$name"
|
||||
rm -rf "$wt"
|
||||
git -C "$ROOT" worktree add --detach --quiet "$wt" HEAD
|
||||
must="$(head -1 "$patch" | sed 's/^# must fail: //')"
|
||||
if ! git -C "$wt" apply "$patch"; then
|
||||
echo "::error::$name: the patch does not apply"; fail=1
|
||||
elif ! (cd "$wt/wai/wai-rs" && cargo build --release --no-default-features --features http --bin wai_http_vectors) > "$WORK/$name.build.log" 2>&1; then
|
||||
echo "::error::$name: the patched tree does not build"; tail -20 "$WORK/$name.build.log"; fail=1
|
||||
else
|
||||
bin="${CARGO_TARGET_DIR:-$wt/wai/wai-rs/target}/release/wai_http_vectors"
|
||||
"$bin" verify "$wt/wai/http-conformance" > "$WORK/$name.verify.log" 2>&1
|
||||
rc=$?
|
||||
missing=""
|
||||
for c in $must; do grep -q "^FAIL $c:" "$WORK/$name.verify.log" || missing="$missing $c"; done
|
||||
if [ $rc -eq 0 ] || [ -n "$missing" ]; then
|
||||
echo "::error::$name: verify exited $rc; cases not failed:$missing"; fail=1
|
||||
else
|
||||
echo "ok $name: verify exited $rc and failed$(printf ' %s' $must) ($(grep -c '^FAIL' "$WORK/$name.verify.log") failures)"
|
||||
fi
|
||||
fi
|
||||
git -C "$ROOT" worktree remove --force "$wt"
|
||||
done
|
||||
|
||||
[ -n "$ONLY" ] && exit $fail
|
||||
cd "$ROOT/wai/http-conformance"
|
||||
for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done tick-noop refinement-unclipped; do
|
||||
if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then
|
||||
echo "::error::http_verify.py --mutate $m passed"; fail=1
|
||||
else
|
||||
echo "ok http_verify.py --mutate $m: $(grep -c '^FAIL' "$WORK/py-$m.log") failures"
|
||||
fi
|
||||
done
|
||||
# The differential runs the unpatched tool: build it again (the patched builds shared
|
||||
# its output path).
|
||||
(cd "$ROOT/wai/wai-rs" && cargo build --release --no-default-features --features http --bin wai_http_vectors) > "$WORK/unpatched.build.log" 2>&1 || fail=1
|
||||
bin="${CARGO_TARGET_DIR:-$ROOT/wai/wai-rs/target}/release/wai_http_vectors"
|
||||
if [ -x "$bin" ]; then
|
||||
"$bin" diff . --seed 7 --count 500 --out "$WORK/diff.jsonl" > /dev/null || fail=1
|
||||
python3 http_verify.py diff "$WORK/diff.jsonl" || fail=1
|
||||
python3 http_verify.py diff "$WORK/diff.jsonl" --flip 10 --seed 3 || fail=1
|
||||
else
|
||||
echo "::error::no release wai_http_vectors at $bin for the differential"; fail=1
|
||||
fi
|
||||
exit $fail
|
||||
15
wai/http-conformance/falsify/select-no-digest.patch
Normal file
15
wai/http-conformance/falsify/select-no-digest.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: select_O7
|
||||
# selection does not test the payload digest (container::select_entries)
|
||||
diff --git a/wai/wai-rs/src/container.rs b/wai/wai-rs/src/container.rs
|
||||
index aeb5d235..7fefc8da 100644
|
||||
--- a/wai/wai-rs/src/container.rs
|
||||
+++ b/wai/wai-rs/src/container.rs
|
||||
@@ -2670,7 +2670,7 @@ pub fn select_entries<V>(
|
||||
skipped.push((index, why));
|
||||
continue;
|
||||
}
|
||||
- if !digest_ok(index, meta) {
|
||||
+ if false && !digest_ok(index, meta) {
|
||||
skipped.push((index, Skip::ComponentDigest));
|
||||
continue;
|
||||
}
|
||||
15
wai/http-conformance/falsify/skip-digest.patch
Normal file
15
wai/http-conformance/falsify/skip-digest.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: fetch_O7
|
||||
# a fetched component is not checked against its sha256 (session::check_component)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index 05e6cb08..7d4e3b38 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -181,7 +181,7 @@ pub fn check_response(req: Option<ByteRange>, r: &ResponseMeta, body_len: u64, e
|
||||
|
||||
/// Whether `bytes` match entry `index`'s `sha256` in `m`; false when it states none.
|
||||
fn check_component(m: &ManifestV2, index: usize, bytes: &[u8]) -> bool {
|
||||
- m.renditions.get(index).is_some_and(|e| e.sha256.is_some() && e.payload_digest_matches(bytes))
|
||||
+ m.renditions.get(index).is_some()
|
||||
}
|
||||
|
||||
enum Stage {
|
||||
15
wai/http-conformance/falsify/skip-on-absent.patch
Normal file
15
wai/http-conformance/falsify/skip-on-absent.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: plan_no_digest_O4 fetch_O4
|
||||
# a base component with no sha256 is fetched by range (plan::plan_components)
|
||||
diff --git a/wai/wai-rs/src/http_binding/plan.rs b/wai/wai-rs/src/http_binding/plan.rs
|
||||
index 42c4bf47..c5dbc3ed 100644
|
||||
--- a/wai/wai-rs/src/http_binding/plan.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/plan.rs
|
||||
@@ -149,7 +149,7 @@ pub fn plan_components(
|
||||
let mut plan = Plan { requests: Vec::new(), whole_rest: None, dropped: Vec::new() };
|
||||
let Some(rest) = rest_of(head) else { return plan };
|
||||
let has_digest = |i: usize| digests_present.get(i).copied().unwrap_or(false);
|
||||
- if let Some(&(index, _)) = wanted.iter().find(|&&(i, c)| c == Class::Base && !has_digest(i)) {
|
||||
+ if let Some(&(index, _)) = wanted.iter().find(|_| false) {
|
||||
plan.whole_rest = Some((rest, WhyWhole::DigestAbsent { index }));
|
||||
return plan;
|
||||
}
|
||||
15
wai/http-conformance/falsify/threshold-all.patch
Normal file
15
wai/http-conformance/falsify/threshold-all.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: plan_OS_base_only_threshold fetch_OS_threshold_stall
|
||||
# refinement bytes count toward the threshold (plan::plan_components)
|
||||
diff --git a/wai/wai-rs/src/http_binding/plan.rs b/wai/wai-rs/src/http_binding/plan.rs
|
||||
index 42c4bf47..4d021ecd 100644
|
||||
--- a/wai/wai-rs/src/http_binding/plan.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/plan.rs
|
||||
@@ -172,7 +172,7 @@ pub fn plan_components(
|
||||
_ => merged.push((class, a, b)),
|
||||
}
|
||||
}
|
||||
- let base: u128 = merged.iter().filter(|(c, _, _)| *c == Class::Base).map(|(_, a, b)| u128::from(b - a)).sum();
|
||||
+ let base: u128 = merged.iter().map(|(_, a, b)| u128::from(b - a)).sum();
|
||||
if base * 1000 >= u128::from(policy.full_threshold_permille) * u128::from(rest.len()) && base > 0 {
|
||||
plan.whole_rest = Some((rest, WhyWhole::Threshold));
|
||||
plan.dropped.clear();
|
||||
16
wai/http-conformance/falsify/tick-noop.patch
Normal file
16
wai/http-conformance/falsify/tick-noop.patch
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# must fail: timed_OS_base_ready_first timed_OS_clock_never_ticked timed_OS_clock_saturates
|
||||
# tick never abandons a refinement past its deadline (session::FetchSession::tick)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index ec31518e..6a10e5a6 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -397,6 +397,9 @@ impl FetchSession {
|
||||
/// the end of `u64` gives a deadline at `u64::MAX`.
|
||||
pub fn tick(&mut self, now_ms: u64) -> Vec<u32> {
|
||||
self.now_ms = now_ms;
|
||||
+ if self.policy.refinement_deadline_ms.is_some() {
|
||||
+ return Vec::new();
|
||||
+ }
|
||||
let (Some(at), Some(d)) = (self.base_ready_at(), self.policy.refinement_deadline_ms) else { return Vec::new() };
|
||||
if self.result.is_some() || now_ms < at.saturating_add(u64::from(d)) {
|
||||
return Vec::new();
|
||||
15
wai/http-conformance/falsify/trust-content-range.patch
Normal file
15
wai/http-conformance/falsify/trust-content-range.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: response_wrong_range
|
||||
# Content-Range is not compared with the request (session::check_response)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index 4730c192..6cd93bea 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -221,7 +221,7 @@ pub fn check_response(req: Option<ByteRange>, r: &ResponseMeta, body_len: u64, e
|
||||
let (a, b) = span.split_once('-').ok_or_else(mismatch)?;
|
||||
let (a, b) = (digits(a).ok_or_else(mismatch)?, digits(b).ok_or_else(mismatch)?);
|
||||
let expect_last = want.last.min(envelope_len.saturating_sub(1));
|
||||
- if a != want.first || b != expect_last || b < a || (b - a).checked_add(1) != Some(body_len) {
|
||||
+ if b < a || (b - a).checked_add(1) != Some(body_len) {
|
||||
return Err(mismatch());
|
||||
}
|
||||
match digits(complete) {
|
||||
15
wai/http-conformance/falsify/union-no-layer.patch
Normal file
15
wai/http-conformance/falsify/union-no-layer.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: union_O10_layers
|
||||
# a layer joins no alt (codecs::described)
|
||||
diff --git a/wai/wai-rs/src/http_binding/codecs.rs b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
index 530ae822..161ed997 100644
|
||||
--- a/wai/wai-rs/src/http_binding/codecs.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
@@ -326,7 +326,7 @@ fn described(objects: &[SeqObject]) -> Result<Vec<Element>, CodecsError> {
|
||||
let el = els[k].as_mut().expect("a base is not a layer");
|
||||
let alt = el.alts.iter_mut().find(|a| a.cap == base_cap)
|
||||
.ok_or_else(|| CodecsError::LayerUnbound(format!("wai.{cap}: its base's capability is not dispatched")))?;
|
||||
- push_unique(&mut alt.plus, cap);
|
||||
+ let _ = (alt, cap);
|
||||
}
|
||||
Ok(els.into_iter().flatten().collect())
|
||||
}
|
||||
15
wai/http-conformance/falsify/wai-prefix-case.patch
Normal file
15
wai/http-conformance/falsify/wai-prefix-case.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: codecs_upper_prefix
|
||||
# an element beginning WAI is read as classical (codecs::is_wai_element)
|
||||
diff --git a/wai/wai-rs/src/http_binding/codecs.rs b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
index 530ae822..cdc19410 100644
|
||||
--- a/wai/wai-rs/src/http_binding/codecs.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/codecs.rs
|
||||
@@ -156,7 +156,7 @@ pub fn parse_element(s: &str) -> Result<Element, CodecsError> {
|
||||
/// any ASCII case. Such an element that is not a well-formed `wai1.` or `wai2.` element
|
||||
/// is malformed, never classical.
|
||||
fn is_wai_element(e: &str) -> bool {
|
||||
- e.as_bytes().get(..3).is_some_and(|b| b.eq_ignore_ascii_case(b"wai"))
|
||||
+ e.as_bytes().get(..3) == Some(&b"wai"[..])
|
||||
}
|
||||
|
||||
/// Parse a `codecs` value: one entry per `,`-separated element, `None` for a
|
||||
15
wai/http-conformance/falsify/whole-applies-no-digest.patch
Normal file
15
wai/http-conformance/falsify/whole-applies-no-digest.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: fetch_ORN_whole
|
||||
# the whole path applies a refinement that states no digest (session::FetchSession::advance_rest)
|
||||
diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs
|
||||
index 47a906ec..c65b2871 100644
|
||||
--- a/wai/wai-rs/src/http_binding/session.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/session.rs
|
||||
@@ -816,7 +816,7 @@ impl FetchSession {
|
||||
for (i, class) in self.sel.applied(&w.manifest, index) {
|
||||
// A refinement that states no digest is dropped on this path as on the
|
||||
// range path (§7 step 5, §10.3).
|
||||
- if class == Class::Refinement && w.manifest.renditions[i].sha256.is_none() {
|
||||
+ if false && class == Class::Refinement && w.manifest.renditions[i].sha256.is_none() {
|
||||
dropped.push((i, WhyWhole::DigestAbsent { index: i }));
|
||||
continue;
|
||||
}
|
||||
1439
wai/http-conformance/http_verify.py
Normal file
1439
wai/http-conformance/http_verify.py
Normal file
File diff suppressed because it is too large
Load diff
226
wai/http-conformance/objects.json
Normal file
226
wai/http-conformance/objects.json
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
{
|
||||
"O1": {
|
||||
"cid": "df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2",
|
||||
"path": "objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai",
|
||||
"n": 859,
|
||||
"h": 859,
|
||||
"codecs": "wai1.neural.int_mlicpp",
|
||||
"what": "WAI1, pinned, below one chunk is not reached: the head proof covers it whole"
|
||||
},
|
||||
"O2": {
|
||||
"cid": "25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34",
|
||||
"path": "objects/25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34.wai",
|
||||
"n": 20250,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.neural.int_mlicpp|image.avif|image.jxl|image.png",
|
||||
"what": "WAI2 image: a pinned integer learned rendition, AVIF, JPEG XL and PNG of one picture"
|
||||
},
|
||||
"O3": {
|
||||
"cid": "6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9",
|
||||
"path": "objects/6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9.wai",
|
||||
"n": 98613,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.av1+meta.hdr10plus",
|
||||
"what": "WAI2 video: AV1 at two rates, HDR10+ metadata bound to the higher by id"
|
||||
},
|
||||
"O4": {
|
||||
"cid": "6bc4da2051c4c4cef757b2a7ee927a5353a6717224de27fdb6a26dd6c0485a15",
|
||||
"path": "objects/6bc4da2051c4c4cef757b2a7ee927a5353a6717224de27fdb6a26dd6c0485a15.wai",
|
||||
"n": 39361,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.av1",
|
||||
"what": "WAI2 whose first selectable entry states no sha256"
|
||||
},
|
||||
"O5": {
|
||||
"cid": "e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b",
|
||||
"path": "objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai",
|
||||
"n": 251,
|
||||
"h": 251,
|
||||
"codecs": "wai1.text.zstd",
|
||||
"what": "a small WAI1, fetched whole"
|
||||
},
|
||||
"O7": {
|
||||
"cid": "922db9b513790966272927c4f542fb01cb25ff9c7b189fb6699bf92894c29a6f",
|
||||
"path": "objects/922db9b513790966272927c4f542fb01cb25ff9c7b189fb6699bf92894c29a6f.wai",
|
||||
"n": 39437,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.av1",
|
||||
"what": "WAI2 whose first entry states a wrong sha256"
|
||||
},
|
||||
"O8": {
|
||||
"cid": "a016e64f311ac1128481d0da292c7cbcb1d98d6c9edd6983ad8b9e385e6c0b09",
|
||||
"path": "objects/a016e64f311ac1128481d0da292c7cbcb1d98d6c9edd6983ad8b9e385e6c0b09.wai",
|
||||
"n": 46433,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.avc+video.lcevc|video.av1",
|
||||
"what": "WAI2: an AVC base, an LCEVC enhancement bound to it, and an AV1 alternative"
|
||||
},
|
||||
"O9a": {
|
||||
"cid": "31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3",
|
||||
"path": "objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai",
|
||||
"n": 21034,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.av1|video.int_hyper",
|
||||
"what": "segment 1 of 3: AV1 and an integer learned video rendition"
|
||||
},
|
||||
"O9b": {
|
||||
"cid": "b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb",
|
||||
"path": "objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai",
|
||||
"n": 18004,
|
||||
"h": 1009,
|
||||
"codecs": "wai1.video.av1",
|
||||
"what": "segment 2 of 3: AV1 only (WAI1)"
|
||||
},
|
||||
"O9c": {
|
||||
"cid": "a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133",
|
||||
"path": "objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai",
|
||||
"n": 18381,
|
||||
"h": 1009,
|
||||
"codecs": "wai1.video.av1",
|
||||
"what": "segment 3 of 3: AV1 only (WAI1)"
|
||||
},
|
||||
"OD": {
|
||||
"cid": "eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228",
|
||||
"path": "objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai",
|
||||
"n": 53794,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.av1+meta.hdr10plus",
|
||||
"what": "Appendix D's synthetic envelope"
|
||||
},
|
||||
"OH": {
|
||||
"cid": "fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3",
|
||||
"path": "objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai",
|
||||
"n": 23519,
|
||||
"h": 6129,
|
||||
"codecs": "wai2.video.av1",
|
||||
"what": "a WAI2 whose head spans several chunks"
|
||||
},
|
||||
"O10": {
|
||||
"cid": "1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8",
|
||||
"path": "objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai",
|
||||
"n": 1825,
|
||||
"h": 1009,
|
||||
"codecs": "wai1.audio.flac",
|
||||
"what": "a staged-delivery base: WAI1 FLAC"
|
||||
},
|
||||
"O10L1": {
|
||||
"cid": "53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab",
|
||||
"path": "objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai",
|
||||
"n": 12711,
|
||||
"h": 1009,
|
||||
"codecs": null,
|
||||
"what": "layer 1 over O10: WAI1 audio refinement"
|
||||
},
|
||||
"O10L2": {
|
||||
"cid": "753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c",
|
||||
"path": "objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai",
|
||||
"n": 18367,
|
||||
"h": 1009,
|
||||
"codecs": null,
|
||||
"what": "layer 2 over O10, after layer 1"
|
||||
},
|
||||
"OS": {
|
||||
"cid": "9aecf3c0a15e19e0ff127071afe1bf03ef2587083b5fb0aeb8283e642fb27cc0",
|
||||
"path": "objects/9aecf3c0a15e19e0ff127071afe1bf03ef2587083b5fb0aeb8283e642fb27cc0.wai",
|
||||
"n": 38460,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.avc+video.lcevc",
|
||||
"what": "WAI2: an AVC base and an LCEVC enhancement nearly four times its size"
|
||||
},
|
||||
"ON": {
|
||||
"cid": "ce447337900fd74988ee4fec8277e1726c1bb1fb6fbd65a74900219d1ded741b",
|
||||
"path": "objects/ce447337900fd74988ee4fec8277e1726c1bb1fb6fbd65a74900219d1ded741b.wai",
|
||||
"n": 38384,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.avc+video.lcevc",
|
||||
"what": "OS with the enhancement stating no sha256"
|
||||
},
|
||||
"OW": {
|
||||
"cid": "2e91576dd7feb0405a41eec039bfd1264fe6e52f81880850bbaa4c30c5a24314",
|
||||
"path": "objects/2e91576dd7feb0405a41eec039bfd1264fe6e52f81880850bbaa4c30c5a24314.wai",
|
||||
"n": 38460,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.avc+video.lcevc",
|
||||
"what": "OS with the enhancement stating a wrong sha256"
|
||||
},
|
||||
"OT": {
|
||||
"cid": "45280f543b7eb77cf29e9752c2a0b47af8e12b8fe289b720c0e1b152ad5179e6",
|
||||
"path": "objects/45280f543b7eb77cf29e9752c2a0b47af8e12b8fe289b720c0e1b152ad5179e6.wai",
|
||||
"n": 12368,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.av1",
|
||||
"what": "WAI2 small enough for the first request to hold whole"
|
||||
},
|
||||
"OF": {
|
||||
"cid": "3b54ebe898cf23b1e5e7b651c860d8548c7f8f6c060dc1c79eecaf47bc4d2644",
|
||||
"path": "objects/3b54ebe898cf23b1e5e7b651c860d8548c7f8f6c060dc1c79eecaf47bc4d2644.wai",
|
||||
"n": 283,
|
||||
"h": 283,
|
||||
"codecs": "wai1.text.zstd",
|
||||
"what": "WAI1 naming a record capability with a payload fallback"
|
||||
},
|
||||
"OE1": {
|
||||
"cid": "4bae50654cbba982c74f55082e5d2ee090c50cb54be34d71d26958464819aa4c",
|
||||
"path": "objects/4bae50654cbba982c74f55082e5d2ee090c50cb54be34d71d26958464819aa4c.wai",
|
||||
"n": 299,
|
||||
"h": 299,
|
||||
"codecs": "wai1.text.zstd",
|
||||
"what": "WAI1 whose payload is encrypted"
|
||||
},
|
||||
"OE2s": {
|
||||
"cid": "2f1336eddd3eb02bab5ffca9b44fd4f38a530bc35f6d42ca98a55ce28997c23f",
|
||||
"path": "objects/2f1336eddd3eb02bab5ffca9b44fd4f38a530bc35f6d42ca98a55ce28997c23f.wai",
|
||||
"n": 5251,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.text.zstd",
|
||||
"what": "WAI2 whose security member is a string"
|
||||
},
|
||||
"OE2t": {
|
||||
"cid": "374d7f60814b24e8cec94b288e2a5fda87bbb047defb550f778c54fe177d8263",
|
||||
"path": "objects/374d7f60814b24e8cec94b288e2a5fda87bbb047defb550f778c54fe177d8263.wai",
|
||||
"n": 5242,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.text.zstd",
|
||||
"what": "WAI2 whose security member is true"
|
||||
},
|
||||
"OR": {
|
||||
"cid": "fd3d6101b76dd3c0b01373d3da28997e2ba499d1ee5d1b27396005a748ca4419",
|
||||
"path": "objects/fd3d6101b76dd3c0b01373d3da28997e2ba499d1ee5d1b27396005a748ca4419.wai",
|
||||
"n": 9434,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.int_motion",
|
||||
"what": "WAI2 with a refinement component whose refines is malformed"
|
||||
},
|
||||
"OPf": {
|
||||
"cid": "9c79f069e1ad17932f27059a763dc2aec26ca3e19c70cf45aafe40d8e3e9893f",
|
||||
"path": "objects/9c79f069e1ad17932f27059a763dc2aec26ca3e19c70cf45aafe40d8e3e9893f.wai",
|
||||
"n": 267,
|
||||
"h": 267,
|
||||
"codecs": "wai1.text.zstd",
|
||||
"what": "WAI1 text whose refines is false: an ordinary object"
|
||||
},
|
||||
"OPe": {
|
||||
"cid": "0223dac27b41616c470d3c8e3f14d24d1e7294153226678051e9f84a0a5a07d7",
|
||||
"path": "objects/0223dac27b41616c470d3c8e3f14d24d1e7294153226678051e9f84a0a5a07d7.wai",
|
||||
"n": 264,
|
||||
"h": 264,
|
||||
"codecs": "wai1.text.zstd",
|
||||
"what": "WAI1 text whose refines is {}: an ordinary object"
|
||||
},
|
||||
"OPw": {
|
||||
"cid": "d3301aea0e39f84acf41e3075d0b0789bc065105520623dda98053cb1eb071da",
|
||||
"path": "objects/d3301aea0e39f84acf41e3075d0b0789bc065105520623dda98053cb1eb071da.wai",
|
||||
"n": 421,
|
||||
"h": 421,
|
||||
"codecs": "wai1.text.zstd",
|
||||
"what": "WAI1 text with a well-formed refines: an ordinary object"
|
||||
},
|
||||
"ORN": {
|
||||
"cid": "a1cc741a342c2f3301523b887ebea597938b4a2e92e8c159c6fefcc4c5b507e8",
|
||||
"path": "objects/a1cc741a342c2f3301523b887ebea597938b4a2e92e8c159c6fefcc4c5b507e8.wai",
|
||||
"n": 32659,
|
||||
"h": 1009,
|
||||
"codecs": "wai2.video.av1+video.int_refine",
|
||||
"what": "WAI2: an AV1 base and a refinement stating no sha256"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"0223dac27b41616c470d3c8e3f14d24d1e7294153226678051e9f84a0a5a07d7"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"2e91576dd7feb0405a41eec039bfd1264fe6e52f81880850bbaa4c30c5a24314"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"2f1336eddd3eb02bab5ffca9b44fd4f38a530bc35f6d42ca98a55ce28997c23f"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"374d7f60814b24e8cec94b288e2a5fda87bbb047defb550f778c54fe177d8263"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"3b54ebe898cf23b1e5e7b651c860d8548c7f8f6c060dc1c79eecaf47bc4d2644"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"45280f543b7eb77cf29e9752c2a0b47af8e12b8fe289b720c0e1b152ad5179e6"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"4bae50654cbba982c74f55082e5d2ee090c50cb54be34d71d26958464819aa4c"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
"6bc4da2051c4c4cef757b2a7ee927a5353a6717224de27fdb6a26dd6c0485a15"
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue