btt 0.5.0

Binary to text encodings
Documentation
#![no_std]

#[cfg(feature = "alloc")]
extern crate alloc;

use core::{fmt::Debug, iter, ops::Deref, str};

#[macro_use]
mod macros;

pub mod alphabet;
pub mod base32;
pub mod base64;
pub mod base85;
pub mod generic;
pub mod hex;
pub mod padding;

mod chunked;

use self::alphabet::{Alphabet, DecodingTable, DynAlphabet, DynDecodingTable};
pub use self::{
	base32::{
		Base32HexLower, Base32HexLowerUnpadded, Base32HexUpper, Base32HexUpperUnpadded,
		Base32Lower, Base32LowerUnpadded, Base32Upper, Base32UpperUnpadded
	},
	base64::{Base64, Base64Unpadded, Base64UrlPadded, Base64UrlUnpadded},
	base85::Z85,
	hex::{HexLower, HexUpper}
};

pub trait Encoder<const LEN: usize>: DynEncoder + Decoder<LEN> {
	fn alphabet(&self) -> &Alphabet<LEN>;

	#[inline]
	fn encode_array<const LEN2: usize>(
		&self,
		src: &[u8]
	) -> Result<ArrayString<LEN2>, EncodeIntoExactError> {
		let mut bytes = [0; LEN2];
		let _ = self.encode_into_exact(src, &mut bytes)?;
		Ok(ArrayString { bytes })
	}
}

pub trait DynEncoder: DynDecoder {
	fn dyn_alphabet(&self) -> &DynAlphabet;
	fn encoded_len(&self, len: usize) -> Option<usize>;

	fn encode_into_exact<'a>(
		&self,
		src: &[u8],
		dst: &'a mut [u8]
	) -> Result<&'a str, EncodeIntoExactError>;

	#[inline]
	fn encode_into<'a>(&self, src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, EncodeIntoError> {
		let dst = dst
			.get_mut(..self.encoded_len(src.len()).ok_or(EncodeIntoError::LengthOverflow)?)
			.ok_or(EncodeIntoError::TooSmall)?;

		Ok(self.encode_into_exact(src, dst).unwrap())
	}

	#[cfg(feature = "alloc")]
	#[inline]
	fn encode_string(&self, src: &[u8]) -> Option<alloc::string::String> {
		let mut vec = alloc::vec::Vec::new();
		let _ = self.encode_into_vec(src, &mut vec)?;
		Some(unsafe { alloc::string::String::from_utf8_unchecked(vec) })
	}

	#[cfg(feature = "alloc")]
	#[inline]
	fn encode_into_vec<'a>(&self, src: &[u8], dst: &'a mut alloc::vec::Vec<u8>) -> Option<&'a str> {
		let start = dst.len();
		dst.extend(iter::repeat_n(0, self.encoded_len(src.len())?));
		Some(self.encode_into_exact(src, &mut dst[start..]).unwrap())
	}

	#[cfg(feature = "alloc")]
	#[inline]
	fn encode_into_string<'a>(
		&self,
		src: &[u8],
		dst: &'a mut alloc::string::String
	) -> Option<&'a str> {
		self.encode_into_vec(src, unsafe { dst.as_mut_vec() })
	}
}

pub trait Decoder<const LEN: usize>: DynDecoder {
	fn decoding_table(&self) -> &DecodingTable<LEN>;
}

pub trait DynDecoder {
	fn dyn_decoding_table(&self) -> &DynDecodingTable;
	fn decoded_len(&self, src: &[u8]) -> Option<usize>;

	fn decode_into_exact<'a>(
		&self,
		src: &[u8],
		dst: &'a mut [u8]
	) -> Result<&'a [u8], DecodeIntoExactError>;

	#[inline]
	fn decode_into<'a>(&self, src: &[u8], dst: &'a mut [u8]) -> Result<&'a [u8], DecodeIntoError> {
		let dst = dst
			.get_mut(..self.decoded_len(src).ok_or(DecodeIntoError::InvalidLength)?)
			.ok_or(DecodeIntoError::TooSmall)?;

		match self.decode_into_exact(src, dst) {
			Ok(s) => Ok(s),
			Err(DecodeIntoExactError::InvalidCharacter(i)) => {
				Err(DecodeIntoError::InvalidCharacter(i))
			}
			Err(DecodeIntoExactError::InvalidChunk { index, len }) => {
				Err(DecodeIntoError::InvalidChunk { index, len })
			}
			Err(DecodeIntoExactError::NonCanonical) => Err(DecodeIntoError::NonCanonical),
			Err(DecodeIntoExactError::InvalidLength | DecodeIntoExactError::LengthMismatch) => {
				unreachable!()
			}
		}
	}

	#[cfg(feature = "alloc")]
	#[inline]
	fn decode_vec(&self, src: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeVecError> {
		let mut vec = alloc::vec::Vec::new();
		let _ = self.decode_into_vec(src, &mut vec)?;
		Ok(vec)
	}

	#[cfg(feature = "alloc")]
	#[inline]
	fn decode_into_vec<'a>(
		&self,
		src: &[u8],
		dst: &'a mut alloc::vec::Vec<u8>
	) -> Result<&'a [u8], DecodeVecError> {
		let start = dst.len();
		dst.extend(iter::repeat_n(0, self.decoded_len(src).ok_or(DecodeVecError::InvalidLength)?));

		match self.decode_into_exact(src, &mut dst[start..]) {
			Ok(dst) => Ok(dst),
			Err(DecodeIntoExactError::InvalidCharacter(i)) => {
				Err(DecodeVecError::InvalidCharacter(i))
			}
			Err(DecodeIntoExactError::InvalidChunk { index, len }) => {
				Err(DecodeVecError::InvalidChunk { index, len })
			}
			Err(DecodeIntoExactError::NonCanonical) => Err(DecodeVecError::NonCanonical),
			Err(DecodeIntoExactError::InvalidLength | DecodeIntoExactError::LengthMismatch) => {
				unreachable!()
			}
		}
	}
}

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ArrayString<const LEN: usize> {
	bytes: [u8; LEN]
}

impl<const LEN: usize> ArrayString<LEN> {
	pub const fn as_str(&self) -> &str {
		unsafe { str::from_utf8_unchecked(&self.bytes) }
	}
}

impl<const LEN: usize> Deref for ArrayString<LEN> {
	type Target = str;

	fn deref(&self) -> &Self::Target {
		self.as_str()
	}
}

impl<const LEN: usize> AsRef<str> for ArrayString<LEN> {
	fn as_ref(&self) -> &str {
		self.as_str()
	}
}

impl<const LEN: usize> AsRef<[u8; LEN]> for ArrayString<LEN> {
	fn as_ref(&self) -> &[u8; LEN] {
		&self.bytes
	}
}

impl<const LEN: usize> AsRef<[u8]> for ArrayString<LEN> {
	fn as_ref(&self) -> &[u8] {
		&self.bytes
	}
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum EncodeIntoError {
	LengthOverflow,
	TooSmall
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum EncodeIntoExactError {
	LengthOverflow,
	LengthMismatch
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DecodeIntoError {
	InvalidLength,
	TooSmall,
	InvalidCharacter(usize),
	NonCanonical,
	InvalidChunk { index: usize, len: usize }
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DecodeIntoExactError {
	InvalidLength,
	LengthMismatch,
	InvalidCharacter(usize),
	NonCanonical,
	InvalidChunk { index: usize, len: usize }
}

#[cfg(feature = "alloc")]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DecodeVecError {
	InvalidLength,
	InvalidCharacter(usize),
	NonCanonical,
	InvalidChunk { index: usize, len: usize }
}

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

	#[allow(dead_code)]
	fn assert_dyn_safe(_: &dyn DynEncoder) {}
}