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
};
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Base32Buf<const N: usize>([u8; N]);
pub type Base32Buf64 = Base32Buf<BASE32_LEN_U64>;
pub type Base32Buf128 = Base32Buf<BASE32_LEN_U128>;
impl<const N: usize> Base32Buf<N> {
#[inline(always)]
pub const fn as_str(&self) -> &str {
unsafe { from_utf8_unchecked(&self.0) }
}
#[inline(always)]
pub const fn as_bytes(&self) -> &[u8; N] {
&self.0
}
}
impl<const N: usize> Deref for Base32Buf<N> {
type Target = str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl<const N: usize> AsRef<str> for Base32Buf<N> {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> AsRef<Path> for Base32Buf<N> {
#[inline(always)]
fn as_ref(&self) -> &Path {
Path::new(self.as_str())
}
}
impl<const N: usize> AsRef<OsStr> for Base32Buf<N> {
#[inline(always)]
fn as_ref(&self) -> &OsStr {
OsStr::new(self.as_str())
}
}
impl<const N: usize> AsRef<[u8]> for Base32Buf<N> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const N: usize> PartialEq<str> for Base32Buf<N> {
#[inline(always)]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<const N: usize> PartialEq<&str> for Base32Buf<N> {
#[inline(always)]
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl<const N: usize> PartialEq<Base32Buf<N>> for str {
#[inline(always)]
fn eq(&self, other: &Base32Buf<N>) -> bool {
self == other.as_str()
}
}
impl<const N: usize> PartialEq<Base32Buf<N>> for &str {
#[inline(always)]
fn eq(&self, other: &Base32Buf<N>) -> bool {
*self == other.as_str()
}
}
impl<const N: usize> PartialEq<String> for Base32Buf<N> {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> PartialEq<Base32Buf<N>> for String {
#[inline(always)]
fn eq(&self, other: &Base32Buf<N>) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> PartialEq<&Base32Buf<N>> for String {
#[inline(always)]
fn eq(&self, other: &&Base32Buf<N>) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> PartialEq<String> for &Base32Buf<N> {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> fmt::Debug for Base32Buf<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<const N: usize> fmt::Display for Base32Buf<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
const fn encode_bits<const BITS: u32, const N: usize>(val: u128) -> Base32Buf<N> {
const {
assert!(
BITS > (N as u32 - 1) * 5 && BITS <= N as u32 * 5,
"Base32 编码位宽与字符数不匹配"
)
};
let mut buf = [0u8; N];
let head_shift = (N as u32 - 1) * 5;
buf[0] = BASE32_LOWER_TABLE[(val >> head_shift) as usize];
let mut i = 1;
while i < N {
buf[i] = BASE32_LOWER_TABLE[((val >> (head_shift - i as u32 * 5)) & 0x1F) as usize];
i += 1;
}
Base32Buf(buf)
}
#[inline]
pub const fn encode_u64(val: u64) -> Base32Buf64 {
encode_bits::<64, BASE32_LEN_U64>(val as u128)
}
#[inline]
pub const fn encode_u128(val: u128) -> Base32Buf128 {
encode_bits::<128, BASE32_LEN_U128>(val)
}
#[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');
};