use std::fmt;
use std::io::Read;
use sha2::{Digest as _, Sha256};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct Sha256Digest([u8; 32]);
impl Sha256Digest {
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
#[must_use]
pub fn to_hex(&self) -> String {
self.to_string()
}
}
impl fmt::Display for Sha256Digest {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in &self.0 {
write!(formatter, "{byte:02x}")?;
}
Ok(())
}
}
#[derive(Clone, Default)]
pub struct Sha256Hasher {
inner: Sha256,
}
impl Sha256Hasher {
#[must_use]
pub fn new() -> Self {
Self {
inner: Sha256::new(),
}
}
pub fn update(&mut self, bytes: &[u8]) {
self.inner.update(bytes);
}
#[must_use]
pub fn finalize(self) -> Sha256Digest {
Sha256Digest(self.inner.finalize().into())
}
#[must_use]
pub fn finalize_hex(self) -> String {
self.finalize().to_hex()
}
}
#[must_use]
pub fn sha256(bytes: &[u8]) -> Sha256Digest {
let mut hasher = Sha256Hasher::new();
hasher.update(bytes);
hasher.finalize()
}
#[must_use]
pub fn sha256_hex(bytes: &[u8]) -> String {
sha256(bytes).to_hex()
}
pub fn sha256_reader<R: Read>(reader: &mut R) -> std::io::Result<Sha256Digest> {
let mut hasher = Sha256Hasher::new();
let mut buffer = [0_u8; 8192];
loop {
let read = reader.read(&mut buffer)?;
if read == 0 {
break;
}
hasher.update(&buffer[..read]);
}
Ok(hasher.finalize())
}
#[cfg(test)]
mod tests {
use super::{sha256, sha256_hex, sha256_reader};
const EMPTY: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
const ABC: &str = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
#[test]
fn sha256_matches_known_answers() {
assert_eq!(sha256(b"").to_hex(), EMPTY);
assert_eq!(sha256(b"abc").to_hex(), ABC);
}
#[test]
fn sha256_hex_matches_known_vector() {
assert_eq!(sha256_hex(b"abc"), ABC);
}
#[test]
fn sha256_hex_is_64_hex_chars() {
let digest = sha256_hex(b"toven");
assert_eq!(digest.len(), 64);
assert!(
digest
.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
);
}
#[test]
fn display_renders_lowercase_hex() {
assert_eq!(sha256(b"abc").to_string(), ABC);
}
#[test]
fn reader_matches_the_in_memory_digest_across_chunk_boundaries() {
let payload = vec![0x5a_u8; 20_000];
let mut cursor = std::io::Cursor::new(payload.clone());
assert_eq!(sha256_reader(&mut cursor).unwrap(), sha256(&payload));
}
}