Skip to main content

wincode/config/
serde.rs

1//! Configuration-aware serialize / deserialize traits and functions.
2#[cfg(feature = "alloc")]
3use alloc::vec::Vec;
4use {
5    crate::{
6        ReadResult, SchemaRead, SchemaReadContext, SchemaReadOwned, SchemaWrite, WriteResult,
7        config::{Config, ConfigCore},
8        error,
9        io::{Reader, Writer},
10    },
11    core::mem::MaybeUninit,
12};
13
14/// Like [`crate::Serialize`], but allows the caller to provide a custom configuration.
15pub trait Serialize<C: Config>: SchemaWrite<C> {
16    /// Serialize a serializable type into a `Vec` of bytes.
17    #[cfg(feature = "alloc")]
18    fn serialize(src: &Self::Src, config: C) -> WriteResult<Vec<u8>> {
19        let capacity = Self::size_of(src)?;
20        let mut buffer = Vec::with_capacity(capacity);
21        let mut writer = buffer.spare_capacity_mut();
22        Self::serialize_into(writer.by_ref(), src, config)?;
23        let len = writer.len();
24        unsafe {
25            #[allow(clippy::arithmetic_side_effects)]
26            buffer.set_len(capacity - len);
27        }
28        Ok(buffer)
29    }
30
31    /// Serialize a serializable type into the given [`Writer`].
32    ///
33    /// # Partial writes
34    ///
35    /// This operation is not transactional. If it returns an error, the writer
36    /// may already contain a prefix of the serialized value. Dynamically sized
37    /// values in particular may discover insufficient capacity only after
38    /// preceding fields have been written.
39    ///
40    /// If the destination must remain unchanged on failure, serialize into a
41    /// temporary buffer and copy the result only after serialization succeeds.
42    /// For a fixed-size destination, callers can instead use
43    /// [`Self::serialized_size`] with the same configuration to check that
44    /// enough space is available first.
45    #[inline]
46    #[expect(unused_variables)]
47    fn serialize_into(mut dst: impl Writer, src: &Self::Src, config: C) -> WriteResult<()> {
48        Self::write(dst.by_ref(), src)?;
49        dst.finish()?;
50        Ok(())
51    }
52
53    /// Get the size in bytes of the type when serialized.
54    #[inline]
55    #[expect(unused_variables)]
56    fn serialized_size(src: &Self::Src, config: C) -> WriteResult<u64> {
57        Self::size_of(src).map(|size| size as u64)
58    }
59}
60
61impl<T, C: Config> Serialize<C> for T where T: SchemaWrite<C> + ?Sized {}
62
63macro_rules! maybe_size_limit {
64    ($config:ty, $src:expr, $kind:ident, |$reader:ident| $body:expr $(,)?) => {{
65        let src = $src;
66
67        match <$config as $crate::config::ConfigCore>::DESERIALIZATION_SIZE_LIMIT {
68            Some(limit) => {
69                let $reader = maybe_size_limit!(@reader $kind, src, limit);
70                $body
71            }
72            None => {
73                let $reader = src;
74                $body
75            }
76        }
77    }};
78
79    (@reader generic, $src:ident, $limit:ident) => {
80        $crate::io::LimitReader::new($src, $limit)
81    };
82
83    (@reader slice, $src:ident, $limit:ident) => {{
84        let src = $src;
85        let len = $limit.min(src.len());
86        &src[..len]
87    }};
88
89    (@reader slice_mut, $src:ident, $limit:ident) => {{
90        let src = $src;
91        let len = $limit.min(src.len());
92        &mut src[..len]
93    }};
94}
95
96/// Like [`crate::Deserialize`], but allows the caller to provide a custom configuration.
97pub trait Deserialize<'de, C: Config>: SchemaRead<'de, C> {
98    /// Deserialize the input bytes into a new `Self::Dst`.
99    #[inline(always)]
100    #[expect(unused_variables)]
101    fn deserialize(src: &'de [u8], config: C) -> ReadResult<Self::Dst> {
102        maybe_size_limit!(C, src, slice, |reader| Self::get(reader))
103    }
104
105    /// Deserialize the input bytes into `dst`.
106    #[inline]
107    #[expect(unused_variables)]
108    fn deserialize_into(
109        src: &'de [u8],
110        dst: &mut MaybeUninit<Self::Dst>,
111        config: C,
112    ) -> ReadResult<()> {
113        maybe_size_limit!(C, src, slice, |reader| Self::read(reader, dst))
114    }
115}
116
117impl<'de, T, C: Config> Deserialize<'de, C> for T where T: SchemaRead<'de, C> {}
118
119/// Like [`crate::DeserializeOwned`], but allows the caller to provide a custom configuration.
120pub trait DeserializeOwned<C: Config>: SchemaReadOwned<C> {
121    /// Deserialize from the given [`Reader`] into a new `Self::Dst`.
122    #[inline(always)]
123    fn deserialize_from<'de>(
124        src: impl Reader<'de>,
125    ) -> ReadResult<<Self as SchemaRead<'de, C>>::Dst> {
126        maybe_size_limit!(C, src, generic, |reader| Self::get(reader))
127    }
128
129    /// Deserialize from the given [`Reader`] into `dst`.
130    #[inline]
131    fn deserialize_from_into<'de>(
132        src: impl Reader<'de>,
133        dst: &mut MaybeUninit<<Self as SchemaRead<'de, C>>::Dst>,
134    ) -> ReadResult<()> {
135        maybe_size_limit!(C, src, generic, |reader| Self::read(reader, dst))
136    }
137}
138
139impl<T, C: Config> DeserializeOwned<C> for T where T: SchemaReadOwned<C> {}
140
141/// Like [`crate::serialize`], but allows the caller to provide a custom configuration.
142///
143/// # Examples
144///
145/// ```
146/// # #[cfg(feature = "alloc")] {
147/// # use wincode::{config::Configuration, len::FixIntLen};
148/// let config = Configuration::default().with_length_encoding::<FixIntLen<u32>>();
149/// let vec: Vec<u8> = vec![1, 2, 3];
150/// let bytes = wincode::config::serialize(&vec, config).unwrap();
151/// assert_eq!(vec.len(), u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize);
152/// # }
153/// ```
154#[cfg(feature = "alloc")]
155pub fn serialize<T, C: Config>(src: &T, config: C) -> WriteResult<Vec<u8>>
156where
157    T: SchemaWrite<C, Src = T> + ?Sized,
158{
159    T::serialize(src, config)
160}
161
162/// Like [`crate::serialize_into`], but allows the caller to provide a custom configuration.
163///
164/// This has the same non-transactional, partial-write behavior documented by
165/// [`crate::serialize_into`].
166#[inline]
167pub fn serialize_into<T, C: Config>(dst: impl Writer, src: &T, config: C) -> WriteResult<()>
168where
169    T: SchemaWrite<C, Src = T> + ?Sized,
170{
171    T::serialize_into(dst, src, config)
172}
173
174/// Like [`crate::serialized_size`], but allows the caller to provide a custom configuration.
175#[inline]
176pub fn serialized_size<T, C: Config>(src: &T, config: C) -> WriteResult<u64>
177where
178    T: SchemaWrite<C, Src = T> + ?Sized,
179{
180    T::serialized_size(src, config)
181}
182
183/// Like [`crate::deserialize`], but allows the caller to provide a custom configuration.
184///
185/// # Examples
186///
187/// ```
188/// # #[cfg(feature = "alloc")] {
189/// # use wincode::{config::Configuration, len::FixIntLen};
190/// let config = Configuration::default().with_length_encoding::<FixIntLen<u32>>();
191/// let vec: Vec<u8> = vec![1, 2, 3];
192/// let bytes = wincode::config::serialize(&vec, config).unwrap();
193/// let deserialized: Vec<u8> = wincode::config::deserialize(&bytes, config).unwrap();
194/// assert_eq!(vec.len(), u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize);
195/// assert_eq!(vec, deserialized);
196/// # }
197/// ```
198#[inline(always)]
199pub fn deserialize<'de, T, C: Config>(src: &'de [u8], config: C) -> ReadResult<T>
200where
201    T: SchemaRead<'de, C, Dst = T>,
202{
203    T::deserialize(src, config)
204}
205
206/// Like [`crate::deserialize_exact`], but with a custom configuration.
207///
208/// # Examples
209///
210/// ```
211/// # #[cfg(feature = "alloc")] {
212/// # use wincode::config::Configuration;
213/// let config = Configuration::default();
214/// let bytes = wincode::config::serialize(&123u64, config).unwrap();
215/// let value: u64 = wincode::config::deserialize_exact(&bytes, config).unwrap();
216/// assert_eq!(value, 123);
217///
218/// let mut extra = bytes.clone();
219/// extra.push(0xAA);
220/// assert!(wincode::config::deserialize_exact::<u64, _>(&extra, config).is_err());
221/// # }
222/// ```
223#[inline(always)]
224#[expect(unused_variables)]
225pub fn deserialize_exact<'de, T, C: Config>(mut src: &'de [u8], config: C) -> ReadResult<T>
226where
227    T: SchemaRead<'de, C, Dst = T>,
228{
229    let value = match C::DESERIALIZATION_SIZE_LIMIT {
230        Some(limit) => {
231            let mut limited = &src[..limit.min(src.len())];
232            let initial_len = limited.len();
233            let value = T::get(limited.by_ref())?;
234
235            #[expect(clippy::arithmetic_side_effects)]
236            let consumed = initial_len - limited.len();
237            src = &src[consumed..];
238
239            value
240        }
241        None => T::get(src.by_ref())?,
242    };
243
244    if src.is_empty() {
245        Ok(value)
246    } else {
247        Err(error::trailing_bytes())
248    }
249}
250
251/// Like [`crate::deserialize_with_context`], but allows the caller to provide a custom configuration.
252#[inline(always)]
253#[expect(unused_variables)]
254pub fn deserialize_with_context<'de, Ctx, T, C: Config>(
255    ctx: Ctx,
256    src: &'de [u8],
257    config: C,
258) -> ReadResult<T>
259where
260    T: SchemaReadContext<'de, C, Ctx, Dst = T>,
261{
262    maybe_size_limit!(C, src, slice, |reader| T::get_with_context(ctx, reader))
263}
264
265/// Like [`crate::deserialize_mut`], but allows the caller to provide a custom configuration.
266#[inline(always)]
267#[expect(unused_variables)]
268pub fn deserialize_mut<'de, T, C: Config>(src: &'de mut [u8], config: C) -> ReadResult<T>
269where
270    T: SchemaRead<'de, C, Dst = T>,
271{
272    maybe_size_limit!(C, src, slice_mut, |reader| T::get(reader))
273}
274
275/// Like [`crate::deserialize_from`], but allows the caller to provide a custom configuration.
276#[inline(always)]
277#[expect(unused_variables)]
278pub fn deserialize_from<'de, T, C: Config>(src: impl Reader<'de>, config: C) -> ReadResult<T>
279where
280    T: SchemaReadOwned<C, Dst = T>,
281{
282    T::deserialize_from(src)
283}
284
285/// Marker trait for types that can be deserialized via direct borrows from a [`Reader`].
286///
287/// <div class="warning">
288/// You should not manually implement this trait for your own type unless you absolutely
289/// know what you're doing. The derive macros will automatically implement this trait for your type
290/// if it is eligible for zero-copy deserialization.
291/// </div>
292///
293/// # Safety
294///
295/// - The type must not have any invalid bit patterns, no layout requirements, no endianness checks, etc.
296pub unsafe trait ZeroCopy<C: ConfigCore>: 'static {
297    /// Like [`crate::ZeroCopy::from_bytes`], but allows the caller to provide a custom configuration.
298    #[inline(always)]
299    #[expect(unused_variables)]
300    fn from_bytes<'de>(bytes: &'de [u8], config: C) -> ReadResult<&'de Self>
301    where
302        Self: SchemaRead<'de, C, Dst = Self> + Sized,
303    {
304        maybe_size_limit!(
305            C,
306            bytes,
307            slice,
308            |reader| <&Self as SchemaRead<'de, C>>::get(reader)
309        )
310    }
311
312    /// Like [`crate::ZeroCopy::from_bytes_mut`], but allows the caller to provide a custom configuration.
313    #[inline(always)]
314    #[expect(unused_variables)]
315    fn from_bytes_mut<'de>(bytes: &'de mut [u8], config: C) -> ReadResult<&'de mut Self>
316    where
317        Self: SchemaRead<'de, C, Dst = Self> + Sized,
318    {
319        maybe_size_limit!(C, bytes, slice_mut, |reader| <&mut Self as SchemaRead<
320            'de,
321            C,
322        >>::get(reader))
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use {
329        super::*,
330        crate::{ReadError, config::Configuration, io::ReadError as IoReadError},
331    };
332
333    #[test]
334    fn configured_deserialization_limit_is_enforced() {
335        let bytes = 42u64.to_le_bytes();
336        let limited = Configuration::default().with_deserialization_size_limit::<4>();
337
338        assert!(matches!(
339            deserialize::<u64, _>(&bytes, limited),
340            Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
341        ));
342        assert!(matches!(
343            deserialize_from::<u64, _>(bytes.as_slice(), limited),
344            Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
345        ));
346
347        let exact = Configuration::default().with_deserialization_size_limit::<8>();
348        assert_eq!(deserialize::<u64, _>(&bytes, exact).unwrap(), 42);
349        assert_eq!(deserialize_exact::<u64, _>(&bytes, exact).unwrap(), 42);
350
351        assert!(matches!(
352            deserialize_exact::<u64, _>(&bytes, limited),
353            Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
354        ));
355
356        let mut trailing = [0; 9];
357        trailing[..8].copy_from_slice(&bytes);
358        assert!(matches!(
359            deserialize_exact::<u64, _>(&trailing, exact),
360            Err(ReadError::TrailingBytes)
361        ));
362        let larger = Configuration::default().with_deserialization_size_limit::<9>();
363        assert!(matches!(
364            deserialize_exact::<u64, _>(&trailing, larger),
365            Err(ReadError::TrailingBytes)
366        ));
367
368        let disabled = limited.disable_deserialization_size_limit();
369        assert_eq!(deserialize::<u64, _>(&bytes, disabled).unwrap(), 42);
370    }
371
372    /// Slice paths enforce the limit through the available input length, while generic paths
373    /// track cumulative reservations with [`LimitReader`](crate::io::LimitReader). Ordinary
374    /// deserialization must accept and reject the same inputs at the same limits.
375    #[cfg(feature = "alloc")]
376    #[test]
377    fn slice_and_generic_paths_agree_at_every_limit() {
378        macro_rules! check {
379            ($ty:ty, $val:expr, $($limit:literal),+) => {$({
380                let bytes = crate::serialize(&$val).unwrap();
381                let cfg = Configuration::default().with_deserialization_size_limit::<$limit>();
382                let via_slice = deserialize::<$ty, _>(&bytes, cfg);
383                let via_reader = deserialize_from::<$ty, _>(bytes.as_slice(), cfg);
384                assert_eq!(
385                    via_slice.is_ok(),
386                    via_reader.is_ok(),
387                    "{} at limit {}: slice={:?} reader={:?}",
388                    stringify!($ty),
389                    $limit,
390                    via_slice.as_ref().err(),
391                    via_reader.as_ref().err(),
392                );
393                if let (Err(a), Err(b)) = (via_slice, via_reader) {
394                    assert_eq!(
395                        alloc::format!("{a:?}"),
396                        alloc::format!("{b:?}"),
397                        "{} at limit {}: differing errors",
398                        stringify!($ty),
399                        $limit,
400                    );
401                }
402            })+};
403        }
404
405        // `Vec<u32>`: 8-byte length prefix followed by a trusted window of `4 * len` bytes.
406        check!(
407            alloc::vec::Vec<u32>,
408            alloc::vec![1u32, 2, 3],
409            0,
410            1,
411            7,
412            8,
413            9,
414            12,
415            16,
416            19,
417            20,
418            21,
419            100
420        );
421        // `Vec<String>`: nested length prefixes and no single trusted window.
422        check!(
423            alloc::vec::Vec<alloc::string::String>,
424            alloc::vec![
425                alloc::string::String::from("ab"),
426                alloc::string::String::from("cde")
427            ],
428            0,
429            8,
430            16,
431            18,
432            26,
433            29,
434            30,
435            31
436        );
437        // Fixed-size types.
438        check!(u64, 42u64, 0, 4, 7, 8, 9);
439        check!(core::ops::Range<u32>, 0u32..5, 0, 4, 7, 8, 9);
440    }
441
442    #[test]
443    fn every_entrypoint_enforces_the_limit() {
444        let bytes = 42u64.to_le_bytes();
445        let short = Configuration::default().with_deserialization_size_limit::<4>();
446        let exact = Configuration::default().with_deserialization_size_limit::<8>();
447
448        let mut dst = MaybeUninit::<u64>::uninit();
449        assert!(matches!(
450            <u64 as Deserialize<_>>::deserialize_into(&bytes, &mut dst, short),
451            Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
452        ));
453        assert!(<u64 as Deserialize<_>>::deserialize_into(&bytes, &mut dst, exact).is_ok());
454
455        // `deserialize_from_into` takes no config argument, so bind `C` through a call.
456        fn from_into<C: Config>(src: &[u8], _config: C) -> ReadResult<u64> {
457            let mut dst = MaybeUninit::uninit();
458            <u64 as DeserializeOwned<C>>::deserialize_from_into(src, &mut dst)?;
459            // SAFETY: `deserialize_from_into` returned `Ok`, initializing `dst`.
460            Ok(unsafe { dst.assume_init() })
461        }
462        assert!(matches!(
463            from_into(bytes.as_slice(), short),
464            Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
465        ));
466        assert_eq!(from_into(bytes.as_slice(), exact).unwrap(), 42);
467
468        let mut mutable = bytes;
469        assert!(matches!(
470            deserialize_mut::<u64, _>(&mut mutable, short),
471            Err(ReadError::Io(IoReadError::ReadSizeLimit(8)))
472        ));
473        assert_eq!(deserialize_mut::<u64, _>(&mut mutable, exact).unwrap(), 42);
474
475        #[cfg(feature = "alloc")]
476        {
477            // `Vec<u32>` with a caller-supplied length reads `4 * len` bytes and no length prefix.
478            let elements = crate::serialize(&[1u32, 2, 3]).unwrap();
479            assert_eq!(elements.len(), 12);
480            assert!(matches!(
481                deserialize_with_context::<_, alloc::vec::Vec<u32>, _>(
482                    crate::schema::context::Len(3),
483                    &elements,
484                    short,
485                ),
486                Err(ReadError::Io(IoReadError::ReadSizeLimit(_)))
487            ));
488            let twelve = Configuration::default().with_deserialization_size_limit::<12>();
489            assert_eq!(
490                deserialize_with_context::<_, alloc::vec::Vec<u32>, _>(
491                    crate::schema::context::Len(3),
492                    &elements,
493                    twelve,
494                )
495                .unwrap(),
496                alloc::vec![1, 2, 3],
497            );
498        }
499    }
500
501    #[test]
502    fn limit_larger_than_input_is_clamped() {
503        let bytes = 42u64.to_le_bytes();
504        let generous = Configuration::default().with_deserialization_size_limit::<1024>();
505
506        assert_eq!(deserialize::<u64, _>(&bytes, generous).unwrap(), 42);
507        assert_eq!(deserialize_exact::<u64, _>(&bytes, generous).unwrap(), 42);
508        assert_eq!(
509            deserialize_from::<u64, _>(bytes.as_slice(), generous).unwrap(),
510            42
511        );
512
513        let mut mutable = bytes;
514        assert_eq!(
515            deserialize_mut::<u64, _>(&mut mutable, generous).unwrap(),
516            42
517        );
518
519        let mut one = [7u8];
520        assert_eq!(*<u8 as ZeroCopy<_>>::from_bytes(&one, generous).unwrap(), 7);
521        assert_eq!(
522            *<u8 as ZeroCopy<_>>::from_bytes_mut(&mut one, generous).unwrap(),
523            7
524        );
525
526        // Trailing bytes are still detected when the limit exceeds the input.
527        let mut trailing = [0u8; 9];
528        trailing[..8].copy_from_slice(&bytes);
529        assert!(matches!(
530            deserialize_exact::<u64, _>(&trailing, generous),
531            Err(ReadError::TrailingBytes)
532        ));
533    }
534
535    #[test]
536    fn zero_copy_deserialization_honors_limit() {
537        let bytes = [42u8];
538        let limited = Configuration::default().with_deserialization_size_limit::<0>();
539
540        assert!(matches!(
541            <u8 as ZeroCopy<_>>::from_bytes(&bytes, limited),
542            Err(ReadError::Io(IoReadError::ReadSizeLimit(1)))
543        ));
544
545        let mut bytes = bytes;
546        assert!(matches!(
547            <u8 as ZeroCopy<_>>::from_bytes_mut(&mut bytes, limited),
548            Err(ReadError::Io(IoReadError::ReadSizeLimit(1)))
549        ));
550    }
551}