Skip to main content

base32/
base32.rs

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