1pub mod errors;
2
3use errors::Base24Error;
4use std::collections::BTreeMap;
5
6type Result<T> = std::result::Result<T, Base24Error>;
7
8const ALPHABET: &str = "ZAC2B3EF4GH5TK67P8RS9WXY";
9const ALPHABET_LENGTH: usize = ALPHABET.len();
10
11struct Base24 {
12 encode_map: BTreeMap<usize, char>,
13 decode_map: BTreeMap<char, usize>,
14}
15
16impl Base24 {
17 pub fn new() -> Base24 {
18 Base24 {
19 encode_map: ALPHABET.char_indices().collect(),
20 decode_map: ALPHABET
21 .char_indices()
22 .map(|(idx, kar)| (kar, idx))
23 .chain(
24 ALPHABET
25 .to_lowercase()
26 .char_indices()
27 .map(|(idx, kar)| (kar, idx)),
28 )
29 .collect(),
30 }
31 }
32
33 pub fn encode(&self, data: &[u8]) -> Result<String> {
34 if data.len() % 4 != 0 {
35 return Err(Base24Error::EncodeInputLengthInvalid);
36 }
37
38 let res = data
39 .chunks(4)
40 .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
41 .map(|mut value| {
42 (0..7)
43 .into_iter()
44 .map(|_| {
45 let idx: usize = value as usize % ALPHABET_LENGTH;
46 value = value / ALPHABET_LENGTH as u32;
47
48 self.encode_map[&idx].clone()
49 })
50 .collect::<Vec<char>>()
51 .iter()
52 .rev()
53 .collect::<String>()
54 })
55 .collect();
56
57 Ok(res)
58 }
59
60 pub fn decode(&self, data: &str) -> Result<Vec<u8>> {
61 let char_vec: Vec<char> = data.chars().collect();
62
63 if char_vec.len() % 7 != 0 {
64 return Err(Base24Error::DecodeInputLengthInvalid);
65 }
66
67 for kar in &char_vec {
69 if !self.decode_map.contains_key(kar) {
70 return Err(Base24Error::DecodeUnsupportedCharacter(kar.clone()));
71 }
72 }
73
74 let res = char_vec
75 .chunks(7)
76 .map(|chunks| {
77 chunks.iter().fold(0u32, |acc, kar| {
78 let idx = self.decode_map.get(kar).unwrap_or_else(|| {
79 unreachable!("We checked for invalid chars before. Something is wrong!")
80 });
81
82 (ALPHABET_LENGTH as u32) * acc + (*idx as u32)
83 })
84 })
85 .flat_map(|value| value.to_be_bytes().to_vec())
86 .collect();
87
88 Ok(res)
89 }
90}
91
92pub fn encode(data: &[u8]) -> Result<String> {
93 Base24::new().encode(data)
94}
95
96pub fn decode(data: &str) -> Result<Vec<u8>> {
97 Base24::new().decode(data)
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_all() {
106 let values: Vec<(Vec<u8>, _)> = vec![
108 ("00000000", "ZZZZZZZ"),
109 ("000000000000000000000000", "ZZZZZZZZZZZZZZZZZZZZZ"),
110 ("00000001", "ZZZZZZA"),
111 ("000000010000000100000001", "ZZZZZZAZZZZZZAZZZZZZA"),
112 ("00000010", "ZZZZZZP"),
113 ("00000030", "ZZZZZCZ"),
114 ("88553311", "5YEATXA"),
115 ("FFFFFFFF", "X5GGBH7"),
116 ("FFFFFFFFFFFFFFFFFFFFFFFF", "X5GGBH7X5GGBH7X5GGBH7"),
117 ("FFFFFFFFFFFFFFFFFFFFFFFF", "x5ggbh7x5ggbh7x5ggbh7"),
118 ("1234567887654321", "A64KHWZ5WEPAGG"),
119 ("1234567887654321", "a64khwz5wepagg"),
120 ("FF0001FF001101FF01023399", "XGES63FZZ247C7ZC2ZA6G"),
121 ("FF0001FF001101FF01023399", "xges63fzz247c7zc2za6g"),
122 (
123 "25896984125478546598563251452658",
124 "2FC28KTA66WRST4XAHRRCF237S8Z",
125 ),
126 (
127 "25896984125478546598563251452658",
128 "2fc28kta66wrst4xahrrcf237s8z",
129 ),
130 ("00000001", "ZZZZZZA"),
131 ("00000002", "ZZZZZZC"),
132 ("00000004", "ZZZZZZB"),
133 ("00000008", "ZZZZZZ4"),
134 ("00000010", "ZZZZZZP"),
135 ("00000020", "ZZZZZA4"),
136 ("00000040", "ZZZZZCP"),
137 ("00000080", "ZZZZZ34"),
138 ("00000100", "ZZZZZHP"),
139 ("00000200", "ZZZZZW4"),
140 ("00000400", "ZZZZARP"),
141 ("00000800", "ZZZZ2K4"),
142 ("00001000", "ZZZZFCP"),
143 ("00002000", "ZZZZ634"),
144 ("00004000", "ZZZABHP"),
145 ("00008000", "ZZZC4W4"),
146 ("00010000", "ZZZB8RP"),
147 ("00020000", "ZZZG5K4"),
148 ("00040000", "ZZZRYCP"),
149 ("00080000", "ZZAKX34"),
150 ("00100000", "ZZ229HP"),
151 ("00200000", "ZZEFPW4"),
152 ("00400000", "ZZT7GRP"),
153 ("00800000", "ZAAESK4"),
154 ("01000000", "ZCCK7CP"),
155 ("02000000", "ZB32E34"),
156 ("04000000", "Z4HETHP"),
157 ("08000000", "ZP9KZW4"),
158 ("10000000", "AG8CARP"),
159 ("20000000", "CSHB2K4"),
160 ("40000000", "3694FCP"),
161 ("80000000", "53PP634"),
162 ]
163 .iter()
164 .map(|(str_data, b24_str)| {
165 let char_vec: Vec<_> = str_data.chars().collect();
166
167 let data: Vec<u8> = char_vec
168 .chunks(2)
169 .map(|chunk| {
170 let byte_str = format!("{}{}", chunk[0], chunk[1]);
171
172 u8::from_str_radix(&byte_str, 16)
173 })
174 .filter_map(|res| res.ok())
175 .collect();
176
177 (data, b24_str.to_string())
178 })
179 .collect();
180
181 for (data, b24_str) in values {
182 let decoded = decode(&b24_str).expect("error during test decode");
183 assert_eq!(decoded, data);
184 assert_eq!(
185 encode(&decoded).expect("error during test encode"),
186 b24_str.to_uppercase()
187 );
188 }
189 }
190
191 #[test]
192 fn random_test() {
193 use rand::distributions::Standard;
194 use rand::{thread_rng, Rng};
195
196 let rng = thread_rng();
197
198 for _ in 0..100 {
199 let original_data: Vec<u8> = rng.sample_iter(Standard).take(64).collect();
200
201 let encoded_data = encode(&original_data).expect("error during test encode");
202 let decoded_data = decode(&encoded_data).expect("error during test decode");
203
204 assert_eq!(decoded_data, original_data);
205 }
206 }
207
208 #[test]
209 fn test_failures() {
210 let test_data: [u8; 5] = [1, 2, 3, 4, 5];
211
212 assert_eq!(
213 encode(&test_data),
214 Err(Base24Error::EncodeInputLengthInvalid)
215 );
216
217 let test_data: &str = "ZZZ";
218
219 assert_eq!(
220 decode(&test_data),
221 Err(Base24Error::DecodeInputLengthInvalid)
222 );
223
224 let test_data: &str = "ZZZZZZO";
225
226 assert_eq!(
227 decode(&test_data),
228 Err(Base24Error::DecodeUnsupportedCharacter('O'))
229 );
230
231 let test_data: &str = "ABC😘EFG";
232
233 assert_eq!(
234 decode(&test_data),
235 Err(Base24Error::DecodeUnsupportedCharacter('😘'))
236 );
237 }
238}