open-standards/wai/wai-rs/src/ffi.rs
Transaction Science b73d691ed4 wai: the energy order of dispatch, and the energy budget that lifts a learned capability
SPEC §4, §7.1 step 2: a sink tries an envelope's capabilities in the energy
order unless its deployer configures another: a decode the platform answers
supported and power-efficient first, a learned capability (NeuralIntegerExact,
NeuralEntropyExact, NeuralFloat, Behavioral) with no energy budget after every
other, the envelope's order within each. The order changes which capability
decodes, never a reason. An energy budget is a decode figure measured on a
named reference sink under OnChipCounter or CalibratedInstrument, in a record
named by its digest; no registration states one yet (REGISTRY, Energy budgets;
the export's optional energy_budget, schema only).

wai-rs: energy_order (DispatchPolicy, Budgets, EnergyBudget, is_learned);
Wai::dispatch_verified and WaiMulti::pick apply it, dispatch_with and pick_with
take another order, the HTTP client's default order and the C ABI's
wai_envelope_select follow it. Tests that are about pins state the manifest
order they run in.

browser-sink §4.7: objects sharing a choice are alternatives; the page decodes
the first in the energy order that routes (no_alternative otherwise).
browser_sink::choose_report, the page's chooseReport (wai-live-web, binding.js),
a choices section in live-conformance/browser (existing sections unchanged),
checked by wai-rs, the page's corpus run and browser_sink_verify.py (34 of 34
mutations caught).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 11b2556827)
2026-10-26 08:30:49 -04:00

595 lines
26 KiB
Rust

//! C ABI for SDK consumers. Stable, language-agnostic surface — every
//! other-language binding (Python ctypes, Node N-API, Swift, JNI, etc.)
//! talks to this. The Rust crate is built as `cdylib`/`staticlib` so it
//! drops in as `libwai.so`/`libwai.dylib`/`wai.dll`.
//!
//! This module (feature `ffi`, which `codecs` implies) is the envelope surface:
//! buffers, errors, packing and unpacking `WAI1`/`WAI2`, and SPEC §7.1 selection.
//! It needs no codec library. The codec wrappers are [`crate::ffi_codecs`]
//! (feature `codecs`, re-exported here) and the neural sink is
//! [`crate::ffi_neural`] (features `neural` and `ffi`).
//!
//! ## Status codes (returned by every wai_* function)
//! - `0` = ok
//! - `1` = invalid argument (null pointer where one shouldn't be, etc.)
//! - `2` = encode/decode failed (codec-level error; details in
//! `wai_last_error`)
//! - `3` = container/envelope error (bad magic, truncated, bad JSON)
//! - `4` = internal panic (a bug in the wrapper — we catch it instead
//! of unwinding into C, which would be UB)
//! - `5` = a pin was refused (SPEC §3.1); `wai_last_error` starts with the reason
//! (`prior_not_held`, `form_required`, …)
//! - `6` = `intent = replicate` is not sound for the capability decoded through
//! (SPEC §7); `wai_last_error` starts with the reason
//! - `7` = no rendition of a `WAI2` envelope can be picked (SPEC §7.1);
//! `wai_last_error` gives each entry's index and reason
//!
//! ## Memory model
//! All byte buffers crossing the boundary are heap-allocated here and
//! must be freed with `wai_buffer_free`. Errors are returned as a
//! non-zero status code; `wai_last_error` returns a null-terminated
//! UTF-8 string of the most recent error per thread.
use std::cell::RefCell;
use std::ffi::{c_char, c_int, CString};
use std::panic;
use std::slice;
pub const WAI_OK: c_int = 0;
pub const WAI_ERR_INVALID_ARG: c_int = 1;
pub const WAI_ERR_CODEC: c_int = 2;
pub const WAI_ERR_CONTAINER: c_int = 3;
pub const WAI_ERR_PANIC: c_int = 4;
/// A pin was refused (SPEC §3.1); `wai_last_error` starts with the reason.
pub const WAI_ERR_PRIOR: c_int = 5;
/// `intent = replicate` is not sound for the capability decoded through (SPEC §7).
pub const WAI_ERR_UNSOUND_REPLICATE: c_int = 6;
/// No rendition of a `WAI2` envelope can be picked (SPEC §7.1).
pub const WAI_ERR_NO_RENDITION: c_int = 7;
#[cfg(feature = "codecs")]
pub use crate::ffi_codecs::*;
thread_local! {
static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
}
pub(crate) fn set_err(e: impl ToString) {
let cs = CString::new(e.to_string())
.unwrap_or_else(|_| CString::new("invalid utf-8 error").unwrap());
LAST_ERROR.with(|c| *c.borrow_mut() = Some(cs));
}
#[repr(C)]
pub struct WaiBuffer {
pub data: *mut u8,
pub len: usize,
pub cap: usize,
}
impl WaiBuffer {
pub(crate) fn from_vec(mut v: Vec<u8>) -> Self {
let data = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();
std::mem::forget(v);
WaiBuffer { data, len, cap }
}
pub(crate) fn empty() -> Self { WaiBuffer { data: std::ptr::null_mut(), len: 0, cap: 0 } }
}
/// Free a buffer previously returned by any `wai_*_encode` /
/// `wai_*_decode` / `wai_envelope_*` call. Safe to call with a null
/// or zero-cap buffer.
///
/// # Safety
/// `buf.data` must have been produced by this crate.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wai_buffer_free(buf: WaiBuffer) {
if !buf.data.is_null() && buf.cap > 0 {
drop(unsafe { Vec::from_raw_parts(buf.data, buf.len, buf.cap) });
}
}
/// Returns a pointer to the most recent error message for this thread,
/// or null if there is none. The pointer is valid until the next FFI
/// call on this thread that sets an error.
#[unsafe(no_mangle)]
pub extern "C" fn wai_last_error() -> *const c_char {
LAST_ERROR.with(|c| match &*c.borrow() {
Some(cs) => cs.as_ptr(),
None => std::ptr::null(),
})
}
// ---- generic helpers ------------------------------------------------
pub(crate) unsafe fn input_slice<'a>(data: *const u8, len: usize) -> Option<&'a [u8]> {
match (data.is_null(), len) {
(true, 0) => Some(&[]),
(true, _) => None, // null but non-zero len
(false, _) => Some(unsafe { slice::from_raw_parts(data, len) }),
}
}
pub(crate) unsafe fn write_buf(out: *mut WaiBuffer, v: Vec<u8>) -> bool {
if out.is_null() { return false; }
unsafe { *out = WaiBuffer::from_vec(v); }
true
}
pub(crate) unsafe fn clear_buf(out: *mut WaiBuffer) {
if !out.is_null() {
unsafe { *out = WaiBuffer::empty(); }
}
}
/// Wraps an FFI body. Validates the `out` buffer pointer is non-null,
/// runs the closure inside `catch_unwind` (so a Rust panic returns
/// `WAI_ERR_PANIC` instead of unwinding into C — which would be UB),
/// and clears `out` to an empty buffer on any error path.
pub(crate) fn ffi_buf_call<F>(out: *mut WaiBuffer, f: F) -> c_int
where F: FnOnce() -> std::result::Result<Vec<u8>, (c_int, String)> + panic::UnwindSafe
{
if out.is_null() {
set_err("output buffer pointer is null");
return WAI_ERR_INVALID_ARG;
}
match panic::catch_unwind(f) {
Ok(Ok(v)) => unsafe {
if write_buf(out, v) { WAI_OK }
else { set_err("write to out failed"); WAI_ERR_INVALID_ARG }
}
Ok(Err((code, msg))) => {
set_err(msg);
unsafe { clear_buf(out); }
code
}
Err(p) => {
let msg = if let Some(s) = p.downcast_ref::<&str>() { (*s).to_string() }
else if let Some(s) = p.downcast_ref::<String>() { s.clone() }
else { "panic with non-string payload".into() };
set_err(format!("internal panic: {msg}"));
unsafe { clear_buf(out); }
WAI_ERR_PANIC
}
}
}
// ---- envelope: wrap an existing payload in a WAI container ----------
/// The C error for an envelope the emit path refused: a candidate is the
/// caller's argument (`WAI_ERR_INVALID_ARG`), a serialisation failure the
/// container's (`WAI_ERR_CONTAINER`).
fn emit_err(e: crate::container::EmitError) -> (c_int, String) {
match e {
crate::container::EmitError::Candidate(_) | crate::container::EmitError::IntPayload { .. } => (WAI_ERR_INVALID_ARG, e.to_string()),
_ => (WAI_ERR_CONTAINER, e.to_string()),
}
}
/// What decoding an integer payload will cost (`wai_int_payload_declared`,
/// `extensions/integer-payloads.md` §7, *Memory and work*): the decoded output's
/// bytes (`output_known` 1 where they are filled), the symbols its streams code, and,
/// where `cost_known` is 1, the most bytes the reference decode holds at once beyond
/// the payload and the parsed set (`working_bytes`) and the networks'
/// multiply-accumulates, `macs_hi·2^64 + macs_lo`.
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct WaiIntDeclared {
pub output_bytes: u64,
pub output_known: u8,
pub symbols: u64,
pub working_bytes: u64,
pub macs_lo: u64,
pub macs_hi: u64,
pub cost_known: u8,
}
impl WaiIntDeclared {
/// The figures `container::declared_size` reads from the header alone.
fn from_size(d: crate::container::DeclaredSize) -> Self {
let macs = d.macs.unwrap_or(0);
WaiIntDeclared {
output_bytes: d.output_bytes.unwrap_or(0),
output_known: d.output_bytes.is_some() as u8,
symbols: d.symbols,
working_bytes: d.working_bytes.unwrap_or(0),
macs_lo: macs as u64,
macs_hi: (macs >> 64) as u64,
cost_known: (d.working_bytes.is_some() && d.macs.is_some()) as u8,
}
}
}
/// The members of `capability`'s parameter set read from `set_path`: inside it where
/// it is a directory, by the names the set lists them under; the file itself for a set
/// of one file; else the file named for the member beside it. A member that cannot be
/// read is absent, and the cost then refuses with `set_incomplete`.
#[cfg(feature = "neural_int")]
fn read_set(capability: &str, set_path: &std::path::Path) -> std::collections::BTreeMap<String, Vec<u8>> {
let members = crate::int_payload::set_members(capability);
members.iter()
.filter_map(|m| {
let f = if set_path.is_dir() {
set_path.join(m)
} else if members.len() == 1 {
set_path.to_path_buf()
} else {
set_path.parent().map(|d| d.join(m)).unwrap_or_else(|| std::path::PathBuf::from(m))
};
std::fs::read(f).ok().map(|b| (m.to_string(), b))
})
.collect()
}
/// Every figure of the declared cost, over the set read from `set_path`.
#[cfg(feature = "neural_int")]
fn cost_with_set(capability: &str, payload: &[u8], set_path: &str) -> std::result::Result<WaiIntDeclared, (c_int, String)> {
use crate::int_payload::{declared_cost, resolve};
let files = read_set(capability, std::path::Path::new(set_path));
let view: std::collections::BTreeMap<String, &[u8]> = files.iter().map(|(k, v)| (k.clone(), v.as_slice())).collect();
let params = resolve(capability, None, &[crate::pin::HeldBytes::Set { listing: b"", files: &view }], None)
.map_err(|r| (WAI_ERR_INVALID_ARG, format!("{}: {r}", r.code())))?;
let c = declared_cost(capability, payload, &params).map_err(|e| (WAI_ERR_INVALID_ARG, e.to_string()))?;
Ok(WaiIntDeclared {
output_bytes: c.output_bytes, output_known: 1, symbols: c.symbols, working_bytes: c.working_bytes,
macs_lo: c.macs as u64, macs_hi: (c.macs >> 64) as u64, cost_known: 1,
})
}
#[cfg(not(feature = "neural_int"))]
fn cost_with_set(capability: &str, _: &[u8], _: &str) -> std::result::Result<WaiIntDeclared, (c_int, String)> {
Err((WAI_ERR_INVALID_ARG, format!("{capability}: this build reads no parameter set (feature neural_int)")))
}
/// Read what decoding the payload of an integer capability will cost before decoding
/// it, so a sink can refuse a payload past its own size policy first
/// (`extensions/integer-payloads.md` §7, *Memory and work*). `capability` is the
/// envelope's capability (or the `WAI2` component's). The payload is parsed as a sink
/// parses it: one that is not the format SPEC §5 pins `capability` to is
/// `WAI_ERR_INVALID_ARG`, with its code in `wai_last_error`, and so is a capability
/// with no pinned integer format.
///
/// With `set_path` null, only the header is read: a `wai.neural.int_hyper` image's
/// size is its synthesis network's, so `output_known` is 0 for it, and `cost_known` is
/// 1 only for a `WIV1` with keyframe 0, which runs no network. With `set_path` naming
/// the capability's parameter set — a directory holding its members by the names §9
/// gives, or the file of a one-file set — every figure is filled
/// (`int_payload::declared_cost`), and the set is checked against the payload as a
/// decode checks it first (`set_incomplete`, `model_mismatch`, `geometry_mismatch`,
/// …). The set is read as given, unverified: a pin is verified when the envelope is
/// decoded. A build without `neural_int` reads no set.
///
/// # Safety
/// `capability` NUL-terminated; `payload` valid for `payload_len` bytes; `set_path`
/// null or NUL-terminated UTF-8; `out` writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wai_int_payload_declared(
capability: *const c_char,
payload: *const u8, payload_len: usize,
set_path: *const c_char,
out: *mut WaiIntDeclared,
) -> c_int {
if capability.is_null() || out.is_null() {
set_err("int_payload_declared: capability/out null");
return WAI_ERR_INVALID_ARG;
}
let r = panic::catch_unwind(|| -> std::result::Result<WaiIntDeclared, (c_int, String)> {
let cap = unsafe { std::ffi::CStr::from_ptr(capability) }.to_string_lossy().to_string();
let p = unsafe { input_slice(payload, payload_len) }
.ok_or((WAI_ERR_INVALID_ARG, "payload null + len>0".into()))?;
let header = match crate::container::declared_size(&cap, p) {
Ok(Some(d)) => WaiIntDeclared::from_size(d),
Ok(None) => return Err((WAI_ERR_INVALID_ARG, format!("{cap} has no pinned integer payload format"))),
Err(e) => return Err((WAI_ERR_INVALID_ARG, e.to_string())),
};
if set_path.is_null() {
return Ok(header);
}
let set = unsafe { std::ffi::CStr::from_ptr(set_path) }.to_str()
.map_err(|_| (WAI_ERR_INVALID_ARG, "set_path is not UTF-8".to_string()))?;
cost_with_set(&cap, p, set)
});
match r {
Ok(Ok(d)) => { unsafe { *out = d; } WAI_OK }
Ok(Err((code, msg))) => { set_err(msg); code }
Err(_) => { set_err("internal panic in int_payload_declared"); WAI_ERR_PANIC }
}
}
/// Builds a complete WAI file from a codec-produced payload + manifest
/// fields. `manifest_json` is a UTF-8 JSON object overriding/supplying
/// any fields; `payload` is the bytes from one of the codec encoders.
/// Refuses (`WAI_ERR_INVALID_ARG`) a manifest that names a candidate capability
/// as its capability or fallback, which an encoder MUST NOT emit (SPEC §5,
/// *Candidate capabilities*), and a payload that is not the format SPEC §5 pins its
/// integer capability or fallback to (*Payloads of the WAI-native integer codecs*):
/// it emits through `Wai::to_emit_bytes`.
///
/// # Safety
/// `manifest_json` NUL-terminated; `payload` valid for `payload_len` bytes; `out` writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wai_envelope_pack(
manifest_json: *const c_char,
payload: *const u8, payload_len: usize,
out: *mut WaiBuffer,
) -> c_int {
ffi_buf_call(out, || {
if manifest_json.is_null() {
return Err((WAI_ERR_INVALID_ARG, "null manifest_json".into()));
}
let s = unsafe { std::ffi::CStr::from_ptr(manifest_json) }
.to_string_lossy().to_string();
let manifest: crate::container::Manifest = serde_json::from_str(&s)
.map_err(|e| (WAI_ERR_CONTAINER, format!("manifest parse: {e}")))?;
let pl = unsafe { input_slice(payload, payload_len) }
.ok_or((WAI_ERR_INVALID_ARG, "payload null + len>0".into()))?
.to_vec();
crate::container::Wai::new(manifest, pl).to_emit_bytes().map_err(emit_err)
})
}
// ---- v1.1: multi-rendition envelope FFI ------------------------
// Multi-rendition is a separate magic ("WAI2") with multiple payloads
// in one envelope. The C ABI keeps it consumer-friendly by serializing
// the rendition metadata (capability + kind + payload offset/length)
// as a JSON sidecar and packing every rendition payload contiguously
// into a single buffer — the caller indexes into the buffer using the
// JSON. No array-of-structs ownership games.
/// Detect WAI version from a byte stream. Returns 1 (WAI1), 2 (WAI2),
/// or 0 with an error message in `wai_last_error` for unknown magic.
///
/// # Safety
/// `bytes` valid for `len` bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wai_envelope_detect_version(
bytes: *const u8, len: usize,
) -> c_int {
let s = match unsafe { input_slice(bytes, len) } {
Some(s) => s,
None => { set_err("bytes null + len>0"); return 0; }
};
match crate::container::detect_version(s) {
Ok(v) => v as c_int,
Err(e) => { set_err(e); 0 }
}
}
/// Pack a WAI2 multi-rendition envelope.
///
/// - `manifest_json` must declare a `renditions` array of length `n`.
/// - `payload_data` is an array of `n` byte-buffer pointers.
/// - `payload_lens` is the matching array of `n` lengths.
/// - The i-th payload pairs with `manifest.renditions[i]`.
///
/// Refuses (`WAI_ERR_INVALID_ARG`) a table with a component, in either role,
/// whose capability is a candidate (SPEC §5, *Candidate capabilities*), or whose
/// payload is not the format SPEC §5 pins its integer capability to: it emits
/// through `WaiMulti::to_emit_bytes`.
///
/// # Safety
/// All array pointers must point to valid memory of the declared length.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wai_envelope_pack_multi(
manifest_json: *const c_char,
payload_data: *const *const u8,
payload_lens: *const usize,
n_payloads: usize,
out: *mut WaiBuffer,
) -> c_int {
ffi_buf_call(out, || {
if manifest_json.is_null() {
return Err((WAI_ERR_INVALID_ARG, "null manifest_json".into()));
}
let mj = unsafe { std::ffi::CStr::from_ptr(manifest_json) }
.to_string_lossy().to_string();
let manifest: crate::container::ManifestV2 = serde_json::from_str(&mj)
.map_err(|e| (WAI_ERR_CONTAINER, format!("manifest parse: {e}")))?;
if manifest.renditions.len() != n_payloads {
return Err((WAI_ERR_INVALID_ARG, format!(
"manifest has {} renditions but n_payloads={n_payloads}",
manifest.renditions.len())));
}
if n_payloads > 0 && (payload_data.is_null() || payload_lens.is_null()) {
return Err((WAI_ERR_INVALID_ARG,
"payload_data/payload_lens null with n_payloads>0".into()));
}
// Collect rendition payloads. Every member of the manifest — the component
// identity members, and any a later revision defines — is kept as written.
let mut payloads: Vec<Vec<u8>> = Vec::with_capacity(n_payloads);
for i in 0..n_payloads {
let ptr = unsafe { *payload_data.add(i) };
let len = unsafe { *payload_lens.add(i) };
let s = unsafe { input_slice(ptr, len) }
.ok_or((WAI_ERR_INVALID_ARG,
format!("payload {i} null with len>0")))?;
payloads.push(s.to_vec());
}
crate::container::WaiMulti::from_parts(manifest, payloads)
.map_err(|e| (WAI_ERR_INVALID_ARG, e.to_string()))?
.to_emit_bytes().map_err(emit_err)
})
}
/// Parse a WAI2 envelope. Writes three outputs:
///
/// - `out_manifest` — the manifest JSON exactly as the envelope carries it
/// (UTF-8 bytes, no NUL), so members this library does not model survive
/// - `out_renditions_json` — a JSON array with one object per component:
/// `capability`, `kind`, `offset` (into `out_payload_block`), `length`, and
/// `selectable` (whether SPEC §7.1 may pick it: a companion, or a companion-only
/// capability, never is), plus `id`, `sha256`, `prior`, `role`, `companion_of`
/// and `companion_of_id` when the entry carries them. A C sink selects with
/// `wai_envelope_select` rather than by hand.
/// - `out_payload_block` — every rendition's payload bytes, packed
/// contiguously. The caller indexes via the JSON above.
///
/// This avoids exposing a C struct array (and the ownership pain that
/// comes with it). Any JSON parser the consumer already has decodes
/// the rendition table in two lines.
///
/// # Safety
/// `bytes` valid for `len` bytes; `out_manifest` writable; `out_renditions_json` writable; `out_payload_block` writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wai_envelope_unpack_multi(
bytes: *const u8, len: usize,
out_manifest: *mut WaiBuffer,
out_renditions_json: *mut WaiBuffer,
out_payload_block: *mut WaiBuffer,
) -> c_int {
if out_manifest.is_null() || out_renditions_json.is_null()
|| out_payload_block.is_null()
{
set_err("unpack_multi: one of the out pointers is null");
return WAI_ERR_INVALID_ARG;
}
match panic::catch_unwind(|| -> std::result::Result<(Vec<u8>, Vec<u8>, Vec<u8>), (c_int, String)> {
let s = unsafe { input_slice(bytes, len) }
.ok_or((WAI_ERR_INVALID_ARG, "bytes null + len>0".into()))?;
let m = crate::container::WaiMulti::from_bytes(s)
.map_err(|e| (WAI_ERR_CONTAINER, format!("envelope parse: {e}")))?;
let mb = crate::container::manifest_bytes(s)
.map_err(|e| (WAI_ERR_CONTAINER, format!("envelope parse: {e}")))?
.to_vec();
// build rendition table JSON + concatenated payload block
let mut table: Vec<serde_json::Value> = Vec::with_capacity(m.renditions.len());
let mut block: Vec<u8> = Vec::new();
for (meta, payload) in m.manifest.renditions.iter().zip(m.renditions.iter()) {
let mut e = serde_json::json!({
"capability": meta.capability,
"kind": meta.kind,
"offset": block.len(),
"length": payload.len(),
"selectable": meta.is_selectable(),
});
let o = e.as_object_mut().unwrap();
let mut put = |k: &str, v: Option<serde_json::Value>| if let Some(v) = v { o.insert(k.into(), v); };
put("id", meta.id.clone().map(Into::into));
put("sha256", meta.sha256.clone().map(Into::into));
put("prior", meta.prior.as_ref().and_then(|p| serde_json::to_value(p).ok()));
put("role", meta.role.clone().map(Into::into));
put("companion_of", meta.companion_of.clone().map(Into::into));
put("companion_of_id", meta.companion_of_id.clone().map(Into::into));
table.push(e);
block.extend_from_slice(payload);
}
let tj = serde_json::to_vec(&serde_json::Value::Array(table))
.map_err(|e| (WAI_ERR_CONTAINER, format!("table serialize: {e}")))?;
Ok((mb, tj, block))
}) {
Ok(Ok((mb, tj, block))) => unsafe {
*out_manifest = WaiBuffer::from_vec(mb);
*out_renditions_json = WaiBuffer::from_vec(tj);
*out_payload_block = WaiBuffer::from_vec(block);
WAI_OK
}
Ok(Err((code, msg))) => {
set_err(msg);
unsafe {
clear_buf(out_manifest);
clear_buf(out_renditions_json);
clear_buf(out_payload_block);
}
code
}
Err(_) => {
set_err("internal panic in envelope_unpack_multi");
unsafe {
clear_buf(out_manifest);
clear_buf(out_renditions_json);
clear_buf(out_payload_block);
}
WAI_ERR_PANIC
}
}
}
/// Parses a WAI envelope. Writes the manifest JSON exactly as the envelope
/// carries it to `out_manifest` (so `security`, and members this library does not
/// model, survive) and the raw payload to `out_payload`.
///
/// # Safety
/// `bytes` valid for `len` bytes; `out_manifest` writable; `out_payload` writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wai_envelope_unpack(
bytes: *const u8, len: usize,
out_manifest: *mut WaiBuffer, out_payload: *mut WaiBuffer,
) -> c_int {
if out_manifest.is_null() || out_payload.is_null() {
set_err("envelope_unpack: out_manifest/out_payload null");
return WAI_ERR_INVALID_ARG;
}
match panic::catch_unwind(|| -> std::result::Result<(Vec<u8>, Vec<u8>), (c_int, String)> {
let s = unsafe { input_slice(bytes, len) }
.ok_or((WAI_ERR_INVALID_ARG, "bytes null + len>0".into()))?;
let wai = crate::container::Wai::from_bytes(s)
.map_err(|e| (WAI_ERR_CONTAINER, format!("envelope parse: {e}")))?;
let mj = crate::container::manifest_bytes(s)
.map_err(|e| (WAI_ERR_CONTAINER, format!("envelope parse: {e}")))?
.to_vec();
Ok((mj, wai.payload))
}) {
Ok(Ok((mj, pl))) => unsafe {
*out_manifest = WaiBuffer::from_vec(mj);
*out_payload = WaiBuffer::from_vec(pl);
WAI_OK
}
Ok(Err((code, msg))) => {
set_err(msg);
unsafe { clear_buf(out_manifest); clear_buf(out_payload); }
code
}
Err(_) => {
set_err("internal panic in envelope_unpack");
unsafe { clear_buf(out_manifest); clear_buf(out_payload); }
WAI_ERR_PANIC
}
}
}
/// SPEC §7.1 selection for a `WAI2` envelope at a sink that advertises the
/// capabilities in `advertised_json` (a NUL-terminated JSON array of capability
/// strings). Writes the index of the picked rendition to `out_index`: the first
/// entry, in SPEC §4's energy order (table order, but an entry whose capability is
/// learned and carries no energy budget after every other), that is selectable (never a companion, nor a capability that is not
/// dispatchable under SPEC §4: companion-only, a candidate, a record or a
/// derivation), advertised, and whose payload matches its `sha256`. This call holds no
/// parameter set, so an entry whose pin applies is skipped (`prior_not_held`); a
/// sink that holds parameter sets decodes through `wai_neural_decode_ex`, which
/// verifies each pin. Returns `WAI_ERR_NO_RENDITION` when nothing is picked, with
/// each entry's index and reason in `wai_last_error`.
///
/// # Safety
/// `envelope` valid for `envelope_len` bytes; `advertised_json` NUL-terminated;
/// `out_index` writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wai_envelope_select(
envelope: *const u8, envelope_len: usize,
advertised_json: *const c_char,
out_index: *mut u32,
) -> c_int {
if out_index.is_null() || advertised_json.is_null() {
set_err("envelope_select: out_index/advertised_json null");
return WAI_ERR_INVALID_ARG;
}
let r = panic::catch_unwind(|| -> std::result::Result<u32, (c_int, String)> {
let s = unsafe { input_slice(envelope, envelope_len) }
.ok_or((WAI_ERR_INVALID_ARG, "envelope null + len>0".into()))?;
let adv = unsafe { std::ffi::CStr::from_ptr(advertised_json) }.to_string_lossy().to_string();
let advertised: Vec<String> = serde_json::from_str(&adv)
.map_err(|e| (WAI_ERR_INVALID_ARG, format!("advertised_json: {e}")))?;
let m = crate::container::WaiMulti::from_bytes(s)
.map_err(|e| (WAI_ERR_CONTAINER, format!("envelope parse: {e}")))?;
match m.pick(|c| advertised.iter().any(|a| a == c), crate::container::no_parameter_sets()) {
Ok(p) => Ok(p.index as u32),
Err(skips) => Err((WAI_ERR_NO_RENDITION, skips.iter()
.map(|(i, k)| format!("{i}: {k}")).collect::<Vec<_>>().join("; "))),
}
});
match r {
Ok(Ok(i)) => { unsafe { *out_index = i; } WAI_OK }
Ok(Err((code, msg))) => { set_err(msg); code }
Err(_) => { set_err("internal panic in envelope_select"); WAI_ERR_PANIC }
}
}