strong_id 0.4.0

Strongly typed, base32 encoded IDs
Documentation
use bitvec::prelude::*;
use std::iter::repeat;
use thiserror::Error;

const ALPHABET: [u8; 32] = [
	b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f',
	b'g', b'h', b'j', b'k', b'm', b'n', b'p', b'q', b'r', b's', b't', b'v', b'w', b'x', b'y', b'z',
];

#[rustfmt::skip]
const ALPHABET_DECODE_MAP: [u8; 256] = [
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01,
    0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C,
    0x0D, 0x0E, 0x0F, 0x10, 0x11, 0xFF, 0x12, 0x13, 0xFF, 0x14,
    0x15, 0xFF, 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C,
    0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
];

pub const fn encodable_bits<T>() -> usize {
	let byte_len = core::mem::size_of::<T>();
	let len = byte_len * 8;
	(len + 5) - ((len + 5) % 5)
}

pub const fn encoded_len<T>() -> usize {
	encodable_bits::<T>() / 5
}

const fn pad_bits_len(bytes: &[u8]) -> usize {
	let bits = bytes.len() * 8;
	((bits + 5) - (bits + 5) % 5) - bits
}

pub fn encode(src: &[u8], dst: &mut [u8]) {
	let mut bv = bitvec![u8, Msb0;];

	bv.extend(repeat(false).take(pad_bits_len(src)));
	bv.extend_from_bitslice(src.view_bits::<Msb0>());

	assert_eq!(
		dst.len(),
		bv.chunks(5).count(),
		"out slice is incorrect size"
	);

	for (i, byte) in bv.chunks(5).enumerate() {
		let o = ALPHABET[byte.load_be::<u8>() as usize];
		dst[i] = o;
	}
}

/// Errors which might occur when encoding or decoding bytes into base32
#[derive(Error, Debug, Eq, PartialEq)]
pub enum Base32Error {
	/// No bytes were supplied
	#[error("empty source bytes")]
	Empty,
	/// An invalid byte was present in the slice
	#[error("invalid source byte")]
	InvalidByte,
	/// The first byte can not be decoded
	#[error("invalid first source byte")]
	InvalidFirstByte,
	/// The output slice is the wrong size and the data may not fit into it
	#[error("out bytes slice is the wrong size. expected {0}, found {1}")]
	InvalidOutBytesSize(usize, usize),
}

pub fn decode(src: &[u8], dst: &mut [u8]) -> Result<(), Base32Error> {
	if src.is_empty() {
		return Err(Base32Error::Empty);
	}

	let mut bv = bitvec![u8, Msb0;];

	let pad_bits_len = pad_bits_len(dst);

	for (i, byte) in src.iter().enumerate() {
		let decoded = ALPHABET_DECODE_MAP[*byte as usize];

		if i == 0 {
			let max_first_byte: u8 = 0x1F >> pad_bits_len;
			if decoded > max_first_byte {
				return Err(Base32Error::InvalidFirstByte);
			}
		}

		if decoded == 0xFF {
			return Err(Base32Error::InvalidByte);
		}

		let bits = decoded.view_bits::<Msb0>();
		bv.extend(&bits[3..]);
	}

	let bv = &bv[pad_bits_len..];

	let chunks_len = bv.chunks(8).count();
	if dst.len() != chunks_len {
		return Err(Base32Error::InvalidOutBytesSize(chunks_len, dst.len()));
	}

	for (i, byte) in bv.chunks(8).enumerate() {
		dst[i] = byte.load_be::<u8>();
	}

	Ok(())
}