/*++ /* NAME /* hash_fnv 3 /* SUMMARY /* Fowler/Noll/Vo hash function /* SYNOPSIS /* #include /* /* HASH_FNV_T hash_fnv( /* const void *src, /* size_t len) /* /* HASH_FNV_T hash_fnvz( /* const char *src) /* DESCRIPTION /* hash_fnv() implements a modified FNV type 1a hash function. /* /* hash_fnvz() provides the same functionality for null-terminated /* strings, avoiding an unnecessary strlen() call. /* /* To thwart collision attacks, the hash function is seeded /* once with ldseed(). To disable seeding (typically, to make /* tests predictable), specify the NORANDOMIZE environment /* variable; the value does not matter. /* /* This implementation works around a "sticky state" problem /* with FNV hash functions: when an input produces a zero hash /* state, and the next input byte is zero, then the hash state /* would not change. To avoid this, hash_fnv() adds 1 to each /* input value. Compile with -DSTRICT_FNV1A to get the standard /* behavior. /* /* The default HASH_FNV_T result type is uint64_t. When compiled /* with -DUSE_FNV_32BIT, the result type is uint32_t. On ancient /* systems without , define HASH_FNV_T on the compiler /* command line as an unsigned 32-bit or 64-bit integer type, /* and specify -DUSE_FNV_32BIT when HASH_FNV_T is a 32-bit type. /* SEE ALSO /* http://www.isthe.com/chongo/tech/comp/fnv/index.html /* https://softwareengineering.stackexchange.com/questions/49550/ /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* * System library */ #include #include #include /* * Utility library. */ #include #include #include /* * Application-specific. */ #ifdef USE_FNV_32BIT #define FNV_prime 0x01000193UL #define FNV_offset_basis 0x811c9dc5UL #else #define FNV_prime 0x00000100000001B3ULL #define FNV_offset_basis 0xcbf29ce484222325ULL #endif /* * Workaround for the sticky all-zero hash state: when the next input byte * is zero, then the operations "hash ^= 0" and "hash *= FNV_prime" would * not change the hash state. To avoid that, add 1 to the every input value. */ #ifdef STRICT_FNV1A #define HASH_FNV_NEW_BITS(new_bits) (new_bits) #else #define HASH_FNV_NEW_BITS(new_bits) (1 + (new_bits)) #endif static HASH_FNV_T hash_fnv_basis = FNV_offset_basis; static int hash_fnv_must_init = 1; /* hash_fnv_init - seed the hash */ static void hash_fnv_init(void) { HASH_FNV_T seed; if (!getenv("NORANDOMIZE")) { ldseed(&seed, sizeof(seed)); hash_fnv_basis ^= seed; } hash_fnv_must_init = 0; } /* hash_fnv - modified FNV 1a hash */ HASH_FNV_T hash_fnv(const void *src, size_t len) { HASH_FNV_T hash; HASH_FNV_T new_bits; if (hash_fnv_must_init) hash_fnv_init(); hash = hash_fnv_basis; while (len-- > 0) { new_bits = *(unsigned char *) src++; hash ^= HASH_FNV_NEW_BITS(new_bits); hash *= FNV_prime; } return (hash); } /* hash_fnvz - modified FNV 1a hash for null-terminated strings */ HASH_FNV_T hash_fnvz(const char *src) { HASH_FNV_T hash; HASH_FNV_T new_bits; if (hash_fnv_must_init) hash_fnv_init(); hash = hash_fnv_basis; while ((new_bits = *(unsigned char *) src++) != 0) { hash ^= HASH_FNV_NEW_BITS(new_bits); hash *= FNV_prime; } return (hash); } #ifdef TEST #include #include #include int main(void) { int pass = 0; int fail = 0; /* * Sanity check. */ #ifdef STRICT_FNV1A msg_fatal("This test requires no STRICT_FNV1A"); #endif /* * Force unseeded hash, to make tests predictable. */ if (putenv("NORANDOMIZE=") != 0) msg_fatal("putenv(\"NORANDOMIZE=\"): %m"); /* * Test: hashing produces the expected results. */ { struct testcase { HASH_FNV_T hval; const char *str; }; static struct testcase testcases[] = { #ifdef USE_FNV_32BIT 0x1c00fc06UL, "overdeeply", 0x1c00fc06UL, "undescript", 0x1e1e52a4UL, "fanfold", 0x1e1e52a4UL, "phrensied", #else 0xda19999ec0bda706ULL, "overdeeply", 0xd7b9e43f26396a66ULL, "undescript", 0xa50c585d385a2604ULL, "fanfold", 0x1ec3ef9bb2b734a4ULL, "phrensied", #endif 0, }; struct testcase *tp; HASH_FNV_T hval; int test_failed; for (tp = testcases; tp->str; tp++) { test_failed = 0; if ((hval = hash_fnvz(tp->str)) != tp->hval) { msg_warn("hash_fnv(\"%s\") want %lu, got: %lu", tp->str, (unsigned long) tp->hval, (unsigned long) hval); test_failed = 1; } if (test_failed) { fail += 1; msg_info("FAIL: %s", tp->str); } else { pass += 1; msg_info("PASS: %s", tp->str); } } } /* * Test: hash_fnvz(s) is equivalent to hash_fnv(s, strlen(s)). No need to * verify the actual result; we already did that above. */ { const char *strval = "foobar"; HASH_FNV_T h1 = hash_fnv(strval, strlen(strval)); HASH_FNV_T h2 = hash_fnvz(strval); if (h1 == h2) { pass += 1; msg_info("PASS: hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)", strval, strval, (long) strlen(strval)); } else { fail += 1; msg_info("FAIL: hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)", strval, strval, (long) strlen(strval)); } } /* * Wrap up. */ msg_info("PASS=%d FAIL=%d", pass, fail); return (fail != 0); } #endif