wai: holdings — which objects of which groups a node holds now, for its neighbours #595

Closed
dcharlot wants to merge 5 commits from wai/holdings into main
493 changed files with 44013 additions and 24 deletions

View file

@ -257,6 +257,11 @@ on:
# tests/bearer_receipts.rs requires this extension's §10 to list the codes the reference
# reports, and bearer_verify.py reads that table.
- 'wai/extensions/bearer-receipts.md'
# tests/carousel.rs requires §9 to list the reference's codes and Appendix A to be its table
# of K', and carousel_verify.py reads both.
- 'wai/extensions/carousel.md'
# tests/holdings.rs requires §7 to list the reference's codes, and holdings_verify.py reads it.
- 'wai/extensions/holdings.md'
# tests/exploration.rs requires this extension's §13 to list the codes the reference
# reports and its §3.2 table to name the reference's descriptors, exploration_verify.py
# reads §13, and the registry's wire identifiers test reads its sections.
@ -316,6 +321,9 @@ on:
# format-probes' tables; and the registry's wire identifiers test reads projection.md,
# loc-carriage.md and delivery-telemetry.md (whose §6.6 cmsd_read_verify.py reads).
- 'wai/extensions/format-probes.md'
# tests/identify.rs requires media-identify §6 and §7 to list the reference's signatures,
# and identify_verify.py reads those tables and the capabilities of its §5.1.
- 'wai/extensions/media-identify.md'
- 'wai/extensions/projection.md'
- 'wai/extensions/loc-carriage.md'
- 'wai/extensions/delivery-telemetry.md'
@ -589,7 +597,7 @@ jobs:
working-directory: wai/wai-rs
run: |
set -euo pipefail
for f in neural_int video_int audio_int splat_int staged stage_measure telemetry http format_probe; do
for f in neural_int video_int audio_int splat_int staged stage_measure telemetry http format_probe identify; do
cargo check --lib --no-default-features --features "$f"
done
# The decode bench prints its machine line from bench_env; no other step builds it.
@ -606,6 +614,8 @@ jobs:
cargo build --lib --target wasm32-unknown-unknown --no-default-features --features telemetry
# And the header probes: a browser reads a payload's parameters the same way.
cargo build --lib --target wasm32-unknown-unknown --no-default-features --features format_probe
# And identification: a browser says what a dropped file is the same way.
cargo build --lib --target wasm32-unknown-unknown --no-default-features --features identify
# carriage: every registered capability round-trips byte-exact. cli_wrap_intent and
# cli_registry run the `wai` binary, which builds without the codecs feature: the
# intent rule of `wai wrap`, its refusal of candidate capabilities (SPEC §5), its media
@ -1182,7 +1192,7 @@ jobs:
# No debuginfo, for the reason given at cargo-classes: this job ran out of the VM's disk
# mid-link beside two others (#550).
CARGO_PROFILE_DEV_DEBUG: '0'
WAI_FEATURES: key_discovery,http,video_manifest,stage_measure,live_events,live_room,capture_take,live_compose,live_record,timed_text,access_services,live_metering,energy_steering,live_moderation,dred_exact,live_ads,live_provenance,synthesis,synthesis_mark,neural_int,exact_upscale,robustness,ai_usage,contribution,settlement,browser_sink,receipt_claims,takedown,influence,direction,co_performance,age_assurance,bearer_receipts,exploration,co_watch,telemetry_aggregate,exact_tonemap,splat_exact,live_watermark,derive_edge,sink_energy,key_release,stitched_presentation,prior_wire,scene_map,avatar_face,receipt_ledger,stream_audit,fleet,native_sink,platform_encode,remote_desktop,stream_termination,live_world,format_probe,live_messaging,conform_result,revenue_share,opportunity_report,channel_lineup,route_record
WAI_FEATURES: key_discovery,http,video_manifest,stage_measure,live_events,live_room,capture_take,live_compose,live_record,timed_text,access_services,live_metering,energy_steering,live_moderation,dred_exact,live_ads,live_provenance,synthesis,synthesis_mark,neural_int,exact_upscale,robustness,ai_usage,contribution,settlement,browser_sink,receipt_claims,takedown,influence,direction,co_performance,age_assurance,bearer_receipts,exploration,co_watch,telemetry_aggregate,exact_tonemap,splat_exact,live_watermark,derive_edge,sink_energy,key_release,stitched_presentation,prior_wire,scene_map,avatar_face,receipt_ledger,stream_audit,fleet,native_sink,platform_encode,remote_desktop,stream_termination,live_world,format_probe,identify,live_messaging,conform_result,revenue_share,opportunity_report,channel_lineup,route_record,carousel,holdings
WAI_PQ_FEATURES: synthesis,pq_signatures
WAI_RELEASE_FEATURES: dred_exact,synthesis_mark,robustness,avatar_face
steps:
@ -1252,12 +1262,12 @@ jobs:
--bin wai_http_vectors --bin wai_stage_measure --bin wai_browser_sink_vectors \
--bin wai_claim_vectors --bin wai_jwp_vectors --bin wai_signer_vectors \
--bin wai_sink_energy_vectors --bin wai_stitched_presentation \
--bin wai_stream_termination_vectors --bin wai_format_probe --bin wai_conform_result_vectors \
--bin wai_stream_termination_vectors --bin wai_format_probe --bin wai_identify_vectors --bin wai_conform_result_vectors \
--bin wai_channel_lineup_vectors
for b in wai_http_vectors wai_stage_measure wai_browser_sink_vectors \
wai_claim_vectors wai_jwp_vectors wai_signer_vectors \
wai_sink_energy_vectors wai_stitched_presentation \
wai_stream_termination_vectors wai_format_probe wai_conform_result_vectors \
wai_stream_termination_vectors wai_format_probe wai_identify_vectors wai_conform_result_vectors \
wai_channel_lineup_vectors; do
cp "${CARGO_TARGET_DIR:-target}/debug/$b" "$RUNNER_TEMP/wai-bins/union/"
done
@ -1273,12 +1283,12 @@ jobs:
--test live_moderation --test live_ads --test ad_presentation --test live_provenance \
--test synthesis --test exact_upscale --test cli_derive --test ai_usage --test contribution --test settlement \
--test browser_sink --test takedown --test influence --test direction --test co_performance \
--test age_assurance --test bearer_receipts --test exploration --test co_watch \
--test age_assurance --test bearer_receipts --test carousel --test holdings --test exploration --test co_watch \
--test telemetry_aggregate --test exact_tonemap --test splat_exact --test live_watermark \
--test derive_edge --test det_cost_bin --test sink_energy --test key_release \
--test stitched_presentation --test prior_wire --test scene_map --test receipt_ledger \
--test stream_audit --test fleet --test native_sink --test platform_encode --test remote_desktop \
--test stream_termination --test live_world --test format_probe --test live_messaging \
--test stream_termination --test live_world --test format_probe --test identify --test live_messaging \
--test conform_result --test revenue_share --test opportunity_report --test channel_lineup --test route_record
cargo test --release --no-run --no-default-features --features "$WAI_RELEASE_FEATURES" \
--test dred_exact --test dred_exact_measure --test synthesis_mark --test robustness \
@ -2337,6 +2347,47 @@ jobs:
(cd wai-rs && cargo test --no-default-features --features "$WAI_FEATURES" --test bearer_receipts)
python3 -I live-conformance/bearer_verify.py live-conformance/bearers
python3 -I claim-conformance/claim_verify.py bearer live-conformance/bearers/vectors.json
# wai.bearer.carousel (extensions/carousel.md): the reference module's unit tests (each code's
# bounds and division into source blocks, K', the passes of advance and repeat, plans, and the
# corpus reaching every stated outcome and failing a changed one) and tests/carousel.rs
# (vectors.json regenerates byte for byte from its fixed seeds, wai-rs gives every case its
# stated outcome, §9 lists exactly the reference's codes, Appendix A is its table of K',
# jwp-receipts §3.1 lists the class codes at their steps, K' and Partition[] agree with an
# independent RFC 6330 implementation for every block size, and each RaptorQ object a case
# holds available decodes with that implementation from what a receiver within the case's
# loss bound holds); then the corpus's second implementation, carousel_verify.py, written
# from the extension's text and sharing no code with wai-rs, which verifies groups and
# objects with jwp_verify.py and claims through steps 1-5 with claim_verify.py, writes the
# context checks, the availability and the plans itself, reads §9 and Appendix A, requires
# every mutated copy of the corpus to fail, and drops each of its own checks in turn; run
# isolated (-I). Last, claim-conformance's own verifier reads every claim of the corpus
# through jwp-receipts §3.1 steps 1 to 4 and requires the stated outcome.
- name: carousel schedule · wai-rs and carousel_verify.py
working-directory: wai
shell: bash
run: |
set -euo pipefail
(cd wai-rs && cargo test --no-default-features --features "$WAI_FEATURES" --lib -- carousel:: claim_receipt::schedule::)
(cd wai-rs && cargo test --no-default-features --features "$WAI_FEATURES" --test carousel)
python3 -I live-conformance/carousel_verify.py live-conformance/carousel
python3 -I claim-conformance/claim_verify.py carousel live-conformance/carousel/vectors.json
# wai.delivery.holdings (extensions/holdings.md): the reference module's unit tests (an entry's
# one form, a record read back and every rule refusing, queries and a plan, and the corpus
# reaching every stated outcome and failing a changed one) and tests/holdings.rs (vectors.json
# regenerates byte for byte from its fixed seeds, wai-rs gives every case its stated outcome,
# and §7 lists exactly the reference's codes); then the corpus's second implementation,
# holdings_verify.py, written from the extension's text and sharing no code with wai-rs, which
# verifies group receipts with jwp_verify.py and signatures with the shared _ed25519.py, writes
# the record, its verdict, queries and plans itself, reads §7, requires every mutated copy of
# the corpus to fail and drops each of its own checks in turn; run isolated (-I).
- name: holdings · wai-rs and holdings_verify.py
working-directory: wai
shell: bash
run: |
set -euo pipefail
(cd wai-rs && cargo test --no-default-features --features "$WAI_FEATURES" --lib -- holdings::)
(cd wai-rs && cargo test --no-default-features --features "$WAI_FEATURES" --test holdings)
python3 -I live-conformance/holdings_verify.py live-conformance/holdings
# wai.media.exploration (extensions/exploration.md): the reference module's unit tests (the
# descriptors of flat, single-row and checkerboard pictures, grid bins at both ends, the
# sample distance's zero, the taste score, derived prompts, and a schedule that replays and
@ -2729,6 +2780,21 @@ jobs:
bash format-probe-conformance/fetch.sh "$fetched"
(cd wai-rs && "$RUNNER_TEMP/wai-bins/union/wai_format_probe" verify ../format-probe-conformance --fetched "$fetched")
python3 -I format-probe-conformance/format_probe_verify.py format-probe-conformance --fetched "$fetched"
# Media identification (extensions/media-identify.md): wai-rs's unit tests (hostile bytes
# through every walk, what a file reads as its bytes) and tests/identify.rs (the corpus
# regenerates byte for byte with every result; the extension's tables; `wai identify`); then
# identify_verify.py, written from the text and sharing no code with wai-rs but
# format_probe_verify.py's probes, which gives every file its whole result, holds ext/ to
# what an independent prober read of it, and requires every mutated copy to fail.
- name: media identification · wai-rs and identify_verify.py
working-directory: wai
shell: bash
run: |
set -euo pipefail
(cd wai-rs && cargo test --no-default-features --features "$WAI_FEATURES" --lib identify::)
(cd wai-rs && cargo test --no-default-features --features "$WAI_FEATURES" --test identify)
(cd wai-rs && "$RUNNER_TEMP/wai-bins/union/wai_identify_vectors" verify ../identify-conformance)
python3 -I identify-conformance/identify_verify.py identify-conformance
# CMSD reading (extensions/delivery-telemetry.md §6.6): the second implementation, written
# from that text and RFC 9651 with its own field parser, reads every field of
# telemetry-conformance/cmsd-read/ and every field expected/cmsd.json states, and requires
@ -2782,7 +2848,7 @@ jobs:
capture_take live_compose live_record timed_text access_services live_metering energy_steering \
live_moderation dred_exact live_ads live_provenance synthesis synthesis_mark neural_int \
exact_upscale robustness ai_usage contribution settlement browser_sink browser_sink,receipt_claims \
takedown influence direction co_performance age_assurance bearer_receipts exploration co_watch \
takedown influence direction co_performance age_assurance bearer_receipts carousel holdings exploration co_watch \
telemetry_aggregate exact_tonemap splat_exact live_watermark derive_edge pq_signatures \
synthesis,pq_signatures sink_energy key_release stitched_presentation prior_wire \
prior_wire,neural_int scene_map avatar_face receipt_ledger stream_audit fleet native_sink \

View file

@ -6,6 +6,80 @@ wire format may change and such changes are recorded here.
## Unreleased
### Who holds which objects near a receiver
- **Holdings** ([extensions/holdings.md](extensions/holdings.md)), the record `wai.delivery.holdings`:
a node's signed record (`WHD1`) of which objects of which groups it holds now, for its neighbours.
Per group, its link hash, its leaf count and either `whole` or a bitmap, in one canonical form,
with the instant it states them at and the one it expires at, signed under SPEC §7 *Signatures*.
Exact, so a neighbour is never sent after an object its holder lacks.
- **What a verifier derives**: a record held to an instant and to the group receipts it has (one
misstated leaf count refuses it whole), a holder's latest record standing and two of one instant
that differ set aside, whether a record holds an object, and an asker's plan: each wanted
object's holders in the asker's order, and the objects no neighbour holds, which come from the
origin. An object a neighbour serves is verified under its receipts like any other.
- **The corpus** [`live-conformance/holdings/`](live-conformance/holdings/): 21 records (a control
for each rule and for the signature, a small-order key among them), 11 verdicts, 6 queries and
a plan over three neighbours. `holdings_verify.py` is a second implementation.
### When each object passes on a bearer with no return path, and when a receiver holds it
- **The carousel schedule** ([extensions/carousel.md](extensions/carousel.md)), the record
`wai.bearer.carousel`: a publisher's signed plan, a jwp-receipts claim of class `schedule`
(§2.4.16) beside the group receipt and the instance's transmission claim, naming for each object
its forward error correction, RaptorQ (RFC 6330 with its erratum 5548) or Compact No-Code
(RFC 5445), with that code's object transmission information, the repair symbols each pass
adds, whether a later pass repeats its symbols or sends new ones, and the times and count of its
passes. Each code's parameters are held to its bounds and the RFCs' division of an object into
source blocks; an entry to its group, its object's content hash and, where the bytes are held,
their length.
- **When an object is held.** For a loss bound in parts per million and an overhead of 0, 1 or 2,
a verifier derives the pass after which a receiver whose losses stay within the bound holds
each object, whatever symbols it loses: a RaptorQ block at K′ + h distinct symbols, the count
RFC 6330 §5.8 states its decoding-failure bound at, and a symbol a repeating pass missed counted
as missed again. It reports the octets each schedule sends and the bound on decoding failure,
and checks a statement that an object or a group is held by an instant. Schedules that
contradict each other, or whose instance is undeclared or two-way, are set aside.
- **The passes a receiver listens to**, from what it holds of each block and when it tunes in: the
passes that add symbols it is certain to keep, when it holds the object, and how long its radio
must be on.
- **The corpus** [`live-conformance/carousel/`](live-conformance/carousel/): 33 verdicts, 50
claims (every bound of the codes met and passed) and 16 plans. `carousel_verify.py` is a second
implementation; `claim_verify.py carousel` reads every claim through steps 1–4; and
`tests/carousel.rs` checks the table of K′ and Partition[] against an independent RFC 6330
implementation for every block size, and decodes each RaptorQ object a case holds available
from what a receiver within the bound holds.
- **Breaking:** `api:rust:wai::claim_receipt::ClaimClass::Schedule` and
`api:rust:wai::claim_receipt::ClaimBody::Schedule`: the class jwp-receipts §2.4.16 registers, which an
exhaustive match over either needs.
- **Breaking:** the eleven reasons of the class, which an exhaustive match over `Reason` needs:
`api:rust:wai::claim_receipt::Reason::ScheduleNotScheduled`,
`api:rust:wai::claim_receipt::Reason::ScheduleObjectsMalformed`,
`api:rust:wai::claim_receipt::Reason::ScheduleOtiInvalid`,
`api:rust:wai::claim_receipt::Reason::ScheduleSymbolsInvalid`,
`api:rust:wai::claim_receipt::Reason::SchedulePassesInvalid`,
`api:rust:wai::claim_receipt::Reason::ScheduleSubjectMismatch`,
`api:rust:wai::claim_receipt::Reason::ScheduleSignerMismatch`,
`api:rust:wai::claim_receipt::Reason::ScheduleLeafOutOfRange`,
`api:rust:wai::claim_receipt::Reason::ScheduleBeforeGroup`,
`api:rust:wai::claim_receipt::Reason::ScheduleContentMismatch` and
`api:rust:wai::claim_receipt::Reason::ScheduleLengthMismatch`.
### What any file is, from its bytes alone
- **`wai identify <file>`** (operation `media.inspect`, [extensions/media-identify.md](extensions/media-identify.md)): the
formats a file's bytes fit, each with the offsets that say so and how far its structure was followed; a container's
tracks with their codec, size and protection as it states them; the registered capabilities they map to; signatures
found inside it; and, where no format is recognised, measures that still hold: how its values are spread and the lags at
which it repeats. `verdict` is `identified`, `ambiguous`, `signature_only` or `unknown`, and there is no confidence
figure: a candidate's strength says what was checked. Nothing is decoded and no protected track is opened. It reads ISO
media, Matroska, transport streams, Ogg, FLAC, RIFF, MPEG audio, ADTS, PNG, JPEG, GIF, IVF, YUV4MPEG2, FLV, AV1 OBU and
Annex B streams and HLS and DASH text, and knows many more by signature. Any size of file costs a bounded amount, and
every walk has been given hostile bytes. It is in the agent door as `media_inspect`, its results marked
`untrusted_content`, because a file's author chooses the strings in it.
- The corpus [`identify-conformance/`](identify-conformance/) holds files from real muxers and encoders, with what an
independent prober reads of them, and negative controls; `identify_verify.py` is a second implementation.
### A probe no longer panics on two adjacent start codes
- `wai.video.avs3` and `wai.audio.avs3`: a payload with a start code prefix directly after another (`00 00 01 00 00 01`)

View file

@ -133,7 +133,7 @@ operation of the toolchain, named by it with `_` for the dot, and the tools come
| Toolset | Tools |
|---|---|
| `core` (the default) | `envelope_inspect`, `registry_list`, `registry_show`, `receipt_verify`, `envelope_wrap`, `conformance_list`, `conformance_run`, `docs_search` |
| `core` (the default) | `envelope_inspect`, `media_inspect`, `registry_list`, `registry_show`, `receipt_verify`, `envelope_wrap`, `conformance_list`, `conformance_run`, `docs_search` |
| `dev` | `dev_start`, `dev_stop`, `room_grant`, `room_join`, `room_leave`, `session_show` |
| `app` (a build with the feature `app`) | `app_guide`, `app_check`, `app_stream`, `app_export`: Create's, below |

View file

@ -182,10 +182,12 @@ and a role are fixed once registered.
| `wai.audio.score` | payload | registered | audio | standalone | royalty-free | standard-defined | — | §5 Interactive, temporal & computed content |
| `wai.avatar.face` | payload | registered | interactive | standalone | royalty-free | generative-exact | — | §5 Interactive, temporal & computed content |
| `wai.avatar.pose` | payload | registered | interactive | standalone | royalty-free | standard-defined | — | §5 Interactive, temporal & computed content |
| `wai.bearer.carousel` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.bearer.receipts` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.capture.take` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.delivery.audit` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.delivery.fleet` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.delivery.holdings` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.delivery.ledger` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.delivery.opportunity` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.delivery.revenue_share` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |

View file

@ -1767,6 +1767,33 @@ over the bearer is held and anchors nothing (`anchor_on_bearer`). The extension
carriage onto FLUTE, DVB multicast ABR, DVB-I, ATSC 3.0, 3GPP MBMS and 5G broadcast, MoQ over
multicast and the applications of hybrid broadcast receivers:
[`extensions/bearer-receipts.md`](extensions/bearer-receipts.md).
And for **when** each object passes on a bearer with no return path — `wai.bearer.carousel`: a
publisher's signed schedule, a jwp-receipts claim of class `schedule` beside the group receipt
and the instance's transmission claim, that names for each object its forward error correction
(RaptorQ, RFC 6330, or Compact No-Code, RFC 5445) with that code's object transmission
information, the repair symbols each pass adds, whether a later pass repeats its symbols or sends
new ones, and the times and count of its passes. A verifier holds the parameters to each code's
bounds and the entries to the group, its objects' content hashes and, where it holds them, their
lengths; sets aside schedules that contradict each other, or whose instance is undeclared or
two-way; and derives, for a loss bound stated in parts per million, the pass after which a
receiver whose losses stay within it holds each object, whatever symbols it loses: a RaptorQ
block at K′ + h distinct symbols, the count RFC 6330 states its decoding-failure bound at, and
a symbol a repeating pass missed counted as missed again. That is the deadline a one-way bearer
can promise, checked with no receiver; a statement that an object or a group is held by an
instant holds or is refused (`availability_after_deadline`, `availability_not_within_schedule`,
`object_not_scheduled`). A receiver derives the passes it must listen to, and when its radio may
be off: [`extensions/carousel.md`](extensions/carousel.md).
And for **who holds what** near a receiver — `wai.delivery.holdings`: a node's signed record
(`WHD1`) of which objects of which groups it holds now, for its neighbours: per group, its link
hash, its leaf count and either `whole` or a bitmap, in one canonical form, signed under SPEC §7
*Signatures*, with an instant it states them at and one it expires at. Exact, so a neighbour is
never sent after an object its holder lacks, and a bit an object where objects are held as groups
are delivered. A verifier holds a record to its instant and to the group receipts it has
(`holdings_leaf_count_mismatch` refuses it whole), keeps a holder's latest record and sets aside
two of one instant that differ, answers whether a record holds an object, and derives which wanted
objects each neighbour holds, in the asker's order, and which must come from the origin. An object
a neighbour serves is verified under its receipts like any other; a record is never evidence of
bytes: [`extensions/holdings.md`](extensions/holdings.md).
And for **exploration** with the exact generator, and **taste** as a declared, owned objective —
`wai.media.exploration`: six integer descriptors over a picture's `RGB8` samples (mean luma,
chroma, detail, palette diversity and the luma centroid's two coordinates) place it without
@ -2203,7 +2230,9 @@ above).
`wai.video.avs3` and `wai.audio.avs3` (and for `wai.image.jxs`, `wai.video.jxs`,
`wai.image.ieee1857_11` and `wai.meta.projection`): it identifies a payload as the format its
capability names and reads the parameters its headers state, or refuses it with a code. A
probe decodes no sample, so it does not change any class above.
probe decodes no sample, so it does not change any class above. Where the capability is not known,
[media-identify](extensions/media-identify.md) says what a file is from its bytes alone, with the
offsets that say so, and runs these probes among its readings.
**One Opus packet.** A `wai.audio.opus.packet` payload is one Opus packet (RFC
6716 §3), its TOC byte first, with no container: the per-packet form streaming

View file

@ -1000,6 +1000,74 @@
"sdk": "wai.media.generate"
}
},
{
"id": "media.inspect",
"summary": "What a file is, from its bytes alone: the formats they fit with the bytes that say so, its tracks, its protection, and measures where no format is recognised",
"cli": "wai identify",
"aliases": [],
"usage": [
"wai identify <file> [--json] any file, not only an envelope (extensions/media-identify.md); unknown stays unknown"
],
"placement": "local",
"effect": "reads",
"dry_run": false,
"confirm": "never",
"route": null,
"doors": {
"cli": true,
"mcp": true,
"http": false
},
"toolset": "core",
"stability": "stable",
"input": {
"type": "object",
"properties": {
"path": {
"type": "string",
"format": "path",
"description": "Any file.",
"x-cli": {
"positional": true
}
}
},
"required": [
"path"
],
"additionalProperties": false
},
"output": {
"type": "object",
"required": [
"format",
"size",
"verdict",
"candidates",
"probes_accepted",
"embedded",
"embedded_capped",
"measures"
],
"description": "The formats the bytes fit, each with the bytes that say so and how far they were followed; what a probe accepted; signatures found inside; and measures that hold whatever the bytes are"
},
"stream": null,
"since": "0.3.0",
"deprecated": null,
"receipt": false,
"needs": {
"all": [
"identify"
]
},
"names": {
"cli": "wai media inspect",
"mcp": "media_inspect",
"c": "wai_media_inspect",
"rust": "wai::media::inspect",
"sdk": "wai.media.inspect"
}
},
{
"id": "media.upscale",
"summary": "Upscale a PNG exactly, by an integer network under a pinned prior, with a signed record a verifier recomputes",

View file

@ -26,6 +26,9 @@ claim are read, verified and their figures read by the OER reference verifier
python3 claim_verify.py bearer <file> steps 1-4 over the `transmission` and
`reconciliation` claims of the bearer corpus
(live-conformance/bearers)
python3 claim_verify.py carousel <file> steps 1-4 over the `transmission` and
`schedule` claims of the carousel corpus
(live-conformance/carousel)
Exit status 0 only if every vector reports its declared outcome, and the fixture and the
bracket arithmetic this script computes equal the literals pinned in wai-rs.
@ -179,7 +182,8 @@ ADMITS = {"energy": {"object"}, "lawful-basis": {"object", "receipt"}, "log-incl
"soft-binding": {"object"}, "soft-binding-span": {"object"}, "binding-recovery": {"object"},
"measurement": {"object", "receipt"}, "operating-point": {"object", "receipt"},
"stage": {"object"}, "delivery-gap": {"object"}, "reconstruct-session": {"object"},
"consumption": {"object"}, "transmission": {"receipt"}, "reconciliation": {"receipt"}}
"consumption": {"object"}, "transmission": {"receipt"}, "reconciliation": {"receipt"},
"schedule": {"receipt"}}
FIGURE = ["figure_profile", "figure_index", "group", "joules_micro", "energy_provenance", "energy_uncertainty",
"energy_counter_filtering", "energy_calibration_ref"]
BODY = {
@ -200,6 +204,8 @@ BODY = {
# §2.4.14, §2.4.15: bearer-receipts §4.1, §5.1.
"transmission": ["bearer", "bearer_id", "energy", "attests"],
"reconciliation": ["bearer", "bearer_id", "leaf_count", "received", "received_root", "attests"],
# §2.4.16: carousel §4.1.
"schedule": ["bearer_id", "objects", "attests"],
"reconstruct-session": ["session_nonce", "media", "host", "decoders", "units", "leaf_count", "output_root",
"primary_units", "fallback_units", "switches", "switches_total", "deadline_us", "missed",
"missed_units", "stage_sets", "framing", "counted_primary", "counted_fallback",
@ -463,6 +469,22 @@ def check_body(cls, b):
need(e["energy_calibration_ref"] is None or isinstance(e["energy_calibration_ref"], str), 2)
need(isinstance(b["attests"], str), 2)
return
if cls == "schedule":
# carousel §4.1: the instance an absolute URI; each entry's members, then each one's type
# in the table's order, `oti`'s members those of the code `fec` names.
need(is_uri(b["bearer_id"]), 2)
need(isinstance(b["objects"], list), 2)
for e in b["objects"]:
need(isinstance(e, dict), 2)
members(e, SCHEDULE_ENTRY, 2)
need(is_int(e["leaf_index"]) and is_hex(e["content_hash"], 32), 2)
need(e["fec"] in SCHEDULE_OTI and isinstance(e["oti"], dict), 2)
members(e["oti"], SCHEDULE_OTI[e["fec"]], 2)
need(all(is_int(v) for v in e["oti"].values()), 2)
need(is_int(e["repair"]) and e["esi"] in ("repeat", "advance"), 2)
need(all(is_int(e[k]) for k in ("first_ms", "period_ms", "duration_ms", "count")), 2)
need(isinstance(b["attests"], str), 2)
return
if cls == "consumption":
# ai-usage §6.1: the arrays' lengths and a term's are in the type column.
need(isinstance(b["objects"], list) and 1 <= len(b["objects"]) <= 1024
@ -1470,6 +1492,75 @@ def fabric_rules(f, joules):
need(sum(s["joules_micro"] for s in sh) == joules, 4, "measurement_fabric_sum")
# carousel §4.1, §5: a schedule entry's members, each code's parameters and their bounds.
SCHEDULE_ENTRY = ["leaf_index", "content_hash", "fec", "oti", "repair", "esi", "first_ms", "period_ms",
"duration_ms", "count"]
SCHEDULE_OTI = {"raptorq": ["transfer_length", "symbol_size", "source_blocks", "sub_blocks", "alignment"],
"no-code": ["transfer_length", "symbol_size", "max_block_length"]}
K_PRIME_MAX = 56_403 # RFC 6330 §5.1.2
RAPTORQ_MAX_F = 942_574_504_275 # RFC 6330 §3.3.2 as its erratum 5548 corrects it
LAST_INSTANT_MS = 253_402_300_799_999 # 9999-12-31T23:59:59.999Z
def ceil_div(a, b):
return -(-a // b)
def schedule_blocks(e):
"""carousel §5.1, §5.2: an entry's source blocks as [(K, how many)], the larger size first
and a size no block has left out; None when its code does not admit its parameters."""
o = e["oti"]
if e["fec"] == "raptorq":
f, t, z, n, al = (o[k] for k in SCHEDULE_OTI["raptorq"])
if not (1 <= f <= RAPTORQ_MAX_F and 1 <= al <= 255 and 1 <= t <= 65_535 and t % al == 0
and 1 <= z <= 255 and 1 <= n <= t // al):
return None
kt = ceil_div(f, t)
if z > kt or ceil_div(kt, z) > K_PRIME_MAX:
return None
# RFC 6330 §4.4.1.2: (KL, KS, ZL, ZS) = Partition[Kt, Z].
kl, ks = ceil_div(kt, z), kt // z
zl = kt - ks * z
sizes = [(kl, zl), (ks, z - zl)]
else:
ln, es, bl = (o[k] for k in SCHEDULE_OTI["no-code"])
if not (1 <= ln < 1 << 48 and 1 <= es <= 65_535 and 1 <= bl < 1 << 32):
return None
# RFC 5052 §9.1: Ts symbols in Nb blocks; I blocks of A_large, the rest of A_small.
ts = ceil_div(ln, es)
nb = ceil_div(ts, bl)
a_large, a_small = ceil_div(ts, nb), ts // nb
if nb > 1 << 16 or a_large > 1 << 16:
return None
i = ts - a_small * nb
sizes = [(a_large, i), (a_small, nb - i)]
return [(k, c) for k, c in sizes if c > 0]
def schedule_bytes(e, blocks):
"""carousel §5.4: count · Σ m · T for RaptorQ, count · L for Compact No-Code."""
if e["fec"] == "raptorq":
return e["count"] * sum(c * (k + e["repair"]) for k, c in blocks) * e["oti"]["symbol_size"]
return e["count"] * e["oti"]["transfer_length"]
def schedule_entry_rules(e):
"""carousel §4.2 rule 3 for one entry, in its order. Returns its blocks."""
blocks = schedule_blocks(e)
need(blocks is not None, 4, "schedule_oti_invalid")
if e["fec"] == "no-code":
named = e["repair"] == 0 and e["esi"] == "repeat"
else:
m = blocks[0][0] + e["repair"]
named = (m if e["esi"] == "repeat" else e["count"] * m) <= 1 << 24
need(named, 4, "schedule_symbols_invalid")
c, p, d = e["count"], e["period_ms"], e["duration_ms"]
formed = (c >= 1 and d >= 1 and (c < 2 or p >= d) and e["first_ms"] + (c - 1) * p + d <= LAST_INSTANT_MS
and schedule_bytes(e, blocks) <= MAX_INT)
need(formed, 4, "schedule_passes_invalid")
return blocks
def class_rules(c):
"""§3.1 step 4 — §2.4.5-§2.4.7 and §2.6."""
cls, b = c["class"], c["body"]
@ -1526,6 +1617,13 @@ def class_rules(c):
need(n >= 1 and len(bits) == (n + 7) // 8, 4, bad)
need(not any(bits[i // 8] & (0x80 >> (i % 8)) for i in range(n, 8 * len(bits))), 4, bad)
need(any(bits), 4, bad)
elif cls == "schedule":
# carousel §4.2.
need(b["attests"] == "scheduled", 4, "schedule_not_scheduled")
idx = [e["leaf_index"] for e in b["objects"]]
need(len(idx) > 0 and all(x < y for x, y in zip(idx, idx[1:])), 4, "schedule_objects_malformed")
for e in b["objects"]:
schedule_entry_rules(e)
elif cls == "log-inclusion":
need(b["log_receipt"] != "", 4, "log_inclusion_no_evidence")
elif cls in ("soft-binding", "soft-binding-span", "binding-recovery"):
@ -2218,6 +2316,39 @@ def bearer_claims(path):
return 1 if fail or n == 0 else 0
def carousel_claims(path):
"""§3.1 steps 1-4 over the claims of the carousel corpus (live-conformance/carousel/vectors.json,
carousel §10): a claim a verdict case states refused at one of those steps is refused there
with the stated reason, and every other `transmission` or `schedule` claim passes them; and
each claim case gets the outcome it states. Steps 5 and 6 are the carousel verifier's."""
with open(path, encoding="utf-8") as f:
doc = json.load(f)
fail = n = 0
checks = []
for case in doc["verdicts"]:
if case["outcome"]["outcome"] != "verdict":
continue
for wire, stated in zip(case["claims"], case["outcome"]["claims"]):
m = re.fullmatch(r"reject step=(\d) reason=(\S+)", stated)
checks.append((case["name"], wire, (int(m[1]), m[2]) if m and int(m[1]) <= 4 else None, True))
for case in doc["claims"]:
m = re.fullmatch(r"reject step=(\d) reason=(\S+)", case["outcome"])
checks.append((case["name"], case["claim"], (int(m[1]), m[2]) if m else None, False))
for name, wire, want, only_bearer in checks:
try:
c = verify_claim(wire)
if only_bearer and c["class"] not in ("transmission", "schedule"):
continue
got = None
except Reject as r:
got = (r.step, r.reason)
n += 1
fail += got != want
print(f"{'PASS' if got == want else 'FAIL'} {name} expected={want} got={got}")
print(f"{n} carousel claims, {fail} failure(s)")
return 1 if fail or n == 0 else 0
def main(argv):
if argv[1:2] == ["brackets"]:
for name, br, _, _ in bracket_fixture():
@ -2230,6 +2361,8 @@ def main(argv):
return consumption_claims(argv[2])
if argv[1:2] == ["bearer"] and len(argv) == 3:
return bearer_claims(argv[2])
if argv[1:2] == ["carousel"] and len(argv) == 3:
return carousel_claims(argv[2])
if argv[1:2] == ["fixture"]:
c, canon, link = fixture()
print("sig ", c["sig"])

View file

@ -34,6 +34,10 @@ and context checks; the verdict a verifier derives for one group from its receip
claims (§6); the upper bound on a receiver's share of a shared transmission's energy (§7);
verification with no return path (§8); and the mappings onto bearers (§9).
When each object passes on a bearer with no return path, and with which forward error
correction, is [carousel](carousel.md)'s: a publisher's `schedule` claim beside the transmission
claim here, from which a verifier derives when a receiver within a loss bound holds each object.
Out of scope: a transport or a bearer; a reception-reporting protocol — a reconciliation
travels on whatever path a sink later has, as a receipt file (http-delivery §8.1) or inside a
bearer's own reporting (§9); a pre-emption authority ([unidirectional-delivery](unidirectional-delivery.md)

460
wai/extensions/carousel.md Normal file
View file

@ -0,0 +1,460 @@
# WAI Extension: Carousel Schedule
> Status: Draft. Registers the record `wai.bearer.carousel`: a publisher's signed schedule of
> when each object of a group passes on a bearer instance, and with which application-layer
> forward error correction. On a bearer with no return path a receiver cannot ask for what it
> missed: it holds an object only if the passes it heard carried enough encoding symbols. The
> schedule says what was planned to be sent, in a jwp-receipts claim of class **schedule**
> (jwp-receipts §2.4.16, §4 here), signed by the group's key beside the group receipt and the
> transmission claim of [bearer-receipts](bearer-receipts.md) §4. From it a verifier derives,
> for each object, when a receiver whose losses stay within a stated bound holds it (§6): the
> deadline a one-way bearer can promise, with the decoding failure the FEC code bounds. A
> receiver derives the passes it must listen to and when it may turn its radio off (§7). WAI
> defines no transport and no code: the codes are RaptorQ (RFC 6330) and Compact No-Code
> (RFC 5445), and the schedule declares their parameters. The reference implementation is the
> `carousel` module of `wai-rs` (feature `carousel`), with the class's body in
> `claim_receipt::schedule`; the corpus is
> [`live-conformance/carousel/`](../live-conformance/carousel/), and its second implementation
> is `live-conformance/carousel_verify.py`.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
>
> Texts read for this revision: RFC 6330 (RaptorQ) with its verified erratum 5548, RFC 5445
> (Compact No-Code) and RFC 5052 §9.1 (block partitioning). A clause number of one of them in
> this text is that RFC's.
## 1. Scope
In scope: the schedule claim (§4); how an object is divided into source blocks and symbols
under each code (§5); when an object is held by a receiver whose losses stay within a bound,
and the statements a verifier may check against it (§6); the passes a receiver listens to (§7).
Out of scope: encoding and decoding symbols, which are the codes' own (RFC 6330 §5, RFC 5445
§3); a transport, and how symbols are packed into packets; a model of a channel. Every
statement here holds for every pattern of loss within the stated bound (§2). Whether losses on a
channel are independent, and how often a bound is exceeded, is a judgement about that
channel, which a relying party makes and a schedule does not carry. Nothing here states
presentation, and nothing here states that any receiver heard anything: that is a
reconciliation's (bearer-receipts §5).
Relationship to the core spec: `wai.bearer.carousel` is a **record** ([SPEC](../SPEC.md) §5):
the schedule claim is a signed record a WAI tool emits and verifies. It names no payload format
and is not dispatchable.
## 2. Terms
- **Bearer instance**, **declaration**, **sink**, **relying party**: as bearer-receipts §2. A
declaration is a transmission claim that stands under bearer-receipts §6.2 step 4.
- **Source block**, **source symbol**, **repair symbol**, **encoding symbol**, **ESI** (encoding
symbol ID): as RFC 6330 §2 and RFC 5052. An object is divided into source blocks; a block of
*K* source symbols is sent as encoding symbols, each named by its ESI in that block.
- **Pass**: one transmission of encoding symbols of every source block of one object on a
bearer instance, from its start to its end. An object's passes are numbered from 1.
- **Instant**: a count of milliseconds since 1970-01-01T00:00:00Z, leap seconds not counted, as
[channel-lineup](channel-lineup.md) writes one.
- **Loss bound** *L*: an integer from 0 to 999,999, in parts per million. A receiver is
**within** *L* when, of the *m* encoding symbols of any one source block sent in any one
pass, it fails to receive at most ⌊*L* · *m* / 1,000,000⌋, whichever they are. It is a bound
on symbols: a relying party whose bound is stated on packets carrying several symbols each
converts it.
- **Overhead** *h*: 0, 1 or 2, the encoding symbols beyond *K′* a RaptorQ block is held at
(§5.3).
## 3. Why a schedule beside the receipt
The group receipt signs its objects and names no bearer (bearer-receipts §3); the transmission
claim names the bearer instance and attests that the publisher transmitted the group, which on
a bearer with no return path says nothing about any receiver. Between the two is what decides
whether a receiver that heard part of a transmission holds an object: how often each object
passes, for how long, and with how many repair symbols. That is a plan, made before anything
is sent, and it is a statement of the publisher's own:
- **A relying party can hold a deadline to it.** A publisher that offers an object to every
receiver within a loss bound by a deadline makes a promise that the schedule either supports
or does not (§6.4), checked with no receiver and no return path.
- **A receiver can plan with it.** It learns which passes add symbols it needs, and listens to
those only (§7). On a receiver powered by a battery the radio is a large part of its energy.
- **It changes no byte of what it schedules.** The schedule names the group by its link hash
and each object by its position and content hash, like a reconciliation. A group carried
before this extension, or by a party that does not use it, verifies exactly as before.
The schedule attests a plan, not a transmission: that is the declaration's, and the schedule
stands only beside one (§6.2). Neither attests that any receiver heard anything.
## 4. The schedule claim (`class: "schedule"`)
A publisher that transmits a group over a `multicast` or `unidirectional` bearer instance
**MAY** declare its schedule there in a `schedule` claim, one per bearer instance. Its
`subject_type` is `receipt` and its `subject` the group receipt's link hash (jwp-receipts §3
step 3). It is signed by the key that signed the group. It attests that the publisher
scheduled the passes it names, and nothing about what any sink received.
### 4.1 Body
| member | type | meaning |
|---|---|---|
| `bearer_id` | absolute URI | The bearer instance (bearer-receipts §2): the `bearer_id` of the transmission claim it schedules. |
| `objects` | array | The objects scheduled, one entry each (below). |
| `attests` | string | **MUST** be `"scheduled"`. Any string passes step 2; another value is refused at step 4. |
Each entry of `objects` has exactly these members:
| member | type | meaning |
|---|---|---|
| `leaf_index` | integer | The object's position in the group (jwp-receipts §2.2). |
| `content_hash` | 32 B hex | The object's content hash (jwp-receipts §2.1): what a receiver that decodes the object checks it against. |
| `fec` | string | The code: `raptorq` (RFC 6330, FEC Encoding ID 6) or `no-code` (RFC 5445, FEC Encoding ID 0). |
| `oti` | object | The code's object transmission information, as §5 reads it. For `raptorq`, exactly `transfer_length` (F), `symbol_size` (T), `source_blocks` (Z), `sub_blocks` (N) and `alignment` (Al), RFC 6330 §3.3.2 and §3.3.3. For `no-code`, exactly `transfer_length`, `symbol_size` (the encoding symbol length) and `max_block_length` (the maximum source block length, in symbols), RFC 5445 §3.2.2.2. Each an integer. |
| `repair` | integer | The repair symbols of each source block sent in each pass, beyond its source symbols (§5.4). |
| `esi` | string | `repeat`: every pass sends the same ESIs. `advance`: each pass sends ESIs no earlier pass sent (§5.4). |
| `first_ms` | integer | The instant the first pass starts. |
| `period_ms` | integer | The time from one pass's start to the next's. |
| `duration_ms` | integer | The time each pass lasts. |
| `count` | integer | The number of passes. |
`fec` and `esi` are read at jwp-receipts §3.1 step 2 against their enumerations, the members of
`oti` against the code `fec` names, `bearer_id` as an absolute URI, and every integer as an
unsigned 64-bit one (`field_type`).
### 4.2 Rules
Checked at jwp-receipts §3.1 step 4, in this order:
1. `attests` **MUST** be `"scheduled"` (`schedule_not_scheduled`).
2. `objects` has at least one entry, and its `leaf_index` values strictly increase
(`schedule_objects_malformed`): one entry for each object scheduled, in group order.
3. Each entry in order, the first of these to fail:
1. its `oti` is one its code admits, §5.1 or §5.2 (`schedule_oti_invalid`);
2. its symbols are ones its code can name, §5.4 (`schedule_symbols_invalid`);
3. its passes are well formed (`schedule_passes_invalid`): `count` and `duration_ms` are at
least 1; with `count` at least 2, `period_ms` is at least `duration_ms`, so one pass of
an object ends before its next starts; the last pass ends,
`first_ms` + (`count` − 1) · `period_ms` + `duration_ms`, at or before 253,402,300,799,999
(9999-12-31T23:59:59.999Z); and the octets its passes send, `bytes_sent` (§5.4), are at
most 2<sup>53</sup> − 1, the greatest integer a receipt states; each computed without
overflow.
### 4.3 Against the group
Checked at jwp-receipts §3.1 step 6 against the group receipt the verifier holds, which has
passed jwp-receipts §3 steps 1 and 2 and whose `group_start` is an RFC 3339 date-time
(`context_invalid` otherwise; another receipt is `context_mismatch`), the object receipts it
holds under that group, each of which has passed jwp-receipts §3 step 5, and the envelope bytes
of those it holds. In order:
1. the group's link hash is the claim's `subject` (`schedule_subject_mismatch`);
2. the claim's `signer_pubkey` is the group's (`schedule_signer_mismatch`): only the publisher
schedules its own transmission;
3. every `leaf_index` is less than the group's `leaf_count` (`schedule_leaf_out_of_range`);
4. no entry's first pass starts before the group does (`schedule_before_group`): ⌊`first_ms`
/ 1000⌋ is at least the seconds jwp-receipts §2.4.3 reads from `group_start`, its fraction
dropped. Nothing can be sent before it exists;
5. for each entry whose object receipt the verifier holds, the receipt's `content_hash` is the
entry's (`schedule_content_mismatch`); and for each whose envelope bytes it holds, their
length is `oti.transfer_length` (`schedule_length_mismatch`).
A schedule checked without the group stops at step 5 and is not counted by §6.
### 4.4 Authority
Confirmed under jwp-receipts §7, a schedule claim needs the authority `schedule`, which is not
pinned-only and is not `delivery`. An origin gives it to the keys that publish its groups, as it
gives `transmission`.
## 5. Blocks and symbols
### 5.1 RaptorQ
The `oti` of a `raptorq` entry, F, T, Z, N and Al, is one RFC 6330 admits when:
- F is from 1 to 942,574,504,275 (§3.3.2 as erratum 5548 corrects it: 56,403 symbols of
65,535 octets in each of 255 blocks);
- T is from 1 to 65,535 and a multiple of Al, and Al from 1 to 255 (§3.3.2, §3.3.3, §4.2);
- Z is from 1 to 255, the range of its 8-bit field (§3.3.3: the parameters are positive);
- N is from 1 to T / Al, so each sub-block has a sub-symbol of at least Al octets (§4.4.1.2);
- with *Kt* = ⌈F / T⌉, Z is at most *Kt*, so no source block is empty, and ⌈*Kt* / Z⌉ is at
most 56,403, *K′*<sub>max</sub> (§4.4.1.2, §5.1.2).
The object is then divided as RFC 6330 §4.4.1.2 divides it: (*KL*, *KS*, *ZL*, *ZS*) =
Partition[*Kt*, Z], where Partition[*I*, *J*] = (⌈*I* / *J*⌉, ⌊*I* / *J*⌋, *I* − ⌊*I* / *J*⌋ · *J*,
*J* − (*I* − ⌊*I* / *J*⌋ · *J*)). The first *ZL* source blocks have *KL* source symbols each and
the remaining *ZS* have *KS*. Each symbol is T octets; the last source symbol of the object is
padded to T.
### 5.2 Compact No-Code
The `oti` of a `no-code` entry, with L its `transfer_length`, E its `symbol_size` and B its
`max_block_length`, is one RFC 5445 admits when L is from 1 to 2<sup>48</sup> − 1, E from 1 to
65,535 and B from 1 to 2<sup>32</sup> − 1 (RFC 5445 §3.2.2.2; an object of no octets has
nothing to schedule), and the division of RFC 5052 §9.1 fits RFC 5445's 16-bit source block
number and encoding symbol ID (§3.2.1):
- the object has *Ts* = ⌈L / E⌉ source symbols, in *Nb* = ⌈*Ts* / B⌉ source blocks;
- *A*<sub>large</sub> = ⌈*Ts* / *Nb*⌉, *A*<sub>small</sub> = ⌊*Ts* / *Nb*⌋ and *I* = *Ts* −
*A*<sub>small</sub> · *Nb*: the first *I* blocks have *A*<sub>large</sub> source symbols and
the others *A*<sub>small</sub>;
- *Nb* is at most 65,536, and *A*<sub>large</sub> at most 65,536.
Every symbol is E octets but the object's last, which holds what remains of L.
### 5.3 What a receiver needs
A block of *K* source symbols is **held** when a receiver can recover it:
- **`no-code`**: when it has received every one of its *K* source symbols. There are no repair
symbols; a symbol missed is missed until a pass sends it again.
- **`raptorq`**: when it has received *K′* + *h* distinct encoding symbols of the block, where
*K′* is the least value at least *K* in RFC 6330 Table 2 (§5.6; its column of *K′* is restated
in Appendix A). RFC 6330 §5.8 bounds the decoder's failure to recover a block of *K′* source
symbols from *K′*, *K′* + 1 and *K′* + 2 encoding symbols, chosen uniformly at random, at
1 in 100, 1 in 10,000 and 1 in 1,000,000 on average. A block of *K* < *K′* symbols is padded
to *K′* with symbols the decoder knows (§5.3.1), so it may need fewer than *K′* + *h*
received; this text asks for the count §5.8 states its bound at, and no fewer.
An **object** is held when every one of its source blocks is.
### 5.4 The symbols a pass sends
Each pass sends, of each source block of *K* source symbols, *m* = *K* + `repair` encoding
symbols:
- `repeat`: ESIs 0 to *m* − 1 in every pass;
- `advance`: in pass *j*, ESIs (*j* − 1) · *m* to *j* · *m* − 1. The first pass holds the source
symbols (ESIs 0 to *K* − 1, RFC 6330 §4.4.2) and every later pass only repair symbols, none
sent before.
An entry's symbols are ones its code can name (§4.2 rule 3.2) when:
- **`no-code`**: `repair` is 0 and `esi` is `repeat`. The code has no repair symbols, and an ESI
past a block's last source symbol names nothing;
- **`raptorq`**: the greatest ESI sent fits the 24-bit field (RFC 6330 §3.2): with *KL* the
larger block's *K*, *KL* + `repair` is at most 2<sup>24</sup> for `repeat`, and `count` ·
(*KL* + `repair`) at most 2<sup>24</sup> for `advance`, computed without overflow.
The octets an entry's passes send, `bytes_sent`, are `count` · Σ *m* · T over its source blocks
for `raptorq`, and `count` · L for `no-code`, whose last symbol is short.
## 6. When an object is held
### 6.1 Inputs
The group receipt; the object receipts the verifier holds under it, each with its envelope
bytes where it holds them; the transmission and schedule claims it holds about the group, each
signer's in the order of its chain; a loss bound *L*; and an overhead *h*.
### 6.2 Steps
The loss bound and overhead are checked first: *L* above 999,999 is `loss_bound_invalid`, and
*h* other than 0, 1 or 2 is `overhead_invalid`; neither derives anything. Then:
1. **The group and its objects**: bearer-receipts §6.2 steps 1 and 2.
2. **The claims**: each claim, in order, under jwp-receipts §3.1 steps 1 to 5, then step 6: a
transmission claim by bearer-receipts §4.3 and a schedule by §4.3 here. A claim of another
class is `context_mismatch` at step 6. A claim refused at any step takes no further part.
3. **The declarations**: bearer-receipts §6.2 step 4.
4. **The schedules**: the accepted schedule claims by `bearer_id`. Two with one `bearer_id` and
different bodies set aside every schedule of that instance (`schedule_contradicted`): a
publisher that changes its plan for one transmission has stated neither, and a relying
party holds no promise that can change under it. Two with the same body are one. A schedule
is then set aside at the first of these that holds: step 3 set aside the declarations of its
`bearer_id` (`bearer_contradicted`); no declaration has its `bearer_id`
(`bearer_not_declared`); its declaration's `bearer` is `bidirectional`
(`schedule_on_bidirectional`), since a receiver with a path back asks for what it lacks and
is not served by a plan. Otherwise it **stands**.
5. **Availability**, for each entry of each standing schedule, by §6.3.
### 6.3 Availability
For an entry, pass *j* ends at the instant `first_ms` + (*j* − 1) · `period_ms` +
`duration_ms`. For each source block of *K* source symbols, with *m* = *K* + `repair` and *x* =
⌊*L* · *m* / 1,000,000⌋ the most a receiver within *L* misses of it in one pass, the first pass
after which a receiver within *L* holds the block is, under §5.3:
- **`raptorq`, `advance`**: the least *j* from 1 to `count` with *j* · (*m* − *x*) ≥ *K′* + *h*.
Every pass adds *m* − *x* symbols none before it sent.
- **`raptorq`, `repeat`**: 1 if *m* − *x* ≥ *K′* + *h*, and none otherwise. A later pass sends
the same ESIs, and the ones a receiver missed may be missed again: within *L*, nothing it adds
is certain.
- **`no-code`**: 1 if *x* is 0, and none otherwise, for the same reason.
The object is **available** at the end of the greatest of its blocks' passes, or is **not
available within the schedule** if any block has none. A verifier reports for each entry its
`available_ms` (or `null`), the pass, `bytes_sent` (§5.4), and, for `raptorq`, the RFC 6330
§5.8 bound at *h* as `failure_one_in` (100, 10,000 or 1,000,000); for `no-code` it is `null`, a
block held being recovered exactly.
### 6.4 Statements offered about an object or a group
A verifier **MAY** be asked to check a statement, on a bearer instance, for a loss bound *L* and
an overhead *h*:
- **object *i* is held by *t***: holds when a standing schedule of the instance has an entry for
object *i* that is available at an instant at or before *t*. It is refused when no standing
schedule of the instance has one (`object_not_scheduled`), when it is not available within
the schedule (`availability_not_within_schedule`), and when it is available after *t*
(`availability_after_deadline`).
- **the group is held by *t***: holds when the statement holds for every object of the group,
0 to `leaf_count` − 1, and is refused with the first object's reason otherwise.
A statement that holds says what the publisher planned: a receiver within *L*, hearing every
pass the schedule names, holds the object by *t*, but for a decoding failure no more likely than
`failure_one_in` states. It does not say the passes were sent (the declaration's) or heard (a
reconciliation's).
## 7. The passes a receiver listens to
A receiver that holds a standing schedule's entry, the instant *t* it is at, its loss bound *L*,
the overhead *h* it decodes at, and, for each source block, the number *n* of distinct encoding
symbols of it that it holds (for `no-code`, of its source symbols), derives the passes to listen
to. The loss bound and overhead are checked as §6.2 checks them, and a holding that does not
give one number for each source block of the object, in the order §5.1 and §5.2 divide it into
them, or that holds more source symbols of a `no-code` block than it has, is `holding_invalid`.
Only passes that start at or after *t* count: a pass under way at *t* has sent symbols the
receiver did not hear. A pass adds, to a block not held, with *m* and *x* as §6.3:
- **`raptorq`, `advance`**: *m* − *x*, none of them sent before;
- **`raptorq`, `repeat`** and **`no-code`**: max(0, *m* − *n* − *x*): of the *m* − *n* ESIs it
lacks, *x* may be the ones it misses again.
The passes are taken in order of their start. While some block is not held, the next pass is
listened to if it adds to a block not held, each such block's *n* growing by what it adds; the
first pass that adds to none ends the plan. The plan is the passes listened to, each with its
start and end; `complete_ms`, the instant the object is held: *t* when it already is, the last
pass's end when the passes listened to suffice, and `null` otherwise; and `listen_ms`, the sum
of their durations: the time the receiver's radio must be on. Outside those passes it **MAY** be
off. Under `repeat` and `no-code` at most one pass adds anything certain within *L*, since after
it a block lacks only symbols the receiver may miss again: a receiver that one pass does not
complete is not certain to be held after any number, and keeps listening, if it does, on its own
judgement of its channel.
## 8. Mappings
A bearer that carries files with FEC states the same parameters in its own description of each
file, for its receivers: on FLUTE (RFC 6726) and the bearers built on it, the FEC Object
Transmission Information of each file in the file delivery table. A schedule restates them,
signed by the publisher, beside the group's receipts, so a relying party with no access to the
bearer can check a promise (§6.4) and a receiver can plan before it tunes (§7). A sink that holds
both compares them; a difference is the bearer's, and the sink reports it rather than choosing
one. The bearer's own repetition (its carousel of descriptions and files) is the schedule's
passes: a pass is one transmission of the object's symbols, whatever the bearer calls it.
## 9. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
| `schedule_not_scheduled` | §4.2 rule 1 |
| `schedule_objects_malformed` | §4.2 rule 2 |
| `schedule_oti_invalid` | §4.2 rule 3.1 |
| `schedule_symbols_invalid` | §4.2 rule 3.2 |
| `schedule_passes_invalid` | §4.2 rule 3.3 |
| `schedule_subject_mismatch` | §4.3 step 1 |
| `schedule_signer_mismatch` | §4.3 step 2 |
| `schedule_leaf_out_of_range` | §4.3 step 3 |
| `schedule_before_group` | §4.3 step 4 |
| `schedule_content_mismatch` | §4.3 step 5 |
| `schedule_length_mismatch` | §4.3 step 5 |
| `schedule_contradicted` | §6.2 step 4 |
| `schedule_on_bidirectional` | §6.2 step 4 |
| `loss_bound_invalid` | §6.2, §7 |
| `overhead_invalid` | §6.2, §7 |
| `holding_invalid` | §7 |
| `object_not_scheduled` | §6.4 |
| `availability_not_within_schedule` | §6.4 |
| `availability_after_deadline` | §6.4 |
A schedule also raises `bearer_contradicted` and `bearer_not_declared` (bearer-receipts §10) at
§6.2 step 4; a group or object the codes of jwp-receipts §3.3; and a claim every code of
jwp-receipts §3.1 steps 1 to 5 and those of step 6 named in §4.3 and bearer-receipts §4.3.
## 10. Conformance
[`live-conformance/carousel/`](../live-conformance/carousel/) holds the corpus, written from
fixed seeds by `wai_carousel_vectors gen` as `vectors.json`. It has:
- **Availability**: RaptorQ objects of one and of several source blocks of two sizes, under
`advance` and `repeat`, at loss bounds of zero and above, reaching a later pass, the last
pass, and none; at each overhead; a Compact No-Code object available at the first pass with
no loss and at none with any; a group of several objects; the same object simulcast on two
instances with different schedules.
- **Statements**: an object and a group held by a deadline, after it, not within the schedule,
and not scheduled.
- **Plans**: a receiver tuning in before the first pass, during one, between passes, holding
part of an object, holding all of it, and one under `repeat` that one pass does not complete.
- **Negative controls**, one for each reason of §9 that a claim or a verdict raises, and the
bounds of §5.1, §5.2 and §5.4 on both sides.
A conforming verifier gives every case the outcome, the reason and everything else the corpus
states. `wai_carousel_vectors verify` and the test `tests/carousel.rs` check that the file
regenerates byte for byte, that `wai-rs` gives every case its outcome, that every reason of §9
appears, that §9 lists exactly the reference's codes, and that Appendix A is the reference's
table. Against an independent implementation of RFC 6330, the test also requires the reference's
*K′* and Partition[] to agree for every block size, and decodes each RaptorQ object a case holds
available at a pass: it encodes the object under the entry's parameters, drops in each pass the
first ⌊*L* · *m* / 1,000,000⌋ symbols of each block (in the first pass, source symbols, so the
decoder must decode), and requires the object back from what is left by that pass's end.
`live-conformance/carousel_verify.py` is the second implementation, written from this text: it
shares no code with `wai-rs`; it verifies groups and objects with the JWP corpus's second
implementation (`jwp-conformance/jwp_verify.py`), and claims through steps 1–5 with the claim
corpus's (`claim-conformance/claim_verify.py`), whose step 4 holds §4.2 with §5.1, §5.2 and
§5.4; it reads Appendix A and §9 from this text, writes §4.3, §5.3, §6 and §7 itself, mutates
copies of the corpus and requires every copy to fail, and drops each of its own checks in turn
and requires the corpus to catch the weakened verifier.
## 11. Security and privacy considerations
- **A schedule is the publisher's word.** Nothing checks that the passes were sent as planned;
a declaration attests transmission and no more, and a relying party that needs to know what
receivers held counts reconciliations (bearer-receipts §6). A schedule that promises more
than was sent is a false statement signed by the group's key, and is evidence of it.
- **A loss bound is the relying party's.** Every statement holds for every pattern of loss
within it (§2). A channel that exceeds it, in a fade or under interference, voids nothing in
the schedule and nothing here predicts how often that happens.
- **It discloses the plan, not the audience.** A schedule names objects, times and code
parameters, which the bearer's own file descriptions already carry to every receiver. A
receiver that plans with it (§7) sends nothing, and a unidirectional bearer's property that
the source learns nothing of its receivers (unidirectional-delivery §2) is kept.
- **Bounded work.** An entry's arithmetic is a few products per source block, and an object has
at most 255 RaptorQ blocks of two sizes or Compact No-Code blocks of two sizes; a schedule's
cost is linear in its entries, whatever the passes' count.
## Appendix A. The values of K′
The column *K′* of RFC 6330 Table 2 (§5.6), in increasing order, restated so that §5.3 can be
applied from this text. The other columns of that table are the code's, and nothing here uses
them.
<!-- k-prime:begin -->
```
10, 12, 18, 20, 26, 30, 32, 36, 42, 46, 48, 49, 55, 60, 62, 69,
75, 84, 88, 91, 95, 97, 101, 114, 119, 125, 127, 138, 140, 149, 153, 160,
166, 168, 179, 181, 185, 187, 200, 213, 217, 225, 236, 242, 248, 257, 263, 269,
280, 295, 301, 305, 324, 337, 341, 347, 355, 362, 368, 372, 380, 385, 393, 405,
418, 428, 434, 447, 453, 466, 478, 486, 491, 497, 511, 526, 532, 542, 549, 557,
563, 573, 580, 588, 594, 600, 606, 619, 633, 640, 648, 666, 675, 685, 693, 703,
718, 728, 736, 747, 759, 778, 792, 802, 811, 821, 835, 845, 860, 870, 891, 903,
913, 926, 938, 950, 963, 977, 989, 1002, 1020, 1032, 1050, 1074, 1085, 1099, 1111, 1136,
1152, 1169, 1183, 1205, 1220, 1236, 1255, 1269, 1285, 1306, 1347, 1361, 1389, 1404, 1420, 1436,
1461, 1477, 1502, 1522, 1539, 1561, 1579, 1600, 1616, 1649, 1673, 1698, 1716, 1734, 1759, 1777,
1800, 1824, 1844, 1863, 1887, 1906, 1926, 1954, 1979, 2005, 2040, 2070, 2103, 2125, 2152, 2195,
2217, 2247, 2278, 2315, 2339, 2367, 2392, 2416, 2447, 2473, 2502, 2528, 2565, 2601, 2640, 2668,
2701, 2737, 2772, 2802, 2831, 2875, 2906, 2938, 2979, 3015, 3056, 3101, 3151, 3186, 3224, 3265,
3299, 3344, 3387, 3423, 3466, 3502, 3539, 3579, 3616, 3658, 3697, 3751, 3792, 3840, 3883, 3924,
3970, 4015, 4069, 4112, 4165, 4207, 4252, 4318, 4365, 4418, 4468, 4513, 4567, 4626, 4681, 4731,
4780, 4838, 4901, 4954, 5008, 5063, 5116, 5172, 5225, 5279, 5334, 5391, 5449, 5506, 5566, 5637,
5694, 5763, 5823, 5896, 5975, 6039, 6102, 6169, 6233, 6296, 6363, 6427, 6518, 6589, 6655, 6730,
6799, 6878, 6956, 7033, 7108, 7185, 7281, 7360, 7445, 7520, 7596, 7675, 7770, 7855, 7935, 8030,
8111, 8194, 8290, 8377, 8474, 8559, 8654, 8744, 8837, 8928, 9019, 9111, 9206, 9303, 9400, 9497,
9601, 9708, 9813, 9916, 10017, 10120, 10241, 10351, 10458, 10567, 10676, 10787, 10899, 11015, 11130, 11245,
11358, 11475, 11590, 11711, 11829, 11956, 12087, 12208, 12333, 12460, 12593, 12726, 12857, 13002, 13143, 13284,
13417, 13558, 13695, 13833, 13974, 14115, 14272, 14415, 14560, 14713, 14862, 15011, 15170, 15325, 15496, 15651,
15808, 15977, 16161, 16336, 16505, 16674, 16851, 17024, 17195, 17376, 17559, 17742, 17929, 18116, 18309, 18503,
18694, 18909, 19126, 19325, 19539, 19740, 19939, 20152, 20355, 20564, 20778, 20988, 21199, 21412, 21629, 21852,
22073, 22301, 22536, 22779, 23010, 23252, 23491, 23730, 23971, 24215, 24476, 24721, 24976, 25230, 25493, 25756,
26022, 26291, 26566, 26838, 27111, 27392, 27682, 27959, 28248, 28548, 28845, 29138, 29434, 29731, 30037, 30346,
30654, 30974, 31285, 31605, 31948, 32272, 32601, 32932, 33282, 33623, 33961, 34302, 34654, 35031, 35395, 35750,
36112, 36479, 36849, 37227, 37606, 37992, 38385, 38787, 39176, 39576, 39980, 40398, 40816, 41226, 41641, 42067,
42490, 42916, 43388, 43840, 44279, 44729, 45183, 45638, 46104, 46574, 47047, 47523, 48007, 48489, 48976, 49470,
49978, 50511, 51017, 51530, 52062, 52586, 53114, 53650, 54188, 54735, 55289, 55843, 56403
```
<!-- k-prime:end -->

208
wai/extensions/holdings.md Normal file
View file

@ -0,0 +1,208 @@
# WAI Extension: Holdings
> Status: Draft. Registers the record `wai.delivery.holdings`: a node's signed statement of which
> objects of which groups it holds now, for its neighbours. A receiver that stores what a
> broadcast carried, a neighbourhood cache or a peer can serve those objects over a short link,
> and a neighbour that knows who holds what fetches from them before it spends the backhaul. A
> holdings record (`WHD1`, §4) lists, for each group a node holds objects of, the group's link
> hash, its object count and either `whole` or a bitmap of the objects held: exact, so a
> neighbour never goes to a node for an object it does not hold, and compact where objects are
> held as groups are delivered. A verifier checks a record's form, signature and freshness and
> holds it to the group receipts it has (§5), answers whether a holder holds an object, and
> derives which wanted objects each neighbour can serve and which must come from the origin
> (§5.4). An object a neighbour serves is verified as every object is (jwp-receipts §3); a
> record is never evidence of an object's bytes. WAI defines no transport. The reference
> implementation is the `holdings` module of `wai-rs` (feature `holdings`); the corpus is
> [`live-conformance/holdings/`](../live-conformance/holdings/), and its second implementation is
> `live-conformance/holdings_verify.py`.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
## 1. Scope
In scope: the holdings record, its form, digest and signature (§4); what a verifier derives from
records and the group receipts it has, at an instant (§5); and how a fetch from a neighbour is
verified (§6).
Out of scope: how records travel (a neighbourhood's own announcements, a file beside a bearer's
carousel, a request); how a node chooses what to keep; how a neighbour is reached or what a link
costs, which the asker orders its neighbours by itself; and objects not delivered under a group
receipt, which have no holdings here.
Relationship to the core spec: `wai.delivery.holdings` is a **record** ([SPEC](../SPEC.md) §5).
It names no payload format and is not dispatchable.
## 2. Terms
- **Group**, **link hash**, **leaf count**: as [jwp-receipts](jwp-receipts.md) §2.4.1 and §3
step 3. Object *i* of a group is its object at position *i*, from 0.
- **Holder**: the node a record speaks for, named by its Ed25519 key. **Asker**: a node that wants
objects and reads its neighbours' records.
- **Instant**: a count of milliseconds since 1970-01-01T00:00:00Z, leap seconds not counted, as
[channel-lineup](channel-lineup.md) writes one.
- **Bitmap**: object *i* is bit `0x80 >> (i mod 8)` of byte ⌊*i* / 8⌋, as a reconciliation's
`received` is ([bearer-receipts](bearer-receipts.md) §5.1).
## 3. Why groups and bitmaps
Everything WAI delivers is delivered under a group receipt that names its objects by position, and
a node that holds objects holds them as they arrived: a whole group, or most of one, from a
broadcast's carousel or a relay. A bitmap per group is exact and costs a bit an object; a group
held whole costs 41 bytes. A probabilistic filter over content hashes costs over twenty bits an
object at a one-in-a-million false-positive rate and sends an asker, now and then, after an
object its neighbour does not hold: on a constrained link that is a wasted exchange each time.
Naming objects by group and position also lets an asker check a record against the receipts it
holds (§5.2), and fetch an object it knows the position of without knowing its bytes.
## 4. The record
### 4.1 Layout
Integers are unsigned and big-endian.
| size | field | meaning |
|---|---|---|
| 4 | `magic` | `WHD1` (ASCII) |
| 1 | `version` | 1 in this revision |
| 32 | `holder` | the holder's Ed25519 key |
| 8 | `as_of_ms` | u64: the instant the record states the holdings at |
| 8 | `expires_ms` | u64: the instant after which it states nothing |
| 4 | `n` | u32: the entries' count |
| … | `entries` | `n` entries, §4.2, in strictly increasing order of `group`'s bytes |
| 64 | `sig` | §4.3 |
### 4.2 An entry
| size | field | meaning |
|---|---|---|
| 32 | `group` | the group receipt's link hash |
| 8 | `leaf_count` | u64: the group's leaf count |
| 1 | `form` | 0: the objects `held` names; 1: every object of the group |
| ⌈`leaf_count` / 8⌉ | `held` | form 0 only: the bitmap of the objects held |
### 4.3 Digest and signature
The **digest** of a record is SHA-256 over its bytes from `magic` through its last entry. The
**signature** is Ed25519 by `holder` over `wai:holdings` (ASCII) and 0x01, followed by the bytes
the digest covers.
### 4.4 Reading and rules
A reader reads exactly the bytes it is given as one record, in the order of §4.1, and refuses it
`holdings_malformed` when the bytes are more than 1,048,576, end before a field the layout
requires or go on after `sig`, or when:
1. its magic is not `WHD1` or its version not 1;
2. `as_of_ms` is not below `expires_ms`, or `expires_ms` is above 2^53 − 1;
3. `n` is 0, or the entries' `group` values are not in strictly increasing order of their bytes;
4. an entry's `leaf_count` is 0 or above 2^53 − 1, or its `form` is not 0 or 1;
5. a form-0 entry's `held` sets a bit at or past `leaf_count`, sets none (a node holding nothing
of a group lists no entry for it), or sets every bit (that holding is form 1).
So each holding has exactly one record form, and two records of one holder at one instant that
state the same holdings are the same bytes. A record that reads is then refused
`holdings_signature_invalid` when its signature does not verify under `holder` as SPEC §7
*Signatures* verifies every signature: a key that is not the canonical encoding of a point of
large order fails, as does a signature whose scalar is not below the group order.
## 5. What a verifier derives
### 5.1 At an instant
A record that reads and verifies is held to the verifier's instant *t*: it is refused
`holdings_not_yet` when *t* is below `as_of_ms`, and `holdings_expired` when *t* is at or past
`expires_ms`. A verifier does not stretch either bound; one whose clock it does not trust
**SHOULD** say what instant it used.
### 5.2 Against the group receipts held
For each group receipt the verifier holds that passes jwp-receipts §3 steps 1 and 2, an entry
whose `group` is its link hash **MUST** have its `leaf_count` (`holdings_leaf_count_mismatch`).
A record with one such entry is refused whole: a holder that misstates a group misstates its
holdings. An entry for a group the verifier holds no receipt of is kept: it cannot be checked
yet, and an asker that fetches an object of it verifies that object under its receipt (§6).
### 5.3 One holder's records
Of the records of one `holder` that stand at *t*, the one with the greatest `as_of_ms` stands
alone: a node's later record states what it holds now. Two with that same `as_of_ms` and
different digests set both aside (`holdings_contradicted`), and records with the same digest are
one.
### 5.4 Who holds what
A verifier **MAY** be asked whether a record that stands holds object *i* of group *G*: **held**
when it has an entry for *G* with *i* below its `leaf_count` and, in form 0, bit *i* set; refused
`object_not_held` when its entry has *i* below its `leaf_count` and bit *i* clear;
`object_out_of_range` when *i* is at or past its `leaf_count`; and `group_not_advertised` when it
has no entry for *G*.
An asker with its neighbours' records, in the order it prefers them (by link cost, say, which it
judges itself), and a list of wanted objects, each a group's link hash and a position, derives for
each wanted object the holders whose record stands and holds it, in that order; and the wanted
objects no holder holds, which it fetches from the origin. An object held by none is not missing:
it is the backhaul's.
## 6. Fetching from a neighbour
An object a neighbour serves is an object like any other: the asker verifies it under its object
receipt and the group receipt (jwp-receipts §3), `delivered-verified` over the bytes it received,
whoever sent them. A record says where to ask, never what the answer is: an object whose bytes do
not verify is refused as jwp-receipts §3 refuses it, and a holder that advertised it and did not
serve it, or served other bytes, gave the asker one wasted exchange and nothing else.
## 7. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
| `holdings_malformed` | §4.4 |
| `holdings_signature_invalid` | §4.4 |
| `holdings_not_yet` | §5.1 |
| `holdings_expired` | §5.1 |
| `holdings_leaf_count_mismatch` | §5.2 |
| `holdings_contradicted` | §5.3 |
| `object_not_held` | §5.4 |
| `object_out_of_range` | §5.4 |
| `group_not_advertised` | §5.4 |
## 8. Conformance
[`live-conformance/holdings/`](../live-conformance/holdings/) holds the corpus, written from fixed
seeds by `wai_holdings_vectors gen` as `vectors.json`. It has:
- **Records**: whole and partial groups, a group of one object and of nine (a bitmap's last byte
part used), several groups, each with its digest and verdict; and one negative control for each
rule of §4.4 and for a signature that does not verify, a non-canonical key among them.
- **Verdicts**: records at an instant before, within and at the end of their window; held against
receipts, matching, mismatching and unknown; a holder's later record standing alone, and two at
one instant contradicting each other.
- **Queries and plans**: every answer of §5.4, and an asker's plan over three neighbours, with
objects held by several, by one and by none.
A conforming verifier gives every case the outcome, the reason and everything else the corpus
states. `wai_holdings_vectors verify` and the test `tests/holdings.rs` check that the file
regenerates byte for byte, that `wai-rs` gives every case its outcome, that every reason of §7
appears, and that §7 lists exactly the reference's codes. `live-conformance/holdings_verify.py` is
the second implementation, written from this text: it shares no code with `wai-rs`; it verifies
group receipts with the JWP corpus's second implementation (`jwp-conformance/jwp_verify.py`),
writes §4, §5 and the plans itself, mutates copies of the corpus and requires every copy to fail,
and drops each of its own checks in turn and requires the corpus to catch the weakened verifier.
## 9. Security and privacy considerations
- **A record discloses what its holder holds**, to everyone it reaches: which groups, and which of
their objects. A node **SHOULD** advertise only groups that reached it over a `multicast` or
`unidirectional` bearer ([bearer-receipts](bearer-receipts.md) §2), which every receiver in
range could hold, and **SHOULD NOT** advertise what it fetched for itself alone. A node **MAY**
sign each record with a fresh key, which unlinks its records from each other and from its
other statements; a neighbour then cannot tell a node's later record from another node's, and
§5.3 does not apply across them.
- **A record is its holder's word**, and costs a wasted exchange when it is false (§6). An asker
that is sent wrong bytes refuses them and **MAY** stop asking that holder; nothing here scores
holders.
- **Freshness**: a record past `expires_ms` states nothing. A holder **SHOULD** set an expiry no
later than it expects to keep what it lists, since an eviction it does not announce sends
askers after objects it no longer has.
- **Bounded work**: a record is at most 1,048,576 bytes, read once, in order; a query is a lookup
by group and a bit test.

View file

@ -531,7 +531,10 @@ states; and step 6, against the receipts it names that the client holds here:
- a `transmission` claim, against the group of an item whose link hash is its subject
([bearer-receipts](bearer-receipts.md) §4.3); and a `reconciliation` claim, against that
group and the object receipts of the items listed under it here (bearer-receipts §5.3):
one that names an object this index does not list is `context_not_held`.
one that names an object this index does not list is `context_not_held`;
- a `schedule` claim, against the group of an item whose link hash is its subject and the
object receipts of the items listed under it here ([carousel](carousel.md) §4.3); an
index carries no envelope bytes, so its lengths are not checked here.
A claim is accepted only when step 6 ran against what it needs. A `measurement` claim
whose figure the client does not hold is `figure_not_held`, as §3.1 states; any other

View file

@ -41,7 +41,7 @@ In scope:
`claim` receipt (§2.4.3) carries one of the others — `energy`,
`lawful-basis`, `log-inclusion`, `measurement`, `operating-point`,
`stage`, `delivery-gap`, `reconstruct-session`, `consumption`,
`transmission`, `reconciliation` (§2.4.5–§2.4.15) — on one
`transmission`, `reconciliation`, `schedule` (§2.4.5–§2.4.16) — on one
envelope, one canonical form and one signature rule, so a statement other
than delivery does not need a field set of its own.
- **Objects not delivered** (§3, §4.4): an object a subscriber did not
@ -417,6 +417,7 @@ key.
| `consumption` | `claim` | `object` (the first object it names) | §2.4.13 | the agent that consumed the objects: its key, named by its `did:key` |
| `transmission` | `claim` | `receipt` (the group transmitted) | §2.4.14 | the group's publisher: the key that signed the group |
| `reconciliation` | `claim` | `receipt` (the group reconciled) | §2.4.15 | the sink that received the objects, never the publisher |
| `schedule` | `claim` | `receipt` (the group scheduled) | §2.4.16 | the group's publisher: the key that signed the group |
Rules:
@ -1342,6 +1343,19 @@ step 2; its class rules are §5.2 there, at step 4; and its context is §5.3 the
the group receipt, whose link hash is its `subject` and whose key does not sign it, and the
object receipts at the positions it names.
#### 2.4.16. `class: "schedule"`
A publisher's plan of when each object of a group passes on a bearer instance with no return
path, and with which forward error correction: for each object, its code (RaptorQ or Compact
No-Code) and that code's object transmission information, the repair symbols each pass adds,
and the times and count of its passes. It attests a plan, not a transmission or a delivery, and
it sits beside the group receipt and the instance's transmission claim, whose bytes it leaves
unchanged. [carousel](carousel.md) defines it: its body's members are §4.1 there
(`bearer_id`, `objects` and `attests`), read at §3.1 step 2; its class rules are §4.2 there, with
§5's bounds on each code's parameters, at step 4; and its context is §4.3 there, at step 6: the
group receipt, whose link hash is its `subject` and whose key signs it, and the object receipts
and envelopes at the positions it names.
### 2.5. Worked example
Manifest JSON (group receipt, formatted for readability — wire form
@ -2088,7 +2102,7 @@ order listed.
defines here (`record`, `binding_type`, a bracket's `opening`, a build's
`profile`, a stage's `tier`, a stage object's `status` and `evidence`, a
consumption claim's usage `vocabulary`, a transmission or reconciliation
claim's `bearer`, a gap's `reason`, a session's
claim's `bearer`, a schedule entry's `fec` and `esi`, a gap's `reason`, a session's
`framing`, its host's `profile`, and the paths
of its decoder entries and switches); a date-time that is not one of §2.4.3 (a gap's
`observed_at`); a URI that is not absolute;
@ -2123,7 +2137,9 @@ order listed.
decoder entry's grammar and tier, the switches' reasons and order, the
missed presentations, the order of its stage sets, its counted bytes and
its classical rendition; in a `transmission` or `reconciliation` claim, its
`attests`, a transmission's energy figure and a reconciliation's bitmap;
`attests`, a transmission's energy figure and a reconciliation's bitmap; in a
`schedule` claim, its `attests`, the order of its entries, and each entry's code
parameters, symbols and passes;
and, in a `consumption` claim, the order of its
objects, usage entries and seen digests, its subject, its terms and its
`signer_id`.
@ -2178,7 +2194,9 @@ order listed.
`reconciliation` claim against that group receipt and the object receipts
at the positions it names, each verified by inclusion at least, as
[bearer-receipts](bearer-receipts.md) §4.3 and §5.3 order them (§2.4.14,
§2.4.15). A group receipt is verified here under §3 steps 1 and 2, and one
§2.4.15); and a `schedule` claim against the group receipt its subject names and
the object receipts and envelopes at the positions it names, as
[carousel](carousel.md) §4.3 orders them (§2.4.16). A group receipt is verified here under §3 steps 1 and 2, and one
that fails them is `context_invalid`. A
declaration is verified here under steps 1–4, and one that fails
them — or is not JSON — is `context_invalid`: its members,
@ -2276,6 +2294,11 @@ and never folds it in: confirmation changes the outcome of no step here.
| 4 | `transmission_not_transmitted` | a `transmission` claim's `attests` is not `transmitted` ([bearer-receipts](bearer-receipts.md) §4.2) |
| 4 | `reconciliation_not_received` | a `reconciliation` claim's `attests` is not `received` (bearer-receipts §5.2) |
| 4 | `reconciliation_bitmap_malformed` | its `leaf_count` is 0, its `received` is not ⌈`leaf_count` / 8⌉ bytes, sets a bit at or past `leaf_count`, or sets none |
| 4 | `schedule_not_scheduled` | a `schedule` claim's `attests` is not `scheduled` ([carousel](carousel.md) §4.2) |
| 4 | `schedule_objects_malformed` | it has no entry, or its entries' `leaf_index` values do not strictly increase |
| 4 | `schedule_oti_invalid` | an entry's code parameters are not ones its code admits (carousel §5.1, §5.2) |
| 4 | `schedule_symbols_invalid` | an entry's symbols are not ones its code can name (carousel §5.4) |
| 4 | `schedule_passes_invalid` | an entry's passes are not well formed: none, of no duration, overlapping, ending past 9999, or sending more than 2<sup>53</sup> − 1 octets |
| 6 | `energy_disagrees_with_delivery` | the figure differs from the `object` receipt's |
| 6 | `energy_class_disagrees_with_delivery` | the object's leaf is labelled, and the `energy` claim's class or evidence differs from the label |
| 6 | `figure_disagrees` | the receipt a `measurement` or `operating-point` claim names carries another figure at `figure_index` |
@ -2322,6 +2345,12 @@ and never folds it in: confirmation changes the outcome of no step here.
| 6 | `reconciliation_leaf_count_mismatch` | its `leaf_count` is not the group's |
| 6 | `reconciliation_objects_not_held` | it names a position whose object receipt the verifier does not hold: recoverable, never accepted |
| 6 | `reconciliation_root_mismatch` | its `received_root` is not the root over the leaves at the positions it names |
| 6 | `schedule_subject_mismatch` | a `schedule` claim's `subject` is not the link hash of the group receipt it is checked against ([carousel](carousel.md) §4.3) |
| 6 | `schedule_signer_mismatch` | it is not signed by the group's key |
| 6 | `schedule_leaf_out_of_range` | an entry's `leaf_index` is not less than the group's `leaf_count` |
| 6 | `schedule_before_group` | an entry's first pass starts before the group's `group_start` |
| 6 | `schedule_content_mismatch` | an entry's `content_hash` is not that of the object receipt the verifier holds at its position |
| 6 | `schedule_length_mismatch` | an entry's transfer length is not the length of the object's envelope the verifier holds |
| 6 | `session_stage_disagrees` | a `stage` claim the session names is about a base, or lists an arrived layer object, the session did not receive; counts a unit the session never presented, or more units at a stage than the session presented at it or above; or no claim whose set holds a unit lists an applied layer that reaches a stage the session presented it at |
### 3.2. Recovery through a soft binding, and the ancestor rule
@ -3544,7 +3573,7 @@ A confirmation needs every one of them:
| item | authorities |
|---|---|
| a `group` receipt, and the `object` receipts under it | `delivery`; and `energy/<class>` for each acquisition class an `object` receipt under it labels, where its leaf binds a label (§2.2), and for the class a labelled group payload signs for the group's total (§2.3) |
| a `claim` | its class's authority: `energy/` followed by its `energy_provenance` for `energy`, for example `energy/OnChipCounter`; `lawful-basis/declaration` or `lawful-basis/decision` by its `record`; its class name for every other class — `log-inclusion`, `soft-binding`, `soft-binding-span`, `binding-recovery`, `measurement`, `operating-point`, `stage`, `delivery-gap`, `reconstruct-session`, `consumption`, `transmission` and `reconciliation`. And `energy/` followed by its `energy_provenance` whenever its body carries that member, whatever its class |
| a `claim` | its class's authority: `energy/` followed by its `energy_provenance` for `energy`, for example `energy/OnChipCounter`; `lawful-basis/declaration` or `lawful-basis/decision` by its `record`; its class name for every other class — `log-inclusion`, `soft-binding`, `soft-binding-span`, `binding-recovery`, `measurement`, `operating-point`, `stage`, `delivery-gap`, `reconstruct-session`, `consumption`, `transmission`, `reconciliation` and `schedule`. And `energy/` followed by its `energy_provenance` whenever its body carries that member, whatever its class |
| the energy figure of a `reconstruct-session` claim whose `energy` is not `null` | `reconstruct-session`, and `energy/` followed by `energy.energy_provenance` |
| the energy figure of a `transmission` claim whose `energy` is not `null` | `transmission`, and `energy/` followed by `energy.energy_provenance` |
| a receipt of another WAI family that signs its own `signer_pubkey` and `signer_id` | `receipt`; and `energy/<class>` for each class its figures carry a label for |
@ -3648,7 +3677,9 @@ its keys is that party is the origin's decision, and its log records it:
a second item, as a session's is: it needs `transmission` and the energy authority of its
class, and the declaration stands without it. A sink with no return path confirms under
anchors provisioned before it was cut off, and a key log that reaches it over the bearer
anchors nothing ([bearer-receipts](bearer-receipts.md) §8).
anchors nothing ([bearer-receipts](bearer-receipts.md) §8). `schedule` lets a key state the
passes of a group it signed over an instance (§2.4.16); an origin gives it to its publishing
keys, as it gives `transmission`, and it is neither pinned-only nor `delivery`.
- **Log inclusion.** A confirmation says under whose authority the log's
evidence was attached. It does not check `log_receipt`: §2.4.7 is
unchanged.

View file

@ -0,0 +1,689 @@
# WAI Extension: Media Identification
> Status: Draft. What a file is, from its bytes alone: the formats they fit, each with the bytes
> that say so and how far they were followed; what a registered probe reads of it; signatures
> found inside it; and measures that hold when no format is recognised. It decodes nothing, and
> what it does not establish it leaves out. The reference implementation is the `identify` module
> of `wai-rs` (feature `identify`), which `wai identify` runs (operation `media.inspect`); the
> corpus is [`identify-conformance/`](../identify-conformance/), and its second implementation is
> `identify-conformance/identify_verify.py`.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
## 1. Scope
§1.1 `wai inspect` reads what an envelope holds. Most files a toolchain meets are not envelopes,
and the first question about one is what it is. Identification answers that for any file, and
it is the first step of working out how a piece of media was made.
§1.2 A result states only what the bytes establish. A claim comes with the offsets of the bytes
that make it, and a walk says where it stopped and why. A format is never guessed from a file
name, and no confidence figure is given: a result has a strength (§4), which says what was
checked. Where the bytes fit no format, the verdict is `unknown` and the measures (§8) are what
is known.
§1.3 What a container states is not what its samples hold. A track row (§5.1) reports what the file
says of a track, and no sample is read to confirm it. No payload is decrypted, and a protected track
is reported as protected and no further.
§1.4 This extension adds no capability and changes no dispatch (SPEC §4). A capability in a result
is the registered capability whose payload a format or track is: it says what `wai wrap` would
carry it under, not that a decoder here accepts it.
## 2. The result
§2.1 A result is a JSON object:
| member | value |
|---|---|
| `format` | `wai.identify/1` |
| `size` | the file's length in bytes |
| `verdict` | `identified`, `ambiguous`, `signature_only` or `unknown` (§4.2) |
| `candidates` | the formats the bytes fit, in the order of §4.3 |
| `probes_accepted` | the capabilities whose probe accepted the bytes (§3.5) |
| `embedded` | signatures found inside the file (§7), each `{at, id}` |
| `embedded_capped` | whether more were found than `embedded` holds |
| `measures` | §8 |
§2.2 A candidate is a JSON object:
| member | value |
|---|---|
| `id` | the format, as §5 and §6 name it, or the capability a probe alone accepted (§3.5) |
| `kind` | `wai`, `container`, `audio`, `image`, `elementary`, `playlist`, `text`, `compression` or `capture` |
| `strength` | `signature`, `structure` or `parameters` (§4.1) |
| `evidence` | the first 8 bytes-that-say-so, each `{at, len, is}`: `len` bytes at offset `at`, which read as `is` |
| `walked_to` | the offset a walk of the file's structures reached, or `null` where the format has no such walk |
| `stopped` | `null`, or `{at, why}`: where a walk stopped before the file's end (§3.3) |
| `facts` | what the format's headers state, by the section that defines it |
| `tracks` | §5.1 |
| `capabilities` | the capabilities it maps to (§1.4), sorted |
## 3. Common rules
§3.1 Integers are read as each section says. `u8`, `be16`, `be24`, `be32` and `be64` are big-endian,
`le16` and `le32` little-endian. All arithmetic of a result is integer arithmetic, but for the
sample
rate of §5.4; two implementations give the same result for the same bytes on any machine.
§3.2 **What is read.** The **head** is the first 1 MiB of the file (1,048,576 bytes), or all of it
if
shorter. The walks of §5.5, §5.9, §5.10, §5.13, §5.17, §5.18, §5.19 and §5.20, the table of §6 and
the
probes read the head alone. The other walks read the file where their lengths lead. The **scan** is
the first 16 MiB of the file (16,777,216 bytes), or all of it if shorter: §7 and §8 read it.
§3.3 **Walks.** A walk reads a format's structures in order and is bounded: at most 4,096 structures
(§5.6: 256), and 64 tracks. A walk that stops before the file's end says why, in `stopped`:
| `why` | meaning |
|---|---|
| `overrun` | a structure declares, or needs, more than the file holds |
| `invalid` | a structure's bytes are not what the format allows there |
| `crc` | a checksum the format defines does not hold |
| `limit` | the walk reached a bound of this extension, or the end of the head where it reads no further |
| `unknown_size` | a structure of unknown size, whose end only reading it would find (§5.4) |
`walked_to` and a stop's `at` are never past the end of the file: an offset past it is taken as the
file's length. A walk that reads the head alone and reaches its end while the file goes on has
stopped
there, `limit`.
§3.4 **Strings.** A string read from the file enters a result only through this filter: its first
64 bytes, each of 0x20 to 0x7E but `\` as itself, and any other byte as `\xNN` in lowercase
hexadecimal.
A file is untrusted input: a reader **MUST** treat these strings as data, and a tool that gives a
result to an agent **MUST** mark it as holding them.
§3.5 **Probes.** The probes of the table of [format-probes](format-probes.md) §2 that can name a
format from the bytes (`wai.image.jxs`, `wai.video.jxs`, `wai.video.hevc`, `wai.video.mvhevc`,
`wai.video.vvc`, `wai.video.lcevc`, `wai.audio.mpegh`, `wai.video.avs3`, `wai.audio.avs3`, in that
order) each read the head. The other two read a payload declared to be theirs, have no signature,
and say nothing of a file they are merely given, so they are not run. For each probe, in order:
- Where it accepts, the capability joins `probes_accepted`. The first candidate of kind `elementary`
that maps to that capability (or, for `wai.video.mvhevc`, to `wai.video.hevc`) has its strength
raised
to `parameters`, the capability added to its `capabilities`, and the probe's result set in its
`facts.probe` under the capability. With no such candidate, a candidate of kind `elementary` is
added
whose `id` is the capability, of strength `parameters`, with evidence `{at: 0, len: <head's
length>,
is: "probe"}`, that capability and the same `facts.probe`.
- Where it refuses with `no_signature`, nothing is recorded.
- Where it refuses otherwise, and a candidate of kind `elementary` already maps to that capability,
its
`facts.probe_refused` holds the code under the capability.
## 4. Strength and verdict
§4.1 **Strength** says how far a candidate's bytes were followed:
- `signature`: a format's fixed bytes are where the format puts them. Nothing else was checked, and
short ones occur by chance: `evidence` shows how many bytes it was.
- `structure`: what follows the signature is what the format's section requires of it.
- `parameters`: a probe of format-probes read the format's headers and accepted them.
§4.2 **The verdict** comes from the candidates at the highest strength present: `unknown` if there
are
no candidates; `ambiguous` if more than one; `signature_only` if one, of strength `signature`; else
`identified`. Two formats that both fit are both reported, and no ranking between them is invented.
§4.3 **Order.** Candidates are in order of strength, strongest first. Within a strength they keep
the
order of the sections that define them: §5.2 to §5.20, then §6's table in its order, then those a
probe alone added, in the order of §3.5.
## 5. Walks
§5.1 **Tracks.** A row of `tracks` holds `track` (the identifier the container gives, or `null`),
`media` (`video`, `audio`, `text` or `other`), `stated` (what the container calls it, through §3.4),
`codec` (the name of the table below, or `null` where this extension has no name for it), and
`width`,
`height`, `channels`, `sample_rate` and `protected`, each `null` where not stated. `protected`, on a
track the container says is protected, is `{scheme, original}`, each `null` where not stated.
A row's codec adds to its candidate's `capabilities` the capability of this table, where it has one:
| codec | capability |
|---|---|
| `avc` | `wai.video.avc` |
| `hevc` | `wai.video.hevc` |
| `vvc` | `wai.video.vvc` |
| `av1` | `wai.video.av1` |
| `vp8` | `wai.video.vp8` |
| `vp9` | `wai.video.vp9` |
| `evc` | `wai.video.evc` |
| `lcevc` | `wai.video.lcevc` |
| `jpeg-xs` | `wai.video.jxs` |
| `jpeg2000` | `wai.video.j2k` |
| `aac` | `wai.audio.aac` |
| `opus` | `wai.audio.opus` |
| `flac` | `wai.audio.flac` |
| `ac4` | `wai.audio.ac4` |
| `mpegh` | `wai.audio.mpegh` |
| `iamf` | `wai.audio.iamf` |
The names `mp3`, `vorbis`, `theora`, `speex`, `ac3`, `eac3`, `webvtt` and `ttml` map to none.
§5.2 **WAI envelopes** (`wai`, kind `wai`). The head begins `WAI1` or `WAI2`: evidence `magic:WAI1`
or
`magic:WAI2`, 4 bytes at 0, and `facts.version`. `le32` at 4 is the manifest's length. If it is
absent
the walk stops at 0 (`overrun`). If it exceeds 1 MiB the walk stops at 8 (`limit`); if the file
holds
less the walk stops at 8 (`overrun`); if the manifest is not JSON, at 8 (`invalid`). Evidence
`manifest`. For `WAI1`: `facts.capability` is `model_requirement.capability`, and `le32` after the
manifest is the payload's length, with evidence `payload_length`; where it or the payload does not
fit
the walk stops there (`overrun`) with `walked_to` there too, else `walked_to` is past the payload.
For
`WAI2`: `le16` after the manifest is `facts.renditions`, evidence `rendition_count`, and `walked_to`
is
past it. A walk that reaches this is `structure`. `wai inspect` reads the rest.
§5.3 **ISO base media** (`isobmff`, kind `container`). The four bytes at 4 are one of `ftyp`,
`styp`,
`moov`, `mdat`, `moof`, `free`, `skip`, `wide`, `sidx` or `pnot`. Boxes are walked from 0 to the end
of
the file. A box: `be32` size and four bytes of type. Every type byte is 0x20 to 0x7E or 0xA9, else
`invalid`. Size 0 runs to the end of the file; size 1 means `be64` at 8 is the size, which must be
at
least 16; any other size below 8 is `invalid`. Fewer bytes than a header needs is `overrun`. Each
box whose header
reads gives evidence `box:<type>`, 4 bytes at its offset plus 4, and its type joins `facts.boxes`
(distinct, first 16); a box that does not end within the file stops the walk there (`overrun`).
`moof`
boxes are counted in `facts.fragments`, present when there are any.
`ftyp` that fits: fewer than 8 content bytes is `invalid`; else `facts.major_brand` is its first
four,
and `facts.compatible_brands` the first 16 of the four-byte brands from its ninth. A major or
compatible brand `avif` or `avis` (among its first 4096 bytes) adds `wai.image.avif`.
`moov` that fits and is at most 64 MiB is read for `trak` boxes (above that, `facts.moov_not_read`
is
`true`). Inside a box, children are read as the top level is, stopping at the first that does not
fit.
A track needs `mdia`, and in it `hdlr` (the handler type is at content offset 8) and
`minf`/`stbl`/`stsd`
with a first entry (the entries start at content offset 8), else it has no row. `track` is from
`tkhd`:
`be32` at content offset 12 if its first byte is 0, else at 20. `media` from the handler: `vide`,
`auxv`,
`pict` is `video`; `soun` is `audio`; `text`, `subt`, `sbtl`, `clcp` is `text`; else `other`.
`stated` is the
entry's type. For `video`: `width` `be16` at entry content 24, `height` at 26, child boxes from 78.
For
`audio`: `channels` `be16` at 16, `sample_rate` `be32` at 24 shifted right 16, and, where `be16` at
8 is
0, child boxes from 28.
An entry of type `encv`, `enca`, `enct` or `encs` is protected: `protected.scheme` is from
`sinf`/`schm`
(content bytes 4 to 8) and `protected.original` from `sinf`/`frma` (its first four), and the codec
is
named from `original` where it is present. `mp4a` is named from its `esds`: `aac` for object type
0x40,
0x66, 0x67 or 0x68, `mp3` for 0x69 or 0x6B, else `null`. The object type is reached as MPEG-4
Systems
lays it out: after four version bytes, tag 3 and its size (one to four bytes of 7 bits, high bit set
on
all but the last); two bytes of identifier and one of flags, then 2 bytes if flag 0x80, a length
byte
and that many if 0x40, and 2 bytes if 0x20; tag 4 and its size; the next byte. Other entry types:
| entry | codec | entry | codec |
|---|---|---|---|
| `avc1` `avc2` `avc3` `avc4` `dvav` `dva1` | `avc` | `hvc1` `hev1` `dvh1` `dvhe` | `hevc` |
| `vvc1` `vvi1` | `vvc` | `av01` `dav1` | `av1` |
| `vp08` | `vp8` | `vp09` | `vp9` |
| `evc1` | `evc` | `lvc1` | `lcevc` |
| `jxsm` | `jpeg-xs` | `mjp2` | `jpeg2000` |
| `Opus` | `opus` | `fLaC` | `flac` |
| `ac-3` | `ac3` | `ec-3` | `eac3` |
| `ac-4` | `ac4` | `mhm1` `mhm2` `mha1` `mha2` | `mpegh` |
| `iamf` | `iamf` | `wvtt` | `webvtt` |
| `stpp` | `ttml` | | |
`structure`: at least two box headers read.
§5.4 **Matroska and WebM** (`matroska`, kind `container`). The head begins `1A 45 DF A3`: evidence
`ebml`, 4 bytes at 0. An element is an identifier of 1 to 4 bytes (its length in the leading zeros
of
its first byte, the marker kept) and a size of 1 to 8 bytes (the same, the marker removed; all ones
is unknown). The header must read in the head, with a known size: else the walk stops at 0
(`invalid` for an
element that does not parse, `overrun` for one that does not fit). `facts.doc_type` is its 0x4282,
through
§3.4, and `facts.doc_type_version` its 0x4287. The next element must be the Segment, 0x18538067:
evidence
`segment`; else the walk stops there, `overrun` if fewer than 5 bytes remain and `invalid`
otherwise. A
Segment with a known size that passes the file's end sets `facts.segment_overruns_by`, and the walk
ends at
the file's end.
Its children are walked to its end. A child that does not parse stops the walk (`overrun` if fewer
than 12
bytes remain, else `invalid`); an unknown size, `unknown_size`; a child that passes the file's end,
`overrun`.
Clusters (0x1F43B675) are counted in `facts.clusters`, present when there are any, one that passes
the file's
end included. Tracks (0x1654AE6B) of at most 1 MiB gives evidence `tracks` and a row for each 0xAE:
`track` 0xD7;
`media` from 0x83: 1 `video`, 2 `audio`, 17 `text`, else `other`; `stated` and `codec` from 0x86;
`width` and
`height` 0xB0 and 0xBA in 0xE0; `channels` 0x9F and `sample_rate` 0xB5 in 0xE1, an IEEE float of 4
or 8 bytes
taken toward zero; `protected` where 0x6D80 holds 0x6240 holding 0x5035. Unsigned values are
big-endian, of 1 to
8 bytes. The codec is `aac` for an identifier beginning `A_AAC`, else:
| identifier | codec | identifier | codec |
|---|---|---|---|
| `V_MPEG4/ISO/AVC` | `avc` | `V_MPEGH/ISO/HEVC` | `hevc` |
| `V_MPEGI/ISO/VVC` | `vvc` | `V_AV1` | `av1` |
| `V_VP8` | `vp8` | `V_VP9` | `vp9` |
| `A_OPUS` | `opus` | `A_FLAC` | `flac` |
| `A_VORBIS` | `vorbis` | `A_AC3` | `ac3` |
| `A_EAC3` | `eac3` | `A_MPEG/L3` | `mp3` |
| `S_TEXT/WEBVTT` | `webvtt` | | |
`structure`: at least one child read.
§5.5 **MPEG transport streams** (`mpeg-ts`, kind `container`). For each layout in order, packet size
and sync
offset (188, 0), (192, 4), (204, 0), and for each start from 0 below the packet size: 0x47 at the
sync offset of
five packets from there. The first that holds is the layout: evidence `sync`, 1 byte at its first
sync,
`facts.packet_size`. Packets are read while 188 bytes from a sync offset lie in the head. A packet
whose sync byte
is not 0x47 stops the walk (`invalid`), at the offset of that byte. PID is 13 bits from byte 1;
`facts.scrambled_packets`
counts those whose byte 3 has either of its top two bits set. With an adaptation field (byte 3 bit
0x20), the payload
is past its length byte and its length. With a payload (bit 0x10), PID 0 and each PID the program
table names are
gathered: a packet with the start bit (byte 1 bit 0x40) begins a section after its pointer field,
and a section's
length is 3 plus the 12 bits from its second byte. A section is used only if at least 12 bytes long
and its MPEG-2
checksum (polynomial 0x04C11DB7, from all ones, not reflected, over the section and its checksum) is
0.
The first section of table 0 on PID 0 is the program table: evidence `pat`, each entry of 4 bytes
from offset 8 to 4 from
its end (the first 64), with program number from the first two bytes and PID from the next 13 bits,
program 0 not being
counted. `facts.programs` is the number of programs. The first section of table 2 on each such PID:
evidence `pmt`.
Program information of the 12 bits from offset 10 starts at 12; streams follow, each a type byte, 13
bits of PID at the
next two bytes and 12 bits of descriptor length at the next two, then that many bytes of
descriptors. A row for each,
after the last packet: `track` the PID, `stated` `stream_type:0x<two lowercase hexadecimal digits>`,
`protected` where a
descriptor with tag 9 is among the program's or the stream's (then `{scheme: null, original:
null}`). Codec and media:
| type | codec | media | type | codec | media |
|---|---|---|---|---|---|
| 0x1B | `avc` | video | 0x24 | `hevc` | video |
| 0x33 | `vvc` | video | 0x0F 0x11 | `aac` | audio |
| 0x03 0x04 | `mp3` | audio | 0x81 | `ac3` | audio |
| 0x87 | `eac3` | audio | 0x01 0x02 0x10 | `null` | video |
Any other type has codec `null` and media `other`. `facts.packets_read` counts the packets. If no
sync byte stopped it,
and the walk's end is before the file's: `limit` if the head is shorter than the file, else
`overrun`. Always `structure`.
§5.6 **Ogg** (`ogg`, kind `container`). The head begins `OggS` and a zero byte. Pages from 0: a
27-byte header beginning the
same, a table of as many bytes as its byte 26, and a body of as many bytes as that table sums to. A
page that does not fit is
`overrun`; a header that does not begin so, `invalid`; a checksum (`le32` at 22) that does not hold
over the page with its checksum
bytes zero, under the polynomial 0x04C11DB7 from zero, not reflected, `crc`. At most 256 pages. Each
gives evidence `page`, 1 page
long, at its offset. Byte 5 bit 0x02 starts a stream: a row with `track` `le32` at 14, read from the
body: `OpusHead` (`stated`
`OpusHead`, `opus`, audio, `channels` byte 9, `sample_rate` `le32` at 12); `\x01vorbis` (`stated`
`vorbis`, `vorbis`, audio, `channels` byte
11, `sample_rate` `le32` at 12); `\x7fFLAC` (`stated` `FLAC`, `flac`, audio); `\x80theora` (`stated`
`theora`, `theora`, video);
`Speex` and three spaces (`stated` `Speex`, `speex`, audio); else `stated` is its first 8 bytes,
media `other`. `facts.pages_read`.
`structure`: two pages, or one that ends at the file's end.
§5.7 **ID3 and FLAC** (`flac`, kind `audio`). An **ID3v2 tag** at 0, for this and §5.9: `ID3`, ten
header bytes, bytes 3 and 4 not 0xFF,
and bytes 6 to 9 each below 0x80. Its length is 10 plus those four as 7 bits each, plus 10 where
byte 5 has bit 0x10. A head that begins
`ID3` with a header that is not one means neither walk applies. `fLaC` at 0, or after the tag:
evidence `fLaC`, and `wai.audio.flac`.
Blocks follow: a byte (bit 0x80: last; low 7 bits: type), then `be24` length. The first must be type
0 of length 34, else `invalid`:
evidence `streaminfo`, 34 bytes at its body, and `facts.sample_rate` (20 bits from body byte 10),
`facts.channels` (3 bits, plus 1),
`facts.bits_per_sample` (5 bits, plus 1) and `facts.samples` (36 bits). A block past the file's end
is `overrun`. After the last block, 2 bytes
0xFF and 0xF8 or 0xF9 are a frame's sync: evidence `frame_sync`, and `structure`. Other bytes there
stop the walk (`invalid`), and no bytes
(`overrun`). `walked_to` is the offset after the last block read.
§5.8 **RIFF** (kind per form). The head begins `RIFF`, with at least 12 bytes. The form at 8: `WAVE`
is `wave`, audio; `AVI ` is `avi`, container;
`WEBP` is `webp`, image; any other is `riff`, container. Evidence `form:<form>`, 4 bytes at 8, and
`facts.form`. The declared end is `le32` at 4 plus 8,
in `facts.declared_size` where it is not the file's size; the walk ends at the lesser of it and the
file's size. Chunks from 12: four bytes of
type and `le32` length, padded to an even length. A header past the end stops the walk (`overrun`)
unless the walk is exactly at the end or beyond it;
each header read gives evidence `chunk:<type>`, and its type joins `facts.chunks` (distinct, first
16). A chunk's content past the end stops the walk there
(`overrun`).
For `wave`: `fmt ` gives, from at most its first 64 bytes, `facts.format_tag` `le16` at 0 (where
that is 0xFFFE, `le16` at 24 instead),
`facts.channels` `le16` at 2, `facts.sample_rate` `le32` at 4 and `facts.bits_per_sample` `le16` at
14, each `null` where absent; `data` gives `facts.data_bytes`.
For `webp`: `VP8 ` with bytes 3 to 5 `9D 01 2A` gives `facts.coding` `lossy`, `facts.width` and
`facts.height` from `le16` at 6 and 8 in their low 14 bits;
`VP8L` with first byte 0x2F and 5 bytes gives `lossless`, and, in `le32` at 1, `width` its low 14
bits plus 1 and `height` the next 14 plus 1; `VP8X` with 10 bytes gives
`extended`, and `width` and `height` the 24-bit little-endian values at 4 and 7, each plus 1.
`walked_to` is the walk's end, at most the end above. `structure`: one chunk read.
§5.9 **MPEG audio** (`mpeg-audio`, kind `audio`). From 0 or after an ID3v2 tag (§5.7). A frame:
0xFF; 3 high bits of the next byte set; version 2 bits (0: 2.5, 2: 2,
3: 1), layer 2 bits (1: III, 2: II, 3: I), the bitrate index in byte 2's high 4 bits (not 0 or 15),
rate index in the next 2 (not 3), padding the next bit; byte 3's high 2 bits are
the channel mode. The version value 1 and layer value 0 are not a frame. Rate is 44100, 48000 or
32000 by index, halved for version 2 and quartered for 2.5. The bitrate in kbit/s, by
index from 1:
| | indexes 1 to 14 |
|---|---|
| version 1, layer I | 32 64 96 128 160 192 224 256 288 320 352 384 416 448 |
| version 1, layer II | 32 48 56 64 80 96 112 128 160 192 224 256 320 384 |
| version 1, layer III | 32 40 48 56 64 80 96 112 128 160 192 224 256 320 |
| version 2 or 2.5, layer I | 32 48 56 64 80 96 112 128 144 160 176 192 224 256 |
| version 2 or 2.5, layers II and III | 8 16 24 32 40 48 56 64 80 96 112 128 144 160 |
Frame length: layer I, (12000 × kbit/s ÷ rate + padding) × 4; layer III of version 2 or 2.5, 72000 ×
kbit/s ÷ rate + padding; otherwise 144000 × kbit/s ÷ rate + padding; division
is integer division. Nothing is a candidate unless the first bytes are a frame and at least one
whole frame is walked. Evidence `id3v2`, 3 bytes at 0, where a tag is there; `frame_header`, 4 bytes
at the first
frame. `facts.version` (`1`, `2` or `2.5`), `facts.layer`, `facts.bitrate_kbps`, `facts.sample_rate`
and `facts.channels` (1 for channel mode 3, else 2), of the first frame. Frames walk through the
head. A frame must
keep the first's version, layer and rate, else the walk stops there (`overrun` if fewer than 4 bytes
remain, else `invalid`). Exactly 128 bytes left beginning `TAG`: evidence `id3v1`, 3 bytes, and the
walk ends after them. A frame past the
file's end is `overrun`, past the head's alone `limit`. `facts.frames_read`. `structure`: three
frames.
§5.10 **ADTS** (`adts`, kind `audio`). A frame: 0xFF; byte 1 with `byte & 0xF6 == 0xF0`; byte 2's
high 2 bits plus 1 the audio object type, its next 4 the rate index (below 13: 96000, 88200, 64000,
48000, 44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000, 7350), and channel configuration from its low bit and byte 3's high 2;
frame length 13 bits from byte 3's low 2, at least 7. Nothing is a candidate unless the head begins
with a frame and at least one whole
frame is walked. Evidence `frame_header`, 7 bytes at 0, `wai.audio.aac`, and
`facts.audio_object_type`, `facts.sample_rate`, `facts.channel_configuration` of the first. Frames
walk as §5.9's do, keeping type and rate (`overrun` under 7 bytes), and with
the same limits. `facts.frames_read`. `structure`: three frames.
§5.11 **PNG** (`png`, kind `image`). The head begins `89 50 4E 47 0D 0A 1A 0A`: evidence
`signature`, and `wai.image.png`. Chunks from 8: `be32` length, four ASCII letters of type (else
`invalid`), data, 4-byte checksum. The first must be `IHDR` of length 13, else
`invalid`; its checksum (polynomial 0xEDB88320 reflected, from all ones, inverted at the end; over
type and data) must hold, else `crc`. Then evidence `ihdr`, 25 bytes, `facts.width`, `facts.height`,
`facts.bit_depth`, `facts.colour_type`, `facts.interlace`, and `structure`.
`acTL` sets `facts.animated`; `cICP` with 4 bytes readable sets `facts.cicp` to them as a list. A
chunk past the file's end, or the end of the file before `IEND`, is `overrun`. `IEND` gives evidence
`iend`, 12 bytes at its offset, and the walk ends after it. `facts.chunks_read`.
Only `IHDR`'s checksum is computed.
§5.12 **JPEG** (`jpeg`, kind `image`). The head begins `FF D8 FF`: evidence `soi`, 2 bytes at 0, and
`wai.image.jpeg`. Markers from 2: 0xFF and a code, a code of 0xFF being a fill byte that advances
one. Fewer than 2 bytes is `overrun`; a first byte not 0xFF is `invalid`. A code of 0xDA gives
evidence `sos`, 2 bytes, and ends the walk there. Others: `be16` length, at least 2 (else
`invalid`), `overrun` if it does not read or the segment passes the file's end. 0xE0 to 0xEF name an
application segment from its first 32 bytes: `JFIF\0` `jfif`, `Exif\0\0` `exif`, `ICC_PROFILE\0`
`icc`, `MPF\0` `mpf`,
the XMP namespace (`http://ns.adobe.com/xap/1.0/\0`) `xmp`, `Adobe` `adobe`, else `other`; <!-- allow-name: the APP14 segment's identifier and the XMP namespace, byte signatures a JPEG carries -->
`facts.applications` holds them, distinct, first 8. Codes 0xC0 to 0xC3, 0xC5 to 0xC7, 0xC9 to 0xCB
and 0xCD to 0xCF are a frame header, needing 6 bytes (else `overrun`): evidence `sof`, the segment's
bytes, `facts.process` (0xC0
`baseline`, 0xC1 `extended`, 0xC2 `progressive`, 0xC3 `lossless`, 0xC9 to 0xCB `arithmetic`, else
`hierarchical`), `facts.precision`, `facts.height`, `facts.width`, `facts.components`, and
`structure`.
§5.13 **GIF** (`gif`, kind `image`). The head begins `GIF87a` or `GIF89a`: evidence
`signature:<it>`, 6 bytes. Fewer than 13 bytes stops the walk at 6. `facts.width` and `facts.height`
from `le16` at 6 and 8; a global table of 3 × 2^(n+1) bytes follows byte 10 flag 0x80 (n its low 3
bits). Blocks from there: 0x3B the
trailer (evidence `trailer`, 1 byte, and the walk ends after it); 0x21 an extension, sub-blocks from
2 on; 0x2C an image, 10 bytes (a local table likewise from its byte 9), one more byte, then
sub-blocks, evidence `image`, 10 bytes, for the first; any other byte is `invalid`. Sub-blocks: a
count byte and that many, ending after a count
of 0. Bytes not in the head stop the walk: `limit` where the head is shorter than the file, else
`overrun`. `facts.frames_read`. `structure`: one image, or the trailer.
§5.14 **IVF** (`ivf`, kind `container`). The head begins `DKIF`: evidence `dkif`, 4 bytes. Under 12
bytes stops at 0 (`overrun`); a version (`le16` at 4) not 0 or a header length (`le16` at 6) under
32 stops at 0 (`invalid`). A row: media `video`, `stated` the four bytes at 8, codec `av1` for
`AV01`, `vp9` for `VP90`, `vp8` for
`VP80`, `width` and `height` `le16` at 12 and 14. Frames from the header length: `le32` size, 8
bytes of time, then that many. `overrun` where one does not fit. `facts.frames_read`. `structure`:
one frame.
§5.15 **YUV4MPEG2** (`y4m`, kind `elementary`). The head begins `YUV4MPEG2 `: evidence `signature`,
10 bytes. A line feed must end the header within 256 bytes: else stop at 0 (`overrun` if the file
ends sooner, else `invalid`). Space-separated tokens: `W` width, `H` height, `C` colour (default
`420`), `F` frame rate; `facts.width`, `facts.height`, `facts.colour`
and `facts.frame_rate`. A colour is a base, and optionally `p` and a decimal depth, 2 bytes a sample
above 8. Bases: `420`, `420jpeg`, `420mpeg2`, `420paldv` (w·h + 2·⌈w/2⌉·⌈h/2⌉ samples a frame);
`422` (w·h + 2·⌈w/2⌉·h); `444` (3·w·h); `mono` (w·h). Any other stops the walk (`invalid`), as do
absent dimensions. Frames: a line beginning `FRAME`, within 256 bytes, then a frame's bytes.
`facts.frames_read`.
`structure`: one frame.
§5.16 **FLV** (`flv`, kind `container`). The head begins `FLV` and 0x01: evidence `signature`, 4
bytes. `be32` at 5, the data offset, must be at least 9, else stop at 0 (`overrun` under 9 bytes,
else `invalid`). Tags from that offset plus 4: 11 header bytes (type in byte 0's low 5 bits, one of
8, 9 or 18, else `invalid`; `be24` length at 1), then the body, then a `be32` that must be 11 plus
the length (else `invalid`; absent, `overrun`).
The first audio tag (type 8, with a body) gives a row: media `audio`, `stated` `sound_format:<n>`
for its first body byte's high 4 bits, codec `mp3` for 2 and `aac` for 10. The first video tag (type
9, with a body) gives a row: where its first body byte has bit 0x80, `stated` is the next four
bytes, with codec `avc` for `avc1`, `hevc` for `hvc1`, `av1` for `av01`, `vp9` for `vp09`; otherwise
`stated` is `codec_id:<n>` for that byte's low 4 bits, with `avc` for 7. The video row comes first.
`facts.tags_read`. `structure`: one tag.
§5.17 **AV1 OBU streams** (`av1-obu`, kind `elementary`). An OBU: a header byte with bits 0x81 clear
and 0x02 set; type its bits 3 to 6, one of 1 to 8 or 15; an extension byte where bit 0x04 is set; a
LEB128 size of at most 8 bytes; that many bytes. The first must be type 1 or 2. They are walked
through the head. A header that does not parse stops the walk at it (`limit` if the head is shorter
than the file and fewer than 8 bytes remain, else `invalid`); one past the file's end, `overrun`;
one past the head's alone, `limit`. The first type 1 gives evidence `sequence_header`, and is read
as AV1 §5.5: `facts.profile`, `facts.level` (the first operating point's `seq_level_idx`),
`facts.width` and `facts.height` (`max_frame_width_minus_1` and `max_frame_height_minus_1`, plus 1).
`facts.units_read`, and `wai.video.av1`. A candidate only if a sequence header reads and at least
two OBUs were walked, and then `structure`: two bytes that merely fit are no claim of AV1.
§5.18 **HLS** (`hls`, kind `playlist`). After an optional UTF-8 byte order mark, `#EXTM3U` and a
line end or the end of the head: evidence `extm3u`, 7 bytes at it. Lines (ended by a line feed, less
a final carriage return) are read: any beginning `#EXT-X-` is a tag; `#EXT-X-STREAM-INF:` counts
`facts.variants`, and the first with `CODECS="` gives `facts.codecs`; `#EXTINF:` counts
`facts.segments`; `#EXT-X-MAP:` counts `facts.maps`; `#EXT-X-ENDLIST` sets `facts.ended`;
`#EXT-X-VERSION:` gives `facts.version` where it is a decimal integer; and the first `#EXT-X-KEY:`
or `#EXT-X-SESSION-KEY:` whose `METHOD=` (to the next comma) is not `NONE` gives `facts.key_method`.
`facts.playlist` is `multivariant` with variants, else `media` with segments, else `other`. Every
one of these facts is present; `structure`: at least one tag.
§5.19 **DASH** (`dash`, kind `playlist`). After an optional byte order mark and any white space,
`<?...?>` and `<!--...-->`, then `<MPD` and white space or `>`: evidence `mpd`, 4 bytes at it.
`urn:mpeg:dash:schema:mpd:2011` anywhere in the head gives evidence `namespace` and `structure`.
`facts.type` is the value of ` type="` in the root tag up to its first `>`. `facts.periods`,
`facts.adaptation_sets`, `facts.representations` and `facts.content_protection` count `<Period`,
`<AdaptationSet`, `<Representation` and `<ContentProtection`; `facts.codecs` holds the distinct
values of `codecs="`, the first 16. It is no XML parser: these are substrings.
§5.20 **Annex B streams** (kind `elementary`). The head holds at least two zero bytes, then 0x01,
and nothing before them. Units are split as format-probes §2 (its Annex B byte streams) does, the
first 4,096 (trailing zero bytes removed). If the byte after the first prefix is 0xBA or 0xB3, §6
has the format, and no reading applies. Each reading below needs every unit's header to hold and its
parameter sets in order, each before the first coded picture of that reading; a stream two readings
fit is reported as both, and no reading is preferred. A reading is `structure`, with
`facts.units_read`. Where the head is shorter than the file, or 4,096 units were read, it stops at
the last unit (`limit`). `walked_to` is `null`.
- `avc-annexb`: one header byte, bit 0x80 clear, type its low 5 bits and 1 to 21; types 5, 7 and 8
have nonzero bits 0x60. Order 7, 8; coded pictures are types 1 to 5. Its first type 7 is read as
ITU-T H.264 §7.3.2.1.1, with emulation prevention removed: a `profile_idc` other than 66, 77, 88,
100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134 or 135 or a failed reading rules the
reading out. Evidence `nal:sps`; facts `profile_idc`, `constraint_flags`, `level_idc`,
`chroma_format_idc`, `width` and `height` less cropping; `wai.video.avc`.
- `hevc-annexb`: two header bytes, bit 0x80 of the first clear, the low 3 bits of the second not
zero, type the first's bits 1 to 6 and one of 0 to 9, 16 to 21 or 32 to 40. Order 32, 33, 34;
coded pictures are types 0 to 9 and 16 to 21. Evidence `nal:sps`; `wai.video.hevc`.
- `vvc-annexb`: two header bytes, bits 0xC0 of the first clear, the low 3 bits of the second not
zero, type the second's high 5 bits and at most 25. Order 15, 16; coded pictures are types 0 to
10. Evidence `nal:sps`; `wai.video.vvc`.
## 6. Formats known by their signature
§6.1 A format of this table is a candidate where every one of its fixed bytes is at its offset in
the head: `strength` `signature`, evidence `signature` for each, and the capabilities it names.
Bytes are in hexadecimal, each group at the offset before its colon. A format with two rows is
either.
| id | kind | bytes | capability |
|---|---|---|---|
| `aiff` | audio | `0:464f524d 8:41494646` | |
| `aiff` | audio | `0:464f524d 8:41494643` | |
| `caf` | audio | `0:636166660001` | |
| `wavpack` | audio | `0:7776706b` | |
| `amr` | audio | `0:2321414d520a` | |
| `amr-wb` | audio | `0:2321414d522d57420a` | |
| `jpegxl` | image | `0:ff0a` | `wai.image.jxl` |
| `jpegxl` | image | `0:0000000c4a584c200d0a870a` | `wai.image.jxl` |
| `jp2` | image | `0:0000000c6a5020200d0a870a` | `wai.image.jp2` |
| `j2k` | image | `0:ff4fff51` | `wai.image.jp2` |
| `tiff` | image | `0:49492a00` | |
| `tiff` | image | `0:4d4d002a` | |
| `bigtiff` | image | `0:49492b00` | |
| `bigtiff` | image | `0:4d4d002b` | |
| `openexr` | image | `0:762f3101` | |
| `dpx` | image | `0:53445058` | |
| `dpx` | image | `0:58504453` | |
| `radiance` | image | `0:233f52414449414e43450a` | |
| `radiance` | image | `0:233f524742450a` | |
| `mxf` | container | `0:060e2b34020501010d0102` | |
| `asf` | container | `0:3026b2758e66cf11a6d900aa0062ce6c` | |
| `mpeg-ps` | container | `0:000001ba` | |
| `mpeg-video` | elementary | `0:000001b3` | |
| `webvtt` | text | `0:574542565454` | |
| `gzip` | compression | `0:1f8b08` | |
| `zstd` | compression | `0:28b52ffd` | |
| `xz` | compression | `0:fd377a585a00` | |
| `bzip2` | compression | `0:425a68` | |
| `zip` | compression | `0:504b0304` | |
| `tar` | compression | `257:7573746172` | |
| `pcap` | capture | `0:d4c3b2a1` | |
| `pcap` | capture | `0:a1b2c3d4` | |
| `pcap` | capture | `0:4d3cb2a1` | |
| `pcap` | capture | `0:a1b23c4d` | |
| `pcapng` | capture | `0:0a0d0d0a` | |
## 7. Signatures inside a file
§7.1 A reverse engineer's first question about a blob is what it holds. The scan is searched for
each of these fixed bytes, none shorter than 8 bytes, so that one in 16 MiB of noise is improbable;
each hit past the file's beginning gives `{at, id}`, `at` being where its structure begins, `back`
bytes before them. A hit whose structure would begin at or before the file's beginning is not
reported. Hits are in the order their bytes are found, and within one offset in this table's order.
At most 32 are reported, and `embedded_capped` says whether another was found. A hit says only that
the bytes are there.
| id | bytes | back |
|---|---|---|
| `png` | `89504e470d0a1a0a` | 0 |
| `jp2` | `0000000c6a5020200d0a870a` | 0 |
| `jpegxl` | `0000000c4a584c200d0a870a` | 0 |
| `mxf` | `060e2b34020501010d0102` | 0 |
| `asf` | `3026b2758e66cf11a6d900aa0062ce6c` | 0 |
| `ogg` | `4f67675300020000000000000000` | 0 |
| `flac` | `664c614300000022` | 0 |
| `flac` | `664c614380000022` | 0 |
| `isobmff` | `6674797069736f6d` | 4 |
| `isobmff` | `6674797069736f32` | 4 |
| `isobmff` | `6674797069736f35` | 4 |
| `isobmff` | `6674797069736f36` | 4 |
| `isobmff` | `667479706d703431` | 4 |
| `isobmff` | `667479706d703432` | 4 |
| `isobmff` | `667479704d344120` | 4 |
| `isobmff` | `667479704d345620` | 4 |
| `isobmff` | `6674797071742020` | 4 |
| `isobmff` | `6674797061766966` | 4 |
| `isobmff` | `6674797068656963` | 4 |
| `isobmff` | `66747970636d6663` | 4 |
| `isobmff` | `6674797064617368` | 4 |
| `isobmff` | `667479706d736468` | 4 |
## 8. Measures
§8.1 `measures` holds, over the scan: `scanned`, its length; `distinct_bytes`, how many of the 256
values occur; `zero_bytes`, how many are zero; `chi_square`, the floor of `256 · Σc² / n` less `n`,
where `c` are the counts of the values and `n` the length, and 0 for an empty scan; `text` (§8.2);
`windows` and `uniform_windows` (§8.3); and `periods` (§8.4).
§8.2 `text` is true where the scan is not empty, is valid UTF-8 (a sequence the scan's end cuts
short allowed), and holds no byte below 0x20 but 0x09, 0x0A, 0x0C and 0x0D, and no 0x7F.
§8.3 A **window** is each whole 4096 bytes of the scan from its start. One is **uniform** when the
sum of its counts squared is at most 70,496: its chi-square against a uniform spread is then at most
310, about the 99th percentile for 255 degrees of freedom. A compressed payload and an encrypted one
both tend to be uniform; this measure cannot tell them apart, and says only that the bytes show no
structure in their values.
§8.4 **Repeats.** Over the first 256 KiB of the scan (`n` bytes), for each lag `l` from 1 to `L +
1`, where `L` is the lesser of 4096 and `⌊n/4⌋`: `m = n − l`; `matches` counts the `i` below `m`
with byte `i` equal to byte `i + l`; `expected` is the floor of `m · Σc² / n²`, at least 1, over
this sample's counts; the excess `e` is `matches − expected`; and `z` is, where `e` is positive, the
floor of the square root of the floor of `e² / expected`, otherwise 0. With `L` below 2 there are no
periods.
The floor is 8 times the median of `|e|` over those lags, the element at index `⌊k/2⌋` of the `k` of
them sorted: data that repeats exactly is not independent from lag to lag, and its noise is many
times what `z` assumes. A lag from 2 to `L`, in increasing order, is reported as `{lag, z}` where
all of these hold, at most 8: `z` is at least 8; `e` is at least the floor; `e` exceeds twice the
greatest of its neighbours' excess and 0; if `2l ≤ L + 1`, `z` at `2l` is at least 8; and it is no
multiple of a lag already reported. A repeat is a record size, a sample width or a table's stride,
found with no knowledge of the format.
## 9. Security
§9.1 Identification reads files nobody vouches for. A conforming implementation **MUST NOT** panic,
read outside the file, allocate more than the file's bounded windows and a bounded number of
structures, or follow a URI in a playlist. The bounds of §3 are what make the cost of a hostile file
bounded.
§9.2 A result can hold strings a file's author chose (§3.4). The operation's agent door marks its
results `untrusted_content`.
§9.3 An implementation **MUST NOT** decrypt, or try to recover the keys of, a protected track. It
reports the scheme the file states and stops.
## 10. Conformance
§10.1 The corpus [`identify-conformance/`](../identify-conformance/) holds files written by real
muxers and encoders from synthetic inputs (`ext/`), generated cases and negative controls (`gen/`),
and `vectors.json`, each file's digest and whole result. `prober.json` is what an independent media
prober reads of `ext/`, the reference checked against it wherever both state a value. A conforming
implementation gives each file its result exactly.

View file

@ -405,6 +405,14 @@ def step6(c, cid, passed, fetcher, exact_algs):
if r.reason == "reconciliation_objects_not_held":
raise Refused("context_not_held", 6)
raise
elif cls == "schedule":
# carousel §4.3: against the group of an item its subject names and the object
# receipts of the items listed under it here, with no envelope bytes.
import carousel_verify as KV
v = next((v for v in passed if JV.link(v["g"]).hex() == c["subject"]), None)
if v is None:
raise Refused("context_not_held", 6)
KV.check_schedule(c, v["g"], [(w["o"], None) for w in passed if JV.link(w["g"]).hex() == c["subject"]])
except CV.Reject as r:
raise Refused(r.reason, r.step)

View file

@ -0,0 +1,21 @@
# identify-conformance — media identification
The corpus of [`extensions/media-identify.md`](../extensions/media-identify.md) §10.
**Feature equivalence.** A conforming implementation, given a vector's file, gives the whole result
`vectors.json` states for it: every candidate with its evidence, where its walk stopped and why, its
facts, tracks and capabilities; what was embedded; and the measures. A vector with `repeat` is its
file that many times over, for what happens past the head and past the scan (§3.2).
- `ext/` — files written by real muxers and encoders, from test patterns and sine waves. The
protected file uses an all-zero test key.
- `gen/` — what `wai_identify_vectors gen` writes: generated cases and negative controls, each saying
in `vectors.json` what it tests, many of them a change to a file of `ext/`.
- `vectors.json` — every vector's file, SHA-256, origin and whole result.
- `prober.json` — what an independent media prober reads of `ext/`: each file's format and streams.
`identify_verify.py` holds the result to it wherever both state a value, so the reference and the
second implementation agreeing is not all that is checked.
- `identify_verify.py` — the second implementation, written from the extension.
**Provenance.** `ext/` and `prober.json` are written by `tools/make_external.sh`, with open tools; it
is not run in CI, because it needs them. `tools/oracle.py` writes `prober.json`.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:mpeg:dash:schema:mpd:2011"
xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
profiles="urn:mpeg:dash:profile:isoff-live:2011"
type="static"
mediaPresentationDuration="PT0.3S"
maxSegmentDuration="PT5.0S"
minBufferTime="PT0.6S">
<ProgramInformation>
</ProgramInformation>
<ServiceDescription id="0">
</ServiceDescription>
<Period id="0" start="PT0.0S">
<AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="10/1" maxWidth="32" maxHeight="32" par="1:1">
<Representation id="0" mimeType="video/mp4" codecs="avc1.42c00a" bandwidth="60373" width="32" height="32" sar="1:1">
<SegmentTemplate timescale="10240" initialization="i.m4s" media="s.m4s" startNumber="1">
<SegmentTimeline>
<S t="0" d="3072" />
</SegmentTimeline>
</SegmentTemplate>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View file

@ -0,0 +1,8 @@
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:0
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:0.300000,
s0.ts
#EXT-X-ENDLIST

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,010 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

View file

@ -0,0 +1,4 @@
WEBVTT
00:00.000 --> 00:01.000
A line.

View file

@ -0,0 +1,5 @@
YUV4MPEG2 W16 H16 F10:1 Ip A1:1 C420jpeg XYSCSS=420JPEG
FRAME
JJJJJJJJ))))jjェェJJJJJJJJ))))jjェェJJ&&66JJ&&66JJ&&66JJ&&66JJ鋳鋳&&鋳JJ鋳鋳&鋳6QQ 鋳@@鋳鋳66QQ 鋳@@鋳鋳66QQ 鋳@鋳鋳鋳6嘆Q 鋳@鋳鋳鋳6嘆Q 鋳@@鋳鋳66QQ 鋳@@鋳鋳66QQ 0鋳@鋳&鋳6QQ 00鋳鋳&&鋳ンンンン<EFBE9D>ハヲン覧梨恍貨覧梨恍貨<E6818D>MOボJZw5d<35>55瓜w5Li55_Zw5d<35>55瓜w`AOボJヲヲヲヲnn゙ヲ演榎|妖ヲ演榎|妖ヲ於ウー困ェ<E59BB0>チлチチd<EFBE81>チ」榁チ挺愧лチチd<EFBE81>イー困ェFRAME
JJJJJJJJ))))jjェェJJJJJJJJ))))jjェェJJ&&66JJ&&66JJ&&66JJ&&66JJ鋳鋳&&鋳JJ鋳鋳&鋳6QQ 鋳@@鋳鋳66QQ 鋳@@鋳鋳66QQ 鋳@鋳鋳鋳6嘆Q 鋳@鋳鋳鋳6嘆Q 鋳@@鋳鋳66QQ 鋳@@鋳鋳66QQ 0鋳@鋳&鋳6QQ 00鋳鋳&&鋳ンンンン<EFBE9D>ハヲン覧梨恍貨覧梨恍貨<E6818D>MOボJZw5d<35>55瓜w5Li55_Zw5d<35>55瓜w`AOボJヲヲヲヲnn゙ヲ演榎|妖ヲ演榎|妖ヲ於ウー困ェ<E59BB0>チлチチd<EFBE81>チ」榁チ挺愧лチチd<EFBE81>イー困ェFRAME
JJJJJJJJ))))jjェェJJJJJJJJ))))jjェェJJ&&66JJ&&66JJ&&66JJ&&66JJ鋳鋳&&鋳JJ鋳鋳&鋳6QQ 鋳@@鋳鋳66QQ 鋳@@鋳鋳66QQ 鋳@鋳鋳鋳6嘆Q 鋳@鋳鋳鋳6嘆Q 鋳@@鋳鋳66QQ 鋳@@鋳鋳66QQ 0鋳@鋳&鋳6QQ 00鋳鋳&&鋳ンンンン<EFBE9D>ハヲン覧梨恍貨覧梨恍貨<E6818D>MOボJZw5d<35>55瓜w5Li55_Zw5d<35>55瓜w`AOボJヲヲヲヲnn゙ヲ演榎|妖ヲ演榎|妖ヲ於ウー困ェ<E59BB0>チлチチd<EFBE81>チ」榁チ挺愧лチチd<EFBE81>イー困ェ

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,2 @@
<EFBFBD>

Binary file not shown.

View file

@ -0,0 +1,2 @@
2
<1D>葮

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,2 @@
<1D><><EFBFBD>22<><32><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

Binary file not shown.

View file

@ -0,0 +1,2 @@
<1D>葮22<><32><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,2 @@
<1D>葮

Binary file not shown.

View file

@ -0,0 +1,2 @@
<1D>葮2

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more