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](https://raw.githubusercontent.com/lordmulder/sponge-hash-aes256/refs/heads/main/.assets/images/sponge256sum-512px.png)
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! &#128161;
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! &#128680;
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 &lt;mulder2@gmx.de&gt;
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//! &#x1F517; <https://crates.io/crates/sponge-hash-aes256>  
187//! &#x1F517; <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}