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    fn enter(&self, depth: usize, size: usize) -> bool {
31        if self.refused.get() {
32            return false;
33        }
34        let Some(nodes) = self.nodes.get().checked_sub(1) else {
35            self.refused.set(true);
36            return false;
37        };
38        if depth > self.depth || !self.bytes(size) {
39            self.refused.set(true);
40            return false;
41        }
42        self.nodes.set(nodes);
43        true
44    }
45    fn bytes(&self, bytes: usize) -> bool {
46        let Some(cost) = bytes.checked_mul(8) else {
47            self.refused.set(true);
48            return false;
49        };
50        let Some(heap) = self.heap.get().checked_sub(cost) else {
51            self.refused.set(true);
52            return false;
53        };
54        self.heap.set(heap);
55        true
56    }
57    fn error(&self) -> Error {
58        if self.refused.get() {
59            Error::Limit
60        } else {
61            Error::Malformed
62        }
63    }
64}
65
66/// Measure one native payload with no output allocation; `bytes` excludes a frame.
67pub fn encoded_len<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<usize, Error> {
68    let budget = Budget::new(limits);
69    let size = postcard::experimental::serialized_size(&encode::Bounded {
70        value,
71        budget: &budget,
72        depth: 1,
73    })
74    .map_err(|_| budget.error())?;
75    if size > limits.bytes {
76        return Err(Error::Limit);
77    }
78    Ok(size)
79}
80
81/// Encode an owned or borrowed native payload under explicit traversal allowances.
82pub fn encode<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<Vec<u8>, Error> {
83    encode_prefixed(value, limits, &[])
84}
85fn encode_prefixed<T: Serialize + ?Sized>(
86    value: &T,
87    limits: Limits,
88    prefix: &[u8],
89) -> Result<Vec<u8>, Error> {
90    let payload_limit = limits.bytes.checked_sub(prefix.len()).ok_or(Error::Limit)?;
91    let size = encoded_len(
92        value,
93        Limits {
94            bytes: payload_limit,
95            allocation: limits
96                .allocation
97                .checked_sub(prefix.len())
98                .ok_or(Error::Limit)?,
99            ..limits
100        },
101    )?;
102    let mut bytes = Vec::new();
103    bytes
104        .try_reserve_exact(size + prefix.len())
105        .map_err(|_| Error::Allocation)?;
106    bytes.extend_from_slice(prefix);
107    let budget = Budget::new(limits);
108    postcard::serialize_with_flavor(
109        &encode::Bounded {
110            value,
111            budget: &budget,
112            depth: 1,
113        },
114        contract::Output {
115            bytes,
116            limit: size + prefix.len(),
117        },
118    )
119    .map_err(|_| budget.error())
120}
121
122/// Decode a complete canonical payload; allocation admission precedes visitor calls.
123pub fn decode<T: Serialize + DeserializeOwned>(bytes: &[u8], limits: Limits) -> Result<T, Error> {
124    if bytes.len() > limits.bytes {
125        return Err(Error::Limit);
126    }
127    let budget = Budget::new(limits);
128    let mut input = postcard::Deserializer::from_bytes(bytes);
129    let value = T::deserialize(decode::Bounded {
130        inner: &mut input,
131        budget: &budget,
132        depth: 1,
133    })
134    .map_err(|_| budget.error())?;
135    if !input.finalize().map_err(|_| Error::Malformed)?.is_empty() {
136        return Err(Error::Trailing);
137    }
138    let budget = Budget::new(limits);
139    let equal = postcard::serialize_with_flavor(
140        &encode::Bounded {
141            value: &value,
142            budget: &budget,
143            depth: 1,
144        },
145        contract::Compare {
146            remaining: bytes,
147            allowance: limits.bytes,
148            equal: true,
149        },
150    )
151    .map_err(|_| budget.error())?;
152    if !equal {
153        return Err(Error::Noncanonical);
154    }
155    Ok(value)
156}
157
158/// Encode a complete request; `limits.bytes` includes the canonical frame header.
159pub fn encode_request<T: Serialize + ?Sized>(
160    id: u64,
161    value: &T,
162    limits: Limits,
163) -> Result<Vec<u8>, Error> {
164    encode_prefixed(value, limits, &contract::header(id, 0))
165}
166/// Encode a complete response, including borrowed response projections.
167pub fn encode_response<T: Serialize + ?Sized>(
168    id: u64,
169    value: &T,
170    limits: Limits,
171) -> Result<Vec<u8>, Error> {
172    encode_prefixed(value, limits, &contract::header(id, 1))
173}
174/// Measure a request or response frame; both directions have equal header length.
175pub fn encoded_frame_len<T: Serialize + ?Sized>(value: &T, limits: Limits) -> Result<usize, Error> {
176    let bytes = limits
177        .bytes
178        .checked_sub(contract::HEADER)
179        .ok_or(Error::Limit)?;
180    encoded_len(
181        value,
182        Limits {
183            bytes,
184            allocation: limits
185                .allocation
186                .checked_sub(contract::HEADER)
187                .ok_or(Error::Limit)?,
188            ..limits
189        },
190    )
191    .map(|size| size + contract::HEADER)
192}
193/// Check request identity and direction before decoding its bounded native value.
194pub fn decode_request<T: Serialize + DeserializeOwned>(
195    id: u64,
196    bytes: &[u8],
197    limits: Limits,
198) -> Result<T, Error> {
199    decode_frame(id, 0, bytes, limits)
200}
201/// Check response identity and direction before decoding its bounded native value.
202pub fn decode_response<T: Serialize + DeserializeOwned>(
203    id: u64,
204    bytes: &[u8],
205    limits: Limits,
206) -> Result<T, Error> {
207    decode_frame(id, 1, bytes, limits)
208}
209fn decode_frame<T: Serialize + DeserializeOwned>(
210    id: u64,
211    direction: u8,
212    bytes: &[u8],
213    limits: Limits,
214) -> Result<T, Error> {
215    if bytes.len() > limits.bytes {
216        return Err(Error::Limit);
217    }
218    let payload = contract::payload(id, direction, bytes)?;
219    decode(
220        payload,
221        Limits {
222            bytes: limits.bytes - contract::HEADER,
223            allocation: limits
224                .allocation
225                .checked_sub(contract::HEADER)
226                .ok_or(Error::Limit)?,
227            ..limits
228        },
229    )
230}