weakauras_codec_base64/decode/mod.rs
1// Copyright 2020-2025 Velithris
2// SPDX-License-Identifier: MIT
3
4// No guarantees about following semver there.
5// Both modules are public for benchmarks and fuzzing.
6#[doc(hidden)]
7pub mod arch;
8#[doc(hidden)]
9pub mod scalar;
10
11use crate::error::{DecodeError, DecodeIntoSliceError};
12/// Decode base64-encoded `input` into the provided slice without validating its length.
13///
14/// On success `Result::Ok` containing the amount of bytes written is returned.
15/// Otherwise, `Result::Err` containing the offset of the first invalid input byte is returned.
16///
17/// # Safety
18///
19/// * `output`'s length must be AT LEAST `input.len() * 3 / 4`
20///
21/// # Example
22///
23/// ```
24/// use weakauras_codec_base64::decode;
25///
26/// let input = b"ivgBS9glGC3BYXgzHa";
27/// let required_capacity = decode::calculate_decoded_len(input).unwrap();
28/// let mut output = Vec::with_capacity(required_capacity);
29///
30/// // SAFETY:
31/// // - buffer's capacity is enough for storing decoded base64-input;
32/// // - decode_into_unchecked returns the amount of bytes written,
33/// // thus it is safe to call set_len using its return value.
34/// unsafe {
35/// let bytes_written =
36/// decode::decode_into_unchecked(input, output.spare_capacity_mut()).unwrap();
37/// output.set_len(bytes_written);
38/// }
39///
40/// assert_eq!(output, b"Hello, world!");
41/// ```
42pub use arch::decode_into_unchecked;
43use core::mem::MaybeUninit;
44
45#[cfg(feature = "alloc")]
46use alloc::vec::Vec;
47
48/// Calculate the amount of bytes required to store base64-encoded `input`
49/// after decoding it.
50///
51/// `None` indicates an invalid length of the `input`.
52///
53/// # Example
54///
55/// ```
56/// use weakauras_codec_base64::decode;
57///
58/// assert_eq!(
59/// decode::calculate_decoded_len(b"ivgBS9glGC3BYXgzHa").unwrap(),
60/// 13
61/// );
62/// ```
63#[inline]
64pub fn calculate_decoded_len(input: &[u8]) -> Option<usize> {
65 // Equivalent to input.len() * 3 / 4 but does not overflow
66 let len = input.len();
67
68 let leftover = len % 4;
69 if leftover == 1 {
70 return None;
71 }
72 let mut result = len / 4 * 3;
73
74 if leftover > 0 {
75 result += leftover - 1;
76 }
77
78 Some(result)
79}
80
81/// Decode base64-encoded `input` into a new `Vec<u8>`.
82///
83/// # Example
84///
85/// ```
86/// use weakauras_codec_base64::{decode::decode_to_vec, error::DecodeError};
87///
88/// fn main() -> Result<(), DecodeError> {
89/// assert_eq!(decode_to_vec(b"ivgBS9glGC3BYXgzHa")?, b"Hello, world!");
90/// Ok(())
91/// }
92/// ```
93#[cfg(feature = "alloc")]
94pub fn decode_to_vec(input: &[u8]) -> Result<Vec<u8>, DecodeError> {
95 let mut buffer =
96 Vec::with_capacity(calculate_decoded_len(input).ok_or(DecodeError::InvalidLength)?);
97
98 // SAFETY:
99 // - buffer's capacity is enough for storing decoded base64-input;
100 // - decode_into_unchecked returns the amount of bytes written,
101 // thus it is safe to call set_len using its return value.
102 unsafe {
103 let written = decode_into_unchecked(input, buffer.spare_capacity_mut())
104 .map_err(DecodeError::InvalidByte)?;
105 buffer.set_len(written)
106 }
107
108 Ok(buffer)
109}
110
111/// Decode base64-encoded `input` into the provided slice.
112///
113/// Returns the amount of bytes written.
114///
115/// # Example
116///
117/// ```
118/// use weakauras_codec_base64::{decode, error::DecodeIntoSliceError};
119///
120/// fn main() -> Result<(), DecodeIntoSliceError> {
121/// let input = b"ivgBS9glGC3BYXgzHa";
122/// let required_capacity = decode::calculate_decoded_len(input).unwrap();
123/// let mut output = Vec::with_capacity(required_capacity);
124///
125/// let bytes_written = decode::decode_into(input, output.spare_capacity_mut())?;
126/// unsafe {
127/// output.set_len(bytes_written);
128/// }
129/// assert_eq!(output, b"Hello, world!");
130/// Ok(())
131/// }
132/// ```
133pub fn decode_into(
134 input: &[u8],
135 output: &mut [MaybeUninit<u8>],
136) -> Result<usize, DecodeIntoSliceError> {
137 let required_capacity = calculate_decoded_len(input).ok_or(DecodeError::InvalidLength)?;
138 if output.len() < required_capacity {
139 return Err(DecodeIntoSliceError::OutputSliceIsTooSmall);
140 }
141
142 // SAFETY: output's len is enough to store decoded base64-input.
143 Ok(unsafe { decode_into_unchecked(input, output).map_err(DecodeError::InvalidByte)? })
144}
145
146#[cfg(test)]
147pub(crate) mod tests;