use crate::ser::SerInner;
use crate::traits::*;
use crate::{MAGIC, MAGIC_REV, VERSION};
use aliasable::boxed::AliasableBox;
use core::{mem::MaybeUninit, ptr::addr_of_mut};
use maybe_dangling::MaybeDangling;
pub mod helpers;
pub use helpers::*;
pub mod mem_case;
pub use mem_case::*;
pub mod read;
pub use read::*;
pub mod reader_with_pos;
pub use reader_with_pos::*;
pub mod slice_with_pos;
pub use slice_with_pos::*;
#[cfg(not(feature = "std"))]
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
#[cfg(feature = "std")]
use std::{io::BufReader, path::Path};
pub type Result<T> = core::result::Result<T, Error>;
pub type DeserType<'a, T> = <T as DeserInner>::DeserType<'a>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Hash256(pub [u8; 32]);
impl core::fmt::Display for Hash256 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for byte in self.0 {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[cfg(feature = "std")]
#[error("Error opening or inspecting file during ε-serde deserialization: {0}")]
FileOpenError(#[source] std::io::Error),
#[cfg(feature = "std")]
#[error("I/O error during ε-serde deserialization: {0}")]
IoError(#[source] std::io::Error),
#[error("Read error during ε-serde deserialization")]
ReadError,
#[error("Capacity overflow while allocating a collection during ε-serde deserialization")]
CapacityOverflow,
#[cfg_attr(
target_endian = "big",
error("The current arch is big-endian but the data is little-endian.")
)]
#[cfg_attr(
target_endian = "little",
error("The current arch is little-endian but the data is big-endian.")
)]
EndiannessMismatch,
#[error(
"Alignment error. Most likely you are deserializing from a memory region with insufficient alignment."
)]
AlignmentError,
#[error("Major version mismatch. Expected {major} but got {0}.", major = VERSION.0)]
MajorVersionMismatch(u16),
#[error("Minor version mismatch. Expected {minor} but got {0}.", minor = VERSION.1)]
MinorVersionMismatch(u16),
#[error("The file was serialized on an architecture where a usize has size {0}, but on the current architecture it has size {size}.", size = core::mem::size_of::<usize>())]
UsizeSizeMismatch(usize),
#[error("Wrong magic cookie 0x{0:016x}. The byte stream does not come from ε-serde.")]
InvalidMagicCookie(u64),
#[error("Invalid tag: 0x{0:02x}")]
InvalidTag(usize),
#[error(
r#"Wrong type hash
Actual: 0x{ser_type_hash}; expected: 0x{self_type_hash}.
The serialized type is
{ser_type_name},
but the deserializing type on which the deserialization method was invoked is
{self_type_name},
which has serialization type
{self_ser_type_name}.
You are trying to deserialize a file with the wrong type."#
)]
TypeHashMismatch {
ser_type_name: String,
ser_type_hash: Box<Hash256>,
self_type_name: String,
self_ser_type_name: String,
self_type_hash: Box<Hash256>,
},
#[error(
r#"Wrong alignment hash
Actual: 0x{ser_align_hash}; expected: 0x{self_align_hash}.
The serialized type is
{ser_type_name},
but the deserializing type on which the deserialization method was invoked is
{self_type_name},
which has serialization type
{self_ser_type_name}.
You are trying to deserialize a file that was serialized on an
architecture with incompatible alignment requirements."#
)]
AlignHashMismatch {
ser_type_name: String,
ser_align_hash: Box<Hash256>,
self_type_name: String,
self_ser_type_name: String,
self_align_hash: Box<Hash256>,
},
}
#[doc(hidden)]
pub struct CovariantProof<T>(core::marker::PhantomData<fn() -> T>);
impl<T> CovariantProof<T> {
#[doc(hidden)]
pub(crate) const fn new() -> Self {
CovariantProof(core::marker::PhantomData)
}
}
#[inline(always)]
pub fn __check_type_covariance<T: DeserInner>() {
let _ = T::__check_covariance(CovariantProof::<T::DeserType<'static>>::new());
}
#[macro_export]
macro_rules! check_covariance {
() => {
#[inline(always)]
fn __check_covariance<'__long: '__short, '__short>(
proof: $crate::deser::CovariantProof<Self::DeserType<'__long>>,
) -> $crate::deser::CovariantProof<Self::DeserType<'__short>> {
proof
}
};
}
#[macro_export]
macro_rules! unsafe_assume_covariance {
($($type:ty),* $(,)?) => {
#[allow(clippy::useless_transmute)]
#[inline(always)]
fn __check_covariance<'__long: '__short, '__short>(
proof: $crate::deser::CovariantProof<Self::DeserType<'__long>>,
) -> $crate::deser::CovariantProof<Self::DeserType<'__short>> {
$(
$crate::deser::__check_type_covariance::<$type>();
)*
unsafe { ::core::mem::transmute(proof) }
}
};
}
struct BackendGuard<S: DeserInner>(*mut MemCase<S>);
impl<S: DeserInner> Drop for BackendGuard<S> {
fn drop(&mut self) {
unsafe { addr_of_mut!((*self.0).1).drop_in_place() };
}
}
pub trait Deserialize: DeserInner {
unsafe fn deserialize_full(backend: &mut impl ReadNoStd) -> Result<Self>;
unsafe fn deserialize_eps(backend: &'_ [u8]) -> Result<Self::DeserType<'_>>;
#[cfg(feature = "std")]
unsafe fn load_full(path: impl AsRef<Path>) -> anyhow::Result<Self> {
let file = std::fs::File::open(path).map_err(Error::FileOpenError)?;
let mut buf_reader = BufReader::new(file);
unsafe { Self::deserialize_full(&mut buf_reader).map_err(|e| e.into()) }
}
unsafe fn read_mem(mut read: impl ReadNoStd, size: usize) -> anyhow::Result<MemCase<Self>> {
let pad_to = align_of::<MemoryAlignment>();
if align_of::<Self>() > pad_to {
return Err(Error::AlignmentError.into());
}
let capacity = size
.checked_add(crate::pad_align_to(size, pad_to))
.ok_or_else(|| {
anyhow::anyhow!("Size too large: adding alignment padding overflows usize")
})?
.max(pad_to);
let layout = core::alloc::Layout::from_size_align(capacity, pad_to)?;
let mut uninit: MaybeUninit<MemCase<Self>> = MaybeUninit::uninit();
let ptr = uninit.as_mut_ptr();
#[allow(invalid_value)]
let mut aligned_vec = unsafe {
#[cfg(not(feature = "std"))]
let alloc_func = alloc::alloc::alloc;
#[cfg(feature = "std")]
let alloc_func = std::alloc::alloc;
#[cfg(not(feature = "std"))]
let handle_alloc_error_func = alloc::alloc::handle_alloc_error;
#[cfg(feature = "std")]
let handle_alloc_error_func = std::alloc::handle_alloc_error;
let raw = alloc_func(layout);
if raw.is_null() {
handle_alloc_error_func(layout);
}
<Vec<MemoryAlignment>>::from_raw_parts(
raw as *mut MemoryAlignment,
capacity / pad_to,
capacity / pad_to,
)
};
let bytes = unsafe {
core::slice::from_raw_parts_mut(aligned_vec.as_mut_ptr() as *mut u8, capacity)
};
read.read_exact(&mut bytes[..size])?;
bytes[size..].fill(0);
let backend = MemBackend::Memory(AliasableBox::from(aligned_vec.into_boxed_slice()));
unsafe {
addr_of_mut!((*ptr).1).write(backend);
}
let guard = BackendGuard(ptr);
let mem = unsafe { (*ptr).1.as_bytes().unwrap() };
let s = unsafe { Self::deserialize_eps(mem) }?;
core::mem::forget(guard);
unsafe {
addr_of_mut!((*ptr).0).write(MaybeDangling::new(s));
}
Ok(unsafe { uninit.assume_init() })
}
#[cfg(feature = "std")]
unsafe fn load_mem(path: impl AsRef<Path>) -> anyhow::Result<MemCase<Self>> {
let file_len = path
.as_ref()
.metadata()
.map_err(Error::FileOpenError)?
.len();
anyhow::ensure!(
file_len <= isize::MAX as u64,
"File too large for the current architecture (longer than isize::MAX)"
);
let file_len = file_len as usize;
let file = std::fs::File::open(path).map_err(Error::FileOpenError)?;
unsafe { Self::read_mem(file, file_len) }
}
#[cfg(feature = "mmap")]
unsafe fn read_mmap(
mut read: impl ReadNoStd,
size: usize,
flags: Flags,
) -> anyhow::Result<MemCase<Self>> {
let capacity = size
.checked_add(crate::pad_align_to(size, 16))
.ok_or_else(|| {
anyhow::anyhow!("Size too large: adding alignment padding overflows usize")
})?
.max(16);
let mut uninit: MaybeUninit<MemCase<Self>> = MaybeUninit::uninit();
let ptr = uninit.as_mut_ptr();
let mut mmap = mmap_rs::MmapOptions::new(capacity)?
.with_flags(flags.mmap_flags())
.map_mut()?;
read.read_exact(&mut mmap[..size])?;
mmap[size..].fill(0);
let backend = MemBackend::Mmap(mmap.make_read_only().map_err(|(_, err)| err)?);
unsafe {
addr_of_mut!((*ptr).1).write(backend);
}
let guard = BackendGuard(ptr);
let mem = unsafe { (*ptr).1.as_bytes().unwrap() };
let s = unsafe { Self::deserialize_eps(mem) }?;
core::mem::forget(guard);
unsafe {
addr_of_mut!((*ptr).0).write(MaybeDangling::new(s));
}
Ok(unsafe { uninit.assume_init() })
}
#[cfg(all(feature = "mmap", feature = "std"))]
unsafe fn load_mmap(path: impl AsRef<Path>, flags: Flags) -> anyhow::Result<MemCase<Self>> {
let file_len = path
.as_ref()
.metadata()
.map_err(Error::FileOpenError)?
.len();
anyhow::ensure!(
file_len <= isize::MAX as u64,
"File too large for the current architecture (longer than isize::MAX)"
);
let file_len = file_len as usize;
let file = std::fs::File::open(path).map_err(Error::FileOpenError)?;
unsafe { Self::read_mmap(file, file_len, flags) }
}
#[cfg(all(feature = "mmap", feature = "std"))]
unsafe fn mmap(path: impl AsRef<Path>, flags: Flags) -> anyhow::Result<MemCase<Self>> {
let file_len = path
.as_ref()
.metadata()
.map_err(Error::FileOpenError)?
.len();
anyhow::ensure!(
file_len <= isize::MAX as u64,
"File too large for the current architecture (longer than isize::MAX)"
);
let file_len = file_len as usize;
let file = std::fs::File::open(path).map_err(Error::FileOpenError)?;
let mut uninit: MaybeUninit<MemCase<Self>> = MaybeUninit::uninit();
let ptr = uninit.as_mut_ptr();
let mmap = unsafe {
mmap_rs::MmapOptions::new(file_len)?
.with_flags(flags.mmap_flags())
.with_file(&file, 0)
.map()?
};
unsafe {
addr_of_mut!((*ptr).1).write(MemBackend::Mmap(mmap));
}
let guard = BackendGuard(ptr);
let mmap = unsafe { (*ptr).1.as_bytes().unwrap() };
let s = unsafe { Self::deserialize_eps(mmap) }?;
core::mem::forget(guard);
unsafe {
addr_of_mut!((*ptr).0).write(MaybeDangling::new(s));
}
Ok(unsafe { uninit.assume_init() })
}
}
pub trait DeserInner: Sized {
type DeserType<'a>;
fn __check_covariance<'__long: '__short, '__short>(
proof: CovariantProof<Self::DeserType<'__long>>,
) -> CovariantProof<Self::DeserType<'__short>>;
unsafe fn _deser_full_inner(backend: &mut impl ReadWithPos) -> Result<Self>;
unsafe fn _deser_eps_inner<'a>(backend: &mut SliceWithPos<'a>) -> Result<Self::DeserType<'a>>;
}
#[diagnostic::on_unimplemented(
message = "type parameter `{T}` is both full-copy and ε-copy (it appears both in a field marked `#[epserde(force_full_copy)]` and in an unmarked field)",
label = "this occurrence of `{T}` in an ε-copy field conflicts with its use in a full-copy field",
note = "consider restricting type parameter `{T}` with `{T}: for<'a> DeserInner<DeserType<'a> = {T}>`",
note = "alternatively, mark the ε-copy field with `#[epserde(force_full_copy)]`",
note = "alternatively, pin the parameter to full-copy with `#[epserde(full_copy({T}))]` on the type"
)]
pub trait EitherFullOrEpsCopy<T: ?Sized> {}
impl<T: ?Sized> EitherFullOrEpsCopy<T> for T {}
#[diagnostic::on_unimplemented(
message = "type parameter `{Self}` must be deep-copy: it occurs as an element of a vector, boxed slice, or array in an ε-copy field",
label = "if `{Self}` were zero-copy, this field would ε-copy deserialize to a slice reference, a type not expressible in the source",
note = "consider restricting type parameter `{Self}` with trait `DeepCopy` (more targeted)",
note = "alternatively, mark the field with `#[epserde(force_full_copy)]`",
note = "alternatively, pin `{Self}` to full-copy with `#[epserde(full_copy({Self}))]` on the type, which makes `{Self}` full-copy in every field"
)]
pub trait DeepCopyInSeq {}
#[diagnostic::do_not_recommend]
impl<T: crate::traits::DeepCopy> DeepCopyInSeq for T {}
#[diagnostic::on_unimplemented(
message = "a field deserialization type is inconsistent with `#[epserde(full_copy(...))]`",
label = "a parameter pinned by `#[epserde(full_copy(...))]` is ε-copy deserialized by this field",
note = "the field ε-copy deserializes to `{Self}`, but `#[epserde(full_copy(...))]` requires `{Expected}`",
note = "consider removing that parameter from `#[epserde(full_copy(...))]`",
note = "alternatively, mark this field with `#[epserde(force_full_copy)]`"
)]
pub trait FullCopyConsistent<Expected: ?Sized> {}
impl<T: ?Sized> FullCopyConsistent<T> for T {}
impl<T: SerInner<SerType: TypeHash + AlignHash> + DeserInner> Deserialize for T {
unsafe fn deserialize_full(backend: &mut impl ReadNoStd) -> Result<Self> {
let mut backend = ReaderWithPos::new(backend);
check_header::<Self>(&mut backend)?;
unsafe { Self::_deser_full_inner(&mut backend) }
}
unsafe fn deserialize_eps(backend: &'_ [u8]) -> Result<Self::DeserType<'_>> {
let mut backend = SliceWithPos::new(backend);
check_header::<Self>(&mut backend)?;
unsafe { Self::_deser_eps_inner(&mut backend) }
}
}
pub fn check_header<T: SerInner<SerType: TypeHash + AlignHash>>(
backend: &mut impl ReadWithPos,
) -> Result<()> {
fn read_type_name(backend: &mut impl ReadWithPos) -> Result<String> {
const MAX_NAME_LEN: usize = 8192;
let len = unsafe { usize::_deser_full_inner(backend) }?;
let mut name = Vec::with_capacity(len.min(MAX_NAME_LEN));
let mut buf = [0u8; 256];
let mut remaining = len;
while remaining > 0 {
let chunk = remaining.min(buf.len());
backend.read_exact(&mut buf[..chunk])?;
let keep = chunk.min(MAX_NAME_LEN.saturating_sub(name.len()));
name.extend_from_slice(&buf[..keep]);
remaining -= chunk;
}
Ok(String::from_utf8_lossy(&name).into_owned())
}
let mut type_hasher = CryptoHasher::new();
T::SerType::type_hash(&mut type_hasher);
let self_type_hash = type_hasher.finalize();
let mut align_hasher = CryptoHasher::new();
let mut offset_of = 0;
T::SerType::align_hash(&mut align_hasher, &mut offset_of);
let self_align_hash = align_hasher.finalize();
let magic = unsafe { u64::_deser_full_inner(backend)? };
match magic {
MAGIC => Ok(()),
MAGIC_REV => Err(Error::EndiannessMismatch),
magic => Err(Error::InvalidMagicCookie(magic)),
}?;
let major = unsafe { u16::_deser_full_inner(backend)? };
if major != VERSION.0 {
return Err(Error::MajorVersionMismatch(major));
}
let minor = unsafe { u16::_deser_full_inner(backend)? };
if minor > VERSION.1 {
return Err(Error::MinorVersionMismatch(minor));
};
let usize_size = unsafe { u8::_deser_full_inner(backend)? };
let usize_size = usize_size as usize;
let native_usize_size = core::mem::size_of::<usize>();
if usize_size != native_usize_size {
return Err(Error::UsizeSizeMismatch(usize_size));
};
let ser_type_hash = unsafe { <[u8; 32]>::_deser_full_inner(backend)? };
let ser_align_hash = unsafe { <[u8; 32]>::_deser_full_inner(backend)? };
if ser_type_hash != self_type_hash {
let ser_type_name = read_type_name(backend).unwrap_or_else(|_| "<unreadable>".to_string());
return Err(Error::TypeHashMismatch {
ser_type_name,
ser_type_hash: Box::new(Hash256(ser_type_hash)),
self_type_name: core::any::type_name::<T>().to_string(),
self_ser_type_name: core::any::type_name::<T::SerType>().to_string(),
self_type_hash: Box::new(Hash256(self_type_hash)),
});
}
if ser_align_hash != self_align_hash {
let ser_type_name = read_type_name(backend).unwrap_or_else(|_| "<unreadable>".to_string());
return Err(Error::AlignHashMismatch {
ser_type_name,
ser_align_hash: Box::new(Hash256(ser_align_hash)),
self_type_name: core::any::type_name::<T>().to_string(),
self_ser_type_name: core::any::type_name::<T::SerType>().to_string(),
self_align_hash: Box::new(Hash256(self_align_hash)),
});
}
let _ = read_type_name(backend)?;
Ok(())
}
pub trait DeserHelper<T: CopySelector> {
type FullType;
type DeserType<'a>;
unsafe fn _deser_full_inner_impl(backend: &mut impl ReadWithPos) -> Result<Self::FullType>;
unsafe fn _deser_eps_inner_impl<'a>(
backend: &mut SliceWithPos<'a>,
) -> Result<Self::DeserType<'a>>;
}