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}