Skip to main content

base64_ng/stream/
decoder_reader.rs

1use super::{OutputQueue, decode_error_to_io, redacted_inner_state, stream_decoder_failed_error};
2use crate::{Alphabet, Engine};
3use std::io::{self, Read};
4
5/// A streaming Base64 decoder for `std::io::Read`.
6///
7/// For padded engines, this reader stops at the terminal padded Base64
8/// block and leaves later bytes unread in the wrapped reader. This preserves
9/// boundaries for callers that decode one Base64 payload from a larger
10/// stream.
11///
12/// # Security
13///
14/// This adapter uses the normal strict decoder, not the [`crate::ct`]
15/// module. It may branch or return early based on malformed input and it
16/// preserves strict error diagnostics. Do not use it for secret-bearing
17/// payloads when malformed-input timing matters; decode a complete frame
18/// with the matching `ct` engine instead.
19pub struct DecoderReader<R, A, const PAD: bool>
20where
21    A: Alphabet,
22{
23    inner: Option<R>,
24    engine: Engine<A, PAD>,
25    pending: [u8; 4],
26    pending_len: usize,
27    output: OutputQueue<3>,
28    finished: bool,
29    terminal_seen: bool,
30    failed: bool,
31}
32
33impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
34where
35    A: Alphabet,
36{
37    /// Creates a new streaming decoder reader.
38    ///
39    /// # Security
40    ///
41    /// Streaming decoder readers use the normal strict decode path. They
42    /// are not constant-time-oriented secret decoders.
43    #[must_use]
44    pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
45        Self {
46            inner: Some(inner),
47            engine,
48            pending: [0; 4],
49            pending_len: 0,
50            output: OutputQueue::new(),
51            finished: false,
52            terminal_seen: false,
53            failed: false,
54        }
55    }
56
57    /// Returns a shared reference to the wrapped reader.
58    #[must_use]
59    pub fn get_ref(&self) -> &R {
60        self.inner_ref()
61    }
62
63    /// Returns a mutable reference to the wrapped reader.
64    pub fn get_mut(&mut self) -> &mut R {
65        self.inner_mut()
66    }
67
68    /// Returns the Base64 engine used by this adapter.
69    #[must_use]
70    pub const fn engine(&self) -> Engine<A, PAD> {
71        self.engine
72    }
73
74    /// Returns whether this adapter uses padded Base64.
75    #[must_use]
76    pub const fn is_padded(&self) -> bool {
77        PAD
78    }
79
80    /// Returns the number of encoded input bytes currently buffered until
81    /// a complete 4-byte Base64 decode quantum is available.
82    #[must_use]
83    pub const fn pending_len(&self) -> usize {
84        self.pending_len
85    }
86
87    /// Returns whether this decoder reader currently holds a partial input
88    /// quantum.
89    #[must_use]
90    pub const fn has_pending_input(&self) -> bool {
91        self.pending_len != 0
92    }
93
94    /// Returns how many additional encoded input bytes are needed to
95    /// complete the currently buffered decode quantum.
96    ///
97    /// Returns `0` when no partial input quantum is buffered.
98    #[must_use]
99    pub const fn pending_input_needed_len(&self) -> usize {
100        if self.has_pending_input() {
101            4 - self.pending_len
102        } else {
103            0
104        }
105    }
106
107    /// Returns the number of decoded bytes currently buffered and ready to
108    /// be read before this adapter polls the wrapped reader again.
109    #[must_use]
110    pub const fn buffered_output_len(&self) -> usize {
111        self.output.len()
112    }
113
114    /// Returns the maximum number of decoded bytes this adapter can buffer
115    /// before returning bytes to the caller.
116    #[must_use]
117    pub const fn buffered_output_capacity(&self) -> usize {
118        self.output.capacity()
119    }
120
121    /// Returns how many more decoded bytes can be buffered before this
122    /// adapter must return bytes to the caller.
123    #[must_use]
124    pub const fn buffered_output_remaining_capacity(&self) -> usize {
125        self.output.available_capacity()
126    }
127
128    /// Returns whether this decoder reader currently has decoded output
129    /// waiting in its internal queue.
130    #[must_use]
131    pub const fn has_buffered_output(&self) -> bool {
132        !self.output.is_empty()
133    }
134
135    /// Returns whether this decoder reader has seen terminal padding.
136    ///
137    /// For padded engines, this becomes `true` after the terminal padded
138    /// block is decoded. The wrapped reader is then left positioned after
139    /// that Base64 block so adjacent framed bytes can be read by the
140    /// caller.
141    #[must_use]
142    pub const fn has_terminal_padding(&self) -> bool {
143        self.terminal_seen
144    }
145
146    /// Returns whether this decoder reader has reached EOF or terminal
147    /// padding in the wrapped reader.
148    ///
149    /// This may become `true` before [`Self::is_finished`] when decoded
150    /// output is still buffered for the caller.
151    #[must_use]
152    pub const fn has_finished_input(&self) -> bool {
153        self.finished
154    }
155
156    /// Returns whether this reader has reached EOF or terminal padding
157    /// and has no decoded output buffered for the caller.
158    #[must_use]
159    pub const fn is_finished(&self) -> bool {
160        self.finished && self.output.is_empty()
161    }
162
163    /// Returns whether this decoder reader has rejected malformed Base64
164    /// input.
165    ///
166    /// Once this returns `true`, later reads return an error. The unchecked
167    /// [`Self::into_inner`] method can still be used for explicit recovery
168    /// of the wrapped reader.
169    #[must_use]
170    pub const fn is_failed(&self) -> bool {
171        self.failed
172    }
173
174    /// Returns whether [`Self::try_into_inner`] can recover the wrapped
175    /// reader without discarding buffered decoded output.
176    #[must_use]
177    pub const fn can_into_inner(&self) -> bool {
178        !self.is_failed() && self.is_finished()
179    }
180
181    /// Consumes the decoder reader and returns the wrapped reader.
182    #[must_use]
183    pub fn into_inner(mut self) -> R {
184        self.take_inner()
185    }
186
187    /// Consumes the decoder reader only after the Base64 payload is fully
188    /// drained.
189    ///
190    /// For padded streams, terminal padding may leave adjacent framed bytes
191    /// unread in the wrapped reader. This method succeeds only after all
192    /// decoded output buffered by this adapter has been read, so recovering
193    /// the wrapped reader does not silently discard decoded bytes.
194    #[allow(clippy::result_large_err)]
195    pub fn try_into_inner(mut self) -> Result<R, Self> {
196        if !self.can_into_inner() {
197            return Err(self);
198        }
199        Ok(self.take_inner())
200    }
201
202    fn inner_ref(&self) -> &R {
203        match &self.inner {
204            Some(inner) => inner,
205            None => unreachable!("stream decoder reader inner reader was already taken"),
206        }
207    }
208
209    fn inner_mut(&mut self) -> &mut R {
210        match &mut self.inner {
211            Some(inner) => inner,
212            None => unreachable!("stream decoder reader inner reader was already taken"),
213        }
214    }
215
216    fn take_inner(&mut self) -> R {
217        match self.inner.take() {
218            Some(inner) => inner,
219            None => unreachable!("stream decoder reader inner reader was already taken"),
220        }
221    }
222
223    fn clear_pending(&mut self) {
224        crate::wipe_bytes(&mut self.pending);
225        self.pending_len = 0;
226    }
227}
228
229impl<R, A, const PAD: bool> Drop for DecoderReader<R, A, PAD>
230where
231    A: Alphabet,
232{
233    fn drop(&mut self) {
234        self.clear_pending();
235        self.output.clear_all();
236    }
237}
238
239impl<R, A, const PAD: bool> core::fmt::Debug for DecoderReader<R, A, PAD>
240where
241    A: Alphabet,
242{
243    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
244        formatter
245            .debug_struct("DecoderReader")
246            .field("inner", &redacted_inner_state(self.inner.is_some()))
247            .field("engine", &self.engine)
248            .field("pending", &"<redacted>")
249            .field("pending_len", &self.pending_len)
250            .field("pending_input_needed_len", &self.pending_input_needed_len())
251            .field("buffered_output_len", &self.output.len())
252            .field("buffered_output_capacity", &self.output.capacity())
253            .field(
254                "buffered_output_remaining_capacity",
255                &self.output.available_capacity(),
256            )
257            .field("can_into_inner", &self.can_into_inner())
258            .field("finished", &self.finished)
259            .field("terminal_padding", &self.terminal_seen)
260            .field("failed", &self.failed)
261            .finish()
262    }
263}
264
265impl<R, A, const PAD: bool> Read for DecoderReader<R, A, PAD>
266where
267    R: Read,
268    A: Alphabet,
269{
270    fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
271        if output.is_empty() {
272            return Ok(0);
273        }
274        if self.failed {
275            return Err(stream_decoder_failed_error());
276        }
277
278        while self.output.is_empty() && !self.finished {
279            self.fill_output()?;
280        }
281
282        Ok(self.output.pop_slice(output))
283    }
284}
285
286impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
287where
288    R: Read,
289    A: Alphabet,
290{
291    fn fill_output(&mut self) -> io::Result<()> {
292        if self.failed {
293            return Err(stream_decoder_failed_error());
294        }
295        if self.terminal_seen {
296            self.finished = true;
297            return Ok(());
298        }
299
300        let mut input = [0u8; 4];
301        let available = 4 - self.pending_len;
302        let read = match self.inner_mut().read(&mut input[..available]) {
303            Ok(read) => read,
304            Err(err) => {
305                crate::wipe_bytes(&mut input);
306                return Err(err);
307            }
308        };
309        if read == 0 {
310            crate::wipe_bytes(&mut input);
311            self.finished = true;
312            self.push_final_pending()?;
313            return Ok(());
314        }
315
316        self.pending[self.pending_len..self.pending_len + read].copy_from_slice(&input[..read]);
317        crate::wipe_bytes(&mut input);
318        self.pending_len += read;
319        if self.pending_len < 4 {
320            return Ok(());
321        }
322
323        let mut quad = self.pending;
324        self.clear_pending();
325        let result = self.push_decoded(&quad);
326        crate::wipe_bytes(&mut quad);
327        result?;
328        if self.terminal_seen {
329            self.finished = true;
330        }
331        Ok(())
332    }
333
334    fn push_final_pending(&mut self) -> io::Result<()> {
335        if self.pending_len == 0 {
336            return Ok(());
337        }
338
339        let mut pending = [0u8; 4];
340        pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
341        let pending_len = self.pending_len;
342        self.clear_pending();
343        let result = self.push_decoded(&pending[..pending_len]);
344        crate::wipe_bytes(&mut pending);
345        result
346    }
347
348    fn push_decoded(&mut self, input: &[u8]) -> io::Result<()> {
349        let mut decoded = [0u8; 3];
350        let written = match self.engine.decode_slice(input, &mut decoded) {
351            Ok(written) => written,
352            Err(err) => {
353                crate::wipe_bytes(&mut decoded);
354                self.failed = true;
355                return Err(decode_error_to_io(err));
356            }
357        };
358        let result = self.output.push_slice(&decoded[..written]);
359        crate::wipe_bytes(&mut decoded);
360        if result.is_err() {
361            self.failed = true;
362        }
363        result?;
364        if input.len() == 4 && written < 3 {
365            self.terminal_seen = true;
366        }
367        Ok(())
368    }
369}