use alloc::vec::Vec;
use core::fmt;
use core::mem::{align_of, size_of};
use core::ptr::NonNull;
use crate::layout::{LayoutError, RegionSetLayout, RoleId, ValidatedRegionLayout};
use crate::slot::{
AcknowledgementCell, AcknowledgementObservation, AcknowledgementReader, AcknowledgementWriter,
ReaderSlot, SlotError, SlotMetadata, WriterSlot,
};
pub unsafe trait ReadOnlyMapping {
fn base(&self) -> NonNull<u8>;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub unsafe trait SoleWriterMapping {
fn base(&self) -> NonNull<u8>;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BindingError {
MappingSizeMismatch {
expected: usize,
actual: usize,
},
MisalignedRecord,
Layout(LayoutError),
Slot(SlotError),
AllocationFailed,
PayloadLengthOverflow,
DestinationTooSmall {
required: usize,
provided: usize,
},
TopologyMismatch,
MissingRoute {
target: RoleId,
slot: u32,
},
}
impl fmt::Display for BindingError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "mapping binding failed: {self:?}")
}
}
#[cfg(feature = "std")]
impl std::error::Error for BindingError {}
impl From<LayoutError> for BindingError {
fn from(value: LayoutError) -> Self {
Self::Layout(value)
}
}
impl From<SlotError> for BindingError {
fn from(value: SlotError) -> Self {
Self::Slot(value)
}
}
unsafe fn volatile_copy(source: *const u8, destination: *mut u8, len: usize) {
for index in 0..len {
unsafe {
destination
.add(index)
.write(core::ptr::read_volatile(source.add(index)));
}
}
}
pub struct ReaderRegion<M> {
mapping: M,
layout: ValidatedRegionLayout,
topology: RegionSetLayout,
}
impl<M: ReadOnlyMapping> ReaderRegion<M> {
pub fn new(
mapping: M,
layout: ValidatedRegionLayout,
topology: RegionSetLayout,
) -> Result<Self, (M, BindingError)> {
if let Err(error) = validate_mapping_size(mapping.len(), &layout) {
return Err((mapping, error));
}
if let Err(error) = validate_topology(&layout, &topology) {
return Err((mapping, error));
}
Ok(Self {
mapping,
layout,
topology,
})
}
pub fn slot(&self, slot: u32) -> Result<ReaderSlot<'_>, BindingError> {
let binding = self.layout.reader_slot_binding(slot)?;
let range = self.layout.slot_range(slot)?;
let header = record::<SlotMetadata, _>(
&self.mapping,
self.mapping.base(),
range.start,
range.len(),
)?;
Ok(unsafe { ReaderSlot::bind(header, binding) }?)
}
pub fn copy_payload(&self, slot: u32, expected_sequence: u64) -> Result<Vec<u8>, BindingError> {
let observation = self.slot(slot)?.observe(expected_sequence)?;
let range = self
.layout
.slot_payload_range(slot, observation.payload_len())?;
let mut owned = Vec::<u8>::new();
owned
.try_reserve_exact(range.len())
.map_err(|_| BindingError::AllocationFailed)?;
unsafe {
owned.set_len(range.len());
volatile_copy(
self.mapping.base().as_ptr().add(range.start),
owned.as_mut_ptr(),
range.len(),
);
}
self.slot(slot)?.recheck(observation)?;
Ok(owned)
}
pub fn copy_payload_into(
&self,
slot: u32,
expected_sequence: u64,
destination: &mut [u8],
) -> Result<usize, BindingError> {
let observation = self.slot(slot)?.observe(expected_sequence)?;
let range = self
.layout
.slot_payload_range(slot, observation.payload_len())?;
if destination.len() < range.len() {
return Err(BindingError::DestinationTooSmall {
required: range.len(),
provided: destination.len(),
});
}
unsafe {
volatile_copy(
self.mapping.base().as_ptr().add(range.start),
destination.as_mut_ptr(),
range.len(),
);
}
self.slot(slot)?.recheck(observation)?;
Ok(range.len())
}
pub fn into_mapping(self) -> M {
self.mapping
}
pub fn acknowledgement(
&self,
target: RoleId,
slot: u32,
) -> Result<AcknowledgementReader<'_>, BindingError> {
let route = self
.topology
.acknowledgement_route(target, slot)
.ok_or(BindingError::MissingRoute { target, slot })?;
let binding = self.layout.acknowledgement_reader_binding(route)?;
let range = self.layout.acknowledgement_range(route.cell_index())?;
let cell = record::<AcknowledgementCell, _>(
&self.mapping,
self.mapping.base(),
range.start,
range.len(),
)?;
Ok(unsafe { AcknowledgementReader::bind(cell, binding) })
}
}
pub struct WriterRegion<M> {
mapping: M,
layout: ValidatedRegionLayout,
topology: RegionSetLayout,
}
impl<M: SoleWriterMapping> WriterRegion<M> {
pub fn new(
mapping: M,
layout: ValidatedRegionLayout,
topology: RegionSetLayout,
) -> Result<Self, (M, BindingError)> {
if let Err(error) = validate_mapping_size(mapping.len(), &layout) {
return Err((mapping, error));
}
if let Err(error) = validate_topology(&layout, &topology) {
return Err((mapping, error));
}
Ok(Self {
mapping,
layout,
topology,
})
}
pub fn into_mapping(self) -> M {
self.mapping
}
pub fn slot(&mut self, slot: u32) -> Result<WriterSlot<'_>, BindingError> {
let target = self.layout.role();
let route = self
.topology
.acknowledgement_route(target, slot)
.ok_or(BindingError::MissingRoute { target, slot })?;
let binding = self.layout.writer_slot_binding(route)?;
let range = self.layout.slot_range(route.slot_index())?;
let header = record::<SlotMetadata, _>(
&self.mapping,
self.mapping.base(),
range.start,
range.len(),
)?;
Ok(unsafe { WriterSlot::bind(header, binding) }?)
}
pub fn publish(
&mut self,
slot: u32,
sequence: u64,
acknowledgement: Option<AcknowledgementObservation>,
payload: &[u8],
) -> Result<(), BindingError> {
let payload_len =
u32::try_from(payload.len()).map_err(|_| BindingError::PayloadLengthOverflow)?;
let range = self.layout.slot_payload_range(slot, payload_len)?;
let base = self.mapping.base();
let mut bound_slot = self.slot(slot)?;
let reservation = bound_slot.prepare_publish(sequence, acknowledgement)?;
unsafe {
core::ptr::copy_nonoverlapping(
payload.as_ptr(),
base.as_ptr().add(range.start),
payload.len(),
);
}
reservation.publish(payload_len)?;
Ok(())
}
pub fn acknowledgement(
&mut self,
target: RoleId,
slot: u32,
) -> Result<AcknowledgementWriter<'_>, BindingError> {
let route = self
.topology
.acknowledgement_route(target, slot)
.ok_or(BindingError::MissingRoute { target, slot })?;
let binding = self.layout.acknowledgement_writer_binding(route)?;
let range = self.layout.acknowledgement_range(route.cell_index())?;
let cell = record::<AcknowledgementCell, _>(
&self.mapping,
self.mapping.base(),
range.start,
range.len(),
)?;
Ok(unsafe { AcknowledgementWriter::bind(cell, binding) })
}
}
fn validate_mapping_size(
actual: usize,
layout: &ValidatedRegionLayout,
) -> Result<(), BindingError> {
if actual == layout.mapping_size() {
Ok(())
} else {
Err(BindingError::MappingSizeMismatch {
expected: layout.mapping_size(),
actual,
})
}
}
fn validate_topology(
layout: &ValidatedRegionLayout,
topology: &RegionSetLayout,
) -> Result<(), BindingError> {
if layout.matches_topology(topology) {
Ok(())
} else {
Err(BindingError::TopologyMismatch)
}
}
fn record<T, M>(
_owner: &M,
base: NonNull<u8>,
offset: usize,
available: usize,
) -> Result<&T, BindingError> {
if available < size_of::<T>() {
return Err(BindingError::Layout(LayoutError::RangeOutOfBounds));
}
let pointer = unsafe { base.as_ptr().add(offset) }.cast::<T>();
if !(pointer as usize).is_multiple_of(align_of::<T>()) {
return Err(BindingError::MisalignedRecord);
}
Ok(unsafe { &*pointer })
}
#[cfg(test)]
#[path = "mapping_test.rs"]
mod tests;