Skip to main content

base32/
base32.rs

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