extern crate clap;
extern crate sha3;
extern crate data_encoding;
extern crate num_cpus;
mod wrapper;
use clap::{Parser,ArgGroup};
use std::process::exit;
use std::{fmt, fs, thread, io};
use std::path::Path;
use std::io::{Error, ErrorKind};
use wrapper::Sha3Mode;
use crate::wrapper::{hash_from_file, read_check_file, hash_from_reader};
use crate::sha3::*;
use std::sync::mpsc;
pub const LICENSE: &str = "GPL-3.0-or-later";
pub const NO_BREAK_SPACE : char = '\u{00a0}';
pub const EXIT_CODE_OK: i32 = 0;
pub const EXIT_CODE_NOK: i32 = 1;
pub const EXIT_CODE_WRONG_PARAMETERS: i32 = 2;
pub const EXIT_CODE_FILE_ERROR: i32 = 64;
pub const EXIT_CODE_HASH_NOT_EQUAL: i32 = 65;
#[derive(PartialEq, Clone, Copy)]
pub enum Mode {
Binary,
Text,
}
impl fmt::Debug for Mode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Mode::Binary => write!(f, "Binary"),
Mode::Text => write!(f, "Text"),
}
}
}
#[derive(Clone)]
pub struct HashTask {
mode : Mode,
file_name : String,
hash_algorithm : Sha3Mode,
is_bsd_display: bool,
ref_hash : Option<String>,
is_quiet : bool,
is_status : bool,
}
struct Worker {
sender: Option<mpsc::Sender<HashTask>>
}
#[derive(Parser)]
#[command(author=env!("CARGO_PKG_AUTHORS"), version=env!("CARGO_PKG_VERSION"), about=env!("CARGO_PKG_DESCRIPTION"), long_about = None)]
#[command(next_line_help = true)]
#[command(group(
ArgGroup::new("sum")
.args(["algorithm","tag"])
.conflicts_with("check"),
))]
#[command(group(
ArgGroup::new("hash")
.args(["check"])
.conflicts_with("algorithm"),
))]
#[command(group(
ArgGroup::new("read_mode")
.args(["text"])
.conflicts_with("binary"),
))]
#[command(group(
ArgGroup::new("show_help")
.args(["license"])
.conflicts_with_all(["binary","text","quiet","status","tag","check"]),
))]
struct Cli {
#[arg(short, long,help = "sha3 algorithm {224 256 384 512 Keccak224 Keccak256 Keccak256Full Keccak384 Keccak512}")]
algorithm: Option<String>,
#[arg(short, long, help = "read SHA3 sums and file path from a file and check them")]
check: Option<String>,
#[arg(short, long, default_value_t = true ,help = "read using Binary mode (default)")]
binary: bool,
#[arg(long, help = "don't print OK for each successfully verified file")]
quiet : bool,
#[arg(long, help = "don't output anything, status code shows success")]
status : bool,
#[arg(long, help = "create a BSD-style checksum")]
tag : bool,
#[arg(short, long, help = "read using Text mode")]
text: bool,
#[arg(short, long, help = "Prints license information")]
license: bool,
#[arg(help = "Displays the check sum for the files")]
files: Option<Vec<String>>,
}
fn main() {
let mut exit_code :i32 = EXIT_CODE_WRONG_PARAMETERS;
let cli = Cli::parse();
if cli.license {
println!("The license is {}. The gpl.txt file contains the full Text.", LICENSE);
exit_code = EXIT_CODE_OK;
}
let mut mode = Mode::Binary;
if cli.text{
mode = Mode::Text
}
if let Some(check) = cli.check.as_deref() {
let file_name = check;
let result_list = read_check_file(file_name, cli.status);
if let Ok(list)=result_list.as_ref(){
let mut tasks:Vec<HashTask> = Vec::new();
for item in list.iter() {
let task = HashTask {
mode : item.mode,
file_name: item.file_name.clone(),
hash_algorithm: item.algorithm,
is_bsd_display: false,
ref_hash: Some(item.hash.clone()),
is_quiet: cli.quiet,
is_status: cli.status,
};
tasks.push(task);
}
exit_code = do_hashes(tasks);
} else {
if !cli.status {
eprintln!("{}",result_list.unwrap_err());
}
exit_code = EXIT_CODE_FILE_ERROR;
}
} else if let Some(algorithm) = cli.algorithm.as_deref() {
let selected_sha3_mode: Option<Sha3Mode> = match algorithm.to_lowercase().as_str() {
"224" => Some(Sha3Mode::Sha3_224),
"256" => Some(Sha3Mode::Sha3_256),
"384" => Some(Sha3Mode::Sha3_384),
"512" => Some(Sha3Mode::Sha3_512),
"sha3_224" => Some(Sha3Mode::Sha3_224),
"sha3_256" => Some(Sha3Mode::Sha3_256),
"sha3_384" => Some(Sha3Mode::Sha3_384),
"sha3_512" => Some(Sha3Mode::Sha3_512),
"keccak224" => Some(Sha3Mode::Keccak224),
"keccak256" => Some(Sha3Mode::Keccak256),
"keccak256full" => Some(Sha3Mode::Keccak256Full),
"keccak384" => Some(Sha3Mode::Keccak384),
"keccak512" => Some(Sha3Mode::Keccak512),
"shake128" => unimplemented!(),
"shake256" => unimplemented!(),
v => {
eprintln!("Invalid value for algorithm. {}", v);
None
},
};
if let Some(selected_mode) = selected_sha3_mode{
if let Some(files) = cli.files.as_deref() {
let mut all_file: Vec<String> = Vec::new();
for param in files {
let candidate = Path::new(param.as_str());
if candidate.is_file() {
all_file.push(param.to_string());
} else if candidate.is_dir() {
if let Ok(files) = fs::read_dir(param) {
files.filter_map(Result::ok)
.filter(|d| d.metadata().unwrap().is_file())
.filter(|d| !d.file_name().into_string().unwrap().starts_with('.'))
.for_each(|f| {
let file_name = f.path().to_str().unwrap().to_string();
all_file.push(file_name);
});
}
} else {
eprintln!("Error file: {} has been rejected.", param);
}
}
let mut tasks: Vec<HashTask> = Vec::new();
for file in all_file {
let task = HashTask {
mode,
file_name: file,
hash_algorithm: selected_mode,
is_bsd_display: cli.tag,
ref_hash: None,
is_quiet: false,
is_status: false,
};
tasks.push(task);
}
exit_code = do_hashes(tasks);
} else {
let result = match selected_mode {
Sha3Mode::Sha3_224 => hash_from_reader::<Sha3_224>(Box::new(io::stdin())),
Sha3Mode::Sha3_256 => hash_from_reader::<Sha3_256>(Box::new(io::stdin())),
Sha3Mode::Sha3_384 => hash_from_reader::<Sha3_384>(Box::new(io::stdin())),
Sha3Mode::Sha3_512 => hash_from_reader::<Sha3_512>(Box::new(io::stdin())),
Sha3Mode::Keccak224 => hash_from_reader::<Keccak224>(Box::new(io::stdin())),
Sha3Mode::Keccak256 => hash_from_reader::<Keccak256>(Box::new(io::stdin())),
Sha3Mode::Keccak384 => hash_from_reader::<Keccak384>(Box::new(io::stdin())),
Sha3Mode::Keccak256Full => hash_from_reader::<Keccak256Full>(Box::new(io::stdin())),
Sha3Mode::Keccak512 => hash_from_reader::<Keccak512>(Box::new(io::stdin())),
_ => Err(Error::new(ErrorKind::Other, "Could not determine algorithm.")),
};
if let Ok(hash) = result.as_ref() {
display_result("-", Some(Mode::Binary), hash.as_str(), None, selected_sha3_mode, false, false);
exit_code = EXIT_CODE_OK;
} else {
eprintln!("{}", result.unwrap_err());
exit_code = EXIT_CODE_FILE_ERROR;
}
}
} else {
exit_code = EXIT_CODE_WRONG_PARAMETERS;
}
}
exit(exit_code);
}
fn do_hashes(tasks:Vec<HashTask>) -> i32 {
let mut result = EXIT_CODE_OK;
if tasks.is_empty() {
result = EXIT_CODE_FILE_ERROR;
} else if tasks.len() == 1 {
let task = tasks.first();
result = execute_task(task.unwrap().clone());
} else {
let result_chn: (mpsc::Sender<i32>, mpsc::Receiver<i32>) = mpsc::channel();
let mut workers : Vec<Worker> = Vec::new();
create_workers(workers.as_mut(), result_chn.0);
let nb_worker = workers.len();
let nb_task = tasks.len();
for i in 0..tasks.len() {
let worker_id = i % nb_worker;
let worker = workers.get(worker_id);
let sender = worker.unwrap().sender.as_ref().unwrap();
let task = tasks.get(i);
sender.send(task.unwrap().clone()).expect("Expect send a task.");
}
for (task_done_cnt, x) in result_chn.1.iter().enumerate() {
if x != EXIT_CODE_OK {
result += x;
}
if task_done_cnt >= nb_task -1 {
break;
}
}
}
result
}
fn create_workers(workers: &mut Vec<Worker>, rlt_sender : mpsc::Sender<i32>) {
let cpus = num_cpus::get();
for i in 0..cpus {
let task_chn: (mpsc::Sender<HashTask>, mpsc::Receiver<HashTask>) = mpsc::channel();
let task_sender = task_chn.0.clone();
let result_sender = rlt_sender.clone();
let builder = thread::Builder::new().name(format!("{}", i));
builder
.spawn(move || {
let receiver = task_chn.1;
for task in receiver.iter() {
let code = execute_task(task);
result_sender.send(code).unwrap();
}
})
.unwrap_or_else(|_| panic!("Expect no error from thread {}", i));
let worker = Worker {
sender: Some(task_sender),
};
workers.push(worker);
}
}
fn execute_task (task : HashTask) -> i32 {
let mut result_code : i32 = EXIT_CODE_OK;
let result = match task.hash_algorithm {
Sha3Mode::Sha3_224 => hash_from_file::<Sha3_224>(task.file_name.as_str(), task.mode,task.is_status),
Sha3Mode::Sha3_256 => hash_from_file::<Sha3_256>(task.file_name.as_str(), task.mode,task.is_status),
Sha3Mode::Sha3_384 => hash_from_file::<Sha3_384>(task.file_name.as_str(), task.mode,task.is_status),
Sha3Mode::Sha3_512 => hash_from_file::<Sha3_512>(task.file_name.as_str(), task.mode,task.is_status),
Sha3Mode::Keccak224 => hash_from_file::<Keccak224>(task.file_name.as_str(), task.mode,task.is_status),
Sha3Mode::Keccak256 => hash_from_file::<Keccak256>(task.file_name.as_str(), task.mode,task.is_status),
Sha3Mode::Keccak384 => hash_from_file::<Keccak384>(task.file_name.as_str(), task.mode,task.is_status),
Sha3Mode::Keccak256Full => hash_from_file::<Keccak256Full>(task.file_name.as_str(), task.mode,task.is_status),
Sha3Mode::Keccak512 => hash_from_file::<Keccak512>(task.file_name.as_str(), task.mode,task.is_status),
_ => Err(Error::new(ErrorKind::Other, "Could not determine algorithm.")),
};
if let Ok(hash) = result.as_ref() {
let display_mode= if task.is_bsd_display { None } else { Some(task.mode) };
display_result(task.file_name.as_str(),display_mode,hash.as_str(),task.ref_hash.as_ref(),Some(task.hash_algorithm),task.is_quiet,task.is_status);
if let Some(the_ref)=task.ref_hash.as_ref() {
if !the_ref.eq_ignore_ascii_case(hash) {
result_code = EXIT_CODE_HASH_NOT_EQUAL;
}
}
} else {
if !task.is_status {
eprintln!("{}",result.unwrap_err());
}
result_code = EXIT_CODE_FILE_ERROR;
};
result_code
}
fn display_result (file_name:&str, mode : Option<Mode>, hash: &str, ref_hash: Option<&String>, algorithm : Option<Sha3Mode>, is_quiet : bool, is_status:bool) {
let mut text : Option<String> = None;
if let Some(the_ref)=ref_hash {
let is_hash_equal = the_ref.eq_ignore_ascii_case(hash);
let hash_match = if is_hash_equal { "Ok" } else { "NOk" };
if !(is_status || is_quiet && is_hash_equal) {
text = Some(format!("{}{}{}",file_name,NO_BREAK_SPACE,hash_match));
}
} else if let Some(the_mode)=mode {
text = match the_mode {
Mode::Binary => Some(format!("{}{}*{}",hash,NO_BREAK_SPACE,file_name)),
_ => Some(format!("{}{}{}",hash,NO_BREAK_SPACE,file_name)),
}
} else {
text = Some(format!("{}{}({}){}={}{}",algorithm.unwrap(),NO_BREAK_SPACE,file_name,NO_BREAK_SPACE,NO_BREAK_SPACE,hash));
}
if text.is_some() {
println!("{}",text.unwrap());
}
}