From 2e7158ff1246f2ed4d4135dc8c72917a87021002 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 5 Oct 2026 07:15:44 -0400 Subject: [PATCH 01/31] wai: HTTP delivery text, the jwp-receipts and SPEC pointers, and the wire identifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit W3-1. The extension http-delivery.md binds envelopes to HTTP: the proposed media type, the codecs union and floor, content-addressed objects, the WHP1 head proof, verified component range fetch with one pick on every path, receipt, key, grant, set and playlist sidecars, cache rules, priorities and deadlines, HLS/DASH adjacency, and the capability probe. Written against main rather than the design's tree: refinements are staged delivery's (enhancement companions, refinement components, layer objects whose layer is refines.layer), a `+cap` may be a companion or a refinement, and a range-fetched refinement component meets staged-delivery §10 through its exploded envelope (http-delivery §8.3, jwp-receipts §3 step 4). SPEC §7.1's component id/sha256 text and digest step were already on main, so W3-1a lands nothing. jwp-receipts: §1 transport pointer, §3 step 4 head-proof cid, new §4.4 and §4.5, §7.9 locations. moq-streaming-format §6, SPEC §2, §7.1 and the wai.video.manifest row. The registry gains WIRE_IDENTIFIERS (the request W3 made to the registry stream, which had not landed), exported as wire_identifiers and checked against the sections it names. The section-ref check already runs on every extension (wai-section-refs.yml). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-conformance.yml | 10 + wai/CHANGELOG.md | 34 ++ wai/REGISTRY.md | 46 +- wai/SPEC.md | 11 +- wai/extensions/http-delivery.md | 806 +++++++++++++++++++++++++ wai/extensions/jwp-receipts.md | 34 ++ wai/extensions/moq-streaming-format.md | 3 + wai/registry/capabilities.json | 219 +++++++ wai/registry/capabilities.schema.json | 52 +- wai/wai-rs/src/codecs/registry.rs | 230 ++++++- 10 files changed, 1441 insertions(+), 4 deletions(-) create mode 100644 wai/extensions/http-delivery.md diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index 823da2153..a13bd8196 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -36,6 +36,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: @@ -61,6 +66,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' diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index c7bea8e52..c697cd44a 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,40 @@ 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 (`.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. +- No envelope byte or receipt byte changes for existing material. + ### Staged delivery: refinement layers, base admission, and WAI2 refinement components - **The extension** [staged-delivery](extensions/staged-delivery.md): a stage set is a diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md index 5ff7d8222..b6561bcd9 100644 --- a/wai/REGISTRY.md +++ b/wai/REGISTRY.md @@ -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 | diff --git a/wai/SPEC.md b/wai/SPEC.md index 7f0831d27..6b6d5d5c2 100644 --- a/wai/SPEC.md +++ b/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 diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md new file mode 100644 index 000000000..4b16ce044 --- /dev/null +++ b/wai/extensions/http-delivery.md @@ -0,0 +1,806 @@ +# 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`, bytes [0, 12 + man_len) — magic, `man_len`, the manifest and + `payload_len`; for `WAI2`, bytes [0, 10 + man_len + 8·n) — 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 [head_len + offset, head_len + offset + length). +- **Covered length**: `covered_len = min(envelope_len, 1024·⌈(15 + head_len)/1024⌉ − 15)`. +- **Alias**: any URI other than `.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 that carries `refines` + ([staged-delivery](staged-delivery.md) §3, §9). + +## 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=`: the object's `envelope_len`. +- `h=`: the object's `covered_len`. +- `c=`: 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: a wrong value costs a request, never a check, since every length +that matters is checked against the head proof (§6.3). + +## 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. Classical elements (for + example `mp4a.40.2`) MAY appear beside WAI elements. +- `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 be one of the two + (`codecs_malformed`). A string that does not match the grammar 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`, `cap` and, when it is not `null`, `fallback`, + each as an `alt`. For a `WAI2`, one `alt` per distinct selectable capability, each + with `+` and every companion capability bound to a rendition of it and every + refinement capability whose base is a rendition of it. The layer objects a playlist + lists for a segment (§10.4) join the `alt` of the capability of the segment's + picked base. `alt`s are ordered by first appearance, and the capabilities after + `+` by first appearance in table 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`). +- **Floor.** The floor of a sequence is the `alt`s whose first `cap` is selectable in + **every** object, each with the capabilities after `+` that are bound to it in + every 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 `.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 +`.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: "" +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=::` (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 envelope bytes [0, covered_len) 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 (`.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 X[start, start+len) as 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` equals §2's formula (`head_proof_malformed`); +2. the chaining values are consumed exactly, and `root` equals the cid + (`head_proof_mismatch`). + +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 `.wai.hp`. +2. Check each response (§7.1). If `covered_len` exceeds the bytes received, request the + rest of [0, covered_len). +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 or refinement the sink will apply, states + no `sha256`, go to step 7 (`component_digest_absent`). +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). Verify each + against its entry's `sha256`. On a mismatch (`component_digest_mismatch`), discard + it and go to step 7. +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 bytes step 6 would request are at least 90 % +of the bytes after `covered_len`. A client MUST NOT send a multi-range request and rely +on a multipart answer. A `WAI1` has one component, its payload: a client fetches it +whole, or as the rest after its head. + +### 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`); +- 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 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. + +## 8. Receipts, keys, grants and parameter sets over HTTP + +### 8.1 Receipt files + +`rcpt/.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/.json`, and reports `parent_not_held` when it +cannot, never skipping the step. + +### 8.2 Receipt index + +`.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": "", + "delivery": [ { "property": { …WAI_RECEIPT… }, "object_receipt": { …jwp-receipts §2.4.2… } } ], + "claims": [ "" ] } +``` + +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/.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/.json`: `{"kind":"wai-module-grants","module":"","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 +(`/`), 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/.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 */` 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 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. + +### 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). + +### 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). + +### 10.4 Layer objects + +A layer object is fetched as any object. 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=".wai#n=…" +``` + +`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=""`, + 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 `.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=&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": { "": { "via": "wasm" | "ua" | "host", + "asked": { "": true | false }, + "provisional": true | false } }, + "held_sets": { "": [ "" ] } } +``` + +- `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.` 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` | §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. + +## 14. Conformance + +- [`http-conformance/`](../http-conformance/) holds objects, head proofs and cases for + §4–§7 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. +- [`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 `.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/.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. + +**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. + +**Plan 2: a policy reorders to `high`, which binds `hdr`.** +- [3594, 53793] is 50,200 B: 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. + +## 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. diff --git a/wai/extensions/jwp-receipts.md b/wai/extensions/jwp-receipts.md index 5dd2439e5..a968fae01 100644 --- a/wai/extensions/jwp-receipts.md +++ b/wai/extensions/jwp-receipts.md @@ -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: diff --git a/wai/extensions/moq-streaming-format.md b/wai/extensions/moq-streaming-format.md index 8f1370591..b0c5d77ff 100644 --- a/wai/extensions/moq-streaming-format.md +++ b/wai/extensions/moq-streaming-format.md @@ -161,6 +161,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. diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index ee08dbeea..20182f378 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -1474,5 +1474,224 @@ "label": "wai.quantum.rearrange", "reason": "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_identifiers": [ + { + "name": "#WAI-LAYER", + "kind": "playlist-tag", + "document": "extensions/http-delivery.md", + "section": "§10.4", + "status": "defined" + }, + { + "name": ".wai", + "kind": "path", + "document": "extensions/http-delivery.md", + "section": "§5.2", + "status": "defined" + }, + { + "name": ".wai.hp", + "kind": "path", + "document": "extensions/http-delivery.md", + "section": "§6.2", + "status": "defined" + }, + { + "name": ".wai.receipts", + "kind": "path", + "document": "extensions/http-delivery.md", + "section": "§8.2", + "status": "defined" + }, + { + "name": "/.well-known/wai-grants/", + "kind": "well-known", + "document": "extensions/http-delivery.md", + "section": "§8.4", + "status": "defined" + }, + { + "name": "/.well-known/wai-key-status", + "kind": "well-known", + "document": "extensions/http-delivery.md", + "section": "§8.4", + "status": "defined" + }, + { + "name": "/.well-known/wai-keys", + "kind": "well-known", + "document": "extensions/jwp-receipts.md", + "section": "§7.1", + "status": "defined" + }, + { + "name": "WAI1", + "kind": "magic", + "document": "SPEC.md", + "section": "§2", + "status": "defined" + }, + { + "name": "WAI2", + "kind": "magic", + "document": "SPEC.md", + "section": "§7.1", + "status": "defined" + }, + { + "name": "WAI_RECEIPT", + "kind": "catalog", + "document": "extensions/moq-streaming-format.md", + "section": "§5", + "status": "defined" + }, + { + "name": "WHP1", + "kind": "magic", + "document": "extensions/http-delivery.md", + "section": "§6.2", + "status": "defined" + }, + { + "name": "X-WAI-FLOOR", + "kind": "playlist-attribute", + "document": "extensions/http-delivery.md", + "section": "§11.1", + "status": "defined" + }, + { + "name": "application/wai", + "kind": "media-type", + "document": "extensions/http-delivery.md", + "section": "§3", + "status": "proposed" + }, + { + "name": "mr/", + "kind": "path", + "document": "extensions/http-delivery.md", + "section": "§8.6", + "status": "defined" + }, + { + "name": "rcpt/", + "kind": "path", + "document": "extensions/http-delivery.md", + "section": "§8.1", + "status": "defined" + }, + { + "name": "science.transaction.wai.multivariant", + "kind": "data-id", + "document": "extensions/http-delivery.md", + "section": "§11.1", + "status": "defined" + }, + { + "name": "tag:transaction.science,2026:wai/floor", + "kind": "scheme", + "document": "extensions/http-delivery.md", + "section": "§11.1", + "status": "defined" + }, + { + "name": "tag:transaction.science,2026:wai/http-delivery", + "kind": "scheme", + "document": "extensions/http-delivery.md", + "section": "§11.1", + "status": "defined" + }, + { + "name": "wai", + "kind": "catalog", + "document": "extensions/moq-streaming-format.md", + "section": "§3", + "status": "defined" + }, + { + "name": "wai-key-status", + "kind": "document-kind", + "document": "extensions/jwp-receipts.md", + "section": "§7.9", + "status": "defined" + }, + { + "name": "wai-module-grant", + "kind": "document-kind", + "document": "extensions/jwp-receipts.md", + "section": "§7.9", + "status": "defined" + }, + { + "name": "wai-module-grants", + "kind": "document-kind", + "document": "extensions/http-delivery.md", + "section": "§8.4", + "status": "defined" + }, + { + "name": "wai-receipt-index", + "kind": "document-kind", + "document": "extensions/http-delivery.md", + "section": "§8.2", + "status": "defined" + }, + { + "name": "wai1", + "kind": "codecs-prefix", + "document": "extensions/http-delivery.md", + "section": "§4.1", + "status": "defined" + }, + { + "name": "wai1", + "kind": "fourcc", + "document": "extensions/http-delivery.md", + "section": "Appendix B", + "status": "provisional" + }, + { + "name": "wai2", + "kind": "codecs-prefix", + "document": "extensions/http-delivery.md", + "section": "§4.1", + "status": "defined" + }, + { + "name": "wai2", + "kind": "fourcc", + "document": "extensions/http-delivery.md", + "section": "Appendix B", + "status": "provisional" + }, + { + "name": "waiC", + "kind": "fourcc", + "document": "extensions/http-delivery.md", + "section": "Appendix B", + "status": "provisional" + }, + { + "name": "wai_capabilities", + "kind": "catalog", + "document": "extensions/moq-streaming-format.md", + "section": "§4", + "status": "defined" + }, + { + "name": "wai_determinism", + "kind": "catalog", + "document": "extensions/moq-streaming-format.md", + "section": "§4", + "status": "defined" + }, + { + "name": "wai_receipts", + "kind": "catalog", + "document": "extensions/moq-streaming-format.md", + "section": "§4", + "status": "defined" + } ] } diff --git a/wai/registry/capabilities.schema.json b/wai/registry/capabilities.schema.json index 1d1db5385..7ab4f36ff 100644 --- a/wai/registry/capabilities.schema.json +++ b/wai/registry/capabilities.schema.json @@ -9,7 +9,8 @@ "schema", "capabilities", "identifiers", - "unregistered_labels" + "unregistered_labels", + "wire_identifiers" ], "properties": { "schema": { @@ -178,6 +179,55 @@ } } } + }, + "wire_identifiers": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "kind", + "document", + "section", + "status" + ], + "properties": { + "name": { + "type": "string" + }, + "kind": { + "enum": [ + "media-type", + "codecs-prefix", + "fourcc", + "magic", + "data-id", + "scheme", + "path", + "well-known", + "playlist-tag", + "playlist-attribute", + "document-kind", + "catalog" + ] + }, + "document": { + "type": "string" + }, + "section": { + "type": "string", + "pattern": "^(§[0-9]+(\\.[0-9]+)*|Appendix [A-Z])$" + }, + "status": { + "enum": [ + "defined", + "provisional", + "proposed" + ] + } + } + } } } } diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 1b2c5b8fd..1b6ecd33e 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -361,6 +361,150 @@ pub const IDENTIFIERS: &[Identifier] = &[ }, ]; +/// What a [`WireIdentifier`] names. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum WireKind { + /// A media type. + MediaType, + /// The prefix of a `codecs` element. + CodecsPrefix, + /// An ISOBMFF four-character code. + FourCc, + /// The magic number at the start of a binary format. + Magic, + /// An HLS `EXT-X-SESSION-DATA` `DATA-ID`. + DataId, + /// A URI scheme identifier (a DASH `schemeIdUri`). + Scheme, + /// A path suffix or a path segment a resource is named by. + Path, + /// A path under `/.well-known/` (RFC 8615). + WellKnown, + /// A playlist tag. + PlaylistTag, + /// A playlist attribute. + PlaylistAttribute, + /// The `kind` member of a JSON document. + DocumentKind, + /// A MoQ catalog value, catalog field or object property. + Catalog, +} + +impl WireKind { + /// Every kind, in declaration order. + pub const fn all() -> [Self; 12] { + [ + Self::MediaType, Self::CodecsPrefix, Self::FourCc, Self::Magic, Self::DataId, + Self::Scheme, Self::Path, Self::WellKnown, Self::PlaylistTag, + Self::PlaylistAttribute, Self::DocumentKind, Self::Catalog, + ] + } + /// The label the export uses. + pub fn label(self) -> &'static str { + match self { + Self::MediaType => "media-type", + Self::CodecsPrefix => "codecs-prefix", + Self::FourCc => "fourcc", + Self::Magic => "magic", + Self::DataId => "data-id", + Self::Scheme => "scheme", + Self::Path => "path", + Self::WellKnown => "well-known", + Self::PlaylistTag => "playlist-tag", + Self::PlaylistAttribute => "playlist-attribute", + Self::DocumentKind => "document-kind", + Self::Catalog => "catalog", + } + } +} + +/// How settled a [`WireIdentifier`] is. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum WireStatus { + /// Defined by its document, and used as defined. + Defined, + /// Defined by its document for test material only, until a registration + /// authority registers it. + Provisional, + /// Proposed for registration; its document says it is not registered. + Proposed, +} + +impl WireStatus { + /// Every status, in declaration order. + pub const fn all() -> [Self; 3] { + [Self::Defined, Self::Provisional, Self::Proposed] + } + /// The label the export uses. + pub fn label(self) -> &'static str { + match self { + Self::Defined => "defined", + Self::Provisional => "provisional", + Self::Proposed => "proposed", + } + } +} + +/// A name a WAI text puts on the wire outside a manifest: a media type, a `codecs` +/// prefix, a magic number, a path, a playlist tag or attribute, a document kind, a +/// catalog field. `document` is the text that defines it, relative to `wai/`, and +/// `section` the section there (`§N` or `Appendix X`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct WireIdentifier { + pub name: &'static str, + pub kind: WireKind, + pub document: &'static str, + pub section: &'static str, + pub status: WireStatus, +} + +const HTTP_DELIVERY: &str = "extensions/http-delivery.md"; +const MOQ_FORMAT: &str = "extensions/moq-streaming-format.md"; +const JWP_RECEIPTS: &str = "extensions/jwp-receipts.md"; + +const fn wire(name: &'static str, kind: WireKind, document: &'static str, section: &'static str, + status: WireStatus) -> WireIdentifier { + WireIdentifier { name, kind, document, section, status } +} + +/// The wire identifiers of the container, of its two delivery bindings +/// (http-delivery, moq-streaming-format) and of the key discovery those bindings +/// serve (jwp-receipts §7), in document order. The export lists them sorted by name +/// and kind. +pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[ + wire("WAI1", WireKind::Magic, "SPEC.md", "§2", WireStatus::Defined), + wire("WAI2", WireKind::Magic, "SPEC.md", "§7.1", WireStatus::Defined), + wire("application/wai", WireKind::MediaType, HTTP_DELIVERY, "§3", WireStatus::Proposed), + wire("wai1", WireKind::CodecsPrefix, HTTP_DELIVERY, "§4.1", WireStatus::Defined), + wire("wai2", WireKind::CodecsPrefix, HTTP_DELIVERY, "§4.1", WireStatus::Defined), + wire(".wai", WireKind::Path, HTTP_DELIVERY, "§5.2", WireStatus::Defined), + wire("WHP1", WireKind::Magic, HTTP_DELIVERY, "§6.2", WireStatus::Defined), + wire(".wai.hp", WireKind::Path, HTTP_DELIVERY, "§6.2", WireStatus::Defined), + wire("rcpt/", WireKind::Path, HTTP_DELIVERY, "§8.1", WireStatus::Defined), + wire(".wai.receipts", WireKind::Path, HTTP_DELIVERY, "§8.2", WireStatus::Defined), + wire("wai-receipt-index", WireKind::DocumentKind, HTTP_DELIVERY, "§8.2", WireStatus::Defined), + wire("/.well-known/wai-key-status", WireKind::WellKnown, HTTP_DELIVERY, "§8.4", WireStatus::Defined), + wire("/.well-known/wai-grants/", WireKind::WellKnown, HTTP_DELIVERY, "§8.4", WireStatus::Defined), + wire("wai-module-grants", WireKind::DocumentKind, HTTP_DELIVERY, "§8.4", WireStatus::Defined), + wire("mr/", WireKind::Path, HTTP_DELIVERY, "§8.6", WireStatus::Defined), + wire("#WAI-LAYER", WireKind::PlaylistTag, HTTP_DELIVERY, "§10.4", WireStatus::Defined), + wire("science.transaction.wai.multivariant", WireKind::DataId, HTTP_DELIVERY, "§11.1", WireStatus::Defined), + wire("X-WAI-FLOOR", WireKind::PlaylistAttribute, HTTP_DELIVERY, "§11.1", WireStatus::Defined), + wire("tag:transaction.science,2026:wai/http-delivery", WireKind::Scheme, HTTP_DELIVERY, "§11.1", WireStatus::Defined), + wire("tag:transaction.science,2026:wai/floor", WireKind::Scheme, HTTP_DELIVERY, "§11.1", WireStatus::Defined), + wire("wai1", WireKind::FourCc, HTTP_DELIVERY, "Appendix B", WireStatus::Provisional), + wire("wai2", WireKind::FourCc, HTTP_DELIVERY, "Appendix B", WireStatus::Provisional), + wire("waiC", WireKind::FourCc, HTTP_DELIVERY, "Appendix B", WireStatus::Provisional), + wire("wai", WireKind::Catalog, MOQ_FORMAT, "§3", WireStatus::Defined), + wire("wai_capabilities", WireKind::Catalog, MOQ_FORMAT, "§4", WireStatus::Defined), + wire("wai_determinism", WireKind::Catalog, MOQ_FORMAT, "§4", WireStatus::Defined), + wire("wai_receipts", WireKind::Catalog, MOQ_FORMAT, "§4", WireStatus::Defined), + wire("WAI_RECEIPT", WireKind::Catalog, MOQ_FORMAT, "§5", WireStatus::Defined), + wire("/.well-known/wai-keys", WireKind::WellKnown, JWP_RECEIPTS, "§7.1", WireStatus::Defined), + wire("wai-key-status", WireKind::DocumentKind, JWP_RECEIPTS, "§7.9", WireStatus::Defined), + wire("wai-module-grant", WireKind::DocumentKind, JWP_RECEIPTS, "§7.9", WireStatus::Defined), +]; + /// A string with the shape of a capability that the reference implementation uses /// as a label but that no revision registers, and why. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -608,11 +752,22 @@ pub fn capabilities_json() -> String { let mut labels: Vec<&UnregisteredLabel> = UNREGISTERED_LABELS.iter().collect(); labels.sort_by_key(|l| l.label); let labels: Vec = labels.iter().map(|l| json!({ "label": l.label, "reason": l.reason })).collect(); + let wires: Vec = sorted_wire_identifiers() + .iter() + .map(|w| json!({ + "name": w.name, + "kind": w.kind.label(), + "document": w.document, + "section": w.section, + "status": w.status.label(), + })) + .collect(); json_text(&json!({ "schema": format!("{FORGE_TREE}/registry/capabilities.schema.json"), "capabilities": caps, "identifiers": ids, "unregistered_labels": labels, + "wire_identifiers": wires, })) } @@ -632,6 +787,8 @@ pub fn capabilities_schema() -> String { let tiers: Vec<&str> = FidelityTier::all().iter().map(|t| t.label()).collect(); let licences: Vec<&str> = LicenseClass::all().iter().map(|l| l.label()).collect(); let replicates: Vec<&str> = ReplicateClass::all().iter().map(|r| r.label()).collect(); + let wire_kinds: Vec<&str> = WireKind::all().iter().map(|k| k.label()).collect(); + let wire_statuses: Vec<&str> = WireStatus::all().iter().map(|s| s.label()).collect(); let string_pattern = "^wai(\\.[a-z0-9_]+)+$"; json_text(&json!({ "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -640,7 +797,7 @@ pub fn capabilities_schema() -> String { "description": "Every string SPEC §5 registers, generated by `wai registry export` from the reference implementation's registry (wai-rs/src/codecs/registry.rs). REGISTRY.md states the policy.", "type": "object", "additionalProperties": false, - "required": ["schema", "capabilities", "identifiers", "unregistered_labels"], + "required": ["schema", "capabilities", "identifiers", "unregistered_labels", "wire_identifiers"], "properties": { "schema": { "type": "string" }, "capabilities": { @@ -697,6 +854,21 @@ pub fn capabilities_schema() -> String { "reason": { "type": "string" } } } + }, + "wire_identifiers": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "kind", "document", "section", "status"], + "properties": { + "name": { "type": "string" }, + "kind": { "enum": wire_kinds }, + "document": { "type": "string" }, + "section": { "type": "string", "pattern": "^(§[0-9]+(\\.[0-9]+)*|Appendix [A-Z])$" }, + "status": { "enum": wire_statuses } + } + } } } })) @@ -1022,9 +1194,22 @@ pub fn registry_md_table() -> String { for l in UNREGISTERED_LABELS { o.push_str(&format!("| `{}` | {} |\n", l.label, l.reason)); } + o.push('\n'); + o.push_str("| wire identifier (not a capability) | kind | status | defined in |\n|---|---|---|---|\n"); + for w in sorted_wire_identifiers() { + let doc = w.document.trim_end_matches(".md").trim_start_matches("extensions/"); + o.push_str(&format!("| `{}` | {} | {} | {} {} |\n", w.name, w.kind.label(), w.status.label(), doc, w.section)); + } o } +/// [`WIRE_IDENTIFIERS`] sorted by name, then kind: the order the export writes. +pub fn sorted_wire_identifiers() -> Vec<&'static WireIdentifier> { + let mut v: Vec<&WireIdentifier> = WIRE_IDENTIFIERS.iter().collect(); + v.sort_by(|a, b| a.name.cmp(b.name).then(a.kind.cmp(&b.kind))); + v +} + /// `md` with the text between its [`REGISTRY_MD_BEGIN`] and [`REGISTRY_MD_END`] /// lines replaced by [`registry_md_table`], or `None` if the markers are missing. pub fn splice_registry_md(md: &str) -> Option { @@ -1343,6 +1528,49 @@ mod tests { assert_eq!(splice_registry_md("no markers"), None); } + /// Every wire identifier is written in the document it names, that document has + /// the section it names, and no name is listed twice with one kind. + #[test] + fn wire_identifiers_are_in_their_documents() { + let mut seen = std::collections::BTreeSet::new(); + for w in WIRE_IDENTIFIERS { + assert!(seen.insert((w.name, w.kind)), "{} ({}) is listed twice", w.name, w.kind.label()); + let text = read(w.document); + assert!(text.contains(w.name), "{} does not appear in {}", w.name, w.document); + let heading = match w.section.strip_prefix('§') { + Some(num) => text.lines().any(|l| { + let t = l.trim_start_matches('#'); + l.starts_with('#') && t.starts_with(' ') + && (t[1..].starts_with(&format!("{num} ")) || t[1..].starts_with(&format!("{num}. "))) + }), + None => text.lines().any(|l| l.starts_with("## ") && l[3..].starts_with(w.section)), + }; + assert!(heading, "{} has no section {} for {}", w.document, w.section, w.name); + } + } + + /// The schema's wire-identifier enums are the labels, and the export writes only + /// values in them. + #[test] + fn wire_identifier_schema_enums_equal_labels() { + let schema: serde_json::Value = serde_json::from_str(&read("registry/capabilities.schema.json")).unwrap(); + let props = &schema["properties"]["wire_identifiers"]["items"]["properties"]; + let enum_of = |field: &str| -> Vec { + props[field]["enum"].as_array().unwrap_or_else(|| panic!("{field} has no enum")) + .iter().filter_map(|v| v.as_str().map(String::from)).collect() + }; + assert_eq!(enum_of("kind"), WireKind::all().iter().map(|k| k.label().to_string()).collect::>()); + assert_eq!(enum_of("status"), WireStatus::all().iter().map(|s| s.label().to_string()).collect::>()); + let export: serde_json::Value = serde_json::from_str(&read("registry/capabilities.json")).unwrap(); + let rows = export["wire_identifiers"].as_array().unwrap(); + assert_eq!(rows.len(), WIRE_IDENTIFIERS.len()); + for r in rows { + for field in ["kind", "status"] { + assert!(enum_of(field).iter().any(|e| r[field] == *e), "{field} {} is not in the schema", r[field]); + } + } + } + /// The record and label strings copied from wai-quantum equal its constants. #[cfg(feature = "quantum_ops")] #[test] -- 2.54.0 (Apple Git-157) From 07b0884763cf3db799a793effc906832e672cef5 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 5 Oct 2026 07:26:06 -0400 Subject: [PATCH 02/31] wai-rs: select_entries and the http_binding core (head, head proof, plan, session, codecs, origin) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit W3-2, the library. container::select_entries is SPEC §7.1's walk over a WAI2 manifest's entries with the digest test passed in, so a client holding only the head and WaiMulti::pick run one function (pick_where now delegates to it); companion_indices and refinement_indices are companions_of_index and refinements_of without payloads, which now delegate to them. http_binding (feature `http`, on jwp_hash's BLAKE3): parse_head and §5.1; covered_len; the WHP1 head proof built and verified with BLAKE3's tree, checking the root before any covered byte is parsed; plan_head and plan_components (one request per range and class, the rest of the object on a missing digest or past the threshold); a sans-IO FetchSession with §7.1's response checks, component digests, the whole-object fallback and refinement deadlines; the codecs grammar, union, floor, check_declared and supports; an origin's headers and Range answers. Integer constants are associated or crate-private so the C header generator does not export them. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/Cargo.toml | 7 +- wai/wai-rs/include/wai.h | 10 + wai/wai-rs/src/container.rs | 173 ++++-- wai/wai-rs/src/http_binding/codecs.rs | 304 ++++++++++ wai/wai-rs/src/http_binding/head.rs | 160 ++++++ wai/wai-rs/src/http_binding/head_proof.rs | 215 +++++++ wai/wai-rs/src/http_binding/mod.rs | 201 +++++++ wai/wai-rs/src/http_binding/origin.rs | 100 ++++ wai/wai-rs/src/http_binding/plan.rs | 168 ++++++ wai/wai-rs/src/http_binding/session.rs | 663 ++++++++++++++++++++++ wai/wai-rs/src/http_binding/tests.rs | 1 + wai/wai-rs/src/lib.rs | 4 + 12 files changed, 1948 insertions(+), 58 deletions(-) create mode 100644 wai/wai-rs/src/http_binding/codecs.rs create mode 100644 wai/wai-rs/src/http_binding/head.rs create mode 100644 wai/wai-rs/src/http_binding/head_proof.rs create mode 100644 wai/wai-rs/src/http_binding/mod.rs create mode 100644 wai/wai-rs/src/http_binding/origin.rs create mode 100644 wai/wai-rs/src/http_binding/plan.rs create mode 100644 wai/wai-rs/src/http_binding/session.rs create mode 100644 wai/wai-rs/src/http_binding/tests.rs diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 361264dda..2294ced6b 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -81,7 +81,7 @@ c2pa = { version = "0.90", optional = true, default-features = false, # ---- interactive worlds (optional, behind the `world` feature) ---- # extensions/interactive-worlds: WWLD container, signed CausalOps, # deterministic fixed-point simulation, replay-equivalence conformance. -blake3 = { version = "1", optional = true } # state hashes, node minting, op/seal commitments +blake3 = { version = "1.8", optional = true } # state hashes, node minting, op/seal commitments; `hazmat` (1.8) for the HTTP head proof ed25519-dalek = { version = "2", optional = true } # op + seal signatures num-bigint = { version = "0.4", optional = true } # oer: exact decimal arithmetic in the composition checks # The curve ed25519-dalek is built on, named directly only by `receipt_claims`: its @@ -184,6 +184,11 @@ jwp_hash = ["dep:blake3"] # (WIR1) is integer rANS (neural_int); content hashes are jwp_hash's BLAKE3; stage # hashes are SHA-256, always on. staged = ["neural_int", "jwp_hash"] +# HTTP delivery (extensions/http-delivery.md): objects named by their content hash, the +# WHP1 head proof (BLAKE3's tree, through jwp_hash's BLAKE3), verified range fetch of one +# WAI2 component, the codecs union and floor, and an origin's responses. Component +# digests are SHA-256, always on. No I/O in the library; `wai http` does it. +http = ["jwp_hash"] # Typed receipt classes on the JWP chain (extensions/jwp-receipts.md §2.4.3–§2.4.7, # §2.6, §3.1–§3.2): `claim` receipts of class energy / lawful-basis / log-inclusion, # the soft-binding class, and recovery under the ancestor rule. Needs `provenance` diff --git a/wai/wai-rs/include/wai.h b/wai/wai-rs/include/wai.h index e80552da8..57e905f88 100644 --- a/wai/wai-rs/include/wai.h +++ b/wai/wai-rs/include/wai.h @@ -316,6 +316,16 @@ */ #define RESOLUTION_FLOOR_UJ_PPM 288676 +/** + * The most chaining values a proof may carry. + */ +#define HeadProof_MAX_CV 64 + +/** + * The fixed part of the file: magic, cid, `envelope_len`, `covered_len`, `n_cv`. + */ +#define HeadProof_FIXED_LEN 53 + /** * `CausalOp::mutation_kind` for an action input (move/look/buttons + free payload). */ diff --git a/wai/wai-rs/src/container.rs b/wai/wai-rs/src/container.rs index 4be7bdc96..aeb5d2352 100644 --- a/wai/wai-rs/src/container.rs +++ b/wai/wai-rs/src/container.rs @@ -2329,40 +2329,18 @@ impl WaiMulti { pub(crate) fn pick_where( &self, eligible: impl Fn(usize, &RenditionMeta) -> Result<(), Skip>, - mut verify: impl FnMut(&str, &Prior) -> Result, + verify: impl FnMut(&str, &Prior) -> Result, ) -> Result, Vec<(usize, Skip)>> { - // SPEC §6.4 step 2, before selection: an encrypted envelope is inert at a sink - // that opens no scheme, and every entry is reported so. - if !manifest_v2_plaintext(&self.manifest) { - return Err((0..self.manifest.renditions.len()).map(|i| (i, Skip::Encrypted)).collect()); - } - let mut skipped = Vec::new(); - for (index, (meta, payload)) in self.manifest.renditions.iter().zip(self.renditions.iter()).enumerate() { - if !meta.is_selectable() { - skipped.push((index, Skip::NotSelectable)); - continue; - } - if let Err(why) = eligible(index, meta) { - skipped.push((index, why)); - continue; - } - if !meta.payload_digest_matches(payload) { - skipped.push((index, Skip::ComponentDigest)); - continue; - } - let verified = match meta.prior.as_ref() { - Some(p) if pin_applies_to(&meta.capability) => match verify(&meta.capability, p) { - Ok(v) => Some(v), - Err(r) => { - skipped.push((index, Skip::Pin(r))); - continue; - } - }, - _ => None, - }; - return Ok(Picked { index, meta, payload, verified, skipped }); - } - Err(skipped) + let order: Vec = (0..self.manifest.renditions.len()).collect(); + let digest_ok = |i: usize, m: &RenditionMeta| self.renditions.get(i).is_some_and(|p| m.payload_digest_matches(p)); + let (index, verified, skipped) = select_entries(&self.manifest, &order, eligible, digest_ok, verify)?; + Ok(Picked { + index, + meta: &self.manifest.renditions[index], + payload: &self.renditions[index], + verified, + skipped, + }) } /// Check that no component, in either role, names a candidate capability @@ -2487,18 +2465,17 @@ impl WaiMulti { /// SPEC §7.1). A sink offers their exploded envelopes to staged-delivery §4 /// ([`WaiMulti::explode`]) after [`Self::check_component_roles`] passes. pub fn refinements_of(&self, index: usize) -> Vec<(usize, &RenditionMeta, &[u8])> { - let metas = &self.manifest.renditions; - let Some(base_id) = metas.get(index).and_then(|b| b.id.as_deref()) else { return Vec::new() }; - metas - .iter() - .zip(self.renditions.iter()) - .enumerate() - .filter(|(_, (m, p))| { - m.is_refinement() - && m.payload_digest_matches(p) - && m.refines.as_ref().and_then(|v| Refines::from_value(v, RefForm::ComponentId).ok()).is_some_and(|r| r.base == base_id) + self.with_matching_payloads(refinement_indices(&self.manifest.renditions, index)) + } + + /// The entries at `indices` whose payload matches its `sha256`, with their + /// payloads. + fn with_matching_payloads(&self, indices: Vec) -> Vec<(usize, &RenditionMeta, &[u8])> { + indices.into_iter() + .filter_map(|i| { + let (m, p) = (self.manifest.renditions.get(i)?, self.renditions.get(i)?); + m.payload_digest_matches(p).then_some((i, m, p.as_slice())) }) - .map(|(i, (m, p))| (i, m, p.as_slice())) .collect() } @@ -2637,19 +2614,7 @@ impl WaiMulti { /// one only if the envelope passes [`Self::check_component_roles`] and it /// recognises the companion's capability. pub fn companions_of_index(&self, index: usize) -> Vec<(usize, &RenditionMeta, &[u8])> { - let metas = &self.manifest.renditions; - let Some(base) = metas.get(index) else { return Vec::new() }; - metas.iter().zip(self.renditions.iter()).enumerate() - .filter(|(_, (m, p))| { - m.is_companion() - && m.payload_digest_matches(p) - && match m.companion_of_id.as_deref() { - Some(id) => base.id.as_deref() == Some(id), - None => m.companion_of.as_deref() == Some(base.capability.as_str()), - } - }) - .map(|(i, (m, p))| (i, m, p.as_slice())) - .collect() + self.with_matching_payloads(companion_indices(&self.manifest.renditions, index)) } /// The companions that name `capability` in `companion_of`, whatever entry @@ -2665,6 +2630,100 @@ impl WaiMulti { pub fn intent(&self) -> &str { &self.manifest.intent } } +/// SPEC §7.1 selection over a `WAI2` manifest's entries alone, with no payload in +/// hand: the walk [`WaiMulti::pick`] makes, in `order` (a deployer policy, SPEC §7.1 +/// step 2: a permutation of a subset of the entry indices; an index not in it is not +/// considered, and an index out of range is ignored). Each entry considered is +/// skipped, in this order, when it is not selectable ([`Skip::NotSelectable`]), when +/// `eligible` refuses it (its reason), when `digest_ok` is false +/// ([`Skip::ComponentDigest`]), or when its pin is refused ([`Skip::Pin`]); the first +/// that passes is picked, with what `verify` returned and the entries skipped before +/// it. An encrypted envelope (SPEC §6.4 step 2) is inert before any entry is +/// considered: every entry is [`Skip::Encrypted`]. +/// +/// `digest_ok` is the payload test: `WaiMulti::pick` passes +/// [`RenditionMeta::payload_digest_matches`] over the entry's payload, and a client +/// that holds only the head passes `|_, _| true`, deferring the test to the bytes it +/// fetches (`extensions/http-delivery.md` §7 step 4). Both are this one function, so +/// the two paths pick alike on bytes that verify (http-delivery §7.2). +pub fn select_entries( + manifest: &ManifestV2, + order: &[usize], + eligible: impl Fn(usize, &RenditionMeta) -> Result<(), Skip>, + digest_ok: impl Fn(usize, &RenditionMeta) -> bool, + mut verify: impl FnMut(&str, &Prior) -> Result, +) -> Result<(usize, Option, Vec<(usize, Skip)>), Vec<(usize, Skip)>> { + let entries = &manifest.renditions; + // SPEC §6.4 step 2, before selection: an encrypted envelope is inert at a sink + // that opens no scheme, and every entry is reported so. + if !manifest_v2_plaintext(manifest) { + return Err((0..entries.len()).map(|i| (i, Skip::Encrypted)).collect()); + } + let mut skipped = Vec::new(); + for &index in order { + let Some(meta) = entries.get(index) else { continue }; + if !meta.is_selectable() { + skipped.push((index, Skip::NotSelectable)); + continue; + } + if let Err(why) = eligible(index, meta) { + skipped.push((index, why)); + continue; + } + if !digest_ok(index, meta) { + skipped.push((index, Skip::ComponentDigest)); + continue; + } + let verified = match meta.prior.as_ref() { + Some(p) if pin_applies_to(&meta.capability) => match verify(&meta.capability, p) { + Ok(v) => Some(v), + Err(r) => { + skipped.push((index, Skip::Pin(r))); + continue; + } + }, + _ => None, + }; + return Ok((index, verified, skipped)); + } + Err(skipped) +} + +/// The companions SPEC §7.1 binds to the entry at `index`, by entry alone: each +/// companion whose `companion_of_id` names that entry's `id`, or, with no +/// `companion_of_id`, whose `companion_of` is that entry's capability. +/// [`WaiMulti::companions_of_index`] is this with the payload digest checked; a client +/// that holds only the head checks it on the bytes it fetches. +pub fn companion_indices(entries: &[RenditionMeta], index: usize) -> Vec { + let Some(base) = entries.get(index) else { return Vec::new() }; + entries.iter().enumerate() + .filter(|(_, m)| { + m.is_companion() + && match m.companion_of_id.as_deref() { + Some(id) => base.id.as_deref() == Some(id), + None => m.companion_of.as_deref() == Some(base.capability.as_str()), + } + }) + .map(|(i, _)| i) + .collect() +} + +/// The refinement components whose `refines.base` names the entry at `index`, in +/// table order, by entry alone: [`WaiMulti::refinements_of`] without the payload +/// digest, which a client that holds only the head checks on the bytes it fetches. +pub fn refinement_indices(entries: &[RenditionMeta], index: usize) -> Vec { + let Some(base_id) = entries.get(index).and_then(|b| b.id.as_deref()) else { return Vec::new() }; + entries.iter().enumerate() + .filter(|(_, m)| { + m.is_refinement() + && m.refines.as_ref() + .and_then(|v| Refines::from_value(v, RefForm::ComponentId).ok()) + .is_some_and(|r| r.base == base_id) + }) + .map(|(i, _)| i) + .collect() +} + /// Sniff the magic and return which envelope version is in this byte /// blob. Lets callers branch on `Wai::from_bytes` vs `WaiMulti::from_bytes` /// without parsing twice. diff --git a/wai/wai-rs/src/http_binding/codecs.rs b/wai/wai-rs/src/http_binding/codecs.rs new file mode 100644 index 000000000..8c0cc84c9 --- /dev/null +++ b/wai/wai-rs/src/http_binding/codecs.rs @@ -0,0 +1,304 @@ +//! The `codecs` parameter (http-delivery §4): the grammar of a WAI element, the union +//! a variant declares (exhaustive), the floor every object of it offers, and support. +//! The registry decides kinds: [`crate::codecs::registry::capability_kind`] and +//! [`crate::codecs::registry::is_candidate`]. + +use super::head::{Head, Version}; +use crate::codecs::registry::{capability_kind, is_candidate, Kind}; +use crate::container::{companion_indices, refinement_indices, ManifestV2}; + +/// The container an element names: `wai1` (every object is a `WAI1`) or `wai2` (some +/// object is a `WAI2`). +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Prefix { + Wai1, + Wai2, +} + +impl Prefix { + /// `wai1` or `wai2`. + pub fn label(self) -> &'static str { + match self { + Prefix::Wai1 => "wai1", + Prefix::Wai2 => "wai2", + } + } +} + +/// One `alt`: a selectable capability and the companion or refinement capabilities +/// applied with it, each without its leading `wai.`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Alt { + pub cap: String, + pub plus: Vec, +} + +/// A WAI element. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Element { + pub prefix: Prefix, + pub alts: Vec, +} + +/// Why a `codecs` value, or a head against one, is refused (§4). +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CodecsError { + Malformed(String), + NotMedia(String), + Candidate(String), + NoCommonCapability, + Undeclared(String), +} + +impl CodecsError { + /// `codecs_malformed`, `codecs_not_media`, `codecs_candidate`, + /// `codecs_no_common_capability` or `codecs_undeclared`. + pub fn code(&self) -> &'static str { + match self { + CodecsError::Malformed(_) => "codecs_malformed", + CodecsError::NotMedia(_) => "codecs_not_media", + CodecsError::Candidate(_) => "codecs_candidate", + CodecsError::NoCommonCapability => "codecs_no_common_capability", + CodecsError::Undeclared(_) => "codecs_undeclared", + } + } +} + +impl std::fmt::Display for CodecsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CodecsError::Malformed(s) | CodecsError::NotMedia(s) | CodecsError::Candidate(s) | CodecsError::Undeclared(s) => { + write!(f, "{}: {s}", self.code()) + } + CodecsError::NoCommonCapability => f.write_str(self.code()), + } + } +} + +impl std::error::Error for CodecsError {} + +/// Whether `cap` (without `wai.`) matches the `cap` rule. +fn cap_matches(cap: &str) -> bool { + let mut parts = cap.split('.'); + let first = parts.next().unwrap_or(""); + let lcalnum = |b: u8| b.is_ascii_lowercase() || b.is_ascii_digit(); + let mut rest = 0; + let ok_rest = parts.all(|p| { + rest += 1; + !p.is_empty() && p.bytes().all(|b| lcalnum(b) || b == b'_') + }); + !first.is_empty() && first.bytes().all(lcalnum) && rest >= 1 && ok_rest +} + +/// What a capability may be in an element, by its registered kind: an error for a +/// record, a derivation or a candidate; otherwise whether it is applied with another +/// (companion-only or refinement-only). A capability this revision does not register +/// may stand either way. +fn applied_kind(cap: &str) -> Result, CodecsError> { + let full = format!("wai.{cap}"); + match capability_kind(&full) { + Some(Kind::Derivation | Kind::Record) => Err(CodecsError::NotMedia(full)), + _ if is_candidate(&full) => Err(CodecsError::Candidate(full)), + Some(Kind::Companion | Kind::Refinement) => Ok(Some(true)), + Some(Kind::Payload) => Ok(Some(false)), + None => Ok(None), + } +} + +fn check_alt(alt: &Alt) -> Result<(), CodecsError> { + for (i, cap) in std::iter::once(&alt.cap).chain(alt.plus.iter()).enumerate() { + if !cap_matches(cap) { + return Err(CodecsError::Malformed(format!("{cap:?} is not a capability without its wai. prefix"))); + } + match (i == 0, applied_kind(cap)?) { + (true, Some(true)) => return Err(CodecsError::Malformed(format!("wai.{cap} is applied with another and cannot lead an alt"))), + (false, Some(false)) => return Err(CodecsError::Malformed(format!("wai.{cap} is standalone media and cannot follow +"))), + _ => {} + } + } + Ok(()) +} + +/// Parse one WAI element (§4.1). +pub fn parse_element(s: &str) -> Result { + let malformed = || CodecsError::Malformed(s.to_string()); + let (prefix, rest) = s.split_once('.').ok_or_else(malformed)?; + let prefix = match prefix { + "wai1" => Prefix::Wai1, + "wai2" => Prefix::Wai2, + _ => return Err(malformed()), + }; + let mut alts = Vec::new(); + for a in rest.split('|') { + let mut caps = a.split('+'); + let cap = caps.next().unwrap_or("").to_string(); + let alt = Alt { cap, plus: caps.map(str::to_string).collect() }; + check_alt(&alt)?; + alts.push(alt); + } + Ok(Element { prefix, alts }) +} + +/// Whether an element of a `codecs` value is a WAI element: its first identifier is +/// `wai` followed by digits, in any case. Such an element that is not `wai1` or +/// `wai2` is malformed, never classical. +fn is_wai_element(e: &str) -> bool { + let first = e.split('.').next().unwrap_or(""); + first.len() > 3 && first[..3].eq_ignore_ascii_case("wai") && first[3..].bytes().all(|b| b.is_ascii_digit()) +} + +/// Parse a `codecs` value: one entry per `,`-separated element, `None` for a +/// classical element (§4.1). +pub fn parse(value: &str) -> Result>, CodecsError> { + value + .split(',') + .map(|e| e.trim()) + .map(|e| if is_wai_element(e) { parse_element(e).map(Some) } else { Ok(None) }) + .collect() +} + +/// The element as text. +pub fn render(e: &Element) -> String { + let alts: Vec = e.alts.iter().map(|a| std::iter::once(a.cap.as_str()).chain(a.plus.iter().map(String::as_str)).collect::>().join("+")).collect(); + format!("{}.{}", e.prefix.label(), alts.join("|")) +} + +fn short(cap: &str) -> String { + cap.strip_prefix("wai.").unwrap_or(cap).to_string() +} + +fn push_unique(v: &mut Vec, s: String) { + if !v.contains(&s) { + v.push(s); + } +} + +/// The alts of one `WAI2` manifest: one per distinct selectable capability in table +/// order, each with the companion capabilities bound to a rendition of it and the +/// refinement capabilities whose base is one, in table order. +fn alts_v2(m: &ManifestV2) -> Vec { + let e = &m.renditions; + let mut alts: Vec = Vec::new(); + for (i, r) in e.iter().enumerate() { + if r.is_companion() || r.is_refinement() { + continue; + } + let cap = short(&r.capability); + let pos = match alts.iter().position(|a| a.cap == cap) { + Some(p) => p, + None => { + alts.push(Alt { cap, plus: Vec::new() }); + alts.len() - 1 + } + }; + let mut bound: Vec = companion_indices(e, i); + bound.extend(refinement_indices(e, i)); + bound.sort_unstable(); + for j in bound { + push_unique(&mut alts[pos].plus, short(&e[j].capability)); + } + } + // `+` capabilities in table order across the renditions of one capability. + for a in &mut alts { + a.plus.sort_by_key(|p| e.iter().position(|r| short(&r.capability) == *p)); + } + alts +} + +/// The element describing one object (§4.2): its union. +pub fn element_of(head: &Head) -> Result { + let bad = |e: super::RangeRefusal| CodecsError::Malformed(e.to_string()); + let (prefix, alts) = match head.version { + Version::Wai1 => { + let m = head.manifest_v1().map_err(bad)?; + let mut alts = vec![Alt { cap: short(&m.model_requirement.capability), plus: Vec::new() }]; + if let Some(f) = m.model_requirement.fallback.as_deref() { + if !alts.iter().any(|a| a.cap == short(f)) { + alts.push(Alt { cap: short(f), plus: Vec::new() }); + } + } + (Prefix::Wai1, alts) + } + Version::Wai2 => (Prefix::Wai2, alts_v2(&head.manifest_v2().map_err(bad)?)), + }; + for a in &alts { + check_alt(a)?; + } + Ok(Element { prefix, alts }) +} + +/// The union of a sequence of objects (§4.2): every `alt` any of them has, by first +/// appearance, each with every capability applied with it in any of them. +pub fn union_of(heads: &[Head]) -> Result { + let mut prefix = Prefix::Wai1; + let mut alts: Vec = Vec::new(); + for h in heads { + let e = element_of(h)?; + prefix = prefix.max(e.prefix); + for a in e.alts { + match alts.iter_mut().find(|x| x.cap == a.cap) { + Some(x) => { + for p in a.plus { + push_unique(&mut x.plus, p); + } + } + None => alts.push(a), + } + } + } + if alts.is_empty() { + return Err(CodecsError::NoCommonCapability); + } + Ok(Element { prefix, alts }) +} + +/// The floor of a sequence of objects (§4.2): the `alt`s whose capability every +/// object has, each with the capabilities applied with it in every object, written +/// with the union's prefix. Empty: `codecs_no_common_capability`. +pub fn floor_of(heads: &[Head]) -> Result { + let union = union_of(heads)?; + let each: Vec = heads.iter().map(element_of).collect::>()?; + let mut alts = Vec::new(); + for a in &union.alts { + let found: Vec<&Alt> = each.iter().filter_map(|e| e.alts.iter().find(|x| x.cap == a.cap)).collect(); + if found.len() != each.len() { + continue; + } + let plus = a.plus.iter().filter(|p| found.iter().all(|x| x.plus.contains(p))).cloned().collect(); + alts.push(Alt { cap: a.cap.clone(), plus }); + } + if alts.is_empty() { + return Err(CodecsError::NoCommonCapability); + } + Ok(Element { prefix: union.prefix, alts }) +} + +/// §7 step 3: refuse an object whose head names a capability, or a container, the +/// variant's union does not list (`codecs_undeclared`). +pub fn check_declared(head: &Head, union: &Element) -> Result<(), CodecsError> { + let e = element_of(head)?; + if e.prefix > union.prefix { + return Err(CodecsError::Undeclared("a WAI2 object under a wai1 element".into())); + } + for a in &e.alts { + let Some(u) = union.alts.iter().find(|u| u.cap == a.cap) else { + return Err(CodecsError::Undeclared(format!("wai.{}", a.cap))); + }; + if let Some(p) = a.plus.iter().find(|p| !u.plus.contains(p)) { + return Err(CodecsError::Undeclared(format!("wai.{p} with wai.{}", a.cap))); + } + } + Ok(()) +} + +/// §4.2's support over the floor: the sink reads the element's container +/// (`containers`) and has the leading capability of at least one of the floor's +/// `alt`s (`has`, given the full capability string). The capabilities after `+` +/// never affect support. +pub fn supports(floor: &Element, containers: &[Prefix], has: impl Fn(&str) -> bool) -> bool { + let reads = match floor.prefix { + Prefix::Wai1 => containers.contains(&Prefix::Wai1), + Prefix::Wai2 => containers.contains(&Prefix::Wai1) && containers.contains(&Prefix::Wai2), + }; + reads && floor.alts.iter().any(|a| has(&format!("wai.{}", a.cap))) +} diff --git a/wai/wai-rs/src/http_binding/head.rs b/wai/wai-rs/src/http_binding/head.rs new file mode 100644 index 000000000..1470133d0 --- /dev/null +++ b/wai/wai-rs/src/http_binding/head.rs @@ -0,0 +1,160 @@ +//! The head of an object (http-delivery §2, §5.1): for `WAI1`, the magic, `man_len`, +//! the manifest and `payload_len`; for `WAI2`, the magic, `man_len`, the manifest, +//! `n_renditions` and the rendition table (SPEC §2, §7.1). The head gives every +//! component's byte range and, through each entry's `sha256`, its digest. + +use super::{RangeRefusal, DOMAIN_LEN}; +use crate::container::{check_unique_members, Manifest, ManifestV2}; + +/// Which container an object is. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Version { + Wai1, + Wai2, +} + +/// One component's byte range in the object: [start, start + len). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ComponentRange { + pub index: usize, + pub start: u64, + pub len: u64, +} + +/// A parsed head. `bytes` is the head itself, [0, head_len). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Head { + pub version: Version, + pub head_len: u64, + pub envelope_len: u64, + pub manifest: std::ops::Range, + pub components: Vec, + pub bytes: Vec, +} + +/// What [`parse_head`] found: the head, or how many bytes from offset 0 it needs. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum HeadParse { + Need(u64), + Head(Head), +} + +/// `covered_len` (http-delivery §2): the head rounded up so that, with the content +/// hash's domain prefix, it ends on a BLAKE3 chunk boundary, and never past the object. +pub fn covered_len(head_len: u64, envelope_len: u64) -> u64 { + let x = DOMAIN_LEN + head_len; + envelope_len.min(x.div_ceil(1024) * 1024 - DOMAIN_LEN) +} + +fn u32_at(b: &[u8], at: usize) -> u64 { + u64::from(u32::from_le_bytes(b[at..at + 4].try_into().expect("4 bytes"))) +} + +/// Parse the head at the start of `prefix`, an object of `envelope_len` bytes, and +/// apply §5.1: the object is exactly one envelope. Returns `Need(n)` while `prefix` +/// holds fewer than the `n` bytes the head needs. +pub fn parse_head(prefix: &[u8], envelope_len: u64) -> Result { + let have = prefix.len() as u64; + if have < 8 { + return Ok(HeadParse::Need(8)); + } + let version = match &prefix[..4] { + b"WAI1" => Version::Wai1, + b"WAI2" => Version::Wai2, + _ => return Err(RangeRefusal::HeadMalformed("the magic is neither WAI1 nor WAI2".into())), + }; + let man_len = u32_at(prefix, 4); + let manifest = 8..8 + man_len; + let (head_len, components) = match version { + Version::Wai1 => { + let head_len = 12 + man_len; + if have < head_len { + return Ok(HeadParse::Need(head_len)); + } + let payload_len = u32_at(prefix, (8 + man_len) as usize); + (head_len, vec![ComponentRange { index: 0, start: head_len, len: payload_len }]) + } + Version::Wai2 => { + if have < 10 + man_len { + return Ok(HeadParse::Need(10 + man_len)); + } + let at = (8 + man_len) as usize; + let n = u64::from(u16::from_le_bytes([prefix[at], prefix[at + 1]])); + let head_len = 10 + man_len + 8 * n; + if have < head_len { + return Ok(HeadParse::Need(head_len)); + } + let table = at + 2; + let mut expected = 0u64; + let mut components = Vec::with_capacity(n as usize); + for i in 0..n as usize { + let off = u32_at(prefix, table + 8 * i); + let len = u32_at(prefix, table + 8 * i + 4); + if off != expected { + return Err(RangeRefusal::HeadMalformed(format!( + "rendition {i}'s offset {off} is not contiguous (expected {expected})"))); + } + components.push(ComponentRange { index: i, start: head_len + off, len }); + expected += len; + } + (head_len, components) + } + }; + let total = head_len + components.iter().map(|c| c.len).sum::(); + if total != envelope_len { + return Err(RangeRefusal::ObjectLengthMismatch { expected: total, got: envelope_len }); + } + let bytes = prefix[..head_len as usize].to_vec(); + let head = Head { version, head_len, envelope_len, manifest, components, bytes }; + // The manifest parses, names no member twice, and (WAI2) has one entry per row. + match head.version { + Version::Wai1 => { + head.manifest_v1()?; + } + Version::Wai2 => { + let m = head.manifest_v2()?; + if m.renditions.len() != head.components.len() { + return Err(RangeRefusal::HeadMalformed(format!( + "the table has {} rows and the manifest {} renditions", + head.components.len(), m.renditions.len()))); + } + } + } + Ok(HeadParse::Head(head)) +} + +impl Head { + fn manifest_bytes(&self) -> &[u8] { + &self.bytes[self.manifest.start as usize..self.manifest.end as usize] + } + + /// The `WAI1` manifest. + pub fn manifest_v1(&self) -> Result { + if self.version != Version::Wai1 { + return Err(RangeRefusal::HeadMalformed("not a WAI1 head".into())); + } + let b = self.manifest_bytes(); + check_unique_members(b).map_err(|e| RangeRefusal::HeadMalformed(e.to_string()))?; + serde_json::from_slice(b).map_err(|e| RangeRefusal::HeadMalformed(e.to_string())) + } + + /// The `WAI2` manifest. + pub fn manifest_v2(&self) -> Result { + if self.version != Version::Wai2 { + return Err(RangeRefusal::HeadMalformed("not a WAI2 head".into())); + } + let b = self.manifest_bytes(); + check_unique_members(b).map_err(|e| RangeRefusal::HeadMalformed(e.to_string()))?; + serde_json::from_slice(b).map_err(|e| RangeRefusal::HeadMalformed(e.to_string())) + } + + /// The component at `index`. + pub fn component(&self, index: usize) -> Option { + self.components.get(index).copied() + } + + /// This object's `covered_len`. + pub fn covered_len(&self) -> u64 { + covered_len(self.head_len, self.envelope_len) + } +} diff --git a/wai/wai-rs/src/http_binding/head_proof.rs b/wai/wai-rs/src/http_binding/head_proof.rs new file mode 100644 index 000000000..cfe5aac70 --- /dev/null +++ b/wai/wai-rs/src/http_binding/head_proof.rs @@ -0,0 +1,215 @@ +//! The `WHP1` head proof (http-delivery §6): the BLAKE3 chaining values that prove an +//! object's bytes [0, covered_len) against its content hash without the rest. +//! +//! The content hash is the BLAKE3 root over `X = "moq-jwp:object\x01" ‖ envelope` +//! (jwp-receipts §2.1). The proof carries the chaining value of every subtree of `X` +//! that lies wholly at or after `c = 15 + covered_len`, in the order §6.3's procedure +//! consumes them; a verifier computes every subtree before `c` from the bytes it holds +//! and merges up to the root. + +use blake3::hazmat::{merge_subtrees_non_root, merge_subtrees_root, ChainingValue, HasherExt, Mode}; + +use super::head::{covered_len, parse_head, HeadParse}; +use super::{RangeRefusal, DOMAIN_LEN}; +use crate::jwp_hash::DOMAIN_OBJECT; + +/// The file's magic, "WHP1". +pub const MAGIC: &[u8; 4] = b"WHP1"; +// Kept out of the crate's public constants, which the C header generator exports. +const MAX_CV: usize = HeadProof::MAX_CV; +const FIXED_LEN: usize = HeadProof::FIXED_LEN; + +const CHUNK: u64 = 1024; + +/// A parsed head proof. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HeadProof { + pub cid: [u8; 32], + pub envelope_len: u64, + pub covered_len: u64, + pub cvs: Vec<[u8; 32]>, +} + +/// The length of the left subtree of a BLAKE3 subtree of `n` bytes (`n` > 1024): +/// `1024 · 2^⌊log₂((n−1)/1024)⌋`, the largest power-of-two number of chunks that +/// leaves at least one byte to the right. +fn left_len(n: u64) -> u64 { + let q = (n - 1) / CHUNK; + CHUNK << (63 - q.leading_zeros()) +} + +/// 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() +} + +/// The non-root chaining value of the subtree `x[..len]` whose first chunk is at +/// byte offset `start` of the whole input. +fn subtree_cv(x: &[u8], start: u64) -> ChainingValue { + let mut h = blake3::Hasher::new(); + h.set_input_offset(start); + h.update(x); + h.finalize_non_root() +} + +/// §6.3's `cv(start, len)`: the chaining value of the subtree [start, start + len) of +/// `X`, computed from `x_prefix` (`X`'s first `c` bytes) where the subtree lies wholly +/// before `c`, taken from `it` where it lies wholly at or after `c`, and merged from +/// its two children otherwise. +fn node_cv( + x_prefix: &[u8], + start: u64, + len: u64, + c: u64, + it: &mut dyn Iterator, +) -> Result { + 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)) + } else { + let l = left_len(len); + let left = node_cv(x_prefix, start, l, c, it)?; + let right = node_cv(x_prefix, start + l, len - l, c, it)?; + Ok(merge_subtrees_non_root(&left, &right, Mode::Hash)) + } +} + +/// The producer's walk: the chaining values of the subtrees at or after `c`, in the +/// order [`node_cv`] consumes them. +fn collect_cvs(x: &[u8], start: u64, len: u64, c: u64, out: &mut Vec<[u8; 32]>) { + if start >= c { + out.push(subtree_cv(&x[start as usize..(start + len) as usize], start)); + } else if start + len > c { + let l = left_len(len); + collect_cvs(x, start, l, c, out); + collect_cvs(x, start + l, len - l, c, out); + } +} + +impl HeadProof { + /// The most chaining values a proof may carry. + pub const MAX_CV: usize = 64; + /// The fixed part of the file: magic, cid, `envelope_len`, `covered_len`, `n_cv`. + pub const FIXED_LEN: usize = 53; + + /// The head proof of `envelope`, whose head must parse (§5.1 included). + pub fn build(envelope: &[u8]) -> Result { + let envelope_len = envelope.len() as u64; + let head = match parse_head(envelope, envelope_len)? { + HeadParse::Head(h) => h, + HeadParse::Need(n) => { + return Err(RangeRefusal::HeadMalformed(format!("the head needs {n} bytes; the object has {envelope_len}"))) + } + }; + let cid = super::content_hash(envelope); + let covered = covered_len(head.head_len, envelope_len); + let mut cvs = Vec::new(); + if covered < envelope_len { + let mut x = Vec::with_capacity(DOMAIN_OBJECT.len() + envelope.len()); + x.extend_from_slice(DOMAIN_OBJECT); + x.extend_from_slice(envelope); + let (l_total, c) = (x.len() as u64, DOMAIN_LEN + covered); + let l = left_len(l_total); + collect_cvs(&x, 0, l, c, &mut cvs); + collect_cvs(&x, l, l_total - l, c, &mut cvs); + } + Ok(HeadProof { cid, envelope_len, covered_len: covered, cvs }) + } + + /// The `WHP1` file. + pub fn to_bytes(&self) -> Vec { + let mut out = Vec::with_capacity(FIXED_LEN + 32 * self.cvs.len()); + out.extend_from_slice(MAGIC); + out.extend_from_slice(&self.cid); + out.extend_from_slice(&self.envelope_len.to_le_bytes()); + out.extend_from_slice(&self.covered_len.to_le_bytes()); + out.push(self.cvs.len() as u8); + for cv in &self.cvs { + out.extend_from_slice(cv); + } + out + } + + /// Read a `WHP1` file: its magic, its length, `n_cv` at most [`MAX_CV`], and a + /// `covered_len` that is the object's length or ends `X` on a chunk boundary + /// before the object's end (§6.3 check 1, the part the file alone decides). + pub fn from_bytes(b: &[u8]) -> Result { + if b.len() < FIXED_LEN || &b[..4] != MAGIC { + return Err(RangeRefusal::HeadProofMalformed("not a WHP1 file")); + } + let n = b[52] as usize; + if n > MAX_CV { + return Err(RangeRefusal::HeadProofMalformed("more than 64 chaining values")); + } + if b.len() != FIXED_LEN + 32 * n { + return Err(RangeRefusal::HeadProofMalformed("the file is not 53 + 32·n_cv bytes")); + } + let cid: [u8; 32] = b[4..36].try_into().expect("32 bytes"); + let envelope_len = u64::from_le_bytes(b[36..44].try_into().expect("8 bytes")); + let covered = u64::from_le_bytes(b[44..52].try_into().expect("8 bytes")); + let aligned = covered < envelope_len && (DOMAIN_LEN + covered) % CHUNK == 0; + if !(covered == envelope_len || aligned) { + return Err(RangeRefusal::HeadProofMalformed("covered_len neither ends a chunk nor is the object's length")); + } + if covered == envelope_len && n != 0 { + return Err(RangeRefusal::HeadProofMalformed("a proof that covers the whole object carries chaining values")); + } + let cvs = b[FIXED_LEN..].chunks_exact(32).map(|c| c.try_into().expect("32 bytes")).collect(); + Ok(HeadProof { cid, envelope_len, covered_len: covered, cvs }) + } + + /// §6.3: check this proof against the object's `cid` and the bytes `prefix` (at + /// least [0, covered_len) of the object), in order: the proof names this object + /// (`head_proof_malformed`); its chaining values are consumed exactly and the + /// root equals the cid (`head_proof_mismatch`); and its `covered_len` is §2's + /// formula for the head the verified bytes give (`head_proof_malformed`). No byte + /// of `prefix` is parsed before its hash is checked. + pub fn verify(&self, cid: &[u8; 32], prefix: &[u8]) -> Result<(), RangeRefusal> { + if &self.cid != cid { + return Err(RangeRefusal::HeadProofMalformed("the proof names another object")); + } + if (prefix.len() as u64) < self.covered_len { + return Err(RangeRefusal::HeadProofMalformed("fewer bytes are held than the proof covers")); + } + let covered = &prefix[..self.covered_len as usize]; + if self.covered_len == self.envelope_len { + if !self.cvs.is_empty() { + return Err(RangeRefusal::HeadProofMalformed("a proof that covers the whole object carries chaining values")); + } + if &super::content_hash(covered) != cid { + return Err(RangeRefusal::HeadProofMismatch); + } + } else { + let mut x = Vec::with_capacity(DOMAIN_OBJECT.len() + covered.len()); + x.extend_from_slice(DOMAIN_OBJECT); + x.extend_from_slice(covered); + let l_total = DOMAIN_LEN + self.envelope_len; + let c = DOMAIN_LEN + self.covered_len; + if c % CHUNK != 0 || c >= l_total { + return Err(RangeRefusal::HeadProofMalformed("covered_len neither ends a chunk nor is the object's length")); + } + let mut it = self.cvs.iter().copied(); + let l = left_len(l_total); + let left = node_cv(&x, 0, l, c, &mut it)?; + let right = node_cv(&x, l, l_total - l, c, &mut it)?; + if it.next().is_some() || &merge_root(&left, &right) != cid { + return Err(RangeRefusal::HeadProofMismatch); + } + } + // The bytes are the object's: now read the head they hold. + let head = match parse_head(covered, self.envelope_len)? { + HeadParse::Head(h) => h, + HeadParse::Need(_) => return Err(RangeRefusal::HeadProofMalformed("covered_len ends before the head does")), + }; + if covered_len(head.head_len, self.envelope_len) != self.covered_len { + return Err(RangeRefusal::HeadProofMalformed("covered_len is not §2's formula for this head")); + } + Ok(()) + } +} + +#[cfg(test)] +pub(super) fn left_len_for_test(n: u64) -> u64 { + left_len(n) +} diff --git a/wai/wai-rs/src/http_binding/mod.rs b/wai/wai-rs/src/http_binding/mod.rs new file mode 100644 index 000000000..7657875f5 --- /dev/null +++ b/wai/wai-rs/src/http_binding/mod.rs @@ -0,0 +1,201 @@ +//! HTTP delivery (`extensions/http-delivery.md`): an envelope served as one immutable +//! object named by its content hash, its head proved against that hash (`WHP1`), and +//! one `WAI2` component fetched by range and checked against its entry's digest, with +//! the pick equal to whole-envelope selection on every path. +//! +//! - [`head`]: the head of a `WAI1` or `WAI2` (§2, §5.1) and `covered_len`; +//! - [`head_proof`]: the `WHP1` head proof (§6); +//! - [`plan`]: which ranges to request, in which priority class (§7, §10.1); +//! - [`session`]: a sans-IO driver of §7, its response checks (§7.1) and the +//! whole-object fallback; +//! - [`codecs`]: the `codecs` grammar, the union and the floor (§4); +//! - [`origin`]: an origin's responses (§5.3) and byte-range answers. +//! +//! Nothing here performs I/O: the `wai http` commands and the reference origin do. + +pub mod codecs; +pub mod head; +pub mod head_proof; +pub mod origin; +pub mod plan; +pub mod session; + +#[cfg(test)] +mod tests; + +/// The proposed media type of an object (§3). It is not registered. +pub const MEDIA_TYPE: &str = "application/wai"; + +/// jwp-receipts §2.1's content hash: an object's name (§2). One implementation, +/// [`crate::jwp_hash::content_hash`]. +pub use crate::jwp_hash::content_hash; + +/// The smallest `envelope_len` a head proof can leave uncovered bytes in: one BLAKE3 +/// chunk less the content-hash domain (§5.4). Below it a client fetches whole. +pub(crate) const MIN_RANGE_OBJECT: u64 = 1024 - DOMAIN_LEN; + +/// The length of jwp-receipts §2.1's domain prefix, `"moq-jwp:object\x01"`. +pub(crate) const DOMAIN_LEN: u64 = crate::jwp_hash::DOMAIN_OBJECT.len() as u64; + +/// 64 lowercase hexadecimal digits. +pub fn hex(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{b:02x}")).collect() +} + +/// 32 bytes from 64 lowercase hexadecimal digits, or `None`. +pub fn unhex32(s: &str) -> Option<[u8; 32]> { + if s.len() != 64 || !s.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) { + return None; + } + let mut out = [0u8; 32]; + for (i, o) in out.iter_mut().enumerate() { + *o = u8::from_str_radix(&s[2 * i..2 * i + 2], 16).ok()?; + } + Some(out) +} + +/// The last path segment of an object's canonical URI, `.wai` (§5.2). +pub fn object_name(cid: &[u8; 32]) -> String { + format!("{}.wai", hex(cid)) +} + +/// The fragment identifiers of an object's URI (§3.1). Unknown keys are ignored, and +/// so is a value that does not parse. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct Fragment { + pub cid: Option<[u8; 32]>, + pub n: Option, + pub h: Option, + pub c: Option, +} + +/// Split `uri` at its fragment: the URI without it, and the identifiers it carries. +pub fn parse_fragment(uri: &str) -> (String, Fragment) { + let Some((base, frag)) = uri.split_once('#') else { return (uri.to_string(), Fragment::default()) }; + let mut f = Fragment::default(); + for pair in frag.split('&') { + let Some((k, v)) = pair.split_once('=') else { continue }; + match k { + "cid" => f.cid = unhex32(v), + "n" => f.n = v.parse().ok(), + "h" => f.h = v.parse().ok(), + "c" if crate::container::is_component_id(v) => f.c = Some(v.to_string()), + _ => {} + } + } + (base.to_string(), f) +} + +/// An inclusive byte range, as an HTTP `Range` names one (RFC 9110 §14.1.2). +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ByteRange { + pub first: u64, + pub last: u64, +} + +impl ByteRange { + /// The range [first, first + len), or `None` when `len` is 0. + pub fn from_len(first: u64, len: u64) -> Option { + (len > 0).then(|| ByteRange { first, last: first + len - 1 }) + } + /// The `Range` header value, `bytes=first-last`. + pub fn header(&self) -> String { + format!("bytes={}-{}", self.first, self.last) + } + /// The number of bytes. + pub fn len(&self) -> u64 { + self.last - self.first + 1 + } + /// Never empty: a range holds at least one byte. + pub fn is_empty(&self) -> bool { + false + } + /// The end, exclusive. + pub fn end(&self) -> u64 { + self.last + 1 + } +} + +/// Why a client refused bytes, or fetched a whole object instead of ranges +/// (http-delivery §13). [`RangeRefusal::code`] is the reason. +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RangeRefusal { + /// §5.1: the envelope's length is not its fields' sum. + ObjectLengthMismatch { expected: u64, got: u64 }, + /// §7 step 3: the head does not parse. + HeadMalformed(String), + /// §6.3 check 1. + HeadProofMalformed(&'static str), + /// §6.3 check 2. + HeadProofMismatch, + /// §7.1: a status other than `206`, or a `200` that is not the whole object. + HttpStatusUnexpected(u16), + /// §7.1: a content coding other than none. + ContentCodingPresent(String), + /// §7.1: `Content-Range` names another range than the one requested. + ContentRangeMismatch { want: ByteRange, got: String }, + /// §7.1: a complete length, or a `200` body length, other than the object's. + CompleteLengthMismatch { want: u64, got: u64 }, + /// §7 step 5: a component the sink uses states no `sha256`. A reason to fetch the + /// whole object, never a skip. + ComponentDigestAbsent { index: usize }, + /// §7 step 6: a component's bytes do not match its entry's `sha256`. A reason to + /// fetch the whole object, where SPEC §7.1's selection skips the entry. + ComponentDigestMismatch { index: usize }, + /// §7 step 7: the whole envelope does not hash to the cid. + ObjectDigestMismatch, + /// §4: a `codecs` value, or a head against one. + Codecs(codecs::CodecsError), + /// SPEC §7.1 step 5: no entry is picked; each entry with its reason, unchanged. + Selection(Vec<(usize, crate::container::Skip)>), +} + +impl RangeRefusal { + /// The reason code (http-delivery §13; `inert` for a selection that picks nothing, + /// whose per-entry reasons are SPEC §7.1's). + pub fn code(&self) -> &'static str { + match self { + Self::ObjectLengthMismatch { .. } => "object_length_mismatch", + Self::HeadMalformed(_) => "head_malformed", + Self::HeadProofMalformed(_) => "head_proof_malformed", + Self::HeadProofMismatch => "head_proof_mismatch", + Self::HttpStatusUnexpected(_) => "http_status_unexpected", + Self::ContentCodingPresent(_) => "content_coding_present", + Self::ContentRangeMismatch { .. } => "content_range_mismatch", + Self::CompleteLengthMismatch { .. } => "complete_length_mismatch", + Self::ComponentDigestAbsent { .. } => "component_digest_absent", + Self::ComponentDigestMismatch { .. } => "component_digest_mismatch", + Self::ObjectDigestMismatch => "object_digest_mismatch", + Self::Codecs(e) => e.code(), + Self::Selection(_) => "inert", + } + } +} + +impl std::fmt::Display for RangeRefusal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ObjectLengthMismatch { expected, got } => write!(f, "object_length_mismatch: the fields give {expected} bytes, the object has {got}"), + Self::HeadMalformed(why) => write!(f, "head_malformed: {why}"), + Self::HeadProofMalformed(why) => write!(f, "head_proof_malformed: {why}"), + Self::HttpStatusUnexpected(s) => write!(f, "http_status_unexpected: {s}"), + Self::ContentCodingPresent(c) => write!(f, "content_coding_present: {c}"), + Self::ContentRangeMismatch { want, got } => write!(f, "content_range_mismatch: asked {}, got {got:?}", want.header()), + Self::CompleteLengthMismatch { want, got } => write!(f, "complete_length_mismatch: {got}, the object has {want}"), + Self::ComponentDigestAbsent { index } => write!(f, "component_digest_absent: entry {index}"), + Self::ComponentDigestMismatch { index } => write!(f, "component_digest_mismatch: entry {index}"), + Self::Codecs(e) => e.fmt(f), + Self::Selection(skips) => { + f.write_str("inert:")?; + for (i, s) in skips { + write!(f, " {i}={}", s.code())?; + } + Ok(()) + } + other => f.write_str(other.code()), + } + } +} + +impl std::error::Error for RangeRefusal {} diff --git a/wai/wai-rs/src/http_binding/origin.rs b/wai/wai-rs/src/http_binding/origin.rs new file mode 100644 index 000000000..386add743 --- /dev/null +++ b/wai/wai-rs/src/http_binding/origin.rs @@ -0,0 +1,100 @@ +//! An origin's side of http-delivery §5.3 and §6.2: the headers it sends for an +//! object, a head proof and a preflight, and its answer to a `Range` header (RFC 9110 +//! §14). The `wai http origin` command serves files with these. + +use super::{hex, MEDIA_TYPE}; + +/// The `Cache-Control` of an immutable resource (§5.3). +pub const IMMUTABLE: &str = "public, max-age=31536000, immutable, no-transform"; + +/// The headers an origin sends for a `GET` or `HEAD` of an object whose content hash +/// is `cid` (§5.3), with the object's `codecs` value when given. No +/// `Content-Encoding`, no `Vary`. +pub fn object_headers(cid: &[u8; 32], codecs: Option<&str>) -> Vec<(&'static str, String)> { + let content_type = match codecs { + Some(c) => format!("{MEDIA_TYPE}; codecs=\"{c}\""), + None => MEDIA_TYPE.to_string(), + }; + vec![ + ("Content-Type", content_type), + ("Cache-Control", IMMUTABLE.to_string()), + ("ETag", format!("\"{}\"", hex(cid))), + ("Accept-Ranges", "bytes".to_string()), + ("Access-Control-Allow-Origin", "*".to_string()), + ("Access-Control-Expose-Headers", "Content-Range, Content-Length, ETag".to_string()), + ("Timing-Allow-Origin", "*".to_string()), + ] +} + +/// The headers for an immutable sidecar (a head proof, a receipt file, a parameter +/// set) of the given content type (§6.2, §8.1, §8.5). +pub fn immutable_headers(content_type: &str) -> Vec<(&'static str, String)> { + vec![ + ("Content-Type", content_type.to_string()), + ("Cache-Control", IMMUTABLE.to_string()), + ("Access-Control-Allow-Origin", "*".to_string()), + ("Timing-Allow-Origin", "*".to_string()), + ] +} + +/// The headers that answer a CORS preflight for an object (§5.3). +pub fn preflight_headers() -> Vec<(&'static str, String)> { + vec![ + ("Access-Control-Allow-Origin", "*".to_string()), + ("Access-Control-Allow-Methods", "GET, HEAD".to_string()), + ("Access-Control-Allow-Headers", "Range, Priority".to_string()), + ("Access-Control-Max-Age", "86400".to_string()), + ] +} + +/// An answer to a request for a representation of `len` bytes. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Served { + pub status: u16, + /// `Content-Range`, on a `206` or a `416`. + pub content_range: Option, + /// The bytes of the representation the body holds: empty on a `416`. + pub body: std::ops::Range, +} + +/// Answer `range` (a `Range` header value, or none) on a representation of `len` +/// bytes: `200` and the whole without one, with a multi-range request, or with one +/// this does not read (RFC 9110 §14.2 lets a server ignore it); `206` and the bytes +/// for one satisfiable range (a first and last position, an open end, or a suffix); +/// `416` with `bytes */len` for an unsatisfiable one. +pub fn serve(len: u64, range: Option<&str>) -> Served { + let whole = Served { status: 200, content_range: None, body: 0..len }; + let unsatisfiable = Served { status: 416, content_range: Some(format!("bytes */{len}")), body: 0..0 }; + let Some(spec) = range.and_then(|r| r.trim().strip_prefix("bytes=")) else { return whole }; + if spec.contains(',') { + return whole; + } + let Some((a, b)) = spec.trim().split_once('-') else { return whole }; + let (a, b) = (a.trim(), b.trim()); + let (first, last) = match (a.is_empty(), b.is_empty()) { + (true, true) => return whole, + (true, false) => { + let Ok(suffix) = b.parse::() else { return whole }; + if suffix == 0 || len == 0 { + return unsatisfiable; + } + (len.saturating_sub(suffix), len - 1) + } + (false, _) => { + let Ok(first) = a.parse::() else { return whole }; + let last = if b.is_empty() { + u64::MAX + } else { + match b.parse::() { + Ok(l) if l >= first => l, + _ => return whole, + } + }; + if first >= len { + return unsatisfiable; + } + (first, last.min(len - 1)) + } + }; + Served { status: 206, content_range: Some(format!("bytes {first}-{last}/{len}")), body: first..last + 1 } +} diff --git a/wai/wai-rs/src/http_binding/plan.rs b/wai/wai-rs/src/http_binding/plan.rs new file mode 100644 index 000000000..1d2902f7a --- /dev/null +++ b/wai/wai-rs/src/http_binding/plan.rs @@ -0,0 +1,168 @@ +//! Which ranges a client requests (http-delivery §7, §10.1): the head first, then the +//! picked component and what it applies, one request per range and priority class, +//! or the rest of the object when ranges cannot be checked or would cost nearly as +//! much. Deterministic, so the corpus pins its output. + +use super::head::Head; +use super::{ByteRange, Fragment, MIN_RANGE_OBJECT}; + +/// A request's priority class (§10.1): a base never waits on a refinement. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Class { + Base, + Refinement, +} + +impl Class { + /// `base` or `refinement`. + pub fn label(self) -> &'static str { + match self { + Class::Base => "base", + Class::Refinement => "refinement", + } + } +} + +/// A client's reference policy: how much to request first, when ranges are not worth +/// it, and how long it waits for a refinement. None of it is normative (§7: the 16369 +/// bytes and the 90 % are what the reference client uses). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FetchPolicy { + /// The first request's length when no `h` fragment is given: sixteen chunks of + /// `X` less the domain prefix. + pub default_head: u64, + /// Fetch the rest instead when the planned ranges reach this share, in ‰, of the + /// bytes after `covered_len`. + pub full_threshold_permille: u16, + /// The deadline on a refinement request, where the client presents live. + pub refinement_deadline_ms: Option, +} + +impl Default for FetchPolicy { + fn default() -> Self { + FetchPolicy { default_head: 16 * 1024 - super::DOMAIN_LEN, full_threshold_permille: 900, refinement_deadline_ms: None } + } +} + +/// Why a client fetched the rest of the object rather than ranges of it. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum WhyWhole { + /// The object is below one chunk (§5.4): fetched whole from the start. + BelowOneChunk, + /// A `WAI1`: its one component is the rest of the object. + SingleComponent, + /// §7 step 5: a component the sink uses states no `sha256`. + DigestAbsent { index: usize }, + /// §7 step 6: a component did not match its `sha256`. + DigestMismatch { index: usize }, + /// §7: the planned ranges reach the policy's share of the rest. + Threshold, +} + +impl WhyWhole { + /// `below_one_chunk`, `single_component`, `component_digest_absent`, + /// `component_digest_mismatch` or `range_threshold`. + pub fn code(self) -> &'static str { + match self { + WhyWhole::BelowOneChunk => "below_one_chunk", + WhyWhole::SingleComponent => "single_component", + WhyWhole::DigestAbsent { .. } => "component_digest_absent", + WhyWhole::DigestMismatch { .. } => "component_digest_mismatch", + WhyWhole::Threshold => "range_threshold", + } + } +} + +/// The requests for a pick: ranges by class, or the rest of the object and why. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Plan { + pub requests: Vec<(ByteRange, Class)>, + pub whole_rest: Option<(ByteRange, WhyWhole)>, +} + +/// The first request (§7 step 1): bytes [0, E), with `E` the `h` fragment or the +/// policy's default, and never past a known `n`. `None` when `n` is below one chunk +/// (§5.4): the client fetches the object whole. +pub fn plan_head(policy: &FetchPolicy, frag: &Fragment) -> Option { + if frag.n.is_some_and(|n| n < MIN_RANGE_OBJECT) { + return None; + } + let mut e = frag.h.filter(|&h| h > 0).unwrap_or(policy.default_head); + if let Some(n) = frag.n { + e = e.min(n); + } + ByteRange::from_len(0, e) +} + +/// The rest of an object after its covered head, or `None` when the head covers it. +pub fn rest_of(head: &Head) -> Option { + let covered = head.covered_len(); + ByteRange::from_len(covered, head.envelope_len - covered) +} + +/// Subtract the `held` ranges from [first, end). +fn subtract(first: u64, end: u64, held: &[ByteRange]) -> Vec<(u64, u64)> { + let mut out = vec![(first, end)]; + for h in held { + let mut next = Vec::new(); + for (a, b) in out { + let (hs, he) = (h.first, h.end()); + if he <= a || hs >= b { + next.push((a, b)); + continue; + } + if a < hs { + next.push((a, hs)); + } + if he < b { + next.push((he, b)); + } + } + out = next; + } + out +} + +/// The requests for the components in `wanted` (§7 steps 5 and 6), each with its +/// class: the rest of the object when one of them states no digest +/// (`digests_present[i]` false for entry `i`); otherwise each component's range less +/// the `held` ranges, ranges that touch merged within one class only, and the rest of +/// the object instead when the ranges reach the policy's threshold of the bytes after +/// `covered_len`, counting both classes. +pub fn plan_components( + head: &Head, + wanted: &[(usize, Class)], + digests_present: &[bool], + held: &[ByteRange], + policy: &FetchPolicy, +) -> Plan { + let Some(rest) = rest_of(head) else { return Plan { requests: Vec::new(), whole_rest: None } }; + if let Some(&(index, _)) = wanted.iter().find(|(i, _)| !digests_present.get(*i).copied().unwrap_or(false)) { + return Plan { requests: Vec::new(), whole_rest: Some((rest, WhyWhole::DigestAbsent { index })) }; + } + let mut pieces: Vec<(Class, u64, u64)> = Vec::new(); + for &(index, class) in wanted { + let Some(c) = head.component(index) else { continue }; + for (a, b) in subtract(c.start, c.start + c.len, held) { + pieces.push((class, a, b)); + } + } + pieces.sort(); + 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), + _ => merged.push((class, a, b)), + } + } + let total: u64 = merged.iter().map(|(_, a, b)| b - a).sum(); + if total * 1000 >= u64::from(policy.full_threshold_permille) * rest.len() { + return Plan { requests: Vec::new(), whole_rest: Some((rest, WhyWhole::Threshold)) }; + } + let mut requests: Vec<(ByteRange, Class)> = merged + .into_iter() + .filter_map(|(class, a, b)| ByteRange::from_len(a, b - a).map(|r| (r, class))) + .collect(); + requests.sort_by_key(|(r, class)| (*class, r.first)); + Plan { requests, whole_rest: None } +} diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs new file mode 100644 index 000000000..05e6cb08c --- /dev/null +++ b/wai/wai-rs/src/http_binding/session.rs @@ -0,0 +1,663 @@ +//! A sans-IO driver of http-delivery §7: it says which requests to make, checks each +//! response (§7.1), verifies the head against the cid (§6) and each component against +//! its entry's digest, selects from the head as SPEC §7.1 does, and falls back to the +//! whole object, selecting again on it, exactly where §7 says to. The caller moves the +//! bytes; nothing here performs I/O or reads a clock. + +use std::collections::BTreeMap; + +use super::codecs::{check_declared, Element}; +use super::head::{parse_head, Head, HeadParse, Version}; +use super::head_proof::HeadProof; +use super::plan::{plan_components, plan_head, rest_of, Class, FetchPolicy, Plan, WhyWhole}; +use super::{ByteRange, Fragment, RangeRefusal}; +use crate::container::{ + companion_indices, refinement_indices, select_entries, ManifestV2, PinRefusal, Prior, RenditionMeta, Skip, Wai, + WaiMulti, +}; + +/// What a response's headers say, as a client reads them. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ResponseMeta { + pub status: u16, + pub content_range: Option, + pub content_encoding: Option, +} + +/// One request a session wants made. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Want { + /// `GET` the object with `Range: `, at the class's priority. + Range(ByteRange, Class), + /// `GET .wai.hp`. + HeadProof, + /// `GET` the object with no `Range` (an object below one chunk). + Whole, + /// `GET` the object with `Range: bytes=-`, the rest after the covered head + /// (§7 step 7). Its last position is the object's. + Rest(ByteRange), +} + +/// How the bytes a session returns were verified. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum VerifiedBy { + /// The head by its proof (§6), each component by its entry's `sha256`. + HeadProof, + /// The whole envelope against the cid. + WholeObject, +} + +impl VerifiedBy { + /// `head-proof` or `whole-object`. + pub fn label(self) -> &'static str { + match self { + VerifiedBy::HeadProof => "head-proof", + VerifiedBy::WholeObject => "whole-object", + } + } +} + +/// What a session delivers: the picked component, the companions and refinements +/// it applies, verified, and how it got them. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Fetched { + pub index: usize, + pub component: Vec, + pub companions: Vec<(usize, Vec)>, + pub refinements: Vec<(usize, Vec)>, + /// Refinements whose request the client abandoned at its deadline (§10.2). + pub abandoned: Vec, + /// The entries selection skipped before the pick, with their reasons. + pub skipped: Vec<(usize, Skip)>, + pub head: Head, + pub verified_by: VerifiedBy, + pub why_whole: Option, + /// Response body bytes received, the head proof's included. + pub body_bytes: u64, + pub requests: u32, +} + +/// The sink's side of selection: what it advertises, how it checks a pin, which +/// companions and refinements it applies, a deployer policy's order, and the union +/// the variant declared. +pub struct Selector { + pub advertises: Box bool>, + pub verify_pin: Box Result<(), PinRefusal>>, + /// Whether the sink applies a companion or refinement entry. + pub applies: Box bool>, + /// The order in which entries are considered (SPEC §7.1 step 2); `None` is table + /// order. + pub order: Option Vec>>, + /// The variant's union (§4.2), when the client holds one. + pub declared: Option, +} + +impl Selector { + /// A sink that advertises what `advertises` accepts, holds no parameter set, + /// applies every companion and refinement, and keeps table order. + pub fn new(advertises: impl Fn(&str) -> bool + 'static) -> Self { + Selector { + advertises: Box::new(advertises), + verify_pin: Box::new(|cap, _| Err(PinRefusal::NotHeld { capability: cap.to_string() })), + applies: Box::new(|_| true), + order: None, + declared: None, + } + } + + fn order_of(&self, m: &ManifestV2) -> Vec { + match &self.order { + Some(f) => f(m), + None => (0..m.renditions.len()).collect(), + } + } + + /// SPEC §7.1 over `m`, with the digest test `digest_ok`. + fn select(&mut self, m: &ManifestV2, digest_ok: impl Fn(usize, &RenditionMeta) -> bool) + -> Result<(usize, Vec<(usize, Skip)>), Vec<(usize, Skip)>> { + let order = self.order_of(m); + let advertises = &self.advertises; + let verify = &mut self.verify_pin; + let eligible = |_: usize, e: &RenditionMeta| if advertises(&e.capability) { Ok(()) } else { Err(Skip::NotEligible) }; + select_entries(m, &order, eligible, digest_ok, |c: &str, p: &Prior| verify(c, p)).map(|(i, _, s)| (i, s)) + } + + /// The companions and refinements of entry `index` the sink applies, with their + /// classes: an enhancement layer or a refinement is a refinement (§10.1). + fn applied(&self, m: &ManifestV2, index: usize) -> Vec<(usize, Class)> { + let e = &m.renditions; + let mut out: Vec<(usize, Class)> = companion_indices(e, index) + .into_iter() + .filter(|&i| (self.applies)(&e[i])) + .map(|i| (i, if crate::codecs::is_enhancement_layer(&e[i].capability) { Class::Refinement } else { Class::Base })) + .collect(); + out.extend(refinement_indices(e, index).into_iter().filter(|&i| (self.applies)(&e[i])).map(|i| (i, Class::Refinement))); + out + } +} + +/// §7.1: check one response to a request for `req` (`None`: no `Range` was sent) +/// on an object of `envelope_len` bytes. Returns the bytes the body holds. +pub fn check_response(req: Option, r: &ResponseMeta, body_len: u64, envelope_len: u64) + -> Result { + let whole = ByteRange::from_len(0, envelope_len); + let coded = |r: &ResponseMeta| r.content_encoding.as_deref() + .map(str::trim) + .filter(|c| !c.is_empty() && !c.eq_ignore_ascii_case("identity")) + .map(str::to_string); + match r.status { + 206 => { + let Some(want) = req else { return Err(RangeRefusal::HttpStatusUnexpected(206)) }; + if let Some(c) = coded(r) { + return Err(RangeRefusal::ContentCodingPresent(c)); + } + let got = r.content_range.clone().unwrap_or_default(); + let mismatch = || RangeRefusal::ContentRangeMismatch { want, got: got.clone() }; + let (span, complete) = got.trim().strip_prefix("bytes ").and_then(|s| s.split_once('/')).ok_or_else(mismatch)?; + let (a, b) = span.split_once('-').ok_or_else(mismatch)?; + let (a, b): (u64, u64) = (a.parse().map_err(|_| mismatch())?, b.parse().map_err(|_| mismatch())?); + let expect_last = want.last.min(envelope_len.saturating_sub(1)); + if a != want.first || b != expect_last || b < a || body_len != b - a + 1 { + return Err(mismatch()); + } + match complete.parse::() { + Ok(n) if n == envelope_len => Ok(ByteRange { first: a, last: b }), + Ok(n) => Err(RangeRefusal::CompleteLengthMismatch { want: envelope_len, got: n }), + Err(_) => Err(RangeRefusal::CompleteLengthMismatch { want: envelope_len, got: 0 }), + } + } + 200 => { + if let Some(c) = coded(r) { + return Err(RangeRefusal::ContentCodingPresent(c)); + } + if body_len != envelope_len { + return Err(RangeRefusal::CompleteLengthMismatch { want: envelope_len, got: body_len }); + } + whole.ok_or(RangeRefusal::CompleteLengthMismatch { want: envelope_len, got: body_len }) + } + s => Err(RangeRefusal::HttpStatusUnexpected(s)), + } +} + +/// 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)) +} + +enum Stage { + /// Waiting for the head and its proof. + Head, + /// Fetching the planned component ranges. + Components { index: usize, skipped: Vec<(usize, Skip)>, wanted: Vec<(usize, Class)> }, + /// Fetching the rest of the object. + Rest { why: WhyWhole }, + /// Finished, delivered or refused. + Done, +} + +/// A fetch of one object by the procedure of http-delivery §7. +pub struct FetchSession { + cid: [u8; 32], + frag: Fragment, + policy: FetchPolicy, + sel: Selector, + next_id: u32, + queued: Vec<(u32, Want)>, + issued: BTreeMap, + early: Vec<(Want, ResponseMeta, Vec)>, + proof: Option, + held: Vec<(ByteRange, Vec)>, + head: Option, + manifest: Option, + stage: Stage, + abandoned: Vec, + body_bytes: u64, + requests: u32, + result: Option>, +} + +impl FetchSession { + /// A session for the object `cid`, with the hints of its URI's fragment. + pub fn new(cid: [u8; 32], frag: Fragment, policy: FetchPolicy, sel: Selector) -> Self { + let mut s = FetchSession { + cid, frag, policy, sel, + next_id: 0, + queued: Vec::new(), + issued: BTreeMap::new(), + early: Vec::new(), + proof: None, + held: Vec::new(), + head: None, + manifest: None, + stage: Stage::Head, + abandoned: Vec::new(), + body_bytes: 0, + requests: 0, + result: None, + }; + match plan_head(&s.policy, &s.frag) { + Some(r) => { + s.queue(Want::Range(r, Class::Base)); + s.queue(Want::HeadProof); + } + None => { + s.stage = Stage::Rest { why: WhyWhole::BelowOneChunk }; + s.queue(Want::Whole); + } + } + s + } + + fn queue(&mut self, w: Want) { + let id = self.next_id; + self.next_id += 1; + self.queued.push((id, w)); + } + + /// The requests to make now, each with its id; each is returned once. + pub fn wants(&mut self) -> Vec<(u32, Want)> { + let out = std::mem::take(&mut self.queued); + for (id, w) in &out { + self.issued.insert(*id, w.clone()); + self.requests += 1; + } + out + } + + /// The outcome, once there is one: the delivery, or the refusal that ended it. + pub fn result(&self) -> Option<&Result> { + self.result.as_ref() + } + + /// The delivery, once the session has one. + pub fn done(&self) -> Option<&Fetched> { + self.result.as_ref().and_then(|r| r.as_ref().ok()) + } + + /// Whether requests are still outstanding. + pub fn outstanding(&self) -> usize { + self.issued.len() + } + + /// The request `id` will not be answered in time: a refinement's is abandoned + /// (§10.2), and the session finishes without it once the base is verified. A base + /// request is not abandoned: a deadline never applies to one. + pub fn on_deadline(&mut self, id: u32) { + if self.result.is_some() { + return; + } + if let Some(Want::Range(r, Class::Refinement)) = self.issued.get(&id).cloned() { + self.issued.remove(&id); + if let (Stage::Components { wanted, .. }, Some(head)) = (&self.stage, &self.head) { + for &(i, class) in wanted { + let c = head.components[i]; + if class == Class::Refinement && c.start < r.end() && r.first < c.start + c.len && !self.abandoned.contains(&i) { + self.abandoned.push(i); + } + } + } + self.advance(); + } + } + + /// A response to request `id`. A refusal ends the session (§7.1: the bytes are + /// discarded) and is returned. + pub fn on_response(&mut self, id: u32, meta: &ResponseMeta, body: &[u8]) -> Result<(), RangeRefusal> { + if let Some(Err(e)) = &self.result { + return Err(e.clone()); + } + let Some(want) = self.issued.remove(&id) else { return Ok(()) }; + if self.result.is_some() { + return Ok(()); + } + self.body_bytes += body.len() as u64; + let r = self.take(want, meta, body).and_then(|()| { + self.advance(); + Ok(()) + }); + if let Err(e) = &r { + self.finish(Err(e.clone())); + } + r + } + + fn finish(&mut self, r: Result) { + self.stage = Stage::Done; + self.queued.clear(); + self.result = Some(r); + } + + fn envelope_len(&self) -> Option { + self.proof.as_ref().map(|p| p.envelope_len) + } + + fn take(&mut self, want: Want, meta: &ResponseMeta, body: &[u8]) -> Result<(), RangeRefusal> { + match want.clone() { + Want::HeadProof => { + if meta.status != 200 { + return Err(RangeRefusal::HttpStatusUnexpected(meta.status)); + } + if let Some(c) = meta.content_encoding.as_deref().filter(|c| !c.trim().is_empty() && !c.trim().eq_ignore_ascii_case("identity")) { + return Err(RangeRefusal::ContentCodingPresent(c.to_string())); + } + let p = HeadProof::from_bytes(body)?; + if p.cid != self.cid { + return Err(RangeRefusal::HeadProofMalformed("the proof names another object")); + } + self.proof = Some(p); + for (w, m, b) in std::mem::take(&mut self.early) { + self.take(w, &m, &b)?; + } + Ok(()) + } + Want::Whole => { + let n = body.len() as u64; + check_response(None, meta, n, n)?; + if super::content_hash(body) != self.cid { + return Err(RangeRefusal::ObjectDigestMismatch); + } + self.held.push((ByteRange::from_len(0, n).unwrap_or(ByteRange { first: 0, last: 0 }), body.to_vec())); + Ok(()) + } + Want::Range(r, _) | Want::Rest(r) => { + let Some(len) = self.envelope_len() else { + // Checked once the proof gives the object's length (§6.3 check 1). + self.early.push((want, meta.clone(), body.to_vec())); + return Ok(()); + }; + let got = check_response(Some(r), meta, body.len() as u64, len)?; + self.held.push((got, body.to_vec())); + Ok(()) + } + } + } + + /// The bytes [first, end) if they are held contiguously. + fn bytes(&self, first: u64, end: u64) -> Option> { + let mut out = Vec::with_capacity((end - first) as usize); + let mut at = first; + while at < end { + let (r, b) = self.held.iter().filter(|(r, _)| r.first <= at && at < r.end()).max_by_key(|(r, _)| r.end())?; + let take_to = r.end().min(end); + out.extend_from_slice(&b[(at - r.first) as usize..(take_to - r.first) as usize]); + at = take_to; + } + Some(out) + } + + /// The length of the prefix held contiguously from offset 0. + fn prefix_len(&self) -> u64 { + let mut at = 0; + loop { + match self.held.iter().filter(|(r, _)| r.first <= at && at < r.end()).map(|(r, _)| r.end()).max() { + Some(e) => at = e, + None => return at, + } + } + } + + fn range_outstanding(&self) -> bool { + self.issued.values().chain(self.queued.iter().map(|(_, w)| w)).any(|w| matches!(w, Want::Range(..) | Want::Rest(..))) + } + + fn advance(&mut self) { + if self.result.is_some() { + return; + } + let r = match self.stage { + Stage::Head => self.advance_head(), + Stage::Components { .. } => self.advance_components(), + Stage::Rest { .. } => self.advance_rest(), + Stage::Done => Ok(()), + }; + if let Err(e) = r { + self.finish(Err(e)); + } + } + + fn advance_head(&mut self) -> Result<(), RangeRefusal> { + let Some(proof) = self.proof.clone() else { return Ok(()) }; + let have = self.prefix_len(); + if have < proof.covered_len { + if !self.range_outstanding() { + let r = ByteRange::from_len(have, proof.covered_len - have).expect("non-empty"); + self.queue(Want::Range(r, Class::Base)); + } + return Ok(()); + } + let prefix = self.bytes(0, proof.covered_len).expect("held"); + proof.verify(&self.cid, &prefix)?; + let head = match parse_head(&prefix, proof.envelope_len)? { + HeadParse::Head(h) => h, + HeadParse::Need(_) => return Err(RangeRefusal::HeadMalformed("the covered bytes do not hold the head".into())), + }; + if let Some(union) = &self.sel.declared { + check_declared(&head, union).map_err(RangeRefusal::Codecs)?; + } + self.head = Some(head.clone()); + match head.version { + Version::Wai1 => { + // Selection reads the manifest alone: decide before fetching the payload. + let m = head.manifest_v1()?; + let w = Wai::new(m, Vec::new()); + let verify = &mut self.sel.verify_pin; + if let Err(inert) = w.dispatch_verified(|c| (self.sel.advertises)(c), |c, p| verify(c, p)) { + let skip = match inert { + crate::container::Inert::PinRefused { refusal, .. } => Skip::Pin(refusal), + crate::container::Inert::Encrypted { .. } => Skip::Encrypted, + _ => Skip::NotEligible, + }; + return Err(RangeRefusal::Selection(vec![(0, skip)])); + } + self.to_rest(&head, WhyWhole::SingleComponent); + } + Version::Wai2 => { + let m = head.manifest_v2()?; + let (index, skipped) = self.sel.select(&m, |_, _| true).map_err(RangeRefusal::Selection)?; + let mut wanted = vec![(index, Class::Base)]; + wanted.extend(self.sel.applied(&m, index)); + let present: Vec = m.renditions.iter().map(|e| e.sha256.is_some()).collect(); + let held: Vec = self.held.iter().map(|(r, _)| *r).collect(); + let plan: Plan = plan_components(&head, &wanted, &present, &held, &self.policy); + self.manifest = Some(m); + match plan.whole_rest { + Some((_, why)) => self.to_rest(&head, why), + None => { + for (r, class) in plan.requests { + self.queue(Want::Range(r, class)); + } + self.stage = Stage::Components { index, skipped, wanted }; + } + } + } + } + self.advance_after_plan() + } + + fn advance_after_plan(&mut self) -> Result<(), RangeRefusal> { + match self.stage { + Stage::Components { .. } => self.advance_components(), + Stage::Rest { .. } => self.advance_rest(), + _ => Ok(()), + } + } + + fn to_rest(&mut self, head: &Head, why: WhyWhole) { + self.stage = Stage::Rest { why }; + if let Some(r) = rest_of(head) { + if self.bytes(r.first, r.end()).is_none() { + self.queue(Want::Rest(r)); + } + } + } + + fn advance_components(&mut self) -> Result<(), RangeRefusal> { + let (Stage::Components { index, skipped, wanted }, Some(head), Some(m)) = (&self.stage, &self.head, &self.manifest) else { + return Ok(()); + }; + let (index, skipped, wanted, head, m) = (*index, skipped.clone(), wanted.clone(), head.clone(), m.clone()); + let mut parts: BTreeMap> = BTreeMap::new(); + let mut pending = false; + for &(i, _) in &wanted { + if self.abandoned.contains(&i) { + continue; + } + let c = head.components[i]; + match self.bytes(c.start, c.start + c.len) { + Some(b) => { + if !check_component(&m, i, &b) { + // §7 step 6: discard, and decide on the whole envelope. + self.issued.retain(|_, w| !matches!(w, Want::Range(..))); + self.queued.clear(); + self.to_rest(&head, WhyWhole::DigestMismatch { index: i }); + return self.advance_rest(); + } + parts.insert(i, b); + } + None => pending = true, + } + } + if pending { + return Ok(()); + } + let component = parts.remove(&index).expect("the pick is held"); + let (mut companions, mut refinements) = (Vec::new(), Vec::new()); + for &(i, _) in &wanted[1..] { + if let Some(b) = parts.remove(&i) { + if m.renditions[i].is_companion() { + companions.push((i, b)); + } else { + refinements.push((i, b)); + } + } + } + let fetched = Fetched { + index, component, companions, refinements, + abandoned: self.abandoned.clone(), + skipped, head, + verified_by: VerifiedBy::HeadProof, + why_whole: None, + body_bytes: self.body_bytes, + requests: self.requests, + }; + self.finish(Ok(fetched)); + Ok(()) + } + + fn advance_rest(&mut self) -> Result<(), RangeRefusal> { + let Stage::Rest { why } = self.stage else { return Ok(()) }; + let n = match (&self.proof, why) { + (Some(p), _) => p.envelope_len, + (None, WhyWhole::BelowOneChunk) => match self.held.first() { + Some((r, _)) => r.end(), + None => return Ok(()), + }, + (None, _) => return Ok(()), + }; + let Some(envelope) = self.bytes(0, n) else { return Ok(()) }; + if super::content_hash(&envelope) != self.cid { + return Err(RangeRefusal::ObjectDigestMismatch); + } + let head = match parse_head(&envelope, n)? { + HeadParse::Head(h) => h, + HeadParse::Need(_) => return Err(RangeRefusal::HeadMalformed("the object is shorter than its head".into())), + }; + if let Some(union) = &self.sel.declared { + check_declared(&head, union).map_err(RangeRefusal::Codecs)?; + } + let fetched = match head.version { + Version::Wai1 => { + let w = Wai::from_bytes(&envelope).map_err(|e| RangeRefusal::HeadMalformed(e.to_string()))?; + let verify = &mut self.sel.verify_pin; + if let Err(inert) = w.dispatch_verified(|c| (self.sel.advertises)(c), |c, p| verify(c, p)) { + let skip = match inert { + crate::container::Inert::PinRefused { refusal, .. } => Skip::Pin(refusal), + crate::container::Inert::Encrypted { .. } => Skip::Encrypted, + _ => Skip::NotEligible, + }; + return Err(RangeRefusal::Selection(vec![(0, skip)])); + } + Fetched { + index: 0, component: w.payload.clone(), companions: Vec::new(), refinements: Vec::new(), + abandoned: Vec::new(), skipped: Vec::new(), head, + verified_by: VerifiedBy::WholeObject, why_whole: Some(why), + body_bytes: self.body_bytes, requests: self.requests, + } + } + Version::Wai2 => { + let w = WaiMulti::from_bytes(&envelope).map_err(|e| RangeRefusal::HeadMalformed(e.to_string()))?; + let payloads = w.renditions.clone(); + let digest_ok = |i: usize, e: &RenditionMeta| payloads.get(i).is_some_and(|p| e.payload_digest_matches(p)); + let (index, skipped) = self.sel.select(&w.manifest, digest_ok).map_err(RangeRefusal::Selection)?; + let applied = self.sel.applied(&w.manifest, index); + let (mut companions, mut refinements) = (Vec::new(), Vec::new()); + for (i, _) in applied { + // A companion or refinement that does not match is not applied (SPEC §7.1). + if !w.manifest.renditions[i].payload_digest_matches(&w.renditions[i]) { + continue; + } + if w.manifest.renditions[i].is_companion() { + companions.push((i, w.renditions[i].clone())); + } else { + refinements.push((i, w.renditions[i].clone())); + } + } + Fetched { + index, component: w.renditions[index].clone(), companions, refinements, + abandoned: Vec::new(), skipped, head, + verified_by: VerifiedBy::WholeObject, why_whole: Some(why), + body_bytes: self.body_bytes, requests: self.requests, + } + } + }; + self.finish(Ok(fetched)); + Ok(()) + } +} + +/// Run `session` to its end: `answer` gives each request's response, or `None` for +/// one that never arrives (which the session abandons, if it is a refinement's, at +/// its deadline). Returns the outcome. +pub fn drive( + session: &mut FetchSession, + mut answer: impl FnMut(&Want) -> Option<(ResponseMeta, Vec)>, +) -> Result { + loop { + if let Some(r) = session.result() { + return r.clone(); + } + let wants = session.wants(); + if wants.is_empty() { + let stuck: Vec = session.issued.keys().copied().collect(); + if stuck.is_empty() { + return Err(RangeRefusal::HeadMalformed("the session wants nothing and has no outcome".into())); + } + for id in stuck { + session.on_deadline(id); + } + if session.result().is_none() && session.wants_pending() == 0 && session.outstanding() > 0 { + return Err(RangeRefusal::HeadMalformed("a base request was never answered".into())); + } + continue; + } + for (id, w) in wants { + match answer(&w) { + Some((meta, body)) => { + if let Err(e) = session.on_response(id, &meta, &body) { + return Err(e); + } + } + None => { + // Left outstanding: a refinement is abandoned when the loop finds + // nothing more to do; a base request is never answered. + } + } + } + } +} + +impl FetchSession { + fn wants_pending(&self) -> usize { + self.queued.len() + } +} diff --git a/wai/wai-rs/src/http_binding/tests.rs b/wai/wai-rs/src/http_binding/tests.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/wai/wai-rs/src/http_binding/tests.rs @@ -0,0 +1 @@ + diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 00d5c98e9..b4b283397 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -309,6 +309,10 @@ mod container_explode; // WIR1, its predictors, the per-unit application algorithm and the live schedule. #[cfg(feature = "staged")] pub mod staged; +// HTTP delivery (extensions/http-delivery.md): the head proof, verified component range +// fetch, the codecs union and floor, and an origin's responses. +#[cfg(feature = "http")] +pub mod http_binding; // Typed receipt classes (extensions/jwp-receipts.md §2.4.3–§2.4.7, §2.6, §3.1–§3.2): // energy, lawful-basis, log-inclusion, soft-binding and binding-recovery statements -- 2.54.0 (Apple Git-157) From 3467a1e85067af673b7d6cdd696cd00216517700 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 5 Oct 2026 07:30:05 -0400 Subject: [PATCH 03/31] =?UTF-8?q?wai-rs:=20http=5Fbinding=20unit=20tests;?= =?UTF-8?q?=20=C2=A76.3=20checks=20the=20head's=20lengths=20after=20the=20?= =?UTF-8?q?root?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Appendix D's numbers (head 594, cid eecd2940…, covered 1009, six chaining values, 245 B proof) reproduced; the head proof against whole-input hashing for 2 KiB to 64 MiB objects and every tamper; left_len against BLAKE3's own rule; plans; response checks; one pick on both paths over eight sinks and three orders; producer-bad and transport-bad digests; deadlines; codecs grammar, union, floor, declared and support; the origin's Range answers. The design's covered_len example (1009 → 2033) is off by one under its own formula: 15 + 1009 is exactly one chunk, so 1009 → 1009 and 1010 → 2033. The root does not encode the object's length, so a wrong envelope_len that keeps the tree's shape passes the root: §6.3 now checks, after the root and before anything else is read, that envelope_len is the length the verified head gives (head_proof_malformed). §7.2 says what a client reports when the head alone shows nothing can be picked. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 18 +- wai/wai-rs/src/http_binding/head_proof.rs | 20 +- wai/wai-rs/src/http_binding/tests.rs | 622 ++++++++++++++++++++++ 3 files changed, 648 insertions(+), 12 deletions(-) diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index 4b16ce044..a921cf538 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -266,13 +266,19 @@ the ROOT flag. `c` is a multiple of 1024 whenever `covered_len < envelope_len`, 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 +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` equals §2's formula (`head_proof_malformed`); + 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`). + (`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. -A producer writes the chaining values in the order the procedure consumes them. +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 @@ -331,7 +337,9 @@ On bytes that verify, §7 picks the same entry that [SPEC](../SPEC.md) §7.1 pic 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. +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 diff --git a/wai/wai-rs/src/http_binding/head_proof.rs b/wai/wai-rs/src/http_binding/head_proof.rs index cfe5aac70..40b6f1fab 100644 --- a/wai/wai-rs/src/http_binding/head_proof.rs +++ b/wai/wai-rs/src/http_binding/head_proof.rs @@ -162,9 +162,10 @@ impl HeadProof { /// §6.3: check this proof against the object's `cid` and the bytes `prefix` (at /// least [0, covered_len) of the object), in order: the proof names this object /// (`head_proof_malformed`); its chaining values are consumed exactly and the - /// root equals the cid (`head_proof_mismatch`); and its `covered_len` is §2's - /// formula for the head the verified bytes give (`head_proof_malformed`). No byte - /// of `prefix` is parsed before its hash is checked. + /// root equals the cid (`head_proof_mismatch`); and its `envelope_len` is the + /// length the verified head gives and its `covered_len` §2's formula for that head + /// (`head_proof_malformed`). No byte of `prefix` is parsed before its hash is + /// checked. pub fn verify(&self, cid: &[u8; 32], prefix: &[u8]) -> Result<(), RangeRefusal> { if &self.cid != cid { return Err(RangeRefusal::HeadProofMalformed("the proof names another object")); @@ -197,10 +198,15 @@ impl HeadProof { return Err(RangeRefusal::HeadProofMismatch); } } - // The bytes are the object's: now read the head they hold. - let head = match parse_head(covered, self.envelope_len)? { - HeadParse::Head(h) => h, - HeadParse::Need(_) => return Err(RangeRefusal::HeadProofMalformed("covered_len ends before the head does")), + // The bytes are the object's: now read the head they hold, whose fields give the + // object's length (§5.1). + let head = match parse_head(covered, self.envelope_len) { + Ok(HeadParse::Head(h)) => h, + Ok(HeadParse::Need(_)) => return Err(RangeRefusal::HeadProofMalformed("covered_len ends before the head does")), + Err(RangeRefusal::ObjectLengthMismatch { .. }) => { + return Err(RangeRefusal::HeadProofMalformed("envelope_len is not the length the head gives")) + } + Err(e) => return Err(e), }; if covered_len(head.head_len, self.envelope_len) != self.covered_len { return Err(RangeRefusal::HeadProofMalformed("covered_len is not §2's formula for this head")); diff --git a/wai/wai-rs/src/http_binding/tests.rs b/wai/wai-rs/src/http_binding/tests.rs index 8b1378917..1a4997c3c 100644 --- a/wai/wai-rs/src/http_binding/tests.rs +++ b/wai/wai-rs/src/http_binding/tests.rs @@ -1 +1,623 @@ +//! Unit tests of the HTTP binding: Appendix D's numbers, the head proof against +//! whole-input hashing, plans, response checks, one pick on every path, the codecs +//! grammar and the origin's Range answers. +use super::codecs::{self, Element, Prefix}; +use super::head::{covered_len, parse_head, HeadParse, Version}; +use super::head_proof::HeadProof; +use super::origin; +use super::plan::{plan_components, plan_head, Class, FetchPolicy, WhyWhole}; +use super::session::{check_response, drive, FetchSession, ResponseMeta, Selector, VerifiedBy, Want}; +use super::*; +use crate::container::{Rendition, RenditionMeta, Skip, WaiMulti}; + +fn pattern(len: usize, a: usize, b: usize) -> Vec { + (0..len).map(|i| ((a * i + b) % 256) as u8).collect() +} + +/// Appendix D's envelope: `low` and `high` AV1 renditions and an HDR10+ companion +/// bound to `high` by id. +fn appendix_d() -> Vec { + WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![ + Rendition::media("wai.video.av1", "av1", pattern(3000, 7, 3)).with_id("low").with_payload_digest(), + Rendition::media("wai.video.av1", "av1", pattern(50_000, 13, 5)).with_id("high").with_payload_digest(), + Rendition::companion("wai.meta.hdr10plus", pattern(200, 31, 1), "wai.video.av1").with_id("hdr") + .with_payload_digest().with_companion_of_id("high"), + ]).to_bytes().unwrap() +} + +fn head_of(env: &[u8]) -> head::Head { + match parse_head(env, env.len() as u64).unwrap() { + HeadParse::Head(h) => h, + HeadParse::Need(n) => panic!("needs {n}"), + } +} + +/// An origin answering `want` from `object` and its proof, after `tamper` changes the +/// served bytes, and `gzip` labels them coded. +struct Served { + object: Vec, + proof: Vec, + gzip: bool, +} + +impl Served { + fn new(object: &[u8]) -> Self { + Served { object: object.to_vec(), proof: HeadProof::build(object).unwrap().to_bytes(), gzip: false } + } + fn answer(&self, w: &Want) -> Option<(ResponseMeta, Vec)> { + let (range, body_of): (Option, &[u8]) = match w { + Want::HeadProof => return Some((ResponseMeta { status: 200, ..Default::default() }, self.proof.clone())), + Want::Whole => (None, &self.object), + Want::Range(r, _) => (Some(r.header()), &self.object), + Want::Rest(r) => (Some(format!("bytes={}-", r.first)), &self.object), + }; + let s = origin::serve(body_of.len() as u64, range.as_deref()); + let meta = ResponseMeta { + status: s.status, + content_range: s.content_range, + content_encoding: self.gzip.then(|| "gzip".to_string()), + }; + Some((meta, body_of[s.body.start as usize..s.body.end as usize].to_vec())) + } +} + +fn fetch(object: &[u8], frag: Fragment, sel: Selector) -> Result { + let served = Served::new(object); + let mut s = FetchSession::new(content_hash(object), frag, FetchPolicy::default(), sel); + drive(&mut s, |w| served.answer(w)) +} + +#[test] +fn content_hash_is_reexport() { + let ours: fn(&[u8]) -> [u8; 32] = content_hash; + let theirs: fn(&[u8]) -> [u8; 32] = crate::jwp_hash::content_hash; + assert!(std::ptr::fn_addr_eq(ours, theirs)); +} + +#[test] +fn head_parse_wai1_wai2() { + let d = appendix_d(); + let h = head_of(&d); + assert_eq!((h.version, h.head_len, h.envelope_len), (Version::Wai2, 594, 53_794)); + assert_eq!(h.manifest, 8..568); + let ranges: Vec<(u64, u64)> = h.components.iter().map(|c| (c.start, c.start + c.len - 1)).collect(); + assert_eq!(ranges, [(594, 3593), (3594, 53593), (53594, 53793)]); + assert_eq!(hex(&d[568..594]), "030000000000b80b0000b80b000050c3000008cf0000c8000000"); + let stream = std::fs::read(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/vectors/int_mlicpp/stream.wai")).unwrap(); + let h = head_of(&stream); + assert_eq!((h.version, h.envelope_len), (Version::Wai1, 859)); + assert_eq!(h.components.len(), 1); + assert_eq!(h.components[0].start + h.components[0].len, 859); +} + +#[test] +fn head_parse_needs_more() { + let d = appendix_d(); + // The count of renditions follows the manifest, so the head's end is known in two steps. + assert_eq!(parse_head(&d[..300], 53_794).unwrap(), HeadParse::Need(570)); + assert_eq!(parse_head(&d[..580], 53_794).unwrap(), HeadParse::Need(594)); + assert_eq!(parse_head(&d[..5], 53_794).unwrap(), HeadParse::Need(8)); + assert!(matches!(parse_head(b"WAI3\0\0\0\0", 8), Err(RangeRefusal::HeadMalformed(_)))); +} + +#[test] +fn object_length_rule() { + let d = appendix_d(); + let mut long = d.clone(); + long.push(0); + let e = parse_head(&long, long.len() as u64).unwrap_err(); + assert_eq!(e.code(), "object_length_mismatch"); + let e = parse_head(&d[..d.len() - 1], d.len() as u64 - 1).unwrap_err(); + assert_eq!(e.code(), "object_length_mismatch"); + assert!(HeadProof::build(&long).is_err()); +} + +#[test] +fn covered_len_formula() { + assert_eq!(covered_len(594, 53_794), 1009); + // 15 + 1009 is one chunk exactly; one byte more needs a second. + assert_eq!(covered_len(1009, 1 << 20), 1009); + assert_eq!(covered_len(1010, 1 << 20), 2033); + assert_eq!(covered_len(12, 1 << 20), 1009); + assert_eq!(covered_len(594, 900), 900); +} + +/// The left-subtree rule equals BLAKE3's own. +#[test] +fn left_len_is_blake3s() { + for n in (1025u64..70_000).step_by(97).chain([2048, 2049, 4096, 4097, 1 << 30, (1 << 30) + 1, u64::MAX / 2]) { + assert_eq!(head_proof::left_len_for_test(n), blake3::hazmat::left_subtree_len(n), "n = {n}"); + } +} + +/// A `WAI2` of `total` bytes with a small head: a manifest of one entry and padding. +fn sized(total: usize) -> Vec { + let probe = WaiMulti::new("binary", "replicate", serde_json::Value::Null, + vec![Rendition::media("wai.text.zstd", "zstd", Vec::new())]).to_bytes().unwrap(); + let payload = pattern(total - probe.len(), 11, 7); + let env = WaiMulti::new("binary", "replicate", serde_json::Value::Null, + vec![Rendition::media("wai.text.zstd", "zstd", payload)]).to_bytes().unwrap(); + assert_eq!(env.len(), total); + env +} + +#[test] +fn head_proof_round_trip_and_tamper() { + for total in [2048usize, 5000, 1 << 20, 64 << 20] { + let env = sized(total); + let cid = content_hash(&env); + let p = HeadProof::build(&env).unwrap(); + let bytes = p.to_bytes(); + assert_eq!(bytes.len(), HeadProof::FIXED_LEN + 32 * p.cvs.len()); + let back = HeadProof::from_bytes(&bytes).unwrap(); + assert_eq!(back, p); + let prefix = &env[..p.covered_len as usize]; + back.verify(&cid, prefix).unwrap(); + + // A flipped covered byte that leaves the head's length alone. + let mut bad = prefix.to_vec(); + let last = bad.len() - 1; + bad[last] ^= 1; + assert_eq!(back.verify(&cid, &bad).unwrap_err(), RangeRefusal::HeadProofMismatch, "{total}"); + // A flipped, a dropped and an extra chaining value. + let mut q = back.clone(); + q.cvs[0][0] ^= 1; + assert_eq!(q.verify(&cid, prefix).unwrap_err(), RangeRefusal::HeadProofMismatch); + let mut q = back.clone(); + q.cvs.pop(); + assert_eq!(q.verify(&cid, prefix).unwrap_err(), RangeRefusal::HeadProofMismatch); + let mut q = back.clone(); + q.cvs.push([0; 32]); + assert_eq!(q.verify(&cid, prefix).unwrap_err(), RangeRefusal::HeadProofMismatch); + // A wrong envelope_len. Where the tree changes shape the root does not match; + // where it keeps its shape the root does, and the verified head's lengths refuse it. + for delta in [1u64, 1024, 4096] { + let mut q = back.clone(); + q.envelope_len += delta; + let code = q.verify(&cid, prefix).unwrap_err().code(); + assert!(code == "head_proof_mismatch" || code == "head_proof_malformed", "{total} +{delta}: {code}"); + } + // A covered_len that is not chunk-aligned, and one aligned but not the formula's. + let mut q = back.clone(); + q.covered_len += 1; + assert_eq!(q.verify(&cid, &env).unwrap_err().code(), "head_proof_malformed"); + let mut f = bytes.clone(); + f[44..52].copy_from_slice(&(p.covered_len + 1).to_le_bytes()); + assert_eq!(HeadProof::from_bytes(&f).unwrap_err().code(), "head_proof_malformed"); + let aligned = HeadProof { covered_len: p.covered_len + 1024, cvs: Vec::new(), ..p.clone() }; + let rebuilt = rebuild_cvs(&env, aligned.covered_len); + let wider = HeadProof { cvs: rebuilt, ..aligned }; + if wider.covered_len < wider.envelope_len { + // The root still matches (the covered bytes are the object's), and check 3 refuses. + assert_eq!(wider.verify(&cid, &env).unwrap_err().code(), "head_proof_malformed", "{total}"); + } + // Another object's cid. + assert_eq!(back.verify(&[0; 32], prefix).unwrap_err().code(), "head_proof_malformed"); + } +} + +/// The chaining values of a proof covering `covered` bytes, built by the same walk +/// a producer makes but from an arbitrary aligned `covered`. +fn rebuild_cvs(env: &[u8], covered: u64) -> Vec<[u8; 32]> { + use blake3::hazmat::HasherExt; + let mut x = crate::jwp_hash::DOMAIN_OBJECT.to_vec(); + x.extend_from_slice(env); + fn walk(x: &[u8], start: u64, len: u64, c: u64, out: &mut Vec<[u8; 32]>) { + if start >= c { + let mut h = blake3::Hasher::new(); + h.set_input_offset(start); + h.update(&x[start as usize..(start + len) as usize]); + out.push(h.finalize_non_root()); + } else if start + len > c { + let l = blake3::hazmat::left_subtree_len(len); + walk(x, start, l, c, out); + walk(x, start + l, len - l, c, out); + } + } + let (n, c) = (x.len() as u64, DOMAIN_LEN + covered); + let l = blake3::hazmat::left_subtree_len(n); + let mut out = Vec::new(); + walk(&x, 0, l, c, &mut out); + walk(&x, l, n - l, c, &mut out); + out +} + +#[test] +fn head_proof_matches_prototype() { + let d = appendix_d(); + assert_eq!(d.len(), 53_794); + let cid = content_hash(&d); + assert_eq!(hex(&cid), "eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228"); + let p = HeadProof::build(&d).unwrap(); + assert_eq!((p.covered_len, p.cvs.len(), p.to_bytes().len()), (1009, 6, 245)); + p.verify(&cid, &d[..1009]).unwrap(); +} + +#[test] +fn whole_object_proof() { + let small = sized(900); + let cid = content_hash(&small); + let p = HeadProof::build(&small).unwrap(); + assert_eq!((p.covered_len, p.cvs.len()), (900, 0)); + p.verify(&cid, &small).unwrap(); + let mut bad = small.clone(); + let last = bad.len() - 1; + bad[last] ^= 1; + assert_eq!(p.verify(&cid, &bad).unwrap_err().code(), "head_proof_mismatch"); +} + +#[test] +fn plan_appendix_d() { + let d = appendix_d(); + let h = head_of(&d); + let policy = FetchPolicy::default(); + assert_eq!(plan_head(&policy, &Fragment { h: Some(1009), ..Default::default() }), Some(ByteRange { first: 0, last: 1008 })); + assert_eq!(plan_head(&policy, &Fragment::default()), Some(ByteRange { first: 0, last: 16368 })); + assert_eq!(plan_head(&policy, &Fragment { n: Some(400), ..Default::default() }), None); + let held = [ByteRange { first: 0, last: 1008 }]; + let all = [true, true, true]; + // Plan 1: low. + let p = plan_components(&h, &[(0, Class::Base)], &all, &held, &policy); + assert_eq!(p.requests, vec![(ByteRange { first: 1009, last: 3593 }, Class::Base)]); + assert_eq!(p.whole_rest, None); + // Plan 2: high with hdr, 951 ‰ of the rest: the rest. + let p = plan_components(&h, &[(1, Class::Base), (2, Class::Base)], &all, &held, &policy); + assert_eq!(p.whole_rest, Some((ByteRange { first: 1009, last: 53793 }, WhyWhole::Threshold))); + assert!(p.requests.is_empty()); +} + +#[test] +fn plan_classes_never_merge() { + let d = appendix_d(); + let h = head_of(&d); + let held = [ByteRange { first: 0, last: 1008 }]; + let all = [true, true, true]; + let policy = FetchPolicy { full_threshold_permille: 1000, ..Default::default() }; + // Adjacent ranges: low (base) and high (refinement here) stay two requests. + let p = plan_components(&h, &[(0, Class::Base), (1, Class::Refinement)], &all, &held, &policy); + assert_eq!(p.requests, vec![ + (ByteRange { first: 1009, last: 3593 }, Class::Base), + (ByteRange { first: 3594, last: 53593 }, Class::Refinement), + ]); + // In one class they merge. + let p = plan_components(&h, &[(0, Class::Base), (1, Class::Base)], &all, &held, &policy); + assert_eq!(p.requests, vec![(ByteRange { first: 1009, last: 53593 }, Class::Base)]); +} + +#[test] +fn plan_no_digest_goes_whole() { + let d = appendix_d(); + let h = head_of(&d); + let held = [ByteRange { first: 0, last: 1008 }]; + let p = plan_components(&h, &[(0, Class::Base)], &[false, true, true], &held, &FetchPolicy::default()); + assert_eq!(p.whole_rest, Some((ByteRange { first: 1009, last: 53793 }, WhyWhole::DigestAbsent { index: 0 }))); +} + +#[test] +fn check_response_cases() { + let r = ByteRange { first: 1009, last: 3593 }; + let ok = ResponseMeta { status: 206, content_range: Some("bytes 1009-3593/53794".into()), content_encoding: None }; + assert_eq!(check_response(Some(r), &ok, 2585, 53_794), Ok(r)); + let code = |m: &ResponseMeta, n: u64| check_response(Some(r), m, n, 53_794).unwrap_err().code(); + assert_eq!(code(&ResponseMeta { content_range: Some("bytes 1009-3592/53794".into()), ..ok.clone() }, 2584), "content_range_mismatch"); + assert_eq!(code(&ResponseMeta { content_encoding: Some("gzip".into()), ..ok.clone() }, 2585), "content_coding_present"); + assert_eq!(code(&ResponseMeta { content_range: Some("bytes 1009-3593/53795".into()), ..ok.clone() }, 2585), "complete_length_mismatch"); + assert_eq!(code(&ResponseMeta { content_range: Some("bytes 1009-3593/*".into()), ..ok.clone() }, 2585), "complete_length_mismatch"); + assert_eq!(code(&ResponseMeta { status: 304, ..Default::default() }, 0), "http_status_unexpected"); + assert_eq!(code(&ResponseMeta { status: 200, ..Default::default() }, 1000), "complete_length_mismatch"); + assert_eq!(check_response(Some(r), &ResponseMeta { status: 200, ..Default::default() }, 53_794, 53_794).unwrap().len(), 53_794); + // A body shorter than its Content-Range claims. + assert_eq!(code(&ok, 2000), "content_range_mismatch"); + // A head request past the end, answered to the end. + let head = ByteRange { first: 0, last: 16368 }; + let m = ResponseMeta { status: 206, content_range: Some("bytes 0-899/900".into()), content_encoding: None }; + assert_eq!(check_response(Some(head), &m, 900, 900), Ok(ByteRange { first: 0, last: 899 })); + // `identity` is no coding. + assert!(check_response(Some(r), &ResponseMeta { content_encoding: Some("identity".into()), ..ok }, 2585, 53_794).is_ok()); +} + +/// The sink of a test descriptor: what it advertises, the sets it holds by digest, +/// and the capabilities it applies with a rendition. +fn sink(adv: &'static [&'static str], applies: &'static [&'static str], order: Option>) -> Selector { + let mut s = Selector::new(move |c| adv.contains(&c)); + s.applies = Box::new(move |e: &RenditionMeta| applies.contains(&e.capability.as_str())); + if let Some(o) = order { + s.order = Some(Box::new(move |_| o.clone())); + } + s +} + +/// SPEC §7.1 on the whole envelope, in `order`. +fn whole_pick(env: &[u8], adv: &[&str], order: &[usize]) -> Result> { + let w = WaiMulti::from_bytes(env).unwrap(); + let eligible = |_: usize, e: &RenditionMeta| if adv.contains(&e.capability.as_str()) { Ok(()) } else { Err(Skip::NotEligible) }; + let digest_ok = |i: usize, e: &RenditionMeta| e.payload_digest_matches(&w.renditions[i]); + crate::container::select_entries(&w.manifest, order, eligible, digest_ok, + |c: &str, _: &crate::container::Prior| Err::<(), _>(crate::container::PinRefusal::NotHeld { capability: c.into() })) + .map(|(i, _, _)| i) +} + +/// Envelopes for the both-paths test: Appendix D, one whose first entry lacks a +/// digest, one whose first digest is wrong, and one whose companion's is. +fn both_paths_envelopes() -> Vec> { + let mut v = vec![appendix_d()]; + v.push(WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![ + Rendition::media("wai.video.av1", "av1", pattern(3000, 7, 3)).with_id("a"), + Rendition::media("wai.video.vp9", "vp9", pattern(4000, 5, 1)).with_id("b").with_payload_digest(), + ]).to_bytes().unwrap()); + let mut bad = Rendition::media("wai.video.av1", "av1", pattern(3000, 7, 3)).with_id("a").with_payload_digest(); + bad.meta.sha256 = Some(crate::pin::sha256_hex(b"not these bytes")); + v.push(WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![ + bad, + Rendition::media("wai.video.vp9", "vp9", pattern(4000, 5, 1)).with_id("b").with_payload_digest(), + ]).to_bytes().unwrap()); + let mut hdr = Rendition::companion("wai.meta.hdr10plus", pattern(200, 31, 1), "wai.video.av1").with_id("hdr").with_payload_digest(); + hdr.meta.sha256 = Some(crate::pin::sha256_hex(b"another descriptor")); + v.push(WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![ + Rendition::media("wai.video.av1", "av1", pattern(9000, 3, 9)).with_id("a").with_payload_digest(), + hdr, + ]).to_bytes().unwrap()); + v +} + +#[test] +fn same_pick_both_paths() { + const ADV: [&[&str]; 8] = [ + &["wai.video.av1"], &["wai.video.vp9"], &["wai.video.av1", "wai.video.vp9"], &[], + &["wai.video.av1", "wai.meta.hdr10plus"], &["wai.image.png"], &["wai.video.vp9", "wai.video.av1"], &["wai.video.int_hyper"], + ]; + for env in both_paths_envelopes() { + let n = WaiMulti::from_bytes(&env).unwrap().manifest.renditions.len(); + let orders: Vec> = vec![(0..n).collect(), (0..n).rev().collect(), (1..n).chain([0]).collect()]; + for adv in ADV { + for order in &orders { + let want = whole_pick(&env, adv, order); + for frag in [Fragment::default(), Fragment { h: Some(1009), n: Some(env.len() as u64), ..Default::default() }] { + let got = fetch(&env, frag, sink(adv, &["wai.meta.hdr10plus"], Some(order.clone()))); + match (&want, &got) { + (Ok(i), Ok(f)) => assert_eq!(*i, f.index, "{adv:?} {order:?}"), + (Err(_), Err(RangeRefusal::Selection(_))) => {} + _ => panic!("paths differ: whole {want:?}, range {got:?} ({adv:?} {order:?})"), + } + } + } + } + } +} + +#[test] +fn mismatch_goes_whole() { + let envs = both_paths_envelopes(); + // A producer's wrong digest: the range path tries it, finds the bytes do not match, + // and decides on the whole envelope, as the whole path does: skip, next entry. + let f = fetch(&envs[2], Fragment::default(), sink(&["wai.video.av1", "wai.video.vp9"], &[], None)).unwrap(); + assert_eq!(f.index, 1); + assert_eq!(f.skipped, vec![(0, Skip::ComponentDigest)]); + assert_eq!((f.verified_by, f.why_whole), (VerifiedBy::WholeObject, Some(WhyWhole::DigestMismatch { index: 0 }))); + assert_eq!(whole_pick(&envs[2], &["wai.video.av1", "wai.video.vp9"], &[0, 1]), Ok(1)); + // A companion with a wrong digest is not applied, on either path. + let f = fetch(&envs[3], Fragment::default(), sink(&["wai.video.av1"], &["wai.meta.hdr10plus"], None)).unwrap(); + assert_eq!((f.index, f.companions.len()), (0, 0)); + // A transport tamper: bytes changed under the same name. + let env = appendix_d(); + let mut served = Served::new(&env); + served.object[2000] ^= 1; + let mut s = FetchSession::new(content_hash(&env), Fragment::default(), FetchPolicy::default(), sink(&["wai.video.av1"], &[], None)); + let e = drive(&mut s, |w| served.answer(w)).unwrap_err(); + assert_eq!(e.code(), "object_digest_mismatch"); + // A head byte changed under the same name. + let mut served = Served::new(&env); + served.object[100] ^= 1; + let mut s = FetchSession::new(content_hash(&env), Fragment::default(), FetchPolicy::default(), sink(&["wai.video.av1"], &[], None)); + assert_eq!(drive(&mut s, |w| served.answer(w)).unwrap_err().code(), "head_proof_mismatch"); + // A content coding. + let mut served = Served::new(&env); + served.gzip = true; + let mut s = FetchSession::new(content_hash(&env), Fragment::default(), FetchPolicy::default(), sink(&["wai.video.av1"], &[], None)); + assert_eq!(drive(&mut s, |w| served.answer(w)).unwrap_err().code(), "content_coding_present"); +} + +#[test] +fn range_fetch_counts_appendix_d() { + let env = appendix_d(); + let frag = Fragment { h: Some(1009), n: Some(53_794), ..Default::default() }; + let f = fetch(&env, frag.clone(), sink(&["wai.video.av1"], &["wai.meta.hdr10plus"], None)).unwrap(); + assert_eq!((f.index, f.verified_by, f.why_whole), (0, VerifiedBy::HeadProof, None)); + assert_eq!((f.body_bytes, f.requests), (1009 + 245 + 2585, 3)); + assert_eq!(f.component, pattern(3000, 7, 3)); + // `high` first: the rest of the object, verified whole, with hdr applied. + let f = fetch(&env, frag, sink(&["wai.video.av1"], &["wai.meta.hdr10plus"], Some(vec![1, 0, 2]))).unwrap(); + assert_eq!((f.index, f.verified_by, f.why_whole), (1, VerifiedBy::WholeObject, Some(WhyWhole::Threshold))); + assert_eq!(f.companions, vec![(2, pattern(200, 31, 1))]); + assert_eq!((f.body_bytes, f.requests), (53_794 + 245, 3)); +} + +#[test] +fn deadline_never_blocks_base() { + // An AVC base and an enhancement layer bound to it: the layer is a refinement. + let env = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![ + Rendition::media("wai.video.avc", "avc", pattern(6000, 3, 1)).with_id("base").with_payload_digest(), + Rendition::from_meta(RenditionMeta::new("wai.video.lcevc", "lcevc").companion_to("wai.video.avc"), pattern(40_000, 9, 2)) + .with_id("enh").with_payload_digest().with_companion_of_id("base"), + Rendition::media("wai.video.vp9", "vp9", pattern(30_000, 5, 4)).with_id("other").with_payload_digest(), + ]).to_bytes().unwrap(); + WaiMulti::from_bytes(&env).unwrap().check_component_roles().unwrap(); + let served = Served::new(&env); + let policy = FetchPolicy::default(); + let mut s = FetchSession::new(content_hash(&env), Fragment::default(), policy, sink(&["wai.video.avc"], &["wai.video.lcevc"], None)); + // The refinement's response never arrives. + let f = drive(&mut s, |w| match w { + Want::Range(_, Class::Refinement) => None, + w => served.answer(w), + }).unwrap(); + assert_eq!((f.index, f.abandoned.clone(), f.companions.len()), (0, vec![1], 0)); + assert_eq!(f.verified_by, VerifiedBy::HeadProof); + // Answered, it is applied, in its own request. + let h = head_of(&env); + let frag = Fragment { h: Some(h.covered_len()), ..Default::default() }; + let mut s = FetchSession::new(content_hash(&env), frag, policy, sink(&["wai.video.avc"], &["wai.video.lcevc"], None)); + let mut classes = Vec::new(); + let f = drive(&mut s, |w| { + if let Want::Range(r, c) = w { + classes.push((*r, *c)); + } + served.answer(w) + }).unwrap(); + assert_eq!(f.companions.len(), 1); + let (base, enh) = (h.components[0], h.components[1]); + let covered = h.covered_len(); + assert_eq!(classes, vec![ + (ByteRange { first: 0, last: covered - 1 }, Class::Base), + (ByteRange { first: covered, last: base.start + base.len - 1 }, Class::Base), + (ByteRange { first: enh.start, last: enh.start + enh.len - 1 }, Class::Refinement), + ]); +} + +#[test] +fn small_objects_whole() { + let small = sized(400); + let f = fetch(&small, Fragment { n: Some(400), ..Default::default() }, sink(&["wai.text.zstd"], &[], None)).unwrap(); + assert_eq!((f.verified_by, f.why_whole, f.requests), (VerifiedBy::WholeObject, Some(WhyWhole::BelowOneChunk), 1)); + // Without `n`: the head request returns the whole object, and its proof covers it. + let f = fetch(&small, Fragment::default(), sink(&["wai.text.zstd"], &[], None)).unwrap(); + assert_eq!(f.index, 0); + let stream = std::fs::read(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/vectors/int_mlicpp/stream.wai")).unwrap(); + let mut s = sink(&["wai.neural.int_mlicpp"], &[], None); + s.verify_pin = Box::new(|_, _| Ok(())); + let f = fetch(&stream, Fragment::default(), s).unwrap(); + assert_eq!((f.index, f.component.len()), (0, stream.len() - head_of(&stream).head_len as usize)); +} + +fn el(s: &str) -> Element { + codecs::parse_element(s).unwrap() +} + +#[test] +fn codecs_grammar() { + for ok in ["wai2.video.av1+meta.hdr10plus|video.int_hyper", "wai1.video.av1", "wai2.image.avif|neural.int_mlicpp", + "wai2.video.avc+video.lcevc", "wai2.video.int_motion+video.int_refine"] { + assert_eq!(codecs::render(&el(ok)), ok); + } + for (bad, code) in [ + ("wai1.Video.av1", "codecs_malformed"), ("wai1.video..av1", "codecs_malformed"), ("wai3.video.av1", "codecs_malformed"), + ("wai1.video.av1+video.vp9", "codecs_malformed"), ("wai1.meta.hdr10plus", "codecs_malformed"), + ("wai1.derive.crop", "codecs_not_media"), ("wai1.splat.sog", "codecs_candidate"), ("wai1.video", "codecs_malformed"), + ("wai1.video.manifest", "codecs_not_media"), ("wai1.video.int_refine", "codecs_malformed"), + ] { + let e = codecs::parse(bad).unwrap_err(); + assert_eq!(e.code(), code, "{bad}"); + } + let v = codecs::parse("mp4a.40.2, wai1.video.av1,avc1.42E01E").unwrap(); + assert_eq!((v[0].is_none(), v[1].is_some(), v[2].is_none()), (true, true, true)); +} + +#[test] +fn every_registered_media_capability_has_an_element() { + use crate::codecs::registry::{registrations, Kind, RegistrationStatus}; + for r in registrations() { + if r.status != RegistrationStatus::Registered { + continue; + } + let cap = r.capability.strip_prefix("wai.").expect("wai."); + let text = match r.kind { + Kind::Payload => format!("wai1.{cap}"), + Kind::Companion => format!("wai2.video.av1+{cap}"), + Kind::Refinement => format!("wai2.video.int_motion+{cap}"), + Kind::Derivation | Kind::Record => continue, + }; + assert_eq!(codecs::render(&el(&text)), text, "{}", r.capability); + } +} + +fn obj(rends: Vec) -> head::Head { + head_of(&WaiMulti::new("video", "replicate", serde_json::Value::Null, rends).to_bytes().unwrap()) +} + +#[test] +fn union_and_floor() { + let a = obj(vec![Rendition::media("wai.video.av1", "av1", vec![1]), Rendition::media("wai.video.int_hyper", "int_hyper", vec![2])]); + let b = obj(vec![Rendition::media("wai.video.av1", "av1", vec![3])]); + let hs = [a.clone(), b.clone(), b.clone()]; + assert_eq!(codecs::render(&codecs::union_of(&hs).unwrap()), "wai2.video.av1|video.int_hyper"); + assert_eq!(codecs::render(&codecs::floor_of(&hs).unwrap()), "wai2.video.av1"); + let c = obj(vec![Rendition::media("wai.video.vp9", "vp9", vec![3])]); + assert_eq!(codecs::floor_of(&[b.clone(), c]).unwrap_err().code(), "codecs_no_common_capability"); + // Appendix D. + let d = head_of(&appendix_d()); + assert_eq!(codecs::render(&codecs::union_of(std::slice::from_ref(&d)).unwrap()), "wai2.video.av1+meta.hdr10plus"); + assert_eq!(codecs::render(&codecs::floor_of(&[d]).unwrap()), "wai2.video.av1+meta.hdr10plus"); +} + +#[test] +fn undeclared_refused() { + let h = obj(vec![Rendition::media("wai.video.vp9", "vp9", vec![1])]); + assert_eq!(codecs::check_declared(&h, &el("wai2.video.av1")).unwrap_err().code(), "codecs_undeclared"); + assert!(codecs::check_declared(&h, &el("wai2.video.av1|video.vp9")).is_ok()); + // A WAI2 under a wai1 element. + assert_eq!(codecs::check_declared(&h, &el("wai1.video.vp9")).unwrap_err().code(), "codecs_undeclared"); + // A companion the union does not list. + let d = head_of(&appendix_d()); + assert_eq!(codecs::check_declared(&d, &el("wai2.video.av1")).unwrap_err().code(), "codecs_undeclared"); + // And a session refuses such an object at step 3. + let mut s = sink(&["wai.video.av1"], &[], None); + s.declared = Some(el("wai2.video.vp9")); + assert_eq!(fetch(&appendix_d(), Fragment::default(), s).unwrap_err().code(), "codecs_undeclared"); +} + +#[test] +fn container_gate() { + let only1 = [Prefix::Wai1]; + let has = |c: &str| c == "wai.video.av1"; + assert!(!codecs::supports(&el("wai2.video.av1"), &only1, has)); + assert!(codecs::supports(&el("wai1.video.av1"), &only1, has)); + assert!(codecs::supports(&el("wai2.video.av1"), &[Prefix::Wai1, Prefix::Wai2], has)); +} + +#[test] +fn supports_floor_only() { + let both = [Prefix::Wai1, Prefix::Wai2]; + let floor = el("wai2.video.av1"); + assert!(!codecs::supports(&floor, &both, |c| c == "wai.video.int_hyper")); + assert!(codecs::supports(&floor, &both, |c| c == "wai.video.av1")); + // Companions never affect support. + assert!(codecs::supports(&el("wai2.video.av1+meta.hdr10plus"), &both, |c| c == "wai.video.av1")); +} + +#[test] +fn origin_serve() { + let s = origin::serve(1000, None); + assert_eq!((s.status, s.body.clone()), (200, 0..1000)); + let s = origin::serve(1000, Some("bytes=10-19")); + assert_eq!((s.status, s.content_range.as_deref(), s.body.clone()), (206, Some("bytes 10-19/1000"), 10..20)); + let s = origin::serve(1000, Some("bytes=-100")); + assert_eq!((s.status, s.content_range.as_deref()), (206, Some("bytes 900-999/1000"))); + let s = origin::serve(1000, Some("bytes=990-")); + assert_eq!((s.status, s.content_range.as_deref()), (206, Some("bytes 990-999/1000"))); + let s = origin::serve(1000, Some("bytes=0-99999")); + assert_eq!(s.content_range.as_deref(), Some("bytes 0-999/1000")); + let s = origin::serve(1000, Some("bytes=1000-")); + assert_eq!((s.status, s.content_range.as_deref(), s.body.clone()), (416, Some("bytes */1000"), 0..0)); + let s = origin::serve(1000, Some("bytes=0-1,5-6")); + assert_eq!(s.status, 200); + let h = origin::object_headers(&[0xab; 32], Some("wai2.video.av1")); + let get = |k: &str| h.iter().find(|(n, _)| *n == k).map(|(_, v)| v.clone()); + assert_eq!(get("Access-Control-Allow-Origin").as_deref(), Some("*")); + assert_eq!(get("ETag"), Some(format!("\"{}\"", "ab".repeat(32)))); + assert_eq!(get("Content-Type").as_deref(), Some("application/wai; codecs=\"wai2.video.av1\"")); + assert!(get("Vary").is_none() && get("Content-Encoding").is_none()); + let p = origin::preflight_headers(); + assert!(p.iter().any(|(n, v)| *n == "Access-Control-Allow-Headers" && v == "Range, Priority")); + assert!(p.iter().any(|(n, _)| *n == "Access-Control-Max-Age")); +} + +#[test] +fn fragment_parse() { + let cid = "ee".repeat(32); + let (base, f) = parse_fragment(&format!("live/1.wai#cid={cid}&n=53794&h=1009&c=low&x=1")); + assert_eq!(base, "live/1.wai"); + assert_eq!(f, Fragment { cid: Some([0xee; 32]), n: Some(53_794), h: Some(1009), c: Some("low".into()) }); + assert_eq!(parse_fragment("a.wai").1, Fragment::default()); + assert_eq!(parse_fragment("a.wai#n=x&cid=zz").1, Fragment::default()); + assert_eq!(object_name(&[0xee; 32]), format!("{cid}.wai")); +} -- 2.54.0 (Apple Git-157) From 1e25008ea15178ff59b3620572d9c02d2e65da56 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 5 Oct 2026 07:32:25 -0400 Subject: [PATCH 04/31] wai-rs: the head-proof test covers a head of several chunks The chunk-counter falsification survived: every subtree before c starts at offset 0 unless the head spans three or more chunks. A WAI2 with a long target member now puts covered subtrees past the first chunk. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/http_binding/tests.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/wai/wai-rs/src/http_binding/tests.rs b/wai/wai-rs/src/http_binding/tests.rs index 1a4997c3c..b33ec3e54 100644 --- a/wai/wai-rs/src/http_binding/tests.rs +++ b/wai/wai-rs/src/http_binding/tests.rs @@ -142,10 +142,23 @@ fn sized(total: usize) -> Vec { env } +/// A `WAI2` whose head spans several chunks: a long `target` member, so the covered +/// prefix holds subtrees that start past the first chunk. +fn large_head(total: usize) -> Vec { + let target = serde_json::json!({ "note": "x".repeat(5000) }); + let build = |payload: Vec| WaiMulti::new("binary", "replicate", target.clone(), + vec![Rendition::media("wai.text.zstd", "zstd", payload)]).to_bytes().unwrap(); + let probe = build(Vec::new()); + let env = build(pattern(total - probe.len(), 11, 7)); + assert!(head_of(&env).head_len > 4096); + env +} + #[test] fn head_proof_round_trip_and_tamper() { - for total in [2048usize, 5000, 1 << 20, 64 << 20] { - let env = sized(total); + let objects = [2048usize, 5000, 1 << 20, 64 << 20].map(sized).into_iter().chain([large_head(9000), large_head(1 << 20)]); + for env in objects { + let total = env.len(); let cid = content_hash(&env); let p = HeadProof::build(&env).unwrap(); let bytes = p.to_bytes(); -- 2.54.0 (Apple Git-157) From 764871baf749de11a364d13cf98948cfcee237aa Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 5 Oct 2026 07:42:24 -0400 Subject: [PATCH 05/31] wai: `wai http` commands, the wai_http_vectors tool and the http-conformance corpus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit W3-2. `wai http publish|codecs|origin|vectors|bench` (feature `http`; without it `wai http` says so): publish writes .wai, its head proof and ETag sidecar; codecs prints a sequence's union and floor; origin serves a directory with §5.3's headers, byte ranges, head proofs, receipt files, indexes and preflights, with an optional delay on a byte range; bench fetches an object by §7 over plain HTTP and writes a measurement record (energy recorded as not measured). wai_http_vectors is the corpus tool, also reachable as `wai http vectors`, so the corpus loop runs it like every other corpus. http-conformance/: objects O1–O5, O7, O8, O9a–c (the design's O1–O9 without receipts), Appendix D's envelope (OD) and a several-chunk head (OH), each with its head proof and ETag sidecar; bad/O6.wai; case proofs; and cases over parse_head, head_proof, plan, response, select, fetch, same_pick, codecs, union_floor and supports. Sources are frozen under src/ with their SHA-256 in SOURCES.json; the picture is the pinned int_mlicpp vector's output, so O2's renditions are one picture. The generator holds every negative case to the reason it exists for and Appendix D's cid, length and proof size to the text. Co-Authored-By: Claude Opus 5.5 --- wai/http-conformance/bad/O6.wai | Bin 0 -> 252 bytes wai/http-conformance/cases.json | 208 +++++ .../cases/head_proof_drop_cv_O3.hp | Bin 0 -> 245 bytes .../cases/head_proof_envelope_len_O3.hp | Bin 0 -> 277 bytes .../cases/head_proof_extra_cv_O3.hp | Bin 0 -> 309 bytes .../cases/head_proof_flip_cv_O3.hp | Bin 0 -> 277 bytes .../cases/head_proof_magic.hp | Bin 0 -> 277 bytes .../cases/head_proof_not_formula_OH.hp | Bin 0 -> 53 bytes .../cases/head_proof_of_O4.hp | Bin 0 -> 245 bytes .../cases/head_proof_unaligned_O3.hp | Bin 0 -> 277 bytes wai/http-conformance/objects.json | 98 ++ ...ae1463693f3769c090744ebd5f1e80fa3b0a34.wai | Bin 0 -> 20250 bytes ...3693f3769c090744ebd5f1e80fa3b0a34.wai.etag | 1 + ...463693f3769c090744ebd5f1e80fa3b0a34.wai.hp | Bin 0 -> 213 bytes ...8508ead30d078690cb7bea2435ae3d1df5f4f3.wai | Bin 0 -> 21034 bytes ...ad30d078690cb7bea2435ae3d1df5f4f3.wai.etag | 1 + ...8ead30d078690cb7bea2435ae3d1df5f4f3.wai.hp | Bin 0 -> 213 bytes ...927a5353a6717224de27fdb6a26dd6c0485a15.wai | Bin 0 -> 39361 bytes ...353a6717224de27fdb6a26dd6c0485a15.wai.etag | 1 + ...a5353a6717224de27fdb6a26dd6c0485a15.wai.hp | Bin 0 -> 245 bytes ...b16b88c8d5ee1e72bafe4892992fc6bef88be9.wai | Bin 0 -> 98613 bytes ...8c8d5ee1e72bafe4892992fc6bef88be9.wai.etag | 1 + ...b88c8d5ee1e72bafe4892992fc6bef88be9.wai.hp | Bin 0 -> 277 bytes ...42fb01cb25ff9c7b189fb6699bf92894c29a6f.wai | Bin 0 -> 39437 bytes ...1cb25ff9c7b189fb6699bf92894c29a6f.wai.etag | 1 + ...b01cb25ff9c7b189fb6699bf92894c29a6f.wai.hp | Bin 0 -> 245 bytes ...2c7cbcb1d98d6c9edd6983ad8b9e385e6c0b09.wai | Bin 0 -> 46433 bytes ...cb1d98d6c9edd6983ad8b9e385e6c0b09.wai.etag | 1 + ...cbcb1d98d6c9edd6983ad8b9e385e6c0b09.wai.hp | Bin 0 -> 245 bytes ...bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai | Bin 0 -> 18381 bytes ...f713aa0d4dc4c2e3dfe40aab759a90133.wai.etag | 1 + ...5ff713aa0d4dc4c2e3dfe40aab759a90133.wai.hp | Bin 0 -> 213 bytes ...107cd5d738100064b4fec189b540f7b7ae03f6.wai | Bin 0 -> 5389 bytes ...5d738100064b4fec189b540f7b7ae03f6.wai.etag | 1 + ...cd5d738100064b4fec189b540f7b7ae03f6.wai.hp | Bin 0 -> 53 bytes ...67039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai | Bin 0 -> 18004 bytes ...e7fe65c640d73a040ebc5d5cf86e4ecbb.wai.etag | 1 + ...39e7fe65c640d73a040ebc5d5cf86e4ecbb.wai.hp | Bin 0 -> 213 bytes ...df937f480788fd669e7337b0f8105a5816a9d2.wai | Bin 0 -> 859 bytes ...f480788fd669e7337b0f8105a5816a9d2.wai.etag | 1 + ...37f480788fd669e7337b0f8105a5816a9d2.wai.hp | Bin 0 -> 53 bytes ...daf54b19fc3a946a3c7980431bc6e0a236093b.wai | Bin 0 -> 251 bytes ...b19fc3a946a3c7980431bc6e0a236093b.wai.etag | 1 + ...54b19fc3a946a3c7980431bc6e0a236093b.wai.hp | Bin 0 -> 53 bytes ...a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai | Bin 0 -> 53794 bytes ...a2a176c1d76c02dde3319f014a3ef3228.wai.etag | 1 + ...e9a2a176c1d76c02dde3319f014a3ef3228.wai.hp | Bin 0 -> 245 bytes wai/http-conformance/src/SOURCES.json | 50 + wai/http-conformance/src/image64.avif | Bin 0 -> 2619 bytes wai/http-conformance/src/image64.jxl | Bin 0 -> 4433 bytes wai/http-conformance/src/image64.png | Bin 0 -> 11848 bytes wai/http-conformance/src/pattern_high.av1 | Bin 0 -> 77040 bytes wai/http-conformance/src/pattern_low.av1 | Bin 0 -> 20766 bytes wai/http-conformance/src/seg0.av1 | Bin 0 -> 18293 bytes wai/http-conformance/src/seg1.av1 | Bin 0 -> 17834 bytes wai/http-conformance/src/seg2.av1 | Bin 0 -> 18211 bytes wai/http-conformance/src/small.zst | Bin 0 -> 94 bytes wai/wai-rs/Cargo.toml | 5 + wai/wai-rs/src/bin/wai.rs | 17 + wai/wai-rs/src/bin/wai_cli/http.rs | 360 ++++++++ wai/wai-rs/src/bin/wai_cli/http_vectors.rs | 866 ++++++++++++++++++ wai/wai-rs/src/bin/wai_http_vectors.rs | 27 + 62 files changed, 1643 insertions(+) create mode 100644 wai/http-conformance/bad/O6.wai create mode 100644 wai/http-conformance/cases.json create mode 100644 wai/http-conformance/cases/head_proof_drop_cv_O3.hp create mode 100644 wai/http-conformance/cases/head_proof_envelope_len_O3.hp create mode 100644 wai/http-conformance/cases/head_proof_extra_cv_O3.hp create mode 100644 wai/http-conformance/cases/head_proof_flip_cv_O3.hp create mode 100644 wai/http-conformance/cases/head_proof_magic.hp create mode 100644 wai/http-conformance/cases/head_proof_not_formula_OH.hp create mode 100644 wai/http-conformance/cases/head_proof_of_O4.hp create mode 100644 wai/http-conformance/cases/head_proof_unaligned_O3.hp create mode 100644 wai/http-conformance/objects.json create mode 100644 wai/http-conformance/objects/25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34.wai create mode 100644 wai/http-conformance/objects/25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34.wai.etag create mode 100644 wai/http-conformance/objects/25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34.wai.hp create mode 100644 wai/http-conformance/objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai create mode 100644 wai/http-conformance/objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai.etag create mode 100644 wai/http-conformance/objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai.hp create mode 100644 wai/http-conformance/objects/6bc4da2051c4c4cef757b2a7ee927a5353a6717224de27fdb6a26dd6c0485a15.wai create mode 100644 wai/http-conformance/objects/6bc4da2051c4c4cef757b2a7ee927a5353a6717224de27fdb6a26dd6c0485a15.wai.etag create mode 100644 wai/http-conformance/objects/6bc4da2051c4c4cef757b2a7ee927a5353a6717224de27fdb6a26dd6c0485a15.wai.hp create mode 100644 wai/http-conformance/objects/6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9.wai create mode 100644 wai/http-conformance/objects/6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9.wai.etag create mode 100644 wai/http-conformance/objects/6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9.wai.hp create mode 100644 wai/http-conformance/objects/922db9b513790966272927c4f542fb01cb25ff9c7b189fb6699bf92894c29a6f.wai create mode 100644 wai/http-conformance/objects/922db9b513790966272927c4f542fb01cb25ff9c7b189fb6699bf92894c29a6f.wai.etag create mode 100644 wai/http-conformance/objects/922db9b513790966272927c4f542fb01cb25ff9c7b189fb6699bf92894c29a6f.wai.hp create mode 100644 wai/http-conformance/objects/a016e64f311ac1128481d0da292c7cbcb1d98d6c9edd6983ad8b9e385e6c0b09.wai create mode 100644 wai/http-conformance/objects/a016e64f311ac1128481d0da292c7cbcb1d98d6c9edd6983ad8b9e385e6c0b09.wai.etag create mode 100644 wai/http-conformance/objects/a016e64f311ac1128481d0da292c7cbcb1d98d6c9edd6983ad8b9e385e6c0b09.wai.hp create mode 100644 wai/http-conformance/objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai create mode 100644 wai/http-conformance/objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai.etag create mode 100644 wai/http-conformance/objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai.hp create mode 100644 wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai create mode 100644 wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai.etag create mode 100644 wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai.hp create mode 100644 wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai create mode 100644 wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai.etag create mode 100644 wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai.hp create mode 100644 wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai create mode 100644 wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai.etag create mode 100644 wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai.hp create mode 100644 wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai create mode 100644 wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai.etag create mode 100644 wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai.hp create mode 100644 wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai create mode 100644 wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai.etag create mode 100644 wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai.hp create mode 100644 wai/http-conformance/src/SOURCES.json create mode 100644 wai/http-conformance/src/image64.avif create mode 100644 wai/http-conformance/src/image64.jxl create mode 100644 wai/http-conformance/src/image64.png create mode 100644 wai/http-conformance/src/pattern_high.av1 create mode 100644 wai/http-conformance/src/pattern_low.av1 create mode 100644 wai/http-conformance/src/seg0.av1 create mode 100644 wai/http-conformance/src/seg1.av1 create mode 100644 wai/http-conformance/src/seg2.av1 create mode 100644 wai/http-conformance/src/small.zst create mode 100644 wai/wai-rs/src/bin/wai_cli/http.rs create mode 100644 wai/wai-rs/src/bin/wai_cli/http_vectors.rs create mode 100644 wai/wai-rs/src/bin/wai_http_vectors.rs diff --git a/wai/http-conformance/bad/O6.wai b/wai/http-conformance/bad/O6.wai new file mode 100644 index 0000000000000000000000000000000000000000..c4ef2dd28928c8ade5e11639aa3521a72d8bdbba GIT binary patch literal 252 zcmWG{^fa8vz`#(gRGyfrWTj-NXP~5`l$)B8nF!>Sq*jywxtVz-sd*(peo<;cPG)jq zNh(N9eoAUid{Js)X=YJsE?B-=DLJtqF)1@Av!oKJ8fc;($OOHr;*u1lS{i<y>Z)lDO($9JHP46UNSp!NxKL!FaQ8Nw_Cgb literal 0 HcmV?d00001 diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json new file mode 100644 index 000000000..484c96685 --- /dev/null +++ b/wai/http-conformance/cases.json @@ -0,0 +1,208 @@ +[ +{"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":5295,"envelope_len":5389,"covered_len":5389,"components":[[5295,94]]}}, +{"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_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_OH","op":"head_proof","input":{"object":"OH"},"expect":{"covered_len":5389,"n_cv":0,"envelope_len":5389}}, +{"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"]]}}, +{"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"]]}}, +{"name":"plan_one_class_O8","op":"plan","input":{"object":"O8","wanted":[[0,"base"],[1,"base"]],"held":[[0,1008]]},"expect":{"requests":[[1009,28598,"base"]]}}, +{"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"]]}}, +{"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"]]}}, +{"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":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":21650,"requests":3,"companions":[],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"24003cee2a7bf7fa45caa412ab4ea9f5f08b6bf44842a73d5a11c2bcf36df464"}}, +{"name":"fetch_O3_low_default_head","op":"fetch","input":{"object":"O3","advertises":["wai.video.av1"],"applies":["wai.meta.hdr10plus"]},"expect":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":21650,"requests":3,"companions":[],"refinements":[],"abandoned":[],"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":{"pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":78526,"requests":3,"companions":[2],"refinements":[],"abandoned":[],"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":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":3839,"requests":3,"companions":[],"refinements":[],"abandoned":[],"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":{"pick":1,"verified_by":"whole-object","why_whole":"range_threshold","body_bytes":54039,"requests":3,"companions":[2],"refinements":[],"abandoned":[],"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":{"pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":51454,"requests":3,"companions":[2],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"b76bf31be170d9df8eafbd020941b5fd4b7ab017af6a1e5c1c03e909aa8f9fb7"}}, +{"name":"fetch_O4","op":"fetch","input":{"object":"O4","fragment":"#n=39361&h=1009","advertises":["wai.video.av1"]},"expect":{"pick":0,"verified_by":"whole-object","why_whole":"component_digest_absent","body_bytes":39606,"requests":3,"companions":[],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"24003cee2a7bf7fa45caa412ab4ea9f5f08b6bf44842a73d5a11c2bcf36df464"}}, +{"name":"fetch_O7","op":"fetch","input":{"object":"O7","fragment":"#n=39437&h=1009","advertises":["wai.video.av1"]},"expect":{"pick":1,"verified_by":"whole-object","why_whole":"component_digest_mismatch","body_bytes":59817,"requests":4,"companions":[],"refinements":[],"abandoned":[],"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":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8844,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"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":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":28844,"requests":4,"companions":[1],"refinements":[],"abandoned":[],"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":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8844,"requests":3,"companions":[],"refinements":[],"abandoned":[],"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":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":16582,"requests":2,"companions":[],"refinements":[],"abandoned":[],"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":{"pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":16582,"requests":2,"companions":[],"refinements":[],"abandoned":[],"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":{"pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":912,"requests":2,"companions":[],"refinements":[],"abandoned":[],"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":{"pick":0,"verified_by":"whole-object","why_whole":"below_one_chunk","body_bytes":251,"requests":1,"companions":[],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}}, +{"name":"fetch_O9b_wai1","op":"fetch","input":{"object":"O9b","fragment":"#n=18004&h=1009","advertises":["wai.video.av1"]},"expect":{"pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":18217,"requests":3,"companions":[],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"edd595431a8992d78fd6f691c6b066efa102ac8c1ffb822505639e0d640174a4"}}, +{"name":"fetch_OH","op":"fetch","input":{"object":"OH","advertises":["wai.text.zstd"]},"expect":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":5442,"requests":2,"companions":[],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}}, +{"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":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":19099,"requests":3,"companions":[],"refinements":[],"abandoned":[],"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=5389&h=1009"},"expect":{"inert":true}}, +{"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=5389&h=1009"},"expect":{"inert":true}}, +{"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=5389&h=1009"},"expect":{"inert":true}}, +{"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=5389&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=5389&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=5389&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=5389&h=1009"},"expect":{"pick":0}}, +{"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=5389&h=1009"},"expect":{"pick":0}}, +{"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=5389&h=1009"},"expect":{"pick":0}}, +{"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=5389&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=5389&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=5389&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":"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}} +] diff --git a/wai/http-conformance/cases/head_proof_drop_cv_O3.hp b/wai/http-conformance/cases/head_proof_drop_cv_O3.hp new file mode 100644 index 0000000000000000000000000000000000000000..98b7ed088b09fd9e66b1e6b53405dbb504f5a817 GIT binary patch literal 245 zcmVn^_>1W^fdK#j0002- z0{{R300006%6LSs73mv2g&fKkk)s)2GYd&skbi;~mB}IXprcT-O@-ZNZ!KReYG?;W z^abXiqkF=Oc%-vtz@RJ>i;YVmIdg!Yfvs_5gaRPgkr~d`%NRUp_vH~3M v2>W9;&?^0^EP|lBwlG@Vk6w32?O>rIJ8ZBjg=H9`n^_>1W{fdK#j0002- z0{{R300007%6LSs73mv2g&fKkk)s)2GYd&skbi;~mB}IXprcT-O@-ZNZ!KReYG?;W z^abXiqkF=Oc%-vtz@RJ>i;YVmIdg!Yfvs_5gaRPgkr~d`%NRUp_vH~3M z2>W9;&?^0^EP|lBwlG@Vk6w32?O>rIJ8ZBjg=H9` zsS1~MqA#UwTV+m(Ph2b+XUy#xIibEueCkQXuL~9jZ1Qcro0hK|ualL*?)HWC*@DH@ zhq^14Y)(6{Ku5T{*IUuDxZzk|?CO#=zY}iAy8M41R}nL_;bmO>Yio8GZ9BWP=;y)BF&-BSnX0EO|Ihl;V}jqMO-wTH zIesUbUC{cwN~dYTPxB=&MNH-f^=z3`xBRtlr)zGI&0n^_>1W^fdK#j0002- z0{{R300007%Xmbt73mv2g&fKkk)s)2GYd&skbi;~mB}IXprcT-O@-ZNZ!KReYG?;W z^abXiqkF=Oc%-vtz@RJ>i;YVmIdg!Yfvs_5gaRPgkr~d`%NRUp_vH~3M z2>W9;&?^0^EP|lBwlG@Vk6w32?O>rIJ8ZBjg=H9`n^_>1W^fdK#j0002- z0{{R300007%6LSs73mv2g&fKkk)s)2GYd&skbi;~mB}IXprcT-O@-ZNZ!KReYG?;W z^abXiqkF=Oc%-vtz@RJ>i;YVmIdg!Yfvs_5gaRPgkr~d`%NRUp_vH~3M z2>W9;&?^0^EP|lBwlG@Vk6w32?O>rIJ8ZBjg=H9`_uZ25PvbF0Jm?dzDo@rp8l0k0&4W&i*= ChY=|N literal 0 HcmV?d00001 diff --git a/wai/http-conformance/cases/head_proof_of_O4.hp b/wai/http-conformance/cases/head_proof_of_O4.hp new file mode 100644 index 0000000000000000000000000000000000000000..3587e7b24a8f7cb9536b09146c5e3cf2a57bf2c8 GIT binary patch literal 245 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34.wai.etag b/wai/http-conformance/objects/25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34.wai.etag new file mode 100644 index 000000000..5ce8759c6 --- /dev/null +++ b/wai/http-conformance/objects/25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34.wai.etag @@ -0,0 +1 @@ +"25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34" diff --git a/wai/http-conformance/objects/25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34.wai.hp b/wai/http-conformance/objects/25130382f5d9c73ae59a04d1fbae1463693f3769c090744ebd5f1e80fa3b0a34.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..6d21ce5adc2b4170f0d7ccfc92bd5793f6149496 GIT binary patch literal 213 zcmV;`04o1iNKi2)69akaSMHUmk$^I|?)!PXGV_0002- z0{{R3000053)yh~EE*qa;(oRGGPNx$-&9P?#NoD*MHGqA#I_z^eDz)U*?pp#^St96 zutXOR*|Tsd`P$xA=gEA2SxO~@3Xc7|HsHhLGqCGxmLrLdUPxHQ%f< z+FPKCaeu4w;;8$-zp5GwtiA0vQvwibC`Wl^neLCF06NwiQ#MrAonTB`x#R PhknCS#*iFDCFwgo-$P_> literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai b/wai/http-conformance/objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai new file mode 100644 index 0000000000000000000000000000000000000000..b4dfaefa3e1bbf631c169fa3e6da3bc679b7fc32 GIT binary patch literal 21034 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 PBDg-Jtjl)vBjPaV5Atj# literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/6bc4da2051c4c4cef757b2a7ee927a5353a6717224de27fdb6a26dd6c0485a15.wai b/wai/http-conformance/objects/6bc4da2051c4c4cef757b2a7ee927a5353a6717224de27fdb6a26dd6c0485a15.wai new file mode 100644 index 0000000000000000000000000000000000000000..bf70114b91a582f73a49b5555f9e2e125e373b99 GIT binary patch literal 39361 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≺>E36jb#fFQF83-k9Xkn18X=V%j7JblvuycoIJfFdG0*#~nQ!uB*!A*jct zzTo&5tZR1PCVBrjZPpZ;44M8kX)PM9Y^w9x)VpKFTS=O&pD;9TD4F0ZaC88LC3}Wv zre20WX#Fe--*6c85?EStlZsf14WkVRpJk#=v2zxC-&l``p|(ExNMsyiKHUda02!gN z1b#qLWe#0`ILo0JHC<}oE%gL4I<5LQ;i5xgNsqXXZno6VtUT|M@gWdezwCrfb2!O~ zZCbmH>BXT8#d10hAz=sIsGC=83h~;%Kw#CNrS%Lnv2tNUTZq*zh>=NM1gm48dcelZ z>@T3qzoOg7w7DYq}X}; z->hVV{zK2ioJ5~9^ngbWmspWY9n}L#HIREWX#y2Ur)xI%j&I2E4e#qVupb6pyiJo3 zIQgmv=hdBJR)1vB?Bl-M09yqomq**#&Mn$)u^J_-qAeUf28t$`e69oZ2lPZgY^qz5 zkn2HWoxpthoZ__EJycv`WL3*V5D?qS3%Z??za`CKEnl32N!B%}UTD~Ut& zLs<^j28p4O^mvqKFx=n?CBvS^02f6r##SQ|3u0VDS$uNetR(msX5kO#eSyg%c6kT- zK`dy2aF(@?Pu5n(3QKpzVG`H8GF#j2j$igrGNuMH2rvaObfZOZ1r$+)fdmJD0Nogd zc^3RB7#aL0@W%neRg9XUtAC5V@r6v{5E`}opU*a5ug>mjgrq7)#ie0ju;`euq$zq- z1^T7hGF{5F*@$xP!`XJCa19zZpZ+j)bE@8+{3N7bT8S0dq|zz|<~N2D4b2FyjoYBEP}oz_g5Tb4-@5g0lvujiDi zHE7yxr$vqv7lyy}5t@5A4AvyvO=;h`eySx35_9n$48 z3bs(56=*Pp4}Z})RJZ~nwOm1Cik4UN=h_)oAOw>fILyovn^b)ee`AtKHEy>ytqQ<&pa=ZN77FuAXSY2??d2Lkjf?ptx|WH zj%b~G4AULr?9%ydw*mt!JJZmw*jp;_j>8gl+1JMCQ1?z<_1EeDPkRX?IbfsKh8^Qi zU9r+Zpi^9a&*lKl7OR(ZgTCGK1ixnts<`>wdYZyPo8mqq4s^~AMPrDHTV*W(p^RvA zy&+jnBY0fSOUOHnoPGtkOAcuHA4G zzLE%WMzO^mmHf6Uz04t#kjYOM?~-~Xu?_1=d~))mqKufcV6RioX3*NBg%4*1uh-ZFMc`I z$k-esk3Es>3ZNb}H6T5~V~c|D$KNyvNA4)CJ zX{p9}*aZLp01^Ni0k}v5000sIGQ$Qk3MdEAu%ktQ1r(BmfdmJD0OEqu6`vYETV4Mv z?`hIFA&8uuf^zd+c<_k)jiy&*y-Bd4e5C@}8`c|4{PEcUpK94h`8ox{0clnS_8HuY zwst+L2LC;{LhH|OE%WubGfXMSe4VKf{iJxW4{EirIxxWiHTG+Uy>`<_iWo|g_(5Pc zWkO`m;TZq3>>AgUUtXcin&0;jz`lZ1YU3+kE~Q|AyXWTPk0z40=B!Cq#Q5{}ZbnMt z^rT)MWfMVx`}{=)CjfKjljR6V3W@@1WNSMmfv%Hde45w2@*FG)BL(ltHIO`OwEI)X zdHb;DO_!Q|*nys{S-Q5qA}JRs0iT5`|3_*aMPk@8Nrdy}$2O)0;xIf**zLggR+aj~ z#ya&QemE{VX14^g>>jv@1QZLo*6L#E*VL5jl)}(rw9WBt6CDVAl{^`9{s-}tb zNI=1ry*=-bZT#jmhYkaoZlu0}+?k_`R9uuA44t&nYumfTp*9&J+*wo^W9frF z99!~?bx}|6!&F;5E3cym-AQ#hcx2Y<`bdX|d(mos<(GP599b*+zfC*AM~hapYc(Jh zPjnafP8Eh)*ht~y7ZNqSLeDAwEgj|3pYTvcr5z9~k@(XzPSeX^QZTZ``F~{fOLJ+o zi*jruZNcON#*?qRXhrZKdH=&1M;8&}edo8hzTQDR@be?J=zjaW ze;O?bN15hE#2?`D-|H{{OZ!cO1fM|a=fVX5000sI8Ufhi1^@sO05bUoDF&eh%ydzr zumXxG!a%MAzyP|0jE6?CKX6&eC7q}t(a>HSRT>ZhpsOjh{Wk*~zAGQ%3W#>3F5g_L zk%=&GlOQ@8g-3XjwYQw`2!Exs`r`7}0iIP={?oS&NWnL1PFp&)%Mp<@GjF& zU-w@)%DPLGeb|Ik6G6cxz*GWTb=)GCEjA-JnKJtIRMR*mwVQMQx>!243gL_<;JI7t zANdfJhCS~IfrX?;MK10QhTJW~^WlLpd1ZERoX>~i&S?x_Ji1a9oX~Ir1@B*L<`5#^ zDLsS!_CNRu5p9+t#AE~(=4vDk;F_TGvoXLf(c zH?@rCJ0#dCg7RDYEut9~hHbZb1aT&C-I}XH8VvP~$|bA@MGBR74Js3oWSB?$@?+f! z+oLb%RG8aLTGxH=hE$3>8_)iiFNT-1tsP8%o&zSIC5o`agM%tStMR&V!~ICqfGNKk zeh6xN#Wp!`Nq%GRl)YF67j#*wYF=4Q2}$jz&~^Ko{`J*nHI(9phOFG$oRO z!ohRMhi)sAA=4mqA7ba+@wDJkqR_q;LKMJql4;_XmZ$WY$vR&Ki>7%Z#?L)-*)rh< zG7KP#n4F_UpaBGugn zf(9Es`v9AAjl0MQUYG6PP30}-6ecKj{40oDrhNYNllg1G`ZO1lo(fI{gdRMTK|nj% ze+tQK2Xl`K7tJ2pu%q8{()mFrvNRFw2m(~G_!^)z?4T7#G^^jF_t72(z!o3OJlYH-e5scHTX zi*6{0)^Rh2edAu0zxmzXf;R=!2{WdVNwzam-DJo)R92X!hGDxg zL?S2($!SsYiNVR$`cppDk{syUOh)=@Za3?v@6yLEPFVcpg%_bRtG9ha01Qm{-G?r2 z+&}c}9@`+-ejDF31pMYqHhHjY3edaHtQR~HM}vX5m@e#Ld-(eA&+slQ0|q(!>p2)& zOzFpmakhcdkS4+Z2nYg1K2J^e?#B+TZrKCf$OlX05=wvk(AZ$(jHMzX`f%!rQ z6fXd$R`&HLT@Z$t=psp(-%$BG;P+)o&~5gDPCUCGw5+J8r9VboF^We;3O6vg>qvLb z^EqHdIHfB~lQ5d5QhrKI5pgM*%p;ZzNE zQqqMZA-C&$|BT0}860z-uI%hUh_T*E26O6t3>V^P=~?+lHE^IbTJG!_^lx!Joa0V2MghDAAHtMVS*kPPJD)bFY1e-r86 z!&V{PUhau^+~I7dHy0<4SyznKhp~g1M~Q%A9A#*E8x&z75DJMGXa&+73E4WFgoCba zTeWZE>U0zY;iHV=CTNKc8d*QsGypOQJ3D@%O{M91C)jEv_9Ft$jkHW~4y;0P5%!-W z84x(MU|WFxh$@Q9wa+O#VYX1C!8c&oLj!~bMYMN)clXcbV~pY!HgIS4K=wx`86h*4 zBE)KWiV8Z@{_JX~J1&m^4QNqAb@||iR4wh0q%M_b-UR>v01^Ni0mxtj000sIGTR0+ z4loF)%%erH0R&QnfdmJD0IPPhXEbSoP`8adzVy;x6C$$9Y677Ues^pm?(9&H2zUu6 zOJCKFC+eQLnA^i)FPeKWY(~PN4Rn};&VE&iP9Gn4o>uG{a*Qa2r~7JnLzq0u?glUj zx9kC765>w&c}siJk4-?9ZoOujj~k5?GGs-^LDtR+2$}myp3Gurf^JQn$X(21Wi3Bu z2N{pju}CV4HKDmYT$Y<(cI;^GR=-{N|2Kq!#(T)56#t2L`2g5&P~Y&b!BbNc_ZbXD zG|}t=PtFX)ru5iR)hB?)0x%D!4Nm6duWm)ipkQ2~z{ND^1QZ0+jFEL8zw_D1dEd_# z!lg(Z<=3;%#wo;v<$9xK6TUf)P&d?TpZGMEq1%x?+HuC7we4_w@Vv;vTU3MR&+a#n zlRNzKb?0;JEK?Xdh^&5L_+M0PGWn4ob1|&fV*L?y1^k%1L-nS!iI2*&_*w@fUuc+J zD$dGs3cz6ou-n4q#(*NsZ*9<>NZYy<-?5yqU$qW4HWCM$v}5(~bLSlso{_>zCKu=2 zUP8?0!DJEZ>I7r#&Fe@dWx1x5x4=Nq)iT)C`JNz*RC42)NWx{281m4MzgJ#gYyuEX zpq)WL!!c?ru~GLda2g1FA)WWTj$;cRded}uM!4aZVs?7qXrDK^8ui06;dnd!Xg%aG zv>>b*TqTozOSbP`Z(Z+8fB$+ONpNX%dK zAd5Yc6snz^%a=-W(;fDpUavR$swrb;501%g^+2_PHDXt7cBKX5WnQjXP1qM0Iay7C zNk)y$sDohJ%>MvNl-NG9$5nF2JjbX)VeeE6ma;D4v?CeGl!jaX0<01>Mig{{UKyZ7 z7z>!kMhl6q59rg3s4-Y5OGqHWf|NLm%anE(I)0002- z0{{R3000068~zEvJ5x@=$1vp@%w&Ml>TxRdGyo0Iu*RRIE|1d4%DNk_2FVe3;+qNdQok=F1U}9SnXJXstFYojG-tTfeGSI=&yN|6-2XG6 zjg`IWm-yd?)qkqC4jx}}7fT}`JI7ZO$i~dfYGP&vG~zTi=QKBBV>dA}Vq*as8MB!g zahtQ7Gjkbp7@M=Paj|`MVKrqpVqq}>8k?B2a+sR4aj=p4(f|Ke!P3gY@_*~V!DYnG zVr<6A#c9IE$_)f^m>F{#8?%_0v$3+7akH?Ro3NNOn*o6=T%63NMqHfcoXi|7?4}$X zoJO2R#w@`9ZyoH+T#XnlO`Tbo9c|rQ{vRw%&0I{JtsGq)oWErMK=Re;f2+gE#>vWU z%n4)znsBig8L^w00F78Um|4tCnc0lk&AGl*HDzXH26A&Un{j+;Zq91NX==i4#tk$x z;$jCfe~snrU~BdTu8D)4qmliWrM^V}Q#Wuh|9bWRHq`%qZSb#F|3S%5kDk=k$l1c| zYZ81-6;d8nATvFwB`FUJ7qg!q7~nr2LgjzU6+H+5(H{gb@+AX)m8P#E1Ofoyd_BJ& z05@0w02KfW_f-Yw`!Be)zzRkn} zrL!k2@;h~H{c_>9Zku^tzEp`$xg=g18QS}2?HQg;v2tWn=RfZVy@0r0_IH_PJG9&@ zdJ(AQs5qxk(HWQx@Ir-%k4yDo^%1II#*3rT%(@auE1EvLpTccsrUa9zez_Zk|;8C3T zawfOmIh}~j6_A4-JEXV~Ymj)WeoS-5sFu!ysL>|x&URp7A?h2ntKBsXZoAmmwpb4s{ut%Rsz0_k0*Z9;IVNG z4Xk9>Cp$G?dj6oNE{?mbPI@Y(3((zI*CC$lm%5 z<$iW14)T7-lzre%nRb7DEvR8$RW=oD^6pU-&yiyw^;}k1?T!aw>50oXeRia>&7BDT zEbw;+y~8;7aK&_tT`c8H*$8pCfxFF0b=*G{eFyt@lA^7$$`5h*UJIV6dA})4cjipL zx#hcHg0a5mz<=G`C=Ms`E zybxLdXWK-pnHgJ&pFk|qymhHgdwQ)cRngyVDFf>IJyDIlCT%acm;xK^hCr#}UJeL@ zlstyx3Beub2JXQp7xiuu%(&TO-q`u-3QGT#B`-y{!zkyX8~F(G(nB{Yeu%whGZ6Yf z=G)rkqm7Mv8>=1g=0rBhUv&^lp;DM z5Zi>}>CF-_t@gTChPa*E!_GszPnHvW9!l{)2fQICN50rB)&!1xrIWgUfY)YeQ$x7% z>5w&GczuYek7y{hR-}CfjrcT+z~1EallYdzPcNVC)GYE9^%}Y^C)e}ombaM-S9JWN z_aoFw8yz$($$2(Y^1hW6 zKPNPG0`GOw(~x4I*7nn^SAJR3^^x)WA{MY*D^=sU;grr!VkMr-djHVK6{D*8)w8GS zioBlx6Td_}5PDr#IhjJFM6|O$2&=l4#2kGZHtvlp7G2!$l$b@lC%h@AzE`0ZA9WdB zT5UzMC5U~f?R$zwh{HF=1h{G9WdKP(xTWc zAErZ-okDdYh{#EkL3njaKoDHr#Bnjp;Y_32sB54H{&$*!ln zWic}m2s;miNTCQr%TYhm3=4#~h|gEkeO0_D?Oir#0Z4@EnlP_c(MGc8VBH728Or*~ zGK`LJ5}+21(7U1X&q7@~whlviD#lSW}3s`|JZyBbkg;%e89-ittWlbECvC;mC(PXYzs_ z;hKH6zq`Pbq#J2Z4Gc*h)+jFf^3|?a&T!y;9HG5Qnkh+fqZpCq;I(_xf55(KngX{2 z+>h(@!K^tB69Zf=pxyPRVCNxu>>hsQA4risXV{af$8TBg{*`kK#^#wYT@_sku`l1U z-ZME`=I~l%bU(lgP24O!p`n;Gl&2IMCmOb-if0xuLAVFgd1=Ch(}nD(_zY=oBr%s> z+NAU?|2RaQuMnJ>F!%OU>^2>*?BvOzM;+=HVW{(?@FY(WkSvwp_Q7b%kC_%b-ei6U;)L&00|d{H7Zj-*8MMRHeTqgu_0X z3VuXIv3V{ql8tKNj47nw&^(JO=P%MxVR0cD=a=Gk8VSwgR+#a%qKqD)M0=0tj#1|X z#l}8zUiR(Y&OK?IVY$VAB=~)IBh5|pzkgIkImTFUsH(8N%Mna!` zddVm=c{TTI3+zHXka*r4#SoQqAT86>8HMDpn6yi~cN#-5=C>&QS_(!21+u&bFhMH^-^WA&=;3@`{Bz~uR zOvDErP4;MR?Ej6v`N;h!Xfa&L$unj@)UBbDJY&sH?le}P(pc29MFukE+m!CPHBuA= z|M~`L61Di}ExWAoPiA!WaUy!^nvTp|uMyfO8a0_l>gY##tCR<(JQY@2#ac8l;b=H6 zV1A8_v+#(_nQ8#jP7b6_r)L2*Th>u_YoD<_(G$JSoCvA*=R=Wn1lo0Xe!-N7dC*Td zwo@_iObgjUFM}6~zEgQ~z`5XcPHOt38ungAwGorB#rsLxN3hax$>N}Tjae^m@(<;{ zRiDw_m8EUfKFJkw{BO;xq&n3}@JC=DELvny!k>;yo)~YE4qk*~SCn*03Jb`WSG{}y`U$JVuP_H<3 zFT)ZzR#qS&2E$@y#icY3D2V6qY3Fm(JOzolnw`1^W1K}9}u8aNv_E|CzG}tboEbVsrZ?dvYuY-RC zP^D#hW85r423M)XP2O!Cl^1?nT6f+<8fb2H*BC!g?NU!mNf38|qjZ9^jCG^REQMlD z&(S%GORNyK_ExRe0K%LxAbsZQNIh_h_91#sc7`XLv?d0uM$cT4l^xL}C)U?SA4zGs zJw)E>8LJbA8zCWuc2fkeF|2}Pufv&t=^Z{SgaszpzJ_HemGL(6?)XHrl=I_+oA0I8 z%opnp#QIq4z6A_%@hLeyC)3~xn00z@KN1)rc)+a<;)l~g^kPGyOquvvf}g)ZAp`ay z?cHp zz5dq2Ym!vT1jY*6I$rm_9i&8CZLMCmqxy0upcqbkT%U)!b-s-sPy=zyWo3H_X#nY& zz{?dlwij>Ck-k99FTPQ}p5J>rNP4=g0r6?{oqeEn$>xFXdIsbC&oi%WohdiNO(=SGS&^)SaDHmLtv8w_lDA{mdNuMqDUvKhF?CgA_TKJ?uePXV#B}LIC#s=;odY!?xM$F>~q0KPz*GL{dP9eg(e=T zn=3E?Pys?|%)Q1TtZTX^BZJzl8gFss`=wQEdrzQiz#QIFcLR2U$vW8lB338Egi@&^ z8-ZW}{nq8#nZeSOb-88tawa~n5CN1eMMKq7Yc>}Ik+`oi`qq&W#Yv0gt(asuRy01g zVt=?TEp-|Kzfxv~MeLi|a z)D~GQLH{y#pw%#qM8Ax{@*-(&oq5jJ#J=W|H;;9Q3qPpxcYpjxY zf78;|afDoD7O&>juW#jtJX5zApG&ivai=^h-KFM;i`!+B`U@RJW+zFY?GEvTqew?J zU$RR)4!A7{{|GGw@~MxH0x3!rXYf$sXrQ;Dc3}vu>Uq}HA&~N;^l_{$U`g1Iaxj`F zOL!a?uvK&*{RkKHx>%~Tl>I~NzQ$6`(Bw4>)7ZqtR7f>S6%1wfx45k)T?ozHIMX{> ztcJ<)q!N`FV{+tg01X`HU6L*d)AcrpMb6n#tyCN&1r2RsD#EgWon>FI`y0jWI+JmJeY>yqpXw7*+dT1dS>SIBaSyt>0Tj^ykfA-F0ugn7XE>O6svQzblG#Wxm{tC+vxw zdz*0gZl3fAQCfCs!QV%rTi&W@O|+@K$~CYS{VHaiK#&L z+f1{-m|mB-`@SFDFt=<7W;O!3c!xW2Q)qA=OPZ%ZSX^NHQw>+TWfjm=^i`MkSR_S|qe0Eo#Yh!U+O8Y(h~lkU_L5lx zwJj*g33P=tAmBXO8dXPa9s|>#^|j3_BYQ}T=O18y(ryIFAOt1O>24eh<|De>PrCSy z?w3D^E;yU5Ztwt`WtmNR+?#KZxn(y)u#sKh+?I>2?Gcv=60nfOodF4_b#(F>uO6Yf zlzo`i^9txGJ`c?ttc2@cSPlqn8i5g^-ulzkY7EBt(oPJQN%Ra`3m+-F`T$@`gzzDI zOQw5t9c)e`{WWs8uO_G1;J65qP3HsJa1ZZ7H#wWs}K4;MZ%GL`W)ZH z-f6Dnbp_;BqmO!J8u>|9nVUD1$NQ zjj>cza@D2%+h2QKTrB5c`*bC{lqB`)m zyeYaP>zKreHPBi*)-G>io!treaW0guVSqBooVe*r(ns6ZgI;q#3>)^|&)4D>5@bmQ z>;yo4VZF~-R|7#(%wtWWq_*P#6+Mh%!16Qo`+J$1G8Fe003NuGM{Cvi&_gHRp@c!G z857vi5swo`2{b!@9=L^RPejkV(}_uY9LTre&~$Ukq+J>cSS45iwfb{YpG{ziG{H3l zzZ(@~RYwiYoB7Z1R?ka8o|8n+5OEvvxpvY7M=PQ2x6UEj2V+s8_@3e!AJ|AM-iE=J zxj{TuCF5a-0Lm$F(E@33gk;~jNZgMnkr_{*e)kP6eJ-d0cm4EDk`vu>YXZiXtBz$p z`J#*n{0st#d@55)=JWJ|Jyu?hAqMHuH65x^`BX(7B|D%-oMAQws$=+J1%fTB4o_J7AH6kp9;wy zJ1y^wdo14>ItCI^r6u*mFPJy>S5N+UmDzA5HACo0HZ_1Riwq3%QrpHPC*p!i^5FrMt!s zb8!H)Luq-c$hLiru?O42xF)n}54<4oayjreMYS{$4)O2yeDqTIJ z3+P-o%;T%e$&S(j=ihiDsg?HrpN3NA(!4GG3VEg{#ep#;d5rdXpF&aqOyB}I4Y*Js zfLOSs05lp1nq318F9;}-Y2ZT=~)l`e~YsIUy2OXKg=4p}#ysOYc*igKlcMQe!je z!ho$Sj$Kp;M=T@k4>pP88@h=3vsCY95A=?$L68RWJ8FK-L!Lu5``~h*!BMK5gbqUI zt|m06Sn$n2rfHzP$|*K<)kwE5L<>U0pjjZDRTQiMI!11sir@xOq&D5-rH4ULZ=Y-N zgVgk7yO5u=UHuJiaPE(RDP&5-70~U%ks%weqnZGiltCgC$8>n45h63|v{jtqB6H7Xt z>jjB>UZy9oJkmnL#~8spYa%kofxtp=8c<;XfK9k$1~N+pG_V@12hf|>4Z&@ykD@g2 zP<-AAmJj|5)@^>&r>!khz4I<$(&iCm3-f9tNLxTP;paBrnLp?w8q?Q%|lPs1^R zcpLpD7n7R}(`sa911du@6|5j{U3LP-CEMOz4_3LVf5QCtNm${NY$UP8-g@QdJ4I2U z{z7cuk`99G)WSGH)WZ;9sWrQe=SD*E&u=rMYR~P=h3r-nBZZLJIS+2FOv+#dS3$1w zXjEm>M0IqbXcxNU-<5-wpZ-N_SIdkRyTy>H;)e2jjdnaZ+~XU}ubPd8Wgcgj#>nh* zkIg!%ttaUr%ri{$*J zRYa_gs1z-fz5x^Htitsk#tst-=KQR+lP=U-VvES(`j?hU%eu_8+F)`ku5(F{#PgKO zPg13H>75JBXzg4^g@%Oa%H`X7iAC)}=0TLw?J0NlrB4hZG&$P63^~04o!7$hxN$p2U4@mL}ID>j+L>fNTq}s0^|11#K`eFqi51$zMs4NTv_AN zoZhGf=xFI3&Gd8?c!V4KL&0N9|1yz!8;fi?tzDUjB49O08e+XA1dZKeV_aeqmuXsr zI8amh^d-t;Av(#CDQ)6yA>F7eFDAM9u~$!y47y1~>RN>kJ(hkG*s$a%+5&rqysg}%KojKbKll^9=!#A#sD2Vo^Lzk2Mks~-am2PA$s6{vDS$?^c?RQPpv1%e5USDk8 z7)+zFB1$HHFnl>tSxPHvKujin!JHIz^CL-RNxf1{%n5p-f>BwXVk4anlf0&FRiR_o z3;^!426ywDbj7=#3fVSkmwEB@-47YFxJe>k%zyHD;hqT;+kH+VI>IJ(&Q+_USh2os zQ=NcWqi>l;#JmjbSmxt*-`b&ma9>#}&pONq9DlhpoNNkCWFz*&6N4Om4A$dvM~>0j z{;@bxgDVX(XyqPFxQLZz8rn{8)+wA^uBMSU9~KNAGnucgK5O;4)cEGF&b*M>d%Hcc z1~e_y`W9j{W31RNP<1`N9yB!}0vYe6KWTr!fk~i1kR!lMU5NA*40$mG7DU7rU`8nS zydZrzUzs}orMb-|O7SNk7F%pJiYEX;h8F~bnVJtUzEl6;fYnL)rfRqr?XUSwq+U%CeA{|Wo-Rtst+TwQ@tZxJzmM=={fpG)y1dh4Ch(9k zviEFO;g+{OkytP{D19<#d|&b0Qr4dDebowNd#h8rQ^O)9;vjSGZr_JzPwL>Y@y!{` zM|jaEAJs~~WFw4iZvB}*Z;1qk7k+0*6_SmblJqMr!NCdKZZX?jjsSEJLYB_q3|z;> zS^tiW5&@6?XX&pX4AxEXpxc;zXth_f)MbGVYAeHWXqfNGB|(NT{ffJX$Pi!Wi>Lrx zkY(GiL&vXepF>a}7&BZ#>=#A|Y^D&Hx=?|D@uK!0+Yuf7$@neleBD9*yRW~n#2OoN zkCspAdrn?4vfh7%#R51y)W@!SBu5se6VCidc1B5-dU8f(OQ{~bbvZ$0gAz>A3gD9cpS)PQ?JgnAv;+t$F^7r9hZqK zgGS6K+i`*L1ZCVF{$>!$|!&W*fjD4pQQsb8t zn@`k;MZ|N$9IVvKc;L#nD%)9cI$ra2Vtimz%B&jh&5i;U4p+C-`~upfly;$mA5_qt z-^mJqtQe>LH0z2lbCdy0y;r0PFF|+(En~uR(Os1)O2Di%NoGd<)hSqRojS`8F_6e6 zOG+ccjk99Mn|&a&cmI(|!fmfN%VL1Lmd_g}BJXddsh=1zXmvK#V83ocdd}v|Y=sh5 z%i`#5;{FT#$miF+f-m5c!Dzt9!SDElORgZZfS@s;Up%qy=@sFaA6!^68h8U%%5xOo zes*JgxG)L5=@(-6OO;)da&TWW+C-o#TQX|1Csw-wkIC)1+S`RuzzHT&TlA%u6!&4Q z9by{l?q{9OXd6GA9a6Rm3#507my<yYf@>d)jn5+Uu~YUA?)Gi_rU}#O}=-#6Ba%BE=&Wi zpE&9GpP)c{-wn?;`&#+VEahdc@p7IxZ?#A3$3zI^Sqy=mRd%olrWpgYt>)U)qFKM9 zQ{d*0Xk7|WwMs~9l3B1Y{L%xu!w362KlY(veFlQ|18MM;86-GfVnlI)gU*$O>=C^ zMXA7U-u^<#bgt+)+@j2=$2xN$Y2RZv1fRS{`#V)f^=8vuA+3>BuFjco42Nj&ivuA_q@Cni4Zy&Fkw zj;VdI=R7}8Vx7s9lmL%w2RdM7@<3*!HN>+A2PE##*|9~K z(jkJsKcgSSH1!bg92wHc(bWwcWUP4X{RwNEelOdi1QGY5h`JwauyaC@%-?ro)rJTx zh2xvC3~}ncj3~pkyA1EqE~?Me#kPDrA9RWNkwpi#vnLCIteika5LmjMCKX9Wr19}r zWB_sDvc1v*h1l=tS+jsc*~)xQd0+=eXIRYMh4bFj5)$s z$E2uxuqlrKKH^OFZvm{}>~ni%w2PF+ycUt38D>FWbZZMYJ(E%5BHLF5k+%8R&1SZ+ zX}HQQfks&2B_g+d51>H_o_y(Ga zz5(Z6+q5>km}~J~TaPdPyuN{yzzV$m`(RxGC9(5&e*#*#SSUfG&Lz;Yc%?io?( z^og2*D$s7gWyWiGT!J)%Wa~rYH>ZDeqJr89{9m9*v$B%Uv3=(_3dN~lg z`kRf^t*&Xr84&9$9;N8o=Ot|H5DoWfcu+P&>nT#`;f=T zk~qPVP8{Bl5{9mXS$5(k36x1U#wl;V#@nUR23h&H_AbTgQ>OBz&GbFfr-n6NW5lPs<`47{P=&wqm0=G5JQve9Bk1|K2s?4SjRSuu=jVmny@^~zEU%jW%Kck(_*2wCdvNd!A^ zyM`xrR73T0KcL-a>Q0L-}QhzYXAivoipK(B@?Ky<57@)LB z0hd65Fw{_2bs;jy2yA2!oVv&WPrx3?@Luea*NhoDui*T2G?&YxZy}dz3)RrXz`4J5 zKXiaK6|#sga_L6v9-9vbTHjP>ITwhHC&^uN=p&dP*;4TGOX5L&Ag3t1i8auV5 zLF6z##zV0T??(h8qCq1?Xsb%?8;0kJ+(I(iTpoM3e3f&`K;L15eMcVYBc_%xDGU_Xu0lR=THR(gky%w$oT>q zhP>!s=g>*m%gP^l#t-#+AX%ReJc=hHj`v{CkZMrF{Z`n8Z}LnNMob`*;wX|dbbuLB zXS|OV0oD4&H?U>z^Ul(0evz4;=oq4slW>LX?qU6}@{D{1-iR@4wj8IrFy+Q6#XQUH z8R+dvr{bZ|J&a>xE{y=lA&P;rB|8Tj_Dz&IQK2^P`pNYbMTm4es!GlG?=Pj2bU#DH z8K*)4*yI6$wTfqBOZ4GOcZxN+ssXMpJ?$>3CRf;)bOg<)cVk_dQ#l>3;1^+neH8~;*~hXeSx~v8pC_&PyxvzCp)2Kt;paMnwT==Zd=M?ia6^&_aub$g#v)8) zimBle{cY-3<#h9s*FVm5a}3v3symMCzMvN-SIN>l3r! zI}$`wX{LQ_$ATe72(iWxW-P7?R>yA-4j&RfS?}f#-8p>s+0*+B z`$_JSZ$p37KtV~iDA2ls!UD3tOs~#&Ica`~i|}-I!`iu$*X%4aJJ!@jv!HGQ?5x8; zAVXxQaq`zGp}MVfxI3xCvxvqr?wyQVQ=z`O)q_yDH8R2~pYMlXDQpEEfC3SyVU8?A zWQZZq3n4%N|4I6K95`0Li$z_ks;;gc55cC>_7pnN$3Y5Rs{{Y=GT}00x6|Vk{@1yGp&9^I2>bW znd*2yCBinz^iKq%eN82tZ&NmZAO9%7+hrVm7T?!GG*i+>a+Jg^W?V)ycLXlAlI$WA z92kVv32);bvMD{K67=!^&csWq930;w!%8EvZt(qO5jxD0mq~(?j2?$kvyA`Obps9s zh=)2d!x(Ot+<^)Rl6;8lQ#$?84m#ct6Uk=8%y$&QDVP(ul>K|W(&OChyy!xX&7H$|MEWqyoEWL+>r_H?3P+0l9}CB1w1!uN{4-$(;$ z6@M~uHF}EnuEru7X(qFtsC+wNl(z+uy3Dq<{@l^7an;CvVXZ0nFO{_h{YzzUpg<&Y zr~=(D+!5F*{!O+U;^u77;K*^f)6$t$ZuaPi-1;50kf~gk!yajWg1Zz%`<;i;AC1tF zzv!;%g86?YX<_`d-lQ8I`6ep~H(0=gq96<%mb8Q6ARY3H5_c)Pfp5#b@edm9n7tu^ zuWz)t#Z#IfmdU7TI?QU(ZCAs$yZOKE1~lr70>in^Nk=|0Slx3B8*0w^lzH$FA!v<6 zpRS)+C{($WIH2b8sH2x@XLCz&eBQrUk+~U{Nm|P)Zv_Qp zPm+Y3?`)f3EIAKKx34BQwA|FrxH=CS)gfyjw>BtDALbM4I=MuNw$7SQ1XZ)zw_Uc3 zHo%FEtc~=?>fl=+5N(E^*QNXq?PsW6_P6!1S6^=`<9%b2k!O2r#s^)cyaHJ;;6G<> z6b#C1t5bf8IdfZg{vpRiFObo^H9i&+=Cb~fLvF)}$#Pf01)-oFQLebR!kW?mI7oBEDZCzGG6)laEDT>N&CQcj^HNW9JbwkR+n- zw|y^;0-KZ4TENV)XdcU{UI`aJF)XHpgUCj8{WV}Xe80zW`Ywm5)-W;3+G%-?m)eMZAwBrPrvyLo0DcVO@LqR8DA^Ym{7Yx)^L2! zWk)jytJrRtKPR@vspE7Ookpcw7yW@w}Rhof%=X$Hgmw4 z`37saYm`d)v7nLIEi~6GYMX*oNS{hPyuK)ut~VV$!dwl3C#(gO11pfYK}k^f=xgzz z?}>^&OZO{_GQ3{y2$p4OPYf=UP{tFHYcGn^N)O@*LcF8FlsrC6(=c*4n#8m#{YPu7 zOmy4wmkf$WXRKRN+$m5Wa5|gO_)$N4RO;Y@f@zx&>a<}@6$_*HW)fw}qDZ_(e;DpA~V-A9{d$#_s(c%p&!U1$F|7o zm}cmJktalcG#o4l#Ub2#5;am_hOVhz2QU?k238of+V^Wi20STABq8uW)1lTNH9tuK z^wzJY2c>O0ngp9o3f;ng)Akiy-GfdXVc*+M_eZo3Ql4ayw~_>(_?27vPwZgXr^Yug zur2hl$Oou|g$J)esuw5E_=EUv2S{7Tx|~Nm%NczIDO-4Ywqd*F2tXZOs@6Li?qEBI z<%>6|VLRIdb{W3>&I)?pk{(H-z)JJjQJiA6&Gtwh>2*gJJZxZUuSO0#!ODXrV1FaQ zdT=&lPWrV*n?WD7CySDNQl&Y};|^qxAQIyG{(xe8?ED#HL&4@`(Fvupo72CjB%#{( zhyn^!B$9n+;S85Ga^nx?HzIc;O9hQ*Ebv3|NCZ2i4G!#GNQj`0nW&zXh3dei5uk(5ZLk-~9cy{q6ZPO*&C(c!dsqqrNzZZ}UViO7ULb-i>g! z(<=59Bd3}pR$yJ7 z#hFyM)27;JnDJrLDHkDC0-OY+fe{0(mJ=?;hRjM7`6qW!KI0_v4L(mfgn^iiGm>} z!8gZ_RYtXbyU(VP9^oFGI-@-$wRkdkY6b!oHp6f$CKALb+tAW3rPk_BA{;5P0?ap5+T@~J9PI?36fUeq3Q_WBEJ}*e{CP%_$e}@ zqeJ;CdfqVb&^jW6QHS!G?4d#K=wZxZ+GO3j<(nD92T2k9{wEAppKSMoTKa@Z=d!P% z2e0W|QUs^gHm9{K!?D^ffvBBka;|w|WZ~*TXh8SeADzN*vu+?Esb9=z6*SI-u&H|9 zU=JOyfdwRC~%j}vall&LrvFq0;78luO7;NmnBf5gYwznH(NJ%@k%@TRcM0%B{MwjS(2;Y-55rRyJlRwaW^Es znl7!B@5`UajNiS+YvNaD;B_~ad~*Qj|Hpz|5*Un#3S{m>a;U5bMjJ=8|3(|7 zpg=To_?eUt8AllMk}$0Q?5d5d6U*JT*yYNv5-NJmN}@8sOr-ceOjCW-RNM16Lgsje zftW)*XX)cQ{T^Z}zdj#zu$8BjOVaOp_&BB@GvI0~q8N^*1hs<#Yr$aGSJr^E!;pG2 z3fg~`$VieEo6auXDrFQUE9bBc)L}Ws9J(sR59kk21a{_=HfA%(wUuyfbT@zBxp1F- zF8lo_*I(=j@qAQ7iW4Ce3Ym+`Dq#&1-(jP9-L3;#Y~HZQRZ$21=i&7vSddjZ3VZy) z2l7)@oukS!B(yZX$+m$=_~~ssL+adq<$7W79(zB>bZoR5jkQTfAaD>U<^hXu; zbaA6`^>uq1MBXZP*rxMQkY1h5=?>Nsv#!JL9SqRd96kSqZKUDfjrlTA8aQIHR`-AS zM*07S1!V^I_q=sm?>2F9;F3J%2Oe9{ZOUWs#^5il2ld%S_+9So?31hYgE%{{NT78& zVCg|}#01=-tJ*(InVx4cUL&pC3y64yl+84#fK~|=d~6`kMD`2ELbz34FvwB`!WH5D z$jnIQdD||=(w(n*PU?!{M$B)-I+nkHozilSN&}Uk4fv!!pu2dJm=`hz7fEkT;dNuw zo>Larp>C6SB`Z5nlx7v&%R+F_Zc&W!B(vuzpRiJdh4)%^e1V$!K(ol80B2%>RM}#0 z!5b{?Q2mxDjXd3h4r|?<&xpY2d9Jx+@HGVAoFKE+)Mai_n$}XDZ7rFJ4fgeKD>cFI zz2y9;#a3=Bkfkp=saNVqPtps4z)&zC1__MgS2UY|z*ZCn@}Hxll1|AcO2(wyQVpi? zZ{9WT%j*#!HUZ7q@-@nLpSLIQLnloItwtt`s#Cc`CpR`C%cE68v*0vUa zI?#U{TKSG9a!;b1=OY_r4YHn|JQGuIy3_BK__ux~+P>Q?Y@;KghJOWTD9b{4XZr+i zSAPwA{vM7S?lL*8TZB&MMdx6#UO1=JKXF6Vk3uu1vNQ>X9lzPIDv}>IfEU(G-WYXIg0G>*JnDO zpQ*U|TQiWF2Go{(!SWWdYaYK89WKy~diHlgA`0m@^<$n@STt=jJPg#gtzkxlBCQC7 zhhB~$1yw>1?&SV~Y2$h4bj}%_%sA%p)_Kmu!hH5=@$j3}+Aya^EQM7kN0X=jPTvZU z|C?ig0I{gSx9&n@3J}-|!?6Bi61fz6H3dyTDo7ODY`%WoU zOSXWX4PyV*yf+oZ9@gSLLz;+hG(bjMinTZV!^Ssj-AvAy;6G>;R}%#E@_(G%%(>Q>5V4+3p4vveN1HBP~qXznb~OlCRC0QOlZizb@>c zfvQ`z(6WO!19CjB{L!0*!qB_zRHkk!S0d#qa#uE$nN0!YJV4PY)-f{x6#{?MbzcpuA?jyt zq2IE&YQhsR0h9(o7-~dAxRe?)i={878Z;1~fJ-V{LPxc@GuP$h+m?Jq`{M$<8D8&c zZkTEtTq7~;c{!qPY)X1}a38rwHypNHLyn-4HE%*~C72^ZGD5&E>D*M1=wY8!pVDAI z;ZL+SQ!3N)czkbJUZQSJt|2BBNC2yknS|cpN10+E-MpMaGT{UfOj}aAZ}E&ct8S6K z55M_jD7>7FqP7m|-Ytl}aJIPL;>R4{EPe+N%q9qIT4g-1BtjL@4@t(|v)DY1_J%a$ z%H*s_`r#LayBv%LUUapGF zsl1!gIs0f%9eoy0PbqLG89eEEuW%yQ2NP^7HY_dfBo|wMPGH0{RyWC-UF(6SME4BF zt7NY(nFy74^HX8p0ab|24V@*&`jt*ekYq-JJj&@RCRuZ4)_(&aQ3Yqrsc($=F%PLs znvH1cNBAJuhQ7y(L(;tGHXU7j6q6v}h!L%bP&?nF4wCLP;EKtX|9w+x4N_sUK(cCH z)n)wMjnGrS8*C}$q_X$h%HO8i9aaNvu_hz&Pk^7piWUqNTLaRbYMVYA6}Ueb4i5k( z0~`=Neq4SVDksy=s2~#*O$%$PV1T_Fb54mzzG)Uu$@SkwI&%BZWdz^_2oR4r@~JgM zX6x(tFAP)bi?W1JC#}6U`>9@ERt~-G-X3DW_2fH`XAs_nU4Jf!r%<%uP~m`qcKd=wE=>6DV+M~hElS=0WpqyuWTR&F z;u$Af?O%HzJDPbM+C8UYA+gT`kDeH>Yg^WE)N`L`^oOiSCq#ipIj=OaN7jON(R)1W z5||7o($@+@TnIvku@YweRRv{vWx@2{|G)hDFJ<3@&>)dRCFZmIeku1UgaeZYXV;jv)|Yenn472ZFHMe!fbEP3@O8B&btQ8- zjNUB3X@qt4u1}<0v5S(HT2C>SWoqG?I8DvLi(Z@ASU*Kga!hl8fZlH=) zFm;xCZ7$T+#6y#;fRcH|!?_(_vWF1(G zIb!bz+fXBGr3!tPFJwcYngM=*012333noHj0WcJ#|GFyiaXQae%I@b$R`R%y(ImH~ zcrfgUGf?hBT!P9KE8(4jr+cKH)7rO3UQL22Efi1;x_(e&RFmrr@H=EnAwE4VuV)A zsp_BjUf!3lPZ`qrkj>=v|A(z_U=D3vqKs|Zwr$(CZ6_zTZQIU??VQ-QZFA0jciyX- zs`&@KcXhAEw|cdA|5@cnUeEZEKFCaOMMxUJ8IjU!A`C`E!`7hije{#f&a>}}GEBK=DsK#kmHY*}wSfTcflF9)c5r;DL< zu~MdXxR*6l4`FD4igCZ~4Os94r5Q=tJ7%hIWMhRc-yM28iGJVXn?X$cPi58WB`*GM zTz13_mF^UQXM{u)VcAvA#Yhq*!nn}Pf4x540OCKXxfGldqXBqx5iZexP*&1VbOlfV zAOv-Mk|8G5f&wn!3myo!SD@x4KWLptob`N*3}JW>ZzZg%r$Wq20w+&ZGY~qN3b3{l zs&9;FyTk(&9yY`^5Mz_5gHm>S+F8&OI7Ieo_f%D&yjB`@I|Z&Qj(opGapCHZuuyXk zD53(aSVN+v4caUf%No1eB{kGdp1j0Kb`xkT0qCF<^fqfwIW2(arS-OQ55Tx|p{PBD z+mN`~YJL2{*n^w)wPimYi5pLFu8wnU@fgrbS8Yoom+3di!uk+?TtYmcEI`+^oSk2W zAPzl2-n@H}S{MyAde!=SFdx6O}qS0XumK$$tF&(Uh zY`8o5%e!iAiHIJ-3_48`JYYkQ-}L;V=gx{WDN_wyHF1$6DO^X^@}pCAPjS4OFS$My z8pGz%Ycw1;$i`sUuX`N?;c?|Y@e$R29=LOyk4eT|nA@m0rA>Ut;p;)=>&NIZOY)^st|JyMrk=V^&El53&^ z033TrIx3ZPGJ?gnN1ur$>4hD!W!=6SEA``yyl76=1GC7XqMr(+@Y>n!J(kLW<|ptS{h@pENd#jqvJhQyoYwnPO=*iX6a z+j`^>V^?Dr&w+Z8T|t7%kZ4MhmGpu9`n7n7--F!0WC)92v_sMSmZ%6PR;cj@GRkc+ z_Xa~>Z4L+9#$?5cTQ}!6J@dJQ@yYthQCP@)Y*5H+U$$mN?ib_x8%VkFuLhyb(K$ID zAih;DN&c$oG4@j{jee?%1Ey?bc!`1ii0saDXE^_`Xmj);m4Sz)n;@n32Gshz1cJx= zG00pd>fT++LQ!2MlSf%_M}k$Nb3g(-Ll>*xyT?iq+ZHH_`9wkH7aMH=$rO}gkj+WR z(Cm=xNmXANjtF$|JYr|_*ecAB`(MoVz2York2W1byLL?>oRZ0mHayF$<{dfs4)E&7h^?Q74~C^})V958%?w}gqwBk-k2f}a`OVvxkv$ll2 zIY){*AM02?@^ctHqc`TMs#lL8irpNTm+`B6to*USn#C$J_H0$>aTH6V)L?SUIwpFA z&0MO91?kxnSGA9spD5|uQyt_v&grUS8my|ttqUNe*JuCywFaEmS7f9BXz`;4%e);3vc}~k@DQMcR-3? zuh`GlWdI&Cx?+jor9@ZWpJ^VD5>W#9pCGFTNsJu$PmuARfY1W?wA=CxJ($3lTBYEj z6h8)KD3v)Z`U2_2&IAxB-<~W$Y(F4rWyEzEe7r7>m8&oJResYiqePhB681Q`kjKfp z6<;&@=0Xo>h8aN0gkh6enog_3kb)3V3D?lz6vbfIR+>>}{xGLGh@Zr3zhDm^-kZpd zTbVDXK?4&N$Ec_c8ay5YO1GV8*wr&{Eg*gkqb)y0O!FJo2BowzhDM^n(5WceemiB@ z5^aebaD`X>C|XaPxLh>@CipXXG7&<62RsAI5Fm=If~Nh``Urw#RsS)agol8vT~Pr( z1~)ZXstTkIVNRlhJl)OkOK*6!&0wu@oXq#H&JDf59U z0P_NSsF{YP4v=ZV?0X=GTWUL{QH`Qqzg)=-4MclE?O%3||8KGJn)PpKl?up2ObDM* z2rR<@%aZul6kvYbO52lOsRshPDk!lB`6w@tDbNQ6>IwX$f}ZuD zpz5mh*rda}>9KNFRK`u`5TXIaZ3n9@LO0|ByqWu*S-`H3+jXaOjyP)~F&Pf&0=&C1 zWUL^qH#OQ_yZtZres)D}(T`w6v6ooWeZL)3Y=qs0`r$m4K^q|F!}XfRxB2 zpx%FDattif3MeWNun$0Rb1V!LV`qNB=h@i8U5wll41>fZZ&@9s#p_oXn|qf(?TcQ+ z)DQGvP+dRvj9C1ySTS<$$m}$YChgzBsLI-U?_={xrQM0`qFKWu?_Od(4`Qvuh)rQ) zlOB!xaAr&;`raAG$o4h#Z!*WIGm`|9w;^S}IY+&`DGh8LVm?w;#Qr?Yeyu8|e7zDZ zl*sQdKypm%)j-0gNT|!QyvVi>(lKTykMbO9t|YWH^^E_zCbhFu2#c&bX7XktWq|4* z2bNKQWlaJN_-6pkW);d-r0`{%SvCbg!hYj^-zDMG4s5S{zi#g-&Z#nA($yOtMeY~PJ!A4C2QU4o6M5Au3rSRrs9@g<)yOi?%3;<93&*~4f# z%7TuK5yqRhr&K}qnAz`fIH%z_p*66IuMqz=xSZnOL39ETB^nWs#~olvIY?sAzj;^a zvWG1VX;x@dZZJn(Y$tSLU2g1wL5!T$n?}bTt9PCPjYsrP6?sJ<@Ah~7U0=ps)nswx zpTW@lLIAvS9hRv>Tsh4QKb47HRv*;3s4E{$lvVAsJBWZ;b4mT|Ds6T1F{DsjZv4j2 zEno=Pd!u)}Cj+$sz$%A7MZ;|y59*nfZ@7#6;_Vf?v>kIziLWdii1n3OLD*g zH10^H$8|U2__%KcXoxfSW%oJK&uKMizAdbA24pvVRYAordc3EA&P!up;9r3@mtF~UNBQG8u3EA2B?0#c$% z0D1g{$y1QT0i*xGq?iZ(#d}wGKK$!&vu^3v5H#;mY_Pd?Ph%=tjt(kOb zD&Yj8HTN2Ip115FRP|2EQVtF8fL4?~fLyr}Q$>h~mft;d}M)KMVxM z8@5N!q@<)Vz)Z$WloTNSf8j44mi1&f?H`tnNbH@G0bSUQe8{Ub1s>lrBOhuw4qc|h z7)8nwvM>^Km!@o?WyG#gUiFl|=|rU*wcP*?pd&IyLtO3yOCMyeUkfyr!ZyutZlxRC zwSw{*!+)LTHRN9}_XlL6CWKG;3v~I2qAkP!!CbMJ62@2vM3!WByylsooN$+{z;&e) ziVQAVEzk%2!}hu5xwC|&$@?mk&xmywa&%h(#<48$Q1GJutUr`5YMNd}bWE&z8InJm zixtFlIX$htz`ewX!9UixHI{_D*21F2xMyGT++#N!R0x$~+>zH||8(p5VXjZx8=uekg15w)LIfhcM10LYTIo)YlY;Ndp!grsh%Nmyg*0?q#jEb9 z1eo7WR(Izd37C7BrgKmU6F{ZP?ty^4!b3p;^m%o_8YST0N9S;)#mmyMdt2&{9g~#_ zF5i4{&>u`ZOtchGZ+{`850=$!*dO3;mXObvEgtyDcISKp+VOx4S$V5lef7OCuVE&U z^pYrz^{5?HJFcM*F4O$qYE60OzaO3g$VAToT44k%Gm9uX6!M>(qe69wq*b#q*^)KB z@#K=SpNhR5ip;B5$Hb#A@%y-iURUzC4TZ-+MJ3v3GaO z(Qz))1M??mjW}VM)-jyh z<)&F5q1)Ld(}s#GVxF>|gDM0!&*!ibgsd*S;wCgyisEW}R5mpB?*U>WZ#hKn#|ef~K1aM2l^P%8$aW7~CV z?c?C%6;bGA*OFZ6J7KnI>Hds232K=~EP41zA%__t&^ek9&1DHCevt_cEYx#nsNv7G z)?V|TB}bUbH(SQl#lWeHpL|4<=&RLQ$x05hGL*pH=F6usBN=Ab(A9k=>@j_5fcW{X zo30BQ;`Ngv>(NSJPu>CnndfF$7FF$ZKC-y6q~yafESbljp4Z)Smw)J&xAs!^UZgR$S z0=p(P3hrJVO!#x?&+jfmOx`D&a z=h{X$BF&EWNQZJ?-iK8da20!aOV_rRzT19VW*G2WIqA{W=WYVE4CuAFqkp!M7_3eT z?CZ!_;8&#&mxIgdlMzvfXk)`~cTxYr9Y-|QGz;aw5SRTpZmpZe)M@FD_d>3jSD_Ab zmGR8ma3xemY43v18p2wAc=3EAvOq%gOHs2^kMR;VlOBIB zs0<^>&y6&lu=&iTj(?%0&c!f(ONgN0?kiT+@CraxLk=SW-!E81`<3C1%wW&t4v zi#w3<+FVz6NZ5xdwC#vuJdN@Msp+7EOfmb>M0+E6)?)1#= zf6rUARDm!Y2!0(75-0XOJBIJRG>SX>&d61(JrxvDZl^D?G;gL8tSmn&) zhA;L!jI$#sb3Z-3HP;}mn{hx+Mmy_ic%pSPaT{SThWN!P-!*C!RR_`IaR*~XCBCsg zugZRqtwD#se2?~H&pY~>bd>m|z(cd!M1P>#fOO9<s^Jztd)46&64;avc~si@1PDw9btEe&wFu}0C+Ma#I`n~&O~E+G ziE+>iH@8O>Vw8!FU`?QtEEOsJfLRLthP30v^cm;I}53=Wuti zMe)7B0zKg6aF)ax5)^IMvvP8g!WN*`gh&Y7{D?snOKCO}(H1qX8oMNoLHVdJ#(tR` zi&9~o4X|f@Q9nI9=8(zy3BShy7!{BlZnXk3)yXrVvY+EHfydMNCOegF(ef;lPHwGe zGze}^S~sg~-3KYc#&jF1%Ec77*>ybaPp5WQ$%XGdYkN+SmIop z(&XlFD9q9`IsS<@WrWuf&l~5|h7*b+S|FX&Is0%7H;Fv-%)_4q8A3ZPncVlCMhyrD zUzYA0|0jf>vg}q#q=H|`MWyk~;>@KtG-N~Hq<6!4nw#fJMD6GFj6s9c-kowoJhfT^ zw96OnH!tXUO=ii0&95mGL28XwFuI)V>aL(wJxTb7v@b-60w2bg>Qw#m_P{FV+h5l= z73QB##?4c9vr5WlIldqWeH2&fJUi^F0}7eYhPBx|G&Is-BV-dCm&$8D5%+uh>BrPL zkeSaLFlkKzRYqdp8R(CN(Vm34SL3*(K6cct7@H1Gs?roU?1#d}cdU3ooUa00Q0`Qb zAV4;~@jT%_W`fJ}er|WOXa!Sn-L~tv^byURQK!ioo37?0VhRiEK4H2bEDy+Crk_E(Q_X%{K^Tdb{R1u4ZAbiY3yIynxU3zQ-xPT8@O zs@dVB?H}U6r_6VvE`B#%eQLI8!&=egiUn@E%HmZWnpoA3CUwfa68!~V00tpJT8gq| zRSYqE6H@`5Bn`9Aq3+5`@Q5L$lWYB_QvR`qqeJ{TLG0O6J(A3yx5Hvd6S8y^etYtw zd;QzW*L(;Tz6`bXv$X3C;t~L!1GLo3Uu!0zYZqZExR% zm@KVXH^<3lNSvn>@wQ_*cDN2km z%+_BLP)Nz1G!hthJE;l2e_vB#Ii0~+lT4<3$4fO<73eGu=C>30q9=Y(k$QC^&l1Ut z5_EqB_siT4`|%bTaPo1=PKh*bz82pCcR=6eBN!B_HpL%nzKOL8R55rXBgedJm%_k+ zTz0^R)~Y|Gx+IE08=K!$2LzcOWOjz@AA{ z**Fng^S$hHt~tqc#J&&|QE;oJP2bWj{lXSGkTLbXgruha8qtGS9HnU_h&E_%w2!G1 zfEs=F32ig#|0Q;c;ZMUyT+QE8L=OPJqb?R8iOw_@s!Dln2wp3sHxAROy3&sJ5~2;Xm^0Z;g%sU1jSo4Wc+A#UL?k)&;9UC=_V&A~n15s6 zwN|;97w6ziHW;Yt_hP6I=}>q>++{K1hX@xP&P^~UcCF8!;}kFa%`;NS`;B1NOJ0vM zwyx_bz9yfbU^+kV-=L;driA5JO(&RUJ{MN7S~Z5@1C51}yX%zbq(bu9U7 zsbO${DENngo3cekEQQxB9a#hV$pXBF<_T3|kZw7yhfu`1?Bq7G1D{B2r)BJMG|w?F z1y4cwNHK?=f?2QaIE)DaK$Q7)>S~*Tcb!AG-i-&SfJrKQ1tS}3+})=z?Q}!0-xwau zn60Em#(O;6&Ri>cWcyz{n zp^dkpMG#c)dwx*vqS69}?Qw%SImgLDa;IA2JlJznlKX?KmfB)})f|v2$RcLs>I~Otr(X1>v2(GjsO1yy1U@=D8!=rWKBfrvf+41KEydRI#evFG>%-#0+G5E4=ug8J26bf zK-^n1Ers0YAKQ%2_IE}qKUA@URSLu5O`5Iu_Z%4K@Mk9i)V|b?t)#+0d_1{^?MjLc z^6Na<(AkKI)&f^g&J^NNA24|)CZO+sL3J7HUt2?ocM%BcP*vwPu=nYt;eqBn$V+pV zQllD*;v_=1UH@Hu&pBJHGS2<_VH2Eo;m*5^ZvM6jYy05hPBM4pX*2qbZQUZrwenZ} znW2@TubVbb6K$_pymKLgIQ6;1q16zh?J2d^`)qgx1ABX$j0*rHtY@(PC){!j2P{2jVf8@+>O?i2@d=Xyq9dty@H zF7vg@c}xrKP>JP=idx>sW+W)-S2VfEYMoIJLY8}nAL=!3`0RT}Nvi;8@dr(rui_KLuJeL@EA+Pkta!43A~ zt(=c$Ljj4*cofmDfb^thu;QH7-L2vM#)T3}uK9sgL7B*(R~GayPRG$XXLV=U0A}9K zn(30q)wjuPu<*?MQe8lWE*8SRM){HSUsFL_z^e{l%x%${2j4++-|fPbw~=~|j%Ud6 zD$q*|71egLc1D&x?!b)(is_gyO7|m_AQe_`Xm(`A2dCJWeqLI*XXXQCc1t36J2{u^ zmJxJ6znkcQ@i7&^CH>2l(S+xw=c0Gjy-bFvHb)U=VZ`hqezW(6_YZ4+3vSJYY)(i2 zwvNQ*Q0t8KK9X@7PyAfNSsX?VYSCcp>AWhl>6cMlTv{ptjgLtQPHOwvGIsFZAu)?% zG4bNvt}MGB6O^@hL_iB>9A?Ns!b@=dOxV4)sdVJvk9^kK0WqkdzYcB}h8rJrZidB1 z1-$jf2WQ-~^Se2=dFs;KiiZ-3u*X7^shC@15ZKBAMg+kgRxjBSupt8~WNE-6MJ5Z~ zL_jk){R}ON=KXMUABHBRhBSkt#4CR5IaX_=(Dnyu-L_IaC8GU=um__2AZV-F?big8 zO^a3ILc_rGsX#S37nhc-T_;75x_%^A?y+sf4B#bQwsO*5TwyZCgv_DPq`*YsU z33NYN{k8>F4ey%3&Y^Qnp~S#0MpkgS8B=2f=D&ZK8qQXAkhVT0cRkLUQ^T2pTW_>y>6moUjHUcOhM1m z*lRMaR4ccpyTzfkA#{~diwb;e?&CeKhm0^j>5$9IqLioNuw!mEz*yxqqoCF-57aXB{h0IhvvCYrF(h+C+RBaaQbfKtSQrGf8KpfsCW`nmYxaMe|v?)Qh0?19r z5H$>-l{ECEA*Q8kw&Uc{Y@SP_*TtEhm31GMH9T)%j+4v(GSe66glb>yX(>=G*sXtq zLI#@1?7T#QtL${4ycp0-WCVN-(}W4BH%0g(s{lNf#|hU9QcR)kTII`p(L{)945Y+j zrr&r;Vs5}UI1m-m!oLXI4tmr}USvk|vCKDJK|m|;npN@p`=gCtb{ykOjASLCSO>Or znrlGeI*m;J7vbLe+wpb0t>*%o8BWu>#28QAb}&TEj*H5l!QZg$g{#EbN(PUS33Gf> zMhZK~!?O)ZT)V>@Q;O^x-^k6B(lqt=iWpqL)+E%1X+jZ_pSm3EKPR2$AIZnn zbPEtd%%NZKJYuQU&yr4l@ZoN$PBD082`YxC!1)tV@lF1mpWy@)4ox8qu|cR}o~mXh z#fF6_cAygZ9%DaXTaZ6qu7UmaC^dUP&oreB-ub+|*Pd+p!>>`S?+ukub9FUC5zqI| z^;xT#%TF^NBKLql+&4K$>t-M7QKh0sZBnCuMVkQpJyccmNG`%4x4gYe;9*fgYpaET zc(!;9k{c~&G{!6`1B@pLPJ=a^&R3n7>g+(gK4JP6L-*JgKO4K?%@_(->nF1f?xgEW zdT1Sr6!m}N+pou0<@=xKC&g#-NxE~{;9qxV7$o~Z%%Y49$t&XvO6d`@85?rR0FV&) zFdYgda-&7MQIVfdCLGOBNTIeXsi6_~o#7q8l+ye0m=Np33-poBqb=bEIATfq7vx9r zW6LgzVWMAxl0OfeXlsx#cr?lWI8pkOj7Mo^8uS7R<(A7GC#inm_V6kLHc6MFhurP- zBG=9ai(cfFh~7-NPU5#?6=FTqFlFINR7iBBrk0_09qos%$J+T?SK)Men}Om z6peEe7?81OO2_jJ<>fdwNU`g`n-$b_P%oa*GFrPFGP2HckyP4_u^&*^7zB0p8^z6b zIPzv12n({s(J#StRe24n|IMFx{9M?~rh4!1 z|0$mj^23bx7=wMng}LM|3d?Wx7W_Li^au*%>cw6`z#Ne5@$2U4S)SrgBz`R5ghzRJ zO*7?tfy7jh%~r`20QB!~J9Ck13Hv;rs|q%D`V&VV07pL1yDe5(RC*8;FY#*wgnKUt>*GdSgL)$Qqf#nDSp+{ym@g zu!nAeO*>jtCEQEOkHOISkwANHP=~(xPF5AP8)*cvCPp|Vyk6e&H}9IH8~T5O?mDcRv3fMD*nym0Rxoi=edMj{ED_r>^-Uld-3zl-sQ&A7BB9MNUbUG-E{C=|7EA$iF29i^ICR|W{t*zMwDl0Wnq zN?Zx9CNennBr|4KWxd=$GU3=o`xlW?w8xL#;*-odE$NDiM7)3$%Ct-WF5)3Qq(KU( zZ*xJb&3t-UowE~%>BzK&Cm~d3%a1)#pFpt`H-MO92vpO8ktff3DR~+?uSwu9Gp1`D zY`of!1bGTlo~Sa&5n|L9pe5H=+v4gic*Bc|cI^!K`o9il(P()U(kf?Shx7OY--;-Ex zIpf2&slP5})T6XrJ`q~qBd`ogL-KLNVii>34PWJ@;X5%sARH$%yE^;0KzfUvw++^- zhGlYNtzh-o-rMQ6#~KG1|4pW|wX?tO6lND^+;oMKhIeI=O1->d?eQ>#3I z*C-mcQz8bYrPlX@Rm*}WKAVlG=-uC`zP9&jfup`pQ zQX*KrLj8L`+(OpW7SL^VcRfggB>D!JRbt#&%rKe`h@vnSV348eQKvoQWJ|gRzs}Dc zify*^u8|g#w$?j@9B1aCf%{pD5+-n|%iH(Y2irTF29BR&1^}T%yFr~5`5JpQ_baQu zqteEYhEC@^=9*UDK$NkBo_orXsZ(FSkcPSpB{0KRqz{l!UXTErx*jkxmvJ{9JSxm; zK^?ekDMb!Evcj&10Yo$*l=RV3ji!xq>4bMr5n`3!0F(M!4oSJSKjLIC-<#m`0K(!Q z2wpdu@_spq^+z+{UAO=sl>%}ys}n$)k?m0*-k^FxO`52gFtd4|Z|K4~2emu|BHW}I zQso8#%)mp@%BaEZZyC>KXeGbr)SsU>Be=)zhF{TfzV~%je5d;aHhHv&SCQ6Mo_$^P z?*sPkl}drrKcLuL4YIqu)0eN?#sYYR&{KtQPfS)ma2Rxch%@{L(XbI{d}8coNAZ#c zRIR<2sJ~3Jfu~zXB@rlWlI3>K6ag~ntfE6gaP%ml8nAl2#j#|XGD`HA@ z+odzE~{IA$KV*VbtpW7JI~oUlFDdeIm$!)u--FwcP9anq zY+66Ivh&mpNbVj}4$FkD&>ti1n%TAAXBv zR%mJpno}&dZqfj!Gw+1NLh{LqKpu@m;@WNARvEXw#H0_A?bpQf)DjLAK^&Uhr0EcP27kmvky1RgWV=Rmr70%z#hxZa^v(7^L`4U=*+ zaq3|wtz*_ivIXSo230`&3ETB?zWJ9ow)o9RiG=1HroN=3(49dCq$w?lXR2Ik$GRO3 zf}P){l`A3yOUf7$(s>YHoT#prRxBsk8GlCL&r*puP;i!2z`y~?LJOS};N5>@IB2C& z={FDlnD?5IE~&b~>2E2mc2i?PK>={PHh^?>X0s&<{AN6V7?7G8m%8?^?ia~x3rL_) zl}WrcYJ&#>=7CYDM?rO$X*0dE_!+&7~Gj(md_7@kDuGRwlDu&!4t;0oK3o*n7vRM&uG4V;GZ8{hf+r zF-iG$VDlN)A*GUf;;VX+t!5VcN@;O7Lo5aG=q;=AMmO3&-cR+-;^5Z>d%}3wqXU4R z3DCzGxmffUslV^1>Iw_9BV+tmIa!|jt_6bBrp8a~z_;>}XGKnoTqCR!@~_3h)JHfCX)Z*7?sr;5*cN zF4KHtYwe4CEo1J`IJS|!xK_Hggm&UDDx~nw%cX?t{bsbIoGX92w@2sU1+Eoa8{bCo zvys~qz>M(Vo8_BLG*J9AY|?|;>XQp`SgtQ0ldO|KB})znfvR6-XQ{@9o>6}^SOT|t{eoZFa??9u5qA>avr=GZ>Kiu8Y-+NW0cYRVGB_;w9-=t(iy8*o z_i@)=;2P-lXneLcq~8I=?jo0tDta&GN(w!UB0NG81r=k&0Z&aLGQ&TP1?D=8d|2k) z=K9q!$@iB@y#zR;vJMzzYsdcr2Nxn5U847VT|!$s`E`c|W!_dUM5p6KUY#JC-x-Qt z&%seTA9%7*Pg|s|=er>B#0hScoF{h0r`MXq&NbFy!WbY6U+NB(Bu*f6efsTWd74+S-o}z!ou;KH9(;Xl1JG78QeKEvZr;MRmrC$%M|qMZW)43lzKU+G8MW| zK;^sL2F{Xpuw?tAGlHnnVJER^b?s z_2XQUnrgke0EZW^VKOMCFc+3i( z4j|VpfHIm#OOw;l0nM6Qor;e`D}rvP!GElRJi?fIggd%XKmbvs&yjmkXh7Fc1v}c_ z6NyHyuSE2zotO73x8%NMYC6W+5ZJF5lz`%qGuYY|$bjRF^*zMn3_5s+(FqdX|nEz6AKH1%cPR&$gHlelL`k_aWFDDN;C2oK280KBCST~ zV_SA2Souf!Z?6c}I<9kKl19|#52@DTI=zAEu_DTKqHGe>Zy34~X}qCED%>9FF@Hak z`C>6l(Nz&AO`&JI9FrRe*L=p!Obgd$43gX-yrMDE9Q!J>q^7j$5-J%Br}iRWJx?!g zZdj-2rn~gWo(GUzzGZQ-h4E|BFtcrA-Nl)n*N$^b zVUzGrLtnrgrCiW-ISt$hDTbn6a&C~(x>eOJATO5t;LJ<5h9K9;5F~%}1&@t@aHFzs zEwUj9nq@jph}4MK_L7wow`tF-jrAw9E2`+dxci^cg$Emj)eq)wC+a8_mNpDA469DD za!_`wi-EH;YtgM4!f_qL%ib858!gJfD(u@TF(f7c7|L@l+)WHj4OSvInO_L`h__CJ zLAqr_c=ibSiRFKfom8?m`zlW2nc1@vpluG9t1fj-O?-uTPsx6uuHHs-mxuert}SC> zC9>#p#|qg!<7VMrmYDgk|1|K@b_tu#}6FsMOf?DVf1DpTYKs$%fNuZ>GEtWy%eUYEwrvspxcswW|B;^s={|E~j ztw#tNd0xLs-&xKU&a6p_bm@q~m1oYU{1|G^#G1}&gNHy-yzNRJ-mdefas9S^KzA4A zpyN-cv$MdqYn{5p;4#fRdS!aFQZ;9BQ&gf4U@in!qMr_kqdLGCs`zHTdc3%v6KEQi z$WfO+#WiBjI>_}XezJm=r5{~Sp6~pzgWsY5O=49TKTcp839TUWcNS;+4pWc`DqLiV zi{tUB=$33Gu?iSc$G~$%xvMUv)K7#t^F*_d(`K zNoWAQTW8z74xE>0@QzjC!EbYOwc3zKq{;Yy&X)b>)PrLF>)&b&YWE)*<;^0mL>fY? z0`b}U4pD(q&(6pz(k8ECR1Pv;P#S}lvW9@c@wgaIDndog7}93q@H`<+#=n=b&Q>>q zA%2E@AV*qlOKjogE|dFYmQ!-P5o~@drRt`nHbGon#yD5DQXa7!$!K*WYguXyYX$@5! zxh=`okE|N)B;T44x-q*?(@5A}P>V!F6}((?_+9k8eAtfD25DJO9Eg(n)&l>GZE9FZ zlBYa1Ah^JsNT^%pv{EnD_ud+JNtl_sDVe@feiOTLlT!`%TwqWQ*&4-z2|8HAhQaF? zziBNV_im5)ep<2<>;A&%CIT9p%Qcv7@CGfh>HfPb`~gjEk^}AP;?1-o3G9!Q=E)3x z>iSTYGy(!g$aiAbXFs+b-<0*j+QU6o-afodtD8VAq}T#C^nQ2$W;XmA=JcLmh=<#{ zv9!ta4GY3Ujl1cCns$$VZvRm(=oQF$ z?F2-snJUoILhT(oefO;G11;Z`nNkgj`!mns_}xz4@>Z80)~L7gbRRAxHo7duqAIpL zyG0n|t;whGlla{7J+dorb2w=g9*f|GdETzQu{<9v^0(B!1*`GN>s0*Q$>I}G!Ixcl zhzGKWnePPK>%fz~nNq6rGwO_`OG34o7 zFxE9@=UP45ft_T6;~f}byD9@%RlT+VMK(XNl%~{aPTj1c5ErdW<|J$&*{x?U9Wt^w zFwHyzd-4yZmJM02Luvv)$p+u7IeL|n6_1I#;d=uk+>sLhZ-2JdL&o0(6cfL;dw*rb z4Zo=#-3g%yTEj1L_@R~jsd=xzQucbIoD0Aq78eEprK1_eCv>$5Ng~RJK9P=g-J`BR zn@?TUWWNu!{_-dz_Ijtv?*56cWj``(n)vg0pIpvrn`RNw^1hRV=bDgibI5M@=8$DD z>03+ID+=fVl0PGVB~nX5fTfqY@zi`quX?-oG zcnT~$Ij7IFXSG)&uqkgi=0#&7AWRpvhTV+rXO8V-z=~H$Zc~JRa6Nc*kp~t=bweKy zDC4CfToj;wMf%*#=aN0(iY$*CfGP0}+1*vL;JwUTFN9qf`rpd}a?fi`_iLzG& z*>H&s7A{z%jRzXamY^GmYd>zvF7Cb!VTDT>z~pwO7&y@=?S!Ys1-51Dp?BLLJx`Ja zct65lJ;;&@!8SRMMEtr*M_?>JhUd5ym}>_;W)Xnkf4c>;=GEnsxER$K#JHc&$vY)^BZNUCY&Xp zDhB^$6}c^zdTDshw=JJ)deb9x$iaQl-2_XL{vLD}C$;t1$E&6+sBZul>?%mcLJr{* z%k^G}!_5TzOkuLORM9!x6$kc4ms^?aYJz;3;oz5-?ukl0pvW9m;RHlzy|GIUU|3*r zjxPf-1+6(@+#)=^<{-uK$>8Tqo9a6H95Nec9P=tW*W31XkXF$3u0Z)%$SMtJ7R}c zP^N*@kXY!i`i|A`JsGdOHGoY%F8pl@ce)d+DLz6*tEeJcoYGR|JJ9xP{aVExAsM^; zkD|>%^*I%mkR?aj5L=(F_l7PL8L+L^W_d*6@1`C|g#2^_@&5!WncU(ZvFbh;)Xf?KOYE_OQQu z3&AyH>K;dn?(xhBg=@zTW6be*Lo9Hlu`WJ^zg}~VAjoF`E3C_DDe%}FO$atBh!%J9 zy(u+8l$^InKRuYLq5nPSqFC^EBl-X4j=eU&605_y>leK#(GtjeHK(LBTE+DJ)IUv< zZqNc2mDykoN`GHH&=L$xh7_6?=k~UIr{xZ6EH-pTsxkL`C@$IngWX;{PFM~1iyZ3B|p(dJt3G=2l*fRqoxSX0E8l>_LN( z_1pIwjN75%_78XGB`!p?PRIl1?Kz*GD;=I^iEpwk4{tC!8q)d_!h?&Li#k_`x+GFk zDqVg#x}DJgRtGb-7l-&*5oOfFO^rWjK0x|vl%J1j585pIeO^fb!3YGg&8)u^;;zBL z0V!xQD2V`-wkn?T`OzizkI}4xWLfd)h&p+WG^*Hjw|)ocWsxk$zJj`N2Pu2sUg@>3 zd-DeLF|zWT#g=2y%3$=ej04>nDacjraN}-*p-vH`L&{3&y9lwPvq9%kWYICAL0$lw z1SreyGLsF_P``5Hb!Ak3n;^8+mxAY^?$3JV<>^9wc;}u@ zd-Ok`6Anp{Efx7E(h(+2acYA1UZ%ajfEm9>*64c^gBEk89rbNfI=r)-U;-sS z_;AC+2=hP;igd=q0fk5Nk@7*Q5iM5Ux>kFsj_Nqr_x>eLzp~<6M5`HiS*eIhOf>v3 ztd)*~G*G{gSeDU_Ge=rOuE{RPkAdqCgU#K-iyj0# zVyNvE>AznX+U2y6h8)1941zz}>)3W__f<_N*dt& zQ@XWN@XI2~>3H+@V**E$dLd|rjYM6pY)>k9HxK-T)xlNj)r5)_y*S6$$ zQiqCm=^HH}7#eF7x6h|Ade6P0D}j>re&#+BxSI$*&*Ltq$vr=Q`nKaUK(`GJjOh4$ zjR9es%zYhSo)>Klc*vMbVv^_r|HdYMo-H^@5GbALsoVRmHwYSAQ2D{w0W;+P;prTh z1Pha9-L`Gpwr$(Crfu7{r)}G|ZQFL=`F3OP`3)yB>dnf^N5;h6_(n=@H}ytMedlRA z{!ui-A1;5frx2Fp?a!Rov9_a%%a4U&!XgqUP*@qyPdkD9-j9sq!d|Bxa;f^XJ@*#^ zdvnXH&@v%DY;-3`lg7oBy*`5b_XSw5Nf(U-Px*cKNGwBwlL}DFUk3bXT41v(n1t#z zF8!6tD;DAs9AC>(u_SY$3$$5|Rk3lfEL%{Y``jYJ+ zt6DZl!AF+A%*hzNCam+}tg5UePCPtVFA^xd}961`8$hpyL z)c$DQ1v?c~rv(ECCEq&ec+^~~n_4OTMyVuGe#5fCdcWKCZ_?j)lM33+gMfuNQu0v- z0;rmWWg$fUgJW=q24zrn1w2IV+;n@6xOu;`~ z&oVlbf7Y$xLrLBz&n!lt8(ee8LB?%ZeZMTuA!1k)to3D<6`vBn!EeZ-!M6WZg=~SNWh!b{=FhP*+tcix3eThvwThLi(>+RJp+RLRLTe-C)>y^j%fb#;^s@bf?VgGTx zOZRSpc6R82oHR)=8w|+$i-4gm4sb+PkoP$iqiY_O!SeUyG&6RZ z3P`zymVMo_Xd}#bWuE`!>DbMk)4SOBa~XNEOfaUyVwajWYs1Y%%#w2BuYzlU=I(PJ z$Bs@>5isZW?xlCw?_=^Gz`$BsV_v}_2xi*Ws9`Ss1XTaBP52n*A{1nbTrNA8v0TWp zh2ix5lj2j8K2Bai21WDimZ)f7_no-i5qUrG^1Zz>< z-SvOy4hgM0oa(<_x2r>twh2fgwbzOTH z1lw5xO&o6)X@TB7XV-GV78-Aw^6fkrlc0c$oArw>-T(?~}zghh*vB-g1U>MAtTO z^9td`t@KMlaN^pjzm5)r2`L-7Ee`3O`D*E`Sn0W3wA;W3x6Q>8O+W_%(t37j5w`$x z8?~@)G5cS$wJL={M7i%tgi>Fu?(2(4ADqH^NWP-~WDll)+ynVnkZb!zwhdw&>O2rUh4Jd=&@Trj+VL--)s; zC@qZLy@30r(_;hGr5XD3tHs-Bj(jsZ`#P#I8%W3Jt@HBiN-(`#s}o-9%YSQ7WdjDw zHV9x(8is2uNnERP)5YNgPN zn85|0aCO|aHr1}pq5e=_m)MbQTfHWMR1Z+o1JB5d+0M9gccSe=`E#5dDe;7(%RUro z@&@L)meG9plMaq^43)KU(`nm5L-iF8ap8Uy(z$BS zWoozSxC#a01yi8+I;Sq5DW3#?HtyZrY(Py51}(Ima3p_y#B}t}RV+=S{bPzI%R;ku zmu`*cg^aeJiALE0kC_eI^&|0Adbqvmyavw4m*$S)M5F1=m{;@|<$(2|0*3FjTI^ZH zC>aFfSk^CA;xy6)jc2fVlhJE13)&&``u6sq)M*NpUSp>M(CB%T(XDklE>gCTD&qU0 z&=|Uzc)rkpc~t%aMXmsVs7tDdbf%3j?`wZ0Eq0D!)hVV@~2q6zyV5kH$5F&6T8?GPiBE;vMW zh`J5H7s|M7MnF$Acs}(kp$tFy2gOS?C>8JdO!4`Y5>q*5ZQRM7$OjrU_}^eV)$}#_ z<7t_y)?Q~o)*zKbQcefs#7)8#Wx9M(r~QeHbU|Vvt>W*?xi4?G#jdjCItDpy%GL%SvY&Q>H>2xwX>6-1YdGA2*pwhmQ z_FF{jrf40X<9&!jq$K7M2x*AemXuE6w;?w$Xr^<4y+)R30ZodcbtQ;HQCZ6C5SJT5 zLwrrOl~u!0jhcb=g@nKt^lKngDmFF25Zuqrg^lE-2`c6p0xvNtRcgxvS7P!HMtKSsg4$cx?6kAlyS+P9 zdh?cYHNk5l!=oG;&juGDOtQVqo!VW zkiU_hwTab&T=(ppj#v%=Bb4{qtvmGAS0h!~IW&r_yoMbgXg3?g8~>t9-SWEKz#Rs1 zlSJK9Jgf_k^h)}|k|R1~Xfo26w{S_3wS`&SlS9`8iVTc;6BBx|E~$~i z&C1f9M85rZdD2AM=f5YIH&6vWI*PwujP3CBIA!5u&g*Of(jMMB9si(cybRMP zx&4OsE&}0{gH%JP6u752tbMZVi0`!C&bJLnWm8L%|b>GIWorm@*=LM>If zQZW+t3xz##jQ`q{W6I{4Mo(TVO?nHzEnBcVdz9Y_aM;G{*Q;|=Ol9-Syy0*4>wzv- zl#6D31AFgl#6~azd}#y`Ne0r0l(e}fJ93V(MNAV(P4hg{0v*FIbeR>s-$Ekqd1cTs zkgPc1^@VT=AcsCl1Lp_uA2xOQ&W$>LYnMDc!`i;3FUpG!n6nQKDJ%jd_$fSKngDdQ z&Its+>UVfU{YBe7ki{4oJq5!FR=^c))MvN;K%DmU)A`^V@bAF>rT8I(%2Q(#@+a5i z$+IRnn8Mi*ch8QxtukAxhy#}ojXB?QQcCrKeL(6|dcGl=0=*q_BfVi_S-aUqzvf8g zAFlXtljj3qRf}&3m{}d6SS&PRp`H>}6(VkImhH2G6WQy6%2_UtkHS;?vCaCkya@z^ z?8Txbyug1hM!fomA|;oI*Uw9rDY%jOVxHG<1Hmp?lRj_q_OPE`cWgWe%U~A-%p^Gc z0`_Jl8YC~{DEk8$A;LTY`@3Y676F#8MD#E0-m*{UFjUhG+X94pNlIur%B8fW_*~IQ zVeP)*pPGs+2N^GgFD;Lf7n$mS9&x>fdyve%TXCl5Q%^NQ7B3df@Uz`fb~#P>fR1C6}$QMws;Mz%lwLQ1!up6a$}*%6MDnP zGogI8cH;Uh{b~562qYlxuBJN_R+PlutLkO+sc{Esz;!kSW~L1_CC>BuDF#8RT?J7E z15sKmlMuwZ*1qyMGYt5`T-^W{_Zlh^|1i#i()q7B1?zHMW0u_5Xk>^-%xi zHU1SM000m|?fbt@jlG4Evu4ciZ)&MGT8*3IbX{)Z)7hEiq-mEvx z?dorBD^ercdNWy#;`clua?@y`laIvqk&1}thf+-#9&coC&6CvaGS;@l%8c&vH9a92 z4=o8ZHEm{xp;Z=`d*ueV5^v8Pp8J;*d5f@gx80~>>*==F8%o6JB)XAOCJRiPG+4Vo zcc|$HwtsglQ@Mrk`70RZD1~uErn}*0dp_mRZVb?+<#1K?cof0ss_v#}U3T!EY^tpd6q&UJ}n9{RP(WkC|3N$Kq7 zbyQI?{_eG`JwxIEUo!qF{KTFW?$vI3DaPjdz_pPBQtcaXfv#&Mx%V7N>QEF6bttg8 zgpEasBR!x8dIu%37(iri2IJ8UfLHN^l|vL`lt;)&w5kSrdZy+P2#L%+bVOwyR0XaVAN0RK4QtDsk5%NEzICkJJ>+1hY4QdGfD!Ikj1k|D3lxO&lHj(1Hy2=ey6Rfg(n_yc* zSW=h1Lr{WAh18!&WoFh)2n^a*GN9$-x;~C1skz>jVBJi2G2RVN*mwwsa~DWkv;YE< z91|4ISHzijHsQjDVX=QHpAc=Jm9YH>tUfePbxRu?25{DK`6BDV|I&&22!%Qs91`>o72qcUEvnbNB&0x(wya9J;W|e_->RUJ95-5ZDMSc&gpEOPNOOIcY~d zko)4(eVy0QNKj|7SIH3HveRJW7{_-+!lG`-H(l#jWg70JE10pu*lq5Kz&gGb_>GHp zKW0#}tw2+9oyGvvO>hD&H^!H;#=4M*y@cIgMCY{C z?TjSy7=og;vW;xHWM&sFy?^Q)gw*+?qlQ#%S#nzp38KX<2&>rqb)2nI{BL1Sx<|Z` zjAPaLspHG0c>!p2v2qNG(7ld5vXP|?dtR?!*QXe>$nIbLR(=wit8t*%bh!ZJ2Egxd zQpFfFSAya5r>^xTU_@0b{JC!7jJykd-ukIBs^k2k?KS=wm$JdIr(dlUbB7`98L(BJ z&Ouphnl%qpfzf{BWVjxgVO2Rd02eKV*V*l}NX-!5A%pCODux=)Y*I?OtvYk!^2!F0 z;o60}Bpy?Xv2&?DPB_6v@UzrrUBX|OeY012_qMT37}5_g~VM>tfyG4;(z}5&8kBm++w8TIQK3MCP+K3b(^ef8pJ_eyeae{*2BY znH05Iw;iJ5&ZYPTGeft&<53?yRr-SRatz9w)aon2y?xu0Q-}I?`RQr0yvxcu_q6**xG6g*}7c>?EYDle1$EJ|C^vbo)2<>n6PRCLR$`5&!ZS^Qrv6HJg zp?^oYKd`E$8=Rv9Ko|{yNL&#`H9b64=f4efUpS}C@yE{@aM10oV)18YCt>r}<+<|H ztblzK*p+SVo-l>~uBjk#bfG=85~(z!DH!mza19}hY0@i?X(lQVa9_p!f|nt14?)pNd@zLEE~6=PpWHlvr3 zrnyGx->eKEfoS8#f4rfDEtbqrWda&s`5xYRIa9DOJ}W#6j={yF(y@<{@1~VKZ zkv)rg!e9fF&Hz`l%Mc-7VX5Igu^uz#JnaYjFpVmSYaBKEj-DUv`9M7xP_W8f|J~8d z!_&rbVZ8JzV%%4-UmIN{sxU5Tp=7Evrr5L`)V*I&D7e77D0LKws8E@Vi=QSCvcn06 zqlNSO@V6>jS*dxavdI!yfYjTj3q-3XCW;KNkJi=0KkNYr+ux<>%FmP)7g$LRwAfG% zgLE~pfYZkWjU?QQkAe!M^Jq?sng?AKX|J`CfWIe@7zXwznw(3crp#HUn@DY;sKDm) zL$CwA<9xjHg!L5h@5`P&PI_742)rM$iT`fA{E~5tsf5U>crR0m!6>Gbe$zb67yIDkbms9u zk$c1@Xo7;kLrH$%qCIFaA)X+ZeW__N&UneS|LQya1WPa^S8%#E6tE)$8y=ucUd)Ar( zcc3}G_gG``)=eDqxayd9N2H2oFkSaH?XfE5FD>s?R;D5lnG)EU6N{!Vk3AitS)Ad8*7!xae)OKklzk4LTuYx-l8HOW>C+|hdMC`5+7eWS&3esvOtgCBq= z#e3P3Z}5+XqFJ4)n%fG`v$d(m|B<;R;hjoq`ND94GB>Up$)6*{EZ8MqD1X?AT7>)tc-&<}+zC zQf?nM(Mw}lSt7`Ir&|1n%(Hs!3Ekq7W|o(*h;aFy-OzzyLnvx`IR;TWxxTF+OV^A@ zG@%tuxVv|O2Xml1u91?sOuSyvTUF>k3X+vlo(q_ui8(n(<4pLHh!^_pEw13gtm&IWK~35#RKsi zm3>KII+XK9@R^SB0TpK4*DaWE1M!m>_##rJJ&|!cS5V|ytkK(q{}NX|P9y*d9SU2D zSKDhP;>UD+J)DozU94KiULNCdV>ABvljZe5u?{l_n9WaSY6imoj$4%^C}1_tX)DKX znseOfc`x?eh?hFZ82vxMOP0I+URmvXHJ$p6VI1MVDR`AEMJ>5`eUX)Jl{XRJ#ga7r zWLAC0E>)Hj66FngoTc_n!>6BMvWic5(cY7+X^yzLZDQJBi-&93^d6U27H8-ztYPDp z`>3ZDt!}Sy<`A>?s|{Cry=KDRbTQ(*XKe$az|5%awP)w&C56v5{65?w?gC7uuG{2}e; zgoKy;i@!xZe~gVFETz8>*T4Dxh0Uyege8WlLUOI85?g^3(hKoI!Pd;3X)8s0Q>}EV zfR##%$?%4h3apEm-);n?m>QZ7c2;j3ui*4uubKBIWi;sAV9{DbEMv2+<@^Y(6)ePe zHNLqax98{OPYsXixzrUxFNY)-m?OhW7sC_x4n0Z|5}@x=5@vX#0{JovDt=enQjoTR zIel3jjRSJ*SR4^%Y#X69XJ_FAL%-fajQ(=7%ZS1t809=pJb<(=SCBi;NX1i1k0>C+ z*0n}?|B(v2LW_5jX{M1(9YW~=6PgGKsWVL5D!IFt#kOAW>b%5FsqVJaZ6RgQ8L zvkebxUyn)nQ8R+%D|GGr`=~w+tbbLmt0m#CVNIMIc;hC<#SUxCvugrJ1#G!J|6|ue2;#|=L#vtPcJl78mgUm01Rwru^_zppjNfPWqDERh%P;^*CV3x!wB z=&AFQfh+8AAiL}yd6tQ*VwTpagXMoFoe*9@(7>N6Q75V8rQ3n`W|_PKVU$_e7h(x4 z+NjzU!HO;HdHcT7>b)-X#@)!r!dOA7!mZ-4>6q?(`qcwRVJR>i|0_u^QBJl*?R77N z05w4$x8y(~bZt-x%ZbBaK^3z5EP^K$|Jz>LJQ#O4RmwS?`*RG|oZ_4ccy|;S1`aro z&Y%bU1yMlWYHyrpn*)6L-0@UUeoCq>$f_;ZffJrXyty_@eoDL!-~F#@bg9Rj!vVdq z?lf<4otp{h2Mrg$(yWcfjpQz6V+j9OyZJ>oaKJWKayc-epr%xC?BjJeBIc&an>z z4vlx;wmNy7I?`GpL%w!Tv1l~s40xZ*8dmvQ6jgXPwZojPA^rZZLHo~-Q0s1aPoh{$ z^XTtVj!%Uc?nEfOLd!Cwh`3<&*#TV(GSd@l9$Pe}gn`kAPt+dZTW=HMuUZ!6^atM( z8V%M^r#RZqAOGlC&ALYcn&obEGPKPE02We+Aw*`wpw*+0qw>RZ0wHI^AdtW5s~EWMG@{^Q)&pAGF#qw*|*%8$l6)UW&j6o4(0oGOrU^ z(s9TtUK*zmtNc;#8ill=!6+Qs(bflD#)V{~Z2Oz6R}kBhi$Z((JUNnpK|DQO(twuc ziLos#Hy%_`lIHl<5Ec7@(<1O!hP$c9wu29CJUzspj>nX8 zb6*PsfD(p=jczAXw;N=Y{CvcF_Plhyh|h>lH@n`zMcdPGZ(*ZXMIQ?Hw-79F2g zyyOhJ^^RlCeBw)xn;e*<&0fDMRMJ}MthLotcl~}f@j-^YN%}*v1y4=<_w2YnUNQQo z4gOY)Z^js@MQpBz2{yO;WYy6P=bCO+8@nyFcnob*cQ8F_5an^(qAsyx2>n_UnIuub zxijdwym{Ea{n0wr~(4yXQ?v@S+Fco=9}*7W{#I}joY4xLZk8C;bo zRk-uQEaRikyPWJ0R#wc;(|xw&M=vyY$PD0WTXE3-0we;Avm1#tP*i!5^6CZnAE4Qj z%>y2X>V2ZrDO}n2Zw$ayX!@i(JIQ_qS<0GUtCg&)G^RU8tKto&1buUeok^1f6rccI z?36AeV_i6S2+hS!GOwX3(Jx!FG8h*6gAJH!+OzhON#(^VxhuM(5@|>wl{uI_h${S! z%x!r|ds32gwnqqMd#bX*SqKQ&FJ3p{U{o+9}9p)o2L!=8!HHv*V!q1@!F

6Tqw8pgIBqrT<1 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 G>;D1H$o{7Q literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9.wai.etag b/wai/http-conformance/objects/6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9.wai.etag new file mode 100644 index 000000000..337282e13 --- /dev/null +++ b/wai/http-conformance/objects/6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9.wai.etag @@ -0,0 +1 @@ +"6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9" diff --git a/wai/http-conformance/objects/6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9.wai.hp b/wai/http-conformance/objects/6e485f112e849422fb9cfd279cb16b88c8d5ee1e72bafe4892992fc6bef88be9.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..41f8853d43ba1d46a79f2d5f86aa8f4ec2404bbe GIT binary patch literal 277 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≥tBi@eXOE936hiWy6N#Dp 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/a016e64f311ac1128481d0da292c7cbcb1d98d6c9edd6983ad8b9e385e6c0b09.wai b/wai/http-conformance/objects/a016e64f311ac1128481d0da292c7cbcb1d98d6c9edd6983ad8b9e385e6c0b09.wai new file mode 100644 index 0000000000000000000000000000000000000000..0aa2fbee0f74d7f3b6014d0b12f83a9ce77034ac GIT binary patch literal 46433 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai b/wai/http-conformance/objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai new file mode 100644 index 0000000000000000000000000000000000000000..8a939215627b9d15966eaef829e981421076bbdc GIT binary patch literal 18381 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 PoOMvpsMwzhA;`**2pVhO literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai b/wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai new file mode 100644 index 0000000000000000000000000000000000000000..c8c879f7739d4146c22da4c5d5318bf831a3c7f6 GIT binary patch literal 5389 zcmeIuze^)Q6bJACvBo76N>pS*3*VbGgenB0B-DX^r+a03e=~=P zV*2f_kW82RN%Zdh)=3O@&eb`Ww<6Cseb)(C&a*-*@Liur5U|M2f@4OJ={ecJwsXGC zfH{$$&-yMiA@8}a6I!&jlcox4MXm>JQd!YM7ATN1ND=Wwky1q#373_6F(xVor4LSG z4vZvKkO@in9_uZ25PvbF0Jm?dzDo@rp8l0gPq<06Aw7 AB>(^b literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai b/wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai new file mode 100644 index 0000000000000000000000000000000000000000..6afc8c9121c695dabac6db34ca01d72eff259744 GIT binary patch literal 18004 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai.etag b/wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai.etag new file mode 100644 index 000000000..d9413c916 --- /dev/null +++ b/wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai.etag @@ -0,0 +1 @@ +"b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb" diff --git a/wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai.hp b/wai/http-conformance/objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..9a5aea7a3f3e79a8a50820ccde0619a5bfde9e95 GIT binary patch literal 213 zcmWId2r%5fTyV8F7pLXY#D&?0>CE%$pT(r`7B6skee~-2wkL0Phqy6-!AE8Y&ARY| zvcU>*qhFfYew*i-#r;0mba2s|H~kNEO_})?ty@}9zFCj6K+92QGM^*gGnK65`EtmE% zL>{sccoB0uK-N#dW8x&0uAa7Vhl0px@148v2siI(^IP}$&p|$uyssy_BiBj^Xf&E{ N{Z#MC^P;>_4FCjVTm%3B literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai b/wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai new file mode 100644 index 0000000000000000000000000000000000000000..808357456be34e4d789de0441d2adc663c986b90 GIT binary patch literal 859 zcmY*VZAepL6u!+VX~hcq+OOqZBSAOs?&j6)M>?#?Af*B=O6}df@7!D4^?G+}4V|=# z{uDuFWJ*y4){h`avs#G;g;5Zd6n&BgeLy3uBr3OidS^c(4u|(W=XuU^J{o*A&P}rz zrk#xzLDk(jInu|`CDgy(pH^Y9)zO(cl;GJ%Se8aQU49(T#X1Ac!vP@(UGifeVy* z%0z@+lFKRj%L5+HSs{2lnD^jvSB1;X@#Q>nQ!NQ=SPc`IM8bckva3jrlqyc4n2u=w zyGJPz+-#gKqan2g)8o(+vR=Vm!UnTXJf5a!ZKzrAtYR1@>qf>vZT5^AX}pm79m;W3 zI+rz#EM~@`cx?HB`6PNYc)D>E*`i;13;^go_2c0M@5XL5S{9#x)c2{JYrV1Q-CrjG zJ|V-DBawy-z*qPImZ{sh`ieq;{_PJ<0|1490Dk$p>mK%G(r*skx8;xbDOb4Z#yyF= zdi&(yK=Wkirb>&!cku2L9oA;D{{(=cZNgIh4JG@b<0@7n;{MCeC~Vn_!)+Yl&* z3bqYDfOMr+t3Ta;4xpfLk?mQw?}jreE25OX{_#Glm`qcC;n7YjV3z3SBP=k>KV-+q&|Beuc3)Zc5e2A6DJmFnn9KG8S(b7I%u9WpECIh9SZ zu2g;MwY}0i*NB&o98+EvtsK#-FJ|5r%lx)=U=xjX7V GB>n(z;eL|< literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai.etag b/wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai.etag new file mode 100644 index 000000000..77f5fa12d --- /dev/null +++ b/wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai.etag @@ -0,0 +1 @@ +"df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2" diff --git a/wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai.hp b/wai/http-conformance/objects/df5337ea38c4d61fd15f006c08df937f480788fd669e7337b0f8105a5816a9d2.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..f8762ab507340e397b90e7374a72fade5456551e GIT binary patch literal 53 zcmWId2r#@KZ2rpP$Tj(k@eDZ}_b1nTuy_1Tn^$bU;fFv}gxJbU(aa2B0HYZINpul2 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai b/wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai new file mode 100644 index 0000000000000000000000000000000000000000..df84a059e7aa350aa647329cfd1ec9d1f9084ca8 GIT binary patch literal 251 zcmWG{^fa8vz`#(gRGyfrWTj-NXP~5`l$)B8nF!>Sq*jywxtVz-sd*(peo<;cPG)jq zNh(N9eoAUid{Js)X=YJsE?B-=DLJtqF)1@Av!oKJ8fc;($OOHr;*u1lS{i<y>Z)lDO($9JHP46UNSp!NxKL!007|ZTf6`O literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai.etag b/wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai.etag new file mode 100644 index 000000000..2855e64e3 --- /dev/null +++ b/wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai.etag @@ -0,0 +1 @@ +"e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b" diff --git a/wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai.hp b/wai/http-conformance/objects/e43e038c10c0494d9647444fd4daf54b19fc3a946a3c7980431bc6e0a236093b.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..c0cdf6831547b21406a3e65523587201608faa00 GIT binary patch literal 53 ycmWId2rztN$J`@uz|(h{yNmyoTVK5;|5#1QvZ-uvmOl1ikr}7;Zw3f}(F_1DbrCKA literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai b/wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai new file mode 100644 index 0000000000000000000000000000000000000000..af8942a8c0b97c8160107c2e7d4e8a2c562beb92 GIT binary patch literal 53794 zcmeI*XHXMq90u?xqA2#>OY919cazN~SP(n*-T?94Mpi-+k^mxN#R~S`d+!zOU9lqe ziem4*V82CY?wp(Zav$&JVFo6<37IX)miK-C&#y;7AVcNUXyUXnMA7^BToY@#~**MyS&&}xrS=esO~VU6mf%o=DDXP_zduaRL{-oO)CPdL4q<4huD z5O}k|tFM)1qBrq+iWg{^H}Mol(K15}tTx`~UoV113A6mZ3_NG#Wj)PHG)IYMQ8r0L z=0%F3%m$igWzoQMgr{hZ$VNg-tUwEtUNTc=LQJw*=DB}fh6u?`bQaM`Qx0pC>#uDg zN-n{vINWxpI`8KuQD5}?;&29a?RkzdFoKCDgp~w_&_;EA)y+Xz*`zM0Na-oY%u$k& zuypq35(TqlW+Y-_8A^RGr`;;48&|MLIEYQ%Q|hcgABWmy^{Kz_sNbIs{k2rtTPZ%o zN9!ieFiHIo*rKe~cn?kXPm1@i-;Reg8m}~sCRLs1k*`SUN;P~9^_v8C?9oTEx&{m# zJz@I1rE50t*njNI#T$2@yw3WOyFl@B+S(M?sCnBiy@|yh9iNamdDg-e>$mMbbRzxA ztq0HFeaV@(aH)#bee`|-tvYlM5yPFaLq?6CHh0PDO)2}1p1yGX&f`}fzkB8{R<=s5 zdd7y$+H~&KH_R3_aQN6sGZ!phw{_RS<7Y2t+<*4=a}KXUB`Z{`%a|LtY~L+d=oi_4 z@W^pf=PX{eF?sKiQ|GT`K6?4#TQ2XSWh&Ryu??EG?$k3>j&R2f8#8gn{AFvm>^zW~ zcIoE5r*A%K@)RslzG@xXz$NtCX zAK!l*|8V@r`48uRT>s$u@4s;U>shptkGVycz7YdPOHv|)UXM2S8YFZ_U4n1xr$V*Yiiy()EYZ-%EEO!k6p-o z@x`lzwqAq4?t&wJ?2M(G_MW_Q|ILs5Woqafx9c6|9y)%`%54YJZan_rQMf`Ku34u( z;r&NUUa)q@(et;Tf6i09imraEZoGZqnCVM4?m2P!-s|t)rK>akZF|YCA>(GR*m~g1 z^+)e>7Ajxc*tBCvznFwc^Vg&tId|*Xr`*LV`}(!)N^EhXr!C&F`}n21ufF9gRgG@c zrl;f_oHT3smi?!%J$#p=V7XdslMca3^ze!ERwo}$&v=@J^~e6l=O5pH9RG0q$N3NE ze_a3I`VZH?xcI|3mvH+JDjhjrM=^f1v*d{a@(+L;olG zf6@Pq{(p>r!1xc0f5G@4jDN!TFN}Y~_&{uA}DsQ*R%GwQ!l|Bm{9zMXXHod?@ZaIT!+(eW4*wngJN$R}@9^K@zr%lr{|^5h z{yY44`0w!F;lIOwhyM=$9sWD~clht{-{HT*e~14L{~i82{CD{8@ZaIT!+(eW4*wng zJN$R}@9^K@zr%lr{|^5h{yY44`0w!F;lIOwhyM=$9sWD~clht{-{HT*e~14L{~i82 z{CD{8@ZbMG{(Fwf{(a)-?z;A=G}k?9>Xvh_3)gEOk+kN-W3QSmq=Y30Gc}bOg$$gt z^J-QpV>kDd&FQZS>DpNnS08_rr$%#e_~HY%e^hK395;K%m5(LauCB?O&b}<@+ctdc zs?>+Mt2YydE!uzUdxZu;17@XM{!qfu#W`tX+KU1{ZTgK_dF+8_wWj>gh5ItTm9O7B zc4qRW_r>*{BPVV+^E`ju*2?G=NAKsV+Jp>Qu=nQIa(=z~&)9zPT`{JUW5W8=&%Emd VT1G8Da?e8>(0B0sJvY9T{R21DHPrwB literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai.etag b/wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai.etag new file mode 100644 index 000000000..1cd36744c --- /dev/null +++ b/wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai.etag @@ -0,0 +1 @@ +"eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228" diff --git a/wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai.hp b/wai/http-conformance/objects/eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..13b0d2745478896b52ede4c793de4e1754ed335a GIT binary patch literal 245 zcmVRzlVc}*gjH9g_5g~vxfbBD>d4Xkk)XAfI11BPzkT4PN5hHDk}gW5 zqoV}tKb5V96mkuZHgV@draM)~pVXE7_#zIvs*xVl^)4l*zq>kYczGd1CkNf}o?+K} z2EEUYng5wME5m%1GQxHtlYRJTefYQ0Q)WQ@N|rZY4%Mh{}qTd_4|( vFufsTP`|~WAo}_m?)!FXoYDo=ipf3l=(Gk|SieAMDuZKSSS8ZxMUbTQMc#H0 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/SOURCES.json b/wai/http-conformance/src/SOURCES.json new file mode 100644 index 000000000..609bdc66a --- /dev/null +++ b/wai/http-conformance/src/SOURCES.json @@ -0,0 +1,50 @@ +{ + "note": "Frozen sources of http-conformance. Each was encoded once, outside this repository, by an encoder of its format; the AV1 streams were checked to decode. Only their bytes matter to the corpus, which checks transport, not decoding.", + "files": { + "image64.avif": { + "sha256": "c56f9ab9a3b1df0bd650c3177bd85427f4a4be8fde82547219bcea4ec254162c", + "bytes": 2619, + "what": "AVIF of the same picture" + }, + "image64.jxl": { + "sha256": "6db8812530d69b6f33069289ee7bc68105f95f0c6a6e8ffffa5b1a7494c30ca1", + "bytes": 4433, + "what": "JPEG XL of the same picture" + }, + "image64.png": { + "sha256": "eb67f66a321f613886bbbed6309f432a5f27fbb5bf1eabfe6a4d1931f425f107", + "bytes": 11848, + "what": "PNG of the 64x64 picture the pinned wai.neural.int_mlicpp vector (wai-rs/tests/vectors/int_mlicpp/stream.wai) decodes to" + }, + "pattern_high.av1": { + "sha256": "68a51be787c439226eb7bb1cf434e913fc1f0e221870da87f70615d667a7ab12", + "bytes": 77040, + "what": "AV1 of the same pattern at a higher rate, in the same framing" + }, + "pattern_low.av1": { + "sha256": "24003cee2a7bf7fa45caa412ab4ea9f5f08b6bf44842a73d5a11c2bcf36df464", + "bytes": 20766, + "what": "AV1 of a 2 s, 320x180, 25 fps test pattern at a low rate, in the reference implementation's AV1 payload framing (n_frames, h, w, fps_num, fps_den, then each temporal unit length-prefixed)" + }, + "seg0.av1": { + "sha256": "0c03a22bebcdb29e26fc1e6dbf040bd86751e8e0be66a2b2ba6709f786a260e8", + "bytes": 18293, + "what": "AV1 of frames 0-15 of the pattern, in the same framing" + }, + "seg1.av1": { + "sha256": "edd595431a8992d78fd6f691c6b066efa102ac8c1ffb822505639e0d640174a4", + "bytes": 17834, + "what": "AV1 of frames 16-31 of the pattern, in the same framing" + }, + "seg2.av1": { + "sha256": "dbf116e87034120670d459dbc4812644799b1dc3ae29cc070659103948ca59bc", + "bytes": 18211, + "what": "AV1 of frames 32-47 of the pattern, in the same framing" + }, + "small.zst": { + "sha256": "591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1", + "bytes": 94, + "what": "a zstd frame of one line of text" + } + } +} diff --git a/wai/http-conformance/src/image64.avif b/wai/http-conformance/src/image64.avif new file mode 100644 index 0000000000000000000000000000000000000000..2d51cfe8325723711ad11c31f8d27427d45893b7 GIT binary patch literal 2619 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 hg<4=TbStEWKwmG!s{8{LnNLOqA^-qr{?8Er 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/seg2.av1 b/wai/http-conformance/src/seg2.av1 new file mode 100644 index 0000000000000000000000000000000000000000..2f3d1d98715d53ff06c28bba3089df945856677b GIT binary patch literal 18211 zcmX`RV|yk{xNZBywr$%<$L!d)ZQHhO+qTuQZQI6q_gZ_O4|P@jfl)Q)T@w)i05t!% z1b_eltpE1^Ho!YH0DugDhWYOZ%JVa|@$N6ce2Rt&AObKVisFhO3Tl}2gMx0xO#8bj zB)NO5h^1Y&8mEyEJBohtXJrq&QhN?bW-DF;BAh=YimtP;HlN@kI(UZ(dd-i8ry@z; zOO>U-wYWRzz9gkbxNqnT9Q}SBCKhTI?$Yci=O1{*^$Ey5+gMt7PD$$FfH1F8MQD}v zx@-+P$b8I000$I~JJKi0Spgm%T(xU$gLG1@GaIt4_P8 z4SPhyWI$%E&&28xgYU#PhtwS+AkB_s>D6lQ1v(oESET~IjFYIdDvmu@+$Hp9cZVBA z8_NDX;JM{$>U4$a8(q06m1Xl$FJye-amprjk?eYsqKDRd5pmIkG?OFDsFVr8=|V?Tl^yxvDQH6Zzq`7cKfQbf zRDxLKpG+!cFX+tEGxoF_h8%gptKl;isGJra?#`snyZo(4pl$A0evd2kk|| z(>qU$HL=PJV1_Mcvb?nZg#{!R+4Ku{5mlNqyAr1pQ@EgAg{TMUO11en? zqLcHg(Ic&icF!!XDd8ipk>ZfJ$?TVdBp`sp1qwWOLF2ltMxzmJK$BnfTJ>7|)W_~5 zB|R=DCt7stfocUZ`^EA>rgU;%h8{dUX=P?ff>uoCXOLf*%s(o64{xQCT+c*eq-~T) zVb_{qND?mV@OjVn&_B_OkpO^2noyp^NL3x{;}U(!u6^g}Ac{%mJ z*cqT~Rdm(Fcg_p$@-yd{UJl(e=W&jWY~ZPr3dDyJTlJLuBi%}mIckQ5z+-vOJ$4f7sbPSP4LYfRj48c){>@Cdh$ODR(V76JJz{6-1!LQ;HIV3yoSQI$9p(1mNMwNqfp z2$%+|V-RG!B{^HAtoh8_v}fkUuI|k<#~5=F{Iqty?bZ9eF~jY56bNWLZ&Y zu{#xtil;L^D+v|{-{tStF+=x`cbk-G86?*>yxk7cxm07L|NNx*td7$6RyIcsL22NYOK=;!9!Op6UbphXKc@*L|<$i$QK=M}TjB6w^EuT-?z!s+A9wy&zAt$RjL z*~<2RfHjY08P_VG!(N+>eQsT4qNSMoq=souRd1CVCF}5Q*L{lm`OH<%8HIm1Y)o%4v5mR9|+((P#Ocy#*gwEqaawnp&qzb z1g_}|H=%kNEQHa7+uPEI{I%xWuIctkG$H#UAE1z_5|I5!1amYyc0Y9se$4|}doA4! z9L6cR3lEaI(z4CuPrq|;#9z~!p2aF#Ufm{*&AeAZSE7;5Pk^>gKw|ogC+drJhNd@9nXOsuW<{=%m-d=rMk0T zQLLnW#+CG?3JslS8h$gSP4C&~JLI(fi<& zw}+|x!~UaKm>{Sg&z-q=<90L=cemA};&MhPi9{uRSR|dQXs!oJsK62KjK&>8N1F?X zlcHJ(>VMg7o>0bC9a4{~Wim|!CA&PubkPmufR8Mr_J19*>8Kg40G(Eoy^USz4WE}i zW%_H+*Q^LaZhaEc-F*bO(j0axv9wx#{-89yxa$7UVlf(7;<2^}JafP?UAS0xd{p!{ zsTcjBV7Nf^gOI?F0Cx;krzp-m4woVe8v( z;9cU2ps=zjlk?n=kW4BfYaM5Y3{6sO;+UFZN4TJx8FQ3iZ>YeBm(~&NEgn=~znYw^h5@ zMudW@Tc?L{AZAg>C>texXq5G687p_%!rUuYBi&EWlT?F3b8?rZl}2n|t8hDrF>4O> z@H35sW1cX{Zqvhm{TsGj^5U>-Blb_N(ya;N;6X0PfiF{|;h&JzC{JsLM1jn@BG-k* z;JI_90(pD;8ba5$Ru@abeJnuzWu=gG}hFka?O}~8Tr;%;`&5Ado zd=xF2i;iDZag3OLy|{LGqA{x$>AHp>93!cmS? z2yri_OWcP5`PY=F<^-|!$xfwtvB`SQq#Q{DQ3Vl_aBQL|x*J%OAR2y+g;WHDYy#Gb z4u>0pP-RljsTyLO-}NWo=zOPw%q5nU;Vub zc6>gkTdi z>-y4indMW@|HGPabF1XVMqqu>QwE?3d%&=WMm z^??Z*`PlRwEq+F#5W;C?=o!zRnF_FtFK7ayQ$tw*WxvY3KaE5#>hX(SOirUgGL1ZE z7n#*<3|@#XS;y>%poX$j%6@E~_-N`?ge-xZVOs+LEbQT*xQ>Efr;HC;OX?+^KcNqr^a=AEdkUGXUGUzBIv-)}{KJbI=`!=tvK7Y^(Z5#aU|7@z0Sg$1a(V>`C6enFT#|>!+D&Rd9YHSjcbK ziW8j?&U9BH3hx`Am%$-$--N43X9d=ztGqwLw3|z1=1CMd!1TSWDBsLuh?`Jhhf%q5 z|8n5B&-ne1ID^7ZNuujpn8VNPpO+l?_!X({fm7w*>_|sG%d-alzpR*a+Gc85(qI=C zKBKyJY@^4FW0HB@$Ie`d(6}?GADM8uVi{BM+K?LXe!_k%3Z-ZdVJY`lPviTRYwbE` zadmI2fp^s$Z?@6(r?-_I(e;$4 z^>qUC?uGs(ZuLutFLW;tv*vc_Xy|i`nvD`flzTGd)rI_+ewp>N@Rb<;Im ztpSd0r>l>^yDnL)nmX)hlu3W>DiE8mBaeBQF1fAgk^I98IAFTqM~a{nXb5vF-9(>nv=LKJ6oA{n+9dj`|{cB-oy~GW~JD; z#k~40aS!Uve`hDk?TMMI14%|gbK z3|xT83p2WYIP4IX6a_syV{c2HEWMxEt$TU=9%427mahxX88ucfZtUWw_JY_8k7b{} ze9ibrG3y}gT9?XFwt{>Sa-G217SR)58bH}|uahrFyj3Hr6NGXPgOORB{Ek_u<*x*! zsSjSN`x^*SsK2DX^vZ}R4@{my@K>VV#jIQG@E^e2FSmkd^5>V7L_=7~)z+fVC!(Sl zA8436LoQ?tb(4$KWZK!^5L0TaX@KxP#A6ojpoqe$2iTUOLjDoQ+Pz+aJf>s7n6dx* zwP5`o4_1p%R`R^Mbz-vz@wLxmch(g&zcEj>+dWulk#~_+Q@S6#D5RU*KEKDkd^;Jo z&MKcTZp3Q`(!c0uMmx8_r!ux7oR3~=1+3GWn0o6_d9AKfM9PF@Im`&i7X z;*|^8r>JCFpLM+T{APxt4xMn6wBU^j^tzeUu^u2hNMkUfQm*8A$H@D^20Jm$BL7ay z0`*gDlFiizR7W=MgRs({{zz@a!h+`7e9`G-N7YFH|4n9drE-VXJ`)2D2svqwP+Zx< zVyDVrK9~6{($P|u%K7>>#4Y+6_|;Gnlc=Upu#-)JZ!Q;+gZpTgLq^)#gt<`e{iiz4 zwf{K9Q&e);4``I?TMK0bp7ZE%sIXmWX7tlD+N5wHH%dy#ce4iicW7{K5m(}!#R{h| z6Xx|4bhS|>oCm2Ln*pgpxrURRV0NVoeit(flz5~9H$rvJ(B zTrnP$5G}^U5h&Y1{H@CSe3>78I<-u_Mt~CXW>fqia*blsi$cpaFF5@(1H&@)YVcN> z{zBoe*tU#Gg8d$_-{@ZcYvvbemT=`F@CP$xuXxVkc3O0ZNL-VG%OC^F-i(B%buF~} zyv&%BCol50vXsQuRb8V3PCjl_>EslUSP9gUt9X%6c?PK)YzuiEi<*LCF0Syfc^5dq zKu`S>FYP&BM+pCLC8~KGcysosn&F2Wme5P$IFk7(D1-bvwpM{x9IBn{Z`^*_WJdrh zfyB?J`s3g`OY=YvD{g*W(3fm|(INAm4OrCvaKBSBR z#`o(nmw86O(Y7luv;i(vNq@d7{9D7sS&q^_g_pt?XE3Pr4834lM)Xj1GlYyMxZ(f%V?lNrjmp5N*G9O`el zDfp}e?AaYsdP2VL%@gPC<(1J{f94?=Dv&z?03uSM03eX^Kt8Z>j|Y%Lf)hXYFi(kN zOwun9HgD!GMf@OrC1y#ICeYZGqO771vzk5>>9Uhf%0j7IRi)EWRNkAcDFsJF^Mbg( zgn0mdpDvtqUYhfR?!DSdX!5bj4;(JrJm~XD%dPc37fGWJ(QnG#A^CEA==D>z^e$W? zdG#ciZA171leFw5K$N`MdyHw>s9FPtRe!ab~kQ3b78CNlgX@3ekyU~YsL@y(zqqB(d_WS1A0S@vy z6j=S~!L$j=A8MmTf^c(ubTr98NvJ=Fxsp{sJLUPnLd&}(pMYW_C3Q?w-Qyh^&NpwO zBwHZFID~LA_=<>3z(TYqEb{MrCv=$2b}gbI=S}X8*Qe}K@6G$Nkf)Piu>5FF$8E7t zC(Cy>Gr~u2A2tjL6TecfV2HilFE=C67~^Xf9J}P7i_0(V!(inp^}DRD#BsrJxA8y2 z?@Gunw#+a~A(Y6C3H?X=2|dF5{ITx zyg(W_xg>^Bp#g9$sAu~nuVR7k8GQ~BPj!hMHX)1(Xq-I0+8J&TaF$IF1axu(6U;g> z7d(ReZhW*J9T?_|Z^~lsLTkpvKv!7uIH6bU8$p|1(&xUs)61%nm78~|45vGqvM6UcxktdkM>r~~x% z@?~Bqlbyzj4dtMx0V`ei?X8-*dnBMSI}u7@88>WI|B`|x#n^GHi&W>n5W-rEDuIu7 z1fr!AFc2R$CSJw&cVQrDxa}I>3GUPatyu+&p&BQja3*$*e6puM#u*Y7QFWpjQnRW) z{KGhSf&io_JMLP9{)#%d(d*xrdik7d(hjc!?}(n6l3P9@BZFxnoAK;k?*tb;dpU+x z3cWq<0n|9&J->EvdP9fLI4_YdmRxwlV1E*!G)*MT3t-G0x7yL*a%{8v76?@Hy9bwWpy^ z!rAeEK~DZp#EJwL7C$wHd3D0R=rFT_!2pMY@~%4!enfC zM(P#6$Cki0Y7V;HvOZmxP65fWlcas_T^VUofg(fQqF1A@5s>F*lIwr$KzS;Kwfsno zOEJ3+&{5i7-eVnk<{@e^u!r_^y?Uxf7H4!dx(CbhUQ^E+SFDTDf(d%K9h-&XtY<&+(v3) z59hpbDtPz z!gEUxU?V3~o#{^`S-iuJvE|^9f=HSKUuasm&7Bs#oWhl}6%3@ORa^TruR>A*_4@+| z6$-4BRbR#jH}m@;C5n7z@=NpeX6^7KF)0}yYd&zI)9uj&v2M!COpocXE!y8TQ_c9uoIf4tBAusr%L2)NEjSCIG znlL5KI=5LG1hhY6@+6htrX~L!Pg;F@RqRQ;>x@fUCsc2rX=4795+@H|PhR|>UzTt(H^Xc%K(PS2j(lh8JASc2YjhFeJcFE(I|Wvv71jJCHFVV_7B z^RrFzL!H1+78=M>kd`kyx$tvH&XeQ?iz5$_w9AInd+tkw{5O6Q zb4H-gf}XX)Lw}E@dMTklhOFT>BaY{W?9576&l`V5zVPHHtql#yYg3hX&!OGksGZn7 zYoU~#iv(M!biFs`d)er+__A#%F^{|HcR|K45MPRrp?-9V0sNGxKGhc294kZ?qX_l` z9_31NYps)KDym^BP*)~zlOkb_TA%sjm4TC+PWQ)p96*HOSE=jPrNPXWGRa@ntQ@h4 zLsiz)>gB!ESFV+i!7!SlA2IAGLU|N8rzo7FsvVqh~C(1FD=e? z_R1ktx7F5TOo|pTySE|5Kqz^+Bc0jDL*j5ZUpYK6DiiYnp(mvU0l7xu-R+$#&~FP| zg2|T3UXFlu`qLHUn5kS*c6ns3C67PsWNX3qUP*mjz93uPfXBnA($zzUj@T}0oVz3j zrUd^op1wCBp#*Ke!mj#q$@ElE8z$Mfq(C+6NaQ9Qg!Kp6f4J}qTXyedG@-tI$)Gzm_oAk;CvNH@>D0ODg!bE4k%yq8oATFA9=TCMeztAIo_J*QHKNz zG0kn-VPJSeontP7U0L+U-n7BG3?OG&LRLR~rG zPEghtq%K`>)GB7B9#f&9Tx(6br0HERAQ9bBLW*`v1mD)$0s|`w!26rx!GMJ+{LBrs zMq;g|5#7P>mO+Xu8ordM87D(x&%8=R<6<=nbA5RORv6w(CXnHjRY#AH*b|Cz=DF}r1j$ff$ZHwBja#YPPGMeNnWeg-Vr z0;9>OM)&F{*9vOmEL`?LYq$4%7D?Q29w!^Wf0o38)xUZ$2MG!C^|slqS@a;9CohSo ziwvKpBKc^y0Q5^5l?tv!O%;pBB=KInsmtE3i-^-U1+$0v$eRaQF>)1zOE-jZTu0p+ zmoTz!i#852>TAP9P9Nzc+I%?B`XJl^j5zbBul}IhXTkz!@=_ZT5KFzDaiRBpMp}|? zVQPdmo*iM*XfHpi;>kY@;0>=?n747Y=ZD#e%@L!f@VG1>ob7oqA649u9psa&Kl=*C zKZS7$yO79?>1_JKAmaj*WaADTaiQ{qKN3lR-slSp#eEn>avPnSn2@}@)x!eht2g`6 zBl@HOco@j(@OPE(-Fw!er6v{DrG8g2Gdv!#>P?(QJdcx7Sr{(b5M21Lu?Rtq%;rZ% zc-GyR3`O*f8BLX#*Tr`)#SNcc>0B?iOwkDFPGN|c{ta7YPqhfONktRKE1 zqgY0sR~HL!2NV}=iL1KR$k+PNH6Hsv2#90@#ynbhg$~QMBt-Op7 zRGU7=Ajf09WKL?kbc7?9&C}}83%@~>!2nNe?woK zGFj0#bjrflXw%)}a2`=f;Yp12e2};kUIU~}w7#HZ`9TG5Kx_EfW0YxYxU`8;({LWv!tTKd2h&hs`Yfiy0wGVMf3j z+CZ-TdpL@8MCNQ* zmwIRhk7%$tgSAm#fnv-vbp+Hd0+ohjPA@As{r*yFiS_-S*pqI)Lj`3!^G%fRSmMUu z>)u|KtxVT6$S-qJ_!-H8V}x$E%5dp@7fy;9TD!Nq^7;SW+-U~AI42PT zZq9bl);#^~)Q3(1`&VTvu==YQ&$$%Y33E7~*6g)RdRPZ*4kzeffX`67F(Jr>&I-a5 z#J2H=WqyIRHN0it*6sAsx{8=#x3385gv2^qzK)loi7UF+Enu4GL4hE_JPp8X)BP9$40zvu#5qT{G#yM6c>4;A6bgUIc2rih^ zZEsTq){T*rkd%N6vJkIU%G)1hE;N~j>iz5*HWkS~XmPZFejzy0KB2r|#)?rrL&Ug0 zg!x_cz?uU;lNk&4puoku*I4m6#s>uiQsc1@b|}@%z09 zSwrRM8I!Q#<`OgX;KFkN=8SY*OU2$DrM-{)=pDnyGct5}Nz6NVOK}74q_B8Xk^xdJ zj@B&V?-0XYynSZGZ5#hO%QTgaOsQ+HI=u)9L6IV^3J;hU&hYwE;vVS~N#5E=QSC7u zY^Y-V5PO+(b|xPR>5fM{P_3{l`6O>ZP{qIIXJKV2WlL=U^0c|^nH;f+FGDW)9FE5t z;q+8N7x#91&pV;(?#un(T)V{h)UT=cBEi-2vp=j^R}(Qr{g#u5PYb0@>>T%nuC-`4Pd#B4Dnc$uIzea*48n#O)@#5is(E{uU^oqDw08y z%-NV~ypqYy(a1l?I)ek7(Nt9kqBkJ4aIz(iEfW8SNV=DER2mu09_~5e2A~)}{1%sr< z;S6MWl5y#LrNfDuEkGG1%jI78q?7D5?HD=UKYmSqIWrDES?qEe#uJ&#bI<&sCpWxg zc1_un-|N}M?=9DWt^2Tj4NjFnJgiYrgE z-&dtT=iI2v(iNH7oW~j+0EAu+!4RO2(9@_h^M?_zK(K|9$A8BeML^ae5`Kz~v&y^y z!3@d>p+EjV4jcc!?_z*i@RLrhgGUFnx6$u!jlv7urTmh1U$-hV+>aimIq3D{uQnt} zbc>=EudiUvQWb|%Yr%>uuuw1YPI=76K@XY1jd*9?d}9wdZR)5b_i&QWTy_LEK;CO< z1e7V!re(DyB{-^^-!^-!In+_zt7mOIQj|14Z%e)TpQl`li(F+SlKd|J{wU51Qec7x zHK}h+Xq?`Kfq5~W22+sVnePT>&&TOLbE_EZ?MkCE{yF z=dje#hxL31y269h(h7lS=`}MPk@Do4z#QplLd9i(hkM9b;(ikTX{Dztzkwv>>S5^j)4|X~u!0KtCc@1@O&z3FIh>Cmmpm7h^EjsSg!vN|A16_ERuDac zHuF;TN1?>3wmc0e)JExRMWW5WRHKVzSH;II@bg!fYG8k+E+#%)0%i&SYUgL^UUxLB z(gIOj7%C}v;sIlaOQ9f0wAjwv_Vd58!j}D$Y$(|hF$96@>4}A5`x+QLQ%lAn*Ao?L zH!}!(N5^7_mD)QP!B3*!sua{#a+@fZQl%qTET>UT)JR^Pt{=uc6?}b5qO-UjR6mz^ z@z)#^tBVtn?+MH=&wPG(m31WQl=Cky+jVLo@-1YFSfxDd}^@{&bd1 zc8A7xVZ3cPR^aMr<^}1w187Y`p;#Qk3R?>#g)T8Ax_WgywAM@iYTV05isAsGe`#__ z(1I=2QL2MlqN>;WAYfrfZ4=^2wB5o>J8LW{{mHZLs*n25N5@m`%js#_9X1y>c*1>! zI=(ffE%Gb=PT&({!VXsC4w_fZ7W~-Ve)?yK)5)G2e3zK=uyc!_We7+VbZ{<07+cQ(|hS+~V8{cnc1h_8zdVx!V$u3ajwoNH2f{rnmD zQ&Qc?Kw*3SZ#OT^1`%o<71W{SEC;d}uIF=;9|TP3(2jPo>_4GXiuz!_@m5{auD zsE$QLc*K*ZNl?tuuW#bmjWRw@y-y=dmF2`|h%xQ2iGa~GUX^4bQ${iV#Dr$Q@1OuBKY6zbEja?8q*~A2$Ou!ZS)8jhQUdab3zmkWA@~Tb~e)y}KEI zts6UoxFoQjz@l`8(7T+X;!i+x(`fAAnHAb?Pu^ZF^8#pApWtLx+9YqJrUH?$XHse< z!jL&aSb}_cklB%KBPSFs&Pu}YyqCU+ERy`dd)1ZI&@@+(Jx5Q~FAyjPyW2%QCGGel z+taR7eLyo*me6YVf35SZzgriqllv=zmTsIENZ^%Ja<0R)Xs})cd8eT_EDNtCWi(Jk z(1x^M8ci>s?}`6xpxb5ZX{V=jJC^FP^Mv>61!eqL0d_ONv z_M;@@T^?!EW8$Z?8MiAR@@9HM=}pu3J1#n zKLpEh|Etaa*B{D)paLrdPIpx(j0XgYAN-F!+c6omj|1Fi@`1`Lo6;0dlG+__zXSu= zqeGxuuFRae#^aB^O9VJaKFqYSRO9d*rJ;)oQuGwru+2+MDsC^qtCHvN{JqoCIgP_+ zxj|bZ0vcorlLpB1JYd5p8JguQ9{bZvaywR~m~VjJ7B1nT?RDY}>v{7w>LFVI5R0$ zv9eH(9wumt=m!!B)N^R>cixQ>Gwk}R%~<5&>H0t5O{p?OKAAAxs0~Jdf?H2Nf?Im! z7Mg;j>C+MAY9q`z6v{_su$lPUkzRx7f3AQ1nM8q~5)r9#;Be(hJ1fh!iLOF;sa%?y z_kn0wBarPci12OXhOyMPWZx*YRi=VVfX$f764^qZoKAa970m4KRGajBA{=>kmWkIo z!pjaNlyCS|x!I@5BiBs>@}4YW1l#su*O#SuP-d$ktn^I0i`km&PwsEmTw>+l}nfw_w#tY`!|>_^$<*y{}dPL_zHSTuSno6t4t?zF4= z9W&S*tb+R52+jbugHv4A9rC-G-M`lG2SD|t9z~(EH=qP` z$|B`y7a&JEa{4JW6p;@W=lSR6X)W)dpa_k~E`NmInrqPY6DrF^3^3^!CLStPc=}02 zMj>WxX}1<+oWQg_@kvSqjN-~^4rEo7vbq9+Uv*sN{QY-g*^|;74Xx8Iixh^%-)m1+ zd6DYxsXTQ9!LS1iWv|{JZ34|T4nMe+sPE*n<2yYdzq)mo3&<#~@?5S)O~!kLp%#a1 zLcJrfwT)&P`0j7CV^dT_k2q^lcCy}!O8D~7yCZy;f@Dj+UD8`F80Q?sz0#2PQ{FY% zZ$mgT$iUB~;%YZvk(1)Q@R_Sa7RUKmlzSy&%buP8a8MjR`|;zfVU2YCG_pHb^fPxs zP=WRbrt2ycw*Q|G|No)W_FRU=S;IC412<1JEyU5^4Ym&xqU6aHl{CDySBraeRgddA zcaSmgesWAwr6RD7yx+BCydc_H!?`*O*?LRx2GaAwFy3RIGFY{2nE%>f5niWg{5A-H zAAjASK>!}i&g?vM7>zPssyy|N@Q>Z8ve`uRX;!lT!lcybK~sgo+^D&3M5A2cuBv>r zP7|7H{OwB$i5yO!iz-5E=1hi|wuJQ-p50H;mOVI`mo_Y+?e)X#EiVsAFmPN06?PMV zE19Pz#nKPTR6DuCgm56z?pe+Eo?VYQPytf3=#>}mRSz~E(Rcd7rBfW;GQQz&20d#+ zK9i@sGX(a$OEGGRq6|v6%*{kur5(_kA7z0<53^s<2^nT4r-(%=f5x{2G(UG)bG_Yc z{?{l%f8&Q7fmztqOu|1kcMH!yW^}-QgWF^E3C;@&96kgve+x~_=>Gxgy1tA`{57xOubG z8=1Wb<05RHXG+t9g$sTv4_{FzD$k@fU^q2bMy>W=g}MjNnaPtgoLMmQe_KzMm|UI^ zicSywN(b>}s6@>$q7MnP=<@fdzLKnD81RQCClHne`Fbez6txS^A{;oS7b}rPP@hZP zc_Md^^XmcKE)@SY0rEM`(t^FX%Suso{BT{8>lEEfPgR1qderst_;xK80BI^TE-0lDS&ba*0gHk(T zzirld31|ax2!hSNyb)UT-%I~%Gumg<2j3VS`h@=;*uKT}Ik~(kD}npf(VQSA^5489 z6R~YnS`+`lNBHsk0leP9$=R9|(#rxb52AXbS4Wr;Ae2I``eIi<5GswmMs>bThLyQP zq1jLTdMC=2_Z#Z}k$D!XN_ob{nuhHi60Awu$Oxkb0TYWD?;hzz!oM13E@ z>YK)2A7+aQ!e%<jkTB}XBDBAeYZyQ5_?VD;*AuB}qXuLHDOkhiH^Vb@^&Q07og-OMd=|*}t z0Ad3EUy1;rT=z%@hbT&m~rXprohlLn;? z5woVhjh$)AldLK>l8Qea;*sL<>4xR9Y@pSIH;RaBj2s*8$|0C=ci3oNC@73-25&Q0 z5iS7476U*<2JhvQq3HP@y?8>3dQ)1IHWiCQQ~QNZ>^-#@ptUk_*`Gph=n_#GF~~Tk zJkq1uP~4r-qT}Rs+ENz9fQ18>@)vscY;Gv3FKj%O$HGVK<9mJunI<(|Dx$Yow~_i9 zRnb`wEpxWEgV-X}S(CNi+KJ@ld{VrE1cLUj)#j#47$`c|ScsOQLINja@?^{3SToat zA0u7HAQAv?C%|J9Klv)K%c+hNm>pJA`g0D>Jf}fUkdZl$WfaF8gFUZQ>+oslcu}^M zEc*5|Tsx2Ig z27>rrYB0{OyXyr#@a`Otm;o{cC~nzp*vVUsnojEAIr>F_G7yVB#nz)Gt2$R{a=Ph00h>KhY(RsaCfmMZ3-U_-iu9 z=kx6a))GAnk(W1*U5{R(2RPSSy+N!AHuZEi?YCByT9dzA+h#JF1td_A*hB^e2$V}j)HS$Rn_9(E|K*wPnP6$8 z1#;Js`+}czQqzRSyq^h&aHw_%0XO^p!35+6Zx^uB7fq@^H{Rvv7AU^&8mR(bT1>@x zG+Rr`bEEFzx=O){f99xHrh7rEX;snUc0MH_*B{1GF_=dE|DPzgL;P=64FqR~ri7TQ zFA~iML;b%n`wL#A1W{)}o%n|H+ipJrp%vRP)wv^v0RDb+KVPsOY_LPyZ4W@wG#Mn! z?c(i>5A;I(f!p)sp|5ik7ij=RbuOj=Y%$N`PR}uj8*J>Fi;xxD#f=jXd?J@D2`Xzf z*xa5CZwBBW1KC#ya4r=WFq%{6EjY2gD21igdi55hHo?D4D!4%z*?{pr@jhY`rU_gP z-Gy1J7wQqyqar9FG}#6>nInQ&+~7JGpfXNS3v;=kJFs4j6vO~Ei&iOl>#%?jEp8OT zNOSL=7j9d=bDj7Cg+LUwl%D0(Y85FQA%{ zHB&w>W%mh=FCq21{J9X1rDoxn7ijgtFb6CN1QeA}+KpHnOVJKc>q9%@q3hZ%91GXf zSMUHqgxGoge%v;_5HmJvrnWVDpi}+)2?hsh7lB%dGhA3eX{s=-YtsBY6Hp1v_&9Yg<1Y z!`Gj0eSTmsJoCyu30!Fq3Hljf>*S=Q|GOq?9s;KqJGgaY??Oa`y-E3Tc=3sGDF0+P zZ*DQz;3ZHgpUE6q)AA`d{13xQk7}{1wRQC?qxH)Nz~5re_G`1^#^J9owQtC9JuniE zQVL8iv%I{GO4tg=`+;j0Z<-Xvj>sga(0|&utVXek%h;N*b6yFevNu}Hk-oTXC_KWy zYJAuKW#rq~dZVzB@=gpeB6h9bnm$W!@*Y1+$AKo zrE1KO?4DDm6*wGE3bLr+k-CX{#Xs}!Xi~?~z7DG0H2k-c`dyW*p7JqW$r4$A@e${@ zh+F&ZF}{Ix4q}#{W$bWah##7!oT5|(o;0tA2T<-Xt!D=sQmP`@x^V?3!4#1r_W?GZ;Aq z;Dcodk6*(>zY^+R4Zdc?Uw<|wjkKqe#!Te`5zDr(_T?!?qm3Pk>nV#~y&&?p)$9nD+FH`t{27~*VVvq`wW^m@tVwaKx#c{O+^hBls80i0G2SQ z4<;y{e-mO=l8eD`iaFH&TF9luhKVuZKB_4U>DUrHH=Fl5pe%8(P$Esg+)jB3#vme( zKZ)zvbPC1d?RRyHp~BNq9|4){3}`slM6O^jWThK<_>~T1itHRyX@bom08_NeXDqHC zB~?x;RWl7X5Gh;Cdo{Hvb&X~DT7hl1l6pl+FyN;)uxM$%ONR*#jB7?9kLPdupRD9m!wr4S$Z+l=v-`Dmd?86pPvJX9$X`Ulz})7zDu$ z3c&bRHQ0E018zt|)lHakB3Y^g2``9c&3ayW3)m z8%6moDxqa1_t?sor@y2L=GV@*2bTTApkKz9-Kna|97n{V?Xp8!{PPZ|3qwRz!Wy8o z?Rg6XGT#L<3?Pe`oTEj+1r$-lfdm790A&H%60+tCWunPXMK2&ua-c%+Mc|KUiHZ+5 z9C%Oz-vc)%5282QY_QijD$mA`8`u{W)xqb`@ogr~Iwy~={SI{o%pu!v1APh!(h;OF zxS#7Z>Khc)rMziQlPCwnrGU()zGk*8;F#Z zdER9UhY!n}Vr^(UHz8%z9ZJ0@Hya6kx|42$;1{x&vm50y?gmRg(fj;lW?rhc1kz^J zyC*7dJhcWWFPB#Z{VQ5q{|F&c337e}FSkjp7bD4@1^HgF!eBzlOY(qqmT6jD6{5rys4-j?ZFRI0F?C8;f(MP`|ER?wC>$ ze+f#1em+z+_D@7f^uO0ld%7OLM5;Y760||j7-OCxcE%c-j*@5AV*4zc>;iB@7$8h> zC1EwWPT~Nsb$O70t5}-zgWnQ!TI)yKG7%sy$ag=*vp*Q2W54N1thQ%~ns&-+F4ZXJ z;oF>>Pa+v8hthj2yN+HCBzNZt*U4|Z-@?W~H#EkP80;$D-4|fhbY#XDr7*{z?C-s}WXb_X5LpPTRMcv72 z8+B9sMUKl*L*u5lY=@rhk)da!dEVduaR=4(F9${ed-qO18^qFp?iK1=kXOs_t7~27 zF}n+H_H21BebCXPD1J8;Q^*t^BoOf8o`CIa3dOoOKg{T5>sC)=KIeE_UfL2V42?PR z_j92)i$Z6>FlpjoGmQlR000sI8Ue^j0{{RL05Ze|G7c~ZsLZ29&;bOI#DN3{fB;s< zuE?P=Wo-tJQAgu>|J4J!&8acBf^p}v7?M1@+3NeLNb$EEpX9SRrZn==MlKlXCX@At z#fn#}8P`|jX*j-boD{_oqDi=7EiQ4Q@%51?E+J5SD4VTOy6jZ9z3p=O-jC$ISCcac z4yq>2N?fJf+)DY069xe*s;rLf zWjB@IDvEpv_N~W{G+VCzKKD!=AI3sx)Rq=5D{N9&!s4}@V~DpKduo{(R4{PHXl(tp za!Euz@$k(jj9{S#ST}aXMOB+A5bf8($MwmXTTVXVaA;i=yM9T(i`UYHFY-H~OAfi2 zFAD~!ZzO+Zz658Q>s#69a2M#faO#>xpG||aCqz_qq>P(A(fl|{vseTJ!#b9gxn?)sKgU1ovgLby#yRjo9xvLiG&8q4DZmGM0bG(s%zxUr z{D5e_!}4SE!n4c6` zFlQ=5=2#%d$HieunYf@$yuS7(E;|gJU1u@a#k5M&4v8>H0<rav==&l){E8$M2#rs)Gg3L8Wc05+{B Aw*UYD literal 0 HcmV?d00001 diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 2294ced6b..a7e182773 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -531,6 +531,11 @@ name = "wai_film_vectors" path = "src/bin/wai_film_vectors.rs" required-features = ["film"] +[[bin]] +name = "wai_http_vectors" +path = "src/bin/wai_http_vectors.rs" +required-features = ["http"] + [[bin]] name = "wai_world_vectors" path = "src/bin/wai_world_vectors.rs" diff --git a/wai/wai-rs/src/bin/wai.rs b/wai/wai-rs/src/bin/wai.rs index 3d1b6e722..ffdb0b173 100644 --- a/wai/wai-rs/src/bin/wai.rs +++ b/wai/wai-rs/src/bin/wai.rs @@ -22,6 +22,13 @@ use wai::*; #[path = "wai_cli/stage.rs"] mod stage; +#[cfg(feature = "http")] +#[path = "wai_cli/http.rs"] +mod http; +#[cfg(feature = "http")] +#[path = "wai_cli/http_vectors.rs"] +mod http_vectors; + fn die(msg: impl std::fmt::Display) -> ExitCode { eprintln!("error: {msg}"); ExitCode::from(2) @@ -41,6 +48,8 @@ fn usage() { eprintln!("wai prior-set print the listing of a parameter set (wai.prior.set) and its pin"); eprintln!("wai stage refine|apply|inspect|pack|explode … staged-delivery layers (a build with `staged`,"); eprintln!(" `video_int`, `audio_int` and `splat_int`); `wai stage` alone prints its usage"); + eprintln!("wai http publish|codecs|origin|vectors|bench … HTTP delivery (a build with `http`);"); + eprintln!(" `wai http` alone prints its usage"); eprintln!(); eprintln!("encode and decode need a build with the `codecs` feature."); eprintln!("codecs: png jpeg avif jxl jxl-lossless opus flac av1 av1-lossless zstd xz"); @@ -816,6 +825,14 @@ fn main() -> ExitCode { "stage" => stage::main(&args[1..]), #[cfg(not(all(feature = "staged", feature = "video_int", feature = "audio_int", feature = "splat_int")))] "stage" => Err("`wai stage` needs a build with the features staged, video_int, audio_int and splat_int".into()), + #[cfg(feature = "http")] + "http" => match http::main(&args[1..]) { + Ok(true) => Ok(()), + Ok(false) => return ExitCode::from(1), + Err(e) => Err(e), + }, + #[cfg(not(feature = "http"))] + "http" => Err("built without http: `wai http` needs a build with the feature `http`".into()), _ => { usage(); return ExitCode::from(2); } }; match r { diff --git a/wai/wai-rs/src/bin/wai_cli/http.rs b/wai/wai-rs/src/bin/wai_cli/http.rs new file mode 100644 index 000000000..ba8fd732e --- /dev/null +++ b/wai/wai-rs/src/bin/wai_cli/http.rs @@ -0,0 +1,360 @@ +//! `wai http …` — HTTP delivery (`extensions/http-delivery.md`): +//! +//! wai http publish … --out

+//! write `.wai`, `.wai.hp` and `.wai.etag` for each envelope and +//! print its cid, `n`, `h` and `codecs` element +//! wai http codecs … +//! print the union and the floor of the sequence (§4.2) +//! wai http origin --root [--port P] [--delay-range FIRST-LAST:MS] +//! serve the directory as an origin (§5.3, §6.2): objects with their headers and +//! byte ranges, head proofs, receipt files, indexes and preflights, on 127.0.0.1 +//! wai http vectors gen|verify|diff … +//! the corpus tool (as `wai_http_vectors`) +//! wai http bench --url http://HOST:PORT/PATH[#frag] --runs N --advertise CAP… --out F +//! fetch an object N times by §7 over plain HTTP and write a measurement record +//! (§9 of the design's method: body bytes by category, header bytes, requests, +//! time to verified bytes); energy is recorded as not measured + +use std::io::{BufRead, BufReader, Read, Write}; +use std::net::{TcpListener, TcpStream}; +use std::path::{Path, PathBuf}; +use std::time::{Duration, Instant}; + +use serde_json::json; + +use wai::http_binding::codecs; +use wai::http_binding::head::{parse_head, HeadParse}; +use wai::http_binding::head_proof::HeadProof; +use wai::http_binding::origin; +use wai::http_binding::plan::FetchPolicy; +use wai::http_binding::session::{FetchSession, ResponseMeta, Selector, Want}; +use wai::http_binding::{content_hash, hex, object_name, parse_fragment, unhex32}; + +fn opt<'a>(args: &'a [String], flag: &str) -> Option<&'a str> { + args.iter().position(|a| a == flag).and_then(|i| args.get(i + 1).map(String::as_str)) +} + +/// The positional arguments: everything not a flag or a flag's value. +fn positional(args: &[String], flags: &[&str]) -> Vec { + let mut out = Vec::new(); + let mut i = 0; + while i < args.len() { + if flags.contains(&args[i].as_str()) { + i += 2; + } else { + out.push(args[i].clone()); + i += 1; + } + } + out +} + +fn usage() -> String { + "usage: wai http publish … --out \n wai http codecs …\n wai http origin --root [--port P] [--delay-range FIRST-LAST:MS]\n wai http vectors gen|verify|diff …\n wai http bench --url URL --runs N --advertise CAP [--advertise CAP]… --out F".into() +} + +pub fn main(args: &[String]) -> Result { + match args.first().map(String::as_str) { + Some("publish") => publish(&args[1..]).map(|()| true), + Some("codecs") => codecs_cmd(&args[1..]).map(|()| true), + Some("origin") => origin_cmd(&args[1..]).map(|()| true), + Some("vectors") => crate::http_vectors::main(&args[1..]), + Some("bench") => bench(&args[1..]).map(|()| true), + _ => Err(usage()), + } +} + +fn read(p: &str) -> Result, String> { + std::fs::read(p).map_err(|e| format!("{p}: {e}")) +} + +fn publish(args: &[String]) -> Result<(), String> { + let out = PathBuf::from(opt(args, "--out").ok_or("publish needs --out ")?); + std::fs::create_dir_all(&out).map_err(|e| e.to_string())?; + let inputs = positional(args, &["--out"]); + if inputs.is_empty() { + return Err(usage()); + } + for p in inputs { + let env = read(&p)?; + let head = match parse_head(&env, env.len() as u64).map_err(|e| format!("{p}: {e}"))? { + HeadParse::Head(h) => h, + HeadParse::Need(n) => return Err(format!("{p}: the head needs {n} bytes")), + }; + let cid = content_hash(&env); + let name = object_name(&cid); + let proof = HeadProof::build(&env).map_err(|e| format!("{p}: {e}"))?; + std::fs::write(out.join(&name), &env).map_err(|e| e.to_string())?; + std::fs::write(out.join(format!("{name}.hp")), proof.to_bytes()).map_err(|e| e.to_string())?; + std::fs::write(out.join(format!("{name}.etag")), format!("\"{}\"\n", hex(&cid))).map_err(|e| e.to_string())?; + let element = codecs::element_of(&head).map(|e| codecs::render(&e)).unwrap_or_else(|e| format!("({e})")); + println!("{}\tn={}\th={}\t{element}\t{p}", hex(&cid), env.len(), head.covered_len()); + } + Ok(()) +} + +fn codecs_cmd(args: &[String]) -> Result<(), String> { + let mut heads = Vec::new(); + for p in args { + let env = read(p)?; + match parse_head(&env, env.len() as u64).map_err(|e| format!("{p}: {e}"))? { + HeadParse::Head(h) => heads.push(h), + HeadParse::Need(n) => return Err(format!("{p}: the head needs {n} bytes")), + } + } + let union = codecs::union_of(&heads).map_err(|e| e.to_string())?; + let floor = codecs::floor_of(&heads).map_err(|e| e.to_string())?; + println!("union\t{}", codecs::render(&union)); + println!("floor\t{}", codecs::render(&floor)); + Ok(()) +} + +// ------------------------------------------------------------------ the origin + +/// A request as the origin reads it. +struct Request { + method: String, + path: String, + headers: Vec<(String, String)>, +} + +impl Request { + fn header(&self, name: &str) -> Option<&str> { + self.headers.iter().find(|(k, _)| k.eq_ignore_ascii_case(name)).map(|(_, v)| v.as_str()) + } +} + +fn read_request(r: &mut BufReader) -> Option { + let mut line = String::new(); + r.read_line(&mut line).ok()?; + let mut parts = line.split_whitespace(); + let (method, target) = (parts.next()?.to_string(), parts.next()?.to_string()); + let mut headers = Vec::new(); + loop { + let mut h = String::new(); + if r.read_line(&mut h).ok()? == 0 { + break; + } + let h = h.trim_end(); + if h.is_empty() { + break; + } + if let Some((k, v)) = h.split_once(':') { + headers.push((k.trim().to_string(), v.trim().to_string())); + } + } + let path = target.split(['?', '#']).next().unwrap_or("/").to_string(); + Some(Request { method, path, headers }) +} + +/// What the origin serves at `path` under `root`: the file and its headers, by the +/// kind of resource its name says it is (§5.3, §6.2, §8). +fn resource(root: &Path, path: &str) -> Option<(Vec, Vec<(&'static str, String)>)> { + let rel = path.trim_start_matches('/'); + if rel.split('/').any(|s| s == ".." || s.is_empty()) { + return None; + } + let file = root.join(rel); + let bytes = std::fs::read(&file).ok()?; + let name = file.file_name()?.to_str()?; + let headers = if let Some(stem) = name.strip_suffix(".wai") { + let cid = unhex32(stem).unwrap_or_else(|| content_hash(&bytes)); + let codecs = parse_head(&bytes, bytes.len() as u64).ok().and_then(|h| match h { + HeadParse::Head(h) => codecs::element_of(&h).ok().map(|e| codecs::render(&e)), + HeadParse::Need(_) => None, + }); + origin::object_headers(&cid, codecs.as_deref()) + } else if name.ends_with(".wai.hp") { + origin::immutable_headers("application/octet-stream") + } else if name.ends_with(".wai.receipts") { + vec![ + ("Content-Type", "application/json".to_string()), + ("Cache-Control", "no-cache, no-transform".to_string()), + ("ETag", format!("\"{}\"", hex(&content_hash(&bytes)))), + ("Access-Control-Allow-Origin", "*".to_string()), + ] + } else if name.ends_with(".json") { + origin::immutable_headers("application/json") + } else { + origin::immutable_headers("application/octet-stream") + }; + Some((bytes, headers)) +} + +fn respond(s: &mut TcpStream, status: u16, headers: &[(&str, String)], body: &[u8], head_only: bool) -> std::io::Result<()> { + let reason = match status { + 200 => "OK", + 204 => "No Content", + 206 => "Partial Content", + 404 => "Not Found", + 405 => "Method Not Allowed", + 416 => "Range Not Satisfiable", + _ => "Status", + }; + let mut out = format!("HTTP/1.1 {status} {reason}\r\n"); + for (k, v) in headers { + out.push_str(&format!("{k}: {v}\r\n")); + } + out.push_str(&format!("Content-Length: {}\r\nConnection: close\r\n\r\n", body.len())); + s.write_all(out.as_bytes())?; + if !head_only { + s.write_all(body)?; + } + s.flush() +} + +fn serve_one(root: &Path, delay: Option<(u64, u64, u64)>, stream: TcpStream) -> std::io::Result<()> { + let mut reader = BufReader::new(stream.try_clone()?); + let Some(req) = read_request(&mut reader) else { return Ok(()) }; + let mut s = stream; + if req.method == "OPTIONS" { + return respond(&mut s, 204, &origin::preflight_headers(), &[], false); + } + if req.method != "GET" && req.method != "HEAD" { + return respond(&mut s, 405, &[("Allow", "GET, HEAD, OPTIONS".to_string())], &[], false); + } + let Some((bytes, mut headers)) = resource(root, &req.path) else { + return respond(&mut s, 404, &[("Cache-Control", "no-store".to_string())], b"not found\n", req.method == "HEAD"); + }; + let range = if req.path.ends_with(".wai") { req.header("Range") } else { None }; + let a = origin::serve(bytes.len() as u64, range); + if let Some(cr) = &a.content_range { + headers.push(("Content-Range", cr.clone())); + } + if let (Some((first, last, ms)), Some(_)) = (delay, &a.content_range) { + if a.body.start <= last && first < a.body.end { + std::thread::sleep(Duration::from_millis(ms)); + } + } + respond(&mut s, a.status, &headers, &bytes[a.body.start as usize..a.body.end as usize], req.method == "HEAD") +} + +fn origin_cmd(args: &[String]) -> Result<(), String> { + let root = PathBuf::from(opt(args, "--root").ok_or("origin needs --root ")?); + let port: u16 = opt(args, "--port").map_or(Ok(8380), str::parse).map_err(|e| format!("--port: {e}"))?; + let delay = match opt(args, "--delay-range") { + None => None, + Some(s) => { + let (r, ms) = s.split_once(':').ok_or("--delay-range FIRST-LAST:MS")?; + let (a, b) = r.split_once('-').ok_or("--delay-range FIRST-LAST:MS")?; + Some((a.parse().map_err(|_| "--delay-range")?, b.parse().map_err(|_| "--delay-range")?, ms.parse().map_err(|_| "--delay-range")?)) + } + }; + let listener = TcpListener::bind(("127.0.0.1", port)).map_err(|e| format!("bind 127.0.0.1:{port}: {e}"))?; + println!("serving {} on http://127.0.0.1:{}/", root.display(), listener.local_addr().map_err(|e| e.to_string())?.port()); + for stream in listener.incoming().flatten() { + let root = root.clone(); + std::thread::spawn(move || { + let _ = serve_one(&root, delay, stream); + }); + } + Ok(()) +} + +// ------------------------------------------------------------------- the bench + +/// One plain-HTTP exchange: the response's status and headers, its body, and the +/// bytes of its status line and headers as received. +fn get(host: &str, path: &str, range: Option<&str>) -> Result<(ResponseMeta, Vec, u64), String> { + let mut s = TcpStream::connect(host).map_err(|e| format!("{host}: {e}"))?; + let mut req = format!("GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\nAccept-Encoding: identity\r\n"); + if let Some(r) = range { + req.push_str(&format!("Range: {r}\r\n")); + } + req.push_str("\r\n"); + s.write_all(req.as_bytes()).map_err(|e| e.to_string())?; + let mut raw = Vec::new(); + s.read_to_end(&mut raw).map_err(|e| e.to_string())?; + let end = raw.windows(4).position(|w| w == b"\r\n\r\n").ok_or("no header end")? + 4; + let head = String::from_utf8_lossy(&raw[..end]).to_string(); + let mut lines = head.lines(); + let status: u16 = lines.next().and_then(|l| l.split_whitespace().nth(1)).and_then(|s| s.parse().ok()).ok_or("no status")?; + let mut meta = ResponseMeta { status, ..Default::default() }; + for l in lines { + if let Some((k, v)) = l.split_once(':') { + match k.trim().to_ascii_lowercase().as_str() { + "content-range" => meta.content_range = Some(v.trim().to_string()), + "content-encoding" => meta.content_encoding = Some(v.trim().to_string()), + _ => {} + } + } + } + Ok((meta, raw[end..].to_vec(), end as u64)) +} + +fn median_iqr(mut v: Vec) -> serde_json::Value { + v.sort_by(|a, b| a.partial_cmp(b).expect("finite")); + let q = |p: f64| v[((v.len() - 1) as f64 * p).round() as usize]; + json!({ "median": q(0.5), "iqr": [q(0.25), q(0.75)] }) +} + +fn bench(args: &[String]) -> Result<(), String> { + if opt(args, "--energy").is_some() { + return Err("--energy: this build brackets no meter around the fetch; run without it, and the record states energy as not measured".into()); + } + let url = opt(args, "--url").ok_or("bench needs --url")?; + let runs: usize = opt(args, "--runs").unwrap_or("30").parse().map_err(|_| "--runs")?; + let out = opt(args, "--out").ok_or("bench needs --out")?; + let adv: Vec = args.iter().enumerate().filter(|(_, a)| *a == "--advertise").filter_map(|(i, _)| args.get(i + 1).cloned()).collect(); + let (base, frag) = parse_fragment(url); + let rest = base.strip_prefix("http://").ok_or("bench fetches over plain http:// only")?; + let (host, path) = rest.split_once('/').map(|(h, p)| (h.to_string(), format!("/{p}"))).ok_or("URL has no path")?; + let stem = path.rsplit('/').next().and_then(|n| n.strip_suffix(".wai")).ok_or("the URL names .wai")?; + let cid = frag.cid.or_else(|| unhex32(stem)).ok_or("no cid: name the object .wai or give #cid=")?; + let (mut ms, mut media, mut proof, mut headers) = (Vec::new(), Vec::new(), Vec::new(), Vec::new()); + let mut last = None; + for _ in 0..runs { + let adv2 = adv.clone(); + let mut s = FetchSession::new(cid, frag.clone(), FetchPolicy::default(), Selector::new(move |c| adv2.iter().any(|a| a == c))); + let t0 = Instant::now(); + let (mut b_media, mut b_proof, mut b_head) = (0u64, 0u64, 0u64); + while s.result().is_none() { + let wants = s.wants(); + if wants.is_empty() { + return Err("the fetch stalled".into()); + } + for (id, w) in wants { + let (p, range) = match &w { + Want::HeadProof => (format!("{path}.hp"), None), + Want::Whole => (path.clone(), None), + Want::Range(r, _) => (path.clone(), Some(r.header())), + Want::Rest(r) => (path.clone(), Some(format!("bytes={}-", r.first))), + }; + let (meta, body, hb) = get(&host, &p, range.as_deref())?; + b_head += hb; + if matches!(w, Want::HeadProof) { b_proof += body.len() as u64 } else { b_media += body.len() as u64 } + if let Err(e) = s.on_response(id, &meta, &body) { + return Err(format!("refused: {e}")); + } + } + } + ms.push(t0.elapsed().as_secs_f64() * 1000.0); + media.push(b_media as f64); + proof.push(b_proof as f64); + headers.push(b_head as f64); + last = s.done().cloned(); + } + let f = last.ok_or("no run completed")?; + let rec = json!({ + "tool": "wai-http-bench", + "git": opt(args, "--git").unwrap_or("unknown"), + "env": { "os": std::env::consts::OS, "arch": std::env::consts::ARCH, "browser": null, + "network": if host.starts_with("127.0.0.1") || host.starts_with("localhost") { "loopback" } else { "site" }, + "condition": "origin, no cache" }, + "object": hex(&cid), + "plan": match f.why_whole { Some(w) => format!("whole ({})", w.code()), None => "ranges".into() }, + "pick": f.index, + "runs": runs, + "body_bytes_media": median_iqr(media), + "body_bytes_proof": median_iqr(proof), + "header_bytes": median_iqr(headers), + "envelope_len": f.head.envelope_len, + "component_len": f.head.components.get(f.index).map(|c| c.len), + "requests": f.requests, + "ms_to_verified": median_iqr(ms), + "energy": "omitted: not measured", + }); + std::fs::write(out, serde_json::to_string_pretty(&rec).expect("json") + "\n").map_err(|e| e.to_string())?; + println!("wrote {out}"); + Ok(()) +} diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs new file mode 100644 index 000000000..ef03e6c4a --- /dev/null +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs @@ -0,0 +1,866 @@ +//! The HTTP delivery corpus (`extensions/http-delivery.md` §14, `wai/http-conformance/`): +//! +//! gen write every object, head proof, case proof, +//! `objects.json` and `cases.json` +//! verify regenerate every file in memory and require the +//! committed bytes; evaluate every case and require +//! its committed outcome; print each failing case +//! diff --seed S --count N --out F +//! write N random envelopes' covered prefixes and +//! head proofs, as JSON lines, for http_verify.py +//! +//! Every object is built from the frozen sources in `src/` (whose SHA-256 values +//! `src/SOURCES.json` states and `verify` checks), two committed vectors of this tree, +//! and payloads computed here; every case's outcome is computed by `wai::http_binding`, +//! and the negative cases are held, at generation, to the reason they exist for. + +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +use serde_json::{json, Value}; + +use wai::container::{Prior, Rendition, RenditionMeta, Skip, Wai, WaiMulti}; +use wai::http_binding::codecs::{self, Prefix}; +use wai::http_binding::head::{parse_head, Head, HeadParse}; +use wai::http_binding::head_proof::HeadProof; +use wai::http_binding::origin; +use wai::http_binding::plan::{plan_components, Class, FetchPolicy, WhyWhole}; +use wai::http_binding::session::{check_response, drive, FetchSession, Fetched, ResponseMeta, Selector, Want}; +use wai::http_binding::{content_hash, hex, parse_fragment, ByteRange, RangeRefusal}; +use wai::pin::HeldBytes; + +/// The committed vectors the corpus carries objects of, relative to the corpus. +const STREAM_WAI: &str = "../wai-rs/tests/vectors/int_mlicpp/stream.wai"; +const MLICPP_SET: &str = "../wai-rs/tests/vectors/int_mlicpp/model.wmb"; +const INT_HYPER_WAI: &str = "../staged-conformance/cases/video_int_hyper_keyframe/base.wai"; + +/// Forms the corpus's sink reads (SPEC §3.1): the set listing form. +const READABLE: &[&str] = &["wai.prior.set"]; + +fn usage() -> String { + "usage: wai_http_vectors gen|verify \n wai_http_vectors diff --seed S --count N --out F".into() +} + +fn opt<'a>(args: &'a [String], flag: &str) -> Option<&'a str> { + args.iter().position(|a| a == flag).and_then(|i| args.get(i + 1).map(String::as_str)) +} + +/// Run the tool on `args` (after the program or subcommand name). `Ok(false)`: a +/// verification failed. +pub fn main(args: &[String]) -> Result { + match (args.first().map(String::as_str), args.get(1)) { + (Some("gen"), Some(dir)) => { + let dir = PathBuf::from(dir); + let files = generate(&dir)?; + for (rel, bytes) in &files { + let p = dir.join(rel); + std::fs::create_dir_all(p.parent().expect("a parent")).map_err(|e| e.to_string())?; + std::fs::write(&p, bytes).map_err(|e| format!("{}: {e}", p.display()))?; + } + println!("wrote {} files under {}", files.len(), dir.display()); + Ok(true) + } + (Some("verify"), Some(dir)) => verify(Path::new(dir)), + (Some("diff"), Some(dir)) => { + let seed: u64 = opt(args, "--seed").and_then(|s| s.parse().ok()).ok_or("diff needs --seed")?; + let count: usize = opt(args, "--count").and_then(|s| s.parse().ok()).ok_or("diff needs --count")?; + let out = opt(args, "--out").ok_or("diff needs --out")?; + let _ = dir; + std::fs::write(out, differential(seed, count)?).map_err(|e| e.to_string())?; + println!("wrote {count} envelopes' prefixes and proofs to {out}"); + Ok(true) + } + _ => Err(usage()), + } +} + +// ---------------------------------------------------------------- the objects + +fn read(dir: &Path, rel: &str) -> Result, String> { + std::fs::read(dir.join(rel)).map_err(|e| format!("{}: {e}", dir.join(rel).display())) +} + +fn pattern(len: usize, a: usize, b: usize) -> Vec { + (0..len).map(|i| ((a * i + b) % 256) as u8).collect() +} + +/// A start-code-delimited byte stream of synthetic NAL units: transport only, it +/// decodes to nothing. +fn annex_b(len: usize, nal_header: u8, a: usize, b: usize) -> Vec { + let mut out = vec![0, 0, 0, 1, nal_header]; + // No 0x000001 or 0x000000 inside: every third byte is at least 0x80. + out.extend((0..len - 5).map(|i| (((a * i + b) % 256) as u8) | if i % 3 == 2 { 0x80 } else { 0 })); + out +} + +fn sha(b: &[u8]) -> String { + wai::pin::sha256_hex(b) +} + +/// The corpus's objects, by id: `(id, bytes, what it covers)`. +fn objects(dir: &Path) -> Result, &'static str)>, String> { + let src = |name: &str| read(dir, &format!("src/{name}")); + let stream = Wai::from_bytes(&read(dir, STREAM_WAI)?).map_err(|e| e.to_string())?; + let ih = Wai::from_bytes(&read(dir, INT_HYPER_WAI)?).map_err(|e| e.to_string())?; + let mlicpp_prior: Prior = stream.prior().cloned().ok_or("stream.wai has a pin")?; + let ih_prior: Prior = ih.prior().cloned().ok_or("the int_hyper base has a pin")?; + let target = |w: u64, h: u64| json!({ "w": w, "h": h }); + let w2 = |media: &str, target: Value, r: Vec| WaiMulti::new(media, "replicate", target, r).to_bytes().map_err(|e| e.to_string()); + let av1 = |name: &str, id: &str| -> Result { Ok(Rendition::media("wai.video.av1", "av1", src(name)?).with_id(id)) }; + let (low, high) = ("pattern_low.av1", "pattern_high.av1"); + + let o1 = read(dir, STREAM_WAI)?; + let o2 = w2("image", target(64, 64), vec![ + Rendition::media("wai.neural.int_mlicpp", "int_mlicpp", stream.payload.clone()).with_prior(mlicpp_prior).with_id("int").with_payload_digest(), + Rendition::media("wai.image.avif", "avif", src("image64.avif")?).with_id("avif").with_payload_digest(), + Rendition::media("wai.image.jxl", "jxl", src("image64.jxl")?).with_id("jxl").with_payload_digest(), + Rendition::media("wai.image.png", "png", src("image64.png")?).with_id("png").with_payload_digest(), + ])?; + let o3 = w2("video", target(320, 180), vec![ + av1(low, "low")?.with_payload_digest(), + av1(high, "high")?.with_payload_digest(), + Rendition::companion("wai.meta.hdr10plus", pattern(200, 31, 1), "wai.video.av1").with_id("hdr").with_payload_digest().with_companion_of_id("high"), + ])?; + let o4 = w2("video", target(320, 180), vec![av1(low, "low")?, av1("seg0.av1", "b")?.with_payload_digest()])?; + let o5 = Wai::new( + wai::container::Manifest::new("text", "replicate", wai::container::ModelRequirement::new("wai.text.zstd"), wai::container::Conditioning::new("zstd")), + src("small.zst")?, + ).to_bytes().map_err(|e| e.to_string())?; + let mut wrong = av1(low, "low")?.with_payload_digest(); + wrong.meta.sha256 = Some(sha(&src("seg0.av1")?)); + let o7 = w2("video", target(320, 180), vec![wrong, av1("seg0.av1", "b")?.with_payload_digest()])?; + let o8 = w2("video", target(320, 180), vec![ + Rendition::media("wai.video.avc", "avc", annex_b(8000, 0x65, 7, 11)).with_id("base").with_payload_digest(), + Rendition::from_meta(RenditionMeta::new("wai.video.lcevc", "lcevc").companion_to("wai.video.avc"), annex_b(20_000, 0x79, 13, 3)) + .with_id("enh").with_payload_digest().with_companion_of_id("base"), + av1("seg1.av1", "alt")?.with_payload_digest(), + ])?; + WaiMulti::from_bytes(&o8).map_err(|e| e.to_string())?.check_component_roles() + .map_err(|e| format!("O8 is not admissible under SPEC §7.1's companion rules: {e:?}"))?; + let o9a = w2("video", target(320, 180), vec![ + av1("seg0.av1", "av1")?.with_payload_digest(), + Rendition::media("wai.video.int_hyper", "int_hyper", ih.payload.clone()).with_prior(ih_prior).with_id("ih").with_payload_digest(), + ])?; + let seg = |name: &str| -> Result, String> { + Wai::new( + wai::container::Manifest::new("video", "replicate", wai::container::ModelRequirement::new("wai.video.av1"), wai::container::Conditioning::new("av1")) + .with_target(target(320, 180)), + src(name)?, + ).to_bytes().map_err(|e| e.to_string()) + }; + let od = w2("video", Value::Null, vec![ + Rendition::media("wai.video.av1", "av1", pattern(3000, 7, 3)).with_id("low").with_payload_digest(), + Rendition::media("wai.video.av1", "av1", pattern(50_000, 13, 5)).with_id("high").with_payload_digest(), + Rendition::companion("wai.meta.hdr10plus", pattern(200, 31, 1), "wai.video.av1").with_id("hdr").with_payload_digest().with_companion_of_id("high"), + ])?; + let oh = w2("text", json!({ "note": "a manifest long enough that the head spans several chunks: ".to_string() + &"x".repeat(5000) }), + vec![Rendition::media("wai.text.zstd", "zstd", src("small.zst")?).with_id("text").with_payload_digest()])?; + Ok(vec![ + ("O1", o1, "WAI1, pinned, below one chunk is not reached: the head proof covers it whole"), + ("O2", o2, "WAI2 image: a pinned integer learned rendition, AVIF, JPEG XL and PNG of one picture"), + ("O3", o3, "WAI2 video: AV1 at two rates, HDR10+ metadata bound to the higher by id"), + ("O4", o4, "WAI2 whose first selectable entry states no sha256"), + ("O5", o5, "a small WAI1, fetched whole"), + ("O7", o7, "WAI2 whose first entry states a wrong sha256"), + ("O8", o8, "WAI2: an AVC base, an LCEVC enhancement bound to it, and an AV1 alternative"), + ("O9a", o9a, "segment 1 of 3: AV1 and an integer learned video rendition"), + ("O9b", seg("seg1.av1")?, "segment 2 of 3: AV1 only (WAI1)"), + ("O9c", seg("seg2.av1")?, "segment 3 of 3: AV1 only (WAI1)"), + ("OD", od, "Appendix D's synthetic envelope"), + ("OH", oh, "a WAI2 whose head spans several chunks"), + ]) +} + +// ------------------------------------------------------------- case evaluation + +/// The corpus as one case sees it. +struct Corpus { + dir: PathBuf, + objects: BTreeMap>, + /// Files written for cases (proofs), by path. + extra: BTreeMap>, +} + +impl Corpus { + fn object(&self, input: &Value) -> Result, String> { + let mut b = if let Some(id) = input["object"].as_str() { + self.objects.get(id).cloned().ok_or(format!("no object {id}"))? + } else if let Some(f) = input["file"].as_str() { + self.file(f)? + } else { + return Err("a case names an object or a file".into()); + }; + patch(&mut b, &input["patch_object"])?; + Ok(b) + } + fn file(&self, rel: &str) -> Result, String> { + match self.extra.get(rel) { + Some(b) => Ok(b.clone()), + None => read(&self.dir, rel), + } + } +} + +fn patch(b: &mut [u8], p: &Value) -> Result<(), String> { + for e in p.as_array().into_iter().flatten() { + let off = e[0].as_u64().ok_or("patch offset")? as usize; + let bytes = unhex(e[1].as_str().ok_or("patch bytes")?)?; + for (i, x) in bytes.iter().enumerate() { + *b.get_mut(off + i).ok_or("patch past the end")? = *x; + } + } + Ok(()) +} + +fn unhex(s: &str) -> Result, String> { + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| e.to_string())).collect() +} + +fn range_json(r: ByteRange) -> Value { + json!([r.first, r.last]) +} + +fn err(e: &RangeRefusal) -> Value { + match e { + RangeRefusal::Selection(s) => json!({ "error": e.code(), "skipped": skips(s) }), + _ => json!({ "error": e.code() }), + } +} + +fn skips(s: &[(usize, Skip)]) -> Value { + Value::Array(s.iter().map(|(i, k)| json!([i, k.code()])).collect()) +} + +fn strs(v: &Value) -> Vec { + v.as_array().into_iter().flatten().filter_map(|x| x.as_str().map(String::from)).collect() +} + +/// The sink a case describes: what it advertises, the parameter sets it holds (one +/// file each), what it applies with a rendition, its order, and a declared union. +fn selector(c: &Corpus, input: &Value) -> Result { + let adv = strs(&input["advertises"]); + let applies = strs(&input["applies"]); + let held: Vec> = strs(&input["held"]).iter().map(|p| c.file(p)).collect::>()?; + let mut s = Selector::new(move |cap| adv.iter().any(|a| a == cap)); + s.applies = Box::new(move |e: &RenditionMeta| applies.iter().any(|a| *a == e.capability)); + s.verify_pin = Box::new(move |cap, prior| { + let files: Vec> = held.iter().map(|b| HeldBytes::File(b)).collect(); + wai::pin::verify_bytes(cap, prior, &files, READABLE).map(|_| ()) + }); + if let Some(order) = input["order"].as_array() { + let order: Vec = order.iter().filter_map(|v| v.as_u64().map(|x| x as usize)).collect(); + s.order = Some(Box::new(move |_| order.clone())); + } + if let Some(d) = input["declared"].as_str() { + s.declared = Some(codecs::parse_element(d).map_err(|e| e.to_string())?); + } + Ok(s) +} + +fn head(b: &[u8]) -> Result { + match parse_head(b, b.len() as u64).map_err(|e| e.to_string())? { + HeadParse::Head(h) => Ok(h), + HeadParse::Need(n) => Err(format!("needs {n}")), + } +} + +fn select(c: &Corpus, input: &Value) -> Result { + let env = c.object(input)?; + let mut sel = selector(c, input)?; + let w = WaiMulti::from_bytes(&env).map_err(|e| e.to_string())?; + let order: Vec = match &sel.order { + Some(f) => f(&w.manifest), + None => (0..w.manifest.renditions.len()).collect(), + }; + let advertises = &sel.advertises; + let verify = &mut sel.verify_pin; + let eligible = |_: usize, e: &RenditionMeta| if advertises(&e.capability) { Ok(()) } else { Err(Skip::NotEligible) }; + let digest_ok = |i: usize, e: &RenditionMeta| e.payload_digest_matches(&w.renditions[i]); + Ok(match wai::container::select_entries(&w.manifest, &order, eligible, digest_ok, |cap: &str, p: &Prior| verify(cap, p)) { + Ok((i, _, s)) => json!({ "pick": i, "skipped": skips(&s) }), + Err(s) => json!({ "inert": skips(&s) }), + }) +} + +/// An origin for one case: serves the object (after a tamper) and its proof. +fn fetch(c: &Corpus, input: &Value) -> Result { + let env = c.object(&json!({ "object": input["object"] }))?; + let mut served = env.clone(); + let tamper = &input["tamper"]; + patch(&mut served, &tamper["patch_object"])?; + let gzip = tamper["kind"].as_str() == Some("gzip"); + let no_refinement = tamper["kind"].as_str() == Some("no_refinement"); + let proof = HeadProof::build(&env).map_err(|e| e.to_string())?.to_bytes(); + let (_, frag) = parse_fragment(input["fragment"].as_str().unwrap_or("")); + let mut policy = FetchPolicy::default(); + if let Some(t) = input["threshold_permille"].as_u64() { + policy.full_threshold_permille = t as u16; + } + let mut s = FetchSession::new(content_hash(&env), frag, policy, selector(c, input)?); + let r = drive(&mut s, |w| { + let range = match w { + Want::HeadProof => return Some((ResponseMeta { status: 200, ..Default::default() }, proof.clone())), + Want::Range(_, Class::Refinement) if no_refinement => return None, + Want::Whole => None, + Want::Range(r, _) => Some(r.header()), + Want::Rest(r) => Some(format!("bytes={}-", r.first)), + }; + let a = origin::serve(served.len() as u64, range.as_deref()); + let meta = ResponseMeta { status: a.status, content_range: a.content_range, content_encoding: gzip.then(|| "gzip".into()) }; + Some((meta, served[a.body.start as usize..a.body.end as usize].to_vec())) + }); + Ok(match r { + Ok(f) => fetched(&f), + Err(e) => err(&e), + }) +} + +fn fetched(f: &Fetched) -> Value { + let idx = |v: &[(usize, Vec)]| Value::Array(v.iter().map(|(i, _)| json!(i)).collect()); + json!({ + "pick": f.index, + "verified_by": f.verified_by.label(), + "why_whole": f.why_whole.map(WhyWhole::code), + "body_bytes": f.body_bytes, + "requests": f.requests, + "companions": idx(&f.companions), + "refinements": idx(&f.refinements), + "abandoned": f.abandoned, + "skipped": skips(&f.skipped), + "component_sha256": sha(&f.component), + }) +} + +fn class_of(s: &str) -> Result { + match s { + "base" => Ok(Class::Base), + "refinement" => Ok(Class::Refinement), + _ => Err(format!("unknown class {s}")), + } +} + +fn prefix_of(v: &Value) -> Result, String> { + strs(v).iter().map(|s| match s.as_str() { + "WAI1" => Ok(Prefix::Wai1), + "WAI2" => Ok(Prefix::Wai2), + _ => Err(format!("unknown container {s}")), + }).collect() +} + +/// A case's outcome under this build. +fn eval(c: &Corpus, op: &str, input: &Value) -> Result { + Ok(match op { + "parse_head" => { + let b = c.object(input)?; + let n = input["envelope_len"].as_u64().unwrap_or(b.len() as u64); + let p = input["prefix"].as_u64().map_or(b.len(), |p| p as usize).min(b.len()); + match parse_head(&b[..p], n) { + Ok(HeadParse::Need(k)) => json!({ "need": k }), + Ok(HeadParse::Head(h)) => json!({ + "version": if h.version == wai::http_binding::head::Version::Wai1 { "WAI1" } else { "WAI2" }, + "head_len": h.head_len, + "envelope_len": h.envelope_len, + "covered_len": h.covered_len(), + "components": h.components.iter().map(|x| json!([x.start, x.len])).collect::>(), + }), + Err(e) => err(&e), + } + } + "head_proof" => { + let b = c.object(input)?; + let id = input["object"].as_str().ok_or("head_proof names an object")?; + let cid = match input["cid"].as_str() { + Some(h) => wai::http_binding::unhex32(h).ok_or("cid")?, + None => content_hash(&c.objects[id]), + }; + let proof = match input["proof"].as_str() { + Some(p) => c.file(p)?, + None => HeadProof::build(&c.objects[id]).map_err(|e| e.to_string())?.to_bytes(), + }; + match HeadProof::from_bytes(&proof).and_then(|p| p.verify(&cid, &b).map(|()| p)) { + Ok(p) => json!({ "covered_len": p.covered_len, "n_cv": p.cvs.len(), "envelope_len": p.envelope_len }), + Err(e) => err(&e), + } + } + "plan" => { + let b = c.object(input)?; + let h = head(&b)?; + let m = h.manifest_v2().map_err(|e| e.to_string())?; + let present: Vec = m.renditions.iter().map(|e| e.sha256.is_some()).collect(); + let wanted: Vec<(usize, Class)> = input["wanted"].as_array().ok_or("wanted")?.iter() + .map(|w| Ok((w[0].as_u64().ok_or("index")? as usize, class_of(w[1].as_str().ok_or("class")?)?))) + .collect::>()?; + let held: Vec = input["held"].as_array().into_iter().flatten() + .filter_map(|r| Some(ByteRange { first: r[0].as_u64()?, last: r[1].as_u64()? })).collect(); + let policy = FetchPolicy { full_threshold_permille: input["threshold_permille"].as_u64().unwrap_or(900) as u16, ..Default::default() }; + let p = plan_components(&h, &wanted, &present, &held, &policy); + match p.whole_rest { + Some((r, why)) => json!({ "whole_rest": range_json(r), "why": why.code() }), + None => json!({ "requests": p.requests.iter().map(|(r, cl)| json!([r.first, r.last, cl.label()])).collect::>() }), + } + } + "response" => { + let req = input["request"].as_array().map(|r| ByteRange { first: r[0].as_u64().unwrap_or(0), last: r[1].as_u64().unwrap_or(0) }); + let meta = ResponseMeta { + status: input["status"].as_u64().ok_or("status")? as u16, + content_range: input["content_range"].as_str().map(String::from), + content_encoding: input["content_encoding"].as_str().map(String::from), + }; + match check_response(req, &meta, input["body_len"].as_u64().ok_or("body_len")?, input["envelope_len"].as_u64().ok_or("envelope_len")?) { + Ok(r) => json!({ "ok": range_json(r) }), + Err(e) => err(&e), + } + } + "select" => select(c, input)?, + "fetch" => fetch(c, input)?, + "same_pick" => { + let whole = select(c, input)?; + let range = fetch(c, input)?; + let want = match whole.get("pick") { + Some(p) => json!({ "pick": p }), + None => json!({ "inert": true }), + }; + let got = match range.get("pick") { + Some(p) => json!({ "pick": p }), + None if range["error"] == "inert" => json!({ "inert": true }), + None => range.clone(), + }; + if want != got { + return Err(format!("paths differ: whole {whole}, range {range}")); + } + want + } + "codecs" => match codecs::parse(input["value"].as_str().ok_or("value")?) { + Ok(v) => json!({ "elements": v.iter().map(|e| e.as_ref().map(codecs::render)).collect::>() }), + Err(e) => json!({ "error": e.code() }), + }, + "union_floor" => { + let heads: Vec = strs(&input["objects"]).iter().map(|id| head(&c.objects[id])).collect::>()?; + match (codecs::union_of(&heads), codecs::floor_of(&heads)) { + (Ok(u), Ok(f)) => json!({ "union": codecs::render(&u), "floor": codecs::render(&f) }), + (Err(e), _) | (_, Err(e)) => json!({ "error": e.code() }), + } + } + "supports" => { + let floor = codecs::parse_element(input["floor"].as_str().ok_or("floor")?).map_err(|e| e.to_string())?; + let caps = strs(&input["caps"]); + json!({ "supports": codecs::supports(&floor, &prefix_of(&input["containers"])?, |c| caps.iter().any(|x| x == c)) }) + } + other => return Err(format!("unknown op {other}")), + }) +} + +// ------------------------------------------------------------------ the cases + +struct Case { + name: String, + op: &'static str, + input: Value, + /// The reason a negative case exists for, held at generation. + intends: Option<&'static str>, +} + +fn case(name: impl Into, op: &'static str, input: Value) -> Case { + Case { name: name.into(), op, input, intends: None } +} + +fn refused(name: impl Into, op: &'static str, input: Value, code: &'static str) -> Case { + Case { name: name.into(), op, input, intends: Some(code) } +} + +/// A proof file for a case, under `cases/`, built from `p` after `f` changes it. +fn proof_variant(p: &HeadProof, f: impl FnOnce(&mut HeadProof)) -> Vec { + let mut q = p.clone(); + f(&mut q); + q.to_bytes() +} + +fn cases(objs: &BTreeMap>, extra: &mut BTreeMap>) -> Result, String> { + let mut v = Vec::new(); + let o = |id: &str| json!({ "object": id }); + let n = |id: &str| objs[id].len() as u64; + + // parse_head. + for id in ["O1", "O3", "O5", "O9b", "OD", "OH"] { + v.push(case(format!("parse_head_{id}"), "parse_head", o(id))); + } + v.push(case("parse_head_need_OD_300", "parse_head", json!({ "object": "OD", "prefix": 300 }))); + v.push(case("parse_head_need_OD_580", "parse_head", json!({ "object": "OD", "prefix": 580 }))); + v.push(case("parse_head_need_O1_6", "parse_head", json!({ "object": "O1", "prefix": 6 }))); + v.push(refused("object_length_trailing", "parse_head", json!({ "file": "bad/O6.wai" }), "object_length_mismatch")); + v.push(refused("object_length_short", "parse_head", json!({ "object": "O3", "envelope_len": n("O3") + 1 }), "object_length_mismatch")); + v.push(refused("head_malformed_magic", "parse_head", json!({ "object": "O5", "patch_object": [[3, "33"]] }), "head_malformed")); + v.push(refused("head_malformed_offset", "parse_head", json!({ "object": "OD", "patch_object": [[574, "01"]] }), "head_malformed")); + + // head_proof: every object, and every way a proof can be wrong. + for id in objs.keys() { + v.push(case(format!("head_proof_ok_{id}"), "head_proof", o(id))); + } + let p3 = HeadProof::build(&objs["O3"]).map_err(|e| e.to_string())?; + let covered = p3.covered_len as usize; + v.push(refused("head_proof_flip_prefix_O3", "head_proof", json!({ "object": "O3", "patch_object": [[covered - 1, format!("{:02x}", objs["O3"][covered - 1] ^ 1)]] }), "head_proof_mismatch")); + let mut put = |name: &str, bytes: Vec| { + let rel = format!("cases/{name}.hp"); + extra.insert(rel.clone(), bytes); + rel + }; + let f = put("head_proof_flip_cv_O3", proof_variant(&p3, |q| q.cvs[0][0] ^= 1)); + v.push(refused("head_proof_flip_cv_O3", "head_proof", json!({ "object": "O3", "proof": f }), "head_proof_mismatch")); + let f = put("head_proof_drop_cv_O3", proof_variant(&p3, |q| { q.cvs.pop(); })); + v.push(refused("head_proof_drop_cv_O3", "head_proof", json!({ "object": "O3", "proof": f }), "head_proof_mismatch")); + let f = put("head_proof_extra_cv_O3", proof_variant(&p3, |q| q.cvs.push([0x5a; 32]))); + v.push(refused("head_proof_extra_cv_O3", "head_proof", json!({ "object": "O3", "proof": f }), "head_proof_mismatch")); + let f = put("head_proof_unaligned_O3", proof_variant(&p3, |q| q.covered_len += 1)); + v.push(refused("head_proof_unaligned_O3", "head_proof", json!({ "object": "O3", "proof": f }), "head_proof_malformed")); + let f = put("head_proof_envelope_len_O3", proof_variant(&p3, |q| q.envelope_len += 3)); + v.push(case("head_proof_envelope_len_O3", "head_proof", json!({ "object": "O3", "proof": f }))); + let mut bad_magic = p3.to_bytes(); + bad_magic[3] = b'2'; + let f = put("head_proof_magic", bad_magic); + v.push(refused("head_proof_magic", "head_proof", json!({ "object": "O3", "proof": f }), "head_proof_malformed")); + let f = put("head_proof_of_O4", HeadProof::build(&objs["O4"]).map_err(|e| e.to_string())?.to_bytes()); + v.push(refused("head_proof_other_object", "head_proof", json!({ "object": "O3", "proof": f }), "head_proof_malformed")); + // A covered_len one chunk longer than the formula's, with the chaining values that make + // its root match: refused by check 3. + let ph = HeadProof::build(&objs["OH"]).map_err(|e| e.to_string())?; + let wider = covered_proof(&objs["OH"], ph.covered_len + 1024); + let f = put("head_proof_not_formula_OH", wider); + v.push(refused("head_proof_not_formula_OH", "head_proof", json!({ "object": "OH", "proof": f }), "head_proof_malformed")); + + // plan: Appendix D, classes, and a missing digest. + let held = json!([[0, 1008]]); + v.push(case("plan_OD_1", "plan", json!({ "object": "OD", "wanted": [[0, "base"]], "held": held }))); + v.push(case("plan_OD_2", "plan", json!({ "object": "OD", "wanted": [[1, "base"], [2, "base"]], "held": held }))); + let h8 = head(&objs["O8"])?; + let held8 = json!([[0, h8.covered_len() - 1]]); + v.push(case("plan_classes_O8", "plan", json!({ "object": "O8", "wanted": [[0, "base"], [1, "refinement"]], "held": held8 }))); + v.push(case("plan_one_class_O8", "plan", json!({ "object": "O8", "wanted": [[0, "base"], [1, "base"]], "held": held8 }))); + v.push(case("plan_no_digest_O4", "plan", json!({ "object": "O4", "wanted": [[0, "base"]], "held": [[0, 1008]] }))); + v.push(case("plan_O3_high", "plan", json!({ "object": "O3", "wanted": [[1, "base"], [2, "base"]], "held": [[0, 1008]] }))); + v.push(case("plan_held_O3", "plan", json!({ "object": "O3", "wanted": [[0, "base"]], "held": [[0, 1008], [2000, 2999]] }))); + + // response: §7.1. + let req = json!([1009, 3593]); + let r206 = |cr: &str, enc: Value, body: u64| json!({ "request": req, "status": 206, "content_range": cr, "content_encoding": enc, "body_len": body, "envelope_len": 53794 }); + v.push(case("response_ok", "response", r206("bytes 1009-3593/53794", Value::Null, 2585))); + v.push(refused("response_wrong_range", "response", r206("bytes 1009-3592/53794", Value::Null, 2584), "content_range_mismatch")); + v.push(refused("response_short_body", "response", r206("bytes 1009-3593/53794", Value::Null, 2000), "content_range_mismatch")); + v.push(refused("response_gzip", "response", r206("bytes 1009-3593/53794", json!("gzip"), 2585), "content_coding_present")); + v.push(refused("response_complete_length", "response", r206("bytes 1009-3593/53795", Value::Null, 2585), "complete_length_mismatch")); + v.push(refused("response_complete_unknown", "response", r206("bytes 1009-3593/*", Value::Null, 2585), "complete_length_mismatch")); + v.push(case("response_identity", "response", r206("bytes 1009-3593/53794", json!("identity"), 2585))); + v.push(refused("response_304", "response", json!({ "request": req, "status": 304, "body_len": 0, "envelope_len": 53794 }), "http_status_unexpected")); + v.push(refused("response_200_short", "response", json!({ "request": req, "status": 200, "body_len": 1000, "envelope_len": 53794 }), "complete_length_mismatch")); + v.push(case("response_200_whole", "response", json!({ "request": req, "status": 200, "body_len": 53794, "envelope_len": 53794 }))); + v.push(case("response_past_end", "response", json!({ "request": [0, 16368], "status": 206, "content_range": "bytes 0-899/900", "body_len": 900, "envelope_len": 900 }))); + v.push(refused("response_206_unasked", "response", json!({ "request": null, "status": 206, "content_range": "bytes 0-9/900", "body_len": 10, "envelope_len": 900 }), "http_status_unexpected")); + + // select: SPEC §7.1 on the whole envelope. + let video = json!(["wai.video.av1"]); + let images = json!(["wai.neural.int_mlicpp", "wai.image.avif", "wai.image.jxl", "wai.image.png"]); + v.push(case("select_O2_held", "select", json!({ "object": "O2", "advertises": images, "held": [MLICPP_SET] }))); + v.push(case("select_O2_not_held", "select", json!({ "object": "O2", "advertises": images, "held": [] }))); + v.push(case("select_O2_png_only", "select", json!({ "object": "O2", "advertises": ["wai.image.png"], "held": [] }))); + v.push(case("select_O3", "select", json!({ "object": "O3", "advertises": video }))); + v.push(case("select_O7", "select", json!({ "object": "O7", "advertises": video }))); + v.push(case("select_O3_nothing", "select", json!({ "object": "O3", "advertises": [] }))); + v.push(case("select_O9a_int_hyper", "select", json!({ "object": "O9a", "advertises": ["wai.video.int_hyper"], "held": [] }))); + + // fetch: the session against an origin, every path. + let frag = |id: &str, h: Option| -> String { + let mut s = format!("#n={}", n(id)); + if let Some(h) = h { + s.push_str(&format!("&h={h}")); + } + s + }; + let cov = |id: &str| -> Result { Ok(head(&objs[id])?.covered_len()) }; + let hdr = json!(["wai.meta.hdr10plus"]); + v.push(case("fetch_O3_low", "fetch", json!({ "object": "O3", "fragment": frag("O3", Some(cov("O3")?)), "advertises": video, "applies": hdr }))); + v.push(case("fetch_O3_low_default_head", "fetch", json!({ "object": "O3", "advertises": video, "applies": hdr }))); + v.push(case("fetch_O3_high", "fetch", json!({ "object": "O3", "fragment": frag("O3", Some(cov("O3")?)), "advertises": video, "applies": hdr, "order": [1, 0, 2] }))); + v.push(case("fetch_OD_1", "fetch", json!({ "object": "OD", "fragment": frag("OD", Some(1009)), "advertises": video, "applies": hdr }))); + v.push(case("fetch_OD_2", "fetch", json!({ "object": "OD", "fragment": frag("OD", Some(1009)), "advertises": video, "applies": hdr, "order": [1, 0, 2] }))); + v.push(case("fetch_OD_2_no_threshold", "fetch", json!({ "object": "OD", "fragment": frag("OD", Some(1009)), "advertises": video, "applies": hdr, "order": [1, 0, 2], "threshold_permille": 1001 }))); + v.push(case("fetch_O4", "fetch", json!({ "object": "O4", "fragment": frag("O4", Some(1009)), "advertises": video }))); + v.push(case("fetch_O7", "fetch", json!({ "object": "O7", "fragment": frag("O7", Some(1009)), "advertises": video }))); + // A byte of `low` after the covered head, changed under the same name. + let at = cov("O3")? + 500; + assert!(head(&objs["O3"])?.components[0].start + head(&objs["O3"])?.components[0].len > at); + v.push(refused("component_tamper_O3", "fetch", json!({ "object": "O3", "fragment": frag("O3", Some(1009)), "advertises": video, + "tamper": { "patch_object": [[at, format!("{:02x}", objs["O3"][at as usize] ^ 1)]] } }), "object_digest_mismatch")); + v.push(refused("head_tamper_O3", "fetch", json!({ "object": "O3", "advertises": video, + "tamper": { "patch_object": [[40, format!("{:02x}", objs["O3"][40] ^ 1)]] } }), "head_proof_mismatch")); + v.push(refused("fetch_gzip_O3", "fetch", json!({ "object": "O3", "advertises": video, "tamper": { "kind": "gzip" } }), "content_coding_present")); + let avc = json!(["wai.video.avc"]); + let lcevc = json!(["wai.video.lcevc"]); + v.push(case("fetch_O8_deadline", "fetch", json!({ "object": "O8", "fragment": frag("O8", Some(cov("O8")?)), "advertises": avc, "applies": lcevc, "tamper": { "kind": "no_refinement" } }))); + v.push(case("fetch_O8_full", "fetch", json!({ "object": "O8", "fragment": frag("O8", Some(cov("O8")?)), "advertises": avc, "applies": lcevc }))); + v.push(case("fetch_O8_no_enhancement", "fetch", json!({ "object": "O8", "fragment": frag("O8", Some(cov("O8")?)), "advertises": avc, "applies": [] }))); + v.push(case("fetch_O2_held", "fetch", json!({ "object": "O2", "advertises": images, "held": [MLICPP_SET] }))); + v.push(case("fetch_O2_not_held", "fetch", json!({ "object": "O2", "advertises": images, "held": [] }))); + v.push(case("fetch_O1", "fetch", json!({ "object": "O1", "advertises": ["wai.neural.int_mlicpp"], "held": [MLICPP_SET] }))); + v.push(case("fetch_O1_not_held", "fetch", json!({ "object": "O1", "advertises": ["wai.neural.int_mlicpp"], "held": [] }))); + v.push(case("fetch_O5_small", "fetch", json!({ "object": "O5", "fragment": frag("O5", None), "advertises": ["wai.text.zstd"] }))); + v.push(case("fetch_O9b_wai1", "fetch", json!({ "object": "O9b", "fragment": frag("O9b", Some(cov("O9b")?)), "advertises": video }))); + v.push(case("fetch_OH", "fetch", json!({ "object": "OH", "advertises": ["wai.text.zstd"] }))); + v.push(refused("fetch_O9a_undeclared", "fetch", json!({ "object": "O9a", "advertises": video, "declared": "wai2.video.av1" }), "codecs_undeclared")); + v.push(case("fetch_O9a_declared", "fetch", json!({ "object": "O9a", "advertises": video, "declared": "wai2.video.av1|video.int_hyper" }))); + + // same_pick: §7.2 over every WAI2 object, four sinks and three orders. + let sinks: [(&str, Value, Value); 4] = [ + ("video", json!(["wai.video.av1", "wai.video.avc"]), json!(["wai.meta.hdr10plus", "wai.video.lcevc"])), + ("image", images.clone(), json!([])), + ("other", json!(["wai.video.int_hyper", "wai.image.png", "wai.text.zstd"]), json!([])), + ("none", json!([]), json!([])), + ]; + for id in ["O2", "O3", "O4", "O7", "O8", "O9a", "OD", "OH"] { + let k = WaiMulti::from_bytes(&objs[id]).map_err(|e| e.to_string())?.manifest.renditions.len(); + let orders: [Vec; 3] = [(0..k).collect(), (0..k).rev().collect(), (1..k).chain([0]).collect()]; + for (sname, adv, applies) in &sinks { + for (oi, order) in orders.iter().enumerate() { + v.push(case(format!("same_pick_{id}_{sname}_{oi}"), "same_pick", + json!({ "object": id, "advertises": adv, "applies": applies, "held": [MLICPP_SET], "order": order, "fragment": frag(id, Some(1009)) }))); + } + } + } + + // codecs: §4.1. + for (i, ok) in ["wai2.video.av1+meta.hdr10plus|video.int_hyper", "wai1.video.av1", "mp4a.40.2,wai1.video.av1, avc1.42E01E", + "wai2.video.avc+video.lcevc", "wai2.video.int_motion+video.int_refine", "wai2.neural.int_mlicpp|image.avif"].iter().enumerate() { + v.push(case(format!("codecs_ok_{i}"), "codecs", json!({ "value": ok }))); + } + for (name, bad, code) in [ + ("codecs_uppercase", "wai1.Video.av1", "codecs_malformed"), ("codecs_empty_segment", "wai1.video..av1", "codecs_malformed"), + ("codecs_wai3", "wai3.video.av1", "codecs_malformed"), ("codecs_plus_standalone", "wai1.video.av1+video.vp9", "codecs_malformed"), + ("codecs_companion_first", "wai1.meta.hdr10plus", "codecs_malformed"), ("codecs_refinement_first", "wai2.video.int_refine", "codecs_malformed"), + ("codecs_derivation", "wai1.derive.crop", "codecs_not_media"), ("codecs_record", "wai1.video.manifest", "codecs_not_media"), + ("codecs_candidate", "wai1.splat.sog", "codecs_candidate"), ("codecs_one_segment", "wai1.video", "codecs_malformed"), + ] { + v.push(refused(name, "codecs", json!({ "value": bad }), code)); + } + + // union and floor: §4.2. + v.push(case("union_O9", "union_floor", json!({ "objects": ["O9a", "O9b", "O9c"] }))); + v.push(case("union_O3", "union_floor", json!({ "objects": ["O3"] }))); + v.push(case("union_O8", "union_floor", json!({ "objects": ["O8"] }))); + v.push(case("union_O2", "union_floor", json!({ "objects": ["O2"] }))); + v.push(case("union_wai1_only", "union_floor", json!({ "objects": ["O9b", "O9c"] }))); + v.push(refused("floor_none", "union_floor", json!({ "objects": ["O9b", "O2"] }), "codecs_no_common_capability")); + + // supports: §4.2, §12.3. + let both = json!(["WAI1", "WAI2"]); + v.push(case("supports_floor", "supports", json!({ "floor": "wai2.video.av1", "containers": both, "caps": ["wai.video.av1"] }))); + v.push(case("supports_union_only", "supports", json!({ "floor": "wai2.video.av1", "containers": both, "caps": ["wai.video.int_hyper"] }))); + v.push(case("supports_container_gate", "supports", json!({ "floor": "wai2.video.av1", "containers": ["WAI1"], "caps": ["wai.video.av1"] }))); + v.push(case("supports_wai1", "supports", json!({ "floor": "wai1.video.av1", "containers": ["WAI1"], "caps": ["wai.video.av1"] }))); + v.push(case("supports_companion_ignored", "supports", json!({ "floor": "wai2.video.av1+meta.hdr10plus", "containers": both, "caps": ["wai.video.av1"] }))); + Ok(v) +} + +/// A head proof of `env` claiming `covered` (aligned) with the chaining values that +/// make its root match: what a producer that rounds `covered_len` wrongly writes. +fn covered_proof(env: &[u8], covered: u64) -> Vec { + use blake3::hazmat::{left_subtree_len, HasherExt}; + let mut x = wai::jwp_hash::DOMAIN_OBJECT.to_vec(); + x.extend_from_slice(env); + fn walk(x: &[u8], start: u64, len: u64, c: u64, out: &mut Vec<[u8; 32]>) { + if start >= c { + let mut h = blake3::Hasher::new(); + h.set_input_offset(start); + h.update(&x[start as usize..(start + len) as usize]); + out.push(h.finalize_non_root()); + } else if start + len > c { + let l = left_subtree_len(len); + walk(x, start, l, c, out); + walk(x, start + l, len - l, c, out); + } + } + let (n, c) = (x.len() as u64, 15 + covered); + let l = left_subtree_len(n); + let mut cvs = Vec::new(); + walk(&x, 0, l, c, &mut cvs); + walk(&x, l, n - l, c, &mut cvs); + HeadProof { cid: content_hash(env), envelope_len: env.len() as u64, covered_len: covered, cvs }.to_bytes() +} + +// ------------------------------------------------------------- generate / verify + +fn json_lines(cases: &[(String, &str, Value, Value)]) -> Vec { + let mut out = String::from("[\n"); + for (i, (name, op, input, expect)) in cases.iter().enumerate() { + let line = json!({ "name": name, "op": op, "input": input, "expect": expect }); + out.push_str(&serde_json::to_string(&line).expect("json")); + out.push_str(if i + 1 < cases.len() { ",\n" } else { "\n" }); + } + out.push_str("]\n"); + out.into_bytes() +} + +/// Every generated file, by path relative to the corpus. +fn generate(dir: &Path) -> Result>, String> { + check_sources(dir)?; + let mut files = BTreeMap::new(); + let mut objs = BTreeMap::new(); + let mut listing = serde_json::Map::new(); + for (id, bytes, what) in objects(dir)? { + let cid = content_hash(&bytes); + let h = head(&bytes)?; + let union = codecs::element_of(&h).map(|e| codecs::render(&e)).ok(); + let name = format!("objects/{}", wai::http_binding::object_name(&cid)); + listing.insert(id.to_string(), json!({ + "cid": hex(&cid), "path": name, "n": bytes.len(), "h": h.covered_len(), "codecs": union, "what": what, + })); + files.insert(format!("{name}.hp"), HeadProof::build(&bytes).map_err(|e| e.to_string())?.to_bytes()); + files.insert(format!("{name}.etag"), format!("\"{}\"\n", hex(&cid)).into_bytes()); + files.insert(name, bytes.clone()); + objs.insert(id.to_string(), bytes); + } + let mut trailing = objs["O5"].clone(); + trailing.push(0); + files.insert("bad/O6.wai".into(), trailing); + files.insert("objects.json".into(), { + let mut s = serde_json::to_string_pretty(&Value::Object(listing)).expect("json"); + s.push('\n'); + s.into_bytes() + }); + let mut extra = BTreeMap::new(); + extra.insert("bad/O6.wai".to_string(), files["bad/O6.wai"].clone()); + let list = cases(&objs, &mut extra)?; + let corpus = Corpus { dir: dir.to_path_buf(), objects: objs, extra: extra.clone() }; + let mut out = Vec::new(); + for c in &list { + let expect = eval(&corpus, c.op, &c.input).map_err(|e| format!("{}: {e}", c.name))?; + if let Some(code) = c.intends { + if expect["error"] != code { + return Err(format!("{} exists for {code}, and this build gives {expect}", c.name)); + } + } + out.push((c.name.clone(), c.op, c.input.clone(), expect)); + } + // Appendix D's numbers, as the extension states them. + let od = &corpus.objects["OD"]; + let pd = HeadProof::build(od).map_err(|e| e.to_string())?; + if hex(&content_hash(od)) != "eecd29409ee510d39ef1b17653a3ae9a2a176c1d76c02dde3319f014a3ef3228" || pd.to_bytes().len() != 245 || od.len() != 53_794 { + return Err("OD no longer has Appendix D's cid, length and proof size".into()); + } + for (k, b) in extra { + if k.starts_with("cases/") { + files.insert(k, b); + } + } + files.insert("cases.json".into(), json_lines(&out)); + Ok(files) +} + +/// The frozen sources have the SHA-256 values `src/SOURCES.json` states. +fn check_sources(dir: &Path) -> Result<(), String> { + let listing: Value = serde_json::from_slice(&read(dir, "src/SOURCES.json")?).map_err(|e| e.to_string())?; + for (name, v) in listing["files"].as_object().ok_or("SOURCES.json has files")? { + let got = sha(&read(dir, &format!("src/{name}"))?); + if v["sha256"].as_str() != Some(got.as_str()) { + return Err(format!("src/{name} has SHA-256 {got}, and SOURCES.json states {}", v["sha256"])); + } + } + Ok(()) +} + +fn verify(dir: &Path) -> Result { + let mut ok = true; + // Every committed case, evaluated by this build. + let committed: Vec = serde_json::from_slice(&read(dir, "cases.json")?).map_err(|e| e.to_string())?; + let listing: Value = serde_json::from_slice(&read(dir, "objects.json")?).map_err(|e| e.to_string())?; + let mut objs = BTreeMap::new(); + for (id, o) in listing.as_object().ok_or("objects.json is an object")? { + let b = read(dir, o["path"].as_str().ok_or("path")?)?; + if hex(&content_hash(&b)) != o["cid"].as_str().unwrap_or("") { + println!("FAIL object {id}: its bytes do not hash to its cid"); + ok = false; + } + objs.insert(id.clone(), b); + } + let corpus = Corpus { dir: dir.to_path_buf(), objects: objs, extra: BTreeMap::new() }; + let mut pass = 0; + for c in &committed { + let name = c["name"].as_str().unwrap_or("?"); + match eval(&corpus, c["op"].as_str().unwrap_or(""), &c["input"]) { + Ok(got) if got == c["expect"] => pass += 1, + Ok(got) => { + println!("FAIL {name}: expected {}, got {got}", c["expect"]); + ok = false; + } + Err(e) => { + println!("FAIL {name}: {e}"); + ok = false; + } + } + } + println!("{pass}/{} cases give their outcome", committed.len()); + // Every file regenerates to the committed bytes. + match generate(dir) { + Ok(files) => { + for (rel, want) in &files { + match std::fs::read(dir.join(rel)) { + Ok(have) if &have == want => {} + _ => { + println!("DIFF {rel}: regeneration does not give the committed bytes"); + ok = false; + } + } + } + println!("{} generated files compared", files.len()); + } + Err(e) => { + println!("FAIL regeneration: {e}"); + ok = false; + } + } + Ok(ok) +} + +// ---------------------------------------------------------------- differential + +/// splitmix64. +fn next(s: &mut u64) -> u64 { + *s = s.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = *s; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) +} + +/// `count` random `WAI2` envelopes, sizes spread across chunk boundaries from 1 KiB +/// to 8 MiB, each as a JSON line with its cid, length, covered prefix and proof. +fn differential(seed: u64, count: usize) -> Result, String> { + let mut s = seed; + let mut out = String::new(); + for i in 0..count { + let exp = 10 + (next(&mut s) % 14); + let jitter = next(&mut s) % 2048; + let total = ((1u64 << exp) + jitter - 1024).clamp(1100, 8 << 20) as usize; + let k = 1 + (next(&mut s) % 4) as usize; + let note = "n".repeat((next(&mut s) % 3000) as usize); + let probe = WaiMulti::new("binary", "replicate", json!({ "i": i, "note": note }), + (0..k).map(|_| Rendition::media("wai.text.zstd", "zstd", Vec::new())).collect()).to_bytes().map_err(|e| e.to_string())?; + if probe.len() >= total { + continue; + } + let mut left = total - probe.len(); + let mut rends = Vec::new(); + for j in 0..k { + let len = if j + 1 == k { left } else { (next(&mut s) as usize) % (left + 1) }; + left -= len; + let a = (next(&mut s) % 251) as usize + 1; + rends.push(Rendition::media("wai.text.zstd", "zstd", pattern(len, a, j)).with_payload_digest()); + } + let env = WaiMulti::new("binary", "replicate", json!({ "i": i, "note": note }), rends).to_bytes().map_err(|e| e.to_string())?; + let p = HeadProof::build(&env).map_err(|e| e.to_string())?; + let line = json!({ + "cid": hex(&content_hash(&env)), "envelope_len": env.len(), + "prefix": hex(&env[..p.covered_len as usize]), "proof": hex(&p.to_bytes()), + }); + out.push_str(&serde_json::to_string(&line).expect("json")); + out.push('\n'); + } + Ok(out.into_bytes()) +} diff --git a/wai/wai-rs/src/bin/wai_http_vectors.rs b/wai/wai-rs/src/bin/wai_http_vectors.rs new file mode 100644 index 000000000..6c6c39928 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_http_vectors.rs @@ -0,0 +1,27 @@ +//! `wai_http_vectors` — writes and verifies the HTTP delivery corpus +//! (`extensions/http-delivery.md` §14, `wai/http-conformance/`). The same tool is +//! `wai http vectors`. +//! +//! wai_http_vectors gen +//! wai_http_vectors verify +//! wai_http_vectors diff --seed S --count N --out F +//! +//! Exit status 1 when a case does not give its committed outcome or a file does not +//! regenerate to its committed bytes, each named on its own line. + +use std::process::ExitCode; + +#[path = "wai_cli/http_vectors.rs"] +mod http_vectors; + +fn main() -> ExitCode { + let args: Vec = std::env::args().skip(1).collect(); + match http_vectors::main(&args) { + Ok(true) => ExitCode::SUCCESS, + Ok(false) => ExitCode::from(1), + Err(e) => { + eprintln!("error: {e}"); + ExitCode::from(2) + } + } +} -- 2.54.0 (Apple Git-157) From e8dbf68c78cae02bbdf3f8ff2be8a79d58cc4fb3 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 5 Oct 2026 07:51:43 -0400 Subject: [PATCH 06/31] wai: http_verify.py, the BLAKE3 tree in _blake3.py, and OH with chaining values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The corpus's second implementation, written from the extension text and SPEC §7.1, standard library only: head, covered_len, the head proof (and each committed proof rebuilt from the text), response checks, plans, selection, the byte-range procedure with its fallback, codecs union, floor and support. It agrees with wai_http_vectors on every case. It checks itself first against a subset of the published BLAKE3 test vectors, its tree functions included; `--mutate` (left-half, no-root, counter-zero, floor-as-codecs, blake3-iv) makes it fail the cases each defect reaches; `diff` verifies the proofs `wai_http_vectors diff` writes and refuses exactly the ones it flips. claim-conformance/_blake3.py, already shared by claim_verify and staged_verify, gains subtree_cv and parent_cv; its hash is unchanged (claim_verify and staged_verify pass). OH now carries a payload past its covered head: verified whole, the counter-zero mutation had nothing to reach. Co-Authored-By: Claude Opus 5.5 --- wai/claim-conformance/_blake3.py | 44 +- wai/http-conformance/.gitattributes | 2 + wai/http-conformance/README.md | 97 ++ wai/http-conformance/blake3_vectors.json | 73 ++ wai/http-conformance/cases.json | 31 +- .../cases/head_proof_not_formula_OH.hp | Bin 53 -> 149 bytes wai/http-conformance/http_verify.py | 891 ++++++++++++++++++ wai/http-conformance/objects.json | 10 +- ...107cd5d738100064b4fec189b540f7b7ae03f6.wai | Bin 5389 -> 0 bytes ...5d738100064b4fec189b540f7b7ae03f6.wai.etag | 1 - ...cd5d738100064b4fec189b540f7b7ae03f6.wai.hp | Bin 53 -> 0 bytes ...9e1df9c867492bdd4009109e5195ed58638ef3.wai | Bin 0 -> 23519 bytes ...9c867492bdd4009109e5195ed58638ef3.wai.etag | 1 + ...df9c867492bdd4009109e5195ed58638ef3.wai.hp | Bin 0 -> 149 bytes wai/wai-rs/src/bin/wai_cli/http_vectors.rs | 7 +- 15 files changed, 1130 insertions(+), 27 deletions(-) create mode 100644 wai/http-conformance/.gitattributes create mode 100644 wai/http-conformance/README.md create mode 100644 wai/http-conformance/blake3_vectors.json create mode 100644 wai/http-conformance/http_verify.py delete mode 100644 wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai delete mode 100644 wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai.etag delete mode 100644 wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai.hp create mode 100644 wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai create mode 100644 wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai.etag create mode 100644 wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai.hp diff --git a/wai/claim-conformance/_blake3.py b/wai/claim-conformance/_blake3.py index 89865deb7..a8de73f0e 100644 --- a/wai/claim-conformance/_blake3.py +++ b/wai/claim-conformance/_blake3.py @@ -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]) diff --git a/wai/http-conformance/.gitattributes b/wai/http-conformance/.gitattributes new file mode 100644 index 000000000..eff1df73a --- /dev/null +++ b/wai/http-conformance/.gitattributes @@ -0,0 +1,2 @@ +# Hashed files: a line-ending conversion on checkout would change every digest. +* -text diff --git a/wai/http-conformance/README.md b/wai/http-conformance/README.md new file mode 100644 index 000000000..17034625b --- /dev/null +++ b/wai/http-conformance/README.md @@ -0,0 +1,97 @@ +# http-conformance — HTTP delivery + +The conformance corpus of [http-delivery](../extensions/http-delivery.md) (§14): objects +served as `.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/.wai` | each object, named by its content hash (§5.2) | +| `objects/.wai.hp` | its head proof (§6.2) | +| `objects/.wai.etag` | its `ETag` value, `""` (§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 | +| OH | a `WAI2` whose head spans several chunks | covered subtrees past the first chunk | + +The HDR10+ block, the AVC base and the LCEVC enhancement are synthetic: they carry the +right start codes and decode to nothing, which transport does not need. OD's payloads +are Appendix D's byte patterns. 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` and `declared`), 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` | +| `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` | + +`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 ranges reach 900 ‰ +of the bytes after `covered_len` (`threshold_permille` changes it); a refinement's +request abandoned when `tamper.kind` is `no_refinement`. The origin answers single +ranges with `206`, and a `tamper` changes the bytes it serves (`patch_object`) or +labels them `gzip`. Body bytes count the head proof. + +## 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), 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. diff --git a/wai/http-conformance/blake3_vectors.json b/wai/http-conformance/blake3_vectors.json new file mode 100644 index 000000000..529093df2 --- /dev/null +++ b/wai/http-conformance/blake3_vectors.json @@ -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" + } + ] +} diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index 484c96685..d9598fcba 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -4,7 +4,7 @@ {"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":5295,"envelope_len":5389,"covered_len":5389,"components":[[5295,94]]}}, +{"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}}, @@ -23,7 +23,7 @@ {"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_OH","op":"head_proof","input":{"object":"OH"},"expect":{"covered_len":5389,"n_cv":0,"envelope_len":5389}}, +{"name":"head_proof_ok_OH","op":"head_proof","input":{"object":"OH"},"expect":{"covered_len":6129,"n_cv":3,"envelope_len":23519}}, {"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"}}, @@ -79,7 +79,8 @@ {"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":{"pick":0,"verified_by":"whole-object","why_whole":"below_one_chunk","body_bytes":251,"requests":1,"companions":[],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}}, {"name":"fetch_O9b_wai1","op":"fetch","input":{"object":"O9b","fragment":"#n=18004&h=1009","advertises":["wai.video.av1"]},"expect":{"pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":18217,"requests":3,"companions":[],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"edd595431a8992d78fd6f691c6b066efa102ac8c1ffb822505639e0d640174a4"}}, -{"name":"fetch_OH","op":"fetch","input":{"object":"OH","advertises":["wai.text.zstd"]},"expect":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":5442,"requests":2,"companions":[],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}}, +{"name":"fetch_OH","op":"fetch","input":{"object":"OH","advertises":["wai.video.av1"]},"expect":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":23668,"requests":3,"companions":[],"refinements":[],"abandoned":[],"skipped":[],"component_sha256":"dbf116e87034120670d459dbc4812644799b1dc3ae29cc070659103948ca59bc"}}, +{"name":"fetch_OH_short_head","op":"fetch","input":{"object":"OH","fragment":"#n=23519&h=1009","advertises":["wai.video.av1"]},"expect":{"pick":0,"verified_by":"whole-object","why_whole":"range_threshold","body_bytes":23668,"requests":4,"companions":[],"refinements":[],"abandoned":[],"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":{"pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":19099,"requests":3,"companions":[],"refinements":[],"abandoned":[],"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}}, @@ -166,18 +167,18 @@ {"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=5389&h=1009"},"expect":{"inert":true}}, -{"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=5389&h=1009"},"expect":{"inert":true}}, -{"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=5389&h=1009"},"expect":{"inert":true}}, -{"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=5389&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=5389&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=5389&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=5389&h=1009"},"expect":{"pick":0}}, -{"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=5389&h=1009"},"expect":{"pick":0}}, -{"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=5389&h=1009"},"expect":{"pick":0}}, -{"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=5389&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=5389&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=5389&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]}}, diff --git a/wai/http-conformance/cases/head_proof_not_formula_OH.hp b/wai/http-conformance/cases/head_proof_not_formula_OH.hp index 07f155eb15fc0d006b30caeab8258b2190ee0ee0..73b77f0b87bf36baa3e6a71171211fe756040fb2 100644 GIT binary patch literal 149 zcmWId2r&F}(Lp=?nT5XSeMQ+$v`~J!IpC2k$y0SNck- zOZ{nneKRh&V&59y3q7&3eHYklNZRq8*Y)r|_Dw%R;tsBm)`>eF?4bBQ;o=7$0KxW1 AV*mgE literal 53 zcmWId2r%5pdVgv)TT{XVmU79d0yS5!TL>_uZ25PvbF0Jm?dzDo@rp8l0k0&4W&i*= ChY=|N diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py new file mode 100644 index 000000000..c0f9ec0d4 --- /dev/null +++ b/wai/http-conformance/http_verify.py @@ -0,0 +1,891 @@ +#!/usr/bin/env python3 +"""Second verifier for http-conformance/ — HTTP delivery (extensions/http-delivery.md). + +Written from the extension text and SPEC §7.1. It shares no code with wai-rs, which +generates the corpus: the head (§2, §5.1), covered_len, the head proof (§6), the +response checks (§7.1), the plans and classes of the reference client (§7, §10.1), the +byte-range procedure with its whole-object fallback (§7), SPEC §7.1's selection with +companions and refinements, the codecs grammar, union, floor and support (§4), all +written here. BLAKE3 is ../claim-conformance/_blake3.py (written from its specification, +shared with the other Python verifiers) and SHA-256 is hashlib. The registry's kinds, +roles and classes are read from ../registry/capabilities.json, the published export. +Only the standard library is needed. + +Pins (SPEC §3.1) are read as far as the corpus needs: a pin applies to a capability that +decodes against a parameter set (registry class neural-int-exact, neural-entropy-exact, +neural-float or generative-exact) or that the registry does not hold; a form-less pin +resolves when the sink holds a file whose SHA-256 is the pin's; a pin in the set form +resolves to nothing here, since the corpus's sinks hold no set listing. Any other shape +stops the verifier, never passes. + + python3 http_verify.py [dir] verify every case and the self-test + python3 http_verify.py [dir] --mutate NAME the same with a deliberate defect: + left-half | no-root | counter-zero | floor-as-codecs | blake3-iv + python3 http_verify.py diff FILE [--flip N --seed S] + verify every head proof `wai_http_vectors diff` wrote; with --flip, change one + chaining-value byte in N of them, chosen by S, and require exactly those refused + +Exit status 0 only when every case gives its committed outcome (or, for diff, every +proof verifies, or exactly the flipped ones fail). +""" +import hashlib, json, os, random, sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) +import _blake3 as B3 # noqa: E402 + +MUTATE = None +DOMAIN = b"moq-jwp:object\x01" +READABLE = ["wai.prior.set"] + + +class Refusal(Exception): + def __init__(self, code, skipped=None): + super().__init__(code) + self.code, self.skipped = code, skipped + + +def sha(b): + return hashlib.sha256(b).hexdigest() + + +def blake3(b): + return B3.blake3(b) + + +def content_hash(env): + return blake3(DOMAIN + env) + + +# ------------------------------------------------------------------ the registry + +REG = {} + + +def load_registry(): + d = json.load(open(os.path.join(HERE, "..", "registry", "capabilities.json"))) + for c in d["capabilities"]: + REG[c["capability"]] = c + + +def kind(cap): + return REG[cap]["kind"] if cap in REG else None + + +def candidate(cap): + return cap in REG and REG[cap]["status"] == "candidate" + + +def dispatchable(cap): + k = kind(cap) + return k is None or (k == "payload" and not candidate(cap)) + + +def enhancement(cap): + return cap in REG and REG[cap]["role"] == "enhancement" + + +def pin_applies(cap): + return cap not in REG or REG[cap]["replicate"] in ("neural-int-exact", "neural-entropy-exact", "neural-float", "generative-exact") + + +# ---------------------------------------------------------------------- the head + +def u32(b, at): + return int.from_bytes(b[at:at + 4], "little") + + +def parse_head(prefix, n): + """§2, §5.1: ("need", k) or a head dict; Refusal on a malformed or mis-sized object.""" + if len(prefix) < 8: + return ("need", 8) + magic = prefix[:4] + if magic not in (b"WAI1", b"WAI2"): + raise Refusal("head_malformed") + ml = u32(prefix, 4) + if magic == b"WAI1": + head_len = 12 + ml + if len(prefix) < head_len: + return ("need", head_len) + comps = [(head_len, u32(prefix, 8 + ml))] + else: + if len(prefix) < 10 + ml: + return ("need", 10 + ml) + k = int.from_bytes(prefix[8 + ml:10 + ml], "little") + head_len = 10 + ml + 8 * k + if len(prefix) < head_len: + return ("need", head_len) + comps, expected = [], 0 + for i in range(k): + off, ln = u32(prefix, 10 + ml + 8 * i), u32(prefix, 14 + ml + 8 * i) + if off != expected: + raise Refusal("head_malformed") + comps.append((head_len + off, ln)) + expected += ln + if head_len + sum(c[1] for c in comps) != n: + raise Refusal("object_length_mismatch") + try: + m = json.loads(prefix[8:8 + ml].decode("utf-8"), object_pairs_hook=unique_members) + except (ValueError, UnicodeDecodeError): + raise Refusal("head_malformed") + if magic == b"WAI2" and len(m.get("renditions", [])) != len(comps): + raise Refusal("head_malformed") + return {"version": magic.decode(), "head_len": head_len, "envelope_len": n, "components": comps, "manifest": m} + + +def unique_members(pairs): + keys = [k for k, _ in pairs] + if len(keys) != len(set(keys)): + raise ValueError("a member named twice") + return dict(pairs) + + +def covered_len(head_len, n): + return min(n, 1024 * -(-(15 + head_len) // 1024) - 15) + + +# ------------------------------------------------------------------ the head proof + +def left(n): + if MUTATE == "left-half": + return max(1, (n // 2) // 1024) * 1024 + return 1024 * 2 ** ((((n - 1) // 1024)).bit_length() - 1) + + +def merge(l, r, root): + if MUTATE == "no-root": + root = False + return B3.parent_cv(l, r, root) + + +def subtree(x, start): + return B3.subtree_cv(x, 0 if MUTATE == "counter-zero" else start) + + +def read_proof(b): + if len(b) < 53 or b[:4] != b"WHP1": + raise Refusal("head_proof_malformed") + k = b[52] + if k > 64 or len(b) != 53 + 32 * k: + raise Refusal("head_proof_malformed") + n, c = int.from_bytes(b[36:44], "little"), int.from_bytes(b[44:52], "little") + if not (c == n or (c < n and (15 + c) % 1024 == 0)) or (c == n and k): + raise Refusal("head_proof_malformed") + return {"cid": b[4:36], "n": n, "covered": c, "cvs": [b[53 + 32 * i:85 + 32 * i] for i in range(k)]} + + +def verify_proof(p, cid, prefix): + """§6.3, checks 1-3, in order.""" + if p["cid"] != cid or len(prefix) < p["covered"]: + raise Refusal("head_proof_malformed") + covered = prefix[:p["covered"]] + if p["covered"] == p["n"]: + if content_hash(covered) != cid: + raise Refusal("head_proof_mismatch") + else: + x, L, c = DOMAIN + covered, 15 + p["n"], 15 + p["covered"] + it = iter(p["cvs"]) + + def cv(start, ln): + if start >= c: + v = next(it, None) + if v is None: + raise Refusal("head_proof_mismatch") + return v + if start + ln <= c: + return subtree(x[start:start + ln], start) + l = left(ln) + return merge(cv(start, l), cv(start + l, ln - l), False) + + l = left(L) + root = merge(cv(0, l), cv(l, L - l), True) + if next(it, None) is not None or root != cid: + raise Refusal("head_proof_mismatch") + try: + h = parse_head(covered, p["n"]) + except Refusal as e: + if e.code == "object_length_mismatch": + raise Refusal("head_proof_malformed") + raise + if isinstance(h, tuple) or covered_len(h["head_len"], p["n"]) != p["covered"]: + raise Refusal("head_proof_malformed") + + +# ------------------------------------------------------------- responses (§7.1) + +def check_response(req, status, cr, enc, body_len, n): + coded = enc is not None and enc.strip() and enc.strip().lower() != "identity" + if status == 206: + if req is None: + raise Refusal("http_status_unexpected") + if coded: + raise Refusal("content_coding_present") + try: + span, complete = (cr or "").strip()[len("bytes "):].split("/") if (cr or "").strip().startswith("bytes ") else (None, None) + a, b = (int(x) for x in span.split("-")) + except (ValueError, AttributeError): + raise Refusal("content_range_mismatch") + if a != req[0] or b != min(req[1], n - 1) or b < a or body_len != b - a + 1: + raise Refusal("content_range_mismatch") + if not complete.isdigit() or int(complete) != n: + raise Refusal("complete_length_mismatch") + return (a, b) + if status == 200: + if coded: + raise Refusal("content_coding_present") + if body_len != n: + raise Refusal("complete_length_mismatch") + return (0, n - 1) + raise Refusal("http_status_unexpected") + + +def origin_serve(n, rng): + """An origin's answer to a Range header on n bytes (RFC 9110 §14), as the corpus's + origin gives it: (status, Content-Range, first, end).""" + if rng is None or not rng.startswith("bytes=") or "," in rng: + return 200, None, 0, n + a, b = rng[len("bytes="):].split("-") + if a == "": + s = int(b) + return 206, "bytes %d-%d/%d" % (max(0, n - s), n - 1, n), max(0, n - s), n + first = int(a) + last = n - 1 if b == "" else min(int(b), n - 1) + if first >= n: + return 416, "bytes */%d" % n, 0, 0 + return 206, "bytes %d-%d/%d" % (first, last, n), first, last + 1 + + +# ------------------------------------------------------------------ selection + +def selectable(e): + return e.get("role") not in ("companion", "refinement") and dispatchable(e["capability"]) + + +def resolve_pin(cap, prior, held): + if not isinstance(prior, dict) or "sha256" not in prior: + raise SystemExit("http_verify: a pin shape this verifier does not read") + form = prior.get("form") + if form is None: + return "ok" if any(sha(h) == prior["sha256"] for h in held) else "prior_not_held" + if form in READABLE: + return "prior_not_held" + raise SystemExit("http_verify: a pin form this verifier does not read") + + +def select(m, order, adv, held, digest_ok): + """SPEC §7.1 step 2 in `order`: (pick, skipped) or Refusal('inert', skipped).""" + entries, skipped = m["renditions"], [] + sec = m.get("security") + if isinstance(sec, dict) and sec.get("encryption") not in (None, "none"): + raise Refusal("inert", [[i, "unsupported_encryption"] for i in range(len(entries))]) + for i in order: + if i >= len(entries): + continue + e = entries[i] + if not selectable(e): + skipped.append([i, "not_selectable"]); continue + if e["capability"] not in adv: + skipped.append([i, "missing_capability"]); continue + if not digest_ok(i, e): + skipped.append([i, "component_digest_mismatch"]); continue + if e.get("prior") is not None and pin_applies(e["capability"]): + r = resolve_pin(e["capability"], e["prior"], held) + if r != "ok": + skipped.append([i, r]); continue + return i, skipped + raise Refusal("inert", skipped) + + +def dispatch_wai1(m, adv, held): + mr = m["model_requirement"] + cap, fb = mr["capability"], mr.get("fallback") + if kind(cap) in ("companion", "refinement"): + raise Refusal("inert", [[0, "missing_capability"]]) + first = None + for c in [cap, fb]: + if c is None or not dispatchable(c) or c not in adv: + continue + p = m.get("conditioning", {}).get("prior") + if p is not None and pin_applies(c): + r = resolve_pin(c, p, held) + if r == "ok": + return 0 + first = first or r + continue + return 0 + raise Refusal("inert", [[0, first or "missing_capability"]]) + + +def companions_of(entries, i): + base = entries[i] + out = [] + for j, e in enumerate(entries): + if e.get("role") != "companion": + continue + cid = e.get("companion_of_id") + if (cid is not None and base.get("id") == cid) or (cid is None and e.get("companion_of") == base["capability"]): + out.append(j) + return out + + +def refinements_of(entries, i): + bid = entries[i].get("id") + if bid is None: + return [] + return [j for j, e in enumerate(entries) if e.get("role") == "refinement" and isinstance(e.get("refines"), dict) and e["refines"].get("base") == bid] + + +def payload_ok(e, payload): + return e.get("sha256") is None or e["sha256"] == sha(payload) + + +# ------------------------------------------------------------------- the plan + +def plan(head, wanted, held, permille): + """§7 steps 5-6 and the threshold: ("rest", (first, last), why) or ("ranges", [...]).""" + n, cov = head["envelope_len"], covered_len(head["head_len"], head["envelope_len"]) + if cov == n: + return ("ranges", []) + rest = (cov, n - 1) + entries = head["manifest"]["renditions"] + for i, _ in wanted: + if entries[i].get("sha256") is None: + return ("rest", rest, "component_digest_absent") + pieces = [] + for i, cl in wanted: + s, ln = head["components"][i] + segs = [(s, s + ln)] + for (hf, hl) in held: + nxt = [] + for a, b in segs: + hs, he = hf, hl + 1 + if he <= a or hs >= b: + nxt.append((a, b)); continue + if a < hs: + nxt.append((a, hs)) + if he < b: + nxt.append((he, b)) + segs = nxt + pieces += [(0 if cl == "base" else 1, a, b) for a, b in segs] + pieces.sort() + merged = [] + for c, a, b in pieces: + if merged and merged[-1][0] == c and merged[-1][2] >= a: + merged[-1][2] = max(merged[-1][2], b) + else: + merged.append([c, a, b]) + total = sum(b - a for _, a, b in merged) + if total * 1000 >= permille * (n - cov): + return ("rest", rest, "range_threshold") + return ("ranges", [(a, b - 1, "base" if c == 0 else "refinement") for c, a, b in merged if b > a]) + + +# ---------------------------------------------------------------- the codecs + +def cap_ok(cap): + parts = cap.split(".") + lc = lambda s, extra="": s and all(ch in "abcdefghijklmnopqrstuvwxyz0123456789" + extra for ch in s) + return len(parts) >= 2 and lc(parts[0]) and all(lc(p, "_") for p in parts[1:]) + + +def check_alt(alt): + for i, c in enumerate(alt): + if not cap_ok(c): + raise Refusal("codecs_malformed") + full = "wai." + c + k = kind(full) + if k in ("derivation", "record"): + raise Refusal("codecs_not_media") + if candidate(full): + raise Refusal("codecs_candidate") + applied = k in ("companion", "refinement") + if i == 0 and applied: + raise Refusal("codecs_malformed") + if i > 0 and k == "payload": + raise Refusal("codecs_malformed") + + +def parse_element(s): + if "." not in s: + raise Refusal("codecs_malformed") + prefix, rest = s.split(".", 1) + if prefix not in ("wai1", "wai2"): + raise Refusal("codecs_malformed") + alts = [a.split("+") for a in rest.split("|")] + for a in alts: + check_alt(a) + return (prefix, alts) + + +def render(e): + return e[0] + "." + "|".join("+".join(a) for a in e[1]) + + +def parse_codecs(value): + out = [] + for e in (x.strip() for x in value.split(",")): + first = e.split(".")[0] + if len(first) > 3 and first[:3].lower() == "wai" and first[3:].isdigit(): + out.append(render(parse_element(e))) + else: + out.append(None) + return out + + +def short(c): + return c[4:] if c.startswith("wai.") else c + + +def element_of(head): + m = head["manifest"] + if head["version"] == "WAI1": + mr = m["model_requirement"] + alts = [[short(mr["capability"])]] + if mr.get("fallback") and short(mr["fallback"]) != alts[0][0]: + alts.append([short(mr["fallback"])]) + prefix = "wai1" + else: + e, alts, prefix = m["renditions"], [], "wai2" + for i, r in enumerate(e): + if r.get("role") in ("companion", "refinement"): + continue + cap = short(r["capability"]) + a = next((x for x in alts if x[0] == cap), None) + if a is None: + a = [cap]; alts.append(a) + for j in sorted(companions_of(e, i) + refinements_of(e, i)): + p = short(e[j]["capability"]) + if p not in a[1:]: + a.append(p) + pos = lambda p: next(k for k, r in enumerate(e) if short(r["capability"]) == p) + alts = [[a[0]] + sorted(a[1:], key=pos) for a in alts] + for a in alts: + check_alt(a) + return (prefix, alts) + + +def union_of(heads): + if MUTATE == "floor-as-codecs": + return floor_of(heads, mutate_ok=True) + prefix, alts = "wai1", [] + for h in heads: + p, al = element_of(h) + prefix = max(prefix, p) + for a in al: + x = next((x for x in alts if x[0] == a[0]), None) + if x is None: + alts.append(list(a)) + else: + x += [c for c in a[1:] if c not in x[1:]] + if not alts: + raise Refusal("codecs_no_common_capability") + return (prefix, alts) + + +def floor_of(heads, mutate_ok=False): + els = [element_of(h) for h in heads] + prefix = max(e[0] for e in els) + seen = [] + for e in els: + for a in e[1]: + if a[0] not in [s[0] for s in seen]: + seen.append(list(a)) + else: + x = next(s for s in seen if s[0] == a[0]) + x += [c for c in a[1:] if c not in x[1:]] + out = [] + for a in seen: + found = [next((x for x in e[1] if x[0] == a[0]), None) for e in els] + if any(f is None for f in found): + continue + out.append([a[0]] + [c for c in a[1:] if all(c in f[1:] for f in found)]) + if not out: + raise Refusal("codecs_no_common_capability") + return (prefix, out) + + +def check_declared(head, union): + p, alts = element_of(head) + if p > union[0]: + raise Refusal("codecs_undeclared") + for a in alts: + u = next((x for x in union[1] if x[0] == a[0]), None) + if u is None or any(c not in u[1:] for c in a[1:]): + raise Refusal("codecs_undeclared") + + +def supports(floor, containers, caps): + reads = "WAI1" in containers and (floor[0] == "wai1" or "WAI2" in containers) + return reads and any(("wai." + a[0]) in caps for a in floor[1]) + + +# ------------------------------------------------------------- the session (§7) + +def fetch(env, served, gzip, no_refinement, frag, sink, permille): + """The reference client against an origin serving `served` under the name of `env`.""" + cid, n_real = content_hash(env), len(served) + proof_bytes = make_proof_file(env) + st = {"bytes": 0, "requests": 0} + held = [] # (first, last, bytes) + + def get(rng, is_refinement=False): + st["requests"] += 1 + if is_refinement and no_refinement: + return None + status, cr, a, b = origin_serve(n_real, rng) + body = served[a:b] + st["bytes"] += len(body) + return status, cr, ("gzip" if gzip else None), body + + def have(first, end): + out, at = b"", first + while at < end: + best = max((h for h in held if h[0] <= at <= h[1]), key=lambda h: h[1], default=None) + if best is None: + return None + take = min(best[1] + 1, end) + out += best[2][at - best[0]:take - best[0]] + at = take + return out + + def prefix_len(): + at = 0 + while True: + e = max((h[1] + 1 for h in held if h[0] <= at <= h[1]), default=None) + if e is None: + return at + at = e + + adv, applies, held_sets, order, declared = sink + n = frag.get("n") + if n is not None and n < 1009: + status, cr, enc, body = get(None) + check_response(None, status, cr, enc, len(body), len(body)) + if content_hash(body) != cid: + raise Refusal("object_digest_mismatch") + return whole(body, "below_one_chunk", sink, st) + e = frag.get("h") or 16369 + if n is not None: + e = min(e, n) + r_head = get("bytes=0-%d" % (e - 1)) + st["requests"] += 1 + st["bytes"] += len(proof_bytes) + p = read_proof(proof_bytes) + if p["cid"] != cid: + raise Refusal("head_proof_malformed") + status, cr, enc, body = r_head + a, b = check_response((0, e - 1), status, cr, enc, len(body), p["n"]) + held.append((a, b, body)) + if prefix_len() < p["covered"]: + h0 = prefix_len() + status, cr, enc, body = get("bytes=%d-%d" % (h0, p["covered"] - 1)) + a, b = check_response((h0, p["covered"] - 1), status, cr, enc, len(body), p["n"]) + held.append((a, b, body)) + prefix = have(0, p["covered"]) + verify_proof(p, cid, prefix) + head = parse_head(prefix, p["n"]) + if declared is not None: + check_declared(head, parse_element(declared)) + + def rest(why): + cov = p["covered"] + if cov < p["n"] and have(cov, p["n"]) is None: + status, cr, enc, body = get("bytes=%d-" % cov) + a, b = check_response((cov, p["n"] - 1), status, cr, enc, len(body), p["n"]) + held.append((a, b, body)) + envb = have(0, p["n"]) + if content_hash(envb) != cid: + raise Refusal("object_digest_mismatch") + return whole(envb, why, sink, st) + + m = head["manifest"] + if head["version"] == "WAI1": + dispatch_wai1(m, adv, held_sets) + return rest("single_component") + entries = m["renditions"] + ordr = order if order is not None else list(range(len(entries))) + pick, skipped = select(m, ordr, adv, held_sets, lambda i, e: True) + wanted = [(pick, "base")] + for j in companions_of(entries, pick): + if entries[j]["capability"] in applies: + wanted.append((j, "refinement" if enhancement(entries[j]["capability"]) else "base")) + for j in refinements_of(entries, pick): + if entries[j]["capability"] in applies: + wanted.append((j, "refinement")) + pl = plan(head, wanted, [(h[0], h[1]) for h in held], permille) + if pl[0] == "rest": + return rest(pl[2]) + abandoned = [] + for (a, b, cl) in pl[1]: + r = get("bytes=%d-%d" % (a, b), cl == "refinement") + if r is None: + for j, wc in wanted: + s, ln = head["components"][j] + if wc == "refinement" and s <= b and a < s + ln and j not in abandoned: + abandoned.append(j) + continue + status, cr, enc, body = r + ra, rb = check_response((a, b), status, cr, enc, len(body), p["n"]) + held.append((ra, rb, body)) + parts = {} + for j, _ in wanted: + if j in abandoned: + continue + s, ln = head["components"][j] + bts = have(s, s + ln) + if entries[j].get("sha256") is None or sha(bts) != entries[j]["sha256"]: + return rest("component_digest_mismatch") + parts[j] = bts + comps = [j for j, _ in wanted[1:] if j in parts and entries[j].get("role") == "companion"] + refs = [j for j, _ in wanted[1:] if j in parts and entries[j].get("role") != "companion"] + return {"pick": pick, "verified_by": "head-proof", "why_whole": None, "body_bytes": st["bytes"], "requests": st["requests"], + "companions": comps, "refinements": refs, "abandoned": abandoned, "skipped": skipped, "component_sha256": sha(parts[pick])} + + +def whole(envb, why, sink, st): + adv, applies, held_sets, order, declared = sink + head = parse_head(envb, len(envb)) + if declared is not None: + check_declared(head, parse_element(declared)) + m = head["manifest"] + if head["version"] == "WAI1": + dispatch_wai1(m, adv, held_sets) + s, ln = head["components"][0] + return {"pick": 0, "verified_by": "whole-object", "why_whole": why, "body_bytes": st["bytes"], "requests": st["requests"], + "companions": [], "refinements": [], "abandoned": [], "skipped": [], "component_sha256": sha(envb[s:s + ln])} + entries = m["renditions"] + pay = [envb[s:s + ln] for s, ln in head["components"]] + ordr = order if order is not None else list(range(len(entries))) + pick, skipped = select(m, ordr, adv, held_sets, lambda i, e: payload_ok(e, pay[i])) + comps, refs = [], [] + for j in companions_of(entries, pick): + if entries[j]["capability"] in applies and payload_ok(entries[j], pay[j]): + comps.append(j) + for j in refinements_of(entries, pick): + if entries[j]["capability"] in applies and payload_ok(entries[j], pay[j]): + refs.append(j) + return {"pick": pick, "verified_by": "whole-object", "why_whole": why, "body_bytes": st["bytes"], "requests": st["requests"], + "companions": comps, "refinements": refs, "abandoned": [], "skipped": skipped, "component_sha256": sha(pay[pick])} + + +def make_proof_file(env): + """A producer's head proof (§6.2), written from the text: the chaining values of the + subtrees at or after c, in the order §6.3 consumes them.""" + h = parse_head(env, len(env)) + cov = covered_len(h["head_len"], len(env)) + cvs = [] + if cov < len(env): + x, L, c = DOMAIN + env, 15 + len(env), 15 + cov + + def walk(start, ln): + if start >= c: + cvs.append(B3.subtree_cv(x[start:start + ln], start)) + elif start + ln > c: + l = 1024 * 2 ** ((((ln - 1) // 1024)).bit_length() - 1) + walk(start, l); walk(start + l, ln - l) + + l = 1024 * 2 ** ((((L - 1) // 1024)).bit_length() - 1) + walk(0, l); walk(l, L - l) + return b"WHP1" + content_hash(env) + len(env).to_bytes(8, "little") + cov.to_bytes(8, "little") + bytes([len(cvs)]) + b"".join(cvs) + + +# ----------------------------------------------------------------- the cases + +def apply_patch(b, patch): + b = bytearray(b) + for off, hx in patch or []: + bs = bytes.fromhex(hx) + b[off:off + len(bs)] = bs + return bytes(b) + + +def frag_of(s): + out = {} + if s and "#" in s: + for kv in s.split("#", 1)[1].split("&"): + if "=" in kv: + k, v = kv.split("=", 1) + if k in ("n", "h") and v.isdigit(): + out[k] = int(v) + return out + + +def sink_of(d, inp): + held = [open(os.path.join(d, p), "rb").read() for p in inp.get("held") or []] + return (inp.get("advertises") or [], inp.get("applies") or [], held, inp.get("order"), inp.get("declared")) + + +def refusal(e): + out = {"error": e.code} + if e.skipped is not None: + out["skipped"] = e.skipped + return out + + +def evaluate(d, objs, op, inp): + def obj(i): + if "object" in i: + b = objs[i["object"]] + else: + b = open(os.path.join(d, i["file"]), "rb").read() + return apply_patch(b, i.get("patch_object")) + + try: + if op == "parse_head": + b = obj(inp) + n = inp.get("envelope_len", len(b)) + p = min(inp.get("prefix", len(b)), len(b)) + h = parse_head(b[:p], n) + if isinstance(h, tuple): + return {"need": h[1]} + return {"version": h["version"], "head_len": h["head_len"], "envelope_len": h["envelope_len"], + "covered_len": covered_len(h["head_len"], n), "components": [list(c) for c in h["components"]]} + if op == "head_proof": + b = obj(inp) + cid = content_hash(objs[inp["object"]]) + pf = open(os.path.join(d, inp["proof"]), "rb").read() if "proof" in inp else make_proof_file(objs[inp["object"]]) + p = read_proof(pf) + verify_proof(p, cid, b) + return {"covered_len": p["covered"], "n_cv": len(p["cvs"]), "envelope_len": p["n"]} + if op == "plan": + b = obj(inp) + h = parse_head(b, len(b)) + pl = plan(h, [tuple(w) for w in inp["wanted"]], [tuple(r) for r in inp.get("held", [])], inp.get("threshold_permille", 900)) + if pl[0] == "rest": + return {"whole_rest": list(pl[1]), "why": pl[2]} + return {"requests": [list(r) for r in pl[1]]} + if op == "response": + r = check_response(inp.get("request"), inp["status"], inp.get("content_range"), inp.get("content_encoding"), inp["body_len"], inp["envelope_len"]) + return {"ok": list(r)} + if op == "select": + b = obj(inp) + h = parse_head(b, len(b)) + pay = [b[s:s + ln] for s, ln in h["components"]] + adv, _, held, order, _ = sink_of(d, inp) + order = order if order is not None else list(range(len(pay))) + try: + i, sk = select(h["manifest"], order, adv, held, lambda i, e: payload_ok(e, pay[i])) + return {"pick": i, "skipped": sk} + except Refusal as e: + return {"inert": e.skipped} + if op in ("fetch", "same_pick"): + env = objs[inp["object"]] + t = inp.get("tamper") or {} + served = apply_patch(env, t.get("patch_object")) + r = fetch(env, served, t.get("kind") == "gzip", t.get("kind") == "no_refinement", frag_of(inp.get("fragment")), + sink_of(d, inp), inp.get("threshold_permille", 900)) + if op == "fetch": + return r + whole_pick = evaluate(d, objs, "select", inp) + if "pick" not in whole_pick or whole_pick["pick"] != r["pick"]: + return {"paths_differ": [whole_pick, r]} + return {"pick": r["pick"]} + if op == "codecs": + return {"elements": parse_codecs(inp["value"])} + if op == "union_floor": + hs = [parse_head(objs[i], len(objs[i])) for i in inp["objects"]] + return {"union": render(union_of(hs)), "floor": render(floor_of(hs))} + if op == "supports": + return {"supports": supports(parse_element(inp["floor"]), inp["containers"], inp["caps"])} + except Refusal as e: + if op == "same_pick" and e.code == "inert": + try: + evaluate(d, objs, "select", inp)["inert"] + return {"inert": True} + except KeyError: + return {"paths_differ": "range inert, whole picks"} + return refusal(e) + raise SystemExit("unknown op " + op) + + +# ------------------------------------------------------------------ self-test + +def self_test(): + v = json.load(open(os.path.join(HERE, "blake3_vectors.json"))) + for c in v["cases"]: + data = bytes(i % 251 for i in range(c["input_len"])) + got = blake3(data) + if MUTATE == "blake3-iv": + got = bytes([got[0] ^ 1]) + got[1:] + if got.hex() != c["hash"]: + print("FAIL self-test: BLAKE3 of %d bytes" % c["input_len"]) + return False + if c["input_len"] > 1024: + n = c["input_len"] + l = 1024 * 2 ** ((((n - 1) // 1024)).bit_length() - 1) + if B3.parent_cv(B3.subtree_cv(data[:l], 0), B3.subtree_cv(data[l:], l), True) != got: + print("FAIL self-test: the tree functions over %d bytes" % n) + return False + return True + + +def run(d): + load_registry() + ok = self_test() + listing = json.load(open(os.path.join(d, "objects.json"))) + objs = {} + for i, o in listing.items(): + b = open(os.path.join(d, o["path"]), "rb").read() + if content_hash(b).hex() != o["cid"]: + print("FAIL object %s: its bytes do not hash to its cid" % i) + ok = False + if open(os.path.join(d, o["path"] + ".hp"), "rb").read() != make_proof_file(b): + print("FAIL object %s: its head proof is not the one the text gives" % i) + ok = False + objs[i] = b + cases = json.load(open(os.path.join(d, "cases.json"))) + passed = 0 + for c in cases: + got = evaluate(d, objs, c["op"], c["input"]) + if got == c["expect"]: + passed += 1 + else: + print("FAIL %s: expected %s, got %s" % (c["name"], json.dumps(c["expect"]), json.dumps(got))) + ok = False + print("%d/%d cases give their outcome%s" % (passed, len(cases), " (mutation %s)" % MUTATE if MUTATE else "")) + return ok + + +def diff(path, flip, seed): + load_registry() + lines = [json.loads(l) for l in open(path) if l.strip()] + rng = random.Random(seed) + candidates = [i for i, l in enumerate(lines) if len(bytes.fromhex(l["proof"])) > 53] + flipped = set(rng.sample(candidates, flip)) if flip else set() + refused = set() + for i, l in enumerate(lines): + proof = bytearray(bytes.fromhex(l["proof"])) + if i in flipped: + k = proof[52] + at = 53 + 32 * rng.randrange(k) + rng.randrange(32) + proof[at] ^= 1 << rng.randrange(8) + try: + p = read_proof(bytes(proof)) + if p["n"] != l["envelope_len"]: + raise Refusal("head_proof_malformed") + verify_proof(p, bytes.fromhex(l["cid"]), bytes.fromhex(l["prefix"])) + except Refusal: + refused.add(i) + print("%d proofs; %d flipped; %d refused" % (len(lines), len(flipped), len(refused))) + if refused != flipped: + print("FAIL: refused %s, flipped %s" % (sorted(refused - flipped)[:10], sorted(flipped - refused)[:10])) + return False + return True + + +def main(argv): + global MUTATE + if "--mutate" in argv: + i = argv.index("--mutate") + MUTATE = argv[i + 1] + argv = argv[:i] + argv[i + 2:] + if argv and argv[0] == "diff": + flip = int(argv[argv.index("--flip") + 1]) if "--flip" in argv else 0 + seed = int(argv[argv.index("--seed") + 1]) if "--seed" in argv else 1 + return 0 if diff(argv[1], flip, seed) else 1 + d = argv[0] if argv else HERE + return 0 if run(d) else 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/wai/http-conformance/objects.json b/wai/http-conformance/objects.json index 96f62a43f..d6352a946 100644 --- a/wai/http-conformance/objects.json +++ b/wai/http-conformance/objects.json @@ -88,11 +88,11 @@ "what": "Appendix D's synthetic envelope" }, "OH": { - "cid": "b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6", - "path": "objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai", - "n": 5389, - "h": 5389, - "codecs": "wai2.text.zstd", + "cid": "fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3", + "path": "objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai", + "n": 23519, + "h": 6129, + "codecs": "wai2.video.av1", "what": "a WAI2 whose head spans several chunks" } } diff --git a/wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai b/wai/http-conformance/objects/b105df957b068260e004771995107cd5d738100064b4fec189b540f7b7ae03f6.wai deleted file mode 100644 index c8c879f7739d4146c22da4c5d5318bf831a3c7f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5389 zcmeIuze^)Q6bJACvBo76N>pS*3*VbGgenB0B-DX^r+a03e=~=P zV*2f_kW82RN%Zdh)=3O@&eb`Ww<6Cseb)(C&a*-*@Liur5U|M2f@4OJ={ecJwsXGC zfH{$$&-yMiA@8}a6I!&jlcox4MXm>JQd!YM7ATN1ND=Wwky1q#373_6F(xVor4LSG z4vZvKkO@in9_uZ25PvbF0Jm?dzDo@rp8l0gPq<06Aw7 AB>(^b diff --git a/wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai b/wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai new file mode 100644 index 0000000000000000000000000000000000000000..c2c2c3e9173a29106498e52de7b68985fdc8f2dc GIT binary patch literal 23519 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai.etag b/wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai.etag new file mode 100644 index 000000000..3ffb4a4f3 --- /dev/null +++ b/wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai.etag @@ -0,0 +1 @@ +"fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3" diff --git a/wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai.hp b/wai/http-conformance/objects/fcc4e739d1ddb9fbe55d45ec3e9e1df9c867492bdd4009109e5195ed58638ef3.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..c5810c7143f5a6b86df60e245a1843b4f40b98e5 GIT binary patch literal 149 zcmWId2r&F}fl)S8w?Mi*tcUA=j;XkXUzL!qmfpPm-mHAi~N?6$puTjgx0hiu&N;9ZC0N?%EJ zsXxuHZ^i{z>|5h|p(l2>?*f|*Njtvtx*p!gzUfCu+`$#nI&sH?9TeXuT>Rhz09K7h A82|tP literal 0 HcmV?d00001 diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs index ef03e6c4a..72235cc8b 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs @@ -153,8 +153,8 @@ fn objects(dir: &Path) -> Result, &'static str)>, Str Rendition::media("wai.video.av1", "av1", pattern(50_000, 13, 5)).with_id("high").with_payload_digest(), Rendition::companion("wai.meta.hdr10plus", pattern(200, 31, 1), "wai.video.av1").with_id("hdr").with_payload_digest().with_companion_of_id("high"), ])?; - let oh = w2("text", json!({ "note": "a manifest long enough that the head spans several chunks: ".to_string() + &"x".repeat(5000) }), - vec![Rendition::media("wai.text.zstd", "zstd", src("small.zst")?).with_id("text").with_payload_digest()])?; + let oh = w2("video", json!({ "w": 320, "h": 180, "note": "a manifest long enough that the head spans several chunks: ".to_string() + &"x".repeat(5000) }), + vec![av1("seg2.av1", "v")?.with_payload_digest()])?; Ok(vec![ ("O1", o1, "WAI1, pinned, below one chunk is not reached: the head proof covers it whole"), ("O2", o2, "WAI2 image: a pinned integer learned rendition, AVIF, JPEG XL and PNG of one picture"), @@ -603,7 +603,8 @@ fn cases(objs: &BTreeMap>, extra: &mut BTreeMap> v.push(case("fetch_O1_not_held", "fetch", json!({ "object": "O1", "advertises": ["wai.neural.int_mlicpp"], "held": [] }))); v.push(case("fetch_O5_small", "fetch", json!({ "object": "O5", "fragment": frag("O5", None), "advertises": ["wai.text.zstd"] }))); v.push(case("fetch_O9b_wai1", "fetch", json!({ "object": "O9b", "fragment": frag("O9b", Some(cov("O9b")?)), "advertises": video }))); - v.push(case("fetch_OH", "fetch", json!({ "object": "OH", "advertises": ["wai.text.zstd"] }))); + v.push(case("fetch_OH", "fetch", json!({ "object": "OH", "advertises": video }))); + v.push(case("fetch_OH_short_head", "fetch", json!({ "object": "OH", "fragment": frag("OH", Some(1009)), "advertises": video }))); v.push(refused("fetch_O9a_undeclared", "fetch", json!({ "object": "O9a", "advertises": video, "declared": "wai2.video.av1" }), "codecs_undeclared")); v.push(case("fetch_O9a_declared", "fetch", json!({ "object": "O9a", "advertises": video, "declared": "wai2.video.av1|video.int_hyper" }))); -- 2.54.0 (Apple Git-157) From 1cddbdad19098519b7b977b43f4084dec44ec304 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 5 Oct 2026 07:52:43 -0400 Subject: [PATCH 07/31] wai: http-conformance falsification patches and their runner Twelve patches, each breaking one function (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, the union, the trailing- byte rule, the missing-digest plan, the class merge, the selection digest test), each naming the cases that must fail. run.sh applies each to a fresh worktree, requires the build, and requires verify to fail naming them; then the Python mutations and the differential. Co-Authored-By: Claude Opus 5.5 --- .../falsify/accept-gzip.patch | 15 +++++ .../falsify/allow-trailing.patch | 15 +++++ wai/http-conformance/falsify/counter.patch | 15 +++++ .../falsify/drop-domain.patch | 14 +++++ .../falsify/intersection.patch | 29 +++++++++ wai/http-conformance/falsify/left-len.patch | 15 +++++ .../falsify/merge-classes.patch | 15 +++++ .../falsify/no-root-flag.patch | 15 +++++ wai/http-conformance/falsify/run.sh | 60 +++++++++++++++++++ .../falsify/select-no-digest.patch | 15 +++++ .../falsify/skip-digest.patch | 15 +++++ .../falsify/skip-on-absent.patch | 15 +++++ .../falsify/trust-content-range.patch | 15 +++++ 13 files changed, 253 insertions(+) create mode 100644 wai/http-conformance/falsify/accept-gzip.patch create mode 100644 wai/http-conformance/falsify/allow-trailing.patch create mode 100644 wai/http-conformance/falsify/counter.patch create mode 100644 wai/http-conformance/falsify/drop-domain.patch create mode 100644 wai/http-conformance/falsify/intersection.patch create mode 100644 wai/http-conformance/falsify/left-len.patch create mode 100644 wai/http-conformance/falsify/merge-classes.patch create mode 100644 wai/http-conformance/falsify/no-root-flag.patch create mode 100755 wai/http-conformance/falsify/run.sh create mode 100644 wai/http-conformance/falsify/select-no-digest.patch create mode 100644 wai/http-conformance/falsify/skip-digest.patch create mode 100644 wai/http-conformance/falsify/skip-on-absent.patch create mode 100644 wai/http-conformance/falsify/trust-content-range.patch diff --git a/wai/http-conformance/falsify/accept-gzip.patch b/wai/http-conformance/falsify/accept-gzip.patch new file mode 100644 index 000000000..486127640 --- /dev/null +++ b/wai/http-conformance/falsify/accept-gzip.patch @@ -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 05e6cb08..5ac70c9f 100644 +--- a/wai/wai-rs/src/http_binding/session.rs ++++ b/wai/wai-rs/src/http_binding/session.rs +@@ -143,7 +143,7 @@ pub fn check_response(req: Option, r: &ResponseMeta, body_len: u64, e + let whole = ByteRange::from_len(0, envelope_len); + 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 => { diff --git a/wai/http-conformance/falsify/allow-trailing.patch b/wai/http-conformance/falsify/allow-trailing.patch new file mode 100644 index 000000000..8b44bc2aa --- /dev/null +++ b/wai/http-conformance/falsify/allow-trailing.patch @@ -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(); +- 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(); diff --git a/wai/http-conformance/falsify/counter.patch b/wai/http-conformance/falsify/counter.patch new file mode 100644 index 000000000..f22f7ba1a --- /dev/null +++ b/wai/http-conformance/falsify/counter.patch @@ -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)?; diff --git a/wai/http-conformance/falsify/drop-domain.patch b/wai/http-conformance/falsify/drop-domain.patch new file mode 100644 index 000000000..a80747661 --- /dev/null +++ b/wai/http-conformance/falsify/drop-domain.patch @@ -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() + } diff --git a/wai/http-conformance/falsify/intersection.patch b/wai/http-conformance/falsify/intersection.patch new file mode 100644 index 000000000..b6ea21549 --- /dev/null +++ b/wai/http-conformance/falsify/intersection.patch @@ -0,0 +1,29 @@ +# 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 8c0cc84c..8463f03f 100644 +--- a/wai/wai-rs/src/http_binding/codecs.rs ++++ b/wai/wai-rs/src/http_binding/codecs.rs +@@ -230,6 +230,7 @@ pub fn element_of(head: &Head) -> Result { + /// The union of a sequence of objects (§4.2): every `alt` any of them has, by first + /// appearance, each with every capability applied with it in any of them. + pub fn union_of(heads: &[Head]) -> Result { ++ if !heads.is_empty() { return floor_of_inner(heads); } + let mut prefix = Prefix::Wai1; + let mut alts: Vec = Vec::new(); + for h in heads { +@@ -255,6 +256,14 @@ pub fn union_of(heads: &[Head]) -> Result { + /// The floor of a sequence of objects (§4.2): the `alt`s whose capability every + /// object has, each with the capabilities applied with it in every object, written + /// with the union's prefix. Empty: `codecs_no_common_capability`. ++fn floor_of_inner(heads: &[Head]) -> Result { ++ let each: Vec = heads.iter().map(element_of).collect::>()?; ++ let first = each.first().ok_or(CodecsError::NoCommonCapability)?.clone(); ++ let alts: Vec = first.alts.into_iter().filter(|a| each.iter().all(|e| e.alts.iter().any(|x| x.cap == a.cap))).collect(); ++ if alts.is_empty() { return Err(CodecsError::NoCommonCapability); } ++ Ok(Element { prefix: each.iter().map(|e| e.prefix).max().unwrap_or(Prefix::Wai1), alts }) ++} ++ + pub fn floor_of(heads: &[Head]) -> Result { + let union = union_of(heads)?; + let each: Vec = heads.iter().map(element_of).collect::>()?; diff --git a/wai/http-conformance/falsify/left-len.patch b/wai/http-conformance/falsify/left-len.patch new file mode 100644 index 000000000..e09b4f81c --- /dev/null +++ b/wai/http-conformance/falsify/left-len.patch @@ -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. diff --git a/wai/http-conformance/falsify/merge-classes.patch b/wai/http-conformance/falsify/merge-classes.patch new file mode 100644 index 000000000..a51827528 --- /dev/null +++ b/wai/http-conformance/falsify/merge-classes.patch @@ -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)), + } + } diff --git a/wai/http-conformance/falsify/no-root-flag.patch b/wai/http-conformance/falsify/no-root-flag.patch new file mode 100644 index 000000000..022e97dff --- /dev/null +++ b/wai/http-conformance/falsify/no-root-flag.patch @@ -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 diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh new file mode 100755 index 000000000..a27961c0f --- /dev/null +++ b/wai/http-conformance/falsify/run.sh @@ -0,0 +1,60 @@ +#!/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] +# +# 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}" +mkdir -p "$WORK" +fail=0 +for patch in "$ROOT"/wai/http-conformance/falsify/*.patch; do + name="$(basename "$patch" .patch)" + 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 + +cd "$ROOT/wai/http-conformance" +for m in left-half no-root counter-zero floor-as-codecs blake3-iv; 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 diff --git a/wai/http-conformance/falsify/select-no-digest.patch b/wai/http-conformance/falsify/select-no-digest.patch new file mode 100644 index 000000000..fbd77188b --- /dev/null +++ b/wai/http-conformance/falsify/select-no-digest.patch @@ -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( + skipped.push((index, why)); + continue; + } +- if !digest_ok(index, meta) { ++ if false && !digest_ok(index, meta) { + skipped.push((index, Skip::ComponentDigest)); + continue; + } diff --git a/wai/http-conformance/falsify/skip-digest.patch b/wai/http-conformance/falsify/skip-digest.patch new file mode 100644 index 000000000..9c2a50104 --- /dev/null +++ b/wai/http-conformance/falsify/skip-digest.patch @@ -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, 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 { diff --git a/wai/http-conformance/falsify/skip-on-absent.patch b/wai/http-conformance/falsify/skip-on-absent.patch new file mode 100644 index 000000000..b2e4cad13 --- /dev/null +++ b/wai/http-conformance/falsify/skip-on-absent.patch @@ -0,0 +1,15 @@ +# must fail: plan_no_digest_O4 fetch_O4 +# a 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 1d2902f7..4eec18d1 100644 +--- a/wai/wai-rs/src/http_binding/plan.rs ++++ b/wai/wai-rs/src/http_binding/plan.rs +@@ -137,7 +137,7 @@ pub fn plan_components( + policy: &FetchPolicy, + ) -> Plan { + let Some(rest) = rest_of(head) else { return Plan { requests: Vec::new(), whole_rest: None } }; +- if let Some(&(index, _)) = wanted.iter().find(|(i, _)| !digests_present.get(*i).copied().unwrap_or(false)) { ++ if let Some(&(index, _)) = wanted.iter().find(|_| false) { + return Plan { requests: Vec::new(), whole_rest: Some((rest, WhyWhole::DigestAbsent { index })) }; + } + let mut pieces: Vec<(Class, u64, u64)> = Vec::new(); diff --git a/wai/http-conformance/falsify/trust-content-range.patch b/wai/http-conformance/falsify/trust-content-range.patch new file mode 100644 index 000000000..a814db013 --- /dev/null +++ b/wai/http-conformance/falsify/trust-content-range.patch @@ -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 05e6cb08..268e5e0b 100644 +--- a/wai/wai-rs/src/http_binding/session.rs ++++ b/wai/wai-rs/src/http_binding/session.rs +@@ -157,7 +157,7 @@ pub fn check_response(req: Option, r: &ResponseMeta, body_len: u64, e + let (a, b) = span.split_once('-').ok_or_else(mismatch)?; + let (a, b): (u64, u64) = (a.parse().map_err(|_| mismatch())?, b.parse().map_err(|_| mismatch())?); + let expect_last = want.last.min(envelope_len.saturating_sub(1)); +- if a != want.first || b != expect_last || b < a || body_len != b - a + 1 { ++ if b < a || body_len != b - a + 1 { + return Err(mismatch()); + } + match complete.parse::() { -- 2.54.0 (Apple Git-157) From 39d9894e73a5d811576182c03c8957673d1ee160 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 5 Oct 2026 08:02:11 -0400 Subject: [PATCH 08/31] wai: cache conformance (public part), the wai-http workflow, and the CI wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit W3-7, public part. cache-conformance/: tests.json (C1–C13, R1–R5 as MUST tests of §9 and the sidecar rules, S1–S2 informative), driver.py (stages the corpus behind an origin and runs the tests against a cache; standard library and _blake3.py only), mutators.py (flip, gzip, truncate, vary: one §9 defect each), run.sh (the reference origin must pass every MUST test; each control must be caught by the tests named for it; labelled caches run where an environment provides /opt/wai-cache, and the summary says when none did) and a README. Reports name a cache by label, package version and configuration digest only. The private harness image is not in this repository. The reference origin gains what the tests need of it: If-Range by strong comparison, a live-edge prefix served with a shorter max-age, §8.4's Cache-Control for the key log and grants, and a request counter (--count-hits) for the informative tests. .github/workflows/wai-http.yml: the falsify job (every patch caught, every Python mutation caught, the differential) and the cache-conformance job (reports uploaded as an artifact). wai-conformance.yml: `http` joins the build, the library tests, the corpus loop (wai_http_vectors), the feature-alone and wasm32 checks; http_verify.py joins the independent verifiers. wai_http_vectors verify now names a case whose evaluation panics. http-delivery.md writes byte spans in words where a half-open interval read as link text to the section-reference check. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-conformance.yml | 32 +- .github/workflows/wai-http.yml | 125 ++++++++ wai/CHANGELOG.md | 23 ++ wai/cache-conformance/README.md | 47 +++ wai/cache-conformance/driver.py | 339 +++++++++++++++++++++ wai/cache-conformance/mutators.py | 82 +++++ wai/cache-conformance/run.sh | 93 ++++++ wai/cache-conformance/tests.json | 25 ++ wai/extensions/http-delivery.md | 16 +- wai/wai-rs/src/bin/wai_cli/http.rs | 84 ++++- wai/wai-rs/src/bin/wai_cli/http_vectors.rs | 20 +- wai/wai-rs/src/http_binding/origin.rs | 28 ++ 12 files changed, 879 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/wai-http.yml create mode 100644 wai/cache-conformance/README.md create mode 100755 wai/cache-conformance/driver.py create mode 100755 wai/cache-conformance/mutators.py create mode 100755 wai/cache-conformance/run.sh create mode 100644 wai/cache-conformance/tests.json diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index a13bd8196..ace6f3c4c 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -131,11 +131,12 @@ jobs: # is in the set because the reference meters (wai_meter, wai_world_meter) write a # measurement report by default and require it: without it they would drop out # 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`. - 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 + # media features build the staged-delivery core, its corpus tool and `wai stage`; http + # builds 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,http - 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 + 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,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 @@ -205,18 +206,19 @@ jobs: run: cargo test --release --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int,generate,staged --lib --test int_kernels_vectors --test int_kernels_geometry --test int_mlicpp --test int_mlicpp_preflight --test int_audio --test int_splat --test generate --test int_payload --test int_payload_preflight --test int_payload_cost --test cli_stage --test staged_resources # 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`. + # medium only under its feature, and none needs `neural`. 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; do + for f in neural_int video_int audio_int splat_int staged http; do cargo check --lib --no-default-features --features "$f" done 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 # carriage: every registered capability round-trips byte-exact. cli_wrap_intent and # cli_registry run the `wai` binary, which builds without the codecs feature: the # intent rule of `wai wrap`, its refusal of candidate capabilities (SPEC §5), its media @@ -287,7 +289,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 + 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,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 @@ -307,6 +309,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 # The pin corpus's second implementation: Python standard library only, sharing no # code with wai-rs. One leg is enough — it checks digests and listing rules, which # do not depend on the architecture — and the macOS host has python3. @@ -335,7 +338,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" + $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,http" function v($grp, $bin, $path) { Write-Output "::group::$grp" cargo run -q --no-default-features --features $F --bin $bin -- verify $path @@ -360,6 +363,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 # The generated v2 vocabulary (wai/vocab/v2.*) is checked as a consumer uses it: # sample manifests, typed and untyped, WAI1 and WAI2, are expanded through the @@ -391,7 +395,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: @@ -445,6 +450,15 @@ 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 - name: claim_verify.py, differential.py, jwp_verify.py, signer_verify.py working-directory: wai shell: bash diff --git a/.github/workflows/wai-http.yml b/.github/workflows/wai-http.yml new file mode 100644 index 000000000..d3af36a0b --- /dev/null +++ b/.github/workflows/wai-http.yml @@ -0,0 +1,125 @@ +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, the union, the trailing- +# byte rule, the missing-digest plan, the class merge, 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. + +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' + - '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' + - '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 + + - 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" + + - name: Upload the reports + if: always() + uses: actions/upload-artifact@v3 + with: + name: cache-conformance-reports + path: ${{ runner.temp }}/cache/reports diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index c697cd44a..b5628f368 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -38,6 +38,29 @@ wire format may change and such changes are recorded here. 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. - No envelope byte or receipt byte changes for existing material. ### Staged delivery: refinement layers, base admission, and WAI2 refinement components diff --git a/wai/cache-conformance/README.md b/wai/cache-conformance/README.md new file mode 100644 index 000000000..80fcb83d9 --- /dev/null +++ b/wai/cache-conformance/README.md @@ -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/