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}