Skip to main content

ic_wire/shape/
mod.rs

1use alloc::boxed::Box;
2use serde::Serialize;
3
4/// Worst-case bounds for one finite value.
5#[derive(Clone, Copy, Debug)]
6pub struct Shape {
7    /// Maximum canonical Postcard payload bytes.
8    pub bytes: usize,
9    /// Maximum requested heap bytes owned by the decoded value.
10    pub heap: usize,
11    /// Maximum visited value nodes, including zero-byte values.
12    pub nodes: usize,
13    /// Maximum nested value depth.
14    pub depth: usize,
15}
16impl Shape {
17    /// A leaf with no allocation.
18    pub const fn scalar(bytes: usize) -> Self {
19        Self {
20            bytes,
21            heap: 0,
22            nodes: 1,
23            depth: 1,
24        }
25    }
26    /// A record with these children. Overflow saturates and fails admission.
27    pub const fn record(children: &[Self]) -> Self {
28        let mut out = Self::scalar(0);
29        let mut i = 0;
30        while i < children.len() {
31            let child = children[i];
32            out.bytes = out.bytes.saturating_add(child.bytes);
33            out.heap = out.heap.saturating_add(child.heap);
34            out.nodes = out.nodes.saturating_add(child.nodes);
35            let depth = child.depth.saturating_add(1);
36            if depth > out.depth {
37                out.depth = depth;
38            }
39            i += 1;
40        }
41        out
42    }
43    /// A tagged choice, using the largest bound in each dimension.
44    pub const fn choice(tag_bytes: usize, alternatives: &[Self]) -> Self {
45        let mut out = Self::scalar(0);
46        let mut i = 0;
47        while i < alternatives.len() {
48            let child = alternatives[i];
49            if child.bytes > out.bytes {
50                out.bytes = child.bytes;
51            }
52            if child.heap > out.heap {
53                out.heap = child.heap;
54            }
55            if child.nodes > out.nodes {
56                out.nodes = child.nodes;
57            }
58            if child.depth > out.depth {
59                out.depth = child.depth;
60            }
61            i += 1;
62        }
63        out.bytes = out.bytes.saturating_add(tag_bytes);
64        out.nodes = out.nodes.saturating_add(1);
65        out.depth = out.depth.saturating_add(1);
66        out
67    }
68}
69
70/// Finite values. Prefer the derive with explicit field identities.
71///
72/// Manual implementations must provide honest bounds, reject before excessive
73/// allocation/work, and serialize deterministically. Unbounded Serde types have
74/// no implementation. Recursive types are unsupported.
75/// Decoding additionally requires `Deserialize` for the input lifetime; a Wire
76/// value may borrow input or support encoding only.
77pub trait Wire: Serialize {
78    /// Internal whole-type guarantee of unchanged values and decoding boundaries.
79    /// Reserved for supplied types and generated code; manual implementations
80    /// must retain this default and the default `__canonical` method.
81    #[doc(hidden)]
82    const __CANONICAL_FIELDS: bool = false;
83
84    /// Internal post-decode verification. Custom implementations retain exact comparison.
85    #[doc(hidden)]
86    fn __canonical(
87        &self,
88        canonical: &mut crate::__private::Canonical<'_>,
89    ) -> Result<(), crate::Error> {
90        canonical.compare(self)
91    }
92
93    /// Static resource authority for this value.
94    const SHAPE: Shape;
95    /// Static projection metadata; custom implementations must opt in honestly.
96    const SCHEMA: crate::schema::Schema = crate::schema::Schema::Opaque;
97}
98
99/// Consumer-selected allowances for finite schema or explicit native admission.
100#[derive(Clone, Copy, Debug)]
101pub struct Limits {
102    /// Maximum encoding bytes; framed APIs include the 11-byte header.
103    pub bytes: usize,
104    /// Maximum value heap plus codec scratch (excludes caller input).
105    pub allocation: usize,
106    /// Maximum visited value nodes, including unit elements.
107    pub nodes: usize,
108    /// Maximum nesting depth.
109    pub depth: usize,
110}
111macro_rules! scalars {
112    ($($ty:ty => ($bytes:expr, $schema:expr)),* $(,)?) => { $(
113        impl Wire for $ty {
114            const __CANONICAL_FIELDS: bool = true;
115            const SHAPE: Shape = Shape::scalar($bytes);
116            const SCHEMA: crate::schema::Schema = $schema;
117        }
118    )* };
119}
120use crate::schema::Schema;
121scalars! { () => (0, Schema::Unit), bool => (1, Schema::Bool),
122u8 => (1, Schema::Unsigned(8)), i8 => (1, Schema::Signed(8)),
123u16 => (3, Schema::Unsigned(16)), i16 => (3, Schema::Signed(16)),
124u32 => (5, Schema::Unsigned(32)), i32 => (5, Schema::Signed(32)),
125u64 => (10, Schema::Unsigned(64)), i64 => (10, Schema::Signed(64)),
126u128 => (19, Schema::Unsigned(128)), i128 => (19, Schema::Signed(128)) }
127impl<T: Wire> Wire for Option<T> {
128    const __CANONICAL_FIELDS: bool = T::__CANONICAL_FIELDS;
129    const SHAPE: Shape = Shape::choice(1, &[T::SHAPE]);
130    const SCHEMA: Schema = Schema::Option(&T::SCHEMA);
131}
132impl<T: Wire, E: Wire> Wire for Result<T, E> {
133    const __CANONICAL_FIELDS: bool = T::__CANONICAL_FIELDS && E::__CANONICAL_FIELDS;
134    const SHAPE: Shape = Shape::choice(1, &[T::SHAPE, E::SHAPE]);
135    const SCHEMA: Schema = Schema::Result(&T::SCHEMA, &E::SCHEMA);
136}
137
138// Serde boxes are transparent: only owned heap storage changes. Saturation
139// preserves the existing fail-closed admission rule, including nested boxes.
140impl<T: Wire> Wire for Box<T> {
141    const __CANONICAL_FIELDS: bool = T::__CANONICAL_FIELDS;
142    const SHAPE: Shape = Shape {
143        heap: core::mem::size_of::<T>().saturating_add(T::SHAPE.heap),
144        ..T::SHAPE
145    };
146    const SCHEMA: Schema = T::SCHEMA;
147}
148
149impl<T: Wire, const N: usize> Wire for [T; N]
150where
151    [T; N]: Serialize,
152{
153    const __CANONICAL_FIELDS: bool = T::__CANONICAL_FIELDS;
154    const SCHEMA: Schema = Schema::Array(&T::SCHEMA, N);
155    const SHAPE: Shape = Shape {
156        bytes: T::SHAPE.bytes.saturating_mul(N),
157        heap: T::SHAPE.heap.saturating_mul(N),
158        nodes: 1_usize.saturating_add(T::SHAPE.nodes.saturating_mul(N)),
159        depth: T::SHAPE.depth.saturating_add(1),
160    };
161}