roam_wire/lib.rs
1#![deny(unsafe_code)]
2
3//! Spec-level wire types.
4//!
5//! Canonical definitions live in `docs/content/spec/_index.md` and `docs/content/shm-spec/_index.md`.
6
7use facet::Facet;
8
9/// Connection ID identifying a virtual connection on a link.
10///
11/// Connection 0 is the root connection, established implicitly when the link is created.
12/// Additional connections are opened via Connect/Accept messages.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Facet)]
14#[repr(transparent)]
15pub struct ConnectionId(pub u64);
16
17impl ConnectionId {
18 /// The root connection (always exists on a link).
19 pub const ROOT: Self = Self(0);
20
21 /// Create a new connection ID.
22 pub const fn new(id: u64) -> Self {
23 Self(id)
24 }
25
26 /// Get the raw u64 value.
27 pub const fn raw(self) -> u64 {
28 self.0
29 }
30
31 /// Check if this is the root connection.
32 pub const fn is_root(self) -> bool {
33 self.0 == 0
34 }
35}
36
37impl From<u64> for ConnectionId {
38 fn from(id: u64) -> Self {
39 Self(id)
40 }
41}
42
43impl From<ConnectionId> for u64 {
44 fn from(id: ConnectionId) -> Self {
45 id.0
46 }
47}
48
49impl std::fmt::Display for ConnectionId {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 write!(f, "conn:{}", self.0)
52 }
53}
54
55/// Request ID identifying an in-flight RPC request.
56///
57/// Request IDs are unique within a connection and monotonically increasing.
58/// r[impl call.request-id.uniqueness]
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Facet)]
60#[repr(transparent)]
61pub struct RequestId(pub u64);
62
63impl RequestId {
64 /// Create a new request ID.
65 pub const fn new(id: u64) -> Self {
66 Self(id)
67 }
68
69 /// Get the raw u64 value.
70 pub const fn raw(self) -> u64 {
71 self.0
72 }
73}
74
75impl From<u64> for RequestId {
76 fn from(id: u64) -> Self {
77 Self(id)
78 }
79}
80
81impl From<RequestId> for u64 {
82 fn from(id: RequestId) -> Self {
83 id.0
84 }
85}
86
87impl std::fmt::Display for RequestId {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(f, "req:{}", self.0)
90 }
91}
92
93/// Method ID identifying an RPC method.
94///
95/// Method IDs are computed as a hash of the service and method names.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Facet)]
97#[repr(transparent)]
98pub struct MethodId(pub u64);
99
100impl MethodId {
101 /// Create a new method ID.
102 pub const fn new(id: u64) -> Self {
103 Self(id)
104 }
105
106 /// Get the raw u64 value.
107 pub const fn raw(self) -> u64 {
108 self.0
109 }
110}
111
112impl From<u64> for MethodId {
113 fn from(id: u64) -> Self {
114 Self(id)
115 }
116}
117
118impl From<MethodId> for u64 {
119 fn from(id: MethodId) -> Self {
120 id.0
121 }
122}
123
124impl std::fmt::Display for MethodId {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(f, "method:{}", self.0)
127 }
128}
129
130/// Hello message for handshake.
131// r[impl message.hello.structure]
132#[repr(u8)]
133#[derive(Debug, Clone, PartialEq, Eq, Facet)]
134pub enum Hello {
135 /// Spec v1 Hello - deprecated, will be rejected.
136 V1 {
137 max_payload_size: u32,
138 initial_channel_credit: u32,
139 } = 0,
140
141 /// Spec v2 Hello - supports virtual connections.
142 V2 {
143 max_payload_size: u32,
144 initial_channel_credit: u32,
145 } = 1,
146}
147
148/// Metadata value.
149// r[impl call.metadata.type]
150#[repr(u8)]
151#[derive(Debug, Clone, PartialEq, Eq, Facet)]
152pub enum MetadataValue {
153 String(String) = 0,
154 Bytes(Vec<u8>) = 1,
155 U64(u64) = 2,
156}
157
158impl MetadataValue {
159 /// Get the byte length of this value.
160 pub fn byte_len(&self) -> usize {
161 match self {
162 MetadataValue::String(s) => s.len(),
163 MetadataValue::Bytes(b) => b.len(),
164 MetadataValue::U64(_) => 8,
165 }
166 }
167}
168
169/// Metadata validation limits.
170///
171/// r[impl call.metadata.limits] - Metadata has size limits.
172pub mod metadata_limits {
173 /// Maximum number of metadata entries.
174 pub const MAX_ENTRIES: usize = 128;
175 /// Maximum key size in bytes.
176 pub const MAX_KEY_SIZE: usize = 256;
177 /// Maximum value size in bytes (16 KB).
178 pub const MAX_VALUE_SIZE: usize = 16 * 1024;
179 /// Maximum total metadata size in bytes (64 KB).
180 pub const MAX_TOTAL_SIZE: usize = 64 * 1024;
181}
182
183/// Validate metadata against protocol limits.
184///
185/// r[impl call.metadata.limits] - Validate all metadata constraints.
186/// r[impl call.metadata.keys] - Keys at most 256 bytes.
187/// r[impl call.metadata.order] - Order is preserved (Vec maintains order).
188/// r[impl call.metadata.duplicates] - Duplicate keys are allowed.
189pub fn validate_metadata(metadata: &[(String, MetadataValue)]) -> Result<(), &'static str> {
190 use metadata_limits::*;
191
192 // Check entry count
193 if metadata.len() > MAX_ENTRIES {
194 return Err("call.metadata.limits");
195 }
196
197 let mut total_size = 0usize;
198
199 for (key, value) in metadata {
200 // Check key size
201 if key.len() > MAX_KEY_SIZE {
202 return Err("call.metadata.limits");
203 }
204
205 // Check value size
206 let value_len = value.byte_len();
207 if value_len > MAX_VALUE_SIZE {
208 return Err("call.metadata.limits");
209 }
210
211 // Accumulate total size
212 total_size += key.len() + value_len;
213 }
214
215 // Check total size
216 if total_size > MAX_TOTAL_SIZE {
217 return Err("call.metadata.limits");
218 }
219
220 Ok(())
221}
222
223/// Metadata type alias for convenience.
224pub type Metadata = Vec<(String, MetadataValue)>;
225
226/// Protocol message.
227///
228/// Variant order is wire-significant (postcard enum discriminants).
229///
230/// # Virtual Connections (v2.0.0)
231///
232/// A link carries multiple virtual connections, each with its own request ID
233/// space, channel ID space, and dispatcher. Connection 0 is implicit on link
234/// establishment. Additional connections are opened via Connect/Accept/Reject.
235///
236/// All messages except Hello, Connect, Accept, and Reject include a `conn_id`
237/// field identifying which virtual connection they belong to.
238#[repr(u8)]
239#[derive(Debug, Clone, PartialEq, Eq, Facet)]
240pub enum Message {
241 // ========================================================================
242 // Link control (no conn_id - applies to entire link)
243 // ========================================================================
244 /// r[impl message.hello.timing] - Sent immediately after link establishment.
245 Hello(Hello) = 0,
246
247 // ========================================================================
248 // Virtual connection control
249 // ========================================================================
250 /// r[impl message.connect.initiate] - Request a new virtual connection.
251 Connect { request_id: u64, metadata: Metadata } = 1,
252
253 /// r[impl message.accept.response] - Accept a virtual connection request.
254 Accept {
255 request_id: u64,
256 conn_id: ConnectionId,
257 metadata: Metadata,
258 } = 2,
259
260 /// r[impl message.reject.response] - Reject a virtual connection request.
261 Reject {
262 request_id: u64,
263 reason: String,
264 metadata: Metadata,
265 } = 3,
266
267 // ========================================================================
268 // Connection control (conn_id scoped)
269 // ========================================================================
270 /// r[impl message.goodbye.send] - Close a virtual connection.
271 /// r[impl message.goodbye.connection-zero] - Goodbye on conn 0 closes entire link.
272 Goodbye {
273 conn_id: ConnectionId,
274 reason: String,
275 } = 4,
276
277 // ========================================================================
278 // RPC (conn_id scoped)
279 // ========================================================================
280 /// r[impl core.metadata] - Request carries metadata key-value pairs.
281 /// r[impl call.metadata.unknown] - Unknown keys are ignored.
282 /// r[impl channeling.request.channels] - Channel IDs listed explicitly for proxy support.
283 Request {
284 conn_id: ConnectionId,
285 request_id: u64,
286 method_id: u64,
287 metadata: Metadata,
288 /// Channel IDs used by this call, in argument declaration order.
289 /// This is the authoritative source - servers MUST use these IDs,
290 /// not any IDs that may be embedded in the payload.
291 channels: Vec<u64>,
292 payload: Vec<u8>,
293 } = 5,
294
295 /// r[impl core.metadata] - Response carries metadata key-value pairs.
296 /// r[impl call.metadata.unknown] - Unknown keys are ignored.
297 Response {
298 conn_id: ConnectionId,
299 request_id: u64,
300 metadata: Metadata,
301 /// Channel IDs for streams in the response, in return type declaration order.
302 /// Client uses these to bind receivers for incoming Data messages.
303 channels: Vec<u64>,
304 payload: Vec<u8>,
305 } = 6,
306
307 /// r[impl call.cancel.message] - Cancel message requests callee stop processing.
308 /// r[impl call.cancel.no-response-required] - Caller should timeout, not wait indefinitely.
309 Cancel {
310 conn_id: ConnectionId,
311 request_id: u64,
312 } = 7,
313
314 // ========================================================================
315 // Channels (conn_id scoped)
316 // ========================================================================
317 // r[impl wire.stream] - Tx<T>/Rx<T> encoded as u64 channel ID on wire
318 Data {
319 conn_id: ConnectionId,
320 channel_id: u64,
321 payload: Vec<u8>,
322 } = 8,
323
324 Close {
325 conn_id: ConnectionId,
326 channel_id: u64,
327 } = 9,
328
329 Reset {
330 conn_id: ConnectionId,
331 channel_id: u64,
332 } = 10,
333
334 Credit {
335 conn_id: ConnectionId,
336 channel_id: u64,
337 bytes: u32,
338 } = 11,
339}