use std::ffi::OsString;
use std::fs::File;
use std::io::{self, prelude::Read, BufReader};
use digest::Digest;
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum HashAlgorithm {
Blake2,
SHA3_256,
SHA3_512,
}
pub(crate) fn get_checksum_fn(ha: &HashAlgorithm) -> fn(&OsString) -> io::Result<String> {
match ha {
HashAlgorithm::Blake2 => get_checksum::<blake2::Blake2b512>,
HashAlgorithm::SHA3_256 => get_checksum::<sha3::Sha3_256>,
HashAlgorithm::SHA3_512 => get_checksum::<sha3::Sha3_512>,
}
}
fn get_checksum<H>(path: &OsString) -> io::Result<String>
where
H: Digest,
digest::Output<H>: std::fmt::LowerHex,
{
log::trace!("Getting checksum for {:?}", path);
let mut hasher = H::new();
let mut buffer = [0u8; 1024];
let mut buf_reader = BufReader::new(File::open(path)?);
loop {
let count = buf_reader.read(&mut buffer)?;
if count == 0 {
break;
}
hasher.update(&buffer[..count]);
}
let result = format!("{:x}", hasher.finalize());
Ok(result)
}
pub(crate) fn get_partial_checksum_fn<const LEN: usize>(
ha: &HashAlgorithm,
) -> fn(&OsString) -> io::Result<String> {
match *ha {
HashAlgorithm::Blake2 => get_partial_checksum::<LEN, blake2::Blake2b512>,
HashAlgorithm::SHA3_256 => get_partial_checksum::<LEN, sha3::Sha3_256>,
HashAlgorithm::SHA3_512 => get_partial_checksum::<LEN, sha3::Sha3_512>,
}
}
fn get_partial_checksum<const LEN: usize, H>(path: &OsString) -> io::Result<String>
where
H: Digest,
digest::Output<H>: std::fmt::LowerHex,
{
let mut hasher = H::new();
let mut buffer = [0u8; LEN];
let mut input = File::open(path)?;
let count = input.read(&mut buffer)?;
hasher.update(&buffer[..count]);
let result = format!("{:x}", hasher.finalize());
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::prelude::*;
use tempdir::TempDir;
#[test]
fn blake2_partial_test() -> io::Result<()> {
let tmp_dir = TempDir::new("duplicate_destroyer_test_dir")?;
let file_path = tmp_dir.path().join("test_file.txt");
let mut tmp_file = File::create(file_path.clone())?;
writeln!(tmp_file, "This is a test string.")?;
drop(tmp_file);
let checksum = get_partial_checksum::<100, blake2::Blake2b512>(&OsString::from(file_path));
let expected_result = String::from(
"fa9ecc82691c5939c7872dc3e39d26a50831e122cbcfc1738001c980233e213dc\
e9e16feb07bdfb93a60ea73e6fa90aca9ce6dd56e5b0626224627b6bc3ad278",
);
assert!(checksum.is_ok());
assert_eq!(expected_result, checksum.unwrap());
Ok(())
}
}