Skip to main content

base64_ng/v2/
ordinary.rs

1//! Transactional ordinary one-shot operations.
2
3use super::{
4    contracts::{BackendFault, Failure, InputError, OperationError, Status},
5    specifications::{Base64, Codec, CodecSettings, EncodePadding},
6};
7
8/// Error returned by a canonical ordinary one-shot operation.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10#[non_exhaustive]
11pub enum OneShotError {
12    /// The encoded output length cannot be represented by `usize`.
13    LengthOverflow,
14    /// Strict validation rejected the ordinary input.
15    Input(InputError),
16    /// Absolute source positions cannot be represented by `usize`.
17    PositionOverflow,
18    /// The caller's destination cannot hold the complete result.
19    OutputTooSmall {
20        /// Exact required output bytes.
21        required: usize,
22        /// Available destination bytes.
23        available: usize,
24    },
25    /// The exact output exceeds the caller-selected allocation limit.
26    AllocationLimitExceeded {
27        /// Exact required output bytes.
28        required: usize,
29        /// Maximum permitted output bytes.
30        limit: usize,
31    },
32    /// `try_reserve_exact` could not reserve the complete output allocation.
33    AllocationFailed {
34        /// Exact requested output bytes.
35        requested: usize,
36    },
37    /// An internal backend or state invariant failed.
38    Backend(BackendFault),
39}
40
41impl core::fmt::Display for OneShotError {
42    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43        match self {
44            Self::LengthOverflow => formatter.write_str("base64 output length overflows usize"),
45            Self::Input(error) => error.fmt(formatter),
46            Self::PositionOverflow => formatter.write_str("base64 source position overflows usize"),
47            Self::OutputTooSmall {
48                required,
49                available,
50            } => write!(
51                formatter,
52                "base64 output buffer too small: required {required}, available {available}"
53            ),
54            Self::AllocationLimitExceeded { required, limit } => write!(
55                formatter,
56                "base64 output length {required} exceeds allocation limit {limit}"
57            ),
58            Self::AllocationFailed { requested } => {
59                write!(
60                    formatter,
61                    "failed to reserve {requested} base64 output bytes"
62                )
63            }
64            Self::Backend(fault) => write!(formatter, "base64 backend fault: {}", fault.as_str()),
65        }
66    }
67}
68
69#[cfg(feature = "std")]
70impl std::error::Error for OneShotError {}
71
72impl<S: Codec> Base64<S> {
73    /// Returns the exact encoded length or reports arithmetic overflow.
74    pub fn encoded_len(&self, input_len: usize) -> Result<usize, OneShotError> {
75        encoded_len_for_settings(self.specification().settings(), input_len)
76    }
77
78    /// Validates ordinary input without producing decoded bytes.
79    pub fn validate(&self, input: &[u8]) -> Result<(), OneShotError> {
80        self.decoded_len(input).map(|_| ())
81    }
82
83    /// Validates `input` and returns its exact decoded length.
84    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, OneShotError> {
85        validate_and_measure(self, input)
86    }
87
88    /// Encodes into a caller-owned slice transactionally.
89    ///
90    /// Every returned error leaves the complete destination unchanged. On
91    /// success, bytes after the returned initialized prefix are unchanged.
92    pub fn encode_into(&self, input: &[u8], output: &mut [u8]) -> Result<usize, OneShotError> {
93        let required = self.encoded_len(input.len())?;
94        require_output(required, output.len())?;
95        encode_validated(self.settings(), input, &mut output[..required]);
96        Ok(required)
97    }
98
99    /// Decodes into a caller-owned slice transactionally.
100    ///
101    /// Validation and exact sizing complete before the first destination
102    /// write. Every returned error therefore leaves the destination unchanged.
103    pub fn decode_into(&self, input: &[u8], output: &mut [u8]) -> Result<usize, OneShotError> {
104        let required = self.decoded_len(input)?;
105        require_output(required, output.len())?;
106        decode_validated(self.settings(), input, &mut output[..required]);
107        Ok(required)
108    }
109}
110
111const fn encoded_len_for_settings(
112    settings: CodecSettings,
113    input_len: usize,
114) -> Result<usize, OneShotError> {
115    let Some(complete) = (input_len / 3).checked_mul(4) else {
116        return Err(OneShotError::LengthOverflow);
117    };
118    let remainder = input_len % 3;
119    let tail = if remainder == 0 {
120        0
121    } else if matches!(settings.encode_padding(), EncodePadding::Padded) {
122        4
123    } else {
124        remainder + 1
125    };
126    match complete.checked_add(tail) {
127        Some(value) => Ok(value),
128        None => Err(OneShotError::LengthOverflow),
129    }
130}
131
132fn require_output(required: usize, available: usize) -> Result<(), OneShotError> {
133    if available < required {
134        Err(OneShotError::OutputTooSmall {
135            required,
136            available,
137        })
138    } else {
139        Ok(())
140    }
141}
142
143fn validate_and_measure<S: Codec>(codec: &Base64<S>, input: &[u8]) -> Result<usize, OneShotError> {
144    let mut decoder = codec.decoder();
145    let mut input_offset = 0;
146    let mut measured_len = 0usize;
147    let mut scratch = [0u8; 3];
148    while input_offset < input.len() {
149        let step = decoder
150            .update(&input[input_offset..], &mut scratch)
151            .map_err(map_operation_error)?;
152        let progress = step.progress();
153        if progress.input_consumed() == 0 && progress.output_produced() == 0 {
154            return Err(OneShotError::Backend(BackendFault::ImpossibleState));
155        }
156        input_offset += progress.input_consumed();
157        measured_len = measured_len
158            .checked_add(progress.output_produced())
159            .ok_or(OneShotError::LengthOverflow)?;
160    }
161
162    loop {
163        let step = decoder.finish(&mut scratch).map_err(map_operation_error)?;
164        measured_len = measured_len
165            .checked_add(step.progress().output_produced())
166            .ok_or(OneShotError::LengthOverflow)?;
167        match step.status() {
168            Status::Complete => return Ok(measured_len),
169            Status::OutputFull(_) => {}
170            Status::NeedInput => {
171                return Err(OneShotError::Backend(BackendFault::ImpossibleState));
172            }
173        }
174    }
175}
176
177pub(super) fn map_operation_error(error: OperationError) -> OneShotError {
178    match error {
179        OperationError::Failed(Failure::Input(error)) => OneShotError::Input(error),
180        OperationError::Failed(Failure::PositionOverflow) => OneShotError::PositionOverflow,
181        OperationError::Failed(Failure::Backend(fault)) => OneShotError::Backend(fault),
182        OperationError::Failed(Failure::ResourceLimit) | OperationError::Terminal(_) => {
183            OneShotError::Backend(BackendFault::ImpossibleState)
184        }
185    }
186}
187
188fn encode_validated(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
189    let alphabet = settings.alphabet().as_array();
190    let mut read = 0;
191    let mut write = 0;
192    while read + 3 <= input.len() {
193        let first = input[read];
194        let second = input[read + 1];
195        let third = input[read + 2];
196        output[write] = alphabet[usize::from(first >> 2)];
197        output[write + 1] = alphabet[usize::from(((first & 3) << 4) | (second >> 4))];
198        output[write + 2] = alphabet[usize::from(((second & 15) << 2) | (third >> 6))];
199        output[write + 3] = alphabet[usize::from(third & 63)];
200        read += 3;
201        write += 4;
202    }
203    encode_tail(settings, &input[read..], &mut output[write..]);
204}
205
206fn encode_tail(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
207    let alphabet = settings.alphabet().as_array();
208    if let [first, rest @ ..] = input {
209        output[0] = alphabet[usize::from(first >> 2)];
210        output[1] = alphabet[usize::from((first & 3) << 4)];
211        if let [second] = rest {
212            output[1] = alphabet[usize::from(((first & 3) << 4) | (second >> 4))];
213            output[2] = alphabet[usize::from((second & 15) << 2)];
214            if settings.encode_padding() == EncodePadding::Padded {
215                output[3] = b'=';
216            }
217        } else if settings.encode_padding() == EncodePadding::Padded {
218            output[2..4].copy_from_slice(b"==");
219        }
220    }
221}
222
223fn decode_validated(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
224    let mut read = 0;
225    let mut write = 0;
226    while read + 4 <= input.len() {
227        let input_quantum = &input[read..read + 4];
228        let first = decode_value(settings, input_quantum[0]);
229        let second = decode_value(settings, input_quantum[1]);
230        output[write] = (first << 2) | (second >> 4);
231        write += 1;
232        if input_quantum[2] != b'=' {
233            let third = decode_value(settings, input_quantum[2]);
234            output[write] = (second << 4) | (third >> 2);
235            write += 1;
236            if input_quantum[3] != b'=' {
237                output[write] = (third << 6) | decode_value(settings, input_quantum[3]);
238                write += 1;
239            }
240        }
241        read += 4;
242    }
243    decode_tail(settings, &input[read..], &mut output[write..]);
244}
245
246fn decode_tail(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
247    if let [first, second, rest @ ..] = input {
248        let first = decode_value(settings, *first);
249        let second = decode_value(settings, *second);
250        output[0] = (first << 2) | (second >> 4);
251        if let [third] = rest
252            && *third != b'='
253        {
254            output[1] = (second << 4) | (decode_value(settings, *third) >> 2);
255        }
256    }
257}
258
259fn decode_value(settings: CodecSettings, byte: u8) -> u8 {
260    settings.alphabet().decode_byte(byte).unwrap_or(0)
261}
262
263#[cfg(test)]
264pub(super) fn encode(
265    profile: super::rfc4648_oracle::Profile,
266    input: &[u8],
267    output: &mut [u8],
268) -> Result<usize, OneShotError> {
269    match profile {
270        super::rfc4648_oracle::Profile::StandardPadded => {
271            super::specifications::STRICT_STANDARD_PADDED.encode_into(input, output)
272        }
273        super::rfc4648_oracle::Profile::StandardUnpadded => {
274            super::specifications::STRICT_STANDARD_UNPADDED.encode_into(input, output)
275        }
276        super::rfc4648_oracle::Profile::UrlSafePadded => {
277            super::specifications::STRICT_URL_SAFE_PADDED.encode_into(input, output)
278        }
279        super::rfc4648_oracle::Profile::UrlSafeUnpadded => {
280            super::specifications::STRICT_URL_SAFE_UNPADDED.encode_into(input, output)
281        }
282    }
283}
284
285#[cfg(test)]
286pub(super) fn decode(
287    profile: super::rfc4648_oracle::Profile,
288    input: &[u8],
289    output: &mut [u8],
290) -> Result<usize, OneShotError> {
291    match profile {
292        super::rfc4648_oracle::Profile::StandardPadded => {
293            super::specifications::STRICT_STANDARD_PADDED.decode_into(input, output)
294        }
295        super::rfc4648_oracle::Profile::StandardUnpadded => {
296            super::specifications::STRICT_STANDARD_UNPADDED.decode_into(input, output)
297        }
298        super::rfc4648_oracle::Profile::UrlSafePadded => {
299            super::specifications::STRICT_URL_SAFE_PADDED.decode_into(input, output)
300        }
301        super::rfc4648_oracle::Profile::UrlSafeUnpadded => {
302            super::specifications::STRICT_URL_SAFE_UNPADDED.decode_into(input, output)
303        }
304    }
305}