use std::fmt;
use std::io::{self, Read};
use std::str::FromStr;
use sha2::{Digest as _, Sha256};
use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Digest {
hex: String,
}
impl Digest {
pub fn of(bytes: &[u8]) -> Self {
Self::from_hasher(Sha256::new().chain_update(bytes))
}
pub fn of_reader(mut reader: impl Read) -> io::Result<Self> {
let mut h = Hasher::default();
let mut buf = vec![0u8; 1 << 16];
loop {
let n = reader.read(&mut buf)?;
if n == 0 {
return Ok(h.finish());
}
h.update(&buf[..n]);
}
}
fn from_hasher(h: Sha256) -> Self {
let hex = h.finalize().iter().map(|b| format!("{b:02x}")).collect();
Self { hex }
}
pub fn hex(&self) -> &str {
&self.hex
}
pub fn verify(&self, actual: &Digest) -> Result<()> {
if self == actual {
Ok(())
} else {
Err(Error::DigestMismatch {
expected: self.clone(),
actual: actual.clone(),
})
}
}
}
impl fmt::Display for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sha256:{}", self.hex)
}
}
impl FromStr for Digest {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let hex = s
.strip_prefix("sha256:")
.filter(|h| h.len() == 64 && h.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f')))
.ok_or_else(|| Error::BadDigest(s.to_string()))?;
Ok(Self {
hex: hex.to_string(),
})
}
}
impl serde::Serialize for Digest {
fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
s.collect_str(self)
}
}
impl<'de> serde::Deserialize<'de> for Digest {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
let s = String::deserialize(d)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Default, Clone)]
pub struct Hasher(Sha256);
impl Hasher {
pub fn update(&mut self, bytes: &[u8]) {
self.0.update(bytes);
}
pub fn finish(self) -> Digest {
Digest::from_hasher(self.0)
}
}