Skip to main content

qubit_codec_binary/codec/
zig_zag_codec.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9use core::{
10    convert::Infallible,
11    marker::PhantomData,
12};
13
14use qubit_codec::Codec;
15
16use crate::{
17    Leb128Codec,
18    Leb128DecodeError,
19    Leb128DecodePolicy,
20    NonStrict,
21};
22
23/// Type-level unchecked ZigZag + unsigned LEB128 codec.
24///
25/// # Type Parameters
26///
27/// - `T`: Signed integer value type to decode from ZigZag-encoded LEB128 bytes
28///   and encode into ZigZag-encoded LEB128 bytes.
29/// - `P`: Type-level decoding policy implementing [`Leb128DecodePolicy`] for
30///   the underlying unsigned LEB128 payload. Use [`crate::Strict`] to reject
31///   non-canonical inputs, or [`NonStrict`] to accept non-canonical inputs.
32///
33/// # Examples
34///
35/// ```
36/// use qubit_codec_binary::{
37///     NonStrict,
38///     ZigZagCodec,
39/// };
40///
41/// let mut output = [0_u8; ZigZagCodec::<i64, NonStrict>::MAX_UNITS_PER_VALUE];
42/// let written = unsafe {
43///     ZigZagCodec::<i64, NonStrict>::encode_unchecked(-42, &mut output, 0)
44/// };
45/// assert_eq!(1, written);
46///
47/// let (decoded, consumed) = unsafe {
48///     ZigZagCodec::<i64, NonStrict>::decode_unchecked(&output[..written], 0)
49/// }.expect("canonical ZigZag LEB128 value should decode");
50/// assert_eq!(-42, decoded);
51/// assert_eq!(1, consumed.get());
52/// ```
53#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
54pub struct ZigZagCodec<T, P = NonStrict> {
55    marker: PhantomData<fn() -> (T, P)>,
56}
57
58macro_rules! impl_zig_zag_codec {
59    ($signed:ty, $unsigned:ty, $shift:expr) => {
60        impl<P> ZigZagCodec<$signed, P>
61        where
62            P: Leb128DecodePolicy,
63        {
64            /// Minimum number of bytes that can represent a complete value.
65            pub const MIN_UNITS_PER_VALUE: usize = 1;
66
67            /// Maximum number of bytes required to encode or decode this type.
68            pub const MAX_UNITS_PER_VALUE: usize =
69                Leb128Codec::<$unsigned, NonStrict>::MAX_UNITS_PER_VALUE;
70
71            /// Decodes a value from `input` starting at `index` without bounds
72            /// checks.
73            ///
74            /// # Parameters
75            ///
76            /// - `input`: Source byte buffer.
77            /// - `index`: Start index in `input`.
78            ///
79            /// # Returns
80            ///
81            /// Returns the decoded value and the non-zero number of consumed
82            /// bytes.
83            ///
84            /// # Errors
85            ///
86            /// Returns [`Leb128DecodeError`] if the underlying LEB128 bytes are
87            /// incomplete, malformed, or non-canonical under strict policy.
88            ///
89            /// # Safety
90            ///
91            /// The caller must guarantee that `index` is a valid boundary and
92            /// at least [`Self::MIN_UNITS_PER_VALUE`] byte is readable from
93            /// `index`.
94            #[inline(always)]
95            pub unsafe fn decode_unchecked(
96                input: &[u8],
97                index: usize,
98            ) -> Result<($signed, core::num::NonZeroUsize), Leb128DecodeError>
99            {
100                debug_assert!(
101                    input.len().saturating_sub(index)
102                        >= Self::MIN_UNITS_PER_VALUE
103                );
104
105                // SAFETY: The caller guarantees enough readable bytes for this
106                // type.
107                let (encoded, consumed) = unsafe {
108                    Leb128Codec::<$unsigned, P>::decode_unchecked(input, index)?
109                };
110                let value =
111                    ((encoded >> 1) as $signed) ^ (-((encoded & 1) as $signed));
112                Ok((value, consumed))
113            }
114
115            /// Encodes `value` into `output` starting at `index` without bounds
116            /// checks.
117            ///
118            /// # Parameters
119            ///
120            /// - `value`: Value to encode.
121            /// - `output`: Destination byte buffer.
122            /// - `index`: Start index in `output`.
123            ///
124            /// # Returns
125            ///
126            /// Returns the number of written bytes.
127            ///
128            /// # Safety
129            ///
130            /// The caller must guarantee that `output.as_mut_ptr().add(index)`
131            /// is valid to write [`Self::MAX_UNITS_PER_VALUE`] bytes.
132            #[inline(always)]
133            pub unsafe fn encode_unchecked(
134                value: $signed,
135                output: &mut [u8],
136                index: usize,
137            ) -> usize {
138                let encoded = ((value as $unsigned) << 1)
139                    ^ ((value >> $shift) as $unsigned);
140                // SAFETY: The caller guarantees enough writable bytes for this
141                // type.
142                unsafe {
143                    Leb128Codec::<$unsigned, NonStrict>::encode_unchecked(
144                        encoded, output, index,
145                    )
146                }
147            }
148        }
149
150        unsafe impl<P> Codec for ZigZagCodec<$signed, P>
151        where
152            P: Leb128DecodePolicy,
153        {
154            type Value = $signed;
155            type Unit = u8;
156            type DecodeError = Leb128DecodeError;
157            type EncodeError = Infallible;
158
159            #[inline(always)]
160            fn min_units_per_value(&self) -> core::num::NonZeroUsize {
161                core::num::NonZeroUsize::MIN
162            }
163
164            #[inline(always)]
165            fn max_units_per_value(&self) -> core::num::NonZeroUsize {
166                // SAFETY: ZigZag LEB128 has a non-zero maximum encoded width.
167                unsafe {
168                    core::num::NonZeroUsize::new_unchecked(
169                        Self::MAX_UNITS_PER_VALUE,
170                    )
171                }
172            }
173
174            #[inline(always)]
175            unsafe fn decode_unchecked(
176                &self,
177                input: &[u8],
178                index: usize,
179            ) -> Result<($signed, core::num::NonZeroUsize), Self::DecodeError>
180            {
181                debug_assert!(
182                    input.len().saturating_sub(index)
183                        >= Self::MIN_UNITS_PER_VALUE
184                );
185
186                // SAFETY: The caller upholds the `Codec::decode_unchecked`
187                // contract.
188                unsafe { Self::decode_unchecked(input, index) }
189            }
190
191            #[inline(always)]
192            unsafe fn encode_unchecked(
193                &self,
194                value: &$signed,
195                output: &mut [u8],
196                index: usize,
197            ) -> Result<usize, Self::EncodeError> {
198                debug_assert!(
199                    output.len().saturating_sub(index)
200                        >= Self::MAX_UNITS_PER_VALUE
201                );
202
203                // SAFETY: The caller upholds the `Codec::encode_unchecked`
204                // contract.
205                Ok(unsafe { Self::encode_unchecked(*value, output, index) })
206            }
207        }
208    };
209}
210
211impl_zig_zag_codec!(i8, u8, 7);
212impl_zig_zag_codec!(i16, u16, 15);
213impl_zig_zag_codec!(i32, u32, 31);
214impl_zig_zag_codec!(i64, u64, 63);
215impl_zig_zag_codec!(i128, u128, 127);
216impl_zig_zag_codec!(isize, usize, isize::BITS - 1);