weakauras_codec_base64/encode/
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::{EncodeError, EncodeIntoSliceError};
12/// Encode `input` as base64 into the provided slice without validating its length.
13///
14/// Returns the amount of bytes written. Written bytes are guaranteed to be ASCII.
15///
16/// # Safety
17///
18/// * `output`'s length must be AT LEAST `(input.len() * 4 + 2) / 3`.
19///
20/// # Example
21///
22/// ```
23/// use weakauras_codec_base64::encode;
24///
25/// let input = b"Hello, world!";
26/// let required_capacity = encode::calculate_encoded_len(input).unwrap();
27/// let mut output = Vec::with_capacity(required_capacity);
28///
29/// // SAFETY:
30/// // - buffer's capacity is enough for storing base64-encoded input;
31/// // - encode_into_unchecked returns the amount of bytes written,
32/// //   thus it is safe to call set_len using its return value.
33/// unsafe {
34///     let bytes_written = encode::encode_into_unchecked(input, output.spare_capacity_mut());
35///     output.set_len(bytes_written);
36/// }
37///
38/// assert_eq!(output, b"ivgBS9glGC3BYXgzHa");
39/// ```
40pub use arch::encode_into_unchecked;
41use core::mem::MaybeUninit;
42
43#[cfg(feature = "alloc")]
44use alloc::{string::String, vec::Vec};
45
46/// Calculate the amount of bytes required to store `input`
47/// after encoding it as base64.
48///
49/// `None` indicates an overflow.
50///
51/// # Example
52///
53/// ```
54/// use weakauras_codec_base64::encode;
55///
56/// assert_eq!(encode::calculate_encoded_len(b"Hello, world!").unwrap(), 18);
57/// ```
58#[inline]
59pub fn calculate_encoded_len(input: &[u8]) -> Option<usize> {
60    // Equivalent to (input.len() * 4 + 2) / 3 but avoids an early overflow
61    let len = input.len();
62    let leftover = len % 3;
63
64    (len / 3).checked_mul(4).and_then(|len| {
65        if leftover > 0 {
66            len.checked_add(leftover + 1)
67        } else {
68            Some(len)
69        }
70    })
71}
72
73/// Encode `input` as base64 into a new `String` with the supplied prefix.
74///
75/// # Example
76///
77/// ```
78/// use weakauras_codec_base64::{encode::encode_to_string_with_prefix, error::EncodeError};
79///
80/// fn main() -> Result<(), EncodeError> {
81///     assert_eq!(
82///         encode_to_string_with_prefix(b"Hello, world!", "!WA:2!")?,
83///         "!WA:2!ivgBS9glGC3BYXgzHa"
84///     );
85///     Ok(())
86/// }
87/// ```
88#[cfg(feature = "alloc")]
89pub fn encode_to_string_with_prefix(input: &[u8], prefix: &str) -> Result<String, EncodeError> {
90    let mut buffer = Vec::with_capacity(
91        calculate_encoded_len(input)
92            .and_then(|len| len.checked_add(prefix.len()))
93            .ok_or(EncodeError::DataIsTooLarge)?,
94    );
95    buffer.extend_from_slice(prefix.as_bytes());
96
97    // SAFETY:
98    // - buffer's capacity is enough for storing both the prefix and base64-encoded input;
99    // - encode_into_unchecked returns the amount of bytes written,
100    //   thus it is safe to call set_len adding its return value
101    //   and the prefix's length (which buffer.len() is currently equal to).
102    unsafe {
103        let written = encode_into_unchecked(input, buffer.spare_capacity_mut());
104        buffer.set_len(buffer.len() + written);
105    }
106
107    // SAFETY:
108    // - prefix is guaranteed to be valid UTF-8, since it is &str;
109    // - encode_into_unchecked writes exclusively ASCII bytes.
110    let result = unsafe { String::from_utf8_unchecked(buffer) };
111    Ok(result)
112}
113
114/// Encode `input` as base64 into a new `String`.
115///
116/// # Example
117///
118/// ```
119/// use weakauras_codec_base64::{encode::encode_to_string, error::EncodeError};
120///
121/// fn main() -> Result<(), EncodeError> {
122///     assert_eq!(encode_to_string(b"Hello, world!")?, "ivgBS9glGC3BYXgzHa");
123///     Ok(())
124/// }
125/// ```
126#[cfg(feature = "alloc")]
127pub fn encode_to_string(input: &[u8]) -> Result<String, EncodeError> {
128    let mut buffer =
129        Vec::with_capacity(calculate_encoded_len(input).ok_or(EncodeError::DataIsTooLarge)?);
130
131    // SAFETY:
132    // - buffer's capacity is enough for storing base64-encoded input;
133    // - encode_into_unchecked returns the amount of bytes written,
134    //   thus it is safe to call set_len using its return value.
135    unsafe {
136        let written = encode_into_unchecked(input, buffer.spare_capacity_mut());
137        buffer.set_len(written);
138    }
139
140    // SAFETY: encode_into_unchecked writes exclusively ASCII bytes.
141    let result = unsafe { String::from_utf8_unchecked(buffer) };
142    Ok(result)
143}
144
145/// Encode `input` as base64 into the provided slice.
146///
147/// Returns the amount of bytes written. Written bytes are guaranteed to be ASCII.
148///
149/// # Example
150///
151/// ```
152/// use weakauras_codec_base64::{encode, error::EncodeIntoSliceError};
153///
154/// fn main() -> Result<(), EncodeIntoSliceError> {
155///     let input = b"Hello, world!";
156///     let required_capacity = encode::calculate_encoded_len(input).unwrap();
157///     let mut output = Vec::with_capacity(required_capacity);
158///
159///     let bytes_written = encode::encode_into(input, output.spare_capacity_mut())?;
160///     unsafe {
161///         output.set_len(bytes_written);
162///     }
163///     assert_eq!(output, b"ivgBS9glGC3BYXgzHa");
164///     Ok(())
165/// }
166/// ```
167pub fn encode_into(
168    input: &[u8],
169    output: &mut [MaybeUninit<u8>],
170) -> Result<usize, EncodeIntoSliceError> {
171    let required_capacity = calculate_encoded_len(input).ok_or(EncodeError::DataIsTooLarge)?;
172    if output.len() < required_capacity {
173        return Err(EncodeIntoSliceError::OutputSliceIsTooSmall);
174    }
175
176    // SAFETY: output's len is enough to store base64-encoded input.
177    Ok(unsafe { encode_into_unchecked(input, output) })
178}
179
180#[cfg(test)]
181pub(crate) mod tests;