use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::io::{Read, Write};
use std::path::Path;
use sha2::digest::Output;
use sha2::Digest;
use crate::file::display_path;
use crate::{bail, file, XXError, XXResult};
pub fn hash_to_str<T: Hash>(t: &T) -> String {
let mut s = DefaultHasher::new();
t.hash(&mut s);
let bytes = s.finish();
format!("{bytes:x}")
}
pub fn file_hash_sha256(path: impl AsRef<Path>) -> XXResult<String> {
let path = path.as_ref();
debug!("Calculating SHA256 checksum for {}", display_path(path));
let h = file_hash::<sha2::Sha256>(path)?;
Ok(format!("{h:x}"))
}
pub fn file_hash_sha512(path: impl AsRef<Path>) -> XXResult<String> {
let path = path.as_ref();
debug!("Calculating SHA512 checksum for {}", display_path(path));
let h = file_hash::<sha2::Sha512>(path)?;
Ok(format!("{h:x}"))
}
pub fn file_hash<H>(path: &Path) -> XXResult<Output<H>>
where
H: Digest + Write,
{
let mut file = file::open(path)?;
let mut hasher = H::new();
let mut buf = [0; 32 * 1024];
loop {
let n = file
.read(&mut buf)
.map_err(|err| XXError::FileError(err, path.to_path_buf()))?;
if n == 0 {
break;
}
hasher
.write_all(&buf[..n])
.map_err(|err| XXError::FileError(err, path.to_path_buf()))?;
}
std::io::copy(&mut file, &mut hasher)
.map_err(|err| XXError::FileError(err, path.to_path_buf()))?;
Ok(hasher.finalize())
}
pub fn ensure_checksum_sha256(path: &Path, checksum: &str) -> XXResult<()> {
let actual = file_hash_sha256(path)?;
if actual != checksum {
bail!(
"Checksum mismatch for file {}:\nExpected: {checksum}\nActual: {actual}",
display_path(path),
);
}
Ok(())
}
pub fn ensure_checksum_sha512(path: &Path, checksum: &str) -> XXResult<()> {
let actual = file_hash_sha512(path)?;
if actual != checksum {
bail!(
"Checksum mismatch for file {}:\nExpected: {checksum}\nActual: {actual}",
display_path(path),
);
}
Ok(())
}
pub fn parse_shasums(text: &str) -> HashMap<String, String> {
text.lines()
.map(|l| {
let mut parts = l.split_whitespace();
let hash = parts.next().unwrap();
let name = parts.next().unwrap();
(name.into(), hash.into())
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_to_str() {
assert_eq!(hash_to_str(&"foo"), "3e8b8c44c3ca73b7");
}
#[test]
fn test_hash_sha256() {
let tmp = tempfile::NamedTempFile::new().unwrap();
tmp.as_file().write_all(b"Hello, world!").unwrap();
let hash = file_hash_sha256(tmp.path()).unwrap();
insta::assert_snapshot!(hash, @"315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3");
}
}