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