roam_frame/
frame.rs

1use bytes::Bytes;
2
3pub const INLINE_PAYLOAD_LEN: usize = 32;
4pub const INLINE_PAYLOAD_SLOT: u32 = 0xFFFF_FFFF;
5
6/// Message descriptor (64 bytes) per SHM spec.
7///
8/// Spec: `docs/content/shm-spec/_index.md` "MsgDesc (64 bytes)".
9#[repr(C, align(64))]
10#[derive(Debug, Clone, Copy)]
11pub struct MsgDesc {
12    // Identity (16 bytes)
13    pub msg_type: u8,
14    pub flags: u8,
15    pub _reserved: [u8; 2],
16    pub id: u32,
17    pub method_id: u64,
18
19    // Payload location (16 bytes)
20    pub payload_slot: u32,
21    pub payload_generation: u32,
22    pub payload_offset: u32,
23    pub payload_len: u32,
24
25    // Inline payload (32 bytes)
26    pub inline_payload: [u8; INLINE_PAYLOAD_LEN],
27}
28
29impl MsgDesc {
30    #[inline]
31    pub fn new(msg_type: u8, id: u32, method_id: u64) -> Self {
32        Self {
33            msg_type,
34            flags: 0,
35            _reserved: [0; 2],
36            id,
37            method_id,
38            payload_slot: INLINE_PAYLOAD_SLOT,
39            payload_generation: 0,
40            payload_offset: 0,
41            payload_len: 0,
42            inline_payload: [0; INLINE_PAYLOAD_LEN],
43        }
44    }
45
46    #[inline]
47    pub fn inline_payload_bytes(&self) -> &[u8] {
48        let len = (self.payload_len as usize).min(INLINE_PAYLOAD_LEN);
49        &self.inline_payload[..len]
50    }
51}
52
53/// Payload storage for a frame.
54#[derive(Debug, Clone)]
55pub enum Payload {
56    /// Payload bytes live inside `MsgDesc::inline_payload`.
57    Inline,
58    /// Payload bytes are owned as a heap allocation.
59    Owned(Vec<u8>),
60    /// Payload bytes are stored in a ref-counted buffer (cheap clone).
61    Bytes(Bytes),
62}
63
64impl Payload {
65    pub fn as_slice<'a>(&'a self, desc: &'a MsgDesc) -> &'a [u8] {
66        match self {
67            Self::Inline => desc.inline_payload_bytes(),
68            Self::Owned(buf) => buf.as_slice(),
69            Self::Bytes(buf) => buf.as_ref(),
70        }
71    }
72
73    pub fn external_slice(&self) -> Option<&[u8]> {
74        match self {
75            Self::Inline => None,
76            Self::Owned(buf) => Some(buf.as_slice()),
77            Self::Bytes(buf) => Some(buf.as_ref()),
78        }
79    }
80
81    pub fn len(&self, desc: &MsgDesc) -> usize {
82        self.as_slice(desc).len()
83    }
84
85    pub fn is_inline(&self) -> bool {
86        matches!(self, Self::Inline)
87    }
88}
89
90/// Owned frame for sending, receiving, or routing.
91#[derive(Debug, Clone)]
92pub struct Frame {
93    pub desc: MsgDesc,
94    pub payload: Payload,
95}
96
97impl Frame {
98    #[inline]
99    pub fn new(desc: MsgDesc) -> Self {
100        Self {
101            desc,
102            payload: Payload::Inline,
103        }
104    }
105
106    #[inline]
107    pub fn with_inline_payload(mut desc: MsgDesc, payload: &[u8]) -> Option<Self> {
108        if payload.len() > INLINE_PAYLOAD_LEN {
109            return None;
110        }
111        desc.payload_slot = INLINE_PAYLOAD_SLOT;
112        desc.payload_generation = 0;
113        desc.payload_offset = 0;
114        desc.payload_len = payload.len() as u32;
115        desc.inline_payload[..payload.len()].copy_from_slice(payload);
116        Some(Self {
117            desc,
118            payload: Payload::Inline,
119        })
120    }
121
122    #[inline]
123    pub fn with_owned_payload(mut desc: MsgDesc, payload: Vec<u8>) -> Self {
124        desc.payload_slot = 0;
125        desc.payload_generation = 0;
126        desc.payload_offset = 0;
127        desc.payload_len = payload.len() as u32;
128        Self {
129            desc,
130            payload: Payload::Owned(payload),
131        }
132    }
133
134    #[inline]
135    pub fn with_bytes_payload(mut desc: MsgDesc, payload: Bytes) -> Self {
136        desc.payload_slot = 0;
137        desc.payload_generation = 0;
138        desc.payload_offset = 0;
139        desc.payload_len = payload.len() as u32;
140        Self {
141            desc,
142            payload: Payload::Bytes(payload),
143        }
144    }
145
146    #[inline]
147    pub fn payload_bytes(&self) -> &[u8] {
148        self.payload.as_slice(&self.desc)
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn msg_desc_is_one_cache_line() {
158        static_assertions::const_assert!(std::mem::size_of::<MsgDesc>() == 64);
159        static_assertions::const_assert!(std::mem::align_of::<MsgDesc>() == 64);
160    }
161
162    #[test]
163    fn inline_payload_roundtrips() {
164        let mut desc = MsgDesc::new(1, 7, 0);
165        let payload = b"hello";
166        let frame = Frame::with_inline_payload(desc, payload).expect("inline payload");
167        assert!(frame.payload.is_inline());
168        assert_eq!(frame.payload_bytes(), payload);
169        desc.payload_len = 999;
170        assert_eq!(desc.inline_payload_bytes().len(), INLINE_PAYLOAD_LEN);
171    }
172}