Skip to main content

base64_ng/engine/
decode_in_place.rs

1use crate::{
2    Alphabet, DecodeError, Engine, LineWrap, compact_wrapped_input, decode_chunk,
3    decode_tail_unpadded, is_legacy_whitespace, read_quad, validate_decode, validate_legacy_decode,
4    validate_wrapped_decode, wipe_bytes, wipe_tail,
5};
6
7impl<A, const PAD: bool> Engine<A, PAD>
8where
9    A: Alphabet,
10{
11    /// Decodes `buffer` in place using a strict line-wrapped profile.
12    ///
13    /// The wrapped profile accepts only the configured line ending. Non-final
14    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
15    /// may be shorter. A single trailing line ending after the final line is
16    /// accepted.
17    ///
18    /// # Security
19    ///
20    /// This method compacts line endings in place before decoding. If
21    /// validation or decoding fails, the buffer contents are unspecified and
22    /// may contain the whitespace-stripped encoded form of the input. This is
23    /// still encoded material, not decoded plaintext, but it remains a modified
24    /// representation of the original payload. On success, bytes after the
25    /// returned decoded prefix may retain the compacted encoded representation.
26    /// Use
27    /// [`Self::decode_in_place_wrapped_clear_tail`] when the buffer may be
28    /// reused or freed without a caller-managed wipe; treat that clear-tail
29    /// variant as the default for secret-bearing wrapped payloads. If the
30    /// original encoded input must be preserved for audit logging or retry,
31    /// copy it before calling any in-place decode method or use a slice-output
32    /// decode API instead.
33    ///
34    /// # Examples
35    ///
36    /// ```
37    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
38    ///
39    /// let mut buffer = *b"aGVs\nbG8=";
40    /// let decoded = STANDARD
41    ///     .decode_in_place_wrapped(&mut buffer, LineWrap::new(4, LineEnding::Lf))
42    ///     .unwrap();
43    ///
44    /// assert_eq!(decoded, b"hello");
45    /// ```
46    pub fn decode_in_place_wrapped<'a>(
47        &self,
48        buffer: &'a mut [u8],
49        wrap: LineWrap,
50    ) -> Result<&'a mut [u8], DecodeError> {
51        let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
52        let compacted = compact_wrapped_input(buffer, wrap)?;
53        let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
54        Ok(&mut buffer[..len])
55    }
56
57    /// Decodes `buffer` in place using a strict line-wrapped profile and clears
58    /// all bytes after the decoded prefix.
59    ///
60    /// If validation or decoding fails, the entire buffer is cleared before the
61    /// error is returned.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
67    ///
68    /// let mut buffer = *b"aGVs\nbG8=";
69    /// let len = STANDARD
70    ///     .decode_in_place_wrapped_clear_tail(&mut buffer, LineWrap::new(4, LineEnding::Lf))
71    ///     .unwrap()
72    ///     .len();
73    ///
74    /// assert_eq!(&buffer[..len], b"hello");
75    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
76    /// ```
77    pub fn decode_in_place_wrapped_clear_tail<'a>(
78        &self,
79        buffer: &'a mut [u8],
80        wrap: LineWrap,
81    ) -> Result<&'a mut [u8], DecodeError> {
82        if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
83            wipe_bytes(buffer);
84            return Err(err);
85        }
86
87        let compacted = match compact_wrapped_input(buffer, wrap) {
88            Ok(compacted) => compacted,
89            Err(err) => {
90                wipe_bytes(buffer);
91                return Err(err);
92            }
93        };
94
95        let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
96            Ok(len) => len,
97            Err(err) => {
98                wipe_bytes(buffer);
99                return Err(err);
100            }
101        };
102        wipe_tail(buffer, len);
103        Ok(&mut buffer[..len])
104    }
105
106    /// Decodes the buffer in place and returns the decoded prefix.
107    ///
108    /// On success, bytes after the returned decoded prefix may retain encoded
109    /// input bytes. Use [`Self::decode_in_place_clear_tail`] when the buffer
110    /// may be reused or freed without a caller-managed wipe.
111    ///
112    /// # Security
113    ///
114    /// This default scalar decoder prioritizes strict validation, exact error
115    /// reporting, and ordinary throughput. It may branch or return early based
116    /// on malformed input and reports exact failure positions and invalid byte
117    /// values through [`DecodeError`]. Do not use this method for token
118    /// comparison, key-material decoding, or secret-bearing validation where
119    /// malformed-input timing matters. Do not log strict decode errors
120    /// verbatim for secret-bearing input; log [`DecodeError::kind`] instead.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use base64_ng::STANDARD_NO_PAD;
126    ///
127    /// let mut buffer = *b"Zm9vYmFy";
128    /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
129    /// assert_eq!(decoded, b"foobar");
130    /// ```
131    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
132        let len = Self::decode_slice_to_start(buffer)?;
133        Ok(&mut buffer[..len])
134    }
135
136    /// Decodes the buffer in place and clears all bytes after the decoded prefix.
137    ///
138    /// If decoding fails, the entire buffer is cleared before the error is
139    /// returned. Use this variant when the encoded or partially decoded data is
140    /// sensitive and the caller wants best-effort cleanup without adding a
141    /// dependency.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use base64_ng::STANDARD;
147    ///
148    /// let mut buffer = *b"aGk=";
149    /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
150    /// assert_eq!(decoded, b"hi");
151    /// ```
152    pub fn decode_in_place_clear_tail<'a>(
153        &self,
154        buffer: &'a mut [u8],
155    ) -> Result<&'a mut [u8], DecodeError> {
156        let len = match Self::decode_slice_to_start(buffer) {
157            Ok(len) => len,
158            Err(err) => {
159                wipe_bytes(buffer);
160                return Err(err);
161            }
162        };
163        wipe_tail(buffer, len);
164        Ok(&mut buffer[..len])
165    }
166
167    /// Decodes `buffer` in place using the explicit legacy whitespace profile.
168    ///
169    /// Ignored whitespace is compacted out before decoding. If validation
170    /// fails, the buffer contents are unspecified. On success, bytes after the
171    /// returned decoded prefix may retain the compacted encoded
172    /// representation. Use [`Self::decode_in_place_legacy_clear_tail`] when the
173    /// buffer may be reused or freed without a caller-managed wipe.
174    pub fn decode_in_place_legacy<'a>(
175        &self,
176        buffer: &'a mut [u8],
177    ) -> Result<&'a mut [u8], DecodeError> {
178        let _required = validate_legacy_decode::<A, PAD>(buffer)?;
179        let mut write = 0;
180        let mut read = 0;
181        while read < buffer.len() {
182            let byte = buffer[read];
183            if !is_legacy_whitespace(byte) {
184                buffer[write] = byte;
185                write += 1;
186            }
187            read += 1;
188        }
189        let len = Self::decode_slice_to_start(&mut buffer[..write])?;
190        Ok(&mut buffer[..len])
191    }
192
193    /// Decodes `buffer` in place using the explicit legacy whitespace profile
194    /// and clears all bytes after the decoded prefix.
195    ///
196    /// If validation or decoding fails, the entire buffer is cleared before the
197    /// error is returned.
198    pub fn decode_in_place_legacy_clear_tail<'a>(
199        &self,
200        buffer: &'a mut [u8],
201    ) -> Result<&'a mut [u8], DecodeError> {
202        if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
203            wipe_bytes(buffer);
204            return Err(err);
205        }
206
207        let mut write = 0;
208        let mut read = 0;
209        while read < buffer.len() {
210            let byte = buffer[read];
211            if !is_legacy_whitespace(byte) {
212                buffer[write] = byte;
213                write += 1;
214            }
215            read += 1;
216        }
217
218        let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
219            Ok(len) => len,
220            Err(err) => {
221                wipe_bytes(buffer);
222                return Err(err);
223            }
224        };
225        wipe_tail(buffer, len);
226        Ok(&mut buffer[..len])
227    }
228
229    fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
230        let _required = validate_decode::<A, PAD>(buffer)?;
231        let input_len = buffer.len();
232        let mut read = 0;
233        let mut write = 0;
234        while read + 4 <= input_len {
235            let chunk = read_quad(buffer, read)?;
236            let available = buffer.len();
237            let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
238                required: write,
239                available,
240            })?;
241            let written = decode_chunk::<A, PAD>(chunk, output_tail)
242                .map_err(|err| err.with_index_offset(read))?;
243            read += 4;
244            write += written;
245            if written < 3 {
246                if read != input_len {
247                    return Err(DecodeError::InvalidPadding { index: read - 4 });
248                }
249                return Ok(write);
250            }
251        }
252
253        let rem = input_len - read;
254        if rem == 0 {
255            return Ok(write);
256        }
257        if PAD {
258            return Err(DecodeError::InvalidLength);
259        }
260        let mut tail = [0u8; 3];
261        tail[..rem].copy_from_slice(&buffer[read..input_len]);
262        let result = decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
263            .map_err(|err| err.with_index_offset(read))
264            .map(|n| write + n);
265        wipe_bytes(&mut tail);
266        result
267    }
268}