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