use core::cell::UnsafeCell;
use core::fmt;
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
use core::ptr::NonNull;
use core::sync::atomic::{AtomicU32, Ordering};
use crate::config::PACKET_BUF_SIZE;
use crate::meta::PacketMeta;
#[cfg(not(test))]
const PACKET_BUF_COUNT: usize = crate::config::PACKET_BUF_COUNT;
#[cfg(test)]
const PACKET_BUF_COUNT: usize = if crate::config::PACKET_BUF_COUNT > 1024 {
crate::config::PACKET_BUF_COUNT
} else {
1024
};
const BITMAP_WORDS: usize = PACKET_BUF_COUNT.div_ceil(32);
cfg_select! {
feature = "packet-buf-align-32" => { #[repr(C, align(32))] struct Data([u8; PACKET_BUF_SIZE]); }
feature = "packet-buf-align-16" => { #[repr(C, align(16))] struct Data([u8; PACKET_BUF_SIZE]); }
feature = "packet-buf-align-8" => { #[repr(C, align(8))] struct Data([u8; PACKET_BUF_SIZE]); }
feature = "packet-buf-align-4" => { #[repr(C, align(4))] struct Data([u8; PACKET_BUF_SIZE]); }
feature = "packet-buf-align-2" => { #[repr(C, align(2))] struct Data([u8; PACKET_BUF_SIZE]); }
_ => { #[repr(C, align(1))] struct Data([u8; PACKET_BUF_SIZE]); }
}
impl Deref for Data {
type Target = [u8; PACKET_BUF_SIZE];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Data {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
struct PacketBufInner {
headroom: u16,
len: u16,
meta: PacketMeta,
data: Data,
}
struct Pool {
used: [AtomicU32; BITMAP_WORDS],
slots: [UnsafeCell<MaybeUninit<PacketBufInner>>; PACKET_BUF_COUNT],
}
unsafe impl Sync for Pool {}
static POOL: Pool = Pool {
used: [const { AtomicU32::new(0) }; BITMAP_WORDS],
slots: [const { UnsafeCell::new(MaybeUninit::zeroed()) }; PACKET_BUF_COUNT],
};
#[cfg(target_has_atomic = "32")]
#[inline(never)]
fn alloc_slot() -> Option<usize> {
for (w, word) in POOL.used.iter().enumerate() {
let mut cur = word.load(Ordering::Relaxed);
loop {
let bit = cur.trailing_ones() as usize;
if bit >= 32 {
break;
}
let index = w * 32 + bit;
if index >= PACKET_BUF_COUNT {
return None;
}
match word.compare_exchange_weak(cur, cur | (1 << bit), Ordering::Acquire, Ordering::Relaxed) {
Ok(_) => return Some(index),
Err(actual) => cur = actual,
}
}
}
None
}
#[cfg(target_has_atomic = "32")]
#[inline(never)]
fn free_slot(index: usize) {
POOL.used[index / 32].fetch_and(!(1 << (index % 32)), Ordering::Release);
}
#[cfg(not(target_has_atomic = "32"))]
fn alloc_slot() -> Option<usize> {
critical_section::with(|_| {
for (w, word) in POOL.used.iter().enumerate() {
let cur = word.load(Ordering::Acquire);
let bit = cur.trailing_ones() as usize;
if bit >= 32 {
continue;
}
let index = w * 32 + bit;
if index >= PACKET_BUF_COUNT {
return None;
}
word.store(cur | (1 << bit), Ordering::Relaxed);
return Some(index);
}
None
})
}
#[cfg(not(target_has_atomic = "32"))]
fn free_slot(index: usize) {
critical_section::with(|_| {
let word = &POOL.used[index / 32];
word.store(word.load(Ordering::Relaxed) & !(1 << (index % 32)), Ordering::Release);
})
}
pub struct PacketBuf {
inner: NonNull<PacketBufInner>,
}
unsafe impl Send for PacketBuf {}
unsafe impl Sync for PacketBuf {}
impl PacketBuf {
pub fn try_new() -> Option<Self> {
let index = alloc_slot()?;
let ptr = POOL.slots[index].get().cast::<PacketBufInner>();
unsafe {
(&raw mut (*ptr).headroom).write(0);
(&raw mut (*ptr).len).write(0);
(&raw mut (*ptr).meta).write(PacketMeta::default());
#[cfg(test)]
(*ptr).data.fill(0xa5);
}
Some(Self {
inner: unsafe { NonNull::new_unchecked(ptr) },
})
}
#[inline]
fn inner(&self) -> &PacketBufInner {
unsafe { self.inner.as_ref() }
}
#[inline]
fn inner_mut(&mut self) -> &mut PacketBufInner {
unsafe { self.inner.as_mut() }
}
pub fn meta(&self) -> PacketMeta {
self.inner().meta
}
pub fn meta_mut(&mut self) -> &mut PacketMeta {
&mut self.inner_mut().meta
}
pub fn set_meta(&mut self, meta: PacketMeta) {
self.inner_mut().meta = meta;
}
pub const fn capacity(&self) -> usize {
PACKET_BUF_SIZE
}
pub fn headroom(&self) -> usize {
self.inner().headroom as usize
}
pub fn len(&self) -> usize {
self.inner().len as usize
}
pub fn is_empty(&self) -> bool {
self.inner().len == 0
}
pub fn tailroom(&self) -> usize {
PACKET_BUF_SIZE - self.headroom() - self.len()
}
pub fn reserve(&mut self, headroom: usize) {
assert!(self.inner().len == 0);
assert!(headroom <= PACKET_BUF_SIZE);
self.inner_mut().headroom = headroom as u16;
}
pub fn push_front(&mut self, n: usize) {
assert!(n <= self.headroom());
let inner = self.inner_mut();
inner.headroom -= n as u16;
inner.len += n as u16;
}
pub fn pull_front(&mut self, n: usize) {
assert!(n <= self.len());
let inner = self.inner_mut();
inner.headroom += n as u16;
inner.len -= n as u16;
}
pub fn ensure_headroom(&mut self, headroom: usize) -> bool {
if self.headroom() >= headroom {
return true;
}
let inner = self.inner_mut();
let len = inner.len as usize;
if headroom + len > PACKET_BUF_SIZE {
return false;
}
let old = inner.headroom as usize;
inner.data.copy_within(old..old + len, headroom);
inner.headroom = headroom as u16;
true
}
pub fn set_len(&mut self, len: usize) {
assert!(self.headroom() + len <= PACKET_BUF_SIZE);
self.inner_mut().len = len as u16;
}
pub fn storage_mut(&mut self) -> &mut [u8] {
&mut self.inner_mut().data[..]
}
}
impl Drop for PacketBuf {
#[inline(never)] fn drop(&mut self) {
let base = POOL.slots.as_ptr() as usize;
let index =
(self.inner.as_ptr() as usize - base) / core::mem::size_of::<UnsafeCell<MaybeUninit<PacketBufInner>>>();
free_slot(index);
}
}
impl Deref for PacketBuf {
type Target = [u8];
fn deref(&self) -> &Self::Target {
let inner = self.inner();
let start = inner.headroom as usize;
let end = start + inner.len as usize;
&inner.data[start..end]
}
}
impl DerefMut for PacketBuf {
fn deref_mut(&mut self) -> &mut Self::Target {
let inner = self.inner_mut();
let start = inner.headroom as usize;
let end = start + inner.len as usize;
&mut inner.data[start..end]
}
}
impl fmt::Debug for PacketBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PacketBuf")
.field("headroom", &self.headroom())
.field("len", &self.len())
.finish()
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for PacketBuf {
fn format(&self, f: defmt::Formatter<'_>) {
defmt::write!(f, "PacketBuf {{ headroom: {}, len: {} }}", self.headroom(), self.len());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::PACKET_BUF_ALIGN;
#[test]
fn push_pull() {
let mut buf = PacketBuf::try_new().unwrap();
assert_eq!(buf.len(), 0);
assert_eq!(buf.headroom(), 0);
assert_eq!(buf.tailroom(), PACKET_BUF_SIZE);
buf.reserve(42);
assert_eq!(buf.headroom(), 42);
buf.set_len(100);
assert_eq!(buf.len(), 100);
assert_eq!(buf.tailroom(), PACKET_BUF_SIZE - 142);
buf.fill(0xaa);
buf.push_front(20);
assert_eq!(buf.headroom(), 22);
assert_eq!(buf.len(), 120);
assert_eq!(buf[20], 0xaa);
buf.pull_front(20);
assert_eq!(buf.headroom(), 42);
assert_eq!(buf.len(), 100);
assert_eq!(buf[0], 0xaa);
}
#[test]
fn ensure_headroom() {
let mut buf = PacketBuf::try_new().unwrap();
buf.reserve(10);
buf.set_len(4);
buf.copy_from_slice(&[1, 2, 3, 4]);
assert!(buf.ensure_headroom(4));
assert_eq!(buf.headroom(), 10);
assert_eq!(&*buf, &[1, 2, 3, 4]);
assert!(buf.ensure_headroom(20));
assert_eq!(buf.headroom(), 20);
assert_eq!(buf.len(), 4);
assert_eq!(&*buf, &[1, 2, 3, 4]);
assert!(buf.ensure_headroom(22));
assert_eq!(&*buf, &[1, 2, 3, 4]);
assert!(!buf.ensure_headroom(PACKET_BUF_SIZE - 3));
assert_eq!(buf.headroom(), 22);
assert_eq!(&*buf, &[1, 2, 3, 4]);
assert!(buf.ensure_headroom(PACKET_BUF_SIZE - 4));
assert_eq!(&*buf, &[1, 2, 3, 4]);
}
#[test]
#[should_panic]
fn push_beyond_headroom() {
let mut buf = PacketBuf::try_new().unwrap();
buf.push_front(1);
}
#[test]
fn storage_is_dma_shaped() {
let mut buf = PacketBuf::try_new().unwrap();
assert_eq!(buf.storage_mut().as_ptr() as usize % PACKET_BUF_ALIGN, 0);
assert_eq!(buf.storage_mut().len() % PACKET_BUF_ALIGN, 0);
assert!(buf.storage_mut().len() >= PACKET_BUF_SIZE);
}
#[test]
fn fresh_buffer_is_reset() {
let mut buf = PacketBuf::try_new().unwrap();
buf.reserve(100);
buf.set_len(200);
buf.fill(0xff);
drop(buf);
let buf = PacketBuf::try_new().unwrap();
assert_eq!(buf.len(), 0);
assert_eq!(buf.headroom(), 0);
assert_eq!(buf.meta(), PacketMeta::default());
}
#[cfg(feature = "packetmeta-id")]
#[test]
fn meta_travels_with_the_buffer() {
let mut buf = PacketBuf::try_new().unwrap();
assert_eq!(buf.meta(), PacketMeta::default());
buf.meta_mut().id = 0xdead_beef;
buf.reserve(20);
buf.set_len(10);
buf.push_front(20);
buf.pull_front(4);
assert_eq!(buf.meta().id, 0xdead_beef);
buf.set_meta(PacketMeta::default());
assert_eq!(buf.meta().id, 0);
}
}