use std::io::Read;
use std::path::Path;
use sha2::{Digest, Sha256};
const READ_CHUNK: usize = 64 * 1024;
pub fn hash_file(path: &Path) -> std::io::Result<String> {
let mut file = std::fs::File::open(path)?;
let mut digest = Sha256::new();
let mut buffer = vec![0_u8; READ_CHUNK];
loop {
let read = file.read(&mut buffer)?;
if read == 0 {
break;
}
digest.update(&buffer[..read]);
}
Ok(hex(&digest.finalize()))
}
#[derive(Debug, Default)]
pub struct Hasher {
inner: Sha256,
}
impl Hasher {
pub fn new() -> Self {
Self {
inner: Sha256::new(),
}
}
pub fn update(&mut self, bytes: &[u8]) {
self.inner.update(bytes);
}
pub fn finish(self) -> String {
hex(&self.inner.finalize())
}
}
fn hex(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
out.push_str(&format!("{byte:02x}"));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_nist_vector_for_abc() {
let mut hasher = Hasher::new();
hasher.update(b"abc");
assert_eq!(
hasher.finish(),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn the_empty_input_vector() {
assert_eq!(
Hasher::new().finish(),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
#[test]
fn incremental_updates_match_a_single_update() {
let mut split = Hasher::new();
split.update(b"ab");
split.update(b"c");
let mut whole = Hasher::new();
whole.update(b"abc");
assert_eq!(split.finish(), whole.finish());
}
#[test]
fn hashing_a_file_matches_hashing_its_bytes() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("f.bin");
std::fs::write(&path, b"abc").expect("write");
assert_eq!(
hash_file(&path).expect("hash"),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
}