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