Skip to main content

qubit_io/stream/
buffered_zig_zag_reader.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;
12use std::io::{
13    Error,
14    ErrorKind,
15    Read,
16    Result,
17    Seek,
18    SeekFrom,
19};
20
21use crate::codec::{
22    DecodePolicy,
23    Leb128DecodeError,
24    NonStrict,
25    Strict,
26    ZigZagCodec,
27};
28use crate::stream::BufferedInput;
29
30/// Buffered reader for ZigZag + unsigned LEB128 integers.
31///
32/// Values are decoded directly from the internal input buffer while the codec
33/// scans for the underlying LEB128 terminating byte.
34///
35/// # Buffered state
36///
37/// This reader may prefetch bytes from the wrapped reader. As a result,
38/// [`Self::get_ref`] can observe an underlying stream position ahead of the
39/// logical position exposed by this wrapper, and [`Self::into_inner`] discards
40/// any prefetched bytes that have not been consumed.
41///
42/// # Target-width integers
43///
44/// `isize` methods use the current Rust target's pointer width. Prefer
45/// fixed-width integer methods such as `read_i64` for persistent files and
46/// cross-platform protocols.
47pub struct BufferedZigZagReader<R, P = NonStrict> {
48    input: BufferedInput<R>,
49    marker: PhantomData<fn() -> P>,
50}
51
52impl<R, P> BufferedZigZagReader<R, P>
53where
54    P: DecodePolicy,
55{
56    /// Creates a buffered ZigZag reader with the default buffer capacity.
57    #[must_use]
58    #[inline]
59    pub fn new(inner: R) -> Self {
60        Self {
61            input: BufferedInput::new(inner),
62            marker: PhantomData,
63        }
64    }
65
66    /// Creates a buffered ZigZag reader with at least `capacity` bytes.
67    #[must_use]
68    #[inline]
69    pub fn with_capacity(inner: R, capacity: usize) -> Self {
70        Self {
71            input: BufferedInput::with_capacity(inner, capacity),
72            marker: PhantomData,
73        }
74    }
75
76    /// Returns whether this reader rejects non-canonical LEB128 encodings.
77    #[must_use]
78    #[inline]
79    pub const fn is_strict(&self) -> bool {
80        P::STRICT
81    }
82
83    /// Returns a shared reference to the underlying reader.
84    ///
85    /// The underlying reader may already be positioned past unread bytes held
86    /// in this wrapper's internal buffer.
87    #[must_use]
88    #[inline]
89    pub const fn get_ref(&self) -> &R {
90        self.input.get_ref()
91    }
92
93    /// Returns an exclusive reference to the underlying reader.
94    ///
95    /// Mutating the underlying reader directly can invalidate prefetched bytes
96    /// already held in this wrapper's internal buffer.
97    #[must_use]
98    #[inline]
99    pub fn get_mut(&mut self) -> &mut R {
100        self.input.get_mut()
101    }
102
103    /// Consumes this wrapper and returns the underlying reader.
104    ///
105    /// Any bytes already prefetched into the internal buffer but not consumed
106    /// by codec methods are discarded.
107    #[must_use]
108    #[inline]
109    pub fn into_inner(self) -> R {
110        self.input.into_inner()
111    }
112}
113
114macro_rules! impl_read_value {
115    ($policy:ty, $method:ident, $ty:ty, $doc:literal) => {
116        #[doc = $doc]
117        #[inline]
118        pub fn $method(&mut self) -> Result<$ty> {
119            type Codec = ZigZagCodec<$ty, $policy>;
120
121            self.input
122                .read_variable_decoded::<{ Codec::REQUIRED_MIN_BUFFER_LEN }, _, _, _, _>(
123                    |bytes, index, available| {
124                        // SAFETY: `read_variable_decoded` only passes bytes already
125                        // present in the internal buffer and caps `available` at
126                        // the codec maximum width.
127                        unsafe { Codec::read_available_unchecked(bytes, index, available) }
128                    },
129                    map_leb128_decode_error,
130                )
131        }
132    };
133}
134
135macro_rules! impl_for_policy {
136    ($policy:ty) => {
137        impl<R> BufferedZigZagReader<R, $policy>
138        where
139            R: Read,
140        {
141            impl_read_value!($policy, read_i8, i8, "Reads a ZigZag `i8`.");
142            impl_read_value!($policy, read_i16, i16, "Reads a ZigZag `i16`.");
143            impl_read_value!($policy, read_i32, i32, "Reads a ZigZag `i32`.");
144            impl_read_value!($policy, read_i64, i64, "Reads a ZigZag `i64`.");
145            impl_read_value!($policy, read_i128, i128, "Reads a ZigZag `i128`.");
146            impl_read_value!($policy, read_isize, isize, "Reads a ZigZag `isize`.");
147        }
148    };
149}
150
151impl_for_policy!(NonStrict);
152impl_for_policy!(Strict);
153
154impl<R, P> Read for BufferedZigZagReader<R, P>
155where
156    R: Read,
157{
158    /// Reads bytes from the buffered reader.
159    #[inline]
160    fn read(&mut self, buffer: &mut [u8]) -> Result<usize> {
161        self.input.read_raw(buffer)
162    }
163}
164
165impl<R, P> Seek for BufferedZigZagReader<R, P>
166where
167    R: Read + Seek,
168{
169    /// Seeks the wrapped reader and discards buffered bytes after success.
170    #[inline]
171    fn seek(&mut self, position: SeekFrom) -> Result<u64> {
172        self.input.seek_raw(position)
173    }
174}
175
176/// Converts a LEB128 decode error into an I/O error.
177#[inline]
178fn map_leb128_decode_error(error: Leb128DecodeError) -> Error {
179    Error::new(ErrorKind::InvalidData, error)
180}