LibQuicR
Loading...
Searching...
No Matches
hash.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems
2// SPDX-License-Identifier: BSD-2-Clause
3
4#pragma once
5
6#include "detail/utilities.h"
7
8#include <array>
9#include <concepts>
10#include <cstdint>
11#include <span>
12#include <string_view>
13
14namespace quicr {
15 namespace detail {
42 {
43 using uint128_t = std::array<std::uint64_t, 2>;
44
45 static constexpr std::uint64_t k0 = 0xc3a5c85c97cb3127ULL;
46 static constexpr std::uint64_t k1 = 0xb492b66fbe98f273ULL;
47 static constexpr std::uint64_t k2 = 0x9ae16a3b2f90404fULL;
48
49 private:
50 template<std::unsigned_integral T>
51 constexpr T UnalignedLoad(std::span<const std::uint8_t> bytes)
52 {
53 T result = 0;
54 if (std::is_constant_evaluated()) {
55 for (size_t i = 0; i < sizeof(T); ++i) {
56 result += bytes[i];
57 result <<= 8;
58 }
59 } else {
60 std::memcpy(&result, bytes.data(), sizeof(T));
61 }
62
63 return result;
64 }
65
66 template<std::unsigned_integral T>
67 constexpr T Fetch(std::span<const std::uint8_t> bytes)
68 {
69 return SwapBytes(UnalignedLoad<T>(bytes));
70 }
71
72 constexpr std::uint64_t Rotate(std::uint64_t val, int shift)
73 {
74 return shift == 0 ? val : ((val >> shift) | (val << ((sizeof(std::uint64_t) * 8) - shift)));
75 }
76
77 constexpr std::uint64_t ShiftMix(std::uint64_t val) { return val ^ (val >> 47); }
78
79 constexpr std::uint64_t Hash128to64(const uint128_t& x)
80 {
81 constexpr std::uint64_t kMul = 0x9ddfea08eb382d69ULL;
82 std::uint64_t a = (x[0] ^ x[1]) * kMul;
83 a ^= (a >> 47);
84 std::uint64_t b = (x[1] ^ a) * kMul;
85 b ^= (b >> 47);
86 b *= kMul;
87 return b;
88 }
89
90 constexpr std::uint64_t HashLen16(std::uint64_t u, std::uint64_t v)
91 {
92 return Hash128to64(uint128_t{ u, v });
93 }
94
95 constexpr std::uint64_t HashLen16(std::uint64_t u, std::uint64_t v, std::uint64_t mul)
96 {
97 // Murmur-inspired hashing.
98 std::uint64_t a = (u ^ v) * mul;
99 a ^= (a >> 47);
100 std::uint64_t b = (v ^ a) * mul;
101 b ^= (b >> 47);
102 b *= mul;
103 return b;
104 }
105
106 constexpr std::uint64_t HashLen0to16(std::span<const std::uint8_t> bytes)
107 {
108 const std::size_t len = bytes.size();
109
110 if (len >= 8) {
111 std::uint64_t mul = k2 + len * 2;
112 std::uint64_t a = Fetch<std::uint64_t>(bytes) + k2;
113 std::uint64_t b = Fetch<std::uint64_t>(bytes.subspan(len - 8));
114 std::uint64_t c = Rotate(b, 37) * mul + a;
115 std::uint64_t d = (Rotate(a, 25) + b) * mul;
116 return HashLen16(c, d, mul);
117 }
118 if (len >= 4) {
119 std::uint64_t mul = k2 + len * 2;
120 std::uint64_t a = Fetch<std::uint32_t>(bytes);
121 return HashLen16(len + (a << 3), Fetch<std::uint32_t>(bytes.subspan(len - 4)), mul);
122 }
123 if (len > 0) {
124 std::uint8_t a = static_cast<std::uint8_t>(bytes[0]);
125 std::uint8_t b = static_cast<std::uint8_t>(bytes[len >> 1]);
126 std::uint8_t c = static_cast<std::uint8_t>(bytes[len - 1]);
127 std::uint32_t y = static_cast<std::uint32_t>(a) + (static_cast<std::uint32_t>(b) << 8);
128 std::uint32_t z = static_cast<std::uint32_t>(len) + (static_cast<std::uint32_t>(c) << 2);
129 return ShiftMix(y * k2 ^ z * k0) * k2;
130 }
131 return k2;
132 }
133
134 constexpr std::uint64_t HashLen17to32(std::span<const std::uint8_t> bytes)
135 {
136 const std::size_t len = bytes.size();
137
138 std::uint64_t mul = k2 + len * 2;
139 std::uint64_t a = Fetch<std::uint64_t>(bytes) * k1;
140 std::uint64_t b = Fetch<std::uint64_t>(bytes.subspan(8));
141 std::uint64_t c = Fetch<std::uint64_t>(bytes.subspan(len - 8)) * mul;
142 std::uint64_t d = Fetch<std::uint64_t>(bytes.subspan(len - 16)) * k2;
143 return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d, a + Rotate(b + k2, 18) + c, mul);
144 }
145
146 constexpr uint128_t WeakHashLen32WithSeeds(std::uint64_t w,
147 std::uint64_t x,
148 std::uint64_t y,
149 std::uint64_t z,
150 std::uint64_t a,
151 std::uint64_t b)
152 {
153 a += w;
154 b = Rotate(b + a + z, 21);
155 std::uint64_t c = a;
156 a += x;
157 a += y;
158 b += Rotate(a, 44);
159 return { a + z, b + c };
160 }
161
162 constexpr uint128_t WeakHashLen32WithSeeds(std::span<const std::uint8_t> bytes,
163 std::uint64_t a,
164 std::uint64_t b)
165 {
166 return WeakHashLen32WithSeeds(Fetch<std::uint64_t>(bytes),
167 Fetch<std::uint64_t>(bytes.subspan(8)),
168 Fetch<std::uint64_t>(bytes.subspan(16)),
169 Fetch<std::uint64_t>(bytes.subspan(24)),
170 a,
171 b);
172 }
173
174 constexpr std::uint64_t HashLen33to64(std::span<const std::uint8_t> bytes)
175 {
176 const std::size_t len = bytes.size();
177 std::uint64_t mul = k2 + len * 2;
178 std::uint64_t a = Fetch<std::uint64_t>(bytes) * k2;
179 std::uint64_t b = Fetch<std::uint64_t>(bytes.subspan(8));
180 std::uint64_t c = Fetch<std::uint64_t>(bytes.subspan(len - 24));
181 std::uint64_t d = Fetch<std::uint64_t>(bytes.subspan(len - 32));
182 std::uint64_t e = Fetch<std::uint64_t>(bytes.subspan(16)) * k2;
183 std::uint64_t f = Fetch<std::uint64_t>(bytes.subspan(24)) * 9;
184 std::uint64_t g = Fetch<std::uint64_t>(bytes.subspan(len - 8));
185 std::uint64_t h = Fetch<std::uint64_t>(bytes.subspan(len - 16)) * mul;
186 std::uint64_t u = Rotate(a + g, 43) + (Rotate(b, 30) + c) * 9;
187 std::uint64_t v = ((a + g) ^ d) + f + 1;
188 std::uint64_t w = SwapBytes((u + v) * mul) + h;
189 std::uint64_t x = Rotate(e + f, 42) + c;
190 std::uint64_t y = (SwapBytes((v + w) * mul) + g) * mul;
191 std::uint64_t z = e + f + c;
192
193 a = SwapBytes((x + z) * mul + y) + b;
194 b = ShiftMix((z + a) * mul + d + h) * mul;
195
196 return b + x;
197 }
198
199 public:
200 constexpr std::uint64_t operator()(std::span<const std::uint8_t> bytes)
201 {
202 std::size_t len = bytes.size();
203
204 if (len <= 16) {
205 return HashLen0to16(bytes);
206 } else if (len <= 32) {
207 return HashLen17to32(bytes);
208 } else if (len <= 64) {
209 return HashLen33to64(bytes);
210 }
211
212 // For strings over 64 bytes we hash the end first, and then as we
213 // loop we keep 56 bytes of state: v, w, x, y, and z.
214 std::uint64_t x = Fetch<std::uint64_t>(bytes.subspan(len - 40));
215 std::uint64_t y =
216 Fetch<std::uint64_t>(bytes.subspan(len - 16)) + Fetch<std::uint64_t>(bytes.subspan(len - 56));
217 std::uint64_t z = HashLen16(Fetch<std::uint64_t>(bytes.subspan(len - 48)) + len,
218 Fetch<std::uint64_t>(bytes.subspan(len - 24)));
219 uint128_t v = WeakHashLen32WithSeeds(bytes.subspan(len - 64), len, z);
220 uint128_t w = WeakHashLen32WithSeeds(bytes.subspan(len - 32), y + k1, x);
221 x = x * k1 + Fetch<std::uint64_t>(bytes);
222
223 // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks.
224 len = (len - 1) & ~static_cast<size_t>(63);
225 do {
226 x = Rotate(x + y + v[0] + Fetch<std::uint64_t>(bytes.subspan(8)), 37) * k1;
227 y = Rotate(y + v[1] + Fetch<std::uint64_t>(bytes.subspan(48)), 42) * k1;
228 x ^= w[1];
229 y += v[0] + Fetch<std::uint64_t>(bytes.subspan(40));
230 z = Rotate(z + w[0], 33) * k1;
231 v = WeakHashLen32WithSeeds(bytes, v[1] * k1, x + w[0]);
232 w =
233 WeakHashLen32WithSeeds(bytes.subspan(32), z + w[1], y + Fetch<std::uint64_t>(bytes.subspan(16)));
234 std::swap(z, x);
235 bytes = bytes.subspan(64);
236 len -= 64;
237 } while (len != 0);
238
239 return HashLen16(HashLen16(v[0], w[0]) + ShiftMix(y) * k1 + z, HashLen16(v[1], w[1]) + x);
240 }
241 };
242 }
243
249 constexpr std::uint64_t hash(std::span<const std::uint8_t> bytes)
250 {
251 return detail::CityHash64{}(bytes);
252 }
253
263 constexpr void hash_combine(std::uint64_t& seed, const std::uint64_t& value)
264 {
265 seed ^= value + 0x9e3779b9 + (seed << 6) + (value >> 2);
266 }
267}
268
269template<>
270struct std::hash<std::span<const std::uint8_t>> : public quicr::detail::CityHash64
271{};
Computes a CityHash64 hash for a span of bytes.
Definition hash.h:42
constexpr std::uint64_t operator()(std::span< const std::uint8_t > bytes)
Definition hash.h:200
Definition hash.h:15
Definition transport.h:28
constexpr std::uint64_t hash(std::span< const std::uint8_t > bytes)
Hash a span of bytes to a 64bit number.
Definition hash.h:249
constexpr void hash_combine(std::uint64_t &seed, const std::uint64_t &value)
Combine (aka add) hash to existing hash.
Definition hash.h:263