Skip to main content

ic_wire/native/
mod.rs

1//! Explicit resource admission for native Serde payloads in the same Postcard format.
2//!
3//! Types, IDs, deterministic serialization and domain rules are consumer-owned.
4//! Resource admission follows Serde visits; no static schema is inferred.
5//! Custom Serde implementations are trusted: allocations/work outside visited
6//! values cannot be constrained here. See the runtime guide for budget semantics.
7#[cfg(test)]
8mod tests;
9
10mod decode;
11mod encode;
12mod parser;
13
14use crate::{Error, Limits, contract};
15use alloc::vec::Vec;
16use core::cell::Cell;
17use serde::{Deserialize, Serialize};
18
19struct Budget {
20    nodes: Cell<usize>,
21    heap_units: Cell<usize>,
22    depth: usize,
23    refused: Cell<bool>,
24}
25impl Budget {
26    fn new(limits: Limits) -> Self {
27        Self {
28            nodes: Cell::new(limits.nodes),
29            // All value charges are multiples of eight bytes. Store whole charge
30            // units instead of bytes: the unused remainder cannot fund a charge.
31            // This removes per-visit multiplication without changing admission,
32            // overflow rejection, wire bytes or consumer limits.
33            heap_units: Cell::new(limits.allocation.saturating_sub(limits.bytes) / 8),
34            depth: limits.depth,
35            refused: Cell::new(limits.allocation < limits.bytes),
36        }
37    }
38    #[cfg_attr(target_arch = "wasm32", inline(always))]
39    fn enter(&self, depth: usize, size: usize) -> bool {
40        self.enter_many(depth, size, 1)
41    }
42    #[cfg_attr(target_arch = "wasm32", inline(always))]
43    fn enter_many(&self, depth: usize, size: usize, count: usize) -> bool {
44        if self.refused.get() {
45            return false;
46        }
47        let Some(nodes) = self.nodes.get().checked_sub(count) else {
48            self.refused.set(true);
49            return false;
50        };
51        if depth > self.depth || !self.bytes(size) {
52            self.refused.set(true);
53            return false;
54        }
55        self.nodes.set(nodes);
56        true
57    }
58    #[cfg_attr(target_arch = "wasm32", inline(always))]
59    fn bytes(&self, bytes: usize) -> bool {
60        let Some(heap) = self.heap_units.get().checked_sub(bytes) else {
61            self.refused.set(true);
62            return false;
63        };
64        self.heap_units.set(heap);
65        true
66    }
67    fn error(&self) -> Error {
68        if self.refused.get() {
69            Error::Limit
70        } else {
71            Error::Malformed
72        }
73    }
74}
75
76/// Measure one native payload with no output allocation; `bytes` excludes a frame.
77pub fn encoded_len<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<usize, Error> {
78    let budget = Budget::new(limits);
79    let size = postcard::experimental::serialized_size(&encode::Bounded {
80        value,
81        budget: &budget,
82        depth: 1,
83    })
84    .map_err(|_| budget.error())?;
85    if size > limits.bytes {
86        return Err(Error::Limit);
87    }
88    Ok(size)
89}
90
91/// Encode an owned or borrowed native payload under explicit traversal allowances.
92pub fn encode<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<Vec<u8>, Error> {
93    encode_prefixed(value, limits, &[])
94}
95fn encode_prefixed<T: Serialize + ?Sized>(
96    value: &T,
97    limits: Limits,
98    prefix: &[u8],
99) -> Result<Vec<u8>, Error> {
100    let mut bytes = Vec::new();
101    encode_prefixed_into(value, limits, prefix, &mut bytes)?;
102    Ok(bytes)
103}
104
105/// Encode into reusable caller storage. Previous contents are cleared, including on error.
106/// Capacity may be lost on serialization failure; successful calls retain it.
107/// Retain a separate immutable frame when bytes must survive subsequent calls for retries.
108pub fn encode_into<T: Serialize + ?Sized>(
109    value: &T,
110    limits: Limits,
111    bytes: &mut Vec<u8>,
112) -> Result<(), Error> {
113    encode_prefixed_into(value, limits, &[], bytes)
114}
115
116/// Encode a payload into caller-owned storage without allocating codec output.
117///
118/// Returns only the completed encoding. A short buffer returns [`Error::Limit`]
119/// before writing; on any other error storage contents are unspecified and must
120/// not be published. The returned slice borrows storage, not the value. Admission,
121/// two-pass sizing and trusted custom Serde rules match [`encode_into`]; storage
122/// capacity does not replace or reduce the configured allocation allowance.
123pub fn encode_to_slice<'a, T: Serialize + ?Sized>(
124    value: &T,
125    limits: Limits,
126    storage: &'a mut [u8],
127) -> Result<&'a mut [u8], Error> {
128    encode_prefixed_to_slice(value, limits, &[], storage)
129}
130
131/// Encode a complete request into caller storage; follows [`encode_to_slice`].
132pub fn encode_request_to_slice<'a, T: Serialize + ?Sized>(
133    id: u64,
134    value: &T,
135    limits: Limits,
136    storage: &'a mut [u8],
137) -> Result<&'a mut [u8], Error> {
138    encode_prefixed_to_slice(value, limits, &contract::header(id, 0), storage)
139}
140
141/// Encode a complete response into caller storage; follows [`encode_to_slice`].
142pub fn encode_response_to_slice<'a, T: Serialize + ?Sized>(
143    id: u64,
144    value: &T,
145    limits: Limits,
146    storage: &'a mut [u8],
147) -> Result<&'a mut [u8], Error> {
148    encode_prefixed_to_slice(value, limits, &contract::header(id, 1), storage)
149}
150
151fn encode_prefixed_to_slice<'a, T: Serialize + ?Sized>(
152    value: &T,
153    limits: Limits,
154    prefix: &[u8],
155    storage: &'a mut [u8],
156) -> Result<&'a mut [u8], Error> {
157    let size = encoded_len(value, payload_limits(limits, prefix.len())?)?;
158    let output = storage.get_mut(..size + prefix.len()).ok_or(Error::Limit)?;
159    output[..prefix.len()].copy_from_slice(prefix);
160    let budget = Budget::new(limits);
161    // New storage API, same Postcard bytes and admission as Vec output. The
162    // slice flavor bounds every write, including inconsistent custom Serialize
163    // passes. Only Ok publishes a completed prefix; no inline/spill policy or
164    // framing change is introduced, and existing consumers need no migration.
165    let payload = postcard::to_slice(
166        &encode::Bounded {
167            value,
168            budget: &budget,
169            depth: 1,
170        },
171        &mut output[prefix.len()..],
172    )
173    .map_err(|_| budget.error())?;
174    let written = prefix.len() + payload.len();
175    Ok(&mut output[..written])
176}
177
178fn encode_prefixed_into<T: Serialize + ?Sized>(
179    value: &T,
180    limits: Limits,
181    prefix: &[u8],
182    bytes: &mut Vec<u8>,
183) -> Result<(), Error> {
184    bytes.clear();
185    let size = encoded_len(value, payload_limits(limits, prefix.len())?)?;
186    bytes
187        .try_reserve_exact(size + prefix.len())
188        .map_err(|_| Error::Allocation)?;
189    bytes.extend_from_slice(prefix);
190    let budget = Budget::new(limits);
191    *bytes = postcard::serialize_with_flavor(
192        &encode::Bounded {
193            value,
194            budget: &budget,
195            depth: 1,
196        },
197        contract::Output {
198            bytes: core::mem::take(bytes),
199            limit: size + prefix.len(),
200        },
201    )
202    .map_err(|_| budget.error())?;
203    Ok(())
204}
205
206/// Encode a request into reusable storage; follows [`encode_into`] failure semantics.
207pub fn encode_request_into<T: Serialize + ?Sized>(
208    id: u64,
209    value: &T,
210    limits: Limits,
211    bytes: &mut Vec<u8>,
212) -> Result<(), Error> {
213    encode_prefixed_into(value, limits, &contract::header(id, 0), bytes)
214}
215
216/// Encode a response into reusable storage; follows [`encode_into`] failure semantics.
217pub fn encode_response_into<T: Serialize + ?Sized>(
218    id: u64,
219    value: &T,
220    limits: Limits,
221    bytes: &mut Vec<u8>,
222) -> Result<(), Error> {
223    encode_prefixed_into(value, limits, &contract::header(id, 1), bytes)
224}
225
226/// Decode a complete Postcard payload; allocation admission precedes visitor calls.
227/// Borrowed bytes and strings may refer directly to the input for its lifetime.
228/// Primitive representations are checked while reading; Serde normalization is consumer-owned.
229pub fn decode<'de, T: Deserialize<'de>>(bytes: &'de [u8], limits: Limits) -> Result<T, Error> {
230    if bytes.len() > limits.bytes {
231        return Err(Error::Limit);
232    }
233    let budget = Budget::new(limits);
234    let mut input = parser::Deserializer::from_bytes(bytes);
235    let value = T::deserialize(decode::Bounded {
236        inner: &mut input,
237        budget: &budget,
238        depth: 1,
239    })
240    .map_err(|_| budget.error())?;
241    if !input.finalize().map_err(|_| Error::Malformed)?.is_empty() {
242        return Err(Error::Trailing);
243    }
244    // Hard cut: decoding no longer reserializes or charges a serialization pass.
245    // Primitive parsing stays strict, but maps/sets and custom Serde may normalize:
246    // e.g. map bytes 02 02 14 01 0a now decode and re-encode as 02 01 0a 02 14.
247    // Consumers own normalization and must retain original bytes for exact retries.
248    Ok(value)
249}
250
251/// Encode a complete request; `limits.bytes` includes the canonical frame header.
252pub fn encode_request<T: Serialize + ?Sized>(
253    id: u64,
254    value: &T,
255    limits: Limits,
256) -> Result<Vec<u8>, Error> {
257    encode_prefixed(value, limits, &contract::header(id, 0))
258}
259/// Encode a complete response, including borrowed response projections.
260pub fn encode_response<T: Serialize + ?Sized>(
261    id: u64,
262    value: &T,
263    limits: Limits,
264) -> Result<Vec<u8>, Error> {
265    encode_prefixed(value, limits, &contract::header(id, 1))
266}
267/// Measure a request or response frame; both directions have equal header length.
268pub fn encoded_frame_len<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<usize, Error> {
269    encoded_len(value, payload_limits(limits, contract::HEADER)?)
270        .map(|size| size + contract::HEADER)
271}
272/// Check request identity and direction before decoding its bounded native value.
273pub fn decode_request<'de, T: Deserialize<'de>>(
274    id: u64,
275    bytes: &'de [u8],
276    limits: Limits,
277) -> Result<T, Error> {
278    decode_frame(id, 0, bytes, limits)
279}
280/// Check response identity and direction before decoding its bounded native value.
281pub fn decode_response<'de, T: Deserialize<'de>>(
282    id: u64,
283    bytes: &'de [u8],
284    limits: Limits,
285) -> Result<T, Error> {
286    decode_frame(id, 1, bytes, limits)
287}
288fn decode_frame<'de, T: Deserialize<'de>>(
289    id: u64,
290    direction: u8,
291    bytes: &'de [u8],
292    limits: Limits,
293) -> Result<T, Error> {
294    if bytes.len() > limits.bytes {
295        return Err(Error::Limit);
296    }
297    let payload = contract::payload(id, direction, bytes)?;
298    decode(payload, payload_limits(limits, contract::HEADER)?)
299}
300
301fn payload_limits(limits: Limits, prefix: usize) -> Result<Limits, Error> {
302    Ok(Limits {
303        bytes: limits.bytes.checked_sub(prefix).ok_or(Error::Limit)?,
304        allocation: limits.allocation.checked_sub(prefix).ok_or(Error::Limit)?,
305        ..limits
306    })
307}