use std::{fmt, fs, io};
use sha3::Digest;
use std::fs::OpenOptions;
use std::io::{BufRead, BufReader, 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" | "224" => Sha3Mode::Sha3_224,
"SHA3_256" | "256" => Sha3Mode::Sha3_256,
"SHA3_384" | "384" => Sha3Mode::Sha3_384,
"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 {v} to Sha3 algorithm."),
}
}
}
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,
}
fn open_file_or_err(
result: io::Result<std::fs::File>,
file_name: &str,
is_status: bool,
) -> io::Result<std::fs::File> {
result.map_err(|e| {
let message = format!("Could not open file {file_name}.");
if !is_status {
eprintln!("{message}");
}
io::Error::new(e.kind(), message)
})
}
pub fn read_check_file(file_name: &str, is_status: bool) -> Result<Vec<CheckFileInfo>, io::Error> {
let file_handler = open_file_or_err(
OpenOptions::new().read(true).write(false).create(false).open(file_name),
file_name,
is_status,
)?;
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.rsplit(crate::NO_BREAK_SPACE).collect();
match tokens.len() {
2 => {
let hash = tokens.get(1).unwrap().to_string();
let mode = if tokens.first().unwrap().starts_with('*') { Mode::Binary } else { Mode::Text };
let file = match mode {
Mode::Binary => tokens.first().unwrap().trim_start_matches('*'),
_ => tokens.get(1).unwrap(),
};
let algorithm = match tokens.get(1).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::other("Could not determine algorithm. Malformed line")),
};
result.push(CheckFileInfo {
algorithm: algorithm?,
mode,
is_bsd_format: false,
hash,
file_name: file.to_string(),
});
}
4 => {
result.push(CheckFileInfo {
algorithm: Sha3Mode::from(tokens.get(3).unwrap().to_string()),
mode: Mode::Binary,
is_bsd_format: true,
hash: tokens.first().unwrap().to_string(),
file_name: tokens.get(2).unwrap().trim_start_matches('(').trim_end_matches(')').to_string(),
});
}
_ => {
let message = format!("Could not tokenize the line. Malformed line {line}");
return Err(io::Error::other(message));
}
}
}
Ok(result)
}
pub fn hash_from_file<D: Digest>(file_name: &str, mode: Mode, is_status: bool) -> Result<String, io::Error> {
let file_handler = open_file_or_err(
OpenOptions::new().read(true).write(false).create(false).open(file_name),
file_name,
is_status,
)?;
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() {
hasher.update(line.unwrap());
}
}
Mode::Binary => {
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(&mut data)?;
hasher.update(data);
} else {
let mut position = 0u64;
while size - position > 0 {
let data_size = 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(&mut data)?;
position += data.len() as u64;
hasher.update(data);
}
}
}
}
Ok(HEXLOWER.encode(&hasher.finalize()))
}
#[allow(dead_code)]
pub fn hash_from_file_io<D>(file_name: &str) -> Result<String, io::Error>
where
D: Digest,
{
let file_handler = open_file_or_err(
OpenOptions::new().read(true).write(false).create(false).open(file_name),
file_name,
false,
)?;
let mut buffered = BufReader::new(file_handler);
let mut hasher = D::new();
let block_size = get_block_size() as usize;
let mut buf = vec![0u8; block_size];
loop {
let n = buffered.read(&mut buf)?;
if n == 0 { break; }
hasher.update(&buf[..n]);
}
Ok(HEXLOWER.encode(&hasher.finalize()))
}
pub fn hash_from_reader<D>(data_reader: Box<dyn Read>) -> Result<String, io::Error>
where
D: Digest,
{
let mut buffered = BufReader::new(data_reader);
let mut hasher = D::new();
let block_size = get_block_size() as usize;
let mut buf = vec![0u8; block_size];
loop {
let n = buffered.read(&mut buf)?;
if n == 0 { break; }
hasher.update(&buf[..n]);
}
Ok(HEXLOWER.encode(&hasher.finalize()))
}
#[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 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_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(), 10);
}
#[test]
fn test_read_check_file_nok() {
let result = read_check_file(CHECK_FILE_NOK, false);
assert!(result.is_err());
}
}