//! 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(m: &T, cap: usize) -> std::io::Result> { struct Capped(Vec, usize); impl std::io::Write for Capped { fn write(&mut self, b: &[u8]) -> std::io::Result { 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::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(v: &Option) -> 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, /// 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, /// The embedding space `min_sim` is measured in. Absent ⇒ `"clip"` by /// convention. #[serde(default, skip_serializing_if = "is_none")] pub sim_metric: Option, /// Members this revision does not define, kept so they survive a round trip. #[serde(flatten)] pub extra: Map, } impl ModelRequirement { /// A requirement for `capability`, with no fallback and no perceptual threshold. pub fn new(capability: impl Into) -> 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) -> 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) -> 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, #[serde(flatten)] pub extra: Map, } impl Supersedes { /// The predecessor, pinned form-less by `sha256`. pub fn new(sha256: impl Into) -> Self { Self { sha256: sha256.into(), form: None, extra: Map::new() } } /// The predecessor's declared form. pub fn with_form(mut self, form: impl Into) -> 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, /// 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, /// The parameter set this one replaces (informative, SPEC §3.1). #[serde(default, skip_serializing_if = "is_none")] pub supersedes: Option, /// Members this revision does not define, kept so they survive a round trip. #[serde(flatten)] pub extra: Map, } /// 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, sha256: impl Into, determinism: impl Into, ) -> 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) -> 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 { 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 { 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 }, /// 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 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 { 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, #[serde(flatten)] pub extra: Map, } impl Conditioning { /// Conditioning of `kind`, with no pin. pub fn new(kind: impl Into) -> 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, #[serde(default, skip_serializing_if = "is_none")] pub kid: Option, #[serde(flatten)] pub extra: Map, } impl Security { /// `security` naming an encryption scheme and a key id. pub fn new(encryption: impl Into, 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) -> 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, /// `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, /// `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, #[serde(default)] pub target: Value, /// Members this revision does not define, kept so they survive a round trip. #[serde(flatten)] pub extra: Map, } impl Manifest { /// A `"1.0"` manifest with a `null` target and no `security`. pub fn new( media: impl Into, intent: impl Into, 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) -> 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), } /// `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, } /// 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 { 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::() != 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 { 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::() != 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, `@`; /// - `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 { 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, } /// 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, } /// 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 { |capability: &str, _: &Prior| Err(PinRefusal::NotHeld { capability: capability.to_string() }) } impl Wai { pub fn new(manifest: Manifest, payload: Vec) -> Self { Self { manifest, payload } } pub fn write>(&self, path: P) -> std::io::Result { 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> { self.to_bytes_within(WriteLimits::SPEC) } /// [`Self::to_bytes`] within `lim`. fn to_bytes_within(&self, lim: WriteLimits) -> std::io::Result> { // 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>(path: P) -> std::io::Result { let bytes = fs::read(path)?; Self::from_bytes(&bytes) } pub fn from_bytes(bytes: &[u8]) -> std::io::Result { // 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> { 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( &self, advertises: impl Fn(&str) -> bool, mut verify: impl FnMut(&str, &Prior) -> Result, ) -> Result, 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 = 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 { 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::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, 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 { 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 { 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>>) -> Result { 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, max: usize, sizes: Option<&'c RefCell>>, } impl Scan<'_> { /// A slot for the size of the container that opens now. fn open(self) -> Option { 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, 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>(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(self, _: bool) -> Result<(), E> { Ok(()) } fn visit_i64(self, _: i64) -> Result<(), E> { Ok(()) } fn visit_u64(self, _: u64) -> Result<(), E> { Ok(()) } fn visit_f64(self, _: f64) -> Result<(), E> { Ok(()) } fn visit_str(self, _: &str) -> Result<(), E> { Ok(()) } fn visit_unit(self) -> Result<(), E> { Ok(()) } fn visit_seq>(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>(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>(self, d: D) -> Result, 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(self, s: &'de str) -> Result, E> { Ok(Cow::Borrowed(s)) } fn visit_str(self, s: &str) -> Result, 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::` 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_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 { 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 { 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, } 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>(self, d: D) -> Result { 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(self, b: bool) -> Result { Ok(Value::Bool(b)) } fn visit_i64(self, n: i64) -> Result { Ok(Value::Number(n.into())) } fn visit_u64(self, n: u64) -> Result { Ok(Value::Number(n.into())) } fn visit_f64(self, x: f64) -> Result { Ok(serde_json::Number::from_f64(x).map_or(Value::Null, Value::Number)) } fn visit_str(self, s: &str) -> Result { Ok(Value::String(s.to_owned())) } fn visit_string(self, s: String) -> Result { Ok(Value::String(s)) } fn visit_unit(self) -> Result { Ok(Value::Null) } fn visit_seq>(self, mut seq: A) -> Result { 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>(self, mut map: A) -> Result { let mut m = Map::with_capacity(self.next_size()); while let Some(k) = map.next_key::()? { 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; fn object_of(v: Value, what: &str) -> Result { match v { Value::Object(m) => Ok(m), _ => Err(serde::de::Error::custom(format_args!("invalid type: {what} is not an object"))), } } fn required(m: &mut Members, k: &'static str) -> Result { match m.shift_remove(k) { Some(v) => serde_json::from_value(v), None => Err(serde::de::Error::missing_field(k)), } } fn optional(m: &mut Members, k: &str) -> Result, serde_json::Error> { match m.shift_remove(k) { Some(v) => serde_json::from_value(v), None => Ok(None), } } /// An `Option` member, moved: `null` or absent is `None`. fn optional_tree(m: &mut Members, k: &str) -> Option { m.shift_remove(k).filter(|v| !v.is_null()) } /// An `Option` member whose `T` is one of the manifest types. fn optional_of(m: &mut Members, k: &str, build: fn(Members) -> Result) -> Result, 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(m: &mut Members, k: &'static str, build: fn(Members) -> Result) -> Result { 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 { 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 { Ok(Supersedes { sha256: required(&mut m, "sha256")?, form: optional(&mut m, "form")?, extra: m }) } fn prior_from(mut m: Members) -> Result { 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 { Ok(Conditioning { kind: required(&mut m, "kind")?, prior: optional_of(&mut m, "prior", prior_from)?, extra: m }) } fn security_from(mut m: Members) -> Result { Ok(Security { encryption: optional(&mut m, "encryption")?, kid: optional(&mut m, "kid")?, extra: m }) } fn manifest_from_tree(v: Value) -> Result { 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 { 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 { 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, /// 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, /// 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, /// 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, /// For a companion: the capability of the component it accompanies. #[serde(default, skip_serializing_if = "is_none")] pub companion_of: Option, /// 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, /// 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, /// 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, /// `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, /// Members this revision does not define, kept so they survive a round trip. #[serde(flatten)] pub extra: Map, } impl RenditionMeta { /// A selectable rendition entry of `capability` and `kind`, with no identity /// members. pub fn new(capability: impl Into, kind: impl Into) -> 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) -> 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) -> 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) -> Self { self.id = Some(id.into()); self } /// State the entry's payload digest. pub fn with_sha256(mut self, sha256: impl Into) -> 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) -> 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, 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) -> 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, String> { match m.extra.get("security") { None | Some(Value::Null) => Ok(None), Some(v) => serde_json::from_value::(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, /// 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, /// 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, } /// [`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, 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, 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, #[serde(default)] pub target: Value, /// Members this revision does not define, kept so they survive a round trip. #[serde(flatten)] pub extra: Map, } 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 { 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, intent: impl Into, renditions: Vec) -> 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, } impl Rendition { /// A selectable media rendition (an alternative of the same content). pub fn media(capability: impl Into, kind: impl Into, payload: Vec) -> 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, payload: Vec, companion_of: impl Into) -> 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) -> 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) -> 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) -> Self { self.meta.companion_of_id = Some(id.into()); self } } pub struct WaiMulti { pub manifest: ManifestV2, pub renditions: Vec>, // 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, pub skipped: Vec<(usize, Skip)>, } impl WaiMulti { pub fn new(media: impl Into, intent: impl Into, target: Value, renditions: Vec) -> Self { let (metas, payloads): (Vec, Vec>) = 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>) -> std::io::Result { 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>(&self, path: P) -> std::io::Result { let bytes = self.to_bytes()?; fs::write(path, &bytes)?; Ok(bytes.len()) } pub fn read>(path: P) -> std::io::Result { 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> { self.to_bytes_within(WriteLimits::SPEC) } /// [`Self::to_bytes`] within `lim`. fn to_bytes_within(&self, lim: WriteLimits) -> std::io::Result> { 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 { 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( &self, advertises: impl Fn(&str) -> bool, verify: impl FnMut(&str, &Prior) -> Result, ) -> Result, 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( &self, verify: impl FnMut(&str, &Prior) -> Result, ) -> Result, 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( &self, eligible: impl Fn(usize, &RenditionMeta) -> Result<(), Skip>, verify: impl FnMut(&str, &Prior) -> Result, ) -> Result, Vec<(usize, Skip)>> { let order: Vec = (0..self.manifest.renditions.len()).collect(); let digest_ok = |i: usize, m: &RenditionMeta| self.renditions.get(i).is_some_and(|p| m.payload_digest_matches(p)); let (index, verified, skipped) = select_entries(&self.manifest, &order, eligible, digest_ok, verify)?; Ok(Picked { index, meta: &self.manifest.renditions[index], payload: &self.renditions[index], verified, skipped, }) } /// Check that no component, in either role, names a candidate capability /// ([`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, 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 { 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) -> 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( manifest: &ManifestV2, order: &[usize], eligible: impl Fn(usize, &RenditionMeta) -> Result<(), Skip>, digest_ok: impl Fn(usize, &RenditionMeta) -> bool, mut verify: impl FnMut(&str, &Prior) -> Result, ) -> Result<(usize, Option, Vec<(usize, Skip)>), Vec<(usize, Skip)>> { let entries = &manifest.renditions; // SPEC §6.4 step 2, before selection: an encrypted envelope is inert at a sink // that opens no scheme, and every entry is reported so. if !manifest_v2_plaintext(manifest) { return Err((0..entries.len()).map(|i| (i, Skip::Encrypted)).collect()); } let mut skipped = Vec::new(); for &index in order { let Some(meta) = entries.get(index) else { continue }; if !meta.is_selectable() { skipped.push((index, Skip::NotSelectable)); continue; } if let Err(why) = eligible(index, meta) { skipped.push((index, why)); continue; } if !digest_ok(index, meta) { skipped.push((index, Skip::ComponentDigest)); continue; } let verified = match meta.prior.as_ref() { Some(p) if pin_applies_to(&meta.capability) => match verify(&meta.capability, p) { Ok(v) => Some(v), Err(r) => { skipped.push((index, Skip::Pin(r))); continue; } }, _ => None, }; return Ok((index, verified, skipped)); } Err(skipped) } /// The companions SPEC §7.1 binds to the entry at `index`, by entry alone: each /// companion whose `companion_of_id` names that entry's `id`, or, with no /// `companion_of_id`, whose `companion_of` is that entry's capability. /// [`WaiMulti::companions_of_index`] is this with the payload digest checked; a client /// that holds only the head checks it on the bytes it fetches. pub fn companion_indices(entries: &[RenditionMeta], index: usize) -> Vec { let Some(base) = entries.get(index) else { return Vec::new() }; entries.iter().enumerate() .filter(|(_, m)| { m.is_companion() && match m.companion_of_id.as_deref() { Some(id) => base.id.as_deref() == Some(id), None => m.companion_of.as_deref() == Some(base.capability.as_str()), } }) .map(|(i, _)| i) .collect() } /// The refinement components whose `refines.base` names the entry at `index`, in /// table order, by entry alone: [`WaiMulti::refinements_of`] without the payload /// digest, which a client that holds only the head checks on the bytes it fetches. pub fn refinement_indices(entries: &[RenditionMeta], index: usize) -> Vec { let Some(base_id) = entries.get(index).and_then(|b| b.id.as_deref()) else { return Vec::new() }; entries.iter().enumerate() .filter(|(_, m)| { m.is_refinement() && m.refines.as_ref() .and_then(|v| Refines::from_value(v, RefForm::ComponentId).ok()) .is_some_and(|r| r.base == base_id) }) .map(|(i, _)| i) .collect() } /// 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 { 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 { 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, 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 = 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| { 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| { 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| { 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 = 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::(r#"{"id":"m","sha256":"ab","determinism":"d","tier":3}"#).is_err()); assert!(serde_json::from_str::(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::( &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| { 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, 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::>(); 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| { 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::>(), [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 = 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: "enc@1.0".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\":\"enc@1.0\",\"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 { 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 { 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}"); } }