diff --git a/.gitignore b/.gitignore index ea8c4bf..d0c7c5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /target +/.idea + +# Example of big file +/tests/win98.iso diff --git a/Cargo.lock b/Cargo.lock index 1048c2a..91f21a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -48,7 +48,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -60,6 +60,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cc" +version = "1.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57c4b4da2a9d619dd035f27316d7a426305b75be93d09e92f2b9229c34feaf" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -143,13 +152,14 @@ dependencies = [ [[package]] name = "ezcheck" -version = "0.1.0" +version = "0.1.1" dependencies = [ "clap", "digest", "md-5", "md2", "md4", + "ring", "sha1", "sha2", ] @@ -164,6 +174,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "heck" version = "0.5.0" @@ -228,6 +249,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "sha1" version = "0.10.6" @@ -250,6 +286,18 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.11.1" @@ -279,6 +327,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.2" @@ -291,6 +345,21 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index e841f43..fa43c25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,28 @@ [package] name = "ezcheck" -version = "0.1.0" +version = "0.1.1" edition = "2021" +description = "An easy tool to calculate and check hash." +repository = "https://github.com/Metaphorme/ezcheck" +documentation = "https://docs.rs/ezcheck" +readme = "README.md" +authors = ["Heqi Liu 77@diazepam.cc"] +license-file = "LICENSE" + +# MSRV +rust-version = "1.71.0" [dependencies] -md2 = { version = "0.10" } -md4 = { version = "0.10" } -md-5 = { version = "0.10" } -sha1 = { version = "0.10" } -sha2 = { version = "0.10" } -digest = { version = "0.10" } -clap = { version = "4.5.20", features = ["derive"]} \ No newline at end of file +md2 = { version = "0.10", optional = true } +md4 = { version = "0.10", optional = true } +md-5 = { version = "0.10", optional = true } +sha1 = { version = "0.10", optional = true } +sha2 = { version = "0.10", optional = true } +digest = { version = "0.10", optional = true } +ring = { version = "0.17" , optional = true } +clap = { version = "4.5", features = ["derive"]} + +[features] +default = [] +hashes_backend = ["md2", "md4", "md-5", "sha1", "sha2", "digest"] +ring_backend = ["ring"] \ No newline at end of file diff --git a/README.md b/README.md index bf213fa..fc94c2a 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,61 @@ # ezcheck -ezcheck(easy check) is an ergonomic, standard-output command-line tool for calculating, comparing, and verifying the hashes of strings and files. +ezcheck(easy check) is an ergonomic, standard-output command-line tool for calculating, comparing, and verifying the hash of strings and files. -ezcheck supports a lot of hash algorithm: MD2(Unsafe), MD4(Unsafe), MD5(Unsafe), SHA1(Unsafe), SHA224, SHA256, SHA384, SHA512. Although many of the hash algorithms are proven to be insecure, ezcheck still provides them for maximum compatibility, but it does not recommend users continue to use them. +ezcheck have two backends: [ring](https://docs.rs/ring) and [hashes](https://docs.rs/hashes), and you can only choose one of them. The main differences between them are: + +| Features | ring | hashes | +|----------------------|------------------------------------------------------------|-----------------------------------------------------------------| +| Speed | Fast | About 10 times slower than ring. | +| Supported algorithms | SHA256, SHA384, SHA512, SHA512/256 | MD2, MD4, MD5, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512/256 | +| Implement languages | Assembly, Rust, C and etc.. | Rust | +| Compatibility | May not work on every machine with different architecture. | Works well with Rust. | + +⚠️ Please notice that although ezcheck(hashes backend) supports a lot of hash algorithms, `MD2`, `MD4`, `MD5`, `SHA1` are proven to be **insecure**. ezcheck still provides them for maximum compatibility, but **it does not recommend users continue to use them**. ## Build ### Requirements -* [Rust](https://www.rust-lang.org/) +* [Rust 1.71.0+](https://www.rust-lang.org/) ### Build ```bash $ git clone https://github.com/Metaphorme/ezcheck $ cd ezcheck -$ cargo build --release +$ # Choose one from ring backend or hashes backend +$ # ring backend +$ cargo build --release --features ring_backend +$ # hashes backend +$ cargo build --release --features hashes_backend +$ $ ./target/release/ezcheck --version ``` ### Run tests ```bash -$ cargo test +$ cargo test --features ring_backend # ring backend +$ cargo test --features hashes_backend # hashes backend ``` ## Usage -Supported hash algorithms: -* MD2(Unsafe) -* MD4(Unsafe) -* MD5(Unsafe) -* SHA1(Unsafe) -* SHA224 -* SHA256 -* SHA384 -* SHA512 +Supported hash algorithms of different backends: + +| ring | hashes | +|------------|------------| +| | MD2 | +| | MD4 | +| | MD5 | +| | SHA1 | +| | SHA224 | +| SHA256 | SHA256 | +| SHA384 | SHA384 | +| SHA512 | SHA512 | +| SHA512 | SHA512 | +| SHA512/256 | SHA512/256 | ### Calculate @@ -76,11 +96,10 @@ $ ezcheck compare sha256 -t "Hello" -c 085f8db32271fe25f561a6fc938b2e264306ec304 SHA256 FAILED Current Hash: 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969 # Auto detect hash algorithm -$ ezcheck compare -f image.jpg -c cb74bb502cc0949aad5cd838f91f0623 -INFO: Hash Algorithm could be MD2, MD4, MD5 -MD2 FAILED Current Hash: 10329710371ab70392948fcef544f728 -MD4 FAILED Current Hash: bebc102992450c68e5543383889e27c9 -MD5 OK +$ ezcheck compare -f image.jpg -c bebc102992450c68e5543383889e27c9 +INFO: Hash Algorithm could be MD5, MD4, MD2 +MD5 FAILED Current Hash: cb74bb502cc0949aad5cd838f91f0623 +MD4 OK ``` ### Check @@ -106,23 +125,27 @@ $ ezcheck check sha256 -c sha256sum.txt image.jpg: SHA256 OK # Auto detect hash algorithm +$ cat sha256sum.txt +9ec44ac67ab1e1c98fe0406478d5297d 滕王阁序.txt +bebc102992450c68e5543383889e27c9 image.jpg $ ezcheck check -c sha256sum.txt -滕王阁序.txt: SHA256 OK -image.jpg: SHA256 OK +滕王阁序.txt: MD5 FAILED Current Hash: 07c4e6a2c2db5f2d3a8998a3dba84a96 +滕王阁序.txt: MD4 OK +image.jpg: MD5 FAILED Current Hash: cb74bb502cc0949aad5cd838f91f0623 +image.jpg: MD4 OK # Actually, ezcheck supports various algorithm in the same check file in auto detect. # 🤔 But why this happens? $ cat sha256sum.txt 00691413c731ee37f551bfaca6a34b8443b3e85d7c0816a6fe90aa8fc8eaec95 滕王阁序.txt 4c03795a6bca220a68eae7c4f136d6247d58671e074bccd58a3b9989da55f56f *image.jpg -cb74bb502cc0949aad5cd838f91f0623 image.jpg +9ec44ac67ab1e1c98fe0406478d5297d 滕王阁序.txt $ ezcheck check -c sha256sum.txt 滕王阁序.txt: SHA256 OK image.jpg: SHA256 OK -image.jpg: MD2 FAILED Current Hash: 10329710371ab70392948fcef544f728 -image.jpg: MD4 FAILED Current Hash: bebc102992450c68e5543383889e27c9 -image.jpg: MD5 OK +滕王阁序.txt: MD5 FAILED Current Hash: 07c4e6a2c2db5f2d3a8998a3dba84a96 +滕王阁序.txt: MD4 OK ``` ## License diff --git a/benchmark.py b/benchmark.py new file mode 100644 index 0000000..6e31ba5 --- /dev/null +++ b/benchmark.py @@ -0,0 +1,84 @@ +# /usr/bin/env python3 +# A simple scrip to benchmark +import subprocess +import time +import os +import platform +from datetime import datetime + +def measure_command_time(command): + start_time = time.time() + result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + end_time = time.time() + elapsed_time = end_time - start_time + # print(f"Command Output:\n{result.stdout.decode()}") + if result.stderr: + return ("Error", f"{command} Error:\n{result.stderr.decode()}") + + return ("Ok", round(elapsed_time, 4)) + +# Bytes -> MB +def file_size_mb(file_path): + return round(os.stat(file_path).st_size / (1024 * 1024), 4) + +# Speed +def calculate_speed(file_size_MB, time_Second): + if time_Second == "Error": + return "Error" + else: + return round(file_size_MB / time_Second, 4) + +def get_git_commit_hash(): + try: + result = subprocess.run( + ['git', 'rev-parse', '--short', 'HEAD'], + capture_output=True, + text=True, + check=True + ) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + print("Error running git command:", e) + return None + +if __name__ == "__main__": + file_to_test = "tests/image.jpg" + commands = { + "sha256sum": "sha256sum " + file_to_test, + "ezcheck(ring)": " target/release/ezcheck calculate sha256 -f " + file_to_test + # "ezcheck(hashes)": " target/release/ezcheck calculate sha256 -f win98.iso" + } + + file_size_MB = file_size_mb(file_to_test) + git_hash = get_git_commit_hash() + + # It runs slowly at the first time after being compiled. + measure_command_time("target/release/ezcheck -V") + + results = [] + for name, command in commands.items(): + r = measure_command_time(command) + if r[0] == 'Ok': + results.append([name, r[1]]) + else: + print(r[1]) + results.append([name, "Error"]) + + sorted_results = sorted(results, key=lambda x: (x[1] == 'Error', x[1])) + + + print("+{:-^53}+".format("BENCHMARK-RESULTS")) + print("| Test file: {: <40}|".format(file_to_test)) + print("| File size: {: <40}|".format(str(file_size_MB) + " MB")) + print("| Git hash: {: <40}|".format(git_hash)) + print("+-----------------+-----------------+-----------------+") + print("|{: ^17}|{: ^17}|{: ^17}|".format("Command", "Speed(MB/s)", "Time(s)")) + print("+-----------------+-----------------+-----------------+") + + for r in sorted_results: + print("|{: ^17}|{: ^17}|{: ^17}|".format(r[0], calculate_speed(file_size_MB, r[1]), r[1])) + + print("+-----------------+-----------------+-----------------+") + print() + print("Platform version: " + platform.version()) + print("Run time: " + datetime.now().strftime("%Y-%m-%d %H:%M:%S")) diff --git a/src/calculator.rs b/src/calculator.rs index a2c8bef..c71d125 100644 --- a/src/calculator.rs +++ b/src/calculator.rs @@ -1,135 +1,238 @@ -pub mod calculator { - use std::fmt; - use std::io::{BufReader, Error, Read}; - use digest::DynDigest; - use crate::extra::extra::bytes_to_hex; - - // https://github.com/rust-lang/rust/issues/47133 - #[allow(dead_code)] - pub const BUFFER_SIZE: usize = 4096; - - #[derive(Copy, Clone, Debug, PartialEq)] - pub enum SupportedAlgorithm { - MD2, - MD4, - MD5, - SHA1, - SHA224, - SHA256, - SHA384, - SHA512, +#[cfg(all(feature = "hashes_backend", feature = "ring_backend"))] +compile_error!("Feature `hashes_backend` and feature `ring_backend` cannot be enabled at the same time."); +#[cfg(not(any(feature = "hashes_backend", feature = "ring_backend")))] +compile_error!("You must enable at least one of the features: 'hashes_backend' or 'ring_backend'."); + +use std::fmt; +use std::io::{BufReader, Error, Read}; +use crate::extra::bytes_to_hex; + +#[cfg(feature = "hashes_backend")] +use digest::DynDigest; + +#[cfg(feature = "ring_backend")] +use ring::digest::{Context, SHA256, SHA384, SHA512, SHA512_256}; + +// https://github.com/rust-lang/rust/issues/47133 +#[allow(dead_code)] +pub const BUFFER_SIZE: usize = 8192; + +#[cfg(feature = "hashes_backend")] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum SupportedAlgorithm { + MD2, + MD4, + MD5, + SHA1, + SHA224, + SHA256, + SHA384, + SHA512, + SHA512_256, +} + +#[cfg(feature = "hashes_backend")] +impl fmt::Display for SupportedAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let algorithm = match self { + SupportedAlgorithm::MD2 => "MD2", + SupportedAlgorithm::MD4 => "MD4", + SupportedAlgorithm::MD5 => "MD5", + SupportedAlgorithm::SHA1 => "SHA1", + SupportedAlgorithm::SHA224 => "SHA224", + SupportedAlgorithm::SHA256 => "SHA256", + SupportedAlgorithm::SHA384 => "SHA384", + SupportedAlgorithm::SHA512 => "SHA512", + SupportedAlgorithm::SHA512_256 => "SHA512_256", + }; + write!(f, "{}", algorithm) } +} - impl fmt::Display for SupportedAlgorithm { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let algorithm = match self { - SupportedAlgorithm::MD2 => "MD2", - SupportedAlgorithm::MD4 => "MD4", - SupportedAlgorithm::MD5 => "MD5", - SupportedAlgorithm::SHA1 => "SHA1", - SupportedAlgorithm::SHA224 => "SHA224", - SupportedAlgorithm::SHA256 => "SHA256", - SupportedAlgorithm::SHA384 => "SHA384", - SupportedAlgorithm::SHA512 => "SHA512", - }; - write!(f, "{}", algorithm) +#[cfg(feature = "hashes_backend")] +pub fn hash_calculator( + mut reader: BufReader, + algorithm: SupportedAlgorithm) +-> Result { + + let mut hasher: Box = match algorithm { + SupportedAlgorithm::MD2 => Box::new(md2::Md2::default()), + SupportedAlgorithm::MD4 => Box::new(md4::Md4::default()), + SupportedAlgorithm::MD5 => Box::new(md5::Md5::default()), + SupportedAlgorithm::SHA1 => Box::new(sha1::Sha1::default()), + SupportedAlgorithm::SHA224 => Box::new(sha2::Sha224::default()), + SupportedAlgorithm::SHA256 => Box::new(sha2::Sha256::default()), + SupportedAlgorithm::SHA384 => Box::new(sha2::Sha384::default()), + SupportedAlgorithm::SHA512 => Box::new(sha2::Sha512::default()), + SupportedAlgorithm::SHA512_256 => Box::new(sha2::Sha512_256::default()), + }; + + let mut buffer = [0u8; BUFFER_SIZE]; + loop { + match reader.read(&mut buffer) { + Ok(read_bytes) => { + if read_bytes == 0 { + break; // Finish reading the file + } + hasher.update(&buffer[..read_bytes]); + } + Err(e) => { + return Err(e); + } } } - pub fn hash_calculator( - mut reader: BufReader, - algorithm: SupportedAlgorithm) - -> Result { - - let mut hasher: Box = match algorithm { - SupportedAlgorithm::MD2 => Box::new(md2::Md2::default()), - SupportedAlgorithm::MD4 => Box::new(md4::Md4::default()), - SupportedAlgorithm::MD5 => Box::new(md5::Md5::default()), - SupportedAlgorithm::SHA1 => Box::new(sha1::Sha1::default()), - SupportedAlgorithm::SHA224 => Box::new(sha2::Sha224::default()), - SupportedAlgorithm::SHA256 => Box::new(sha2::Sha256::default()), - SupportedAlgorithm::SHA384 => Box::new(sha2::Sha384::default()), - SupportedAlgorithm::SHA512 => Box::new(sha2::Sha512::default()), + Ok(bytes_to_hex(&*hasher.finalize_reset())) +} + +// fn sent_data_to_digest(mut reader: BufReader, hasher: ) {} + +#[cfg(feature = "ring_backend")] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum SupportedAlgorithm { + SHA256, + SHA384, + SHA512, + SHA512_256, +} + +#[cfg(feature = "ring_backend")] +impl fmt::Display for SupportedAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let algorithm = match self { + SupportedAlgorithm::SHA256 => "SHA256", + SupportedAlgorithm::SHA384 => "SHA384", + SupportedAlgorithm::SHA512 => "SHA512", + SupportedAlgorithm::SHA512_256 => "SHA512_256", }; + write!(f, "{}", algorithm) + } +} - let mut buffer = [0u8; BUFFER_SIZE]; - loop { - match reader.read(&mut buffer) { - Ok(read_bytes) => { - if read_bytes == 0 { - break; // Finish reading the file - } - hasher.update(&buffer[..read_bytes]); - } - Err(e) => { - return Err(e); +#[cfg(feature = "ring_backend")] +pub fn hash_calculator( + mut reader: BufReader, + algorithm: SupportedAlgorithm) +-> Result { + let mut hasher: Context = match algorithm { + SupportedAlgorithm::SHA256 => Context::new(&SHA256), + SupportedAlgorithm::SHA384 => Context::new(&SHA384), + SupportedAlgorithm::SHA512 => Context::new(&SHA512), + SupportedAlgorithm::SHA512_256 => Context::new(&SHA512_256), + }; + + let mut buffer = [0u8; BUFFER_SIZE]; + loop { + match reader.read(&mut buffer) { + Ok(read_bytes) => { + if read_bytes == 0 { + break; // Finish reading the file } + hasher.update(&buffer[..read_bytes]); + } + Err(e) => { + return Err(e); } } - - Ok(bytes_to_hex(&*hasher.finalize_reset())) } + + Ok(bytes_to_hex(&hasher.finish().as_ref().to_vec())) } #[cfg(test)] mod test_calculator { use std::io::BufReader; use std::fs::File; - use super::calculator; + use super::*; const TEST_WORD: &[u8; 16] = b"Veni, vidi, vici"; + #[cfg(feature = "hashes_backend")] #[test] - fn test_algorithm() { + fn test_md2() { let reader = BufReader::new(&TEST_WORD[..]); assert_eq!( - calculator::hash_calculator(reader, calculator::SupportedAlgorithm::MD2).unwrap(), + hash_calculator(reader, SupportedAlgorithm::MD2).unwrap(), "3354cef96052efb872e8c0391a5cfb34" ); + } + #[cfg(feature = "hashes_backend")] + #[test] + fn test_md4() { let reader = BufReader::new(&TEST_WORD[..]); assert_eq!( - calculator::hash_calculator(reader, calculator::SupportedAlgorithm::MD4).unwrap(), + hash_calculator(reader, SupportedAlgorithm::MD4).unwrap(), "5c79b96c023c5a269ad205d33bce0f60" ); + } + #[cfg(feature = "hashes_backend")] + #[test] + fn test_md5() { let reader = BufReader::new(&TEST_WORD[..]); assert_eq!( - calculator::hash_calculator(reader, calculator::SupportedAlgorithm::MD5).unwrap(), + hash_calculator(reader, SupportedAlgorithm::MD5).unwrap(), "af1e16b12fec10c5ad09fb6478005b6c" ); + } + #[cfg(feature = "hashes_backend")] + #[test] + fn test_sha1() { let reader = BufReader::new(&TEST_WORD[..]); assert_eq!( - calculator::hash_calculator(reader, calculator::SupportedAlgorithm::SHA1).unwrap(), + hash_calculator(reader, SupportedAlgorithm::SHA1).unwrap(), "5df99149d56d7f82a9751ac4c36ada25d07f5e49" ); + } + #[cfg(feature = "hashes_backend")] + #[test] + fn test_sha224() { let reader = BufReader::new(&TEST_WORD[..]); assert_eq!( - calculator::hash_calculator(reader, calculator::SupportedAlgorithm::SHA224).unwrap(), + hash_calculator(reader, SupportedAlgorithm::SHA224).unwrap(), "9111df25d5715bc4ab42d6777f48d1bd592f7f991fbbc356ae370167" ); + } + #[test] + fn test_sha256() { let reader = BufReader::new(&TEST_WORD[..]); assert_eq!( - calculator::hash_calculator(reader, calculator::SupportedAlgorithm::SHA256).unwrap(), + hash_calculator(reader, SupportedAlgorithm::SHA256).unwrap(), "b1610284c94bbf9aa78333e57ddce234a5e845d61e09ce91a7e19fa24737f466" ); + } + #[test] + fn test_sha384() { let reader = BufReader::new(&TEST_WORD[..]); assert_eq!( - calculator::hash_calculator(reader, calculator::SupportedAlgorithm::SHA384).unwrap(), + hash_calculator(reader, SupportedAlgorithm::SHA384).unwrap(), "aed14590fa99f83c701236d63c50085faa8e57c7196846411dc595c42751e5e17d6bc10b767541d76eecdda086c5d4fc" ); + } + #[test] + fn test_sha512() { let reader = BufReader::new(&TEST_WORD[..]); assert_eq!( - calculator::hash_calculator(reader, calculator::SupportedAlgorithm::SHA512).unwrap(), + hash_calculator(reader, SupportedAlgorithm::SHA512).unwrap(), "6cf905a2c09fa2d9090f2712e2ae6d0fc8188cc845a1dc9dff4b3bd33e9d4fa43991cbb7cc3cf5d5aa8e32098796eb01e3f03c25c6ea863226e617ad6e5abec2" ); } + #[test] + fn test_sha512_256() { + let reader = BufReader::new(&TEST_WORD[..]); + assert_eq!( + hash_calculator(reader, SupportedAlgorithm::SHA512_256).unwrap(), + "aea4f1ce7ac12b2374482816aa44d33935fb445d8e8892aeb501c82a97f76d8d" + ); + } + #[test] fn test_read_file() { let test_file = "tests/滕王阁序.txt"; @@ -137,8 +240,8 @@ mod test_calculator { let reader = BufReader::new(file); assert_eq!( - calculator::hash_calculator(reader, calculator::SupportedAlgorithm::SHA256).unwrap(), + hash_calculator(reader, SupportedAlgorithm::SHA256).unwrap(), "00691413c731ee37f551bfaca6a34b8443b3e85d7c0816a6fe90aa8fc8eaec95" ); } -} \ No newline at end of file +} diff --git a/src/extra.rs b/src/extra.rs index d9cb32b..cff1233 100644 --- a/src/extra.rs +++ b/src/extra.rs @@ -1,54 +1,69 @@ -pub mod extra { - use std::fmt::Write; - use crate::calculator::calculator::SupportedAlgorithm; - - // Bytes to Hex - pub fn bytes_to_hex(bytes: &[u8]) -> String { - let mut hex_string = String::with_capacity(bytes.len() * 2); - for byte in bytes { - write!(hex_string, "{:02x}", byte).unwrap(); - } - hex_string +#[cfg(all(feature = "hashes_backend", feature = "ring_backend"))] +compile_error!("Feature `hashes_backend` and feature `ring_backend` cannot be enabled at the same time."); +#[cfg(not(any(feature = "hashes_backend", feature = "ring_backend")))] +compile_error!("You must enable at least one of the features: 'hashes_backend' or 'ring_backend'."); + +use std::fmt::Write; +use crate::calculator::SupportedAlgorithm; + +// Bytes to Hex +pub fn bytes_to_hex(bytes: &[u8]) -> String { + let mut hex_string = String::with_capacity(bytes.len() * 2); + for byte in bytes { + write!(hex_string, "{:02x}", byte).unwrap(); } + hex_string +} - // // Hex to Bytes - // use std::io; - // pub fn hex_to_bytes(hex: &str) -> Result, io::Error> { - // if hex.len() % 2 != 0 { - // return Err(io::Error::new(io::ErrorKind::InvalidData, "Error: Invalid hex.")); - // } - // - // (0..hex.len()) - // .step_by(2) - // .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))) - // .collect() - // } +// // Hex to Bytes +// use std::io; +// pub fn hex_to_bytes(hex: &str) -> Result, io::Error> { +// if hex.len() % 2 != 0 { +// return Err(io::Error::new(io::ErrorKind::InvalidData, "Error: Invalid hex.")); +// } +// +// (0..hex.len()) +// .step_by(2) +// .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))) +// .collect() +// } + +// Detect hash algorithm +#[cfg(feature = "hashes_backend")] +pub fn detect_hash_algorithm>(hash: S) + -> Result, String> { + match hash.as_ref().len() { + 40 => Ok(vec!(SupportedAlgorithm::SHA1)), + 56 => Ok(vec!(SupportedAlgorithm::SHA224)), + 64 => Ok(vec!(SupportedAlgorithm::SHA256, SupportedAlgorithm::SHA512_256)), + 96 => Ok(vec!(SupportedAlgorithm::SHA384)), + 128 => Ok(vec!(SupportedAlgorithm::SHA512)), + 32 => Ok(vec!(SupportedAlgorithm::MD5, SupportedAlgorithm::MD4, SupportedAlgorithm::MD2)), + _ => Err(String::from("Error: Invalid hash.")), + } +} - // Detect hash algorithm - pub fn detect_hash_algorithm>(hash: S) - -> Result, String> { - match hash.as_ref().len() { - 40 => Ok(vec!(SupportedAlgorithm::SHA1)), - 56 => Ok(vec!(SupportedAlgorithm::SHA224)), - 64 => Ok(vec!(SupportedAlgorithm::SHA256)), - 96 => Ok(vec!(SupportedAlgorithm::SHA384)), - 128 => Ok(vec!(SupportedAlgorithm::SHA512)), - 32 => Ok(vec!(SupportedAlgorithm::MD2, SupportedAlgorithm::MD4, SupportedAlgorithm::MD5)), - _ => Err(String::from("Error: Invalid hash.")), - } +#[cfg(feature = "ring_backend")] +pub fn detect_hash_algorithm>(hash: S) + -> Result, String> { + match hash.as_ref().len() { + 64 => Ok(vec!(SupportedAlgorithm::SHA256, SupportedAlgorithm::SHA512_256)), + 96 => Ok(vec!(SupportedAlgorithm::SHA384)), + 128 => Ok(vec!(SupportedAlgorithm::SHA512)), + _ => Err(String::from("Error: Invalid hash.")), } } #[cfg(test)] mod test_extra { - use super::extra; - use crate::calculator::calculator::SupportedAlgorithm; + use super::*; + use crate::calculator::SupportedAlgorithm; // #[test] // fn test_transform_of_bytes_and_hex() { // let hex = "00691413c731ee37f551bfaca6a34b8443b3e85d7c0816a6fe90aa8fc8eaec95"; // assert_eq!( - // extra::bytes_to_hex(&extra::hex_to_bytes(hex).unwrap()), + // extra::bytes_to_hex(&hex_to_bytes(hex).unwrap()), // hex // ) // } @@ -56,7 +71,7 @@ mod test_extra { #[test] fn test_detect_hash_algorithm() { assert_eq!( - extra::detect_hash_algorithm("00691413c731ee37f551bfaca6a34b8443b3e85d7c0816a6fe90aa8fc8eaec95").unwrap()[0], + detect_hash_algorithm("00691413c731ee37f551bfaca6a34b8443b3e85d7c0816a6fe90aa8fc8eaec95").unwrap()[0], SupportedAlgorithm::SHA256 ) } diff --git a/src/lib.rs b/src/lib.rs index e08c92e..cf9439e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,196 +1,226 @@ +#[cfg(all(feature = "hashes_backend", feature = "ring_backend"))] +compile_error!("Feature `hashes_backend` and feature `ring_backend` cannot be enabled at the same time."); +#[cfg(not(any(feature = "hashes_backend", feature = "ring_backend")))] +compile_error!("You must enable at least one of the features: 'hashes_backend' or 'ring_backend'."); + pub mod calculator; pub mod extra; +use std::fmt; +use std::fs::File; +use std::io::{BufRead, BufReader}; -pub mod core { - use std::fmt; - use std::fs::File; - use std::io::{BufRead, BufReader}; - use crate::calculator::calculator; - use crate::extra::extra; +pub struct Calculate { + data: Data, + algorithm: calculator::SupportedAlgorithm, +} - pub trait Compute { - fn compute(&self) -> Result; +impl Calculate { + pub fn new(data: Data, algorithm: calculator::SupportedAlgorithm) -> Calculate { + Self { + data, + algorithm, + } } +} - pub struct Calculate { - data: Data, - algorithm: calculator::SupportedAlgorithm, +impl Calculate { + pub fn compute(&self) -> Result { + self.data.compute_hash(self.algorithm) } +} - impl Calculate { - pub fn new(data: Data, algorithm: calculator::SupportedAlgorithm) -> Calculate { - Self { - data, - algorithm, - } - } - } +pub struct Compare { + pub data: Data, + compare: String, + algorithm: calculator::SupportedAlgorithm, +} - impl Compute for Calculate { - fn compute(&self) -> Result { - self.data.compute_hash(self.algorithm) +impl Compare { + pub fn new(data: Data, compare: String, algorithm: calculator::SupportedAlgorithm) -> Compare { + Self { + data, + compare, + algorithm, } } +} - pub struct Compare { - pub data: Data, - compare: String, - algorithm: calculator::SupportedAlgorithm, - } +#[derive(Debug)] +pub enum IfMatch { + Match(String), + Failed(String), +} - impl Compare { - pub fn new(data: Data, compare: String, algorithm: calculator::SupportedAlgorithm) -> Compare { - Self { - data, - compare, - algorithm, - } +impl PartialEq for IfMatch { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (IfMatch::Match(_), IfMatch::Match(_)) => true, + (IfMatch::Failed(_), IfMatch::Failed(_)) => true, + _ => false, } } +} - impl Compute for Compare { - fn compute(&self) -> Result { - let hash_result = match self.data.compute_hash(self.algorithm) { - Ok(hash_result) => hash_result, - Err(error) => return Err(error), - }; +impl Compare { + pub fn compute(&self) -> Result { + let hash_result = match self.data.compute_hash(self.algorithm) { + Ok(hash_result) => hash_result, + Err(error) => return Err(error), + }; - if hash_result == self.compare { - Ok(format!("{:8} OK", self.algorithm)) - } else { - Ok(format!("{:8} FAILED Current Hash: {}", self.algorithm, hash_result)) - } + if hash_result == self.compare { + Ok(IfMatch::Match(format!("{:8} OK", self.algorithm))) + } else { + Ok(IfMatch::Failed(format!("{:8} FAILED Current Hash: {}", self.algorithm, hash_result))) } } +} - pub enum Data { - ReadFile(String), // Input data from a file - // Stream, // Input data from data stream - Text(String), // Input data from user input - } +pub enum Data { + ReadFile(String), // Input data from a file + // Stream, // Input data from data stream + Text(String), // Input data from user input +} - impl fmt::Display for Data { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let algorithm = match self { - Data::ReadFile(file_name) => file_name, - Data::Text(text) => text, - }; - write!(f, "{}", algorithm) - } +impl fmt::Display for Data { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let algorithm = match self { + Data::ReadFile(file_name) => file_name, + Data::Text(text) => text, + }; + write!(f, "{}", algorithm) } +} - pub trait ComputeHash { - fn compute_hash(&self, algorithm: calculator::SupportedAlgorithm) -> Result; - } +pub trait ComputeHash { + fn compute_hash(&self, algorithm: calculator::SupportedAlgorithm) -> Result; +} + +impl ComputeHash for Data { + fn compute_hash(&self, algorithm: calculator::SupportedAlgorithm) -> Result { + match self { + Data::ReadFile(path) => { + if let Err(e) = File::open(path) { + Err(format!("Error: Error opening file: {}", e)) + } else { + let file = File::open(path).unwrap(); + let reader = BufReader::new(file); - impl ComputeHash for Data { - fn compute_hash(&self, algorithm: calculator::SupportedAlgorithm) -> Result { - match self { - Data::ReadFile(path) => { - if let Err(e) = File::open(path) { - Err(format!("Error: Error opening file: {}", e)) - } else { - let file = File::open(path).unwrap(); - let reader = BufReader::new(file); - - match calculator::hash_calculator(reader, algorithm) { - Ok(hash) => Ok(hash), - Err(e) => Err(format!("Error: Error calculating hash: {}", e)), - } - } - } - Data::Text(text) => { - let reader = BufReader::new(text.as_bytes()); match calculator::hash_calculator(reader, algorithm) { Ok(hash) => Ok(hash), Err(e) => Err(format!("Error: Error calculating hash: {}", e)), } } } + Data::Text(text) => { + let reader = BufReader::new(text.as_bytes()); + match calculator::hash_calculator(reader, algorithm) { + Ok(hash) => Ok(hash), + Err(e) => Err(format!("Error: Error calculating hash: {}", e)), + } + } } } +} - pub fn match_algorithm>(algorithm: S) -> Result { - let algorithm = algorithm.as_ref(); - - match algorithm { - "MD2" | "Md2" | "mD2" | "md2" => Ok(calculator::SupportedAlgorithm::MD2), - "MD4" | "Md4" | "mD4" | "md4" => Ok(calculator::SupportedAlgorithm::MD4), - "MD5" | "Md5" | "mD5" | "md5" => Ok(calculator::SupportedAlgorithm::MD5), - "sha1" | "Sha1" | "sHa1" | "shA1" | "SHa1" | "sHA1" | "ShA1" | "SHA1" => Ok(calculator::SupportedAlgorithm::SHA1), - "sha224" | "Sha224" | "sHa224" | "shA224" | "SHa224" | "sHA224" | "ShA224" | "SHA224" => Ok(calculator::SupportedAlgorithm::SHA224), - "sha256" | "Sha256" | "sHa256" | "shA256" | "SHa256" | "sHA256" | "ShA256" | "SHA256" => Ok(calculator::SupportedAlgorithm::SHA256), - "sha384" | "Sha384" | "sHa384" | "shA384" | "SHa384" | "sHA384" | "ShA384" | "SHA384" => Ok(calculator::SupportedAlgorithm::SHA384), - "sha512" | "Sha512" | "sHa512" | "shA512" | "SHa512" | "sHA512" | "ShA512" | "SHA512" => Ok(calculator::SupportedAlgorithm::SHA512), - _ => Err(format!("Error: Unsupported algorithm: {}", algorithm)), - } +#[cfg(feature = "hashes_backend")] +pub fn match_algorithm>(algorithm: S) -> Result { + let algorithm = algorithm.as_ref(); + + match algorithm { + "MD2" | "Md2" | "mD2" | "md2" => Ok(calculator::SupportedAlgorithm::MD2), + "MD4" | "Md4" | "mD4" | "md4" => Ok(calculator::SupportedAlgorithm::MD4), + "MD5" | "Md5" | "mD5" | "md5" => Ok(calculator::SupportedAlgorithm::MD5), + "sha1" | "Sha1" | "sHa1" | "shA1" | "SHa1" | "sHA1" | "ShA1" | "SHA1" => Ok(calculator::SupportedAlgorithm::SHA1), + "sha224" | "Sha224" | "sHa224" | "shA224" | "SHa224" | "sHA224" | "ShA224" | "SHA224" => Ok(calculator::SupportedAlgorithm::SHA224), + "sha256" | "Sha256" | "sHa256" | "shA256" | "SHa256" | "sHA256" | "ShA256" | "SHA256" => Ok(calculator::SupportedAlgorithm::SHA256), + "sha384" | "Sha384" | "sHa384" | "shA384" | "SHa384" | "sHA384" | "ShA384" | "SHA384" => Ok(calculator::SupportedAlgorithm::SHA384), + "sha512" | "Sha512" | "sHa512" | "shA512" | "SHa512" | "sHA512" | "ShA512" | "SHA512" => Ok(calculator::SupportedAlgorithm::SHA512), + "sha512_256" | "Sha512_256" | "sHa512_256" | "shA512_256" | "SHa512_256" | "sHA512_256" | "ShA512_256" | "SHA512_256" | "sha512-256" | "Sha512-256" | "sHa512-256" | "shA512-256" | "SHa512-256" | "sHA512-256" | "ShA512-256" | "SHA512-256" => Ok(calculator::SupportedAlgorithm::SHA512_256), + _ => Err(format!("Error: Unsupported algorithm: {}", algorithm)), } +} - pub fn phase_shasum_file> - (shasum_file_path: S, algorithm: Option) - -> Result, String> { - /* - Example shasum file: - ee1fb7719c31070f1fbdc8f2d32370c9d1ca6962 image.png - ee1fb7719c31070f1fbdc8f2d32370c9d1ca6962 *image.png - ^ In binary mode, neglected. - */ - let shasum_file_path = shasum_file_path.as_ref(); - let mut detect_algorithm = true; - if algorithm.is_some() { - detect_algorithm = false; - } +#[cfg(feature = "ring_backend")] +pub fn match_algorithm>(algorithm: S) -> Result { + let algorithm = algorithm.as_ref(); - let file = match File::open(shasum_file_path) { - Ok(file) => file, - Err(error) => return Err(format!("Error: Error opening file: {}", error)), - }; - let reader = BufReader::new(file); + match algorithm { + "sha256" | "Sha256" | "sHa256" | "shA256" | "SHa256" | "sHA256" | "ShA256" | "SHA256" => Ok(calculator::SupportedAlgorithm::SHA256), + "sha384" | "Sha384" | "sHa384" | "shA384" | "SHa384" | "sHA384" | "ShA384" | "SHA384" => Ok(calculator::SupportedAlgorithm::SHA384), + "sha512" | "Sha512" | "sHa512" | "shA512" | "SHa512" | "sHA512" | "ShA512" | "SHA512" => Ok(calculator::SupportedAlgorithm::SHA512), + "sha512_256" | "Sha512_256" | "sHa512_256" | "shA512_256" | "SHa512_256" | "sHA512_256" | "ShA512_256" | "SHA512_256" | "sha512-256" | "Sha512-256" | "sHa512-256" | "shA512-256" | "SHa512-256" | "sHA512-256" | "ShA512-256" | "SHA512-256" => Ok(calculator::SupportedAlgorithm::SHA512_256), + _ => Err(format!("Error: Unsupported algorithm: {}", algorithm)), + } +} - let mut compare_tasks = vec![]; +pub fn phase_shasum_file> +(shasum_file_path: S, algorithm: Option) + -> Result, String> { + /* + Example shasum file: + ee1fb7719c31070f1fbdc8f2d32370c9d1ca6962 image.png + ee1fb7719c31070f1fbdc8f2d32370c9d1ca6962 *image.png + ^ In binary mode, neglected. + */ + let shasum_file_path = shasum_file_path.as_ref(); + let mut detect_algorithm = true; + if algorithm.is_some() { + detect_algorithm = false; + } - for line in reader.lines() { - let line = line.unwrap(); - let parts: Vec<&str> = line.split_whitespace().collect(); // Split + let file = match File::open(shasum_file_path) { + Ok(file) => file, + Err(error) => return Err(format!("Error: Error opening file: {}", error)), + }; + let reader = BufReader::new(file); - if parts.len() == 2 { - let hash = parts[0]; - let mut file_path = parts[1].to_string(); + let mut compare_tasks = vec![]; - let algorithms = match detect_algorithm { - true => { - match extra::detect_hash_algorithm(hash) { - Ok(result) => result, - Err(e) => return Err(format!("{} {}", e, hash)), - } - } - false => vec![algorithm.unwrap()] - }; + for line in reader.lines() { + let line = line.unwrap(); + let parts: Vec<&str> = line.split_whitespace().collect(); // Split - if file_path.starts_with("*") { // Neglect * starts with filename - file_path.remove(0); - } + if parts.len() == 2 { + let hash = parts[0]; + let mut file_path = parts[1].to_string(); - for algorithm in algorithms { - compare_tasks.push(Compare::new( - Data::ReadFile(file_path.clone()), - hash.to_string(), - algorithm, - )); + let algorithms = match detect_algorithm { + true => { + match extra::detect_hash_algorithm(hash) { + Ok(result) => result, + Err(e) => return Err(format!("{} {}", e, hash)), + } } + false => vec![algorithm.unwrap()] + }; - } else { - return Err("Error: Not a valid shasum file.".to_string()); + if file_path.starts_with("*") { // Neglect * starts with filename + file_path.remove(0); } + + for algorithm in algorithms { + compare_tasks.push(Compare::new( + Data::ReadFile(file_path.clone()), + hash.to_string(), + algorithm, + )); + } + + } else if parts.is_empty() { // Blank line + continue; + } else { + return Err("Error: Not a valid shasum file.".to_string()); } - Ok(compare_tasks) } + Ok(compare_tasks) } #[cfg(test)] mod test_core { - use super::core::{Calculate, Compare, Data, Compute}; - use crate::calculator::calculator; + use super::{Calculate, Compare, Data}; + use crate::calculator; + use crate::IfMatch::{Failed, Match}; #[test] fn test_calculate_compute_hash_file() { @@ -225,7 +255,7 @@ mod test_core { ); assert_eq!( task.compute().unwrap(), - "SHA256 OK" + Match("".to_string()) ) } @@ -238,7 +268,7 @@ mod test_core { ); assert_eq!( task.compute().unwrap(), - "SHA256 FAILED Current Hash: b1610284c94bbf9aa78333e57ddce234a5e845d61e09ce91a7e19fa24737f466" + Failed(String::from("")) ) } diff --git a/src/main.rs b/src/main.rs index 89808da..b69f320 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,35 @@ +#[cfg(all(feature = "hashes_backend", feature = "ring_backend"))] +compile_error!("Feature `hashes_backend` and feature `ring_backend` cannot be enabled at the same time."); +#[cfg(not(any(feature = "hashes_backend", feature = "ring_backend")))] +compile_error!("You must enable at least one of the features: 'hashes_backend' or 'ring_backend'."); + use std::process; use clap::{Parser, Subcommand}; -use ezcheck::core::{ Calculate, Data, Compare, Compute, match_algorithm, phase_shasum_file }; -use ezcheck::calculator::calculator::SupportedAlgorithm; -use ezcheck::extra::extra; +use ezcheck::{ Calculate, Data, Compare, IfMatch, match_algorithm, phase_shasum_file }; +use ezcheck::calculator::SupportedAlgorithm; +use ezcheck::extra; + +#[cfg(feature = "hashes_backend")] +#[derive(Parser)] +#[command(name = "ezcheck")] +#[command(version = "0.1.1 (Hashes Backend)")] +#[command(about = "An easy tool to calculate and check hash.\nMade with love by Heqi Liu, https://github.com/metaphorme")] +struct Cli { + #[command(subcommand)] + args: Args, +} +#[cfg(feature = "ring_backend")] #[derive(Parser)] #[command(name = "ezcheck")] -#[command(version = "0.1.0")] +#[command(version = "0.1.1 (Ring Backend)")] #[command(about = "An easy tool to calculate and check hash.\nMade with love by Heqi Liu, https://github.com/metaphorme")] struct Cli { #[command(subcommand)] args: Args, } +#[cfg(feature = "hashes_backend")] #[derive(Subcommand)] enum Args { /// Calculate hash for a file or text. @@ -27,6 +44,7 @@ enum Args { /// * SHA256(default) /// * SHA384 /// * SHA512 + /// * SHA512/256 #[arg(verbatim_doc_comment)] algorithm: Option, @@ -52,6 +70,7 @@ enum Args { /// * SHA256 /// * SHA384 /// * SHA512 + /// * SHA512/256 #[arg(verbatim_doc_comment)] algorithm: Option, @@ -81,6 +100,73 @@ enum Args { /// * SHA256 /// * SHA384 /// * SHA512 + /// * SHA512/256 + #[arg(verbatim_doc_comment)] + algorithm: Option, + + /// shasum file to check with. + #[arg(short, long)] + check_file: Option, + } +} + +#[cfg(feature = "ring_backend")] +#[derive(Subcommand)] +enum Args { + /// Calculate hash for a file or text. + Calculate { + /// Optional algorithm to use for calculate hash + /// Supported algorithms: + /// * SHA256(default) + /// * SHA384 + /// * SHA512 + /// * SHA512/256 + #[arg(verbatim_doc_comment)] + algorithm: Option, + + /// File to calculate hash, specify filename with -f/--file or directly provide the filename. + #[arg(short, long)] + file: Option, + + /// Direct text input for hash calculation. + #[arg(short, long)] + text: Option, + }, + + /// Compare with given hash. + Compare { + /// Optional algorithm to use for calculate hash. + /// Leave blank to automatically detect the hash algorithm. + /// Supported algorithms: + /// * SHA256 + /// * SHA384 + /// * SHA512 + /// * SHA512/256 + #[arg(verbatim_doc_comment)] + algorithm: Option, + + /// File to calculate hash, specify filename with -f/--file or directly provide the filename. + #[arg(short, long)] + file: Option, + + /// Direct text input for hash comparing. + #[arg(short, long)] + text: Option, + + /// Hash to compare with. + #[arg(short, long)] + check_hash: Option, + }, + + /// Check with given shasum file. + Check { + /// Optional algorithm to use for calculate hash. + /// Leave blank to automatically detect the hash algorithm. + /// Supported algorithms: + /// * SHA256 + /// * SHA384 + /// * SHA512 + /// * SHA512/256 #[arg(verbatim_doc_comment)] algorithm: Option, @@ -219,7 +305,13 @@ fn main() { let result = task.compute(); match result { - Ok(result) => println!("{}", result), + Ok(IfMatch::Match(message)) => { + println!("{}", message); + break; + }, + Ok(IfMatch::Failed(message)) => { + println!("{}", message); + }, Err(e) => eprintln!("{}", e), } } @@ -228,23 +320,39 @@ fn main() { Args::Check { algorithm, check_file, } => { match check_file { Some(f) => { - let tasks = match phase_shasum_file( + match phase_shasum_file ( f, detect_algorithm(algorithm), ) { - Ok(tasks) => tasks, + Ok(tasks) => { + let mut file_name = String::new(); + let mut file_matched = false; + for task in tasks { + if file_name == task.data.to_string() && file_matched == true { + continue; + } else { + let result = task.compute(); + match result { + Ok(IfMatch::Match(message))=> { + file_name = task.data.to_string(); + file_matched = true; + println!("{}: {}", task.data, message); + }, + Ok(IfMatch::Failed(message)) => { + file_name = task.data.to_string(); + file_matched = false; + println!("{}: {}", task.data, message); + } + Err(e) => eprintln!("{}: {}", task.data, e), + } + } + } + }, Err(e) => { eprintln!("{}", e); process::exit(1); } }; - for task in tasks { - let result = task.compute(); - match result { - Ok(result) => println!("{}: {}", task.data ,result), - Err(e) => eprintln!("{}", e), - } - } } None => eprintln!("Must provide a check file.\nRun `ezcheck check --help` for more information."), }