#![no_std]
#![deny(
clippy::all,
clippy::cargo,
clippy::nursery,
clippy::must_use_candidate,
clippy::undocumented_unsafe_blocks,
missing_debug_implementations,
missing_docs,
rustdoc::all
)]
#![allow(clippy::multiple_crate_versions)]
#[cfg_attr(test, macro_use)]
#[cfg(test)]
extern crate std;
#[cfg(feature = "alloc")]
extern crate alloc;
#[doc(hidden)]
pub mod test_utils;
#[cfg(feature = "alloc")]
mod boxed;
mod bytes_ref;
mod iter;
mod raw;
mod tag;
#[cfg(feature = "alloc")]
pub use boxed::{clone_dyn, new_boxed};
pub use bytes_ref::BytesRef;
pub use iter::TagIter;
pub use tag::{MaybeDynSized, Tag};
use core::fmt::Debug;
use core::ptr::NonNull;
use core::slice;
use thiserror::Error;
pub const ALIGNMENT: usize = 8;
pub unsafe trait Header: Clone + Sized + PartialEq + Eq + Debug {
#[must_use]
fn total_size(&self) -> usize;
#[must_use]
fn payload_len(&self) -> usize {
let total_size = self.total_size();
assert!(total_size >= size_of::<Self>());
total_size - size_of::<Self>()
}
fn set_size(&mut self, total_size: usize);
}
#[derive(Debug, PartialEq, Eq, ptr_meta::Pointee)]
#[repr(C, align(8))]
pub struct DynSizedStructure<H: Header> {
header: H,
payload: [u8],
}
impl<H: Header> DynSizedStructure<H> {
pub fn ref_from_bytes(bytes: BytesRef<'_, H>) -> Result<&Self, MemoryError> {
let ptr = bytes.as_ptr().cast::<H>();
let hdr = unsafe { &*ptr };
let total_size = hdr.total_size();
let header_size = size_of::<H>();
if total_size < header_size {
return Err(MemoryError::SizeInsufficient(total_size, header_size));
}
if total_size > bytes.len() {
return Err(MemoryError::InvalidReportedTotalSize(
total_size,
bytes.len(),
));
}
let payload_len = total_size - header_size;
let dst_size = payload_len;
let ptr = ptr_meta::from_raw_parts(ptr.cast(), dst_size);
let reference = unsafe { &*ptr };
Ok(reference)
}
pub fn ref_from_slice(bytes: &[u8]) -> Result<&Self, MemoryError> {
let bytes = BytesRef::<H>::try_from(bytes)?;
Self::ref_from_bytes(bytes)
}
pub unsafe fn ref_from_ptr<'a>(ptr: NonNull<H>) -> Result<&'a Self, MemoryError> {
let ptr = ptr.as_ptr().cast_const();
if ptr.cast::<u8>().align_offset(ALIGNMENT) != 0 {
return Err(MemoryError::WrongAlignment);
}
let hdr = unsafe { &*ptr };
let total_size = hdr.total_size();
let header_size = size_of::<H>();
if total_size < header_size {
return Err(MemoryError::SizeInsufficient(total_size, header_size));
}
let slice = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), total_size) };
Self::ref_from_slice(slice)
}
pub const fn header(&self) -> &H {
&self.header
}
pub const fn payload(&self) -> &[u8] {
&self.payload
}
pub fn cast<T: MaybeDynSized<Header = H> + ?Sized>(&self) -> &T
where
T::Metadata: Default,
{
let base_ptr = &raw const *self;
assert!(T::BASE_SIZE >= size_of::<H>());
assert!(
size_of_val(self) >= T::BASE_SIZE,
"source is too small to be cast to the target type"
);
let t_dst_size = T::dst_len(self.header());
let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
let t_ref = unsafe { &*t_ptr };
assert_eq!(size_of_val(self), size_of_val(t_ref));
t_ref
}
}
pub fn validate_tag_sequence(
bytes: &[u8],
mut is_end_tag: impl FnMut(&[u8]) -> bool,
) -> Result<bool, MemoryError> {
const TAG_HEADER_SIZE: usize = size_of::<u32>() * 2;
if bytes.as_ptr().align_offset(ALIGNMENT) != 0 {
return Err(MemoryError::WrongAlignment);
}
let mut offset = 0;
while offset < bytes.len() {
let remaining = bytes.len() - offset;
if remaining < TAG_HEADER_SIZE {
return Err(MemoryError::ShorterThanHeader);
}
let tag = &bytes[offset..];
let total_size =
u32::from_le_bytes(tag[4..8].try_into().expect("slice has exactly 4 bytes")) as usize;
if total_size < TAG_HEADER_SIZE {
return Err(MemoryError::SizeInsufficient(total_size, TAG_HEADER_SIZE));
}
let padded_size = total_size
.checked_add(ALIGNMENT - 1)
.map(|size| size & !(ALIGNMENT - 1))
.ok_or(MemoryError::InvalidReportedTotalSize(total_size, remaining))?;
if padded_size > remaining {
return Err(MemoryError::InvalidReportedTotalSize(
padded_size,
remaining,
));
}
offset += padded_size;
if is_end_tag(&tag[..total_size]) {
if offset == bytes.len() {
return Ok(true);
}
return Err(MemoryError::InvalidReportedTotalSize(offset, bytes.len()));
}
}
Ok(false)
}
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Error)]
pub enum MemoryError {
#[error("memory points to null")]
Null,
#[error("memory is not properly aligned")]
WrongAlignment,
#[error("memory range is shorter than the size of the header structure")]
ShorterThanHeader,
#[error("memory range is shorter than the size of the header structure")]
SizeInsufficient(usize , usize ),
#[error("memory is missing required padding")]
MissingPadding,
#[error(
"header reports an invalid total size of 0x{0:x} while only 0x{1:x} bytes are available"
)]
InvalidReportedTotalSize(usize , usize ),
}
#[must_use]
pub const fn increase_to_alignment(size: usize) -> usize {
let mask = ALIGNMENT - 1;
(size + mask) & !mask
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{AlignedBytes, DummyTestHeader};
use core::borrow::Borrow;
#[test]
fn test_increase_to_alignment() {
assert_eq!(increase_to_alignment(0), 0);
assert_eq!(increase_to_alignment(1), 8);
assert_eq!(increase_to_alignment(7), 8);
assert_eq!(increase_to_alignment(8), 8);
assert_eq!(increase_to_alignment(9), 16);
}
#[test]
fn test_cast_generic_tag_to_sized_tag() {
#[repr(C)]
struct CustomSizedTag {
tag_header: DummyTestHeader,
a: u32,
b: u32,
}
unsafe impl MaybeDynSized for CustomSizedTag {
type Header = DummyTestHeader;
const BASE_SIZE: usize = size_of::<Self>();
fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
}
let bytes = AlignedBytes([
0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8,
16, 0, 0, 0,
0xef, 0xbe, 0xad, 0xde,
0x37, 0x13, 0x37, 0x13,
]);
let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
let custom_tag = tag.cast::<CustomSizedTag>();
assert_eq!(size_of_val(custom_tag), 16);
assert_eq!(custom_tag.a, 0xdead_beef);
assert_eq!(custom_tag.b, 0x1337_1337);
}
#[test]
fn test_cast_generic_tag_to_self() {
#[rustfmt::skip]
let bytes = AlignedBytes::new(
[
0x37, 0x13, 0, 0,
18, 0, 0, 0,
0, 1, 2, 3,
4, 5, 6, 7,
8, 9,
0, 0, 0, 0, 0, 0
],
);
let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
let tag = tag.cast::<DynSizedStructure<DummyTestHeader>>();
assert_eq!(tag.header().typ(), 0x1337);
assert_eq!(tag.header().size(), 18);
}
#[test]
fn test_ref_from_ptr_rejects_misaligned() {
let bytes = AlignedBytes([0x37, 0x13, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let misaligned = (&raw const bytes.0[4]).cast::<DummyTestHeader>();
let ptr = NonNull::new(misaligned.cast_mut()).unwrap();
let result = unsafe { DynSizedStructure::<DummyTestHeader>::ref_from_ptr(ptr) };
assert_eq!(result, Err(MemoryError::WrongAlignment));
}
#[test]
#[should_panic(expected = "source is too small to be cast to the target type")]
fn test_cast_rejects_too_small_source() {
#[repr(C, align(8))]
struct CustomSizedTag {
tag_header: DummyTestHeader,
a: u32,
b: u32,
}
unsafe impl MaybeDynSized for CustomSizedTag {
type Header = DummyTestHeader;
const BASE_SIZE: usize = size_of::<Self>();
fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
}
let bytes = AlignedBytes([0x37, 0x13, 0, 0, 8, 0, 0, 0]);
let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
let _ = tag.cast::<CustomSizedTag>();
}
#[test]
fn test_ref_from_slice_rejects_oversized_header() {
#[rustfmt::skip]
let bytes = AlignedBytes::new(
[
0x37, 0x13, 0, 0,
24, 0, 0, 0,
0, 1, 2, 3,
4, 5, 6, 7,
],
);
assert_eq!(
DynSizedStructure::<DummyTestHeader>::ref_from_slice(bytes.borrow()),
Err(MemoryError::InvalidReportedTotalSize(24, 16))
);
}
#[test]
fn test_ref_from_slice_rejects_too_small_reported_size() {
#[rustfmt::skip]
let bytes = AlignedBytes::new(
[
0x37, 0x13, 0, 0,
4, 0, 0, 0,
0, 1, 2, 3,
0, 0, 0, 0,
],
);
assert_eq!(
DynSizedStructure::<DummyTestHeader>::ref_from_slice(bytes.borrow()),
Err(MemoryError::SizeInsufficient(4, 8))
);
}
}