use std::{
fs::File,
io::{BufReader, Read},
path::{Path, PathBuf},
};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::{error::io_path, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileDigest {
pub path: PathBuf,
pub sha256: String,
pub size: u64,
}
pub fn sha256_file(path: impl AsRef<Path>) -> Result<FileDigest> {
let path = path.as_ref();
let file = File::open(path).map_err(|error| io_path(path, error))?;
let size = file.metadata().map_err(|error| io_path(path, error))?.len();
let mut reader = BufReader::with_capacity(1024 * 1024, file);
let mut hasher = Sha256::new();
let mut buf = vec![0_u8; 1024 * 1024];
loop {
let read = reader
.read(&mut buf)
.map_err(|error| io_path(path, error))?;
if read == 0 {
break;
}
hasher.update(&buf[..read]);
}
Ok(FileDigest {
path: path.to_path_buf(),
sha256: hex::encode(hasher.finalize()),
size,
})
}
pub fn sha256_bytes(bytes: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(bytes);
hex::encode(hasher.finalize())
}
pub fn verify_sha256(path: impl AsRef<Path>, expected: &str) -> Result<FileDigest> {
let digest = sha256_file(path.as_ref())?;
if !digest.sha256.eq_ignore_ascii_case(expected) {
return Err(crate::Error::HashMismatch {
path: path.as_ref().to_path_buf(),
expected: expected.to_string(),
actual: digest.sha256,
});
}
Ok(digest)
}