Skip to main content

base64/
base64.rs

1#![cfg_attr(not(test), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! Fast base64 encoding and decoding with SIMD acceleration, constant-time
5//! operations, and `const fn` support.
6//!
7//! Four alphabet variants are available via [`Alphabet`]:
8//!
9//! | Variant             | Characters       | Padding |
10//! |---------------------|------------------|---------|
11//! | `Standard`          | `A-Za-z0-9+/`    | `=`     |
12//! | `StandardNoPadding` | `A-Za-z0-9+/`    | none    |
13//! | `Url`               | `A-Za-z0-9-_`    | `=`     |
14//! | `UrlNoPadding`      | `A-Za-z0-9-_`    | none    |
15//!
16//! # Feature flags
17//!
18//! | Flag    | Description                                             |
19//! |---------|---------------------------------------------------------|
20//! | `alloc` | `String`/`Vec`-returning convenience APIs (enabled by default) |
21//! | `serde` | Serde [`serialize`](crate::serde::serialize)/[`deserialize`](crate::serde::deserialize) helpers  |
22//!
23//! # Performance
24//!
25//! The [`encode_into`] and [`decode_into`] functions
26//! automatically dispatch to SIMD-accelerated paths (AVX2 on x86/x86_64,
27//! NEON on aarch64). When a constant-time guarantee is required, use
28//! [`encode_into_constant_time`] or [`decode_into_constant_time`].
29//!
30//! # `const fn` support
31//!
32//! [`encode_array`] and [`decode_array`] are `const fn`, enabling base64
33//! encoding and decoding at compile time.
34//!
35//! # Examples
36//!
37//! ```rust
38//! use base64::{Alphabet, encode, decode};
39//!
40//! let encoded = encode(b"hello world", Alphabet::Standard);
41//! assert_eq!(encoded, "aGVsbG8gd29ybGQ=");
42//!
43//! let decoded = decode(b"aGVsbG8gd29ybGQ=", Alphabet::Standard).unwrap();
44//! assert_eq!(decoded, b"hello world");
45//!
46//! let url = encode(b"hello world", Alphabet::Url);
47//! assert_eq!(url, "aGVsbG8gd29ybGQ=");
48//! ```
49
50#[cfg(any(feature = "alloc", test))]
51extern crate alloc;
52
53#[cfg(all(feature = "serde", any(feature = "alloc", test)))]
54mod serde;
55// Re-export the `#[serde(with = "base64")]` helpers so they are reachable from outside the
56// crate (and therefore not dead code).
57#[cfg(all(feature = "serde", any(feature = "alloc", test)))]
58pub use self::serde::{deserialize, serialize};
59
60// SIMD kernels are only reachable through the compile-time dispatch in `encode_into` /
61// `decode_into` below, so they are compiled only when the matching target feature is
62// enabled. This crate is `no_std`, so runtime CPU detection is not possible.
63#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
64mod base64_neon;
65
66#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
67mod base64_avx2;
68
69#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
70mod base64_wasm_simd128;
71
72const PAD: u8 = b'=';
73
74/// The base64 alphabet used for encoding and decoding.
75///
76/// | Variant             | Characters       | Padding |
77/// |---------------------|------------------|---------|
78/// | `Standard`          | `A-Za-z0-9+/`    | `=`     |
79/// | `StandardNoPadding` | `A-Za-z0-9+/`    | none    |
80/// | `Url`               | `A-Za-z0-9-_`    | `=`     |
81/// | `UrlNoPadding`      | `A-Za-z0-9-_`    | none    |
82///
83/// # Example
84///
85/// ```rust
86/// use base64::Alphabet;
87///
88/// let encoded = base64::encode(b"hello", Alphabet::Url);
89/// assert_eq!(encoded, "aGVsbG8=");
90/// ```
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum Alphabet {
93    Standard,
94    StandardNoPadding,
95    Url,
96    UrlNoPadding,
97}
98
99impl Alphabet {
100    #[inline]
101    const fn is_padded(&self) -> bool {
102        matches!(self, Alphabet::Standard | Alphabet::Url)
103    }
104}
105
106/// Errors that can occur during base64 encoding.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum EncodeError {
109    /// The output buffer length does not match the expected encoded length.
110    InvalidOutputLength,
111    /// The encoded output length overflows `usize`.
112    OutputOverflow,
113}
114
115/// Errors that can occur during base64 decoding.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum DecodeError {
118    /// The input contains a character that is not valid for the chosen
119    /// [`Alphabet`].
120    InvalidInput,
121    /// The input length is not valid for base64 decoding.
122    InvalidInputLength,
123    /// The input has invalid padding (e.g. missing `=` when expected,
124    /// unexpected `=`, or wrong number of padding characters).
125    InvalidPadding,
126    /// The output length is not valid.
127    InvalidOutputLength,
128}
129
130impl core::fmt::Display for EncodeError {
131    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
132        match self {
133            Self::InvalidOutputLength => f.write_str("output buffer size must be exactly equal to decoded_len(input)"),
134            Self::OutputOverflow => f.write_str("output length overflows usize::MAX"),
135        }
136    }
137}
138
139impl core::fmt::Display for DecodeError {
140    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141        match self {
142            Self::InvalidInput => f.write_str("invalid base64 character"),
143            Self::InvalidInputLength => f.write_str("invalid base64 length"),
144            Self::InvalidOutputLength => f.write_str("output length is not valid"),
145            Self::InvalidPadding => f.write_str("invalid base64 padding"),
146        }
147    }
148}
149
150impl core::error::Error for EncodeError {}
151
152impl core::error::Error for DecodeError {}
153
154////////////////////////////////////////////////////////////////////////////////////////////////////
155/// Encode
156////////////////////////////////////////////////////////////////////////////////////////////////////
157
158/// Returns the size in bytes of the input data after base64 encoding.
159///
160/// Returns `None` if the output size overflows `usize`.
161///
162/// # Example
163///
164/// ```rust
165/// assert_eq!(base64::encoded_length(3, true), Some(4));
166/// assert_eq!(base64::encoded_length(1, false), Some(2));
167/// assert_eq!(base64::encoded_length(usize::MAX, true), None);
168/// ```
169pub const fn encoded_length(data_length: usize, padding: bool) -> Option<usize> {
170    let complete_chunks = data_length / 3;
171    let remaining = data_length % 3;
172    let base = match complete_chunks.checked_mul(4) {
173        Some(v) => v,
174        None => return None,
175    };
176    if remaining == 0 {
177        Some(base)
178    } else if padding {
179        base.checked_add(4)
180    } else if remaining == 1 {
181        Some(base + 2)
182    } else {
183        Some(base + 3)
184    }
185}
186
187/// Encodes bytes to a base64 string using the given [`Alphabet`].
188///
189/// # Example
190///
191/// ```rust
192/// let encoded = base64::encode(b"hello world", base64::Alphabet::Standard);
193/// assert_eq!(encoded, "aGVsbG8gd29ybGQ=");
194/// ```
195#[cfg(feature = "alloc")]
196pub fn encode(data: impl AsRef<[u8]>, alphabet: Alphabet) -> alloc::string::String {
197    let data = data.as_ref();
198    let len = encoded_length(data.len(), alphabet.is_padded()).expect("encoded length overflow");
199    let mut output = alloc::vec![0u8; len];
200    encode_into(&mut output, data, alphabet).unwrap();
201    // SAFETY: base64 only produces ASCII characters, which are valid UTF-8.
202    unsafe { alloc::string::String::from_utf8_unchecked(output) }
203}
204
205/// Encodes `data` into a fixed-size array at compile time.
206///
207/// The generic parameter `OUT` is the output array length. It must be exactly
208/// the encoded length of `data` or a compile-time panic is raised.
209///
210/// # Example
211///
212/// ```rust
213/// const DATA: [u8; 3] = [0x66, 0x6F, 0x6F];
214/// const B64: [u8; 4] = base64::encode_array::<4>(&DATA, base64::Alphabet::Standard);
215/// assert_eq!(&B64, b"Zm9v");
216/// ```
217pub const fn encode_array<const OUT: usize>(data: &[u8], alphabet: Alphabet) -> [u8; OUT] {
218    let mut out_buffer = [0u8; OUT];
219    match encode_into_constant_time(&mut out_buffer, data, alphabet) {
220        Ok(_) => {}
221        Err(_) => panic!("output buffer size is not valid"),
222    };
223    out_buffer
224}
225
226/// Encodes bytes into an existing buffer.
227///
228/// Dispatches to a SIMD-accelerated implementation (AVX2 or NEON) when
229/// the target feature is available.
230///
231/// See [`encode_into_constant_time`] for security-sensitive and cryptographic operations.
232///
233/// # Errors
234///
235/// Returns [`EncodeError`] if `output.len()` does not match the expected encoded
236/// length or if the encoded length overflows `usize`.
237///
238/// # Example
239///
240/// ```rust
241/// let mut buf = [0u8; 16];
242/// base64::encode_into(&mut buf, b"hello world", base64::Alphabet::Standard).unwrap();
243/// assert_eq!(&buf, b"aGVsbG8gd29ybGQ=");
244/// ```
245pub fn encode_into(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
246    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
247    if data.len() >= 48 {
248        check_encode_output_length(output.len(), data.len(), alphabet)?;
249        return unsafe { base64_neon::encode_into(output, data, alphabet) };
250    }
251
252    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
253    if data.len() >= 24 {
254        check_encode_output_length(output.len(), data.len(), alphabet)?;
255        return unsafe { base64_avx2::encode_into(output, data, alphabet) };
256    }
257
258    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
259    if data.len() >= 12 {
260        check_encode_output_length(output.len(), data.len(), alphabet)?;
261        return base64_wasm_simd128::encode_into(output, data, alphabet);
262    }
263
264    return encode_into_constant_time(output, data, alphabet);
265}
266
267/// Constant-time base64 encoding. Processes all input data without
268/// secret-dependent branches or memory accesses, making it suitable
269/// for cryptographic applications.
270///
271/// Consumers may prefer the faster [`encode_into`] which dispatches to
272/// a SIMD-accelerated path when available (non constant-time).
273///
274/// # Example
275///
276/// ```rust
277/// let mut buf = [0u8; 4];
278/// base64::encode_into_constant_time(&mut buf, b"foo", base64::Alphabet::Standard).unwrap();
279/// assert_eq!(&buf, b"Zm9v");
280/// ```
281pub const fn encode_into_constant_time(output: &mut [u8], data: &[u8], alphabet: Alphabet) -> Result<(), EncodeError> {
282    match check_encode_output_length(output.len(), data.len(), alphabet) {
283        Ok(_) => {}
284        Err(err) => return Err(err),
285    };
286
287    let padding = alphabet.is_padded();
288    let len = data.len();
289    let mut i = 0;
290
291    while i + 24 <= len {
292        let b0 = data[i];
293        let b1 = data[i + 1];
294        let b2 = data[i + 2];
295        let b3 = data[i + 3];
296        let b4 = data[i + 4];
297        let b5 = data[i + 5];
298        let b6 = data[i + 6];
299        let b7 = data[i + 7];
300        let b8 = data[i + 8];
301        let b9 = data[i + 9];
302        let b10 = data[i + 10];
303        let b11 = data[i + 11];
304        let b12 = data[i + 12];
305        let b13 = data[i + 13];
306        let b14 = data[i + 14];
307        let b15 = data[i + 15];
308        let b16 = data[i + 16];
309        let b17 = data[i + 17];
310        let b18 = data[i + 18];
311        let b19 = data[i + 19];
312        let b20 = data[i + 20];
313        let b21 = data[i + 21];
314        let b22 = data[i + 22];
315        let b23 = data[i + 23];
316
317        let o = (i / 3) * 4;
318        output[o] = sextet_to_base64_char(b0 >> 2, alphabet);
319        output[o + 1] = sextet_to_base64_char(((b0 & 0x03) << 4) | (b1 >> 4), alphabet);
320        output[o + 2] = sextet_to_base64_char(((b1 & 0x0F) << 2) | (b2 >> 6), alphabet);
321        output[o + 3] = sextet_to_base64_char(b2 & 0x3F, alphabet);
322
323        output[o + 4] = sextet_to_base64_char(b3 >> 2, alphabet);
324        output[o + 5] = sextet_to_base64_char(((b3 & 0x03) << 4) | (b4 >> 4), alphabet);
325        output[o + 6] = sextet_to_base64_char(((b4 & 0x0F) << 2) | (b5 >> 6), alphabet);
326        output[o + 7] = sextet_to_base64_char(b5 & 0x3F, alphabet);
327
328        output[o + 8] = sextet_to_base64_char(b6 >> 2, alphabet);
329        output[o + 9] = sextet_to_base64_char(((b6 & 0x03) << 4) | (b7 >> 4), alphabet);
330        output[o + 10] = sextet_to_base64_char(((b7 & 0x0F) << 2) | (b8 >> 6), alphabet);
331        output[o + 11] = sextet_to_base64_char(b8 & 0x3F, alphabet);
332
333        output[o + 12] = sextet_to_base64_char(b9 >> 2, alphabet);
334        output[o + 13] = sextet_to_base64_char(((b9 & 0x03) << 4) | (b10 >> 4), alphabet);
335        output[o + 14] = sextet_to_base64_char(((b10 & 0x0F) << 2) | (b11 >> 6), alphabet);
336        output[o + 15] = sextet_to_base64_char(b11 & 0x3F, alphabet);
337
338        output[o + 16] = sextet_to_base64_char(b12 >> 2, alphabet);
339        output[o + 17] = sextet_to_base64_char(((b12 & 0x03) << 4) | (b13 >> 4), alphabet);
340        output[o + 18] = sextet_to_base64_char(((b13 & 0x0F) << 2) | (b14 >> 6), alphabet);
341        output[o + 19] = sextet_to_base64_char(b14 & 0x3F, alphabet);
342
343        output[o + 20] = sextet_to_base64_char(b15 >> 2, alphabet);
344        output[o + 21] = sextet_to_base64_char(((b15 & 0x03) << 4) | (b16 >> 4), alphabet);
345        output[o + 22] = sextet_to_base64_char(((b16 & 0x0F) << 2) | (b17 >> 6), alphabet);
346        output[o + 23] = sextet_to_base64_char(b17 & 0x3F, alphabet);
347
348        output[o + 24] = sextet_to_base64_char(b18 >> 2, alphabet);
349        output[o + 25] = sextet_to_base64_char(((b18 & 0x03) << 4) | (b19 >> 4), alphabet);
350        output[o + 26] = sextet_to_base64_char(((b19 & 0x0F) << 2) | (b20 >> 6), alphabet);
351        output[o + 27] = sextet_to_base64_char(b20 & 0x3F, alphabet);
352
353        output[o + 28] = sextet_to_base64_char(b21 >> 2, alphabet);
354        output[o + 29] = sextet_to_base64_char(((b21 & 0x03) << 4) | (b22 >> 4), alphabet);
355        output[o + 30] = sextet_to_base64_char(((b22 & 0x0F) << 2) | (b23 >> 6), alphabet);
356        output[o + 31] = sextet_to_base64_char(b23 & 0x3F, alphabet);
357
358        i += 24;
359    }
360
361    while i + 3 <= len {
362        let b0 = data[i];
363        let b1 = data[i + 1];
364        let b2 = data[i + 2];
365        let o = (i / 3) * 4;
366        output[o] = sextet_to_base64_char(b0 >> 2, alphabet);
367        output[o + 1] = sextet_to_base64_char(((b0 & 0x03) << 4) | (b1 >> 4), alphabet);
368        output[o + 2] = sextet_to_base64_char(((b1 & 0x0F) << 2) | (b2 >> 6), alphabet);
369        output[o + 3] = sextet_to_base64_char(b2 & 0x3F, alphabet);
370        i += 3;
371    }
372
373    let remaining = len - i;
374    if remaining > 0 {
375        let o = (i / 3) * 4;
376        let b0 = data[i];
377        let b1 = if i + 1 < len { data[i + 1] } else { 0 };
378
379        let rem1 = (remaining == 1) as u8;
380        let rem2 = (remaining == 2) as u8;
381        let m1 = 0u8.wrapping_sub(rem1);
382        let m2 = 0u8.wrapping_sub(rem2);
383
384        output[o] = sextet_to_base64_char(b0 >> 2, alphabet);
385
386        let o1_rem1 = sextet_to_base64_char((b0 & 0x03) << 4, alphabet);
387        let o1_rem2 = sextet_to_base64_char(((b0 & 0x03) << 4) | (b1 >> 4), alphabet);
388        output[o + 1] = (o1_rem1 & m1) | (o1_rem2 & m2);
389
390        if padding {
391            let o2_rem1 = PAD;
392            let o2_rem2 = sextet_to_base64_char((b1 & 0x0F) << 2, alphabet);
393            output[o + 2] = (o2_rem1 & m1) | (o2_rem2 & m2);
394            output[o + 3] = PAD;
395        } else {
396            if remaining == 2 {
397                output[o + 2] = sextet_to_base64_char((b1 & 0x0F) << 2, alphabet);
398            }
399        }
400    }
401
402    Ok(())
403}
404
405/// Appends the base64-encoded representation of `data` to a [`String`].
406///
407/// # Example
408///
409/// ```rust
410/// let mut s = String::from("tag: ");
411/// base64::encode_into_string(&mut s, b"hello", base64::Alphabet::Standard);
412/// assert_eq!(s, "tag: aGVsbG8=");
413/// ```
414#[cfg(feature = "alloc")]
415pub fn encode_into_string(output: &mut alloc::string::String, data: &[u8], alphabet: Alphabet) {
416    let encoded_length = encoded_length(data.len(), alphabet.is_padded()).expect("output length overflow");
417    if encoded_length <= 256 {
418        // zero-alloc version for small data
419        let mut buf = [0u8; 256];
420        let buf = &mut buf[..encoded_length];
421        encode_into(buf, data, alphabet).unwrap();
422        // SAFETY: base64 only produces ASCII characters, which are valid UTF-8.
423        output.push_str(unsafe { core::str::from_utf8_unchecked(buf) });
424    } else {
425        let mut buf = alloc::vec![0u8; encoded_length];
426        encode_into(&mut buf, data, alphabet).unwrap();
427        // SAFETY: base64 only produces ASCII characters, which are valid UTF-8.
428        output.push_str(unsafe { core::str::from_utf8_unchecked(&buf) });
429    }
430}
431
432/// Checks that `output_length == encoded_length(data_length, padding)`
433#[inline]
434const fn check_encode_output_length(
435    output_length: usize,
436    data_length: usize,
437    alphabet: Alphabet,
438) -> Result<(), EncodeError> {
439    let padding = alphabet.is_padded();
440
441    let expected_output_length = match encoded_length(data_length, padding) {
442        Some(length) => length,
443        None => return Err(EncodeError::OutputOverflow),
444    };
445    if output_length != expected_output_length {
446        return Err(EncodeError::InvalidOutputLength);
447    }
448
449    return Ok(());
450}
451
452/// Returns 0x00 if lo <= v <= hi, 0xFF otherwise.
453/// Uses sign-bit propagation for branchless range checking.
454#[inline]
455const fn not_in_range(v: u8, lo: u8, hi: u8) -> u8 {
456    (((v.wrapping_sub(lo) as i8) | (hi.wrapping_sub(v) as i8)) >> 7) as u8
457}
458
459/// Constant-time mapping: 6-bit value (0-63) to base64 character.
460/// No secret-dependent branches or memory accesses.
461#[inline]
462const fn sextet_to_base64_char(v: u8, alphabet: Alphabet) -> u8 {
463    let v = v & 0x3F;
464
465    let not_upper = not_in_range(v, 0, 25);
466    let not_lower = not_in_range(v, 26, 51);
467    let not_digit = not_in_range(v, 52, 61);
468    let not_62 = not_in_range(v, 62, 62);
469    let not_63 = not_in_range(v, 63, 63);
470
471    let upper_val = v + b'A';
472    let lower_val = v.wrapping_sub(26).wrapping_add(b'a');
473    let digit_val = v.wrapping_sub(52).wrapping_add(b'0');
474
475    let (ch_62, ch_63) = match alphabet {
476        Alphabet::Standard | Alphabet::StandardNoPadding => (b'+', b'/'),
477        Alphabet::Url | Alphabet::UrlNoPadding => (b'-', b'_'),
478    };
479
480    (upper_val & !not_upper)
481        | (lower_val & !not_lower)
482        | (digit_val & !not_digit)
483        | (ch_62 & !not_62)
484        | (ch_63 & !not_63)
485}
486
487////////////////////////////////////////////////////////////////////////////////////////////////////
488/// Decode
489////////////////////////////////////////////////////////////////////////////////////////////////////
490
491/// Decodes a base64 string into bytes.
492///
493/// # Errors
494///
495/// Returns [`DecodeError`] if any character is invalid for the chosen
496/// [`Alphabet`], the input length is not valid, or padding is incorrect.
497///
498/// # Example
499///
500/// ```rust
501/// let decoded = base64::decode(b"aGVsbG8=", base64::Alphabet::Standard).unwrap();
502/// assert_eq!(decoded, b"hello");
503/// ```
504#[cfg(feature = "alloc")]
505pub fn decode(data: impl AsRef<[u8]>, alphabet: Alphabet) -> Result<alloc::vec::Vec<u8>, DecodeError> {
506    let data = data.as_ref();
507    let (content_len, _) = strip_padding_info(data, alphabet.is_padded())?;
508    let output_len = decoded_length(content_len)?;
509    let mut output = alloc::vec![0u8; output_len];
510    decode_into(&mut output, data, alphabet)?;
511    Ok(output)
512}
513
514/// Decodes a base64 string into a fixed-size array at compile time.
515///
516/// The generic parameter `OUT` is the output array length. It must be exactly
517/// the decoded length of the input or a an error is returned.
518///
519/// # Example
520///
521/// ```rust
522/// const RESULT: Result<[u8; 5], base64::DecodeError> =
523///     base64::decode_array::<5>(b"aGVsbG8=", base64::Alphabet::Standard);
524/// assert_eq!(RESULT.unwrap(), *b"hello");
525/// ```
526pub const fn decode_array<const OUT: usize>(encoded_data: &[u8], alphabet: Alphabet) -> Result<[u8; OUT], DecodeError> {
527    let mut result = [0u8; OUT];
528    match decode_into_constant_time(&mut result, encoded_data, alphabet) {
529        Ok(_) => Ok(result),
530        Err(err) => Err(err),
531    }
532}
533
534/// Decodes a base64 string into an existing buffer.
535///
536/// Dispatches to a SIMD-accelerated implementation (AVX2 or NEON) when
537/// the target feature is available.
538///
539/// See [`decode_into_constant_time`] for security-sensitive and cryptographic operations.
540///
541/// # Errors
542///
543/// Returns [`DecodeError`] if any character is invalid for the chosen
544/// [`Alphabet`], if the input length is not valid, if padding is incorrect,
545/// or if `output.len()` is too small to hold the decoded data.
546///
547/// # Example
548///
549/// ```rust
550/// let mut buf = [0u8; 5];
551/// base64::decode_into(&mut buf, b"aGVsbG8=", base64::Alphabet::Standard).unwrap();
552/// assert_eq!(&buf, b"hello");
553/// ```
554pub fn decode_into(output: &mut [u8], encoded_data: &[u8], alphabet: Alphabet) -> Result<usize, DecodeError> {
555    let (content_len, _) = strip_padding_info(encoded_data, alphabet.is_padded())?;
556    let computed_output = decoded_length(content_len)?;
557    if output.len() < computed_output {
558        return Err(DecodeError::InvalidOutputLength);
559    }
560
561    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
562    if content_len >= 32 {
563        let content = &encoded_data[..content_len];
564        unsafe { base64_neon::decode_into(output, content, alphabet)? };
565        return Ok(computed_output);
566    }
567
568    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
569    if content_len >= 32 {
570        let content = &encoded_data[..content_len];
571        unsafe { base64_avx2::decode_into(output, content, alphabet)? };
572        return Ok(computed_output);
573    }
574
575    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
576    if content_len >= 16 {
577        let content = &encoded_data[..content_len];
578        base64_wasm_simd128::decode_into(output, content, alphabet)?;
579        return Ok(computed_output);
580    }
581
582    decode_into_constant_time(output, encoded_data, alphabet)
583}
584
585/// Constant-time base64 decoding. Processes all input data without
586/// secret-dependent branches or memory accesses, making it suitable
587/// for cryptographic applications.
588///
589/// Consumers may prefer the faster [`decode_into`] which dispatches to
590/// a SIMD-accelerated path when available (non constant-time).
591///
592/// # Example
593///
594/// ```rust
595/// let mut buf = [0u8; 3];
596/// base64::decode_into_constant_time(&mut buf, b"Zm9v", base64::Alphabet::Standard).unwrap();
597/// assert_eq!(&buf, b"foo");
598/// ```
599pub const fn decode_into_constant_time(
600    output: &mut [u8],
601    encoded_data: &[u8],
602    alphabet: Alphabet,
603) -> Result<usize, DecodeError> {
604    let in_len = encoded_data.len();
605    let padding = alphabet.is_padded();
606
607    if in_len == 0 {
608        return Ok(0);
609    }
610
611    let (content_len, _padding_len) = match strip_padding_info(encoded_data, padding) {
612        Ok(info) => info,
613        Err(e) => return Err(e),
614    };
615
616    if content_len == 0 {
617        return Ok(0);
618    }
619
620    let computed_output = match decoded_length(content_len) {
621        Ok(len) => len,
622        Err(e) => return Err(e),
623    };
624
625    if output.len() < computed_output {
626        return Err(DecodeError::InvalidOutputLength);
627    }
628
629    let mut err: u8 = 0;
630    let mut i = 0;
631    let mut o = 0;
632
633    while i + 32 <= content_len {
634        let v0 = base64_char_to_sextet(encoded_data[i], alphabet);
635        let v1 = base64_char_to_sextet(encoded_data[i + 1], alphabet);
636        let v2 = base64_char_to_sextet(encoded_data[i + 2], alphabet);
637        let v3 = base64_char_to_sextet(encoded_data[i + 3], alphabet);
638        err |= v0 | v1 | v2 | v3;
639        output[o] = (v0 << 2) | (v1 >> 4);
640        output[o + 1] = (v1 << 4) | (v2 >> 2);
641        output[o + 2] = (v2 << 6) | v3;
642
643        let v4 = base64_char_to_sextet(encoded_data[i + 4], alphabet);
644        let v5 = base64_char_to_sextet(encoded_data[i + 5], alphabet);
645        let v6 = base64_char_to_sextet(encoded_data[i + 6], alphabet);
646        let v7 = base64_char_to_sextet(encoded_data[i + 7], alphabet);
647        err |= v4 | v5 | v6 | v7;
648        output[o + 3] = (v4 << 2) | (v5 >> 4);
649        output[o + 4] = (v5 << 4) | (v6 >> 2);
650        output[o + 5] = (v6 << 6) | v7;
651
652        let v8 = base64_char_to_sextet(encoded_data[i + 8], alphabet);
653        let v9 = base64_char_to_sextet(encoded_data[i + 9], alphabet);
654        let v10 = base64_char_to_sextet(encoded_data[i + 10], alphabet);
655        let v11 = base64_char_to_sextet(encoded_data[i + 11], alphabet);
656        err |= v8 | v9 | v10 | v11;
657        output[o + 6] = (v8 << 2) | (v9 >> 4);
658        output[o + 7] = (v9 << 4) | (v10 >> 2);
659        output[o + 8] = (v10 << 6) | v11;
660
661        let v12 = base64_char_to_sextet(encoded_data[i + 12], alphabet);
662        let v13 = base64_char_to_sextet(encoded_data[i + 13], alphabet);
663        let v14 = base64_char_to_sextet(encoded_data[i + 14], alphabet);
664        let v15 = base64_char_to_sextet(encoded_data[i + 15], alphabet);
665        err |= v12 | v13 | v14 | v15;
666        output[o + 9] = (v12 << 2) | (v13 >> 4);
667        output[o + 10] = (v13 << 4) | (v14 >> 2);
668        output[o + 11] = (v14 << 6) | v15;
669
670        let v16 = base64_char_to_sextet(encoded_data[i + 16], alphabet);
671        let v17 = base64_char_to_sextet(encoded_data[i + 17], alphabet);
672        let v18 = base64_char_to_sextet(encoded_data[i + 18], alphabet);
673        let v19 = base64_char_to_sextet(encoded_data[i + 19], alphabet);
674        err |= v16 | v17 | v18 | v19;
675        output[o + 12] = (v16 << 2) | (v17 >> 4);
676        output[o + 13] = (v17 << 4) | (v18 >> 2);
677        output[o + 14] = (v18 << 6) | v19;
678
679        let v20 = base64_char_to_sextet(encoded_data[i + 20], alphabet);
680        let v21 = base64_char_to_sextet(encoded_data[i + 21], alphabet);
681        let v22 = base64_char_to_sextet(encoded_data[i + 22], alphabet);
682        let v23 = base64_char_to_sextet(encoded_data[i + 23], alphabet);
683        err |= v20 | v21 | v22 | v23;
684        output[o + 15] = (v20 << 2) | (v21 >> 4);
685        output[o + 16] = (v21 << 4) | (v22 >> 2);
686        output[o + 17] = (v22 << 6) | v23;
687
688        let v24 = base64_char_to_sextet(encoded_data[i + 24], alphabet);
689        let v25 = base64_char_to_sextet(encoded_data[i + 25], alphabet);
690        let v26 = base64_char_to_sextet(encoded_data[i + 26], alphabet);
691        let v27 = base64_char_to_sextet(encoded_data[i + 27], alphabet);
692        err |= v24 | v25 | v26 | v27;
693        output[o + 18] = (v24 << 2) | (v25 >> 4);
694        output[o + 19] = (v25 << 4) | (v26 >> 2);
695        output[o + 20] = (v26 << 6) | v27;
696
697        let v28 = base64_char_to_sextet(encoded_data[i + 28], alphabet);
698        let v29 = base64_char_to_sextet(encoded_data[i + 29], alphabet);
699        let v30 = base64_char_to_sextet(encoded_data[i + 30], alphabet);
700        let v31 = base64_char_to_sextet(encoded_data[i + 31], alphabet);
701        err |= v28 | v29 | v30 | v31;
702        output[o + 21] = (v28 << 2) | (v29 >> 4);
703        output[o + 22] = (v29 << 4) | (v30 >> 2);
704        output[o + 23] = (v30 << 6) | v31;
705
706        i += 32;
707        o += 24;
708    }
709
710    while i + 4 <= content_len {
711        let v0 = base64_char_to_sextet(encoded_data[i], alphabet);
712        let v1 = base64_char_to_sextet(encoded_data[i + 1], alphabet);
713        let v2 = base64_char_to_sextet(encoded_data[i + 2], alphabet);
714        let v3 = base64_char_to_sextet(encoded_data[i + 3], alphabet);
715        err |= v0 | v1 | v2 | v3;
716        output[o] = (v0 << 2) | (v1 >> 4);
717        output[o + 1] = (v1 << 4) | (v2 >> 2);
718        output[o + 2] = (v2 << 6) | v3;
719        i += 4;
720        o += 3;
721    }
722
723    let remaining = content_len - i;
724    let rem2 = (remaining == 2) as u8;
725    let rem3 = (remaining == 3) as u8;
726    let rem0 = (remaining == 0) as u8;
727    let valid_rem = rem0 | rem2 | rem3;
728    err |= (1 - valid_rem) << 6;
729
730    let rem2_mask = 0u8.wrapping_sub(rem2);
731    let rem3_mask = 0u8.wrapping_sub(rem3);
732
733    if remaining > 0 {
734        let c0 = encoded_data[i];
735        let c1 = if i + 1 < content_len { encoded_data[i + 1] } else { b'A' };
736        let c2 = if i + 2 < content_len { encoded_data[i + 2] } else { b'A' };
737
738        let v0 = base64_char_to_sextet(c0, alphabet);
739        let v1 = base64_char_to_sextet(c1, alphabet);
740        let v2 = base64_char_to_sextet(c2, alphabet);
741
742        let m0 = 0u8.wrapping_sub((i < content_len) as u8);
743        let m1 = 0u8.wrapping_sub((i + 1 < content_len) as u8);
744        let m2 = 0u8.wrapping_sub((i + 2 < content_len) as u8);
745        err |= (v0 & m0) | (v1 & m1) | (v2 & m2);
746
747        // Reject non-canonical trailing bits
748        // remaining == 2: bottom 4 bits of v1 are unused -> must be zero
749        // remaining == 3: bottom 2 bits of v2 are unused -> must be zero
750        let v1_trailing = v1 & 0x0F;
751        let v2_trailing = v2 & 0x03;
752        let trailing = (v1_trailing & rem2_mask) | (v2_trailing & rem3_mask);
753        err |= ((trailing != 0) as u8) << 6;
754
755        let out0 = (v0 << 2) | (v1 >> 4);
756        let out1 = (v1 << 4) | (v2 >> 2);
757
758        output[o] = out0;
759        if remaining == 3 {
760            output[o + 1] = out1;
761        }
762    }
763
764    if err >= 64 {
765        return Err(DecodeError::InvalidInput);
766    }
767
768    Ok(computed_output)
769}
770
771/// Constant-time mapping: base64 character to 6-bit value.
772/// Valid characters return 0-63. Invalid characters return a value with bit 6 set (>= 64).
773#[inline]
774const fn base64_char_to_sextet(c: u8, alphabet: Alphabet) -> u8 {
775    let not_upper = not_in_range(c, b'A', b'Z');
776    let not_lower = not_in_range(c, b'a', b'z');
777    let not_digit = not_in_range(c, b'0', b'9');
778
779    let upper_val = c.wrapping_sub(b'A');
780    let lower_val = c.wrapping_sub(b'a').wrapping_add(26);
781    let digit_val = c.wrapping_sub(b'0').wrapping_add(52);
782
783    let (ch_62, ch_63) = match alphabet {
784        Alphabet::Standard | Alphabet::StandardNoPadding => (b'+', b'/'),
785        Alphabet::Url | Alphabet::UrlNoPadding => (b'-', b'_'),
786    };
787    let not_62 = not_in_range(c, ch_62, ch_62);
788    let not_63 = not_in_range(c, ch_63, ch_63);
789
790    let value = (upper_val & !not_upper)
791        | (lower_val & !not_lower)
792        | (digit_val & !not_digit)
793        | (62 & !not_62)
794        | (63 & !not_63);
795
796    let invalid = not_upper & not_lower & not_digit & not_62 & not_63;
797    value | (invalid & 0x40)
798}
799
800pub(crate) const fn strip_padding_info(data: &[u8], expect_padding: bool) -> Result<(usize, usize), DecodeError> {
801    let in_len = data.len();
802
803    if !expect_padding {
804        let last_is_pad = if in_len > 0 { (data[in_len - 1] == PAD) as u8 } else { 0 };
805        if last_is_pad != 0 {
806            return Err(DecodeError::InvalidPadding);
807        }
808        return Ok((in_len, 0));
809    }
810
811    // For padded input, examine up to the last 3 bytes branchlessly.
812    // Valid base64 has at most 2 trailing '=' characters.
813    let b0 = if in_len > 0 { data[in_len - 1] } else { 0 };
814    let b1 = if in_len > 1 { data[in_len - 2] } else { 0 };
815    let b2 = if in_len > 2 { data[in_len - 3] } else { 0 };
816
817    let p0 = (b0 == PAD) as usize;
818    let p1 = (b1 == PAD) as usize;
819    let p2 = (b2 == PAD) as usize;
820
821    // pad_count is the number of trailing PADs (0..3).
822    let pad_count = p0 + (p0 & p1) + (p0 & p1 & p2);
823    let content_len = in_len - pad_count;
824
825    let mut err_len: u8 = 0;
826    let mut err_pad: u8 = 0;
827
828    // If padding is present, total length must be a multiple of 4.
829    let has_pads = (pad_count != 0) as u8;
830    let len_mod4_ok = ((in_len & 3) == 0) as u8;
831    err_len |= has_pads & (1 - len_mod4_ok);
832
833    // More than 2 padding characters is invalid.
834    err_pad |= (pad_count > 2) as u8;
835
836    if err_len != 0 {
837        return Err(DecodeError::InvalidInputLength);
838    }
839
840    // Padding count must match the content length modulo 4.
841    // 2 pads => content_len % 4 == 2
842    // 1 pad  => content_len % 4 == 3
843    let content_mod4 = content_len & 3;
844    let expected_mod4 = 4usize.wrapping_sub(pad_count);
845    let mod4_ok = (content_mod4 == expected_mod4) as u8;
846    err_pad |= has_pads & (1 - mod4_ok);
847
848    if err_pad != 0 {
849        return Err(DecodeError::InvalidPadding);
850    }
851
852    Ok((content_len, pad_count))
853}
854
855/// Returns the size in bytes of the data after decoding from Base64.
856/// Returns `None` if the length overflows `usize`.
857pub(crate) const fn decoded_length(encoded_data_length: usize) -> Result<usize, DecodeError> {
858    let full_blocks = encoded_data_length / 4;
859    let rem = encoded_data_length % 4;
860
861    let base = match full_blocks.checked_mul(3) {
862        Some(v) => v,
863        None => return Err(DecodeError::InvalidInputLength),
864    };
865
866    match rem {
867        0 => Ok(base),
868        2 => match base.checked_add(1) {
869            Some(v) => Ok(v),
870            None => Err(DecodeError::InvalidInputLength),
871        },
872        3 => match base.checked_add(2) {
873            Some(v) => Ok(v),
874            None => Err(DecodeError::InvalidInputLength),
875        },
876        _ => Err(DecodeError::InvalidInputLength),
877    }
878}
879
880#[cfg(test)]
881mod tests {
882    use super::*;
883
884    // (input_bytes, alphabet, expected_encoded_str, description)
885    const ENCODE_VECTORS: &[(&[u8], Alphabet, &str, &str)] = &[
886        // RFC 4648 test vectors
887        (b"", Alphabet::Standard, "", "RFC4648: empty"),
888        (b"f", Alphabet::Standard, "Zg==", "RFC4648: 'f'"),
889        (b"fo", Alphabet::Standard, "Zm8=", "RFC4648: 'fo'"),
890        (b"foo", Alphabet::Standard, "Zm9v", "RFC4648: 'foo'"),
891        (b"foob", Alphabet::Standard, "Zm9vYg==", "RFC4648: 'foob'"),
892        (b"fooba", Alphabet::Standard, "Zm9vYmE=", "RFC4648: 'fooba'"),
893        (b"foobar", Alphabet::Standard, "Zm9vYmFy", "RFC4648: 'foobar'"),
894        // RFC 4648 Section 9 illustration vectors
895        (
896            &[0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e],
897            Alphabet::Standard,
898            "FPucA9l+",
899            "illustration: 6 bytes",
900        ),
901        (
902            &[0x14, 0xfb, 0x9c, 0x03, 0xd9],
903            Alphabet::Standard,
904            "FPucA9k=",
905            "illustration: 5 bytes",
906        ),
907        (
908            &[0x14, 0xfb, 0x9c, 0x03],
909            Alphabet::Standard,
910            "FPucAw==",
911            "illustration: 4 bytes",
912        ),
913        (b"Man", Alphabet::Standard, "TWFu", "illustration: 'Man'"),
914        (b"Ma", Alphabet::Standard, "TWE=", "illustration: 'Ma'"),
915        (b"M", Alphabet::Standard, "TQ==", "illustration: 'M'"),
916        // Single bytes
917        (b"\x00", Alphabet::Standard, "AA==", "single byte 0x00"),
918        (b"\xFF", Alphabet::Standard, "/w==", "single byte 0xFF"),
919        (b"\xAB", Alphabet::Standard, "qw==", "single byte 0xAB"),
920        (b"\xFF", Alphabet::Url, "_w==", "single byte 0xFF URL"),
921        // Two bytes
922        (b"\x00\x00", Alphabet::Standard, "AAA=", "two bytes 0x00"),
923        (b"\xFF\xFF", Alphabet::Standard, "//8=", "two bytes 0xFF"),
924        // Three bytes
925        (b"bar", Alphabet::Standard, "YmFy", "three bytes 'bar'"),
926        // No padding
927        (b"f", Alphabet::StandardNoPadding, "Zg", "no-pad: 'f'"),
928        (b"fo", Alphabet::StandardNoPadding, "Zm8", "no-pad: 'fo'"),
929        (b"foo", Alphabet::StandardNoPadding, "Zm9v", "no-pad: 'foo'"),
930        // URL-safe
931        (b"\xFF\xEC\x20\x55\x00", Alphabet::Url, "_-wgVQA=", "URL padded"),
932        (b"\xFF\xEC\x20\x55\x00", Alphabet::UrlNoPadding, "_-wgVQA", "URL no-pad"),
933    ];
934
935    // (encoded_str, alphabet, expected_bytes, description)
936    const DECODE_VECTORS: &[(&[u8], Alphabet, &[u8], &str)] = &[
937        (b"", Alphabet::Standard, b"", "RFC4648: empty"),
938        (b"Zg==", Alphabet::Standard, b"f", "RFC4648: 'f'"),
939        (b"Zm8=", Alphabet::Standard, b"fo", "RFC4648: 'fo'"),
940        (b"Zm9v", Alphabet::Standard, b"foo", "RFC4648: 'foo'"),
941        (b"Zm9vYg==", Alphabet::Standard, b"foob", "RFC4648: 'foob'"),
942        (b"Zm9vYmE=", Alphabet::Standard, b"fooba", "RFC4648: 'fooba'"),
943        (b"Zm9vYmFy", Alphabet::Standard, b"foobar", "RFC4648: 'foobar'"),
944        (b"AA==", Alphabet::Standard, b"\x00", "single byte 0x00"),
945        (b"/w==", Alphabet::Standard, b"\xFF", "single byte 0xFF"),
946        (b"qw==", Alphabet::Standard, b"\xAB", "single byte 0xAB"),
947        (b"AAA=", Alphabet::Standard, b"\x00\x00", "two bytes 0x00"),
948        (b"//8=", Alphabet::Standard, b"\xFF\xFF", "two bytes 0xFF"),
949        (b"Zg", Alphabet::StandardNoPadding, b"f", "no-pad: 'f'"),
950        (b"Zm8", Alphabet::StandardNoPadding, b"fo", "no-pad: 'fo'"),
951        (b"Zm9v", Alphabet::StandardNoPadding, b"foo", "no-pad: 'foo'"),
952        (b"_-wgVQA=", Alphabet::Url, b"\xFF\xEC\x20\x55\x00", "URL padded"),
953        (b"_-wgVQA", Alphabet::UrlNoPadding, b"\xFF\xEC\x20\x55\x00", "URL no-pad"),
954    ];
955
956    // (encoded_str, alphabet, expected_error, description)
957    const DECODE_ERROR_VECTORS: &[(&[u8], Alphabet, DecodeError, &str)] = &[
958        (b"!!", Alphabet::Standard, DecodeError::InvalidInput, "two invalid chars"),
959        (
960            b"Zg!!",
961            Alphabet::Standard,
962            DecodeError::InvalidInput,
963            "valid prefix + invalid suffix",
964        ),
965        (b"!A==", Alphabet::Standard, DecodeError::InvalidInput, "invalid first char"),
966        (b"A", Alphabet::Standard, DecodeError::InvalidInputLength, "single char"),
967        (b"AAAAA", Alphabet::Standard, DecodeError::InvalidInputLength, "5 chars"),
968        (b"Z===", Alphabet::Standard, DecodeError::InvalidPadding, "1 content + 3 pads"),
969        (
970            b"Zg=A",
971            Alphabet::Standard,
972            DecodeError::InvalidInput,
973            "interior '=' before valid char",
974        ),
975        (
976            b"Zg===",
977            Alphabet::Standard,
978            DecodeError::InvalidInputLength,
979            "valid + 3 pads (invalid length)",
980        ),
981        (
982            b"Zg==",
983            Alphabet::StandardNoPadding,
984            DecodeError::InvalidPadding,
985            "no-pad rejects padding",
986        ),
987        (
988            b"Zm8=",
989            Alphabet::StandardNoPadding,
990            DecodeError::InvalidPadding,
991            "no-pad rejects padding 2 bytes",
992        ),
993        (b"=", Alphabet::Standard, DecodeError::InvalidInputLength, "single pad only"),
994        (b"==", Alphabet::Standard, DecodeError::InvalidInputLength, "double pad only"),
995        (b"A===", Alphabet::Standard, DecodeError::InvalidPadding, "1 content + 3 pads"),
996    ];
997
998    // (data_length, padding, expected_result, description)
999    const ENCODED_LENGTH_VECTORS: &[(usize, bool, Option<usize>, &str)] = &[
1000        (0, true, Some(0), "empty padded"),
1001        (1, true, Some(4), "1 byte padded"),
1002        (2, true, Some(4), "2 bytes padded"),
1003        (3, true, Some(4), "3 bytes padded"),
1004        (4, true, Some(8), "4 bytes padded"),
1005        (5, true, Some(8), "5 bytes padded"),
1006        (0, false, Some(0), "empty unpadded"),
1007        (1, false, Some(2), "1 byte unpadded"),
1008        (2, false, Some(3), "2 bytes unpadded"),
1009        (3, false, Some(4), "3 bytes unpadded"),
1010        (4, false, Some(6), "4 bytes unpadded"),
1011        (5, false, Some(7), "5 bytes unpadded"),
1012        (usize::MAX, true, None, "overflow padded"),
1013        (usize::MAX, false, None, "overflow unpadded"),
1014        (usize::MAX / 4 * 3 + 3, true, None, "overflow at boundary"),
1015    ];
1016
1017    // (initial_string, input_bytes, alphabet, expected_output, description)
1018    const ENCODE_INTO_STRING_VECTORS: &[(&str, &[u8], Alphabet, &str, &str)] = &[
1019        ("", b"", Alphabet::Standard, "", "empty"),
1020        ("prefix", b"", Alphabet::Standard, "prefix", "empty data with prefix"),
1021        ("", b"\x00", Alphabet::Standard, "AA==", "single null byte"),
1022        ("", b"fo", Alphabet::Standard, "Zm8=", "two bytes 'fo'"),
1023        ("", b"foo", Alphabet::Standard, "Zm9v", "three bytes 'foo'"),
1024        ("", b"f", Alphabet::StandardNoPadding, "Zg", "no-pad: 'f'"),
1025        ("", b"fo", Alphabet::StandardNoPadding, "Zm8", "no-pad: 'fo'"),
1026        ("", b"foo", Alphabet::StandardNoPadding, "Zm9v", "no-pad: 'foo'"),
1027        ("", b"\xFF\xEC\x20\x55\x00", Alphabet::Url, "_-wgVQA=", "URL padded"),
1028        ("", b"\xFF\xEC\x20\x55\x00", Alphabet::UrlNoPadding, "_-wgVQA", "URL no-pad"),
1029    ];
1030
1031    const ALL_ALPHABETS: &[Alphabet] = &[
1032        Alphabet::Standard,
1033        Alphabet::StandardNoPadding,
1034        Alphabet::Url,
1035        Alphabet::UrlNoPadding,
1036    ];
1037
1038    const ROUNDTRIP_SIZES: &[usize] = &[
1039        0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 23, 24, 25, 31, 32, 33, 47, 48, 49, 63, 64, 65, 127, 128, 129,
1040    ];
1041
1042    const SIMD_BOUNDARY_SIZES: &[usize] = &[
1043        22, 23, 24, 25, 26, 30, 31, 32, 33, 34, 46, 47, 48, 49, 50, 62, 63, 64, 65, 66,
1044    ];
1045
1046    #[test]
1047    fn test_encode() {
1048        for &(input, alphabet, expected, desc) in ENCODE_VECTORS {
1049            let result = encode(input, alphabet);
1050            assert_eq!(result, expected, "encode: {desc}");
1051        }
1052    }
1053
1054    #[test]
1055    fn test_decode() {
1056        for &(encoded, alphabet, expected, desc) in DECODE_VECTORS {
1057            let result = decode(encoded, alphabet).unwrap();
1058            assert_eq!(&result, expected, "decode: {desc}");
1059        }
1060    }
1061
1062    #[test]
1063    fn test_decode_error() {
1064        for &(encoded, alphabet, expected_err, desc) in DECODE_ERROR_VECTORS {
1065            let result = decode(encoded, alphabet);
1066            assert_eq!(result, Err(expected_err), "decode error: {desc}");
1067        }
1068
1069        // 32-byte input with invalid char at position 31 (SIMD boundary via dispatch)
1070        let mut input = alloc::vec![b'A'; 32];
1071        input[31] = b'!';
1072        assert_eq!(decode(&input, Alphabet::Standard), Err(DecodeError::InvalidInput));
1073    }
1074
1075    #[test]
1076    fn test_encoded_length() {
1077        for &(data_len, padding, expected, desc) in ENCODED_LENGTH_VECTORS {
1078            let result = encoded_length(data_len, padding);
1079            assert_eq!(result, expected, "encoded_length: {desc}");
1080        }
1081    }
1082
1083    #[test]
1084    fn test_encode_into_string() {
1085        for &(initial, input, alphabet, expected, desc) in ENCODE_INTO_STRING_VECTORS {
1086            let mut s = alloc::string::String::from(initial);
1087            encode_into_string(&mut s, input, alphabet);
1088            assert_eq!(s, expected, "encode_into_string: {desc}");
1089        }
1090
1091        // Multi-append
1092        let mut s = alloc::string::String::from("~~");
1093        encode_into_string(&mut s, b"foo", Alphabet::Standard);
1094        assert_eq!(s, "~~Zm9v");
1095        encode_into_string(&mut s, b"bar", Alphabet::Standard);
1096        assert_eq!(s, "~~Zm9vYmFy");
1097
1098        for alphabet in ALL_ALPHABETS {
1099            let expected = encode(b"hello world", *alphabet);
1100            let mut s = alloc::string::String::new();
1101            encode_into_string(&mut s, b"hello world", *alphabet);
1102            assert_eq!(s, expected, "encode_into_string alphabet {alphabet:?}");
1103        }
1104    }
1105
1106    #[test]
1107    fn test_roundtrip() {
1108        for &len in ROUNDTRIP_SIZES {
1109            let data: Vec<u8> = (0..len as u8).collect();
1110            for alphabet in ALL_ALPHABETS {
1111                let encoded = encode(&data, *alphabet);
1112                let decoded = decode(encoded.as_bytes(), *alphabet).unwrap();
1113                assert_eq!(decoded, data, "roundtrip len={len} alphabet={alphabet:?}");
1114            }
1115        }
1116    }
1117
1118    #[test]
1119    fn test_roundtrip_large() {
1120        let size = 4096;
1121
1122        let data = alloc::vec![0x00u8; size];
1123        let elen = encoded_length(size, true).expect("encoded_len overflow");
1124        let mut encoded = alloc::vec![0u8; elen];
1125        encode_into_constant_time(&mut encoded, &data, Alphabet::Standard).unwrap();
1126        let mut decoded = alloc::vec![0u8; size];
1127        decode_into_constant_time(&mut decoded, &encoded, Alphabet::Standard).unwrap();
1128        assert_eq!(decoded, data, "4096 zeroes constant-time");
1129
1130        let data = alloc::vec![0xFFu8; size];
1131        let mut encoded = alloc::vec![0u8; elen];
1132        encode_into_constant_time(&mut encoded, &data, Alphabet::Standard).unwrap();
1133        decode_into_constant_time(&mut decoded, &encoded, Alphabet::Standard).unwrap();
1134        assert_eq!(decoded, data, "4096 0xFF constant-time");
1135
1136        let data: Vec<u8> = (0..=255).cycle().take(size).collect();
1137        let encoded = encode(&data, Alphabet::Standard);
1138        let decoded = decode(encoded.as_bytes(), Alphabet::Standard).unwrap();
1139        assert_eq!(decoded, data, "4096 cycle dispatch");
1140
1141        let data: Vec<u8> = (0..=255).collect();
1142        let mut s = alloc::string::String::new();
1143        encode_into_string(&mut s, &data, Alphabet::Standard);
1144        let decoded = decode(s.as_bytes(), Alphabet::Standard).unwrap();
1145        assert_eq!(decoded, data, "256-byte encode_into_string roundtrip");
1146
1147        let data: Vec<u8> = (0..255).cycle().take(4096).collect();
1148        let expected = encode(&data, Alphabet::Standard);
1149        let mut s = alloc::string::String::new();
1150        encode_into_string(&mut s, &data, Alphabet::Standard);
1151        assert_eq!(s, expected, "4096-byte encode_into_string");
1152    }
1153
1154    #[test]
1155    fn test_encode_all_single_bytes() {
1156        for byte in 0..=255u8 {
1157            for alphabet in &[Alphabet::Standard, Alphabet::Url] {
1158                let padding = alphabet.is_padded();
1159                let elen = encoded_length(1, padding).unwrap();
1160                let mut encoded = alloc::vec![0u8; elen];
1161                encode_into_constant_time(&mut encoded, &[byte], *alphabet).unwrap();
1162                let mut decoded = [0u8; 1];
1163                decode_into_constant_time(&mut decoded, &encoded, *alphabet).unwrap();
1164                assert_eq!(decoded[0], byte, "single byte roundtrip {byte:#04x} alphabet={alphabet:?}");
1165            }
1166        }
1167    }
1168
1169    #[test]
1170    fn test_decode_invalid_char_every_position() {
1171        let mut out = [0u8; 32];
1172
1173        for pos in 0..32 {
1174            let mut input = [b'A'; 32];
1175            input[pos] = b'!';
1176            assert_eq!(
1177                decode_into_constant_time(&mut out, &input, Alphabet::Standard),
1178                Err(DecodeError::InvalidInput),
1179                "invalid char at position {pos} in 32-byte block"
1180            );
1181        }
1182
1183        for pos in 0..36 {
1184            let mut input = [b'A'; 36];
1185            input[pos] = b'!';
1186            assert_eq!(
1187                decode_into_constant_time(&mut out, &input, Alphabet::Standard),
1188                Err(DecodeError::InvalidInput),
1189                "invalid char at position {pos} in 36-byte block"
1190            );
1191        }
1192    }
1193
1194    #[test]
1195    fn test_decode_non_canonical_trailing_bits() {
1196        let mut out = [0u8; 2];
1197
1198        // remaining == 2: bottom 4 bits of v1 must be zero
1199        for &(input, expected) in &[
1200            (b"/w==" as &[u8], Ok(())),
1201            (b"/x==" as &[u8], Err(DecodeError::InvalidInput)),
1202            (b"/y==" as &[u8], Err(DecodeError::InvalidInput)),
1203            (b"/z==" as &[u8], Err(DecodeError::InvalidInput)),
1204        ] {
1205            assert_eq!(
1206                decode_into_constant_time(&mut out, input, Alphabet::Standard).map(|_| ()),
1207                expected,
1208                "non-canonical (rem=2): {:?}",
1209                core::str::from_utf8(input)
1210            );
1211        }
1212
1213        // remaining == 3: bottom 2 bits of v2 must be zero
1214        for &(input, expected) in &[
1215            (b"iYU=" as &[u8], Ok(())),
1216            (b"iYV=" as &[u8], Err(DecodeError::InvalidInput)),
1217            (b"iYW=" as &[u8], Err(DecodeError::InvalidInput)),
1218            (b"iYX=" as &[u8], Err(DecodeError::InvalidInput)),
1219        ] {
1220            assert_eq!(
1221                decode_into_constant_time(&mut out, input, Alphabet::Standard).map(|_| ()),
1222                expected,
1223                "non-canonical (rem=3): {:?}",
1224                core::str::from_utf8(input)
1225            );
1226        }
1227    }
1228
1229    #[test]
1230    fn test_decode_rejects_interior_padding() {
1231        let mut out = [0u8; 4];
1232        assert_eq!(
1233            decode_into_constant_time(&mut out, b"A=AA", Alphabet::Standard),
1234            Err(DecodeError::InvalidInput)
1235        );
1236        assert_eq!(
1237            decode_into_constant_time(&mut out, b"AA=A", Alphabet::Standard),
1238            Err(DecodeError::InvalidInput)
1239        );
1240        assert_eq!(
1241            decode_into_constant_time(&mut out, b"AA==", Alphabet::StandardNoPadding),
1242            Err(DecodeError::InvalidPadding)
1243        );
1244    }
1245
1246    #[test]
1247    fn test_roundtrip_simd_boundary_sizes() {
1248        let mut data_buf = Vec::new();
1249        let mut enc_buf = Vec::new();
1250
1251        for &input_len in SIMD_BOUNDARY_SIZES {
1252            data_buf.clear();
1253            for b in 0..input_len {
1254                data_buf.push(b as u8);
1255            }
1256
1257            for alphabet in ALL_ALPHABETS {
1258                let padding = alphabet.is_padded();
1259                let encoded_length = encoded_length(input_len, padding).expect("encoded_len overflow");
1260                enc_buf.resize(encoded_length, 0);
1261                encode_into_constant_time(&mut enc_buf, &data_buf, *alphabet).unwrap();
1262
1263                let mut decoded = alloc::vec![0u8; input_len];
1264                assert_eq!(
1265                    decode_into_constant_time(&mut decoded, &enc_buf, *alphabet),
1266                    Ok(input_len),
1267                    "decode failed len={input_len} alphabet={alphabet:?}"
1268                );
1269                assert_eq!(&decoded, &data_buf, "roundtrip mismatch len={input_len} alphabet={alphabet:?}");
1270            }
1271        }
1272    }
1273
1274    #[test]
1275    fn test_const_encode() {
1276        const DATA: [u8; 3] = [0x66, 0x6F, 0x6F];
1277        const B64: [u8; 4] = encode_array::<4>(&DATA, Alphabet::Standard);
1278        assert_eq!(&B64, b"Zm9v");
1279
1280        const B64_URL: [u8; 4] = encode_array::<4>(&DATA, Alphabet::Url);
1281        assert_eq!(&B64_URL, b"Zm9v");
1282
1283        const B64_EMPTY: [u8; 0] = encode_array::<0>(b"", Alphabet::Standard);
1284        assert_eq!(B64_EMPTY.len(), 0);
1285
1286        const NOPAD_DATA: [u8; 2] = [0x66, 0x6F];
1287        const B64_NOPAD: [u8; 3] = encode_array::<3>(&NOPAD_DATA, Alphabet::StandardNoPadding);
1288        assert_eq!(&B64_NOPAD, b"Zm8");
1289    }
1290
1291    #[test]
1292    fn test_const_decode() {
1293        const RESULT: Result<[u8; 3], DecodeError> = decode_array::<3>(b"Zm9v", Alphabet::Standard);
1294        assert_eq!(RESULT.unwrap(), [0x66, 0x6F, 0x6F]);
1295
1296        const RESULT_EMPTY: Result<[u8; 0], DecodeError> = decode_array::<0>(b"", Alphabet::Standard);
1297        assert_eq!(RESULT_EMPTY.unwrap().len(), 0);
1298
1299        const RESULT_NOPAD: Result<[u8; 2], DecodeError> = decode_array::<2>(b"Zm8", Alphabet::StandardNoPadding);
1300        assert_eq!(RESULT_NOPAD.unwrap(), [0x66, 0x6F]);
1301    }
1302
1303    #[test]
1304    fn test_const_decode_error() {
1305        const ERR_INVALID: Result<[u8; 1], DecodeError> = decode_array::<1>(b"!!", Alphabet::Standard);
1306        assert_eq!(ERR_INVALID, Err(DecodeError::InvalidInput));
1307
1308        const ERR_SIZE: Result<[u8; 0], DecodeError> = decode_array::<0>(b"Zg==", Alphabet::Standard);
1309        assert_eq!(ERR_SIZE, Err(DecodeError::InvalidOutputLength));
1310    }
1311
1312    #[test]
1313    fn test_buffer_management() {
1314        let mut out = [0u8; 1];
1315        assert_eq!(
1316            encode_into(&mut out, b"hello", Alphabet::Standard),
1317            Err(EncodeError::InvalidOutputLength)
1318        );
1319
1320        let mut out = [0u8; 3];
1321        decode_into(&mut out, b"Zm9v", Alphabet::Standard).unwrap();
1322        assert_eq!(&out, b"foo");
1323
1324        let mut out = [0u8; 2];
1325        assert_eq!(
1326            decode_into(&mut out, b"Zm9v", Alphabet::Standard),
1327            Err(DecodeError::InvalidOutputLength)
1328        );
1329    }
1330
1331    #[test]
1332    fn test_display_error() {
1333        assert_eq!(format!("{}", DecodeError::InvalidInput), "invalid base64 character");
1334        assert_eq!(format!("{}", DecodeError::InvalidInputLength), "invalid base64 length");
1335        assert_eq!(format!("{}", DecodeError::InvalidPadding), "invalid base64 padding");
1336        assert_eq!(
1337            format!("{}", EncodeError::InvalidOutputLength),
1338            "output buffer size must be exactly equal to decoded_len(input)"
1339        );
1340        assert_eq!(format!("{}", EncodeError::OutputOverflow), "output length overflows usize::MAX");
1341    }
1342
1343    #[cfg(feature = "serde")]
1344    #[test]
1345    fn test_serde() {
1346        #[derive(::serde::Serialize, ::serde::Deserialize)]
1347        struct Data(#[serde(with = "crate::serde")] Vec<u8>);
1348
1349        let data = Data(b"hello world".to_vec());
1350        let json = ::serde_json::to_string(&data).unwrap();
1351        let deserialized: Data = ::serde_json::from_str(&json).unwrap();
1352        assert_eq!(deserialized.0, b"hello world");
1353    }
1354}