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}