Skip to main content

tape_base64/
lib.rs

1//! Base64 encoding and decoding
2//!
3//! One kernel per architecture. Every conversion writes into a caller's
4//! buffer, nothing allocates, and the crate is `no_std`.
5//!
6//! ```
7//! let mut encoded = [0u8; 8];
8//! let written = tape_base64::encode(b"hello", &mut encoded).unwrap();
9//! assert_eq!(&encoded[..written], b"aGVsbG8=");
10//!
11//! let mut decoded = [0u8; 5];
12//! let written = tape_base64::decode(b"aGVsbG8=", &mut decoded).unwrap();
13//! assert_eq!(&decoded[..written], b"hello");
14//! ```
15
16#![no_std]
17#![forbid(unsafe_op_in_unsafe_fn)]
18#![deny(missing_docs)]
19
20#[cfg(feature = "std")]
21extern crate std;
22
23mod alphabet;
24mod backend;
25mod codec;
26mod error;
27mod scalar;
28mod stream;
29
30#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
31mod neon;
32
33#[cfg(target_arch = "x86_64")]
34mod x86;
35
36#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
37mod wasm;
38
39#[cfg(any(target_arch = "x86_64", test))]
40mod wide;
41
42pub use error::{DecodeError, EncodeError};
43pub use stream::{Decoder, Encoder};
44
45use alphabet::{Standard, UrlSafe};
46
47/// Which 64 characters a codec spells values with
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49enum Kind {
50    Standard,
51    UrlSafe,
52}
53
54/// An alphabet and a padding rule
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub struct Codec {
57    kind: Kind,
58    is_padded: bool,
59}
60
61/// RFC 4648 section 4: the alphabet ending `+/`, padded with `=`
62pub const STANDARD: Codec = Codec {
63    kind: Kind::Standard,
64    is_padded: true,
65};
66
67/// RFC 4648 section 4 without padding, where `=` is refused like any byte
68pub const STANDARD_NO_PAD: Codec = Codec {
69    kind: Kind::Standard,
70    is_padded: false,
71};
72
73/// RFC 4648 section 5: the alphabet ending `-_`, with padding
74pub const URL_SAFE: Codec = Codec {
75    kind: Kind::UrlSafe,
76    is_padded: true,
77};
78
79/// RFC 4648 section 5 without padding
80pub const URL_SAFE_NO_PAD: Codec = Codec {
81    kind: Kind::UrlSafe,
82    is_padded: false,
83};
84
85impl Codec {
86    /// Characters the encoding of `len` bytes takes, `None` if it overflows
87    #[inline]
88    pub const fn encoded_len(&self, len: usize) -> Option<usize> {
89        let groups = len / 3;
90        self.length(groups, len % 3)
91    }
92
93    /// Characters this many whole groups and this many bytes over take
94    #[inline]
95    const fn length(&self, groups: usize, rest: usize) -> Option<usize> {
96        if groups > MAX_GROUPS {
97            return None;
98        }
99        let tail = if rest == 0 {
100            0
101        } else if self.is_padded {
102            4
103        } else {
104            rest + 1
105        };
106        Some(groups * 4 + tail)
107    }
108
109    /// The most bytes the decoding of `len` characters can take
110    #[inline]
111    pub const fn decoded_len_max(&self, len: usize) -> usize {
112        decoded_len(len)
113    }
114
115    /// Bytes `input` decodes to, from its length and padding without reading it
116    #[inline]
117    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
118        Ok(decoded_len(self.payload(input)?.len()))
119    }
120
121    /// Encode `input` into `out`, returning how many characters were written
122    #[inline]
123    pub fn encode(&self, input: &[u8], out: &mut [u8]) -> Result<usize, EncodeError> {
124        let groups = input.len() / 3;
125        let needed = match self.length(groups, input.len() - groups * 3) {
126            Some(needed) => needed,
127            None => return Err(EncodeError::TooLong),
128        };
129        if out.len() < needed {
130            return Err(EncodeError::BufferTooSmall { needed });
131        }
132        let out = &mut out[..needed];
133        match self.kind {
134            Kind::Standard => codec::encode::<Standard>(input, out, groups, self.is_padded),
135            Kind::UrlSafe => codec::encode::<UrlSafe>(input, out, groups, self.is_padded),
136        }
137        Ok(needed)
138    }
139
140    /// Decode `input` into `out`, returning how many bytes were written
141    #[inline]
142    pub fn decode(&self, input: &[u8], out: &mut [u8]) -> Result<usize, DecodeError> {
143        let payload = self.payload(input)?;
144        let quads = payload.len() / 4;
145        let needed = decoded_len(payload.len());
146        if out.len() < needed {
147            return Err(DecodeError::BufferTooSmall { needed });
148        }
149        let out = &mut out[..needed];
150        match self.kind {
151            Kind::Standard => codec::decode::<Standard>(payload, out, quads),
152            Kind::UrlSafe => codec::decode::<UrlSafe>(payload, out, quads),
153        }
154    }
155
156    /// The characters of `input` that hold bits, padding checked and removed
157    #[inline]
158    fn payload<'input>(&self, input: &'input [u8]) -> Result<&'input [u8], DecodeError> {
159        let len = input.len();
160        if !length_ok(len, self.is_padded) {
161            return Err(DecodeError::InvalidLength { length: len });
162        }
163        // Past this check the last group of a padded input is there to read.
164        if !self.is_padded || len < 4 {
165            return Ok(input);
166        }
167
168        // `&` not `&&`, so this is arithmetic rather than three dependent branches.
169        let one = usize::from(input[len - 1] == b'=');
170        let two = one & usize::from(input[len - 2] == b'=');
171        let three = two & usize::from(input[len - 3] == b'=');
172        // Two is the most padding an encoding has.
173        if three != 0 {
174            return Err(DecodeError::InvalidPadding { position: len - 3 });
175        }
176        Ok(&input[..len - one - two])
177    }
178}
179
180/// Bytes that a payload of `len` characters, padding already off, decodes to
181pub(crate) const fn decoded_len(len: usize) -> usize {
182    len / 4 * 3 + (len % 4).saturating_sub(1)
183}
184
185/// Whether `len` characters are a count some encoding produces
186pub(crate) const fn length_ok(len: usize, is_padded: bool) -> bool {
187    if is_padded {
188        len.is_multiple_of(4)
189    } else {
190        len % 4 != 1
191    }
192}
193
194/// The most whole groups whose encoding still has a length
195const MAX_GROUPS: usize = (usize::MAX - 4) / 4;
196
197/// Encode with `STANDARD`, returning how many characters were written
198#[inline]
199pub fn encode(input: &[u8], out: &mut [u8]) -> Result<usize, EncodeError> {
200    STANDARD.encode(input, out)
201}
202
203/// Decode with `STANDARD`, returning how many bytes were written
204#[inline]
205pub fn decode(input: &[u8], out: &mut [u8]) -> Result<usize, DecodeError> {
206    STANDARD.decode(input, out)
207}
208
209/// Characters the `STANDARD` encoding of `len` bytes takes
210#[inline]
211pub const fn encoded_len(len: usize) -> Option<usize> {
212    STANDARD.encoded_len(len)
213}
214
215/// Internals the test suite checks and the benchmarks time, not a public API
216#[cfg(feature = "internals")]
217#[doc(hidden)]
218pub mod testing {
219    use crate::alphabet::{Standard, UrlSafe};
220    use crate::backend;
221    use crate::error::DecodeError;
222    use crate::scalar;
223
224    #[cfg(target_arch = "x86_64")]
225    use crate::x86;
226
227    pub fn portable_encode(input: &[u8], out: &mut [u8], is_padded: bool) {
228        let codec = if is_padded {
229            crate::STANDARD
230        } else {
231            crate::STANDARD_NO_PAD
232        };
233        assert!(
234            out.len() >= codec.encoded_len(input.len()).expect("a length this small"),
235            "the walk wants room for the whole encoding"
236        );
237        let groups = scalar::encode_bulk::<Standard>(input, out, input.len() / 3);
238        scalar::encode_rest::<Standard>(&input[groups * 3..], &mut out[groups * 4..], is_padded);
239    }
240
241    pub fn portable_decode(input: &[u8], out: &mut [u8]) -> Result<usize, DecodeError> {
242        assert!(
243            out.len() >= crate::decoded_len(input.len()),
244            "the walk wants room for the whole decoding"
245        );
246        let quads = scalar::decode_bulk::<Standard>(input, out, input.len() / 4);
247        let (read, written) = (quads * 4, quads * 3);
248        if input.len() - read >= 4 {
249            return Err(scalar::find_invalid::<Standard>(&input[read..], read));
250        }
251        Ok(written + scalar::decode_rest::<Standard>(&input[read..], &mut out[written..], read)?)
252    }
253
254    pub fn vector_encode_bulk(input: &[u8], out: &mut [u8], is_url_safe: bool) -> (usize, usize) {
255        let groups = core::cmp::min(input.len() / 3, out.len() / 4);
256        let done = if is_url_safe {
257            backend::encode_bulk::<UrlSafe>(input, out, groups)
258        } else {
259            backend::encode_bulk::<Standard>(input, out, groups)
260        };
261        (done * 3, done * 4)
262    }
263
264    pub fn vector_decode_bulk(input: &[u8], out: &mut [u8], is_url_safe: bool) -> (usize, usize) {
265        let quads = core::cmp::min(input.len() / 4, out.len() / 3);
266        let done = if is_url_safe {
267            backend::decode_bulk::<UrlSafe>(input, out, quads)
268        } else {
269            backend::decode_bulk::<Standard>(input, out, quads)
270        };
271        (done * 4, done * 3)
272    }
273
274    pub fn has_vector_path() -> bool {
275        #[cfg(target_arch = "x86_64")]
276        {
277            x86::available() != x86::PORTABLE
278        }
279        #[cfg(not(target_arch = "x86_64"))]
280        {
281            cfg!(any(
282                all(target_arch = "aarch64", target_feature = "neon"),
283                all(target_arch = "wasm32", target_feature = "simd128")
284            ))
285        }
286    }
287
288    /// Pin the codec to one path, for a test
289    #[cfg(target_arch = "x86_64")]
290    pub use crate::x86::{available, force, force_stream_floor, AVX2, PORTABLE, UNKNOWN, WIDE};
291}