use std::borrow::Borrow;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Digest<const N: usize>(pub [u8; N]);
impl<'de, const N: usize> Deserialize<'de> for Digest<N> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let dig: String = Deserialize::deserialize(deserializer)?;
let dig = hex::decode(dig).map_err(|e| Error::custom(format!("invalid hex: {e}")))?;
let dig = dig.try_into().map_err(|v: Vec<_>| {
Error::custom(format!(
"expected digest to have length of {N}, got {}",
v.len()
))
})?;
Ok(Digest(dig))
}
}
impl<const N: usize> Serialize for Digest<N> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex = self.to_string();
serializer.serialize_str(&hex)
}
}
impl<const N: usize> AsRef<[u8; N]> for Digest<N> {
fn as_ref(&self) -> &[u8; N] {
&self.0
}
}
impl<const N: usize> Borrow<[u8; N]> for Digest<N> {
fn borrow(&self) -> &[u8; N] {
&self.0
}
}
impl<const N: usize> Deref for Digest<N> {
type Target = [u8; N];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct DigestError(String);
impl Display for DigestError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Error for DigestError {}
impl<const N: usize> TryFrom<Vec<u8>> for Digest<N> {
type Error = DigestError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let len = value.len();
let array: [u8; N] = value.try_into().map_err(|_| {
DigestError(format!("Expected a Vec of length {} but it was {}", N, len))
})?;
Ok(Digest(array))
}
}
impl<const N: usize> Display for Digest<N> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum HashType {
Md5(Digest<16>),
SHA1(Digest<20>),
SHA256(Digest<32>),
SHA384(Digest<48>),
SHA512(Digest<64>),
}
impl Display for HashType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
HashType::Md5(h) => write!(f, "MD5: {h}"),
HashType::SHA1(h) => write!(f, "SHA-1: {h}"),
HashType::SHA256(h) => write!(f, "SHA-256: {h}"),
HashType::SHA384(h) => write!(f, "SHA-384: {h}"),
HashType::SHA512(h) => write!(f, "SHA-512: {h}"),
}
}
}
impl HashType {
pub fn name(&self) -> &'static str {
match self {
HashType::Md5(_) => "md5",
HashType::SHA1(_) => "sha1",
HashType::SHA256(_) => "sha256",
HashType::SHA384(_) => "sha384",
HashType::SHA512(_) => "sha512",
}
}
pub fn the_hash(&self) -> String {
match self {
HashType::Md5(h) => h.to_string(),
HashType::SHA1(h) => h.to_string(),
HashType::SHA256(h) => h.to_string(),
HashType::SHA384(h) => h.to_string(),
HashType::SHA512(h) => h.to_string(),
}
}
}
impl TryFrom<String> for HashType {
type Error = DigestError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let decoded = hex::decode(&value).unwrap();
Ok(match decoded.len() {
16 => HashType::Md5(Digest::try_from(decoded)?),
20 => HashType::SHA1(Digest::try_from(decoded)?),
32 => HashType::SHA256(Digest::try_from(decoded)?),
48 => HashType::SHA384(Digest::try_from(decoded)?),
64 => HashType::SHA512(Digest::try_from(decoded)?),
_ => return Err(DigestError(format!("unknown hash size {}", value.len()))),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strings() {
let digest = Digest([0x00, 0x11, 0x22, 0x33]);
assert_eq!(digest.to_string(), "00112233");
assert!(HashType::try_from(String::from("00112233")).is_err());
}
#[test]
fn sha1() {
const TEST: &str = "3204c1ca863c2068214900e831fb8047b934bf88";
let digest = HashType::try_from(String::from(TEST)).unwrap();
assert_eq!(digest.name(), "sha1");
if let HashType::Md5(_) = digest {
panic!("Failed: SHA-1 hash was made into MD-5");
}
if let HashType::SHA256(_) = digest {
panic!("Failed: SHA-1 hash was made into SHA-256");
}
if let HashType::SHA384(_) = digest {
panic!("Failed: SHA-1 hash was made into SHA-384");
}
if let HashType::SHA512(_) = digest {
panic!("Failed: SHA-1 hash was made into SHA-512");
}
}
#[test]
fn sha256() {
const TEST: &str = "d154b8420fc56a629df2e6d918be53310d8ac39a926aa5f60ae59a66298969a0";
let digest = HashType::try_from(String::from(TEST)).unwrap();
assert_eq!(digest.name(), "sha256");
if let HashType::Md5(_) = digest {
panic!("Failed: SHA-256 hash was made into MD-5");
}
if let HashType::SHA1(_) = digest {
panic!("Failed: SHA-256 hash was made into SHA-1");
}
if let HashType::SHA384(_) = digest {
panic!("Failed: SHA-256 hash was made into SHA-384");
}
if let HashType::SHA512(_) = digest {
panic!("Failed: SHA-256 hash was made into SHA-512");
}
}
#[test]
fn sha512() {
const TEST: &str = "dafe60f7d02b0151909550d6f20343d0fe374b044d40221c13295a312489e1b702edbeac99ffda85f61b812b1ddd0c9394cda0c1162bffb716f04d996ff73cdf";
let digest = HashType::try_from(String::from(TEST)).unwrap();
assert_eq!(digest.name(), "sha512");
if let HashType::Md5(_) = digest {
panic!("Failed: SHA-512 hash was made into MD-5");
}
if let HashType::SHA1(_) = digest {
panic!("Failed: SHA-512 hash was made into SHA-1");
}
if let HashType::SHA256(_) = digest {
panic!("Failed: SHA-512 hash was made into SHA-256");
}
if let HashType::SHA384(_) = digest {
panic!("Failed: SHA-512 hash was made into SHA-384");
}
}
}