Skip to content

Commit

Permalink
Add xxhash's xxh32 and xxh64, common trait
Browse files Browse the repository at this point in the history
  • Loading branch information
nyurik committed Dec 21, 2023
1 parent 68aea9a commit 0f0d21f
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .cargo-husky/hooks/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ cargo build
cargo test --workspace --all-targets --all-features --bins --tests --lib --benches

{ set +x ;} 2> /dev/null
for hash in "fnv" "xxh3"; do
for hash in "fnv" "xxh3" "xxh32" "xxh64"; do
echo "################################################################################################################################"
set -x
cargo test --no-default-features --lib --bins --examples --tests --benches --features "$hash"
Expand Down
9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "noncrypto-digests"
version = "0.2.3"
description = "Expose various non-cryptographic hashing functions with Digest traits"
version = "0.3.0"
description = "Implement Digest trait for non-cryptographic hashing functions like fnv and xxhash"
authors = ["Yuri Astrakhan <YuriAstrakhan@gmail.com>"]
repository = "https://github.com/nyurik/noncrypto-digests"
edition = "2021"
Expand All @@ -11,9 +11,11 @@ categories = ["cryptography"]
rust-version = "1.60.0"

[features]
default = ["fnv", "xxh3"]
default = ["fnv", "xxh3", "xxh32", "xxh64"]
fnv = ["dep:fnv"]
xxh3 = ["dep:xxhash-rust", "xxhash-rust?/xxh3"]
xxh32 = ["dep:xxhash-rust", "xxhash-rust?/xxh32"]
xxh64 = ["dep:xxhash-rust", "xxhash-rust?/xxh64"]

[dependencies]
digest = "0.10.7"
Expand All @@ -30,3 +32,4 @@ unused_qualifications = "warn"

[lints.clippy]
pedantic = { level = "warn", priority = -1 }
module_name_repetitions = "allow"
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,30 @@
[![CI build](https://github.com/nyurik/noncrypto-digests/actions/workflows/ci.yml/badge.svg)](https://github.com/nyurik/noncrypto-digests/actions)


Expose various non-cryptographic hashing functions with Digest traits. This allows users to use any hashing function with the same trait interface, and switch between them easily.
Implement [digest::Digest](https://docs.rs/digest/latest/digest/trait.Digest.html) trait for non-cryptographic hashing functions like fnv and xxhash. This allows users to use all cryptographic and non-cryptographic hashing functions polymorphically.

## Usage

```rust
use digest::Digest;
use hex::ToHex;
use noncrypto_digests::{Fnv, Xxh3_64, Xxh3_128};
use noncrypto_digests::{Fnv, Xxh3_64, Xxh3_128, Xxh32, Xxh64};

/// This function takes any Digest type, and returns a hex-encoded string.
pub fn hash<T: Digest>(data: impl AsRef<[u8]>) -> String {
// Note that some hashers provide seed value set to 0 by default.
// Use `...::from_hasher(hasher)` function to instantiate them.
let mut hasher = T::new();
hasher.update(data);
hasher.finalize().to_vec().encode_hex_upper()
}

fn main() {
// Use Fnv hash
assert_eq!(hash::<Fnv>("password"), "4B1A493507B3A318");
// Use Xxh3 64bit hash
assert_eq!(hash::<Xxh3_64>("password"), "336576D7E0E06F9A");
// Use Xxh3 128bit hash
assert_eq!(hash::<Xxh3_128>("password"), "9CFA9055952177DA0B120BE86072A8F0");
assert_eq!(hash::<Xxh32>("password"), "106C6CED");
assert_eq!(hash::<Xxh64>("password"), "90007DAF3980EF1F");
}
```

Expand Down
37 changes: 37 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
pub trait HashWrapper {
type Hasher;

fn from_hasher(hasher: Self::Hasher) -> Self;

fn get_hasher(&self) -> &Self::Hasher;

fn get_hasher_mut(&mut self) -> &mut Self::Hasher;
}

macro_rules! impl_hash_wrapper {
($hash_wrapper:ident, $hasher:ty, $output_size:ident) => {
impl HashWrapper for $hash_wrapper {
type Hasher = $hasher;

fn from_hasher(hasher: Self::Hasher) -> Self {
Self(hasher)
}

fn get_hasher(&self) -> &Self::Hasher {
&self.0
}

fn get_hasher_mut(&mut self) -> &mut Self::Hasher {
&mut self.0
}
}

impl OutputSizeUser for $hash_wrapper {
type OutputSize = $output_size;
}

impl HashMarker for $hash_wrapper {}
};
}

pub(crate) use impl_hash_wrapper;
15 changes: 6 additions & 9 deletions src/fnv.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use std::hash::Hasher as StdHasher;

pub use ::fnv::FnvHasher;
use digest::typenum::U8;
use digest::{FixedOutput, HashMarker, Output, OutputSizeUser, Update};
use fnv::FnvHasher;

use crate::common::{impl_hash_wrapper, HashWrapper};

#[derive(Default)]
pub struct Fnv(FnvHasher);

impl OutputSizeUser for Fnv {
type OutputSize = digest::typenum::U8;
}

impl HashMarker for Fnv {}
impl_hash_wrapper!(Fnv, FnvHasher, U8);

impl Clone for Fnv {
fn clone(&self) -> Self {
Expand All @@ -34,11 +33,9 @@ impl FixedOutput for Fnv {

#[cfg(test)]
mod tests {
use fnv::FnvHasher;
use insta::assert_snapshot;
use std::hash::Hasher;

use super::Fnv;
use super::*;
use crate::tests::hash;

#[test]
Expand Down
27 changes: 21 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
#![cfg_attr(feature = "default", doc = include_str!("../README.md"))]
#![forbid(unsafe_code)]

#[cfg(not(any(feature = "fnv", feature = "xxh3")))]
compile_error!("At least one of the features `fnv` or `xxh3` must be enabled.");
#[cfg(not(any(
feature = "fnv",
feature = "xxh3",
feature = "xxh32",
feature = "xxh64"
)))]
compile_error!("At least one of these features must be enabled: fnv, xxh3, xxh32, xxh64");

mod common;

#[cfg(feature = "fnv")]
mod fnv;

#[cfg(feature = "fnv")]
pub use fnv::Fnv;
pub use fnv::{Fnv, FnvHasher};

#[cfg(feature = "xxh3")]
mod xxh3;

#[cfg(feature = "xxh3")]
pub use xxh3::{Xxh3_128, Xxh3_64};
pub use xxh3::{Xxh3Hasher, Xxh3_128, Xxh3_64};

#[cfg(feature = "xxh32")]
mod xxh32;
#[cfg(feature = "xxh32")]
pub use xxh32::{Xxh32, Xxh32Hasher};

#[cfg(feature = "xxh64")]
mod xxh64;
#[cfg(feature = "xxh64")]
pub use xxh64::{Xxh64, Xxh64Hasher};

#[cfg(test)]
mod tests {
Expand Down
25 changes: 12 additions & 13 deletions src/xxh3.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
use std::hash::Hasher as _;

pub use ::xxhash_rust::xxh3::Xxh3 as Xxh3Hasher;
use digest::typenum::{U16, U8};
use digest::{FixedOutput, HashMarker, Output, OutputSizeUser, Update};
use std::hash::Hasher as _;
use xxhash_rust::xxh3::Xxh3;

use crate::common::{impl_hash_wrapper, HashWrapper};

macro_rules! make_hasher {
($name:ident, $internal:ty, $digest:expr, $output_size:ident) => {
($hash_wrapper:ident, $hasher:ty, $digest:expr, $output_size:ident) => {
#[derive(Clone, Default)]
pub struct $name($internal);

impl OutputSizeUser for $name {
type OutputSize = $output_size;
}
pub struct $hash_wrapper($hasher);

impl HashMarker for $name {}
impl_hash_wrapper!($hash_wrapper, $hasher, $output_size);

impl Update for $name {
impl Update for $hash_wrapper {
fn update(&mut self, data: &[u8]) {
self.0.write(data);
}
}

impl FixedOutput for $name {
impl FixedOutput for $hash_wrapper {
fn finalize_into(self, out: &mut Output<Self>) {
let result = $digest(&self.0);
let bytes = result.to_be_bytes();
Expand All @@ -30,8 +29,8 @@ macro_rules! make_hasher {
};
}

make_hasher!(Xxh3_64, Xxh3, Xxh3::digest, U8);
make_hasher!(Xxh3_128, Xxh3, Xxh3::digest128, U16);
make_hasher!(Xxh3_64, Xxh3Hasher, Xxh3Hasher::digest, U8);
make_hasher!(Xxh3_128, Xxh3Hasher, Xxh3Hasher::digest128, U16);

#[cfg(test)]
mod tests {
Expand Down
47 changes: 47 additions & 0 deletions src/xxh32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pub use ::xxhash_rust::xxh32::Xxh32 as Xxh32Hasher;
use digest::typenum::U4;
use digest::{FixedOutput, HashMarker, Output, OutputSizeUser, Update};

use crate::common::{impl_hash_wrapper, HashWrapper};

#[derive(Clone)]
pub struct Xxh32(Xxh32Hasher);

impl_hash_wrapper!(Xxh32, Xxh32Hasher, U4);

impl Default for Xxh32 {
fn default() -> Self {
Self(Xxh32Hasher::new(0))
}
}

impl Update for Xxh32 {
fn update(&mut self, data: &[u8]) {
self.0.update(data);
}
}

impl FixedOutput for Xxh32 {
fn finalize_into(self, out: &mut Output<Self>) {
let result = self.0.digest();
let bytes = result.to_be_bytes();
out.copy_from_slice(&bytes);
}
}

#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use xxhash_rust::xxh32::xxh32;

use super::*;
use crate::tests::hash;

#[test]
fn test_xxh32() {
let default = xxh32(&[], 0);
assert_eq!(hash::<Xxh32>(""), format!("{default:0>8X}"));
assert_snapshot!(hash::<Xxh32>(""), @"02CC5D05");
assert_snapshot!(hash::<Xxh32>("hello"), @"FB0077F9");
}
}
47 changes: 47 additions & 0 deletions src/xxh64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pub use ::xxhash_rust::xxh64::Xxh64 as Xxh64Hasher;
use digest::typenum::U8;
use digest::{FixedOutput, HashMarker, Output, OutputSizeUser, Update};

use crate::common::{impl_hash_wrapper, HashWrapper};

#[derive(Clone)]
pub struct Xxh64(Xxh64Hasher);

impl_hash_wrapper!(Xxh64, Xxh64Hasher, U8);

impl Default for Xxh64 {
fn default() -> Self {
Self(Xxh64Hasher::new(0))
}
}

impl Update for Xxh64 {
fn update(&mut self, data: &[u8]) {
self.0.update(data);
}
}

impl FixedOutput for Xxh64 {
fn finalize_into(self, out: &mut Output<Self>) {
let result = self.0.digest();
let bytes = result.to_be_bytes();
out.copy_from_slice(&bytes);
}
}

#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use xxhash_rust::xxh64::xxh64;

use super::*;
use crate::tests::hash;

#[test]
fn test_xxh64() {
let default = xxh64(&[], 0);
assert_eq!(hash::<Xxh64>(""), format!("{default:0>8X}"));
assert_snapshot!(hash::<Xxh64>(""), @"EF46DB3751D8E999");
assert_snapshot!(hash::<Xxh64>("hello"), @"26C7827D889F6DA3");
}
}
16 changes: 14 additions & 2 deletions tests/test.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Most importantly we need to make sure the hash object is accessible

use digest::Digest;
use hex::ToHex;
use insta::assert_snapshot;
Expand All @@ -8,17 +10,27 @@ pub fn hash<T: Digest>(data: impl AsRef<[u8]>) -> String {
hasher.finalize().to_vec().encode_hex_upper()
}

/// Most importantly we need to make sure the hash object is accessible
#[test]
#[cfg(feature = "fnv")]
fn fnv() {
assert_snapshot!(hash::<noncrypto_digests::Fnv>("password"), @"4B1A493507B3A318");
}

/// Most importantly we need to make sure the hash object is accessible
#[test]
#[cfg(feature = "xxh3")]
fn xxh3() {
assert_snapshot!(hash::<noncrypto_digests::Xxh3_64>("password"), @"336576D7E0E06F9A");
assert_snapshot!(hash::<noncrypto_digests::Xxh3_128>("password"), @"9CFA9055952177DA0B120BE86072A8F0");
}

#[test]
#[cfg(feature = "xxh32")]
fn xxh32() {
assert_snapshot!(hash::<noncrypto_digests::Xxh32>("password"), @"106C6CED");
}

#[test]
#[cfg(feature = "xxh64")]
fn xxh64() {
assert_snapshot!(hash::<noncrypto_digests::Xxh64>("password"), @"90007DAF3980EF1F");
}

0 comments on commit 0f0d21f

Please sign in to comment.