1use crate::char_index;
2use crate::is_valid_char;
3use crate::DecodeError;
4use crate::Input;
5use crate::Output;
6
7pub 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)); output.push(byte!(1, b, c, d)); output.push(byte!(2, d, e)); output.push(byte!(3, e, f, g)); output.push(byte!(4, g, h)); }
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)); 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)); 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)); 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)); Ok(())
77}
78
79pub 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
104pub 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}