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