btt 0.5.0

Binary to text encodings
Documentation
use crate::{
	chunked::{ChunkedDecoder, ChunkedEncoder, ChunkedEncoderImpl},
	padding::{Equals, Unpadded}
};

const BASE32_DECODING_TABLE: &DecodingTable<32> = unsafe {
	&Alphabet::new_unchecked(b"abcdefghijklmnopqrstuvwxyz234567")
		.decoding_table()
		.merge(&Alphabet::new_unchecked(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567").decoding_table())
		.unwrap()
};

const BASE32_HEX_DECODING_TABLE: &DecodingTable<32> = unsafe {
	&Alphabet::new_unchecked(b"0123456789abcdefghijklmnopqrstuv")
		.decoding_table()
		.merge(&Alphabet::new_unchecked(b"0123456789ABCDEFGHIJKLMNOPQRSTUV").decoding_table())
		.unwrap()
};

const BASE32_CROCKFORD_DECODING_TABLE: &DecodingTable<32> = &DecodingTable::from_slices(&[
	b"0oO", b"1iIlL", b"2", b"3", b"4", b"5", b"6", b"7", b"8", b"9", b"aA", b"bB", b"cC", b"dD",
	b"eE", b"fF", b"gG", b"hH", b"jJ", b"kK", b"mM", b"nN", b"pP", b"qQ", b"rR", b"sS", b"tT",
	b"vV", b"wW", b"xX", b"yY", b"zZ"
])
.unwrap();

type Base32EncoderImpl<A, D, P> = ChunkedEncoder<A, D, P, Base32Impl, 32, 5, 8>;
type Base32DecoderImpl<D, P> = ChunkedDecoder<D, P, Base32Impl, 32, 5, 8>;

impl_encoding!(Base32Encoder, Base32Decoder, 32, Base32EncoderImpl, Base32DecoderImpl);

impl_encoder!(Base32Lower, 32, b"abcdefghijklmnopqrstuvwxyz234567", BASE32_DECODING_TABLE, Equals);

impl_encoder!(
	Base32LowerUnpadded,
	32,
	b"abcdefghijklmnopqrstuvwxyz234567",
	BASE32_DECODING_TABLE,
	Unpadded
);

impl_encoder!(Base32Upper, 32, b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", BASE32_DECODING_TABLE, Equals);

impl_encoder!(
	Base32UpperUnpadded,
	32,
	b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
	BASE32_DECODING_TABLE,
	Unpadded
);

impl_encoder!(
	Base32HexLower,
	32,
	b"0123456789abcdefghijklmnopqrstuv",
	BASE32_HEX_DECODING_TABLE,
	Equals
);

impl_encoder!(
	Base32HexLowerUnpadded,
	32,
	b"0123456789abcdefghijklmnopqrstuv",
	BASE32_HEX_DECODING_TABLE,
	Unpadded
);

impl_encoder!(
	Base32HexUpper,
	32,
	b"0123456789ABCDEFGHIJKLMNOPQRSTUV",
	BASE32_HEX_DECODING_TABLE,
	Equals
);

impl_encoder!(
	Base32HexUpperUnpadded,
	32,
	b"0123456789ABCDEFGHIJKLMNOPQRSTUV",
	BASE32_HEX_DECODING_TABLE,
	Unpadded
);

impl_encoder!(
	Base32CrockfordLower,
	32,
	b"0123456789abcdefghjkmnpqrstvwxyz",
	BASE32_CROCKFORD_DECODING_TABLE,
	Unpadded
);

impl_encoder!(
	Base32CrockfordLowerPadded,
	32,
	b"0123456789abcdefghjkmnpqrstvwxyz",
	BASE32_CROCKFORD_DECODING_TABLE,
	Equals
);

impl_encoder!(
	Base32CrockfordUpper,
	32,
	b"0123456789ABCDEFGHJKMNPQRSTVWXYZ",
	BASE32_CROCKFORD_DECODING_TABLE,
	Unpadded
);

impl_encoder!(
	Base32CrockfordUpperPadded,
	32,
	b"0123456789ABCDEFGHJKMNPQRSTVWXYZ",
	BASE32_CROCKFORD_DECODING_TABLE,
	Equals
);

#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct Base32Impl;

impl ChunkedEncoderImpl<5, 8> for Base32Impl {
	#[inline]
	fn padding_len(len: usize) -> usize {
		debug_assert!(len <= 5);
		(len * 8).div_ceil(5)
	}

	#[inline]
	fn encoded_len(len: usize, padded: bool) -> Option<usize> {
		if padded {
			len.div_ceil(5).checked_mul(8)
		} else {
			let rem = len % 5;
			(len / 5).checked_mul(8)?.checked_add(Self::padding_len(rem))
		}
	}

	#[inline]
	fn decoded_len(src: &[u8], padding: Option<u8>) -> Option<usize> {
		if src.is_empty() {
			return Some(0);
		}

		let (q, r) = if let Some(padding) = padding {
			if !src.len().is_multiple_of(8) {
				return None;
			}

			let padding = src.iter().rev().take(8).take_while(|&&b| b == padding).count();

			if !matches!(padding, 0 | 1 | 3 | 4 | 6) {
				return None;
			}

			((src.len() / 8).saturating_sub(1), 8 - padding)
		} else {
			let r = src.len() % 8;

			if !matches!(r, 0 | 2 | 4 | 5 | 7) {
				return None;
			}

			(src.len() / 8, r)
		};

		Some(q * 5 + r * 5 / 8)
	}

	#[inline]
	fn encode_chunk_raw(src: &[u8; 5], dst: &mut [u8; 8]) {
		let src = src.iter().fold(0, |acc, &b| acc << 8 | u64::from(b));

		dst.iter_mut()
			.enumerate()
			.for_each(|(i, dst)| *dst = (src >> (5 * (7 - i))) as u8 & 0b1_1111);
	}

	#[inline]
	fn decode_raw_chunk(src: &[u8; 8], dst: &mut [u8; 5]) -> Result<(), ()> {
		let src = src.iter().fold(0, |acc, &b| acc << 5 | u64::from(b));
		dst.iter_mut().enumerate().for_each(|(i, dst)| *dst = (src >> ((4 - i) * 8)) as u8);
		Ok(())
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	fn test(src: &[u8], expected: &str) {
		let mut buf = [0; 64];

		assert_eq!(Base32Upper.encoded_len(src.len()).unwrap(), expected.len());
		assert_eq!(Base32Upper.encode_into(src, &mut buf).unwrap(), expected);
		assert_eq!(Base32Upper.decode_into(expected.as_bytes(), &mut buf).unwrap(), src);

		let expected = expected.trim_end_matches('=');
		assert_eq!(Base32UpperUnpadded.encoded_len(src.len()).unwrap(), expected.len());
		assert_eq!(Base32UpperUnpadded.encode_into(src, &mut buf).unwrap(), expected);
		assert_eq!(Base32UpperUnpadded.decode_into(expected.as_bytes(), &mut buf).unwrap(), src);
	}

	#[test]
	fn rfc4648() {
		test(b"", "");
		test(b"f", "MY======");
		test(b"fo", "MZXQ====");
		test(b"foo", "MZXW6===");
		test(b"foob", "MZXW6YQ=");
		test(b"fooba", "MZXW6YTB");
		test(b"foobar", "MZXW6YTBOI======");
	}
}