No reference sink read the declared cost: a few kilobytes of valid stream could make the neural sink, or its C ABI, decode a clip at the 2^30-byte cap. - neural::ModelRegistry::with_cost_limit takes a limit on each figure of int_payload::DeclaredCost (DeclaredCost::new, a figure left unbounded at its type's maximum). An integer payload over it is refused before any symbol is decoded, with the new code cost_over_limit (int_payload::check_cost, neural::DecodeError::OverCostLimit); it is the sink's policy, so dispatch moves on as for a capability the sink cannot decode: a WAI1 to its fallback, a WAI2 to its next entry. integer-payloads §8 lists the code and its routing, and §7 places the check after the networks are planned. - neural::declared_cost reads the cost over the parameters resolve_verified returns, so a registry user reads it over the bytes a pin verified without building int_payload::IntParams. - The C ABI's neural entry points do not advertise a capability that decodes to integer video, PCM or splat attributes, which have no wai_decoded_t kind (neural::decodes_to_int): such a payload is refused before it is decoded, not after. - wai_int_payload_declared takes the parameter set's path, or null for the header alone, and wai_int_declared_t gains working_bytes, the multiply-accumulates (macs_lo, macs_hi) and cost_known. The header alone fixes them for a keyframe-0 WIV1 (int_wire::Wiv1::keyframe0_working_bytes, which declared_cost now uses too; container::DeclaredSize gains working_bytes and macs). Tests: neural_cost_limit (its own binary, a live-bytes allocator): a clip over the limit refused having held under 64 KiB, the next WAI2 entry decoded; the C ABI refusing the clip under 64 KiB through wai_neural_decode_ex and wai_neural_decode_video; the cost over a registry's set and through the C ABI equal to the library's, and set_incomplete without a member. ffi_envelope and int_payload_cost check the header-only figures. Co-Authored-By: Claude Opus 5.5 <[email protected]>
260 lines
14 KiB
Rust
260 lines
14 KiB
Rust
//! The C ABI's envelope surface (feature `ffi`, no codec library): unpack returns
|
|
//! each manifest exactly as the envelope carries it, the `WAI2` rendition table
|
|
//! carries every component's identity and whether SPEC §7.1 may select it, packing
|
|
//! keeps members this library does not model, `wai_envelope_select` picks by §7.1
|
|
//! and reports each skipped entry's reason, and the packers refuse a candidate
|
|
//! capability (SPEC §5).
|
|
#![cfg(feature = "ffi")]
|
|
|
|
use std::ffi::{CStr, CString};
|
|
use std::ptr;
|
|
|
|
use wai::container::{Prior, Rendition, WaiMulti};
|
|
use wai::ffi::*;
|
|
|
|
fn empty() -> WaiBuffer {
|
|
WaiBuffer { data: ptr::null_mut(), len: 0, cap: 0 }
|
|
}
|
|
|
|
fn take(b: WaiBuffer) -> Vec<u8> {
|
|
let v = if b.data.is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(b.data, b.len) }.to_vec() };
|
|
unsafe { wai_buffer_free(b) };
|
|
v
|
|
}
|
|
|
|
fn last_error() -> String {
|
|
let p = wai_last_error();
|
|
if p.is_null() { String::new() } else { unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned() }
|
|
}
|
|
|
|
fn stream_wai() -> Vec<u8> {
|
|
std::fs::read(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_mlicpp/stream.wai")).unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn unpack_returns_the_original_manifest_bytes() {
|
|
let env = stream_wai();
|
|
let (mut m, mut p) = (empty(), empty());
|
|
assert_eq!(unsafe { wai_envelope_unpack(env.as_ptr(), env.len(), &mut m, &mut p) }, WAI_OK);
|
|
let m = take(m);
|
|
assert_eq!(m.len(), 339);
|
|
assert_eq!(m, env[8..8 + 339]);
|
|
assert_eq!(take(p).len(), env.len() - 8 - 339 - 4);
|
|
|
|
// A manifest with `security`, whitespace and a member this library does not
|
|
// model comes back unchanged.
|
|
let man = br#"{"wai":"1.0", "media":"text","intent":"replicate","model_requirement":{"capability":"wai.text.zstd"},"conditioning":{"kind":"zstd"},"security":{"encryption":"mls-sframe","kid":2},"target":null,"later":1}"#;
|
|
let mut e = b"WAI1".to_vec();
|
|
e.extend_from_slice(&(man.len() as u32).to_le_bytes());
|
|
e.extend_from_slice(man);
|
|
e.extend_from_slice(&1u32.to_le_bytes());
|
|
e.push(7);
|
|
let (mut m, mut p) = (empty(), empty());
|
|
assert_eq!(unsafe { wai_envelope_unpack(e.as_ptr(), e.len(), &mut m, &mut p) }, WAI_OK);
|
|
assert_eq!(take(m), man);
|
|
take(p);
|
|
}
|
|
|
|
fn multi() -> Vec<u8> {
|
|
WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![
|
|
Rendition::media("wai.video.lcevc", "lcevc", b"MISLABELLED".to_vec()),
|
|
Rendition::media("wai.neural.int_mlicpp", "int_mlicpp", b"PINNED".to_vec())
|
|
.with_id("pinned")
|
|
.with_prior(Prior::new("m", "ab".repeat(32), "int")),
|
|
Rendition::media("wai.video.hevc", "hevc", b"HEVC".to_vec()).with_id("base").with_payload_digest(),
|
|
Rendition::companion("wai.meta.hdr10plus", b"META".to_vec(), "wai.video.hevc").with_companion_of_id("base"),
|
|
]).to_bytes().unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn unpack_multi_table_carries_identity_and_selectability() {
|
|
let env = multi();
|
|
let (mut m, mut t, mut b) = (empty(), empty(), empty());
|
|
assert_eq!(unsafe { wai_envelope_unpack_multi(env.as_ptr(), env.len(), &mut m, &mut t, &mut b) }, WAI_OK);
|
|
let ml = u32::from_le_bytes(env[4..8].try_into().unwrap()) as usize;
|
|
assert_eq!(take(m), env[8..8 + ml]);
|
|
let table: serde_json::Value = serde_json::from_slice(&take(t)).unwrap();
|
|
take(b);
|
|
let sel: Vec<bool> = table.as_array().unwrap().iter().map(|e| e["selectable"].as_bool().unwrap()).collect();
|
|
assert_eq!(sel, [false, true, true, false], "a companion-only entry and a companion are never selectable");
|
|
assert_eq!(table[1]["prior"]["sha256"], "ab".repeat(32));
|
|
assert_eq!(table[1]["id"], "pinned");
|
|
assert_eq!(table[2]["sha256"], wai::pin::sha256_hex(b"HEVC"));
|
|
assert_eq!(table[3]["role"], "companion");
|
|
assert_eq!(table[3]["companion_of"], "wai.video.hevc");
|
|
assert_eq!(table[3]["companion_of_id"], "base");
|
|
assert!(table[0].get("prior").is_none() && table[0].get("id").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn envelope_select_follows_spec_7_1() {
|
|
let env = multi();
|
|
let all = CString::new(r#"["wai.video.lcevc","wai.neural.int_mlicpp","wai.video.hevc","wai.meta.hdr10plus"]"#).unwrap();
|
|
let mut idx = u32::MAX;
|
|
assert_eq!(unsafe { wai_envelope_select(env.as_ptr(), env.len(), all.as_ptr(), &mut idx) }, WAI_OK);
|
|
// Entry 0 is companion-only, entry 1's pin applies and this call holds no set.
|
|
assert_eq!(idx, 2);
|
|
let none = CString::new(r#"["wai.neural.int_mlicpp"]"#).unwrap();
|
|
assert_eq!(unsafe { wai_envelope_select(env.as_ptr(), env.len(), none.as_ptr(), &mut idx) }, WAI_ERR_NO_RENDITION);
|
|
let why = last_error();
|
|
assert!(why.contains("0: not_selectable") && why.contains("1: prior_not_held: ") && why.contains("2: missing_capability"), "{why}");
|
|
// A bad argument is reported, not a panic.
|
|
let bad = CString::new("not json").unwrap();
|
|
assert_eq!(unsafe { wai_envelope_select(env.as_ptr(), env.len(), bad.as_ptr(), &mut idx) }, WAI_ERR_INVALID_ARG);
|
|
assert_eq!(unsafe { wai_envelope_select(env.as_ptr(), env.len(), all.as_ptr(), ptr::null_mut()) }, WAI_ERR_INVALID_ARG);
|
|
}
|
|
|
|
#[test]
|
|
fn pack_multi_keeps_every_member() {
|
|
let manifest = CString::new(r#"{"wai":"1.1","media":"image","intent":"replicate","renditions":[{"capability":"wai.image.png","kind":"png","id":"p","sha256":"0000000000000000000000000000000000000000000000000000000000000000","later":{"x":1}}],"target":null}"#).unwrap();
|
|
let payload = b"PNG";
|
|
let ptrs = [payload.as_ptr()];
|
|
let lens = [payload.len()];
|
|
let mut out = empty();
|
|
assert_eq!(unsafe { wai_envelope_pack_multi(manifest.as_ptr(), ptrs.as_ptr(), lens.as_ptr(), 1, &mut out) }, WAI_OK);
|
|
let env = take(out);
|
|
let ml = u32::from_le_bytes(env[4..8].try_into().unwrap()) as usize;
|
|
assert_eq!(&env[8..8 + ml], manifest.as_bytes());
|
|
// A mismatched payload count is an argument error.
|
|
let mut out = empty();
|
|
assert_eq!(unsafe { wai_envelope_pack_multi(manifest.as_ptr(), ptrs.as_ptr(), lens.as_ptr(), 0, &mut out) }, WAI_ERR_INVALID_ARG);
|
|
}
|
|
|
|
/// SPEC §4, §5 and §7.1: a candidate is never selected, whatever the sink
|
|
/// advertises; the table marks it not selectable, and selection reports why.
|
|
#[test]
|
|
fn envelope_select_never_picks_a_candidate() {
|
|
let env = WaiMulti::new("binary", "improve", serde_json::Value::Null, vec![
|
|
Rendition::media("wai.tensor.nnc", "bytes", b"TENSOR".to_vec()),
|
|
Rendition::media("wai.image.png", "png", b"PNG".to_vec()),
|
|
]).to_bytes().unwrap();
|
|
let (mut m, mut t, mut b) = (empty(), empty(), empty());
|
|
assert_eq!(unsafe { wai_envelope_unpack_multi(env.as_ptr(), env.len(), &mut m, &mut t, &mut b) }, WAI_OK);
|
|
take(m);
|
|
take(b);
|
|
let table: serde_json::Value = serde_json::from_slice(&take(t)).unwrap();
|
|
let sel: Vec<bool> = table.as_array().unwrap().iter().map(|e| e["selectable"].as_bool().unwrap()).collect();
|
|
assert_eq!(sel, [false, true], "a candidate is never selectable");
|
|
let all = CString::new(r#"["wai.tensor.nnc","wai.image.png"]"#).unwrap();
|
|
let mut idx = u32::MAX;
|
|
assert_eq!(unsafe { wai_envelope_select(env.as_ptr(), env.len(), all.as_ptr(), &mut idx) }, WAI_OK);
|
|
assert_eq!(idx, 1);
|
|
let only = CString::new(r#"["wai.tensor.nnc"]"#).unwrap();
|
|
assert_eq!(unsafe { wai_envelope_select(env.as_ptr(), env.len(), only.as_ptr(), &mut idx) }, WAI_ERR_NO_RENDITION);
|
|
let why = last_error();
|
|
assert!(why.contains("0: not_selectable") && why.contains("1: missing_capability"), "{why}");
|
|
}
|
|
|
|
/// SPEC §5, *Candidate capabilities*: an encoder MUST NOT emit one, as the
|
|
/// capability, the fallback, or a `WAI2` component, and the C packers refuse it.
|
|
#[test]
|
|
fn envelope_pack_refuses_a_candidate() {
|
|
let man = |cap: &str, fb: &str| CString::new(format!(
|
|
r#"{{"wai":"1.0","media":"binary","intent":"improve","model_requirement":{{"capability":"{cap}","fallback":{fb}}},"conditioning":{{"kind":"bytes"}},"target":null}}"#)).unwrap();
|
|
let payload = b"bytes";
|
|
for (cap, fb) in [("wai.splat.sog", "null"), ("wai.image.png", "\"wai.tensor.nnc\"")] {
|
|
let m = man(cap, fb);
|
|
let mut out = empty();
|
|
let rc = unsafe { wai_envelope_pack(m.as_ptr(), payload.as_ptr(), payload.len(), &mut out) };
|
|
assert_eq!(rc, WAI_ERR_INVALID_ARG, "{cap} / {fb}");
|
|
assert!(out.data.is_null());
|
|
assert!(last_error().contains("MUST NOT emit"), "{}", last_error());
|
|
}
|
|
let ok = man("wai.image.png", "null");
|
|
let mut out = empty();
|
|
assert_eq!(unsafe { wai_envelope_pack(ok.as_ptr(), payload.as_ptr(), payload.len(), &mut out) }, WAI_OK);
|
|
take(out);
|
|
|
|
let ptrs = [payload.as_ptr(), payload.as_ptr()];
|
|
let lens = [payload.len(), payload.len()];
|
|
for comps in [
|
|
r#"[{"capability":"wai.splat.sog","kind":"bytes"}]"#,
|
|
r#"[{"capability":"wai.video.av1","kind":"av1"},{"capability":"wai.tensor.nnc","kind":"bytes","role":"companion","companion_of":"wai.video.av1"}]"#,
|
|
] {
|
|
let multi = CString::new(format!(
|
|
r#"{{"wai":"1.1","media":"binary","intent":"improve","renditions":{comps},"target":null}}"#)).unwrap();
|
|
let n = serde_json::from_str::<serde_json::Value>(comps).unwrap().as_array().unwrap().len();
|
|
let mut out = empty();
|
|
let rc = unsafe { wai_envelope_pack_multi(multi.as_ptr(), ptrs.as_ptr(), lens.as_ptr(), n, &mut out) };
|
|
assert_eq!(rc, WAI_ERR_INVALID_ARG, "{comps}");
|
|
assert!(out.data.is_null());
|
|
}
|
|
}
|
|
|
|
/// SPEC §5, *Payloads of the WAI-native integer codecs*: the C packers refuse a payload
|
|
/// that is not the format SPEC §5 pins its integer capability (or fallback, or `WAI2`
|
|
/// component) to, parsed as a sink parses it: a tool's JSON form, a `WIV1` cut inside
|
|
/// its header, and a keyframe-0 `WIV1` under `wai.video.int_hyper`, whose magic and
|
|
/// version are right. The committed payloads are written.
|
|
#[test]
|
|
fn envelope_pack_refuses_an_integer_payload_that_is_not_its_format() {
|
|
let vec = |rel: &str| std::fs::read(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors").join(rel)).unwrap();
|
|
let (k0, k1, json) = (vec("int_video/int_video_bitstream.wiv1"), vec("int_video/video_nk_bitstream.wiv1"), vec("int_video/int_video_bitstream.json"));
|
|
let man = |cap: &str, fb: &str| CString::new(format!(
|
|
r#"{{"wai":"1.0","media":"video","intent":"improve","model_requirement":{{"capability":"{cap}","fallback":{fb}}},"conditioning":{{"kind":"bytes"}},"target":null}}"#)).unwrap();
|
|
let pack = |m: &CString, p: &[u8]| {
|
|
let mut out = empty();
|
|
let rc = unsafe { wai_envelope_pack(m.as_ptr(), p.as_ptr(), p.len(), &mut out) };
|
|
if rc == WAI_OK { take(out); } else { assert!(out.data.is_null()); }
|
|
(rc, if rc == WAI_OK { String::new() } else { last_error() })
|
|
};
|
|
let (mot, hyp) = ("wai.video.int_motion", "wai.video.int_hyper");
|
|
for (m, p, code) in [
|
|
(man(mot, "null"), &json[..], "bad_magic"),
|
|
(man(mot, "null"), &k0[..40], "stream_length"),
|
|
(man(hyp, "null"), &k0[..], "keyframe_capability_mismatch"),
|
|
(man("wai.video.av1", &format!("\"{mot}\"")), &k1[..], "keyframe_capability_mismatch"),
|
|
] {
|
|
let (rc, why) = pack(&m, p);
|
|
assert_eq!(rc, WAI_ERR_INVALID_ARG, "{code}");
|
|
assert!(why.contains(&format!("({code}:")), "{why}");
|
|
}
|
|
assert_eq!(pack(&man(mot, "null"), &k0).0, WAI_OK);
|
|
assert_eq!(pack(&man(hyp, "null"), &k1).0, WAI_OK);
|
|
|
|
let multi = CString::new(format!(
|
|
r#"{{"wai":"1.1","media":"video","intent":"improve","renditions":[{{"capability":"{mot}","kind":"bytes"}},{{"capability":"{hyp}","kind":"bytes"}}],"target":null}}"#)).unwrap();
|
|
let pack2 = |a: &[u8], b: &[u8]| {
|
|
let (ptrs, lens) = ([a.as_ptr(), b.as_ptr()], [a.len(), b.len()]);
|
|
let mut out = empty();
|
|
let rc = unsafe { wai_envelope_pack_multi(multi.as_ptr(), ptrs.as_ptr(), lens.as_ptr(), 2, &mut out) };
|
|
if rc == WAI_OK { take(out); } else { assert!(out.data.is_null()); }
|
|
(rc, if rc == WAI_OK { String::new() } else { last_error() })
|
|
};
|
|
assert_eq!(pack2(&k0, &k1).0, WAI_OK);
|
|
let (rc, why) = pack2(&k0, &k0);
|
|
assert_eq!(rc, WAI_ERR_INVALID_ARG);
|
|
assert!(why.contains("component 1:") && why.contains("(keyframe_capability_mismatch:"), "{why}");
|
|
}
|
|
|
|
/// `wai_int_payload_declared` reads what an integer payload's header declares, before
|
|
/// any decode: a `WIV1`'s clip bytes and symbols, and, for keyframe 0, which runs no
|
|
/// network, its working bytes and no multiply-accumulates; a `WIH1`'s symbols, its
|
|
/// image size and its cost being its networks'; and refuses a payload that is not the
|
|
/// capability's format, or a capability with none.
|
|
#[test]
|
|
fn int_payload_declared_reads_the_header() {
|
|
let vec = |rel: &str| std::fs::read(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors").join(rel)).unwrap();
|
|
let declared = |cap: &str, p: &[u8]| {
|
|
let c = CString::new(cap).unwrap();
|
|
let mut out = WaiIntDeclared::default();
|
|
let rc = unsafe { wai_int_payload_declared(c.as_ptr(), p.as_ptr(), p.len(), std::ptr::null(), &mut out) };
|
|
(rc, out, if rc == WAI_OK { String::new() } else { last_error() })
|
|
};
|
|
let k0 = vec("int_video/int_video_bitstream.wiv1");
|
|
let v = wai::int_wire::parse_wiv1(&k0, Some(0)).unwrap();
|
|
let clip = v.n_frames as u64 * v.h as u64 * v.w as u64 * 3;
|
|
let (rc, out, _) = declared("wai.video.int_motion", &k0);
|
|
assert_eq!((rc, out.output_bytes, out.output_known, out.symbols), (WAI_OK, clip, 1, clip));
|
|
assert_eq!((out.cost_known, out.working_bytes, out.macs_lo, out.macs_hi), (1, v.keyframe0_working_bytes(), 0, 0));
|
|
assert!(out.working_bytes > clip, "the clip, its table, its motion vectors and its frame list");
|
|
let wih1 = vec("int_hyper/tiny_bitstream.wih1");
|
|
let k = wai::int_wire::parse_wih1(&wih1).unwrap();
|
|
let (rc, out, _) = declared("wai.neural.int_hyper", &wih1);
|
|
assert_eq!((rc, out.output_known, out.symbols), (WAI_OK, 0, (k.n_z * k.zh * k.zw + k.m * k.sh * k.sw) as u64));
|
|
assert_eq!(out.cost_known, 0, "a WIH1's cost is its networks'");
|
|
let (rc, _, why) = declared("wai.video.int_motion", &vec("int_video/int_video_bitstream.json"));
|
|
assert!(rc == WAI_ERR_INVALID_ARG && why.contains("(bad_magic:"), "{why}");
|
|
let (rc, _, _) = declared("wai.image.png", &k0);
|
|
assert_eq!(rc, WAI_ERR_INVALID_ARG);
|
|
}
|