Skip to main content

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//! ## Exit status
164//!
165//! The process returns one of the following exit status codes:
166//!
167//! - **0** &ndash; The process completed successfully.
168//! - **1** &ndash; The process completed, but one or more files were skipped due to errors (only when `--keep-going` is used).
169//! - **2** &ndash; The process failed due to an error (for example, an I/O error).
170//! - **3** &ndash; The process has been interrupted by the user; generated output may be incomplete.
171//!
172//! ## Platform support
173//!
174//! This crate uses Rust edition 2021, and requires `rustc` version 1.89.0 or newer.
175//!
176//! The following targets are officially supported, other platforms may function but are **not** guaranteed:
177//!
178//! - Linux
179//! - Windows
180//! - macOS
181//! - *BSD (FreeBSD, OpenBSD, NetBSD, etc.)
182//! - Haiku OS
183//! - Solaris / Illumos
184//!
185//! ## License
186//!
187//! Copyright (C) 2025-2026 by LoRd_MuldeR &lt;mulder2@gmx.de&gt;
188//!
189//! Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
190//!
191//! 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.
192//!
193//! ## See also
194//!
195//! &#x1F517; <https://crates.io/crates/sponge-hash-aes256>  
196//! &#x1F517; <https://github.com/lordmulder/sponge-hash-aes256>
197
198mod arguments;
199mod common;
200mod digest;
201mod environment;
202mod io;
203mod os;
204mod process;
205mod self_test;
206mod thread_pool;
207mod verify;
208
209use num::Integer;
210use sponge_hash_aes256::DEFAULT_DIGEST_SIZE;
211use std::{
212    io::stdout,
213    process::{abort, ExitCode},
214    sync::Arc,
215    thread,
216    time::Duration,
217};
218
219use crate::common::{Aborted, ExitStatus, Flag};
220use crate::environment::Env;
221use crate::verify::verify_files;
222use crate::{
223    arguments::Args,
224    common::{MAX_DIGEST_SIZE, MAX_SNAIL_LEVEL},
225    process::process_files,
226    self_test::self_test,
227};
228
229// Enable MiMalloc, if the "with-mimalloc" feature is enabled
230#[cfg(feature = "with-mimalloc")]
231#[global_allocator]
232static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
233
234// ---------------------------------------------------------------------------
235// Main
236// ---------------------------------------------------------------------------
237
238/// The actual "main" function
239fn sponge256sum_main(args: Arc<Args>) -> Result<ExitStatus, Aborted> {
240    // Initialize the SimpleLogger, if the "with-logging" feature is enabled
241    #[cfg(feature = "with-logging")]
242    simple_logger::SimpleLogger::new().init().unwrap();
243
244    // Compute the digest size, in bytes (falling back to the default, it unspecified)
245    let (digest_size, digest_rem) = match args.length {
246        Some(digest_bits) => digest_bits.get().div_rem(&(u8::BITS as usize)),
247        None => (DEFAULT_DIGEST_SIZE, 0usize),
248    };
249
250    // Make sure that the digest size is divisble by eight
251    if digest_rem != 0usize {
252        print_error!(args, "Error: Digest output size must be divisible by eight! (given value: {}, remainder: {})", args.length.unwrap().get(), digest_rem);
253        return Ok(ExitStatus::Failure);
254    }
255
256    // Make sure that the digest size doesn't exceed the allowable maximum
257    if digest_size > MAX_DIGEST_SIZE {
258        print_error!(args, "Error: Digest output size exceeds the allowable maximum! (given value: {})", digest_size * 8usize);
259        return Ok(ExitStatus::Failure);
260    }
261
262    // Check for snail level being out of bounds
263    if args.snail > MAX_SNAIL_LEVEL {
264        print_error!(args, "\n{}", include_str!("../../.assets/text/goat.txt"));
265        return Ok(ExitStatus::Failure);
266    }
267
268    // Check the maximum allowable info length
269    if args.info.as_ref().is_some_and(|str| str.len() > u8::MAX as usize) {
270        print_error!(args, "Error: Length of context info must not exceed 255 characters! (given length: {})", args.info.as_ref().unwrap().len());
271        return Ok(ExitStatus::Failure);
272    }
273
274    // Parse additional options from environment variables
275    let env = match Env::from_env() {
276        Ok(options) => options,
277        Err(error) => {
278            print_error!(args, "Error: Environment variable {}={:?} is invalid!", error.name, error.value);
279            return Ok(ExitStatus::Failure);
280        }
281    };
282
283    // Install the interrupt (CTRL+C) handling routine
284    let halt = Arc::new(Default::default());
285    let halt_cloned = Arc::clone(&halt);
286    let _ctrlc = ctrlc::set_handler(move || ctrlc_handler(&halt_cloned));
287
288    // Acquire stdout handle
289    let mut output = stdout().lock();
290
291    // Run built-in self-test, if it was requested by the user
292    if args.self_test {
293        self_test(&mut output, &args, &env, &halt)
294    } else if !args.check {
295        // Process all input files/directories that were given on the command-line
296        process_files(&mut output, digest_size, args, &env, halt)
297    } else {
298        // Verify all checksum files that were given on the command-line
299        verify_files(&mut output, args, &env, halt)
300    }
301}
302
303// ---------------------------------------------------------------------------
304// Interrupt handler
305// ---------------------------------------------------------------------------
306
307/// The SIGINT (CTRL+C) interrupt handler routine
308///
309/// If the process does not exit cleanly after 10 seconds, we just proceed with the abort!
310fn ctrlc_handler(halt: &Arc<Flag>) -> ! {
311    let _ = halt.abort_process();
312    thread::sleep(Duration::from_secs(10u64));
313    abort();
314}
315
316// ---------------------------------------------------------------------------
317// Entry point
318// ---------------------------------------------------------------------------
319
320/// Applicationm entry point (“main” function)
321fn main() -> ExitCode {
322    // Initialize the Args from the given command-line arguments
323    let args = match Args::try_parse_command_line() {
324        Ok(args) => Arc::new(args),
325        Err(exit_code) => return exit_code,
326    };
327
328    // Call the actual "main" function
329    match sponge256sum_main(Arc::clone(&args)) {
330        Ok(status) => status.into(),
331        Err(Aborted) => {
332            print_error!(args, "Aborted: The process has been interrupted by the user!");
333            Aborted.into()
334        }
335    }
336}