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;