Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: CI

on:
push: ~
pull_request:
branches:
- main

concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [x] feature: generalize `path_file` argument type, from `&str` to `AsRef<Path>`
- [x] feature: allow only regular file for hashing with `sum_file`
- [x] enhancement: add additional entropy to filename to fix race condition issue on parallel tests launch
- [x] feature: implement CLI application
- [x] documentation: add basic and advanced usage examples
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,20 @@ categories = ["algorithms", "filesystem", "encoding"]
keywords = ["checksum", "digest", "hash", "murmur3", "encoding"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "imohash"
path = "src/lib.rs"

[[bin]]
name = "imohash"
path = "src/main.rs"

[dependencies]
# Common dependencies
murmur3 = "0.5"
# Dependencies only for binary
clap = { version = "4.5.35", features = ["std"] }
hex = "0.4.3"

[dev-dependencies]
md-5 = "0.10.5"
Expand Down
176 changes: 176 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,182 @@ let hash_value = hasher.sum("hello".as_bytes()).unwrap();
let hash_value = hasher.sum_file("samples/system.evtx").unwrap();
```

<details>
<summary>Advanced asynchronous usage example</summary>

```rust
use std::sync::Arc;
use std::thread;
use std::thread::JoinHandle;
use std::path::{Path, PathBuf};
use imohash;

pub fn sum_files<P: AsRef<Path>>(
file_paths: Vec<P>,
sample_size: Option<u32>,
sample_threshold: Option<u32>,
threads_count: Option<u8>,
) -> std::sync::mpsc::Receiver<(u128, PathBuf)> {
let sample_size = sample_size.unwrap_or(imohash::SAMPLE_SIZE);
let sample_threshold = sample_threshold.unwrap_or(imohash::SAMPLE_THRESHOLD);
let threads_count = threads_count.unwrap_or_else(|| {
(thread::available_parallelism().unwrap().get() * 2) as u8 // two threads per core
}) as usize;
assert!(threads_count > 0);

let hasher = Arc::new(imohash::Hasher::with_sample_size_and_threshold(
sample_size,
sample_threshold,
));

let (sender, receiver): (
std::sync::mpsc::Sender<(u128, PathBuf)>,
std::sync::mpsc::Receiver<(u128, PathBuf)>,
) = std::sync::mpsc::channel();
let sender = Arc::new(sender);

let mut threads: Vec<JoinHandle<()>> = Vec::with_capacity(threads_count);
let chunk_size = (file_paths.len() + threads_count - 1) / threads_count;

for file_paths_chunk in file_paths.chunks(chunk_size) {
let shared_hasher = Arc::clone(&hasher);
let shared_sender = Arc::clone(&sender);
let file_path_list: Vec<PathBuf> = file_paths_chunk
.into_iter()
.map(|path| path.as_ref().to_path_buf())
.collect();

let handle = thread::spawn(move || {
for file_path in file_path_list {
let hash = shared_hasher.sum_file(&file_path);
match hash {
Err(_) => continue, // Path is directory
Ok(hash) => shared_sender.send((hash, file_path)).unwrap(),
}
}
});

threads.push(handle);
}

receiver
}

fn main() {
let receiver: std::sync::mpsc::Receiver<(u128, PathBuf)> = sum_files(
vec![
PathBuf::from("/bin/cp"),
PathBuf::from("/bin/mv"),
PathBuf::from("/bin/rm"),
// ...
], // file_paths
None, // sample_size
None, // sample_threshold
None // threads_count
);

loop {
let hash_result: Result<(u128, PathBuf), std::sync::mpsc::RecvError> = receiver.recv();
match hash_result {
// Since sender is not dropping explicitly, RecvError will occur, when no any Sender
Err(std::sync::mpsc::RecvError) => break,
Ok(hash_result) => {
let (hash, file_path) = hash_result;
println!("{} {}", hash, file_path.as_path().to_str().unwrap())
}
}
}
}
```

</details>

### CLI application

Component provides a CLI sample application to hash files, similar to md5sum.

#### Install

Install `imohash` binary with `cargo`:

```sh
cargo install --bin imohash imohash # NAME_OF_BINARY PACKAGE_NAME
```

The installed binary will be located in `~/.cargo/bin/imohash` and will be available globally as `imohash`.

#### Usage

```sh
imohash # ... options and arguments
```

Application options and arguments:

- `-t` / `--sample-threshold` — Sample threshold value.
- `-s` / `--sample-size` — Sample size value. The entire file will be hashed (i.e. no sampling), if `sample_size < 1`
- `-f` / `--format` of `{ int | bytes | hex }` — Hash representation format. Default `hex`
- `-i` / `--interactive` — Interactive hash computation mode. **Conflicts with** `[file_path ...]` argument
- `--threads` — Count of threads to compute files sum in. **Conflicts with** `-i/--interactive` argument
- `[file_path ...]` — File paths to compute hash of. **Conflicts with** `-i/--interactive` argument

**Usage example:**

1. Compute hash sum of file or files:
```sh
# echo example > /tmp/my_file
imohash /tmp/my_file
```
will print:
```
0877d8731ad98e5ee1cc09c0a87772bf /tmp/my_file
```
2. Compute hash sum of file(s) with `find` application result:
```sh
# dd if=/dev/random of=/tmp/1.iso bs=1M count=64
# dd if=/dev/random of=/tmp/2.iso bs=1M count=64
# cp /tmp/1.iso /tmp/3.iso

find /tmp -type f -iname '*.iso' -exec imohash {} \+
```
will print:
```
808080203afea9085df78cd992f28546 /tmp/1.iso
8080802011cdd41fbddd9c1f853c1330 /tmp/2.iso
808080203afea9085df78cd992f28546 /tmp/3.iso # as same as #1 !
```
3. Compute hash of string content (as bytes data) interactively:
```sh
imohash -i # ... or implicitly: imohash
```

```
Interactive mode (format: hex)
> example
07ce528a343b2b99d4bd1bcdd648d138
> example 2
09b17440da02c7feb0b54f89d4d7b142
>
```

#### Benchmark

See [benchmark](docs/benchmark.md) for details:

[![performance comparison graphic](docs/_static/performance_comparison.svg)](docs/_static/performance_comparison.svg)

<details>
<summary>Time per operation graphic</summary>

[![performance comparison / time per operation graphic](docs/_static/time_per_operation.svg)](docs/_static/time_per_operation.svg)

</details>

Graphics analyze reveals:

1. optimal number of threads is equals to: `(number of process cores)` OR `(number of process cores) * 2`
2. multithreading increases processing performance up to 8-10x times

## Algorithm

Consult the [documentation](https://github.com/kalafut/imohash/blob/master/algorithm.md) for more information.
Expand Down
Loading