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