use core::cell::Cell;
use core::ffi::c_void;
use core::marker::PhantomData;
use core::mem::size_of;
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
use crate::kernel::stream_buffer::xStreamBufferGenericCreate;
use crate::kernel::stream_buffer::{
configMESSAGE_BUFFER_LENGTH_TYPE, sbTYPE_MESSAGE_BUFFER, vStreamBufferDelete,
xStreamBufferBytesAvailable, xStreamBufferGenericCreateStatic, xStreamBufferIsEmpty,
xStreamBufferIsFull, xStreamBufferNextMessageLengthBytes, xStreamBufferReceive,
xStreamBufferReceiveFromISR, xStreamBufferReset, xStreamBufferResetFromISR, xStreamBufferSend,
xStreamBufferSendFromISR, xStreamBufferSpacesAvailable, StaticStreamBuffer_t,
StreamBufferHandle_t,
};
use crate::sync::task::TaskContext;
use crate::sync::{assert_can_block, assert_task_context};
use crate::types::*;
const MESSAGE_LENGTH_BYTES: usize = size_of::<configMESSAGE_BUFFER_LENGTH_TYPE>();
pub struct MessageBuffer {
handle: StreamBufferHandle_t,
capacity: usize,
_not_sync: PhantomData<Cell<()>>,
}
unsafe impl Send for MessageBuffer {}
pub struct MessageBufferSender<'a> {
handle: StreamBufferHandle_t,
capacity: usize,
_owner: PhantomData<&'a mut MessageBuffer>,
_not_sync: PhantomData<Cell<()>>,
}
unsafe impl Send for MessageBufferSender<'_> {}
pub struct MessageBufferReceiver<'a> {
handle: StreamBufferHandle_t,
capacity: usize,
_owner: PhantomData<&'a mut MessageBuffer>,
_not_sync: PhantomData<Cell<()>>,
}
unsafe impl Send for MessageBufferReceiver<'_> {}
impl MessageBuffer {
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
pub fn new(_context: &TaskContext, size: usize) -> Option<Self> {
assert_task_context("MessageBuffer::new");
if size <= MESSAGE_LENGTH_BYTES || size.checked_add(1).is_none() {
return None;
}
let handle =
unsafe { xStreamBufferGenericCreate(size, 1, sbTYPE_MESSAGE_BUFFER, None, None) };
Self::from_created_handle(handle, size)
}
pub fn new_static(
_context: &TaskContext,
storage: &'static mut [u8],
message_buffer: &'static mut StaticStreamBuffer_t,
) -> Option<Self> {
assert_task_context("MessageBuffer::new_static");
let capacity = storage.len().checked_sub(1)?;
if capacity <= MESSAGE_LENGTH_BYTES {
return None;
}
let handle = unsafe {
xStreamBufferGenericCreateStatic(
storage.len(),
1,
sbTYPE_MESSAGE_BUFFER,
storage.as_mut_ptr(),
message_buffer as *mut StaticStreamBuffer_t,
None,
None,
)
};
Self::from_created_handle(handle, capacity)
}
fn from_created_handle(handle: StreamBufferHandle_t, capacity: usize) -> Option<Self> {
if handle.is_null() {
None
} else {
Some(Self {
handle,
capacity,
_not_sync: PhantomData,
})
}
}
pub fn split(&mut self) -> (MessageBufferSender<'_>, MessageBufferReceiver<'_>) {
(
MessageBufferSender {
handle: self.handle,
capacity: self.capacity,
_owner: PhantomData,
_not_sync: PhantomData,
},
MessageBufferReceiver {
handle: self.handle,
capacity: self.capacity,
_owner: PhantomData,
_not_sync: PhantomData,
},
)
}
pub const fn capacity(&self) -> usize {
self.capacity
}
pub const fn max_message_size(&self) -> usize {
self.capacity - MESSAGE_LENGTH_BYTES
}
pub fn send(&mut self, _context: &TaskContext, data: &[u8]) -> bool {
send_task(self.handle, self.capacity, data, portMAX_DELAY)
}
pub fn try_send(&mut self, _context: &TaskContext, data: &[u8]) -> bool {
send_task(self.handle, self.capacity, data, 0)
}
pub fn send_timeout(&mut self, _context: &TaskContext, data: &[u8], ticks: TickType_t) -> bool {
send_task(self.handle, self.capacity, data, ticks)
}
pub unsafe fn send_from_isr(&mut self, data: &[u8]) -> (bool, bool) {
unsafe { send_isr(self.handle, self.capacity, data) }
}
pub fn receive(&mut self, _context: &TaskContext, output: &mut [u8]) -> usize {
receive_task(self.handle, output, portMAX_DELAY)
}
pub fn try_receive(&mut self, _context: &TaskContext, output: &mut [u8]) -> usize {
receive_task(self.handle, output, 0)
}
pub fn receive_timeout(
&mut self,
_context: &TaskContext,
output: &mut [u8],
ticks: TickType_t,
) -> usize {
receive_task(self.handle, output, ticks)
}
pub unsafe fn receive_from_isr(&mut self, output: &mut [u8]) -> (usize, bool) {
unsafe { receive_isr(self.handle, output) }
}
pub fn next_message_size(&self, _context: &TaskContext) -> usize {
assert_task_context("MessageBuffer::next_message_size");
next_message_size(self.handle)
}
pub fn bytes_used(&self, _context: &TaskContext) -> usize {
assert_task_context("MessageBuffer::bytes_used");
bytes_used(self.handle)
}
pub fn spaces(&self, _context: &TaskContext) -> usize {
assert_task_context("MessageBuffer::spaces");
spaces(self.handle)
}
pub fn is_empty(&self, _context: &TaskContext) -> bool {
assert_task_context("MessageBuffer::is_empty");
is_empty(self.handle)
}
pub fn is_full(&self, _context: &TaskContext) -> bool {
assert_task_context("MessageBuffer::is_full");
is_full(self.handle)
}
pub fn reset(&mut self, _context: &TaskContext) -> bool {
assert_task_context("MessageBuffer::reset");
unsafe { xStreamBufferReset(self.handle) == pdPASS }
}
pub unsafe fn reset_from_isr(&mut self) -> bool {
unsafe { xStreamBufferResetFromISR(self.handle) == pdPASS }
}
pub unsafe fn delete(self, _context: &TaskContext) {
unsafe { vStreamBufferDelete(self.handle) };
}
pub unsafe fn raw_handle(&self) -> StreamBufferHandle_t {
self.handle
}
}
impl MessageBufferSender<'_> {
pub const fn capacity(&self) -> usize {
self.capacity
}
pub const fn max_message_size(&self) -> usize {
self.capacity - MESSAGE_LENGTH_BYTES
}
pub fn send(&mut self, _context: &TaskContext, data: &[u8]) -> bool {
send_task(self.handle, self.capacity, data, portMAX_DELAY)
}
pub fn try_send(&mut self, _context: &TaskContext, data: &[u8]) -> bool {
send_task(self.handle, self.capacity, data, 0)
}
pub fn send_timeout(&mut self, _context: &TaskContext, data: &[u8], ticks: TickType_t) -> bool {
send_task(self.handle, self.capacity, data, ticks)
}
pub unsafe fn send_from_isr(&mut self, data: &[u8]) -> (bool, bool) {
unsafe { send_isr(self.handle, self.capacity, data) }
}
pub fn spaces(&self, _context: &TaskContext) -> usize {
assert_task_context("MessageBufferSender::spaces");
spaces(self.handle)
}
pub fn is_full(&self, _context: &TaskContext) -> bool {
assert_task_context("MessageBufferSender::is_full");
is_full(self.handle)
}
}
impl MessageBufferReceiver<'_> {
pub const fn capacity(&self) -> usize {
self.capacity
}
pub const fn max_message_size(&self) -> usize {
self.capacity - MESSAGE_LENGTH_BYTES
}
pub fn receive(&mut self, _context: &TaskContext, output: &mut [u8]) -> usize {
receive_task(self.handle, output, portMAX_DELAY)
}
pub fn try_receive(&mut self, _context: &TaskContext, output: &mut [u8]) -> usize {
receive_task(self.handle, output, 0)
}
pub fn receive_timeout(
&mut self,
_context: &TaskContext,
output: &mut [u8],
ticks: TickType_t,
) -> usize {
receive_task(self.handle, output, ticks)
}
pub unsafe fn receive_from_isr(&mut self, output: &mut [u8]) -> (usize, bool) {
unsafe { receive_isr(self.handle, output) }
}
pub fn next_message_size(&self, _context: &TaskContext) -> usize {
assert_task_context("MessageBufferReceiver::next_message_size");
next_message_size(self.handle)
}
pub fn bytes_used(&self, _context: &TaskContext) -> usize {
assert_task_context("MessageBufferReceiver::bytes_used");
bytes_used(self.handle)
}
pub fn is_empty(&self, _context: &TaskContext) -> bool {
assert_task_context("MessageBufferReceiver::is_empty");
is_empty(self.handle)
}
}
fn message_fits(capacity: usize, data_len: usize) -> bool {
data_len != 0
&& data_len
.checked_add(MESSAGE_LENGTH_BYTES)
.is_some_and(|required| required <= capacity)
}
fn send_task(
handle: StreamBufferHandle_t,
capacity: usize,
data: &[u8],
ticks: TickType_t,
) -> bool {
assert_can_block("MessageBuffer::send", ticks);
if !message_fits(capacity, data.len()) {
return false;
}
unsafe {
xStreamBufferSend(handle, data.as_ptr().cast::<c_void>(), data.len(), ticks) == data.len()
}
}
fn receive_task(handle: StreamBufferHandle_t, output: &mut [u8], ticks: TickType_t) -> usize {
assert_can_block("MessageBuffer::receive", ticks);
if output.is_empty() {
return 0;
}
unsafe {
xStreamBufferReceive(
handle,
output.as_mut_ptr().cast::<c_void>(),
output.len(),
ticks,
)
}
}
unsafe fn send_isr(handle: StreamBufferHandle_t, capacity: usize, data: &[u8]) -> (bool, bool) {
if !message_fits(capacity, data.len()) {
return (false, false);
}
let mut higher_priority_task_woken = pdFALSE;
let sent = unsafe {
xStreamBufferSendFromISR(
handle,
data.as_ptr().cast::<c_void>(),
data.len(),
&mut higher_priority_task_woken,
)
};
(sent == data.len(), higher_priority_task_woken != pdFALSE)
}
unsafe fn receive_isr(handle: StreamBufferHandle_t, output: &mut [u8]) -> (usize, bool) {
if output.is_empty() {
return (0, false);
}
let mut higher_priority_task_woken = pdFALSE;
let received = unsafe {
xStreamBufferReceiveFromISR(
handle,
output.as_mut_ptr().cast::<c_void>(),
output.len(),
&mut higher_priority_task_woken,
)
};
(received, higher_priority_task_woken != pdFALSE)
}
fn next_message_size(handle: StreamBufferHandle_t) -> usize {
unsafe { xStreamBufferNextMessageLengthBytes(handle) }
}
fn bytes_used(handle: StreamBufferHandle_t) -> usize {
unsafe { xStreamBufferBytesAvailable(handle) }
}
fn spaces(handle: StreamBufferHandle_t) -> usize {
unsafe { xStreamBufferSpacesAvailable(handle) }
}
fn is_empty(handle: StreamBufferHandle_t) -> bool {
unsafe { xStreamBufferIsEmpty(handle) != pdFALSE }
}
fn is_full(handle: StreamBufferHandle_t) -> bool {
unsafe { xStreamBufferIsFull(handle) != pdFALSE }
}
#[cfg(test)]
mod tests {
use super::*;
use std::boxed::Box;
use std::vec;
fn static_messages(context: &TaskContext, storage_len: usize) -> Option<MessageBuffer> {
let storage = Box::leak(vec![0_u8; storage_len].into_boxed_slice());
let control = Box::leak(Box::new(StaticStreamBuffer_t::new()));
MessageBuffer::new_static(context, storage, control)
}
#[test]
fn rejects_storage_that_cannot_hold_a_nonempty_message() {
crate::port::test_port_reset();
let context = unsafe { TaskContext::assume() };
assert!(static_messages(&context, 0).is_none());
assert!(static_messages(&context, MESSAGE_LENGTH_BYTES + 1).is_none());
assert!(static_messages(&context, MESSAGE_LENGTH_BYTES + 2).is_some());
}
#[test]
fn validates_message_size_before_calling_the_kernel() {
crate::port::test_port_reset();
let context = unsafe { TaskContext::assume() };
let mut messages = static_messages(&context, 16).expect("valid message buffer");
assert_eq!(messages.capacity(), 15);
assert_eq!(messages.max_message_size(), 15 - MESSAGE_LENGTH_BYTES);
assert!(!messages.try_send(&context, &[]));
assert!(!messages.try_send(&context, &[0_u8; 16]));
assert!(messages.try_send(&context, b"hello"));
assert_eq!(messages.next_message_size(&context), 5);
let mut too_small = [0_u8; 4];
assert_eq!(messages.try_receive(&context, &mut too_small), 0);
assert_eq!(messages.next_message_size(&context), 5);
let mut output = [0_u8; 5];
assert_eq!(messages.try_receive(&context, &mut output), 5);
assert_eq!(&output, b"hello");
}
#[test]
fn split_endpoints_preserve_message_boundaries() {
crate::port::test_port_reset();
let context = unsafe { TaskContext::assume() };
let mut messages = static_messages(&context, 32).expect("valid message buffer");
let (mut sender, mut receiver) = messages.split();
assert!(sender.try_send(&context, b"first"));
assert!(sender.try_send(&context, b"second"));
let mut output = [0_u8; 8];
assert_eq!(receiver.try_receive(&context, &mut output), 5);
assert_eq!(&output[..5], b"first");
assert_eq!(receiver.try_receive(&context, &mut output), 6);
assert_eq!(&output[..6], b"second");
}
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
#[test]
fn rejects_dynamic_size_overflow() {
crate::port::test_port_reset();
let context = unsafe { TaskContext::assume() };
assert!(MessageBuffer::new(&context, usize::MAX).is_none());
}
}