Coverage Report

Created: 2026-10-17 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
};