1#![no_std]
2
3const FNV_OFFSET_BASIS_32: u32 = 0x811c9dc5;
4const FNV_OFFSET_BASIS_64: u64 = 0xcbf29ce484222325;
5const FNV_OFFSET_BASIS_128: u128 = 0x6c62272e07bb014262b821756295c58d;
6
7const FNV_PRIME_32: u32 = 0x01000193;
8const FNV_PRIME_64: u64 = 0x00000100000001B3;
9const FNV_PRIME_128: u128 = 0x0000000001000000000000000000013B;
10
11macro_rules! fnv_hash_impl {
12 ($typ:ty, $size:literal, $fn_name:ident, $str_fn_name:ident, $offset:expr, $prime:expr) => {
13 #[doc = concat![
14 "Computes ",
15 stringify!($size),
16 "-bits fnv1a hash of the given slice, or up-to limit if provided. ",
17 "If limit is zero or exceeds slice length, slice length is used instead.",
18 ]]
19 pub const fn $fn_name(bytes: &[u8], limit: Option<usize>) -> $typ {
20 let prime = $prime;
21
22 let mut hash = $offset;
23 let mut i = 0;
24 let len = match limit {
25 Some(v) if 0 < v && v <= bytes.len() => {
26 v
27 },
28 _ => {
29 bytes.len()
30 }
31 };
32
33 while i < len {
34 hash ^= bytes[i] as $typ;
35 hash = hash.wrapping_mul(prime);
36 i += 1;
37 }
38 hash
39 }
40
41 #[doc = concat![
42 "Computes ",
43 stringify!($size),
44 "-bit fnv1a hash from a str."
45 ]]
46 #[inline(always)]
47 pub const fn $str_fn_name(input: &str) -> $typ {
48 $fn_name(input.as_bytes(), None)
49 }
50 }
51}
52
53fnv_hash_impl!{u32, 32, fnv1a_hash_32, fnv1a_hash_str_32, FNV_OFFSET_BASIS_32, FNV_PRIME_32}
54fnv_hash_impl!{u64, 64, fnv1a_hash_64, fnv1a_hash_str_64, FNV_OFFSET_BASIS_64, FNV_PRIME_64}
55fnv_hash_impl!{u128, 128, fnv1a_hash_128, fnv1a_hash_str_128, FNV_OFFSET_BASIS_128, FNV_PRIME_128}
56
57#[inline(always)]
62pub const fn fnv1a_hash_16_xor(bytes: &[u8], limit: Option<usize>) -> u16 {
63 let bytes = fnv1a_hash_32(bytes, limit).to_ne_bytes();
64 let upper: u16 = u16::from_ne_bytes([bytes[0], bytes[1]]);
65 let lower: u16 = u16::from_ne_bytes([bytes[2], bytes[3]]);
66 upper ^ lower
67}
68
69#[inline(always)]
71pub const fn fnv1a_hash_str_16_xor(input: &str) -> u16 {
72 fnv1a_hash_16_xor(input.as_bytes(), None)
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn test_hash<T: Eq + core::fmt::Debug>(hash_func: impl FnOnce(&str) -> T, source_str: &str, expected: T) {
80 let hashed = hash_func(source_str);
81 let bit_size = core::mem::size_of::<T>() * 8;
82 assert_eq!(hashed, expected, "fnv1a-{bit_size} hash for {source_str}")
83 }
84
85 const FOOBAR: &str = "foobar";
86 const FOOBAR_HASH_32: u32 = 0xbf9cf968;
87 const FOOBAR_HASH_64: u64 = 0x85944171f73967e8;
88 const FOOBAR_HASH_128: u128 = 0x343e1662793c64bf6f0d3597ba446f18;
89
90 #[test]
91 fn test_32() {
92 test_hash(fnv1a_hash_str_32, FOOBAR, FOOBAR_HASH_32)
93 }
94 #[test]
95 fn test_64() {
96 test_hash(fnv1a_hash_str_64, FOOBAR, FOOBAR_HASH_64)
97 }
98 #[test]
99 fn test_128() {
100 test_hash(fnv1a_hash_str_128, FOOBAR, FOOBAR_HASH_128)
101 }
102}