#![feature(
allocator_api,
maybe_uninit_slice,
offset_of_enum,
ptr_alignment_type,
ptr_metadata
)]
use core::fmt::{self, Debug, Formatter};
use core::mem::MaybeUninit;
use core::ops::Range;
use core::ptr::{Alignment, read};
use core::str::Utf8Error;
use aligned_alloc::{AlignedAlloc, new_uninit_boxed_slice};
#[macro_use]
mod _macros;
pub mod aligned_alloc;
pub mod boxed;
mod impls;
pub mod list;
pub mod option;
pub mod primitive;
pub mod result;
pub mod string;
pub mod tuple;
pub mod util;
#[cfg(test)]
pub mod test;
pub use nibblecode_derive::Serialize;
use util::check_alignment;
pub enum SerializeError {
SizedOutOfRange,
OverflowedPointer,
ListTooLong,
StringTooLong,
BufferTooSmall { serialized_size: usize, len: usize },
}
impl Debug for SerializeError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SerializeError::SizedOutOfRange => f.write_str("pointer sized magnitude too big"),
SerializeError::OverflowedPointer => f.write_str("overflowed pointer size"),
SerializeError::ListTooLong => f.write_str("list was too long for the pointer size"),
SerializeError::StringTooLong => {
f.write_str("string was too long for the archived representation")
}
SerializeError::BufferTooSmall {
serialized_size,
len,
} => write!(
f,
"buffer too small: trying to write {serialized_size} bytes into buffer of length \
{len}",
),
}
}
}
pub enum VerifyError {
InvalidPointer {
address: *const u8,
size: usize,
range: Range<*const u8>,
},
ListTooLong,
StringTooLong,
Utf8Error(Utf8Error),
NonZeroCheckError,
InvalidEnumDiscriminantError {
enum_name: &'static str,
invalid_discriminant: u8,
},
UnalignedPointer {
address: usize,
align: Alignment,
},
InvalidChar,
InvalidBool {
byte: u8,
},
}
impl Debug for VerifyError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
VerifyError::InvalidPointer {
address,
size,
range,
} => write!(
f,
"pointer overran buffer: ptr {address:p} size {size} in range {range:p}",
),
VerifyError::ListTooLong => f.write_str("list size was > `isize::MAX`"),
VerifyError::StringTooLong => f.write_str("string size was > `isize::MAX`"),
VerifyError::Utf8Error(utf8_error) => utf8_error.fmt(f),
VerifyError::NonZeroCheckError => f.write_str("nonzero integer is zero"),
VerifyError::InvalidEnumDiscriminantError {
enum_name,
invalid_discriminant,
} => write!(
f,
"invalid discriminant '{invalid_discriminant}' for enum '{enum_name}'"
),
VerifyError::UnalignedPointer { address, align } => write!(
f,
"unaligned pointer: ptr {address:p} unaligned for alignment {align:?}"
),
VerifyError::InvalidChar => f.write_str("char out of range"),
VerifyError::InvalidBool { byte } => {
write!(f, "bool set to invalid byte {byte}, expected either 0 or 1")
}
}
}
}
pub trait Serialize {
type Archived;
const ALIGN: Alignment;
const COPY_OPTIMIZATION: bool = false;
unsafe fn serialize(
&self,
out: *mut MaybeUninit<Self::Archived>,
heap: *mut MaybeUninit<u8>,
) -> usize;
fn serialized_size(&self, offset: usize) -> Result<usize, SerializeError>;
unsafe fn verify(this: *const Self::Archived, buffer_end: *const u8)
-> Result<(), VerifyError>;
}
#[inline]
pub unsafe fn to_bytes_in_unchecked<'a, T: Serialize + ?Sized>(
value: &T,
buffer: &'a mut [MaybeUninit<u8>],
) -> usize {
unsafe {
if T::COPY_OPTIMIZATION {
*(buffer as *mut [MaybeUninit<u8>]).cast() =
MaybeUninit::new(read((value as *const T).cast::<T::Archived>()));
size_of::<T::Archived>()
} else {
size_of::<T::Archived>()
+ value.serialize(
(&raw mut *buffer).cast(),
(&raw mut *buffer)
.cast::<MaybeUninit<u8>>()
.add(size_of::<T::Archived>()),
)
}
}
}
#[inline]
pub fn to_bytes_in<'a, T: Serialize + ?Sized>(
value: &T,
buffer: &'a mut [MaybeUninit<u8>],
) -> Result<usize, SerializeError> {
value
.serialized_size(size_of::<T::Archived>())
.and_then(|heap_size| {
let serialized_size = size_of::<T::Archived>() + heap_size;
if serialized_size <= buffer.len() {
unsafe {
to_bytes_in_unchecked(value, &mut *buffer);
}
Ok(serialized_size)
} else {
Err(SerializeError::BufferTooSmall {
serialized_size,
len: buffer.len(),
})
}
})
}
#[inline]
pub fn to_bytes<T: Serialize + ?Sized>(
value: &T,
) -> Result<Box<[u8], AlignedAlloc<T>>, SerializeError> {
value
.serialized_size(size_of::<T::Archived>())
.map(|heap_size| {
let mut buffer = new_uninit_boxed_slice::<T>(heap_size);
unsafe {
to_bytes_in_unchecked(value, &mut *buffer);
buffer.assume_init()
}
})
}
#[inline]
#[must_use]
pub unsafe fn access_ptr_unchecked<T: Serialize + ?Sized>(bytes: &[u8]) -> *const T::Archived {
<*const [u8]>::cast(bytes)
}
#[inline]
pub fn access_ptr<T: Serialize + ?Sized>(bytes: &[u8]) -> Result<*const T::Archived, VerifyError> {
let bytes = bytes as *const [u8];
let address = bytes.cast::<u8>();
check_alignment(address, T::ALIGN)?;
let archived = bytes.cast::<T::Archived>();
unsafe {
let buffer_end = address.add(bytes.len());
if bytes.len() < size_of::<T::Archived>() {
return Err(VerifyError::InvalidPointer {
address,
size: size_of::<T::Archived>(),
range: address..buffer_end,
});
}
T::verify(archived, buffer_end)?;
Ok(archived)
}
}
#[inline]
#[must_use]
pub unsafe fn access_unchecked<T: Serialize + ?Sized>(bytes: &[u8]) -> &T::Archived {
unsafe { &*access_ptr_unchecked::<T>(bytes) }
}
#[inline]
pub fn access<T: Serialize + ?Sized>(bytes: &[u8]) -> Result<&T::Archived, VerifyError> {
access_ptr::<T>(bytes).map(|archived| unsafe { &*archived })
}
#[inline]
#[must_use]
pub unsafe fn access_unchecked_mut<T: Serialize + ?Sized>(bytes: &mut [u8]) -> &mut T::Archived {
unsafe { &mut *<*mut [u8]>::cast(bytes) }
}
#[inline]
pub fn access_mut<T: Serialize + ?Sized>(
bytes: &mut [u8],
) -> Result<&mut T::Archived, VerifyError> {
access_ptr::<T>(bytes).map(|archived| unsafe { &mut *archived.cast_mut() })
}