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