Skip to main content

base64/
base64.rs

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