base64_simd/
lib.rs

1//! SIMD-accelerated base64 encoding and decoding.
2//!
3//! # Examples
4//!
5//! ```
6//! # #[cfg(feature = "alloc")]
7//! # {
8//! let bytes = b"hello world";
9//! let base64 = base64_simd::STANDARD;
10//!
11//! let encoded = base64.encode_to_string(bytes);
12//! assert_eq!(encoded, "aGVsbG8gd29ybGQ=");
13//!
14//! let decoded = base64.decode_to_vec(encoded).unwrap();
15//! assert_eq!(decoded, bytes);
16//! # }
17//! ```
18//!
19#![doc=vsimd::shared_docs!()]
20//
21#![cfg_attr(not(any(feature = "std", test)), no_std)]
22#![cfg_attr(feature = "unstable", feature(arm_target_feature))]
23#![cfg_attr(docsrs, feature(doc_cfg))]
24#![cfg_attr(test, deny(warnings))]
25//
26#![deny(
27    missing_debug_implementations,
28    missing_docs,
29    clippy::all,
30    clippy::pedantic,
31    clippy::cargo,
32    clippy::missing_inline_in_public_items
33)]
34#![warn(clippy::todo)]
35#![allow(
36    clippy::inline_always,
37    clippy::wildcard_imports,
38    clippy::module_name_repetitions,
39    clippy::cast_sign_loss,
40    clippy::cast_possible_truncation,
41    clippy::cast_lossless,
42    clippy::cast_possible_wrap,
43    clippy::items_after_statements,
44    clippy::match_same_arms,
45    clippy::verbose_bit_mask
46)]
47
48#[cfg(feature = "alloc")]
49extern crate alloc;
50
51#[macro_use]
52mod error;
53pub use self::error::Error;
54
55mod alsw;
56mod ascii;
57mod check;
58mod decode;
59mod encode;
60
61mod multiversion;
62
63#[cfg(feature = "alloc")]
64mod heap;
65
66mod forgiving;
67pub use self::forgiving::*;
68
69pub use outref::{AsOut, Out};
70
71// -----------------------------------------------------------------------------
72
73use crate::decode::decoded_length;
74use crate::encode::encoded_length_unchecked;
75
76use vsimd::tools::{slice_mut, slice_parts};
77
78#[cfg(feature = "alloc")]
79use alloc::{string::String, vec::Vec};
80
81const STANDARD_CHARSET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
82const URL_SAFE_CHARSET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
83
84/// Base64 variant
85#[derive(Debug)]
86pub struct Base64 {
87    config: Config,
88}
89
90#[derive(Debug, Clone, Copy)]
91enum Kind {
92    Standard,
93    UrlSafe,
94}
95
96#[derive(Debug, Clone, Copy)]
97struct Config {
98    kind: Kind,
99    extra: Extra,
100}
101
102#[derive(Debug, Clone, Copy)]
103enum Extra {
104    Pad,
105    NoPad,
106    Forgiving,
107}
108
109impl Extra {
110    /// Whether to add padding when encoding
111    #[inline(always)]
112    #[must_use]
113    const fn padding(self) -> bool {
114        match self {
115            Extra::Pad => true,
116            Extra::NoPad => false,
117            Extra::Forgiving => true,
118        }
119    }
120
121    #[inline(always)]
122    #[must_use]
123    const fn forgiving(self) -> bool {
124        match self {
125            Extra::Pad => false,
126            Extra::NoPad => false,
127            Extra::Forgiving => true,
128        }
129    }
130}
131
132/// Standard charset with padding.
133pub const STANDARD: Base64 = Base64 {
134    config: Config {
135        kind: Kind::Standard,
136        extra: Extra::Pad,
137    },
138};
139
140/// URL-Safe charset with padding.
141pub const URL_SAFE: Base64 = Base64 {
142    config: Config {
143        kind: Kind::UrlSafe,
144        extra: Extra::Pad,
145    },
146};
147
148/// Standard charset without padding.
149pub const STANDARD_NO_PAD: Base64 = Base64 {
150    config: Config {
151        kind: Kind::Standard,
152        extra: Extra::NoPad,
153    },
154};
155
156/// URL-Safe charset without padding.
157pub const URL_SAFE_NO_PAD: Base64 = Base64 {
158    config: Config {
159        kind: Kind::UrlSafe,
160        extra: Extra::NoPad,
161    },
162};
163
164const STANDARD_FORGIVING: Base64 = Base64 {
165    config: Config {
166        kind: Kind::Standard,
167        extra: Extra::Forgiving,
168    },
169};
170
171impl Base64 {
172    /// Returns the character set.
173    #[inline]
174    #[must_use]
175    pub const fn charset(&self) -> &[u8; 64] {
176        match self.config.kind {
177            Kind::Standard => STANDARD_CHARSET,
178            Kind::UrlSafe => URL_SAFE_CHARSET,
179        }
180    }
181
182    /// Calculates the encoded length.
183    ///
184    /// # Panics
185    /// This function will panic if `n > isize::MAX`.
186    #[inline]
187    #[must_use]
188    pub const fn encoded_length(&self, n: usize) -> usize {
189        assert!(n <= usize::MAX / 2);
190        encoded_length_unchecked(n, self.config)
191    }
192
193    /// Estimates the decoded length.
194    ///
195    /// The result is an upper bound which can be used for allocation.
196    #[inline]
197    #[must_use]
198    pub const fn estimated_decoded_length(&self, n: usize) -> usize {
199        if n % 4 == 0 {
200            n / 4 * 3
201        } else {
202            (n / 4 + 1) * 3
203        }
204    }
205
206    /// Calculates the decoded length.
207    ///
208    /// The result is a precise value which can be used for allocation.
209    ///
210    /// # Errors
211    /// This function returns `Err` if the content of `data` is partially invalid.
212    #[inline]
213    pub fn decoded_length(&self, data: &[u8]) -> Result<usize, Error> {
214        let (_, m) = decoded_length(data, self.config)?;
215        Ok(m)
216    }
217
218    /// Checks whether `data` is a base64 string.
219    ///
220    /// # Errors
221    /// This function returns `Err` if the content of `data` is invalid.
222    #[inline]
223    pub fn check(&self, data: &[u8]) -> Result<(), Error> {
224        let (n, _) = decoded_length(data, self.config)?;
225        unsafe { crate::multiversion::check::auto(data.as_ptr(), n, self.config) }
226    }
227
228    /// Encodes bytes to a base64 string.
229    ///
230    /// # Panics
231    /// This function will panic if the length of `dst` is not enough.
232    #[inline]
233    #[must_use]
234    pub fn encode<'d>(&self, src: &[u8], mut dst: Out<'d, [u8]>) -> &'d mut [u8] {
235        unsafe {
236            let m = encoded_length_unchecked(src.len(), self.config);
237            assert!(dst.len() >= m);
238
239            let (src, len) = slice_parts(src);
240            let dst = dst.as_mut_ptr();
241            self::multiversion::encode::auto(src, len, dst, self.config);
242
243            slice_mut(dst, m)
244        }
245    }
246
247    /// Encodes bytes to a base64 string and returns [`&mut str`](str).
248    ///
249    /// # Panics
250    /// This function will panic if the length of `dst` is not enough.
251    #[inline]
252    #[must_use]
253    pub fn encode_as_str<'d>(&self, src: &[u8], dst: Out<'d, [u8]>) -> &'d mut str {
254        let ans = self.encode(src, dst);
255        unsafe { core::str::from_utf8_unchecked_mut(ans) }
256    }
257
258    /// Decodes a base64 string to bytes.
259    ///
260    /// # Errors
261    /// This function returns `Err` if the content of `src` is invalid.
262    ///
263    /// # Panics
264    /// This function will panic if the length of `dst` is not enough.
265    #[inline]
266    pub fn decode<'d>(&self, src: &[u8], mut dst: Out<'d, [u8]>) -> Result<&'d mut [u8], Error> {
267        unsafe {
268            let (n, m) = decoded_length(src, self.config)?;
269            assert!(dst.len() >= m);
270
271            let src = src.as_ptr();
272            let dst = dst.as_mut_ptr();
273            self::multiversion::decode::auto(src, dst, n, self.config)?;
274
275            Ok(slice_mut(dst, m))
276        }
277    }
278
279    /// Decodes a base64 string to bytes and writes inplace.
280    ///
281    /// # Errors
282    /// This function returns `Err` if the content of `data` is invalid.
283    #[inline]
284    pub fn decode_inplace<'d>(&self, data: &'d mut [u8]) -> Result<&'d mut [u8], Error> {
285        unsafe {
286            let (n, m) = decoded_length(data, self.config)?;
287
288            let dst: *mut u8 = data.as_mut_ptr();
289            let src: *const u8 = dst;
290            self::multiversion::decode::auto(src, dst, n, self.config)?;
291
292            Ok(slice_mut(dst, m))
293        }
294    }
295
296    /// Encodes bytes to a base64 string and returns a specified type.
297    #[inline]
298    #[must_use]
299    pub fn encode_type<T: FromBase64Encode>(&self, data: impl AsRef<[u8]>) -> T {
300        T::from_base64_encode(self, data.as_ref())
301    }
302
303    /// Decodes a base64 string to bytes and returns a specified type.
304    ///
305    /// # Errors
306    /// This function returns `Err` if the content of `data` is invalid.
307    #[inline]
308    pub fn decode_type<T: FromBase64Decode>(&self, data: impl AsRef<[u8]>) -> Result<T, Error> {
309        T::from_base64_decode(self, data.as_ref())
310    }
311
312    /// Encodes bytes to a base64 string and appends to a specified type.
313    #[inline]
314    pub fn encode_append<T: AppendBase64Encode>(&self, src: impl AsRef<[u8]>, dst: &mut T) {
315        T::append_base64_encode(self, src.as_ref(), dst);
316    }
317
318    /// Decodes a base64 string to bytes and appends to a specified type.
319    ///
320    /// # Errors
321    /// This function returns `Err` if the content of `src` is invalid.
322    #[inline]
323    pub fn decode_append<T: AppendBase64Decode>(&self, src: impl AsRef<[u8]>, dst: &mut T) -> Result<(), Error> {
324        T::append_base64_decode(self, src.as_ref(), dst)
325    }
326
327    /// Encodes bytes to a base64 string.
328    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
329    #[cfg(feature = "alloc")]
330    #[inline]
331    #[must_use]
332    pub fn encode_to_string(&self, data: impl AsRef<[u8]>) -> String {
333        self.encode_type(data)
334    }
335
336    /// Decodes a base64 string to bytes.
337    ///
338    /// # Errors
339    /// This function returns `Err` if the content of `data` is invalid.
340    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
341    #[cfg(feature = "alloc")]
342    #[inline]
343    pub fn decode_to_vec(&self, data: impl AsRef<[u8]>) -> Result<Vec<u8>, Error> {
344        self.decode_type(data)
345    }
346}
347
348/// Types that can represent a base64 string.
349pub trait FromBase64Encode: Sized {
350    /// Encodes bytes to a base64 string and returns the self type.
351    fn from_base64_encode(base64: &Base64, data: &[u8]) -> Self;
352}
353
354/// Types that can be decoded from a base64 string.
355pub trait FromBase64Decode: Sized {
356    /// Decodes a base64 string to bytes and returns the self type.
357    ///
358    /// # Errors
359    /// This function returns `Err` if the content of `data` is invalid.
360    fn from_base64_decode(base64: &Base64, data: &[u8]) -> Result<Self, Error>;
361}
362
363/// Types that can append a base64 string.
364pub trait AppendBase64Encode: FromBase64Encode {
365    /// Encodes bytes to a base64 string and appends into the self type.
366    fn append_base64_encode(base64: &Base64, src: &[u8], dst: &mut Self);
367}
368
369/// Types that can append bytes decoded from from a base64 string.
370pub trait AppendBase64Decode: FromBase64Decode {
371    /// Decodes a base64 string to bytes and appends to the self type.
372    ///
373    /// # Errors
374    /// This function returns `Err` if the content of `src` is invalid.
375    fn append_base64_decode(base64: &Base64, src: &[u8], dst: &mut Self) -> Result<(), Error>;
376}