use crate::ser::SerInner;
use crate::traits::*;
use crate::{MAGIC, MAGIC_REV, VERSION};
use core::hash::Hasher;
use core::{mem::MaybeUninit, ptr::addr_of_mut};
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::{
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>;
#[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) }
}
};
}
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 align_to = align_of::<MemoryAlignment>();
if align_of::<Self>() > align_to {
return Err(Error::AlignmentError.into());
}
let capacity = size + crate::pad_align_to(size, align_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;
<Vec<MemoryAlignment>>::from_raw_parts(
alloc_func(core::alloc::Layout::from_size_align(capacity, align_to)?)
as *mut MemoryAlignment,
capacity / align_to,
capacity / align_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(aligned_vec.into_boxed_slice());
unsafe {
addr_of_mut!((*ptr).1).write(backend);
}
let mem = unsafe { (*ptr).1.as_ref().unwrap() };
let s = unsafe { Self::deserialize_eps(mem) }?;
unsafe {
addr_of_mut!((*ptr).0).write(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()?.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)?;
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 + crate::pad_align_to(size, 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 mem = unsafe { (*ptr).1.as_ref().unwrap() };
let s = unsafe { Self::deserialize_eps(mem) }?;
unsafe {
addr_of_mut!((*ptr).0).write(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()?.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)?;
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()?.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)?;
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 mmap = unsafe { (*ptr).1.as_ref().unwrap() };
let s = unsafe { Self::deserialize_eps(mmap) }?;
unsafe {
addr_of_mut!((*ptr).0).write(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>>;
}
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<()> {
let self_type_name = core::any::type_name::<T>().to_string();
let self_ser_type_name = core::any::type_name::<T::SerType>().to_string();
let mut type_hasher = xxhash_rust::xxh3::Xxh3::new();
T::SerType::type_hash(&mut type_hasher);
let self_type_hash = type_hasher.finish();
let mut align_hasher = xxhash_rust::xxh3::Xxh3::new();
let mut offset_of = 0;
T::SerType::align_hash(&mut align_hasher, &mut offset_of);
let self_align_hash = align_hasher.finish();
let magic = unsafe { u64::_deser_full_inner(backend)? };
match magic {
MAGIC => Ok(()),
MAGIC_REV => Err(Error::EndiannessError),
magic => Err(Error::MagicCookieError(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 { u64::_deser_full_inner(backend)? };
let ser_align_hash = unsafe { u64::_deser_full_inner(backend)? };
let ser_type_name = unsafe { String::_deser_full_inner(backend)? }.to_string();
if ser_type_hash != self_type_hash {
return Err(Error::WrongTypeHash {
ser_type_name,
ser_type_hash,
self_type_name,
self_ser_type_name,
self_type_hash,
});
}
if ser_align_hash != self_align_hash {
return Err(Error::WrongAlignHash {
ser_type_name,
ser_align_hash,
self_type_name,
self_ser_type_name,
self_align_hash,
});
}
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>>;
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Error reading stats for file during ε-serde deserialization: {0}")]
#[cfg(feature = "std")]
FileOpenError(std::io::Error),
#[error("Read error during ε-serde deserialization")]
ReadError,
#[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.")
)]
EndiannessError,
#[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.")]
MagicCookieError(u64),
#[error("Invalid tag: 0x{0:02x}")]
InvalidTag(usize),
#[error(
r#"Wrong type hash
Actual: 0x{ser_type_hash:016x}; expected: 0x{self_type_hash:016x}.
The serialized type is
'{ser_type_name}',
but the deserializable 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. You might also be
trying to deserialize a tuple of mixed zero-copy types, which is no longer
supported since 0.8.0, an instance containing tuples, whose type hash was fixed
in 0.9.0, or an instance containing a vector or a string that was serialized
before 0.10.0."#
)]
WrongTypeHash {
ser_type_name: String,
ser_type_hash: u64,
self_type_name: String,
self_ser_type_name: String,
self_type_hash: u64,
},
#[error(
r#"Wrong alignment hash
Actual: 0x{ser_align_hash:016x}; expected: 0x{self_align_hash:016x}.
The serialized type is
'{ser_type_name}',
but the deserializable type on which the deserialization method was invoked is
'{self_type_name}',
which has serialization type
{self_ser_type_name}.
You might be trying to deserialize a file that was serialized on an
architecture with different alignment requirements, or some of the fields of
the type might have changed their copy type (zero or deep). You might also be
trying to deserialize an array, whose alignment hash has been fixed in 0.8.0.
It is also possible that you are trying to deserialize a file serialized before
version 0.10.0 in which repr attributes were not sorted lexicographically, or
a range in a file serialized before version 0.12.0."#
)]
WrongAlignHash {
ser_type_name: String,
ser_align_hash: u64,
self_type_name: String,
self_ser_type_name: String,
self_align_hash: u64,
},
}