Skip to main content

base64/engine/general_purpose/
mod.rs

1//! Provides the [`GeneralPurpose`] engine and associated config types.
2//!
3//! See preconfigured engines like [`STANDARD_NO_PAD`] or [`STANDARD_NO_PAD_INDIFFERENT`].
4use crate::alphabet::Symbol;
5use crate::{
6    alphabet,
7    alphabet::Alphabet,
8    engine::{Config, DecodeMetadata, DecodePaddingMode},
9    DecodeSliceError,
10};
11use core::convert::TryInto;
12
13pub(crate) mod decode;
14pub(crate) mod decode_suffix;
15
16pub use decode::GeneralPurposeEstimate;
17
18pub(crate) const INVALID_VALUE: u8 = 255;
19
20/// A general-purpose base64 engine.
21///
22/// - It uses no vector CPU instructions, so it will work on any system. For a version that uses
23///   SIMD where available, see the SIMD engines behind the `simd-unsafe` feature.
24/// - It is reasonably fast (~2-3GiB/s).
25/// - It is not constant-time, though, so it is vulnerable to timing side-channel attacks. For loading cryptographic keys, etc, it is suggested to use the forthcoming constant-time implementation.
26
27#[derive(Debug, Clone)]
28pub struct GeneralPurpose {
29    encode_table: [u8; 64],
30    decode_table: [u8; 256],
31    pub(crate) padding: Symbol,
32    config: GeneralPurposeConfig,
33}
34
35/// A purely scalar base64 engine that never uses hardware-specific vector instructions.
36///
37/// This is an alias for [`GeneralPurpose`], giving an explicit name for callers who want to
38/// guarantee a scalar-only implementation.
39pub type Scalar = GeneralPurpose;
40
41impl GeneralPurpose {
42    /// Create a `GeneralPurpose` engine from an [Alphabet].
43    ///
44    /// While not very expensive to initialize, ideally these should be cached
45    /// if the engine will be used repeatedly.
46    #[must_use]
47    pub const fn new(alphabet: &Alphabet, config: GeneralPurposeConfig) -> Self {
48        Self {
49            encode_table: encode_table(alphabet),
50            decode_table: decode_table(alphabet),
51            padding: alphabet.padding,
52            config,
53        }
54    }
55
56    /// The 6-bit-index-to-ASCII encode table.
57    #[cfg(all(
58        feature = "simd-unsafe",
59        any(
60            target_arch = "x86_64",
61            all(target_arch = "aarch64", target_feature = "neon")
62        )
63    ))]
64    pub(crate) fn encode_table(&self) -> &[u8; 64] {
65        &self.encode_table
66    }
67
68    /// The ASCII-to-6-bit-value decode table.
69    #[cfg(all(
70        feature = "simd-unsafe",
71        any(
72            target_arch = "x86_64",
73            all(target_arch = "aarch64", target_feature = "neon")
74        )
75    ))]
76    pub(crate) fn decode_table(&self) -> &[u8; 256] {
77        &self.decode_table
78    }
79}
80
81impl super::Engine for GeneralPurpose {
82    type Config = GeneralPurposeConfig;
83    type DecodeEstimate = GeneralPurposeEstimate;
84
85    fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
86        encode_helper(&self.encode_table, input, output, |_, _| (0, 0))
87    }
88
89    fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
90        GeneralPurposeEstimate::new(input_len)
91    }
92
93    fn internal_decode(
94        &self,
95        input: &[u8],
96        output: &mut [u8],
97        estimate: Self::DecodeEstimate,
98    ) -> Result<DecodeMetadata, DecodeSliceError> {
99        decode::decode_helper(
100            input,
101            &estimate,
102            output,
103            &self.decode_table,
104            self.config.decode_allow_trailing_bits,
105            self.padding,
106            self.config.decode_padding_mode,
107            |_, _, _| (0, 0),
108        )
109    }
110
111    fn config(&self) -> &Self::Config {
112        &self.config
113    }
114
115    fn padding(&self) -> Symbol {
116        self.padding
117    }
118}
119
120/// Scalar base64 encode of `input` into `output`, returning the number of bytes written.
121///
122/// `simd_prefix` gets first crack at the input, returning `(input_consumed, output_written)`. It
123/// must consume whole 3-byte groups: `input_consumed % 3 == 0`, `output_written == input_consumed /
124/// 3 * 4`, both in bounds, and only those `output_written` bytes are written. `(0, 0)` (pure scalar)
125/// is always valid.
126#[inline]
127pub(crate) fn encode_helper(
128    encode_table: &[u8; 64],
129    input: &[u8],
130    output: &mut [u8],
131    simd_prefix: impl FnOnce(&[u8], &mut [u8]) -> (usize, usize),
132) -> usize {
133    let (input_index, output_index) = simd_prefix(input, output);
134
135    debug_assert!(
136        input_index % 3 == 0,
137        "prefix must consume whole 3-byte groups"
138    );
139    debug_assert!(
140        output_index == input_index / 3 * 4,
141        "prefix output must match consumed input"
142    );
143    debug_assert!(input_index <= input.len());
144    debug_assert!(output_index <= output.len());
145
146    encode_scalar_tail(encode_table, input, output, input_index, output_index)
147}
148
149/// Scalar encode of `input[input_index..]` into `output[output_index..]`, resuming from a 3-byte
150/// group boundary. Returns the total number of output bytes written.
151fn encode_scalar_tail(
152    encode_table: &[u8; 64],
153    input: &[u8],
154    output: &mut [u8],
155    mut input_index: usize,
156    mut output_index: usize,
157) -> usize {
158    const BLOCKS_PER_FAST_LOOP: usize = 4;
159    const LOW_SIX_BITS: u64 = 0x3F;
160
161    // we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need
162    // 2 trailing bytes to be available to read..
163    let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2);
164
165    if last_fast_index > 0 {
166        while input_index <= last_fast_index {
167            // Major performance wins from letting the optimizer do the bounds check once, mostly
168            // on the output side
169            let input_chunk = &input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))];
170            let output_chunk = &mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)];
171
172            // Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent
173            // to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for
174            // large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect
175            // that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte
176            // SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once.
177            // Plus, single-digit percentage performance differences might well be quite different
178            // on different hardware.
179
180            let input_u64 = read_u64(&input_chunk[0..]);
181
182            output_chunk[0] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
183            output_chunk[1] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
184            output_chunk[2] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
185            output_chunk[3] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
186            output_chunk[4] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
187            output_chunk[5] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
188            output_chunk[6] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
189            output_chunk[7] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
190
191            let input_u64 = read_u64(&input_chunk[6..]);
192
193            output_chunk[8] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
194            output_chunk[9] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
195            output_chunk[10] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
196            output_chunk[11] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
197            output_chunk[12] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
198            output_chunk[13] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
199            output_chunk[14] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
200            output_chunk[15] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
201
202            let input_u64 = read_u64(&input_chunk[12..]);
203
204            output_chunk[16] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
205            output_chunk[17] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
206            output_chunk[18] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
207            output_chunk[19] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
208            output_chunk[20] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
209            output_chunk[21] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
210            output_chunk[22] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
211            output_chunk[23] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
212
213            let input_u64 = read_u64(&input_chunk[18..]);
214
215            output_chunk[24] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
216            output_chunk[25] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
217            output_chunk[26] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
218            output_chunk[27] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
219            output_chunk[28] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
220            output_chunk[29] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
221            output_chunk[30] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
222            output_chunk[31] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
223
224            output_index += BLOCKS_PER_FAST_LOOP * 8;
225            input_index += BLOCKS_PER_FAST_LOOP * 6;
226        }
227    }
228
229    // Encode what's left after the fast loop.
230
231    const LOW_SIX_BITS_U8: u8 = 0x3F;
232
233    let rem = input.len() % 3;
234    let start_of_rem = input.len() - rem;
235
236    // start at the first index not handled by fast loop, which may be 0.
237
238    while input_index < start_of_rem {
239        let input_chunk = &input[input_index..(input_index + 3)];
240        let output_chunk = &mut output[output_index..(output_index + 4)];
241
242        output_chunk[0] = encode_table[(input_chunk[0] >> 2) as usize];
243        output_chunk[1] =
244            encode_table[((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize];
245        output_chunk[2] =
246            encode_table[((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize];
247        output_chunk[3] = encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize];
248
249        input_index += 3;
250        output_index += 4;
251    }
252
253    if rem == 2 {
254        output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
255        output[output_index + 1] = encode_table[((input[start_of_rem] << 4
256            | input[start_of_rem + 1] >> 4)
257            & LOW_SIX_BITS_U8) as usize];
258        output[output_index + 2] =
259            encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize];
260        output_index += 3;
261    } else if rem == 1 {
262        output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
263        output[output_index + 1] =
264            encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize];
265        output_index += 2;
266    }
267
268    output_index
269}
270
271/// Returns a table mapping a 6-bit index to the ASCII byte encoding of the index
272pub(crate) const fn encode_table(alphabet: &Alphabet) -> [u8; 64] {
273    // the encode table is just the alphabet:
274    // 6-bit index lookup -> printable byte
275    let mut encode_table = [0_u8; 64];
276    {
277        let mut index = 0;
278        while index < 64 {
279            encode_table[index] = alphabet.symbols[index];
280            index += 1;
281        }
282    }
283
284    encode_table
285}
286
287/// Returns a table mapping base64 bytes as the lookup index to either:
288/// - [`INVALID_VALUE`] for bytes that aren't members of the alphabet
289/// - a byte whose lower 6 bits are the value that was encoded into the index byte
290pub(crate) const fn decode_table(alphabet: &Alphabet) -> [u8; 256] {
291    let mut decode_table = [INVALID_VALUE; 256];
292
293    // Since the table is full of `INVALID_VALUE` already, we only need to overwrite
294    // the parts that are valid.
295    let mut index = 0_usize;
296    while index < 64 {
297        // The index in the alphabet is the 6-bit value we care about.
298        // Since the index is in 0-63, it is safe to cast to u8.
299        decode_table[alphabet.symbols[index] as usize] = index as u8;
300        index += 1;
301    }
302
303    decode_table
304}
305
306#[inline]
307fn read_u64(s: &[u8]) -> u64 {
308    u64::from_be_bytes(s[..8].try_into().unwrap())
309}
310
311/// Contains configuration parameters for base64 encoding and decoding.
312///
313/// ```
314/// # use base64::engine::GeneralPurposeConfig;
315/// let config = GeneralPurposeConfig::new()
316///     .with_encode_padding(false);
317///     // further customize using `.with_*` methods as needed
318/// ```
319///
320/// The constants [PAD] and [`NO_PAD`] cover most use cases.
321///
322/// To specify the characters used, see [Alphabet].
323#[derive(Clone, Copy, Debug)]
324pub struct GeneralPurposeConfig {
325    encode_padding: bool,
326    decode_allow_trailing_bits: bool,
327    decode_padding_mode: DecodePaddingMode,
328}
329
330impl GeneralPurposeConfig {
331    /// Create a new config with `padding` = `true`, `decode_allow_trailing_bits` = `false`, and
332    /// `decode_padding_mode = DecodePaddingMode::RequireCanonicalPadding`.
333    ///
334    /// This probably matches most people's expectations, but consider disabling padding to save
335    /// a few bytes unless you specifically need it for compatibility with some legacy system.
336    #[must_use]
337    pub const fn new() -> Self {
338        Self {
339            // RFC states that padding must be applied by default
340            encode_padding: true,
341            decode_allow_trailing_bits: false,
342            decode_padding_mode: DecodePaddingMode::RequireCanonical,
343        }
344    }
345
346    /// Create a new config based on `self` with an updated `padding` setting.
347    ///
348    /// If `padding` is `true`, encoding will append either 1 or 2 `=` padding characters as needed
349    /// to produce an output whose length is a multiple of 4.
350    ///
351    /// Padding is not needed for correct decoding and only serves to waste bytes, but it's in the
352    /// [spec](https://datatracker.ietf.org/doc/html/rfc4648#section-3.2).
353    ///
354    /// For new applications, consider not using padding if the decoders you're using don't require
355    /// padding to be present.
356    #[must_use]
357    pub const fn with_encode_padding(self, padding: bool) -> Self {
358        Self {
359            encode_padding: padding,
360            ..self
361        }
362    }
363
364    /// Create a new config based on `self` with an updated `decode_allow_trailing_bits` setting.
365    ///
366    /// Most users will not need to configure this. It's useful if you need to decode base64
367    /// produced by a buggy encoder that has bits set in the unused space on the last base64
368    /// character as per [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode).
369    /// If invalid trailing bits are present and this is `true`, those bits will
370    /// be silently ignored, else `DecodeError::InvalidLastSymbol` will be emitted.
371    #[must_use]
372    pub const fn with_decode_allow_trailing_bits(self, allow: bool) -> Self {
373        Self {
374            decode_allow_trailing_bits: allow,
375            ..self
376        }
377    }
378
379    /// Create a new config based on `self` with an updated `decode_padding_mode` setting.
380    ///
381    /// Padding is not useful in terms of representing encoded data -- it makes no difference to
382    /// the decoder if padding is present or not, so if you have some un-padded input to decode, it
383    /// is perfectly fine to use `DecodePaddingMode::Indifferent` to prevent errors from being
384    /// emitted.
385    ///
386    /// However, since in practice
387    /// [people who learned nothing from BER vs DER seem to expect base64 to have one canonical encoding](https://eprint.iacr.org/2022/361),
388    /// the default setting is the stricter `DecodePaddingMode::RequireCanonicalPadding`.
389    ///
390    /// Or, if "canonical" in your circumstance means _no_ padding rather than padding to the
391    /// next multiple of four, there's `DecodePaddingMode::RequireNoPadding`.
392    #[must_use]
393    pub const fn with_decode_padding_mode(self, mode: DecodePaddingMode) -> Self {
394        Self {
395            decode_padding_mode: mode,
396            ..self
397        }
398    }
399}
400
401impl Default for GeneralPurposeConfig {
402    /// Delegates to [`GeneralPurposeConfig::new`].
403    fn default() -> Self {
404        Self::new()
405    }
406}
407
408impl Config for GeneralPurposeConfig {
409    fn encode_padding(&self) -> bool {
410        self.encode_padding
411    }
412}
413
414#[cfg(all(
415    feature = "simd-unsafe",
416    any(
417        target_arch = "x86_64",
418        all(target_arch = "aarch64", target_feature = "neon")
419    )
420))]
421impl GeneralPurposeConfig {
422    /// Whether trailing bits are allowed when decoding.
423    pub(crate) fn decode_allow_trailing_bits(&self) -> bool {
424        self.decode_allow_trailing_bits
425    }
426
427    /// The decode padding mode.
428    pub(crate) fn decode_padding_mode(&self) -> DecodePaddingMode {
429        self.decode_padding_mode
430    }
431}
432
433/// A [`GeneralPurpose`] engine using the [`alphabet::STANDARD`] base64 alphabet and [`PAD`] config.
434///
435/// Does not allow trailing bits when decoding.
436pub const STANDARD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD);
437
438/// A [`GeneralPurpose`] engine using the [`alphabet::STANDARD`] base64 alphabet and
439/// [`PAD_INDIFFERENT`] config.
440///
441/// Does not allow trailing bits when decoding.
442pub const STANDARD_PAD_INDIFFERENT: GeneralPurpose =
443    GeneralPurpose::new(&alphabet::STANDARD, PAD_INDIFFERENT);
444
445/// A [`GeneralPurpose`] engine using the [`alphabet::STANDARD`] base64 alphabet and [`NO_PAD`] config.
446///
447/// Does not allow trailing bits when decoding.
448pub const STANDARD_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, NO_PAD);
449
450/// A [`GeneralPurpose`] engine using the [`alphabet::STANDARD`] base64 alphabet and
451/// [`NO_PAD_INDIFFERENT`] config.
452///
453/// Does not allow trailing bits when decoding.
454pub const STANDARD_NO_PAD_INDIFFERENT: GeneralPurpose =
455    GeneralPurpose::new(&alphabet::STANDARD, NO_PAD_INDIFFERENT);
456
457/// A [`GeneralPurpose`] engine using the [`alphabet::URL_SAFE`] base64 alphabet and [`PAD`] config.
458///
459/// Does not allow trailing bits when decoding.
460pub const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, PAD);
461
462/// A [`GeneralPurpose`] engine using the [`alphabet::URL_SAFE`] base64 alphabet and
463/// [`PAD_INDIFFERENT`] config.
464///
465/// Does not allow trailing bits when decoding.
466pub const URL_SAFE_PAD_INDIFFERENT: GeneralPurpose =
467    GeneralPurpose::new(&alphabet::URL_SAFE, PAD_INDIFFERENT);
468
469/// A [`GeneralPurpose`] engine using the [`alphabet::URL_SAFE`] base64 alphabet and [`NO_PAD`] config.
470///
471/// Does not allow trailing bits when decoding.
472pub const URL_SAFE_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD);
473
474/// A [`GeneralPurpose`] engine using the [`alphabet::URL_SAFE`] base64 alphabet and
475/// [`NO_PAD_INDIFFERENT`] config.
476///
477/// Does not allow trailing bits when decoding.
478pub const URL_SAFE_NO_PAD_INDIFFERENT: GeneralPurpose =
479    GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD_INDIFFERENT);
480
481/// Include padding bytes when encoding, and require that they be present when decoding.
482///
483/// Does not allow trailing bits when decoding.
484///
485/// This is the standard per the base64 RFC, but consider using [`NO_PAD`] or [`NO_PAD_INDIFFERENT`]
486/// instead as padding serves little purpose in practice.
487pub const PAD: GeneralPurposeConfig = GeneralPurposeConfig::new();
488
489/// Include padding bytes when encoding, but allow input with or without padding when decoding.
490///
491/// Does not allow trailing bits when decoding.
492pub const PAD_INDIFFERENT: GeneralPurposeConfig = GeneralPurposeConfig::new()
493    .with_encode_padding(true)
494    .with_decode_padding_mode(DecodePaddingMode::Indifferent);
495
496/// Don't add padding when encoding, and require that there is no padding when decoding.
497///
498/// Does not allow trailing bits when decoding.
499pub const NO_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new()
500    .with_encode_padding(false)
501    .with_decode_padding_mode(DecodePaddingMode::RequireNone);
502
503/// Don't add padding when encoding, and allow input with or without padding when decoding.
504///
505/// Does not allow trailing bits when decoding.
506pub const NO_PAD_INDIFFERENT: GeneralPurposeConfig = GeneralPurposeConfig::new()
507    .with_encode_padding(false)
508    .with_decode_padding_mode(DecodePaddingMode::Indifferent);