use core::cell::UnsafeCell;
use core::net::{Ipv4Addr, SocketAddrV4};
use core::sync::atomic::{AtomicBool, AtomicU16, AtomicU32, AtomicUsize, Ordering};
pub struct RxSlot<const CAP: usize> {
has_datagram: AtomicBool,
local_port: AtomicU16,
src_addr: AtomicU32,
src_port: AtomicU16,
len: AtomicUsize,
data: UnsafeCell<[u8; CAP]>,
}
unsafe impl<const CAP: usize> Sync for RxSlot<CAP> {}
impl<const CAP: usize> RxSlot<CAP> {
const fn new() -> Self {
Self {
has_datagram: AtomicBool::new(false),
local_port: AtomicU16::new(0),
src_addr: AtomicU32::new(0),
src_port: AtomicU16::new(0),
len: AtomicUsize::new(0),
data: UnsafeCell::new([0u8; CAP]),
}
}
}
pub struct RxMailbox<const SLOTS: usize, const CAP: usize> {
slots: [RxSlot<CAP>; SLOTS],
}
impl<const SLOTS: usize, const CAP: usize> Default for RxMailbox<SLOTS, CAP> {
fn default() -> Self {
Self::new()
}
}
impl<const SLOTS: usize, const CAP: usize> RxMailbox<SLOTS, CAP> {
#[must_use]
pub const fn new() -> Self {
Self {
slots: [const { RxSlot::new() }; SLOTS],
}
}
#[must_use]
pub const fn capacity(&self) -> usize {
CAP
}
pub unsafe fn push(
&self,
local_port: u16,
src_addr: u32,
src_port: u16,
buf: *const u8,
len: usize,
) -> bool {
for slot in &self.slots {
if slot.has_datagram.load(Ordering::Acquire) {
continue;
}
let dst = unsafe { &mut *slot.data.get() };
let n = if len < CAP { len } else { CAP };
unsafe { core::ptr::copy_nonoverlapping(buf, dst.as_mut_ptr(), n) };
slot.len.store(n, Ordering::Release);
slot.src_addr.store(src_addr, Ordering::Release);
slot.src_port.store(src_port, Ordering::Release);
slot.local_port.store(local_port, Ordering::Release);
slot.has_datagram.store(true, Ordering::Release);
return true;
}
false
}
pub fn take(&self, port: u16, out: &mut [u8]) -> Option<(usize, SocketAddrV4, bool)> {
for slot in &self.slots {
if !slot.has_datagram.load(Ordering::Acquire) {
continue;
}
if slot.local_port.load(Ordering::Acquire) != port {
continue;
}
let src_addr = slot.src_addr.load(Ordering::Acquire);
let src_port = slot.src_port.load(Ordering::Acquire);
let datagram_len = slot.len.load(Ordering::Acquire);
let copy_len = datagram_len.min(out.len());
unsafe {
let src_ptr = (*slot.data.get()).as_ptr();
core::ptr::copy_nonoverlapping(src_ptr, out.as_mut_ptr(), copy_len);
}
slot.has_datagram.store(false, Ordering::Release);
let src = SocketAddrV4::new(Ipv4Addr::from(src_addr.to_be_bytes()), src_port);
return Some((copy_len, src, datagram_len > copy_len));
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_then_take_round_trips_port_and_payload() {
let mb: RxMailbox<2, 16> = RxMailbox::new();
let payload = [1u8, 2, 3, 4];
let src_addr = u32::from_be_bytes([192, 0, 2, 7]);
assert!(unsafe { mb.push(30490, src_addr, 40000, payload.as_ptr(), payload.len()) });
let mut buf = [0u8; 16];
assert!(mb.take(10000, &mut buf).is_none());
let (n, src, trunc) = mb.take(30490, &mut buf).expect("datagram for port");
assert_eq!(&buf[..n], &payload);
assert_eq!(src, SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 7), 40000));
assert!(!trunc);
assert!(mb.take(30490, &mut buf).is_none());
}
#[test]
fn full_pool_drops() {
let mb: RxMailbox<1, 8> = RxMailbox::new();
let d = [9u8; 4];
assert!(unsafe { mb.push(1, 0, 0, d.as_ptr(), d.len()) });
assert!(!unsafe { mb.push(1, 0, 0, d.as_ptr(), d.len()) });
}
#[test]
fn take_into_short_buffer_sets_truncated() {
let mb: RxMailbox<1, 8> = RxMailbox::new();
let d = [1u8, 2, 3];
assert!(unsafe { mb.push(1, 0, 0, d.as_ptr(), d.len()) });
let mut buf = [0u8; 2];
let (n, _src, trunc) = mb.take(1, &mut buf).unwrap();
assert_eq!(n, 2);
assert!(
trunc,
"3-byte datagram into 2-byte buffer must flag truncation"
);
}
}