base64_ng/alphabet.rs
1//! Base64 alphabets and custom alphabet validation.
2
3use crate::{ct_mask_eq_u8, ct_mask_lt_u8};
4
5/// Alphabet validation error.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum AlphabetError {
8 /// The alphabet contains a non-visible-ASCII byte.
9 InvalidByte {
10 /// Byte index in the alphabet table.
11 index: usize,
12 /// Invalid byte value.
13 byte: u8,
14 },
15 /// The alphabet contains the padding byte `=`.
16 PaddingByte {
17 /// Byte index in the alphabet table.
18 index: usize,
19 },
20 /// The alphabet maps more than one value to the same byte.
21 DuplicateByte {
22 /// First byte index.
23 first: usize,
24 /// Second byte index.
25 second: usize,
26 /// Duplicated byte value.
27 byte: u8,
28 },
29}
30
31impl core::fmt::Display for AlphabetError {
32 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33 match self {
34 Self::InvalidByte { index, byte } => {
35 write!(
36 f,
37 "invalid base64 alphabet byte 0x{byte:02x} at index {index}"
38 )
39 }
40 Self::PaddingByte { index } => {
41 write!(f, "base64 alphabet contains padding byte at index {index}")
42 }
43 Self::DuplicateByte {
44 first,
45 second,
46 byte,
47 } => write!(
48 f,
49 "base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
50 ),
51 }
52 }
53}
54
55#[cfg(feature = "std")]
56impl std::error::Error for AlphabetError {}
57
58/// Defines a custom [`Alphabet`] from a 64-byte string literal.
59///
60/// The generated alphabet is validated at compile time with
61/// [`validate_alphabet`]. Invalid, duplicate, or padding bytes fail the build
62/// instead of creating a malformed runtime profile.
63///
64/// The generated implementation uses the conservative default
65/// [`Alphabet::encode`] behavior: every emitted Base64 byte performs a fixed
66/// 64-entry scan to avoid secret-indexed table lookups. Built-in alphabets use
67/// optimized arithmetic mappers.
68///
69/// The generated [`Alphabet::decode`] implementation delegates to
70/// [`decode_alphabet_byte`]. The constant-time-oriented [`ct`](crate::ct)
71/// module scans the generated `ENCODE` table directly and does not call the
72/// generated `decode` method.
73///
74/// # Examples
75///
76/// ```
77/// base64_ng::define_alphabet! {
78/// struct DotSlash = b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
79/// }
80///
81/// let engine = base64_ng::Engine::<DotSlash, false>::new();
82/// let mut encoded = [0u8; 4];
83/// let written = engine.encode_slice(&[0xff, 0xff, 0xff], &mut encoded).unwrap();
84/// assert_eq!(&encoded[..written], b"9999");
85/// ```
86///
87/// Invalid alphabets fail during compilation:
88///
89/// ```compile_fail
90/// base64_ng::define_alphabet! {
91/// struct Bad = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
92/// }
93/// ```
94#[macro_export]
95macro_rules! define_alphabet {
96 ($(#[$meta:meta])* $vis:vis struct $name:ident = $encode:expr;) => {
97 $(#[$meta])*
98 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
99 $vis struct $name;
100
101 impl $crate::Alphabet for $name {
102 const ENCODE: [u8; 64] = *$encode;
103
104 #[inline]
105 fn decode(byte: u8) -> Option<u8> {
106 $crate::decode_alphabet_byte(byte, &Self::ENCODE)
107 }
108 }
109
110 const _: [(); 1] = [(); match $crate::validate_alphabet(
111 &<$name as $crate::Alphabet>::ENCODE,
112 ) {
113 Ok(()) => 1,
114 Err(_) => 0,
115 }];
116 };
117}
118
119/// Validates a 64-byte Base64 alphabet table.
120///
121/// A valid alphabet must contain exactly 64 unique visible ASCII bytes and must
122/// not contain the padding byte `=`.
123///
124/// # Examples
125///
126/// ```
127/// use base64_ng::{Alphabet, Standard, validate_alphabet};
128///
129/// validate_alphabet(&Standard::ENCODE).unwrap();
130/// ```
131pub const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
132 let mut index = 0;
133 while index < encode.len() {
134 let byte = encode[index];
135 if !is_visible_ascii(byte) {
136 return Err(AlphabetError::InvalidByte { index, byte });
137 }
138 if byte == b'=' {
139 return Err(AlphabetError::PaddingByte { index });
140 }
141
142 let mut duplicate = index + 1;
143 while duplicate < encode.len() {
144 if encode[duplicate] == byte {
145 return Err(AlphabetError::DuplicateByte {
146 first: index,
147 second: duplicate,
148 byte,
149 });
150 }
151 duplicate += 1;
152 }
153
154 index += 1;
155 }
156
157 Ok(())
158}
159
160/// Decodes one byte by scanning a caller-provided alphabet table.
161///
162/// This helper is intended for custom [`Alphabet`] implementations. Validate
163/// the table with [`validate_alphabet`] before trusting the alphabet in a
164/// protocol or public API. The scan always visits all 64 entries before
165/// returning so the match position does not create an early-return timing
166/// signal in the source-level implementation.
167///
168/// # Security
169///
170/// This helper is part of the normal strict decoder path, not the
171/// constant-time-oriented [`ct`](crate::ct) module. It is a `const fn` so it
172/// does not use the optimizer barriers, volatile accumulator reads, or
173/// generated-code evidence hooks used by the private `ct` scanner. Do not rely
174/// on this helper for military or cryptographic constant-time guarantees under
175/// LTO or future compiler rewrites. For secret-bearing custom alphabets, use
176/// [`Engine::ct_decoder`](crate::Engine::ct_decoder) or the [`ct`](crate::ct)
177/// module, which scans [`Alphabet::ENCODE`] directly and does not call
178/// [`Alphabet::decode`].
179///
180/// # Examples
181///
182/// ```
183/// use base64_ng::{Alphabet, decode_alphabet_byte};
184///
185/// struct DotSlash;
186///
187/// impl Alphabet for DotSlash {
188/// const ENCODE: [u8; 64] =
189/// *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
190///
191/// fn decode(byte: u8) -> Option<u8> {
192/// decode_alphabet_byte(byte, &Self::ENCODE)
193/// }
194/// }
195///
196/// assert_eq!(DotSlash::decode(b'.'), Some(0));
197/// assert_eq!(DotSlash::decode(b'9'), Some(63));
198/// ```
199#[must_use]
200pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
201 let mut index = 0;
202 let mut candidate = 0;
203 let mut decoded = 0;
204 let mut valid = 0;
205 while index < encode.len() {
206 let matches = ct_mask_eq_u8(byte, encode[index]);
207 decoded |= candidate & matches;
208 valid |= matches;
209 index += 1;
210 candidate += 1;
211 }
212
213 if valid == 0 { None } else { Some(decoded) }
214}
215
216/// A Base64 alphabet.
217///
218/// # Security
219///
220/// The default [`Alphabet::encode`] implementation is constant-time-oriented:
221/// it scans all 64 alphabet entries instead of using `ENCODE[value as usize]`.
222/// Direct callers that override `encode` with a table lookup make those direct
223/// calls timing-sensitive with respect to the selected 6-bit value. Public
224/// [`Engine`](crate::Engine) encoding does not call this overridable method:
225/// [`Alphabet::ENCODE`] is its sole output definition for const, scalar, SIMD,
226/// wrapped, and in-place surfaces.
227///
228/// The normal strict decode path calls [`Alphabet::decode`] and is not a
229/// constant-time decoder. The [`ct`](crate::ct) module does not call
230/// [`Alphabet::decode`]; it scans [`Alphabet::ENCODE`] directly with its own
231/// fixed 64-entry mapper. A custom non-constant-time `decode` implementation
232/// therefore affects normal strict decode diagnostics and timing, but not the
233/// `ct` module's symbol-mapping loop.
234pub trait Alphabet {
235 /// Encoding table indexed by 6-bit values.
236 const ENCODE: [u8; 64];
237
238 /// Encode one 6-bit value into an alphabet byte.
239 ///
240 /// The default implementation scans the alphabet table instead of using a
241 /// secret-indexed table lookup. Built-in alphabets override this with the
242 /// branch-minimized ASCII arithmetic mapper. Custom alphabets that keep the
243 /// default method prioritize timing posture over throughput for direct
244 /// calls. This method is retained as a public low-level mapping helper for
245 /// API compatibility; [`Engine`](crate::Engine) uses [`Self::ENCODE`]
246 /// directly and is unaffected by overrides.
247 #[must_use]
248 fn encode(value: u8) -> u8 {
249 encode_alphabet_value(value, &Self::ENCODE)
250 }
251
252 /// Decode one byte into a 6-bit value.
253 ///
254 /// Implementations that want conservative custom-alphabet timing posture
255 /// should delegate to [`decode_alphabet_byte`], which scans all 64 entries
256 /// before returning. The `ct` module ignores this method and scans
257 /// [`Self::ENCODE`] directly.
258 fn decode(byte: u8) -> Option<u8>;
259}
260
261const fn is_visible_ascii(byte: u8) -> bool {
262 byte >= 0x21 && byte <= 0x7e
263}
264
265/// The RFC 4648 standard Base64 alphabet.
266#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
267pub struct Standard;
268
269impl Alphabet for Standard {
270 const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
271
272 #[inline]
273 fn encode(value: u8) -> u8 {
274 encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
275 }
276
277 #[inline]
278 fn decode(byte: u8) -> Option<u8> {
279 decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
280 }
281}
282
283/// The RFC 4648 URL-safe Base64 alphabet.
284#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
285pub struct UrlSafe;
286
287impl Alphabet for UrlSafe {
288 const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
289
290 #[inline]
291 fn encode(value: u8) -> u8 {
292 encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
293 }
294
295 #[inline]
296 fn decode(byte: u8) -> Option<u8> {
297 decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
298 }
299}
300
301/// The bcrypt Base64 alphabet.
302///
303/// This alphabet is commonly used by bcrypt hash strings. It is provided as an
304/// alphabet/profile building block; `base64-ng` does not parse or verify full
305/// bcrypt password-hash records.
306///
307/// # Security
308///
309/// The strict [`Alphabet::decode`] implementation delegates to
310/// [`decode_alphabet_byte`]. That helper scans the full alphabet, but it is a
311/// `const fn` and does not use the additional optimizer barriers used by the
312/// [`ct`](crate::ct) module. Do not use strict `Engine<Bcrypt, _>` decode as a
313/// token, key, or password-hash verifier. Use [`crate::ct::CtEngine`] with this
314/// alphabet for secret-bearing comparison workflows.
315#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
316pub struct Bcrypt;
317
318impl Alphabet for Bcrypt {
319 const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
320
321 #[inline]
322 fn decode(byte: u8) -> Option<u8> {
323 decode_alphabet_byte(byte, &Self::ENCODE)
324 }
325}
326
327/// The Unix `crypt(3)` Base64 alphabet.
328///
329/// This alphabet is provided as an explicit legacy interoperability profile.
330/// `base64-ng` does not parse or verify complete password-hash records.
331///
332/// # Security
333///
334/// The strict [`Alphabet::decode`] implementation delegates to
335/// [`decode_alphabet_byte`]. That helper scans the full alphabet, but it is a
336/// `const fn` and does not use the additional optimizer barriers used by the
337/// [`ct`](crate::ct) module. Do not use strict `Engine<Crypt, _>` decode as a
338/// token, key, or password-hash verifier. Use [`crate::ct::CtEngine`] with this
339/// alphabet for secret-bearing comparison workflows.
340#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
341pub struct Crypt;
342
343impl Alphabet for Crypt {
344 const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
345
346 #[inline]
347 fn decode(byte: u8) -> Option<u8> {
348 decode_alphabet_byte(byte, &Self::ENCODE)
349 }
350}
351
352#[inline]
353pub(crate) const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
354 encode_alphabet_value(value, &A::ENCODE)
355}
356
357#[derive(Clone, Copy)]
358pub(crate) enum RuntimeEncodeMapper {
359 StandardFamily { value_62: u8, value_63: u8 },
360 ScannedTable,
361}
362
363impl RuntimeEncodeMapper {
364 pub(crate) const fn for_alphabet<A: Alphabet>() -> Self {
365 const STANDARD_PREFIX: [u8; 62] =
366 *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
367
368 let mut index = 0;
369 while index < STANDARD_PREFIX.len() {
370 if A::ENCODE[index] != STANDARD_PREFIX[index] {
371 return Self::ScannedTable;
372 }
373 index += 1;
374 }
375
376 let value_62 = A::ENCODE[62];
377 let value_63 = A::ENCODE[63];
378 if (value_62 == b'+' && value_63 == b'/') || (value_62 == b'-' && value_63 == b'_') {
379 Self::StandardFamily { value_62, value_63 }
380 } else {
381 Self::ScannedTable
382 }
383 }
384
385 #[inline]
386 pub(crate) fn encode<A: Alphabet>(self, value: u8) -> u8 {
387 match self {
388 Self::StandardFamily { value_62, value_63 } => {
389 encode_ascii_base64(value, value_62, value_63)
390 }
391 Self::ScannedTable => encode_alphabet_value(value, &A::ENCODE),
392 }
393 }
394}
395
396pub(crate) struct RuntimeEncodeMapperFor<A: Alphabet>(core::marker::PhantomData<A>);
397
398impl<A: Alphabet> RuntimeEncodeMapperFor<A> {
399 pub(crate) const VALUE: RuntimeEncodeMapper = RuntimeEncodeMapper::for_alphabet::<A>();
400}
401
402#[inline]
403const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
404 let mut output = 0;
405 let mut index = 0;
406 let mut candidate = 0;
407 while index < encode.len() {
408 output |= encode[index] & ct_mask_eq_u8(value, candidate);
409 index += 1;
410 candidate += 1;
411 }
412 output
413}
414
415#[inline]
416const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
417 let upper = ct_mask_lt_u8(value, 26);
418 let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
419 let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
420 let value_62 = ct_mask_eq_u8(value, 0x3e);
421 let value_63 = ct_mask_eq_u8(value, 0x3f);
422
423 (value.wrapping_add(b'A') & upper)
424 | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
425 | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
426 | (value_62_byte & value_62)
427 | (value_63_byte & value_63)
428}
429
430#[inline]
431fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
432 let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
433 let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
434 let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
435 let value_62 = ct_mask_eq_u8(byte, value_62_byte);
436 let value_63 = ct_mask_eq_u8(byte, value_63_byte);
437 let valid = upper | lower | digit | value_62 | value_63;
438
439 let decoded = (byte.wrapping_sub(b'A') & upper)
440 | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
441 | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
442 | (0x3e & value_62)
443 | (0x3f & value_63);
444
445 if valid == 0 { None } else { Some(decoded) }
446}