1use core::{array, convert, fmt};
4#[cfg(any(feature = "std", test))]
5use std::error;
6
7const ALPHABET_LEN: usize = 64;
9
10pub(crate) const PADDING_SYMBOL: Symbol = Symbol(b'=');
12
13#[derive(Clone, Eq, PartialEq)]
55pub struct Alphabet {
56 pub(crate) symbols: [u8; ALPHABET_LEN],
58 pub(crate) padding: Symbol,
59}
60
61impl Alphabet {
62 const fn from_str_unchecked(alphabet: &str, padding: Symbol) -> Self {
65 let mut symbols = [0_u8; ALPHABET_LEN];
66 let source_bytes = alphabet.as_bytes();
67
68 let mut index = 0;
70 while index < ALPHABET_LEN {
71 symbols[index] = source_bytes[index];
72 index += 1;
73 }
74
75 Self { symbols, padding }
76 }
77
78 pub const fn new(alphabet: &str) -> Result<Self, ParseAlphabetError> {
85 Self::new_with_padding(alphabet, PADDING_SYMBOL)
86 }
87
88 pub const fn new_with_padding(
95 alphabet: &str,
96 padding: Symbol,
97 ) -> Result<Self, ParseAlphabetError> {
98 let bytes = alphabet.as_bytes();
99 if bytes.len() != ALPHABET_LEN {
100 return Err(ParseAlphabetError::InvalidLength);
101 }
102
103 {
104 let mut index = 0;
105 while index < ALPHABET_LEN {
106 let byte = bytes[index];
107
108 if !is_valid_b64_symbol(byte) {
109 return Err(ParseAlphabetError::UnprintableByte(byte));
110 }
111 if byte == padding.as_u8() {
112 return Err(ParseAlphabetError::ReservedByte(byte));
113 }
114
115 let mut probe_index = 0;
120 while probe_index < ALPHABET_LEN {
121 if probe_index != index && byte == bytes[probe_index] {
122 return Err(ParseAlphabetError::DuplicatedByte(byte));
123 }
124
125 probe_index += 1;
126 }
127
128 index += 1;
129 }
130 }
131
132 Ok(Self::from_str_unchecked(alphabet, padding))
133 }
134
135 #[must_use]
137 pub fn as_str(&self) -> &str {
138 core::str::from_utf8(&self.symbols).unwrap()
139 }
140
141 pub fn symbols(&self) -> [Symbol; ALPHABET_LEN] {
143 array::from_fn(|i| {
144 Symbol(self.symbols[i])
146 })
147 }
148
149 pub fn padding(&self) -> Symbol {
151 self.padding
152 }
153}
154
155impl fmt::Debug for Alphabet {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 write!(
158 f,
159 "Alphabet {{ symbols: {:?}, padding: '{:?}' }}",
160 self.as_str(),
161 self.padding
162 )
163 }
164}
165
166#[derive(Clone, Copy, PartialEq, Eq)]
171pub struct Symbol(u8);
172
173impl Symbol {
174 pub const fn new(symbol: u8) -> Option<Self> {
176 if is_valid_b64_symbol(symbol) {
177 Some(Self(symbol))
178 } else {
179 None
180 }
181 }
182
183 pub const fn as_u8(&self) -> u8 {
185 self.0
186 }
187
188 pub fn as_char(&self) -> char {
190 char::from(self.0)
192 }
193}
194
195impl From<Symbol> for u8 {
196 fn from(value: Symbol) -> Self {
197 value.0
198 }
199}
200
201impl fmt::Debug for Symbol {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(f, "{}", self.as_char())
204 }
205}
206
207pub(crate) const fn is_valid_b64_symbol(byte: u8) -> bool {
210 byte >= 32_u8 && byte <= 126_u8
211}
212
213impl convert::TryFrom<&str> for Alphabet {
214 type Error = ParseAlphabetError;
215
216 fn try_from(value: &str) -> Result<Self, Self::Error> {
217 Self::new(value)
218 }
219}
220
221#[derive(Debug, Eq, PartialEq)]
223pub enum ParseAlphabetError {
224 InvalidLength,
226 DuplicatedByte(u8),
228 UnprintableByte(u8),
230 ReservedByte(u8),
232}
233
234impl fmt::Display for ParseAlphabetError {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 match self {
237 Self::InvalidLength => write!(f, "Invalid length - must be 64 bytes"),
238 Self::DuplicatedByte(b) => write!(f, "Duplicated byte: {:#04x}", b),
239 Self::UnprintableByte(b) => write!(f, "Unprintable byte: {:#04x}", b),
240 Self::ReservedByte(b) => write!(f, "Reserved byte: {:#04x}", b),
241 }
242 }
243}
244
245#[cfg(any(feature = "std", test))]
246impl error::Error for ParseAlphabetError {}
247
248pub const STANDARD: Alphabet = Alphabet::from_str_unchecked(
252 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
253 PADDING_SYMBOL,
254);
255
256pub const URL_SAFE: Alphabet = Alphabet::from_str_unchecked(
260 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
261 PADDING_SYMBOL,
262);
263
264pub const CRYPT: Alphabet = Alphabet::from_str_unchecked(
268 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
269 PADDING_SYMBOL,
270);
271
272pub const BCRYPT: Alphabet = Alphabet::from_str_unchecked(
274 "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
275 PADDING_SYMBOL,
276);
277
278pub const IMAP_MUTF7: Alphabet = Alphabet::from_str_unchecked(
282 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
283 PADDING_SYMBOL,
284);
285
286pub const BIN_HEX: Alphabet = Alphabet::from_str_unchecked(
290 "!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr",
291 PADDING_SYMBOL,
292);
293
294#[cfg(test)]
295mod tests {
296 use crate::alphabet::*;
297 use core::convert::TryFrom as _;
298
299 #[test]
300 fn detects_duplicate_start() {
301 assert_eq!(
302 ParseAlphabetError::DuplicatedByte(b'A'),
303 Alphabet::new("AACDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
304 .unwrap_err()
305 );
306 }
307
308 #[test]
309 fn detects_duplicate_end() {
310 assert_eq!(
311 ParseAlphabetError::DuplicatedByte(b'/'),
312 Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789//")
313 .unwrap_err()
314 );
315 }
316
317 #[test]
318 fn detects_duplicate_middle() {
319 assert_eq!(
320 ParseAlphabetError::DuplicatedByte(b'Z'),
321 Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/")
322 .unwrap_err()
323 );
324 }
325
326 #[test]
327 fn detects_length() {
328 assert_eq!(
329 ParseAlphabetError::InvalidLength,
330 Alphabet::new(
331 "xxxxxxxxxABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/",
332 )
333 .unwrap_err()
334 );
335 }
336
337 #[test]
338 fn detects_padding() {
339 assert_eq!(
340 ParseAlphabetError::ReservedByte(b'='),
341 Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=")
342 .unwrap_err()
343 );
344 }
345
346 #[test]
347 fn detects_unprintable() {
348 assert_eq!(
350 ParseAlphabetError::UnprintableByte(0xc),
351 Alphabet::new("\x0cBCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
352 .unwrap_err()
353 );
354 }
355
356 #[test]
357 fn same_as_unchecked() {
358 assert_eq!(
359 STANDARD,
360 Alphabet::try_from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
361 .unwrap()
362 );
363 }
364
365 #[test]
366 fn str_same_as_input() {
367 let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
368 let a = Alphabet::try_from(alphabet).unwrap();
369 assert_eq!(alphabet, a.as_str())
370 }
371
372 #[test]
373 fn symbol_matches_char_for_all_valid_symbols() {
374 for symbol in (0..=u8::MAX).filter_map(Symbol::new) {
375 let bytes = &[symbol.as_u8()];
377 let s = std::str::from_utf8(bytes).unwrap();
378 assert_eq!(1, s.chars().count());
379
380 let char = s.chars().next().unwrap();
381 assert_eq!(char, symbol.as_char());
382 }
383 }
384
385 #[test]
386 fn alphabet_debug() {
387 assert_eq!(
388 r##"Alphabet { symbols: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", padding: '=' }"##,
389 format!("{STANDARD:?}")
390 );
391 }
392
393 #[test]
394 fn alphabet_symbols() {
395 assert_eq!(
396 STANDARD.as_str(),
397 STANDARD
398 .symbols()
399 .iter()
400 .map(|s| s.as_char())
401 .collect::<String>()
402 );
403 }
404}