open-standards/wai/wai-rs/src/container.rs
Transaction Science ddc695d23f wai: read a manifest within a bound of its values and length (SPEC §2)
read_manifest and read_manifest_v2 count the manifest's values in the first
pass (refusing past 2^22, manifest_too_many_values), then build one JSON
tree at the sizes counted and move its members into the manifest types, so
no member is held twice as serde's flatten holds it. Every reference reader
(Wai, WaiMulti, the HTTP head) reads through them; the writers refuse what
they refuse.

tests/manifest_parse_memory.rs measures the peak with a counting allocator
against 128 bytes a value plus one a byte; tests/manifest_reader_equivalence.rs
holds the reader to serde's derived reader over every committed envelope and
1488 mutations.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 22:57:23 -04:00

4888 lines
249 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! The WAI envelope: magic "WAI1" | u32 manifest_len | manifest JSON |
//! payload bytes. The manifest declares media, intent, the capability
//! REQUIREMENT (a name the sink resolves against its registered codecs)
//! and an optional fallback.
//!
//! For a *floor/zeroth* capability the name is enough: its bytes have
//! standardized semantics (a JXL bitstream decodes to the same pixels on
//! any conformant libjxl), so naming the codec pins the result. For a
//! capability that **decodes against a parameter set** (SPEC §3.1) the name is
//! not enough: the payload is meaningless except against a specific set of
//! networks and tables, and one capability name can front different sets that
//! reconstruct differently. So the manifest MAY carry a *pin* ([`Prior`]): the
//! SHA-256 of the whole parameter set, in a declared form, plus how the decode is
//! made reproducible. The pin names identity, not location — the envelope never
//! carries the set — and a sink verifies it before it decodes
//! ([`Wai::dispatch_verified`], [`WaiMulti::pick`]; the digest check itself is
//! [`crate::pin`]).
//!
//! Every optional manifest member is skipped when absent, and members this
//! revision does not define are kept (`extra`), so an envelope round-trips
//! through these types without losing what it carried.
//!
//! The manifest types are `#[non_exhaustive]`: build them with their
//! constructors and `with_*` builders ([`Manifest::new`], [`Prior::new`],
//! [`RenditionMeta::new`], …), so a member a later revision adds does not break
//! code that builds them.
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::fs;
use std::path::Path;
use crate::codecs::{DeclaredTier, DeterminismTier, ParameterSetShape};
pub const MAGIC: &[u8; 4] = b"WAI1"; // v1.0 single-payload
pub const MAGIC_V2: &[u8; 4] = b"WAI2"; // v1.1 multi-rendition
/// The longest manifest, in bytes, a reader must accept (SPEC §2, *Reader limits*):
/// 2^27 (128 MiB). It is sized to the largest manifest a conforming layer has: a `prev`
/// (staged-delivery §3) that names a different producer for each of the 2^20 units a
/// `WIR1` layer may cover (staged-delivery Appendix A.3, `n_units ≤ 2^20`), about 90
/// bytes a run. The reference readers refuse a longer one before they read it, and the
/// reference writers refuse to write one, so `man_len` never asks a reader to read
/// more. It bounds the bytes read; [`MANIFEST_MAX_VALUES`] bounds what a parse builds
/// from them. The manifest's nesting has its own limit: the JSON reader refuses 128
/// nested arrays and objects and takes 127 ([`check_manifest_text`]).
pub const MANIFEST_MAX_LEN: usize = 1 << 27;
/// The most JSON values a manifest may hold that a reader must accept (SPEC §2, *Reader
/// limits*): 2^22, counting the manifest object and every value nested in it, at any
/// depth; a member's name is not a value. It is sized to the same layer as
/// [`MANIFEST_MAX_LEN`]: a `prev` that names a different producer for each of 2^20 units
/// is three values a run (the run, its `producer` and its `count`), 3·2^20 and a few more
/// in all. The reference readers count a manifest's values before they parse it
/// ([`check_manifest_text`]) and refuse it as soon as the count passes the limit
/// (`manifest_too_many_values`), so the JSON tree a parse builds holds at most this many
/// values, whatever the manifest's length.
pub const MANIFEST_MAX_VALUES: usize = 1 << 22;
/// SPEC §2, *Reader limits*: `Err` with the code `manifest_too_large` when a manifest
/// of `len` bytes is longer than [`MANIFEST_MAX_LEN`].
pub fn check_manifest_len(len: u64) -> Result<(), String> {
if len > MANIFEST_MAX_LEN as u64 {
return Err(format!(
"manifest_too_large: the manifest is {len} bytes; a reader need accept no more than {MANIFEST_MAX_LEN} (SPEC §2)"
));
}
Ok(())
}
/// What a container writer may write: the longest manifest, and the largest length a
/// `u32` field holds. [`Wai::to_bytes`] and [`WaiMulti::to_bytes`] write within
/// [`WriteLimits::SPEC`]; a test passes limits it can reach without serialising 128 MiB
/// or holding 4 GiB.
#[derive(Clone, Copy, Debug)]
struct WriteLimits {
manifest: usize,
field: u32,
}
impl WriteLimits {
/// SPEC §2's reader limit for the manifest, and the whole of a `u32`.
const SPEC: Self = Self { manifest: MANIFEST_MAX_LEN, field: u32::MAX };
}
/// A manifest's JSON as the writers emit it (no insignificant whitespace, members in
/// order), refused with `manifest_too_large` as soon as it would pass `cap` bytes
/// ([`MANIFEST_MAX_LEN`] in the writers), so a writer never builds more of it than that;
/// then read as a reader reads it ([`check_manifest_text`]), so a writer never emits a
/// manifest past [`MANIFEST_MAX_VALUES`] (`manifest_too_many_values`), nested past 127, or
/// naming a member twice.
fn manifest_json<T: Serialize>(m: &T, cap: usize) -> std::io::Result<Vec<u8>> {
struct Capped(Vec<u8>, usize);
impl std::io::Write for Capped {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
if self.0.len().saturating_add(b.len()) > self.1 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, format!(
"manifest_too_large: the manifest passes {} bytes, the most a reader need accept (SPEC §2)", self.1)));
}
self.0.extend_from_slice(b);
Ok(b.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut w = Capped(Vec::new(), cap);
serde_json::to_writer(&mut w, m).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
check_manifest_text(&w.0).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
Ok(w.0)
}
/// A length a container writes into a `u32` field (SPEC §2, §7.1), or an error
/// naming the field when it is past `max` (in the writers, `u32::MAX`: when it does not
/// fit).
fn u32_field(len: usize, max: u32, what: &str) -> std::io::Result<u32> {
u32::try_from(len).ok().filter(|&l| l <= max).ok_or_else(|| std::io::Error::new(
std::io::ErrorKind::InvalidData, format!("{what} is {len} bytes, past the u32 field that holds it")))
}
fn is_none<T>(v: &Option<T>) -> bool {
v.is_none()
}
/// `model_requirement` (SPEC §3): the capability a sink must resolve, and an
/// optional fallback. Build one with [`ModelRequirement::new`].
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ModelRequirement {
pub capability: String,
#[serde(default, skip_serializing_if = "is_none")]
pub fallback: Option<String>,
/// Behavioral conformance threshold for the *perceptual* (semantic /
/// generative) path: the file requires a sink whose regenerated output
/// scores at least this similarity against the transmitted understanding,
/// in the embedding space [`Self::sim_metric`] names. Absent for floor and
/// bit-exact capabilities.
#[serde(default, skip_serializing_if = "is_none")]
pub min_sim: Option<f32>,
/// The embedding space `min_sim` is measured in. Absent ⇒ `"clip"` by
/// convention.
#[serde(default, skip_serializing_if = "is_none")]
pub sim_metric: Option<String>,
/// Members this revision does not define, kept so they survive a round trip.
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl ModelRequirement {
/// A requirement for `capability`, with no fallback and no perceptual threshold.
pub fn new(capability: impl Into<String>) -> Self {
Self { capability: capability.into(), fallback: None, min_sim: None, sim_metric: None, extra: Map::new() }
}
/// Declare a fallback capability (SPEC §4 step 5).
pub fn with_fallback(mut self, fallback: impl Into<String>) -> Self {
self.fallback = Some(fallback.into());
self
}
/// Declare a perceptual similarity threshold.
pub fn with_min_sim(mut self, min_sim: f32) -> Self {
self.min_sim = Some(min_sim);
self
}
/// Name the embedding space of the similarity threshold.
pub fn with_sim_metric(mut self, metric: impl Into<String>) -> Self {
self.sim_metric = Some(metric.into());
self
}
}
/// The parameter set a pin's set **replaces** (SPEC §3.1, `supersedes`), named as a
/// pin names one. Informative: it changes no decision, only the reason a sink that
/// holds the predecessor reports (`digest_superseded`). A successor never satisfies
/// its predecessor's pin.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Supersedes {
/// The predecessor's SHA-256, in its form, as 64 lowercase hexadecimal digits.
pub sha256: String,
/// The predecessor's declared form, when it was pinned in one.
#[serde(default, skip_serializing_if = "is_none")]
pub form: Option<String>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl Supersedes {
/// The predecessor, pinned form-less by `sha256`.
pub fn new(sha256: impl Into<String>) -> Self {
Self { sha256: sha256.into(), form: None, extra: Map::new() }
}
/// The predecessor's declared form.
pub fn with_form(mut self, form: impl Into<String>) -> Self {
self.form = Some(form.into());
self
}
}
/// The **pin** (SPEC §3.1): the identity of the parameter set a payload was coded
/// against — the SHA-256 of the whole set, in its declared form — plus how the
/// decode is made reproducible. Carried as `conditioning.prior` in a `WAI1`
/// manifest and as a rendition entry's `prior` in a `WAI2` one.
///
/// ```
/// use wai::codecs::{DeclaredTier, CAP_PRIOR_SET};
/// use wai::container::Prior;
///
/// let legacy = Prior::new("int_hyper/q4", "ef".repeat(32), "int-rans/p16+int-synth");
/// // A pin written before `tier` existed reads its tier from `determinism`.
/// assert_eq!(legacy.declared_tier(), Some(wai::codecs::DeterminismTier::DecodeEquivalence));
/// assert!(legacy.is_cross_hardware_exact());
/// let set = Prior::new("int_mlicpp/a", "85".repeat(32), "int-rans/p16+int-mlicpp/i64")
/// .with_form(CAP_PRIOR_SET)
/// .with_tier(DeclaredTier::DecodeEquivalence);
/// assert_eq!(set.tier(), Some("decode-equivalence"));
/// assert!(set.is_cross_hardware_exact());
/// ```
///
/// `#[non_exhaustive]`: build one with [`Prior::new`] and the `with_*` builders.
/// Every optional member is skipped when absent, so a pin written without one keeps
/// its bytes, and members this revision does not define are kept in `extra`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Prior {
/// A name for people and logs. A sink never selects, resolves or accepts a
/// parameter set by it.
pub id: String,
/// The SHA-256 of the parameter set in its form (SPEC §3.1), as 64 lowercase
/// hexadecimal digits: with no `form`, of the one file that holds the set;
/// in the form `wai.prior.set`, of the set's listing.
pub sha256: String,
/// How the decode is made reproducible, in words, e.g.
/// `"int-rans/p16+int-mlicpp/i64"`. Informative, except that SPEC §7 reads it
/// for a pin without `tier` ([`Prior::determinism_class`]).
pub determinism: String,
/// The registered prior form the digest covers
/// ([`crate::codecs::prior_form::prior_forms`]). Absent: the digest covers the
/// one file that holds the set, which SPEC §3.1 admits only for a capability
/// registered with a one-file set.
#[serde(default, skip_serializing_if = "is_none")]
pub form: Option<String>,
/// The reproducibility tier the sender claims for the decode: a token of the
/// closed vocabulary [`crate::codecs::TIER_TOKENS`]. A token this revision does
/// not register reads as `none`. Set with [`Prior::with_tier`].
#[serde(default, skip_serializing_if = "is_none")]
pub tier: Option<String>,
/// The parameter set this one replaces (informative, SPEC §3.1).
#[serde(default, skip_serializing_if = "is_none")]
pub supersedes: Option<Supersedes>,
/// Members this revision does not define, kept so they survive a round trip.
#[serde(flatten)]
pub extra: Map<String, Value>,
}
/// Why a [`Prior`]'s declared form cannot be used at this sink. Either way the
/// pin is refused: a sink never resolves a pin against an artifact in a form
/// other than the one declared, even one whose unpacked parameters compare equal.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PriorFormError {
/// The declared form is not a registered prior form (a payload capability,
/// or a form from a registry this sink does not know).
NotAPriorForm(String),
/// A registered prior form this sink has no reader for.
Unreadable(String),
}
impl std::fmt::Display for PriorFormError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotAPriorForm(form) => write!(f, "declared prior form '{form}' is not a registered prior form"),
Self::Unreadable(form) => write!(f, "declared prior form '{form}' cannot be read by this sink"),
}
}
}
impl std::error::Error for PriorFormError {}
/// How reproducible a pin's decode is, read from its `determinism` words. This is
/// the **legacy reading** SPEC §7 fixes for a pin without `tier`: a pin declares
/// `decode-equivalence` exactly when its `determinism`, in ASCII lower case,
/// contains `int`, `fixed` or `integer` and none of `f32`, `f64` and `float`. It
/// reads a declaration and can misread one; the registry class, checked first, is
/// what guarantees the arithmetic.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeterminismClass {
/// The words name integer-only inference: reads as `decode-equivalence`.
IntegerExact,
/// The words name a float stage: reproducible on the same build at best.
FloatReproducible,
/// Unrecognized words: no tier.
Unknown,
}
/// Whether `s` is 64 lowercase hexadecimal digits — the grammar of every SHA-256
/// WAI writes (SPEC §7 *Digests*).
pub fn is_sha256_hex(s: &str) -> bool {
s.len() == 64 && s.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
}
impl Prior {
/// A pin by `id`, the `sha256` of the one file that holds the set, and its
/// `determinism` words, with every optional member absent. It serializes
/// exactly as a pin written before the optional members existed.
pub fn new(
id: impl Into<String>,
sha256: impl Into<String>,
determinism: impl Into<String>,
) -> Self {
Self {
id: id.into(),
sha256: sha256.into(),
determinism: determinism.into(),
form: None,
tier: None,
supersedes: None,
extra: Map::new(),
}
}
/// The same pin, declaring the form its digest covers ([`Prior::form`]). The
/// form is not checked here; see [`crate::pin::verify_bytes`].
pub fn with_form(mut self, form: impl Into<String>) -> Self {
self.form = Some(form.into());
self
}
/// The same pin, stating the tier the sender claims. The tier is typed, so this
/// crate writes only a registered token.
pub fn with_tier(mut self, tier: DeclaredTier) -> Self {
self.tier = Some(tier.token().to_string());
self
}
/// The same pin, naming the set it replaces.
pub fn with_supersedes(mut self, predecessor: Supersedes) -> Self {
self.supersedes = Some(predecessor);
self
}
/// The declared form, if any.
pub fn form(&self) -> Option<&str> {
self.form.as_deref()
}
/// The stated `tier` token, if any — registered or not.
pub fn tier(&self) -> Option<&str> {
self.tier.as_deref()
}
/// The stated predecessor, if any — well-formed or not
/// ([`Prior::supersedes_finding`]).
pub fn supersedes(&self) -> Option<&Supersedes> {
self.supersedes.as_ref()
}
/// Whether `tier` is absent or a registered token. A verifier reports an
/// unregistered one (`tier_unregistered`); a sink reads it as `none`.
pub fn tier_is_registered(&self) -> bool {
self.tier().is_none_or(|t| DeclaredTier::from_token(t).is_some())
}
/// The tier this pin declares (SPEC §7): its `tier` when present (an
/// unregistered token, or `none`, declares none); without `tier`, the legacy
/// reading of `determinism` ([`Prior::determinism_class`]):
/// `decode-equivalence` when it reads integer-exact, none otherwise.
pub fn declared_tier(&self) -> Option<DeterminismTier> {
match self.tier() {
Some(t) => DeterminismTier::from_token(t),
None => (self.determinism_class() == DeterminismClass::IntegerExact)
.then_some(DeterminismTier::DecodeEquivalence),
}
}
/// The legacy reading of `determinism` (see [`DeterminismClass`]). It is what a
/// pin without `tier` declares, frozen so a pin written before `tier` keeps its
/// verdict. So `"int-rans/p16+onnx-cpu-f32/1thread"` (integer entropy, float
/// synthesis) reads as [`DeterminismClass::FloatReproducible`].
pub fn determinism_class(&self) -> DeterminismClass {
let d = self.determinism.to_ascii_lowercase();
let floaty = d.contains("f32") || d.contains("f64") || d.contains("float");
let integer = d.contains("int") || d.contains("fixed") || d.contains("integer");
match (integer, floaty) {
(true, false) => DeterminismClass::IntegerExact,
(_, true) => DeterminismClass::FloatReproducible,
(false, false) => DeterminismClass::Unknown,
}
}
/// True iff this pin backs decode-equivalence: it declares
/// `decode-equivalence` ([`Prior::declared_tier`]) and its form, if any, is
/// one the registry lists as established exact
/// ([`Prior::form_preserves_exactness`]). The form rule lives here so every
/// `replicate` check that reads this predicate applies it.
pub fn is_cross_hardware_exact(&self) -> bool {
self.declared_tier() == Some(DeterminismTier::DecodeEquivalence)
&& self.form_preserves_exactness()
}
/// Whether reading the pinned set out of its declared form is established to
/// hand every conforming sink the same parameters: no form (the digest covers
/// the bytes), or a form the registry lists
/// ([`form_reconstruction_is_exact`](crate::codecs::prior_form::form_reconstruction_is_exact):
/// `wai.prior.set`; no coded form).
pub fn form_preserves_exactness(&self) -> bool {
self.form().is_none_or(crate::codecs::prior_form::form_reconstruction_is_exact)
}
/// Check the declared form: it must be a registered prior form, and one the
/// sink can read. An undeclared form passes.
pub fn check_form(&self, readable: &[&str]) -> Result<(), PriorFormError> {
match self.form() {
None => Ok(()),
Some(f) if !crate::codecs::prior_form::is_prior_form(f) => {
Err(PriorFormError::NotAPriorForm(f.to_string()))
}
Some(f) if !readable.contains(&f) => Err(PriorFormError::Unreadable(f.to_string())),
Some(_) => Ok(()),
}
}
/// SPEC §3.1 check 1: `sha256` is 64 lowercase hexadecimal digits.
pub fn check_syntax(&self) -> Result<(), PinRefusal> {
if is_sha256_hex(&self.sha256) {
Ok(())
} else {
Err(PinRefusal::Malformed {
why: format!("sha256 '{}' is not 64 lowercase hexadecimal digits", self.sha256),
})
}
}
/// Why `supersedes` is malformed, if it is (`supersedes_malformed`): its
/// `sha256` is not 64 lowercase hex, equals the pin's own, or its `form` is not
/// a registered prior form. A malformed `supersedes` is a static finding only:
/// a sink ignores the member, and never reports `digest_superseded` for it.
pub fn supersedes_finding(&self) -> Option<String> {
let s = self.supersedes()?;
if !is_sha256_hex(&s.sha256) {
return Some(format!("supersedes.sha256 '{}' is not 64 lowercase hexadecimal digits", s.sha256));
}
if s.sha256 == self.sha256 {
return Some("supersedes.sha256 equals the pin's own sha256".into());
}
if let Some(f) = s.form.as_deref() {
if !crate::codecs::prior_form::is_prior_form(f) {
return Some(format!("supersedes.form '{f}' is not a registered prior form"));
}
}
None
}
/// The predecessor a sink may act on: `supersedes` when it is well-formed.
pub fn usable_supersedes(&self) -> Option<&Supersedes> {
self.supersedes_finding().is_none().then(|| self.supersedes()).flatten()
}
}
/// Why a sink refuses a pin (SPEC §3.1). The checks run in order, and the first
/// that fails is reported: `pin_malformed`, `form_not_registered`,
/// `form_unreadable`, `form_required`, `prior_revoked` or `prior_not_authorised`
/// (check 5, run only by a sink that holds the capability's pinned sets to
/// jwp-receipts §7.9, [`crate::pin::PinAuthority`]), `prior_not_held` or
/// `digest_superseded`, `set_malformed`, `part_mismatch`. `set_incomplete` is
/// raised during a pinned decode. Each makes the capability unsupported for that envelope at that sink;
/// none is a reason to decode anyway.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PinRefusal {
/// Check 1: `sha256` is not 64 lowercase hexadecimal digits.
Malformed { why: String },
/// Check 2: `form` names no registered prior form.
FormNotRegistered { form: String },
/// Check 3: `form` is registered and this sink has no reader for it — the
/// sink's gap, never reported as a mismatch.
FormUnreadable { form: String },
/// Check 4: `form` is absent and `capability` is not registered with a
/// one-file set.
FormRequired { capability: String },
/// Check 5: the sink holds `capability`'s pinned sets to jwp-receipts §7.9, and a
/// key log it holds revokes the set's digest (§7.9 check 5). `revoked_by` names the
/// revoking origin where it is not the one whose grant was checked.
Revoked { capability: String, revoked_by: Option<String> },
/// Check 5: the sink holds `capability`'s pinned sets to jwp-receipts §7.9, and §7.9
/// does not hold the set for a reason other than revocation. `check` and `reason`
/// are §7.9's (for example `4` and `module_status_stale`: no fresh status, so the
/// sink is offline or has no trusted clock), or `None` when the sink holds no
/// grant of form `decoder` for the set.
NotAuthorised { capability: String, check: Option<&'static str>, reason: Option<&'static str> },
/// Check 6: the sink holds no parameter set for `capability` whose digest, in
/// the pin's form, equals the pin.
NotHeld { capability: String },
/// Check 6, refined: the sink holds the set the pin's `supersedes` names.
Superseded { capability: String, predecessor: String },
/// Check 7: the listing whose digest matches breaks a listing rule.
SetMalformed { why: String },
/// Check 8: in every held copy of the matching listing, a file it names is
/// missing, unreadable, or has another digest.
PartMismatch { path: String },
/// During a pinned decode: the decode needs `path`, which the verified set
/// does not contain.
SetIncomplete { path: String },
}
/// Every pin refusal reason, in the order SPEC §3.1 checks them (`set_incomplete`
/// last: it is raised during a decode, not by verification).
pub const PIN_REFUSAL_CODES: &[&str] = &[
"pin_malformed",
"form_not_registered",
"form_unreadable",
"form_required",
"prior_revoked",
"prior_not_authorised",
"prior_not_held",
"digest_superseded",
"set_malformed",
"part_mismatch",
"set_incomplete",
];
impl PinRefusal {
/// The reason SPEC §3.1 names (snake_case).
pub fn code(&self) -> &'static str {
match self {
Self::Malformed { .. } => "pin_malformed",
Self::FormNotRegistered { .. } => "form_not_registered",
Self::FormUnreadable { .. } => "form_unreadable",
Self::FormRequired { .. } => "form_required",
Self::Revoked { .. } => "prior_revoked",
Self::NotAuthorised { .. } => "prior_not_authorised",
Self::NotHeld { .. } => "prior_not_held",
Self::Superseded { .. } => "digest_superseded",
Self::SetMalformed { .. } => "set_malformed",
Self::PartMismatch { .. } => "part_mismatch",
Self::SetIncomplete { .. } => "set_incomplete",
}
}
}
impl std::fmt::Display for PinRefusal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Malformed { why } => write!(f, "malformed pin: {why}"),
Self::FormNotRegistered { form } => write!(f, "declared prior form '{form}' is not a registered prior form"),
Self::FormUnreadable { form } => write!(f, "declared prior form '{form}' cannot be read by this sink"),
Self::FormRequired { capability } => write!(
f, "{capability} is not registered with a one-file parameter set, so a pin over it must declare a form (wai.prior.set)"),
Self::Revoked { capability, revoked_by } => {
write!(f, "the pinned parameter set for {capability} is revoked by a key log this sink holds (jwp-receipts §7.9 check 5)")?;
match revoked_by {
Some(o) => write!(f, ", in {o}'s"),
None => Ok(()),
}
}
Self::NotAuthorised { capability, check: Some(c), reason: Some(r) } => write!(
f, "the pinned parameter set for {capability} is not held under jwp-receipts §7.9: check {c}, {r}"),
Self::NotAuthorised { capability, .. } => write!(
f, "the pinned parameter set for {capability} is not held under jwp-receipts §7.9: this sink holds no grant for it"),
Self::NotHeld { capability } => write!(
f, "this sink holds no parameter set for {capability} whose digest equals the pin"),
Self::Superseded { capability, predecessor } => write!(
f, "this sink holds only the parameter set {predecessor} for {capability}, which the pinned set replaces"),
Self::SetMalformed { why } => write!(f, "the pinned listing is malformed: {why}"),
Self::PartMismatch { path } => write!(
f, "file '{path}' of the pinned set is missing, unreadable, or has another digest"),
Self::SetIncomplete { path } => write!(
f, "the decode needs '{path}', which the verified parameter set does not contain"),
}
}
}
impl std::error::Error for PinRefusal {}
impl From<PriorFormError> for PinRefusal {
fn from(e: PriorFormError) -> Self {
match e {
PriorFormError::NotAPriorForm(form) => Self::FormNotRegistered { form },
PriorFormError::Unreadable(form) => Self::FormUnreadable { form },
}
}
}
/// Whether a pin applies to `cap` (SPEC §3.1, *Where a pin sits*): `cap` decodes
/// against a parameter set, or this revision does not register it — a sink that
/// advertises an unregistered capability and finds a pin on it verifies the pin as
/// several files, so it fails closed.
pub fn pin_applies_to(cap: &str) -> bool {
crate::codecs::decodes_against_parameter_set(cap) || !crate::codecs::is_known_capability(cap)
}
/// What the pin rule needs of `cap` before any file is hashed: the shape a pin over
/// it must take. An unregistered capability is treated as several files.
pub fn pin_shape(cap: &str) -> ParameterSetShape {
crate::codecs::parameter_set_shape(cap).unwrap_or(ParameterSetShape::SeveralFiles)
}
/// Why a `(intent, pin)` pair does not make a sound `replicate` claim over a
/// `NeuralIntegerExact` capability (SPEC §7). See [`replicate_prior_refusal`].
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PriorRefusal {
/// `replicate` with no pin (`replicate_unpinned`).
Absent,
/// A pin without `tier` whose `determinism` does not read as integer-exact
/// (`replicate_tier`).
NotIntegerExact(DeterminismClass),
/// A pin whose `tier` (named here) is not `decode-equivalence`
/// (`replicate_tier`).
TierNotDecodeEquivalence(String),
/// A pin that declares a form (named here) the registry does not list as
/// established exact, or that is not a registered prior form at all
/// (`replicate_form_not_exact`).
FormNotExact(String),
}
impl PriorRefusal {
/// The reason SPEC §7 names.
pub fn code(&self) -> &'static str {
match self {
Self::Absent => "replicate_unpinned",
Self::NotIntegerExact(_) | Self::TierNotDecodeEquivalence(_) => "replicate_tier",
Self::FormNotExact(_) => "replicate_form_not_exact",
}
}
}
impl std::fmt::Display for PriorRefusal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const FLOAT: &str = "float decode is not byte-identical across hardware — tag this \
perceptual/improve, or pin an integer-only prior";
match self {
Self::Absent => write!(f, "no prior is pinned; {FLOAT}"),
Self::NotIntegerExact(class) => {
write!(f, "the prior is not integer-exact (determinism {class:?}); {FLOAT}")
}
Self::TierNotDecodeEquivalence(tier) => write!(
f,
"the pin declares tier '{tier}', not decode-equivalence; {FLOAT}"
),
Self::FormNotExact(form) if !crate::codecs::prior_form::is_prior_form(form) => write!(
f,
"the prior's contract is integer-exact, but its declared form '{form}' is not a \
registered prior form, so nothing states what reading the artifact out of it \
reconstructs — tag this perceptual/improve, or pin the set in the form \
wai.prior.set"
),
Self::FormNotExact(form) => write!(
f,
"the prior's contract is integer-exact, but its declared form '{form}' is not \
established to reconstruct identical tensors at every reader, so two sinks can \
read different weights out of it — tag this perceptual/improve, or pin the set \
in the form wai.prior.set"
),
}
}
}
/// The pin half of SPEC §7's replicate rule, with its reason: `None` exactly when
/// [`replicate_is_sound`] holds for `(intent, prior)` (or `intent` is not
/// `replicate`). In order: no pin (`Absent`); a `tier` other than
/// `decode-equivalence` (`TierNotDecodeEquivalence`); with no `tier`, a
/// `determinism` that does not read as integer-exact (`NotIntegerExact`); a form
/// not established exact (`FormNotExact`). This reads the sender's declaration
/// only; [`crate::codecs::replicate_refusal`] checks the registry class first.
pub fn replicate_prior_refusal(intent: &str, prior: Option<&Prior>) -> Option<PriorRefusal> {
if intent != "replicate" {
return None;
}
let p = match prior {
None => return Some(PriorRefusal::Absent),
Some(p) => p,
};
match p.tier() {
Some(t) if DeterminismTier::from_token(t) != Some(DeterminismTier::DecodeEquivalence) => {
return Some(PriorRefusal::TierNotDecodeEquivalence(t.to_string()));
}
Some(_) => {}
None if p.determinism_class() != DeterminismClass::IntegerExact => {
return Some(PriorRefusal::NotIntegerExact(p.determinism_class()));
}
None => {}
}
if !p.form_preserves_exactness() {
return Some(PriorRefusal::FormNotExact(p.form().unwrap_or_default().to_string()));
}
None
}
/// The pin half of SPEC §7: whether `(intent, prior)` declares what a `replicate`
/// over a `NeuralIntegerExact` capability needs. [`Wai::replicate_is_sound`] and
/// [`crate::codecs::replicate_refusal`] apply the registry class too.
pub fn replicate_is_sound(intent: &str, prior: Option<&Prior>) -> bool {
replicate_prior_refusal(intent, prior).is_none()
}
/// A finding a verifier makes about a manifest's pins without holding any
/// parameter set ([`Wai::check_pin`], [`WaiMulti::check_pins`]).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PinFinding {
/// The reason: `prior_not_applicable`, `pin_malformed`, `tier_unregistered`,
/// `supersedes_malformed`, `form_not_registered`, `form_required`,
/// `tier_exceeds_class`, or a `replicate_*` reason.
pub code: &'static str,
pub detail: String,
}
impl std::fmt::Display for PinFinding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.code, self.detail)
}
}
/// Every static pin finding, in the order a verifier checks them.
pub const PIN_FINDING_CODES: &[&str] = &[
"prior_not_applicable",
"pin_malformed",
"tier_unregistered",
"supersedes_malformed",
"form_not_registered",
"form_required",
"tier_exceeds_class",
"replicate_unpinned",
"replicate_tier",
"replicate_form_not_exact",
];
/// The static pin findings for a pin that would apply to the capabilities `caps`
/// (the first is the one decoded through first), under `intent`.
fn static_pin_findings(caps: &[&str], prior: Option<&Prior>, intent: &str) -> Result<(), PinFinding> {
let finding = |code: &'static str, detail: String| Err(PinFinding { code, detail });
if let Some(p) = prior {
let applies: Vec<&str> = caps.iter().copied().filter(|c| pin_applies_to(c)).collect();
if applies.is_empty() {
return finding("prior_not_applicable", format!(
"a pin over {} means nothing: no capability it applies to decodes against a parameter set",
caps.join(", ")));
}
if let Err(r) = p.check_syntax() {
return finding("pin_malformed", r.to_string());
}
if !p.tier_is_registered() {
return finding("tier_unregistered", format!(
"tier '{}' is not a registered token; a sink reads it as none", p.tier().unwrap_or_default()));
}
if let Some(why) = p.supersedes_finding() {
return finding("supersedes_malformed", why);
}
if let Some(f) = p.form() {
if !crate::codecs::prior_form::is_prior_form(f) {
return finding("form_not_registered", PinRefusal::FormNotRegistered { form: f.into() }.to_string());
}
} else if let Some(c) = applies.iter().find(|c| pin_shape(c) != ParameterSetShape::OneFile) {
return finding("form_required", PinRefusal::FormRequired { capability: c.to_string() }.to_string());
}
if let Some(c) = applies.iter().find(|c| crate::codecs::tier_exceeds_class(c, p)) {
return finding("tier_exceeds_class", format!(
"tier '{}' claims more than {c}'s registry class gives ({})",
p.tier().unwrap_or_default(),
crate::codecs::class_tier(c).map(DeclaredTier::token).unwrap_or("none")));
}
}
if let Some(&first) = caps.first() {
if let Some(crate::codecs::ReplicateRefusal::Prior(r)) =
crate::codecs::replicate_refusal(first, intent, prior)
{
return finding(r.code(), r.to_string());
}
}
Ok(())
}
/// The `conditioning` object (SPEC §3). Build one with [`Conditioning::new`].
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Conditioning {
pub kind: String,
/// The pin (SPEC §3.1) of the parameter set the payload was coded against. It
/// applies to whichever of the capability and the fallback decodes the payload,
/// where that capability decodes against a parameter set.
#[serde(default, skip_serializing_if = "is_none")]
pub prior: Option<Prior>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl Conditioning {
/// Conditioning of `kind`, with no pin.
pub fn new(kind: impl Into<String>) -> Self {
Self { kind: kind.into(), prior: None, extra: Map::new() }
}
/// Pin the parameter set the payload was coded against.
pub fn with_prior(mut self, prior: Prior) -> Self {
self.prior = Some(prior);
self
}
}
/// The `security` object (SPEC §6.1). Absent, `null`, or `encryption: "none"`
/// means the payload is plaintext.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Security {
#[serde(default, skip_serializing_if = "is_none")]
pub encryption: Option<String>,
#[serde(default, skip_serializing_if = "is_none")]
pub kid: Option<u64>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl Security {
/// `security` naming an encryption scheme and a key id.
pub fn new(encryption: impl Into<String>, kid: u64) -> Self {
Self { encryption: Some(encryption.into()), kid: Some(kid), extra: Map::new() }
}
/// Whether the payload is plaintext (`encryption` absent or `"none"`).
pub fn is_plaintext(&self) -> bool {
self.encryption.as_deref().is_none_or(|e| e == "none")
}
}
/// SPEC §6.4 step 2 at a sink that implements no encryption scheme, as the reference
/// dispatchers are: a manifest whose `security` declares an encryption other than
/// `"none"` makes the envelope inert here, and no ciphertext reaches a codec. A sink
/// that opens a scheme decrypts first and dispatches the plaintext envelope (§6.4
/// step 3), whose `security` is plaintext. `declared` is the encryption as written.
fn encryption_inert(declared: impl Into<String>) -> Inert {
Inert::Encrypted { encryption: declared.into() }
}
/// A `WAI1` manifest (SPEC §3). Build one with [`Manifest::new`].
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Manifest {
pub wai: String,
pub media: String,
pub intent: String,
pub model_requirement: ModelRequirement,
pub conditioning: Conditioning,
#[serde(default, skip_serializing_if = "is_none")]
pub security: Option<Security>,
/// `refines` (`extensions/staged-delivery.md` §3): what a refinement-only
/// capability's payload refines, by content hash. Kept as the JSON it is, so a
/// malformed value still parses ([`Refines::from_value`] reports it,
/// `refines-malformed`).
#[serde(default, skip_serializing_if = "is_none")]
pub refines: Option<Value>,
/// `derived_from` (SPEC §7.1, *Derived renditions*): the payload presents another
/// object, the motif, and was made from it by a named renderer or encoder. Kept as
/// the JSON it is, so a malformed value still parses ([`DerivedFrom::from_value`]
/// reports it); a reader of an earlier revision ignores it (§8).
#[serde(default, skip_serializing_if = "is_none")]
pub derived_from: Option<Value>,
#[serde(default)]
pub target: Value,
/// Members this revision does not define, kept so they survive a round trip.
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl Manifest {
/// A `"1.0"` manifest with a `null` target and no `security`.
pub fn new(
media: impl Into<String>,
intent: impl Into<String>,
model_requirement: ModelRequirement,
conditioning: Conditioning,
) -> Self {
Self {
wai: "1.0".into(),
media: media.into(),
intent: intent.into(),
model_requirement,
conditioning,
security: None,
refines: None,
derived_from: None,
target: Value::Null,
extra: Map::new(),
}
}
/// Set `target`.
pub fn with_target(mut self, target: Value) -> Self {
self.target = target;
self
}
/// Set `refines` (`extensions/staged-delivery.md` §3).
pub fn with_refines(mut self, refines: &Refines) -> Self {
self.refines = Some(refines.to_value());
self
}
/// Set `derived_from` (SPEC §7.1, *Derived renditions*).
pub fn with_derived_from(mut self, d: &DerivedFrom) -> Self {
self.derived_from = Some(d.to_value());
self
}
/// Set the `wai` version string.
pub fn with_wai(mut self, wai: impl Into<String>) -> Self {
self.wai = wai.into();
self
}
/// Set `security`.
pub fn with_security(mut self, security: Security) -> Self {
self.security = Some(security);
self
}
}
/// How the hashes of a `refines` member name objects: a content hash
/// (`jwp-receipts` §2.1, 64 lowercase hexadecimal digits) in a `WAI1` manifest, a
/// component `id` ([`is_component_id`]) in a `WAI2` entry, which names the entry
/// whose exploded envelope is the object (SPEC §7.1).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RefForm {
ContentHash,
ComponentId,
}
impl RefForm {
fn accepts(self, s: &str) -> bool {
match self {
RefForm::ContentHash => s.len() == 64 && s.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)),
RefForm::ComponentId => is_component_id(s),
}
}
}
/// `refines.prev` (`extensions/staged-delivery.md` §3): the producer each unit of
/// the span is expected to have — one for every unit, or runs in span order.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Prev {
One(String),
Runs(Vec<PrevRun>),
}
/// `count` consecutive units of a span whose producer is `producer`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrevRun {
pub producer: String,
pub count: u32,
}
/// `refines.span`: `count` consecutive units from `from`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub from: u32,
pub count: u32,
}
/// A well-formed `refines` member (`extensions/staged-delivery.md` §3).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Refines {
pub base: String,
pub prev: Prev,
pub layer: u8,
pub span: Option<Span>,
}
/// Why a `refines` member is malformed (`refines-malformed`): what failed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RefinesMalformed(pub &'static str);
impl std::fmt::Display for RefinesMalformed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "refines-malformed: {}", self.0)
}
}
impl std::error::Error for RefinesMalformed {}
impl Refines {
/// Read `refines` as staged-delivery §3 defines it, with its references in `form`.
/// Members it does not define are ignored (SPEC §8). Malformed: a value that is
/// not an object; `base` or a producer that is not a string of `form`; `prev`
/// neither such a string nor a non-empty array of runs; a run that is not an
/// object of `producer` and an integer `count` ≥ 1; two adjacent runs with the
/// same producer, or an array of one run (a single producer is written as a
/// string); `layer` not an integer in 1..=255; at layer 1 a producer other than
/// `base`; `span` neither absent, `null`, nor an object of integer `from` ≥ 0 and
/// `count` ≥ 1, or past `u32`; with a span, run counts that do not sum to its
/// `count`. An integer is written as one (SPEC §2): `-0`, a fraction or an exponent
/// is not an integer, and `serde_json` reads each as a float, which no `as_u64`
/// accepts. Whether the
/// span lies within the base's units, and, with no span, whether the runs sum to
/// them, needs the base ([`Refines::check_units`]).
pub fn from_value(v: &Value, form: RefForm) -> Result<Refines, RefinesMalformed> {
let bad = |why: &'static str| RefinesMalformed(why);
let o = v.as_object().ok_or(bad("refines is not an object"))?;
let base = o.get("base").and_then(Value::as_str).filter(|b| form.accepts(b)).ok_or(bad("base is not a reference"))?;
let layer = o
.get("layer")
.and_then(Value::as_u64)
.filter(|l| (1..=255).contains(l))
.ok_or(bad("layer is not an integer in 1..=255"))? as u8;
let span = match o.get("span") {
// `null` in an OPTIONAL member is the member absent (SPEC §3.1).
None | Some(Value::Null) => None,
Some(sp) => {
let so = sp.as_object().ok_or(bad("span is not an object"))?;
let from = so.get("from").and_then(Value::as_u64).and_then(|f| u32::try_from(f).ok()).ok_or(bad("span.from is not an integer ≥ 0 within u32"))?;
let count = so
.get("count")
.and_then(Value::as_u64)
.and_then(|c| u32::try_from(c).ok())
.filter(|&c| c >= 1)
.ok_or(bad("span.count is not an integer ≥ 1 within u32"))?;
if from.checked_add(count).is_none() {
return Err(bad("the span runs past u32"));
}
Some(Span { from, count })
}
};
let prev = match o.get("prev") {
Some(Value::String(p)) if form.accepts(p) => Prev::One(p.clone()),
Some(Value::Array(a)) if !a.is_empty() => {
let mut runs = Vec::with_capacity(a.len().min(1 << 16));
for r in a {
let ro = r.as_object().ok_or(bad("a prev run is not an object"))?;
let producer = ro.get("producer").and_then(Value::as_str).filter(|p| form.accepts(p)).ok_or(bad("a run's producer is not a reference"))?;
let count = ro
.get("count")
.and_then(Value::as_u64)
.and_then(|c| u32::try_from(c).ok())
.filter(|&c| c >= 1)
.ok_or(bad("a run's count is not an integer ≥ 1 within u32"))?;
if runs.last().is_some_and(|l: &PrevRun| l.producer == producer) {
return Err(bad("two adjacent runs name the same producer"));
}
runs.push(PrevRun { producer: producer.to_string(), count });
}
if runs.len() == 1 {
return Err(bad("prev is an array of one run; a single producer is written as a string"));
}
Prev::Runs(runs)
}
_ => return Err(bad("prev is neither a reference nor an array of runs")),
};
let r = Refines { base: base.to_string(), prev, layer, span };
if layer == 1 && r.producers().any(|p| p != r.base) {
return Err(bad("at layer 1 every producer prev names must be base"));
}
if let (Some(sp), Prev::Runs(runs)) = (span, &r.prev) {
if runs.iter().map(|x| x.count as u64).sum::<u64>() != sp.count as u64 {
return Err(bad("the run counts do not sum to the span's count"));
}
}
Ok(r)
}
/// The JSON this member is written as: `base`, `prev`, `layer`, then `span` when
/// present.
pub fn to_value(&self) -> Value {
let mut o = Map::new();
o.insert("base".into(), Value::from(self.base.clone()));
o.insert(
"prev".into(),
match &self.prev {
Prev::One(p) => Value::from(p.clone()),
Prev::Runs(runs) => Value::Array(
runs.iter()
.map(|r| {
let mut m = Map::new();
m.insert("producer".into(), Value::from(r.producer.clone()));
m.insert("count".into(), Value::from(r.count));
Value::Object(m)
})
.collect(),
),
},
);
o.insert("layer".into(), Value::from(self.layer));
if let Some(sp) = self.span {
let mut m = Map::new();
m.insert("from".into(), Value::from(sp.from));
m.insert("count".into(), Value::from(sp.count));
o.insert("span".into(), Value::Object(m));
}
Value::Object(o)
}
/// Every producer `prev` names, in span order.
pub fn producers(&self) -> impl Iterator<Item = &str> {
let v: Vec<&str> = match &self.prev {
Prev::One(p) => vec![p.as_str()],
Prev::Runs(runs) => runs.iter().map(|r| r.producer.as_str()).collect(),
};
v.into_iter()
}
/// The units the layer covers, `(from, count)`, of a base of `base_units` units:
/// its span, or every unit.
pub fn units(&self, base_units: u32) -> (u32, u32) {
match self.span {
Some(sp) => (sp.from, sp.count),
None => (0, base_units),
}
}
/// The checks that need the base's unit count (staged-delivery §3): the span lies
/// within the base's units, and the runs sum to the units the layer covers.
pub fn check_units(&self, base_units: u32) -> Result<(), RefinesMalformed> {
let (from, count) = self.units(base_units);
if from as u64 + count as u64 > base_units as u64 || count == 0 {
return Err(RefinesMalformed("the span lies outside the base's units"));
}
if let Prev::Runs(runs) = &self.prev {
if runs.iter().map(|x| x.count as u64).sum::<u64>() != count as u64 {
return Err(RefinesMalformed("the run counts do not sum to the units the layer covers"));
}
}
Ok(())
}
/// The producer `prev` gives for the `i`-th unit of the span (0-based), or
/// `None` past the runs. Linear in the runs; a sink resolving every unit of a span
/// indexes the runs once (`staged::StageChain` keeps their ends and bisects).
pub fn producer_for(&self, i: u32) -> Option<&str> {
match &self.prev {
Prev::One(p) => Some(p),
Prev::Runs(runs) => {
let mut at = 0u64;
for r in runs {
at += r.count as u64;
if (i as u64) < at {
return Some(&r.producer);
}
}
None
}
}
}
}
/// A well-formed `derived_from` member (SPEC §7.1, *Derived renditions*): the payload
/// presents another object, the **motif**, and was made from it by a named renderer or
/// encoder.
///
/// - `motif`: the motif's content hash (jwp-receipts §2.1), 64 lowercase hexadecimal
/// digits;
/// - `motif_capability`: the motif's capability;
/// - `renderer`: the renderer or encoder that made the payload, `<id>@<version>`;
/// - `settings`: `BLAKE3("wai:render-settings\x01" ‖ JCS(settings))`, 64 lowercase
/// hexadecimal digits.
///
/// A sink never presents such a payload's output as meeting the motif's equivalence
/// criterion, its tier or a `replicate` intent. A verifier checks `motif` against a
/// motif it holds; every other member is the producer's statement.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DerivedFrom {
pub motif: String,
pub motif_capability: String,
pub renderer: String,
pub settings: String,
}
/// Why a `derived_from` member is malformed: what failed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DerivedFromMalformed(pub &'static str);
impl std::fmt::Display for DerivedFromMalformed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "derived-from-malformed: {}", self.0)
}
}
impl std::error::Error for DerivedFromMalformed {}
impl DerivedFrom {
/// The domain of `settings`.
pub const SETTINGS_DOMAIN: &'static [u8] = b"wai:render-settings\x01";
/// Read `derived_from`. Members it does not define are ignored (SPEC §8).
/// Malformed: a value that is not an object; `motif` or `settings` not 64 lowercase
/// hexadecimal digits; `motif_capability` or `renderer` not a non-empty string.
pub fn from_value(v: &Value) -> Result<DerivedFrom, DerivedFromMalformed> {
let bad = DerivedFromMalformed;
let o = v.as_object().ok_or(bad("derived_from is not an object"))?;
let hex64 = |k: &str, why: &'static str| {
o.get(k).and_then(Value::as_str).filter(|s| RefForm::ContentHash.accepts(s)).map(str::to_string).ok_or(bad(why))
};
let text = |k: &str, why: &'static str| o.get(k).and_then(Value::as_str).filter(|s| !s.is_empty()).map(str::to_string).ok_or(bad(why));
Ok(DerivedFrom {
motif: hex64("motif", "motif is not 64 lowercase hexadecimal digits")?,
motif_capability: text("motif_capability", "motif_capability is not a non-empty string")?,
renderer: text("renderer", "renderer is not a non-empty string")?,
settings: hex64("settings", "settings is not 64 lowercase hexadecimal digits")?,
})
}
/// The JSON this member is written as: `motif`, `motif_capability`, `renderer`,
/// `settings`.
pub fn to_value(&self) -> Value {
let mut o = Map::new();
o.insert("motif".into(), Value::from(self.motif.clone()));
o.insert("motif_capability".into(), Value::from(self.motif_capability.clone()));
o.insert("renderer".into(), Value::from(self.renderer.clone()));
o.insert("settings".into(), Value::from(self.settings.clone()));
Value::Object(o)
}
}
pub struct Wai {
pub manifest: Manifest,
pub payload: Vec<u8>,
}
/// Where a `WAI1` envelope dispatches (SPEC §4): the capability, and what the
/// pin verifier returned for it when a pin applied.
#[derive(Debug)]
pub struct Dispatch<'a, V> {
pub capability: &'a str,
pub verified: Option<V>,
}
/// Why a `WAI1` envelope is inert at a sink (SPEC §4 step 6).
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Inert {
/// Neither the capability nor the fallback is advertised (or dispatchable).
MissingCapability,
/// The pin was refused for `capability` (the first refusal kept, step 3 before
/// step 5).
PinRefused { capability: String, refusal: PinRefusal },
/// The manifest declares an encryption this sink cannot open (SPEC §6.4 step 2,
/// `unsupported_encryption`): the reference dispatchers implement no scheme, so an
/// encrypted envelope is inert at them before any capability is considered.
Encrypted { encryption: String },
}
impl Inert {
/// `missing_capability`, `unsupported_encryption`, or the pin's reason.
pub fn code(&self) -> &'static str {
match self {
Self::MissingCapability => "missing_capability",
Self::PinRefused { refusal, .. } => refusal.code(),
Self::Encrypted { .. } => "unsupported_encryption",
}
}
}
impl std::fmt::Display for Inert {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MissingCapability => write!(f, "missing_capability: the sink advertises neither the capability nor the fallback"),
Self::PinRefused { capability, refusal } => write!(f, "{}: {capability}: {refusal}", refusal.code()),
Self::Encrypted { encryption } => write!(
f,
"unsupported_encryption: the manifest declares the encryption {encryption}, which this sink cannot \
open (SPEC §6.4): the file is inert here, and its payload is not decoded"
),
}
}
}
/// A pin verifier for a sink that holds no parameter set: it refuses every pin
/// that applies with `prior_not_held`. For tools that select without decoding.
pub fn no_parameter_sets() -> impl FnMut(&str, &Prior) -> Result<std::convert::Infallible, PinRefusal> {
|capability: &str, _: &Prior| Err(PinRefusal::NotHeld { capability: capability.to_string() })
}
impl Wai {
pub fn new(manifest: Manifest, payload: Vec<u8>) -> Self {
Self { manifest, payload }
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> std::io::Result<usize> {
let bytes = self.to_bytes()?;
fs::write(path, &bytes)?;
Ok(bytes.len())
}
/// The envelope's bytes. Refuses a manifest longer than SPEC §2's reader limit
/// (`manifest_too_large`), and a payload whose length does not fit its `u32` field.
pub fn to_bytes(&self) -> std::io::Result<Vec<u8>> {
self.to_bytes_within(WriteLimits::SPEC)
}
/// [`Self::to_bytes`] within `lim`.
fn to_bytes_within(&self, lim: WriteLimits) -> std::io::Result<Vec<u8>> {
// No insignificant whitespace, key order preserved (serde_json's
// `preserve_order`), so the bytes round-trip identically.
let m = manifest_json(&self.manifest, lim.manifest)?;
let payload_len = u32_field(self.payload.len(), lim.field, "the payload")?;
let mut out = Vec::with_capacity(12 + m.len() + self.payload.len());
out.extend_from_slice(MAGIC);
out.extend_from_slice(&(m.len() as u32).to_le_bytes());
out.extend_from_slice(&m);
out.extend_from_slice(&payload_len.to_le_bytes());
out.extend_from_slice(&self.payload);
Ok(out)
}
pub fn read<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
let bytes = fs::read(path)?;
Self::from_bytes(&bytes)
}
pub fn from_bytes(bytes: &[u8]) -> std::io::Result<Self> {
// Every field is bounds-checked: malformed input (truncated /
// wrong-magic / length-prefix overrunning the buffer / etc.)
// must return Err, never panic. Fuzz smoke test enforces this.
let need = |off: usize, n: usize, what: &str| -> std::io::Result<()> {
if off.saturating_add(n) > bytes.len() {
Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("truncated WAI ({what} needs {n} bytes at offset {off}, have {})",
bytes.len())))
} else { Ok(()) }
};
need(0, 4, "magic")?;
if &bytes[..4] != MAGIC {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
"not a WAI file (magic mismatch)"));
}
need(4, 4, "manifest length")?;
let ml = u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize;
check_manifest_len(ml as u64)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
need(8, ml, "manifest body")?;
let m = read_manifest(&bytes[8..8 + ml])
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let p_off = 8 + ml;
need(p_off, 4, "payload length")?;
let pl = u32::from_le_bytes(bytes[p_off..p_off + 4].try_into().unwrap()) as usize;
need(p_off + 4, pl, "payload body")?;
let payload = bytes[p_off + 4..p_off + 4 + pl].to_vec();
Ok(Self { manifest: m, payload })
}
pub fn capability(&self) -> &str {
&self.manifest.model_requirement.capability
}
pub fn fallback(&self) -> Option<&str> {
self.manifest.model_requirement.fallback.as_deref()
}
/// The manifest's `refines`, read with content-hash references, or `None` when it
/// carries none (`extensions/staged-delivery.md` §3).
pub fn refines(&self) -> Option<Result<Refines, RefinesMalformed>> {
self.manifest.refines.as_ref().map(|v| Refines::from_value(v, RefForm::ContentHash))
}
/// SPEC §4 dispatch at a sink that advertises the capabilities `advertises`
/// accepts and verifies pins with `verify` (SPEC §3.1). First, SPEC §6.4 step 2:
/// a manifest that declares an encryption other than `"none"` is inert here
/// ([`Inert::Encrypted`]), since this dispatcher opens no scheme. Step 3 takes the
/// capability when the sink advertises it and, where the pin applies to it,
/// `verify` accepts the pin; step 5 does the same for the fallback. A refused
/// pin makes that capability unsupported for this envelope: dispatch moves on,
/// keeping the first refusal, and an inert envelope reports it
/// ([`Inert::PinRefused`]) — else [`Inert::MissingCapability`]. One pin applies
/// to whichever capability decodes the payload. Only a capability
/// [`crate::codecs::is_dispatchable`] accepts is ever taken, whatever
/// `advertises` says:
///
/// - a companion-only capability ([`crate::codecs::is_companion_only`]) is
/// never dispatched standalone (SPEC §5): named as the capability, the
/// payload is companion data and the envelope is inert; named as the
/// fallback, step 5 is skipped, and step 3 is unaffected. So an envelope that
/// [`Self::check_component_roles`] reports for its fallback alone still
/// decodes at a sink that advertises its capability;
/// - a refinement-only capability ([`crate::codecs::is_refinement_only`]) is
/// handled alike (SPEC §4): its payload refines the stage below it and is
/// processed by `extensions/staged-delivery.md` §4 (`crate::staged`), never
/// decoded here;
/// - a candidate ([`crate::codecs::is_candidate`]) is treated as unsupported
/// (SPEC §5, *Candidate capabilities*): as the capability, step 5 may still
/// take a usable fallback; as the fallback, it is never taken;
/// - a record or a derivation names no payload format, so it is never taken;
/// as the capability, step 5 may still take a usable fallback;
/// - a string this revision does not register is left to `advertises`: a
/// later MINOR revision may register it (SPEC §8), and a sink that has a
/// codec for it says so by advertising it (SPEC §4, step 3). A pin on it is
/// verified as a set of several files ([`pin_applies_to`]), so it fails
/// closed.
pub fn dispatch_verified<V>(
&self,
advertises: impl Fn(&str) -> bool,
mut verify: impl FnMut(&str, &Prior) -> Result<V, PinRefusal>,
) -> Result<Dispatch<'_, V>, Inert> {
// SPEC §6.4 step 2, before §4: an encrypted payload is inert at a sink that
// opens no scheme.
if let Some(sec) = self.manifest.security.as_ref().filter(|s| !s.is_plaintext()) {
return Err(encryption_inert(format!("{:?}", sec.encryption.as_deref().unwrap_or_default())));
}
let primary = self.capability();
// A companion-only payload is companion data, and a refinement-only payload
// refines the stage below it (staged-delivery §4 processes it): neither is
// decoded on its own, so step 5 is not taken either.
if crate::codecs::is_companion_only(primary) || crate::codecs::is_refinement_only(primary) {
return Err(Inert::MissingCapability);
}
let mut first_refusal: Option<Inert> = None;
for cap in [Some(primary), self.fallback()].into_iter().flatten() {
if !crate::codecs::is_dispatchable(cap) || !advertises(cap) {
continue;
}
match self.prior() {
Some(p) if pin_applies_to(cap) => match verify(cap, p) {
Ok(v) => return Ok(Dispatch { capability: cap, verified: Some(v) }),
Err(refusal) => {
if first_refusal.is_none() {
first_refusal = Some(Inert::PinRefused { capability: cap.to_string(), refusal });
}
}
},
_ => return Ok(Dispatch { capability: cap, verified: None }),
}
}
Err(first_refusal.unwrap_or(Inert::MissingCapability))
}
/// What a verifier can decide about this envelope's pin without holding a
/// parameter set — the first finding, in [`PIN_FINDING_CODES`] order.
pub fn check_pin(&self) -> Result<(), PinFinding> {
let caps: Vec<&str> = [Some(self.capability()), self.fallback()].into_iter().flatten().collect();
static_pin_findings(&caps, self.prior(), self.intent())
}
pub fn media(&self) -> &str {
&self.manifest.media
}
pub fn intent(&self) -> &str {
&self.manifest.intent
}
pub fn kind(&self) -> &str {
&self.manifest.conditioning.kind
}
/// The pin, if the manifest carries one.
pub fn prior(&self) -> Option<&Prior> {
self.manifest.conditioning.prior.as_ref()
}
/// The embedding space the perceptual conformance threshold is measured in.
/// Defaults to `"clip"` when unset (back-compat).
pub fn sim_metric(&self) -> &str {
self.manifest.model_requirement.sim_metric.as_deref().unwrap_or("clip")
}
/// Whether this envelope makes a *sound* `replicate` claim (SPEC §7) through
/// its capability. See [`crate::codecs::replicate_refusal`].
pub fn replicate_is_sound(&self) -> bool {
self.replicate_refusal().is_none()
}
/// Why this envelope's `replicate` claim through its capability is unsound, or
/// `None` if it is sound (or does not claim `replicate`).
pub fn replicate_refusal(&self) -> Option<crate::codecs::ReplicateRefusal> {
self.replicate_refusal_for(&self.manifest.model_requirement.capability)
}
/// Why this envelope's `replicate` claim is unsound when it decodes through
/// `capability` (the capability, or the fallback at SPEC §4 step 5).
pub fn replicate_refusal_for(&self, capability: &str) -> Option<crate::codecs::ReplicateRefusal> {
crate::codecs::replicate_refusal(capability, &self.manifest.intent, self.prior())
}
/// Check this v1.0 envelope against the component-role rules of SPEC §5.
/// A companion-only capability ([`crate::codecs::is_companion_only`]: HDR
/// dynamic metadata, an LCEVC enhancement) means nothing without the base
/// payload it accompanies, and a `WAI1` carries exactly one payload, so it
/// MUST NOT be the envelope's `capability` — nor its `fallback`, because
/// dispatch step 5 would decode the payload as that capability on its own.
/// The v1.1 counterpart is [`WaiMulti::check_component_roles`].
///
/// A refinement-only capability ([`crate::codecs::is_refinement_only`]) is carried
/// as a `WAI1` `capability` only with a well-formed `refines` and a `null`
/// `fallback`, and is never a `fallback`; a manifest whose capability is not
/// refinement-only carries no `refines` (`extensions/staged-delivery.md` §3; SPEC
/// §8). Checked in that order, after the companion rules.
pub fn check_component_roles(&self) -> Result<(), RoleError> {
let capability = self.capability();
if crate::codecs::is_companion_only(capability) {
return Err(RoleError::CompanionOnlyAsCapability { capability: capability.to_string() });
}
if let Some(fb) = self.fallback() {
if crate::codecs::is_companion_only(fb) {
return Err(RoleError::CompanionOnlyAsFallback { capability: fb.to_string() });
}
}
let refinement = crate::codecs::is_refinement_only(capability);
if let Some(fb) = self.fallback() {
if crate::codecs::is_refinement_only(fb) {
return Err(RoleError::RefinementOnlyAsFallback { capability: fb.to_string() });
}
}
match (refinement, self.refines()) {
(true, None) => Err(RoleError::RefinementWithoutRefines { capability: capability.to_string() }),
(true, Some(Err(e))) => Err(RoleError::RefinesMalformed { index: None, detail: e.0 }),
(true, Some(Ok(_))) if self.fallback().is_some() => {
Err(RoleError::RefinementWithFallback { capability: capability.to_string() })
}
(false, Some(_)) => Err(RoleError::RefinesWithoutRefinement { capability: capability.to_string() }),
_ => Ok(()),
}
}
/// Check that this envelope names no candidate capability
/// ([`crate::codecs::is_candidate`]), which an encoder MUST NOT emit (SPEC §5,
/// *Candidate capabilities*): first the `capability`, then the `fallback`.
/// Parsing stays format-agnostic, so such an envelope still parses; this is
/// what reports it.
pub fn check_candidates(&self) -> Result<(), CandidateError> {
check_candidates_v1(self.capability(), self.fallback())
}
/// Check that the payload is the format SPEC §5 pins for the `capability`, and for
/// the `fallback` (step 5 decodes the same payload through it), where either is an
/// integer capability with a pinned format ([`check_int_payload_framing`]). An
/// encrypted payload (`security.encryption` other than `"none"`) is ciphertext, so
/// it is not checked here: a sink decrypts it first (SPEC §6.4) and checks the
/// plaintext when it decodes it.
pub fn check_int_payload_framing(&self) -> Result<(), FramingError> {
if !self.manifest.security.as_ref().is_none_or(Security::is_plaintext) {
return Ok(());
}
check_int_payload_framing(self.capability(), &self.payload)?;
match self.fallback() {
Some(fb) => check_int_payload_framing(fb, &self.payload),
None => Ok(()),
}
}
/// The envelope's bytes, for an encoder to emit: [`Self::check_candidates`], then
/// [`Self::check_int_payload_framing`], then [`Self::to_bytes`]. The C packer
/// `wai_envelope_pack` emits through this, so an encoder that uses it cannot emit a
/// candidate, or an integer payload that is not its capability's pinned format
/// (SPEC §5).
pub fn to_emit_bytes(&self) -> Result<Vec<u8>, EmitError> {
self.check_candidates().map_err(EmitError::Candidate)?;
self.check_int_payload_framing().map_err(|error| EmitError::IntPayload { component: None, error })?;
self.to_bytes().map_err(EmitError::Serialize)
}
/// Why this envelope's `replicate` claim is unsound by its pin alone. See
/// [`replicate_prior_refusal`].
pub fn replicate_prior_refusal(&self) -> Option<PriorRefusal> {
replicate_prior_refusal(&self.manifest.intent, self.prior())
}
}
/// Whether the JSON text `text` names a member twice in one object, at any depth
/// (SPEC §2: member names are unique within an object). `serde_json` keeps the last of
/// two equal names in a `Value` and refuses them only in a struct's own fields, so a
/// manifest is read once more by this check, which refuses any repeat. Names compare
/// after escapes are read, so `"a"` and `"a"` are one name. The text's own JSON
/// errors are reported as they are. A manifest is read by [`check_manifest_text`],
/// which makes this check and counts the manifest's values; this one counts nothing.
pub fn check_unique_members(text: &[u8]) -> Result<(), serde_json::Error> {
scan_json(text, usize::MAX, None).map(|_| ())
}
/// The checks a reader makes on a manifest's text before it builds anything from it
/// (SPEC §2, *Reader limits*): it is JSON, it names no member twice in one object
/// ([`check_unique_members`]), it nests at most 127 deep, and it holds at most
/// [`MANIFEST_MAX_VALUES`] values, refused with `manifest_too_many_values` as soon as the
/// count passes the limit, so a manifest past it is read only that far. The pass builds
/// no value: it holds the member names of each object open on the path to the value it
/// reads, borrowed from `text` unless a name carries an escape. Returns the number of
/// values.
pub fn check_manifest_text(text: &[u8]) -> Result<usize, serde_json::Error> {
scan_json(text, MANIFEST_MAX_VALUES, None)
}
/// One pass over the JSON text `text`: refuses a member named twice in one object, and
/// more than `max_values` values; returns how many values it holds. With `sizes`, pushes
/// each array's and object's number of elements or members, the containers in the order
/// they open.
fn scan_json(text: &[u8], max_values: usize, sizes: Option<&std::cell::RefCell<Vec<u32>>>) -> Result<usize, serde_json::Error> {
use serde::de::{DeserializeSeed, Deserializer, Error as _, MapAccess, SeqAccess, Visitor};
use std::borrow::Cow;
use std::cell::{Cell, RefCell};
/// One value, counted before it is read.
#[derive(Clone, Copy)]
struct Scan<'c> {
values: &'c Cell<usize>,
max: usize,
sizes: Option<&'c RefCell<Vec<u32>>>,
}
impl Scan<'_> {
/// A slot for the size of the container that opens now.
fn open(self) -> Option<usize> {
self.sizes.map(|s| {
let mut s = s.borrow_mut();
s.push(0);
s.len() - 1
})
}
/// The container in `slot` closed with `n` elements or members (at most
/// `max_values`, so within `u32` for a manifest).
fn close(self, slot: Option<usize>, n: usize) {
if let (Some(s), Some(i)) = (self.sizes, slot) {
s.borrow_mut()[i] = u32::try_from(n).unwrap_or(u32::MAX);
}
}
}
impl<'de> DeserializeSeed<'de> for Scan<'_> {
type Value = ();
fn deserialize<D: Deserializer<'de>>(self, d: D) -> Result<(), D::Error> {
let n = self.values.get() + 1;
if n > self.max {
return Err(D::Error::custom(format_args!(
"manifest_too_many_values: the manifest holds more than {} values, the most a reader need accept (SPEC §2)",
self.max
)));
}
self.values.set(n);
d.deserialize_any(self)
}
}
impl<'de> Visitor<'de> for Scan<'_> {
type Value = ();
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("a JSON value")
}
fn visit_bool<E>(self, _: bool) -> Result<(), E> {
Ok(())
}
fn visit_i64<E>(self, _: i64) -> Result<(), E> {
Ok(())
}
fn visit_u64<E>(self, _: u64) -> Result<(), E> {
Ok(())
}
fn visit_f64<E>(self, _: f64) -> Result<(), E> {
Ok(())
}
fn visit_str<E>(self, _: &str) -> Result<(), E> {
Ok(())
}
fn visit_unit<E>(self) -> Result<(), E> {
Ok(())
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<(), A::Error> {
let slot = self.open();
let mut n = 0;
while seq.next_element_seed(self)?.is_some() {
n += 1;
}
self.close(slot, n);
Ok(())
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<(), A::Error> {
let slot = self.open();
let mut seen = std::collections::HashSet::new();
while let Some(k) = map.next_key_seed(Name)? {
if !seen.insert(k) {
return Err(A::Error::custom("an object names a member twice"));
}
map.next_value_seed(self)?;
}
self.close(slot, seen.len());
Ok(())
}
}
/// A member's name, borrowed from the text when it carries no escape.
struct Name;
impl<'de> DeserializeSeed<'de> for Name {
type Value = Cow<'de, str>;
fn deserialize<D: Deserializer<'de>>(self, d: D) -> Result<Cow<'de, str>, D::Error> {
d.deserialize_str(Name)
}
}
impl<'de> Visitor<'de> for Name {
type Value = Cow<'de, str>;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("a member name")
}
fn visit_borrowed_str<E>(self, s: &'de str) -> Result<Cow<'de, str>, E> {
Ok(Cow::Borrowed(s))
}
fn visit_str<E>(self, s: &str) -> Result<Cow<'de, str>, E> {
Ok(Cow::Owned(s.to_owned()))
}
}
let values = Cell::new(0);
let mut de = serde_json::Deserializer::from_slice(text);
Scan { values: &values, max: max_values, sizes }.deserialize(&mut de)?;
de.end()?;
Ok(values.get())
}
/// A `WAI1` manifest read from its text as the reference readers read it (SPEC §2): the
/// checks of [`check_manifest_text`], then the [`Manifest`] that
/// `serde_json::from_slice::<Manifest>` builds from the same text, built with less
/// memory. The text is read once more into one JSON tree, each array and object
/// allocated at the size the first pass counted, and the manifest's members are moved
/// out of that tree into their fields; nothing is held twice, where serde's derived
/// reader holds every member a type does not define as an intermediate copy while it
/// builds the tree it keeps. So what a parse allocates is bounded by the value limit
/// and the text's length (SPEC §2, *Reader limits*; `tests/manifest_parse_memory.rs`).
pub fn read_manifest(text: &[u8]) -> Result<Manifest, serde_json::Error> {
manifest_from_tree(read_tree(text)?)
}
/// A `WAI2` manifest (SPEC §7.1) read as [`read_manifest`] reads a `WAI1`'s. A
/// `renditions` longer than the 65 535 rows a `WAI2` table holds is refused before its
/// entries are built.
pub fn read_manifest_v2(text: &[u8]) -> Result<ManifestV2, serde_json::Error> {
manifest_v2_from_tree(read_tree(text)?)
}
/// The JSON tree of the manifest text `text`, after [`check_manifest_text`]'s checks,
/// each array and object allocated at its size.
fn read_tree(text: &[u8]) -> Result<Value, serde_json::Error> {
use serde::de::{DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor};
use std::cell::{Cell, RefCell};
let sizes = RefCell::new(Vec::new());
scan_json(text, MANIFEST_MAX_VALUES, Some(&sizes))?;
let mut sizes = sizes.into_inner();
sizes.shrink_to_fit();
/// One value of the tree; `at` is the next container's index in `sizes`.
#[derive(Clone, Copy)]
struct Tree<'s> {
sizes: &'s [u32],
at: &'s Cell<usize>,
}
impl Tree<'_> {
fn next_size(self) -> usize {
let i = self.at.get();
self.at.set(i + 1);
self.sizes.get(i).map_or(0, |&n| n as usize)
}
}
impl<'de> DeserializeSeed<'de> for Tree<'_> {
type Value = Value;
fn deserialize<D: Deserializer<'de>>(self, d: D) -> Result<Value, D::Error> {
d.deserialize_any(self)
}
}
// As `serde_json`'s own `Value` visitor builds each value.
impl<'de> Visitor<'de> for Tree<'_> {
type Value = Value;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("a JSON value")
}
fn visit_bool<E>(self, b: bool) -> Result<Value, E> {
Ok(Value::Bool(b))
}
fn visit_i64<E>(self, n: i64) -> Result<Value, E> {
Ok(Value::Number(n.into()))
}
fn visit_u64<E>(self, n: u64) -> Result<Value, E> {
Ok(Value::Number(n.into()))
}
fn visit_f64<E>(self, x: f64) -> Result<Value, E> {
Ok(serde_json::Number::from_f64(x).map_or(Value::Null, Value::Number))
}
fn visit_str<E>(self, s: &str) -> Result<Value, E> {
Ok(Value::String(s.to_owned()))
}
fn visit_string<E>(self, s: String) -> Result<Value, E> {
Ok(Value::String(s))
}
fn visit_unit<E>(self) -> Result<Value, E> {
Ok(Value::Null)
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Value, A::Error> {
let mut v = Vec::with_capacity(self.next_size());
while let Some(x) = seq.next_element_seed(self)? {
v.push(x);
}
Ok(Value::Array(v))
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Value, A::Error> {
let mut m = Map::with_capacity(self.next_size());
while let Some(k) = map.next_key::<String>()? {
let v = map.next_value_seed(self)?;
m.insert(k, v);
}
Ok(Value::Object(m))
}
}
let at = Cell::new(0);
let mut de = serde_json::Deserializer::from_slice(text);
let v = Tree { sizes: &sizes, at: &at }.deserialize(&mut de)?;
de.end()?;
Ok(v)
}
// The manifest types built from a tree by moving its members, each member read as
// serde's derived reader reads it: a field `T` from its value as `T` reads it, a missing
// field refused unless the type gives it a default (an `Option` is `None`, `target` is
// `null`), an `Option` whose value is `null` is `None`, and the members a type does not
// define left in order in `extra`. `tests/manifest_reader_equivalence.rs` holds the two
// readers to the same result.
type Members = Map<String, Value>;
fn object_of(v: Value, what: &str) -> Result<Members, serde_json::Error> {
match v {
Value::Object(m) => Ok(m),
_ => Err(serde::de::Error::custom(format_args!("invalid type: {what} is not an object"))),
}
}
fn required<T: serde::de::DeserializeOwned>(m: &mut Members, k: &'static str) -> Result<T, serde_json::Error> {
match m.shift_remove(k) {
Some(v) => serde_json::from_value(v),
None => Err(serde::de::Error::missing_field(k)),
}
}
fn optional<T: serde::de::DeserializeOwned>(m: &mut Members, k: &str) -> Result<Option<T>, serde_json::Error> {
match m.shift_remove(k) {
Some(v) => serde_json::from_value(v),
None => Ok(None),
}
}
/// An `Option<Value>` member, moved: `null` or absent is `None`.
fn optional_tree(m: &mut Members, k: &str) -> Option<Value> {
m.shift_remove(k).filter(|v| !v.is_null())
}
/// An `Option<T>` member whose `T` is one of the manifest types.
fn optional_of<T>(m: &mut Members, k: &str, build: fn(Members) -> Result<T, serde_json::Error>) -> Result<Option<T>, serde_json::Error> {
match m.shift_remove(k) {
None | Some(Value::Null) => Ok(None),
Some(v) => build(object_of(v, k)?).map(Some),
}
}
fn required_of<T>(m: &mut Members, k: &'static str, build: fn(Members) -> Result<T, serde_json::Error>) -> Result<T, serde_json::Error> {
match m.shift_remove(k) {
None => Err(serde::de::Error::missing_field(k)),
Some(v) => build(object_of(v, k)?),
}
}
fn model_requirement_from(mut m: Members) -> Result<ModelRequirement, serde_json::Error> {
Ok(ModelRequirement {
capability: required(&mut m, "capability")?,
fallback: optional(&mut m, "fallback")?,
min_sim: optional(&mut m, "min_sim")?,
sim_metric: optional(&mut m, "sim_metric")?,
extra: m,
})
}
fn supersedes_from(mut m: Members) -> Result<Supersedes, serde_json::Error> {
Ok(Supersedes { sha256: required(&mut m, "sha256")?, form: optional(&mut m, "form")?, extra: m })
}
fn prior_from(mut m: Members) -> Result<Prior, serde_json::Error> {
Ok(Prior {
id: required(&mut m, "id")?,
sha256: required(&mut m, "sha256")?,
determinism: required(&mut m, "determinism")?,
form: optional(&mut m, "form")?,
tier: optional(&mut m, "tier")?,
supersedes: optional_of(&mut m, "supersedes", supersedes_from)?,
extra: m,
})
}
fn conditioning_from(mut m: Members) -> Result<Conditioning, serde_json::Error> {
Ok(Conditioning { kind: required(&mut m, "kind")?, prior: optional_of(&mut m, "prior", prior_from)?, extra: m })
}
fn security_from(mut m: Members) -> Result<Security, serde_json::Error> {
Ok(Security { encryption: optional(&mut m, "encryption")?, kid: optional(&mut m, "kid")?, extra: m })
}
fn manifest_from_tree(v: Value) -> Result<Manifest, serde_json::Error> {
let mut m = object_of(v, "the manifest")?;
Ok(Manifest {
wai: required(&mut m, "wai")?,
media: required(&mut m, "media")?,
intent: required(&mut m, "intent")?,
model_requirement: required_of(&mut m, "model_requirement", model_requirement_from)?,
conditioning: required_of(&mut m, "conditioning", conditioning_from)?,
security: optional_of(&mut m, "security", security_from)?,
refines: optional_tree(&mut m, "refines"),
derived_from: optional_tree(&mut m, "derived_from"),
target: m.shift_remove("target").unwrap_or(Value::Null),
extra: m,
})
}
fn rendition_from(mut m: Members) -> Result<RenditionMeta, serde_json::Error> {
Ok(RenditionMeta {
capability: required(&mut m, "capability")?,
kind: required(&mut m, "kind")?,
id: optional(&mut m, "id")?,
sha256: optional(&mut m, "sha256")?,
prior: optional_of(&mut m, "prior", prior_from)?,
role: optional(&mut m, "role")?,
companion_of: optional(&mut m, "companion_of")?,
companion_of_id: optional(&mut m, "companion_of_id")?,
refines: optional_tree(&mut m, "refines"),
envelope_manifest: optional_tree(&mut m, "envelope_manifest"),
derived_from: optional_tree(&mut m, "derived_from"),
extra: m,
})
}
fn manifest_v2_from_tree(v: Value) -> Result<ManifestV2, serde_json::Error> {
let mut m = object_of(v, "the manifest")?;
let wai = required(&mut m, "wai")?;
let media = required(&mut m, "media")?;
let intent = required(&mut m, "intent")?;
let renditions = match m.shift_remove("renditions") {
None => return Err(serde::de::Error::missing_field("renditions")),
Some(Value::Array(rs)) if rs.len() > u16::MAX as usize => {
return Err(serde::de::Error::custom(format_args!(
"the manifest lists {} renditions, more than the 65535 rows a WAI2 table holds (SPEC §7.1)",
rs.len()
)));
}
Some(Value::Array(rs)) => {
let mut out = Vec::with_capacity(rs.len());
for r in rs {
out.push(rendition_from(object_of(r, "a rendition")?)?);
}
out
}
Some(_) => return Err(serde::de::Error::custom("invalid type: renditions is not an array")),
};
Ok(ManifestV2 { wai, media, intent, renditions, target: m.shift_remove("target").unwrap_or(Value::Null), extra: m })
}
/// The manifest bytes of a `WAI1` or `WAI2` envelope, exactly as they were
/// written, bounds-checked as the parsers check them. A tool that hands a manifest
/// on (the C FFI's unpack calls) returns these, so nothing the Rust types do not
/// model is lost.
pub fn manifest_bytes(envelope: &[u8]) -> std::io::Result<&[u8]> {
let bad = |m: String| std::io::Error::new(std::io::ErrorKind::InvalidData, m);
if envelope.len() < 8 {
return Err(bad(format!("truncated envelope ({} bytes; need 8 for magic and manifest length)", envelope.len())));
}
if &envelope[..4] != MAGIC && &envelope[..4] != MAGIC_V2 {
return Err(bad("unknown magic (not WAI1/WAI2)".into()));
}
let ml = u32::from_le_bytes(envelope[4..8].try_into().unwrap()) as usize;
check_manifest_len(ml as u64).map_err(bad)?;
if 8usize.saturating_add(ml) > envelope.len() {
return Err(bad(format!("truncated envelope (manifest body needs {ml} bytes at offset 8, have {})", envelope.len())));
}
Ok(&envelope[8..8 + ml])
}
// ---- v1.1: multi-rendition envelope (WAI2) ----------------------
//
// One envelope carries several components: renditions of the SAME content,
// of which the sink picks one (SPEC §7.1), and companions applied with the
// rendition it picked. Each component may carry an identity: an `id`, its
// payload digest `sha256`, and its parameter-set pin `prior`.
/// Whether `id` is a component id (SPEC §7.1): 1 to 64 of `a`–`z`, `0`–`9`, `_`
/// and `-`. It contains no `.`, so it never equals a capability string.
pub fn is_component_id(id: &str) -> bool {
(1..=64).contains(&id.len())
&& id.bytes().all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_' || b == b'-')
}
/// A `WAI2` component entry (SPEC §7.1). Build one with [`RenditionMeta::new`].
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RenditionMeta {
pub capability: String,
pub kind: String,
/// The component's address: unique within the envelope ([`is_component_id`]).
#[serde(default, skip_serializing_if = "is_none")]
pub id: Option<String>,
/// The SHA-256 of this entry's payload bytes, as 64 lowercase hexadecimal
/// digits. A sink checks it before it decodes or applies the entry.
#[serde(default, skip_serializing_if = "is_none")]
pub sha256: Option<String>,
/// The pin (SPEC §3.1) of the parameter set this entry's payload was coded
/// against.
#[serde(default, skip_serializing_if = "is_none")]
pub prior: Option<Prior>,
/// Component role. Absent ⇒ `"rendition"` (a selectable alternative of the
/// same content). `"companion"` marks a component applied *with* the chosen
/// rendition: a companion-only capability ([`crate::codecs::is_companion_only`]).
#[serde(default, skip_serializing_if = "is_none")]
pub role: Option<String>,
/// For a companion: the capability of the component it accompanies.
#[serde(default, skip_serializing_if = "is_none")]
pub companion_of: Option<String>,
/// For a companion: the `id` of the one entry it binds to. That entry's
/// capability equals `companion_of`, so a sink that does not read this member
/// binds by capability.
#[serde(default, skip_serializing_if = "is_none")]
pub companion_of_id: Option<String>,
/// For a refinement (`role: "refinement"`): what it refines
/// (`extensions/staged-delivery.md` §3), its base and producers named by the `id`
/// of entries in this envelope (SPEC §7.1, *Refinement components*). Kept as the
/// JSON it is, so a malformed value still parses ([`Refines::from_value`] with
/// [`RefForm::ComponentId`] reports it).
#[serde(default, skip_serializing_if = "is_none")]
pub refines: Option<Value>,
/// The exact manifest of this entry as a `WAI1` envelope (SPEC §7.1, *Exploding a
/// component*): what [`WaiMulti::explode`] writes as the exploded envelope's
/// manifest, verbatim from the envelope bytes.
#[serde(default, skip_serializing_if = "is_none")]
pub envelope_manifest: Option<Value>,
/// `derived_from` (SPEC §7.1, *Derived renditions*): this entry's payload presents
/// another object, made from it by a named renderer or encoder. Kept as the JSON it
/// is ([`DerivedFrom::from_value`]).
#[serde(default, skip_serializing_if = "is_none")]
pub derived_from: Option<Value>,
/// Members this revision does not define, kept so they survive a round trip.
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl RenditionMeta {
/// A selectable rendition entry of `capability` and `kind`, with no identity
/// members.
pub fn new(capability: impl Into<String>, kind: impl Into<String>) -> Self {
Self {
capability: capability.into(),
kind: kind.into(),
id: None,
sha256: None,
prior: None,
role: None,
companion_of: None,
companion_of_id: None,
refines: None,
envelope_manifest: None,
derived_from: None,
extra: Map::new(),
}
}
/// Pin the parameter set this entry's payload was coded against.
pub fn with_prior(mut self, prior: Prior) -> Self {
self.prior = Some(prior);
self
}
/// Set the role.
pub fn with_role(mut self, role: impl Into<String>) -> Self {
self.role = Some(role.into());
self
}
/// Mark this entry a companion of the component of capability `base`.
pub fn companion_to(mut self, base: impl Into<String>) -> Self {
self.role = Some("companion".into());
self.companion_of = Some(base.into());
self
}
/// Set `derived_from` (SPEC §7.1, *Derived renditions*).
pub fn with_derived_from(mut self, d: &DerivedFrom) -> Self {
self.derived_from = Some(d.to_value());
self
}
/// Give the entry an id.
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
/// State the entry's payload digest.
pub fn with_sha256(mut self, sha256: impl Into<String>) -> Self {
self.sha256 = Some(sha256.into());
self
}
/// Bind this companion to the entry whose `id` is `id`.
pub fn with_companion_of_id(mut self, id: impl Into<String>) -> Self {
self.companion_of_id = Some(id.into());
self
}
/// Whether this component is a companion descriptor (not a selectable
/// rendition).
pub fn is_companion(&self) -> bool {
self.role.as_deref() == Some("companion")
}
/// Whether this component is a refinement (`role: "refinement"`, SPEC §7.1).
pub fn is_refinement(&self) -> bool {
self.role.as_deref() == Some("refinement")
}
/// Whether `pick`/`primary` may select this component: it is not marked a
/// companion **and** [`crate::codecs::is_dispatchable`] accepts its
/// capability — so not a companion-only capability, a candidate, a record or
/// a derivation. An HDR metadata block or an LCEVC enhancement mislabelled as
/// a rendition is still never selected — it has no picture or sound of its
/// own. [`WaiMulti::check_component_roles`] reports the mislabelling, and
/// [`WaiMulti::check_candidates`] a candidate.
pub fn is_selectable(&self) -> bool {
!self.is_companion() && !self.is_refinement() && crate::codecs::is_dispatchable(&self.capability)
}
/// Whether `payload` matches this entry's stated `sha256` (true when none is
/// stated). A malformed digest matches nothing.
pub fn payload_digest_matches(&self, payload: &[u8]) -> bool {
match self.sha256.as_deref() {
None => true,
Some(want) => is_sha256_hex(want) && crate::pin::sha256_hex(payload) == want,
}
}
/// Whether this entry's `sha256` is absent or well formed — 64 lowercase hex digits
/// ([`is_sha256_hex`]) — read from the entry alone: one that is not matches no payload
/// ([`Self::payload_digest_matches`]), so no sink decodes the entry whatever its
/// payload. Of an encrypted envelope, whose payloads a verifier without the key does
/// not read, this is what the entry gives of its digest (jwp-receipts §2.4.12 rule 4).
pub fn digest_well_formed(&self) -> bool {
self.sha256.as_deref().is_none_or(is_sha256_hex)
}
}
/// An envelope that breaks the component rules of SPEC §5 / §7.1. Returned by
/// [`Wai::check_component_roles`] (v1.0) and [`WaiMulti::check_component_roles`]
/// (v1.1); `index` is the component's position in `manifest.renditions`.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RoleError {
/// v1.0: a companion-only capability named as the `WAI1` `capability`. A
/// `WAI1` carries one payload, so there is no base for it to accompany.
CompanionOnlyAsCapability { capability: String },
/// v1.0: a companion-only capability named as the `WAI1` `fallback`.
/// Dispatch step 5 would decode the payload as that capability on its own.
CompanionOnlyAsFallback { capability: String },
/// A companion-only capability (HDR dynamic metadata, an enhancement
/// layer) listed as a selectable rendition instead of as a companion.
CompanionOnlyAsRendition { index: usize, capability: String },
/// A registered payload capability, standalone media (one that is not
/// companion-only, such as `wai.audio.iamf` or `wai.video.hevc`), marked
/// `role = "companion"`.
StandaloneAsCompanion { index: usize, capability: String },
/// A component, in any role, whose capability is a record or a derivation (SPEC §5,
/// *Kinds of registered string*), which names no payload format.
RecordOrDerivationAsComponent { index: usize, capability: String },
/// A companion without `companion_of`, or whose `companion_of` (or
/// `companion_of_id`) names no selectable rendition in this envelope.
UnboundCompanion { index: usize, capability: String },
/// A companion whose `companion_of` names a capability it does not apply to
/// ([`crate::codecs::companion_applies_to`]).
IncompatibleBase { index: usize, capability: String, base: String },
/// An enhancement layer, bound by capability alone, whose `companion_of` does
/// not resolve to exactly one selectable rendition. With two renditions of the
/// base capability, `companion_of_id` names the one it was encoded against.
EnhancementBaseNotUnique { index: usize, capability: String, matches: usize },
/// A second enhancement layer bound to a base that already has one (the
/// component at `first`).
DuplicateEnhancementLayer { index: usize, capability: String, base: String, first: usize },
/// A component `id` that is not 1 to 64 of `a`–`z`, `0`–`9`, `_`, `-`.
ComponentIdMalformed { index: usize },
/// A component `id` an earlier component (at `first`) already has.
DuplicateComponentId { index: usize, first: usize },
/// A companion whose `companion_of_id` names no component in the envelope.
CompanionIdUnresolved { index: usize, id: String },
/// A companion whose `companion_of_id` names a component whose capability is
/// `capability`, not the companion's `companion_of`.
CompanionIdCapabilityMismatch { index: usize, id: String, capability: String },
/// v1.0: a refinement-only capability named as the `WAI1` `fallback`. It is never
/// decoded on its own.
RefinementOnlyAsFallback { capability: String },
/// v1.0: a refinement-only `capability` with no `refines`
/// (`extensions/staged-delivery.md` §3).
RefinementWithoutRefines { capability: String },
/// v1.0: a refinement-only `capability` with a `fallback`; its fallback MUST be
/// `null`.
RefinementWithFallback { capability: String },
/// v1.0: `refines` on a manifest whose capability is not refinement-only.
RefinesWithoutRefinement { capability: String },
/// A `refines` member that is malformed (`refines-malformed`): the `WAI2`
/// component's index, or `None` for a `WAI1` manifest.
RefinesMalformed { index: Option<usize>, detail: &'static str },
/// v1.1: a refinement-only capability carried as a rendition or a companion.
RefinementOnlyAsComponent { index: usize, capability: String },
/// v1.1: `refines` on an entry whose role is not `"refinement"`.
RefinesOnNonRefinement { index: usize },
/// v1.1: a `role: "refinement"` entry whose capability is not refinement-only.
RefinementNotRefinementOnly { index: usize, capability: String },
/// v1.1: a refinement, or an entry it names, without an `id` or a `sha256`.
RefinementIdentityMissing { index: usize },
/// v1.1: a refinement whose `refines.base` names no selectable entry.
RefinementBaseMissing { index: usize, base: String },
/// v1.1: a refinement whose capability does not admit its base entry's (SPEC §5,
/// *Base admission*).
RefinementBaseNotAdmitted { index: usize, capability: String, base: String },
/// v1.1: a producer in a refinement's `prev` that names neither a selectable entry
/// nor a refinement before it.
RefinementPrevMissing { index: usize, producer: String },
/// v1.1: a refinement placed before a selectable entry.
RefinementBeforeSelectable { index: usize, selectable: usize },
}
impl RoleError {
/// A token for the error, for tools and corpora.
pub fn code(&self) -> &'static str {
match self {
RoleError::CompanionOnlyAsCapability { .. } => "companion-only-as-capability",
RoleError::CompanionOnlyAsFallback { .. } => "companion-only-as-fallback",
RoleError::CompanionOnlyAsRendition { .. } => "companion-only-as-rendition",
RoleError::StandaloneAsCompanion { .. } => "standalone-as-companion",
RoleError::RecordOrDerivationAsComponent { .. } => "record-or-derivation-as-component",
RoleError::UnboundCompanion { .. } => "unbound-companion",
RoleError::IncompatibleBase { .. } => "incompatible-base",
RoleError::EnhancementBaseNotUnique { .. } => "enhancement-base-not-unique",
RoleError::DuplicateEnhancementLayer { .. } => "duplicate-enhancement-layer",
RoleError::ComponentIdMalformed { .. } => "component-id-malformed",
RoleError::DuplicateComponentId { .. } => "duplicate-component-id",
RoleError::CompanionIdUnresolved { .. } => "companion-id-unresolved",
RoleError::CompanionIdCapabilityMismatch { .. } => "companion-id-capability-mismatch",
RoleError::RefinementOnlyAsFallback { .. } => "refinement-only-as-fallback",
RoleError::RefinementWithoutRefines { .. } => "refinement-without-refines",
RoleError::RefinementWithFallback { .. } => "refinement-with-fallback",
RoleError::RefinesWithoutRefinement { .. } => "refines-without-refinement",
RoleError::RefinesMalformed { .. } => "refines-malformed",
RoleError::RefinementOnlyAsComponent { .. } => "refinement-only-as-component",
RoleError::RefinesOnNonRefinement { .. } => "refines-on-non-refinement",
RoleError::RefinementNotRefinementOnly { .. } => "refinement-not-refinement-only",
RoleError::RefinementIdentityMissing { .. } => "refinement-identity-missing",
RoleError::RefinementBaseMissing { .. } => "refinement-base-missing",
RoleError::RefinementBaseNotAdmitted { .. } => "refinement-base-not-admitted",
RoleError::RefinementPrevMissing { .. } => "refinement-prev-missing",
RoleError::RefinementBeforeSelectable { .. } => "refinement-before-selectable",
}
}
}
impl std::fmt::Display for RoleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RoleError::CompanionOnlyAsCapability { capability } => write!(
f, "{capability} is companion-only and cannot be a WAI1 capability; carry it as a WAI2 companion bound to its base rendition"),
RoleError::CompanionOnlyAsFallback { capability } => write!(
f, "{capability} is companion-only and cannot be a WAI1 fallback; it is never dispatched on its own"),
RoleError::DuplicateEnhancementLayer { index, capability, base, first } => write!(
f, "enhancement layer {index} ({capability}) binds to {base}, which already has enhancement layer {first}; a base takes at most one"),
RoleError::CompanionOnlyAsRendition { index, capability } => write!(
f, "component {index} ({capability}) is companion-only but is not marked role=\"companion\""),
RoleError::StandaloneAsCompanion { index, capability } => write!(
f, "component {index} ({capability}) is standalone media, not a companion; carry it as a rendition"),
RoleError::RecordOrDerivationAsComponent { index, capability } => write!(
f, "component {index} ({capability}) is a record or a derivation, which names no payload format: no component of a WAI2"),
RoleError::UnboundCompanion { index, capability } => write!(
f, "companion {index} ({capability}) names no selectable rendition in companion_of"),
RoleError::IncompatibleBase { index, capability, base } => {
let bases = crate::codecs::enhancement_bases(capability);
if bases.is_empty() {
write!(f, "companion {index} ({capability}) does not apply to {base}; \
it describes a video payload (a wai.video.* capability, \
a neural video codec or a film)")
} else {
write!(f, "companion {index} ({capability}) does not apply to {base}; \
its base codecs are {}", bases.join(", "))
}
}
RoleError::EnhancementBaseNotUnique { index, capability, matches } => write!(
f, "enhancement layer {index} ({capability}) must bind to exactly one selectable video rendition; companion_of matches {matches} (name the base by companion_of_id)"),
RoleError::ComponentIdMalformed { index } => write!(
f, "component {index} has an id that is not 1 to 64 of a-z, 0-9, '_' and '-'"),
RoleError::DuplicateComponentId { index, first } => write!(
f, "component {index} repeats the id of component {first}"),
RoleError::CompanionIdUnresolved { index, id } => write!(
f, "companion {index} names companion_of_id '{id}', which no component has"),
RoleError::CompanionIdCapabilityMismatch { index, id, capability } => write!(
f, "companion {index} names companion_of_id '{id}', whose capability {capability} is not its companion_of"),
RoleError::RefinementOnlyAsFallback { capability } => write!(
f, "{capability} is refinement-only and cannot be a WAI1 fallback; it is never decoded on its own"),
RoleError::RefinementWithoutRefines { capability } => write!(
f, "{capability} is refinement-only and its manifest carries no refines (extensions/staged-delivery.md \u{a7}3)"),
RoleError::RefinementWithFallback { capability } => write!(
f, "{capability} is refinement-only, so its fallback must be null"),
RoleError::RefinesWithoutRefinement { capability } => write!(
f, "refines is carried with {capability}, which is not refinement-only"),
RoleError::RefinesMalformed { index: None, detail } => write!(f, "refines-malformed: {detail}"),
RoleError::RefinesMalformed { index: Some(i), detail } => write!(f, "component {i}: refines-malformed: {detail}"),
RoleError::RefinementOnlyAsComponent { index, capability } => write!(
f, "component {index} ({capability}) is refinement-only and is not carried with role=\"refinement\""),
RoleError::RefinesOnNonRefinement { index } => write!(
f, "component {index} carries refines but its role is not \"refinement\""),
RoleError::RefinementNotRefinementOnly { index, capability } => write!(
f, "component {index} has role=\"refinement\" but {capability} is not refinement-only"),
RoleError::RefinementIdentityMissing { index } => write!(
f, "refinement {index}, or an entry it names, carries no id or no sha256 (SPEC \u{a7}7.1)"),
RoleError::RefinementBaseMissing { index, base } => write!(
f, "refinement {index} names base '{base}', which is no selectable entry"),
RoleError::RefinementBaseNotAdmitted { index, capability, base } => write!(
f, "refinement {index} ({capability}) does not apply over {base} (SPEC \u{a7}5, Base admission)"),
RoleError::RefinementPrevMissing { index, producer } => write!(
f, "refinement {index} names producer '{producer}', which is neither a selectable entry nor a refinement before it"),
RoleError::RefinementBeforeSelectable { index, selectable } => write!(
f, "refinement {index} precedes selectable entry {selectable}; refinements follow every selectable entry"),
}
}
}
impl std::error::Error for RoleError {}
/// An envelope that names a candidate capability ([`crate::codecs::is_candidate`]),
/// which an encoder MUST NOT emit (SPEC §5, *Candidate capabilities*). Returned by
/// [`Wai::check_candidates`] and [`WaiMulti::check_candidates`]. The envelope still
/// parses.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CandidateError {
/// v1.0: a candidate named as the `WAI1` `capability`.
AsCapability { capability: String },
/// v1.0: a candidate named as the `WAI1` `fallback`.
AsFallback { capability: String },
/// v1.1: a `WAI2` component, in either role, whose capability is a candidate.
AsComponent { index: usize, capability: String },
}
const CANDIDATE_TAIL: &str = "is a candidate capability: no payload format is pinned for it, so an encoder MUST NOT emit it (SPEC \u{a7}5, Candidate capabilities)";
impl std::fmt::Display for CandidateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CandidateError::AsCapability { capability } => write!(f, "the capability {capability} {CANDIDATE_TAIL}"),
CandidateError::AsFallback { capability } => write!(f, "the fallback {capability} {CANDIDATE_TAIL}"),
CandidateError::AsComponent { index, capability } => write!(f, "component {index} ({capability}) {CANDIDATE_TAIL}"),
}
}
}
impl std::error::Error for CandidateError {}
/// [`Wai::check_candidates`] over a `WAI1` manifest's two fields: the
/// `capability`, then the `fallback`. For a writer that holds the manifest fields
/// rather than a [`Wai`] (the wasm `pack`).
pub fn check_candidates_v1(capability: &str, fallback: Option<&str>) -> Result<(), CandidateError> {
if crate::codecs::is_candidate(capability) {
return Err(CandidateError::AsCapability { capability: capability.to_string() });
}
match fallback {
Some(fb) if crate::codecs::is_candidate(fb) => Err(CandidateError::AsFallback { capability: fb.to_string() }),
_ => Ok(()),
}
}
/// [`WaiMulti::check_candidates`] over a `WAI2` component table's capabilities,
/// in table order. For a writer that holds the table rather than a [`WaiMulti`]
/// (the wasm `packMulti`).
pub fn check_component_candidates<'a>(capabilities: impl IntoIterator<Item = &'a str>) -> Result<(), CandidateError> {
match capabilities.into_iter().enumerate().find(|(_, c)| crate::codecs::is_candidate(c)) {
Some((index, capability)) => Err(CandidateError::AsComponent { index, capability: capability.to_string() }),
None => Ok(()),
}
}
/// Whether a `WAI2` manifest's payloads are plaintext: no `security` member, or one
/// whose `encryption` is absent, `null` or `"none"` (SPEC §6). A `security` member
/// that is not a security object is not plaintext: a sink treats the payloads as
/// ciphertext it cannot open, and an encoder does not write it
/// ([`manifest_v2_security`]).
pub fn manifest_v2_plaintext(m: &ManifestV2) -> bool {
matches!(manifest_v2_security(m), Ok(s) if s.as_ref().is_none_or(Security::is_plaintext))
}
/// A `WAI2` manifest's `security` member (SPEC §6.1, §7.1): `None` when absent or
/// `null`; the object; or why the member is not a security object (`true`, a string,
/// an array, an `encryption` that is not a string, …), which an encoder does not write
/// ([`WaiMulti::to_emit_bytes`]).
pub fn manifest_v2_security(m: &ManifestV2) -> Result<Option<Security>, String> {
match m.extra.get("security") {
None | Some(Value::Null) => Ok(None),
Some(v) => serde_json::from_value::<Security>(v.clone()).map(Some)
.map_err(|e| format!("the manifest's `security` member {v} is not a security object (SPEC §6.1): {e}")),
}
}
/// Why a payload is not the format SPEC §5 pins its capability to (*Payloads of the
/// WAI-native integer codecs*, `extensions/integer-payloads.md`): the capability, the
/// code the extension's §8 names and what failed. The payload is parsed as a sink parses
/// it (§2–§6), so every code a parse can give is possible here: `bad_magic` (none of the
/// four formats; a JSON form lands here), `format_capability_mismatch`,
/// `keyframe_capability_mismatch`, `version`, `reserved_nonzero`, `field_range`,
/// `too_large`, `cdf_malformed`, `stream_length`, `nested_length` and `trailing`. What
/// only a decode checks, against a parameter set and through the streams (§7), is not.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FramingError {
pub capability: String,
pub code: &'static str,
pub detail: &'static str,
}
impl std::fmt::Display for FramingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let want = if crate::codecs::is_refinement_only(&self.capability) {
Some("WIR1".to_string())
} else {
crate::codecs::pinned_int_payload(&self.capability).map(|m| String::from_utf8_lossy(&m).into_owned())
};
write!(f, "the payload is not the {} payload SPEC \u{a7}5 pins {} to ({}: {}; extensions/integer-payloads.md)",
want.as_deref().unwrap_or("?"), self.capability, self.code, self.detail)
}
}
impl std::error::Error for FramingError {}
/// Check that `payload` is the format SPEC §5 pins `capability`'s payload to
/// ([`crate::codecs::PINNED_INT_PAYLOADS`]): it is parsed with the wire parser a sink
/// runs (`int_payload::wire`, with the capability's keyframe kind for `WIV1`), which
/// checks every rule of the extension's §2–§6 in its order and reports the first that
/// fails ([`FramingError`]). `Ok` for a capability with no pinned integer format. It
/// needs no parameter set and no integer decoder, so every emit path runs it
/// ([`Wai::to_emit_bytes`], [`WaiMulti::to_emit_bytes`], `wai wrap`, the browser's
/// writers). `payload` is plaintext: an encrypted envelope's payload is checked after
/// it is decrypted (SPEC §6.4), and its emit paths skip the check.
pub fn check_int_payload_framing(capability: &str, payload: &[u8]) -> Result<(), FramingError> {
use crate::int_wire as w;
if crate::codecs::is_refinement_only(capability) {
return check_refinement_framing(capability, payload);
}
let parsed = match crate::codecs::pinned_int_payload(capability) {
None => return Ok(()),
Some(w::MAGIC_WIV1) => w::parse_wiv1(payload, Some((capability == crate::codecs::CAP_VIDEO_INT_HYPER) as u8)).map(|_| ()),
Some(w::MAGIC_WIH1) => w::parse_wih1(payload).map(|_| ()),
Some(w::MAGIC_WIA1) => w::parse_wia1(payload).map(|_| ()),
Some(_) => w::parse_wis1(payload).map(|_| ()),
};
parsed.map_err(|e| FramingError { capability: capability.to_string(), code: e.code(), detail: e.detail })
}
/// The framing check of a refinement-only capability's payload: `WIR1`, parsed for
/// the capability's unit kind (`extensions/staged-delivery.md` Appendix A.1). A build
/// without the `staged` feature has no `WIR1` parser, so it refuses to emit one
/// (`unsupported_capability`).
#[cfg(feature = "staged")]
fn check_refinement_framing(capability: &str, payload: &[u8]) -> Result<(), FramingError> {
let fail = |code: &'static str, detail: &'static str| FramingError { capability: capability.to_string(), code, detail };
let kind = crate::staged::unit_kind(capability).ok_or(fail("unsupported_capability", "no unit kind for this refinement"))?;
crate::staged::wir1::parse(payload, kind).map(|_| ()).map_err(|e| fail(e.code(), e.detail))
}
#[cfg(not(feature = "staged"))]
fn check_refinement_framing(capability: &str, _: &[u8]) -> Result<(), FramingError> {
Err(FramingError {
capability: capability.to_string(),
code: "unsupported_capability",
detail: "this build cannot check a WIR1 payload (feature staged)",
})
}
/// What an integer payload's header declares about its decode, read before any
/// symbol is decoded and with no parameter set (`extensions/integer-payloads.md` §7).
/// A sink applies its own size policy to it and refuses a payload past that policy
/// before decoding; `int_payload::declared_cost` adds what the parameter set fixes
/// (the networks' buffers, a `WIH1` image's size).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct DeclaredSize {
/// The decoded output's bytes, where the header alone fixes it: the frames of a
/// `WIV1` (`n_frames·h·w·3`), the PCM of a `WIA1` (`2·t_out`), the attribute tensor
/// of a `WIS1` (`attr·s·s`). `None` for a `WIH1`, whose image size is its
/// synthesis network's.
pub output_bytes: Option<u64>,
/// The symbols the payload's streams code: one entropy-decode step each.
pub symbols: u64,
/// The most bytes the reference decode holds at once beyond the payload, where the
/// header alone fixes it: a `WIV1` with keyframe 0
/// ([`crate::int_wire::Wiv1::keyframe0_working_bytes`]). `None` where a network
/// runs, whose buffers the parameter set sizes.
pub working_bytes: Option<u64>,
/// The networks' multiply-accumulates, where the header alone fixes them: 0 for a
/// `WIV1` with keyframe 0, which runs no network; `None` otherwise.
pub macs: Option<u128>,
}
/// [`DeclaredSize`] for `payload` under `capability`, after the parse
/// [`check_int_payload_framing`] makes. `Ok(None)` for a capability with no pinned
/// integer format.
pub fn declared_size(capability: &str, payload: &[u8]) -> Result<Option<DeclaredSize>, FramingError> {
use crate::int_wire as w;
let fail = |e: w::WireError| FramingError { capability: capability.to_string(), code: e.code(), detail: e.detail };
let wih1 = |k: &w::Wih1<'_>| k.n_z as u64 * k.zh as u64 * k.zw as u64 + k.m as u64 * k.sh as u64 * k.sw as u64;
Ok(Some(match crate::codecs::pinned_int_payload(capability) {
None => return Ok(None),
Some(w::MAGIC_WIV1) => {
let v = w::parse_wiv1(payload, Some((capability == crate::codecs::CAP_VIDEO_INT_HYPER) as u8)).map_err(fail)?;
let frame = v.h as u64 * v.w as u64 * 3;
let output_bytes = Some(frame * v.n_frames as u64);
match v.wih1 {
None => DeclaredSize { output_bytes, symbols: frame * v.n_frames as u64, working_bytes: Some(v.keyframe0_working_bytes()), macs: Some(0) },
Some(k) => {
let symbols = frame * (v.n_frames as u64 - 1) + wih1(&w::parse_wih1(k).map_err(fail)?);
DeclaredSize { output_bytes, symbols, working_bytes: None, macs: None }
}
}
}
Some(w::MAGIC_WIH1) => DeclaredSize { output_bytes: None, symbols: wih1(&w::parse_wih1(payload).map_err(fail)?), working_bytes: None, macs: None },
Some(w::MAGIC_WIA1) => {
let a = w::parse_wia1(payload).map_err(fail)?;
DeclaredSize { output_bytes: Some(2 * a.t_out as u64), symbols: a.n_ch as u64 * a.lat_len as u64, working_bytes: None, macs: None }
}
Some(_) => {
let v = w::parse_wis1(payload).map_err(fail)?;
DeclaredSize { output_bytes: Some(v.attr as u64 * v.s as u64 * v.s as u64), symbols: v.c as u64 * v.sh as u64 * v.sw as u64, working_bytes: None, macs: None }
}
}))
}
/// Why [`Wai::to_emit_bytes`] or [`WaiMulti::to_emit_bytes`] wrote nothing.
#[derive(Debug)]
#[non_exhaustive]
pub enum EmitError {
/// The envelope names a candidate capability (SPEC §5).
Candidate(CandidateError),
/// The envelope did not serialise.
Serialize(std::io::Error),
/// A payload is not the format SPEC §5 pins its integer capability to
/// ([`check_int_payload_framing`]): the `WAI2` component's index, or `None` for a
/// `WAI1`'s payload.
IntPayload { component: Option<usize>, error: FramingError },
/// A `WAI2` manifest's `security` member is not a security object
/// ([`manifest_v2_security`]): an encoder that writes one could claim an
/// encryption no sink can read, or none.
Security(String),
}
impl std::fmt::Display for EmitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EmitError::Candidate(e) => e.fmt(f),
EmitError::Serialize(e) => write!(f, "envelope serialize: {e}"),
EmitError::IntPayload { component: Some(i), error } => write!(f, "component {i}: {error}"),
EmitError::IntPayload { component: None, error } => error.fmt(f),
EmitError::Security(why) => f.write_str(why),
}
}
}
impl std::error::Error for EmitError {}
/// A `WAI2` manifest (SPEC §7.1). Build one with [`ManifestV2::new`].
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ManifestV2 {
pub wai: String, // "1.1"
pub media: String,
pub intent: String,
pub renditions: Vec<RenditionMeta>,
#[serde(default)]
pub target: Value,
/// Members this revision does not define, kept so they survive a round trip.
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl ManifestV2 {
/// The index of the base a companion at `index` binds to, when it binds to one
/// entry: the entry `companion_of_id` names, or the one selectable entry of
/// capability `companion_of`.
fn bound_base(&self, index: usize) -> Option<usize> {
let metas = &self.renditions;
let m = metas.get(index)?;
if let Some(id) = m.companion_of_id.as_deref() {
return metas.iter().position(|b| b.id.as_deref() == Some(id));
}
let target = m.companion_of.as_deref()?;
let mut it = metas.iter().enumerate().filter(|(_, b)| b.is_selectable() && b.capability == target);
match (it.next(), it.next()) {
(Some((i, _)), None) => Some(i),
_ => None,
}
}
/// SPEC §7.1, *Refinement components*, for the entry at `index`, in this order:
/// its capability is refinement-only; it carries `id` and `sha256`; its `refines`
/// is well formed with component ids; `refines.base` names a selectable entry with
/// an `id` and a `sha256`; its capability admits that entry's (SPEC §5, *Base
/// admission*); every producer of `prev` names a selectable entry or a refinement
/// before it, with an `id` and a `sha256`; and it follows every selectable entry.
fn check_refinement(&self, index: usize) -> Result<(), RoleError> {
let metas = &self.renditions;
let m = &metas[index];
if !crate::codecs::is_refinement_only(&m.capability) {
return Err(RoleError::RefinementNotRefinementOnly { index, capability: m.capability.clone() });
}
if m.id.is_none() || m.sha256.is_none() {
return Err(RoleError::RefinementIdentityMissing { index });
}
let refines = match m.refines.as_ref().map(|v| Refines::from_value(v, RefForm::ComponentId)) {
Some(Ok(r)) => r,
Some(Err(e)) => return Err(RoleError::RefinesMalformed { index: Some(index), detail: e.0 }),
None => return Err(RoleError::RefinesMalformed { index: Some(index), detail: "a refinement carries no refines" }),
};
let by_id = |id: &str| metas.iter().position(|e| e.id.as_deref() == Some(id));
let Some(b) = by_id(&refines.base).filter(|&b| metas[b].is_selectable()) else {
return Err(RoleError::RefinementBaseMissing { index, base: refines.base.clone() });
};
if metas[b].sha256.is_none() {
return Err(RoleError::RefinementIdentityMissing { index });
}
if !crate::codecs::admits(&m.capability, &metas[b].capability) {
return Err(RoleError::RefinementBaseNotAdmitted { index, capability: m.capability.clone(), base: metas[b].capability.clone() });
}
for p in refines.producers() {
let ok = by_id(p).filter(|&e| metas[e].is_selectable() || (e < index && metas[e].is_refinement()));
match ok {
None => return Err(RoleError::RefinementPrevMissing { index, producer: p.to_string() }),
Some(e) if metas[e].sha256.is_none() => return Err(RoleError::RefinementIdentityMissing { index }),
Some(_) => {}
}
}
if let Some(selectable) = (index + 1..metas.len()).find(|&j| metas[j].is_selectable()) {
return Err(RoleError::RefinementBeforeSelectable { index, selectable });
}
Ok(())
}
/// Check the component table against SPEC §5 / §7.1, using the capability
/// registry, in component order (the first violation is returned):
///
/// - every `id` is a component id ([`is_component_id`]) and unique
/// ([`RoleError::ComponentIdMalformed`], [`RoleError::DuplicateComponentId`]);
/// - a companion-only capability is carried with `role = "companion"`
/// ([`RoleError::CompanionOnlyAsRendition`]);
/// - a `role = "refinement"` entry obeys *Refinement components*
/// ([`Self::check_refinement`]'s rules); `refines` sits on no other entry, and a
/// refinement-only capability on no other role
/// ([`RoleError::RefinesOnNonRefinement`], [`RoleError::RefinementOnlyAsComponent`]);
///
/// and, for each `role = "companion"` component, SPEC §7.1's three rules:
///
/// 1. its capability is companion-only or unregistered: not a payload
/// ([`RoleError::StandaloneAsCompanion`]); a record or a derivation is no component
/// in any role ([`RoleError::RecordOrDerivationAsComponent`], checked first);
/// 2. `companion_of` is present ([`RoleError::UnboundCompanion`]) and names a
/// capability it applies to ([`RoleError::IncompatibleBase`]); a
/// `companion_of_id` resolves to an entry ([`RoleError::CompanionIdUnresolved`])
/// whose capability is `companion_of`
/// ([`RoleError::CompanionIdCapabilityMismatch`]);
/// 3. a selectable rendition of that capability is present
/// ([`RoleError::UnboundCompanion`]) — with `companion_of_id`, exactly that
/// entry; for an enhancement layer bound by capability alone, exactly one
/// ([`RoleError::EnhancementBaseNotUnique`]); and a base takes at most one
/// enhancement layer, checked by index ([`RoleError::DuplicateEnhancementLayer`]).
///
/// A descriptor companion (HDR dynamic metadata) bound by capability may bind
/// to several renditions of one capability, and several descriptors may bind
/// to one base. A companion whose capability this revision does not register is
/// held to rule 3's presence check only.
pub fn check_component_roles(&self) -> Result<(), RoleError> {
let metas = &self.renditions;
for (index, m) in metas.iter().enumerate() {
if let Some(id) = m.id.as_deref() {
if !is_component_id(id) {
return Err(RoleError::ComponentIdMalformed { index });
}
if let Some(first) = metas[..index].iter().position(|e| e.id.as_deref() == Some(id)) {
return Err(RoleError::DuplicateComponentId { index, first });
}
}
}
for (index, m) in metas.iter().enumerate() {
let capability = m.capability.clone();
// A record or a derivation names no payload format: no component in any role.
if matches!(crate::codecs::capability_kind(&m.capability), Some(crate::codecs::Kind::Record | crate::codecs::Kind::Derivation)) {
return Err(RoleError::RecordOrDerivationAsComponent { index, capability });
}
if m.is_refinement() {
self.check_refinement(index)?;
continue;
}
if m.refines.is_some() {
return Err(RoleError::RefinesOnNonRefinement { index });
}
if crate::codecs::is_refinement_only(&m.capability) {
return Err(RoleError::RefinementOnlyAsComponent { index, capability });
}
let companion_only = crate::codecs::is_companion_only(&m.capability);
if !m.is_companion() {
if companion_only {
return Err(RoleError::CompanionOnlyAsRendition { index, capability });
}
continue;
}
// Rule 1: only a companion-only (or unregistered) capability is a companion; a
// payload is standalone media (a record or a derivation was refused above).
if !companion_only && crate::codecs::capability_kind(&m.capability).is_some() {
return Err(RoleError::StandaloneAsCompanion { index, capability });
}
let Some(target) = m.companion_of.as_deref() else {
return Err(RoleError::UnboundCompanion { index, capability });
};
// Rule 2: the named base is one this companion applies to.
if companion_only && !crate::codecs::companion_applies_to(&m.capability, target) {
let base = target.to_string();
return Err(RoleError::IncompatibleBase { index, capability, base });
}
// Rule 2, by id: the named entry exists and has the named capability.
if let Some(id) = m.companion_of_id.as_deref() {
let Some(b) = metas.iter().position(|e| e.id.as_deref() == Some(id)) else {
return Err(RoleError::CompanionIdUnresolved { index, id: id.to_string() });
};
if metas[b].capability != target {
return Err(RoleError::CompanionIdCapabilityMismatch {
index, id: id.to_string(), capability: metas[b].capability.clone() });
}
// Rule 3: that entry is a selectable rendition.
if !metas[b].is_selectable() {
return Err(RoleError::UnboundCompanion { index, capability });
}
} else {
// Rule 3: the base is present, and unique for an enhancement layer.
let bases = metas.iter()
.filter(|b| b.is_selectable() && b.capability == target)
.count();
if crate::codecs::is_enhancement_layer(&m.capability) {
if bases != 1 {
return Err(RoleError::EnhancementBaseNotUnique { index, capability, matches: bases });
}
} else if bases == 0 {
return Err(RoleError::UnboundCompanion { index, capability });
}
}
// At most one enhancement layer per base, by index.
if crate::codecs::is_enhancement_layer(&m.capability) {
let base_index = self.bound_base(index);
let earlier = (0..index).find(|&e| {
metas[e].is_companion()
&& crate::codecs::is_enhancement_layer(&metas[e].capability)
&& self.bound_base(e).is_some()
&& self.bound_base(e) == base_index
});
if let Some(first) = earlier {
let base = target.to_string();
return Err(RoleError::DuplicateEnhancementLayer { index, capability, base, first });
}
}
}
Ok(())
}
/// A `"1.1"` manifest with a `null` target.
pub fn new(media: impl Into<String>, intent: impl Into<String>, renditions: Vec<RenditionMeta>) -> Self {
Self {
wai: "1.1".into(),
media: media.into(),
intent: intent.into(),
renditions,
target: Value::Null,
extra: Map::new(),
}
}
/// Set `target`.
pub fn with_target(mut self, target: Value) -> Self {
self.target = target;
self
}
}
/// One component to pack into a `WAI2`: its entry and its payload.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Rendition {
pub meta: RenditionMeta,
pub payload: Vec<u8>,
}
impl Rendition {
/// A selectable media rendition (an alternative of the same content).
pub fn media(capability: impl Into<String>, kind: impl Into<String>, payload: Vec<u8>) -> Self {
Self { meta: RenditionMeta::new(capability, kind), payload }
}
/// A companion descriptor bound to the component of capability
/// `companion_of` — e.g. an HDR10+ metadata block for a `wai.video.av1`
/// payload, carried in the same envelope.
pub fn companion(capability: impl Into<String>, payload: Vec<u8>, companion_of: impl Into<String>) -> Self {
Self { meta: RenditionMeta::new(capability, "descriptor").companion_to(companion_of), payload }
}
/// A component from an entry and its payload.
pub fn from_meta(meta: RenditionMeta, payload: Vec<u8>) -> Self {
Self { meta, payload }
}
/// Pin the parameter set the payload was coded against.
pub fn with_prior(mut self, prior: Prior) -> Self {
self.meta.prior = Some(prior);
self
}
/// Give the component an id.
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.meta.id = Some(id.into());
self
}
/// State the payload's SHA-256, computed from the payload.
pub fn with_payload_digest(mut self) -> Self {
self.meta.sha256 = Some(crate::pin::sha256_hex(&self.payload));
self
}
/// Bind this companion to the component whose `id` is `id`.
pub fn with_companion_of_id(mut self, id: impl Into<String>) -> Self {
self.meta.companion_of_id = Some(id.into());
self
}
}
pub struct WaiMulti {
pub manifest: ManifestV2,
pub renditions: Vec<Vec<u8>>, // payload bytes, parallel to manifest.renditions
}
/// Why [`WaiMulti::pick`] skipped an entry (SPEC §7.1 step 5).
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Skip {
/// A companion, or a capability that is not [`crate::codecs::is_dispatchable`]
/// (companion-only, a candidate, a record or a derivation): never presented as
/// media.
NotSelectable,
/// The sink does not advertise the capability (`missing_capability`).
NotEligible,
/// The entry's payload does not match its `sha256`
/// (`component_digest_mismatch`).
ComponentDigest,
/// The entry's pin was refused.
Pin(PinRefusal),
/// A policy that serves only sound `replicate` claims left the entry out:
/// SPEC §7's rule refuses `replicate` through it (`replicate_*`).
Replicate(crate::codecs::ReplicateRefusal),
/// The manifest declares an encryption the sink cannot open (SPEC §6.4 step 2,
/// `unsupported_encryption`): every entry's payload is ciphertext here.
Encrypted,
}
impl Skip {
/// The reason: `not_selectable`, `missing_capability`,
/// `component_digest_mismatch`, the pin's reason, the `replicate_*` reason, or
/// `unsupported_encryption`.
pub fn code(&self) -> &'static str {
match self {
Self::NotSelectable => "not_selectable",
Self::NotEligible => "missing_capability",
Self::ComponentDigest => "component_digest_mismatch",
Self::Pin(r) => r.code(),
Self::Replicate(r) => r.code(),
Self::Encrypted => "unsupported_encryption",
}
}
}
impl std::fmt::Display for Skip {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pin(r) => write!(f, "{}: {r}", r.code()),
Self::Replicate(r) => write!(f, "{}: {r}", r.code()),
other => f.write_str(other.code()),
}
}
}
/// The rendition [`WaiMulti::pick`] chose: its index, entry and payload, what the
/// pin verifier returned for it (when a pin applied), and the entries skipped
/// before it with their reasons.
#[derive(Debug)]
pub struct Picked<'a, V> {
pub index: usize,
pub meta: &'a RenditionMeta,
pub payload: &'a [u8],
pub verified: Option<V>,
pub skipped: Vec<(usize, Skip)>,
}
impl WaiMulti {
pub fn new(media: impl Into<String>, intent: impl Into<String>,
target: Value, renditions: Vec<Rendition>) -> Self {
let (metas, payloads): (Vec<RenditionMeta>, Vec<Vec<u8>>) =
renditions.into_iter().map(|r| (r.meta, r.payload)).unzip();
Self { manifest: ManifestV2::new(media, intent, metas).with_target(target), renditions: payloads }
}
/// An envelope from a manifest and the payloads parallel to its entries. Every
/// member of the manifest is kept, including ones this revision does not define.
pub fn from_parts(manifest: ManifestV2, renditions: Vec<Vec<u8>>) -> std::io::Result<Self> {
if manifest.renditions.len() != renditions.len() {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("manifest has {} renditions but payload list has {}",
manifest.renditions.len(), renditions.len())));
}
Ok(Self { manifest, renditions })
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> std::io::Result<usize> {
let bytes = self.to_bytes()?;
fs::write(path, &bytes)?;
Ok(bytes.len())
}
pub fn read<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
let bytes = fs::read(path)?;
Self::from_bytes(&bytes)
}
/// The envelope's bytes. Refuses a manifest longer than SPEC §2's reader limit
/// (`manifest_too_large`), and a payload, or the payloads' total, that does not fit
/// a `u32` field.
pub fn to_bytes(&self) -> std::io::Result<Vec<u8>> {
self.to_bytes_within(WriteLimits::SPEC)
}
/// [`Self::to_bytes`] within `lim`.
fn to_bytes_within(&self, lim: WriteLimits) -> std::io::Result<Vec<u8>> {
if self.manifest.renditions.len() != self.renditions.len() {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("manifest has {} renditions but payload list has {}",
self.manifest.renditions.len(), self.renditions.len())));
}
if self.renditions.len() > u16::MAX as usize {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
"too many renditions for u16 count field"));
}
let m = manifest_json(&self.manifest, lim.manifest)?;
let table_bytes = 2 + self.renditions.len() * 8;
let payload_bytes: usize = self.renditions.iter().map(|p| p.len()).sum();
let mut out = Vec::with_capacity(8 + m.len() + table_bytes + payload_bytes);
out.extend_from_slice(MAGIC_V2);
out.extend_from_slice(&(m.len() as u32).to_le_bytes());
out.extend_from_slice(&m);
out.extend_from_slice(&(self.renditions.len() as u16).to_le_bytes());
// Rendition table: (u32 offset, u32 length); offsets are relative to
// the start of the payloads block (the byte right after the table).
let mut off: u32 = 0;
for (i, p) in self.renditions.iter().enumerate() {
let len = u32_field(p.len(), lim.field, &format!("rendition {i}'s payload"))?;
out.extend_from_slice(&off.to_le_bytes());
out.extend_from_slice(&len.to_le_bytes());
off = off.checked_add(len).filter(|&o| o <= lim.field).ok_or_else(|| std::io::Error::new(
std::io::ErrorKind::InvalidData, "payload sum overflows u32"))?;
}
for p in &self.renditions {
out.extend_from_slice(p);
}
Ok(out)
}
pub fn from_bytes(bytes: &[u8]) -> std::io::Result<Self> {
let need = |off: usize, n: usize, what: &str| -> std::io::Result<()> {
if off.saturating_add(n) > bytes.len() {
Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("truncated WAI2 ({what} needs {n} bytes at offset {off}, have {})",
bytes.len())))
} else { Ok(()) }
};
need(0, 4, "magic")?;
if &bytes[..4] != MAGIC_V2 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
"not a WAI2 file (magic mismatch)"));
}
need(4, 4, "manifest length")?;
let ml = u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize;
check_manifest_len(ml as u64)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
need(8, ml, "manifest body")?;
let manifest = read_manifest_v2(&bytes[8..8 + ml])
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let mut o = 8 + ml;
need(o, 2, "rendition count")?;
let n = u16::from_le_bytes(bytes[o..o + 2].try_into().unwrap()) as usize;
o += 2;
if n != manifest.renditions.len() {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("rendition table count {} != manifest renditions {}",
n, manifest.renditions.len())));
}
need(o, n * 8, "rendition table")?;
let mut entries: Vec<(u32, u32)> = Vec::with_capacity(n);
for i in 0..n {
let off = u32::from_le_bytes(bytes[o + i*8..o + i*8 + 4].try_into().unwrap());
let len = u32::from_le_bytes(bytes[o + i*8 + 4..o + i*8 + 8].try_into().unwrap());
entries.push((off, len));
}
o += n * 8;
let payload_block_start = o;
// In u64, so the sum cannot wrap where usize is 32 bits (wasm32): at most
// u16::MAX lengths of at most u32::MAX each.
let total_payloads: u64 = entries.iter().map(|(_, l)| u64::from(*l)).sum();
if (payload_block_start as u64).saturating_add(total_payloads) > bytes.len() as u64 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("truncated WAI2 (payload block needs {total_payloads} bytes at offset \
{payload_block_start}, have {})", bytes.len())));
}
// Validate offsets: each entry's (off, len) must lie within the
// payload block and not overlap previous entries' ranges (we
// require monotonic contiguous packing to keep parsing simple).
let mut expected = 0u32;
let mut renditions = Vec::with_capacity(n);
for (off, len) in &entries {
if *off != expected {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("rendition offset {off} not contiguous (expected {expected})")));
}
// The offsets are contiguous and their lengths sum within the file
// (checked above), so these cannot overflow; checked all the same.
let range = payload_block_start.checked_add(*off as usize)
.and_then(|s| s.checked_add(*len as usize).map(|e| (s, e)));
let Some((start, end)) = range else {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "offset arithmetic overflows"));
};
if end > bytes.len() {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("rendition byte range [{start}, {end}) exceeds file length {}",
bytes.len())));
}
renditions.push(bytes[start..end].to_vec());
expected = expected.checked_add(*len).ok_or_else(|| std::io::Error::new(
std::io::ErrorKind::InvalidData, "offset arithmetic overflows"))?;
}
Ok(Self { manifest, renditions })
}
/// SPEC §7.1 selection at a sink that advertises the capabilities `advertises`
/// accepts and verifies pins with `verify` (SPEC §3.1). Walks the entries in
/// order and picks the first that is selectable ([`RenditionMeta::is_selectable`]:
/// never a companion, a companion-only capability, a candidate, a record or a
/// derivation), whose capability is advertised (a string this revision does not
/// register is selected when `advertises` accepts it, SPEC §4, §8), whose
/// payload matches its `sha256` when it states one, and whose `prior`, where a
/// pin applies ([`pin_applies_to`]), `verify` accepts. An entry whose pin is
/// refused is skipped as if its capability were not advertised, and its reason
/// kept. When nothing is picked the envelope is inert at this sink, and the
/// error gives every entry's index and reason. An envelope whose manifest declares
/// an encryption other than `"none"` is inert before any entry is considered (SPEC
/// §6.4 step 2; this dispatcher opens no scheme): every entry is
/// [`Skip::Encrypted`].
pub fn pick<V>(
&self,
advertises: impl Fn(&str) -> bool,
verify: impl FnMut(&str, &Prior) -> Result<V, PinRefusal>,
) -> Result<Picked<'_, V>, Vec<(usize, Skip)>> {
self.pick_where(|_, m| if advertises(&m.capability) { Ok(()) } else { Err(Skip::NotEligible) }, verify)
}
/// [`Self::pick`] at a sink that advertises every capability — the first entry
/// in deployer-preferred order that passes the component digest and pin checks.
pub fn primary<V>(
&self,
verify: impl FnMut(&str, &Prior) -> Result<V, PinRefusal>,
) -> Result<Picked<'_, V>, Vec<(usize, Skip)>> {
self.pick(|_| true, verify)
}
/// [`Self::pick`] with a per-entry eligibility test in place of the
/// capability test: a deployer policy MAY restrict the entries a sink
/// considers (SPEC §7.1 step 2), and gives the reason it leaves one out; every
/// entry it considers passes the digest and pin checks.
pub(crate) fn pick_where<V>(
&self,
eligible: impl Fn(usize, &RenditionMeta) -> Result<(), Skip>,
verify: impl FnMut(&str, &Prior) -> Result<V, PinRefusal>,
) -> Result<Picked<'_, V>, Vec<(usize, Skip)>> {
let order: Vec<usize> = (0..self.manifest.renditions.len()).collect();
let digest_ok = |i: usize, m: &RenditionMeta| self.renditions.get(i).is_some_and(|p| m.payload_digest_matches(p));
let (index, verified, skipped) = select_entries(&self.manifest, &order, eligible, digest_ok, verify)?;
Ok(Picked {
index,
meta: &self.manifest.renditions[index],
payload: &self.renditions[index],
verified,
skipped,
})
}
/// Check that no component, in either role, names a candidate capability
/// ([`crate::codecs::is_candidate`]), which an encoder MUST NOT emit (SPEC §5,
/// *Candidate capabilities*). Reports the first such component in table order.
pub fn check_candidates(&self) -> Result<(), CandidateError> {
check_component_candidates(self.manifest.renditions.iter().map(|m| m.capability.as_str()))
}
/// [`check_int_payload_framing`] over every component, in table order: the
/// first component whose payload is not its capability's pinned format, with its
/// index. Skipped for an encrypted envelope (a `security` member whose
/// `encryption` is not `"none"`), whose payloads are ciphertext until a sink
/// decrypts them (SPEC §6.4).
pub fn check_int_payload_framing(&self) -> Result<(), (usize, FramingError)> {
if !manifest_v2_plaintext(&self.manifest) {
return Ok(());
}
for (i, (m, p)) in self.manifest.renditions.iter().zip(&self.renditions).enumerate() {
check_int_payload_framing(&m.capability, p).map_err(|e| (i, e))?;
}
Ok(())
}
/// The envelope's bytes, for an encoder to emit: [`Self::check_candidates`], then
/// [`manifest_v2_security`], then [`Self::check_int_payload_framing`], then
/// [`Self::to_bytes`]. The C packer `wai_envelope_pack_multi` emits through this, so
/// an encoder that uses it cannot emit a candidate, a `security` member that is not
/// a security object (SPEC §6.1), or a component whose integer payload is not its
/// capability's pinned format (SPEC §5).
pub fn to_emit_bytes(&self) -> Result<Vec<u8>, EmitError> {
self.check_candidates().map_err(EmitError::Candidate)?;
manifest_v2_security(&self.manifest).map_err(EmitError::Security)?;
self.check_int_payload_framing().map_err(|(i, error)| EmitError::IntPayload { component: Some(i), error })?;
self.to_bytes().map_err(EmitError::Serialize)
}
/// Why a `replicate` claim through entry `index` is unsound (SPEC §7), or
/// `None` if it is sound, does not claim `replicate`, or `index` is out of range.
pub fn replicate_refusal(&self, index: usize) -> Option<crate::codecs::ReplicateRefusal> {
let m = self.manifest.renditions.get(index)?;
crate::codecs::replicate_refusal(&m.capability, &self.manifest.intent, m.prior.as_ref())
}
/// What a verifier can decide about each entry's pin without holding a
/// parameter set: the first finding, with the entry's index.
pub fn check_pins(&self) -> Result<(), (usize, PinFinding)> {
for (i, m) in self.manifest.renditions.iter().enumerate() {
if m.is_companion() && m.prior.is_none() {
continue;
}
static_pin_findings(&[m.capability.as_str()], m.prior.as_ref(), &self.manifest.intent)
.map_err(|f| (i, f))?;
}
Ok(())
}
/// The refinements of the entry at `index`, in table order: each entry with
/// `role: "refinement"` whose `refines.base` names that entry's `id`, and whose
/// payload matches its `sha256` (a refinement that does not match is not applied,
/// SPEC §7.1). A sink offers their exploded envelopes to staged-delivery §4
/// ([`WaiMulti::explode`]) after [`Self::check_component_roles`] passes.
pub fn refinements_of(&self, index: usize) -> Vec<(usize, &RenditionMeta, &[u8])> {
self.with_matching_payloads(refinement_indices(&self.manifest.renditions, index))
}
/// The entries at `indices` whose payload matches its `sha256`, with their
/// payloads.
fn with_matching_payloads(&self, indices: Vec<usize>) -> Vec<(usize, &RenditionMeta, &[u8])> {
indices.into_iter()
.filter_map(|i| {
let (m, p) = (self.manifest.renditions.get(i)?, self.renditions.get(i)?);
m.payload_digest_matches(p).then_some((i, m, p.as_slice()))
})
.collect()
}
/// Check the component table against SPEC §5 / §7.1 ([`ManifestV2::check_component_roles`]).
/// A sink offers the envelope's refinements to staged-delivery §4 only when it passes.
pub fn check_component_roles(&self) -> Result<(), RoleError> {
self.manifest.check_component_roles()
}
/// All companions (HDR dynamic metadata, enhancement layers) carried in
/// this envelope — applied *with* the chosen rendition, not instead of it.
pub fn companions(&self) -> Vec<(&RenditionMeta, &[u8])> {
self.manifest.renditions.iter().zip(self.renditions.iter())
.filter(|(m, _)| m.is_companion())
.map(|(m, p)| (m, p.as_slice()))
.collect()
}
/// The companions bound to the entry at `index` — what a sink applies with the
/// rendition [`Self::pick`] chose: each companion whose `companion_of_id` names
/// that entry, or, with no `companion_of_id`, whose `companion_of` is that
/// entry's capability. A companion whose payload does not match its `sha256` is
/// left out (it is not applied). As with [`Self::companions_of`], a sink applies
/// one only if the envelope passes [`Self::check_component_roles`] and it
/// recognises the companion's capability.
pub fn companions_of_index(&self, index: usize) -> Vec<(usize, &RenditionMeta, &[u8])> {
self.with_matching_payloads(companion_indices(&self.manifest.renditions, index))
}
/// The companions that name `capability` in `companion_of`, whatever entry
/// they bind to by id. Binding by capability alone; a sink that picked one
/// entry uses [`Self::companions_of_index`].
pub fn companions_of(&self, capability: &str) -> Vec<(&RenditionMeta, &[u8])> {
self.companions().into_iter()
.filter(|(m, _)| m.companion_of.as_deref() == Some(capability))
.collect()
}
pub fn media(&self) -> &str { &self.manifest.media }
pub fn intent(&self) -> &str { &self.manifest.intent }
}
/// SPEC §7.1 selection over a `WAI2` manifest's entries alone, with no payload in
/// hand: the walk [`WaiMulti::pick`] makes, in `order` (a deployer policy, SPEC §7.1
/// step 2: a permutation of a subset of the entry indices; an index not in it is not
/// considered, and an index out of range is ignored). Each entry considered is
/// skipped, in this order, when it is not selectable ([`Skip::NotSelectable`]), when
/// `eligible` refuses it (its reason), when `digest_ok` is false
/// ([`Skip::ComponentDigest`]), or when its pin is refused ([`Skip::Pin`]); the first
/// that passes is picked, with what `verify` returned and the entries skipped before
/// it. An encrypted envelope (SPEC §6.4 step 2) is inert before any entry is
/// considered: every entry is [`Skip::Encrypted`].
///
/// `digest_ok` is the payload test: `WaiMulti::pick` passes
/// [`RenditionMeta::payload_digest_matches`] over the entry's payload, and a client
/// that holds only the head passes `|_, _| true`, deferring the test to the bytes it
/// fetches (`extensions/http-delivery.md` §7 step 4). Both are this one function, so
/// the two paths pick alike on bytes that verify (http-delivery §7.2).
pub fn select_entries<V>(
manifest: &ManifestV2,
order: &[usize],
eligible: impl Fn(usize, &RenditionMeta) -> Result<(), Skip>,
digest_ok: impl Fn(usize, &RenditionMeta) -> bool,
mut verify: impl FnMut(&str, &Prior) -> Result<V, PinRefusal>,
) -> Result<(usize, Option<V>, Vec<(usize, Skip)>), Vec<(usize, Skip)>> {
let entries = &manifest.renditions;
// SPEC §6.4 step 2, before selection: an encrypted envelope is inert at a sink
// that opens no scheme, and every entry is reported so.
if !manifest_v2_plaintext(manifest) {
return Err((0..entries.len()).map(|i| (i, Skip::Encrypted)).collect());
}
let mut skipped = Vec::new();
for &index in order {
let Some(meta) = entries.get(index) else { continue };
if !meta.is_selectable() {
skipped.push((index, Skip::NotSelectable));
continue;
}
if let Err(why) = eligible(index, meta) {
skipped.push((index, why));
continue;
}
if !digest_ok(index, meta) {
skipped.push((index, Skip::ComponentDigest));
continue;
}
let verified = match meta.prior.as_ref() {
Some(p) if pin_applies_to(&meta.capability) => match verify(&meta.capability, p) {
Ok(v) => Some(v),
Err(r) => {
skipped.push((index, Skip::Pin(r)));
continue;
}
},
_ => None,
};
return Ok((index, verified, skipped));
}
Err(skipped)
}
/// The companions SPEC §7.1 binds to the entry at `index`, by entry alone: each
/// companion whose `companion_of_id` names that entry's `id`, or, with no
/// `companion_of_id`, whose `companion_of` is that entry's capability.
/// [`WaiMulti::companions_of_index`] is this with the payload digest checked; a client
/// that holds only the head checks it on the bytes it fetches.
pub fn companion_indices(entries: &[RenditionMeta], index: usize) -> Vec<usize> {
let Some(base) = entries.get(index) else { return Vec::new() };
entries.iter().enumerate()
.filter(|(_, m)| {
m.is_companion()
&& match m.companion_of_id.as_deref() {
Some(id) => base.id.as_deref() == Some(id),
None => m.companion_of.as_deref() == Some(base.capability.as_str()),
}
})
.map(|(i, _)| i)
.collect()
}
/// The refinement components whose `refines.base` names the entry at `index`, in
/// table order, by entry alone: [`WaiMulti::refinements_of`] without the payload
/// digest, which a client that holds only the head checks on the bytes it fetches.
pub fn refinement_indices(entries: &[RenditionMeta], index: usize) -> Vec<usize> {
let Some(base_id) = entries.get(index).and_then(|b| b.id.as_deref()) else { return Vec::new() };
entries.iter().enumerate()
.filter(|(_, m)| {
m.is_refinement()
&& m.refines.as_ref()
.and_then(|v| Refines::from_value(v, RefForm::ComponentId).ok())
.is_some_and(|r| r.base == base_id)
})
.map(|(i, _)| i)
.collect()
}
/// The refinement components whose `refines.base`, read on its own, is the `id` of the
/// entry at `index`, in table order: [`refinement_indices`] without the rest of
/// `refines` read. Where every component conforms (SPEC §7.1) the two agree, since each
/// refinement's `refines` is then well formed; where one does not, these are the
/// refinements a client planning from the head drops and reports
/// (`components_nonconforming`, http-delivery §7 step 4), a malformed `refines` that
/// names the pick among them.
pub fn refinements_naming(entries: &[RenditionMeta], index: usize) -> Vec<usize> {
let Some(base_id) = entries.get(index).and_then(|b| b.id.as_deref()) else { return Vec::new() };
entries.iter().enumerate()
.filter(|(_, m)| m.is_refinement() && m.refines.as_ref().and_then(|v| v.get("base")).and_then(Value::as_str) == Some(base_id))
.map(|(i, _)| i)
.collect()
}
/// Sniff the magic and return which envelope version is in this byte
/// blob. Lets callers branch on `Wai::from_bytes` vs `WaiMulti::from_bytes`
/// without parsing twice.
pub fn detect_version(bytes: &[u8]) -> std::io::Result<u8> {
if bytes.len() < 4 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
"too short for magic"));
}
match &bytes[..4] {
b"WAI1" => Ok(1),
b"WAI2" => Ok(2),
_ => Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
"unknown magic (not WAI1/WAI2)")),
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
/// A pin verifier that accepts every pin: for tests about selection order.
fn accept(_: &str, _: &Prior) -> Result<(), PinRefusal> {
Ok(())
}
/// SPEC §4 dispatch with no parameter set held: the capability, or `None`.
fn dispatched<'a>(w: &'a Wai, advertises: impl Fn(&str) -> bool) -> Option<&'a str> {
w.dispatch_verified(advertises, no_parameter_sets()).ok().map(|d| d.capability)
}
/// A rendition table whose lengths wrap a 32-bit sum (0xFFFF_FFF0 + 0x20 =
/// 0x10 mod 2^32) over a 16-byte payload block is truncated, on every target:
/// the parse returns an error rather than slicing out of range.
#[test]
fn wai2_lengths_that_wrap_32_bits_are_truncation() {
let mut bytes = WaiMulti::new("image", "improve", Value::Null, vec![
Rendition::media("wai.image.png", "png", vec![0; 8]),
Rendition::media("wai.image.avif", "avif", vec![0; 8]),
]).to_bytes().unwrap();
let table = bytes.len() - 16 - 16;
bytes[table + 4..table + 8].copy_from_slice(&0xFFFF_FFF0u32.to_le_bytes());
bytes[table + 8..table + 12].copy_from_slice(&0xFFFF_FFF0u32.to_le_bytes());
bytes[table + 12..table + 16].copy_from_slice(&0x20u32.to_le_bytes());
let e = WaiMulti::from_bytes(&bytes).err().expect("a wrapped length sum must not parse");
assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
assert!(e.to_string().contains("payload block"), "{e}");
}
#[test]
fn round_trip_envelope() {
let m = Manifest::new(
"image",
"replicate",
ModelRequirement::new("wai.zeroth.dct").with_fallback("zeroth"),
Conditioning::new("image_zeroth"),
)
.with_target(serde_json::json!({"w": 512, "h": 512}));
let w = Wai::new(m, b"hello world".to_vec());
let bytes = w.to_bytes().unwrap();
let w2 = Wai::from_bytes(&bytes).unwrap();
assert_eq!(w2.capability(), "wai.zeroth.dct");
assert_eq!(w2.payload, b"hello world");
assert_eq!(w2.media(), "image");
// Byte-compat: a floor envelope (no prior) MUST NOT emit a `prior`
// key, so existing files round-trip byte-for-byte after the field
// was added.
assert!(w2.prior().is_none());
assert!(!bytes.windows(7).any(|win| win == b"\"prior\""),
"floor envelope must not serialize a prior key");
}
#[test]
fn prior_round_trips() {
let m = Manifest::new(
"image",
"replicate",
ModelRequirement::new("wai.neural.bmshj2018").with_fallback("wai.image.png"),
Conditioning::new("bmshj2018").with_prior(Prior::new(
"bmshj2018-factorized/q3",
"ab".repeat(32),
"int-rans/p16+onnx-cpu-f32/1thread",
)),
)
.with_target(serde_json::json!({"w": 256, "h": 256}));
let bytes = Wai::new(m.clone(), b"latents".to_vec()).to_bytes().unwrap();
let back = Wai::from_bytes(&bytes).unwrap();
let p = back.prior().expect("prior must survive the wire");
assert_eq!(p.id, "bmshj2018-factorized/q3");
assert_eq!(p.sha256, "ab".repeat(32));
assert_eq!(p.determinism, "int-rans/p16+onnx-cpu-f32/1thread");
// The neural envelope DOES carry the key.
assert!(bytes.windows(7).any(|win| win == b"\"prior\""));
}
#[test]
fn determinism_class_and_replicate_soundness() {
let mk = |det: &str| Prior::new("m", "ab".repeat(32), det);
// Integer rANS + FLOAT synthesis → only "exact here", not everywhere.
let float_synth = mk("int-rans/p16+onnx-cpu-f32/1thread");
assert_eq!(float_synth.determinism_class(), DeterminismClass::FloatReproducible);
assert!(!float_synth.is_cross_hardware_exact());
// Fully integerized inference → byte-identical across hardware.
let integerized = mk("int-rans/p16+onnx-int8-integerized");
assert_eq!(integerized.determinism_class(), DeterminismClass::IntegerExact);
assert!(integerized.is_cross_hardware_exact());
// Unrecognized contract → not reproducible.
assert_eq!(mk("magic").determinism_class(), DeterminismClass::Unknown);
// The honesty rule: `replicate` is sound only with an integer-exact prior.
assert!(!replicate_is_sound("replicate", None));
assert!(!replicate_is_sound("replicate", Some(&float_synth)));
assert!(replicate_is_sound("replicate", Some(&integerized)));
// Non-replicate intents aren't constrained by it.
assert!(replicate_is_sound("improve", Some(&float_synth)));
assert!(replicate_is_sound("create", None));
}
/// The envelope-level check applies both halves of SPEC §7: the capability's
/// registry class first, then the prior. An integer-declared prior over JPEG AI
/// (entropy-consistency only) or over a float codec does not make `replicate`
/// sound; the same prior over an integer-exact capability does.
#[test]
fn envelope_replicate_soundness_consults_the_registry() {
use crate::codecs::{ReplicateClass, ReplicateRefusal};
let env = |cap: &str, intent: &str, det: Option<&str>| {
let mut c = Conditioning::new("k");
c.prior = det.map(|d| Prior::new("m", "ab".repeat(32), d));
Wai::new(
Manifest::new("image", intent, ModelRequirement::new(cap), c)
.with_target(serde_json::json!({})),
vec![],
)
};
let int = Some("int-rans/p16+int-synth/i64");
let jpegai = env("wai.image.jpegai", "replicate", int);
assert!(!jpegai.replicate_is_sound());
assert_eq!(jpegai.replicate_refusal(),
Some(ReplicateRefusal::Class(ReplicateClass::NeuralEntropyExact)));
assert!(!env("wai.neural.bmshj2018", "replicate", int).replicate_is_sound());
assert!(env("wai.neural.int_synth", "replicate", int).replicate_is_sound());
assert!(!env("wai.neural.int_synth", "replicate", Some("int-rans+onnx-f32")).replicate_is_sound());
assert!(!env("wai.neural.int_synth", "replicate", None).replicate_is_sound());
// A floor codec carries no prior and is not refused by the neural rule.
assert!(env("wai.image.png", "replicate", None).replicate_is_sound());
// Nor is a codec whose standard conforms decoders to a tolerance: over it
// `replicate` promises the standard's conformance, not the same bytes.
assert!(env("wai.audio.opus", "replicate", None).replicate_is_sound());
// Non-replicate intents are unconstrained.
assert!(env("wai.image.jpegai", "improve", None).replicate_is_sound());
}
const NNC: &str = crate::codecs::CAP_PRIOR_NNC;
fn with_form(form: Option<&str>) -> Prior {
let p = Prior::new("int_hyper/q4", "ef".repeat(32), "int-rans/p16+int-synth");
match form {
Some(f) => p.with_form(f),
None => p,
}
}
#[test]
fn prior_without_a_form_round_trips_byte_for_byte() {
// A manifest written before `form` existed parses, and re-serializes to
// the identical bytes: the field is absent, not `null`.
let legacy = r#"{"wai":"1.0","media":"image","intent":"replicate","model_requirement":{"capability":"wai.neural.int_hyper"},"conditioning":{"kind":"int_hyper","prior":{"id":"int_hyper/q4","sha256":"efef","determinism":"int-rans/p16+int-synth"}},"target":null}"#;
let m: Manifest = serde_json::from_str(legacy).unwrap();
assert_eq!(m.conditioning.prior.as_ref().unwrap().form(), None);
assert_eq!(serde_json::to_string(&m).unwrap(), legacy);
}
#[test]
fn constructor_builds_the_prior_a_literal_would() {
// `Prior::new` leaves the optional form absent, so it serializes exactly
// as a prior written before `form` existed; `with_form` declares one.
let p = Prior::new("int_hyper/q4", "ef".repeat(32), "int-rans/p16+int-synth");
assert_eq!(p, with_form(None));
assert_eq!(
serde_json::to_string(&p).unwrap(),
format!(r#"{{"id":"int_hyper/q4","sha256":"{}","determinism":"int-rans/p16+int-synth"}}"#,
"ef".repeat(32))
);
assert_eq!(p.clone().with_form(NNC), with_form(Some(NNC)));
assert_eq!(p.with_form(NNC).form(), Some(NNC));
}
#[test]
fn declared_form_round_trips_through_the_envelope() {
let mut w = Wai::new(Manifest::new(
"image", "replicate",
ModelRequirement::new("wai.neural.int_hyper"),
Conditioning::new("int_hyper"),
), vec![1, 2, 3]);
w.manifest.conditioning.prior = Some(with_form(Some(NNC)));
let back = Wai::from_bytes(&w.to_bytes().unwrap()).unwrap();
assert_eq!(back.prior(), Some(&with_form(Some(NNC))));
assert_eq!(back.prior().unwrap().form(), Some(NNC));
}
#[test]
fn a_declared_form_must_be_registered_and_readable() {
// Undeclared: the digest alone identifies the artifact.
assert_eq!(with_form(None).check_form(&[]), Ok(()));
// Registered, but this sink has no reader for it: refused, never
// resolved against an artifact in some other form.
assert_eq!(with_form(Some(NNC)).check_form(&[]), Err(PriorFormError::Unreadable(NNC.into())));
assert_eq!(with_form(Some(NNC)).check_form(&[NNC]), Ok(()));
// Not a prior form at all: a payload codec, or an unknown string.
for bad in ["wai.image.png", "wai.tensor.nnc", "wai.prior.nope"] {
assert_eq!(
with_form(Some(bad)).check_form(&[bad]),
Err(PriorFormError::NotAPriorForm(bad.into())),
"{bad}"
);
}
}
#[test]
fn replicate_cannot_rest_on_a_form_not_established_exact() {
// The same integer-exact network: sound when the artifact is the bytes
// the digest covers; not sound once it must be decoded out of a form
// whose reconstruction is not established exact at every reader.
let plain = with_form(None);
let nnc = with_form(Some(NNC));
// One contract, integer-exact for both …
assert_eq!(plain.determinism_class(), DeterminismClass::IntegerExact);
assert_eq!(nnc.determinism_class(), DeterminismClass::IntegerExact);
assert!(plain.form_preserves_exactness());
assert!(!nnc.form_preserves_exactness());
// … but only the prior read as the bytes its digest covers is exact across
// hardware. The predicate every `replicate` check reads carries the form rule.
assert!(plain.is_cross_hardware_exact());
assert!(!nnc.is_cross_hardware_exact());
assert!(replicate_is_sound("replicate", Some(&plain)));
assert!(!replicate_is_sound("replicate", Some(&nnc)));
// Only `replicate` is gated.
assert!(replicate_is_sound("improve", Some(&nnc)));
}
#[test]
fn replicate_refusal_names_its_reason() {
// The reason a strict decoder reports. The form refusal must not fall back
// to the float wording: that network's contract IS integer-exact.
let mk = |det: &str, form: Option<&str>| {
let p = Prior::new("m", "ab".repeat(32), det);
match form {
Some(f) => p.with_form(f),
None => p,
}
};
let integer = mk("int-rans/p16+int-synth", None);
let floaty = mk("int-rans/p16+onnx-cpu-f32/1thread", None);
let integer_nnc = mk("int-rans/p16+int-synth", Some(NNC));
assert_eq!(replicate_prior_refusal("replicate", Some(&integer)), None);
assert_eq!(replicate_prior_refusal("improve", None), None);
assert_eq!(replicate_prior_refusal("improve", Some(&integer_nnc)), None);
assert_eq!(replicate_prior_refusal("replicate", None), Some(PriorRefusal::Absent));
assert_eq!(
replicate_prior_refusal("replicate", Some(&floaty)),
Some(PriorRefusal::NotIntegerExact(DeterminismClass::FloatReproducible))
);
// A float contract is refused as float even when it also declares a form.
assert_eq!(
replicate_prior_refusal("replicate", Some(&mk("onnx-cpu-f32", Some(NNC)))),
Some(PriorRefusal::NotIntegerExact(DeterminismClass::FloatReproducible))
);
let why = replicate_prior_refusal("replicate", Some(&integer_nnc));
assert_eq!(why, Some(PriorRefusal::FormNotExact(NNC.into())));
let text = why.unwrap().to_string();
assert!(text.contains(NNC) && text.contains("declared form"), "{text}");
assert!(text.contains("not established"), "{text}");
assert!(!text.contains("not integer-exact"), "{text}");
// A form outside the registry is refused as such, not as a form whose
// reconstruction is merely unestablished.
let nope = replicate_prior_refusal("replicate", Some(&mk("int-rans/p16+int-synth",
Some("wai.prior.nope"))));
assert_eq!(nope, Some(PriorRefusal::FormNotExact("wai.prior.nope".into())));
let text = nope.unwrap().to_string();
assert!(text.contains("not a registered prior form"), "{text}");
assert!(PriorRefusal::NotIntegerExact(DeterminismClass::Unknown)
.to_string().contains("not integer-exact"));
assert!(PriorRefusal::Absent.to_string().contains("no prior is pinned"));
}
// ---- v1.1: multi-rendition WAI2 ----
fn sample_multi() -> WaiMulti {
WaiMulti::new("audio", "replicate",
serde_json::json!({"sr": 48000, "dur": 5.0}),
vec![
Rendition::media("wai.neural.encodec32", "encodec_tokens",
b"NEURAL-TOKENS-2317-BYTES".to_vec())
.with_prior(Prior::new("encodec32/v1", "cd".repeat(32), "int-tokens")),
Rendition::media("wai.audio.opus", "opus", b"OPUS-PAYLOAD-4411-BYTES".to_vec()),
])
}
#[test]
fn multi_rendition_round_trip() {
let m = sample_multi();
let bytes = m.to_bytes().unwrap();
assert_eq!(detect_version(&bytes).unwrap(), 2);
let dec = WaiMulti::from_bytes(&bytes).unwrap();
assert_eq!(dec.manifest.wai, "1.1");
assert_eq!(dec.renditions.len(), 2);
assert_eq!(dec.renditions[0], b"NEURAL-TOKENS-2317-BYTES");
assert_eq!(dec.renditions[1], b"OPUS-PAYLOAD-4411-BYTES");
assert_eq!(dec.manifest.renditions[0].capability, "wai.neural.encodec32");
assert_eq!(dec.manifest.renditions[1].capability, "wai.audio.opus");
// Per-rendition prior survives the wire on the neural rendition, and
// the floor rendition carries none.
assert_eq!(dec.manifest.renditions[0].prior.as_ref().map(|p| p.id.as_str()),
Some("encodec32/v1"));
assert!(dec.manifest.renditions[1].prior.is_none());
}
#[test]
fn companion_descriptors_bind_to_a_primary() {
// one HDR video payload (two codec renditions of it) + two HDR
// dynamic-metadata companions (HDR10+ and ST 2094-10) bound to the AV1
// rendition. One envelope, intrinsic binding.
let m = WaiMulti::new(
"video",
"replicate",
serde_json::Value::Null,
vec![
Rendition::media("wai.video.av1", "av1", b"AV1-HDR-VIDEO".to_vec()),
Rendition::media("wai.image.jp2", "j2k", b"J2K-MEZZANINE".to_vec()),
Rendition::companion("wai.meta.hdr10plus", b"ST2094-40-DYNAMIC-METADATA".to_vec(), "wai.video.av1"),
Rendition::companion("wai.meta.dovi", b"ST2094-10-RPU".to_vec(), "wai.video.av1"),
],
);
let dec = WaiMulti::from_bytes(&m.to_bytes().unwrap()).unwrap();
// pick / primary only see the selectable video renditions, never the companions.
assert_eq!(dec.primary(accept).unwrap().meta.capability, "wai.video.av1");
assert!(dec.pick(|c| c == "wai.meta.hdr10plus", accept).is_err(), "a companion is not selectable as a rendition");
assert_eq!(dec.pick(|c| c == "wai.image.jp2", accept).unwrap().payload, b"J2K-MEZZANINE");
// the companions are carried, byte-exact, and bound to the AV1 rendition.
let comps = dec.companions();
assert_eq!(comps.len(), 2);
let hdr = dec.companions_of("wai.video.av1");
assert_eq!(hdr.len(), 2);
assert!(hdr.iter().any(|(m, p)| m.capability == "wai.meta.hdr10plus" && *p == b"ST2094-40-DYNAMIC-METADATA"));
assert!(dec.companions_of("wai.image.jp2").is_empty());
// a v1.1 rendition (role absent) is not a companion — back-compat.
assert!(!dec.manifest.renditions[0].is_companion());
// and the envelope satisfies the registry-aware role rules.
assert_eq!(dec.check_component_roles(), Ok(()));
}
#[test]
fn lcevc_enhancement_binds_to_exactly_one_base() {
// The separate (dual-track-shaped) LCEVC carriage: an HD VVC base that
// any VVC sink presents on its own, plus the enhancement companion that
// an LCEVC-capable sink adds on top. An AV1 rendition of the same
// content rides alongside as a royalty-free alternative.
let m = WaiMulti::new(
"video",
"replicate",
serde_json::Value::Null,
vec![
Rendition::media("wai.video.vvc", "vvc", b"VVC-HD-BASE".to_vec()),
Rendition::media("wai.video.av1", "av1", b"AV1-UHD".to_vec()),
Rendition::companion("wai.video.lcevc", b"LCEVC-ENHANCEMENT".to_vec(), "wai.video.vvc"),
],
);
let dec = WaiMulti::from_bytes(&m.to_bytes().unwrap()).unwrap();
assert_eq!(dec.check_component_roles(), Ok(()));
// The enhancement is never selectable; the base is, and the enhancement
// is bound to it byte-exact.
assert!(dec.pick(|c| c == "wai.video.lcevc", accept).is_err());
assert_eq!(dec.primary(accept).unwrap().meta.capability, "wai.video.vvc");
let enh = dec.companions_of("wai.video.vvc");
assert_eq!(enh.len(), 1);
assert_eq!(enh[0].0.capability, "wai.video.lcevc");
assert_eq!(enh[0].1, b"LCEVC-ENHANCEMENT");
assert!(dec.companions_of("wai.video.av1").is_empty());
}
#[test]
fn lcevc_binding_rejects_ambiguous_missing_and_mislabelled_bases() {
let lcevc = || Rendition::companion("wai.video.lcevc", b"ENH".to_vec(), "wai.video.hevc");
// Two HEVC renditions: the enhancement was encoded against ONE of them,
// and companion_of (a capability) cannot say which.
let two = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"HEVC-A".to_vec()),
Rendition::media("wai.video.hevc", "hevc", b"HEVC-B".to_vec()),
lcevc(),
]);
assert_eq!(two.check_component_roles(),
Err(RoleError::EnhancementBaseNotUnique { index: 2, capability: "wai.video.lcevc".into(), matches: 2 }));
// No HEVC base at all.
let none = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.av1", "av1", b"AV1".to_vec()),
lcevc(),
]);
assert_eq!(none.check_component_roles(),
Err(RoleError::EnhancementBaseNotUnique { index: 1, capability: "wai.video.lcevc".into(), matches: 0 }));
// A non-video base (an enhancement of an audio payload is meaningless):
// present and unique, but not one of LCEVC's registered base codecs.
let audio = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.audio.aac", "aac", b"AAC".to_vec()),
Rendition::companion("wai.video.lcevc", b"ENH".to_vec(), "wai.audio.aac"),
]);
assert_eq!(audio.check_component_roles(),
Err(RoleError::IncompatibleBase { index: 1, capability: "wai.video.lcevc".into(),
base: "wai.audio.aac".into() }));
// The enhancement mislabelled as a rendition: reported, and never picked
// even by a sink that advertises an LCEVC decoder.
let mis = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.lcevc", "lcevc", b"ENH".to_vec()),
Rendition::media("wai.video.hevc", "hevc", b"HEVC".to_vec()),
]);
assert_eq!(mis.check_component_roles(),
Err(RoleError::CompanionOnlyAsRendition { index: 0, capability: "wai.video.lcevc".into() }));
assert_eq!(mis.primary(accept).unwrap().meta.capability, "wai.video.hevc");
assert!(mis.pick(|c| c == "wai.video.lcevc", accept).is_err());
}
#[test]
fn a_base_takes_at_most_one_enhancement_layer() {
// Two LCEVC enhancements bound to one HEVC base: the base resolves
// uniquely, but a sink applies every companion of the rendition it
// picked, so it would stack two sets of residuals on one picture.
let enh = |p: &[u8], of: &str| Rendition::companion("wai.video.lcevc", p.to_vec(), of);
let stacked = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"HEVC".to_vec()),
enh(b"ENH-1", "wai.video.hevc"),
enh(b"ENH-2", "wai.video.hevc"),
]);
assert_eq!(stacked.companions_of("wai.video.hevc").len(), 2, "both would be applied");
assert_eq!(stacked.check_component_roles(),
Err(RoleError::DuplicateEnhancementLayer {
index: 2, capability: "wai.video.lcevc".into(),
base: "wai.video.hevc".into(), first: 1 }));
// One enhancement per base across two different bases conforms, and a
// descriptor companion on an enhanced base does not count against it.
let one_each = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"HEVC".to_vec()),
Rendition::media("wai.video.avc", "avc", b"AVC".to_vec()),
enh(b"ENH-HEVC", "wai.video.hevc"),
enh(b"ENH-AVC", "wai.video.avc"),
Rendition::companion("wai.meta.hdr10plus", b"ST2094-40".to_vec(), "wai.video.hevc"),
]);
assert_eq!(one_each.check_component_roles(), Ok(()));
}
#[test]
fn v1_envelope_rejects_companion_only_capability_and_fallback() {
let v1 = |cap: &str, fallback: Option<&str>| Wai::new(
Manifest::new("video", "replicate", {
let r = ModelRequirement::new(cap);
match fallback { Some(f) => r.with_fallback(f), None => r }
}, Conditioning::new("bytes")),
b"BYTES".to_vec(),
);
// A WAI1 has one payload, so a companion-only capability has no base.
for cap in ["wai.video.lcevc", "wai.meta.hdr10plus", "wai.meta.dovi"] {
assert_eq!(v1(cap, None).check_component_roles(),
Err(RoleError::CompanionOnlyAsCapability { capability: cap.into() }));
// ...and it survives the wire, because parsing stays format-agnostic;
// the check is what reports it.
let back = Wai::from_bytes(&v1(cap, None).to_bytes().unwrap()).unwrap();
assert!(back.check_component_roles().is_err());
}
// Named as a fallback, dispatch step 5 would decode it standalone.
assert_eq!(v1("wai.video.hevc", Some("wai.video.lcevc")).check_component_roles(),
Err(RoleError::CompanionOnlyAsFallback { capability: "wai.video.lcevc".into() }));
// Ordinary WAI1 envelopes conform, with or without a fallback.
assert_eq!(v1("wai.video.hevc", None).check_component_roles(), Ok(()));
assert_eq!(v1("wai.video.evc.baseline", Some("wai.video.evc")).check_component_roles(), Ok(()));
}
/// SPEC §4 with the §5 companion-only rule. A companion-only fallback makes
/// the envelope non-conforming, but step 3 still decodes via a primary the
/// sink advertises; only step 5 is closed to it. A companion-only primary is
/// never dispatched, even with a usable fallback.
#[test]
fn dispatch_skips_a_companion_only_fallback_but_keeps_step_3() {
let v1 = |cap: &str, fallback: Option<&str>| Wai::new(
Manifest::new("image", "replicate", {
let r = ModelRequirement::new(cap);
match fallback { Some(f) => r.with_fallback(f), None => r }
}, Conditioning::new("bytes")),
b"BYTES".to_vec(),
);
// A sink that advertises everything, including the companion-only
// capabilities: advertising one never makes it standalone-dispatchable.
let all = |_: &str| true;
let only = |set: &'static [&'static str]| move |c: &str| set.iter().any(|s| *s == c);
// Primary advertised: step 3, whatever the fallback names.
let png_lcevc = v1("wai.image.png", Some("wai.video.lcevc"));
assert!(png_lcevc.check_component_roles().is_err(), "non-conforming envelope");
assert_eq!(dispatched(&png_lcevc, all), Some("wai.image.png"));
assert_eq!(dispatched(&png_lcevc, only(&["wai.image.png"])), Some("wai.image.png"));
// Primary not advertised: step 5 would dispatch the fallback, but a
// companion-only one is never dispatched, so the envelope is inert.
assert_eq!(dispatched(&png_lcevc, only(&["wai.video.lcevc"])), None);
// A companion-only primary is inert even with a usable fallback.
assert_eq!(dispatched(&v1("wai.video.lcevc", Some("wai.video.hevc")), all), None);
// Ordinary envelopes: step 3, then step 5, then inert.
let evc = v1("wai.video.evc.baseline", Some("wai.video.evc"));
assert_eq!(dispatched(&evc, only(&["wai.video.evc.baseline", "wai.video.evc"])),
Some("wai.video.evc.baseline"));
assert_eq!(dispatched(&evc, only(&["wai.video.evc"])), Some("wai.video.evc"));
assert_eq!(dispatched(&evc, only(&["wai.image.png"])), None);
assert_eq!(dispatched(&v1("wai.image.png", None), only(&["wai.audio.flac"])), None);
}
#[test]
fn descriptor_companions_bind_but_need_not_be_unique() {
// HDR10+ metadata describes the content, so it may apply to either of
// two renditions of one capability — unlike an enhancement layer.
let m = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"HEVC-HI".to_vec()),
Rendition::media("wai.video.hevc", "hevc", b"HEVC-LO".to_vec()),
Rendition::companion("wai.meta.hdr10plus", b"ST2094-40".to_vec(), "wai.video.hevc"),
]);
assert_eq!(m.check_component_roles(), Ok(()));
// ...but it must bind to something present.
let dangling = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.av1", "av1", b"AV1".to_vec()),
Rendition::companion("wai.meta.hdr10plus", b"ST2094-40".to_vec(), "wai.video.hevc"),
]);
assert_eq!(dangling.check_component_roles(),
Err(RoleError::UnboundCompanion { index: 1, capability: "wai.meta.hdr10plus".into() }));
// A companion with no companion_of at all binds to nothing.
let mut no_target = Rendition::companion("wai.meta.dovi", b"RPU".to_vec(), "x");
no_target.meta.companion_of = None;
let unbound = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"HEVC".to_vec()),
no_target,
]);
assert!(matches!(unbound.check_component_roles(), Err(RoleError::UnboundCompanion { index: 1, .. })));
}
/// SPEC §7.1, rule 1: a registered capability that is not companion-only is
/// standalone media — its payload decodes on its own — and is carried as a
/// rendition, never marked companion.
#[test]
fn a_standalone_codec_is_never_a_companion() {
for (cap, of) in [
// IAMF carries its own audio substreams; it is not mix metadata.
("wai.audio.iamf", "wai.video.av1"),
// A second video codec, bound to a video base of the same content.
("wai.video.hevc", "wai.video.av1"),
// An audio track bound to its video.
("wai.audio.opus", "wai.video.av1"),
// A standalone codec bound to a base of its own kind.
("wai.image.png", "wai.image.jp2"),
] {
let m = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.av1", "av1", b"AV1".to_vec()),
Rendition::media("wai.image.jp2", "j2k", b"J2K".to_vec()),
Rendition::companion(cap, b"PAYLOAD".to_vec(), of),
]);
assert_eq!(m.check_component_roles(),
Err(RoleError::StandaloneAsCompanion { index: 2, capability: cap.into() }), "{cap}");
// Marked companion, it is never selectable either, so the check is
// the only thing that says the envelope is wrong.
assert!(m.pick(|c| c == cap, accept).is_err(), "{cap}");
}
// Reported even with no companion_of: rule 1 comes first.
let mut unbound = Rendition::companion("wai.audio.iamf", b"IAMF".to_vec(), "x");
unbound.meta.companion_of = None;
let m = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.av1", "av1", b"AV1".to_vec()),
unbound,
]);
assert_eq!(m.check_component_roles(),
Err(RoleError::StandaloneAsCompanion { index: 1, capability: "wai.audio.iamf".into() }));
// The same IAMF stream as a rendition beside the video conforms.
let ok = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.av1", "av1", b"AV1".to_vec()),
Rendition::media("wai.audio.iamf", "iamf", b"IAMF".to_vec()),
]);
assert_eq!(ok.check_component_roles(), Ok(()));
}
/// SPEC §7.1, rule 1 and 3 for a capability this revision does not register:
/// its role and bases are unknown here, so it is held only to a present
/// base — a later revision may register it as a companion.
#[test]
fn an_unregistered_companion_needs_only_a_present_base() {
let m = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.av1", "av1", b"AV1".to_vec()),
Rendition::companion("wai.meta.future", b"DESCRIPTOR".to_vec(), "wai.video.av1"),
]);
assert_eq!(m.check_component_roles(), Ok(()));
let dangling = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.av1", "av1", b"AV1".to_vec()),
Rendition::companion("wai.meta.future", b"DESCRIPTOR".to_vec(), "wai.video.hevc"),
]);
assert_eq!(dangling.check_component_roles(),
Err(RoleError::UnboundCompanion { index: 1, capability: "wai.meta.future".into() }));
}
/// SPEC §5 / §7.1, rule 2 for HDR dynamic metadata: it was registered as a
/// companion to a video payload, so among registered capabilities it binds
/// to one in the `video` media class — `wai.video.*`, the neural video
/// codecs, the films — and to nothing else. An unregistered base is held to
/// rule 3 only.
#[test]
fn hdr_descriptors_bind_only_to_video() {
let env = |hdr: &str, base: &str, kind: &str| WaiMulti::new(
"video", "replicate", serde_json::Value::Null, vec![
Rendition::media(base, kind, b"BASE".to_vec()),
Rendition::companion(hdr, b"ST2094".to_vec(), base),
]);
for hdr in ["wai.meta.hdr10plus", "wai.meta.dovi"] {
// Not video: audio, images (a neural image codec among them), a
// volumetric and a world payload.
for base in ["wai.audio.opus", "wai.audio.aac", "wai.audio.iamf", "wai.image.avif",
"wai.image.jp2", "wai.neural.glc", "wai.splat.4d", "wai.world.replay"] {
assert_eq!(env(hdr, base, "x").check_component_roles(),
Err(RoleError::IncompatibleBase { index: 1, capability: hdr.into(), base: base.into() }),
"{hdr} on {base}");
}
// Video: wai.video.* (including the codecs LCEVC is not registered
// over), the neural video codecs outside that namespace, the films.
for base in ["wai.video.av1", "wai.video.hevc", "wai.video.j2k", "wai.video.av2",
"wai.video.int_motion", "wai.neural.video_bmshj2018", "wai.neural.dcvc_rt",
"wai.neural.dcvc_fm", "wai.film.linear", "wai.film.reel"] {
assert_eq!(env(hdr, base, "x").check_component_roles(), Ok(()), "{hdr} on {base}");
}
// A base this revision does not register is held only to rule 3: present
// it binds, absent it is unbound.
assert_eq!(env(hdr, "wai.neural.video_future", "x").check_component_roles(), Ok(()));
let absent_unknown = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.av1", "av1", b"AV1".to_vec()),
Rendition::companion(hdr, b"ST2094".to_vec(), "wai.neural.video_future"),
]);
assert_eq!(absent_unknown.check_component_roles(),
Err(RoleError::UnboundCompanion { index: 1, capability: hdr.into() }));
}
// The refusal says what the descriptor does apply to.
let msg = env("wai.meta.hdr10plus", "wai.audio.opus", "opus").check_component_roles()
.unwrap_err().to_string();
assert!(msg.contains("video payload") && msg.contains("neural video"), "{msg}");
// Never to a companion: HDR metadata bound to an enhancement layer.
let on_enh = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"HEVC".to_vec()),
Rendition::companion("wai.video.lcevc", b"ENH".to_vec(), "wai.video.hevc"),
Rendition::companion("wai.meta.hdr10plus", b"ST2094".to_vec(), "wai.video.lcevc"),
]);
assert_eq!(on_enh.check_component_roles(),
Err(RoleError::IncompatibleBase { index: 2, capability: "wai.meta.hdr10plus".into(),
base: "wai.video.lcevc".into() }));
// The mismatch is caught even when the named base is absent: the name alone
// says it cannot apply.
let absent = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media("wai.video.av1", "av1", b"AV1".to_vec()),
Rendition::companion("wai.meta.dovi", b"RPU".to_vec(), "wai.audio.opus"),
]);
assert!(matches!(absent.check_component_roles(), Err(RoleError::IncompatibleBase { index: 1, .. })));
}
/// SPEC §5 / §7.1, rule 2 for LCEVC: it binds only to a base codec its
/// registration names (AVC, HEVC, VVC, EVC, VP9, AV1), not to any
/// `wai.video.*`.
#[test]
fn lcevc_binds_only_to_its_base_codecs() {
let env = |base: &str| WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
Rendition::media(base, "x", b"BASE".to_vec()),
Rendition::companion("wai.video.lcevc", b"ENH".to_vec(), base),
]);
for &base in crate::codecs::enhancement_bases("wai.video.lcevc") {
assert_eq!(env(base).check_component_roles(), Ok(()), "LCEVC on {base}");
}
for base in ["wai.video.j2k", "wai.video.av2", "wai.video.avs3", "wai.video.int_motion",
"wai.video.int_hyper", "wai.video.future", "wai.image.jp2"] {
assert_eq!(env(base).check_component_roles(),
Err(RoleError::IncompatibleBase { index: 1, capability: "wai.video.lcevc".into(),
base: base.into() }),
"LCEVC on {base}");
}
// The error names the bases it would accept.
let msg = env("wai.video.j2k").check_component_roles().unwrap_err().to_string();
assert!(msg.contains("wai.video.hevc") && msg.contains("wai.video.av1"), "{msg}");
}
#[test]
fn multi_pick_by_capability() {
let m = sample_multi();
let picked = m.pick(|c| c == "wai.audio.opus", accept).unwrap();
assert_eq!(picked.meta.capability, "wai.audio.opus");
assert_eq!(picked.payload, b"OPUS-PAYLOAD-4411-BYTES");
// primary = first rendition (deployer-preferred order)
let pmeta = m.primary(accept).unwrap().meta;
assert_eq!(pmeta.capability, "wai.neural.encodec32");
}
/// SPEC §5, *Candidate capabilities*: a candidate is never dispatched, picked
/// or treated as a primary, and the candidate check reports every place one
/// can be named; such an envelope still parses.
#[test]
fn a_candidate_is_never_dispatched_picked_or_emitted() {
use crate::codecs::{is_candidate, known_capabilities};
let v1 = |cap: &str, fb: Option<&str>| {
let req = match fb {
Some(fb) => ModelRequirement::new(cap).with_fallback(fb),
None => ModelRequirement::new(cap),
};
Wai::new(Manifest::new("binary", "improve", req, Conditioning::new("bytes")), b"payload".to_vec())
};
let all = |_: &str| true;
let candidates: Vec<&str> = known_capabilities().iter().copied().filter(|c| is_candidate(c)).collect();
assert!(!candidates.is_empty());
for &cand in &candidates {
// A candidate primary is unsupported: step 5 takes a usable fallback …
assert_eq!(dispatched(&v1(cand, Some("wai.image.png")), all), Some("wai.image.png"), "{cand}");
// … and without one the envelope is inert.
assert_eq!(dispatched(&v1(cand, None), all), None, "{cand}");
assert_eq!(v1(cand, None).dispatch_verified(all, no_parameter_sets()).unwrap_err(),
Inert::MissingCapability, "{cand}");
// A candidate fallback is never taken, even advertised alone.
assert_eq!(dispatched(&v1("wai.image.png", Some(cand)), |c| c == cand), None, "{cand}");
// The check names each place, and every message says why.
let as_cap = v1(cand, None).check_candidates().unwrap_err();
assert_eq!(as_cap, CandidateError::AsCapability { capability: cand.into() });
let as_fb = v1("wai.image.png", Some(cand)).check_candidates().unwrap_err();
assert_eq!(as_fb, CandidateError::AsFallback { capability: cand.into() });
for e in [&as_cap, &as_fb] {
assert!(e.to_string().contains("MUST NOT emit"), "{e}");
}
// Such an envelope still parses, byte for byte.
let env = v1(cand, Some("wai.image.png"));
let back = Wai::from_bytes(&env.to_bytes().unwrap()).unwrap();
assert_eq!(back.capability(), cand);
assert!(back.check_candidates().is_err());
// WAI2: a candidate at index 0 is skipped by primary and pick, and reported.
let m = WaiMulti::new("binary", "improve", serde_json::Value::Null, vec![
Rendition::media(cand, "bytes", b"c".to_vec()),
Rendition::media("wai.image.png", "png", b"p".to_vec()),
]);
let back = WaiMulti::from_bytes(&m.to_bytes().unwrap()).unwrap();
assert_eq!(back.primary(no_parameter_sets()).unwrap().meta.capability, "wai.image.png", "{cand}");
let p = back.pick(all, no_parameter_sets()).unwrap();
assert_eq!(p.meta.capability, "wai.image.png", "{cand}");
assert_eq!(p.skipped, [(0, Skip::NotSelectable)], "{cand}");
let e = back.pick(|c| c == cand, no_parameter_sets()).unwrap_err();
assert_eq!(e, [(0, Skip::NotSelectable), (1, Skip::NotEligible)], "{cand}");
assert_eq!(back.check_candidates(),
Err(CandidateError::AsComponent { index: 0, capability: cand.into() }));
}
// A registered, pinned capability is unaffected.
assert_eq!(dispatched(&v1("wai.pc.gpcc", None), all), Some("wai.pc.gpcc"));
assert_eq!(v1("wai.pc.gpcc", None).check_candidates(), Ok(()));
// A string this revision does not register is the sink's to advertise
// (SPEC §4, §8): taken when advertised, the fallback otherwise.
assert_eq!(dispatched(&v1("wai.image.nope", Some("wai.image.png")), all), Some("wai.image.nope"));
assert_eq!(dispatched(&v1("wai.image.nope", Some("wai.image.png")), |c| c == "wai.image.png"),
Some("wai.image.png"));
// A record or derivation names no payload: never taken, whatever is advertised.
assert_eq!(dispatched(&v1("wai.video.manifest", Some("wai.image.png")), all), Some("wai.image.png"));
assert_eq!(dispatched(&v1("wai.derive.crop", None), all), None);
let m = WaiMulti::new("binary", "improve", serde_json::Value::Null, vec![
Rendition::media("wai.video.manifest", "bytes", b"r".to_vec()),
Rendition::media("wai.image.nope", "bytes", b"n".to_vec()),
]);
assert!(!m.manifest.renditions[0].is_selectable());
assert_eq!(m.primary(no_parameter_sets()).unwrap().meta.capability, "wai.image.nope");
assert_eq!(m.pick(|c| c == "wai.image.nope", no_parameter_sets()).unwrap().meta.capability, "wai.image.nope");
}
/// SPEC §5, *Payloads of the WAI-native integer codecs*: `to_emit_bytes` (the C
/// packers' emit path) parses an integer payload as a sink does and writes nothing
/// when it is not its capability's pinned format, as the capability, the fallback
/// or a `WAI2` component: a keyframe-0 `WIV1` under `wai.video.int_hyper`, a `WIV1`
/// cut inside its tables, a tool's JSON form. The committed payloads are written.
#[test]
fn the_emit_path_refuses_an_integer_payload_that_is_not_its_format() {
let read = |rel: &str| std::fs::read(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors").join(rel)).unwrap();
let (k0, k1) = (read("int_video/int_video_bitstream.wiv1"), read("int_video/video_nk_bitstream.wiv1"));
let (mot, hyp) = (crate::codecs::CAP_VIDEO_INT_MOTION, crate::codecs::CAP_VIDEO_INT_HYPER);
let v1 = |cap: &str, fb: Option<&str>, p: &[u8]| {
let mut req = ModelRequirement::new(cap);
if let Some(f) = fb {
req = req.with_fallback(f);
}
Wai::new(Manifest::new("video", "improve", req, Conditioning::new("bytes")), p.to_vec()).to_emit_bytes()
};
let code = |r: Result<Vec<u8>, EmitError>| match r {
Err(EmitError::IntPayload { component, error }) => Some((component, error.code)),
Err(e) => panic!("{e}"),
Ok(_) => None,
};
assert_eq!(code(v1(hyp, None, &k0)), Some((None, "keyframe_capability_mismatch")));
assert_eq!(code(v1(mot, None, &k0[..40])), Some((None, "stream_length")));
assert_eq!(code(v1(mot, None, &read("int_video/int_video_bitstream.json"))), Some((None, "bad_magic")));
assert_eq!(code(v1(crate::codecs::CAP_VIDEO_AV1, Some(mot), &k1)), Some((None, "keyframe_capability_mismatch")));
assert_eq!(code(v1(mot, None, &k0)), None);
assert_eq!(code(v1(hyp, None, &k1)), None);
let v2 = |a: &[u8], b: &[u8]| WaiMulti::new("video", "improve", serde_json::Value::Null, vec![
Rendition::media(mot, "bytes", a.to_vec()), Rendition::media(hyp, "bytes", b.to_vec()),
]).to_emit_bytes();
assert_eq!(code(v2(&k0, &k0)), Some((Some(1), "keyframe_capability_mismatch")));
assert_eq!(code(v2(&k0, &k1)), None);
}
/// An encrypted envelope's payload is ciphertext (SPEC §6): the emit paths do not
/// check it against the integer format, since a sink decrypts it first (§6.4) and
/// checks the plaintext when it decodes it. A `WAI1` and a `WAI2` that declare an
/// encryption are written; the same bytes in plaintext are refused.
#[test]
fn an_encrypted_integer_payload_is_emitted() {
let read = |rel: &str| std::fs::read(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors").join(rel)).unwrap();
let plain = read("int_video/int_video_bitstream.wiv1");
let cipher: Vec<u8> = plain.iter().enumerate().map(|(i, b)| b ^ (i as u8).wrapping_mul(151).wrapping_add(7)).collect();
let mot = crate::codecs::CAP_VIDEO_INT_MOTION;
let v1 = |security: Option<Security>| {
let mut m = Manifest::new("video", "improve", ModelRequirement::new(mot), Conditioning::new("bytes"));
m.security = security;
Wai::new(m, cipher.clone()).to_emit_bytes()
};
assert!(v1(Some(Security::new("aes-256-gcm", 7))).is_ok(), "an encrypted WAI1 is written");
assert!(matches!(v1(None), Err(EmitError::IntPayload { .. })), "the ciphertext in plaintext is refused");
assert!(matches!(v1(Some(Security::new("none", 0))), Err(EmitError::IntPayload { .. })), "`none` is plaintext");
let mut multi = WaiMulti::new("video", "improve", serde_json::Value::Null, vec![Rendition::media(mot, "bytes", cipher.clone())]);
assert!(matches!(multi.to_emit_bytes(), Err(EmitError::IntPayload { .. })));
multi.manifest.extra.insert("security".into(), serde_json::json!({"encryption": "mls-sframe", "kid": 0}));
assert!(multi.to_emit_bytes().is_ok(), "an encrypted WAI2 is written");
}
/// SPEC §6.4 step 2 at the reference dispatchers, which open no scheme: a manifest
/// that declares an encryption other than `"none"` is inert before any capability
/// is considered (`unsupported_encryption`), so a payload sent in the clear under a
/// false claim of encryption is not decoded as plaintext; `"none"`, `null` and an
/// absent member are plaintext. A `WAI2` `security` member that is not a security
/// object is inert too, and is never written.
#[test]
fn an_encrypted_envelope_is_inert_at_the_reference_dispatchers() {
let png = "wai.image.png";
let all = |_: &str| true;
let v1 = |security: Option<Security>| {
let mut m = Manifest::new("image", "improve", ModelRequirement::new(png), Conditioning::new("bytes"));
m.security = security;
Wai::new(m, b"bytes".to_vec())
};
assert_eq!(v1(None).dispatch_verified(all, no_parameter_sets()).unwrap().capability, png);
assert_eq!(v1(Some(Security::new("none", 0))).dispatch_verified(all, no_parameter_sets()).unwrap().capability, png);
let e = v1(Some(Security::new("aes-256-gcm", 7))).dispatch_verified(all, no_parameter_sets()).unwrap_err();
assert_eq!(e.code(), "unsupported_encryption");
assert!(e.to_string().contains("\"aes-256-gcm\""), "{e}");
let multi = |security: Option<Value>| {
let mut m = WaiMulti::new("image", "improve", Value::Null,
vec![Rendition::media(png, "bytes", b"a".to_vec()), Rendition::media(png, "bytes", b"b".to_vec())]);
if let Some(v) = security {
m.manifest.extra.insert("security".into(), v);
}
m
};
for plain in [None, Some(Value::Null), Some(serde_json::json!({"encryption": "none"})), Some(serde_json::json!({}))] {
let m = multi(plain.clone());
assert_eq!(m.pick(all, no_parameter_sets()).unwrap().index, 0, "{plain:?}");
assert!(m.to_emit_bytes().is_ok(), "{plain:?}");
}
let encrypted = multi(Some(serde_json::json!({"encryption": "mls-sframe", "kid": 0})));
assert!(encrypted.to_emit_bytes().is_ok(), "an encrypted WAI2 is written");
let skips = encrypted.pick(all, no_parameter_sets()).unwrap_err();
assert_eq!(skips, vec![(0, Skip::Encrypted), (1, Skip::Encrypted)]);
assert_eq!(skips[0].1.code(), "unsupported_encryption");
for bad in [serde_json::json!(true), serde_json::json!("aes"), serde_json::json!([]), serde_json::json!({"encryption": 5})] {
let m = multi(Some(bad.clone()));
assert!(matches!(m.to_emit_bytes(), Err(EmitError::Security(ref why)) if why.contains("is not a security object")), "{bad}");
let back = WaiMulti::from_bytes(&m.to_bytes().unwrap()).unwrap();
assert_eq!(back.pick(all, no_parameter_sets()).unwrap_err()[0].1, Skip::Encrypted, "{bad}: read, it is inert");
}
}
/// The emit path of the C packers (`wai_envelope_pack`,
/// `wai_envelope_pack_multi`), driven exactly as they drive it: the manifest
/// JSON parsed into the manifest type, the envelope built, and the bytes taken
/// from `to_emit_bytes`, which refuses a candidate in every place one can be
/// named and writes nothing. No codec feature is needed.
#[test]
fn the_emit_path_refuses_a_candidate() {
let v1 = |cap: &str, fb: &str| {
let json = format!(r#"{{"wai":"1.0","media":"binary","intent":"improve","model_requirement":{{"capability":"{cap}","fallback":{fb}}},"conditioning":{{"kind":"bytes"}},"target":null}}"#);
let manifest: Manifest = serde_json::from_str(&json).unwrap();
Wai::new(manifest, b"bytes".to_vec()).to_emit_bytes()
};
let refused = v1("wai.splat.sog", "null").unwrap_err();
assert!(matches!(refused, EmitError::Candidate(CandidateError::AsCapability { .. })), "{refused}");
let refused = v1("wai.image.png", "\"wai.tensor.nnc\"").unwrap_err();
assert!(matches!(refused, EmitError::Candidate(CandidateError::AsFallback { .. })), "{refused}");
assert!(refused.to_string().contains("MUST NOT emit"), "{refused}");
let ok = v1("wai.image.png", "null").unwrap();
assert_eq!(Wai::from_bytes(&ok).unwrap().capability(), "wai.image.png");
let v2 = |caps: &[(&str, Option<&str>)]| {
let comps: Vec<String> = caps.iter().map(|(c, of)| match of {
Some(of) => format!(r#"{{"capability":"{c}","kind":"bytes","role":"companion","companion_of":"{of}"}}"#),
None => format!(r#"{{"capability":"{c}","kind":"bytes"}}"#),
}).collect();
let json = format!(r#"{{"wai":"1.1","media":"binary","intent":"improve","renditions":[{}],"target":null}}"#, comps.join(","));
let manifest: ManifestV2 = serde_json::from_str(&json).unwrap();
let payloads = vec![b"p".to_vec(); manifest.renditions.len()];
WaiMulti::from_parts(manifest, payloads).unwrap().to_emit_bytes()
};
// A candidate as a rendition, and as a companion.
let refused = v2(&[("wai.splat.sog", None)]).unwrap_err();
assert!(matches!(refused, EmitError::Candidate(CandidateError::AsComponent { index: 0, .. })), "{refused}");
let refused = v2(&[("wai.video.av1", None), ("wai.tensor.nnc", Some("wai.video.av1"))]).unwrap_err();
assert!(matches!(refused, EmitError::Candidate(CandidateError::AsComponent { index: 1, .. })), "{refused}");
assert!(v2(&[("wai.video.av1", None), ("wai.meta.hdr10plus", Some("wai.video.av1"))]).is_ok());
// The free functions the wasm writers use agree with the methods.
assert_eq!(check_candidates_v1("wai.splat.sog", None),
Err(CandidateError::AsCapability { capability: "wai.splat.sog".into() }));
assert_eq!(check_component_candidates(["wai.image.png", "wai.splat.sog"]),
Err(CandidateError::AsComponent { index: 1, capability: "wai.splat.sog".into() }));
assert_eq!(check_component_candidates(["wai.image.png"]), Ok(()));
}
#[test]
fn version_detection_and_cross_rejection() {
let v1 = Wai::new(
Manifest::new("image", "replicate", ModelRequirement::new("wai.image.png"),
Conditioning::new("png")),
b"x".to_vec(),
).to_bytes().unwrap();
let v2 = sample_multi().to_bytes().unwrap();
assert_eq!(detect_version(&v1).unwrap(), 1);
assert_eq!(detect_version(&v2).unwrap(), 2);
// a v1 reader on a v2 envelope MUST refuse cleanly (no silent
// fallback — that would hide a major-version mismatch)
assert!(Wai::from_bytes(&v2).is_err());
// and vice versa
assert!(WaiMulti::from_bytes(&v1).is_err());
}
#[test]
fn multi_rejects_truncation() {
let bytes = sample_multi().to_bytes().unwrap();
for cut in [3, 7, 30, bytes.len() - 5] {
assert!(WaiMulti::from_bytes(&bytes[..cut]).is_err(),
"truncation at {cut} should be rejected");
}
}
// ---- the parameter-set pin (SPEC §3.1) ----
use crate::codecs::{DeclaredTier, ReplicateRefusal, CAP_PRIOR_SET};
const MLIC: &str = "wai.neural.int_mlicpp";
const STREAM: &[u8] = include_bytes!("../tests/vectors/int_mlicpp/stream.wai");
const STREAM_SET: &[u8] = include_bytes!("../tests/vectors/int_mlicpp/stream_set.wai");
const MODEL_SHA: &str = "7ef44f3b1fc4a2df6c3e3b68460f595cae3d31cb7f9018699e29e738340272a4";
const LISTING_SHA: &str = "85a05b0dac15566c5a657ddeee253eaa9f54ce852ae04924f5b04151901d9d1b";
#[test]
fn pin_members_round_trip_and_stay_absent() {
let a = "a1".repeat(32);
let p = Prior::new("m", "ab".repeat(32), "int")
.with_tier(DeclaredTier::DecodeEquivalence)
.with_supersedes(Supersedes::new(a.clone()));
assert_eq!(
serde_json::to_string(&p).unwrap(),
format!(r#"{{"id":"m","sha256":"{}","determinism":"int","tier":"decode-equivalence","supersedes":{{"sha256":"{a}"}}}}"#,
"ab".repeat(32))
);
let back: Prior = serde_json::from_str(&serde_json::to_string(&p).unwrap()).unwrap();
assert_eq!(back, p);
// `with_tier(None)` writes the registered token "none".
assert_eq!(Prior::new("m", "ab".repeat(32), "int").with_tier(DeclaredTier::None).tier(), Some("none"));
// `Prior::new` writes none of the optional members.
assert_eq!(serde_json::to_string(&Prior::new("m", "ab", "d")).unwrap(),
r#"{"id":"m","sha256":"ab","determinism":"d"}"#);
// A member this revision does not define is kept.
let future = r#"{"id":"m","sha256":"ab","determinism":"d","later":{"x":1}}"#;
let f: Prior = serde_json::from_str(future).unwrap();
assert_eq!(serde_json::to_string(&f).unwrap(), future);
}
#[test]
fn null_member_equals_absent() {
// SPEC §3.1: `null` in an optional pin member is the same as absent.
let with_nulls = r#"{"id":"m","sha256":"ab","determinism":"d","form":null,"tier":null,"supersedes":null}"#;
let p: Prior = serde_json::from_str(with_nulls).unwrap();
assert_eq!(p, Prior::new("m", "ab", "d"));
assert_eq!(serde_json::to_string(&p).unwrap(), r#"{"id":"m","sha256":"ab","determinism":"d"}"#);
// A member of the wrong JSON type is a malformed manifest.
assert!(serde_json::from_str::<Prior>(r#"{"id":"m","sha256":"ab","determinism":"d","tier":3}"#).is_err());
assert!(serde_json::from_str::<Prior>(r#"{"id":"m","sha256":"ab","determinism":"d","supersedes":"x"}"#).is_err());
// The same holds for a rendition entry's identity members (SPEC §7.1).
let entry = |extra: &str| serde_json::from_str::<RenditionMeta>(
&format!(r#"{{"capability":"wai.image.png","kind":"png"{extra}}}"#));
assert!(entry(r#","id":null,"sha256":null,"companion_of_id":null"#).is_ok());
for bad in [r#","id":7"#, r#","sha256":true"#, r#","companion_of_id":["a"]"#] {
assert!(entry(bad).is_err(), "{bad}");
}
}
#[test]
fn committed_int_mlicpp_envelope_round_trips_byte_for_byte() {
for (bytes, len) in [(STREAM, 859usize), (STREAM_SET, 910)] {
assert_eq!(bytes.len(), len);
let w = Wai::from_bytes(bytes).unwrap();
assert_eq!(w.to_bytes().unwrap(), bytes, "re-serialization must keep every byte");
let p = w.prior().unwrap();
assert_eq!(p.check_syntax(), Ok(()));
assert_eq!(p.declared_tier(), Some(DeterminismTier::DecodeEquivalence));
assert_eq!(w.replicate_refusal(), None);
assert_eq!(w.check_pin(), Ok(()));
}
let legacy = Wai::from_bytes(STREAM).unwrap();
assert_eq!(legacy.prior().unwrap().sha256, MODEL_SHA);
assert_eq!((legacy.prior().unwrap().form(), legacy.prior().unwrap().tier()), (None, None));
let set = Wai::from_bytes(STREAM_SET).unwrap();
assert_eq!(set.prior().unwrap().sha256, LISTING_SHA);
assert_eq!(set.prior().unwrap().form(), Some(CAP_PRIOR_SET));
// The two envelopes differ only in their pins.
assert_eq!(set.payload, legacy.payload);
let strip = |w: &Wai| { let mut m = w.manifest.clone(); m.conditioning.prior = None; serde_json::to_string(&m).unwrap() };
assert_eq!(strip(&set), strip(&legacy));
}
#[test]
fn pin_syntax_is_checked() {
let good = Prior::new("m", MODEL_SHA, "int");
assert_eq!(good.check_syntax(), Ok(()));
for bad in [MODEL_SHA.to_ascii_uppercase(), MODEL_SHA[..63].to_string(),
format!("{}g", &MODEL_SHA[..63]), String::new()] {
assert_eq!(Prior::new("m", bad.clone(), "int").check_syntax().unwrap_err().code(), "pin_malformed", "{bad}");
}
// `supersedes` is informative: a bad one is a static finding, not a refusal.
let selfref = good.clone().with_supersedes(Supersedes::new(MODEL_SHA));
assert_eq!(selfref.check_syntax(), Ok(()));
assert!(selfref.supersedes_finding().unwrap().contains("equals"));
assert!(selfref.usable_supersedes().is_none());
let badform = good.clone().with_supersedes(Supersedes::new(LISTING_SHA).with_form("wai.image.png"));
assert!(badform.supersedes_finding().unwrap().contains("not a registered prior form"));
let ok = good.clone().with_supersedes(Supersedes::new(LISTING_SHA).with_form(CAP_PRIOR_SET));
assert_eq!(ok.supersedes_finding(), None);
// An unregistered tier token is not malformed; it declares no tier.
let mut t = good.clone();
t.tier = Some("decode_equivalence".into());
assert_eq!(t.check_syntax(), Ok(()));
assert!(!t.tier_is_registered());
assert_eq!(t.declared_tier(), None);
}
#[test]
fn replicate_reads_the_declared_tier() {
let synth = "wai.neural.int_synth";
let env = |prior: Option<Prior>| {
let mut c = Conditioning::new("int_synth");
c.prior = prior;
Wai::new(Manifest::new("image", "replicate", ModelRequirement::new(synth), c), vec![])
};
let code = |w: &Wai| w.replicate_refusal().map(|r| r.code());
let int = Prior::new("m", "ab".repeat(32), "int-rans/p16+int-synth");
assert_eq!(code(&env(None)), Some("replicate_unpinned"));
assert_eq!(code(&env(Some(int.clone().with_tier(DeclaredTier::None)))), Some("replicate_tier"));
assert_eq!(code(&env(Some(int.clone().with_tier(DeclaredTier::EntropyConsistency)))), Some("replicate_tier"));
assert_eq!(code(&env(Some(Prior::new("m", "ab".repeat(32), "onnx-f32")))), Some("replicate_tier"));
assert_eq!(code(&env(Some(int.clone().with_form(NNC)))), Some("replicate_form_not_exact"));
// A set-form pin declaring decode-equivalence is sound.
assert_eq!(code(&env(Some(int.clone().with_form(CAP_PRIOR_SET).with_tier(DeclaredTier::DecodeEquivalence)))), None);
// An explicit tier wins over the words, which are informative.
let words_float = Prior::new("m", "ab".repeat(32), "onnx-cpu-f32").with_tier(DeclaredTier::DecodeEquivalence);
assert_eq!(code(&env(Some(words_float))), None);
// An unregistered tier token reads as none.
let mut odd = int.clone();
odd.tier = Some("decode_equivalence".into());
assert_eq!(code(&env(Some(odd))), Some("replicate_tier"));
// The legacy reading is unchanged for tier-less pins.
assert_eq!(code(&env(Some(int))), None);
assert_eq!(ReplicateRefusal::Unregistered.code(), "replicate_unregistered");
assert_eq!(ReplicateRefusal::Class(crate::codecs::ReplicateClass::NeuralFloat).code(), "replicate_class");
}
fn mlic_env(prior: Option<Prior>, fallback: Option<&str>) -> Wai {
let mut req = ModelRequirement::new(MLIC);
req.fallback = fallback.map(String::from);
let mut c = Conditioning::new("int_mlicpp");
c.prior = prior;
Wai::new(Manifest::new("image", "replicate", req, c), b"payload".to_vec())
}
#[test]
fn wai1_dispatch_treats_a_refused_pin_as_unsupported() {
let all = |_: &str| true;
let pin = Prior::new("m", MODEL_SHA, "int");
let refuse = |c: &str, _: &Prior| -> Result<(), PinRefusal> { Err(PinRefusal::NotHeld { capability: c.into() }) };
// A refused pin falls back to png.
let w = mlic_env(Some(pin.clone()), Some("wai.image.png"));
let d = w.dispatch_verified(all, refuse).unwrap();
assert_eq!((d.capability, d.verified), ("wai.image.png", None));
// With no fallback the envelope is inert and names the reason.
let e = mlic_env(Some(pin.clone()), None).dispatch_verified(all, refuse).unwrap_err();
assert_eq!(e.code(), "prior_not_held");
assert!(matches!(e, Inert::PinRefused { ref capability, .. } if capability == MLIC));
// A verified pin dispatches the primary, carrying what the verifier returned.
let d = w.dispatch_verified(all, |_, p: &Prior| Ok::<_, PinRefusal>(p.sha256.clone())).unwrap();
assert_eq!((d.capability, d.verified.as_deref()), (MLIC, Some(MODEL_SHA)));
// A pin on a capability that decodes against no parameter set never calls the verifier.
let mut calls = 0;
let png = Wai::new(Manifest::new("image", "replicate", ModelRequirement::new("wai.image.png"),
Conditioning::new("png").with_prior(pin.clone())), vec![]);
let d = png.dispatch_verified(all, |_, _| { calls += 1; Ok::<(), PinRefusal>(()) }).unwrap();
assert_eq!((d.capability, calls), ("wai.image.png", 0));
// One pin applies to whichever capability decodes: the fallback is verified too.
let mut seen = Vec::new();
let e = mlic_env(Some(pin.clone()), Some("wai.neural.int_synth"))
.dispatch_verified(all, |c, _| { seen.push(c.to_string()); Err::<(), _>(PinRefusal::NotHeld { capability: c.into() }) })
.unwrap_err();
assert_eq!(seen, [MLIC, "wai.neural.int_synth"]);
assert_eq!(e, Inert::PinRefused { capability: MLIC.into(), refusal: PinRefusal::NotHeld { capability: MLIC.into() } });
// An unpinned envelope dispatches without verification, and an unadvertised one is inert.
assert_eq!(mlic_env(None, None).dispatch_verified(all, refuse).unwrap().capability, MLIC);
assert_eq!(mlic_env(None, None).dispatch_verified(|_| false, refuse).unwrap_err(), Inert::MissingCapability);
// A candidate is never dispatched, whatever its pin and whatever the sink
// advertises (SPEC §4, §5): step 5 takes the fallback, and the candidate's
// pin is never verified.
let mut seen = Vec::new();
let cand = Wai::new(Manifest::new("binary", "improve",
ModelRequirement::new(crate::codecs::CAP_SPLAT_SOG).with_fallback("wai.image.png"),
Conditioning::new("bytes").with_prior(pin.clone())), vec![]);
let d = cand.dispatch_verified(all, |c, _| { seen.push(c.to_string()); Ok::<(), PinRefusal>(()) }).unwrap();
assert_eq!(d.capability, "wai.image.png");
assert!(seen.is_empty(), "{seen:?}");
// An unregistered capability with a pin is verified (fail closed).
let mut calls = 0;
let future = Wai::new(Manifest::new("image", "replicate", ModelRequirement::new("wai.neural.future"),
Conditioning::new("x").with_prior(pin)), vec![]);
let _ = future.dispatch_verified(all, |_, _| { calls += 1; Ok::<(), PinRefusal>(()) });
assert_eq!(calls, 1);
}
/// A verifier over held digests, as a sink holding one-file sets would run it.
fn holding(held: &'static [&'static str]) -> impl FnMut(&str, &Prior) -> Result<(), PinRefusal> {
move |cap: &str, p: &Prior| {
if held.contains(&p.sha256.as_str()) {
Ok(())
} else if p.usable_supersedes().is_some_and(|s| held.contains(&s.sha256.as_str())) {
Err(PinRefusal::Superseded { capability: cap.into(), predecessor: p.supersedes().unwrap().sha256.clone() })
} else {
Err(PinRefusal::NotHeld { capability: cap.into() })
}
}
}
#[test]
fn wai2_pick_picks_the_rendition_whose_pin_resolves() {
const A: &str = "aa00000000000000000000000000000000000000000000000000000000000000";
const B: &str = "bb00000000000000000000000000000000000000000000000000000000000000";
const C: &str = "cc00000000000000000000000000000000000000000000000000000000000000";
let m = WaiMulti::new("image", "replicate", Value::Null, vec![
Rendition::media(MLIC, "int_mlicpp", b"B".to_vec())
.with_prior(Prior::new("b", B, "int").with_tier(DeclaredTier::DecodeEquivalence)
.with_supersedes(Supersedes::new(A))),
Rendition::media(MLIC, "int_mlicpp", b"A".to_vec())
.with_prior(Prior::new("a", A, "int").with_tier(DeclaredTier::DecodeEquivalence)),
Rendition::media("wai.image.avif", "avif", b"AVIF".to_vec()),
]);
let all = |_: &str| true;
let codes = |s: &[(usize, Skip)]| s.iter().map(|(i, k)| (*i, k.code())).collect::<Vec<_>>();
let p = m.pick(all, holding(&[A])).unwrap();
assert_eq!((p.index, p.payload), (1, &b"A"[..]));
assert_eq!(codes(&p.skipped), [(0, "digest_superseded")]);
assert_eq!(m.pick(all, holding(&[B])).unwrap().index, 0);
let p = m.pick(all, holding(&[C])).unwrap();
assert_eq!(p.index, 2);
assert_eq!(codes(&p.skipped), [(0, "prior_not_held"), (1, "prior_not_held")]);
// Nothing left: inert, with a reason for every entry.
let e = m.pick(|c| c != "wai.image.avif", holding(&[C])).unwrap_err();
assert_eq!(codes(&e), [(0, "prior_not_held"), (1, "prior_not_held"), (2, "missing_capability")]);
// The pin decides, not the capability.
assert_eq!(m.pick(|c| c == MLIC, holding(&[A])).unwrap().index, 1);
assert_eq!(m.primary(holding(&[B])).unwrap().index, 0);
// A candidate is never picked, whatever its pin and whatever the sink
// advertises: it is skipped as not selectable before its pin is verified
// (SPEC §4, §7.1).
let mut calls = 0;
let c = WaiMulti::new("binary", "improve", Value::Null, vec![
Rendition::media(crate::codecs::CAP_TENSOR_NNC, "bytes", b"T".to_vec())
.with_prior(Prior::new("t", A, "int")),
Rendition::media("wai.image.avif", "avif", b"AVIF".to_vec()),
]);
let p = c.pick(all, |_, _| { calls += 1; Ok::<(), PinRefusal>(()) }).unwrap();
assert_eq!((p.index, calls), (1, 0));
assert_eq!(codes(&p.skipped), [(0, "not_selectable")]);
assert_eq!(codes(&c.pick(|cap| cap == crate::codecs::CAP_TENSOR_NNC, holding(&[A])).unwrap_err()),
[(0, "not_selectable"), (1, "missing_capability")]);
}
#[test]
fn manifest_bytes_are_the_original() {
let v1_manifest = br#"{"wai":"1.0", "media":"image","intent":"replicate","model_requirement":{"capability":"wai.text.zstd"},"conditioning":{"kind":"zstd"},"security":{"encryption":"none","kid":0},"target":null}"#;
let mut env = b"WAI1".to_vec();
env.extend_from_slice(&(v1_manifest.len() as u32).to_le_bytes());
env.extend_from_slice(v1_manifest);
env.extend_from_slice(&3u32.to_le_bytes());
env.extend_from_slice(b"abc");
assert_eq!(manifest_bytes(&env).unwrap(), v1_manifest);
// The Rust types now keep `security` too, re-serialized without the whitespace.
let w = Wai::from_bytes(&env).unwrap();
assert_eq!(w.manifest.security, Some(Security::new("none", 0)));
assert!(serde_json::to_string(&w.manifest).unwrap().contains(r#""security":{"encryption":"none","kid":0}"#));
let v2 = WaiMulti::from_parts(
serde_json::from_str(r#"{"wai":"1.1","media":"audio","intent":"replicate","renditions":[{"capability":"wai.audio.opus","kind":"opus","later":true}],"target":null}"#).unwrap(),
vec![b"OPUS".to_vec()]).unwrap().to_bytes().unwrap();
let m = manifest_bytes(&v2).unwrap();
assert!(std::str::from_utf8(m).unwrap().contains(r#""later":true"#));
assert!(manifest_bytes(&env[..10]).is_err());
assert!(manifest_bytes(b"WAI3\0\0\0\0").is_err());
}
#[test]
fn static_pin_findings() {
let code = |w: &Wai| w.check_pin().err().map(|f| f.code);
let png = |p: Prior| Wai::new(Manifest::new("image", "replicate", ModelRequirement::new("wai.image.png"),
Conditioning::new("png").with_prior(p)), vec![]);
let on = |cap: &str, intent: &str, p: Option<Prior>| {
let mut c = Conditioning::new("k");
c.prior = p;
Wai::new(Manifest::new("image", intent, ModelRequirement::new(cap), c), vec![])
};
let pin = Prior::new("m", MODEL_SHA, "int");
assert_eq!(code(&png(pin.clone())), Some("prior_not_applicable"));
assert_eq!(code(&on("wai.neural.bmshj2018", "improve", Some(pin.clone()))), Some("form_required"));
assert_eq!(code(&on("wai.neural.future", "improve", Some(pin.clone()))), Some("form_required"));
assert_eq!(code(&on("wai.image.jpegai", "improve",
Some(pin.clone().with_form(CAP_PRIOR_SET).with_tier(DeclaredTier::DecodeEquivalence)))),
Some("tier_exceeds_class"));
assert_eq!(code(&on("wai.image.jpegai", "improve",
Some(pin.clone().with_form(CAP_PRIOR_SET).with_tier(DeclaredTier::EntropyConsistency)))),
None);
assert_eq!(code(&on("wai.neural.int_synth", "replicate", Some(pin.clone().with_tier(DeclaredTier::None)))),
Some("replicate_tier"));
assert_eq!(code(&on("wai.neural.int_synth", "replicate", None)), Some("replicate_unpinned"));
assert_eq!(code(&on(MLIC, "replicate", Some(Prior::new("m", MODEL_SHA.to_ascii_uppercase(), "int")))),
Some("pin_malformed"));
let mut odd = pin.clone();
odd.tier = Some("exact".into());
assert_eq!(code(&on(MLIC, "improve", Some(odd))), Some("tier_unregistered"));
assert_eq!(code(&on(MLIC, "improve", Some(pin.clone().with_supersedes(Supersedes::new(MODEL_SHA))))),
Some("supersedes_malformed"));
assert_eq!(code(&on(MLIC, "improve", Some(pin.clone().with_form("wai.image.png")))),
Some("form_not_registered"));
assert_eq!(code(&Wai::from_bytes(STREAM).unwrap()), None);
// Order: prior_not_applicable before pin_malformed.
assert_eq!(code(&png(Prior::new("m", "AB", "int"))), Some("prior_not_applicable"));
for c in PIN_FINDING_CODES {
assert!(c.chars().all(|ch| ch.is_ascii_lowercase() || ch == '_'), "{c} is snake_case");
}
}
// ---- component identity (SPEC §7.1) ----
#[test]
fn component_ids_and_digests_round_trip_and_stay_absent() {
let m = WaiMulti::new("video", "replicate", Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"HEVC".to_vec()).with_id("base-hd").with_payload_digest(),
Rendition::companion("wai.video.lcevc", b"ENH".to_vec(), "wai.video.hevc").with_companion_of_id("base-hd"),
]);
let bytes = m.to_bytes().unwrap();
let back = WaiMulti::from_bytes(&bytes).unwrap();
assert_eq!(back.to_bytes().unwrap(), bytes);
let e = &back.manifest.renditions;
assert_eq!(e[0].id.as_deref(), Some("base-hd"));
assert_eq!(e[0].sha256.as_deref(), Some(crate::pin::sha256_hex(b"HEVC").as_str()));
assert_eq!(e[1].companion_of_id.as_deref(), Some("base-hd"));
// Absent members are not written.
let plain = WaiMulti::new("audio", "replicate", Value::Null, vec![Rendition::media("wai.audio.opus", "opus", vec![1])]);
let j = serde_json::to_string(&plain.manifest).unwrap();
assert_eq!(j, r#"{"wai":"1.1","media":"audio","intent":"replicate","renditions":[{"capability":"wai.audio.opus","kind":"opus"}],"target":null}"#);
}
#[test]
fn component_digest_mismatch_skips_the_entry() {
let mut m = WaiMulti::new("image", "replicate", Value::Null, vec![
Rendition::media("wai.image.png", "png", b"PNG-A".to_vec()).with_payload_digest(),
Rendition::media("wai.image.png", "png", b"PNG-B".to_vec()).with_payload_digest(),
Rendition::companion("wai.meta.hdr10plus", b"META".to_vec(), "wai.image.png").with_payload_digest(),
]);
assert_eq!(m.pick(|_| true, accept).unwrap().index, 0);
m.renditions[0][0] ^= 1;
let p = m.pick(|_| true, accept).unwrap();
assert_eq!(p.index, 1);
assert_eq!(p.skipped, [(0, Skip::ComponentDigest)]);
assert_eq!(Skip::ComponentDigest.code(), "component_digest_mismatch");
let class = crate::codecs::replicate_refusal("wai.image.jpegai", "replicate", None).unwrap();
assert_eq!(Skip::Replicate(class).code(), "replicate_class");
// A companion whose payload does not match is not applied.
assert_eq!(m.companions_of_index(1).len(), 1);
m.renditions[2][0] ^= 1;
assert!(m.companions_of_index(1).is_empty());
// A malformed digest matches nothing.
let mut bad = RenditionMeta::new("wai.image.png", "png");
bad.sha256 = Some("AB".repeat(32));
assert!(!bad.payload_digest_matches(b""));
}
#[test]
fn companion_of_id_binds_one_of_two_same_capability_renditions() {
let two = |bind: bool| {
let enh = Rendition::companion("wai.video.lcevc", b"ENH".to_vec(), "wai.video.hevc");
WaiMulti::new("video", "replicate", Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"HEVC-A".to_vec()).with_id("a"),
Rendition::media("wai.video.hevc", "hevc", b"HEVC-B".to_vec()).with_id("b"),
if bind { enh.with_companion_of_id("b") } else { enh },
])
};
assert_eq!(two(true).check_component_roles(), Ok(()));
// Without the id the binding is ambiguous, as an earlier sink reads it.
assert_eq!(two(false).check_component_roles(),
Err(RoleError::EnhancementBaseNotUnique { index: 2, capability: "wai.video.lcevc".into(), matches: 2 }));
// The layer applies to entry 1 only.
let m = two(true);
assert!(m.companions_of_index(0).is_empty());
assert_eq!(m.companions_of_index(1).iter().map(|c| c.0).collect::<Vec<_>>(), [2]);
// Two layers on one base by id are stacked: refused by index.
let stacked = WaiMulti::new("video", "replicate", Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"A".to_vec()).with_id("a"),
Rendition::media("wai.video.hevc", "hevc", b"B".to_vec()).with_id("b"),
Rendition::companion("wai.video.lcevc", b"E1".to_vec(), "wai.video.hevc").with_companion_of_id("a"),
Rendition::companion("wai.video.lcevc", b"E2".to_vec(), "wai.video.hevc").with_companion_of_id("a"),
]);
assert!(matches!(stacked.check_component_roles(), Err(RoleError::DuplicateEnhancementLayer { index: 3, first: 2, .. })));
// One layer per base, by id, conforms.
let each = WaiMulti::new("video", "replicate", Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"A".to_vec()).with_id("a"),
Rendition::media("wai.video.hevc", "hevc", b"B".to_vec()).with_id("b"),
Rendition::companion("wai.video.lcevc", b"E1".to_vec(), "wai.video.hevc").with_companion_of_id("a"),
Rendition::companion("wai.video.lcevc", b"E2".to_vec(), "wai.video.hevc").with_companion_of_id("b"),
]);
assert_eq!(each.check_component_roles(), Ok(()));
}
#[test]
fn component_id_grammar_and_uniqueness() {
for ok in ["a", "base-hd", "r_1", &"x".repeat(64)] {
assert!(is_component_id(ok), "{ok}");
}
for bad in ["", "Base", "a.b", "wai.video.hevc", "a b", &"x".repeat(65)] {
assert!(!is_component_id(bad), "{bad}");
}
let with = |ids: [&str; 2]| WaiMulti::new("image", "replicate", Value::Null, vec![
Rendition::media("wai.image.png", "png", vec![1]).with_id(ids[0]),
Rendition::media("wai.image.avif", "avif", vec![2]).with_id(ids[1]),
]);
assert_eq!(with(["a", "a"]).check_component_roles(), Err(RoleError::DuplicateComponentId { index: 1, first: 0 }));
assert_eq!(with(["a", "B"]).check_component_roles(), Err(RoleError::ComponentIdMalformed { index: 1 }));
// companion_of_id must resolve, and name an entry of capability companion_of.
let m = |id: &str| WaiMulti::new("video", "replicate", Value::Null, vec![
Rendition::media("wai.video.hevc", "hevc", b"H".to_vec()).with_id("h"),
Rendition::media("wai.video.av1", "av1", b"A".to_vec()).with_id("v"),
Rendition::companion("wai.meta.hdr10plus", b"M".to_vec(), "wai.video.hevc").with_companion_of_id(id),
]);
assert_eq!(m("h").check_component_roles(), Ok(()));
assert_eq!(m("zz").check_component_roles(), Err(RoleError::CompanionIdUnresolved { index: 2, id: "zz".into() }));
assert_eq!(m("v").check_component_roles(),
Err(RoleError::CompanionIdCapabilityMismatch { index: 2, id: "v".into(), capability: "wai.video.av1".into() }));
}
/// vocab/v1 maps the pin's members: the scoped JSON-LD term `prior` names
/// exactly the members a pin serializes, and the SHACL shape's tier list is
/// the registered tokens.
#[test]
fn vocab_pin_terms_match_prior_members() {
// v1 carries the pin terms as additive terms; v2 is generated from the
// registry (`wai registry export`) and carries the same terms and shapes.
for (name, context, shapes) in [
("v1", include_str!("../../vocab/v1.jsonld"), include_str!("../../vocab/v1.shapes.ttl")),
("v2", include_str!("../../vocab/v2.jsonld"), include_str!("../../vocab/v2.shapes.ttl")),
] {
let ctx: Value = serde_json::from_str(context).unwrap();
let mut terms: Vec<&str> = ctx["@context"]["prior"]["@context"].as_object()
.unwrap_or_else(|| panic!("{name}: a scoped `prior` term"))
.keys().map(String::as_str).filter(|k| !k.starts_with('@')).collect();
terms.sort_unstable();
let full = Prior::new("m", "ab".repeat(32), "int").with_form(CAP_PRIOR_SET)
.with_tier(DeclaredTier::DecodeEquivalence).with_supersedes(Supersedes::new("cd".repeat(32)));
let v = serde_json::to_value(&full).unwrap();
let mut members: Vec<&str> = v.as_object().unwrap().keys().map(String::as_str).collect();
members.sort_unstable();
assert_eq!(terms, members, "{name}");
assert!(ctx["@context"]["ParameterSetPin"].as_str().is_some_and(|t| t.ends_with("ParameterSetPin")), "{name}");
let tiers: Vec<String> = crate::codecs::TIER_TOKENS.iter().map(|(t, _)| format!("\"{t}\"")).collect();
assert!(shapes.contains(&format!("sh:in ( {} )", tiers.join(" "))), "{name}: the shape's tier list");
// The context types no `prior` object, so the pin shape must target the
// objects of wai:prior (a class target would never fire), and `supersedes`
// is checked as a digest and a form, not as a whole pin.
let pin_shape = shapes.split("wai:ParameterSetPinShape a sh:NodeShape").nth(1)
.unwrap_or_else(|| panic!("{name}: a ParameterSetPinShape"))
.split("\n\n").next().unwrap();
assert!(pin_shape.contains("sh:targetObjectsOf wai:prior"), "{name}: {pin_shape}");
assert!(!pin_shape.contains("sh:targetClass"), "{name}: {pin_shape}");
assert!(pin_shape.contains("sh:path wai:pinSupersedes ; sh:node wai:SupersedesShape"), "{name}: {pin_shape}");
let sup = shapes.split("wai:SupersedesShape a sh:NodeShape").nth(1)
.unwrap_or_else(|| panic!("{name}: a SupersedesShape"))
.split("\n\n").next().unwrap();
assert!(sup.contains("sh:path wai:pinSha256") && sup.contains("sh:path wai:pinForm"), "{name}: {sup}");
assert!(!sup.contains("wai:pinId") && !sup.contains("wai:pinDeterminism"), "{name}: {sup}");
}
// v2 defines each pin term once, the tier property among them.
let v2 = include_str!("../../vocab/v2.ttl");
for term in ["ParameterSetPin", "prior", "pinId", "pinSha256", "pinDeterminism", "pinForm", "pinTier", "pinSupersedes"] {
assert_eq!(v2.matches(&format!("\nwai:{term} a ")).count(), 1, "v2.ttl defines wai:{term} once");
}
}
/// `derived_from` (SPEC §7.1, *Derived renditions*): an OPTIONAL member under the
/// MINOR rule (§8). Written after `refines`, read back to the value it was, absent
/// from the bytes when unset, and a malformed value still parses (its typed reading
/// reports it).
#[test]
fn derived_from_is_an_optional_member_that_never_breaks_a_parse() {
let d = DerivedFrom { motif: "ab".repeat(32), motif_capability: "wai.video.int_motion".into(), renderer: "[email protected]".into(), settings: "cd".repeat(32) };
let m = Manifest::new("video", "replicate", ModelRequirement::new("wai.video.av1"), Conditioning::new("av1"));
let plain = Wai::new(m.clone(), b"p".to_vec()).to_bytes().unwrap();
assert!(!String::from_utf8_lossy(&plain).contains("derived_from"));
let with = Wai::new(m.with_derived_from(&d), b"p".to_vec()).to_bytes().unwrap();
let text = String::from_utf8_lossy(&with).to_string();
assert!(text.contains(&format!("\"derived_from\":{{\"motif\":\"{}\",\"motif_capability\":\"wai.video.int_motion\",\"renderer\":\"[email protected]\",\"settings\":\"{}\"}},\"target\"", "ab".repeat(32), "cd".repeat(32))), "{text}");
let back = Wai::from_bytes(&with).unwrap();
assert_eq!(DerivedFrom::from_value(back.manifest.derived_from.as_ref().unwrap()).unwrap(), d);
assert_eq!(back.to_bytes().unwrap(), with, "the member round-trips byte for byte");
// A malformed member parses; the typed reading names what is wrong.
let bad = String::from_utf8(with[8..8 + u32::from_le_bytes(with[4..8].try_into().unwrap()) as usize].to_vec()).unwrap().replace(&"ab".repeat(32), &"AB".repeat(32));
let m2: Manifest = serde_json::from_str(&bad).unwrap();
assert_eq!(DerivedFrom::from_value(m2.derived_from.as_ref().unwrap()).unwrap_err().0, "motif is not 64 lowercase hexadecimal digits");
for (v, why) in [
(serde_json::json!(1), "derived_from is not an object"),
(serde_json::json!({"motif": "ab".repeat(32), "motif_capability": "", "renderer": "r", "settings": "cd".repeat(32)}), "motif_capability is not a non-empty string"),
(serde_json::json!({"motif": "ab".repeat(32), "motif_capability": "c", "settings": "cd".repeat(32)}), "renderer is not a non-empty string"),
(serde_json::json!({"motif": "ab".repeat(32), "motif_capability": "c", "renderer": "r", "settings": "x"}), "settings is not 64 lowercase hexadecimal digits"),
] {
assert_eq!(DerivedFrom::from_value(&v).unwrap_err().0, why);
}
// An entry of a WAI2 carries it the same way.
let e = RenditionMeta::new("wai.video.av1", "av1").with_derived_from(&d);
let j = serde_json::to_string(&e).unwrap();
assert!(j.ends_with(&format!("\"settings\":\"{}\"}}}}", "cd".repeat(32))), "{j}");
let back: RenditionMeta = serde_json::from_str(&j).unwrap();
assert_eq!(DerivedFrom::from_value(back.derived_from.as_ref().unwrap()).unwrap(), d);
}
/// A `WAI1` (or, with `v2`, a `WAI2`) envelope whose manifest is exactly `len`
/// bytes: a small manifest with one member no revision defines, `pad`, filled to
/// length (a reader ignores such a member, SPEC §8), over a 3-byte payload.
fn envelope_with_manifest_len(v2: bool, len: usize) -> Vec<u8> {
let head = if v2 {
r#"{"wai":"1.1","media":"text","intent":"replicate","renditions":[{"capability":"wai.text.zstd","kind":"zstd"}],"target":null,"pad":""#
} else {
r#"{"wai":"1.0","media":"text","intent":"replicate","model_requirement":{"capability":"wai.text.zstd"},"conditioning":{"kind":"zstd"},"target":null,"pad":""#
};
let m = format!("{head}{}\"}}", "x".repeat(len - head.len() - 2));
assert_eq!(m.len(), len);
let mut b = if v2 { MAGIC_V2.to_vec() } else { MAGIC.to_vec() };
b.extend_from_slice(&(m.len() as u32).to_le_bytes());
b.extend_from_slice(m.as_bytes());
if v2 {
b.extend_from_slice(&1u16.to_le_bytes());
b.extend_from_slice(&0u32.to_le_bytes());
}
b.extend_from_slice(&3u32.to_le_bytes());
b.extend_from_slice(b"abc");
b
}
/// SPEC §2, *Reader limits*, from the length field: eight bytes declaring a manifest
/// of exactly `MANIFEST_MAX_LEN` bytes are refused only as truncated, and one byte
/// more, or the most a u32 can declare, as too large, by both readers and by
/// `manifest_bytes`. A 2 MiB manifest, the size a staged layer's `prev` reaches over
/// some 23 000 units with alternating producers, is read and written back byte for
/// byte. Both writers stop at their limits: an envelope is written at limits equal to
/// its manifest's and its payloads' lengths, and refused at one byte less of either
/// (at the real limits the check would serialise 128 MiB, which a debug build takes
/// many seconds over, or hold a 4 GiB payload).
#[test]
fn a_manifest_past_the_reader_limit_is_refused_and_never_written() {
assert_eq!(MANIFEST_MAX_LEN, 134_217_728);
let too_large = |e: String| assert!(e.starts_with("manifest_too_large:"), "{e}");
let truncated = |e: String| assert!(e.contains("truncated"), "{e}");
for magic in [MAGIC, MAGIC_V2] {
let read = |b: &[u8]| if magic == MAGIC { Wai::from_bytes(b).err() } else { WaiMulti::from_bytes(b).err() }
.expect("refused").to_string();
let at = [&magic[..], &(MANIFEST_MAX_LEN as u32).to_le_bytes()].concat();
truncated(read(&at));
truncated(manifest_bytes(&at).unwrap_err().to_string());
for declared in [MANIFEST_MAX_LEN as u32 + 1, u32::MAX] {
let b = [&magic[..], &declared.to_le_bytes()].concat();
too_large(read(&b));
too_large(manifest_bytes(&b).unwrap_err().to_string());
}
}
let len = 2 << 20;
let one = envelope_with_manifest_len(false, len);
let w = Wai::from_bytes(&one).expect("a 2 MiB manifest is read");
assert_eq!(w.to_bytes().unwrap(), one, "and written back byte for byte");
let multi = envelope_with_manifest_len(true, len);
let w2 = WaiMulti::from_bytes(&multi).expect("a 2 MiB WAI2 manifest is read");
assert_eq!(w2.to_bytes().unwrap(), multi, "and written back byte for byte");
// Each payload is 3 bytes.
let lim = |manifest, field| WriteLimits { manifest, field };
let past_field = |e: std::io::Error, what: &str| {
let e = e.to_string();
assert!(e.contains(&format!("{what} is 3 bytes, past the u32 field")), "{e}");
};
assert_eq!(w.to_bytes_within(lim(len, 3)).unwrap(), one, "written at its own lengths");
too_large(w.to_bytes_within(lim(len - 1, 3)).unwrap_err().to_string());
past_field(w.to_bytes_within(lim(len, 2)).unwrap_err(), "the payload");
assert_eq!(w2.to_bytes_within(lim(len, 3)).unwrap(), multi, "written at its own lengths");
too_large(w2.to_bytes_within(lim(len - 1, 3)).unwrap_err().to_string());
past_field(w2.to_bytes_within(lim(len, 2)).unwrap_err(), "rendition 0's payload");
// A second rendition of 3 bytes: each fits a field of 5, their total does not.
let mut w3 = WaiMulti::from_bytes(&multi).unwrap();
w3.manifest.renditions.push(w3.manifest.renditions[0].clone());
w3.renditions.push(b"def".to_vec());
assert!(w3.to_bytes_within(lim(MANIFEST_MAX_LEN, 6)).is_ok());
let e = w3.to_bytes_within(lim(MANIFEST_MAX_LEN, 5)).unwrap_err().to_string();
assert!(e.contains("payload sum overflows u32"), "{e}");
}
/// The manifest's nesting limit the reference readers apply (SPEC §2, *Reader
/// limits*): 127 nested arrays and objects are read, 128 are refused, in `WAI1` and
/// `WAI2` manifests alike.
#[test]
fn a_manifest_nested_past_127_is_refused() {
for v2 in [false, true] {
let base = envelope_with_manifest_len(v2, 300);
let ml = u32::from_le_bytes(base[4..8].try_into().unwrap()) as usize;
let text = std::str::from_utf8(&base[8..8 + ml]).unwrap();
let rest = &base[8 + ml..];
// The manifest object is level 1, so `pad` holding n nested arrays is n + 1.
let pad = text.find("\"pad\":").unwrap();
let nested = |levels: usize| {
let n = levels - 1;
let m = format!("{}\"pad\":{}{}}}", &text[..pad], "[".repeat(n), "]".repeat(n));
[&base[..4], &(m.len() as u32).to_le_bytes(), m.as_bytes(), rest].concat()
};
let read = |b: &[u8]| if v2 { WaiMulti::from_bytes(b).map(|_| ()) } else { Wai::from_bytes(b).map(|_| ()) };
assert!(read(&nested(127)).is_ok(), "v2={v2}: 127 levels are read");
assert!(read(&nested(128)).is_err(), "v2={v2}: 128 levels are refused");
}
}
/// A `WAI1` (or, with `v2`, a `WAI2`) envelope whose manifest holds exactly `values`
/// values: the small manifest of [`envelope_with_manifest_len`], whose `pad` is an
/// array of zeros. Its other members are 10 values in either form, `pad` among them.
pub(crate) fn envelope_with_manifest_values(v2: bool, values: usize) -> Vec<u8> {
let base = envelope_with_manifest_len(v2, 300);
let ml = u32::from_le_bytes(base[4..8].try_into().unwrap()) as usize;
let text = std::str::from_utf8(&base[8..8 + ml]).unwrap();
let pad = text.find("\"pad\":").unwrap();
// A reader that read one value more or less than this would fail the tests that
// use it at the limit and one past it.
let m = format!("{}\"pad\":[0{}]}}", &text[..pad], ",0".repeat(values - 11));
[&base[..4], &(m.len() as u32).to_le_bytes(), m.as_bytes(), &base[8 + ml..]].concat()
}
/// SPEC §2, *Reader limits*: a manifest of [`MANIFEST_MAX_VALUES`] values is read and
/// one value more is refused (`manifest_too_many_values`), by both readers, in `WAI1`
/// and `WAI2` manifests alike; the writers refuse to write one past the limit. The
/// count takes every value at any depth, the manifest object among them, and no
/// member name.
#[test]
fn a_manifest_past_the_value_limit_is_refused_and_never_written() {
assert_eq!(MANIFEST_MAX_VALUES, 4_194_304);
assert_eq!(check_manifest_text(br#"{"a":[1,{"b":null}],"c":"d"}"#).unwrap(), 6);
assert_eq!(check_manifest_text(br#"{}"#).unwrap(), 1);
let too_many = |e: String| assert!(e.starts_with("manifest_too_many_values:"), "{e}");
for v2 in [false, true] {
let read = |b: &[u8]| if v2 { WaiMulti::from_bytes(b).map(|_| ()) } else { Wai::from_bytes(b).map(|_| ()) };
let at = envelope_with_manifest_values(v2, MANIFEST_MAX_VALUES);
read(&at).unwrap_or_else(|e| panic!("v2={v2}: the limit is read: {e}"));
too_many(read(&envelope_with_manifest_values(v2, MANIFEST_MAX_VALUES + 1)).unwrap_err().to_string());
}
// The writers: the envelope at the limit is written back; one zero more is not.
let at = envelope_with_manifest_values(false, MANIFEST_MAX_VALUES);
let mut w = Wai::from_bytes(&at).unwrap();
assert_eq!(w.to_bytes().unwrap(), at);
w.manifest.extra["pad"].as_array_mut().unwrap().push(Value::from(0));
too_many(w.to_bytes().unwrap_err().to_string());
let at = envelope_with_manifest_values(true, MANIFEST_MAX_VALUES);
let mut w2 = WaiMulti::from_bytes(&at).unwrap();
assert_eq!(w2.to_bytes().unwrap(), at);
w2.manifest.extra["pad"].as_array_mut().unwrap().push(Value::Null);
too_many(w2.to_bytes().unwrap_err().to_string());
}
/// A length a container writes into a u32 field must fit it: the writers refuse a
/// payload past 2^32 − 1 bytes instead of writing its length modulo 2^32.
#[test]
#[cfg(target_pointer_width = "64")]
fn a_length_past_a_u32_field_is_refused() {
assert_eq!(u32_field(u32::MAX as usize, u32::MAX, "x").unwrap(), u32::MAX);
let e = u32_field(u32::MAX as usize + 1, u32::MAX, "the payload").unwrap_err().to_string();
assert!(e.contains("the payload is 4294967296 bytes, past the u32 field"), "{e}");
}
}