use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::io::Read;
use std::path::Path;
use sha2::Digest;
use crate::file::display_path;
use crate::{XXError, XXResult, bail, file};
fn hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
std::fmt::Write::write_fmt(&mut s, format_args!("{b:02x}")).unwrap();
}
s
}
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(hex(h.as_slice()))
}
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(hex(h.as_slice()))
}
pub fn file_hash<H>(path: &Path) -> XXResult<sha2::digest::Output<H>>
where
H: Digest,
{
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.update(&buf[..n]);
}
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()
}
pub fn sha256(data: &[u8]) -> String {
let mut hasher = sha2::Sha256::new();
hasher.update(data);
hex(hasher.finalize().as_slice())
}
pub fn sha512(data: &[u8]) -> String {
let mut hasher = sha2::Sha512::new();
hasher.update(data);
hex(hasher.finalize().as_slice())
}
#[cfg(feature = "hash_md5")]
pub fn file_hash_md5(path: impl AsRef<Path>) -> XXResult<String> {
let path = path.as_ref();
debug!("Calculating MD5 checksum for {}", display_path(path));
let mut file = file::open(path)?;
let mut hasher = md5::Md5::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.update(&buf[..n]);
}
Ok(hex(hasher.finalize().as_slice()))
}
#[cfg(feature = "hash_md5")]
pub fn md5(data: &[u8]) -> String {
use md5::Digest;
let mut hasher = md5::Md5::new();
hasher.update(data);
hex(hasher.finalize().as_slice())
}
#[cfg(feature = "hash_sha1")]
pub fn file_hash_sha1(path: impl AsRef<Path>) -> XXResult<String> {
let path = path.as_ref();
debug!("Calculating SHA1 checksum for {}", display_path(path));
let mut file = file::open(path)?;
let mut hasher = sha1::Sha1::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.update(&buf[..n]);
}
Ok(hex(hasher.finalize().as_slice()))
}
#[cfg(feature = "hash_sha1")]
pub fn sha1(data: &[u8]) -> String {
use sha1::Digest;
let mut hasher = sha1::Sha1::new();
hasher.update(data);
hex(hasher.finalize().as_slice())
}
#[cfg(feature = "hash_blake3")]
pub fn file_hash_blake3(path: impl AsRef<Path>) -> XXResult<String> {
let path = path.as_ref();
debug!("Calculating Blake3 checksum for {}", display_path(path));
let mut file = file::open(path)?;
let mut hasher = blake3::Hasher::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.update(&buf[..n]);
}
Ok(hasher.finalize().to_hex().to_string())
}
#[cfg(feature = "hash_blake3")]
pub fn blake3(data: &[u8]) -> String {
blake3::hash(data).to_hex().to_string()
}
#[cfg(feature = "hash_blake3")]
pub fn ensure_checksum_blake3(path: &Path, checksum: &str) -> XXResult<()> {
let actual = file_hash_blake3(path)?;
if actual != checksum {
bail!(
"Checksum mismatch for file {}:\nExpected: {checksum}\nActual: {actual}",
display_path(path),
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::io::Write;
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");
}
#[test]
fn test_sha256_bytes() {
let hash = sha256(b"hello world");
assert_eq!(
hash,
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
);
}
#[test]
fn test_sha512_bytes() {
let hash = sha512(b"hello world");
assert!(hash.len() == 128); }
#[cfg(feature = "hash_md5")]
#[test]
fn test_md5_bytes() {
let hash = md5(b"hello world");
assert_eq!(hash, "5eb63bbbe01eeed093cb22bb8f5acdc3");
}
#[cfg(feature = "hash_sha1")]
#[test]
fn test_sha1_bytes() {
let hash = sha1(b"hello world");
assert_eq!(hash, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed");
}
#[cfg(feature = "hash_blake3")]
#[test]
fn test_blake3_bytes() {
let hash = blake3(b"hello world");
assert_eq!(
hash,
"d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24"
);
}
}