use {
crate::{
SchemaRead, SchemaWrite, TypeMeta,
config::{ConfigCore, PREALLOCATION_SIZE_LIMIT_DISABLED},
error::{
PreallocationError, ReadResult, WriteResult, pointer_sized_decode_error,
preallocation_size_limit, write_length_encoding_overflow,
},
int_encoding::{ByteOrder, Endian},
io::{Reader, Writer},
},
core::{any::type_name, marker::PhantomData},
};
pub const PREALLOCATION_SIZE_LIMIT_USE_CONFIG: usize = 0;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum PreallocationLimitOverride {
#[default]
UseConfig,
NoLimit,
Override(usize),
}
impl PreallocationLimitOverride {
#[inline]
pub const fn to_opt_limit_with_config<C: ConfigCore>(self) -> Option<usize> {
match self {
PreallocationLimitOverride::UseConfig => C::PREALLOCATION_SIZE_LIMIT,
PreallocationLimitOverride::NoLimit => None,
PreallocationLimitOverride::Override(limit) => Some(limit),
}
}
#[inline]
pub const fn from_usize(limit: usize) -> Self {
match limit {
PREALLOCATION_SIZE_LIMIT_USE_CONFIG => PreallocationLimitOverride::UseConfig,
PREALLOCATION_SIZE_LIMIT_DISABLED => PreallocationLimitOverride::NoLimit,
_ => PreallocationLimitOverride::Override(limit),
}
}
}
pub unsafe trait SeqLen<C: ConfigCore> {
const PREALLOCATION_SIZE_LIMIT_OVERRIDE: PreallocationLimitOverride =
PreallocationLimitOverride::UseConfig;
#[inline]
fn prealloc_check<T>(len: usize) -> Result<(), PreallocationError> {
fn check(len: usize, type_size: usize, limit: usize) -> Result<(), PreallocationError> {
let needed = len
.checked_mul(type_size)
.ok_or_else(|| preallocation_size_limit(usize::MAX, limit))?;
if needed > limit {
return Err(preallocation_size_limit(needed, limit));
}
Ok(())
}
if let Some(prealloc_limit) =
Self::PREALLOCATION_SIZE_LIMIT_OVERRIDE.to_opt_limit_with_config::<C>()
{
check(len, size_of::<T>().max(1), prealloc_limit)?;
}
Ok(())
}
#[inline]
fn read_prealloc_check<'de, T>(reader: impl Reader<'de>) -> ReadResult<usize> {
let len = Self::read(reader)?;
Self::prealloc_check::<T>(len)?;
Ok(len)
}
fn read<'de>(reader: impl Reader<'de>) -> ReadResult<usize>;
fn write(writer: impl Writer, len: usize) -> WriteResult<()>;
fn write_bytes_needed_prealloc_check<T>(len: usize) -> WriteResult<usize> {
Self::prealloc_check::<T>(len)?;
Self::write_bytes_needed(len)
}
fn write_bytes_needed(len: usize) -> WriteResult<usize>;
}
pub struct UseIntLen<T, const PREALLOCATION_SIZE_LIMIT: usize = PREALLOCATION_SIZE_LIMIT_USE_CONFIG>(
PhantomData<T>,
);
unsafe impl<const PREALLOCATION_SIZE_LIMIT: usize, T, C: ConfigCore> SeqLen<C>
for UseIntLen<T, PREALLOCATION_SIZE_LIMIT>
where
T: SchemaWrite<C> + for<'de> SchemaRead<'de, C>,
T::Src: TryFrom<usize>,
usize: for<'de> TryFrom<<T as SchemaRead<'de, C>>::Dst>,
{
const PREALLOCATION_SIZE_LIMIT_OVERRIDE: PreallocationLimitOverride =
PreallocationLimitOverride::from_usize(PREALLOCATION_SIZE_LIMIT);
#[inline(always)]
fn read<'de>(reader: impl Reader<'de>) -> ReadResult<usize> {
let len = T::get(reader)?;
let Ok(len) = usize::try_from(len) else {
return Err(pointer_sized_decode_error());
};
Ok(len)
}
#[inline(always)]
fn write(writer: impl Writer, len: usize) -> WriteResult<()> {
let Ok(len) = T::Src::try_from(len) else {
return Err(write_length_encoding_overflow(type_name::<T::Src>()));
};
T::write(writer, &len)
}
#[inline(always)]
fn write_bytes_needed(len: usize) -> WriteResult<usize> {
if let TypeMeta::Static { size, .. } = <T as SchemaWrite<C>>::TYPE_META {
return Ok(size);
}
let Ok(len) = T::Src::try_from(len) else {
return Err(write_length_encoding_overflow(type_name::<T::Src>()));
};
T::size_of(&len)
}
}
macro_rules! impl_use_int_primitive {
($($type:ty),+) => {
$(
unsafe impl<C: ConfigCore> SeqLen<C> for $type {
#[inline(always)]
#[allow(irrefutable_let_patterns)]
fn read<'de>(reader: impl Reader<'de>) -> ReadResult<usize> {
let len = <$type as SchemaRead<C>>::get(reader)?;
let Ok(len) = usize::try_from(len) else {
return Err(pointer_sized_decode_error());
};
Ok(len)
}
#[inline(always)]
fn write(writer: impl Writer, len: usize) -> WriteResult<()> {
let Ok(len) = <$type>::try_from(len) else {
return Err(write_length_encoding_overflow(type_name::<$type>()));
};
<$type as SchemaWrite<C>>::write(writer, &len)
}
#[inline(always)]
fn write_bytes_needed(len: usize) -> WriteResult<usize> {
if let TypeMeta::Static { size, .. } = <$type as SchemaWrite<C>>::TYPE_META {
return Ok(size);
}
let Ok(len) = <$type>::try_from(len) else {
return Err(write_length_encoding_overflow(type_name::<$type>()));
};
<$type as SchemaWrite<C>>::size_of(&len)
}
}
)+
};
}
impl_use_int_primitive!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
pub struct FixIntLen<T, const PREALLOCATION_SIZE_LIMIT: usize = PREALLOCATION_SIZE_LIMIT_USE_CONFIG>(
PhantomData<T>,
);
macro_rules! impl_fix_int {
($type:ty) => {
unsafe impl<const PREALLOCATION_SIZE_LIMIT: usize, C: ConfigCore> SeqLen<C>
for FixIntLen<$type, PREALLOCATION_SIZE_LIMIT>
{
const PREALLOCATION_SIZE_LIMIT_OVERRIDE: PreallocationLimitOverride =
PreallocationLimitOverride::from_usize(PREALLOCATION_SIZE_LIMIT);
#[inline(always)]
#[allow(irrefutable_let_patterns)]
fn read<'de>(mut reader: impl Reader<'de>) -> ReadResult<usize> {
let bytes = reader.take_array::<{ size_of::<$type>() }>()?;
let len = match C::ByteOrder::ENDIAN {
Endian::Big => <$type>::from_be_bytes(bytes),
Endian::Little => <$type>::from_le_bytes(bytes),
};
let Ok(len) = usize::try_from(len) else {
return Err(pointer_sized_decode_error());
};
Ok(len)
}
#[inline(always)]
fn write(mut writer: impl Writer, len: usize) -> WriteResult<()> {
let Ok(len) = <$type>::try_from(len) else {
return Err(write_length_encoding_overflow(type_name::<$type>()));
};
let bytes = match C::ByteOrder::ENDIAN {
Endian::Big => len.to_be_bytes(),
Endian::Little => len.to_le_bytes(),
};
writer.write(&bytes)?;
Ok(())
}
#[inline(always)]
fn write_bytes_needed(_: usize) -> WriteResult<usize> {
Ok(size_of::<$type>())
}
}
};
}
impl_fix_int!(u8);
impl_fix_int!(u16);
impl_fix_int!(u32);
impl_fix_int!(u64);
impl_fix_int!(u128);
impl_fix_int!(i8);
impl_fix_int!(i16);
impl_fix_int!(i32);
impl_fix_int!(i64);
impl_fix_int!(i128);
pub type BincodeLen<const PREALLOCATION_SIZE_LIMIT: usize = PREALLOCATION_SIZE_LIMIT_USE_CONFIG> =
UseIntLen<u64, PREALLOCATION_SIZE_LIMIT>;