Skip to main content

qubit_io/codec/
zig_zag_codec.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10
11use core::marker::PhantomData;
12
13use crate::{
14    DecodePolicy,
15    Leb128Codec,
16    Leb128DecodeError,
17    NonStrict,
18};
19
20/// Type-level unchecked ZigZag + unsigned LEB128 codec.
21///
22/// `T` selects the signed integer type and `P` selects the LEB128 decoding
23/// policy used after ZigZag conversion.
24#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
25pub struct ZigZagCodec<T, P = NonStrict> {
26    marker: PhantomData<fn() -> (T, P)>,
27}
28
29macro_rules! impl_zig_zag_codec {
30    ($signed:ty, $unsigned:ty, $shift:expr) => {
31        impl<P> ZigZagCodec<$signed, P>
32        where
33            P: DecodePolicy,
34        {
35            /// Minimum number of bytes required to encode or decode this type.
36            pub const REQUIRED_MIN_BUFFER_LEN: usize = Leb128Codec::<$unsigned, NonStrict>::REQUIRED_MIN_BUFFER_LEN;
37
38            /// Decodes a value from `input` starting at `index` without bounds checks.
39            ///
40            /// # Parameters
41            ///
42            /// - `input`: Source byte buffer.
43            /// - `index`: Start index in `input`.
44            ///
45            /// # Returns
46            ///
47            /// Returns the decoded value and the number of consumed bytes.
48            ///
49            /// # Errors
50            ///
51            /// Returns [`Leb128DecodeError`] if the underlying LEB128 bytes are invalid.
52            ///
53            /// # Safety
54            ///
55            /// The caller must guarantee that `input.as_ptr().add(index)` is valid to
56            /// read [`Self::REQUIRED_MIN_BUFFER_LEN`] bytes, or that a valid terminating byte
57            /// appears before that limit.
58            #[inline(always)]
59            pub unsafe fn read_unchecked(input: &[u8], index: usize) -> Result<($signed, usize), Leb128DecodeError> {
60                // SAFETY: The caller guarantees enough readable bytes for this type.
61                let (encoded, consumed) = unsafe { Leb128Codec::<$unsigned, P>::read_unchecked(input, index)? };
62                let value = ((encoded >> 1) as $signed) ^ (-((encoded & 1) as $signed));
63                Ok((value, consumed))
64            }
65
66            /// Tries to decode from the currently available bytes without bounds checks.
67            ///
68            /// This internal entry point lets buffered readers decode the underlying
69            /// unsigned LEB128 payload while scanning for its terminating byte.
70            ///
71            /// # Parameters
72            ///
73            /// - `input`: Source byte buffer.
74            /// - `index`: Start index in `input`.
75            /// - `available`: Number of readable bytes currently available from
76            ///   `index`.
77            ///
78            /// # Returns
79            ///
80            /// Returns `Ok(Some((value, consumed)))` when a complete value is
81            /// decoded. Returns `Ok(None)` when more bytes are needed. Returns
82            /// `Err((error, consumed))` when the underlying LEB128 payload is
83            /// invalid and should be consumed before the error is reported.
84            ///
85            /// # Safety
86            ///
87            /// The caller must guarantee that `input.as_ptr().add(index)` is valid
88            /// to read `available` bytes and that `available` is no greater than
89            /// [`Self::REQUIRED_MIN_BUFFER_LEN`].
90            #[inline(always)]
91            pub(crate) unsafe fn read_available_unchecked(
92                input: &[u8],
93                index: usize,
94                available: usize,
95            ) -> Result<Option<($signed, usize)>, (Leb128DecodeError, usize)> {
96                // SAFETY: The caller guarantees that exactly `available` bytes
97                // are readable from `index`.
98                let result = unsafe { Leb128Codec::<$unsigned, P>::read_available_unchecked(input, index, available)? };
99                Ok(result.map(|(encoded, consumed)| {
100                    let value = ((encoded >> 1) as $signed) ^ (-((encoded & 1) as $signed));
101                    (value, consumed)
102                }))
103            }
104
105            /// Encodes `value` into `output` starting at `index` without bounds checks.
106            ///
107            /// # Parameters
108            ///
109            /// - `output`: Destination byte buffer.
110            /// - `index`: Start index in `output`.
111            /// - `value`: Value to encode.
112            ///
113            /// # Returns
114            ///
115            /// Returns the number of written bytes.
116            ///
117            /// # Safety
118            ///
119            /// The caller must guarantee that `output.as_mut_ptr().add(index)` is valid
120            /// to write [`Self::REQUIRED_MIN_BUFFER_LEN`] bytes.
121            #[inline(always)]
122            pub unsafe fn write_unchecked(output: &mut [u8], index: usize, value: $signed) -> usize {
123                let encoded = ((value as $unsigned) << 1) ^ ((value >> $shift) as $unsigned);
124                // SAFETY: The caller guarantees enough writable bytes for this type.
125                unsafe { Leb128Codec::<$unsigned, NonStrict>::write_unchecked(output, index, encoded) }
126            }
127        }
128    };
129}
130
131impl_zig_zag_codec!(i8, u8, 7);
132impl_zig_zag_codec!(i16, u16, 15);
133impl_zig_zag_codec!(i32, u32, 31);
134impl_zig_zag_codec!(i64, u64, 63);
135impl_zig_zag_codec!(i128, u128, 127);
136impl_zig_zag_codec!(isize, usize, isize::BITS - 1);