use std::{fmt, fs, io};
use crate::sha3::Digest;
use std::fs::{OpenOptions};
use std::io::{BufReader, BufRead, Read};
use data_encoding::{HEXLOWER};
use crate::Mode;
const DEFAULT_BLOCK_SIZE: u64 = 65536;
fn get_block_size() -> u64 {
match option_env!("SHA3_BLOCK_SIZE") {
Some(v) => v.parse::<u64>().unwrap_or(DEFAULT_BLOCK_SIZE),
None => DEFAULT_BLOCK_SIZE
}
}
#[derive(PartialEq, Clone, Copy)]
pub enum Sha3Mode {
Sha3_224,
Sha3_256,
Sha3_384,
Sha3_512,
Keccak224,
Keccak256,
Keccak256Full,
Keccak384,
Keccak512,
Shake128,
Shake256,
}
impl fmt::Debug for Sha3Mode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Sha3Mode::Sha3_224 => write!(f, "SHA3_224"),
Sha3Mode::Sha3_256 => write!(f, "SHA3_256"),
Sha3Mode::Sha3_512 => write!(f, "SHA3_512"),
Sha3Mode::Sha3_384 => write!(f, "SHA3_384"),
Sha3Mode::Keccak224 => write!(f, "KECCAK224"),
Sha3Mode::Keccak256 => write!(f, "KECCAK256"),
Sha3Mode::Keccak256Full => write!(f, "KECCAK256FULL"),
Sha3Mode::Keccak384 => write!(f, "KECCAK384"),
Sha3Mode::Keccak512 => write!(f, "KECCAK512"),
Sha3Mode::Shake128 => write!(f, "SHAKE128"),
Sha3Mode::Shake256 => write!(f, "SHAKE256"),
}
}
}
impl fmt::Display for Sha3Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Sha3Mode::Sha3_224 => write!(f, "SHA3_224"),
Sha3Mode::Sha3_256 => write!(f, "SHA3_256"),
Sha3Mode::Sha3_512 => write!(f, "SHA3_512"),
Sha3Mode::Sha3_384 => write!(f, "SHA3_384"),
Sha3Mode::Keccak224 => write!(f, "KECCAK224"),
Sha3Mode::Keccak256 => write!(f, "KECCAK256"),
Sha3Mode::Keccak256Full => write!(f, "KECCAK256FULL"),
Sha3Mode::Keccak384 => write!(f, "KECCAK384"),
Sha3Mode::Keccak512 => write!(f, "KECCAK512"),
Sha3Mode::Shake128 => write!(f, "SHAKE128"),
Sha3Mode::Shake256 => write!(f, "SHAKE256"),
}
}
}
impl From<String> for Sha3Mode {
fn from(mode: String) -> Self {
match mode.to_uppercase().as_str() {
"SHA3_224" => Sha3Mode::Sha3_224,
"224" => Sha3Mode::Sha3_224,
"SHA3_256" => Sha3Mode::Sha3_256,
"256" => Sha3Mode::Sha3_256,
"SHA3_384" => Sha3Mode::Sha3_384,
"384" => Sha3Mode::Sha3_384,
"SHA3_512" => Sha3Mode::Sha3_512,
"512" => Sha3Mode::Sha3_512,
"KECCAK224" => Sha3Mode::Keccak224,
"KECCAK256" => Sha3Mode::Keccak256,
"KECCAK256FULL" => Sha3Mode::Keccak256Full,
"KECCAK384" => Sha3Mode::Keccak384,
"KECCAK512" => Sha3Mode::Keccak512,
"SHAKE128" => Sha3Mode::Shake128,
"SHAKE256" => Sha3Mode::Shake256,
v @ &_ => panic!("Could not convert {} to Sha3 algorithm.",v),
}
}
}
impl From<Sha3Mode> for &str{
fn from(sha3: Sha3Mode) -> &'static str {
match sha3 {
Sha3Mode::Sha3_224 => "SHA3_224",
Sha3Mode::Sha3_256 => "SHA3_256",
Sha3Mode::Sha3_512 => "SHA3_512",
Sha3Mode::Sha3_384 => "SHA3_384",
Sha3Mode::Keccak224 => "KECCAK224",
Sha3Mode::Keccak256 => "KECCAK256",
Sha3Mode::Keccak256Full => "KECCAK256FULL",
Sha3Mode::Keccak384 => "KECCAK384",
Sha3Mode::Keccak512 => "KECCAK512",
Sha3Mode::Shake128 =>"SHAKE128",
Sha3Mode::Shake256 => "SHAKE256",
}
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct CheckFileInfo {
pub algorithm: Sha3Mode,
pub mode: Mode,
pub is_bsd_format: bool,
pub hash: String,
pub file_name: String,
}
pub fn read_check_file(file_name: &str,is_status:bool) -> Result<Vec <CheckFileInfo>,io::Error>{
let result_file_handler = OpenOptions::new()
.read(true)
.write(false)
.create(false)
.open(file_name);
if result_file_handler.is_err() {
let message = format!("Could not open file {}.",file_name);
if !is_status {
eprintln!("{}",message);
}
let error = result_file_handler.err().unwrap_or_else(|| io::Error::new(io::ErrorKind::NotFound, message));
return Err(error);
}
let file_handler = result_file_handler.unwrap();
let buffered = BufReader::new(file_handler);
let mut result: Vec <CheckFileInfo> = Vec::new();
for r_line in buffered.lines() {
let line : String = r_line.unwrap();
let tokens : Vec<&str> = line.split_whitespace().collect();
match tokens.len() {
2 => {
let hash = tokens.get(0).unwrap().to_string();
let mode = if tokens.get(1).unwrap().starts_with('*') {Mode::Binary} else { Mode::Text };
let file = match mode {
Mode::Binary => tokens.get(1).unwrap().trim_start_matches('*'),
_=> tokens.get(1).unwrap(),
};
let algorithm = match tokens.get(0).unwrap().len() {
56 => Ok(Sha3Mode::Sha3_224),
64 => Ok(Sha3Mode::Sha3_256),
96 => Ok(Sha3Mode::Sha3_384),
128 => Ok(Sha3Mode::Sha3_512),
_ => Err(io::Error::new(io::ErrorKind::Other, "Could not determine algorithm. Malformed line")),
};
if algorithm.is_err() {
return Err(algorithm.err().unwrap());
}
let s_line = CheckFileInfo {
algorithm: algorithm.unwrap(),
mode,
is_bsd_format: false,
hash,
file_name: file.to_string(),
};
result.push(s_line);
},
4 => {
let s_line = CheckFileInfo {
algorithm: Sha3Mode::from(tokens.get(0).unwrap().to_string()),
mode: Mode::Binary,
is_bsd_format: true,
hash: tokens.get(3).unwrap().to_string(),
file_name: tokens.get(1).unwrap().trim_start_matches('(').trim_end_matches(')').to_string(),
};
result.push(s_line);
},
_ => {
let message = format!("Could not tokenize the line. Malformed line {}",line);
return Err(io::Error::new(io::ErrorKind::Other, message));
},
}
}
Ok(result)
}
pub fn hash_from_file <D: Digest>(file_name: &str,mode : Mode,is_status:bool) -> Result<String,io::Error> {
let result_file_handler = OpenOptions::new()
.read(true)
.write(false)
.create(false)
.open(file_name);
if result_file_handler.is_err() {
let message = format!("Could not open file {}.",file_name);
if !is_status {
eprintln!("{}",message);
}
let error = result_file_handler.err().unwrap_or_else(|| io::Error::new(io::ErrorKind::NotFound, message));
return Err(error);
}
let file_handler = result_file_handler.unwrap();
let size = fs::metadata(file_name).unwrap().len();
let mut hasher = D::new();
let mut buffered = BufReader::new(file_handler);
match mode {
Mode::Text => {
for line in buffered.lines() {
let line = line.unwrap();
hasher.update(line);
}
}
_ => {
let block_size = get_block_size();
if size<=block_size {
let mut data : Vec<u8> = Vec::with_capacity(size as usize);
buffered.read_to_end(data.as_mut())?;
hasher.update(data);
} else {
let mut position = 0;
while size-position > 0 {
let data_size : usize = if size-position >= block_size { block_size as usize} else { (size-position) as usize };
let mut data : Vec<u8> = vec![0;data_size];
buffered.read_exact(data.as_mut())?;
position += data.len() as u64;
hasher.update(data);
}
}
}
}
let hash = hasher.finalize();
Ok(HEXLOWER.encode(&hash))
}
#[allow(dead_code)]
pub fn hash_from_file_io <D: Digest>(file_name: &str) -> Result<String,io::Error>
where D: std::io::Write
{
let result_file_handler = OpenOptions::new()
.read(true)
.write(false)
.create(false)
.open(file_name);
if result_file_handler.is_err() {
let message = format!("Could not open file {}.",file_name);
eprintln!("{}",message);
let error = result_file_handler.err().unwrap_or_else(|| io::Error::new(io::ErrorKind::NotFound, message));
return Err(error);
}
let file_handler = result_file_handler.unwrap();
let mut buffered = BufReader::new(file_handler);
let mut hasher = D::new();
let _n = io::copy(&mut buffered, &mut hasher)?;
let hash = hasher.finalize();
Ok(HEXLOWER.encode(&hash))
}
pub fn hash_from_reader <D: Digest>(data_reader: Box<dyn Read>) -> Result<String,io::Error>
where D: std::io::Write
{
let mut buffered = BufReader::new(data_reader);
let mut hasher = D::new();
let _n = io::copy(&mut buffered, &mut hasher)?;
let hash = hasher.finalize();
Ok(HEXLOWER.encode(&hash))
}
#[cfg(test)]
mod tests {
use crate::Mode;
use crate::wrapper::{hash_from_file, hash_from_file_io, read_check_file};
use std::time::Instant;
use crate::sha3::*;
const CHECK_FILE : &str = "./tests/check-file.txt";
const CHECK_FILE_NOK : &str = "./tests/check-file_nok.txt";
const F5_FILE : (&str,&str) = ("./tests/data/f5.raw","62412944684d0ff87aaf88f67537f5a1e6df9a12de7d2274c7eaa219c2ac9cf6ae7fb3262cf47657d465683bab868d63b5d0c85c3dc087194bd4c205ad932708");
const F1_FILE : (&str,&str) = ("./tests/data/f1.raw","42e4f1c5b2e098e641bbc4e83700dc7a5dd7be25e43d979ebf6c36f4f33d548c8ebe5b888516794aaa9a8de61ab07e84da86d95dcfd121c0312d13202f6c87e9");
const F1_FILE_224 : (&str,&str) = ("./tests/data/f1.raw","5941ff906cf10dbfa8605512f2b75d3471ebb5844e554bf083f77d5d");
const F1_FILE_256 : (&str,&str) = ("./tests/data/f1.raw","5fceef81eb395c5db97b344b5119a5a6089d6bebe686fd11cc364820a32b1c36");
const F1_FILE_384 : (&str,&str) = ("./tests/data/f1.raw","b50e02127404b8f4a870d07b3fb1342838ac84dd07fc19c4080056bf007b8237040c8d90555dbd26fcfdd365001c5fd3");
const F1_FILE_KECCAK224: (&str, &str) = ("./tests/data/f1.raw", "2b5539fae2c74329d11efc85f3a0273cf6f56d7525ab67b151b98616");
const F1_FILE_KECCAK256: (&str, &str) = ("./tests/data/f1.raw", "2a9d36de8c15de4e17ac939e48e8c46af0cba879a61696474b53fd9e460203c6");
const F1_FILE_KECCAK256FULL: (&str, &str) = ("./tests/data/f1.raw", "2a9d36de8c15de4e17ac939e48e8c46af0cba879a61696474b53fd9e460203c65e20e16d321cfd67b3674a769de85d76fbc5d5bd385efeafaa803b3d27758069ad94feb37c02d7b776bcc4a63e606dd1623da3775b4db3b08fed11e9ab7d27d162fd6e731b662dd8bc6caaf8db5f81c4bd41d808642db19bdd6aadc3e6906500439ad40fb0f91c7d09389dfee7d52934284522de3714965ab2e587e6071f3bd1b0e7000d2840a493dff48e6cc16f2c9446c6b236b9e95b3aabe3f1891e510f17406c158b157bb8f9");
const F1_FILE_KECCAK384: (&str, &str) = ("./tests/data/f1.raw", "a950f25266bf58211f8c75112e2e5d0a506fbb2ac777cc28ea75f0262c9a9ad0aa44828ff5a7051efb1f1ef1a26f463d");
const F1_FILE_KECCAK512: (&str, &str) = ("./tests/data/f1.raw", "7fc7ec2b472038298be81caa81a65fc2430779dd9fbddf46d9c95d380f86572b478a399e3254dcf3949557c40a6c8abb6c4bd0980ddea81ed7813567c89945b6");
const ONE_LINE_FILE : (&str,&str) = ("./tests/data/one-line-text.txt","32400b5e89822de254e8d5d94252c52bdcb27a3562ca593e980364d9848b8041b98eabe16c1a6797484941d2376864a1b0e248b0f7af8b1555a778c336a5bf48");
const UTF8_TEXT_POSIX : (&str,&str) = ("./tests/data/UTF8-Text-POSIX.txt","b6e9fe2e2f52d18950681868c10d95909e06ee50cec4029f5b585445acc325a19bd9b9750da6cbecbb0e7c84b52e40b7b00efd05689c4d466ab9f2b112ee4697");
#[test]
fn test_hash_from_file_f5() {
let start = Instant::now();
let result=hash_from_file::<Sha3_512>(F5_FILE.0,Mode::Binary,false).unwrap();
let duration = start.elapsed();
println!("elapsed time: {:?} hash: {}",duration,result);
assert_eq!(result,F5_FILE.1);
}
#[test]
fn test_hash_from_file_f5_io() {
let start = Instant::now();
let result=hash_from_file_io::<Sha3_512>(F5_FILE.0).unwrap();
let duration = start.elapsed();
println!("elapsed time fct io: {:?} hash: {}",duration,result);
assert_eq!(result,F5_FILE.1);
}
#[test]
fn test_hash_from_file_f1() {
let result=hash_from_file::<Sha3_512>(F1_FILE.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,F1_FILE.1);
}
#[test]
fn test_hash_from_file_f1_224() {
let result=hash_from_file::<Sha3_224>(F1_FILE_224.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,F1_FILE_224.1);
}
#[test]
fn test_hash_from_file_f1_256() {
let result=hash_from_file::<Sha3_256>(F1_FILE_256.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,F1_FILE_256.1);
}
#[test]
fn test_hash_from_file_f1_384() {
let result=hash_from_file::<Sha3_384>(F1_FILE_384.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,F1_FILE_384.1);
}
#[test]
fn test_hash_from_file_f1_keccak256() {
let result=hash_from_file::<Keccak256>(F1_FILE_KECCAK256.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,F1_FILE_KECCAK256.1);
}
#[test]
fn test_hash_from_file_f1_keccak256full() {
let result=hash_from_file::<Keccak256Full>(F1_FILE_KECCAK256FULL.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,F1_FILE_KECCAK256FULL.1);
}
#[test]
fn test_hash_from_file_f1_keccak384() {
let result=hash_from_file::<Keccak384>(F1_FILE_KECCAK384.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,F1_FILE_KECCAK384.1);
}
#[test]
fn test_hash_from_file_f1_keccak512() {
let result=hash_from_file::<Keccak512>(F1_FILE_KECCAK512.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,F1_FILE_KECCAK512.1);
}
#[test]
fn test_hash_from_file_f1_keccak224() {
let result=hash_from_file::<Keccak224>(F1_FILE_KECCAK224.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,F1_FILE_KECCAK224.1);
}
#[test]
fn test_hash_from_file_one_line() {
let result=hash_from_file::<sha3::Sha3_512>(ONE_LINE_FILE.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,ONE_LINE_FILE.1);
}
#[test]
fn test_hash_from_file_one_line_mode_text() {
let result=hash_from_file::<Sha3_512>(ONE_LINE_FILE.0,Mode::Text,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,ONE_LINE_FILE.1);
}
#[test]
#[cfg_attr(target_os = "windows", ignore)]
fn test_hash_from_file_utf8_txt_bin() {
let result=hash_from_file::<Sha3_512>(UTF8_TEXT_POSIX.0,Mode::Binary,false).unwrap();
println!("hash: {}",result);
assert_eq!(result,UTF8_TEXT_POSIX.1);
}
#[test]
fn test_hash_from_file_utf8_txt_txt() {
let result=hash_from_file::<Sha3_512>(UTF8_TEXT_POSIX.0,Mode::Text,false).unwrap();
println!("hash: {}",result);
assert_ne!(result,UTF8_TEXT_POSIX.1);
}
#[test]
fn test_read_check_file() {
let result = read_check_file(CHECK_FILE,false);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(),6);
}
#[test]
fn test_read_check_file_nok() {
let result = read_check_file(CHECK_FILE_NOK,false);
assert!(result.is_err());
}
}