simple_base64/
alphabet.rs1use crate::PAD_BYTE;
4use core::{convert, fmt};
5#[cfg(any(feature = "std", test))]
6use std::error;
7
8const ALPHABET_SIZE: usize = 64;
9
10#[derive(Clone, Debug, Eq, PartialEq)]
54pub struct Alphabet {
55 pub(crate) symbols: [u8; ALPHABET_SIZE],
56}
57
58impl Alphabet {
59 const fn from_str_unchecked(alphabet: &str) -> Self {
62 let mut symbols = [0_u8; ALPHABET_SIZE];
63 let source_bytes = alphabet.as_bytes();
64
65 let mut index = 0;
67 while index < ALPHABET_SIZE {
68 symbols[index] = source_bytes[index];
69 index += 1;
70 }
71
72 Self { symbols }
73 }
74
75 pub const fn new(alphabet: &str) -> Result<Self, ParseAlphabetError> {
79 let bytes = alphabet.as_bytes();
80 if bytes.len() != ALPHABET_SIZE {
81 return Err(ParseAlphabetError::InvalidLength);
82 }
83
84 {
85 let mut index = 0;
86 while index < ALPHABET_SIZE {
87 let byte = bytes[index];
88
89 if !(byte >= 32_u8 && byte <= 126_u8) {
92 return Err(ParseAlphabetError::UnprintableByte(byte));
93 }
94 if byte == PAD_BYTE {
96 return Err(ParseAlphabetError::ReservedByte(byte));
97 }
98
99 let mut probe_index = 0;
104 while probe_index < ALPHABET_SIZE {
105 if probe_index == index {
106 probe_index += 1;
107 continue;
108 }
109
110 let probe_byte = bytes[probe_index];
111
112 if byte == probe_byte {
113 return Err(ParseAlphabetError::DuplicatedByte(byte));
114 }
115
116 probe_index += 1;
117 }
118
119 index += 1;
120 }
121 }
122
123 Ok(Self::from_str_unchecked(alphabet))
124 }
125}
126
127impl convert::TryFrom<&str> for Alphabet {
128 type Error = ParseAlphabetError;
129
130 fn try_from(value: &str) -> Result<Self, Self::Error> {
131 Self::new(value)
132 }
133}
134
135#[derive(Debug, Eq, PartialEq)]
137pub enum ParseAlphabetError {
138 InvalidLength,
140 DuplicatedByte(u8),
142 UnprintableByte(u8),
144 ReservedByte(u8),
146}
147
148impl fmt::Display for ParseAlphabetError {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 match self {
151 Self::InvalidLength => write!(f, "Invalid length - must be 64 bytes"),
152 Self::DuplicatedByte(b) => write!(f, "Duplicated byte: {:#04x}", b),
153 Self::UnprintableByte(b) => write!(f, "Unprintable byte: {:#04x}", b),
154 Self::ReservedByte(b) => write!(f, "Reserved byte: {:#04x}", b),
155 }
156 }
157}
158
159#[cfg(any(feature = "std", test))]
160impl error::Error for ParseAlphabetError {}
161
162pub const STANDARD: Alphabet = Alphabet::from_str_unchecked(
166 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
167);
168
169pub const URL_SAFE: Alphabet = Alphabet::from_str_unchecked(
173 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
174);
175
176pub const CRYPT: Alphabet = Alphabet::from_str_unchecked(
180 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
181);
182
183pub const BCRYPT: Alphabet = Alphabet::from_str_unchecked(
185 "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
186);
187
188pub const IMAP_MUTF7: Alphabet = Alphabet::from_str_unchecked(
192 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
193);
194
195pub const BIN_HEX: Alphabet = Alphabet::from_str_unchecked(
199 "!\"#$%&'()*+,-0123456789@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdehijklmpqr",
200);
201
202#[cfg(test)]
203mod tests {
204 use crate::alphabet::*;
205 use core::convert::TryFrom as _;
206
207 #[test]
208 fn detects_duplicate_start() {
209 assert_eq!(
210 ParseAlphabetError::DuplicatedByte(b'A'),
211 Alphabet::new("AACDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
212 .unwrap_err()
213 );
214 }
215
216 #[test]
217 fn detects_duplicate_end() {
218 assert_eq!(
219 ParseAlphabetError::DuplicatedByte(b'/'),
220 Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789//")
221 .unwrap_err()
222 );
223 }
224
225 #[test]
226 fn detects_duplicate_middle() {
227 assert_eq!(
228 ParseAlphabetError::DuplicatedByte(b'Z'),
229 Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/")
230 .unwrap_err()
231 );
232 }
233
234 #[test]
235 fn detects_length() {
236 assert_eq!(
237 ParseAlphabetError::InvalidLength,
238 Alphabet::new(
239 "xxxxxxxxxABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/",
240 )
241 .unwrap_err()
242 );
243 }
244
245 #[test]
246 fn detects_padding() {
247 assert_eq!(
248 ParseAlphabetError::ReservedByte(b'='),
249 Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=")
250 .unwrap_err()
251 );
252 }
253
254 #[test]
255 fn detects_unprintable() {
256 assert_eq!(
258 ParseAlphabetError::UnprintableByte(0xc),
259 Alphabet::new("\x0cBCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
260 .unwrap_err()
261 );
262 }
263
264 #[test]
265 fn same_as_unchecked() {
266 assert_eq!(
267 STANDARD,
268 Alphabet::try_from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
269 .unwrap()
270 );
271 }
272}