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