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);