#[cfg(target_arch = "x86_64")]
use crate::wide::ascii_values;
#[cfg_attr(
not(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(target_arch = "wasm32", target_feature = "simd128"),
target_arch = "x86_64"
)),
allow(dead_code)
)]
pub(crate) trait Alphabet {
const ENCODE: &'static [u8; 64];
const DECODE: &'static [u8; 256];
const LUT_LO: &'static [u8; 16];
const LUT_HI: &'static [u8; 16];
const LUT_ROLL: &'static [u8; 16];
const ODD_CHAR: u8;
#[cfg(target_arch = "x86_64")]
const ASCII_LOW: &'static [i8; 64];
#[cfg(target_arch = "x86_64")]
const ASCII_HIGH: &'static [i8; 64];
#[cfg(any(
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
))]
const SPELLING: &'static [i8; 16];
}
#[cfg(any(
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
))]
const fn spelling(chars: &[u8; 64]) -> [i8; 16] {
let mut table = [0i8; 16];
table[0] = chars[0] as i8;
table[1] = chars[26] as i8 - 26;
let mut digit = 0;
while digit < 10 {
table[2 + digit] = chars[52 + digit] as i8 - (52 + digit) as i8;
digit += 1;
}
table[12] = chars[62] as i8 - 62;
table[13] = chars[63] as i8 - 63;
table
}
pub(crate) const INVALID: u8 = 0xFF;
#[cfg_attr(
not(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(target_arch = "wasm32", target_feature = "simd128"),
target_arch = "x86_64"
)),
allow(dead_code)
)]
pub(crate) const ODD_VALUE: u8 = 63;
pub(crate) struct Standard;
pub(crate) struct UrlSafe;
const STANDARD_CHARS: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const URL_SAFE_CHARS: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
const fn invert(chars: &[u8; 64]) -> [u8; 256] {
let mut table = [INVALID; 256];
let mut value = 0;
while value < 64 {
table[chars[value] as usize] = value as u8;
value += 1;
}
table
}
static STANDARD_DECODE: [u8; 256] = invert(STANDARD_CHARS);
static URL_SAFE_DECODE: [u8; 256] = invert(URL_SAFE_CHARS);
#[cfg(target_arch = "x86_64")]
static STANDARD_ASCII_LOW: [i8; 64] = ascii_values(STANDARD_CHARS, 0);
#[cfg(target_arch = "x86_64")]
static STANDARD_ASCII_HIGH: [i8; 64] = ascii_values(STANDARD_CHARS, 1);
#[cfg(target_arch = "x86_64")]
static URL_SAFE_ASCII_LOW: [i8; 64] = ascii_values(URL_SAFE_CHARS, 0);
#[cfg(target_arch = "x86_64")]
static URL_SAFE_ASCII_HIGH: [i8; 64] = ascii_values(URL_SAFE_CHARS, 1);
#[cfg(any(
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
))]
static STANDARD_SPELLING: [i8; 16] = spelling(STANDARD_CHARS);
#[cfg(any(
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
))]
static URL_SAFE_SPELLING: [i8; 16] = spelling(URL_SAFE_CHARS);
impl Alphabet for Standard {
const ENCODE: &'static [u8; 64] = STANDARD_CHARS;
const DECODE: &'static [u8; 256] = &STANDARD_DECODE;
const LUT_LO: &'static [u8; 16] = &[
0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A,
];
const LUT_HI: &'static [u8; 16] = &[
0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
];
const LUT_ROLL: &'static [u8; 16] = &[
0, 0, 19, 4, 0xBF, 0xBF, 0xB9, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0,
];
const ODD_CHAR: u8 = b'/';
#[cfg(target_arch = "x86_64")]
const ASCII_LOW: &'static [i8; 64] = &STANDARD_ASCII_LOW;
#[cfg(target_arch = "x86_64")]
const ASCII_HIGH: &'static [i8; 64] = &STANDARD_ASCII_HIGH;
#[cfg(any(
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
))]
const SPELLING: &'static [i8; 16] = &STANDARD_SPELLING;
}
impl Alphabet for UrlSafe {
const ENCODE: &'static [u8; 64] = URL_SAFE_CHARS;
const DECODE: &'static [u8; 256] = &URL_SAFE_DECODE;
const LUT_LO: &'static [u8; 16] = &[
0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x3B, 0x3B, 0x3A, 0x3B, 0x33,
];
const LUT_HI: &'static [u8; 16] = &[
0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
];
const LUT_ROLL: &'static [u8; 16] = &[
0, 0, 17, 4, 0xBF, 0xBF, 0xB9, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0,
];
const ODD_CHAR: u8 = b'_';
#[cfg(target_arch = "x86_64")]
const ASCII_LOW: &'static [i8; 64] = &URL_SAFE_ASCII_LOW;
#[cfg(target_arch = "x86_64")]
const ASCII_HIGH: &'static [i8; 64] = &URL_SAFE_ASCII_HIGH;
#[cfg(any(
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
))]
const SPELLING: &'static [i8; 16] = &URL_SAFE_SPELLING;
}
#[cfg(test)]
mod tests {
use super::*;
fn nibble_decode<A: Alphabet>(byte: u8) -> Option<u8> {
let high = (byte >> 4) as usize;
let low = (byte & 0x0F) as usize;
if A::LUT_HI[high] & A::LUT_LO[low] != 0 {
return None;
}
if byte == A::ODD_CHAR {
return Some(ODD_VALUE);
}
Some(byte.wrapping_add(A::LUT_ROLL[high]))
}
fn tables_agree<A: Alphabet>() {
for byte in 0..=u8::MAX {
let table = A::DECODE[byte as usize];
let nibbles = nibble_decode::<A>(byte);
match nibbles {
None => assert_eq!(
table, INVALID,
"byte {byte:#04x} accepted by one, not the other"
),
Some(value) => assert_eq!(table, value, "byte {byte:#04x} decoded two ways"),
}
}
}
fn is_a_bijection<A: Alphabet>() {
let mut is_spelled = [false; 64];
for (value, &byte) in A::ENCODE.iter().enumerate() {
assert_eq!(A::DECODE[byte as usize], value as u8);
assert!(!is_spelled[value], "value {value} spelled twice");
is_spelled[value] = true;
}
assert!(is_spelled.iter().all(|&hit| hit));
assert_eq!(A::DECODE[A::ODD_CHAR as usize], ODD_VALUE);
}
#[test]
fn standard_tables() {
tables_agree::<Standard>();
}
#[test]
fn url_safe_tables() {
tables_agree::<UrlSafe>();
}
#[test]
fn standard_bijection() {
is_a_bijection::<Standard>();
}
#[test]
fn url_safe_bijection() {
is_a_bijection::<UrlSafe>();
}
#[test]
fn padding_refused() {
assert_eq!(Standard::DECODE[b'=' as usize], INVALID);
assert_eq!(UrlSafe::DECODE[b'=' as usize], INVALID);
assert!(nibble_decode::<Standard>(b'=').is_none());
assert!(nibble_decode::<UrlSafe>(b'=').is_none());
}
}