#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use {
crate::{
ReadResult, SchemaRead, SchemaReadContext, SchemaReadOwned, SchemaWrite, WriteResult,
config::{Config, ConfigCore},
error,
io::{Reader, Writer},
},
core::mem::MaybeUninit,
};
pub trait Serialize<C: Config>: SchemaWrite<C> {
#[cfg(feature = "alloc")]
fn serialize(src: &Self::Src, config: C) -> WriteResult<Vec<u8>> {
let capacity = Self::size_of(src)?;
let mut buffer = Vec::with_capacity(capacity);
let mut writer = buffer.spare_capacity_mut();
Self::serialize_into(writer.by_ref(), src, config)?;
let len = writer.len();
unsafe {
#[allow(clippy::arithmetic_side_effects)]
buffer.set_len(capacity - len);
}
Ok(buffer)
}
#[inline]
#[expect(unused_variables)]
fn serialize_into(mut dst: impl Writer, src: &Self::Src, config: C) -> WriteResult<()> {
Self::write(dst.by_ref(), src)?;
dst.finish()?;
Ok(())
}
#[inline]
#[expect(unused_variables)]
fn serialized_size(src: &Self::Src, config: C) -> WriteResult<u64> {
Self::size_of(src).map(|size| size as u64)
}
}
impl<T, C: Config> Serialize<C> for T where T: SchemaWrite<C> + ?Sized {}
macro_rules! maybe_size_limit {
($config:ty, $src:expr, $kind:ident, |$reader:ident| $body:expr $(,)?) => {{
let src = $src;
match <$config as $crate::config::ConfigCore>::DESERIALIZATION_SIZE_LIMIT {
Some(limit) => {
let $reader = maybe_size_limit!(@reader $kind, src, limit);
$body
}
None => {
let $reader = src;
$body
}
}
}};
(@reader generic, $src:ident, $limit:ident) => {
$crate::io::LimitReader::new($src, $limit)
};
(@reader slice, $src:ident, $limit:ident) => {{
let src = $src;
let len = $limit.min(src.len());
&src[..len]
}};
(@reader slice_mut, $src:ident, $limit:ident) => {{
let src = $src;
let len = $limit.min(src.len());
&mut src[..len]
}};
}
pub trait Deserialize<'de, C: Config>: SchemaRead<'de, C> {
#[inline(always)]
#[expect(unused_variables)]
fn deserialize(src: &'de [u8], config: C) -> ReadResult<Self::Dst> {
maybe_size_limit!(C, src, slice, |reader| Self::get(reader))
}
#[inline]
#[expect(unused_variables)]
fn deserialize_into(
src: &'de [u8],
dst: &mut MaybeUninit<Self::Dst>,
config: C,
) -> ReadResult<()> {
maybe_size_limit!(C, src, slice, |reader| Self::read(reader, dst))
}
}
impl<'de, T, C: Config> Deserialize<'de, C> for T where T: SchemaRead<'de, C> {}
pub trait DeserializeOwned<C: Config>: SchemaReadOwned<C> {
#[inline(always)]
fn deserialize_from<'de>(
src: impl Reader<'de>,
) -> ReadResult<<Self as SchemaRead<'de, C>>::Dst> {
maybe_size_limit!(C, src, generic, |reader| Self::get(reader))
}
#[inline]
fn deserialize_from_into<'de>(
src: impl Reader<'de>,
dst: &mut MaybeUninit<<Self as SchemaRead<'de, C>>::Dst>,
) -> ReadResult<()> {
maybe_size_limit!(C, src, generic, |reader| Self::read(reader, dst))
}
}
impl<T, C: Config> DeserializeOwned<C> for T where T: SchemaReadOwned<C> {}
#[cfg(feature = "alloc")]
pub fn serialize<T, C: Config>(src: &T, config: C) -> WriteResult<Vec<u8>>
where
T: SchemaWrite<C, Src = T> + ?Sized,
{
T::serialize(src, config)
}
#[inline]
pub fn serialize_into<T, C: Config>(dst: impl Writer, src: &T, config: C) -> WriteResult<()>
where
T: SchemaWrite<C, Src = T> + ?Sized,
{
T::serialize_into(dst, src, config)
}
#[inline]
pub fn serialized_size<T, C: Config>(src: &T, config: C) -> WriteResult<u64>
where
T: SchemaWrite<C, Src = T> + ?Sized,
{
T::serialized_size(src, config)
}
#[inline(always)]
pub fn deserialize<'de, T, C: Config>(src: &'de [u8], config: C) -> ReadResult<T>
where
T: SchemaRead<'de, C, Dst = T>,
{
T::deserialize(src, config)
}
#[inline(always)]
#[expect(unused_variables)]
pub fn deserialize_exact<'de, T, C: Config>(mut src: &'de [u8], config: C) -> ReadResult<T>
where
T: SchemaRead<'de, C, Dst = T>,
{
let value = match C::DESERIALIZATION_SIZE_LIMIT {
Some(limit) => {
let mut limited = &src[..limit.min(src.len())];
let initial_len = limited.len();
let value = T::get(limited.by_ref())?;
#[expect(clippy::arithmetic_side_effects)]
let consumed = initial_len - limited.len();
src = &src[consumed..];
value
}
None => T::get(src.by_ref())?,
};
if src.is_empty() {
Ok(value)
} else {
Err(error::trailing_bytes())
}
}
#[inline(always)]
#[expect(unused_variables)]
pub fn deserialize_with_context<'de, Ctx, T, C: Config>(
ctx: Ctx,
src: &'de [u8],
config: C,
) -> ReadResult<T>
where
T: SchemaReadContext<'de, C, Ctx, Dst = T>,
{
maybe_size_limit!(C, src, slice, |reader| T::get_with_context(ctx, reader))
}
#[inline(always)]
#[expect(unused_variables)]
pub fn deserialize_mut<'de, T, C: Config>(src: &'de mut [u8], config: C) -> ReadResult<T>
where
T: SchemaRead<'de, C, Dst = T>,
{
maybe_size_limit!(C, src, slice_mut, |reader| T::get(reader))
}
#[inline(always)]
#[expect(unused_variables)]
pub fn deserialize_from<'de, T, C: Config>(src: impl Reader<'de>, config: C) -> ReadResult<T>
where
T: SchemaReadOwned<C, Dst = T>,
{
T::deserialize_from(src)
}
pub unsafe trait ZeroCopy<C: ConfigCore>: 'static {
#[inline(always)]
#[expect(unused_variables)]
fn from_bytes<'de>(bytes: &'de [u8], config: C) -> ReadResult<&'de Self>
where
Self: SchemaRead<'de, C, Dst = Self> + Sized,
{
maybe_size_limit!(
C,
bytes,
slice,
|reader| <&Self as SchemaRead<'de, C>>::get(reader)
)
}
#[inline(always)]
#[expect(unused_variables)]
fn from_bytes_mut<'de>(bytes: &'de mut [u8], config: C) -> ReadResult<&'de mut Self>
where
Self: SchemaRead<'de, C, Dst = Self> + Sized,
{
maybe_size_limit!(C, bytes, slice_mut, |reader| <&mut Self as SchemaRead<
'de,
C,
>>::get(reader))
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{ReadError, config::Configuration, io::ReadError as IoReadError},
};
#[test]
fn configured_deserialization_limit_is_enforced() {
let bytes = 42u64.to_le_bytes();
let limited = Configuration::default().with_deserialization_size_limit::<4>();
assert!(matches!(
deserialize::<u64, _>(&bytes, limited),
Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
));
assert!(matches!(
deserialize_from::<u64, _>(bytes.as_slice(), limited),
Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
));
let exact = Configuration::default().with_deserialization_size_limit::<8>();
assert_eq!(deserialize::<u64, _>(&bytes, exact).unwrap(), 42);
assert_eq!(deserialize_exact::<u64, _>(&bytes, exact).unwrap(), 42);
assert!(matches!(
deserialize_exact::<u64, _>(&bytes, limited),
Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
));
let mut trailing = [0; 9];
trailing[..8].copy_from_slice(&bytes);
assert!(matches!(
deserialize_exact::<u64, _>(&trailing, exact),
Err(ReadError::TrailingBytes)
));
let larger = Configuration::default().with_deserialization_size_limit::<9>();
assert!(matches!(
deserialize_exact::<u64, _>(&trailing, larger),
Err(ReadError::TrailingBytes)
));
let disabled = limited.disable_deserialization_size_limit();
assert_eq!(deserialize::<u64, _>(&bytes, disabled).unwrap(), 42);
}
#[cfg(feature = "alloc")]
#[test]
fn slice_and_generic_paths_agree_at_every_limit() {
macro_rules! check {
($ty:ty, $val:expr, $($limit:literal),+) => {$({
let bytes = crate::serialize(&$val).unwrap();
let cfg = Configuration::default().with_deserialization_size_limit::<$limit>();
let via_slice = deserialize::<$ty, _>(&bytes, cfg);
let via_reader = deserialize_from::<$ty, _>(bytes.as_slice(), cfg);
assert_eq!(
via_slice.is_ok(),
via_reader.is_ok(),
"{} at limit {}: slice={:?} reader={:?}",
stringify!($ty),
$limit,
via_slice.as_ref().err(),
via_reader.as_ref().err(),
);
if let (Err(a), Err(b)) = (via_slice, via_reader) {
assert_eq!(
alloc::format!("{a:?}"),
alloc::format!("{b:?}"),
"{} at limit {}: differing errors",
stringify!($ty),
$limit,
);
}
})+};
}
check!(
alloc::vec::Vec<u32>,
alloc::vec![1u32, 2, 3],
0,
1,
7,
8,
9,
12,
16,
19,
20,
21,
100
);
check!(
alloc::vec::Vec<alloc::string::String>,
alloc::vec![
alloc::string::String::from("ab"),
alloc::string::String::from("cde")
],
0,
8,
16,
18,
26,
29,
30,
31
);
check!(u64, 42u64, 0, 4, 7, 8, 9);
check!(core::ops::Range<u32>, 0u32..5, 0, 4, 7, 8, 9);
}
#[test]
fn every_entrypoint_enforces_the_limit() {
let bytes = 42u64.to_le_bytes();
let short = Configuration::default().with_deserialization_size_limit::<4>();
let exact = Configuration::default().with_deserialization_size_limit::<8>();
let mut dst = MaybeUninit::<u64>::uninit();
assert!(matches!(
<u64 as Deserialize<_>>::deserialize_into(&bytes, &mut dst, short),
Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
));
assert!(<u64 as Deserialize<_>>::deserialize_into(&bytes, &mut dst, exact).is_ok());
fn from_into<C: Config>(src: &[u8], _config: C) -> ReadResult<u64> {
let mut dst = MaybeUninit::uninit();
<u64 as DeserializeOwned<C>>::deserialize_from_into(src, &mut dst)?;
Ok(unsafe { dst.assume_init() })
}
assert!(matches!(
from_into(bytes.as_slice(), short),
Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
));
assert_eq!(from_into(bytes.as_slice(), exact).unwrap(), 42);
let mut mutable = bytes;
assert!(matches!(
deserialize_mut::<u64, _>(&mut mutable, short),
Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
));
assert_eq!(deserialize_mut::<u64, _>(&mut mutable, exact).unwrap(), 42);
#[cfg(feature = "alloc")]
{
let elements = crate::serialize(&[1u32, 2, 3]).unwrap();
assert_eq!(elements.len(), 12);
assert!(matches!(
deserialize_with_context::<_, alloc::vec::Vec<u32>, _>(
crate::schema::context::Len(3),
&elements,
short,
),
Err(ReadError::Io(IoReadError::ReadSizeLimit(_)))
));
let twelve = Configuration::default().with_deserialization_size_limit::<12>();
assert_eq!(
deserialize_with_context::<_, alloc::vec::Vec<u32>, _>(
crate::schema::context::Len(3),
&elements,
twelve,
)
.unwrap(),
alloc::vec![1, 2, 3],
);
}
}
#[test]
fn limit_larger_than_input_is_clamped() {
let bytes = 42u64.to_le_bytes();
let generous = Configuration::default().with_deserialization_size_limit::<1024>();
assert_eq!(deserialize::<u64, _>(&bytes, generous).unwrap(), 42);
assert_eq!(deserialize_exact::<u64, _>(&bytes, generous).unwrap(), 42);
assert_eq!(
deserialize_from::<u64, _>(bytes.as_slice(), generous).unwrap(),
42
);
let mut mutable = bytes;
assert_eq!(
deserialize_mut::<u64, _>(&mut mutable, generous).unwrap(),
42
);
let mut one = [7u8];
assert_eq!(*<u8 as ZeroCopy<_>>::from_bytes(&one, generous).unwrap(), 7);
assert_eq!(
*<u8 as ZeroCopy<_>>::from_bytes_mut(&mut one, generous).unwrap(),
7
);
let mut trailing = [0u8; 9];
trailing[..8].copy_from_slice(&bytes);
assert!(matches!(
deserialize_exact::<u64, _>(&trailing, generous),
Err(ReadError::TrailingBytes)
));
}
#[test]
fn zero_copy_deserialization_honors_limit() {
let bytes = [42u8];
let limited = Configuration::default().with_deserialization_size_limit::<0>();
assert!(matches!(
<u8 as ZeroCopy<_>>::from_bytes(&bytes, limited),
Err(ReadError::Io(IoReadError::ReadSizeLimit(1)))
));
let mut bytes = bytes;
assert!(matches!(
<u8 as ZeroCopy<_>>::from_bytes_mut(&mut bytes, limited),
Err(ReadError::Io(IoReadError::ReadSizeLimit(1)))
));
}
}