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}