sponge256sum/main.rs
1// SPDX-License-Identifier: 0BSD
2// sponge256sum
3// Copyright (C) 2025-2026 by LoRd_MuldeR <mulder2@gmx.de>
4
5#![doc(hidden)]
6
7//! 
8//!
9//! # sponge256sum
10//!
11//! A command-line tool for computing [**SpongeHash-AES256**](https://github.com/lordmulder/sponge-hash-aes256/) message digest.
12//!
13//! This program is designed as a drop-in replacement for [`sha1sum`](https://manpages.debian.org/trixie/coreutils/sha1sum.1.en.html), [`sha256sum`](https://manpages.debian.org/trixie/coreutils/sha256sum.1.en.html) and related utilities.
14//!
15//! Please see the [library documentation](sponge_hash_aes256) for details! 💡
16//!
17//! ## Synopsis
18//!
19//! This command-line application can be used as follows:
20//!
21//! ```plaintext
22//! Usage: sponge256sum [OPTIONS] [FILES]...
23//!
24//! Arguments:
25//! [FILES]... Files to be processed
26//!
27//! Options:
28//! -b, --binary Read the input file(s) in binary mode, i.e., default mode
29//! -t, --text Read the input file(s) in text mode
30//! -c, --check Read and verify checksums from the provided input file(s)
31//! -d, --dirs Enable processing of directories as arguments
32//! -r, --recursive Recursively process the provided directories (implies -d)
33//! -a, --all Iterate all kinds of files, instead of just regular files
34//! -k, --keep-going Continue processing even if errors are encountered
35//! -l, --length <LENGTH> Digest output size, in bits (default: 256, maximum: 2048)
36//! -i, --info <INFO> Include additional context information
37//! -s, --snail... Enable "snail" mode, i.e., slow down the hash computation
38//! -q, --quiet Do not output any error messages or warnings
39//! -p, --plain Print digest(s) in plain format, i.e., without file names
40//! -0, --null Separate digest(s) by NULL characters instead of newlines
41//! -m, --multi-threading Enable multi-threaded processing of input files
42//! -f, --flush Explicitly flush 'stdout' stream after printing a digest
43//! -T, --self-test Run the built-in self-test (BIST)
44//! -h, --help Print help
45//! -V, --version Print version
46//!
47//! If no input files are specified, reads input data from the 'stdin' stream.
48//! Returns a non-zero exit code if any errors occurred; otherwise, zero.
49//! ```
50//!
51//! ## Examples
52//!
53//! Here are some `sponge256sum` usage examples:
54//!
55//! * Compute the hash values (digests) of one or multiple input files:
56//! ```sh
57//! $ sponge256sum /path/to/first.dat /path/to/second.dat /path/to/third.dat
58//! ```
59//!
60//! * Selecting multiple input files can also be done with wildcards:
61//! ```sh
62//! $ sponge256sum /path/to/*.dat
63//! ```
64//!
65//! * Process all files in a directory, *including* all files found in all subdirectories:
66//! ```sh
67//! $ sponge256sum --recursive /path/to/base-dir
68//! ```
69//!
70//! * Compute the hash value (digest) of the data from the `stdin` stream:
71//! ```sh
72//! $ printf "Lorem ipsum dolor sit amet consetetur sadipscing" | sponge256sum
73//! ```
74//!
75//! * Verify files (hashes) from an existing checksum file:
76//! ```sh
77//! $ sponge256sum --check /path/to/SPONGE256SUMS.txt
78//! ```
79//!
80//! ## Options
81//!
82//! The following options are available, among others:
83//!
84//! - **Directory processing**
85//!
86//! The `--dirs` option enables directory processing. This means that for each input file name (path) that resolves to a directory, the program processes all files contained in that directory, but **without** descending into subdirectories.
87//!
88//! Additionally, the `--recursive` option enables *recursive* directory scanning, behaving identically to the `--dirs` option except that it also traverses subdirectories. The `--recursive` option implies the `--dirs` option.
89//!
90//! Furthermore, the `--all` option can be combined with the `--dirs` or `--recursive` options to process **all** files found in a directory. By default, the program will only process “regular” files, *skipping* special files like FIFOs or sockets.
91//!
92//! - **Checksum verification**
93//!
94//! The `--check` option runs the program in verification mode. This means that a list of checksums (hash values) is read from each given input file, and those checksums are then verified against the corresponding target files.
95//!
96//! This mode expects input files to contain one checksum (and its corresponding file path) per line, formatted as follows:
97//! ```
98//! <HASH_VALUE_HEX><SPACE><FILE_PATH><EOL>
99//! ```
100//!
101//! All checksums (hash values) in a particular checksum file are expected to have the same length, in bits.
102//!
103//! If the `--info`, `--text` or `--snail` option has been used to calculate the hash values in a checksum file, then the ***same*** `--info`, `--text` or `--snail` parameter(s) **must** be used for the checksum verification again! 🚨
104//!
105//! - **Multi-threading**
106//!
107//! The `--multi-threading` option enables [multithreading](https://en.wikipedia.org/wiki/Thread_(computing)) mode, in which multiple files can be processed concurrently.
108//!
109//! Note that, in this mode, the order in which the files will be processed is ***undefined***. That is because the work will be distributed across multiple “worker” threads and each result is printed as soon as it becomes available.
110//!
111//! Also note that each file still is processed by a single thread, so this mode is mostly useful when processing ***many*** files.
112//!
113//! - **Output length**
114//!
115//! The `--length <LENGTH>` option can be used to specify the digest output size, in bits. The default size is 256 bits.
116//!
117//! Currently, the maximum output size is 1024 bits. Also, the output size, in bits, must be divisible by eight!
118//!
119//! - **Context information**
120//!
121//! The `--info <INFO>` option can be used to include some additional context information in the hash computation.
122//!
123//! For each unique “info” string, different digests (hash values) are generated from the same messages (inputs).
124//!
125//! This enables proper *domain separation* for different uses, e.g., applications or protocols, of the same hash function.
126//!
127//! - **Snail mode**
128//!
129//! The `--snail` option can be passed to the program, optionally more than once, to slow down the hash computation.
130//!
131//! This improves the security of certain applications, e.g., password hashing, by making “brute force” attacks harder.
132//!
133//! Count | Number of permutation rounds | Throughput (in KiB/s)
134//! ------ | ---------------------------- | --------------------:
135//! – | 1 (default) | 249,245.54
136//! **×1** | 13 | 8,595.85
137//! **×2** | 251 | 441.40
138//! **×3** | 4093 | 25.82
139//! **×4** | 65521 | 1.61
140//!
141//! - **Text mode**
142//!
143//! The `--text` option enables “text” mode. In this mode, the input file is read as a *text* file, line by line.
144//!
145//! Unlike in “binary” mode (the default), platform-specific line endings will be normalized to a single `\n` character.
146//!
147//! ## Environment
148//!
149//! The following environment variables are recognized:
150//!
151//! - **`SPONGE256SUM_THREAD_COUNT`**:
152//! Specifies the number of threads to be used in `--multi-threading` mode.
153//! If set to **0**, which is the default, the number of CPU cores is detected automatically at runtime.
154//! Please note that the number of threads is currently limited to the range from 1 to 32.
155//!
156//! - **`SPONGE256SUM_DIRWALK_STRATEGY`**:
157//! Selects the search strategy to be used for walking the directory tree in `--recursive` mode.
158//! This can be `BFS` (breadth-first search) or `DFS` (depath-first search). Default is `BFS`.
159//!
160//! - **`SPONGE256SUM_SELFTEST_PASSES`**:
161//! Specifies the number of passes to be executed in `--self-test` mode. Default is **3**.
162//!
163//! ## Platform support
164//!
165//! This crate uses Rust edition 2021, and requires `rustc` version 1.89.0 or newer.
166//!
167//! The following targets are officially supported, other platforms may function but are **not** guaranteed:
168//!
169//! - Linux
170//! - Windows
171//! - macOS
172//! - *BSD (FreeBSD, OpenBSD, NetBSD, etc.)
173//! - Haiku OS
174//! - Solaris / Illumos
175//!
176//! ## License
177//!
178//! Copyright (C) 2025-2026 by LoRd_MuldeR <mulder2@gmx.de>
179//!
180//! Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
181//!
182//! THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
183//!
184//! ## See also
185//!
186//! 🔗 <https://crates.io/crates/sponge-hash-aes256>
187//! 🔗 <https://github.com/lordmulder/sponge-hash-aes256>
188
189mod arguments;
190mod common;
191mod digest;
192mod environment;
193mod io;
194mod process;
195mod self_test;
196mod thread_pool;
197mod verify;
198
199use num::Integer;
200use sponge_hash_aes256::DEFAULT_DIGEST_SIZE;
201use std::process::abort;
202use std::thread;
203use std::time::Duration;
204use std::{io::stdout, process::ExitCode, sync::Arc};
205
206use crate::common::{Aborted, Flag};
207use crate::environment::Env;
208use crate::verify::verify_files;
209use crate::{
210 arguments::Args,
211 common::{MAX_DIGEST_SIZE, MAX_SNAIL_LEVEL},
212 process::process_files,
213 self_test::self_test,
214};
215
216// Enable MiMalloc, if the "with-mimalloc" feature is enabled
217#[cfg(feature = "with-mimalloc")]
218#[global_allocator]
219static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
220
221// ---------------------------------------------------------------------------
222// Main
223// ---------------------------------------------------------------------------
224
225/// The actual "main" function
226fn sponge256sum_main(args: Arc<Args>) -> Result<bool, Aborted> {
227 // Initialize the SimpleLogger, if the "with-logging" feature is enabled
228 #[cfg(feature = "with-logging")]
229 simple_logger::SimpleLogger::new().init().unwrap();
230
231 // Compute the digest size, in bytes (falling back to the default, it unspecified)
232 let (digest_size, digest_rem) = match args.length {
233 Some(digest_bits) => digest_bits.get().div_rem(&(u8::BITS as usize)),
234 None => (DEFAULT_DIGEST_SIZE, 0usize),
235 };
236
237 // Make sure that the digest size is divisble by eight
238 if digest_rem != 0usize {
239 print_error!(args, "Error: Digest output size must be divisible by eight! (given value: {}, remainder: {})", args.length.unwrap().get(), digest_rem);
240 return Ok(false);
241 }
242
243 // Make sure that the digest size doesn't exceed the allowable maximum
244 if digest_size > MAX_DIGEST_SIZE {
245 print_error!(args, "Error: Digest output size exceeds the allowable maximum! (given value: {})", digest_size * 8usize);
246 return Ok(false);
247 }
248
249 // Check for snail level being out of bounds
250 if args.snail > MAX_SNAIL_LEVEL {
251 print_error!(args, "\n{}", include_str!("../../.assets/text/goat.txt"));
252 return Ok(false);
253 }
254
255 // Check the maximum allowable info length
256 if args.info.as_ref().is_some_and(|str| str.len() > u8::MAX as usize) {
257 print_error!(args, "Error: Length of context info must not exceed 255 characters! (given length: {})", args.info.as_ref().unwrap().len());
258 return Ok(false);
259 }
260
261 // Parse additional options from environment variables
262 let env = match Env::from_env() {
263 Ok(options) => options,
264 Err(error) => {
265 print_error!(args, "Error: Environment variable {}={:?} is invalid!", error.name, error.value);
266 return Ok(false);
267 }
268 };
269
270 // Install the interrupt (CTRL+C) handling routine
271 let halt = Arc::new(Default::default());
272 let halt_cloned = Arc::clone(&halt);
273 let _ctrlc = ctrlc::set_handler(move || ctrlc_handler(&halt_cloned));
274
275 // Acquire stdout handle
276 let mut output = stdout().lock();
277
278 // Run built-in self-test, if it was requested by the user
279 if args.self_test {
280 self_test(&mut output, &args, &env, &halt)
281 } else if !args.check {
282 // Process all input files/directories that were given on the command-line
283 process_files(&mut output, digest_size, args, &env, halt)
284 } else {
285 // Verify all checksum files that were given on the command-line
286 verify_files(&mut output, args, &env, halt)
287 }
288}
289
290// ---------------------------------------------------------------------------
291// Interrupt handler
292// ---------------------------------------------------------------------------
293
294/// The SIGINT (CTRL+C) interrupt handler routine
295///
296/// If the process does not exit cleanly after 10 seconds, we just proceed with the abort!
297fn ctrlc_handler(halt: &Arc<Flag>) -> ! {
298 let _ = halt.abort_process();
299 thread::sleep(Duration::from_secs(10u64));
300 abort();
301}
302
303// ---------------------------------------------------------------------------
304// Entry point
305// ---------------------------------------------------------------------------
306
307/// Applicationm entry point (“main” function)
308fn main() -> ExitCode {
309 // Initialize the Args from the given command-line arguments
310 let args = match Args::try_parse_command_line() {
311 Ok(args) => Arc::new(args),
312 Err(exit_code) => return exit_code,
313 };
314
315 // Call the actual "main" function
316 match sponge256sum_main(Arc::clone(&args)) {
317 Ok(true) => ExitCode::SUCCESS,
318 Ok(false) => ExitCode::FAILURE,
319 Err(Aborted) => {
320 print_error!(args, "Aborted: The process has been interrupted by the user!");
321 ExitCode::from(130u8)
322 }
323 }
324}