use {
anyhow::{Result, anyhow},
clap::ValueEnum,
rayon::prelude::*,
sha2::{Digest, Sha256, Sha512, digest::DynDigest},
std::{
fmt::Write as _,
fs::File,
io::{BufRead, BufReader, Read, Write, copy},
path::Path,
},
};
#[cfg(test)]
mod tests;
const BUFFER_SIZE: usize = 4096;
#[derive(Clone, Copy, Debug, clap::ValueEnum)]
pub enum Hash {
Blake3,
Sha256,
Sha512,
Blake3Sha256,
Blake3Sha512,
Sha256Sha512,
All,
}
impl Hash {
pub fn hash_file<P: AsRef<Path>>(&self, file: P) -> Result<Vec<(String, String)>> {
match self {
Hash::Blake3 => file_blake3(file),
Hash::Sha256 => file_sha256(file),
Hash::Sha512 => file_sha512(file),
Hash::Blake3Sha256 => file_blake3_sha256(file),
Hash::Blake3Sha512 => file_blake3_sha512(file),
Hash::Sha256Sha512 => file_sha256_sha512(file),
Hash::All => file_all(file),
}
}
#[allow(clippy::missing_panics_doc)]
pub fn process_file<P: AsRef<Path>>(&self, file: P) -> Result<String> {
let file = file.as_ref();
let hashes = self.hash_file(file)?;
Ok(if let Ok(expected) = self.expected(file) {
format!(
"{}: {}",
file.display(),
if hashes == expected { "OK" } else { "FAILED" },
)
} else {
let mut r = vec![];
for (ckfile, hash) in &hashes {
let mut ckfile = File::create(ckfile)?;
let filename = file.file_name().unwrap().to_str().unwrap();
let content = format!("{hash} {filename}\n");
ckfile.write_all(content.as_bytes())?;
r.push(format!("{hash} {}", file.display()));
}
r.join("\n")
})
}
pub fn expected<P: AsRef<Path>>(&self, file: P) -> Result<Vec<(String, String)>> {
let file = file.as_ref();
let mut r = vec![];
let ckfiles = match self {
Hash::Blake3 => vec![format!("{}.b3", file.display())],
Hash::Sha256 => vec![format!("{}.sha256", file.display())],
Hash::Sha512 => vec![format!("{}.sha512", file.display())],
Hash::Blake3Sha256 => vec![
format!("{}.b3", file.display()),
format!("{}.sha256", file.display()),
],
Hash::Blake3Sha512 => vec![
format!("{}.b3", file.display()),
format!("{}.sha512", file.display()),
],
Hash::Sha256Sha512 => vec![
format!("{}.sha256", file.display()),
format!("{}.sha512", file.display()),
],
Hash::All => vec![
format!("{}.b3", file.display()),
format!("{}.sha256", file.display()),
format!("{}.sha512", file.display()),
],
};
for ckfile in ckfiles {
let mut reader = BufReader::new(File::open(&ckfile)?);
let mut expected = String::new();
reader.read_line(&mut expected)?;
expected = expected.lines().next().unwrap().to_string();
expected.truncate(expected.find(' ').unwrap_or(expected.len()));
r.push((ckfile, expected));
}
Ok(r)
}
}
pub fn file_sha256<P: AsRef<Path>>(file: P) -> Result<Vec<(String, String)>> {
let file = file.as_ref();
let mut f = BufReader::new(File::open(file)?);
let mut hasher = Sha256::new();
let mut buffer = [0; BUFFER_SIZE];
loop {
let bytes_read = f.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
Digest::update(&mut hasher, &buffer[..bytes_read]);
}
let mut buffer = vec![0; hasher.output_size()];
DynDigest::finalize_into(hasher, &mut buffer)?;
let hash = to_hex_string(&buffer);
Ok(vec![(
format!("{}.sha256", file.display()),
format!("SHA256:{hash}"),
)])
}
pub fn file_sha512<P: AsRef<Path>>(file: P) -> Result<Vec<(String, String)>> {
let file = file.as_ref();
let mut f = File::open(file)?;
let mut hasher = Sha512::new();
let mut buffer = [0; BUFFER_SIZE];
loop {
let bytes_read = f.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
Digest::update(&mut hasher, &buffer[..bytes_read]);
}
let mut buffer = vec![0; hasher.output_size()];
DynDigest::finalize_into(hasher, &mut buffer)?;
let hash = to_hex_string(&buffer);
Ok(vec![(
format!("{}.sha512", file.display()),
format!("SHA512:{hash}"),
)])
}
pub fn file_blake3<P: AsRef<Path>>(file: P) -> Result<Vec<(String, String)>> {
let file = file.as_ref();
let mut f = File::open(file)?;
let mut hasher = blake3::Hasher::new();
copy(&mut f, &mut hasher)?;
Ok(vec![(
format!("{}.b3", file.display()),
format!("BLAKE3:{}", hasher.finalize()),
)])
}
pub fn file_blake3_sha256<P: AsRef<Path>>(file: P) -> Result<Vec<(String, String)>> {
let file = file.as_ref();
let mut f = File::open(file)?;
let mut hasher_b3 = blake3::Hasher::new();
let mut hasher_sha256 = Sha256::new();
let mut buffer = [0; BUFFER_SIZE];
loop {
let bytes_read = f.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
let buf = &buffer[..bytes_read];
hasher_b3.update(buf);
Digest::update(&mut hasher_sha256, buf);
}
let mut buffer = vec![0; hasher_sha256.output_size()];
DynDigest::finalize_into(hasher_sha256, &mut buffer)?;
let hash_sha256 = to_hex_string(&buffer);
Ok(vec![
(
format!("{}.b3", file.display()),
format!("BLAKE3:{}", hasher_b3.finalize()),
),
(
format!("{}.sha256", file.display()),
format!("SHA256:{hash_sha256}"),
),
])
}
pub fn file_blake3_sha512<P: AsRef<Path>>(file: P) -> Result<Vec<(String, String)>> {
let file = file.as_ref();
let mut f = File::open(file)?;
let mut hasher_b3 = blake3::Hasher::new();
let mut hasher_sha512 = Sha512::new();
let mut buffer = [0; BUFFER_SIZE];
loop {
let bytes_read = f.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
let buf = &buffer[..bytes_read];
hasher_b3.update(buf);
Digest::update(&mut hasher_sha512, buf);
}
let mut buffer = vec![0; hasher_sha512.output_size()];
DynDigest::finalize_into(hasher_sha512, &mut buffer)?;
let hash_sha512 = to_hex_string(&buffer);
Ok(vec![
(
format!("{}.b3", file.display()),
format!("BLAKE3:{}", hasher_b3.finalize()),
),
(
format!("{}.sha512", file.display()),
format!("SHA512:{hash_sha512}"),
),
])
}
pub fn file_sha256_sha512<P: AsRef<Path>>(file: P) -> Result<Vec<(String, String)>> {
let file = file.as_ref();
let mut f = File::open(file)?;
let mut hasher_sha256 = Sha256::new();
let mut hasher_sha512 = Sha512::new();
let mut buffer = [0; BUFFER_SIZE];
loop {
let bytes_read = f.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
let buf = &buffer[..bytes_read];
Digest::update(&mut hasher_sha256, buf);
Digest::update(&mut hasher_sha512, buf);
}
let mut buffer = vec![0; hasher_sha256.output_size()];
DynDigest::finalize_into(hasher_sha256, &mut buffer)?;
let hash_sha256 = to_hex_string(&buffer);
let mut buffer = vec![0; hasher_sha512.output_size()];
DynDigest::finalize_into(hasher_sha512, &mut buffer)?;
let hash_sha512 = to_hex_string(&buffer);
Ok(vec![
(
format!("{}.sha256", file.display()),
format!("SHA256:{hash_sha256}"),
),
(
format!("{}.sha512", file.display()),
format!("SHA512:{hash_sha512}"),
),
])
}
pub fn file_all<P: AsRef<Path>>(file: P) -> Result<Vec<(String, String)>> {
let file = file.as_ref();
let mut f = File::open(file)?;
let mut hasher_b3 = blake3::Hasher::new();
let mut hasher_sha256 = Sha256::new();
let mut hasher_sha512 = Sha512::new();
let mut buffer = [0; BUFFER_SIZE];
loop {
let bytes_read = f.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
let buf = &buffer[..bytes_read];
hasher_b3.update(buf);
Digest::update(&mut hasher_sha256, buf);
Digest::update(&mut hasher_sha512, buf);
}
let mut buffer = vec![0; hasher_sha256.output_size()];
DynDigest::finalize_into(hasher_sha256, &mut buffer)?;
let hash_sha256 = to_hex_string(&buffer);
let mut buffer = vec![0; hasher_sha512.output_size()];
DynDigest::finalize_into(hasher_sha512, &mut buffer)?;
let hash_sha512 = to_hex_string(&buffer);
Ok(vec![
(
format!("{}.b3", file.display()),
format!("BLAKE3:{}", hasher_b3.finalize()),
),
(
format!("{}.sha256", file.display()),
format!("SHA256:{hash_sha256}"),
),
(
format!("{}.sha512", file.display()),
format!("SHA512:{hash_sha512}"),
),
])
}
#[derive(Clone, Debug, ValueEnum)]
pub enum ProcessOption {
RayonParIter,
SequentialForLoop,
SequentialIter,
Threading,
Messaging,
}
impl ProcessOption {
pub fn run<P: AsRef<Path> + Clone + Send + Sync + 'static>(
&self,
files: &[P],
hash: Hash,
) -> Vec<Result<String>> {
match self {
ProcessOption::SequentialForLoop => seq_for_loop(files, hash),
ProcessOption::SequentialIter => seq_iter(files, hash),
ProcessOption::Threading => threading(files, hash),
ProcessOption::Messaging => messaging(files, hash),
ProcessOption::RayonParIter => rayon_par_iter(files, hash),
}
}
}
pub fn seq_for_loop<P: AsRef<Path> + Clone + Send + Sync + 'static>(
files: &[P],
hash: Hash,
) -> Vec<Result<String>> {
let mut r = vec![];
for file in files {
r.push(hash.process_file(file));
}
r
}
pub fn seq_iter<P: AsRef<Path> + Clone + Send + Sync + 'static>(
files: &[P],
hash: Hash,
) -> Vec<Result<String>> {
files.iter().map(|file| hash.process_file(file)).collect()
}
pub fn threading<P: AsRef<Path> + Clone + Send + Sync + 'static>(
files: &[P],
hash: Hash,
) -> Vec<Result<String>> {
let mut r = vec![];
let mut handles = vec![];
for file in files.iter().cloned() {
handles.push(std::thread::spawn(move || hash.process_file(file)));
}
for handle in handles {
match handle.join() {
Ok(t) => {
r.push(t);
}
Err(e) => {
r.push(Err(anyhow!(format!("{e:?}"))));
}
}
}
r
}
pub fn messaging<P: AsRef<Path> + Clone + Send + Sync + 'static>(
files: &[P],
hash: Hash,
) -> Vec<Result<String>> {
let mut r = vec![];
let mut rxs = vec![];
for file in files.iter().cloned() {
let (tx, rx) = std::sync::mpsc::channel();
rxs.push(rx);
std::thread::spawn(move || tx.send(hash.process_file(file)).unwrap());
}
for rx in rxs {
match rx.recv() {
Ok(t) => {
r.push(t);
}
Err(e) => {
r.push(Err(anyhow!(format!("{e:?}"))));
}
}
}
r
}
pub fn rayon_par_iter<P: AsRef<Path> + Clone + Send + Sync + 'static>(
files: &[P],
hash: Hash,
) -> Vec<Result<String>> {
files
.par_iter()
.map(|file| hash.process_file(file))
.collect()
}
fn to_hex_string(buffer: &[u8]) -> String {
buffer.iter().fold(String::new(), |mut output, b| {
let _ = write!(output, "{b:02x}");
output
})
}