1pub(crate) const ALPHABET_LEN: usize = 64;
9
10pub(super) const STANDARD_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
12 table: *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
13};
14pub(super) const URL_SAFE_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
16 table: *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
17};
18pub(super) const BCRYPT_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
19 table: *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
20};
21pub(super) const CRYPT_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
22 table: *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
23};
24pub(super) const PBKDF2_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
25 table: *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./",
26};
27pub const BINHEX_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
31 table: *b"!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr",
32};
33pub(super) const IMAP_MUTF7_ALPHABET: ValidatedAlphabet = ValidatedAlphabet {
34 table: *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
35};
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum ValidatedAlphabetError {
40 InvalidLength {
42 actual: usize,
44 },
45 InvalidByte {
47 index: usize,
49 byte: u8,
51 },
52 PaddingByte {
54 index: usize,
56 },
57 DuplicateByte {
59 first: usize,
61 second: usize,
63 byte: u8,
65 },
66}
67
68impl core::fmt::Display for ValidatedAlphabetError {
69 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70 match self {
71 Self::InvalidLength { actual } => {
72 write!(
73 formatter,
74 "base64 alphabet has length {actual}; expected {ALPHABET_LEN}"
75 )
76 }
77 Self::InvalidByte { index, byte } => {
78 write!(
79 formatter,
80 "invalid base64 alphabet byte 0x{byte:02x} at index {index}"
81 )
82 }
83 Self::PaddingByte { index } => {
84 write!(
85 formatter,
86 "base64 alphabet contains padding byte at index {index}"
87 )
88 }
89 Self::DuplicateByte {
90 first,
91 second,
92 byte,
93 } => write!(
94 formatter,
95 "base64 alphabet byte 0x{byte:02x} is duplicated at indexes \
96 {first} and {second}"
97 ),
98 }
99 }
100}
101
102#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
107#[repr(transparent)]
108pub struct ValidatedAlphabet {
109 table: [u8; ALPHABET_LEN],
110}
111
112impl ValidatedAlphabet {
113 pub const fn new(table: [u8; ALPHABET_LEN]) -> Result<Self, ValidatedAlphabetError> {
115 match validate_table(&table) {
116 Ok(()) => Ok(Self { table }),
117 Err(error) => Err(error),
118 }
119 }
120
121 pub const fn try_from_slice(bytes: &[u8]) -> Result<Self, ValidatedAlphabetError> {
123 if bytes.len() != ALPHABET_LEN {
124 return Err(ValidatedAlphabetError::InvalidLength {
125 actual: bytes.len(),
126 });
127 }
128
129 let mut table = [0u8; ALPHABET_LEN];
130 let mut index = 0;
131 while index < ALPHABET_LEN {
132 table[index] = bytes[index];
133 index += 1;
134 }
135 Self::new(table)
136 }
137
138 #[must_use]
140 pub const fn as_array(&self) -> &[u8; ALPHABET_LEN] {
141 &self.table
142 }
143
144 #[allow(clippy::cast_lossless)]
146 #[must_use]
147 pub const fn encode_value(&self, value: u8) -> Option<u8> {
148 if value < 64 {
149 Some(self.table[value as usize])
150 } else {
151 None
152 }
153 }
154
155 #[must_use]
157 pub const fn decode_byte(&self, byte: u8) -> Option<u8> {
158 let mut index = 0;
159 let mut candidate = 0u8;
160 while index < ALPHABET_LEN {
161 if self.table[index] == byte {
162 return Some(candidate);
163 }
164 index += 1;
165 candidate += 1;
166 }
167 None
168 }
169}
170
171impl TryFrom<[u8; ALPHABET_LEN]> for ValidatedAlphabet {
172 type Error = ValidatedAlphabetError;
173
174 fn try_from(table: [u8; ALPHABET_LEN]) -> Result<Self, Self::Error> {
175 Self::new(table)
176 }
177}
178
179impl TryFrom<&[u8]> for ValidatedAlphabet {
180 type Error = ValidatedAlphabetError;
181
182 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
183 Self::try_from_slice(bytes)
184 }
185}
186
187const fn validate_table(table: &[u8; ALPHABET_LEN]) -> Result<(), ValidatedAlphabetError> {
188 let mut index = 0;
189 while index < ALPHABET_LEN {
190 match validate_position(table, index) {
191 Ok(()) => {}
192 Err(error) => return Err(error),
193 }
194 index += 1;
195 }
196 Ok(())
197}
198
199const fn validate_position(
200 table: &[u8; ALPHABET_LEN],
201 index: usize,
202) -> Result<(), ValidatedAlphabetError> {
203 let byte = table[index];
204 if byte < 0x21 || byte > 0x7e {
205 return Err(ValidatedAlphabetError::InvalidByte { index, byte });
206 }
207 if byte == b'=' {
208 return Err(ValidatedAlphabetError::PaddingByte { index });
209 }
210
211 let mut duplicate = index + 1;
212 while duplicate < ALPHABET_LEN {
213 if table[duplicate] == byte {
214 return Err(ValidatedAlphabetError::DuplicateByte {
215 first: index,
216 second: duplicate,
217 byte,
218 });
219 }
220 duplicate += 1;
221 }
222 Ok(())
223}
224
225#[cfg(kani)]
227pub(crate) fn validate_position_for_proof(
228 table: &[u8; ALPHABET_LEN],
229 index: usize,
230) -> Result<(), ValidatedAlphabetError> {
231 validate_position(table, index)
232}