use base64ct::{Base64, Encoding};
use sha3::Digest;
use std::fmt::Display;
use tracing::{error, warn};
use facet::Facet;
#[derive(thiserror::Error, Debug)]
pub enum Errors {
#[error(transparent)]
StdIo(#[from] std::io::Error),
#[error("Attempted to load")]
TooFewBytes { needed: usize, provided: usize },
#[error("{0}")]
Custom(String),
}
impl From<String> for Errors {
fn from(value: String) -> Self {
Self::Custom(value)
}
}
impl From<&str> for Errors {
fn from(value: &str) -> Self {
Self::from(value.to_string())
}
}
pub type Res<T> = Result<T, Errors>;
const BLAKE3_LEN: usize = 32;
const SHA3_256_LEN: usize = 32;
const SHA3_512_LEN: usize = 64;
#[derive(Facet, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Copy, Debug)]
#[repr(C)]
pub enum Hish {
Blake3([u8; BLAKE3_LEN]),
Sha256([u8; SHA3_256_LEN]),
Sha512([u8; SHA3_512_LEN]),
}
impl Hish {
fn sha256_empty_arr() -> [u8; SHA3_256_LEN] {
[0u8; SHA3_256_LEN]
}
fn sha512_empty_arr() -> [u8; SHA3_512_LEN] {
[0u8; SHA3_512_LEN]
}
fn blake3_empty_arr() -> [u8; BLAKE3_LEN] {
[0u8; BLAKE3_LEN]
}
fn sha256_empty() -> Self {
Self::Sha256([0u8; SHA3_256_LEN])
}
fn sha512_empty() -> Self {
Self::Sha512([0u8; SHA3_512_LEN])
}
fn blake3_empty() -> Self {
Self::Blake3([0u8; BLAKE3_LEN])
}
const fn len(&self) -> usize {
match self {
Hish::Blake3(_) => BLAKE3_LEN,
Hish::Sha256(_) => SHA3_256_LEN,
Hish::Sha512(_) => SHA3_512_LEN,
}
}
const fn prefix(&self) -> &'static str {
match self {
Hish::Blake3(_) => "blake3",
Hish::Sha256(_) => "sha256",
Hish::Sha512(_) => "sha512",
}
}
fn bytes_ref(&self) -> &[u8] {
match self {
Hish::Blake3(b) => b,
Hish::Sha256(b) => b,
Hish::Sha512(b) => b,
}
}
}
impl Hish {
fn length_err(&self, passed_len: usize) -> String {
let hash_len = self.len();
let prefix = self.prefix();
if hash_len < passed_len {
format!("Passed too many bytes: Expected <{hash_len}>, got <{passed_len}>")
} else if hash_len > passed_len {
format!("Passed too few bytes: Expected <{hash_len}>, got <{passed_len}>")
} else {
error!(
"Produced an error that the wrong amounts of bytes were passed, but it was the correct amount!"
);
format!("Passed the correct amount of bytes: Expected <{hash_len}>, got <{passed_len}>")
}
}
fn replace_unchecked(&mut self, bytes: &[u8]) {
match self {
Hish::Blake3(inner) => inner.copy_from_slice(&bytes[0..BLAKE3_LEN]),
Hish::Sha256(inner) => inner.copy_from_slice(&bytes[0..SHA3_256_LEN]),
Hish::Sha512(inner) => inner.copy_from_slice(&bytes[0..SHA3_512_LEN]),
}
}
}
impl Display for Hish {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let p = self.prefix();
let b = Base64::encode_string(self.bytes_ref());
write!(f, "{p}/{b}")
}
}
impl From<sha3::Sha3_256> for Hish {
fn from(value: sha3::Sha3_256) -> Self {
Self::Sha256(value.finalize().into())
}
}
impl From<sha3::Sha3_512> for Hish {
fn from(value: sha3::Sha3_512) -> Self {
Self::Sha512(value.finalize().into())
}
}
impl From<blake3::Hash> for Hish {
fn from(value: blake3::Hash) -> Self {
Self::Blake3(value.into())
}
}
impl TryFrom<(&str, Vec<u8>)> for Hish {
type Error = Errors;
fn try_from(value: (&str, Vec<u8>)) -> Result<Self, Self::Error> {
let len = value.1.len();
let (mut h, hish_len) = match value.0 {
"blake3" | "b3" => (Self::blake3_empty(), BLAKE3_LEN),
"sha3_256" | "sha256" => (Self::sha256_empty(), SHA3_256_LEN),
"sha3_512" | "sha512" => (Self::sha512_empty(), SHA3_256_LEN),
_ => return Err(format!("'{}' is not a name of a hash algorithm", value.0).into()),
};
let m = if len == hish_len {
h.replace_unchecked(value.1.as_ref());
return Ok(h);
} else {
h.length_err(len)
};
warn!(message = m);
Err(m.into())
}
}