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//! This does not implement `Wire` for arbitrary types or infer a finite schema.
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;
12
13use crate::{Error, Limits, contract};
14use alloc::vec::Vec;
15use core::cell::Cell;
16use serde::{Deserialize, Serialize};
17
18struct Budget {
19    nodes: Cell<usize>,
20    heap_units: Cell<usize>,
21    depth: usize,
22    refused: Cell<bool>,
23}
24impl Budget {
25    fn new(limits: Limits) -> Self {
26        Self {
27            nodes: Cell::new(limits.nodes),
28            // All value charges are multiples of eight bytes. Store whole charge
29            // units instead of bytes: the unused remainder cannot fund a charge.
30            // This removes per-visit multiplication without changing admission,
31            // overflow rejection, wire bytes or consumer limits.
32            heap_units: Cell::new(limits.allocation.saturating_sub(limits.bytes) / 8),
33            depth: limits.depth,
34            refused: Cell::new(limits.allocation < limits.bytes),
35        }
36    }
37    #[cfg_attr(target_arch = "wasm32", inline(always))]
38    fn enter(&self, depth: usize, size: usize) -> bool {
39        self.enter_many(depth, size, 1)
40    }
41    #[cfg_attr(target_arch = "wasm32", inline(always))]
42    fn enter_many(&self, depth: usize, size: usize, count: usize) -> bool {
43        if self.refused.get() {
44            return false;
45        }
46        let Some(nodes) = self.nodes.get().checked_sub(count) else {
47            self.refused.set(true);
48            return false;
49        };
50        if depth > self.depth || !self.bytes(size) {
51            self.refused.set(true);
52            return false;
53        }
54        self.nodes.set(nodes);
55        true
56    }
57    #[cfg_attr(target_arch = "wasm32", inline(always))]
58    fn bytes(&self, bytes: usize) -> bool {
59        let Some(heap) = self.heap_units.get().checked_sub(bytes) else {
60            self.refused.set(true);
61            return false;
62        };
63        self.heap_units.set(heap);
64        true
65    }
66    fn error(&self) -> Error {
67        if self.refused.get() {
68            Error::Limit
69        } else {
70            Error::Malformed
71        }
72    }
73}
74
75/// Measure one native payload with no output allocation; `bytes` excludes a frame.
76pub fn encoded_len<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<usize, Error> {
77    let budget = Budget::new(limits);
78    let size = postcard::experimental::serialized_size(&encode::Bounded {
79        value,
80        budget: &budget,
81        depth: 1,
82    })
83    .map_err(|_| budget.error())?;
84    if size > limits.bytes {
85        return Err(Error::Limit);
86    }
87    Ok(size)
88}
89
90/// Encode an owned or borrowed native payload under explicit traversal allowances.
91pub fn encode<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<Vec<u8>, Error> {
92    encode_prefixed(value, limits, &[])
93}
94fn encode_prefixed<T: Serialize + ?Sized>(
95    value: &T,
96    limits: Limits,
97    prefix: &[u8],
98) -> Result<Vec<u8>, Error> {
99    let mut bytes = Vec::new();
100    encode_prefixed_into(value, limits, prefix, &mut bytes)?;
101    Ok(bytes)
102}
103
104/// Encode into reusable caller storage. Previous contents are cleared, including on error.
105/// Capacity may be lost on serialization failure; successful calls retain it.
106/// Retain a separate immutable frame when bytes must survive subsequent calls for retries.
107pub fn encode_into<T: Serialize + ?Sized>(
108    value: &T,
109    limits: Limits,
110    bytes: &mut Vec<u8>,
111) -> Result<(), Error> {
112    encode_prefixed_into(value, limits, &[], bytes)
113}
114fn encode_prefixed_into<T: Serialize + ?Sized>(
115    value: &T,
116    limits: Limits,
117    prefix: &[u8],
118    bytes: &mut Vec<u8>,
119) -> Result<(), Error> {
120    bytes.clear();
121    let size = encoded_len(value, payload_limits(limits, prefix.len())?)?;
122    bytes
123        .try_reserve_exact(size + prefix.len())
124        .map_err(|_| Error::Allocation)?;
125    bytes.extend_from_slice(prefix);
126    let budget = Budget::new(limits);
127    *bytes = postcard::serialize_with_flavor(
128        &encode::Bounded {
129            value,
130            budget: &budget,
131            depth: 1,
132        },
133        contract::Output {
134            bytes: core::mem::take(bytes),
135            limit: size + prefix.len(),
136        },
137    )
138    .map_err(|_| budget.error())?;
139    Ok(())
140}
141
142/// Encode a request into reusable storage; follows [`encode_into`] failure semantics.
143pub fn encode_request_into<T: Serialize + ?Sized>(
144    id: u64,
145    value: &T,
146    limits: Limits,
147    bytes: &mut Vec<u8>,
148) -> Result<(), Error> {
149    encode_prefixed_into(value, limits, &contract::header(id, 0), bytes)
150}
151
152/// Encode a response into reusable storage; follows [`encode_into`] failure semantics.
153pub fn encode_response_into<T: Serialize + ?Sized>(
154    id: u64,
155    value: &T,
156    limits: Limits,
157    bytes: &mut Vec<u8>,
158) -> Result<(), Error> {
159    encode_prefixed_into(value, limits, &contract::header(id, 1), bytes)
160}
161
162/// Decode a complete canonical payload; allocation admission precedes visitor calls.
163/// Borrowed bytes and strings may refer directly to the input for its lifetime.
164/// Canonical verification and conservative resource charges also apply to borrowed values.
165pub fn decode<'de, T: Serialize + Deserialize<'de>>(
166    bytes: &'de [u8],
167    limits: Limits,
168) -> Result<T, Error> {
169    if bytes.len() > limits.bytes {
170        return Err(Error::Limit);
171    }
172    let budget = Budget::new(limits);
173    let mut input = postcard::Deserializer::from_bytes(bytes);
174    let value = T::deserialize(decode::Bounded {
175        inner: &mut input,
176        budget: &budget,
177        depth: 1,
178    })
179    .map_err(|_| budget.error())?;
180    if !input.finalize().map_err(|_| Error::Malformed)?.is_empty() {
181        return Err(Error::Trailing);
182    }
183    let budget = Budget::new(limits);
184    let equal = postcard::serialize_with_flavor(
185        &encode::Bounded {
186            value: &value,
187            budget: &budget,
188            depth: 1,
189        },
190        contract::Compare {
191            remaining: bytes,
192            allowance: limits.bytes,
193            equal: true,
194        },
195    )
196    .map_err(|_| budget.error())?;
197    if !equal {
198        return Err(Error::Noncanonical);
199    }
200    Ok(value)
201}
202
203/// Encode a complete request; `limits.bytes` includes the canonical frame header.
204pub fn encode_request<T: Serialize + ?Sized>(
205    id: u64,
206    value: &T,
207    limits: Limits,
208) -> Result<Vec<u8>, Error> {
209    encode_prefixed(value, limits, &contract::header(id, 0))
210}
211/// Encode a complete response, including borrowed response projections.
212pub fn encode_response<T: Serialize + ?Sized>(
213    id: u64,
214    value: &T,
215    limits: Limits,
216) -> Result<Vec<u8>, Error> {
217    encode_prefixed(value, limits, &contract::header(id, 1))
218}
219/// Measure a request or response frame; both directions have equal header length.
220pub fn encoded_frame_len<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<usize, Error> {
221    encoded_len(value, payload_limits(limits, contract::HEADER)?)
222        .map(|size| size + contract::HEADER)
223}
224/// Check request identity and direction before decoding its bounded native value.
225pub fn decode_request<'de, T: Serialize + Deserialize<'de>>(
226    id: u64,
227    bytes: &'de [u8],
228    limits: Limits,
229) -> Result<T, Error> {
230    decode_frame(id, 0, bytes, limits)
231}
232/// Check response identity and direction before decoding its bounded native value.
233pub fn decode_response<'de, T: Serialize + Deserialize<'de>>(
234    id: u64,
235    bytes: &'de [u8],
236    limits: Limits,
237) -> Result<T, Error> {
238    decode_frame(id, 1, bytes, limits)
239}
240fn decode_frame<'de, T: Serialize + Deserialize<'de>>(
241    id: u64,
242    direction: u8,
243    bytes: &'de [u8],
244    limits: Limits,
245) -> Result<T, Error> {
246    if bytes.len() > limits.bytes {
247        return Err(Error::Limit);
248    }
249    let payload = contract::payload(id, direction, bytes)?;
250    decode(payload, payload_limits(limits, contract::HEADER)?)
251}
252
253fn payload_limits(limits: Limits, prefix: usize) -> Result<Limits, Error> {
254    Ok(Limits {
255        bytes: limits.bytes.checked_sub(prefix).ok_or(Error::Limit)?,
256        allocation: limits.allocation.checked_sub(prefix).ok_or(Error::Limit)?,
257        ..limits
258    })
259}