Skip to main content

bb_runtime/
envelope.rs

1//! `WireEnvelope` codec
2//!
3//! Encodes/decodes the canonical `WireEnvelope` proto message via
4//! prost. The envelope carries a resolved
5//! `dest_peer_addresses: repeated bytes` ordered address list plus
6//! one or more typed `SlotFill`s. Each fill's `dest_suffix`
7//! identifies the local destination (data-plane slot or
8//! control-plane component op) via *intra-node* address segments;
9//! receivers route by parsing the suffix. The wire syscall resolves
10//! `PeerId → Vec<Address>` via the framework's `AddressBook` before
11//! shipping; the host transport picks one entry by capability.
12
13use prost::Message;
14
15pub use bb_ir::proto::bb_core::{CorrelationKind, SlotFill, WireCorrelation, WireEnvelope};
16
17/// Current `WireEnvelope` schema version. Bumped when a field's
18/// semantics changes in a way old code cannot soundly handle.
19pub const ENVELOPE_SCHEMA_VERSION: u32 = 1;
20
21/// Supported schema versions this build will accept on
22/// `decode_capped`. Mismatch surfaces as
23/// `EnvelopeDecodeError::VersionMismatch`.
24pub const SUPPORTED_SCHEMA_VERSIONS: &[u32] = &[ENVELOPE_SCHEMA_VERSION];
25
26/// bounded-decode caps applied by
27/// [`EnvelopeCodec::decode_capped`] before any prost allocation.
28/// Production deployments override via `NodeConfig` ();
29/// the defaults match the design's "16 MiB / 256 / 4 MiB / 4 KiB"
30/// recommendation in
31/// `docs-plan/CORRECTED_ARCHITECTURE.md` §Edge bounds.
32#[derive(Clone, Copy, Debug)]
33pub struct EnvelopeCaps {
34    /// Reject inbound buffers whose length exceeds this.
35    pub max_total_bytes: usize,
36    /// Reject envelopes whose `fills.len()` exceeds this.
37    pub max_slot_fills: usize,
38    /// Reject any `SlotFill` whose `payload.len()` exceeds this.
39    pub max_per_fill_bytes: usize,
40    /// Reject any `SlotFill` whose `dest_suffix.len()` exceeds this.
41    pub max_dest_suffix_bytes: usize,
42    /// Reject envelopes whose `src_peer_addresses.len()` exceeds
43    /// this. Caps sender-driven AddressBook growth at the receiver.
44    pub max_src_peer_addresses: usize,
45    /// Reject any `src_peer_addresses` entry whose length exceeds
46    /// this. Caps per-address allocation to a single multiaddr's
47    /// realistic envelope.
48    pub max_src_peer_address_bytes: usize,
49}
50
51impl Default for EnvelopeCaps {
52    fn default() -> Self {
53        Self {
54            max_total_bytes: 16 * 1024 * 1024,
55            max_slot_fills: 256,
56            max_per_fill_bytes: 4 * 1024 * 1024,
57            max_dest_suffix_bytes: 4 * 1024,
58            max_src_peer_addresses: 8,
59            max_src_peer_address_bytes: 256,
60        }
61    }
62}
63
64impl EnvelopeCaps {
65    /// Tighter preset for edge / embedded deployments.
66    pub fn edge() -> Self {
67        Self {
68            max_total_bytes: 256 * 1024,
69            max_slot_fills: 16,
70            max_per_fill_bytes: 64 * 1024,
71            max_dest_suffix_bytes: 512,
72            max_src_peer_addresses: 4,
73            max_src_peer_address_bytes: 256,
74        }
75    }
76}
77
78/// Errors `EnvelopeCodec::decode_capped` can surface.
79#[derive(Debug)]
80pub enum EnvelopeDecodeError {
81    /// Prost rejected the wire-format bytes.
82    Malformed(prost::DecodeError),
83    /// buffer exceeded `EnvelopeCaps.max_total_bytes`.
84    OversizeEnvelope {
85        /// Cap that was breached.
86        cap_bytes: usize,
87        /// Observed buffer length.
88        got_bytes: usize,
89    },
90    /// A single `SlotFill` exceeded `max_per_fill_bytes` or
91    /// `max_dest_suffix_bytes`.
92    OversizeSlotFill {
93        /// Which limit was breached.
94        which: &'static str,
95        /// Cap that was breached.
96        cap_bytes: usize,
97        /// Observed length.
98        got_bytes: usize,
99    },
100    /// `fills.len()` exceeded `max_slot_fills`.
101    TooManySlotFills {
102        /// Cap that was breached.
103        cap: usize,
104        /// Observed count.
105        got: usize,
106    },
107    /// envelope's `schema_version` is not in
108    /// `SUPPORTED_SCHEMA_VERSIONS`.
109    VersionMismatch {
110        /// Version the inbound envelope advertised.
111        got: u32,
112        /// Versions this build supports.
113        supported: &'static [u32],
114    },
115    /// `src_peer_addresses.len()` exceeded
116    /// `max_src_peer_addresses`.
117    TooManySrcPeerAddresses {
118        /// Cap that was breached.
119        cap: usize,
120        /// Observed count.
121        got: usize,
122    },
123    /// A single `src_peer_addresses` entry exceeded
124    /// `max_src_peer_address_bytes`.
125    OversizeSrcPeerAddress {
126        /// Cap that was breached.
127        cap_bytes: usize,
128        /// Observed length.
129        got_bytes: usize,
130    },
131}
132
133impl std::fmt::Display for EnvelopeDecodeError {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        match self {
136            Self::Malformed(e) => write!(f, "malformed envelope bytes: {e}"),
137            Self::OversizeEnvelope {
138                cap_bytes,
139                got_bytes,
140            } => write!(
141                f,
142                "envelope buffer too large: cap={cap_bytes} got={got_bytes}",
143            ),
144            Self::OversizeSlotFill {
145                which,
146                cap_bytes,
147                got_bytes,
148            } => write!(
149                f,
150                "slot fill {which} exceeds cap: cap={cap_bytes} got={got_bytes}",
151            ),
152            Self::TooManySlotFills { cap, got } => write!(
153                f,
154                "envelope carries too many slot fills: cap={cap} got={got}",
155            ),
156            Self::VersionMismatch { got, supported } => write!(
157                f,
158                "envelope schema_version mismatch: got={got} supported={supported:?}",
159            ),
160            Self::TooManySrcPeerAddresses { cap, got } => write!(
161                f,
162                "envelope carries too many src_peer_addresses: cap={cap} got={got}",
163            ),
164            Self::OversizeSrcPeerAddress {
165                cap_bytes,
166                got_bytes,
167            } => write!(
168                f,
169                "src_peer_addresses entry too large: cap={cap_bytes} got={got_bytes}",
170            ),
171        }
172    }
173}
174
175impl std::error::Error for EnvelopeDecodeError {}
176
177/// Envelope encode + decode helper. Stateless; thin façade over
178/// prost's `Message::encode_to_vec` / `Message::decode`.
179pub struct EnvelopeCodec;
180
181impl EnvelopeCodec {
182    /// Encode `env` to a prost wire-format byte vector.
183    ///
184    /// Callers must stamp [`ENVELOPE_SCHEMA_VERSION`] on the envelope
185    /// before encode; production paths land it on push into
186    /// `OutboundQueue::push` so this function avoids cloning the
187    /// (potentially large) payload bytes solely to set one u32.
188    pub fn encode(env: &WireEnvelope) -> Vec<u8> {
189        env.encode_to_vec()
190    }
191
192    /// Bounded decode — the only inbound decode entry. Rejects the
193    /// buffer at the length / fill-count / schema-version layer
194    /// BEFORE prost allocation, so an adversarial sender can't
195    /// pre-balloon memory by advertising large lengths in the
196    /// protobuf header.
197    pub fn decode_capped(
198        bytes: &[u8],
199        caps: &EnvelopeCaps,
200    ) -> Result<WireEnvelope, EnvelopeDecodeError> {
201        if bytes.len() > caps.max_total_bytes {
202            return Err(EnvelopeDecodeError::OversizeEnvelope {
203                cap_bytes: caps.max_total_bytes,
204                got_bytes: bytes.len(),
205            });
206        }
207        let env = WireEnvelope::decode(bytes).map_err(EnvelopeDecodeError::Malformed)?;
208        if !SUPPORTED_SCHEMA_VERSIONS.contains(&env.schema_version) && env.schema_version != 0 {
209            return Err(EnvelopeDecodeError::VersionMismatch {
210                got: env.schema_version,
211                supported: SUPPORTED_SCHEMA_VERSIONS,
212            });
213        }
214        if env.fills.len() > caps.max_slot_fills {
215            return Err(EnvelopeDecodeError::TooManySlotFills {
216                cap: caps.max_slot_fills,
217                got: env.fills.len(),
218            });
219        }
220        for fill in &env.fills {
221            if fill.payload.len() > caps.max_per_fill_bytes {
222                return Err(EnvelopeDecodeError::OversizeSlotFill {
223                    which: "payload",
224                    cap_bytes: caps.max_per_fill_bytes,
225                    got_bytes: fill.payload.len(),
226                });
227            }
228            if fill.dest_suffix.len() > caps.max_dest_suffix_bytes {
229                return Err(EnvelopeDecodeError::OversizeSlotFill {
230                    which: "dest_suffix",
231                    cap_bytes: caps.max_dest_suffix_bytes,
232                    got_bytes: fill.dest_suffix.len(),
233                });
234            }
235        }
236        if env.src_peer_addresses.len() > caps.max_src_peer_addresses {
237            return Err(EnvelopeDecodeError::TooManySrcPeerAddresses {
238                cap: caps.max_src_peer_addresses,
239                got: env.src_peer_addresses.len(),
240            });
241        }
242        for addr in &env.src_peer_addresses {
243            if addr.len() > caps.max_src_peer_address_bytes {
244                return Err(EnvelopeDecodeError::OversizeSrcPeerAddress {
245                    cap_bytes: caps.max_src_peer_address_bytes,
246                    got_bytes: addr.len(),
247                });
248            }
249        }
250        Ok(env)
251    }
252}
253