Skip to main content

base64_ng/engine/
decode.rs

1#[cfg(feature = "alloc")]
2use crate::SecretBuffer;
3#[cfg(feature = "alloc")]
4use crate::validate_decode;
5use crate::{
6    Alphabet, DecodeError, DecodedBuffer, Engine, LineWrap, decode_backend, decode_legacy_to_slice,
7    decode_wrapped_to_slice, validate_legacy_decode, validate_wrapped_decode, wipe_bytes,
8    wipe_tail,
9};
10
11impl<A, const PAD: bool> Engine<A, PAD>
12where
13    A: Alphabet,
14{
15    /// Decodes `input` into `output`, returning the number of bytes written.
16    ///
17    /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
18    /// and trailing non-padding data are rejected.
19    ///
20    /// # Security
21    ///
22    /// This default scalar decoder prioritizes strict validation, exact error
23    /// reporting, and ordinary throughput. It may branch or return early based
24    /// on byte validity, malformed input, padding position, and output
25    /// capacity. It also reports exact failure positions and invalid byte
26    /// values through [`DecodeError`]. Do not use this method for token
27    /// comparison, key-material decoding, or secret-bearing validation where
28    /// malformed-input timing matters. Do not log strict decode errors
29    /// verbatim for secret-bearing input; log [`DecodeError::kind`] instead.
30    /// Use [`crate::ct`],
31    /// [`crate::ct::STANDARD`], [`crate::ct::URL_SAFE_NO_PAD`], or
32    /// [`Self::ct_decoder`] with `decode_slice_clear_tail` for
33    /// constant-time-oriented secret decoding.
34    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
35    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
36        decode_backend::decode_slice::<A, PAD>(input, output)
37    }
38
39    /// Decodes `input` into `output` and clears all bytes after the decoded
40    /// prefix.
41    ///
42    /// If decoding fails, the entire output buffer is cleared before the error
43    /// is returned.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use base64_ng::STANDARD;
49    ///
50    /// let mut output = [0xff; 8];
51    /// let written = STANDARD
52    ///     .decode_slice_clear_tail(b"aGk=", &mut output)
53    ///     .unwrap();
54    ///
55    /// assert_eq!(&output[..written], b"hi");
56    /// assert!(output[written..].iter().all(|byte| *byte == 0));
57    /// ```
58    pub fn decode_slice_clear_tail(
59        &self,
60        input: &[u8],
61        output: &mut [u8],
62    ) -> Result<usize, DecodeError> {
63        let written = match self.decode_slice(input, output) {
64            Ok(written) => written,
65            Err(err) => {
66                wipe_bytes(output);
67                return Err(err);
68            }
69        };
70        wipe_tail(output, written);
71        Ok(written)
72    }
73
74    /// Decodes `input` into a stack-backed buffer.
75    ///
76    /// This helper is useful for short decoded values where callers want the
77    /// convenience of an owned result without enabling `alloc`.
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use base64_ng::STANDARD;
83    ///
84    /// let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
85    ///
86    /// assert_eq!(decoded.as_bytes(), b"hello");
87    /// ```
88    pub fn decode_buffer<const CAP: usize>(
89        &self,
90        input: &[u8],
91    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
92        let mut output = DecodedBuffer::new();
93        let written = match self.decode_slice_clear_tail(input, output.as_mut_capacity()) {
94            Ok(written) => written,
95            Err(err) => {
96                output.clear();
97                return Err(err);
98            }
99        };
100        output.set_filled(written)?;
101        Ok(output)
102    }
103
104    /// Decodes `input` using the explicit legacy whitespace profile.
105    ///
106    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
107    /// Alphabet selection, padding placement, trailing data after padding, and
108    /// non-canonical trailing bits remain strict.
109    ///
110    /// # Security
111    ///
112    /// This method uses the normal strict decode path after legacy whitespace
113    /// handling. It may branch or return early based on malformed input and is
114    /// not a constant-time token validator or key-material decoder. Use
115    /// [`crate::ct`] for secret-bearing payloads.
116    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
117    pub fn decode_slice_legacy(
118        &self,
119        input: &[u8],
120        output: &mut [u8],
121    ) -> Result<usize, DecodeError> {
122        let required = validate_legacy_decode::<A, PAD>(input)?;
123        if output.len() < required {
124            return Err(DecodeError::OutputTooSmall {
125                required,
126                available: output.len(),
127            });
128        }
129        decode_legacy_to_slice::<A, PAD>(input, output)
130    }
131
132    /// Decodes `input` using the explicit legacy whitespace profile and clears
133    /// all bytes after the decoded prefix.
134    ///
135    /// If validation or decoding fails, the entire output buffer is cleared
136    /// before the error is returned.
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use base64_ng::STANDARD;
142    ///
143    /// let mut output = [0xff; 8];
144    /// let written = STANDARD
145    ///     .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
146    ///     .unwrap();
147    ///
148    /// assert_eq!(&output[..written], b"hi");
149    /// assert!(output[written..].iter().all(|byte| *byte == 0));
150    /// ```
151    pub fn decode_slice_legacy_clear_tail(
152        &self,
153        input: &[u8],
154        output: &mut [u8],
155    ) -> Result<usize, DecodeError> {
156        let written = match self.decode_slice_legacy(input, output) {
157            Ok(written) => written,
158            Err(err) => {
159                wipe_bytes(output);
160                return Err(err);
161            }
162        };
163        wipe_tail(output, written);
164        Ok(written)
165    }
166
167    /// Decodes `input` into a stack-backed buffer using the explicit legacy
168    /// whitespace profile.
169    ///
170    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
171    /// Alphabet selection, padding placement, trailing data after padding, and
172    /// non-canonical trailing bits remain strict. If decoding fails, the
173    /// internal backing array is cleared before the error is returned.
174    pub fn decode_buffer_legacy<const CAP: usize>(
175        &self,
176        input: &[u8],
177    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
178        let mut output = DecodedBuffer::new();
179        let written = match self.decode_slice_legacy_clear_tail(input, output.as_mut_capacity()) {
180            Ok(written) => written,
181            Err(err) => {
182                output.clear();
183                return Err(err);
184            }
185        };
186        output.set_filled(written)?;
187        Ok(output)
188    }
189
190    /// Decodes `input` using a strict line-wrapped profile.
191    ///
192    /// The wrapped profile accepts only the configured line ending. Non-final
193    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
194    /// may be shorter. A single trailing line ending after the final line is
195    /// accepted.
196    ///
197    /// # Security
198    ///
199    /// This method uses the normal strict decode path after line-profile
200    /// validation. It may branch or return early based on malformed input and
201    /// is not a constant-time token validator or key-material decoder. Use
202    /// [`crate::ct`] for secret-bearing payloads.
203    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
204    pub fn decode_slice_wrapped(
205        &self,
206        input: &[u8],
207        output: &mut [u8],
208        wrap: LineWrap,
209    ) -> Result<usize, DecodeError> {
210        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
211        if output.len() < required {
212            return Err(DecodeError::OutputTooSmall {
213                required,
214                available: output.len(),
215            });
216        }
217        decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
218    }
219
220    /// Decodes `input` using a strict line-wrapped profile and clears all bytes
221    /// after the decoded prefix.
222    ///
223    /// If validation or decoding fails, the entire output buffer is cleared
224    /// before the error is returned.
225    pub fn decode_slice_wrapped_clear_tail(
226        &self,
227        input: &[u8],
228        output: &mut [u8],
229        wrap: LineWrap,
230    ) -> Result<usize, DecodeError> {
231        let written = match self.decode_slice_wrapped(input, output, wrap) {
232            Ok(written) => written,
233            Err(err) => {
234                wipe_bytes(output);
235                return Err(err);
236            }
237        };
238        wipe_tail(output, written);
239        Ok(written)
240    }
241
242    /// Decodes `input` using a strict line-wrapped profile into a stack-backed
243    /// buffer.
244    ///
245    /// The wrapped profile accepts only the configured line ending. Non-final
246    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
247    /// may be shorter. A single trailing line ending after the final line is
248    /// accepted. If decoding fails, the internal backing array is cleared
249    /// before the error is returned.
250    pub fn decode_wrapped_buffer<const CAP: usize>(
251        &self,
252        input: &[u8],
253        wrap: LineWrap,
254    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
255        let mut output = DecodedBuffer::new();
256        let written =
257            match self.decode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
258                Ok(written) => written,
259                Err(err) => {
260                    output.clear();
261                    return Err(err);
262                }
263            };
264        output.set_filled(written)?;
265        Ok(output)
266    }
267
268    /// Decodes `input` into a newly allocated byte vector.
269    ///
270    /// This is strict decoding with the same semantics as [`Self::decode_slice`].
271    #[cfg(feature = "alloc")]
272    #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
273    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
274        let required = validate_decode::<A, PAD>(input)?;
275        let mut output = alloc::vec![0; required];
276        let written = match self.decode_slice(input, &mut output) {
277            Ok(written) => written,
278            Err(err) => {
279                wipe_bytes(&mut output);
280                return Err(err);
281            }
282        };
283        output.truncate(written);
284        Ok(output)
285    }
286
287    /// Decodes `input` into a redacted owned secret buffer.
288    ///
289    /// On malformed input, the intermediate output buffer is cleared before the
290    /// error is returned by [`Self::decode_vec`].
291    #[cfg(feature = "alloc")]
292    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
293        self.decode_vec(input).map(SecretBuffer::from_vec)
294    }
295
296    /// Decodes `input` into a newly allocated byte vector using the explicit
297    /// legacy whitespace profile.
298    #[cfg(feature = "alloc")]
299    #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
300    pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
301        let required = validate_legacy_decode::<A, PAD>(input)?;
302        let mut output = alloc::vec![0; required];
303        let written = match self.decode_slice_legacy(input, &mut output) {
304            Ok(written) => written,
305            Err(err) => {
306                wipe_bytes(&mut output);
307                return Err(err);
308            }
309        };
310        output.truncate(written);
311        Ok(output)
312    }
313
314    /// Decodes `input` into a redacted owned secret buffer using the explicit
315    /// legacy whitespace profile.
316    ///
317    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
318    /// Alphabet selection, padding placement, trailing data after padding, and
319    /// non-canonical trailing bits remain strict.
320    #[cfg(feature = "alloc")]
321    pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
322        self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
323    }
324
325    /// Decodes line-wrapped input into a newly allocated byte vector.
326    #[cfg(feature = "alloc")]
327    #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
328    pub fn decode_wrapped_vec(
329        &self,
330        input: &[u8],
331        wrap: LineWrap,
332    ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
333        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
334        let mut output = alloc::vec![0; required];
335        let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
336            Ok(written) => written,
337            Err(err) => {
338                wipe_bytes(&mut output);
339                return Err(err);
340            }
341        };
342        output.truncate(written);
343        Ok(output)
344    }
345
346    /// Decodes line-wrapped input into a redacted owned secret buffer.
347    ///
348    /// The wrapped profile accepts only the configured line ending. Non-final
349    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
350    /// may be shorter. A single trailing line ending after the final line is
351    /// accepted.
352    #[cfg(feature = "alloc")]
353    pub fn decode_wrapped_secret(
354        &self,
355        input: &[u8],
356        wrap: LineWrap,
357    ) -> Result<SecretBuffer, DecodeError> {
358        self.decode_wrapped_vec(input, wrap)
359            .map(SecretBuffer::from_vec)
360    }
361}