use crate::TocError;
use sha1::{
Digest,
Sha1,
};
use std::{
fmt,
str::FromStr,
};
#[cfg_attr(docsrs, doc(cfg(feature = "sha1")))]
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub struct ShaB64([u8; 20]);
impl fmt::Display for ShaB64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = [b'-'; 28];
for (raw, dst) in self.0.chunks_exact(3).zip(buf.chunks_exact_mut(4)) {
dst[0] = base64_encode(raw[0] >> 2);
dst[1] = base64_encode(((raw[0] & 0b0000_0011) << 4) | (raw[1] >> 4));
dst[2] = base64_encode(((raw[1] & 0b0000_1111) << 2) | (raw[2] >> 6));
dst[3] = base64_encode(raw[2] & 0b0011_1111);
}
buf[24] = base64_encode(self.0[18] >> 2);
buf[25] = base64_encode(((self.0[18] & 0b0000_0011) << 4) | (self.0[19] >> 4));
buf[26] = base64_encode((self.0[19] & 0b0000_1111) << 2);
std::str::from_utf8(buf.as_slice())
.map_err(|_| fmt::Error)
.and_then(|s| <str as fmt::Display>::fmt(s, f))
}
}
impl From<Sha1> for ShaB64 {
#[inline]
fn from(src: Sha1) -> Self { Self(<[u8; 20]>::from(src.finalize())) }
}
impl FromStr for ShaB64 {
type Err = TocError;
#[inline]
fn from_str(src: &str) -> Result<Self, Self::Err> { Self::decode(src) }
}
impl TryFrom<&str> for ShaB64 {
type Error = TocError;
#[inline]
fn try_from(src: &str) -> Result<Self, Self::Error> { Self::decode(src) }
}
impl ShaB64 {
pub fn decode<S>(src: S) -> Result<Self, TocError>
where S: AsRef<str> {
let src = src.as_ref().as_bytes();
if src.len() == 28 && src[27] == b'-' {
let mut out = [0_u8; 20];
for (i, chunk) in out.chunks_exact_mut(3).zip(src.chunks_exact(4)) {
let a = base64_decode(chunk[0])?;
let b = base64_decode(chunk[1])?;
let c = base64_decode(chunk[2])?;
let d = base64_decode(chunk[3])?;
i.copy_from_slice(&[
((a & 0b0011_1111) << 2) | (b >> 4),
((b & 0b0000_1111) << 4) | (c >> 2),
((c & 0b0000_0011) << 6) | d & 0b0011_1111,
]);
}
let a = base64_decode(src[24])?;
let b = base64_decode(src[25])?;
let c = base64_decode(src[26])?;
out[18] = ((a & 0b0011_1111) << 2) | (b >> 4);
out[19] = ((b & 0b0000_1111) << 4) | (c >> 2);
Ok(Self(out))
}
else { Err(TocError::ShaB64Decode) }
}
#[inline]
pub(crate) fn push_to_string(&self, out: &mut String) {
for chunk in self.0.chunks_exact(3) {
out.push(base64_encode(chunk[0] >> 2) as char);
out.push(base64_encode(((chunk[0] & 0b0000_0011) << 4) | (chunk[1] >> 4)) as char);
out.push(base64_encode(((chunk[1] & 0b0000_1111) << 2) | (chunk[2] >> 6)) as char);
out.push(base64_encode(chunk[2] & 0b0011_1111) as char);
}
out.push(base64_encode(self.0[18] >> 2) as char);
out.push(base64_encode(((self.0[18] & 0b0000_0011) << 4) | (self.0[19] >> 4)) as char);
out.push(base64_encode((self.0[19] & 0b0000_1111) << 2) as char);
out.push('-');
}
}
const fn base64_encode(byte: u8) -> u8 {
debug_assert!(byte < 64, "BUG: base64 encoding byte is not 6-bit!");
match byte {
0..=25 => byte + 65,
26..=51 => byte + 71,
52..=61 => byte - 4,
62 => b'.',
63 => b'_',
_ => unreachable!(), }
}
const fn base64_decode(byte: u8) -> Result<u8, TocError> {
match byte {
b'A'..=b'Z' => Ok(byte - 65),
b'a'..=b'z' => Ok(byte - 71),
b'0'..=b'9' => Ok(byte + 4),
b'.' => Ok(62),
b'_' => Ok(63),
_ => Err(TocError::ShaB64Decode),
}
}