wai: HTTP playlists, live delivery and rendition switching — one renderer, sealed playlists, strict signatures #175
328 changed files with 13759 additions and 294 deletions
23
.github/workflows/wai-conformance.yml
vendored
23
.github/workflows/wai-conformance.yml
vendored
|
|
@ -274,11 +274,14 @@ jobs:
|
|||
# tests/cli_http.rs runs `wai http publish`, `codecs`, `origin` and `bench` as a user
|
||||
# does: objects named by their content hash with their head proofs, a sequence's union
|
||||
# and floor, the origin's headers, ranges and If-Range on loopback, and a fetch by
|
||||
# the byte-range procedure whose record states energy as not measured. Cargo.toml
|
||||
# declares it with required-features = ["http"].
|
||||
# the byte-range procedure whose record states energy as not measured; and `hls`,
|
||||
# `mpd`, `seal-playlist` and `check-playlist`, whose receipts need video_manifest.
|
||||
# Cargo.toml declares it with required-features = ["http"]. tests/dash_read_bounds.rs
|
||||
# holds Appendix I's reader to the document's size under a counting allocator, and an
|
||||
# element's attributes to linear CPU time; a binary of its own, one test.
|
||||
- name: The wai http commands
|
||||
working-directory: wai/wai-rs
|
||||
run: cargo test --no-default-features --features http --test cli_http
|
||||
run: cargo test --no-default-features --features http,video_manifest --test cli_http --test dash_read_bounds
|
||||
# tests/staged_moq.rs runs the MoQ trace of staged-conformance/moq/ (moq-streaming-format
|
||||
# §4.1) with signed receipts: a chain for each track, a receipt held for its parent and
|
||||
# admitted when it arrives, the relay's per-hop delivery timeout, dropped objects checked
|
||||
|
|
@ -348,7 +351,7 @@ jobs:
|
|||
working-directory: wai/wai-rs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
F=world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,quantum,quantum_ops,oer,receipt_claims,key_discovery,video_int,audio_int,splat_int,staged,stage_measure,telemetry,http
|
||||
F=world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,quantum,quantum_ops,oer,receipt_claims,key_discovery,video_int,audio_int,splat_int,staged,stage_measure,telemetry,http,video_manifest
|
||||
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
|
||||
|
|
@ -399,7 +402,7 @@ jobs:
|
|||
shell: pwsh
|
||||
working-directory: wai/wai-rs
|
||||
run: |
|
||||
$F = "world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,quantum,quantum_ops,oer,receipt_claims,key_discovery,video_int,audio_int,splat_int,staged,stage_measure,telemetry,http"
|
||||
$F = "world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,quantum,quantum_ops,oer,receipt_claims,key_discovery,video_int,audio_int,splat_int,staged,stage_measure,telemetry,http,video_manifest"
|
||||
function v($grp, $bin, $path) {
|
||||
Write-Output "::group::$grp"
|
||||
cargo run -q --no-default-features --features $F --bin $bin -- verify $path
|
||||
|
|
@ -515,7 +518,9 @@ jobs:
|
|||
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,
|
||||
# selection, the byte-range procedure and its fallback, the codecs union and floor, the
|
||||
# playlists (read, rendered, held adjacent, the classical master unchanged), playlist
|
||||
# receipts (Ed25519 from RFC 8032) and the live edge, and the adaptation policies,
|
||||
# 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
|
||||
|
|
@ -525,7 +530,9 @@ jobs:
|
|||
# Outside the corpus: wai_http_vectors fuzz writes seeded cases (Content-Range values,
|
||||
# codecs values, #WAI-LAYER lines, manifests and their member types, encryption,
|
||||
# refines, layers declared against their bases, layers over WAI2 components, objects
|
||||
# past the first request through a tampering origin) with wai-rs's outcomes, and
|
||||
# past the first request through a tampering origin; playlists, adjacent pairs,
|
||||
# published masters, renderings, adaptation states, the integer log and changed
|
||||
# receipt files) with wai-rs's outcomes, and
|
||||
# http_verify.py must give every one, a refusal by the same code. It prints each
|
||||
# family's count of cases and of refusals. Two fixed seeds, so a run is reproducible.
|
||||
- name: The HTTP differential outside the corpus (wai-rs against http_verify.py)
|
||||
|
|
@ -533,7 +540,7 @@ jobs:
|
|||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
(cd wai-rs && cargo build --no-default-features --features http --bin wai_http_vectors)
|
||||
(cd wai-rs && cargo build --no-default-features --features http,video_manifest --bin wai_http_vectors)
|
||||
for seed in 11 12; do
|
||||
wai-rs/target/debug/wai_http_vectors fuzz http-conformance --seed "$seed" --count 400 --out "$RUNNER_TEMP/http-fuzz-$seed.json"
|
||||
python3 http-conformance/http_verify.py cases "$RUNNER_TEMP/http-fuzz-$seed.json" http-conformance
|
||||
|
|
|
|||
18
.github/workflows/wai-http.yml
vendored
18
.github/workflows/wai-http.yml
vendored
|
|
@ -9,7 +9,9 @@ name: WAI HTTP delivery
|
|||
# union, layers' place in it and their dispatch, the wai prefix's case, duplicate
|
||||
# capabilities, a record beside a fallback, the trailing-byte rule, the missing-digest
|
||||
# plan, the class merge, the base-only threshold, refused refinement responses, stale
|
||||
# bytes after a mismatch, an unanswered base, the selection digest test); it
|
||||
# bytes after a mismatch, an unanswered base, the selection digest test; and on the
|
||||
# playlist side adjacency, rendering, bandwidths, durations, the classical master, the
|
||||
# playlist receipt and its chain, listed cids, and the adaptation policies); 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
|
||||
|
|
@ -41,6 +43,13 @@ on:
|
|||
- '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/src/bin/wai_cli/http_vectors_playlists.rs'
|
||||
- 'wai/wai-rs/src/bin/wai_cli/http_playlists.rs'
|
||||
# The playlists render through hls_render and are sealed by wai_video_manifest.
|
||||
- 'wai/wai-rs/src/hls_render.rs'
|
||||
- 'wai/wai-rs/src/wai_video_manifest.rs'
|
||||
# Playlist receipts verify Ed25519 through the shared strict rule.
|
||||
- 'wai/wai-rs/src/sig.rs'
|
||||
- 'wai/wai-rs/Cargo.toml'
|
||||
- 'wai/http-conformance/**'
|
||||
- 'wai/cache-conformance/**'
|
||||
|
|
@ -61,6 +70,13 @@ on:
|
|||
- '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/src/bin/wai_cli/http_vectors_playlists.rs'
|
||||
- 'wai/wai-rs/src/bin/wai_cli/http_playlists.rs'
|
||||
# The playlists render through hls_render and are sealed by wai_video_manifest.
|
||||
- 'wai/wai-rs/src/hls_render.rs'
|
||||
- 'wai/wai-rs/src/wai_video_manifest.rs'
|
||||
# Playlist receipts verify Ed25519 through the shared strict rule.
|
||||
- 'wai/wai-rs/src/sig.rs'
|
||||
- 'wai/wai-rs/Cargo.toml'
|
||||
- 'wai/http-conformance/**'
|
||||
- 'wai/cache-conformance/**'
|
||||
|
|
|
|||
|
|
@ -372,6 +372,68 @@ wire format may change and such changes are recorded here.
|
|||
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.
|
||||
- **Playlists and live** (http-delivery §8.6, §10.4, §11, Appendices G and H):
|
||||
- `hls_render::render_hls_with` renders on-demand, live and low-latency WAI media
|
||||
playlists (media and discontinuity sequences, a fixed target duration, parts with
|
||||
their server control, discontinuities, program date-times, `#WAI-LAYER` lines with
|
||||
`N` only, the open segment and its preload hint) and, under the legacy parameters
|
||||
with no extras, gives `render_hls`'s bytes; the module needs no dependency, and
|
||||
`wai_video_manifest` re-exports it under its old paths;
|
||||
- the playlist receipt (`ManifestReceipt`) carries `render` and `extras`; its
|
||||
signature binds a render digest only when a rendering is present, so every receipt
|
||||
sealed before verifies unchanged (one sealed by the earlier tree is pinned), and a
|
||||
rendering has one form; `seal_with`, the receipt file's strict JSON form and
|
||||
`mr/<manifest_hash>.json` are new, and a live playlist's revisions chain;
|
||||
- `http_binding::hls` reads media playlists, holds a WAI playlist adjacent to its
|
||||
classical one (durations to the millisecond, rounded half up, read exactly), renders
|
||||
the WAI media and multivariant playlists (bandwidths computed from the objects),
|
||||
and keeps the classical master to its one session-data line (`classical_changed`);
|
||||
`http_binding::dash` writes `wai.mpd` on the classical timeline;
|
||||
`http_binding::playlist_receipt` seals and checks playlists and live chains
|
||||
(`playlist_receipt_malformed`, `_mismatch`, `_invalid`, `_unchained`;
|
||||
`playlist_malformed`);
|
||||
- `wai http hls|mpd|seal-playlist|check-playlist`; the origin serves playlists to be
|
||||
revalidated on every use;
|
||||
- the corpus gains a staged pair with a layer over its second segment (O11), a
|
||||
low-latency set of two segments of twelve parts each, every part its own object
|
||||
(O12), a player fixture's WAI segments (O13) beside a playable classical
|
||||
presentation, the WAI playlists beside their classical ones, their receipts (the
|
||||
live revisions chained) and the published classical master. O10 was already the
|
||||
layer set, so these took the next numbers.
|
||||
- **Adaptation** (http-delivery Appendix F): `http_binding::abr`'s four reference
|
||||
policies (throughput, buffer, utility over buffer, look-ahead) in integers, the budget
|
||||
rule that never uses an unlabelled figure, an entry order the session applies on both
|
||||
paths (the selector's order now sees the verified head), and layer depth; seeded
|
||||
vectors per policy, the integer log, entry orders and depths, which `http_verify.py`
|
||||
reproduces.
|
||||
- **Playlists and adaptation, after review** (http-delivery §8.6, §11, Appendices F, H
|
||||
and I):
|
||||
- a live client walks back to the head it holds across revisions it skipped: every
|
||||
receipt is also served at `mr/<receipt_hash>.json`, the head fetched again is
|
||||
unchanged, and a walk is bounded (`playlist_receipt_parent_missing`,
|
||||
`playlist_receipt_walk_exceeded`); no revision lists another cid for an alias or a
|
||||
media sequence number an earlier one bound (`playlist_listing_rebound`);
|
||||
- Ed25519 is verified strictly (`wai::sig::verify_strict`: a canonical key of large
|
||||
order, `S` < L, `R` of large order compared byte for byte), by the playlist receipt
|
||||
and `http_verify.py`;
|
||||
- DASH adjacency is checked over an XML subset (Appendix I); `wai.mpd` takes the
|
||||
classical MPD's period, timescale, `t` and `d` and leaves a gap out; MPD values are
|
||||
escaped and checked as XML;
|
||||
- adaptation states have stated bounds (`abr_state_out_of_bounds`) and are computed
|
||||
exactly within them; under a budget only a labelled option within it is chosen;
|
||||
- a gap segment carries no parts or layer lines; the session-data line has one place
|
||||
in the classical master; a receipt file holds no member twice, no lone surrogate
|
||||
and no number past 2^53 − 1, a grant of no joule ceiling written `null`;
|
||||
`seal-playlist --grant`; the origin answers a far-future `_HLS_msn` with 400.
|
||||
- an MPD is held as written: timelines in runs, never expanding `@r`, compared run by
|
||||
run, at most 256 per document, and an element's attributes read in linear time
|
||||
(Appendix I);
|
||||
- a budget never raises the policy's choice: with nothing at or below it within the
|
||||
budget, option 0 and the budget `unmet` (replacing `floor`);
|
||||
- past the walk bound, a revision ahead of the head re-anchors the chain
|
||||
(`reanchored`), keeping its bindings, the revisions between unsigned; bindings key a
|
||||
line by its URL resolved against the playlist's (RFC 3986), a gap never withdraws a
|
||||
listed segment, and a binding is kept while one of the last 256 revisions lists it.
|
||||
- **What review found, fixed** (http-delivery §3.1, §4, §7, §7.1, §10, §13, Appendix D):
|
||||
- a layer object (a `WAI1` whose capability is refinement-only) is never dispatched:
|
||||
it is fetched whole at refinement urgency, verified against its cid and handed to
|
||||
|
|
|
|||
|
|
@ -619,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. 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.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` and through a render digest the signature covers ([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. Version 2 ([packaging](extensions/packaging.md) §9) binds the policy digest and one stage index per component, names each object by its jwp-receipts §2.1 content hash, and covers every media class a packager packs; the string keeps `video` for history | `wai_video_package.rs`, `package.rs` |
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@
|
|||
> 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.
|
||||
> is in `wai-rs`: `http_binding` (feature `http`) for §3–§7, the origin side of §5
|
||||
> and §9, the playlists of §10.4 and §11 and the adaptation policies of Appendix F;
|
||||
> `wai_video_manifest` (feature `video_manifest`) for the playlist receipts of §8.6;
|
||||
> and the `wai http` commands of the `wai` tool. Keywords **MUST**, **MUST NOT**,
|
||||
> **SHOULD**, **MAY** are RFC 2119 / RFC 8174.
|
||||
|
||||
## 1. Scope
|
||||
|
||||
|
|
@ -456,16 +458,64 @@ 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`,
|
||||
A WAI media playlist (§11) is authenticated by a manifest receipt (`wai.video.manifest`,
|
||||
[SPEC](../SPEC.md) §5) whose `manifest_hash` is the BLAKE3 of the playlist bytes,
|
||||
served immutable at `mr/<manifest_hash>.json` resolved against the playlist URI. Each
|
||||
revision of a live playlist has its own receipt, whose `parent_receipt_hash` names the
|
||||
previous one. A playlist receipt MAY carry `render`, the rendering parameters of the
|
||||
playlist it signs (version, media sequence, discontinuity sequence, part target,
|
||||
whether it ends), and `extras`, the per-segment lines that rendering adds
|
||||
(discontinuities, program date-time, parts, layer lines). When both are absent, the
|
||||
playlist is rendered as before. `manifest_hash` covers the rendered bytes, so both are
|
||||
bound by the signature through it.
|
||||
served immutable at `mr/<manifest_hash>.json` resolved against the playlist URI, in the
|
||||
form of Appendix H, and with the same bytes at `mr/<receipt_hash>.json`, its receipt
|
||||
hash (Appendix H), so a receipt can be fetched by the link a later one names. Each
|
||||
revision of a live playlist has its own receipt, whose `parent_receipt_hash` is the
|
||||
receipt hash of the previous revision's receipt, under the same key. A playlist receipt MAY carry `render`, the rendering parameters of the
|
||||
playlist it signs (version, media sequence, discontinuity sequence, target duration,
|
||||
part target, part hold-back, blocking reload, whether it ends), and `extras`, the
|
||||
per-segment lines that rendering adds (discontinuities, program date-times, parts,
|
||||
layer lines, a preload hint), rendered as Appendix G states. When both are absent, the
|
||||
playlist is rendered as before. `manifest_hash` covers the rendered bytes, and the
|
||||
signature also covers a digest of `render` and `extras` themselves (Appendix H), so a
|
||||
rendering has one form. A public playlist is sealed with the entitlement `public`, no
|
||||
geography, blackout or ad pod, and its media sequence as the window, under the grant
|
||||
its publisher holds (`grant`: a grant of no ceilings when it holds none).
|
||||
|
||||
This revision signs media playlists only. A receipt binds neither the multivariant
|
||||
playlist that names the media playlist nor a `wai.mpd`, and nothing binds
|
||||
`class.content_id` to the presentation: it is the publisher's label, signed as given,
|
||||
so a client learns from it only what the signer chose to write. A client that needs the
|
||||
multivariant playlist's variants, or the content's identity, authenticated holds them
|
||||
from elsewhere.
|
||||
|
||||
A client checks a playlist against a receipt file in this order:
|
||||
|
||||
1. the file reads as Appendix H's form (`playlist_receipt_malformed`);
|
||||
2. its name is `<manifest_hash>.json` (`receipt_name_mismatch`);
|
||||
3. the playlist's BLAKE3 is `manifest_hash`, and rendering the receipt's segments,
|
||||
`render` and `extras` gives the playlist's bytes (`playlist_receipt_mismatch`);
|
||||
4. the receipt verifies (`playlist_receipt_invalid`): its signature, and every value it
|
||||
recomputes (Appendix H).
|
||||
|
||||
A client of a live playlist holds a chain: its head, the latest receipt that verified
|
||||
and chained, and what the receipts in it bound. The first receipt it verifies is the
|
||||
head. A later revision's receipt that is the head itself (a reload with no new
|
||||
revision) leaves the chain as it is. Any other becomes the head only when it chains to
|
||||
the head: it is under the head's key (`playlist_receipt_unchained`), and following
|
||||
`parent_receipt_hash` from it reaches the head's receipt hash. Each parent on the way is
|
||||
fetched from `mr/<parent_receipt_hash>.json` and checked in order: it is served
|
||||
(`playlist_receipt_parent_missing`); it reads as Appendix H's form
|
||||
(`playlist_receipt_malformed`); its receipt hash is the link it was fetched by
|
||||
(`receipt_name_mismatch`); it verifies (`playlist_receipt_invalid`); and it is under the
|
||||
same key (`playlist_receipt_unchained`). A walk that reaches a receipt with no parent
|
||||
before the head does not chain (`playlist_receipt_unchained`), so a revision older than
|
||||
the head never does. A client fetches at most 64 parents for one revision, and MAY hold
|
||||
a lower bound. When the head is further back than that, the client re-anchors the chain
|
||||
at the revision in hand if that revision is ahead of the head (its media sequence number
|
||||
after its last complete segment is greater than the head's): the revision becomes the
|
||||
head (`reanchored`), the chain keeps what it bound, and the revisions between the old
|
||||
head and the new one are not signed: no part they alone list is held at **signed**. A
|
||||
revision past the bound and not ahead of the head is refused
|
||||
(`playlist_receipt_walk_exceeded`), so an older or stale revision never moves the chain
|
||||
back; the head fetched again, however often, changes nothing. Then the walked receipts,
|
||||
oldest first, and the new head bind what they list (§11.5), a re-anchored head alone; one
|
||||
that lists another cid for a URL or a media sequence number the chain bound, or a gap
|
||||
where it bound an object, is refused (`playlist_listing_rebound`). A refused revision
|
||||
leaves the chain as it was.
|
||||
|
||||
## 9. Cache rules
|
||||
|
||||
|
|
@ -566,7 +616,9 @@ request abandoned at its deadline, or whose response
|
|||
§7.1 refuses, leaves the layer not delivered (§10.5).
|
||||
|
||||
A WAI media playlist lists the layer objects of a segment or part after its `EXTINF`
|
||||
or `EXT-X-PART` line, in the order a sink applies them:
|
||||
or `EXT-X-PART` line, in the order a sink applies them: directly after it, before the
|
||||
segment's URI, with only other `#WAI-LAYER` lines (and blank lines) between, and `N`
|
||||
increasing from line to line (`playlist_malformed` otherwise):
|
||||
|
||||
```
|
||||
#WAI-LAYER:N=<1..255>,URI="<cid>.wai#n=…"
|
||||
|
|
@ -606,14 +658,49 @@ arrived after its units were due (staged-delivery §10.2).
|
|||
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.
|
||||
`AVERAGE-BANDWIDTH` computed from the objects' `envelope_len` over their durations,
|
||||
never stated by hand. A segment's bytes are the `envelope_len` of its object and of the
|
||||
layer objects listed with it; a segment marked `EXT-X-GAP` counts nothing, and a live
|
||||
variant with no segment yet is counted by its parts. With each duration in seconds as
|
||||
exactly as the packager holds it, `BANDWIDTH` is the largest `⌈8·bytes/duration⌉` of
|
||||
any segment, which is never below RFC 8216's peak over a run of segments, and
|
||||
`AVERAGE-BANDWIDTH` is `⌈8·Σbytes/Σduration⌉` (from a playlist's milliseconds,
|
||||
`⌈8000·bytes/duration_ms⌉`). Nothing in `wai.m3u8` needs `EXT-X-VERSION`; a publisher
|
||||
MAY write one, `EXT-X-INDEPENDENT-SEGMENTS` when every segment it lists decodes
|
||||
without the one before it, and further `EXT-X-SESSION-DATA` lines of its own.
|
||||
- A WAI media playlist takes the classical one's version, target duration, playlist
|
||||
type and `EXT-X-INDEPENDENT-SEGMENTS`, and never its `EXT-X-MAP`: a WAI object has no
|
||||
initialization section. A segment a publisher does not offer as a WAI object at that
|
||||
point (a class that takes it from the classical presentation) is marked `EXT-X-GAP`,
|
||||
and a sink takes the classical segment there.
|
||||
- `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.
|
||||
`value` is the floor. The reference `wai.mpd` is static: one `Period` with the
|
||||
classical one's `@id` (none when it has none) and `@start` (in seconds, without a
|
||||
fraction when whole and with three decimals otherwise), `mediaPresentationDuration`
|
||||
the timeline's length in seconds with three decimals, `minBufferTime` its longest
|
||||
segment rounded up to whole seconds, and in each `Representation` a `SegmentList` with
|
||||
the classical timeline's `@timescale` whose `SegmentTimeline` has one `S` per segment
|
||||
it offers, with the classical `t` and `d`, and one `SegmentURL` per such segment naming
|
||||
its object. A segment the WAI media playlist marks `EXT-X-GAP` is left out, `S` and
|
||||
`SegmentURL` both: a `Representation` whose timeline leaves out a segment offers none
|
||||
there. A layer's `Representation` names the one below it by `@dependencyId`. The
|
||||
classical timeline is the classical MPD's, read as Appendix I states: by default its
|
||||
first `Period` and that period's first `AdaptationSet` with a timeline. Beside a
|
||||
presentation with no classical MPD, it is the classical HLS media playlist's at
|
||||
timescale 1000, `t` from 0, in a `Period` starting at 0. Attribute values are escaped,
|
||||
tab, LF and CR as character references, and a value holding a character that is not
|
||||
an XML `Char` is not written (`playlist_malformed`).
|
||||
- The published classical multivariant playlist is its original, byte for byte, or the
|
||||
original with the session-data line (its own line end, LF or CR LF) inserted at one
|
||||
place: after the first line and the `EXT-X-VERSION` and `EXT-X-INDEPENDENT-SEGMENTS`
|
||||
lines that directly follow it, before any other line. A packager MUST NOT publish one
|
||||
that differs otherwise (`classical_changed`): the line anywhere else, between an
|
||||
`EXT-X-STREAM-INF` and its URI among them, is refused, as is an original whose line
|
||||
before that place does not end.
|
||||
|
||||
### 11.2 Alignment
|
||||
|
||||
|
|
@ -621,12 +708,25 @@ A WAI media playlist is **adjacent** to a classical media playlist when, segment
|
|||
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
|
||||
part durations to the millisecond in each segment. In DASH, both read as Appendix I
|
||||
states: each WAI `Period` has a classical `Period` with the same `@id` and `@start`
|
||||
(compared in milliseconds), and has an `AdaptationSet`; each WAI `AdaptationSet` has a
|
||||
timeline; and each of its timelines is not empty, has the `@timescale` of a timeline of
|
||||
that classical period, and lists only that timeline's segments, each with its `t` and
|
||||
`d`, in order (a segment left out is a gap). 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.
|
||||
do not affect alignment, nor does `EXT-X-GAP`: a gap segment keeps its interval.
|
||||
|
||||
A duration is compared in milliseconds: its decimal value, read exactly, rounded to the
|
||||
nearest millisecond with a half rounded up (so `4.00008` and `4.000` are both 4000). A
|
||||
missing `EXT-X-MEDIA-SEQUENCE` or `EXT-X-DISCONTINUITY-SEQUENCE` is 0, and either after a
|
||||
segment is malformed. Part durations are compared in each segment both playlists list
|
||||
parts for, and in the open segments (§11.4) when both have one; one side listing parts
|
||||
the other has stopped listing is aligned. A playlist that does not read as a media
|
||||
playlist — a delta update (`EXT-X-SKIP`) and a multivariant playlist among them — is not
|
||||
compared (`playlist_malformed`).
|
||||
|
||||
### 11.3 Live at segment granularity (Live-S)
|
||||
|
||||
|
|
@ -652,7 +752,10 @@ Each part is its own object, served under an alias, and listed with its cid:
|
|||
```
|
||||
|
||||
- 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.
|
||||
the alias before the part exists; the origin holds the request until it does. An
|
||||
origin answers at once with 400 a blocking reload whose `_HLS_msn` is more than two
|
||||
past the last segment of its current playlist, or that has `_HLS_part` without
|
||||
`_HLS_msn`, as the HLS second edition requires, rather than holding it.
|
||||
- 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
|
||||
|
|
@ -665,12 +768,35 @@ Each part is its own object, served under an alias, and listed with its cid:
|
|||
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.
|
||||
- A WAI Live-P playlist takes the classical playlist's target duration, part target and
|
||||
blocking reload, and its part hold-back (three part targets when it states none). A
|
||||
segment's parts precede its `EXTINF`, its discontinuity and date-time precede its
|
||||
first part, the open segment's parts follow the last segment's URI, and the preload
|
||||
hint is the last line (Appendix G), as the HLS second edition places them. A hint
|
||||
with no open part yet follows the last segment.
|
||||
|
||||
### 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.
|
||||
|
||||
A revision MUST NOT list another cid for a URL an earlier revision listed (an alias, or
|
||||
any part, segment or layer URI), nor another object at a media sequence number an
|
||||
earlier revision listed an object at, nor a gap there: a listed segment is not
|
||||
withdrawn (a gap segment lists no object at its number). A URL is the line's URI
|
||||
resolved against the playlist's URL (RFC 3986 §5.2) and normalized (§6.2.2: the scheme
|
||||
and host in lower case, percent-encodings' hexadecimal digits in upper case, unreserved
|
||||
characters decoded, dot segments removed), without its fragment, so two spellings of
|
||||
one URL are one binding. A client keeps both bindings from the revisions it accepts, a
|
||||
URL's cid being the first one listed with it (§5.2), and refuses a revision that lists
|
||||
another (`playlist_listing_rebound`), taking none of its listings. A chain of receipts
|
||||
binds the same way over the receipts in it (§8.6).
|
||||
|
||||
A client keeps a binding while one of the last 256 revisions it took lists it, and MAY
|
||||
keep it longer; what it holds is then bounded by what those revisions list, however
|
||||
long the playlist runs. A rebinding of a URL or a sequence number that none of the last
|
||||
256 revisions listed is not detected.
|
||||
|
||||
### 11.6 Playlist authentication
|
||||
|
||||
A WAI playlist SHOULD be authenticated by its receipt (§8.6). A client that verifies it
|
||||
|
|
@ -681,7 +807,10 @@ each object is still bound to the cid the playlist names. A client reports which
|
|||
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.
|
||||
presenting; it MUST NOT report the part as signed until it has. A part is listed by the
|
||||
first accepted revision whose playlist names its alias with a `cid`; it is signed when a
|
||||
receipt in the chain the client holds (§8.6), the parents walked to it included, binds
|
||||
the alias to that cid.
|
||||
|
||||
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
|
||||
|
|
@ -760,11 +889,16 @@ is a share of sessions, not of devices or people.
|
|||
| `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 |
|
||||
| `receipt_name_mismatch`, `receipt_not_about_object` | §8.1, §8.2, §8.6 |
|
||||
| `codecs_malformed`, `codecs_not_media`, `codecs_candidate`, `codecs_no_common_capability`, `codecs_undeclared`, `codecs_layer_unbound` | §4 |
|
||||
| `base_unanswered` | §10.1 |
|
||||
| `not_a_layer`, `layer_tag_malformed` | §10.4 |
|
||||
| `adjacency_misaligned` | §11.2 |
|
||||
| `playlist_malformed` | §10.4, §11.2, Appendix G, Appendix I |
|
||||
| `classical_changed` | §11.1 |
|
||||
| `playlist_receipt_malformed`, `playlist_receipt_mismatch`, `playlist_receipt_invalid`, `playlist_receipt_unchained`, `playlist_receipt_parent_missing`, `playlist_receipt_walk_exceeded` | §8.6 |
|
||||
| `playlist_listing_rebound` | §8.6, §11.5 |
|
||||
| `abr_state_out_of_bounds` | Appendix F |
|
||||
|
||||
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
|
||||
|
|
@ -780,7 +914,13 @@ step 5 states.
|
|||
code with `wai-rs`, report the same outcome for every case, and for every case of a
|
||||
seeded differential over inputs outside the corpus (`wai_http_vectors fuzz`): every
|
||||
`Content-Range`, `codecs` value, `#WAI-LAYER` line, manifest, member type, encryption,
|
||||
`refines` and layer sequence it generates.
|
||||
`refines` and layer sequence it generates. For §8.6, §10.4, §11 and Appendix F the
|
||||
corpus holds WAI playlists rendered from their inputs beside their classical ones,
|
||||
their receipts (a live playlist's chained, and walked across revisions a client
|
||||
skipped or fetched again), a published classical master and a
|
||||
playable classical presentation for players, and the policies' choices; the
|
||||
differential adds playlists, adjacent pairs, published masters, renderings, receipt
|
||||
files, adaptation states and MPD pairs.
|
||||
- [`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.
|
||||
|
|
@ -900,9 +1040,222 @@ request ends at `covered_len`. Without `h`, the first request is 16,369 bytes.
|
|||
| 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 |
|
||||
| The HLS second-edition draft (draft 22): `EXT-X-PART` (its placement before the parent segment's URI, and the media segment tags before its first part), `EXT-X-PART-INF`, `EXT-X-SERVER-CONTROL` (`PART-HOLD-BACK` required with parts, at least twice the part target), `EXT-X-PRELOAD-HINT` | read |
|
||||
| The HLS second-edition draft's blocking-reload clauses and when parts stop being listed | 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.
|
||||
|
||||
## Appendix F — Reference adaptation policies (informative)
|
||||
|
||||
A client MAY order a `WAI2`'s entries by deployer policy ([SPEC](../SPEC.md) §7.1
|
||||
step 2) and choose how many layers it fetches (§10.4). The reference client's policies
|
||||
choose among options of increasing size from measured delivery, in integers only, so
|
||||
every implementation chooses the same; the corpus pins their choices.
|
||||
|
||||
**Inputs.** A state holds the buffer `b` (ms), samples `(at, bytes, ms)` oldest first,
|
||||
the last choice, each option's bytes `S_k`, each option's energy figure where a receipt
|
||||
labels it with its acquisition class ([energy-measurement](energy-measurement.md) §2;
|
||||
none otherwise), a joule budget if held, and the media duration `D` one option covers
|
||||
(ms). Divisions round down unless stated; `⌈x/y⌉` rounds up.
|
||||
|
||||
**Bounds.** Each value lies within its field's type: `b`, `D`, and the policies' `R`,
|
||||
`C`, `v` and `M` below 2^32; `n` and `h` below 2^8; `p` below 2^16; each sample's
|
||||
`at`, `bytes` and `ms`, the last choice, each `S_k`, each figure and the budget below
|
||||
2^64. A state holds at most 256 options (and at most 256 figures), and a layer depth is
|
||||
chosen over at most 256 objects, the bytes and the figure through each depth below 2^64.
|
||||
A state or a policy beyond these is refused (`abr_state_out_of_bounds`). Within them
|
||||
every quantity below is computed exactly, never saturated: a rate or a throughput
|
||||
reaches 2^85, the look-ahead's stall times `max_k u_k + 65536` reaches 2^103, and the
|
||||
utility-over-buffer cross-multiplication reaches 2^141, so an implementation in fixed
|
||||
widths compares those products in 192 bits.
|
||||
|
||||
- The throughput of the last `n` samples (`n` 0 taken as 1): `⌊8000·Σbytes / Σmax(ms, 1)⌋`
|
||||
b/s, 0 with no sample.
|
||||
- An option's rate: `⌈8000·S_k / max(D, 1)⌉` b/s. "The highest option under `r`" is the
|
||||
largest index whose rate is at most `r`, else 0.
|
||||
- `log2_q16(a, b)`, `log2(a/b)` in units of 1/65536: 0 when `a ≤ b` (`b` taken as at
|
||||
least 1); else with `x = ⌊65536·a/b⌋`, `e = ⌊log2 x⌋ − 16`, `f = (x >> e) − 65536`,
|
||||
`i = f >> 12`, `r = f & 4095` and `T = [0, 5732, 11136, 16248, 21098, 25711, 30109,
|
||||
34312, 38336, 42196, 45904, 49472, 52911, 56229, 59434, 62534, 65536]`
|
||||
(`T[i] = round(65536·log2(1 + i/16))`), it is `65536·e + T[i] + ⌊(T[i+1] − T[i])·r / 4096⌋`.
|
||||
- An option's utility: `u_k = log2_q16(max(S_k, 1), max(S_0, 1))`.
|
||||
|
||||
**Policies.** Each chooses an option index (0 when there is none):
|
||||
|
||||
- *Throughput* `(n, p)`: the highest option under `⌊throughput(n)·p/1000⌋`.
|
||||
- *Buffer* `(R, C)`: 0 when `b ≤ R`; the highest index when `C = 0` or `b ≥ R + C`; else
|
||||
the highest option under `rate_0 + ⌊max(rate_top − rate_0, 0)·(b − R)/C⌋`.
|
||||
- *Utility over buffer* `(v, M)`: with `q = ⌊65536·b/D'⌋`, `q_max = ⌊65536·M/D'⌋`
|
||||
(`D' = max(D, 1)`), `V_n = max(q_max − 65536, 0)` and `V_d = max_k u_k + 65536`, each
|
||||
option scores `N_k = ⌊V_n·v·(u_k + 65536) / (V_d·1000)⌋ − q`. If any `N_k > 0`, the
|
||||
option with the largest `N_k/S'_k` among them (`S'_k = max(S_k, 1)`, compared by
|
||||
cross-multiplication); else the option with the largest `N_k`. Ties go to the lower
|
||||
index. This is a rule of the utility-over-buffer kind; unlike the published algorithm
|
||||
of that kind, it never waits for the buffer to drain.
|
||||
- *Look-ahead* `(h, p)`: with `P = ⌊throughput(5)·p/1000⌋` (0 chooses 0), `h` taken in
|
||||
1 to 5 and lowered while `K^h > 65536` for `K` options, every sequence of `h` choices,
|
||||
in increasing order of the sequence read as base-`K` digits, most significant first,
|
||||
is played from the buffer `b`: each choice downloads in `⌈8000·S_k/P⌉` ms, stalling by
|
||||
what passes the buffer, then adds `D`. Its score is `1000·Σu − 1000·Σ|u_t − u_{t−1}|`
|
||||
(the first step against the last choice, when there is one below `K`) less the stall
|
||||
in ms times `(max_k u_k + 65536)`. The first choice of the first best sequence is
|
||||
taken.
|
||||
|
||||
**Budget.** A budget never raises the policy's choice. Without a budget the choice
|
||||
stands (`not_set`). When no option has a labelled figure, the choice stands and the
|
||||
budget is not applied (`not_applied`): an unlabelled figure is never used. Otherwise the
|
||||
choice is the highest option at or below the policy's whose labelled figure is within
|
||||
the budget: the policy's own (`within`) or one below it (`lowered`), so an unlabelled
|
||||
option never escapes the budget. When no option at or below the policy's has a labelled
|
||||
figure within it, the choice is option 0 and the budget is not met (`unmet`): a client
|
||||
fetches the least it can, and reports that the budget was not held. A figure past the
|
||||
options names none.
|
||||
|
||||
**Entry order.** Within a `WAI2`, the selectable entries whose capability the sink has
|
||||
are grouped by capability in order of first appearance; a group's options are its
|
||||
entries by component length (then table index), each option's figure the entry's. The
|
||||
group is ordered as the choice under the budget, then the options below it from the
|
||||
nearest down, then those above it from the nearest up; every other entry follows in
|
||||
table order. Selection then runs unchanged on the head and on the whole envelope, so
|
||||
the pick is the same on every path (§7.2).
|
||||
|
||||
**Layer depth.** Over a base and its layers, option `k` fetches through layer `k`: its
|
||||
bytes are the `envelope_len`s through it, and its figure the sum of the labelled figures
|
||||
through it (none when any is unlabelled).
|
||||
|
||||
## Appendix G — The rendering a playlist receipt signs
|
||||
|
||||
A WAI media playlist is rendered from its segments (each a duration in milliseconds, a
|
||||
URI, and whether it is an ad), the parameters of §8.6 and the extras, with lines ending
|
||||
in LF and every duration written as seconds with three fractional digits:
|
||||
|
||||
1. `#EXTM3U`; `#EXT-X-VERSION:<v>`; `#EXT-X-TARGETDURATION:<t>`, `t` the parameter or,
|
||||
when it has none, the largest segment duration rounded up to whole seconds;
|
||||
`#EXT-X-PART-INF:PART-TARGET=<d>` with a part target; `#EXT-X-SERVER-CONTROL:` with
|
||||
`CAN-BLOCK-RELOAD=YES` and `PART-HOLD-BACK=<d>`, those that apply, in that order,
|
||||
when either does; `#EXT-X-MEDIA-SEQUENCE:<n>`; `#EXT-X-DISCONTINUITY-SEQUENCE:<n>`
|
||||
when it is not 0; `#EXT-X-PLAYLIST-TYPE:<VOD or EVENT>` with a type;
|
||||
`#EXT-X-INDEPENDENT-SEGMENTS` when it applies; `#EXT-X-MAP:URI="<uri>"` with a map
|
||||
(a classical playlist's; a WAI playlist has none).
|
||||
2. Each segment: `#EXT-X-DISCONTINUITY` once, for an ad or a discontinuity;
|
||||
`#EXT-X-PROGRAM-DATE-TIME:<value>`; each part as
|
||||
`#EXT-X-PART:DURATION=<d>[,INDEPENDENT=YES],URI="<uri>"` followed by its layer lines;
|
||||
`#EXT-X-GAP` for a gap; `#EXTINF:<d>,`; the segment's layer lines; its URI. A layer
|
||||
line is `#WAI-LAYER:N=<n>,URI="<uri>"`.
|
||||
3. The open segment, when there is one extra more than segments: its discontinuity,
|
||||
date-time and parts, written as in step 2.
|
||||
4. `#EXT-X-PRELOAD-HINT:TYPE=PART,URI="<uri>"` from the last extra; `#EXT-X-ENDLIST`
|
||||
when the playlist ends.
|
||||
|
||||
Extras are none, one per segment, or one more. A rendering is refused
|
||||
(`playlist_malformed`) when it would not read back as its inputs: a URI that is empty,
|
||||
begins with `#` or holds CR or LF (or `"`, where quoted, the map's among them); a
|
||||
program date-time outside
|
||||
ASCII letters, digits and `:.+-`; layer numbers outside 1 to 255 or not increasing;
|
||||
parts with no part target; a part target with no part hold-back of at least twice it;
|
||||
a preload hint on any but the last extra, or in a playlist that ends; a gap segment
|
||||
with parts or layer lines (it offers no object to divide or refine); an open segment
|
||||
in a playlist that ends, with layer lines or a gap of its own, with a discontinuity or date-time
|
||||
before any part, or with neither a part nor a hint; version 0.
|
||||
|
||||
## Appendix H — The playlist receipt file
|
||||
|
||||
`mr/<manifest_hash>.json` is one JSON object with these members and no other:
|
||||
`kind` (`"wai.video.manifest"`), `manifest_hash`, `session_class_hash`, `class`
|
||||
(`content_id`, `entitlement`, `geo`, `blackouts`, `ad_pod_id`, `window`), `segments`
|
||||
(each `index`, `kind` — `content` or `ad` —, `uri`, `duration_ms`, `ad_id`),
|
||||
`merkle_root`, `work_compose_ops`, `viewers_served`, `joules_micro`, the acquisition
|
||||
class's members of [energy-measurement](energy-measurement.md) §2.7 when the figure is
|
||||
metered, `grant` (`grant_hash`, `capability`, `joule_ceiling_micro`, `funds_ceiling`),
|
||||
`parent_receipt_hash`, `signer_pubkey`, `signer_id`, `sig`, and `render` (`version`,
|
||||
`media_sequence`, `discontinuity_sequence`, `target_duration_s`, `part_target_ms`,
|
||||
`part_hold_back_ms`, `can_block_reload`, `end_list`, `playlist_type` — `VOD`, `EVENT` or
|
||||
null —, `independent_segments`, `map_uri`) with `extras` (each `discontinuity`, `gap`,
|
||||
`program_date_time`, `parts` — each `duration_ms`, `independent`,
|
||||
`uri`, `layers` —, `layers` — each `n`, `uri` —, `preload_hint`), the two together or
|
||||
not at all. Hashes are lowercase hexadecimal; a number is an unsigned integer of at
|
||||
most 2^53 − 1 written as digits alone, and a writer refuses to seal a receipt it cannot
|
||||
write so. A grant's `joule_ceiling_micro` is `null` when the grant sets no joule ceiling
|
||||
(it signs as 2^64 − 1), and `funds_ceiling` `null` when it sets no funds ceiling. The
|
||||
file is read as I-JSON (RFC 7493): no object holds a member name
|
||||
twice, at any depth and with names compared after their escapes are read, and no
|
||||
string holds a `\u` escape that leaves a surrogate unpaired; either is
|
||||
`playlist_receipt_malformed`, so no two readers can read two receipts from one file.
|
||||
|
||||
Below, `‖` concatenates, integers are big-endian of the stated width in bytes, and a
|
||||
string is its UTF-8 length in 4 bytes, then its bytes:
|
||||
|
||||
- a segment's leaf: BLAKE3 of `"wai:video-manifest-seg\x01"` ‖ `index` (8) ‖ 1 for
|
||||
content or 2 for an ad (1) ‖ `duration_ms` (8) ‖ `uri` ‖ (1 ‖ `ad_id`, or 0);
|
||||
- `merkle_root`: [jwp-receipts](jwp-receipts.md) §2.2's root over the leaves;
|
||||
- `session_class_hash`: BLAKE3 of `"wai:video-manifest-class\x01"` ‖ `content_id` ‖
|
||||
`entitlement` ‖ `geo` ‖ `ad_pod_id` ‖ the number of distinct `blackouts` (4) ‖ each,
|
||||
in byte order ‖ `window` (8);
|
||||
- the render digest: BLAKE3 of `"wai:video-manifest-render\x01"` ‖ the RFC 8785 form of
|
||||
`{"render": …, "extras": […]}`;
|
||||
- the signed bytes: `"wai:video-manifest-receipt\x01"` ‖ `manifest_hash` ‖
|
||||
`session_class_hash` ‖ `merkle_root` ‖ `work_compose_ops` (8) ‖ `viewers_served` (8) ‖
|
||||
`joules_micro` (8) ‖ the class's tag and evidence of
|
||||
[energy-measurement](energy-measurement.md) §2.7, or 0 unmetered ‖ `grant_hash` ‖
|
||||
`capability` ‖ `joule_ceiling_micro` (8) ‖ (1 ‖ `funds_ceiling` (8), or 0) ‖
|
||||
(1 ‖ `parent_receipt_hash`, or 0) ‖ the number of segments (8) ‖ each leaf ‖
|
||||
`signer_pubkey` ‖ `signer_id`, and with a rendering, 1 ‖ the render digest;
|
||||
- the receipt hash: BLAKE3 of `"wai:video-manifest-receipt-id\x01"` ‖ the signed bytes ‖
|
||||
`sig`.
|
||||
|
||||
A receipt verifies when: the leaves give `merkle_root`; `class` gives
|
||||
`session_class_hash`; without `render` there are no `extras`, and with it the rendering
|
||||
reads back (Appendix G) and has an RFC 8785 form; the BLAKE3 of the rendering is
|
||||
`manifest_hash`; `work_compose_ops` is the number of segments and `viewers_served` is
|
||||
at least 1; the segment at position `i` has `index` `i`, and `ad_id` exactly when it is
|
||||
an ad; `joules_micro` and the class are both present or the figure is the unmetered 0;
|
||||
and `sig` is an Ed25519 signature (RFC 8032) by `signer_pubkey` over the signed bytes,
|
||||
verified strictly: `signer_pubkey` decodes (§5.1.3) and is the one encoding of its point
|
||||
(`y` < p, and no sign bit when `x` is 0), and neither it nor `R` is of small order
|
||||
(`[8]P` is not the identity); `S` < L; and the encoding of `[S]B − [k]A` equals `R`
|
||||
byte for byte, so an `R` that is not its point's one encoding never verifies. A verifier
|
||||
that accepts a key or an `R` this refuses accepts a signature any party can make: under
|
||||
a key of small order, `(R, S) = (B, 1)` verifies over every message.
|
||||
|
||||
## Appendix I — Reading an MPD
|
||||
|
||||
An MPD is read as XML 1.0 in this subset, and refused (`playlist_malformed`) outside it:
|
||||
|
||||
- UTF-8, with an optional byte order mark; CR LF and a lone CR are read as LF (XML 1.0
|
||||
§2.11), and every character is an XML `Char`;
|
||||
- an optional XML declaration first (`<?xml` followed by white space or `?`, up to the
|
||||
first `?>`); before and after the root element, and among content, comments (with no
|
||||
`--` inside) and white space; no document type declaration, CDATA section or other
|
||||
processing instruction;
|
||||
- names of ASCII letters, digits, `_`, `:`, `-` and `.`, not beginning with a digit,
|
||||
`-` or `.`; an element's local name is its name after its last `:`;
|
||||
- attribute values in `"` or `'`, holding no `<`, a literal tab or LF read as a space
|
||||
(XML 1.0 §3.3.3); no attribute twice in an element;
|
||||
- references, in values and in text, to `amp`, `lt`, `gt`, `quot` and `apos`, and
|
||||
character references (`&#` and at most 10 decimal digits, or `&#x` and at most 8
|
||||
hexadecimal digits) to an XML `Char`; no `]]>` in text;
|
||||
- elements nested at most 64 deep, and nothing after the root element but comments and
|
||||
white space.
|
||||
|
||||
Text is not read. The root's local name is `MPD`, with at least one `Period`. A
|
||||
`Period`'s `@start` is an `xs:duration` of days, hours, minutes and seconds
|
||||
(`P[nD][T[nH][nM][n[.n]S]]`, with at least one of them, in digits alone), read in
|
||||
milliseconds as §11.2 reads a duration; absent, it is 0 for the first period and refused
|
||||
for a later one. An `AdaptationSet`'s timelines are the `SegmentTimeline` of its own
|
||||
`SegmentTemplate` or `SegmentList`, and of each of its `Representation`s'. A timeline's
|
||||
`@timescale` is that element's, else that of the same kind of element in the
|
||||
`AdaptationSet` and then the `Period`, else 1; 0 is refused. Each `S` has `@d`, a
|
||||
positive integer, and may have `@t` (by default the end of the `S` before it, 0 for
|
||||
the first) and `@r` (by default 0: the segment repeated `@r` more times). `@t`, `@d`,
|
||||
`@r` and `@timescale` are digits alone; a segment whose end, `@t + @d·(@r + 1)` for the
|
||||
last of a run, passes 2^64 − 1 is refused, and so is a document of more than 256
|
||||
timelines. Other elements and attributes are not read.
|
||||
|
||||
A reader holds a timeline as it is written, one run per `S`, and never expands `@r`:
|
||||
what it holds is bounded by the document's size, whatever `@r` says. §11.2's comparison
|
||||
runs run by run: each step passes a WAI run or a classical one, so it is linear in the
|
||||
runs of the two timelines. The names an element's attributes have are kept in a set, so
|
||||
an element is read in time linear in its attributes.
|
||||
|
|
|
|||
|
|
@ -654,14 +654,16 @@ codecs string *F* and maximum frame size `FW×FH`:
|
|||
|
||||
- `hls/classic/master.m3u8`: `#EXTM3U`, `#EXT-X-VERSION:7`,
|
||||
`#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="F",RESOLUTION=FWxFH`,
|
||||
`floor-<c>.m3u8`.
|
||||
`../classic/floor-<c>.m3u8`: a URI that names the same playlist from every class's
|
||||
directory, so each class's master differs from this one only by the session-data
|
||||
line, as [http-delivery](http-delivery.md) §11.1 and §11.8 require.
|
||||
- `hls/classic/floor-<c>.m3u8`: `#EXTM3U`, `#EXT-X-VERSION:7`,
|
||||
`#EXT-X-TARGETDURATION:<t>`, `#EXT-X-MEDIA-SEQUENCE:0`, `#EXT-X-PLAYLIST-TYPE:VOD`,
|
||||
`#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-MAP:URI="../../floor/<c>/init.mp4"`, then per
|
||||
piece `#EXTINF:<S.mmm>,` and `../../floor/<c>/<sha256>.m4s`, then `#EXT-X-ENDLIST`.
|
||||
- `hls/<class>/master.m3u8`: the classical master's lines, with, when the class lists a
|
||||
WAI object, `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"`
|
||||
after `#EXT-X-INDEPENDENT-SEGMENTS`, and the variant's URI `../classic/floor-<c>.m3u8`.
|
||||
- `hls/<class>/master.m3u8`: the classical master, byte for byte, with, when the class
|
||||
lists a WAI object, `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"`
|
||||
after `#EXT-X-INDEPENDENT-SEGMENTS`, and no other change.
|
||||
- `hls/<class>/wai.m3u8`, when the class lists a WAI object: `#EXTM3U`,
|
||||
`#EXT-X-VERSION:7`, `#EXT-X-INDEPENDENT-SEGMENTS`,
|
||||
`#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.class",VALUE="<class>"`,
|
||||
|
|
@ -671,10 +673,14 @@ codecs string *F* and maximum frame size `FW×FH`:
|
|||
over the objects the class lists.
|
||||
- `hls/<class>/wai-<c>.m3u8`: the floor playlist's lines without `#EXT-X-MAP`, adjacent
|
||||
to it ([http-delivery](http-delivery.md) §11.2); per piece, `#EXT-X-GAP` when the
|
||||
class takes it as floor only, `#EXTINF:<S.mmm>,`, its base as
|
||||
`../../objects/<hash>.wai#n=<byte_len>`, then each refinement the class lists, in
|
||||
entry order, as `#WAI-LAYER:N=<layer>,URI="../../objects/<hash>.wai#n=<byte_len>"`
|
||||
([http-delivery](http-delivery.md) §10.4).
|
||||
class takes it as floor only, `#EXTINF:<S.mmm>,`, each refinement the class lists,
|
||||
in entry order, as `#WAI-LAYER:N=<layer>,URI="../../objects/<hash>.wai#n=<byte_len>"`,
|
||||
then its base as `../../objects/<hash>.wai#n=<byte_len>`: a layer line follows its
|
||||
segment's `EXTINF` ([http-delivery](http-delivery.md) §10.4).
|
||||
|
||||
Every HLS file above and every MPD below is a rendering of
|
||||
[http-delivery](http-delivery.md)'s (Appendix G for a media playlist): the reference
|
||||
packager writes them with the binding's one HLS and one DASH renderer.
|
||||
|
||||
DASH, the same as two MPDs ([http-delivery](http-delivery.md) §11.1), each
|
||||
`<?xml version="1.0" encoding="UTF-8"?>`, then
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ The conformance corpus of [http-delivery](../extensions/http-delivery.md) (§14)
|
|||
served as `<cid>.wai` with their `WHP1` head proofs (§6) and ETag sidecars, and cases
|
||||
for §4–§7 — the head, the head proof, the response checks, the plans, selection on the
|
||||
head and on the whole envelope, the byte-range procedure with its fallback, the
|
||||
`codecs` grammar, union, floor and support. Two implementations check every case and
|
||||
must agree:
|
||||
`codecs` grammar, union, floor and support; and for §8.6, §10.4, §11 and Appendix F the
|
||||
WAI playlists beside their classical ones, their receipts, the live edge, and the
|
||||
reference adaptation policies. 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`;
|
||||
with the features `http` and `video_manifest` (the playlist receipts);
|
||||
- `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
|
||||
code with `wai-rs`. It needs only the Python standard library (its Ed25519 check of the
|
||||
playlist receipts is written from RFC 8032), and reads
|
||||
`../claim-conformance/_blake3.py` (BLAKE3 from its specification) and
|
||||
`../registry/capabilities.json` (the published registry export).
|
||||
|
||||
|
|
@ -29,6 +31,17 @@ must agree:
|
|||
| `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) |
|
||||
| `src/live/`, `src/players/`, `src/playlists/` | frozen sources of the playlist side: the integer video payloads of O12 and O13, the classical playlists the WAI ones sit beside, and the player fixture's classical master as authored |
|
||||
| `playlists/o9/`, `playlists/o10/`, `playlists/o11/` | WAI media and multivariant playlists (and MPDs) over O9 (and over O9 with its second segment a gap, which `wai-gap.mpd` leaves out), O10 with its layers, and O11, each beside its classical playlist in `src/playlists/`; O9's MPDs are on the timeline of `src/playlists/o9-classical.mpd` (90 kHz, written with `@r`, beside an audio set on its own timescale) |
|
||||
| `live/` | four revisions of a low-latency WAI playlist over O12 beside `src/live/classical-r*.m3u8`, and its multivariant playlist; the parts are listed under aliases `720/<sequence>.<part>.wai` with their cids (the alias files are not in the corpus: the cases serve the objects under them, and `wai http origin --root . --live-edge live/wai-720.m3u8=live/wai-r0.m3u8,live/wai-r1.m3u8,live/wai-r2.m3u8,live/wai-r3.m3u8` replays the edge over HTTP, blocking reloads and hinted parts included) |
|
||||
| `players/` | the player fixture: `classical/` (frozen) is a playable classical presentation, `master.m3u8` its master as published with the session-data line, and the WAI multivariant and media playlists and MPD over O13 |
|
||||
| `*/mr/<manifest_hash>.json` | each WAI media playlist's receipt (§8.6, Appendix H), sealed with the corpus's test key (seed 32 bytes of `0x57`, signer `did:example:http-vectors`; it attests nothing) under a grant of no ceilings, O10's under a test grant with both ceilings; the live revisions are chained |
|
||||
| `cases/live-r2-orphan.json` | the third live revision sealed with no parent: a receipt that verifies and does not chain |
|
||||
| `*/mr/<receipt_hash>.json` | each receipt again under its receipt hash, where a client walking back from a later revision fetches it by its link (§8.6) |
|
||||
| `cases/other-key/` | the third live revision's receipt chained to the second by hash and signed by another key (`r2.json`, and in `mr/` under its receipt hash), and the fourth revision's receipt chained to it (`r3.json`) |
|
||||
| `cases/relist/` | the third live revision listing the alias of part 0 of segment 1201 with part 1's object (`wai-r2.m3u8`), sealed and chained to the second (`r2.json`, and in `mr/`); the fourth revision's receipt chained to it (`r3-after.json`); the fourth revision listing segment 1201's object at sequence 1200 (`wai-r3.m3u8`, `r3.json`); the third revision listing that alias as `./720/1201.0.wai`, with part 1's object and with its own (`wai-r2-dot*.m3u8`, `r2-dot*.json`); and the fourth revision withdrawing segment 1200 as a gap (`wai-r3-gap.m3u8`, `r3-gap.json`) |
|
||||
| `cases/walk-substituted/`, `cases/walk-invalid/` | parents a walk fetches by their links: the second revision's receipt served under the third's receipt hash; and the third revision changed after it was signed, served under its receipt hash, with the fourth revision's receipt chained to it (`r3.json`) |
|
||||
| `cases/sig-*.json` | the O9 playlist's receipt re-signed at the edges of Ed25519 (Appendix H): the identity key, canonical and respelled `y` = p + 1, under `(R, S) = (B, 1)`; under the key B (scalar 1), `R` the identity, canonical and respelled, with `S` = k, so the cofactorless equation holds point for point; and under the key B a signature the strict rule accepts (`sig-key-base-point.json`) |
|
||||
|
||||
## Objects
|
||||
|
||||
|
|
@ -54,6 +67,16 @@ must agree:
|
|||
| OPf, OPe, OPw | a `WAI1` naming `wai.text.zstd` whose `refines` is `false`, `{}`, and well formed | a payload carrying `refines` is an ordinary object: dispatched, described by its capability, `not_a_layer` when listed as a layer (§10.4) |
|
||||
| ORN | an AV1 base most of the object and a refinement component stating no `sha256` | the refinement dropped (`component_digest_absent`) on the whole path and the range path alike |
|
||||
| `bad/OK.wai`, `bad/OKN.wai` | a string key id; a numeric `kind` | the manifest's member types |
|
||||
| O11a, O11b, O11bL1 | the staged-delivery pair of `../staged-conformance/cases/live_multi_base/`: two `WAI1` int_motion bases of two frames each, and a layer over the second | `#WAI-LAYER` after one segment only; the layer joined to its own base in the union; depth over a base with one layer |
|
||||
| O12s0, O12s1, O12p0_00 – O12p1_11 | a low-latency set: two segments of 3.84 s, each its own object, and their twenty-four 0.32 s parts, each its own object that decodes alone | Live-P adjacency, parts listed under aliases with cids, the live revision chain, parts verified against the cid the first revision listing them lists |
|
||||
| O13a – O13c | the player fixture's WAI segments: one second each of the clip the classical presentation in `players/classical/` carries | a WAI playlist beside a playable classical one |
|
||||
|
||||
O10 is the layer set of the byte-range side, so the playlist side's objects take the
|
||||
next numbers: O11 is a layered sequence and O12 the low-latency set. The payloads of
|
||||
O12 and O13 are `wai.video.int_motion` clips of a synthetic picture, encoded once by
|
||||
this tree's integer video encoder, frozen in `src/` and checked to decode to the
|
||||
encoder's reconstruction; every frame of each clip is distinct, so no two objects are
|
||||
the same bytes.
|
||||
|
||||
The HDR10+ block, the AVC bases and the LCEVC enhancements are synthetic: they carry
|
||||
the right start codes and decode to nothing, which transport does not need. OD's and
|
||||
|
|
@ -77,6 +100,21 @@ outside this repository, and the AV1 streams were checked to decode.
|
|||
| `union_floor` | a sequence of objects | the union and the floor, or `error` |
|
||||
| `supports` | a floor, the sink's containers and capabilities | `supports` |
|
||||
| `layer_tag` | a `#WAI-LAYER` line | `n` and `uri`, or `error` |
|
||||
| `media_playlist` | a `classical` playlist, the `segments` (each an `object`, its `layers`, its `parts` with their `alias`), an `open` segment, a `uri_prefix`, `end_list` and the `file` it is committed as | the rendering's BLAKE3 and length, and whether the file holds those bytes; or `error` |
|
||||
| `multivariant` | each variant's `uri`, the WAI media `playlist` it names, `resolution`, `frame_rate_milli` | as `media_playlist`; the objects are found by the cids the playlist lists |
|
||||
| `mpd` | a WAI media `playlist`, the classical timeline (`classical_mpd` and its `classical_set` and `period_id`, Appendix I; else the `classical` HLS playlist at timescale 1000), `set_id`, `rep_id` | as `media_playlist`; a gap segment is left out |
|
||||
| `dash_adjacency` | a `wai` and a `classical` MPD (files, or `*_text`), optionally edited | `adjacent`, or `error` (§11.2 in DASH, Appendix I) |
|
||||
| `playlist_parse` | a playlist `file` or `text` | its parameters, segments (duration, discontinuity, date-time, parts, layer numbers, URI), open segment and hint, and whether it is in the rendered form; or `error` |
|
||||
| `adjacency` | a `wai` and a `classical` playlist (files, or `*_text`), optionally edited (`edit_wai`, `edit_classical`: `[from, to]` pairs, each replaced once) | `adjacent`, or `error` |
|
||||
| `unchanged` | an `original` and a `published` classical master (files, or `*_text`), `edit_published` | `unchanged`, or `error` |
|
||||
| `playlist_receipt` | a `playlist`, its `receipt` file and the `name` it is fetched as (the file's own by default), each optionally edited | the receipt hash and its parent, or `error` |
|
||||
| `live` | `revisions` in the order a client fetched them (each a WAI playlist, its receipt, its classical playlist, `edit_wai`), `fetches` (an `alias` and the `object` served under it, optionally patched), the playlist's `url` (against which its lines resolve; `https://origin.example/live/wai-720.m3u8` by default), where parents are fetched from (`parents`, a directory holding `<receipt_hash>.json`; the first revision's `mr/` by default) and the walk's bound (`walk_bound`, 64 by default) | per revision its adjacency, its listing against what accepted revisions bound, its receipt, what it did to the chain (`true`, `unchanged`, `reanchored`, or the reason) and the parents walked; per fetch the accepted revision that first lists the alias, the outcome and what held (`listed`, or `signed` when a receipt in the chain binds the alias's URL to that cid) |
|
||||
| `uri_resolve` | a `base` and a `reference` | the `url` (RFC 3986 §5.2 and §6.2.2, without the fragment) |
|
||||
| `abr` | a `policy` and a `state` (Appendix F) | the `choice` and what the `budget` did, or `error` beyond Appendix F's bounds |
|
||||
| `abr_order` | an object, what the sink `advertises`, a `policy`, a `state` | the entry `order` |
|
||||
| `depth` | layer `objects` (or `layers_n`), a `policy`, a `state` | the `depth` and what the `budget` did, or `error` |
|
||||
| `ilog2` | `a`, `b` | `log2_q16` |
|
||||
| `render` | `segments`, `render` and `extras` written inline | the rendering's BLAKE3 and length, or `error` |
|
||||
|
||||
An input names an object by id, a `file`, or an `envelope_hex` written inline;
|
||||
`union_floor` also takes `envelopes_hex`. A sink's `declared` is the union it was given
|
||||
|
|
@ -85,6 +123,9 @@ An input names an object by id, a `file`, or an `envelope_hex` written inline;
|
|||
`alt` of that base's capability. A `declared` that does not parse, or a base that is
|
||||
not an object, refuses the case with its code.
|
||||
|
||||
`fetch`, `select` and `same_pick` also take `abr` (a `policy` and a `state`): the sink's
|
||||
entry order is then the policy's over the verified head (Appendix F), on both paths.
|
||||
|
||||
`fetch` runs the reference client's policy: a first request of 16369 bytes unless `h`
|
||||
is given, never past `n`; the rest of the object when the planned base-class ranges
|
||||
reach 900 ‰ of the bytes after `covered_len` (`threshold_permille` changes it); and,
|
||||
|
|
@ -100,7 +141,7 @@ dropped with why.
|
|||
|
||||
## Regenerating
|
||||
|
||||
cargo run --no-default-features --features http --bin wai_http_vectors -- gen ../http-conformance
|
||||
cargo run --no-default-features --features http,video_manifest --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`
|
||||
|
|
@ -116,7 +157,10 @@ each to a fresh worktree, requires the build to succeed, and requires
|
|||
--mutate NAME` does the same on the Python side (`left-half`, `no-root`,
|
||||
`counter-zero` fail the proof cases; `floor-as-codecs` fails `union_O9`; `blake3-iv`
|
||||
fails the self-test; `base-at-done`, `tick-noop` and `refinement-unclipped` fail the
|
||||
`timed_*` cases), and the job checks a differential: `wai_http_vectors diff`
|
||||
`timed_*` cases; `pdt-ignored`, `dseq-ignored`, `parts-after-extinf`, `average-floor`,
|
||||
`render-digest-off`, `name-unchecked`, `chain-unchecked`, `ilog2-no-interp`,
|
||||
`bola-waits`, `unlabelled-as-zero` and `round-half-down` fail the playlist, receipt and
|
||||
adaptation cases they reach), 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.
|
||||
|
|
@ -149,7 +193,16 @@ inputs where two readers most easily part, in families:
|
|||
or companion and an alternative each stating a right, a wrong or no digest, fetched
|
||||
through an origin tampered one way, at thresholds either side of the plan;
|
||||
`fuzz_whole_refine`: the whole path, its base's digest absent or the threshold low,
|
||||
with a refinement whose digest is right, wrong or absent.
|
||||
with a refinement whose digest is right, wrong or absent;
|
||||
- `fuzz_playlist_parse`: playlists in a packager's order with at most one line changed,
|
||||
and lines from every tag the reader reads, valid and not; `fuzz_adjacency`: one
|
||||
timeline written as a classical and a WAI playlist, the WAI one perturbed one way or
|
||||
not at all; `fuzz_unchanged`: a master and a published form of it; `fuzz_render`:
|
||||
renderings from inline inputs, well and badly formed; `fuzz_receipt`: a committed
|
||||
receipt file with one member changed;
|
||||
- `fuzz_abr`: states and parameters of the four policies, sizes out of order, samples
|
||||
of 0 ms, empty options and budgets; `fuzz_ilog2`: the integer log at random points
|
||||
and the ends of `u64`.
|
||||
|
||||
A refusal is an outcome like any other: `http_verify.py cases` must give the same
|
||||
code. A case wai-rs gives no outcome for stops the run, and `fuzz` prints, per family,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
1
wai/http-conformance/cases/live-r2-orphan.json
Normal file
1
wai/http-conformance/cases/live-r2-orphan.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
wai/http-conformance/cases/other-key/r2.json
Normal file
1
wai/http-conformance/cases/other-key/r2.json
Normal file
File diff suppressed because one or more lines are too long
1
wai/http-conformance/cases/other-key/r3.json
Normal file
1
wai/http-conformance/cases/other-key/r3.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"eba7d6f7efd7d3b43d1b44eb82645a2736ad931fe99cc27af3de657bed2d6ee13f079ca53fb5ff8c08404ced7ced0769cbaf43c051ca9b3d217d26dbad3a7707","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]}
|
||||
File diff suppressed because one or more lines are too long
1
wai/http-conformance/cases/relist/r2-dot-same.json
Normal file
1
wai/http-conformance/cases/relist/r2-dot-same.json
Normal file
File diff suppressed because one or more lines are too long
1
wai/http-conformance/cases/relist/r2-dot.json
Normal file
1
wai/http-conformance/cases/relist/r2-dot.json
Normal file
File diff suppressed because one or more lines are too long
1
wai/http-conformance/cases/relist/r2.json
Normal file
1
wai/http-conformance/cases/relist/r2.json
Normal file
File diff suppressed because one or more lines are too long
1
wai/http-conformance/cases/relist/r3-after.json
Normal file
1
wai/http-conformance/cases/relist/r3-after.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"0f6f640981fe8a69c0631835056547d825d21155ac95abc410baf8a990cea12a70d1c3db6835fec26b8e63ba26f78c2febba35605333c5f915fceb4f0f070b03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]}
|
||||
1
wai/http-conformance/cases/relist/r3-gap.json
Normal file
1
wai/http-conformance/cases/relist/r3-gap.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"9738e08a98c5867a031789c26269d72cbfabd153483128811d4feb0e33e4e136","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"3bf9989633f662d7084a06d5698c98bf112c81041b2060627a10d2da4a141e20e58cda96925718090d6bb4800b52dab8745f8ce2a368ce279bb9b88578ff7a0e","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":true,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]}
|
||||
1
wai/http-conformance/cases/relist/r3.json
Normal file
1
wai/http-conformance/cases/relist/r3.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"198e04e4e3aa8c149d7adbcc6c1b5ed0a56956ceb3c8d1f34e42104f318dcbdb","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"c5e68c2958aa68df360e32a66700bdff0c36c01e63d0d6bd80b9f03b2bd66a87","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b08962976e408f754a89d2cdc1a5ca5f1cae76a186e8a155ff97cbe3f327239113e9beec406d25a73013ec79f87b2ac5d53171b16e0203a4d5c06d29132a2e0c","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]}
|
||||
36
wai/http-conformance/cases/relist/wai-r2-dot-same.m3u8
Normal file
36
wai/http-conformance/cases/relist/wai-r2-dot-same.m3u8
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:9
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=0.320
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960
|
||||
#EXT-X-MEDIA-SEQUENCE:1200
|
||||
#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014"
|
||||
#EXTINF:3.840,
|
||||
../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="./720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222"
|
||||
#EXTINF:3.840,
|
||||
../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118
|
||||
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720/1202.0.wai"
|
||||
36
wai/http-conformance/cases/relist/wai-r2-dot.m3u8
Normal file
36
wai/http-conformance/cases/relist/wai-r2-dot.m3u8
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:9
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=0.320
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960
|
||||
#EXT-X-MEDIA-SEQUENCE:1200
|
||||
#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014"
|
||||
#EXTINF:3.840,
|
||||
../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="./720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222"
|
||||
#EXTINF:3.840,
|
||||
../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118
|
||||
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720/1202.0.wai"
|
||||
36
wai/http-conformance/cases/relist/wai-r2.m3u8
Normal file
36
wai/http-conformance/cases/relist/wai-r2.m3u8
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:9
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=0.320
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960
|
||||
#EXT-X-MEDIA-SEQUENCE:1200
|
||||
#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014"
|
||||
#EXTINF:3.840,
|
||||
../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222"
|
||||
#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222"
|
||||
#EXTINF:3.840,
|
||||
../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118
|
||||
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720/1202.0.wai"
|
||||
13
wai/http-conformance/cases/relist/wai-r3-gap.m3u8
Normal file
13
wai/http-conformance/cases/relist/wai-r3-gap.m3u8
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:9
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=0.320
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960
|
||||
#EXT-X-MEDIA-SEQUENCE:1200
|
||||
#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z
|
||||
#EXT-X-GAP
|
||||
#EXTINF:3.840,
|
||||
../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606
|
||||
#EXTINF:3.840,
|
||||
../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118
|
||||
#EXT-X-ENDLIST
|
||||
12
wai/http-conformance/cases/relist/wai-r3.m3u8
Normal file
12
wai/http-conformance/cases/relist/wai-r3.m3u8
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:9
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=0.320
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960
|
||||
#EXT-X-MEDIA-SEQUENCE:1200
|
||||
#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z
|
||||
#EXTINF:3.840,
|
||||
../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118
|
||||
#EXTINF:3.840,
|
||||
../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118
|
||||
#EXT-X-ENDLIST
|
||||
1
wai/http-conformance/cases/sig-key-base-point.json
Normal file
1
wai/http-conformance/cases/sig-key-base-point.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666662f03c04a2605fd41de75215b9a872c6a05423724a0ed9ee29c5c6854cc8a7d05","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]}
|
||||
1
wai/http-conformance/cases/sig-key-identity-long.json
Normal file
1
wai/http-conformance/cases/sig-key-identity-long.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666660100000000000000000000000000000000000000000000000000000000000000","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]}
|
||||
1
wai/http-conformance/cases/sig-key-identity.json
Normal file
1
wai/http-conformance/cases/sig-key-identity.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"0100000000000000000000000000000000000000000000000000000000000000","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666660100000000000000000000000000000000000000000000000000000000000000","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]}
|
||||
1
wai/http-conformance/cases/sig-r-non-canonical.json
Normal file
1
wai/http-conformance/cases/sig-r-non-canonical.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f080a137c08411f3c035f03a58f1b5131bcb5887f5082212f2caa4d827d8aed04","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]}
|
||||
1
wai/http-conformance/cases/sig-r-small-order.json
Normal file
1
wai/http-conformance/cases/sig-r-small-order.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"0100000000000000000000000000000000000000000000000000000000000000f57a2a3a17cc1400dba50adbc733921a1b095514a8d91a25909f588c437ac10f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]}
|
||||
File diff suppressed because one or more lines are too long
1
wai/http-conformance/cases/walk-invalid/r3.json
Normal file
1
wai/http-conformance/cases/walk-invalid/r3.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"532a6470fd1df4eb9eca528cb34215932108c4da3fde179d6cbaa3b91c296b1c","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"f4e778d20184fa490c5e0d89da546a9131dbbf9c9f16edc5b0f845f80687103ca6fff7ea16c5d847bb667a26c1fe8adf44b10dcc1b9d54849939ff9f22fd890d","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null}],"merkle_root":"00e2bc6cdc748c6ab760c8e867603fd4d9a72d3a58f985bd13810a8d77aedcd3","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"8186f3adb28113d8ad08c923d24df463eb898d7a95b04902d23ca208fc17fe677249dc30f280faef8ec5aeac81abdb6b6fcd577075d9e9346f445c3b8c13fa03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]}],"layers":[],"preload_hint":"720/1201.3.wai"}]}
|
||||
15
wai/http-conformance/falsify/abr-options-unbounded.patch
Normal file
15
wai/http-conformance/falsify/abr-options-unbounded.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_out_of_bounds_options abr_out_of_bounds_figures
|
||||
# options past the bound read (abr::check_state)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index 6fb8973c..9dace574 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -53,7 +53,7 @@ pub const MAX_OPTIONS: usize = 256;
|
||||
/// field below 2^32, each byte count, figure and budget below 2^64): at most
|
||||
/// [`MAX_OPTIONS`] options and figures.
|
||||
pub fn check_state(s: &AbrState) -> Result<(), AbrError> {
|
||||
- if s.option_bytes.len() > MAX_OPTIONS || s.option_joules_micro.len() > MAX_OPTIONS {
|
||||
+ if false {
|
||||
return Err(AbrError("more options than 256"));
|
||||
}
|
||||
Ok(())
|
||||
15
wai/http-conformance/falsify/abr-parameter-truncated.patch
Normal file
15
wai/http-conformance/falsify/abr-parameter-truncated.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_out_of_bounds_last_n abr_out_of_bounds_horizon
|
||||
# a parameter past its type truncated (the corpus tool's policy reading)
|
||||
diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs
|
||||
index 3d4fecbb..b61542ec 100644
|
||||
--- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs
|
||||
+++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs
|
||||
@@ -420,7 +420,7 @@ fn bounded_opt(v: &Value, max: u64, what: &str) -> Result<Option<u64>, Stop> {
|
||||
/// and the like, each parameter within its type (Appendix F).
|
||||
pub(super) fn policy(v: &Value) -> Result<Box<dyn Policy>, Stop> {
|
||||
let u = |k: &str, max: u64| bounded(&v[k], max, &format!("policy.{k}"));
|
||||
- let (u8m, u16m, u32m) = (u64::from(u8::MAX), u64::from(u16::MAX), u64::from(u32::MAX));
|
||||
+ let (u8m, u16m, u32m) = (u64::MAX, u64::MAX, u64::MAX);
|
||||
Ok(match v["id"].as_str() {
|
||||
Some("throughput") => Box::new(Throughput { last_n: u("last_n", u8m)? as u8, safety_permille: u("safety_permille", u16m)? as u16 }),
|
||||
Some("buffer_based") => Box::new(BufferBased { reservoir_ms: u("reservoir_ms", u32m)? as u32, cushion_ms: u("cushion_ms", u32m)? as u32 }),
|
||||
15
wai/http-conformance/falsify/adjacency-dseq.patch
Normal file
15
wai/http-conformance/falsify/adjacency-dseq.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: adjacency_discontinuity_sequence
|
||||
# adjacency ignores a larger discontinuity sequence (hls::timelines_adjacent)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 2623644b..aa290009 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -394,7 +394,7 @@ pub fn timelines_adjacent(wai: &MediaPlaylist, classical: &MediaPlaylist) -> Res
|
||||
if wai.media_sequence != classical.media_sequence {
|
||||
return Err(misaligned(format!("EXT-X-MEDIA-SEQUENCE {} beside {}", wai.media_sequence, classical.media_sequence)));
|
||||
}
|
||||
- if wai.discontinuity_sequence != classical.discontinuity_sequence {
|
||||
+ if wai.discontinuity_sequence < classical.discontinuity_sequence {
|
||||
return Err(misaligned(format!("EXT-X-DISCONTINUITY-SEQUENCE {} beside {}", wai.discontinuity_sequence, classical.discontinuity_sequence)));
|
||||
}
|
||||
if wai.segments.len() != classical.segments.len() {
|
||||
15
wai/http-conformance/falsify/adjacency-pdt.patch
Normal file
15
wai/http-conformance/falsify/adjacency-pdt.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: adjacency_program_date_time
|
||||
# adjacency ignores another program date-time (hls::timelines_adjacent)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 2623644b..e93d4430 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -412,7 +412,7 @@ pub fn timelines_adjacent(wai: &MediaPlaylist, classical: &MediaPlaylist) -> Res
|
||||
if w.extra.discontinuity != c.extra.discontinuity {
|
||||
return Err(misaligned(format!("segment {seq}: a discontinuity on one side only")));
|
||||
}
|
||||
- if w.extra.program_date_time != c.extra.program_date_time {
|
||||
+ if w.extra.program_date_time.is_some() && c.extra.program_date_time.is_none() {
|
||||
return Err(misaligned(format!("segment {seq}: another program date-time")));
|
||||
}
|
||||
if both_parts && !w.extra.parts.is_empty() && !c.extra.parts.is_empty() && parts_ms(&w.extra.parts) != parts_ms(&c.extra.parts) {
|
||||
15
wai/http-conformance/falsify/bandwidth-average-floor.patch
Normal file
15
wai/http-conformance/falsify/bandwidth-average-floor.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: multivariant_playlists_o9_wai_m3u8
|
||||
# AVERAGE-BANDWIDTH rounded down (hls::bandwidths)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 2623644b..604ff92b 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -608,7 +608,7 @@ pub fn bandwidths(objects: &[(u64, u64)]) -> Option<(u64, u64)> {
|
||||
let rate = |n: u128, d: u128| u64::try_from((8000 * n).div_ceil(d)).unwrap_or(u64::MAX);
|
||||
let peak = objects.iter().map(|&(n, d)| rate(u128::from(n), u128::from(d))).max()?;
|
||||
let (sn, sd) = objects.iter().fold((0u128, 0u128), |(a, b), &(n, d)| (a + u128::from(n), b + u128::from(d)));
|
||||
- Some((peak, rate(sn, sd)))
|
||||
+ Some((peak, u64::try_from(8000 * sn / sd).unwrap_or(u64::MAX)))
|
||||
}
|
||||
|
||||
/// One variant of `wai.m3u8` (§11.1).
|
||||
15
wai/http-conformance/falsify/bola-narrow-product.patch
Normal file
15
wai/http-conformance/falsify/bola-narrow-product.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_extreme_bola_like_past_128_bits
|
||||
# the utility-over-buffer cross-multiplication in 128 bits, wrapping (abr::BolaLike::choose)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index 6fb8973c..0f72e915 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -302,7 +302,7 @@ impl Policy for BolaLike {
|
||||
for i in (0..k).filter(|&i| score[i] > 0) {
|
||||
// score[i]/size(i) > score[b]/size(b), by cross-multiplication widened
|
||||
// past 128 bits: a score times a size reaches 2^141.
|
||||
- if best.is_none_or(|b| product_greater(score[i] as u128, size(b), score[b] as u128, size(i))) {
|
||||
+ if best.is_none_or(|b| (score[i] as u128).wrapping_mul(u128::from(size(b))) > (score[b] as u128).wrapping_mul(u128::from(size(i)))) {
|
||||
best = Some(i);
|
||||
}
|
||||
}
|
||||
15
wai/http-conformance/falsify/bola-waits.patch
Normal file
15
wai/http-conformance/falsify/bola-waits.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_bola_like_00 abr_bola_like_02
|
||||
# the utility rule takes the lowest option where no option scores, as if it waited (abr::BolaLike::choose)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index a8046b34..86597807 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -241,6 +241,8 @@ impl Policy for BolaLike {
|
||||
best = Some(i);
|
||||
}
|
||||
}
|
||||
+ } else if k > 0 {
|
||||
+ return 0;
|
||||
} else {
|
||||
for i in 0..k {
|
||||
if best.is_none_or(|b| score[i] > score[b]) {
|
||||
15
wai/http-conformance/falsify/budget-raises.patch
Normal file
15
wai/http-conformance/falsify/budget-raises.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_budget_not_raised_to_labelled_options abr_budget_not_raised_to_one_within
|
||||
# the budget raises the choice to the lowest labelled option above it within the budget (abr::clamp_to_budget)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index cc9cd96a..a0cb30b9 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -398,7 +398,7 @@ pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) {
|
||||
match (0..=choice).rev().find(|&k| fig(k).is_some_and(|j| j <= b)) {
|
||||
Some(k) if k == choice => (k, Budget::Within),
|
||||
Some(k) => (k, Budget::Lowered),
|
||||
- None => (0, Budget::Unmet),
|
||||
+ None => ((choice + 1..n).find(|&k| fig(k).is_some_and(|j| j <= b)).unwrap_or(0), Budget::Unmet),
|
||||
}
|
||||
}
|
||||
|
||||
15
wai/http-conformance/falsify/budget-unlabelled-escapes.patch
Normal file
15
wai/http-conformance/falsify/budget-unlabelled-escapes.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_budget_unlabelled_choice
|
||||
# an unlabelled choice leaves the budget unapplied (abr::clamp_to_budget)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index cc9cd96a..20028eea 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -392,7 +392,7 @@ pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) {
|
||||
let Some(b) = s.joule_budget_micro else { return (choice, Budget::NotSet) };
|
||||
let n = s.option_bytes.len().max(choice + 1);
|
||||
let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten() } else { None };
|
||||
- if (0..n).all(|k| fig(k).is_none()) {
|
||||
+ if (0..n).all(|k| fig(k).is_none()) || fig(choice).is_none() {
|
||||
return (choice, Budget::NotApplied);
|
||||
}
|
||||
match (0..=choice).rev().find(|&k| fig(k).is_some_and(|j| j <= b)) {
|
||||
15
wai/http-conformance/falsify/budget-unlabelled.patch
Normal file
15
wai/http-conformance/falsify/budget-unlabelled.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_budget_not_applied abr_budget_unlabelled_choice depth_O10_budget_unlabelled_layer
|
||||
# an unlabelled figure taken as zero joules (abr::clamp_to_budget)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index cc9cd96a..18d9cbfe 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -391,7 +391,7 @@ pub fn labelled_figure(joules_micro: u64, class: Option<&crate::energy_class::En
|
||||
pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) {
|
||||
let Some(b) = s.joule_budget_micro else { return (choice, Budget::NotSet) };
|
||||
let n = s.option_bytes.len().max(choice + 1);
|
||||
- let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten() } else { None };
|
||||
+ let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten().or(Some(0)) } else { None };
|
||||
if (0..n).all(|k| fig(k).is_none()) {
|
||||
return (choice, Budget::NotApplied);
|
||||
}
|
||||
15
wai/http-conformance/falsify/budget-unmet-least-figure.patch
Normal file
15
wai/http-conformance/falsify/budget-unmet-least-figure.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_budget_unmet_not_the_least_figure abr_budget_not_raised_past_the_budget
|
||||
# with nothing within the budget, the option of the least labelled figure (abr::clamp_to_budget)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index cc9cd96a..a2ae9f56 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -398,7 +398,7 @@ pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) {
|
||||
match (0..=choice).rev().find(|&k| fig(k).is_some_and(|j| j <= b)) {
|
||||
Some(k) if k == choice => (k, Budget::Within),
|
||||
Some(k) => (k, Budget::Lowered),
|
||||
- None => (0, Budget::Unmet),
|
||||
+ None => ((0..n).filter_map(|k| fig(k).map(|j| (j, k))).min().map_or(0, |(_, k)| k), Budget::Unmet),
|
||||
}
|
||||
}
|
||||
|
||||
14
wai/http-conformance/falsify/chain-bindings-dropped.patch
Normal file
14
wai/http-conformance/falsify/chain-bindings-dropped.patch
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# must fail: live_edge
|
||||
# what the chain's receipts list not bound (LiveChain::advance)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index 6666c22a..14d8229f 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -240,7 +240,6 @@ impl LiveChain {
|
||||
for r in walked.iter().rev().chain(std::iter::once(cur)) {
|
||||
bind(&mut b, r)?;
|
||||
}
|
||||
- self.bindings = b;
|
||||
self.head = Some(cur.clone());
|
||||
Ok(Step::Chained { walked: walked.len() })
|
||||
}
|
||||
24
wai/http-conformance/falsify/chain-key-unchecked.patch
Normal file
24
wai/http-conformance/falsify/chain-key-unchecked.patch
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# must fail: live_edge_other_key live_edge_other_key_walked
|
||||
# a receipt chained by hash under another key accepted (LiveChain::advance)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index 6666c22a..58f23c43 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -207,7 +207,7 @@ impl LiveChain {
|
||||
if cur.receipt_hash() == target {
|
||||
return Ok(Step::Unchanged);
|
||||
}
|
||||
- if cur.signer_pubkey != head.signer_pubkey {
|
||||
+ if false {
|
||||
return Err(ReceiptError::Unchained);
|
||||
}
|
||||
let mut walked: Vec<ManifestReceipt> = Vec::new();
|
||||
@@ -230,7 +230,7 @@ impl LiveChain {
|
||||
if !r.verify() {
|
||||
return Err(ReceiptError::Invalid);
|
||||
}
|
||||
- if r.signer_pubkey != cur.signer_pubkey {
|
||||
+ if false {
|
||||
return Err(ReceiptError::Unchained);
|
||||
}
|
||||
link = r.parent_receipt_hash;
|
||||
18
wai/http-conformance/falsify/chain-parent-unchecked.patch
Normal file
18
wai/http-conformance/falsify/chain-parent-unchecked.patch
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# must fail: live_edge_unchained live_edge_skip live_edge_other_key_walked
|
||||
# a live revision's parent links not walked (playlist_receipt::LiveChain::advance)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index 6fb6141d..db167d4c 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -228,9 +228,9 @@ impl LiveChain {
|
||||
let mut link = cur.parent_receipt_hash;
|
||||
loop {
|
||||
let l = match link {
|
||||
- None => return Err(ReceiptError::Unchained),
|
||||
+ None => break,
|
||||
Some(l) if l == target => break,
|
||||
- Some(l) => l,
|
||||
+ Some(_) => break,
|
||||
};
|
||||
if walked.len() == self.bound {
|
||||
// §8.6: past the bound, a revision ahead of the head (more segments
|
||||
16
wai/http-conformance/falsify/chain-reload-as-revision.patch
Normal file
16
wai/http-conformance/falsify/chain-reload-as-revision.patch
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# must fail: live_edge_unchanged_reload
|
||||
# the head fetched again taken as a new revision (LiveChain::advance)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index 6666c22a..7acdf1be 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -204,9 +204,6 @@ impl LiveChain {
|
||||
return Ok(Step::First);
|
||||
};
|
||||
let target = head.receipt_hash();
|
||||
- if cur.receipt_hash() == target {
|
||||
- return Ok(Step::Unchanged);
|
||||
- }
|
||||
if cur.signer_pubkey != head.signer_pubkey {
|
||||
return Err(ReceiptError::Unchained);
|
||||
}
|
||||
15
wai/http-conformance/falsify/chain-walk-unbounded.patch
Normal file
15
wai/http-conformance/falsify/chain-walk-unbounded.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: live_edge_reanchored live_edge_reanchor_not_backwards
|
||||
# the walk back to the head not bounded (LiveChain::advance)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index 6fb6141d..b3b67ab0 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -232,7 +232,7 @@ impl LiveChain {
|
||||
Some(l) if l == target => break,
|
||||
Some(l) => l,
|
||||
};
|
||||
- if walked.len() == self.bound {
|
||||
+ if false && walked.len() == self.bound {
|
||||
// §8.6: past the bound, a revision ahead of the head (more segments
|
||||
// complete) re-anchors the chain, its bindings kept; one that is not
|
||||
// ahead is refused, so a stale or older revision never rolls it back.
|
||||
14
wai/http-conformance/falsify/dash-order-unchecked.patch
Normal file
14
wai/http-conformance/falsify/dash-order-unchecked.patch
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# must fail: dash_adjacency_out_of_order
|
||||
# the classical cursor reset for each WAI run: segments out of order accepted (dash_read::within)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 71e36cde..7a416543 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -477,6 +477,7 @@ fn within(wai: &[Run], classical: &[Run]) -> bool {
|
||||
// The classical cursor: a run, and the segments of it already passed.
|
||||
let (mut ci, mut co) = (0usize, 0u128);
|
||||
for w in wai {
|
||||
+ (ci, co) = (0, 0);
|
||||
let (mut t, mut left) = (u128::from(w.t), u128::from(w.count));
|
||||
while left > 0 {
|
||||
// The first classical segment at or after the cursor whose time is `t`.
|
||||
15
wai/http-conformance/falsify/dash-r-ignored.patch
Normal file
15
wai/http-conformance/falsify/dash-r-ignored.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: dash_adjacency_o9 mpd_playlists_o9_wai_mpd
|
||||
# @r read as no repeat (dash_read::timeline_of)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 71e36cde..6611250e 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -412,7 +412,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result<Option<Timeline>,
|
||||
if end > u128::from(u64::MAX) {
|
||||
return Err(malformed("a segment time past 2^64 - 1"));
|
||||
}
|
||||
- runs.push(Run { t, d, count: r + 1 });
|
||||
+ runs.push(Run { t, d, count: 1 });
|
||||
next = end as u64;
|
||||
}
|
||||
Ok(Some(Timeline { timescale, runs }))
|
||||
15
wai/http-conformance/falsify/dash-run-end-unchecked.patch
Normal file
15
wai/http-conformance/falsify/dash-run-end-unchecked.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: dash_adjacency_r_end_past_u64
|
||||
# a run whose end passes 2^64 - 1 read (dash_read::timeline_of)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 71e36cde..5b55d5a1 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -409,7 +409,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result<Option<Timeline>,
|
||||
let r = s.attr("r").map(|v| unsigned(v, "@r")).transpose()?.unwrap_or(0);
|
||||
// The run's end, t + d·(r + 1), at most 2^64 − 1: computed, never expanded.
|
||||
let end = u128::from(t) + u128::from(d) * (u128::from(r) + 1);
|
||||
- if end > u128::from(u64::MAX) {
|
||||
+ if false && end > u128::from(u64::MAX) {
|
||||
return Err(malformed("a segment time past 2^64 - 1"));
|
||||
}
|
||||
runs.push(Run { t, d, count: r + 1 });
|
||||
15
wai/http-conformance/falsify/dash-start-unchecked.patch
Normal file
15
wai/http-conformance/falsify/dash-start-unchecked.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: dash_adjacency_period_start
|
||||
# a Period@start not compared (dash_read::check_dash_adjacent)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 79d55df6..dd967c6c 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -450,7 +450,7 @@ pub fn check_dash_adjacent(wai: &str, classical: &str) -> Result<(), PlaylistErr
|
||||
let k = read_mpd(classical)?;
|
||||
for wp in &w {
|
||||
let kp = k.iter().find(|p| p.id == wp.id).ok_or_else(|| misaligned(format!("no classical Period with the @id {:?}", wp.id)))?;
|
||||
- if kp.start_ms != wp.start_ms {
|
||||
+ if false {
|
||||
return Err(misaligned(format!("Period {:?} starts at {} ms, the classical one at {} ms", wp.id, wp.start_ms, kp.start_ms)));
|
||||
}
|
||||
if wp.sets.is_empty() {
|
||||
15
wai/http-conformance/falsify/dash-timelines-unbounded.patch
Normal file
15
wai/http-conformance/falsify/dash-timelines-unbounded.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: dash_adjacency_timelines_past_the_subset
|
||||
# a document of more than 256 timelines read (dash_read::read_mpd)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 71e36cde..d08ed0fd 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -435,7 +435,7 @@ pub fn read_mpd(text: &str) -> Result<Vec<PeriodTimelines>, PlaylistError> {
|
||||
let mut held = 0usize;
|
||||
let mut count = |timelines: &Vec<Timeline>| -> Result<(), PlaylistError> {
|
||||
held += timelines.len();
|
||||
- if held > MAX_TIMELINES { Err(malformed("more SegmentTimelines than the subset's 256")) } else { Ok(()) }
|
||||
+ if false && held > MAX_TIMELINES { Err(malformed("more SegmentTimelines than the subset's 256")) } else { Ok(()) }
|
||||
};
|
||||
for (i, p) in root.children_named("Period").enumerate() {
|
||||
let start_ms = match p.attr("start") {
|
||||
15
wai/http-conformance/falsify/dash-timescale-unchecked.patch
Normal file
15
wai/http-conformance/falsify/dash-timescale-unchecked.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: dash_adjacency_timescale dash_adjacency_the_audio_timescale
|
||||
# a timescale not compared (dash_read::check_dash_adjacent)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 71e36cde..6e7a5b83 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -526,7 +526,7 @@ pub fn check_dash_adjacent(wai: &str, classical: &str) -> Result<(), PlaylistErr
|
||||
}
|
||||
for tl in &s.timelines {
|
||||
let aligned = tl.segment_count() > 0
|
||||
- && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| c.timescale == tl.timescale && within(&tl.runs, &c.runs));
|
||||
+ && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| within(&tl.runs, &c.runs));
|
||||
if !aligned {
|
||||
return Err(misaligned(format!("a timeline of AdaptationSet {:?} is not a classical one's", s.id)));
|
||||
}
|
||||
15
wai/http-conformance/falsify/dash-zero-d.patch
Normal file
15
wai/http-conformance/falsify/dash-zero-d.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: dash_adjacency_zero_d
|
||||
# an S of no duration read (dash_read::timeline_of)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 71e36cde..f4aec871 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -402,7 +402,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result<Option<Timeline>,
|
||||
let mut next = 0u64;
|
||||
for s in st.children_named("S") {
|
||||
let d = unsigned(s.attr("d").ok_or_else(|| malformed("an S has no @d"))?, "@d")?;
|
||||
- if d == 0 {
|
||||
+ if false {
|
||||
return Err(malformed("an S of no duration"));
|
||||
}
|
||||
let t = s.attr("t").map(|v| unsigned(v, "@t")).transpose()?.unwrap_or(next);
|
||||
15
wai/http-conformance/falsify/decimal-truncates.patch
Normal file
15
wai/http-conformance/falsify/decimal-truncates.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: playlist_parse_rounding
|
||||
# durations truncated to the millisecond, not rounded (hls::decimal_ms)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 2623644b..552dbde8 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -92,7 +92,7 @@ pub fn decimal_ms(s: &str) -> Option<u64> {
|
||||
let digit = |i: usize| f.as_bytes().get(i).map_or(0, |b| u64::from(b - b'0'));
|
||||
let whole: u64 = int.parse().ok()?;
|
||||
let ms = whole.checked_mul(1000)?.checked_add(digit(0) * 100 + digit(1) * 10 + digit(2))?;
|
||||
- if digit(3) >= 5 { ms.checked_add(1) } else { Some(ms) }
|
||||
+ if digit(3) >= 10 { ms.checked_add(1) } else { Some(ms) }
|
||||
}
|
||||
|
||||
fn decimal_int(s: &str) -> Option<u64> {
|
||||
15
wai/http-conformance/falsify/depth-per-layer.patch
Normal file
15
wai/http-conformance/falsify/depth-per-layer.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: depth_O10_throughput_between depth_O11b_bola_like_0
|
||||
# a depth's bytes taken as its own layer's, not the sum through it (abr::depth)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index 6fb8973c..c6472097 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -432,7 +432,7 @@ pub fn depth(layers_n: &[u64], p: &dyn Policy, s: &AbrState) -> Result<(usize, B
|
||||
let mut acc = 0u64;
|
||||
st.option_bytes = Vec::with_capacity(layers_n.len());
|
||||
for &n in layers_n {
|
||||
- acc = acc.checked_add(n).ok_or(AbrError("the bytes through a depth pass 2^64 - 1"))?;
|
||||
+ acc = n;
|
||||
st.option_bytes.push(acc);
|
||||
}
|
||||
st.option_joules_micro = Vec::with_capacity(layers_n.len());
|
||||
15
wai/http-conformance/falsify/depth-sum-saturates.patch
Normal file
15
wai/http-conformance/falsify/depth-sum-saturates.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: depth_out_of_bounds_bytes
|
||||
# the bytes through a depth saturate (abr::depth)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index 6fb8973c..42b3495d 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -432,7 +432,7 @@ pub fn depth(layers_n: &[u64], p: &dyn Policy, s: &AbrState) -> Result<(usize, B
|
||||
let mut acc = 0u64;
|
||||
st.option_bytes = Vec::with_capacity(layers_n.len());
|
||||
for &n in layers_n {
|
||||
- acc = acc.checked_add(n).ok_or(AbrError("the bytes through a depth pass 2^64 - 1"))?;
|
||||
+ acc = acc.saturating_add(n);
|
||||
st.option_bytes.push(acc);
|
||||
}
|
||||
st.option_joules_micro = Vec::with_capacity(layers_n.len());
|
||||
15
wai/http-conformance/falsify/gap-carries.patch
Normal file
15
wai/http-conformance/falsify/gap-carries.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: render_gap_with_parts render_gap_with_layers media_playlist_gap_with_layers
|
||||
# a gap segment with parts or layer lines rendered (hls_render::check_render)
|
||||
diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs
|
||||
index ba3f11d4..ba588609 100644
|
||||
--- a/wai/wai-rs/src/hls_render.rs
|
||||
+++ b/wai/wai-rs/src/hls_render.rs
|
||||
@@ -359,7 +359,7 @@ pub fn check_render(segments: &[Segment], r: &HlsRender, extras: &[SegmentExtra]
|
||||
check_layers(&p.layers, &at)?;
|
||||
}
|
||||
check_layers(&e.layers, &at)?;
|
||||
- if i < n && e.gap && (!e.parts.is_empty() || !e.layers.is_empty()) {
|
||||
+ if false && i < n && e.gap && (!e.parts.is_empty() || !e.layers.is_empty()) {
|
||||
return Err(RenderError(format!("{at}: a gap carries parts or layer lines")));
|
||||
}
|
||||
if let Some(h) = &e.preload_hint {
|
||||
16
wai/http-conformance/falsify/gap-not-rendered.patch
Normal file
16
wai/http-conformance/falsify/gap-not-rendered.patch
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# must fail: media_playlist_playlists_o9_wai_180_gap_m3u8
|
||||
# a gap segment rendered as a segment (hls_render::render_hls_with)
|
||||
diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs
|
||||
index 7e29b8dd..52cd436d 100644
|
||||
--- a/wai/wai-rs/src/hls_render.rs
|
||||
+++ b/wai/wai-rs/src/hls_render.rs
|
||||
@@ -259,9 +259,6 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt
|
||||
out.push_str(&format!("#EXT-X-PROGRAM-DATE-TIME:{pdt}\n"));
|
||||
}
|
||||
part_lines(&mut out, &e.parts);
|
||||
- if e.gap {
|
||||
- out.push_str("#EXT-X-GAP\n");
|
||||
- }
|
||||
// Fixed 3-dp seconds so the byte output is stable across platforms.
|
||||
out.push_str(&format!("#EXTINF:{},\n", seconds(s.duration_ms)));
|
||||
layer_lines(&mut out, &e.layers);
|
||||
15
wai/http-conformance/falsify/gap-withdraws.patch
Normal file
15
wai/http-conformance/falsify/gap-withdraws.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: live_gap_withdraws_a_segment
|
||||
# a gap at a sequence number bound to an object accepted (hls::Bindings::bind)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 1852df35..e38575cd 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -982,7 +982,7 @@ impl Bindings {
|
||||
for (i, s) in p.segments.iter().enumerate() {
|
||||
let n = p.media_sequence.checked_add(i as u64).ok_or_else(|| malformed("the media sequence number passes 2^64"))?;
|
||||
if s.extra.gap {
|
||||
- if let Some((held, _)) = next.sequences.get(&n) {
|
||||
+ if let Some((held, _)) = next.sequences.get(&n).filter(|_| false) {
|
||||
return Err(PlaylistError::Rebound(format!("media sequence {n} was listed with {}, and is now a gap", hex(held))));
|
||||
}
|
||||
continue;
|
||||
15
wai/http-conformance/falsify/grant-ceiling-written-out.patch
Normal file
15
wai/http-conformance/falsify/grant-ceiling-written-out.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: regeneration
|
||||
# no joule ceiling written as 2^64 - 1 (wai_video_manifest::grant_json)
|
||||
diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
index a7574e49..41275c34 100644
|
||||
--- a/wai/wai-rs/src/wai_video_manifest.rs
|
||||
+++ b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
@@ -199,7 +199,7 @@ fn numbers_within(v: &serde_json::Value) -> bool {
|
||||
pub fn grant_json(g: &GrantRef) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"grant_hash": hex(&g.grant_hash), "capability": g.capability,
|
||||
- "joule_ceiling_micro": (g.joule_ceiling_micro != u64::MAX).then_some(g.joule_ceiling_micro), "funds_ceiling": g.funds_ceiling,
|
||||
+ "joule_ceiling_micro": g.joule_ceiling_micro, "funds_ceiling": g.funds_ceiling,
|
||||
})
|
||||
}
|
||||
|
||||
15
wai/http-conformance/falsify/ilog2-no-interp.patch
Normal file
15
wai/http-conformance/falsify/ilog2-no-interp.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: ilog2_1001_1000 ilog2_7_3
|
||||
# the integer log without its interpolation (abr::ilog2_q16)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index a8046b34..7fb9c495 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -145,7 +145,7 @@ pub fn ilog2_q16(a: u64, b: u64) -> u64 {
|
||||
let e = (127 - x.leading_zeros()) as u64 - 16;
|
||||
let f = ((x >> e) - 65536) as u64;
|
||||
let (i, r) = ((f >> 12) as usize, f & 4095);
|
||||
- e * 65536 + LOG2_TABLE[i] + (((LOG2_TABLE[i + 1] - LOG2_TABLE[i]) * r) >> 12)
|
||||
+ e * 65536 + LOG2_TABLE[i] + (r & 0)
|
||||
}
|
||||
|
||||
/// The throughput the last `n` samples give, in bits per second: `⌊8000·Σbytes / Σms⌋`,
|
||||
17
wai/http-conformance/falsify/layer-after-uri.patch
Normal file
17
wai/http-conformance/falsify/layer-after-uri.patch
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# must fail: media_playlist_playlists_o10_wai_a_m3u8 media_playlist_playlists_o11_wai_v_m3u8
|
||||
# layer lines rendered after the segment's URI (hls_render::render_hls_with)
|
||||
diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs
|
||||
index 7e29b8dd..3c910dc0 100644
|
||||
--- a/wai/wai-rs/src/hls_render.rs
|
||||
+++ b/wai/wai-rs/src/hls_render.rs
|
||||
@@ -264,9 +264,9 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt
|
||||
}
|
||||
// Fixed 3-dp seconds so the byte output is stable across platforms.
|
||||
out.push_str(&format!("#EXTINF:{},\n", seconds(s.duration_ms)));
|
||||
- layer_lines(&mut out, &e.layers);
|
||||
out.push_str(&s.uri);
|
||||
out.push('\n');
|
||||
+ layer_lines(&mut out, &e.layers);
|
||||
}
|
||||
if let Some(open) = extras.get(segments.len()) {
|
||||
if open.discontinuity {
|
||||
16
wai/http-conformance/falsify/layers-not-counted.patch
Normal file
16
wai/http-conformance/falsify/layers-not-counted.patch
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# must fail: multivariant_playlists_o10_wai_m3u8
|
||||
# a variant's rates without the layer objects listed with its segments (the corpus tool's listed_objects)
|
||||
diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs
|
||||
index 29d4d12c..bebf0a80 100644
|
||||
--- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs
|
||||
+++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs
|
||||
@@ -315,9 +315,6 @@ fn listed_objects(c: &Corpus, p: &MediaPlaylist) -> Result<(Vec<(u64, u64)>, Vec
|
||||
let b = resolve(&s.uri)?;
|
||||
let mut n = b.len() as u64;
|
||||
all.push(b);
|
||||
- for l in &s.extra.layers {
|
||||
- n += resolve(&l.uri)?.len() as u64;
|
||||
- }
|
||||
segs.push((n, s.duration_ms));
|
||||
extra(&s.extra, &mut parts, &mut all)?;
|
||||
}
|
||||
16
wai/http-conformance/falsify/listed-open-parts.patch
Normal file
16
wai/http-conformance/falsify/listed-open-parts.patch
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# must fail: live_edge
|
||||
# the open segment's parts not read for their cids (hls::listed_cid)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 2623644b..fc201603 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -741,9 +741,6 @@ pub fn listed_cid(p: &MediaPlaylist, uri: &str) -> Option<[u8; 32]> {
|
||||
lines.extend(s.extra.layers.iter().map(|l| l.uri.as_str()));
|
||||
lines.push(&s.uri);
|
||||
}
|
||||
- if let Some(o) = &p.open {
|
||||
- parts(o, &mut lines);
|
||||
- }
|
||||
let line = lines.into_iter().find(|l| super::parse_fragment(l).0 == uri)?;
|
||||
let (base, frag) = super::parse_fragment(line);
|
||||
frag.cid.or_else(|| {
|
||||
15
wai/http-conformance/falsify/mpc-no-stall.patch
Normal file
15
wai/http-conformance/falsify/mpc-no-stall.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_mpc_04 abr_order_O3_mpc_0 depth_O10_mpc_0
|
||||
# the look-ahead does not count stalls (abr::Mpc::choose)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index 6fb8973c..852404d4 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -364,7 +364,7 @@ impl Policy for Mpc {
|
||||
}
|
||||
prev = Some(uo);
|
||||
}
|
||||
- qoe -= rebuf as i128 * (u_top + 65536);
|
||||
+ qoe -= rebuf as i128 * 0;
|
||||
if best.is_none_or(|(q, _)| qoe > q) {
|
||||
best = Some((qoe, seq[0]));
|
||||
}
|
||||
15
wai/http-conformance/falsify/mpd-char-unchecked.patch
Normal file
15
wai/http-conformance/falsify/mpd-char-unchecked.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: mpd_rep_id_not_a_char
|
||||
# a value outside XML written (dash::esc)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash.rs b/wai/wai-rs/src/http_binding/dash.rs
|
||||
index 7dd63c36..1ee1165b 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash.rs
|
||||
@@ -158,7 +158,7 @@ fn esc(s: &str) -> Result<String, PlaylistError> {
|
||||
'\t' => out.push_str("	"),
|
||||
'\n' => out.push_str(" "),
|
||||
'\r' => out.push_str(" "),
|
||||
- c if xml_char(c) => out.push(c),
|
||||
+ c if true || xml_char(c) => out.push(c),
|
||||
c => return Err(PlaylistError::Malformed(format!("U+{:04X} is not an XML character", c as u32))),
|
||||
}
|
||||
}
|
||||
15
wai/http-conformance/falsify/mpd-gap-listed.patch
Normal file
15
wai/http-conformance/falsify/mpd-gap-listed.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: mpd_playlists_o9_wai_gap_mpd
|
||||
# a gap segment listed in wai.mpd (the corpus tool's mpd rendering)
|
||||
diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs
|
||||
index be66d9a2..027a96e5 100644
|
||||
--- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs
|
||||
+++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs
|
||||
@@ -365,7 +365,7 @@ fn render_mpd(c: &Corpus, input: &Value) -> Result<String, Stop> {
|
||||
DashTimeline::from_hls(&classical, input["period_id"].as_str().unwrap_or("p0"), 0).map_err(|e| pl_err(&e))?
|
||||
}
|
||||
};
|
||||
- let media = p.segments.iter().map(|s| (!s.extra.gap).then(|| s.uri.clone())).collect();
|
||||
+ let media = p.segments.iter().map(|s| Some(s.uri.clone())).collect();
|
||||
let rep = WaiRepresentation { id: input["rep_id"].as_str().ok_or("rep_id")?.into(), bandwidth: peak, media };
|
||||
wai_mpd(&t, input["set_id"].as_u64().unwrap_or(1) as u32, &union, &floor, &[rep]).map_err(|e| pl_err(&e))
|
||||
}
|
||||
15
wai/http-conformance/falsify/order-by-table.patch
Normal file
15
wai/http-conformance/falsify/order-by-table.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: abr_order_O7_throughput_0
|
||||
# a capability's entries left in table order, not ordered by size (abr::order_manifest)
|
||||
diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs
|
||||
index a8046b34..8c2cc113 100644
|
||||
--- a/wai/wai-rs/src/http_binding/abr.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/abr.rs
|
||||
@@ -389,7 +389,7 @@ pub fn order_manifest(head: &Head, m: &ManifestV2, eligible: &dyn Fn(&str) -> bo
|
||||
}
|
||||
let mut order = Vec::with_capacity(e.len());
|
||||
for (_, mut g) in groups {
|
||||
- g.sort_by_key(|&i| (len(i), i));
|
||||
+ g.sort_by_key(|&i| i);
|
||||
let mut st = s.clone();
|
||||
st.option_bytes = g.iter().map(|&i| len(i)).collect();
|
||||
st.option_joules_micro = g.iter().map(|&i| s.option_joules_micro.get(i).copied().flatten()).collect();
|
||||
15
wai/http-conformance/falsify/reanchor-backwards.patch
Normal file
15
wai/http-conformance/falsify/reanchor-backwards.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: live_edge_reanchor_not_backwards
|
||||
# past the bound, any revision re-anchors, an older one too (LiveChain::advance)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index 6fb6141d..f71d65eb 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -236,7 +236,7 @@ impl LiveChain {
|
||||
// §8.6: past the bound, a revision ahead of the head (more segments
|
||||
// complete) re-anchors the chain, its bindings kept; one that is not
|
||||
// ahead is refused, so a stale or older revision never rolls it back.
|
||||
- if last_segment(cur) <= last_segment(head) {
|
||||
+ if false && last_segment(cur) <= last_segment(head) {
|
||||
return Err(ReceiptError::WalkExceeded);
|
||||
}
|
||||
let mut b = self.bindings.clone();
|
||||
15
wai/http-conformance/falsify/rebind-sequence.patch
Normal file
15
wai/http-conformance/falsify/rebind-sequence.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: live_resequenced
|
||||
# a media sequence number listed again with another object accepted (hls::Bindings::bind)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 1852df35..a6e2e383 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -989,7 +989,7 @@ impl Bindings {
|
||||
}
|
||||
let Some((_, cid)) = uri_cid(&s.uri) else { continue };
|
||||
match next.sequences.get(&n) {
|
||||
- Some((held, _)) if *held != cid => {
|
||||
+ Some((held, _)) if false && *held != cid => {
|
||||
return Err(PlaylistError::Rebound(format!("media sequence {n} was listed with {}, and is now listed with {}", hex(held), hex(&cid))));
|
||||
}
|
||||
_ => {
|
||||
15
wai/http-conformance/falsify/rebind-spelling.patch
Normal file
15
wai/http-conformance/falsify/rebind-spelling.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: live_relisted_dot_alias
|
||||
# bindings keyed by a line's spelling, not its resolved URL (hls::Bindings::bind)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 1852df35..3f2a43aa 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -951,7 +951,7 @@ impl Bindings {
|
||||
let base = self.base.clone();
|
||||
let mut uri = |line: &str| -> Result<(), PlaylistError> {
|
||||
let Some((_, cid)) = uri_cid(line) else { return Ok(()) };
|
||||
- let key = super::uri::resolve(&base, line);
|
||||
+ let key = super::parse_fragment(line).0;
|
||||
match next.uris.get(&key) {
|
||||
Some((held, _)) if *held != cid => Err(PlaylistError::Rebound(format!("{key} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))),
|
||||
_ => {
|
||||
15
wai/http-conformance/falsify/rebind-uri.patch
Normal file
15
wai/http-conformance/falsify/rebind-uri.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: live_relisted_alias live_relisted_alias_walked
|
||||
# a URL listed again with another cid accepted (hls::Bindings::bind)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 1852df35..867bafe5 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -953,7 +953,7 @@ impl Bindings {
|
||||
let Some((_, cid)) = uri_cid(line) else { return Ok(()) };
|
||||
let key = super::uri::resolve(&base, line);
|
||||
match next.uris.get(&key) {
|
||||
- Some((held, _)) if *held != cid => Err(PlaylistError::Rebound(format!("{key} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))),
|
||||
+ Some((held, _)) if false && *held != cid => Err(PlaylistError::Rebound(format!("{key} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))),
|
||||
_ => {
|
||||
next.uris.insert(key, (cid, rev));
|
||||
Ok(())
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: playlist_receipt_duplicate_escaped_name
|
||||
# member names compared as written, not as read (ManifestReceipt::from_json)
|
||||
diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
index b639ad0b..7658b047 100644
|
||||
--- a/wai/wai-rs/src/wai_video_manifest.rs
|
||||
+++ b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
@@ -210,7 +210,7 @@ fn duplicate_member(text: &str) -> Option<String> {
|
||||
i += if b[i] == b'\\' { 2 } else { 1 };
|
||||
}
|
||||
if let Some(Some((names, true))) = stack.last_mut() {
|
||||
- let name: String = serde_json::from_str(&text[start..=i.min(b.len() - 1)]).ok()?;
|
||||
+ let name: String = text[start..=i.min(b.len() - 1)].to_string();
|
||||
if !names.insert(name.clone()) {
|
||||
return Some(name);
|
||||
}
|
||||
15
wai/http-conformance/falsify/receipt-duplicates.patch
Normal file
15
wai/http-conformance/falsify/receipt-duplicates.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: playlist_receipt_duplicate_first playlist_receipt_duplicate_last playlist_receipt_duplicate_in_class playlist_receipt_duplicate_escaped_name
|
||||
# a receipt file with a member twice read (ManifestReceipt::from_json)
|
||||
diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
index b639ad0b..368608bf 100644
|
||||
--- a/wai/wai-rs/src/wai_video_manifest.rs
|
||||
+++ b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
@@ -579,7 +579,7 @@ impl ManifestReceipt {
|
||||
// A member name twice in one object is refused, at any depth and however the
|
||||
// names are escaped: a reader that keeps the first and one that keeps the last
|
||||
// would read two receipts from one file (I-JSON, RFC 7493 §2.3).
|
||||
- if let Some(name) = duplicate_member(text) {
|
||||
+ if let Some(name) = duplicate_member(text).filter(|_| false) {
|
||||
return Err(format!("the member {name:?} twice in one object"));
|
||||
}
|
||||
let o = v.as_object().ok_or("a receipt is a JSON object")?;
|
||||
15
wai/http-conformance/falsify/receipt-name-unchecked.patch
Normal file
15
wai/http-conformance/falsify/receipt-name-unchecked.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: playlist_receipt_name
|
||||
# a receipt file accepted under another name (playlist_receipt::check_playlist)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index a42d4379..5dedec6e 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -101,7 +101,7 @@ pub fn seal_playlist(
|
||||
pub fn check_playlist(playlist: &[u8], receipt: &[u8], name: Option<&str>) -> Result<ManifestReceipt, ReceiptError> {
|
||||
let text = std::str::from_utf8(receipt).map_err(|_| ReceiptError::Malformed("not UTF-8".into()))?;
|
||||
let r = ManifestReceipt::from_json(text).map_err(ReceiptError::Malformed)?;
|
||||
- if name.is_some_and(|n| n != r.receipt_file_name()) {
|
||||
+ if name.is_some_and(|n| n.is_empty()) {
|
||||
return Err(ReceiptError::NameMismatch);
|
||||
}
|
||||
if *blake3::hash(playlist).as_bytes() != r.manifest_hash || r.rendered().as_bytes() != playlist {
|
||||
24
wai/http-conformance/falsify/receipt-numbers-unbounded.patch
Normal file
24
wai/http-conformance/falsify/receipt-numbers-unbounded.patch
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# must fail: playlist_receipt_ceiling_past_2_53 playlist_receipt_ceiling_written_out playlist_receipt_window_past_2_53
|
||||
# numbers past 2^53 - 1 read (ManifestReceipt::from_json)
|
||||
diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
index a7574e49..60e021ee 100644
|
||||
--- a/wai/wai-rs/src/wai_video_manifest.rs
|
||||
+++ b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
@@ -186,7 +186,7 @@ pub const MAX_FILE_NUMBER: u64 = (1 << 53) - 1;
|
||||
fn numbers_within(v: &serde_json::Value) -> bool {
|
||||
use serde_json::Value;
|
||||
match v {
|
||||
- Value::Number(n) => n.as_u64().is_some_and(|x| x <= MAX_FILE_NUMBER),
|
||||
+ Value::Number(n) => n.as_u64().is_some(),
|
||||
Value::Array(a) => a.iter().all(numbers_within),
|
||||
Value::Object(o) => o.values().all(numbers_within),
|
||||
_ => true,
|
||||
@@ -211,7 +211,7 @@ pub fn grant_from_json(v: &serde_json::Value) -> Result<GrantRef, String> {
|
||||
let ceiling = |k: &str| -> Result<Option<u64>, String> {
|
||||
match g.get(k) {
|
||||
Some(Value::Null) => Ok(None),
|
||||
- Some(n) => n.as_u64().filter(|&x| x <= MAX_FILE_NUMBER).map(Some).ok_or(format!("grant.{k} is a number of at most 2^53 - 1, or null")),
|
||||
+ Some(n) => n.as_u64().map(Some).ok_or(format!("grant.{k} is a number of at most 2^53 - 1, or null")),
|
||||
None => Err(format!("grant.{k}")),
|
||||
}
|
||||
};
|
||||
18
wai/http-conformance/falsify/receipt-render-unsigned.patch
Normal file
18
wai/http-conformance/falsify/receipt-render-unsigned.patch
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# must fail: playlist_receipt_playlists_o9_wai_180_m3u8 live_edge
|
||||
# the render digest left out of the signed payload (wai_video_manifest::ManifestReceipt::signing_payload)
|
||||
diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
index fd25c628..fb9b1669 100644
|
||||
--- a/wai/wai-rs/src/wai_video_manifest.rs
|
||||
+++ b/wai/wai-rs/src/wai_video_manifest.rs
|
||||
@@ -273,10 +273,7 @@ impl ManifestReceipt {
|
||||
// rendering has no canonical form).
|
||||
if self.render.is_some() {
|
||||
match self.render_digest() {
|
||||
- Some(d) => {
|
||||
- o.push(1);
|
||||
- o.extend_from_slice(&d);
|
||||
- }
|
||||
+ Some(_) => {}
|
||||
None => o.push(2),
|
||||
}
|
||||
}
|
||||
20
wai/http-conformance/falsify/render-parts-after-extinf.patch
Normal file
20
wai/http-conformance/falsify/render-parts-after-extinf.patch
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# must fail: media_playlist_live_wai_r1_m3u8 playlist_receipt_live_wai_r1_m3u8
|
||||
# a segment's parts rendered after its EXTINF, not before it (hls_render::render_hls_with)
|
||||
diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs
|
||||
index 7e29b8dd..0d62afe4 100644
|
||||
--- a/wai/wai-rs/src/hls_render.rs
|
||||
+++ b/wai/wai-rs/src/hls_render.rs
|
||||
@@ -258,12 +258,12 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt
|
||||
if let Some(pdt) = &e.program_date_time {
|
||||
out.push_str(&format!("#EXT-X-PROGRAM-DATE-TIME:{pdt}\n"));
|
||||
}
|
||||
- part_lines(&mut out, &e.parts);
|
||||
if e.gap {
|
||||
out.push_str("#EXT-X-GAP\n");
|
||||
}
|
||||
// Fixed 3-dp seconds so the byte output is stable across platforms.
|
||||
out.push_str(&format!("#EXTINF:{},\n", seconds(s.duration_ms)));
|
||||
+ part_lines(&mut out, &e.parts);
|
||||
layer_lines(&mut out, &e.layers);
|
||||
out.push_str(&s.uri);
|
||||
out.push('\n');
|
||||
|
|
@ -8,7 +8,8 @@
|
|||
# bash wai/http-conformance/falsify/run.sh [work-dir [patch-name…]]
|
||||
#
|
||||
# With patch names, only those patches run (and nothing else).
|
||||
# Run from the repository root. CARGO_TARGET_DIR, if set, is shared by the builds.
|
||||
# Run from the repository root. CARGO_TARGET_DIR, if set, is shared by the builds, which
|
||||
# need the features http and video_manifest (the playlist receipts).
|
||||
set -uo pipefail
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
WORK="${1:-${RUNNER_TEMP:-/tmp}/wai-http-falsify}"
|
||||
|
|
@ -25,7 +26,7 @@ for patch in "$ROOT"/wai/http-conformance/falsify/*.patch; do
|
|||
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
|
||||
elif ! (cd "$wt/wai/wai-rs" && cargo build --release --no-default-features --features http,video_manifest --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"
|
||||
|
|
@ -44,7 +45,15 @@ done
|
|||
|
||||
[ -n "$ONLY" ] && exit $fail
|
||||
cd "$ROOT/wai/http-conformance"
|
||||
for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done tick-noop refinement-unclipped; do
|
||||
for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done tick-noop refinement-unclipped \
|
||||
pdt-ignored dseq-ignored parts-after-extinf average-floor render-digest-off name-unchecked chain-unchecked \
|
||||
ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r \
|
||||
rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked \
|
||||
xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored \
|
||||
unlabelled-escapes abr-unbounded gap-carries receipt-duplicates receipt-surrogates session-data-anywhere \
|
||||
dash-r-expanded xml-attributes-scanned dash-timelines-unbounded budget-raises \
|
||||
uri-unresolved gap-withdraws reanchor-backwards walk-exceeded-wedges walk-name-unchecked walk-verify-unchecked bindings-unbounded \
|
||||
file-numbers-unbounded; 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
|
||||
|
|
@ -53,7 +62,7 @@ for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done t
|
|||
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
|
||||
(cd "$ROOT/wai/wai-rs" && cargo build --release --no-default-features --features http,video_manifest --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
|
||||
|
|
|
|||
15
wai/http-conformance/falsify/session-data-more-lines.patch
Normal file
15
wai/http-conformance/falsify/session-data-more-lines.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: unchanged_two_lines unchanged_blank_line_too
|
||||
# more than the one session-data line inserted (hls::classical_unchanged)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 107ae8a1..6a704c25 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -847,7 +847,7 @@ pub fn classical_unchanged(original: &str, published: &str) -> Result<(), Playli
|
||||
let (head, tail) = original.split_at(at);
|
||||
let inserted = published.strip_prefix(head).and_then(|r| r.strip_suffix(tail)).ok_or_else(changed)?;
|
||||
let bare = inserted.strip_suffix('\n').map(|x| x.strip_suffix('\r').unwrap_or(x)).ok_or_else(changed)?;
|
||||
- if bare.contains('\n') || !is_session_data_line(bare) {
|
||||
+ if !is_session_data_line(bare.lines().next().unwrap_or("")) {
|
||||
return Err(changed());
|
||||
}
|
||||
Ok(())
|
||||
15
wai/http-conformance/falsify/session-data-position.patch
Normal file
15
wai/http-conformance/falsify/session-data-position.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: unchanged_players unchanged_at_the_end
|
||||
# the session-data line's place taken past every line (hls::session_data_position)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 107ae8a1..8f5f5bd9 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -812,7 +812,7 @@ pub fn session_data_position(original: &str) -> Option<usize> {
|
||||
let mut at = 0;
|
||||
for (i, l) in original.split_inclusive('\n').enumerate() {
|
||||
let bare = l.strip_suffix('\n').map(|x| x.strip_suffix('\r').unwrap_or(x)).unwrap_or(l);
|
||||
- if i > 0 && !(bare.starts_with("#EXT-X-VERSION:") || bare == "#EXT-X-INDEPENDENT-SEGMENTS") {
|
||||
+ if false && i > 0 && !(bare.starts_with("#EXT-X-VERSION:") || bare == "#EXT-X-INDEPENDENT-SEGMENTS") {
|
||||
break;
|
||||
}
|
||||
if !l.ends_with('\n') {
|
||||
25
wai/http-conformance/falsify/sig-key-lenient.patch
Normal file
25
wai/http-conformance/falsify/sig-key-lenient.patch
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# must fail: playlist_receipt_sig_key_identity playlist_receipt_sig_key_identity_long playlist_receipt_sig_r_small_order
|
||||
# keys read as the library reads them, and the equation checked leniently (wai::sig)
|
||||
diff --git a/wai/wai-rs/src/sig.rs b/wai/wai-rs/src/sig.rs
|
||||
index 46135933..01f85c39 100644
|
||||
--- a/wai/wai-rs/src/sig.rs
|
||||
+++ b/wai/wai-rs/src/sig.rs
|
||||
@@ -62,9 +62,6 @@ fn below_l(s: &[u8; 32]) -> bool {
|
||||
/// large order.
|
||||
pub fn strict_key(bytes: &[u8; 32]) -> Result<VerifyingKey, SigError> {
|
||||
let key = VerifyingKey::from_bytes(bytes).map_err(|_| SigError::BadKey)?;
|
||||
- if key.to_edwards().compress().to_bytes() != *bytes || key.is_weak() {
|
||||
- return Err(SigError::BadKey);
|
||||
- }
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
@@ -75,7 +72,7 @@ pub fn verify_strict(pubkey: &[u8; 32], msg: &[u8], sig: &[u8; 64]) -> Result<()
|
||||
if !below_l(s) {
|
||||
return Err(SigError::BadSignature);
|
||||
}
|
||||
- key.verify_strict(msg, &Signature::from_bytes(sig)).map_err(|_| SigError::BadSignature)
|
||||
+ ed25519_dalek::Verifier::verify(&key, msg, &Signature::from_bytes(sig)).map_err(|_| SigError::BadSignature)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
15
wai/http-conformance/falsify/sig-verify-lenient.patch
Normal file
15
wai/http-conformance/falsify/sig-verify-lenient.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: playlist_receipt_sig_r_small_order
|
||||
# R of small order accepted (wai::sig::verify_strict checks the equation leniently)
|
||||
diff --git a/wai/wai-rs/src/sig.rs b/wai/wai-rs/src/sig.rs
|
||||
index 46135933..ebe58160 100644
|
||||
--- a/wai/wai-rs/src/sig.rs
|
||||
+++ b/wai/wai-rs/src/sig.rs
|
||||
@@ -75,7 +75,7 @@ pub fn verify_strict(pubkey: &[u8; 32], msg: &[u8], sig: &[u8; 64]) -> Result<()
|
||||
if !below_l(s) {
|
||||
return Err(SigError::BadSignature);
|
||||
}
|
||||
- key.verify_strict(msg, &Signature::from_bytes(sig)).map_err(|_| SigError::BadSignature)
|
||||
+ ed25519_dalek::Verifier::verify(&key, msg, &Signature::from_bytes(sig)).map_err(|_| SigError::BadSignature)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
14
wai/http-conformance/falsify/skip-read.patch
Normal file
14
wai/http-conformance/falsify/skip-read.patch
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# must fail: playlist_parse_skip
|
||||
# a delta update read as a whole playlist (hls::parse_media_playlist)
|
||||
diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs
|
||||
index 2623644b..87e34cce 100644
|
||||
--- a/wai/wai-rs/src/http_binding/hls.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/hls.rs
|
||||
@@ -322,7 +322,6 @@ pub fn parse_media_playlist(text: &str) -> Result<MediaPlaylist, PlaylistError>
|
||||
}
|
||||
}
|
||||
"#EXT-X-ENDLIST" => p.end_list = true,
|
||||
- "#EXT-X-SKIP" => return Err(malformed("a delta update (EXT-X-SKIP) is not compared")),
|
||||
"#EXT-X-STREAM-INF" | "#EXT-X-I-FRAME-STREAM-INF" | "#EXT-X-MEDIA" => {
|
||||
return Err(malformed(format!("{tag}: a multivariant playlist, not a media playlist")))
|
||||
}
|
||||
24
wai/http-conformance/falsify/uri-dots-kept.patch
Normal file
24
wai/http-conformance/falsify/uri-dots-kept.patch
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# must fail: uri_resolve_rfc_02 uri_resolve_rfc_17 live_relisted_dot_alias
|
||||
# dot segments kept in a resolved URL (http_binding::uri::resolve)
|
||||
diff --git a/wai/wai-rs/src/http_binding/uri.rs b/wai/wai-rs/src/http_binding/uri.rs
|
||||
index 0f23cf96..140ead1a 100644
|
||||
--- a/wai/wai-rs/src/http_binding/uri.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/uri.rs
|
||||
@@ -127,7 +127,7 @@ pub fn resolve(base: &str, reference: &str) -> String {
|
||||
Parts { scheme: b.scheme, authority: b.authority, path: remove_dot_segments(&r.path), query: r.query }
|
||||
} else {
|
||||
let merged = merge(&b, &r.path);
|
||||
- Parts { scheme: b.scheme, authority: b.authority, path: remove_dot_segments(&merged), query: r.query }
|
||||
+ Parts { scheme: b.scheme, authority: b.authority, path: merged, query: r.query }
|
||||
};
|
||||
let mut out = String::new();
|
||||
if let Some(s) = &t.scheme {
|
||||
@@ -146,7 +146,7 @@ pub fn resolve(base: &str, reference: &str) -> String {
|
||||
None => out.push_str(&a.to_ascii_lowercase()),
|
||||
}
|
||||
}
|
||||
- out.push_str(&remove_dot_segments(&normalize_percent(&t.path)));
|
||||
+ out.push_str(&normalize_percent(&t.path));
|
||||
if let Some(q) = &t.query {
|
||||
out.push('?');
|
||||
out.push_str(&normalize_percent(q));
|
||||
15
wai/http-conformance/falsify/uri-percent-kept.patch
Normal file
15
wai/http-conformance/falsify/uri-percent-kept.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: uri_resolve_alias_03
|
||||
# percent-encoded unreserved characters left encoded (http_binding::uri::resolve)
|
||||
diff --git a/wai/wai-rs/src/http_binding/uri.rs b/wai/wai-rs/src/http_binding/uri.rs
|
||||
index 0f23cf96..5f74c61a 100644
|
||||
--- a/wai/wai-rs/src/http_binding/uri.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/uri.rs
|
||||
@@ -97,7 +97,7 @@ fn normalize_percent(s: &str) -> String {
|
||||
while i < b.len() {
|
||||
if b[i] == b'%' && i + 2 < b.len() && b[i + 1].is_ascii_hexdigit() && b[i + 2].is_ascii_hexdigit() {
|
||||
let v = u8::from_str_radix(&s[i + 1..i + 3], 16).expect("two hexadecimal digits");
|
||||
- if v.is_ascii_alphanumeric() || b"-._~".contains(&v) {
|
||||
+ if false {
|
||||
out.push(v as char);
|
||||
} else {
|
||||
out.push('%');
|
||||
15
wai/http-conformance/falsify/walk-exceeded-wedges.patch
Normal file
15
wai/http-conformance/falsify/walk-exceeded-wedges.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: live_edge_reanchored live_edge_reanchored_skipping
|
||||
# past the bound, every revision refused, so the client is wedged (LiveChain::advance)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index 6fb6141d..cd1d8dea 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -236,7 +236,7 @@ impl LiveChain {
|
||||
// §8.6: past the bound, a revision ahead of the head (more segments
|
||||
// complete) re-anchors the chain, its bindings kept; one that is not
|
||||
// ahead is refused, so a stale or older revision never rolls it back.
|
||||
- if last_segment(cur) <= last_segment(head) {
|
||||
+ if true || last_segment(cur) <= last_segment(head) {
|
||||
return Err(ReceiptError::WalkExceeded);
|
||||
}
|
||||
let mut b = self.bindings.clone();
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: live_edge_parent_substituted
|
||||
# a parent fetched by its link not held to it (LiveChain::advance)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index 6fb6141d..7d582b26 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -248,7 +248,7 @@ impl LiveChain {
|
||||
let bytes = parent(&l).ok_or(ReceiptError::ParentMissing)?;
|
||||
let text = std::str::from_utf8(&bytes).map_err(|_| ReceiptError::Malformed("not UTF-8".into()))?;
|
||||
let r = ManifestReceipt::from_json(text).map_err(ReceiptError::Malformed)?;
|
||||
- if r.receipt_hash() != l {
|
||||
+ if false && r.receipt_hash() != l {
|
||||
return Err(ReceiptError::NameMismatch);
|
||||
}
|
||||
if !r.verify() {
|
||||
15
wai/http-conformance/falsify/walk-parent-unverified.patch
Normal file
15
wai/http-conformance/falsify/walk-parent-unverified.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: live_edge_parent_invalid
|
||||
# a parent the walk fetches not verified (LiveChain::advance)
|
||||
diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
index 6fb6141d..08eec4e6 100644
|
||||
--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs
|
||||
@@ -251,7 +251,7 @@ impl LiveChain {
|
||||
if r.receipt_hash() != l {
|
||||
return Err(ReceiptError::NameMismatch);
|
||||
}
|
||||
- if !r.verify() {
|
||||
+ if false && !r.verify() {
|
||||
return Err(ReceiptError::Invalid);
|
||||
}
|
||||
if r.signer_pubkey != cur.signer_pubkey {
|
||||
15
wai/http-conformance/falsify/xml-attribute-twice.patch
Normal file
15
wai/http-conformance/falsify/xml-attribute-twice.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: dash_adjacency_attribute_twice
|
||||
# an attribute twice read (dash_read::read_xml)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 71e36cde..ea32b008 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -204,7 +204,7 @@ impl<'a> Reader<'a> {
|
||||
}
|
||||
self.space();
|
||||
let v = self.attr_value()?;
|
||||
- if !names.insert(k.clone()) {
|
||||
+ if !names.insert(k.clone()) && false {
|
||||
return Err(malformed(format!("the attribute {k} twice")));
|
||||
}
|
||||
attrs.push((k, v));
|
||||
16
wai/http-conformance/falsify/xml-cdata-read.patch
Normal file
16
wai/http-conformance/falsify/xml-cdata-read.patch
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# must fail: dash_adjacency_cdata
|
||||
# a CDATA section read (dash_read::read_xml)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 79d55df6..c268e915 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -248,6 +248,9 @@ pub fn read_xml(text: &str) -> Result<Element, PlaylistError> {
|
||||
Some(parent) => parent.children.push(e),
|
||||
None => done = Some(e),
|
||||
}
|
||||
+ } else if r.rest().starts_with("<![CDATA[") {
|
||||
+ let end = r.rest().find("]]>").unwrap_or(0);
|
||||
+ r.at += end + 3;
|
||||
} else if r.rest().starts_with("<!") || r.rest().starts_with("<?") {
|
||||
return Err(malformed("a declaration, CDATA section or processing instruction is outside the subset"));
|
||||
} else {
|
||||
15
wai/http-conformance/falsify/xml-char-ref-unchecked.patch
Normal file
15
wai/http-conformance/falsify/xml-char-ref-unchecked.patch
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# must fail: dash_adjacency_char_ref_not_a_char
|
||||
# a character reference to a non-character read (dash_read::read_xml)
|
||||
diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
index 79d55df6..fdd384ae 100644
|
||||
--- a/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
+++ b/wai/wai-rs/src/http_binding/dash_read.rs
|
||||
@@ -120,7 +120,7 @@ impl<'a> Reader<'a> {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
- n.and_then(char::from_u32).filter(|&c| xml_char(c)).ok_or_else(|| malformed("a reference to an entity or character outside the subset"))?
|
||||
+ n.and_then(char::from_u32).ok_or_else(|| malformed("a reference to an entity or character outside the subset"))?
|
||||
}
|
||||
};
|
||||
self.at += end + 1;
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null}],"merkle_root":"00e2bc6cdc748c6ab760c8e867603fd4d9a72d3a58f985bd13810a8d77aedcd3","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"8186f3adb28113d8ad08c923d24df463eb898d7a95b04902d23ca208fc17fe677249dc30f280faef8ec5aeac81abdb6b6fcd577075d9e9346f445c3b8c13fa03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]}],"layers":[],"preload_hint":"720/1201.3.wai"}]}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[],"merkle_root":"0000000000000000000000000000000000000000000000000000000000000000","work_compose_ops":0,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"5d213b6e5ce07b2c7391f0d73501c7ed8e91d6b81ab418e6b1eff4169d127f6f943306b5e1dc44844f69505c34156c040686a6e4151b37ddda98a617f2ac7609","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]}],"layers":[],"preload_hint":"720/1200.5.wai"}]}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
{"kind":"wai.video.manifest","manifest_hash":"6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[],"merkle_root":"0000000000000000000000000000000000000000000000000000000000000000","work_compose_ops":0,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"5d213b6e5ce07b2c7391f0d73501c7ed8e91d6b81ab418e6b1eff4169d127f6f943306b5e1dc44844f69505c34156c040686a6e4151b37ddda98a617f2ac7609","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]}],"layers":[],"preload_hint":"720/1200.5.wai"}]}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue