Skip to main content

wincode/
len.rs

1//! Support for heterogenous sequence length encoding.
2use {
3    crate::{
4        SchemaRead, SchemaWrite, TypeMeta,
5        config::{ConfigCore, PREALLOCATION_SIZE_LIMIT_DISABLED},
6        error::{
7            PreallocationError, ReadResult, WriteResult, pointer_sized_decode_error,
8            preallocation_size_limit, write_length_encoding_overflow,
9        },
10        int_encoding::{ByteOrder, Endian},
11        io::{Reader, Writer},
12    },
13    core::{any::type_name, marker::PhantomData},
14};
15
16pub const PREALLOCATION_SIZE_LIMIT_USE_CONFIG: usize = 0;
17
18/// Cap a requested capacity at the `2^(8 * size_of::<K>())` distinct values a
19/// `K` can represent.
20///
21/// A collection that keys on a unique `K` cannot hold more entries than there
22/// are `K` values, so a malicious length keyed on a tiny type (ZST, `u8`, ...)
23/// can drive an oversized `with_capacity` the data can never fill, even within
24/// the preallocation limit. The cap is an upper bound on distinct keys, so it
25/// never under-allocates. `None` (representable count `>= usize::MAX`) means no
26/// useful cap.
27#[cfg(any(feature = "std", feature = "indexmap"))]
28#[inline]
29pub(crate) fn unique_key_capacity<K>(len: usize) -> usize {
30    match u32::try_from(size_of::<K>())
31        .ok()
32        .and_then(|bytes| bytes.checked_mul(u8::BITS))
33        .and_then(|bits| 1usize.checked_shl(bits))
34    {
35        Some(max_keys) => len.min(max_keys),
36        None => len,
37    }
38}
39
40/// [`SeqLen`] level override of configured preallocation size limit.
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
42pub enum PreallocationLimitOverride {
43    /// Use the configuration's preallocation size limit.
44    #[default]
45    UseConfig,
46    /// Override with no limit.
47    NoLimit,
48    /// Override with a specific limit, in bytes.
49    Override(usize),
50}
51
52impl PreallocationLimitOverride {
53    /// Convert the given [`PreallocationLimitOverride`] to an `Option<usize>`,
54    /// reconciling with the given configuration.
55    ///
56    /// If the override is [`PreallocationLimitOverride::UseConfig`], then the
57    /// configuration's preallocation size limit is returned.
58    /// If the override is [`PreallocationLimitOverride::NoLimit`], then `None` is returned.
59    /// Otherwise, the override is returned.
60    #[inline]
61    pub const fn to_opt_limit_with_config<C: ConfigCore>(self) -> Option<usize> {
62        match self {
63            PreallocationLimitOverride::UseConfig => C::PREALLOCATION_SIZE_LIMIT,
64            PreallocationLimitOverride::NoLimit => None,
65            PreallocationLimitOverride::Override(limit) => Some(limit),
66        }
67    }
68
69    /// Convert a raw preallocation usize value to a [`PreallocationLimitOverride`].
70    ///
71    /// Handles special case values [`PREALLOCATION_SIZE_LIMIT_USE_CONFIG`] and
72    /// [`PREALLOCATION_SIZE_LIMIT_DISABLED`].
73    #[inline]
74    pub const fn from_usize(limit: usize) -> Self {
75        match limit {
76            PREALLOCATION_SIZE_LIMIT_USE_CONFIG => PreallocationLimitOverride::UseConfig,
77            PREALLOCATION_SIZE_LIMIT_DISABLED => PreallocationLimitOverride::NoLimit,
78            _ => PreallocationLimitOverride::Override(limit),
79        }
80    }
81}
82
83/// Behavior to support heterogenous sequence length encoding.
84///
85/// It is possible for sequences to have different length encoding schemes.
86/// This trait abstracts over that possibility, allowing users to specify
87/// the length encoding scheme for a sequence.
88///
89/// # Safety
90///
91/// Implementors must adhere to the Safety section of the method `write_bytes_needed`.
92pub unsafe trait SeqLen<C: ConfigCore> {
93    /// [`SeqLen`] level override of configured preallocation size limit, in bytes.
94    ///
95    /// Allows specializing specific uses of a given [`SeqLen`] implementation
96    /// to override any configured preallocation size limit.
97    const PREALLOCATION_SIZE_LIMIT_OVERRIDE: PreallocationLimitOverride =
98        PreallocationLimitOverride::UseConfig;
99
100    #[inline]
101    fn prealloc_check<T>(len: usize) -> Result<(), PreallocationError> {
102        fn check(len: usize, type_size: usize, limit: usize) -> Result<(), PreallocationError> {
103            let needed = len
104                .checked_mul(type_size)
105                .ok_or_else(|| preallocation_size_limit(usize::MAX, limit))?;
106            if needed > limit {
107                return Err(preallocation_size_limit(needed, limit));
108            }
109            Ok(())
110        }
111        // Everything here can be const-folded by the compiler.
112        if let Some(prealloc_limit) =
113            Self::PREALLOCATION_SIZE_LIMIT_OVERRIDE.to_opt_limit_with_config::<C>()
114        {
115            // ZSTs do not require element storage in Vec-like containers, but
116            // user-controlled lengths still drive iteration and may allocate
117            // collection metadata. Charge one byte per ZST element so the
118            // preallocation limit also bounds sequence length.
119            check(len, size_of::<T>().max(1), prealloc_limit)?;
120        }
121        Ok(())
122    }
123
124    /// Read the length of a sequence from the reader, where
125    /// `T` is the type of the sequence elements. This can be used to
126    /// enforce size constraints for preallocations.
127    ///
128    /// May return an error if some length condition is not met
129    /// (e.g., size constraints, overflow, etc.).
130    #[inline]
131    fn read_prealloc_check<'de, T>(reader: impl Reader<'de>) -> ReadResult<usize> {
132        let len = Self::read(reader)?;
133        Self::prealloc_check::<T>(len)?;
134        Ok(len)
135    }
136    /// Read the length of a sequence, without doing any preallocation size checks.
137    ///
138    /// Note this may still return typical read errors and there is no unsafety implied.
139    fn read<'de>(reader: impl Reader<'de>) -> ReadResult<usize>;
140    /// Write the length of a sequence to the writer.
141    fn write(writer: impl Writer, len: usize) -> WriteResult<()>;
142    /// Calculate the number of bytes needed to write the given length.
143    ///
144    /// Return an error if the written size would be larger than the
145    /// corresponding allocation limit while reading.
146    ///
147    /// # Safety
148    ///
149    /// If `Ok(…)` is returned, it must contain the exact number of bytes
150    /// written by the `write` function for this particular object instance.
151    fn write_bytes_needed_prealloc_check<T>(len: usize) -> WriteResult<usize> {
152        Self::prealloc_check::<T>(len)?;
153        Self::write_bytes_needed(len)
154    }
155    /// Calculate the number of bytes needed to write the given length.
156    ///
157    /// Useful for variable length encoding schemes.
158    ///
159    /// # Safety
160    ///
161    /// If `Ok(…)` is returned, it must contain the exact number of bytes
162    /// written by the `write` function for this particular object instance.
163    fn write_bytes_needed(len: usize) -> WriteResult<usize>;
164}
165
166/// Use the configuration's integer encoding for sequence length encoding.
167///
168/// For example, if the configuration's integer encoding is `FixInt`, then `UseIntLen<u64>`
169/// will use the fixed-width u64 encoding.
170/// If the configuration's integer encoding is `VarInt`, then `UseIntLen<u64>` will use
171/// the variable-width u64 encoding.
172///
173/// This is bincode's default behavior.
174///
175/// Allows overriding the preallocation size limit per individual use.
176///
177/// # Examples
178///
179/// Override the preallocation size limit to 8 bytes.
180///
181/// ```
182/// # #[cfg(feature = "alloc")] {
183/// # use wincode::{containers, len::UseIntLen, SchemaRead, SchemaWrite};
184/// type Max8Bytes = UseIntLen<u32, 8>;
185///
186/// #[derive(SchemaWrite, SchemaRead)]
187/// struct OverrideLen {
188///     #[wincode(with = "containers::Vec<u8, Max8Bytes>")]
189///     bytes: Vec<u8>,
190/// }
191///
192/// let data_ok = OverrideLen { bytes: vec![0; 8] };
193/// let serialized = wincode::serialize(&data_ok).unwrap();
194/// assert!(wincode::deserialize::<OverrideLen>(&serialized).is_ok());
195///
196/// let data_err = OverrideLen { bytes: vec![0; 9] };
197/// assert!(wincode::serialize(&data_err).is_err());
198/// let serialized = wincode::serialize(&vec![0; 9]).unwrap();
199/// assert!(wincode::deserialize::<OverrideLen>(&serialized).is_err());
200/// # }
201/// ```
202pub struct UseIntLen<T, const PREALLOCATION_SIZE_LIMIT: usize = PREALLOCATION_SIZE_LIMIT_USE_CONFIG>(
203    PhantomData<T>,
204);
205
206unsafe impl<const PREALLOCATION_SIZE_LIMIT: usize, T, C: ConfigCore> SeqLen<C>
207    for UseIntLen<T, PREALLOCATION_SIZE_LIMIT>
208where
209    T: SchemaWrite<C> + for<'de> SchemaRead<'de, C>,
210    T::Src: TryFrom<usize>,
211    usize: for<'de> TryFrom<<T as SchemaRead<'de, C>>::Dst>,
212{
213    const PREALLOCATION_SIZE_LIMIT_OVERRIDE: PreallocationLimitOverride =
214        PreallocationLimitOverride::from_usize(PREALLOCATION_SIZE_LIMIT);
215
216    #[inline(always)]
217    fn read<'de>(reader: impl Reader<'de>) -> ReadResult<usize> {
218        let len = T::get(reader)?;
219        let Ok(len) = usize::try_from(len) else {
220            return Err(pointer_sized_decode_error());
221        };
222        Ok(len)
223    }
224
225    #[inline(always)]
226    fn write(writer: impl Writer, len: usize) -> WriteResult<()> {
227        let Ok(len) = T::Src::try_from(len) else {
228            return Err(write_length_encoding_overflow(type_name::<T::Src>()));
229        };
230        T::write(writer, &len)
231    }
232
233    #[inline(always)]
234    fn write_bytes_needed(len: usize) -> WriteResult<usize> {
235        if let TypeMeta::Static { size, .. } = <T as SchemaWrite<C>>::TYPE_META {
236            return Ok(size);
237        }
238        let Ok(len) = T::Src::try_from(len) else {
239            return Err(write_length_encoding_overflow(type_name::<T::Src>()));
240        };
241        T::size_of(&len)
242    }
243}
244
245/// Allow using integer primitives directly as [`SeqLen`].
246///
247/// Will use the configuration's integer encoding.
248macro_rules! impl_use_int_primitive {
249    ($($type:ty),+) => {
250        $(
251            unsafe impl<C: ConfigCore> SeqLen<C> for $type {
252                #[inline(always)]
253                #[allow(irrefutable_let_patterns)]
254                fn read<'de>(reader: impl Reader<'de>) -> ReadResult<usize> {
255                    let len = <$type as SchemaRead<C>>::get(reader)?;
256                    let Ok(len) = usize::try_from(len) else {
257                        return Err(pointer_sized_decode_error());
258                    };
259                    Ok(len)
260                }
261
262                #[inline(always)]
263                fn write(writer: impl Writer, len: usize) -> WriteResult<()> {
264                    let Ok(len) = <$type>::try_from(len) else {
265                        return Err(write_length_encoding_overflow(type_name::<$type>()));
266                    };
267                    <$type as SchemaWrite<C>>::write(writer, &len)
268                }
269
270                #[inline(always)]
271                fn write_bytes_needed(len: usize) -> WriteResult<usize> {
272                    if let TypeMeta::Static { size, .. } = <$type as SchemaWrite<C>>::TYPE_META {
273                        return Ok(size);
274                    }
275                    let Ok(len) = <$type>::try_from(len) else {
276                        return Err(write_length_encoding_overflow(type_name::<$type>()));
277                    };
278                    <$type as SchemaWrite<C>>::size_of(&len)
279                }
280            }
281        )+
282    };
283}
284
285impl_use_int_primitive!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
286
287/// Fixed-width integer length encoding.
288///
289/// Integers respect the configured byte order.
290///
291/// Allows overriding the preallocation size limit per individual use.
292///
293/// # Examples
294///
295/// Override the preallocation size limit to 8 bytes.
296///
297/// ```
298/// # #[cfg(feature = "alloc")] {
299/// # use wincode::{containers, len::FixIntLen, SchemaRead, SchemaWrite};
300/// type Max8Bytes = FixIntLen<u32, 8>;
301///
302/// #[derive(SchemaWrite, SchemaRead)]
303/// struct OverrideLen {
304///     #[wincode(with = "containers::Vec<u8, Max8Bytes>")]
305///     bytes: Vec<u8>,
306/// }
307///
308/// let data_ok = OverrideLen { bytes: vec![0; 8] };
309/// let serialized = wincode::serialize(&data_ok).unwrap();
310/// assert!(wincode::deserialize::<OverrideLen>(&serialized).is_ok());
311///
312/// let data_err = OverrideLen { bytes: vec![0; 9] };
313/// assert!(wincode::serialize(&data_err).is_err());
314/// let serialized = wincode::serialize(&vec![0; 9]).unwrap();
315/// assert!(wincode::deserialize::<OverrideLen>(&serialized).is_err());
316/// # }
317/// ```
318pub struct FixIntLen<T, const PREALLOCATION_SIZE_LIMIT: usize = PREALLOCATION_SIZE_LIMIT_USE_CONFIG>(
319    PhantomData<T>,
320);
321
322macro_rules! impl_fix_int {
323    ($type:ty) => {
324        unsafe impl<const PREALLOCATION_SIZE_LIMIT: usize, C: ConfigCore> SeqLen<C>
325            for FixIntLen<$type, PREALLOCATION_SIZE_LIMIT>
326        {
327            const PREALLOCATION_SIZE_LIMIT_OVERRIDE: PreallocationLimitOverride =
328                PreallocationLimitOverride::from_usize(PREALLOCATION_SIZE_LIMIT);
329
330            #[inline(always)]
331            #[allow(irrefutable_let_patterns)]
332            fn read<'de>(mut reader: impl Reader<'de>) -> ReadResult<usize> {
333                let bytes = reader.take_array::<{ size_of::<$type>() }>()?;
334                let len = match C::ByteOrder::ENDIAN {
335                    Endian::Big => <$type>::from_be_bytes(bytes),
336                    Endian::Little => <$type>::from_le_bytes(bytes),
337                };
338                let Ok(len) = usize::try_from(len) else {
339                    return Err(pointer_sized_decode_error());
340                };
341                Ok(len)
342            }
343
344            #[inline(always)]
345            fn write(mut writer: impl Writer, len: usize) -> WriteResult<()> {
346                let Ok(len) = <$type>::try_from(len) else {
347                    return Err(write_length_encoding_overflow(type_name::<$type>()));
348                };
349                let bytes = match C::ByteOrder::ENDIAN {
350                    Endian::Big => len.to_be_bytes(),
351                    Endian::Little => len.to_le_bytes(),
352                };
353                writer.write(&bytes)?;
354                Ok(())
355            }
356
357            #[inline(always)]
358            fn write_bytes_needed(_: usize) -> WriteResult<usize> {
359                Ok(size_of::<$type>())
360            }
361        }
362    };
363}
364
365impl_fix_int!(u8);
366impl_fix_int!(u16);
367impl_fix_int!(u32);
368impl_fix_int!(u64);
369impl_fix_int!(u128);
370
371impl_fix_int!(i8);
372impl_fix_int!(i16);
373impl_fix_int!(i32);
374impl_fix_int!(i64);
375impl_fix_int!(i128);
376
377/// Bincode always uses a `u64` encoded with the configuration's integer encoding.
378///
379/// Allows overriding the preallocation size limit per individual use.
380///
381/// # Examples
382///
383/// Override the preallocation size limit to 8 bytes.
384///
385/// ```
386/// # #[cfg(feature = "alloc")] {
387/// # use wincode::{containers, len::BincodeLen, SchemaRead, SchemaWrite};
388/// type Max8Bytes = BincodeLen<8>;
389///
390/// #[derive(SchemaWrite, SchemaRead)]
391/// struct OverrideLen {
392///     #[wincode(with = "containers::Vec<u8, Max8Bytes>")]
393///     bytes: Vec<u8>,
394/// }
395///
396/// let data_ok = OverrideLen { bytes: vec![0; 8] };
397/// let serialized = wincode::serialize(&data_ok).unwrap();
398/// assert!(wincode::deserialize::<OverrideLen>(&serialized).is_ok());
399///
400/// let data_err = OverrideLen { bytes: vec![0; 9] };
401/// assert!(wincode::serialize(&data_err).is_err());
402/// let serialized = wincode::serialize(&vec![0; 9]).unwrap();
403/// assert!(wincode::deserialize::<OverrideLen>(&serialized).is_err());
404/// # }
405/// ```
406pub type BincodeLen<const PREALLOCATION_SIZE_LIMIT: usize = PREALLOCATION_SIZE_LIMIT_USE_CONFIG> =
407    UseIntLen<u64, PREALLOCATION_SIZE_LIMIT>;
408
409#[cfg(all(test, any(feature = "std", feature = "indexmap")))]
410mod tests {
411    use super::unique_key_capacity;
412
413    #[test]
414    fn caps_at_representable_key_count() {
415        // ZST: 1 value; u8: 256; u16: 65536. `len` below the cap is untouched;
416        // 8+ byte keys represent >= usize::MAX values, so no cap applies.
417        assert_eq!(unique_key_capacity::<()>(usize::MAX), 1);
418        assert_eq!(unique_key_capacity::<()>(0), 0);
419        assert_eq!(unique_key_capacity::<u8>(usize::MAX), 256);
420        assert_eq!(unique_key_capacity::<u8>(10), 10);
421        assert_eq!(unique_key_capacity::<u16>(usize::MAX), 1 << 16);
422        assert_eq!(unique_key_capacity::<u64>(usize::MAX), usize::MAX);
423        assert_eq!(unique_key_capacity::<[u8; 32]>(12_345), 12_345);
424    }
425}