use crate::{open_file, Arguments, MyResult};
use ahash::AHasher;
use blake3::Hasher as Blake3Hasher;
use clap::ValueEnum;
use rustc_hash::FxHasher;
use serde::Serialize;
use sha2::{Digest, Sha256, Sha512};
use std::{
fmt,
fs::File,
hash::Hasher,
io::{BufReader, Read},
path::PathBuf,
};
const FIRST_BYTES: usize = 80;
const BUFFER_SIZE: usize = 64 * 1024;
const HEX: [char; 16] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
];
pub trait SliceExtension {
fn to_hex_string(self) -> String;
}
impl SliceExtension for &[u8] {
fn to_hex_string(self) -> String {
self.iter()
.flat_map(|byte| {
let a: char = HEX[(*byte as usize) / 16];
let b: char = HEX[(*byte as usize) % 16];
[a, b]
})
.collect()
}
}
pub trait PathBufExtension {
fn get_hash(&self, arguments: &Arguments, procedure: u8) -> MyResult<Option<String>>;
}
impl PathBufExtension for PathBuf {
fn get_hash(&self, arguments: &Arguments, procedure: u8) -> MyResult<Option<String>> {
let mut file: File = open_file(self)?;
let hash: String = if procedure == 3 {
arguments.algorithm.calculate_hash(file)?
} else {
let mut buffer = [0_u8; FIRST_BYTES];
let count = file.read(&mut buffer)?;
let mut hasher = AHasher::default();
hasher.write(&buffer[..count]);
hasher.finish().to_string()
};
Ok(Some(hash))
}
}
#[derive(Debug, Default, Clone, Copy, ValueEnum, Serialize)]
#[serde(rename_all = "PascalCase")]
pub enum Algorithm {
Ahash,
#[default]
Blake3,
Fxhash,
SHA256,
SHA512,
}
impl fmt::Display for Algorithm {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
self.serialize(formatter)
}
}
impl Algorithm {
pub fn calculate_hash(&self, file: File) -> MyResult<String> {
let reader: BufReader<File> = BufReader::with_capacity(BUFFER_SIZE, file);
match self {
Algorithm::Ahash => get_ahash(reader),
Algorithm::Blake3 => get_blake3(reader),
Algorithm::Fxhash => get_fxhash(reader),
Algorithm::SHA256 => get_sha256(reader),
Algorithm::SHA512 => get_sha512(reader),
}
}
}
fn get_ahash(mut reader: impl Read) -> MyResult<String> {
let mut buffer = [0_u8; BUFFER_SIZE];
let mut hasher = AHasher::default();
loop {
let count = reader.read(&mut buffer)?;
if count == 0 {
break;
}
hasher.write(&buffer[..count]);
}
let hash: String = hasher.finish().to_string();
Ok(hash)
}
fn get_blake3<R>(mut reader: R) -> MyResult<String>
where
R: Read,
{
let mut buffer = [0_u8; BUFFER_SIZE];
let mut hasher = Blake3Hasher::new();
loop {
let count = reader.read(&mut buffer)?;
if count == 0 {
break;
}
hasher.update(&buffer[..count]);
}
let hash: String = hasher.finalize().to_string();
Ok(hash)
}
fn get_fxhash<R>(mut reader: R) -> MyResult<String>
where
R: Read,
{
let mut buffer = [0_u8; BUFFER_SIZE];
let mut hasher = FxHasher::default();
loop {
let count = reader.read(&mut buffer)?;
if count == 0 {
break;
}
hasher.write(&buffer[..count]);
}
let hash: String = hasher.finish().to_string();
Ok(hash)
}
fn get_sha256<R>(mut reader: R) -> MyResult<String>
where
R: Read,
{
let mut buffer = [0_u8; BUFFER_SIZE];
let mut hasher = Sha256::new();
loop {
let count = reader.read(&mut buffer)?;
if count == 0 {
break;
}
hasher.update(&buffer[..count]);
}
let hash: String = hasher.finalize().to_hex_string();
Ok(hash)
}
fn get_sha512<R>(mut reader: R) -> MyResult<String>
where
R: Read,
{
let mut buffer = [0_u8; BUFFER_SIZE];
let mut hasher = Sha512::new();
loop {
let count = reader.read(&mut buffer)?;
if count == 0 {
break;
}
hasher.update(&buffer[..count]);
}
let hash: String = hasher.finalize().to_hex_string();
Ok(hash)
}