Skip to main content

shm_primitives/
segment.rs

1use crate::sync::{AtomicU32, AtomicU64, Ordering};
2
3/// Magic bytes that identify a v7 roam SHM segment.
4///
5/// r[impl shm.segment.magic.v7]
6pub const MAGIC: [u8; 8] = *b"ROAMHUB\x07";
7
8/// Current segment format version.
9pub const SEGMENT_VERSION: u32 = 7;
10
11/// Fixed size of the segment header in bytes.
12pub const SEGMENT_HEADER_SIZE: usize = 128;
13
14/// Parameters for initializing a fresh segment header.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct SegmentHeaderInit {
17    pub total_size: u64,
18    pub max_payload_size: u32,
19    pub inline_threshold: u32,
20    pub max_guests: u32,
21    pub bipbuf_capacity: u32,
22    pub peer_table_offset: u64,
23    pub var_pool_offset: u64,
24    pub heartbeat_interval: u64,
25    pub num_var_slot_classes: u32,
26}
27
28/// The segment header lives at offset 0 of every roam SHM segment.
29///
30/// All fields are set by the host at creation time and treated as read-only
31/// by guests after attach — except `host_goodbye` and `current_size`, which
32/// the host may update at runtime.
33///
34/// r[impl shm.segment]
35/// r[impl shm.segment.header]
36/// r[impl shm.segment.config]
37#[repr(C)]
38pub struct SegmentHeader {
39    /// "ROAMHUB\x07" — identifies a v7 roam SHM segment.
40    pub magic: [u8; 8],
41    /// Segment format version (currently 7).
42    pub version: u32,
43    /// Always 128 — allows future extension without breaking older readers.
44    pub header_size: u32,
45    /// Total size of the segment in bytes (set at creation).
46    pub total_size: u64,
47    /// Maximum payload size in bytes.
48    pub max_payload_size: u32,
49    /// Inline threshold: payloads ≤ this go inline; larger ones use a slot ref.
50    /// 0 means use the default (256 bytes).
51    pub inline_threshold: u32,
52    /// Maximum number of guests (≤ 255).
53    pub max_guests: u32,
54    /// BipBuffer data region size per direction, in bytes.
55    pub bipbuf_capacity: u32,
56    /// Byte offset of the peer table from the start of the segment.
57    pub peer_table_offset: u64,
58    /// Byte offset of the shared VarSlotPool from the start of the segment.
59    pub var_pool_offset: u64,
60    /// Heartbeat interval in nanoseconds; 0 = heartbeats disabled.
61    pub heartbeat_interval: u64,
62    /// Set to non-zero by the host during orderly shutdown.
63    pub host_goodbye: AtomicU32,
64    /// Number of var-slot size classes described at `var_pool_offset`.
65    pub num_var_slot_classes: u32,
66    /// Current segment size in bytes. May grow if extents are appended.
67    pub current_size: AtomicU64,
68    _reserved: [u8; 48],
69}
70
71#[cfg(not(loom))]
72const _: () = assert!(core::mem::size_of::<SegmentHeader>() == SEGMENT_HEADER_SIZE);
73
74impl SegmentHeader {
75    /// Write initial values into a zeroed header.
76    ///
77    /// # Safety
78    ///
79    /// `self` must point into exclusively-owned, zeroed memory.
80    pub unsafe fn init(&mut self, init: SegmentHeaderInit) {
81        self.magic = MAGIC;
82        self.version = SEGMENT_VERSION;
83        self.header_size = SEGMENT_HEADER_SIZE as u32;
84        self.total_size = init.total_size;
85        self.max_payload_size = init.max_payload_size;
86        self.inline_threshold = init.inline_threshold;
87        self.max_guests = init.max_guests;
88        self.bipbuf_capacity = init.bipbuf_capacity;
89        self.peer_table_offset = init.peer_table_offset;
90        self.var_pool_offset = init.var_pool_offset;
91        self.heartbeat_interval = init.heartbeat_interval;
92        self.host_goodbye = AtomicU32::new(0);
93        self.num_var_slot_classes = init.num_var_slot_classes;
94        self.current_size = AtomicU64::new(init.total_size);
95        self._reserved = [0u8; 48];
96    }
97
98    /// Validate that the header looks like a v7 roam segment.
99    ///
100    /// Returns `Err` with a description if validation fails.
101    ///
102    /// r[impl shm.segment.magic.v7]
103    pub fn validate(&self) -> Result<(), &'static str> {
104        if self.magic != MAGIC {
105            return Err("bad magic: not a roam v7 segment");
106        }
107        if self.version != SEGMENT_VERSION {
108            return Err("unsupported segment version");
109        }
110        if self.header_size != SEGMENT_HEADER_SIZE as u32 {
111            return Err("unexpected header_size");
112        }
113        if self.num_var_slot_classes == 0 {
114            return Err("segment missing var-slot classes");
115        }
116        Ok(())
117    }
118
119    /// Read the effective inline threshold (substituting the default if 0).
120    #[inline]
121    pub fn effective_inline_threshold(&self) -> u32 {
122        if self.inline_threshold == 0 {
123            256
124        } else {
125            self.inline_threshold
126        }
127    }
128
129    /// Read the current segment size.
130    #[inline]
131    pub fn current_size(&self) -> u64 {
132        self.current_size.load(Ordering::Acquire)
133    }
134
135    /// Check whether the host has raised the goodbye flag.
136    #[inline]
137    pub fn host_goodbye(&self) -> bool {
138        self.host_goodbye.load(Ordering::Acquire) != 0
139    }
140}
141
142#[cfg(all(test, not(loom)))]
143mod tests {
144    use super::*;
145    use crate::region::HeapRegion;
146
147    fn make_header() -> (HeapRegion, *mut SegmentHeader) {
148        let region = HeapRegion::new_zeroed(SEGMENT_HEADER_SIZE);
149        let r = region.region();
150        let hdr: *mut SegmentHeader = unsafe { r.get_mut::<SegmentHeader>(0) };
151        unsafe {
152            (*hdr).init(SegmentHeaderInit {
153                total_size: 65536,
154                max_payload_size: 65536,
155                inline_threshold: 0,
156                max_guests: 4,
157                bipbuf_capacity: 16384,
158                peer_table_offset: 128,
159                var_pool_offset: 4096,
160                heartbeat_interval: 0,
161                num_var_slot_classes: 1,
162            });
163        }
164        (region, hdr)
165    }
166
167    #[test]
168    fn roundtrip() {
169        let (_region, hdr) = make_header();
170        let hdr = unsafe { &*hdr };
171
172        assert_eq!(hdr.magic, MAGIC);
173        assert_eq!(hdr.version, SEGMENT_VERSION);
174        assert_eq!(hdr.header_size, 128);
175        assert_eq!(hdr.total_size, 65536);
176        assert_eq!(hdr.max_guests, 4);
177        assert_eq!(hdr.bipbuf_capacity, 16384);
178        assert_eq!(hdr.peer_table_offset, 128);
179        assert_eq!(hdr.var_pool_offset, 4096);
180        assert_eq!(hdr.num_var_slot_classes, 1);
181        assert_eq!(hdr.current_size(), 65536);
182    }
183
184    #[test]
185    fn validate_ok() {
186        let (_region, hdr) = make_header();
187        unsafe { &*hdr }.validate().expect("valid header");
188    }
189
190    #[test]
191    fn validate_bad_magic() {
192        let (_region, hdr) = make_header();
193        let hdr = unsafe { &mut *hdr };
194        hdr.magic[7] = 0x01; // corrupt version byte in magic
195        assert!(hdr.validate().is_err());
196    }
197
198    #[test]
199    fn validate_bad_version() {
200        let (_region, hdr) = make_header();
201        let hdr = unsafe { &mut *hdr };
202        hdr.version = 99;
203        assert!(hdr.validate().is_err());
204    }
205
206    #[test]
207    fn inline_threshold_default() {
208        let (_region, hdr) = make_header();
209        let hdr = unsafe { &*hdr };
210        // inline_threshold was 0 → should return 256
211        assert_eq!(hdr.effective_inline_threshold(), 256);
212    }
213
214    #[test]
215    fn host_goodbye_flag() {
216        let (_region, hdr) = make_header();
217        let hdr = unsafe { &*hdr };
218        assert!(!hdr.host_goodbye());
219        hdr.host_goodbye.store(1, Ordering::Release);
220        assert!(hdr.host_goodbye());
221    }
222
223    #[test]
224    fn current_size_matches_total() {
225        let (_region, hdr) = make_header();
226        let hdr = unsafe { &*hdr };
227        assert_eq!(hdr.current_size(), hdr.total_size);
228    }
229}