1const ALPHABET: &[u8; 32] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
10
11pub fn encode(bytes: &[u8]) -> String {
24 if bytes.is_empty() {
25 return String::new();
26 }
27 let mut result = String::with_capacity((bytes.len() * 8).div_ceil(5));
28 let mut buffer: u64 = 0;
29 let mut bits_left = 0;
30
31 for &byte in bytes {
32 buffer = (buffer << 8) | byte as u64;
33 bits_left += 8;
34 while bits_left >= 5 {
35 bits_left -= 5;
36 let idx = ((buffer >> bits_left) & 0x1F) as usize;
37 result.push(ALPHABET[idx] as char);
38 }
39 }
40 if bits_left > 0 {
41 let idx = ((buffer << (5 - bits_left)) & 0x1F) as usize;
42 result.push(ALPHABET[idx] as char);
43 }
44 result
45}
46
47pub fn decode(encoded: &str) -> crate::Result<Vec<u8>> {
69 if encoded.is_empty() {
70 return Ok(Vec::new());
71 }
72 let mut result = Vec::with_capacity(encoded.len() * 5 / 8);
73 let mut buffer: u64 = 0;
74 let mut bits_left = 0;
75
76 for ch in encoded.chars() {
77 let val = decode_char(ch.to_ascii_uppercase())?;
78 buffer = (buffer << 5) | val as u64;
79 bits_left += 5;
80 if bits_left >= 8 {
81 bits_left -= 8;
82 result.push((buffer >> bits_left) as u8);
83 }
84 }
85 Ok(result)
86}
87
88fn decode_char(ch: char) -> crate::Result<u8> {
89 match ch {
90 'A'..='Z' => Ok(ch as u8 - b'A'),
91 '2'..='7' => Ok(ch as u8 - b'2' + 26),
92 _ => Err(crate::Error::bad_request(format!(
93 "invalid base32 character: '{ch}'"
94 ))),
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn encode_empty() {
104 assert_eq!(encode(b""), "");
105 }
106
107 #[test]
108 fn encode_rfc4648_vectors() {
109 assert_eq!(encode(b"f"), "MY");
111 assert_eq!(encode(b"fo"), "MZXQ");
112 assert_eq!(encode(b"foo"), "MZXW6");
113 assert_eq!(encode(b"foob"), "MZXW6YQ");
114 assert_eq!(encode(b"fooba"), "MZXW6YTB");
115 assert_eq!(encode(b"foobar"), "MZXW6YTBOI");
116 }
117
118 #[test]
119 fn decode_rfc4648_vectors() {
120 assert_eq!(decode("MY").unwrap(), b"f");
121 assert_eq!(decode("MZXQ").unwrap(), b"fo");
122 assert_eq!(decode("MZXW6").unwrap(), b"foo");
123 assert_eq!(decode("MZXW6YQ").unwrap(), b"foob");
124 assert_eq!(decode("MZXW6YTB").unwrap(), b"fooba");
125 assert_eq!(decode("MZXW6YTBOI").unwrap(), b"foobar");
126 }
127
128 #[test]
129 fn decode_case_insensitive() {
130 assert_eq!(decode("mzxw6").unwrap(), b"foo");
131 assert_eq!(decode("Mzxw6").unwrap(), b"foo");
132 }
133
134 #[test]
135 fn roundtrip_random_bytes() {
136 let bytes: Vec<u8> = (0..=255).collect();
137 let encoded = encode(&bytes);
138 let decoded = decode(&encoded).unwrap();
139 assert_eq!(decoded, bytes);
140 }
141
142 #[test]
143 fn decode_invalid_char() {
144 assert!(decode("MZXW1").is_err()); }
146
147 #[test]
148 fn encode_20_byte_totp_secret() {
149 let secret = [0u8; 20];
150 let encoded = encode(&secret);
151 assert_eq!(encoded.len(), 32); let decoded = decode(&encoded).unwrap();
153 assert_eq!(decoded, secret);
154 }
155}