1use libc::{
8 MAP_FAILED, MAP_SHARED, O_CREAT, O_EXCL, O_RDWR, PROT_READ, PROT_WRITE, c_void, close, fstat,
9 ftruncate, mmap, munmap, shm_open, shm_unlink,
10};
11use std::ffi::CString;
12use std::fmt;
13use std::io;
14use std::mem::{size_of, zeroed};
15use std::ptr::{self, addr_of};
16use std::sync::atomic::{AtomicU64, Ordering};
17
18pub const SHM_NAME: &str = "/ipc_ring48_queue";
20
21pub const PAYLOAD_SIZE: usize = 48;
23
24const MAGIC: u64 = 0x4950_4352_494E_4734; const VERSION: u64 = 1;
26const FLAGS: u64 = 0;
27
28#[repr(C, align(64))]
29pub struct SharedHeader {
30 pub magic: u64,
31 pub version: u64,
32 pub region_size: u64,
33 pub payload_size: u64,
34 pub capacity: u64,
35 pub flags: u64,
36 pub producer_pid: u64,
37 pub consumer_pid: u64,
38}
39
40#[repr(C, align(64))]
41pub struct CounterLine {
42 pub value: AtomicU64,
43 pub reserved: [u8; 56],
44}
45
46#[repr(C, align(64))]
47pub struct Slot48 {
48 pub payload: [u8; PAYLOAD_SIZE],
49 pub reserved: [u8; 16],
50}
51
52const HEADER_SIZE: usize = size_of::<SharedHeader>();
53const COUNTER_SIZE: usize = size_of::<CounterLine>();
54const SLOT_SIZE: usize = size_of::<Slot48>();
55const BASE_SIZE: usize = HEADER_SIZE + COUNTER_SIZE + COUNTER_SIZE;
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum PushError {
60 Full,
61}
62
63impl fmt::Display for PushError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 PushError::Full => write!(f, "queue full"),
67 }
68 }
69}
70
71impl std::error::Error for PushError {}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct QueueStats {
76 pub capacity: u64,
77 pub len: u64,
78 pub head: u64,
79 pub tail: u64,
80 pub region_size: u64,
81}
82
83struct Mapping {
84 ptr: *mut u8,
85 len: usize,
86 fd: i32,
87}
88
89impl Mapping {
90 fn create_or_open(capacity: usize) -> io::Result<Self> {
91 validate_capacity(capacity)?;
92
93 let expected_region_size = region_size_for_capacity(capacity)?;
94 let name = CString::new(SHM_NAME).unwrap();
95
96 let mut created = false;
97
98 let fd = unsafe { shm_open(name.as_ptr(), O_CREAT | O_EXCL | O_RDWR, 0o600) };
99
100 let fd = if fd >= 0 {
101 created = true;
102 fd
103 } else {
104 let err = io::Error::last_os_error();
105
106 if err.raw_os_error() == Some(libc::EEXIST) {
107 let fd = unsafe { shm_open(name.as_ptr(), O_RDWR, 0o600) };
108
109 if fd < 0 {
110 return Err(io::Error::last_os_error());
111 }
112
113 fd
114 } else {
115 return Err(err);
116 }
117 };
118
119 let region_size = if created {
120 let rc = unsafe { ftruncate(fd, expected_region_size as libc::off_t) };
121
122 if rc != 0 {
123 let err = io::Error::last_os_error();
124
125 unsafe {
126 close(fd);
127 }
128
129 return Err(err);
130 }
131
132 expected_region_size
133 } else {
134 file_size(fd)?
135 };
136
137 let raw = unsafe {
138 mmap(
139 ptr::null_mut(),
140 region_size,
141 PROT_READ | PROT_WRITE,
142 MAP_SHARED,
143 fd,
144 0,
145 )
146 };
147
148 if raw == MAP_FAILED {
149 let err = io::Error::last_os_error();
150
151 unsafe {
152 close(fd);
153 }
154
155 return Err(err);
156 }
157
158 let mapping = Self {
159 ptr: raw as *mut u8,
160 len: region_size,
161 fd,
162 };
163
164 if created {
165 mapping.initialise(capacity)?;
166 } else {
167 mapping.validate()?;
168
169 if mapping.capacity() != capacity as u64 {
170 return Err(io::Error::new(
171 io::ErrorKind::InvalidInput,
172 "existing queue capacity does not match requested capacity",
173 ));
174 }
175 }
176
177 Ok(mapping)
178 }
179
180 fn open_existing() -> io::Result<Self> {
181 let name = CString::new(SHM_NAME).unwrap();
182
183 let fd = unsafe { shm_open(name.as_ptr(), O_RDWR, 0o600) };
184
185 if fd < 0 {
186 return Err(io::Error::last_os_error());
187 }
188
189 let region_size = file_size(fd)?;
190
191 let raw = unsafe {
192 mmap(
193 ptr::null_mut(),
194 region_size,
195 PROT_READ | PROT_WRITE,
196 MAP_SHARED,
197 fd,
198 0,
199 )
200 };
201
202 if raw == MAP_FAILED {
203 let err = io::Error::last_os_error();
204
205 unsafe {
206 close(fd);
207 }
208
209 return Err(err);
210 }
211
212 let mapping = Self {
213 ptr: raw as *mut u8,
214 len: region_size,
215 fd,
216 };
217
218 mapping.validate()?;
219
220 Ok(mapping)
221 }
222
223 fn initialise(&self, capacity: usize) -> io::Result<()> {
224 validate_capacity(capacity)?;
225
226 unsafe {
227 ptr::write_bytes(self.ptr, 0, self.len);
228
229 let header = self.header_mut();
230
231 (*header).magic = MAGIC;
232 (*header).version = VERSION;
233 (*header).region_size = self.len as u64;
234 (*header).payload_size = PAYLOAD_SIZE as u64;
235 (*header).capacity = capacity as u64;
236 (*header).flags = FLAGS;
237 (*header).producer_pid = libc::getpid() as u64;
238 (*header).consumer_pid = 0;
239
240 (*self.head()).value = AtomicU64::new(0);
241 (*self.tail()).value = AtomicU64::new(0);
242 }
243
244 Ok(())
245 }
246
247 fn validate(&self) -> io::Result<()> {
248 unsafe {
249 let header = self.header();
250
251 let magic = ptr::read_volatile(addr_of!((*header).magic));
252 let version = ptr::read_volatile(addr_of!((*header).version));
253 let region_size = ptr::read_volatile(addr_of!((*header).region_size));
254 let payload_size = ptr::read_volatile(addr_of!((*header).payload_size));
255 let capacity = ptr::read_volatile(addr_of!((*header).capacity));
256
257 if magic != MAGIC {
258 return Err(io::Error::new(
259 io::ErrorKind::InvalidData,
260 "shared memory magic mismatch",
261 ));
262 }
263
264 if version != VERSION {
265 return Err(io::Error::new(
266 io::ErrorKind::InvalidData,
267 "shared memory version mismatch",
268 ));
269 }
270
271 if region_size != self.len as u64 {
272 return Err(io::Error::new(
273 io::ErrorKind::InvalidData,
274 "shared memory region size mismatch",
275 ));
276 }
277
278 if payload_size != PAYLOAD_SIZE as u64 {
279 return Err(io::Error::new(
280 io::ErrorKind::InvalidData,
281 "shared memory payload size mismatch",
282 ));
283 }
284
285 let capacity_usize = usize::try_from(capacity).map_err(|_| {
286 io::Error::new(io::ErrorKind::InvalidData, "capacity does not fit usize")
287 })?;
288
289 validate_capacity(capacity_usize)?;
290
291 let expected_region_size = region_size_for_capacity(capacity_usize)?;
292
293 if expected_region_size != self.len {
294 return Err(io::Error::new(
295 io::ErrorKind::InvalidData,
296 "capacity does not match region size",
297 ));
298 }
299 }
300
301 Ok(())
302 }
303
304 fn push(&self, value: [u8; PAYLOAD_SIZE]) -> Result<(), PushError> {
305 unsafe {
306 let head = &(*self.head()).value;
307 let tail = &(*self.tail()).value;
308
309 let current_head = head.load(Ordering::Relaxed);
310 let current_tail = tail.load(Ordering::Acquire);
311 let capacity = self.capacity();
312
313 if current_head.wrapping_sub(current_tail) == capacity {
314 return Err(PushError::Full);
315 }
316
317 let index = current_head & (capacity - 1);
318 let slot = self.slot(index);
319
320 ptr::copy_nonoverlapping(value.as_ptr(), (*slot).payload.as_mut_ptr(), PAYLOAD_SIZE);
321
322 head.store(current_head.wrapping_add(1), Ordering::Release);
323
324 Ok(())
325 }
326 }
327
328 fn pop(&self) -> Option<[u8; PAYLOAD_SIZE]> {
329 unsafe {
330 let head = &(*self.head()).value;
331 let tail = &(*self.tail()).value;
332
333 let current_tail = tail.load(Ordering::Relaxed);
334 let current_head = head.load(Ordering::Acquire);
335
336 if current_tail == current_head {
337 return None;
338 }
339
340 let capacity = self.capacity();
341 let index = current_tail & (capacity - 1);
342 let slot = self.slot(index);
343
344 let mut out = [0u8; PAYLOAD_SIZE];
345
346 ptr::copy_nonoverlapping((*slot).payload.as_ptr(), out.as_mut_ptr(), PAYLOAD_SIZE);
347
348 tail.store(current_tail.wrapping_add(1), Ordering::Release);
349
350 Some(out)
351 }
352 }
353
354 fn stats(&self) -> QueueStats {
355 unsafe {
356 let head = (*self.head()).value.load(Ordering::Acquire);
357 let tail = (*self.tail()).value.load(Ordering::Acquire);
358 let capacity = self.capacity();
359
360 QueueStats {
361 capacity,
362 len: head.wrapping_sub(tail),
363 head,
364 tail,
365 region_size: self.len as u64,
366 }
367 }
368 }
369
370 fn set_producer_pid(&self) {
371 unsafe {
372 (*self.header_mut()).producer_pid = libc::getpid() as u64;
373 }
374 }
375
376 fn set_consumer_pid(&self) {
377 unsafe {
378 (*self.header_mut()).consumer_pid = libc::getpid() as u64;
379 }
380 }
381
382 fn capacity(&self) -> u64 {
383 unsafe { ptr::read_volatile(addr_of!((*self.header()).capacity)) }
384 }
385
386 unsafe fn header(&self) -> *const SharedHeader {
387 self.ptr as *const SharedHeader
388 }
389
390 unsafe fn header_mut(&self) -> *mut SharedHeader {
391 self.ptr as *mut SharedHeader
392 }
393
394 unsafe fn head(&self) -> *mut CounterLine {
395 unsafe { self.ptr.add(HEADER_SIZE) as *mut CounterLine }
396 }
397
398 unsafe fn tail(&self) -> *mut CounterLine {
399 unsafe { self.ptr.add(HEADER_SIZE + COUNTER_SIZE) as *mut CounterLine }
400 }
401
402 unsafe fn slot(&self, index: u64) -> *mut Slot48 {
403 unsafe { self.ptr.add(BASE_SIZE + index as usize * SLOT_SIZE) as *mut Slot48 }
404 }
405}
406
407impl Drop for Mapping {
408 fn drop(&mut self) {
409 unsafe {
410 munmap(self.ptr as *mut c_void, self.len);
411 close(self.fd);
412 }
413 }
414}
415
416pub struct Producer {
420 mapping: Mapping,
421}
422
423impl Producer {
424 pub fn create_or_open(capacity: usize) -> io::Result<Self> {
428 let mapping = Mapping::create_or_open(capacity)?;
429 mapping.set_producer_pid();
430
431 Ok(Self { mapping })
432 }
433
434 pub fn open() -> io::Result<Self> {
436 let mapping = Mapping::open_existing()?;
437 mapping.set_producer_pid();
438
439 Ok(Self { mapping })
440 }
441
442 pub fn push(&self, value: [u8; PAYLOAD_SIZE]) -> Result<(), PushError> {
446 self.mapping.push(value)
447 }
448
449 pub fn capacity(&self) -> u64 {
451 self.mapping.stats().capacity
452 }
453
454 pub fn len(&self) -> u64 {
456 self.mapping.stats().len
457 }
458
459 pub fn is_empty(&self) -> bool {
461 self.len() == 0
462 }
463
464 pub fn stats(&self) -> QueueStats {
466 self.mapping.stats()
467 }
468}
469
470pub struct Consumer {
474 mapping: Mapping,
475}
476
477impl Consumer {
478 pub fn open() -> io::Result<Self> {
480 let mapping = Mapping::open_existing()?;
481 mapping.set_consumer_pid();
482
483 Ok(Self { mapping })
484 }
485
486 pub fn pop(&self) -> Option<[u8; PAYLOAD_SIZE]> {
490 self.mapping.pop()
491 }
492
493 pub fn capacity(&self) -> u64 {
495 self.mapping.stats().capacity
496 }
497
498 pub fn len(&self) -> u64 {
500 self.mapping.stats().len
501 }
502
503 pub fn is_empty(&self) -> bool {
505 self.len() == 0
506 }
507
508 pub fn stats(&self) -> QueueStats {
510 self.mapping.stats()
511 }
512}
513
514pub fn stats() -> io::Result<QueueStats> {
516 Ok(Mapping::open_existing()?.stats())
517}
518
519pub fn unlink() -> io::Result<()> {
523 let name = CString::new(SHM_NAME).unwrap();
524
525 let rc = unsafe { shm_unlink(name.as_ptr()) };
526
527 if rc != 0 {
528 let err = io::Error::last_os_error();
529
530 if err.raw_os_error() == Some(libc::ENOENT) {
531 return Ok(());
532 }
533
534 return Err(err);
535 }
536
537 Ok(())
538}
539
540fn validate_capacity(capacity: usize) -> io::Result<()> {
541 if capacity == 0 {
542 return Err(io::Error::new(
543 io::ErrorKind::InvalidInput,
544 "capacity must be greater than zero",
545 ));
546 }
547
548 if !capacity.is_power_of_two() {
549 return Err(io::Error::new(
550 io::ErrorKind::InvalidInput,
551 "capacity must be a power of two",
552 ));
553 }
554
555 Ok(())
556}
557
558fn region_size_for_capacity(capacity: usize) -> io::Result<usize> {
559 let slots_size = capacity.checked_mul(SLOT_SIZE).ok_or_else(|| {
560 io::Error::new(
561 io::ErrorKind::InvalidInput,
562 "capacity overflows region size",
563 )
564 })?;
565
566 BASE_SIZE
567 .checked_add(slots_size)
568 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "region size overflow"))
569}
570
571fn file_size(fd: i32) -> io::Result<usize> {
572 let mut stat_buf: libc::stat = unsafe { zeroed() };
573
574 let rc = unsafe { fstat(fd, &mut stat_buf) };
575
576 if rc != 0 {
577 return Err(io::Error::last_os_error());
578 }
579
580 if stat_buf.st_size <= 0 {
581 return Err(io::Error::new(
582 io::ErrorKind::InvalidData,
583 "shared memory object has invalid size",
584 ));
585 }
586
587 usize::try_from(stat_buf.st_size).map_err(|_| {
588 io::Error::new(
589 io::ErrorKind::InvalidData,
590 "shared memory object size does not fit usize",
591 )
592 })
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
600 fn capacity_must_be_non_zero() {
601 let err = validate_capacity(0).expect_err("zero capacity is invalid");
602 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
603 }
604
605 #[test]
606 fn capacity_must_be_power_of_two() {
607 let err = validate_capacity(3).expect_err("non-power-of-two capacity is invalid");
608 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
609 }
610
611 #[test]
612 fn power_of_two_capacity_is_valid() {
613 validate_capacity(1024).expect("power-of-two capacity is valid");
614 }
615
616 #[test]
617 fn region_size_calculation_matches_layout() {
618 let capacity = 1024;
619 let expected = size_of::<SharedHeader>()
620 + size_of::<CounterLine>()
621 + size_of::<CounterLine>()
622 + capacity * size_of::<Slot48>();
623
624 assert_eq!(region_size_for_capacity(capacity).unwrap(), expected);
625 }
626
627 #[test]
628 fn slot_layout_is_one_cacheline() {
629 assert_eq!(size_of::<Slot48>(), 64);
630 assert_eq!(PAYLOAD_SIZE, 48);
631 }
632}