Skip to main content

base64_ng/v2/
legacy.rs

1//! Explicit legacy ASCII-whitespace compatibility decoding.
2
3use super::{
4    contracts::{BackendFault, OperationError, Status, Step},
5    incremental_decoder::DecoderState,
6    ordinary::{OneShotError, map_operation_error},
7    specifications::{Base64, Codec},
8};
9
10/// The one retained legacy transport-whitespace decode policy.
11///
12/// It ignores only ASCII space, tab, carriage return, and line feed. It is an
13/// ordinary compatibility policy and is not available through `secret::*`.
14#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
15pub struct LegacyAsciiWhitespace;
16
17/// Explicit legacy decoder policy value.
18pub const ASCII_WHITESPACE: LegacyAsciiWhitespace = LegacyAsciiWhitespace;
19
20/// Heapless ordinary decoder that ignores the documented legacy whitespace.
21///
22/// Detailed malformed-input errors retain indexes in the original source,
23/// including across whitespace-only chunks. The state does not wipe on drop.
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct LegacyWhitespaceDecoder {
26    inner: DecoderState,
27}
28
29impl LegacyAsciiWhitespace {
30    /// Constructs a decoder for one explicit codec.
31    pub fn decoder<S: Codec>(&self, codec: &Base64<S>) -> LegacyWhitespaceDecoder {
32        LegacyWhitespaceDecoder {
33            inner: DecoderState::new_legacy_ascii_whitespace(codec.settings()),
34        }
35    }
36
37    /// Validates legacy-whitespace input without producing decoded bytes.
38    pub fn validate<S: Codec>(&self, codec: &Base64<S>, input: &[u8]) -> Result<(), OneShotError> {
39        self.decoded_len(codec, input).map(|_| ())
40    }
41
42    /// Validates input and returns its exact decoded length.
43    pub fn decoded_len<S: Codec>(
44        &self,
45        codec: &Base64<S>,
46        input: &[u8],
47    ) -> Result<usize, OneShotError> {
48        measure(self.decoder(codec), input)
49    }
50
51    /// Decodes transactionally after complete validation and exact sizing.
52    ///
53    /// Every returned error leaves the complete destination unchanged.
54    pub fn decode_into<S: Codec>(
55        &self,
56        codec: &Base64<S>,
57        input: &[u8],
58        output: &mut [u8],
59    ) -> Result<usize, OneShotError> {
60        let required = self.decoded_len(codec, input)?;
61        if output.len() < required {
62            return Err(OneShotError::OutputTooSmall {
63                required,
64                available: output.len(),
65            });
66        }
67        decode_validated(self.decoder(codec), input, &mut output[..required])?;
68        Ok(required)
69    }
70}
71
72impl LegacyWhitespaceDecoder {
73    /// Accepts one original source fragment and writes decoded bytes that fit.
74    pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<Step, OperationError> {
75        self.inner.update(input, output)
76    }
77
78    /// Finalizes the selected codec's padding policy.
79    pub fn finish(&mut self, output: &mut [u8]) -> Result<Step, OperationError> {
80        self.inner.finish(output)
81    }
82
83    /// Resets the state for an unrelated ordinary message.
84    pub fn reset(&mut self) {
85        self.inner.reset();
86    }
87
88    /// Returns the number of original source bytes accepted since reset.
89    #[must_use]
90    pub const fn source_position(&self) -> usize {
91        self.inner.source_position()
92    }
93
94    #[cfg(test)]
95    pub(crate) fn set_source_position_for_test(&mut self, source_position: usize) {
96        self.inner.set_source_position_for_test(source_position);
97    }
98}
99
100fn measure(mut decoder: LegacyWhitespaceDecoder, input: &[u8]) -> Result<usize, OneShotError> {
101    let mut input_offset = 0;
102    let mut output_len = 0usize;
103    let mut scratch = [0u8; 3];
104    while input_offset < input.len() {
105        let step = decoder
106            .update(&input[input_offset..], &mut scratch)
107            .map_err(map_operation_error)?;
108        let progress = step.progress();
109        if progress.input_consumed() == 0 && progress.output_produced() == 0 {
110            return Err(OneShotError::Backend(BackendFault::ImpossibleState));
111        }
112        input_offset += progress.input_consumed();
113        output_len = output_len
114            .checked_add(progress.output_produced())
115            .ok_or(OneShotError::LengthOverflow)?;
116    }
117    finish_measurement(&mut decoder, output_len, &mut scratch)
118}
119
120fn finish_measurement(
121    decoder: &mut LegacyWhitespaceDecoder,
122    mut output_len: usize,
123    scratch: &mut [u8; 3],
124) -> Result<usize, OneShotError> {
125    loop {
126        let step = decoder.finish(scratch).map_err(map_operation_error)?;
127        output_len = output_len
128            .checked_add(step.progress().output_produced())
129            .ok_or(OneShotError::LengthOverflow)?;
130        match step.status() {
131            Status::Complete => return Ok(output_len),
132            Status::OutputFull(_) if step.progress().output_produced() != 0 => {}
133            Status::OutputFull(_) | Status::NeedInput => {
134                return Err(OneShotError::Backend(BackendFault::ImpossibleState));
135            }
136        }
137    }
138}
139
140fn decode_validated(
141    mut decoder: LegacyWhitespaceDecoder,
142    input: &[u8],
143    output: &mut [u8],
144) -> Result<(), OneShotError> {
145    let mut input_offset = 0;
146    let mut output_offset = 0;
147    while input_offset < input.len() {
148        let step = decoder
149            .update(&input[input_offset..], &mut output[output_offset..])
150            .map_err(map_operation_error)?;
151        let progress = step.progress();
152        if progress.input_consumed() == 0 && progress.output_produced() == 0 {
153            return Err(OneShotError::Backend(BackendFault::ImpossibleState));
154        }
155        input_offset += progress.input_consumed();
156        output_offset += progress.output_produced();
157    }
158    loop {
159        let step = decoder
160            .finish(&mut output[output_offset..])
161            .map_err(map_operation_error)?;
162        output_offset += step.progress().output_produced();
163        match step.status() {
164            Status::Complete => return Ok(()),
165            Status::OutputFull(_) if step.progress().output_produced() != 0 => {}
166            Status::OutputFull(_) | Status::NeedInput => {
167                return Err(OneShotError::Backend(BackendFault::ImpossibleState));
168            }
169        }
170    }
171}