From b2ad653c18478b7d423a34a4794fafde840aef97 Mon Sep 17 00:00:00 2001 From: khizmax Date: Sat, 5 Sep 2015 20:24:21 +0300 Subject: [PATCH] Added MD5, SHA256, CityHash hash function for MultiLevelHashMap testing --- doxygen/image/multilevel_hashset.png | Bin 51585 -> 64016 bytes projects/Win/vc12/unit-prerequisites.vcxproj | 7 + projects/source.test-common.mk | 7 +- tests/CMakeLists.txt | 5 +- tests/hashing/city.cc | 628 ++++++++++++++++++ tests/hashing/city.h | 112 ++++ tests/hashing/hash_func.h | 110 +++ tests/hashing/md5.cpp | 380 +++++++++++ tests/hashing/md5.h | 78 +++ tests/hashing/sha256.cpp | 411 ++++++++++++ tests/hashing/sha256.h | 78 +++ tests/unit/map2/map_defs.h | 37 +- tests/unit/map2/map_type_multilevel_hashmap.h | 61 ++ 13 files changed, 1910 insertions(+), 4 deletions(-) create mode 100644 tests/hashing/city.cc create mode 100644 tests/hashing/city.h create mode 100644 tests/hashing/hash_func.h create mode 100644 tests/hashing/md5.cpp create mode 100644 tests/hashing/md5.h create mode 100644 tests/hashing/sha256.cpp create mode 100644 tests/hashing/sha256.h diff --git a/doxygen/image/multilevel_hashset.png b/doxygen/image/multilevel_hashset.png index 2df82c0b386261ce44e9e857775e14ab923569bc..63b213bc770ae326449da331171f8326710904e0 100644 GIT binary patch literal 64016 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'$g@znk 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 Ze1UG^G*}O1lb?Wp5+X9f<$`(v|3A*fnPvb0 diff --git a/projects/Win/vc12/unit-prerequisites.vcxproj b/projects/Win/vc12/unit-prerequisites.vcxproj index 4d40f1a8..848ab3a8 100644 --- a/projects/Win/vc12/unit-prerequisites.vcxproj +++ b/projects/Win/vc12/unit-prerequisites.vcxproj @@ -415,12 +415,19 @@ + + + + + + + diff --git a/projects/source.test-common.mk b/projects/source.test-common.mk index a867a529..99b66930 100644 --- a/projects/source.test-common.mk +++ b/projects/source.test-common.mk @@ -3,4 +3,9 @@ CDS_TESTCOMMON_SOURCES := \ tests/cppunit/test_main.cpp \ tests/cppunit/thread.cpp \ tests/unit/michael_alloc.cpp \ - tests/unit/ellen_bintree_update_desc_pool.cpp + tests/unit/ellen_bintree_update_desc_pool.cpp \ + tests/hashing/city.cc \ + tests/hashing/md5.cpp \ + tests/hashing/sha256.cpp + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index af28e734..3c77e1af 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,7 +5,10 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) set(SOURCES cppunit/test_main.cpp cppunit/thread.cpp unit/michael_alloc.cpp - unit/ellen_bintree_update_desc_pool.cpp) + unit/ellen_bintree_update_desc_pool.cpp + hashing/city.cc + hashing/md5.cpp + hashing/sha256.cpp) add_library(${TEST_COMMON} OBJECT ${SOURCES}) diff --git a/tests/hashing/city.cc b/tests/hashing/city.cc new file mode 100644 index 00000000..c33aec06 --- /dev/null +++ b/tests/hashing/city.cc @@ -0,0 +1,628 @@ +// Copyright (c) 2011 Google, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// This file provides CityHash64() and related functions. +// +// It's probably possible to create even faster hash functions by +// writing a program that systematically explores some of the space of +// possible hash functions, by using SIMD instructions, or by +// compromising on hash quality. + +//#include "config.h" +#include "city.h" + +#include +#include // for memcpy and memset + +using namespace std; + +static uint64 UNALIGNED_LOAD64(const char *p) { + uint64 result; + memcpy(&result, p, sizeof(result)); + return result; +} + +static uint32 UNALIGNED_LOAD32(const char *p) { + uint32 result; + memcpy(&result, p, sizeof(result)); + return result; +} + +#ifdef _MSC_VER + +#include +#define bswap_32(x) _byteswap_ulong(x) +#define bswap_64(x) _byteswap_uint64(x) + +#elif defined(__APPLE__) + +// Mac OS X / Darwin features +#include +#define bswap_32(x) OSSwapInt32(x) +#define bswap_64(x) OSSwapInt64(x) + +#elif defined(__NetBSD__) + +#include +#include +#if defined(__BSWAP_RENAME) && !defined(__bswap_32) +#define bswap_32(x) bswap32(x) +#define bswap_64(x) bswap64(x) +#endif + +#else + +#include + +#endif + +#ifdef WORDS_BIGENDIAN +#define uint32_in_expected_order(x) (bswap_32(x)) +#define uint64_in_expected_order(x) (bswap_64(x)) +#else +#define uint32_in_expected_order(x) (x) +#define uint64_in_expected_order(x) (x) +#endif + +#if !defined(LIKELY) +#if HAVE_BUILTIN_EXPECT +#define LIKELY(x) (__builtin_expect(!!(x), 1)) +#else +#define LIKELY(x) (x) +#endif +#endif + +static uint64 Fetch64(const char *p) { + return uint64_in_expected_order(UNALIGNED_LOAD64(p)); +} + +static uint32 Fetch32(const char *p) { + return uint32_in_expected_order(UNALIGNED_LOAD32(p)); +} + +// Some primes between 2^63 and 2^64 for various uses. +static const uint64 k0 = 0xc3a5c85c97cb3127ULL; +static const uint64 k1 = 0xb492b66fbe98f273ULL; +static const uint64 k2 = 0x9ae16a3b2f90404fULL; + +// Magic numbers for 32-bit hashing. Copied from Murmur3. +static const uint32_t c1 = 0xcc9e2d51; +static const uint32_t c2 = 0x1b873593; + +// A 32-bit to 32-bit integer hash copied from Murmur3. +static uint32 fmix(uint32 h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +static uint32 Rotate32(uint32 val, int shift) { + // Avoid shifting by 32: doing so yields an undefined result. + return shift == 0 ? val : ((val >> shift) | (val << (32 - shift))); +} + +#undef PERMUTE3 +#define PERMUTE3(a, b, c) do { std::swap(a, b); std::swap(a, c); } while (0) + +static uint32 Mur(uint32 a, uint32 h) { + // Helper from Murmur3 for combining two 32-bit values. + a *= c1; + a = Rotate32(a, 17); + a *= c2; + h ^= a; + h = Rotate32(h, 19); + return h * 5 + 0xe6546b64; +} + +static uint32 Hash32Len13to24(const char *s, size_t len) { + uint32 a = Fetch32(s - 4 + (len >> 1)); + uint32 b = Fetch32(s + 4); + uint32 c = Fetch32(s + len - 8); + uint32 d = Fetch32(s + (len >> 1)); + uint32 e = Fetch32(s); + uint32 f = Fetch32(s + len - 4); + uint32 h = len; + + return fmix(Mur(f, Mur(e, Mur(d, Mur(c, Mur(b, Mur(a, h))))))); +} + +static uint32 Hash32Len0to4(const char *s, size_t len) { + uint32 b = 0; + uint32 c = 9; + for (int i = 0; i < len; i++) { + signed char v = s[i]; + b = b * c1 + v; + c ^= b; + } + return fmix(Mur(b, Mur(len, c))); +} + +static uint32 Hash32Len5to12(const char *s, size_t len) { + uint32 a = len, b = len * 5, c = 9, d = b; + a += Fetch32(s); + b += Fetch32(s + len - 4); + c += Fetch32(s + ((len >> 1) & 4)); + return fmix(Mur(c, Mur(b, Mur(a, d)))); +} + +uint32 CityHash32(const char *s, size_t len) { + if (len <= 24) { + return len <= 12 ? + (len <= 4 ? Hash32Len0to4(s, len) : Hash32Len5to12(s, len)) : + Hash32Len13to24(s, len); + } + + // len > 24 + uint32 h = len, g = c1 * len, f = g; + uint32 a0 = Rotate32(Fetch32(s + len - 4) * c1, 17) * c2; + uint32 a1 = Rotate32(Fetch32(s + len - 8) * c1, 17) * c2; + uint32 a2 = Rotate32(Fetch32(s + len - 16) * c1, 17) * c2; + uint32 a3 = Rotate32(Fetch32(s + len - 12) * c1, 17) * c2; + uint32 a4 = Rotate32(Fetch32(s + len - 20) * c1, 17) * c2; + h ^= a0; + h = Rotate32(h, 19); + h = h * 5 + 0xe6546b64; + h ^= a2; + h = Rotate32(h, 19); + h = h * 5 + 0xe6546b64; + g ^= a1; + g = Rotate32(g, 19); + g = g * 5 + 0xe6546b64; + g ^= a3; + g = Rotate32(g, 19); + g = g * 5 + 0xe6546b64; + f += a4; + f = Rotate32(f, 19); + f = f * 5 + 0xe6546b64; + size_t iters = (len - 1) / 20; + do { + uint32 a0 = Rotate32(Fetch32(s) * c1, 17) * c2; + uint32 a1 = Fetch32(s + 4); + uint32 a2 = Rotate32(Fetch32(s + 8) * c1, 17) * c2; + uint32 a3 = Rotate32(Fetch32(s + 12) * c1, 17) * c2; + uint32 a4 = Fetch32(s + 16); + h ^= a0; + h = Rotate32(h, 18); + h = h * 5 + 0xe6546b64; + f += a1; + f = Rotate32(f, 19); + f = f * c1; + g += a2; + g = Rotate32(g, 18); + g = g * 5 + 0xe6546b64; + h ^= a3 + a1; + h = Rotate32(h, 19); + h = h * 5 + 0xe6546b64; + g ^= a4; + g = bswap_32(g) * 5; + h += a4 * 5; + h = bswap_32(h); + f += a0; + PERMUTE3(f, h, g); + s += 20; + } while (--iters != 0); + g = Rotate32(g, 11) * c1; + g = Rotate32(g, 17) * c1; + f = Rotate32(f, 11) * c1; + f = Rotate32(f, 17) * c1; + h = Rotate32(h + g, 19); + h = h * 5 + 0xe6546b64; + h = Rotate32(h, 17) * c1; + h = Rotate32(h + f, 19); + h = h * 5 + 0xe6546b64; + h = Rotate32(h, 17) * c1; + return h; +} + +// Bitwise right rotate. Normally this will compile to a single +// instruction, especially if the shift is a manifest constant. +static uint64 Rotate(uint64 val, int shift) { + // Avoid shifting by 64: doing so yields an undefined result. + return shift == 0 ? val : ((val >> shift) | (val << (64 - shift))); +} + +static uint64 ShiftMix(uint64 val) { + return val ^ (val >> 47); +} + +static uint64 HashLen16(uint64 u, uint64 v) { + return Hash128to64(uint128(u, v)); +} + +static uint64 HashLen16(uint64 u, uint64 v, uint64 mul) { + // Murmur-inspired hashing. + uint64 a = (u ^ v) * mul; + a ^= (a >> 47); + uint64 b = (v ^ a) * mul; + b ^= (b >> 47); + b *= mul; + return b; +} + +static uint64 HashLen0to16(const char *s, size_t len) { + if (len >= 8) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) + k2; + uint64 b = Fetch64(s + len - 8); + uint64 c = Rotate(b, 37) * mul + a; + uint64 d = (Rotate(a, 25) + b) * mul; + return HashLen16(c, d, mul); + } + if (len >= 4) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch32(s); + return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul); + } + if (len > 0) { + uint8 a = s[0]; + uint8 b = s[len >> 1]; + uint8 c = s[len - 1]; + uint32 y = static_cast(a) + (static_cast(b) << 8); + uint32 z = len + (static_cast(c) << 2); + return ShiftMix(y * k2 ^ z * k0) * k2; + } + return k2; +} + +// This probably works well for 16-byte strings as well, but it may be overkill +// in that case. +static uint64 HashLen17to32(const char *s, size_t len) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) * k1; + uint64 b = Fetch64(s + 8); + uint64 c = Fetch64(s + len - 8) * mul; + uint64 d = Fetch64(s + len - 16) * k2; + return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d, + a + Rotate(b + k2, 18) + c, mul); +} + +// Return a 16-byte hash for 48 bytes. Quick and dirty. +// Callers do best to use "random-looking" values for a and b. +static pair WeakHashLen32WithSeeds( + uint64 w, uint64 x, uint64 y, uint64 z, uint64 a, uint64 b) { + a += w; + b = Rotate(b + a + z, 21); + uint64 c = a; + a += x; + a += y; + b += Rotate(a, 44); + return make_pair(a + z, b + c); +} + +// Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty. +static pair WeakHashLen32WithSeeds( + const char* s, uint64 a, uint64 b) { + return WeakHashLen32WithSeeds(Fetch64(s), + Fetch64(s + 8), + Fetch64(s + 16), + Fetch64(s + 24), + a, + b); +} + +// Return an 8-byte hash for 33 to 64 bytes. +static uint64 HashLen33to64(const char *s, size_t len) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) * k2; + uint64 b = Fetch64(s + 8); + uint64 c = Fetch64(s + len - 24); + uint64 d = Fetch64(s + len - 32); + uint64 e = Fetch64(s + 16) * k2; + uint64 f = Fetch64(s + 24) * 9; + uint64 g = Fetch64(s + len - 8); + uint64 h = Fetch64(s + len - 16) * mul; + uint64 u = Rotate(a + g, 43) + (Rotate(b, 30) + c) * 9; + uint64 v = ((a + g) ^ d) + f + 1; + uint64 w = bswap_64((u + v) * mul) + h; + uint64 x = Rotate(e + f, 42) + c; + uint64 y = (bswap_64((v + w) * mul) + g) * mul; + uint64 z = e + f + c; + a = bswap_64((x + z) * mul + y) + b; + b = ShiftMix((z + a) * mul + d + h) * mul; + return b + x; +} + +uint64 CityHash64(const char *s, size_t len) { + if (len <= 32) { + if (len <= 16) { + return HashLen0to16(s, len); + } else { + return HashLen17to32(s, len); + } + } else if (len <= 64) { + return HashLen33to64(s, len); + } + + // For strings over 64 bytes we hash the end first, and then as we + // loop we keep 56 bytes of state: v, w, x, y, and z. + uint64 x = Fetch64(s + len - 40); + uint64 y = Fetch64(s + len - 16) + Fetch64(s + len - 56); + uint64 z = HashLen16(Fetch64(s + len - 48) + len, Fetch64(s + len - 24)); + pair v = WeakHashLen32WithSeeds(s + len - 64, len, z); + pair w = WeakHashLen32WithSeeds(s + len - 32, y + k1, x); + x = x * k1 + Fetch64(s); + + // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks. + len = (len - 1) & ~static_cast(63); + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 64; + } while (len != 0); + return HashLen16(HashLen16(v.first, w.first) + ShiftMix(y) * k1 + z, + HashLen16(v.second, w.second) + x); +} + +uint64 CityHash64WithSeed(const char *s, size_t len, uint64 seed) { + return CityHash64WithSeeds(s, len, k2, seed); +} + +uint64 CityHash64WithSeeds(const char *s, size_t len, + uint64 seed0, uint64 seed1) { + return HashLen16(CityHash64(s, len) - seed0, seed1); +} + +// A subroutine for CityHash128(). Returns a decent 128-bit hash for strings +// of any length representable in signed long. Based on City and Murmur. +static uint128 CityMurmur(const char *s, size_t len, uint128 seed) { + uint64 a = Uint128Low64(seed); + uint64 b = Uint128High64(seed); + uint64 c = 0; + uint64 d = 0; + signed long l = len - 16; + if (l <= 0) { // len <= 16 + a = ShiftMix(a * k1) * k1; + c = b * k1 + HashLen0to16(s, len); + d = ShiftMix(a + (len >= 8 ? Fetch64(s) : c)); + } else { // len > 16 + c = HashLen16(Fetch64(s + len - 8) + k1, a); + d = HashLen16(b + len, c + Fetch64(s + len - 16)); + a += d; + do { + a ^= ShiftMix(Fetch64(s) * k1) * k1; + a *= k1; + b ^= a; + c ^= ShiftMix(Fetch64(s + 8) * k1) * k1; + c *= k1; + d ^= c; + s += 16; + l -= 16; + } while (l > 0); + } + a = HashLen16(a, c); + b = HashLen16(d, b); + return uint128(a ^ b, HashLen16(b, a)); +} + +uint128 CityHash128WithSeed(const char *s, size_t len, uint128 seed) { + if (len < 128) { + return CityMurmur(s, len, seed); + } + + // We expect len >= 128 to be the common case. Keep 56 bytes of state: + // v, w, x, y, and z. + pair v, w; + uint64 x = Uint128Low64(seed); + uint64 y = Uint128High64(seed); + uint64 z = len * k1; + v.first = Rotate(y ^ k1, 49) * k1 + Fetch64(s); + v.second = Rotate(v.first, 42) * k1 + Fetch64(s + 8); + w.first = Rotate(y + z, 35) * k1 + x; + w.second = Rotate(x + Fetch64(s + 88), 53) * k1; + + // This is the same inner loop as CityHash64(), manually unrolled. + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 128; + } while (LIKELY(len >= 128)); + x += Rotate(v.first + z, 49) * k0; + y = y * k0 + Rotate(w.second, 37); + z = z * k0 + Rotate(w.first, 27); + w.first *= 9; + v.first *= k0; + // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. + for (size_t tail_done = 0; tail_done < len; ) { + tail_done += 32; + y = Rotate(x + y, 42) * k0 + v.second; + w.first += Fetch64(s + len - tail_done + 16); + x = x * k0 + w.first; + z += w.second + Fetch64(s + len - tail_done); + w.second += v.first; + v = WeakHashLen32WithSeeds(s + len - tail_done, v.first + z, v.second); + v.first *= k0; + } + // At this point our 56 bytes of state should contain more than + // enough information for a strong 128-bit hash. We use two + // different 56-byte-to-8-byte hashes to get a 16-byte final result. + x = HashLen16(x, v.first); + y = HashLen16(y + z, w.first); + return uint128(HashLen16(x + v.second, w.second) + y, + HashLen16(x + w.second, y + v.second)); +} + +uint128 CityHash128(const char *s, size_t len) { + return len >= 16 ? + CityHash128WithSeed(s + 16, len - 16, + uint128(Fetch64(s), Fetch64(s + 8) + k0)) : + CityHash128WithSeed(s, len, uint128(k0, k1)); +} + +#ifdef __SSE4_2__ +#include +#include + +// Requires len >= 240. +static void CityHashCrc256Long(const char *s, size_t len, + uint32 seed, uint64 *result) { + uint64 a = Fetch64(s + 56) + k0; + uint64 b = Fetch64(s + 96) + k0; + uint64 c = result[0] = HashLen16(b, len); + uint64 d = result[1] = Fetch64(s + 120) * k0 + len; + uint64 e = Fetch64(s + 184) + seed; + uint64 f = 0; + uint64 g = 0; + uint64 h = c + d; + uint64 x = seed; + uint64 y = 0; + uint64 z = 0; + + // 240 bytes of input per iter. + size_t iters = len / 240; + len -= iters * 240; + do { +#undef CHUNK +#define CHUNK(r) \ + PERMUTE3(x, z, y); \ + b += Fetch64(s); \ + c += Fetch64(s + 8); \ + d += Fetch64(s + 16); \ + e += Fetch64(s + 24); \ + f += Fetch64(s + 32); \ + a += b; \ + h += f; \ + b += c; \ + f += d; \ + g += e; \ + e += z; \ + g += x; \ + z = _mm_crc32_u64(z, b + g); \ + y = _mm_crc32_u64(y, e + h); \ + x = _mm_crc32_u64(x, f + a); \ + e = Rotate(e, r); \ + c += e; \ + s += 40 + + CHUNK(0); PERMUTE3(a, h, c); + CHUNK(33); PERMUTE3(a, h, f); + CHUNK(0); PERMUTE3(b, h, f); + CHUNK(42); PERMUTE3(b, h, d); + CHUNK(0); PERMUTE3(b, h, e); + CHUNK(33); PERMUTE3(a, h, e); + } while (--iters > 0); + + while (len >= 40) { + CHUNK(29); + e ^= Rotate(a, 20); + h += Rotate(b, 30); + g ^= Rotate(c, 40); + f += Rotate(d, 34); + PERMUTE3(c, h, g); + len -= 40; + } + if (len > 0) { + s = s + len - 40; + CHUNK(33); + e ^= Rotate(a, 43); + h += Rotate(b, 42); + g ^= Rotate(c, 41); + f += Rotate(d, 40); + } + result[0] ^= h; + result[1] ^= g; + g += h; + a = HashLen16(a, g + z); + x += y << 32; + b += x; + c = HashLen16(c, z) + h; + d = HashLen16(d, e + result[0]); + g += e; + h += HashLen16(x, f); + e = HashLen16(a, d) + g; + z = HashLen16(b, c) + a; + y = HashLen16(g, h) + c; + result[0] = e + z + y + x; + a = ShiftMix((a + y) * k0) * k0 + b; + result[1] += a + result[0]; + a = ShiftMix(a * k0) * k0 + c; + result[2] = a + result[1]; + a = ShiftMix((a + e) * k0) * k0; + result[3] = a + result[2]; +} + +// Requires len < 240. +static void CityHashCrc256Short(const char *s, size_t len, uint64 *result) { + char buf[240]; + memcpy(buf, s, len); + memset(buf + len, 0, 240 - len); + CityHashCrc256Long(buf, 240, ~static_cast(len), result); +} + +void CityHashCrc256(const char *s, size_t len, uint64 *result) { + if (LIKELY(len >= 240)) { + CityHashCrc256Long(s, len, 0, result); + } else { + CityHashCrc256Short(s, len, result); + } +} + +uint128 CityHashCrc128WithSeed(const char *s, size_t len, uint128 seed) { + if (len <= 900) { + return CityHash128WithSeed(s, len, seed); + } else { + uint64 result[4]; + CityHashCrc256(s, len, result); + uint64 u = Uint128High64(seed) + result[0]; + uint64 v = Uint128Low64(seed) + result[1]; + return uint128(HashLen16(u, v + result[2]), + HashLen16(Rotate(v, 32), u * k0 + result[3])); + } +} + +uint128 CityHashCrc128(const char *s, size_t len) { + if (len <= 900) { + return CityHash128(s, len); + } else { + uint64 result[4]; + CityHashCrc256(s, len, result); + return uint128(result[2], result[3]); + } +} + +#endif diff --git a/tests/hashing/city.h b/tests/hashing/city.h new file mode 100644 index 00000000..94499ce4 --- /dev/null +++ b/tests/hashing/city.h @@ -0,0 +1,112 @@ +// Copyright (c) 2011 Google, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// http://code.google.com/p/cityhash/ +// +// This file provides a few functions for hashing strings. All of them are +// high-quality functions in the sense that they pass standard tests such +// as Austin Appleby's SMHasher. They are also fast. +// +// For 64-bit x86 code, on short strings, we don't know of anything faster than +// CityHash64 that is of comparable quality. We believe our nearest competitor +// is Murmur3. For 64-bit x86 code, CityHash64 is an excellent choice for hash +// tables and most other hashing (excluding cryptography). +// +// For 64-bit x86 code, on long strings, the picture is more complicated. +// On many recent Intel CPUs, such as Nehalem, Westmere, Sandy Bridge, etc., +// CityHashCrc128 appears to be faster than all competitors of comparable +// quality. CityHash128 is also good but not quite as fast. We believe our +// nearest competitor is Bob Jenkins' Spooky. We don't have great data for +// other 64-bit CPUs, but for long strings we know that Spooky is slightly +// faster than CityHash on some relatively recent AMD x86-64 CPUs, for example. +// Note that CityHashCrc128 is declared in citycrc.h. +// +// For 32-bit x86 code, we don't know of anything faster than CityHash32 that +// is of comparable quality. We believe our nearest competitor is Murmur3A. +// (On 64-bit CPUs, it is typically faster to use the other CityHash variants.) +// +// Functions in the CityHash family are not suitable for cryptography. +// +// Please see CityHash's README file for more details on our performance +// measurements and so on. +// +// WARNING: This code has been only lightly tested on big-endian platforms! +// It is known to work well on little-endian platforms that have a small penalty +// for unaligned reads, such as current Intel and AMD moderate-to-high-end CPUs. +// It should work on all 32-bit and 64-bit platforms that allow unaligned reads; +// bug reports are welcome. +// +// By the way, for some hash functions, given strings a and b, the hash +// of a+b is easily derived from the hashes of a and b. This property +// doesn't hold for any hash functions in this file. + +#ifndef CITY_HASH_H_ +#define CITY_HASH_H_ + +#include // for size_t. +#include +#include + +typedef uint8_t uint8; +typedef uint32_t uint32; +typedef uint64_t uint64; +typedef std::pair uint128; + +inline uint64 Uint128Low64(const uint128& x) { return x.first; } +inline uint64 Uint128High64(const uint128& x) { return x.second; } + +// Hash function for a byte array. +uint64 CityHash64(const char *buf, size_t len); + +// Hash function for a byte array. For convenience, a 64-bit seed is also +// hashed into the result. +uint64 CityHash64WithSeed(const char *buf, size_t len, uint64 seed); + +// Hash function for a byte array. For convenience, two seeds are also +// hashed into the result. +uint64 CityHash64WithSeeds(const char *buf, size_t len, + uint64 seed0, uint64 seed1); + +// Hash function for a byte array. +uint128 CityHash128(const char *s, size_t len); + +// Hash function for a byte array. For convenience, a 128-bit seed is also +// hashed into the result. +uint128 CityHash128WithSeed(const char *s, size_t len, uint128 seed); + +// Hash function for a byte array. Most useful in 32-bit binaries. +uint32 CityHash32(const char *buf, size_t len); + +// Hash 128 input bits down to 64 bits of output. +// This is intended to be a reasonably good hash function. +inline uint64 Hash128to64(const uint128& x) { + // Murmur-inspired hashing. + const uint64 kMul = 0x9ddfea08eb382d69ULL; + uint64 a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul; + a ^= (a >> 47); + uint64 b = (Uint128High64(x) ^ a) * kMul; + b ^= (b >> 47); + b *= kMul; + return b; +} + +#endif // CITY_HASH_H_ diff --git a/tests/hashing/hash_func.h b/tests/hashing/hash_func.h new file mode 100644 index 00000000..3ea5ce92 --- /dev/null +++ b/tests/hashing/hash_func.h @@ -0,0 +1,110 @@ +//$$CDS-header$$ + +#ifndef CDSUNIT_HASH_FUNC_H +#define CDSUNIT_HASH_FUNC_H + +#include "hashing/sha256.h" +#include "hashing/md5.h" +#include "hashing/city.h" + +namespace hashing { + + template + class hasher { + typedef Hasher hasher_type; + hasher_type m_hasher; + public: + struct hash_type { + uint8_t h[hasher_type::HashBytes]; + }; + + hash_type operator()( void const * pBuf, size_t len ) + { + m_hasher.reset(); + m_hasher.add(pBuf, len); + hash_type result; + m_hasher.getHash( result.h ); + return result; + } + + hash_type operator()( std::string const& s ) + { + return operator()( reinterpret_cast(s.c_str()), s.length()); + } + + template + hash_type operator()( T const& s ) + { + return operator()( reinterpret_cast(&s), sizeof(s)); + } + }; + + typedef hasher sha256; + typedef hasher md5; + + class city32 { + public: + typedef uint32_t hash_type; + + hash_type operator()( void const * pBuf, size_t len ) + { + return CityHash32( reinterpret_cast( pBuf ), len ); + } + + hash_type operator()( std::string const& s ) + { + return CityHash32( s.c_str(), s.length() ); + } + + template + hash_type operator()( T const& s ) + { + return CityHash32( reinterpret_cast( &s ), sizeof(s)); + } + }; + + class city64 { + public: + typedef uint64_t hash_type; + + hash_type operator()( void const * pBuf, size_t len ) + { + return CityHash64( reinterpret_cast( pBuf ), len ); + } + + hash_type operator()( std::string const& s ) + { + return CityHash64( s.c_str(), s.length() ); + } + + template + hash_type operator()( T const& s ) + { + return CityHash64( reinterpret_cast( &s ), sizeof(s)); + } + }; + + class city128 { + public: + typedef uint128 hash_type; + + hash_type operator()( void const * pBuf, size_t len ) + { + return CityHash128( reinterpret_cast( pBuf ), len ); + } + + hash_type operator()( std::string const& s ) + { + return CityHash128( s.c_str(), s.length() ); + } + + template + hash_type operator()( T const& s ) + { + return CityHash128( reinterpret_cast( &s ), sizeof(s)); + } + }; + +} // namespace hashing + +#endif // #ifndef CDSUNIT_HASH_FUNC_H diff --git a/tests/hashing/md5.cpp b/tests/hashing/md5.cpp new file mode 100644 index 00000000..27433195 --- /dev/null +++ b/tests/hashing/md5.cpp @@ -0,0 +1,380 @@ +// ////////////////////////////////////////////////////////// +// md5.cpp +// Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. +// see http://create.stephan-brumme.com/disclaimer.html +// + +#include "md5.h" + +#ifndef _MSC_VER +#include +#endif + + +/// same as reset() +MD5::MD5() +{ + reset(); +} + + +/// restart +void MD5::reset() +{ + m_numBytes = 0; + m_bufferSize = 0; + + // according to RFC 1321 + m_hash[0] = 0x67452301; + m_hash[1] = 0xefcdab89; + m_hash[2] = 0x98badcfe; + m_hash[3] = 0x10325476; +} + + +namespace +{ + // mix functions for processBlock() + inline uint32_t f1(uint32_t b, uint32_t c, uint32_t d) + { + return d ^ (b & (c ^ d)); // original: f = (b & c) | ((~b) & d); + } + + inline uint32_t f2(uint32_t b, uint32_t c, uint32_t d) + { + return c ^ (d & (b ^ c)); // original: f = (b & d) | (c & (~d)); + } + + inline uint32_t f3(uint32_t b, uint32_t c, uint32_t d) + { + return b ^ c ^ d; + } + + inline uint32_t f4(uint32_t b, uint32_t c, uint32_t d) + { + return c ^ (b | ~d); + } + + inline uint32_t rotate(uint32_t a, uint32_t c) + { + return (a << c) | (a >> (32 - c)); + } + +#if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) + inline uint32_t swap(uint32_t x) + { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(x); +#endif +#ifdef MSC_VER + return _byteswap_ulong(x); +#endif + + return (x >> 24) | + ((x >> 8) & 0x0000FF00) | + ((x << 8) & 0x00FF0000) | + (x << 24); + } +#endif +} + + +/// process 64 bytes +void MD5::processBlock(const void* data) +{ + // get last hash + uint32_t a = m_hash[0]; + uint32_t b = m_hash[1]; + uint32_t c = m_hash[2]; + uint32_t d = m_hash[3]; + + // data represented as 16x 32-bit words + const uint32_t* words = (uint32_t*) data; + + // computations are little endian, swap data if necessary +#if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) +#define LITTLEENDIAN(x) swap(x) +#else +#define LITTLEENDIAN(x) (x) +#endif + + // first round + uint32_t word0 = LITTLEENDIAN(words[ 0]); + a = rotate(a + f1(b,c,d) + word0 + 0xd76aa478, 7) + b; + uint32_t word1 = LITTLEENDIAN(words[ 1]); + d = rotate(d + f1(a,b,c) + word1 + 0xe8c7b756, 12) + a; + uint32_t word2 = LITTLEENDIAN(words[ 2]); + c = rotate(c + f1(d,a,b) + word2 + 0x242070db, 17) + d; + uint32_t word3 = LITTLEENDIAN(words[ 3]); + b = rotate(b + f1(c,d,a) + word3 + 0xc1bdceee, 22) + c; + + uint32_t word4 = LITTLEENDIAN(words[ 4]); + a = rotate(a + f1(b,c,d) + word4 + 0xf57c0faf, 7) + b; + uint32_t word5 = LITTLEENDIAN(words[ 5]); + d = rotate(d + f1(a,b,c) + word5 + 0x4787c62a, 12) + a; + uint32_t word6 = LITTLEENDIAN(words[ 6]); + c = rotate(c + f1(d,a,b) + word6 + 0xa8304613, 17) + d; + uint32_t word7 = LITTLEENDIAN(words[ 7]); + b = rotate(b + f1(c,d,a) + word7 + 0xfd469501, 22) + c; + + uint32_t word8 = LITTLEENDIAN(words[ 8]); + a = rotate(a + f1(b,c,d) + word8 + 0x698098d8, 7) + b; + uint32_t word9 = LITTLEENDIAN(words[ 9]); + d = rotate(d + f1(a,b,c) + word9 + 0x8b44f7af, 12) + a; + uint32_t word10 = LITTLEENDIAN(words[10]); + c = rotate(c + f1(d,a,b) + word10 + 0xffff5bb1, 17) + d; + uint32_t word11 = LITTLEENDIAN(words[11]); + b = rotate(b + f1(c,d,a) + word11 + 0x895cd7be, 22) + c; + + uint32_t word12 = LITTLEENDIAN(words[12]); + a = rotate(a + f1(b,c,d) + word12 + 0x6b901122, 7) + b; + uint32_t word13 = LITTLEENDIAN(words[13]); + d = rotate(d + f1(a,b,c) + word13 + 0xfd987193, 12) + a; + uint32_t word14 = LITTLEENDIAN(words[14]); + c = rotate(c + f1(d,a,b) + word14 + 0xa679438e, 17) + d; + uint32_t word15 = LITTLEENDIAN(words[15]); + b = rotate(b + f1(c,d,a) + word15 + 0x49b40821, 22) + c; + + // second round + a = rotate(a + f2(b,c,d) + word1 + 0xf61e2562, 5) + b; + d = rotate(d + f2(a,b,c) + word6 + 0xc040b340, 9) + a; + c = rotate(c + f2(d,a,b) + word11 + 0x265e5a51, 14) + d; + b = rotate(b + f2(c,d,a) + word0 + 0xe9b6c7aa, 20) + c; + + a = rotate(a + f2(b,c,d) + word5 + 0xd62f105d, 5) + b; + d = rotate(d + f2(a,b,c) + word10 + 0x02441453, 9) + a; + c = rotate(c + f2(d,a,b) + word15 + 0xd8a1e681, 14) + d; + b = rotate(b + f2(c,d,a) + word4 + 0xe7d3fbc8, 20) + c; + + a = rotate(a + f2(b,c,d) + word9 + 0x21e1cde6, 5) + b; + d = rotate(d + f2(a,b,c) + word14 + 0xc33707d6, 9) + a; + c = rotate(c + f2(d,a,b) + word3 + 0xf4d50d87, 14) + d; + b = rotate(b + f2(c,d,a) + word8 + 0x455a14ed, 20) + c; + + a = rotate(a + f2(b,c,d) + word13 + 0xa9e3e905, 5) + b; + d = rotate(d + f2(a,b,c) + word2 + 0xfcefa3f8, 9) + a; + c = rotate(c + f2(d,a,b) + word7 + 0x676f02d9, 14) + d; + b = rotate(b + f2(c,d,a) + word12 + 0x8d2a4c8a, 20) + c; + + // third round + a = rotate(a + f3(b,c,d) + word5 + 0xfffa3942, 4) + b; + d = rotate(d + f3(a,b,c) + word8 + 0x8771f681, 11) + a; + c = rotate(c + f3(d,a,b) + word11 + 0x6d9d6122, 16) + d; + b = rotate(b + f3(c,d,a) + word14 + 0xfde5380c, 23) + c; + + a = rotate(a + f3(b,c,d) + word1 + 0xa4beea44, 4) + b; + d = rotate(d + f3(a,b,c) + word4 + 0x4bdecfa9, 11) + a; + c = rotate(c + f3(d,a,b) + word7 + 0xf6bb4b60, 16) + d; + b = rotate(b + f3(c,d,a) + word10 + 0xbebfbc70, 23) + c; + + a = rotate(a + f3(b,c,d) + word13 + 0x289b7ec6, 4) + b; + d = rotate(d + f3(a,b,c) + word0 + 0xeaa127fa, 11) + a; + c = rotate(c + f3(d,a,b) + word3 + 0xd4ef3085, 16) + d; + b = rotate(b + f3(c,d,a) + word6 + 0x04881d05, 23) + c; + + a = rotate(a + f3(b,c,d) + word9 + 0xd9d4d039, 4) + b; + d = rotate(d + f3(a,b,c) + word12 + 0xe6db99e5, 11) + a; + c = rotate(c + f3(d,a,b) + word15 + 0x1fa27cf8, 16) + d; + b = rotate(b + f3(c,d,a) + word2 + 0xc4ac5665, 23) + c; + + // fourth round + a = rotate(a + f4(b,c,d) + word0 + 0xf4292244, 6) + b; + d = rotate(d + f4(a,b,c) + word7 + 0x432aff97, 10) + a; + c = rotate(c + f4(d,a,b) + word14 + 0xab9423a7, 15) + d; + b = rotate(b + f4(c,d,a) + word5 + 0xfc93a039, 21) + c; + + a = rotate(a + f4(b,c,d) + word12 + 0x655b59c3, 6) + b; + d = rotate(d + f4(a,b,c) + word3 + 0x8f0ccc92, 10) + a; + c = rotate(c + f4(d,a,b) + word10 + 0xffeff47d, 15) + d; + b = rotate(b + f4(c,d,a) + word1 + 0x85845dd1, 21) + c; + + a = rotate(a + f4(b,c,d) + word8 + 0x6fa87e4f, 6) + b; + d = rotate(d + f4(a,b,c) + word15 + 0xfe2ce6e0, 10) + a; + c = rotate(c + f4(d,a,b) + word6 + 0xa3014314, 15) + d; + b = rotate(b + f4(c,d,a) + word13 + 0x4e0811a1, 21) + c; + + a = rotate(a + f4(b,c,d) + word4 + 0xf7537e82, 6) + b; + d = rotate(d + f4(a,b,c) + word11 + 0xbd3af235, 10) + a; + c = rotate(c + f4(d,a,b) + word2 + 0x2ad7d2bb, 15) + d; + b = rotate(b + f4(c,d,a) + word9 + 0xeb86d391, 21) + c; + + // update hash + m_hash[0] += a; + m_hash[1] += b; + m_hash[2] += c; + m_hash[3] += d; +} + + +/// add arbitrary number of bytes +void MD5::add(const void* data, size_t numBytes) +{ + const uint8_t* current = (const uint8_t*) data; + + if (m_bufferSize > 0) + { + while (numBytes > 0 && m_bufferSize < BlockSize) + { + m_buffer[m_bufferSize++] = *current++; + numBytes--; + } + } + + // full buffer + if (m_bufferSize == BlockSize) + { + processBlock(m_buffer); + m_numBytes += BlockSize; + m_bufferSize = 0; + } + + // no more data ? + if (numBytes == 0) + return; + + // process full blocks + while (numBytes >= BlockSize) + { + processBlock(current); + current += BlockSize; + m_numBytes += BlockSize; + numBytes -= BlockSize; + } + + // keep remaining bytes in buffer + while (numBytes > 0) + { + m_buffer[m_bufferSize++] = *current++; + numBytes--; + } +} + + +/// process final block, less than 64 bytes +void MD5::processBuffer() +{ + // the input bytes are considered as bits strings, where the first bit is the most significant bit of the byte + + // - append "1" bit to message + // - append "0" bits until message length in bit mod 512 is 448 + // - append length as 64 bit integer + + // number of bits + size_t paddedLength = m_bufferSize * 8; + + // plus one bit set to 1 (always appended) + paddedLength++; + + // number of bits must be (numBits % 512) = 448 + size_t lower11Bits = paddedLength & 511; + if (lower11Bits <= 448) + paddedLength += 448 - lower11Bits; + else + paddedLength += 512 + 448 - lower11Bits; + // convert from bits to bytes + paddedLength /= 8; + + // only needed if additional data flows over into a second block + unsigned char extra[BlockSize]; + + // append a "1" bit, 128 => binary 10000000 + if (m_bufferSize < BlockSize) + m_buffer[m_bufferSize] = 128; + else + extra[0] = 128; + + size_t i; + for (i = m_bufferSize + 1; i < BlockSize; i++) + m_buffer[i] = 0; + for (; i < paddedLength; i++) + extra[i - BlockSize] = 0; + + // add message length in bits as 64 bit number + uint64_t msgBits = 8 * (m_numBytes + m_bufferSize); + // find right position + unsigned char* addLength; + if (paddedLength < BlockSize) + addLength = m_buffer + paddedLength; + else + addLength = extra + paddedLength - BlockSize; + + // must be little endian + *addLength++ = msgBits & 0xFF; msgBits >>= 8; + *addLength++ = msgBits & 0xFF; msgBits >>= 8; + *addLength++ = msgBits & 0xFF; msgBits >>= 8; + *addLength++ = msgBits & 0xFF; msgBits >>= 8; + *addLength++ = msgBits & 0xFF; msgBits >>= 8; + *addLength++ = msgBits & 0xFF; msgBits >>= 8; + *addLength++ = msgBits & 0xFF; msgBits >>= 8; + *addLength++ = msgBits & 0xFF; + + // process blocks + processBlock(m_buffer); + // flowed over into a second block ? + if (paddedLength > BlockSize) + processBlock(extra); +} + + +/// return latest hash as 32 hex characters +std::string MD5::getHash() +{ + // compute hash (as raw bytes) + unsigned char rawHash[HashBytes]; + getHash(rawHash); + + // convert to hex string + std::string result; + result.reserve(2 * HashBytes); + for (int i = 0; i < HashBytes; i++) + { + static const char dec2hex[16+1] = "0123456789abcdef"; + result += dec2hex[(rawHash[i] >> 4) & 15]; + result += dec2hex[ rawHash[i] & 15]; + } + + return result; +} + + +/// return latest hash as bytes +void MD5::getHash(unsigned char buffer[MD5::HashBytes]) +{ + // save old hash if buffer is partially filled + uint32_t oldHash[HashValues]; + for (int i = 0; i < HashValues; i++) + oldHash[i] = m_hash[i]; + + // process remaining bytes + processBuffer(); + + unsigned char* current = buffer; + for (int i = 0; i < HashValues; i++) + { + *current++ = m_hash[i] & 0xFF; + *current++ = (m_hash[i] >> 8) & 0xFF; + *current++ = (m_hash[i] >> 16) & 0xFF; + *current++ = (m_hash[i] >> 24) & 0xFF; + + // restore old hash + m_hash[i] = oldHash[i]; + } +} + + +/// compute MD5 of a memory block +std::string MD5::operator()(const void* data, size_t numBytes) +{ + reset(); + add(data, numBytes); + return getHash(); +} + + +/// compute MD5 of a string, excluding final zero +std::string MD5::operator()(const std::string& text) +{ + reset(); + add(text.c_str(), text.size()); + return getHash(); +} diff --git a/tests/hashing/md5.h b/tests/hashing/md5.h new file mode 100644 index 00000000..f53e2d9e --- /dev/null +++ b/tests/hashing/md5.h @@ -0,0 +1,78 @@ +// ////////////////////////////////////////////////////////// +// md5.h +// Copyright (c) 2014 Stephan Brumme. All rights reserved. +// see http://create.stephan-brumme.com/disclaimer.html +// + +#pragma once + +//#include "hash.h" +#include + +// define fixed size integer types +#ifdef _MSC_VER +// Windows +typedef unsigned __int8 uint8_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#else +// GCC +#include +#endif + + +/// compute MD5 hash +/** Usage: + MD5 md5; + std::string myHash = md5("Hello World"); // std::string + std::string myHash2 = md5("How are you", 11); // arbitrary data, 11 bytes + + // or in a streaming fashion: + + MD5 md5; + while (more data available) + md5.add(pointer to fresh data, number of new bytes); + std::string myHash3 = md5.getHash(); + */ +class MD5 //: public Hash +{ +public: + /// split into 64 byte blocks (=> 512 bits), hash is 16 bytes long + enum { BlockSize = 512 / 8, HashBytes = 16 }; + + /// same as reset() + MD5(); + + /// compute MD5 of a memory block + std::string operator()(const void* data, size_t numBytes); + /// compute MD5 of a string, excluding final zero + std::string operator()(const std::string& text); + + /// add arbitrary number of bytes + void add(const void* data, size_t numBytes); + + /// return latest hash as 32 hex characters + std::string getHash(); + /// return latest hash as bytes + void getHash(unsigned char buffer[HashBytes]); + + /// restart + void reset(); + +private: + /// process 64 bytes + void processBlock(const void* data); + /// process everything left in the internal buffer + void processBuffer(); + + /// size of processed data in bytes + uint64_t m_numBytes; + /// valid bytes in m_buffer + size_t m_bufferSize; + /// bytes not processed yet + uint8_t m_buffer[BlockSize]; + + enum { HashValues = HashBytes / 4 }; + /// hash, stored as integers + uint32_t m_hash[HashValues]; +}; diff --git a/tests/hashing/sha256.cpp b/tests/hashing/sha256.cpp new file mode 100644 index 00000000..0c6138cd --- /dev/null +++ b/tests/hashing/sha256.cpp @@ -0,0 +1,411 @@ +// ////////////////////////////////////////////////////////// +// sha256.cpp +// Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. +// see http://create.stephan-brumme.com/disclaimer.html +// + +#include "sha256.h" + +// big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN +#ifndef _MSC_VER +#include +#endif + + +/// same as reset() +SHA256::SHA256() +{ + reset(); +} + + +/// restart +void SHA256::reset() +{ + m_numBytes = 0; + m_bufferSize = 0; + + // according to RFC 1321 + m_hash[0] = 0x6a09e667; + m_hash[1] = 0xbb67ae85; + m_hash[2] = 0x3c6ef372; + m_hash[3] = 0xa54ff53a; + m_hash[4] = 0x510e527f; + m_hash[5] = 0x9b05688c; + m_hash[6] = 0x1f83d9ab; + m_hash[7] = 0x5be0cd19; +} + + +namespace +{ + inline uint32_t rotate(uint32_t a, uint32_t c) + { + return (a >> c) | (a << (32 - c)); + } + + inline uint32_t swap(uint32_t x) + { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(x); +#endif +#ifdef MSC_VER + return _byteswap_ulong(x); +#endif + + return (x >> 24) | + ((x >> 8) & 0x0000FF00) | + ((x << 8) & 0x00FF0000) | + (x << 24); + } + + // mix functions for processBlock() + inline uint32_t f1(uint32_t e, uint32_t f, uint32_t g) + { + uint32_t term1 = rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25); + uint32_t term2 = (e & f) ^ (~e & g); //(g ^ (e & (f ^ g))) + return term1 + term2; + } + + inline uint32_t f2(uint32_t a, uint32_t b, uint32_t c) + { + uint32_t term1 = rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22); + uint32_t term2 = ((a | b) & c) | (a & b); //(a & (b ^ c)) ^ (b & c); + return term1 + term2; + } +} + + +/// process 64 bytes +void SHA256::processBlock(const void* data) +{ + // get last hash + uint32_t a = m_hash[0]; + uint32_t b = m_hash[1]; + uint32_t c = m_hash[2]; + uint32_t d = m_hash[3]; + uint32_t e = m_hash[4]; + uint32_t f = m_hash[5]; + uint32_t g = m_hash[6]; + uint32_t h = m_hash[7]; + + // data represented as 16x 32-bit words + const uint32_t* input = (uint32_t*) data; + // convert to big endian + uint32_t words[64]; + int i; + for (i = 0; i < 16; i++) +#if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) + words[i] = input[i]; +#else + words[i] = swap(input[i]); +#endif + + uint32_t x,y; // temporaries + + // first round + x = h + f1(e,f,g) + 0x428a2f98 + words[ 0]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x71374491 + words[ 1]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0xb5c0fbcf + words[ 2]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0xe9b5dba5 + words[ 3]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x3956c25b + words[ 4]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x59f111f1 + words[ 5]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x923f82a4 + words[ 6]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0xab1c5ed5 + words[ 7]; y = f2(b,c,d); e += x; a = x + y; + + // secound round + x = h + f1(e,f,g) + 0xd807aa98 + words[ 8]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x12835b01 + words[ 9]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x243185be + words[10]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x550c7dc3 + words[11]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x72be5d74 + words[12]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x80deb1fe + words[13]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x9bdc06a7 + words[14]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0xc19bf174 + words[15]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 24 words + for (; i < 24; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // third round + x = h + f1(e,f,g) + 0xe49b69c1 + words[16]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0xefbe4786 + words[17]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x0fc19dc6 + words[18]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x240ca1cc + words[19]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x2de92c6f + words[20]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x4a7484aa + words[21]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x5cb0a9dc + words[22]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x76f988da + words[23]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 32 words + for (; i < 32; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // fourth round + x = h + f1(e,f,g) + 0x983e5152 + words[24]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0xa831c66d + words[25]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0xb00327c8 + words[26]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0xbf597fc7 + words[27]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0xc6e00bf3 + words[28]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0xd5a79147 + words[29]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x06ca6351 + words[30]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x14292967 + words[31]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 40 words + for (; i < 40; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // fifth round + x = h + f1(e,f,g) + 0x27b70a85 + words[32]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x2e1b2138 + words[33]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x4d2c6dfc + words[34]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x53380d13 + words[35]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x650a7354 + words[36]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x766a0abb + words[37]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x81c2c92e + words[38]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x92722c85 + words[39]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 48 words + for (; i < 48; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // sixth round + x = h + f1(e,f,g) + 0xa2bfe8a1 + words[40]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0xa81a664b + words[41]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0xc24b8b70 + words[42]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0xc76c51a3 + words[43]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0xd192e819 + words[44]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0xd6990624 + words[45]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0xf40e3585 + words[46]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x106aa070 + words[47]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 56 words + for (; i < 56; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // seventh round + x = h + f1(e,f,g) + 0x19a4c116 + words[48]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x1e376c08 + words[49]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x2748774c + words[50]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x34b0bcb5 + words[51]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x391c0cb3 + words[52]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x4ed8aa4a + words[53]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x5b9cca4f + words[54]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x682e6ff3 + words[55]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 64 words + for (; i < 64; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // eigth round + x = h + f1(e,f,g) + 0x748f82ee + words[56]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x78a5636f + words[57]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x84c87814 + words[58]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x8cc70208 + words[59]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x90befffa + words[60]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0xa4506ceb + words[61]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0xbef9a3f7 + words[62]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0xc67178f2 + words[63]; y = f2(b,c,d); e += x; a = x + y; + + // update hash + m_hash[0] += a; + m_hash[1] += b; + m_hash[2] += c; + m_hash[3] += d; + m_hash[4] += e; + m_hash[5] += f; + m_hash[6] += g; + m_hash[7] += h; +} + + +/// add arbitrary number of bytes +void SHA256::add(const void* data, size_t numBytes) +{ + const uint8_t* current = (const uint8_t*) data; + + if (m_bufferSize > 0) + { + while (numBytes > 0 && m_bufferSize < BlockSize) + { + m_buffer[m_bufferSize++] = *current++; + numBytes--; + } + } + + // full buffer + if (m_bufferSize == BlockSize) + { + processBlock(m_buffer); + m_numBytes += BlockSize; + m_bufferSize = 0; + } + + // no more data ? + if (numBytes == 0) + return; + + // process full blocks + while (numBytes >= BlockSize) + { + processBlock(current); + current += BlockSize; + m_numBytes += BlockSize; + numBytes -= BlockSize; + } + + // keep remaining bytes in buffer + while (numBytes > 0) + { + m_buffer[m_bufferSize++] = *current++; + numBytes--; + } +} + + +/// process final block, less than 64 bytes +void SHA256::processBuffer() +{ + // the input bytes are considered as bits strings, where the first bit is the most significant bit of the byte + + // - append "1" bit to message + // - append "0" bits until message length in bit mod 512 is 448 + // - append length as 64 bit integer + + // number of bits + size_t paddedLength = m_bufferSize * 8; + + // plus one bit set to 1 (always appended) + paddedLength++; + + // number of bits must be (numBits % 512) = 448 + size_t lower11Bits = paddedLength & 511; + if (lower11Bits <= 448) + paddedLength += 448 - lower11Bits; + else + paddedLength += 512 + 448 - lower11Bits; + // convert from bits to bytes + paddedLength /= 8; + + // only needed if additional data flows over into a second block + unsigned char extra[BlockSize]; + + // append a "1" bit, 128 => binary 10000000 + if (m_bufferSize < BlockSize) + m_buffer[m_bufferSize] = 128; + else + extra[0] = 128; + + size_t i; + for (i = m_bufferSize + 1; i < BlockSize; i++) + m_buffer[i] = 0; + for (; i < paddedLength; i++) + extra[i - BlockSize] = 0; + + // add message length in bits as 64 bit number + uint64_t msgBits = 8 * (m_numBytes + m_bufferSize); + // find right position + unsigned char* addLength; + if (paddedLength < BlockSize) + addLength = m_buffer + paddedLength; + else + addLength = extra + paddedLength - BlockSize; + + // must be big endian + *addLength++ = (unsigned char)((msgBits >> 56) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 48) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 40) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 32) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 24) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 16) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 8) & 0xFF); + *addLength = (unsigned char)( msgBits & 0xFF); + + // process blocks + processBlock(m_buffer); + // flowed over into a second block ? + if (paddedLength > BlockSize) + processBlock(extra); +} + + +/// return latest hash as 64 hex characters +std::string SHA256::getHash() +{ + // compute hash (as raw bytes) + unsigned char rawHash[HashBytes]; + getHash(rawHash); + + // convert to hex string + std::string result; + result.reserve(2 * HashBytes); + for (int i = 0; i < HashBytes; i++) + { + static const char dec2hex[16+1] = "0123456789abcdef"; + result += dec2hex[(rawHash[i] >> 4) & 15]; + result += dec2hex[ rawHash[i] & 15]; + } + + return result; +} + + +/// return latest hash as bytes +void SHA256::getHash(unsigned char buffer[SHA256::HashBytes]) +{ + // save old hash if buffer is partially filled + uint32_t oldHash[HashValues]; + for (int i = 0; i < HashValues; i++) + oldHash[i] = m_hash[i]; + + // process remaining bytes + processBuffer(); + + unsigned char* current = buffer; + for (int i = 0; i < HashValues; i++) + { + *current++ = (m_hash[i] >> 24) & 0xFF; + *current++ = (m_hash[i] >> 16) & 0xFF; + *current++ = (m_hash[i] >> 8) & 0xFF; + *current++ = m_hash[i] & 0xFF; + + // restore old hash + m_hash[i] = oldHash[i]; + } +} + + +/// compute SHA256 of a memory block +std::string SHA256::operator()(const void* data, size_t numBytes) +{ + reset(); + add(data, numBytes); + return getHash(); +} + + +/// compute SHA256 of a string, excluding final zero +std::string SHA256::operator()(const std::string& text) +{ + reset(); + add(text.c_str(), text.size()); + return getHash(); +} diff --git a/tests/hashing/sha256.h b/tests/hashing/sha256.h new file mode 100644 index 00000000..aeaf314f --- /dev/null +++ b/tests/hashing/sha256.h @@ -0,0 +1,78 @@ +// ////////////////////////////////////////////////////////// +// sha256.h +// Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. +// see http://create.stephan-brumme.com/disclaimer.html +// + +#pragma once + +//#include "hash.h" +#include + +// define fixed size integer types +#ifdef _MSC_VER +// Windows +typedef unsigned __int8 uint8_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#else +// GCC +#include +#endif + + +/// compute SHA256 hash +/** Usage: + SHA256 sha256; + std::string myHash = sha256("Hello World"); // std::string + std::string myHash2 = sha256("How are you", 11); // arbitrary data, 11 bytes + + // or in a streaming fashion: + + SHA256 sha256; + while (more data available) + sha256.add(pointer to fresh data, number of new bytes); + std::string myHash3 = sha256.getHash(); + */ +class SHA256 //: public Hash +{ +public: + /// split into 64 byte blocks (=> 512 bits), hash is 32 bytes long + enum { BlockSize = 512 / 8, HashBytes = 32 }; + + /// same as reset() + SHA256(); + + /// compute SHA256 of a memory block + std::string operator()(const void* data, size_t numBytes); + /// compute SHA256 of a string, excluding final zero + std::string operator()(const std::string& text); + + /// add arbitrary number of bytes + void add(const void* data, size_t numBytes); + + /// return latest hash as 64 hex characters + std::string getHash(); + /// return latest hash as bytes + void getHash(unsigned char buffer[HashBytes]); + + /// restart + void reset(); + +private: + /// process 64 bytes + void processBlock(const void* data); + /// process everything left in the internal buffer + void processBuffer(); + + /// size of processed data in bytes + uint64_t m_numBytes; + /// valid bytes in m_buffer + size_t m_bufferSize; + /// bytes not processed yet + uint8_t m_buffer[BlockSize]; + + enum { HashValues = HashBytes / 4 }; + /// hash, stored as integers + uint32_t m_hash[HashValues]; +}; diff --git a/tests/unit/map2/map_defs.h b/tests/unit/map2/map_defs.h index 47c17bbe..644f9cb6 100644 --- a/tests/unit/map2/map_defs.h +++ b/tests/unit/map2/map_defs.h @@ -727,12 +727,45 @@ TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_stdhash) \ TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_stdhash_stat) \ TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_stdhash) \ - TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_stdhash_stat) + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_stdhash_stat) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_md5) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_md5_stat) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_md5) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_md5_stat) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_sha256) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_sha256_stat) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_sha256) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_sha256_stat) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_city64) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_city64_stat) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_city64) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_city64_stat) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_city128) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_hp_city128_stat) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_city128) \ + TEST_CASE(tag_MultiLevelHashMap, MultiLevelHashMap_dhp_city128_stat) \ + #undef CDSUNIT_TEST_MultiLevelHashMap #define CDSUNIT_TEST_MultiLevelHashMap \ CPPUNIT_TEST(MultiLevelHashMap_hp_stdhash) \ CPPUNIT_TEST(MultiLevelHashMap_hp_stdhash_stat) \ CPPUNIT_TEST(MultiLevelHashMap_dhp_stdhash) \ - CPPUNIT_TEST(MultiLevelHashMap_dhp_stdhash_stat) + CPPUNIT_TEST(MultiLevelHashMap_dhp_stdhash_stat) \ + CPPUNIT_TEST(MultiLevelHashMap_hp_md5) \ + CPPUNIT_TEST(MultiLevelHashMap_hp_md5_stat) \ + CPPUNIT_TEST(MultiLevelHashMap_dhp_md5) \ + CPPUNIT_TEST(MultiLevelHashMap_dhp_md5_stat) \ + CPPUNIT_TEST(MultiLevelHashMap_hp_sha256) \ + CPPUNIT_TEST(MultiLevelHashMap_hp_sha256_stat) \ + CPPUNIT_TEST(MultiLevelHashMap_dhp_sha256) \ + CPPUNIT_TEST(MultiLevelHashMap_dhp_sha256_stat) \ + CPPUNIT_TEST(MultiLevelHashMap_hp_city64) \ + CPPUNIT_TEST(MultiLevelHashMap_hp_city64_stat) \ + CPPUNIT_TEST(MultiLevelHashMap_dhp_city64) \ + CPPUNIT_TEST(MultiLevelHashMap_dhp_city64_stat) \ + CPPUNIT_TEST(MultiLevelHashMap_hp_city128) \ + CPPUNIT_TEST(MultiLevelHashMap_hp_city128_stat) \ + CPPUNIT_TEST(MultiLevelHashMap_dhp_city128) \ + CPPUNIT_TEST(MultiLevelHashMap_dhp_city128_stat) \ diff --git a/tests/unit/map2/map_type_multilevel_hashmap.h b/tests/unit/map2/map_type_multilevel_hashmap.h index 2c25a8e9..307ebc1a 100644 --- a/tests/unit/map2/map_type_multilevel_hashmap.h +++ b/tests/unit/map2/map_type_multilevel_hashmap.h @@ -9,6 +9,7 @@ #include #include "print_multilevel_hashset_stat.h" +#include "hashing/hash_func.h" namespace map2 { @@ -46,6 +47,66 @@ namespace map2 { typedef MultiLevelHashMap< cds::gc::HP, Key, Value, traits_MultiLevelHashMap_stat > MultiLevelHashMap_hp_stdhash_stat; typedef MultiLevelHashMap< cds::gc::DHP, Key, Value, traits_MultiLevelHashMap_stat > MultiLevelHashMap_dhp_stdhash_stat; + + // SHA256 + struct traits_MultiLevelHashMap_sha256 : public cc::multilevel_hashmap::traits + { + typedef ::hashing::sha256 hash; + }; + typedef MultiLevelHashMap< cds::gc::HP, Key, Value, traits_MultiLevelHashMap_sha256 > MultiLevelHashMap_hp_sha256; + typedef MultiLevelHashMap< cds::gc::DHP, Key, Value, traits_MultiLevelHashMap_sha256 > MultiLevelHashMap_dhp_sha256; + + struct traits_MultiLevelHashMap_sha256_stat : public traits_MultiLevelHashMap_sha256 + { + typedef cc::multilevel_hashmap::stat<> stat; + }; + typedef MultiLevelHashMap< cds::gc::HP, Key, Value, traits_MultiLevelHashMap_sha256_stat > MultiLevelHashMap_hp_sha256_stat; + typedef MultiLevelHashMap< cds::gc::DHP, Key, Value, traits_MultiLevelHashMap_sha256_stat > MultiLevelHashMap_dhp_sha256_stat; + + //MD5 + struct traits_MultiLevelHashMap_md5 : public cc::multilevel_hashmap::traits + { + typedef ::hashing::md5 hash; + }; + typedef MultiLevelHashMap< cds::gc::HP, Key, Value, traits_MultiLevelHashMap_md5 > MultiLevelHashMap_hp_md5; + typedef MultiLevelHashMap< cds::gc::DHP, Key, Value, traits_MultiLevelHashMap_md5 > MultiLevelHashMap_dhp_md5; + + struct traits_MultiLevelHashMap_md5_stat : public traits_MultiLevelHashMap_md5 + { + typedef cc::multilevel_hashmap::stat<> stat; + }; + typedef MultiLevelHashMap< cds::gc::HP, Key, Value, traits_MultiLevelHashMap_md5_stat > MultiLevelHashMap_hp_md5_stat; + typedef MultiLevelHashMap< cds::gc::DHP, Key, Value, traits_MultiLevelHashMap_md5_stat > MultiLevelHashMap_dhp_md5_stat; + + // CityHash + struct traits_MultiLevelHashMap_city64 : public cc::multilevel_hashmap::traits + { + typedef ::hashing::city64 hash; + }; + typedef MultiLevelHashMap< cds::gc::HP, Key, Value, traits_MultiLevelHashMap_city64 > MultiLevelHashMap_hp_city64; + typedef MultiLevelHashMap< cds::gc::DHP, Key, Value, traits_MultiLevelHashMap_city64 > MultiLevelHashMap_dhp_city64; + + struct traits_MultiLevelHashMap_city64_stat : public traits_MultiLevelHashMap_city64 + { + typedef cc::multilevel_hashmap::stat<> stat; + }; + typedef MultiLevelHashMap< cds::gc::HP, Key, Value, traits_MultiLevelHashMap_city64_stat > MultiLevelHashMap_hp_city64_stat; + typedef MultiLevelHashMap< cds::gc::DHP, Key, Value, traits_MultiLevelHashMap_city64_stat > MultiLevelHashMap_dhp_city64_stat; + + struct traits_MultiLevelHashMap_city128 : public cc::multilevel_hashmap::traits + { + typedef ::hashing::city128 hash; + }; + typedef MultiLevelHashMap< cds::gc::HP, Key, Value, traits_MultiLevelHashMap_city128 > MultiLevelHashMap_hp_city128; + typedef MultiLevelHashMap< cds::gc::DHP, Key, Value, traits_MultiLevelHashMap_city128 > MultiLevelHashMap_dhp_city128; + + struct traits_MultiLevelHashMap_city128_stat : public traits_MultiLevelHashMap_city128 + { + typedef cc::multilevel_hashmap::stat<> stat; + }; + typedef MultiLevelHashMap< cds::gc::HP, Key, Value, traits_MultiLevelHashMap_city128_stat > MultiLevelHashMap_hp_city128_stat; + typedef MultiLevelHashMap< cds::gc::DHP, Key, Value, traits_MultiLevelHashMap_city128_stat > MultiLevelHashMap_dhp_city128_stat; + }; template -- 2.34.1