use crate::Sha256;
use std::fmt;
const PREFIX: [u8; 4] = [0x01, 0x55, 0x12, 0x20];
const CIDV1_LEN: usize = PREFIX.len() + 32;
const BASE32_LOWER: &[u8; 32] = b"abcdefghijklmnopqrstuvwxyz234567";
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CidV1Error {
#[non_exhaustive]
UnsupportedMultibase,
#[non_exhaustive]
InvalidBase32 {
at: u32,
byte: u8,
},
#[non_exhaustive]
NonCanonicalBase32,
#[non_exhaustive]
WrongLength {
got: u32,
},
#[non_exhaustive]
UnsupportedPrefix {
got: [u8; 4],
},
}
impl fmt::Display for CidV1Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedMultibase => {
f.write_str("CIDv1 string must start with the multibase prefix 'b'")
}
Self::InvalidBase32 { at, byte } => write!(
f,
"CIDv1 string contains a non-base32 byte 0x{byte:02x} at byte {at}"
),
Self::NonCanonicalBase32 => f.write_str("CIDv1 string is not canonical base32"),
Self::WrongLength { got } => {
write!(f, "binary CIDv1 must be {CIDV1_LEN} bytes (got {got})")
}
Self::UnsupportedPrefix { got } => write!(
f,
"CIDv1 prefix must be 01 55 12 20 (got {:02x} {:02x} {:02x} {:02x})",
got[0], got[1], got[2], got[3]
),
}
}
}
impl std::error::Error for CidV1Error {}
impl Sha256 {
pub fn to_cidv1_bytes(&self) -> [u8; CIDV1_LEN] {
let mut out = [0u8; CIDV1_LEN];
out[..PREFIX.len()].copy_from_slice(&PREFIX);
out[PREFIX.len()..].copy_from_slice(&self.to_bytes());
out
}
pub fn to_cidv1(&self) -> String {
let bytes = self.to_cidv1_bytes();
let mut out = String::with_capacity(1 + (bytes.len() * 8).div_ceil(5));
out.push('b');
let mut acc: u32 = 0;
let mut bits = 0;
for &b in &bytes {
acc = (acc << 8) | u32::from(b);
bits += 8;
while bits >= 5 {
bits -= 5;
out.push(char::from(BASE32_LOWER[((acc >> bits) & 0x1f) as usize]));
}
}
if bits > 0 {
out.push(char::from(
BASE32_LOWER[((acc << (5 - bits)) & 0x1f) as usize],
));
}
out
}
pub fn from_cidv1_bytes(bytes: &[u8]) -> Result<Self, CidV1Error> {
let digest: &[u8; 32] = bytes
.strip_prefix(&PREFIX)
.and_then(|rest| rest.try_into().ok())
.ok_or_else(|| match bytes.first_chunk::<4>() {
Some(got) if *got != PREFIX => CidV1Error::UnsupportedPrefix { got: *got },
_ => CidV1Error::WrongLength {
got: u32::try_from(bytes.len()).unwrap_or(u32::MAX),
},
})?;
Ok(Self::from(digest))
}
pub fn from_cidv1(s: &str) -> Result<Self, CidV1Error> {
let text = s
.as_bytes()
.strip_prefix(b"b")
.ok_or(CidV1Error::UnsupportedMultibase)?;
let mut bytes = Vec::with_capacity(text.len() * 5 / 8);
let mut acc: u32 = 0;
let mut bits = 0;
for (i, &c) in text.iter().enumerate() {
let value = match c {
b'a'..=b'z' => c - b'a',
b'2'..=b'7' => c - b'2' + 26,
_ => {
return Err(CidV1Error::InvalidBase32 {
at: u32::try_from(i + 1).unwrap_or(u32::MAX),
byte: c,
});
}
};
acc = ((acc << 5) | u32::from(value)) & 0xff_ff;
bits += 5;
if bits >= 8 {
bits -= 8;
bytes.push((acc >> bits) as u8);
}
}
if bits >= 5 || acc & ((1 << bits) - 1) != 0 {
return Err(CidV1Error::NonCanonicalBase32);
}
Self::from_cidv1_bytes(&bytes)
}
}