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