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