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