Skip to main content

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 v3 Hello - metadata includes flags.
136    V3 {
137        max_payload_size: u32,
138        initial_channel_credit: u32,
139    } = 0,
140}
141
142/// Metadata value.
143// r[impl call.metadata.type]
144#[repr(u8)]
145#[derive(Debug, Clone, PartialEq, Eq, Facet)]
146pub enum MetadataValue {
147    String(String) = 0,
148    Bytes(Vec<u8>) = 1,
149    U64(u64) = 2,
150}
151
152impl MetadataValue {
153    /// Get the byte length of this value.
154    pub fn byte_len(&self) -> usize {
155        match self {
156            MetadataValue::String(s) => s.len(),
157            MetadataValue::Bytes(b) => b.len(),
158            MetadataValue::U64(_) => 8,
159        }
160    }
161}
162
163/// Metadata entry flags.
164///
165/// r[impl call.metadata.flags] - Flags control metadata handling behavior.
166pub mod metadata_flags {
167    /// No special handling.
168    pub const NONE: u64 = 0;
169
170    /// Value MUST NOT be logged, traced, or included in error messages.
171    pub const SENSITIVE: u64 = 1 << 0;
172
173    /// Value MUST NOT be forwarded to downstream calls.
174    pub const NO_PROPAGATE: u64 = 1 << 1;
175}
176
177/// Metadata validation limits.
178///
179/// r[impl call.metadata.limits] - Metadata has size limits.
180pub mod metadata_limits {
181    /// Maximum number of metadata entries.
182    pub const MAX_ENTRIES: usize = 128;
183    /// Maximum key size in bytes.
184    pub const MAX_KEY_SIZE: usize = 256;
185    /// Maximum value size in bytes (16 KB).
186    pub const MAX_VALUE_SIZE: usize = 16 * 1024;
187    /// Maximum total metadata size in bytes (64 KB).
188    pub const MAX_TOTAL_SIZE: usize = 64 * 1024;
189}
190
191/// Validate metadata against protocol limits.
192///
193/// r[impl call.metadata.limits] - Validate all metadata constraints.
194/// r[impl call.metadata.keys] - Keys at most 256 bytes.
195/// r[impl call.metadata.order] - Order is preserved (Vec maintains order).
196/// r[impl call.metadata.duplicates] - Duplicate keys are allowed.
197pub fn validate_metadata(metadata: &[(String, MetadataValue, u64)]) -> Result<(), &'static str> {
198    use metadata_limits::*;
199
200    // Check entry count
201    if metadata.len() > MAX_ENTRIES {
202        return Err("call.metadata.limits");
203    }
204
205    let mut total_size = 0usize;
206
207    for (key, value, _flags) in metadata {
208        // Check key size
209        if key.len() > MAX_KEY_SIZE {
210            return Err("call.metadata.limits");
211        }
212
213        // Check value size
214        let value_len = value.byte_len();
215        if value_len > MAX_VALUE_SIZE {
216            return Err("call.metadata.limits");
217        }
218
219        // Accumulate total size (flags are varint-encoded, typically 1 byte)
220        total_size += key.len() + value_len;
221    }
222
223    // Check total size
224    if total_size > MAX_TOTAL_SIZE {
225        return Err("call.metadata.limits");
226    }
227
228    Ok(())
229}
230
231/// Metadata entry: (key, value, flags).
232///
233/// r[impl call.metadata.type] - Metadata is a list of entries.
234/// r[impl call.metadata.flags] - Each entry includes flags for handling behavior.
235pub type Metadata = Vec<(String, MetadataValue, u64)>;
236
237/// Protocol message.
238///
239/// Variant order is wire-significant (postcard enum discriminants).
240///
241/// # Virtual Connections (v2.0.0)
242///
243/// A link carries multiple virtual connections, each with its own request ID
244/// space, channel ID space, and dispatcher. Connection 0 is implicit on link
245/// establishment. Additional connections are opened via Connect/Accept/Reject.
246///
247/// All messages except Hello, Connect, Accept, and Reject include a `conn_id`
248/// field identifying which virtual connection they belong to.
249#[repr(u8)]
250#[derive(Debug, Clone, PartialEq, Eq, Facet)]
251pub enum Message {
252    // ========================================================================
253    // Link control (no conn_id - applies to entire link)
254    // ========================================================================
255    /// r[impl message.hello.timing] - Sent immediately after link establishment.
256    Hello(Hello) = 0,
257
258    // ========================================================================
259    // Virtual connection control
260    // ========================================================================
261    /// r[impl message.connect.initiate] - Request a new virtual connection.
262    Connect { request_id: u64, metadata: Metadata } = 1,
263
264    /// r[impl message.accept.response] - Accept a virtual connection request.
265    Accept {
266        request_id: u64,
267        conn_id: ConnectionId,
268        metadata: Metadata,
269    } = 2,
270
271    /// r[impl message.reject.response] - Reject a virtual connection request.
272    Reject {
273        request_id: u64,
274        reason: String,
275        metadata: Metadata,
276    } = 3,
277
278    // ========================================================================
279    // Connection control (conn_id scoped)
280    // ========================================================================
281    /// r[impl message.goodbye.send] - Close a virtual connection.
282    /// r[impl message.goodbye.connection-zero] - Goodbye on conn 0 closes entire link.
283    Goodbye {
284        conn_id: ConnectionId,
285        reason: String,
286    } = 4,
287
288    // ========================================================================
289    // RPC (conn_id scoped)
290    // ========================================================================
291    /// r[impl core.metadata] - Request carries metadata key-value pairs.
292    /// r[impl call.metadata.unknown] - Unknown keys are ignored.
293    /// r[impl channeling.request.channels] - Channel IDs listed explicitly for proxy support.
294    Request {
295        conn_id: ConnectionId,
296        request_id: u64,
297        method_id: u64,
298        metadata: Metadata,
299        /// Channel IDs used by this call, in argument declaration order.
300        /// This is the authoritative source - servers MUST use these IDs,
301        /// not any IDs that may be embedded in the payload.
302        channels: Vec<u64>,
303        payload: Vec<u8>,
304    } = 5,
305
306    /// r[impl core.metadata] - Response carries metadata key-value pairs.
307    /// r[impl call.metadata.unknown] - Unknown keys are ignored.
308    Response {
309        conn_id: ConnectionId,
310        request_id: u64,
311        metadata: Metadata,
312        /// Channel IDs for streams in the response, in return type declaration order.
313        /// Client uses these to bind receivers for incoming Data messages.
314        channels: Vec<u64>,
315        payload: Vec<u8>,
316    } = 6,
317
318    /// r[impl call.cancel.message] - Cancel message requests callee stop processing.
319    /// r[impl call.cancel.no-response-required] - Caller should timeout, not wait indefinitely.
320    Cancel {
321        conn_id: ConnectionId,
322        request_id: u64,
323    } = 7,
324
325    // ========================================================================
326    // Channels (conn_id scoped)
327    // ========================================================================
328    // r[impl wire.stream] - Tx<T>/Rx<T> encoded as u64 channel ID on wire
329    Data {
330        conn_id: ConnectionId,
331        channel_id: u64,
332        payload: Vec<u8>,
333    } = 8,
334
335    Close {
336        conn_id: ConnectionId,
337        channel_id: u64,
338    } = 9,
339
340    Reset {
341        conn_id: ConnectionId,
342        channel_id: u64,
343    } = 10,
344
345    Credit {
346        conn_id: ConnectionId,
347        channel_id: u64,
348        bytes: u32,
349    } = 11,
350}