Skip to main content

wincode/schema/
int_encoding.rs

1//! Integer encoding and byte order configuration.
2//!
3//! This module defines the [`ByteOrder`] markers and the [`IntEncoding`] trait used
4//! by configuration types to control how integers are serialized.
5use {
6    crate::{
7        ReadResult, WriteResult,
8        config::{ConfigCore, ZeroCopy},
9        error::invalid_tag_encoding,
10        io::{Reader, Writer},
11    },
12    pastey::paste,
13};
14
15/// Byte order trait.
16///
17/// Used for constraining byte order configuration in type bounds.
18pub trait ByteOrder: 'static {
19    const ENDIAN: Endian;
20}
21
22/// Endianness enum.
23///
24/// Used for term-level evaluation of byte order.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Endian {
27    Big,
28    Little,
29}
30
31/// Big-endian [`ByteOrder`] marker.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct BigEndian;
34
35/// Marker trait for constraining by platform endianness.
36///
37/// For example, [`LittleEndian`] only satisfies [`PlatformEndian`] on little-endian platforms.
38/// This can be used to, for example, constrain [`ZeroCopy`] implementations only when the
39/// configured byte order matches the platform endianness.
40///
41/// # Safety
42///
43/// Implementations must ensure that implementations are gated by the correct platform endianness.
44pub unsafe trait PlatformEndian {}
45
46#[cfg(target_endian = "big")]
47unsafe impl PlatformEndian for BigEndian {}
48
49/// Little-endian [`ByteOrder`] marker.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct LittleEndian;
52
53#[cfg(target_endian = "little")]
54unsafe impl PlatformEndian for LittleEndian {}
55
56impl ByteOrder for BigEndian {
57    const ENDIAN: Endian = Endian::Big;
58}
59
60impl ByteOrder for LittleEndian {
61    const ENDIAN: Endian = Endian::Little;
62}
63
64/// Implement encoding and decoding using a fixed-width implementation.
65///
66/// This will use the configured byte order and call the associated
67/// `to_be_bytes`, `from_be_bytes`, `to_le_bytes` or `from_le_bytes` method.
68macro_rules! impl_fix_int {
69    ($byte_order:ty => $($ty:ty),*) => {
70        paste! {
71            $(
72                #[inline(always)]
73                fn [<encode_ $ty>] (val: $ty, mut writer: impl Writer) -> WriteResult<()> {
74                    let bytes = match <$byte_order>::ENDIAN {
75                        Endian::Big => val.to_be_bytes(),
76                        Endian::Little => val.to_le_bytes(),
77                    };
78                    Ok(writer.write(&bytes)?)
79                }
80
81                #[inline(always)]
82                fn [<decode_ $ty>] <'de>(mut reader: impl Reader<'de>) -> ReadResult<$ty> {
83                    let bytes = reader.take_array::<{ size_of::<$ty>() }>()?;
84
85                    let val = match <$byte_order>::ENDIAN {
86                        Endian::Big => <$ty>::from_be_bytes(bytes),
87                        Endian::Little => <$ty>::from_le_bytes(bytes),
88                    };
89
90                    Ok(val)
91                }
92
93                #[inline(always)]
94                fn [<size_of_ $ty>](_val: $ty) -> usize {
95                    size_of::<$ty>()
96                }
97            )*
98        }
99    };
100}
101
102/// Integer encoding trait.
103///
104/// This trait provides encoding, decoding, and sizing for all integer types.
105///
106/// # Safety
107///
108/// Implementors must adhere to the Safety section of the associated constants
109/// `STATIC` and `ZERO_COPY`.
110///
111/// `size_of_*` implementations must always correspond to the number of bytes read by the
112/// corresponding `decode_*` and bytes written by the corresponding `encode_*`.
113pub unsafe trait IntEncoding<B: ByteOrder>: 'static {
114    /// Whether the encoded length for all integer types `T` is constant and equal
115    /// to `size_of::<T>()`.
116    ///
117    /// # SAFETY
118    ///
119    /// If `STATIC` is `true`, for all integer types `T`, the encoded size must be
120    /// constant for all values of `T` and equal to `size_of::<T>()`.
121    const STATIC: bool;
122
123    /// Whether the encoding format for integer types `T` matches their in-memory representation.
124    ///
125    /// # SAFETY
126    ///
127    /// If `ZERO_COPY` is `true`, for all integer types `T`, the in-memory representation
128    /// must correspond exactly to the serialized form, and all byte sequences must
129    /// be valid in-memory representations of `T`. This must respect both the
130    /// configured byte order and the platform endianness, and any direct reads must
131    /// uphold `T`'s alignment requirements.
132    const ZERO_COPY: bool;
133
134    /// Encode the given `u16` value and write it to the writer.
135    fn encode_u16(val: u16, writer: impl Writer) -> WriteResult<()>;
136
137    /// Get the encoded size of the given `u16` value.
138    ///
139    /// # SAFETY
140    ///
141    /// Must return the exact number of bytes written by the [`Self::encode_u16`] function
142    /// and read by the [`Self::decode_u16`] function for this particular u16 instance.
143    fn size_of_u16(val: u16) -> usize;
144
145    /// Decode a `u16` value from the reader.
146    fn decode_u16<'de>(reader: impl Reader<'de>) -> ReadResult<u16>;
147
148    /// Encode a `u32` value and write it to the writer.
149    fn encode_u32(val: u32, writer: impl Writer) -> WriteResult<()>;
150
151    /// Get the encoded size of the given `u32` value.
152    ///
153    /// # SAFETY
154    ///
155    /// Must return the exact number of bytes written by the [`Self::encode_u32`] function
156    /// and read by the [`Self::decode_u32`] function for this particular u32 instance.
157    fn size_of_u32(val: u32) -> usize;
158
159    /// Decode a `u32` value from the reader.
160    fn decode_u32<'de>(reader: impl Reader<'de>) -> ReadResult<u32>;
161
162    /// Encode a `u64` value and write it to the writer.
163    fn encode_u64(val: u64, writer: impl Writer) -> WriteResult<()>;
164
165    /// Get the encoded size of the given `u64` value.
166    ///
167    /// # SAFETY
168    ///
169    /// Must return the exact number of bytes written by the [`Self::encode_u64`] function
170    /// and read by the [`Self::decode_u64`] function for this particular u64 instance.
171    fn size_of_u64(val: u64) -> usize;
172
173    /// Decode a `u64` value from the reader.
174    fn decode_u64<'de>(reader: impl Reader<'de>) -> ReadResult<u64>;
175
176    /// Encode a `u128` value and write it to the writer.
177    fn encode_u128(val: u128, writer: impl Writer) -> WriteResult<()>;
178
179    /// Get the encoded size of the given `u128` value.
180    ///
181    /// # SAFETY
182    ///
183    /// Must return the exact number of bytes written by the [`Self::encode_u128`] function
184    /// and read by the [`Self::decode_u128`] function for this particular u128 instance.
185    fn size_of_u128(val: u128) -> usize;
186
187    /// Decode a `u128` value from the reader.
188    fn decode_u128<'de>(reader: impl Reader<'de>) -> ReadResult<u128>;
189
190    /// Encode a `i16` value and write it to the writer.
191    fn encode_i16(val: i16, writer: impl Writer) -> WriteResult<()>;
192
193    /// Get the encoded size of the given `i16` value.
194    ///
195    /// # SAFETY
196    ///
197    /// Must return the exact number of bytes written by the [`Self::encode_i16`] function
198    /// and read by the [`Self::decode_i16`] function for this particular i16 instance.
199    fn size_of_i16(val: i16) -> usize;
200
201    /// Decode a `i16` value from the reader.
202    fn decode_i16<'de>(reader: impl Reader<'de>) -> ReadResult<i16>;
203
204    /// Encode a `i32` value and write it to the writer.
205    fn encode_i32(val: i32, writer: impl Writer) -> WriteResult<()>;
206
207    /// Get the encoded size of the given `i32` value.
208    ///
209    /// # SAFETY
210    ///
211    /// Must return the exact number of bytes written by the [`Self::encode_i32`] function
212    /// and read by the [`Self::decode_i32`] function for this particular i32 instance.
213    fn size_of_i32(val: i32) -> usize;
214
215    /// Decode a `i32` value from the reader.
216    fn decode_i32<'de>(reader: impl Reader<'de>) -> ReadResult<i32>;
217
218    /// Encode a `i64` value and write it to the writer.
219    fn encode_i64(val: i64, writer: impl Writer) -> WriteResult<()>;
220
221    /// Get the encoded size of the given `i64` value.
222    ///
223    /// # SAFETY
224    ///
225    /// Must return the exact number of bytes written by the [`Self::encode_i64`] function
226    /// and read by the [`Self::decode_i64`] function for this particular i64 instance.
227    fn size_of_i64(val: i64) -> usize;
228
229    /// Decode a `i64` value from the reader.
230    fn decode_i64<'de>(reader: impl Reader<'de>) -> ReadResult<i64>;
231
232    /// Encode a `i128` value and write it to the writer.
233    fn encode_i128(val: i128, writer: impl Writer) -> WriteResult<()>;
234
235    /// Get the encoded size of the given `i128` value.
236    ///
237    /// # SAFETY
238    ///
239    /// Must return the exact number of bytes written by the [`Self::encode_i128`] function
240    /// and read by the [`Self::decode_i128`] function for this particular i128 instance.
241    fn size_of_i128(val: i128) -> usize;
242
243    /// Decode a `i128` value from the reader.
244    fn decode_i128<'de>(reader: impl Reader<'de>) -> ReadResult<i128>;
245}
246
247/// Fixed width integer encoding.
248///
249/// For all integer types, will encode `to_<byte_order>_bytes` and decode
250/// using the corresponding `from_<byte_order>_bytes` method.
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub struct FixInt;
253
254unsafe impl IntEncoding<BigEndian> for FixInt {
255    const STATIC: bool = true;
256    #[cfg(target_endian = "big")]
257    const ZERO_COPY: bool = true;
258    #[cfg(target_endian = "little")]
259    const ZERO_COPY: bool = false;
260
261    impl_fix_int!(BigEndian => u16, u32, u64, u128, i16, i32, i64, i128);
262}
263
264unsafe impl IntEncoding<LittleEndian> for FixInt {
265    const STATIC: bool = true;
266    #[cfg(target_endian = "big")]
267    const ZERO_COPY: bool = false;
268    #[cfg(target_endian = "little")]
269    const ZERO_COPY: bool = true;
270
271    impl_fix_int!(LittleEndian => u16, u32, u64, u128, i16, i32, i64, i128);
272}
273
274/// Convenience to allow trait implementations to hook into the configured
275/// [`IntEncoding`] and conditionally enable [`ZeroCopy`].
276///
277/// For example, if a [`SchemaRead`](crate::SchemaRead) / [`SchemaWrite`](crate::SchemaWrite)
278/// implementation delegates its integer encoding to the configuration's
279/// [`IntEncoding`], it can constrain its [`ZeroCopy`] bound by that encoding.
280unsafe impl<C: ConfigCore> ZeroCopy<C> for FixInt where C::ByteOrder: PlatformEndian {}
281
282/// Variable length integer encoding.
283///
284/// Performance note: variable length integer encoding will hurt serialization and deserialization
285/// performance significantly relative to fixed width integer encoding. Additionally, all zero-copy
286/// capabilities on integers will be lost. Variable length integer encoding may be beneficial if
287/// reducing the resulting size of serialized data is important, but if serialization / deserialization
288/// performance is important, fixed width integer encoding is highly recommended.
289///
290/// Encoding an unsigned integer v (of any type excepting u8) works as follows:
291///
292/// 1. If `u < 251`, encode it as a single byte with that value.
293/// 2. If `251 <= u < 2**16`, encode it as a literal byte 251, followed by a u16 with value `u`.
294/// 3. If `2**16 <= u < 2**32`, encode it as a literal byte 252, followed by a u32 with value `u`.
295/// 4. If `2**32 <= u < 2**64`, encode it as a literal byte 253, followed by a u64 with value `u`.
296/// 5. If `2**64 <= u < 2**128`, encode it as a literal byte 254, followed by a u128 with value `u`.
297///
298/// Then, for signed integers, we first convert to unsigned using the zigzag algorithm,
299/// and then encode them as we do for unsigned integers generally. The reason we use this
300/// algorithm is that it encodes those values which are close to zero in less bytes; the
301/// obvious algorithm, where we encode the cast values, gives a very large encoding for all
302/// negative values.
303///
304/// The zigzag algorithm is defined as follows:
305///
306/// ```
307/// # type Signed = i32;
308/// # type Unsigned = u32;
309/// fn zigzag(v: Signed) -> Unsigned {
310///     match v {
311///         0 => 0,
312///         // To avoid the edge case of Signed::min_value()
313///         // !n is equal to `-n - 1`, so this is:
314///         // !n * 2 + 1 = 2(-n - 1) + 1 = -2n - 2 + 1 = -2n - 1
315///         v if v < 0 => !(v as Unsigned) * 2 + 1,
316///         v if v > 0 => (v as Unsigned) * 2,
317/// #       _ => unreachable!()
318///     }
319/// }
320/// ```
321///
322/// And works such that:
323///
324/// ```
325/// # let zigzag = |n: i64| -> u64 {
326/// #     match n {
327/// #         0 => 0,
328/// #         v if v < 0 => !(v as u64) * 2 + 1,
329/// #         v if v > 0 => (v as u64) * 2,
330/// #         _ => unreachable!(),
331/// #     }
332/// # };
333/// assert_eq!(zigzag(0), 0);
334/// assert_eq!(zigzag(-1), 1);
335/// assert_eq!(zigzag(1), 2);
336/// assert_eq!(zigzag(-2), 3);
337/// assert_eq!(zigzag(2), 4);
338/// // etc
339/// assert_eq!(zigzag(i64::min_value()), u64::max_value());
340/// ```
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub struct VarInt;
343
344/// Attempt to convert the given byte slice into the target type using configured endianess.
345///
346/// Errors if the byte slice does not contain enough bytes.
347macro_rules! try_from_endian_bytes {
348    ($reader:ident => $ty:ty as $target:ty) => {{
349        let ar = $reader.take_array::<{ size_of::<$ty>() }>()?;
350        let val = match B::ENDIAN {
351            Endian::Big => <$ty>::from_be_bytes(ar),
352            Endian::Little => <$ty>::from_le_bytes(ar),
353        };
354        val as $target
355    }};
356    ($reader:ident => $ty:ty) => {{
357        try_from_endian_bytes!($reader => $ty as $ty)
358    }};
359}
360
361/// Decode zigzag-encoded signed integers using the underlying unsigned VarInt decoder.
362macro_rules! varint_decode_signed {
363    ($ty:ty => $target:ty) => {
364        paste! {
365            #[inline]
366            fn [<decode_ $ty>]<'de>(reader: impl Reader<'de>) -> ReadResult<$ty> {
367                let n = <VarInt as IntEncoding<B>>::[<decode_ $target>](reader)?;
368                Ok(if n % 2 == 0 {
369                    // positive number
370                    (n / 2) as _
371                } else {
372                    // negative number
373                    // !m * 2 + 1 = n
374                    // !m * 2 = n - 1
375                    // !m = (n - 1) / 2
376                    // m = !((n - 1) / 2)
377                    // since we have n is odd, we have floor(n / 2) = floor((n - 1) / 2)
378                    !(n / 2) as _
379                })
380            }
381        }
382    };
383}
384
385/// Return the encoded length of zigzag-encoded signed integers.
386macro_rules! varint_size_of_signed {
387    ($ty:ty => $target:ty) => {
388        paste! {
389            #[inline]
390            #[expect(clippy::arithmetic_side_effects)]
391            fn [<size_of_ $ty>](val: $ty) -> usize {
392                let n: $target = if val < 0 {
393                    (!(val as $target)) * 2 + 1
394                } else {
395                    (val as $target) * 2
396                };
397                <VarInt as IntEncoding<B>>::[<size_of_ $target>](n)
398            }
399        }
400    };
401}
402
403/// Encode signed integers by zigzag-mapping to the corresponding unsigned [`VarInt`] encoder.
404macro_rules! varint_encode_signed {
405    ($ty:ty => $target:ty) => {
406        paste! {
407            #[inline]
408            #[expect(clippy::arithmetic_side_effects)]
409            fn [<encode_ $ty>](val: $ty, writer: impl Writer) -> WriteResult<()> {
410                let n: $target = if val < 0 {
411                    (!(val as $target)) * 2 + 1
412                } else {
413                    (val as $target) * 2
414                };
415                <VarInt as IntEncoding<B>>::[<encode_ $target>](n, writer)
416            }
417        }
418    };
419}
420
421/// Emit tag+payload for unsigned values based on the first matching cast width.
422macro_rules! varint_encode_unsigned_impl {
423    ($val:ident, $writer:ident, $ty:ty, $tag:ident => $cast:ty) => {{
424        let needed = size_of::<$cast>() + 1;
425        // SAFETY: tag (1 byte) + payload (`size_of::<$cast>()`) fully initialize the trusted
426        // window, so all writes stay within `needed` bytes.
427        let mut writer = unsafe { $writer.as_trusted_for(needed) }?;
428        writer.write(&[$tag])?;
429        let encoded = $val as $cast;
430        let bytes = match B::ENDIAN {
431            Endian::Big => encoded.to_be_bytes(),
432            Endian::Little => encoded.to_le_bytes(),
433        };
434        writer.write(&bytes)?;
435        writer.finish()?;
436        Ok(())
437    }};
438    ($val:ident, $writer:ident, $ty:ty, $tag:ident => $cast:ty, $($rest_tag:ident => $rest_cast:ty),+ $(,)?) => {{
439        if $val <= <$cast>::MAX as $ty {
440            let needed = size_of::<$cast>() + 1;
441            // SAFETY: tag (1 byte) + payload (`size_of::<$cast>()`) fully initialize the trusted
442            // window, so all writes stay within `needed` bytes.
443            let mut writer = unsafe { $writer.as_trusted_for(needed) }?;
444            writer.write(&[$tag])?;
445            let encoded = $val as $cast;
446            let bytes = match B::ENDIAN {
447                Endian::Big => encoded.to_be_bytes(),
448                Endian::Little => encoded.to_le_bytes(),
449            };
450            writer.write(&bytes)?;
451            writer.finish()?;
452            Ok(())
453        } else {
454            varint_encode_unsigned_impl!($val, $writer, $ty, $($rest_tag => $rest_cast),+)
455        }
456    }};
457}
458
459/// Generate `encode_*` functions for unsigned VarInt encodings using a tag->cast list.
460macro_rules! varint_encode_unsigned {
461    ($ty:ty, $($tag:ident => $cast:ty),+ $(,)?) => {
462        paste! {
463            #[inline]
464            #[expect(clippy::arithmetic_side_effects)]
465            fn [<encode_ $ty>](val: $ty, mut writer: impl Writer) -> WriteResult<()> {
466                if val <= SINGLE_BYTE_MAX as $ty {
467                    writer.write(&[val as u8])?;
468                    return Ok(());
469                }
470                varint_encode_unsigned_impl!(val, writer, $ty, $($tag => $cast),+)
471            }
472        }
473    };
474}
475
476const SINGLE_BYTE_MAX: u8 = 250;
477const U16_BYTE: u8 = 251;
478const U32_BYTE: u8 = 252;
479const U64_BYTE: u8 = 253;
480const U128_BYTE: u8 = 254;
481
482unsafe impl<B: ByteOrder> IntEncoding<B> for VarInt {
483    const STATIC: bool = false;
484
485    const ZERO_COPY: bool = false;
486
487    #[inline]
488    fn size_of_u16(val: u16) -> usize {
489        if val <= SINGLE_BYTE_MAX as u16 { 1 } else { 3 }
490    }
491
492    fn decode_u16<'de>(mut reader: impl Reader<'de>) -> ReadResult<u16> {
493        let byte = reader.take_byte()?;
494        let out = match byte {
495            byte @ 0..=SINGLE_BYTE_MAX => byte as u16,
496            U16_BYTE => try_from_endian_bytes!(reader => u16),
497            byte => return Err(invalid_tag_encoding(byte as usize)),
498        };
499
500        Ok(out)
501    }
502
503    varint_encode_unsigned!(u16, U16_BYTE => u16);
504
505    #[inline]
506    fn size_of_u32(val: u32) -> usize {
507        if val <= SINGLE_BYTE_MAX as u32 {
508            1
509        } else if val <= u16::MAX as u32 {
510            3
511        } else {
512            5
513        }
514    }
515
516    fn decode_u32<'de>(mut reader: impl Reader<'de>) -> ReadResult<u32> {
517        let byte = reader.take_byte()?;
518        let out = match byte {
519            byte @ 0..=SINGLE_BYTE_MAX => byte as u32,
520            U16_BYTE => try_from_endian_bytes!(reader => u16 as u32),
521            U32_BYTE => try_from_endian_bytes!(reader => u32),
522            byte => return Err(invalid_tag_encoding(byte as usize)),
523        };
524        Ok(out)
525    }
526
527    varint_encode_unsigned!(u32, U16_BYTE => u16, U32_BYTE => u32);
528
529    #[inline]
530    fn size_of_u64(val: u64) -> usize {
531        if val <= SINGLE_BYTE_MAX as u64 {
532            1
533        } else if val <= u16::MAX as u64 {
534            3
535        } else if val <= u32::MAX as u64 {
536            5
537        } else {
538            9
539        }
540    }
541
542    fn decode_u64<'de>(mut reader: impl Reader<'de>) -> ReadResult<u64> {
543        let byte = reader.take_byte()?;
544        let out = match byte {
545            byte @ 0..=SINGLE_BYTE_MAX => byte as u64,
546            U16_BYTE => try_from_endian_bytes!(reader => u16 as u64),
547            U32_BYTE => try_from_endian_bytes!(reader => u32 as u64),
548            U64_BYTE => try_from_endian_bytes!(reader => u64),
549            byte => return Err(invalid_tag_encoding(byte as usize)),
550        };
551        Ok(out)
552    }
553
554    varint_encode_unsigned!(u64, U16_BYTE => u16, U32_BYTE => u32, U64_BYTE => u64);
555
556    #[inline]
557    fn size_of_u128(val: u128) -> usize {
558        if val <= SINGLE_BYTE_MAX as u128 {
559            1
560        } else if val <= u16::MAX as u128 {
561            3
562        } else if val <= u32::MAX as u128 {
563            5
564        } else if val <= u64::MAX as u128 {
565            9
566        } else {
567            17
568        }
569    }
570
571    fn decode_u128<'de>(mut reader: impl Reader<'de>) -> ReadResult<u128> {
572        let byte = reader.take_byte()?;
573        let out = match byte {
574            byte @ 0..=SINGLE_BYTE_MAX => byte as u128,
575            U16_BYTE => try_from_endian_bytes!(reader => u16 as u128),
576            U32_BYTE => try_from_endian_bytes!(reader => u32 as u128),
577            U64_BYTE => try_from_endian_bytes!(reader => u64 as u128),
578            U128_BYTE => try_from_endian_bytes!(reader => u128),
579            byte => return Err(invalid_tag_encoding(byte as usize)),
580        };
581
582        Ok(out)
583    }
584
585    varint_encode_unsigned!(
586        u128,
587        U16_BYTE => u16,
588        U32_BYTE => u32,
589        U64_BYTE => u64,
590        U128_BYTE => u128,
591    );
592
593    varint_size_of_signed!(i16 => u16);
594    varint_size_of_signed!(i32 => u32);
595    varint_size_of_signed!(i64 => u64);
596    varint_size_of_signed!(i128 => u128);
597
598    varint_encode_signed!(i16 => u16);
599    varint_encode_signed!(i32 => u32);
600    varint_encode_signed!(i64 => u64);
601    varint_encode_signed!(i128 => u128);
602
603    varint_decode_signed!(i16 => u16);
604    varint_decode_signed!(i32 => u32);
605    varint_decode_signed!(i64 => u64);
606    varint_decode_signed!(i128 => u128);
607}