]> dgit.raspbian.org Git - dovecot.git/commitdiff
[PATCH 01/12] lib: Add XXH64 hash implementation
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 13 Apr 2026 10:30:03 +0000 (12:30 +0200)
committerNoah Meyerhans <noahm@debian.org>
Wed, 16 Sep 2026 19:06:35 +0000 (15:06 -0400)
Adds a streaming XXH64 (non-cryptographic) hash with init/loop/result
API, a one-shot xxh64_data(), and an xxh64_to_32() XOR-fold inline.
Registered in hash_methods[] for use via the generic hash_method API.

Gbp-Pq: Name 0001-lib-Add-XXH64-hash-implementation.patch

src/lib/Makefile.am
src/lib/hash-method.c
src/lib/test-hash-method.c
src/lib/xxh64.c [new file with mode: 0644]
src/lib/xxh64.h [new file with mode: 0644]

index 6da8a308e5bbb9a93ddd7364a33a16d3114819b9..eb9b9ddda3afb76c716b212ad016f3a245c91a8c 100644 (file)
@@ -177,6 +177,7 @@ liblib_la_SOURCES = \
        sha1.c \
        sha2.c \
        sha3.c \
+       xxh64.c \
        sleep.c \
        sort.c \
        stats-dist.c \
@@ -337,6 +338,7 @@ headers = \
        sha1.h \
        sha2.h \
        sha3.h \
+       xxh64.h \
        sleep.h \
        sort.h \
        stats-dist.h \
index d7a6bed0eaf5b3ab3726cd1ee78278b346505a78..bdeefbadc66abac717f4ee86c18f8fcd9bbd51f8 100644 (file)
@@ -6,6 +6,7 @@
 #include "sha1.h"
 #include "sha2.h"
 #include "sha3.h"
+#include "xxh64.h"
 #include "hash-method.h"
 
 const struct hash_method *hash_method_lookup(const char *name)
@@ -93,6 +94,7 @@ const struct hash_method *hash_methods[] = {
        &hash_method_sha512,
        &hash_method_sha3_256,
        &hash_method_sha3_512,
+       &hash_method_xxh64,
        &hash_method_size,
        NULL
 };
index 6592ec90e606bc7781e1612b11ec7b52b5956d6f..6384dc99ca3c0069328268a28b0feddbb266b73e 100644 (file)
@@ -425,6 +425,39 @@ static void test_hash_methods_fips() {
                        "\x6d\xf6\x54\x5a\x1c\xe8\xba\x00",
                        512 / 8,
                },
+               /* xxh64 reference vectors (seed=0) */
+               { "xxh64",
+                       "",
+                       0,
+                       1,
+                       "\xef\x46\xdb\x37\x51\xd8\xe9\x99",
+                       64 / 8
+               },
+               { "xxh64",
+                       "abc",
+                       3,
+                       1,
+                       "\x44\xbc\x2c\xf5\xad\x77\x09\x99",
+                       64 / 8
+               },
+               /* 62 bytes: exercises the >= 32-byte accumulator path */
+               { "xxh64",
+                       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+                       "ghijklmnopqrstuvwxyz0123456789",
+                       62,
+                       1,
+                       "\xba\x76\x1a\x9a\xa8\x0e\x0d\x36",
+                       64 / 8
+               },
+               /* 16 bytes × 3 rounds: exercises chunked input across the 32-byte block boundary */
+               { "xxh64",
+                       "\x00\x01\x02\x03\x04\x05\x06\x07"
+                       "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+                       16,
+                       3,
+                       "\x32\x0d\xcd\xab\xd8\x29\x7a\x9e",
+                       64 / 8
+               },
        };
 
        for(size_t i = 0; i < N_ELEMENTS(test_vectors); i++) {
diff --git a/src/lib/xxh64.c b/src/lib/xxh64.c
new file mode 100644 (file)
index 0000000..1c17833
--- /dev/null
@@ -0,0 +1,184 @@
+/* Copyright (c) 2025 Dovecot authors, see the included COPYING file */
+
+/* XXHash64 - Fast non-cryptographic hash function.
+ * Based on Yann Collet's public domain XXHash64 algorithm.
+ * Reference: https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md
+ */
+
+#include "lib.h"
+#include "xxh64.h"
+
+#define XXH64_PRIME1  UINT64_C(0x9E3779B185EBCA87)
+#define XXH64_PRIME2  UINT64_C(0xC2B2AE3D27D4EB4F)
+#define XXH64_PRIME3  UINT64_C(0x165667B19E3779F9)
+#define XXH64_PRIME4  UINT64_C(0x85EBCA77C2B27589)
+#define XXH64_PRIME5  UINT64_C(0x27D4EB2F165667C5)
+
+#define XXH64_ROTL(x, r) (((x) << (r)) | ((x) >> (64 - (r))))
+
+static inline uint64_t xxh64_read64(const void *p)
+{
+       uint64_t v;
+       memcpy(&v, p, sizeof(v));
+       return v;
+}
+
+static inline uint32_t xxh64_read32(const void *p)
+{
+       uint32_t v;
+       memcpy(&v, p, sizeof(v));
+       return v;
+}
+
+static uint64_t ATTR_UNSIGNED_WRAPS xxh64_round(uint64_t acc, uint64_t input)
+{
+       acc += input * XXH64_PRIME2;
+       acc = XXH64_ROTL(acc, 31);
+       acc *= XXH64_PRIME1;
+       return acc;
+}
+
+static uint64_t ATTR_UNSIGNED_WRAPS xxh64_merge_acc(uint64_t h64, uint64_t acc)
+{
+       acc = xxh64_round(0, acc);
+       h64 ^= acc;
+       h64 = h64 * XXH64_PRIME1 + XXH64_PRIME4;
+       return h64;
+}
+
+void ATTR_UNSIGNED_WRAPS xxh64_init(struct xxh64_context *ctx, uint64_t seed)
+{
+       ctx->seed = seed;
+       ctx->v1 = seed + XXH64_PRIME1 + XXH64_PRIME2;
+       ctx->v2 = seed + XXH64_PRIME2;
+       ctx->v3 = seed;
+       ctx->v4 = seed - XXH64_PRIME1;
+       ctx->total_len = 0;
+       ctx->buf_used = 0;
+}
+
+void ATTR_UNSIGNED_WRAPS xxh64_loop(struct xxh64_context *ctx, const void *data, size_t size)
+{
+       const unsigned char *p = data;
+       const unsigned char *end = p + size;
+
+       ctx->total_len += size;
+
+       if (ctx->buf_used + size < 32) {
+               memcpy(ctx->buf + ctx->buf_used, p, size);
+               ctx->buf_used += size;
+               return;
+       }
+
+       if (ctx->buf_used > 0) {
+               size_t fill = 32 - ctx->buf_used;
+               memcpy(ctx->buf + ctx->buf_used, p, fill);
+               p += fill;
+               ctx->v1 = xxh64_round(ctx->v1, xxh64_read64(ctx->buf));
+               ctx->v2 = xxh64_round(ctx->v2, xxh64_read64(ctx->buf + 8));
+               ctx->v3 = xxh64_round(ctx->v3, xxh64_read64(ctx->buf + 16));
+               ctx->v4 = xxh64_round(ctx->v4, xxh64_read64(ctx->buf + 24));
+               ctx->buf_used = 0;
+       }
+
+       while (p + 32 <= end) {
+               ctx->v1 = xxh64_round(ctx->v1, xxh64_read64(p));
+               ctx->v2 = xxh64_round(ctx->v2, xxh64_read64(p + 8));
+               ctx->v3 = xxh64_round(ctx->v3, xxh64_read64(p + 16));
+               ctx->v4 = xxh64_round(ctx->v4, xxh64_read64(p + 24));
+               p += 32;
+       }
+
+       if (p < end) {
+               ctx->buf_used = (unsigned int)(end - p);
+               memcpy(ctx->buf, p, ctx->buf_used);
+       }
+}
+
+uint64_t ATTR_UNSIGNED_WRAPS xxh64_result(struct xxh64_context *ctx)
+{
+       const unsigned char *p = ctx->buf;
+       const unsigned char *end = p + ctx->buf_used;
+       uint64_t h64;
+
+       if (ctx->total_len >= 32) {
+               h64 = XXH64_ROTL(ctx->v1,  1) + XXH64_ROTL(ctx->v2,  7) +
+                     XXH64_ROTL(ctx->v3, 12) + XXH64_ROTL(ctx->v4, 18);
+               h64 = xxh64_merge_acc(h64, ctx->v1);
+               h64 = xxh64_merge_acc(h64, ctx->v2);
+               h64 = xxh64_merge_acc(h64, ctx->v3);
+               h64 = xxh64_merge_acc(h64, ctx->v4);
+       } else {
+               h64 = ctx->seed + XXH64_PRIME5;
+       }
+
+       h64 += ctx->total_len;
+
+       while (p + 8 <= end) {
+               h64 ^= xxh64_round(0, xxh64_read64(p));
+               h64 = XXH64_ROTL(h64, 27) * XXH64_PRIME1 + XXH64_PRIME4;
+               p += 8;
+       }
+       if (p + 4 <= end) {
+               h64 ^= (uint64_t)xxh64_read32(p) * XXH64_PRIME1;
+               h64 = XXH64_ROTL(h64, 23) * XXH64_PRIME2 + XXH64_PRIME3;
+               p += 4;
+       }
+       while (p < end) {
+               h64 ^= (uint64_t)*p * XXH64_PRIME5;
+               h64 = XXH64_ROTL(h64, 11) * XXH64_PRIME1;
+               p++;
+       }
+
+       /* Avalanche */
+       h64 ^= h64 >> 33;
+       h64 *= XXH64_PRIME2;
+       h64 ^= h64 >> 29;
+       h64 *= XXH64_PRIME3;
+       h64 ^= h64 >> 32;
+
+       return h64;
+}
+
+uint64_t xxh64_data(const void *data, size_t size, uint64_t seed)
+{
+       struct xxh64_context ctx;
+
+       xxh64_init(&ctx, seed);
+       xxh64_loop(&ctx, data, size);
+       return xxh64_result(&ctx);
+}
+
+static void xxh64_hash_init(void *context)
+{
+       xxh64_init(context, 0);
+}
+
+static void xxh64_hash_loop(void *context, const void *data, size_t size)
+{
+       xxh64_loop(context, data, size);
+}
+
+static void xxh64_hash_result(void *context, unsigned char *digest_r)
+{
+       uint64_t hash = xxh64_result(context);
+
+       digest_r[0] = (hash >> 56) & 0xff;
+       digest_r[1] = (hash >> 48) & 0xff;
+       digest_r[2] = (hash >> 40) & 0xff;
+       digest_r[3] = (hash >> 32) & 0xff;
+       digest_r[4] = (hash >> 24) & 0xff;
+       digest_r[5] = (hash >> 16) & 0xff;
+       digest_r[6] = (hash >>  8) & 0xff;
+       digest_r[7] = (hash >>  0) & 0xff;
+}
+
+const struct hash_method hash_method_xxh64 = {
+       .name = "xxh64",
+       .block_size = 32,
+       .context_size = sizeof(struct xxh64_context),
+       .digest_size = XXH64_RESULTLEN,
+       .init = xxh64_hash_init,
+       .loop = xxh64_hash_loop,
+       .result = xxh64_hash_result,
+};
diff --git a/src/lib/xxh64.h b/src/lib/xxh64.h
new file mode 100644 (file)
index 0000000..7beb4ca
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef XXH64_H
+#define XXH64_H
+
+#include "hash-method.h"
+
+#define XXH64_RESULTLEN 8
+
+struct xxh64_context {
+       uint64_t seed;
+       uint64_t v1, v2, v3, v4;
+       uint64_t total_len;
+       unsigned char buf[32];
+       unsigned int buf_used;
+};
+
+void xxh64_init(struct xxh64_context *ctx, uint64_t seed);
+void xxh64_loop(struct xxh64_context *ctx, const void *data, size_t size);
+uint64_t xxh64_result(struct xxh64_context *ctx);
+
+uint64_t xxh64_data(const void *data, size_t size, uint64_t seed) ATTR_PURE;
+
+/* XOR-fold the 64-bit hash to 32 bits */
+static inline uint32_t xxh64_to_32(uint64_t hash)
+{
+       return (uint32_t)(hash ^ (hash >> 32));
+}
+
+extern const struct hash_method hash_method_xxh64;
+
+#endif