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