use core::{fmt, ops::Deref, str::from_utf8_unchecked};
use std::{ffi::OsStr, path::Path};
pub const BASE32_LEN_U64: usize = 13;
pub const BASE32_LEN_U128: usize = 26;
pub const BASE32_LOWER_TABLE: &[u8; 32] = b"0123456789abcdefghijklmnopqrstuv";
const BASE32_DECODE_TABLE: [u8; 256] = {
let mut table = [0xFFu8; 256];
let mut i = 0usize;
while i < 10 {
table[b'0' as usize + i] = i as u8;
i += 1;
}
let mut j = 0usize;
while j < 22 {
table[b'a' as usize + j] = (10 + j) as u8;
table[b'A' as usize + j] = (10 + j) as u8;
j += 1;
}
table
};
macro_rules! base32_buf {
($(#[$meta:meta])* $name:ident, $len:expr) => {
$(#[$meta])*
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct $name(pub [u8; $len]);
impl $name {
#[inline(always)]
pub const fn as_str(&self) -> &str {
unsafe { from_utf8_unchecked(&self.0) }
}
#[inline(always)]
pub const fn as_bytes(&self) -> &[u8; $len] {
&self.0
}
}
impl Deref for $name {
type Target = str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl AsRef<str> for $name {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<Path> for $name {
#[inline(always)]
fn as_ref(&self) -> &Path {
Path::new(self.as_str())
}
}
impl AsRef<OsStr> for $name {
#[inline(always)]
fn as_ref(&self) -> &OsStr {
OsStr::new(self.as_str())
}
}
impl AsRef<[u8]> for $name {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl PartialEq<str> for $name {
#[inline(always)]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&str> for $name {
#[inline(always)]
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<$name> for str {
#[inline(always)]
fn eq(&self, other: &$name) -> bool {
self == other.as_str()
}
}
impl PartialEq<$name> for &str {
#[inline(always)]
fn eq(&self, other: &$name) -> bool {
*self == other.as_str()
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
};
}
base32_buf!(
Base32Buf64,
BASE32_LEN_U64
);
base32_buf!(
Base32Buf128,
BASE32_LEN_U128
);
#[inline]
pub const fn encode_u64(val: u64) -> Base32Buf64 {
let mut buf = [0u8; BASE32_LEN_U64];
buf[0] = BASE32_LOWER_TABLE[((val >> 60) & 0x0F) as usize];
let mut i = 1;
while i < BASE32_LEN_U64 {
let shift = 60 - i as u32 * 5;
buf[i] = BASE32_LOWER_TABLE[((val >> shift) & 0x1F) as usize];
i += 1;
}
Base32Buf64(buf)
}
#[inline]
pub const fn encode_u128(val: u128) -> Base32Buf128 {
let mut buf = [0u8; BASE32_LEN_U128];
buf[0] = BASE32_LOWER_TABLE[((val >> 125) & 0x07) as usize];
let mut i = 1;
while i < BASE32_LEN_U128 {
let shift = 125 - i as u32 * 5;
buf[i] = BASE32_LOWER_TABLE[((val >> shift) & 0x1F) as usize];
i += 1;
}
Base32Buf128(buf)
}
#[inline]
pub fn is_base32(s: &str) -> bool {
s.as_bytes()
.iter()
.all(|&b| BASE32_DECODE_TABLE[b as usize] != 0xFF)
}
fn decode_base32(s: &str, expect_len: usize, head_bits: u32) -> Option<u128> {
if s.len() != expect_len {
return None;
}
let bytes = s.as_bytes();
let head = BASE32_DECODE_TABLE[bytes[0] as usize];
if head >= (1 << head_bits) {
return None;
}
let mut acc = head as u128;
for &b in &bytes[1..] {
let digit = BASE32_DECODE_TABLE[b as usize];
if digit == 0xFF {
return None;
}
acc = (acc << 5) | digit as u128;
}
Some(acc)
}
#[inline]
pub fn decode_u64(s: &str) -> Option<u64> {
u64::try_from(decode_base32(s, BASE32_LEN_U64, 4)?).ok()
}
#[inline]
pub fn decode_u128(s: &str) -> Option<u128> {
decode_base32(s, BASE32_LEN_U128, 3)
}
const _: () = {
assert!(BASE32_LEN_U64 * 5 >= 64);
assert!((BASE32_LEN_U64 - 1) * 5 < 64);
assert!(BASE32_LEN_U128 * 5 >= 128);
assert!((BASE32_LEN_U128 - 1) * 5 < 128);
let b64 = encode_u64(u64::MAX);
assert!(b64.0[0] == b'f');
assert!(b64.0[12] == b'v');
let b128 = encode_u128(u128::MAX);
assert!(b128.0[0] == b'7');
assert!(b128.0[25] == b'v');
};