base32_fs/
decode.rs

1use crate::char_index;
2use crate::is_valid_char;
3use crate::DecodeError;
4use crate::Input;
5use crate::Output;
6
7/// Decode `input` byte sequence using BASE32 encoding and write the resulting byte sequence to
8/// `output`.
9pub fn decode<I: Input<8>, O: Output + ?Sized>(
10    mut input: I,
11    output: &mut O,
12) -> Result<(), DecodeError> {
13    macro_rules! byte {
14        (0, $a: ident, $b: ident) => {
15            ($a << 3) | (($b >> 2) & 0b111)
16        };
17        (1, $b: ident, $c: ident, $d: ident) => {
18            (($b & 0b11) << 6) | ($c << 1) | ($d >> 4)
19        };
20        (2, $d: ident, $e: ident) => {
21            (($d & 0b1111) << 4) | ($e >> 1)
22        };
23        (3, $e: ident, $f: ident, $g: ident) => {
24            (($e & 0b1) << 7) | ($f << 2) | ($g >> 3)
25        };
26        (4, $g: ident, $h: ident) => {
27            (($g & 0b111) << 5) | $h
28        };
29    }
30    while let Some(chunk) = input.next_chunk() {
31        if !is_valid_chunk(chunk) {
32            return Err(DecodeError);
33        }
34        let a = char_index(chunk[0]);
35        let b = char_index(chunk[1]);
36        let c = char_index(chunk[2]);
37        let d = char_index(chunk[3]);
38        let e = char_index(chunk[4]);
39        let f = char_index(chunk[5]);
40        let g = char_index(chunk[6]);
41        let h = char_index(chunk[7]);
42        output.push(byte!(0, a, b)); // 5 + 3 bits
43        output.push(byte!(1, b, c, d)); // 2 + 5 + 1 bits
44        output.push(byte!(2, d, e)); // 4 + 4 bits
45        output.push(byte!(3, e, f, g)); // 1 + 5 + 2 bits
46        output.push(byte!(4, g, h)); // 3 + 5 bits
47    }
48    let remainder = input.remainder();
49    if !is_valid_remainder(remainder) {
50        return Err(DecodeError);
51    }
52    let remaining = remainder.len();
53    if remaining == 0 {
54        return Ok(());
55    }
56    let a = char_index(remainder[0]);
57    let b = remainder.get(1).copied().map(char_index).unwrap_or(0);
58    output.push(byte!(0, a, b)); // 5 + 3 bits
59    if remaining == 1 || remaining == 2 {
60        return Ok(());
61    }
62    let c = remainder.get(2).copied().map(char_index).unwrap_or(0);
63    let d = remainder.get(3).copied().map(char_index).unwrap_or(0);
64    output.push(byte!(1, b, c, d)); // 2 + 5 + 1 bits
65    if remaining == 3 || remaining == 4 {
66        return Ok(());
67    }
68    let e = remainder.get(4).copied().map(char_index).unwrap_or(0);
69    output.push(byte!(2, d, e)); // 4 + 4 bits
70    if remaining == 5 {
71        return Ok(());
72    }
73    let f = remainder.get(5).copied().map(char_index).unwrap_or(0);
74    let g = remainder.get(6).copied().map(char_index).unwrap_or(0);
75    output.push(byte!(3, e, f, g)); // 1 + 5 + 2 bits
76    Ok(())
77}
78
79/// Returns the length of the original byte sequence for the given BASE32-encoded string length.
80///
81/// Returns `None` if `input_len` is invalid (i.e. was not returned by
82/// [`encoded_len`](crate::encoded_len)).
83pub const fn decoded_len(input_len: usize) -> Option<usize> {
84    match remainder_decoded_len(input_len % 8) {
85        Some(remainder_len) => Some(input_len / 8 * 5 + remainder_len),
86        None => None,
87    }
88}
89
90const fn remainder_decoded_len(remainder_len: usize) -> Option<usize> {
91    debug_assert!(remainder_len < 8);
92    match remainder_len {
93        0 => Some(0),
94        1 => None,
95        2 => Some(1),
96        3 => None,
97        4 => Some(2),
98        5 => Some(3),
99        6 => None,
100        _ => Some(4),
101    }
102}
103
104/// Returns `true` if the `input` is a valid BASE32-encoded string.
105pub fn is_valid(input: &[u8]) -> bool {
106    decoded_len(input.len()).is_some() && is_valid_chunk(input)
107}
108
109fn is_valid_chunk(input: &[u8]) -> bool {
110    input.iter().copied().all(is_valid_char)
111}
112
113fn is_valid_remainder(input: &[u8]) -> bool {
114    is_valid_chunk(input) && remainder_decoded_len(input.len()).is_some()
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use arbtest::arbtest;
121
122    use crate::CHARS;
123
124    #[test]
125    fn test_is_valid_chunk() {
126        arbtest(|u| {
127            let chunk: [u8; 8] = u.arbitrary()?;
128            assert_eq!(
129                is_valid_chunk_slow(&chunk),
130                is_valid_chunk(&chunk),
131                "chunk = {chunk:?}"
132            );
133            Ok(())
134        });
135    }
136
137    fn is_valid_chunk_slow(input: &[u8]) -> bool {
138        input.iter().all(|b| CHARS.contains(b))
139    }
140}