1use core::num::NonZeroUsize;
4
5use super::{
6 contracts::{
7 BackendFault, Failure, InputError, Lifecycle, OperationError, Progress, SourceSpan, Step,
8 },
9 decode_primitives::{
10 is_legacy_ascii_whitespace, one_byte_tail_is_canonical, pack_full_quantum,
11 two_byte_tail_is_canonical,
12 },
13 specifications::{Base64, Codec, CodecSettings, DecodePadding, TrailingBits},
14};
15
16const INPUT_QUANTUM: usize = 4;
17const OUTPUT_QUANTUM: usize = 3;
18
19#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct DecoderState {
26 settings: CodecSettings,
27 input_mode: InputMode,
28 quantum: [u8; INPUT_QUANTUM],
29 quantum_indexes: [usize; INPUT_QUANTUM],
30 quantum_len: usize,
31 pending: [u8; OUTPUT_QUANTUM],
32 pending_start: usize,
33 pending_len: usize,
34 terminal_padding: bool,
35 lifecycle: Lifecycle,
36}
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39enum InputMode {
40 Strict,
41 IgnoreLegacyAsciiWhitespace,
42}
43
44impl DecoderState {
45 pub(crate) const fn new_padded(settings: CodecSettings) -> Self {
47 Self {
48 settings,
49 input_mode: InputMode::Strict,
50 quantum: [0; INPUT_QUANTUM],
51 quantum_indexes: [0; INPUT_QUANTUM],
52 quantum_len: 0,
53 pending: [0; OUTPUT_QUANTUM],
54 pending_start: 0,
55 pending_len: 0,
56 terminal_padding: false,
57 lifecycle: Lifecycle::new(),
58 }
59 }
60
61 pub(crate) const fn new_unpadded(settings: CodecSettings) -> Self {
63 Self::new_padded(settings)
64 }
65
66 pub(crate) const fn new_legacy_ascii_whitespace(settings: CodecSettings) -> Self {
67 let mut state = Self::new_padded(settings);
68 state.input_mode = InputMode::IgnoreLegacyAsciiWhitespace;
69 state
70 }
71
72 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<Step, OperationError> {
74 let span = self.lifecycle.reserve_input(input.len())?;
75 let consumed = match self.plan_update(input, output.len(), span) {
76 Ok(consumed) => consumed,
77 Err(failure) => return Err(self.lifecycle.fail(failure)),
78 };
79 self.lifecycle.commit_input(span, consumed)?;
80
81 let mut produced = self.drain_pending(output);
82 let source_start = self.lifecycle.source_position() - consumed;
83 let mut input_offset = 0;
84 while input_offset < consumed {
85 if self.ignores(input[input_offset]) {
86 input_offset += 1;
87 continue;
88 }
89 self.quantum[self.quantum_len] = input[input_offset];
90 self.quantum_indexes[self.quantum_len] = source_start + input_offset;
91 self.quantum_len += 1;
92 input_offset += 1;
93
94 if self.quantum_len == INPUT_QUANTUM {
95 let Ok(decoded) = decode_quantum(self.settings, self.quantum, self.quantum_indexes)
96 else {
97 return Err(self
98 .lifecycle
99 .fail(Failure::Backend(BackendFault::ImpossibleState)));
100 };
101 self.pending = decoded.bytes;
102 self.pending_start = 0;
103 self.pending_len = decoded.len;
104 self.terminal_padding = decoded.terminal_padding;
105 self.quantum_len = 0;
106 produced += self.drain_pending(&mut output[produced..]);
107 }
108 }
109
110 let progress = Progress::new(consumed, produced);
111 if self.pending_len != 0 || consumed != input.len() {
112 self.lifecycle.output_full(progress, NonZeroUsize::MIN)
113 } else {
114 self.lifecycle.need_input(progress)
115 }
116 }
117
118 pub fn finish(&mut self, output: &mut [u8]) -> Result<Step, OperationError> {
120 if self.lifecycle.begin_finish()? {
121 return self.lifecycle.finish(Progress::ZERO);
122 }
123
124 let mut produced = self.drain_pending(output);
125 if self.pending_len != 0 {
126 return self
127 .lifecycle
128 .output_full(Progress::new(0, produced), NonZeroUsize::MIN);
129 }
130 if self.quantum_len != 0 {
131 let decoded = match self.settings.decode_padding() {
132 DecodePadding::RequireCanonical => {
133 let failure = Failure::Input(InputError::TruncatedInput {
134 index: self.lifecycle.source_position(),
135 });
136 return Err(self.lifecycle.fail(failure));
137 }
138 DecodePadding::Forbid | DecodePadding::Indifferent => {
139 match decode_final_tail(
140 self.settings,
141 &self.quantum[..self.quantum_len],
142 &self.quantum_indexes[..self.quantum_len],
143 ) {
144 Ok(decoded) => decoded,
145 Err(error) => return Err(self.lifecycle.fail(Failure::Input(error))),
146 }
147 }
148 };
149 self.pending = decoded.bytes;
150 self.pending_start = 0;
151 self.pending_len = decoded.len;
152 self.quantum_len = 0;
153 produced += self.drain_pending(&mut output[produced..]);
154 if self.pending_len != 0 {
155 return self
156 .lifecycle
157 .output_full(Progress::new(0, produced), NonZeroUsize::MIN);
158 }
159 }
160 self.lifecycle.finish(Progress::new(0, produced))
161 }
162
163 pub fn reset(&mut self) {
165 self.quantum = [0; INPUT_QUANTUM];
166 self.quantum_indexes = [0; INPUT_QUANTUM];
167 self.quantum_len = 0;
168 self.pending = [0; OUTPUT_QUANTUM];
169 self.pending_start = 0;
170 self.pending_len = 0;
171 self.terminal_padding = false;
172 self.lifecycle.reset();
173 }
174
175 pub fn clear(&mut self) {
181 self.wipe();
182 }
183
184 #[must_use]
186 pub const fn source_position(&self) -> usize {
187 self.lifecycle.source_position()
188 }
189
190 #[must_use]
192 pub const fn buffered_input_len(&self) -> usize {
193 self.quantum_len
194 }
195
196 #[must_use]
198 pub const fn has_terminal_padding(&self) -> bool {
199 self.terminal_padding
200 }
201
202 pub(crate) fn wipe(&mut self) {
204 crate::wipe_bytes(&mut self.quantum);
205 crate::wipe_bytes(&mut self.pending);
206 self.quantum_indexes = [0; INPUT_QUANTUM];
207 self.quantum_len = 0;
208 self.pending_start = 0;
209 self.pending_len = 0;
210 self.terminal_padding = false;
211 self.lifecycle.reset();
212 }
213
214 fn plan_update(
215 &self,
216 input: &[u8],
217 output_len: usize,
218 span: SourceSpan,
219 ) -> Result<usize, Failure> {
220 let pending_written = self.pending_len.min(output_len);
221 let mut pending = self.pending_len - pending_written;
222 if pending != 0 {
223 return Ok(0);
224 }
225
226 let mut available_output = output_len - pending_written;
227 let mut quantum = self.quantum;
228 let mut indexes = self.quantum_indexes;
229 let mut quantum_len = self.quantum_len;
230 let mut terminal_padding = self.terminal_padding;
231 let mut consumed = 0;
232
233 while consumed < input.len() {
234 let index = span
235 .index(consumed)
236 .ok_or(Failure::Backend(BackendFault::ImpossibleState))?;
237 if self.ignores(input[consumed]) {
238 consumed += 1;
239 continue;
240 }
241 if terminal_padding {
242 return Err(Failure::Input(InputError::TrailingData { index }));
243 }
244
245 validate_partial_symbol(
246 self.settings,
247 quantum,
248 &indexes,
249 quantum_len,
250 input[consumed],
251 index,
252 )
253 .map_err(Failure::Input)?;
254 quantum[quantum_len] = input[consumed];
255 indexes[quantum_len] = index;
256 quantum_len += 1;
257 consumed += 1;
258
259 if quantum_len == INPUT_QUANTUM {
260 let decoded =
261 decode_quantum(self.settings, quantum, indexes).map_err(Failure::Input)?;
262 quantum_len = 0;
263 terminal_padding = decoded.terminal_padding;
264 let written = decoded.len.min(available_output);
265 available_output -= written;
266 pending = decoded.len - written;
267 if pending != 0 {
268 break;
269 }
270 }
271 }
272 Ok(consumed)
273 }
274
275 const fn ignores(&self, byte: u8) -> bool {
276 matches!(self.input_mode, InputMode::IgnoreLegacyAsciiWhitespace)
277 && is_legacy_ascii_whitespace(byte)
278 }
279
280 fn drain_pending(&mut self, output: &mut [u8]) -> usize {
281 let written = self.pending_len.min(output.len());
282 let pending_end = self.pending_start + written;
283 output[..written].copy_from_slice(&self.pending[self.pending_start..pending_end]);
284 self.pending_start = pending_end;
285 self.pending_len -= written;
286 if self.pending_len == 0 {
287 self.pending_start = 0;
288 }
289 written
290 }
291
292 #[cfg(kani)]
293 pub(crate) fn proof_invariants(&self) -> bool {
294 self.quantum_len < INPUT_QUANTUM
295 && self.pending_start <= OUTPUT_QUANTUM
296 && self.pending_len <= OUTPUT_QUANTUM
297 && self.pending_start + self.pending_len <= OUTPUT_QUANTUM
298 && !(self.quantum_len != 0 && self.pending_len != 0)
299 && matches!(
300 self.settings.decode_padding(),
301 DecodePadding::RequireCanonical
302 | DecodePadding::Forbid
303 | DecodePadding::Indifferent
304 )
305 }
306
307 #[cfg(test)]
308 pub(crate) fn set_source_position_for_test(&mut self, source_position: usize) {
309 self.lifecycle = Lifecycle::at_source_position(source_position);
310 }
311}
312
313impl<S: Codec> Base64<S> {
314 pub fn decoder(&self) -> DecoderState {
316 match self.settings().decode_padding() {
317 DecodePadding::RequireCanonical => DecoderState::new_padded(self.settings()),
318 DecodePadding::Forbid | DecodePadding::Indifferent => {
319 DecoderState::new_unpadded(self.settings())
320 }
321 }
322 }
323}
324
325#[derive(Clone, Copy)]
326struct DecodedQuantum {
327 bytes: [u8; OUTPUT_QUANTUM],
328 len: usize,
329 terminal_padding: bool,
330}
331
332fn validate_partial_symbol(
333 settings: CodecSettings,
334 quantum: [u8; INPUT_QUANTUM],
335 indexes: &[usize; INPUT_QUANTUM],
336 position: usize,
337 byte: u8,
338 index: usize,
339) -> Result<(), InputError> {
340 if matches!(settings.decode_padding(), DecodePadding::Forbid) {
341 return decode_symbol(settings, byte, index).map(|_| ());
342 }
343 match position {
344 0 | 1 => decode_symbol(settings, byte, index).map(|_| ()),
345 2 => {
346 if byte == b'=' {
347 Ok(())
348 } else {
349 decode_symbol(settings, byte, index).map(|_| ())
350 }
351 }
352 3 if quantum[2] == b'=' && byte != b'=' => {
353 Err(InputError::InvalidPadding { index: indexes[2] })
354 }
355 3 => {
356 if byte == b'=' {
357 Ok(())
358 } else {
359 decode_symbol(settings, byte, index).map(|_| ())
360 }
361 }
362 _ => Err(InputError::InvalidLength),
363 }
364}
365
366fn decode_quantum(
367 settings: CodecSettings,
368 input: [u8; INPUT_QUANTUM],
369 indexes: [usize; INPUT_QUANTUM],
370) -> Result<DecodedQuantum, InputError> {
371 let first = decode_symbol(settings, input[0], indexes[0])?;
372 let second = decode_symbol(settings, input[1], indexes[1])?;
373
374 if matches!(settings.decode_padding(), DecodePadding::Forbid) {
375 let third = decode_symbol(settings, input[2], indexes[2])?;
376 let fourth = decode_symbol(settings, input[3], indexes[3])?;
377 return Ok(DecodedQuantum {
378 bytes: pack_full_quantum(first, second, third, fourth),
379 len: 3,
380 terminal_padding: false,
381 });
382 }
383
384 match (input[2], input[3]) {
385 (b'=', b'=') => {
386 if !one_byte_tail_is_canonical(second)
387 && settings.trailing_bits() == TrailingBits::RequireCanonical
388 {
389 return Err(InputError::NonCanonicalTrailingBits { index: indexes[1] });
390 }
391 Ok(DecodedQuantum {
392 bytes: [(first << 2) | (second >> 4), 0, 0],
393 len: 1,
394 terminal_padding: true,
395 })
396 }
397 (b'=', _) => Err(InputError::InvalidPadding { index: indexes[2] }),
398 (third, b'=') => {
399 let third = decode_symbol(settings, third, indexes[2])?;
400 if !two_byte_tail_is_canonical(third)
401 && settings.trailing_bits() == TrailingBits::RequireCanonical
402 {
403 return Err(InputError::NonCanonicalTrailingBits { index: indexes[2] });
404 }
405 Ok(DecodedQuantum {
406 bytes: [
407 (first << 2) | (second >> 4),
408 (second << 4) | (third >> 2),
409 0,
410 ],
411 len: 2,
412 terminal_padding: true,
413 })
414 }
415 (third, fourth) => {
416 let third = decode_symbol(settings, third, indexes[2])?;
417 let fourth = decode_symbol(settings, fourth, indexes[3])?;
418 Ok(DecodedQuantum {
419 bytes: pack_full_quantum(first, second, third, fourth),
420 len: 3,
421 terminal_padding: false,
422 })
423 }
424 }
425}
426
427fn decode_unpadded_tail(
428 settings: CodecSettings,
429 input: &[u8],
430 indexes: &[usize],
431) -> Result<DecodedQuantum, InputError> {
432 match input {
433 [first, second] => {
434 let first = decode_symbol(settings, *first, indexes[0])?;
435 let second = decode_symbol(settings, *second, indexes[1])?;
436 if !one_byte_tail_is_canonical(second)
437 && settings.trailing_bits() == TrailingBits::RequireCanonical
438 {
439 return Err(InputError::NonCanonicalTrailingBits { index: indexes[1] });
440 }
441 Ok(DecodedQuantum {
442 bytes: [(first << 2) | (second >> 4), 0, 0],
443 len: 1,
444 terminal_padding: false,
445 })
446 }
447 [first, second, third] => {
448 let first = decode_symbol(settings, *first, indexes[0])?;
449 let second = decode_symbol(settings, *second, indexes[1])?;
450 let third = decode_symbol(settings, *third, indexes[2])?;
451 if !two_byte_tail_is_canonical(third)
452 && settings.trailing_bits() == TrailingBits::RequireCanonical
453 {
454 return Err(InputError::NonCanonicalTrailingBits { index: indexes[2] });
455 }
456 Ok(DecodedQuantum {
457 bytes: [
458 (first << 2) | (second >> 4),
459 (second << 4) | (third >> 2),
460 0,
461 ],
462 len: 2,
463 terminal_padding: false,
464 })
465 }
466 _ => Err(InputError::InvalidLength),
467 }
468}
469
470fn decode_final_tail(
471 settings: CodecSettings,
472 input: &[u8],
473 indexes: &[usize],
474) -> Result<DecodedQuantum, InputError> {
475 if settings.decode_padding() == DecodePadding::Indifferent
476 && let [first, second, b'='] = input
477 {
478 let first = decode_symbol(settings, *first, indexes[0])?;
479 let second = decode_symbol(settings, *second, indexes[1])?;
480 if !one_byte_tail_is_canonical(second)
481 && settings.trailing_bits() == TrailingBits::RequireCanonical
482 {
483 return Err(InputError::NonCanonicalTrailingBits { index: indexes[1] });
484 }
485 return Ok(DecodedQuantum {
486 bytes: [(first << 2) | (second >> 4), 0, 0],
487 len: 1,
488 terminal_padding: true,
489 });
490 }
491 decode_unpadded_tail(settings, input, indexes)
492}
493
494fn decode_symbol(settings: CodecSettings, byte: u8, index: usize) -> Result<u8, InputError> {
495 match settings.alphabet().decode_byte(byte) {
496 Some(value) => Ok(value),
497 None if byte == b'=' => Err(InputError::InvalidPadding { index }),
498 None => Err(InputError::InvalidByte { index, byte }),
499 }
500}