/src/dovecot/src/lib/xxh64.c
Line | Count | Source |
1 | | /* Copyright (c) 2025 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | /* XXHash64 - Fast non-cryptographic hash function. |
4 | | * Based on Yann Collet's public domain XXHash64 algorithm. |
5 | | * Reference: https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md |
6 | | */ |
7 | | |
8 | | #include "lib.h" |
9 | | #include "xxh64.h" |
10 | | |
11 | 0 | #define XXH64_PRIME1 UINT64_C(0x9E3779B185EBCA87) |
12 | 0 | #define XXH64_PRIME2 UINT64_C(0xC2B2AE3D27D4EB4F) |
13 | 0 | #define XXH64_PRIME3 UINT64_C(0x165667B19E3779F9) |
14 | 0 | #define XXH64_PRIME4 UINT64_C(0x85EBCA77C2B27589) |
15 | 0 | #define XXH64_PRIME5 UINT64_C(0x27D4EB2F165667C5) |
16 | | |
17 | 0 | #define XXH64_ROTL(x, r) (((x) << (r)) | ((x) >> (64 - (r)))) |
18 | | |
19 | | static uint64_t ATTR_UNSIGNED_WRAPS xxh64_round(uint64_t acc, uint64_t input) |
20 | 0 | { |
21 | 0 | acc += input * XXH64_PRIME2; |
22 | 0 | acc = XXH64_ROTL(acc, 31); |
23 | 0 | acc *= XXH64_PRIME1; |
24 | 0 | return acc; |
25 | 0 | } |
26 | | |
27 | | static uint64_t ATTR_UNSIGNED_WRAPS xxh64_merge_acc(uint64_t h64, uint64_t acc) |
28 | 0 | { |
29 | 0 | acc = xxh64_round(0, acc); |
30 | 0 | h64 ^= acc; |
31 | 0 | h64 = h64 * XXH64_PRIME1 + XXH64_PRIME4; |
32 | 0 | return h64; |
33 | 0 | } |
34 | | |
35 | | void ATTR_UNSIGNED_WRAPS xxh64_init(struct xxh64_context *ctx, uint64_t seed) |
36 | 0 | { |
37 | 0 | ctx->seed = seed; |
38 | 0 | ctx->v1 = seed + XXH64_PRIME1 + XXH64_PRIME2; |
39 | 0 | ctx->v2 = seed + XXH64_PRIME2; |
40 | 0 | ctx->v3 = seed; |
41 | 0 | ctx->v4 = seed - XXH64_PRIME1; |
42 | 0 | ctx->total_len = 0; |
43 | 0 | ctx->buf_used = 0; |
44 | 0 | } |
45 | | |
46 | | void ATTR_UNSIGNED_WRAPS xxh64_loop(struct xxh64_context *ctx, const void *data, size_t size) |
47 | 0 | { |
48 | 0 | const unsigned char *p = data; |
49 | 0 | const unsigned char *end = p + size; |
50 | |
|
51 | 0 | ctx->total_len += size; |
52 | |
|
53 | 0 | if (ctx->buf_used + size < 32) { |
54 | 0 | memcpy(ctx->buf + ctx->buf_used, p, size); |
55 | 0 | ctx->buf_used += size; |
56 | 0 | return; |
57 | 0 | } |
58 | | |
59 | 0 | if (ctx->buf_used > 0) { |
60 | 0 | size_t fill = 32 - ctx->buf_used; |
61 | 0 | i_assert(fill <= size); |
62 | 0 | memcpy(ctx->buf + ctx->buf_used, p, fill); |
63 | 0 | p += fill; |
64 | 0 | ctx->v1 = xxh64_round(ctx->v1, |
65 | 0 | le64_to_cpu_unaligned(ctx->buf)); |
66 | 0 | ctx->v2 = xxh64_round(ctx->v2, |
67 | 0 | le64_to_cpu_unaligned(ctx->buf + 8)); |
68 | 0 | ctx->v3 = xxh64_round(ctx->v3, |
69 | 0 | le64_to_cpu_unaligned(ctx->buf + 16)); |
70 | 0 | ctx->v4 = xxh64_round(ctx->v4, |
71 | 0 | le64_to_cpu_unaligned(ctx->buf + 24)); |
72 | 0 | ctx->buf_used = 0; |
73 | 0 | } |
74 | | |
75 | 0 | while (p + 32 <= end) { |
76 | 0 | ctx->v1 = xxh64_round(ctx->v1, le64_to_cpu_unaligned(p)); |
77 | 0 | ctx->v2 = xxh64_round(ctx->v2, le64_to_cpu_unaligned(p + 8)); |
78 | 0 | ctx->v3 = xxh64_round(ctx->v3, le64_to_cpu_unaligned(p + 16)); |
79 | 0 | ctx->v4 = xxh64_round(ctx->v4, le64_to_cpu_unaligned(p + 24)); |
80 | 0 | p += 32; |
81 | 0 | } |
82 | |
|
83 | 0 | if (p < end) { |
84 | 0 | ctx->buf_used = (unsigned int)(end - p); |
85 | 0 | memcpy(ctx->buf, p, ctx->buf_used); |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | | uint64_t ATTR_UNSIGNED_WRAPS xxh64_result(struct xxh64_context *ctx) |
90 | 0 | { |
91 | 0 | const unsigned char *p = ctx->buf; |
92 | 0 | const unsigned char *end = p + ctx->buf_used; |
93 | 0 | uint64_t h64; |
94 | |
|
95 | 0 | if (ctx->total_len >= 32) { |
96 | 0 | h64 = XXH64_ROTL(ctx->v1, 1) + XXH64_ROTL(ctx->v2, 7) + |
97 | 0 | XXH64_ROTL(ctx->v3, 12) + XXH64_ROTL(ctx->v4, 18); |
98 | 0 | h64 = xxh64_merge_acc(h64, ctx->v1); |
99 | 0 | h64 = xxh64_merge_acc(h64, ctx->v2); |
100 | 0 | h64 = xxh64_merge_acc(h64, ctx->v3); |
101 | 0 | h64 = xxh64_merge_acc(h64, ctx->v4); |
102 | 0 | } else { |
103 | 0 | h64 = ctx->seed + XXH64_PRIME5; |
104 | 0 | } |
105 | |
|
106 | 0 | h64 += ctx->total_len; |
107 | |
|
108 | 0 | while (p + 8 <= end) { |
109 | 0 | h64 ^= xxh64_round(0, le64_to_cpu_unaligned(p)); |
110 | 0 | h64 = XXH64_ROTL(h64, 27) * XXH64_PRIME1 + XXH64_PRIME4; |
111 | 0 | p += 8; |
112 | 0 | } |
113 | 0 | if (p + 4 <= end) { |
114 | 0 | h64 ^= (uint64_t)le32_to_cpu_unaligned(p) * XXH64_PRIME1; |
115 | 0 | h64 = XXH64_ROTL(h64, 23) * XXH64_PRIME2 + XXH64_PRIME3; |
116 | 0 | p += 4; |
117 | 0 | } |
118 | 0 | while (p < end) { |
119 | 0 | h64 ^= (uint64_t)*p * XXH64_PRIME5; |
120 | 0 | h64 = XXH64_ROTL(h64, 11) * XXH64_PRIME1; |
121 | 0 | p++; |
122 | 0 | } |
123 | | |
124 | | /* Avalanche */ |
125 | 0 | h64 ^= h64 >> 33; |
126 | 0 | h64 *= XXH64_PRIME2; |
127 | 0 | h64 ^= h64 >> 29; |
128 | 0 | h64 *= XXH64_PRIME3; |
129 | 0 | h64 ^= h64 >> 32; |
130 | |
|
131 | 0 | return h64; |
132 | 0 | } |
133 | | |
134 | | uint64_t xxh64_data(const void *data, size_t size, uint64_t seed) |
135 | 0 | { |
136 | 0 | struct xxh64_context ctx; |
137 | |
|
138 | 0 | xxh64_init(&ctx, seed); |
139 | 0 | xxh64_loop(&ctx, data, size); |
140 | 0 | return xxh64_result(&ctx); |
141 | 0 | } |
142 | | |
143 | | static void xxh64_hash_init(void *context) |
144 | 0 | { |
145 | 0 | xxh64_init(context, 0); |
146 | 0 | } |
147 | | |
148 | | static void xxh64_hash_loop(void *context, const void *data, size_t size) |
149 | 0 | { |
150 | 0 | xxh64_loop(context, data, size); |
151 | 0 | } |
152 | | |
153 | | static void xxh64_hash_result(void *context, unsigned char *digest_r) |
154 | 0 | { |
155 | 0 | uint64_t hash = xxh64_result(context); |
156 | |
|
157 | 0 | digest_r[0] = (hash >> 56) & 0xff; |
158 | 0 | digest_r[1] = (hash >> 48) & 0xff; |
159 | 0 | digest_r[2] = (hash >> 40) & 0xff; |
160 | 0 | digest_r[3] = (hash >> 32) & 0xff; |
161 | 0 | digest_r[4] = (hash >> 24) & 0xff; |
162 | 0 | digest_r[5] = (hash >> 16) & 0xff; |
163 | 0 | digest_r[6] = (hash >> 8) & 0xff; |
164 | 0 | digest_r[7] = (hash >> 0) & 0xff; |
165 | 0 | } |
166 | | |
167 | | const struct hash_method hash_method_xxh64 = { |
168 | | .name = "xxh64", |
169 | | .block_size = 32, |
170 | | .context_size = sizeof(struct xxh64_context), |
171 | | .digest_size = XXH64_RESULTLEN, |
172 | | .init = xxh64_hash_init, |
173 | | .loop = xxh64_hash_loop, |
174 | | .result = xxh64_hash_result, |
175 | | }; |