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::{Serialize, de::DeserializeOwned};
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 size = encoded_len(value, payload_limits(limits, prefix.len())?)?;
97    let mut bytes = Vec::new();
98    bytes
99        .try_reserve_exact(size + prefix.len())
100        .map_err(|_| Error::Allocation)?;
101    bytes.extend_from_slice(prefix);
102    let budget = Budget::new(limits);
103    postcard::serialize_with_flavor(
104        &encode::Bounded {
105            value,
106            budget: &budget,
107            depth: 1,
108        },
109        contract::Output {
110            bytes,
111            limit: size + prefix.len(),
112        },
113    )
114    .map_err(|_| budget.error())
115}
116
117/// Decode a complete canonical payload; allocation admission precedes visitor calls.
118pub fn decode<T: Serialize + DeserializeOwned>(bytes: &[u8], limits: Limits) -> Result<T, Error> {
119    if bytes.len() > limits.bytes {
120        return Err(Error::Limit);
121    }
122    let budget = Budget::new(limits);
123    let mut input = postcard::Deserializer::from_bytes(bytes);
124    let value = T::deserialize(decode::Bounded {
125        inner: &mut input,
126        budget: &budget,
127        depth: 1,
128    })
129    .map_err(|_| budget.error())?;
130    if !input.finalize().map_err(|_| Error::Malformed)?.is_empty() {
131        return Err(Error::Trailing);
132    }
133    let budget = Budget::new(limits);
134    let equal = postcard::serialize_with_flavor(
135        &encode::Bounded {
136            value: &value,
137            budget: &budget,
138            depth: 1,
139        },
140        contract::Compare {
141            remaining: bytes,
142            allowance: limits.bytes,
143            equal: true,
144        },
145    )
146    .map_err(|_| budget.error())?;
147    if !equal {
148        return Err(Error::Noncanonical);
149    }
150    Ok(value)
151}
152
153/// Encode a complete request; `limits.bytes` includes the canonical frame header.
154pub fn encode_request<T: Serialize + ?Sized>(
155    id: u64,
156    value: &T,
157    limits: Limits,
158) -> Result<Vec<u8>, Error> {
159    encode_prefixed(value, limits, &contract::header(id, 0))
160}
161/// Encode a complete response, including borrowed response projections.
162pub fn encode_response<T: Serialize + ?Sized>(
163    id: u64,
164    value: &T,
165    limits: Limits,
166) -> Result<Vec<u8>, Error> {
167    encode_prefixed(value, limits, &contract::header(id, 1))
168}
169/// Measure a request or response frame; both directions have equal header length.
170pub fn encoded_frame_len<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<usize, Error> {
171    encoded_len(value, payload_limits(limits, contract::HEADER)?)
172        .map(|size| size + contract::HEADER)
173}
174/// Check request identity and direction before decoding its bounded native value.
175pub fn decode_request<T: Serialize + DeserializeOwned>(
176    id: u64,
177    bytes: &[u8],
178    limits: Limits,
179) -> Result<T, Error> {
180    decode_frame(id, 0, bytes, limits)
181}
182/// Check response identity and direction before decoding its bounded native value.
183pub fn decode_response<T: Serialize + DeserializeOwned>(
184    id: u64,
185    bytes: &[u8],
186    limits: Limits,
187) -> Result<T, Error> {
188    decode_frame(id, 1, bytes, limits)
189}
190fn decode_frame<T: Serialize + DeserializeOwned>(
191    id: u64,
192    direction: u8,
193    bytes: &[u8],
194    limits: Limits,
195) -> Result<T, Error> {
196    if bytes.len() > limits.bytes {
197        return Err(Error::Limit);
198    }
199    let payload = contract::payload(id, direction, bytes)?;
200    decode(payload, payload_limits(limits, contract::HEADER)?)
201}
202
203fn payload_limits(limits: Limits, prefix: usize) -> Result<Limits, Error> {
204    Ok(Limits {
205        bytes: limits.bytes.checked_sub(prefix).ok_or(Error::Limit)?,
206        allocation: limits.allocation.checked_sub(prefix).ok_or(Error::Limit)?,
207        ..limits
208    })
209}