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