Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/hash/hash_xxhash.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Author: Anatol Belski <ab@php.net>                                   |
12
   +----------------------------------------------------------------------+
13
*/
14
15
#include "php_hash.h"
16
#include "php_hash_xxhash.h"
17
18
static hash_spec_result php_hash_xxh32_unserialize(
19
    php_hashcontext_object *hash, zend_long magic, const zval *zv);
20
static hash_spec_result php_hash_xxh64_unserialize(
21
    php_hashcontext_object *hash, zend_long magic, const zval *zv);
22
23
const php_hash_ops php_hash_xxh32_ops = {
24
  "xxh32",
25
  (php_hash_init_func_t) PHP_XXH32Init,
26
  (php_hash_update_func_t) PHP_XXH32Update,
27
  (php_hash_final_func_t) PHP_XXH32Final,
28
  (php_hash_copy_func_t) PHP_XXH32Copy,
29
  php_hash_serialize,
30
  php_hash_xxh32_unserialize,
31
  PHP_XXH32_SPEC,
32
  4,
33
  4,
34
  sizeof(PHP_XXH32_CTX),
35
  0,
36
  0
37
};
38
39
PHP_HASH_API void PHP_XXH32Init(PHP_XXH32_CTX *ctx, HashTable *args)
40
105
{
41
  /* XXH32_createState() is not used intentionally. */
42
105
  memset(&ctx->s, 0, sizeof ctx->s);
43
44
105
  if (args) {
45
0
    zval *seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
46
    /* This might be a bit too restrictive, but thinking that a seed might be set
47
      once and for all, it should be done a clean way. */
48
0
    if (seed) {
49
0
      if (IS_LONG == Z_TYPE_P(seed)) {
50
0
        XXH32_reset(&ctx->s, (XXH32_hash_t)Z_LVAL_P(seed));
51
0
        return;
52
0
      } else {
53
0
        php_error_docref(NULL, E_DEPRECATED, "Passing a seed of a type other than int is deprecated because it is the same as setting the seed to 0");
54
0
      }
55
0
    }
56
0
  }
57
58
105
  XXH32_reset(&ctx->s, 0);
59
105
}
60
61
PHP_HASH_API void PHP_XXH32Update(PHP_XXH32_CTX *ctx, const unsigned char *in, size_t len)
62
64
{
63
64
  XXH32_update(&ctx->s, in, len);
64
64
}
65
66
PHP_HASH_API void PHP_XXH32Final(unsigned char digest[4], PHP_XXH32_CTX *ctx)
67
64
{
68
64
  XXH32_canonicalFromHash((XXH32_canonical_t*)digest, XXH32_digest(&ctx->s));
69
64
}
70
71
PHP_HASH_API zend_result PHP_XXH32Copy(const php_hash_ops *ops, const PHP_XXH32_CTX *orig_context, PHP_XXH32_CTX *copy_context)
72
0
{
73
0
  copy_context->s = orig_context->s;
74
0
  return SUCCESS;
75
0
}
76
77
static hash_spec_result php_hash_xxh32_unserialize(
78
    php_hashcontext_object *hash, zend_long magic, const zval *zv)
79
105
{
80
105
  PHP_XXH32_CTX *ctx = (PHP_XXH32_CTX *) hash->context;
81
105
  hash_spec_result r = HASH_SPEC_FAILURE;
82
105
  if (magic == PHP_HASH_SERIALIZE_MAGIC_SPEC
83
75
    && (r = php_hash_unserialize_spec(hash, zv, PHP_XXH32_SPEC)) == HASH_SPEC_SUCCESS
84
68
    && ctx->s.memsize < 16) {
85
64
    return HASH_SPEC_SUCCESS;
86
64
  }
87
88
41
    return r != HASH_SPEC_SUCCESS ? r : CONTEXT_VALIDATION_FAILURE;
89
105
}
90
91
const php_hash_ops php_hash_xxh64_ops = {
92
  "xxh64",
93
  (php_hash_init_func_t) PHP_XXH64Init,
94
  (php_hash_update_func_t) PHP_XXH64Update,
95
  (php_hash_final_func_t) PHP_XXH64Final,
96
  (php_hash_copy_func_t) PHP_XXH64Copy,
97
  php_hash_serialize,
98
  php_hash_xxh64_unserialize,
99
  PHP_XXH64_SPEC,
100
  8,
101
  8,
102
  sizeof(PHP_XXH64_CTX),
103
  0,
104
  0
105
};
106
107
PHP_HASH_API void PHP_XXH64Init(PHP_XXH64_CTX *ctx, HashTable *args)
108
93
{
109
  /* XXH64_createState() is not used intentionally. */
110
93
  memset(&ctx->s, 0, sizeof ctx->s);
111
112
93
  if (args) {
113
0
    zval *seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
114
    /* This might be a bit too restrictive, but thinking that a seed might be set
115
      once and for all, it should be done a clean way. */
116
0
    if (seed) {
117
0
      if (IS_LONG == Z_TYPE_P(seed)) {
118
0
        XXH64_reset(&ctx->s, (XXH64_hash_t)Z_LVAL_P(seed));
119
0
        return;
120
0
      } else {
121
0
        php_error_docref(NULL, E_DEPRECATED, "Passing a seed of a type other than int is deprecated because it is the same as setting the seed to 0");
122
0
      }
123
0
    }
124
0
  }
125
126
93
  XXH64_reset(&ctx->s, 0);
127
93
}
128
129
PHP_HASH_API void PHP_XXH64Update(PHP_XXH64_CTX *ctx, const unsigned char *in, size_t len)
130
72
{
131
72
  XXH64_update(&ctx->s, in, len);
132
72
}
133
134
PHP_HASH_API void PHP_XXH64Final(unsigned char digest[8], PHP_XXH64_CTX *ctx)
135
72
{
136
72
  XXH64_canonicalFromHash((XXH64_canonical_t*)digest, XXH64_digest(&ctx->s));
137
72
}
138
139
PHP_HASH_API zend_result PHP_XXH64Copy(const php_hash_ops *ops, const PHP_XXH64_CTX *orig_context, PHP_XXH64_CTX *copy_context)
140
0
{
141
0
  copy_context->s = orig_context->s;
142
0
  return SUCCESS;
143
0
}
144
145
const php_hash_ops php_hash_xxh3_64_ops = {
146
  "xxh3",
147
  (php_hash_init_func_t) PHP_XXH3_64_Init,
148
  (php_hash_update_func_t) PHP_XXH3_64_Update,
149
  (php_hash_final_func_t) PHP_XXH3_64_Final,
150
  (php_hash_copy_func_t) PHP_XXH3_64_Copy,
151
  php_hash_serialize,
152
  php_hash_unserialize,
153
  NULL,
154
  8,
155
  8,
156
  sizeof(PHP_XXH3_64_CTX),
157
  0,
158
  64
159
};
160
161
typedef XXH_errorcode (*xxh3_reset_with_secret_func_t)(XXH3_state_t*, const void*, size_t);
162
typedef XXH_errorcode (*xxh3_reset_with_seed_func_t)(XXH3_state_t*, XXH64_hash_t);
163
164
static void _PHP_XXH3_Init(PHP_XXH3_64_CTX *ctx, HashTable *args,
165
    xxh3_reset_with_seed_func_t func_init_seed, xxh3_reset_with_secret_func_t func_init_secret, const char* algo_name)
166
0
{
167
0
  memset(&ctx->s, 0, sizeof ctx->s);
168
169
0
  if (args) {
170
0
    zval *_seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
171
0
    zval *_secret = zend_hash_str_find_deref(args, "secret", sizeof("secret") - 1);
172
173
0
    if (_seed && _secret) {
174
0
      zend_throw_error(NULL, "%s: Only one of seed or secret is to be passed for initialization", algo_name);
175
0
      return;
176
0
    }
177
178
0
    if (_seed && IS_LONG != Z_TYPE_P(_seed)) {
179
0
      php_error_docref(NULL, E_DEPRECATED, "Passing a seed of a type other than int is deprecated because it is ignored");
180
0
    }
181
182
0
    if (_seed && IS_LONG == Z_TYPE_P(_seed)) {
183
      /* This might be a bit too restrictive, but thinking that a seed might be set
184
        once and for all, it should be done a clean way. */
185
0
      func_init_seed(&ctx->s, (XXH64_hash_t)Z_LVAL_P(_seed));
186
0
      return;
187
0
    } else if (_secret) {
188
0
      if (IS_STRING != Z_TYPE_P(_secret)) {
189
0
        php_error_docref(NULL, E_DEPRECATED, "Passing a secret of a type other than string is deprecated because it implicitly converts to a string, potentially hiding bugs");
190
0
      }
191
0
      zend_string *secret_string = zval_try_get_string(_secret);
192
0
      if (UNEXPECTED(!secret_string)) {
193
0
        ZEND_ASSERT(EG(exception));
194
0
        return;
195
0
      }
196
0
      size_t len = ZSTR_LEN(secret_string);
197
0
      if (len < PHP_XXH3_SECRET_SIZE_MIN) {
198
0
        zend_string_release(secret_string);
199
0
        zend_throw_error(NULL, "%s: Secret length must be >= %u bytes, %zu bytes passed", algo_name, XXH3_SECRET_SIZE_MIN, len);
200
0
        return;
201
0
      }
202
0
      if (len > sizeof(ctx->secret)) {
203
0
        len = sizeof(ctx->secret);
204
0
        php_error_docref(NULL, E_WARNING, "%s: Secret content exceeding %zu bytes discarded", algo_name, sizeof(ctx->secret));
205
0
      }
206
0
      memcpy((unsigned char *)ctx->secret, ZSTR_VAL(secret_string), len);
207
0
      zend_string_release(secret_string);
208
0
      func_init_secret(&ctx->s, ctx->secret, len);
209
0
      return;
210
0
    }
211
0
  }
212
213
0
  func_init_seed(&ctx->s, 0);
214
0
}
215
216
PHP_HASH_API void PHP_XXH3_64_Init(PHP_XXH3_64_CTX *ctx, HashTable *args)
217
0
{
218
0
  _PHP_XXH3_Init(ctx, args, XXH3_64bits_reset_withSeed, XXH3_64bits_reset_withSecret, "xxh3");
219
0
}
220
221
PHP_HASH_API void PHP_XXH3_64_Update(PHP_XXH3_64_CTX *ctx, const unsigned char *in, size_t len)
222
0
{
223
0
  XXH3_64bits_update(&ctx->s, in, len);
224
0
}
225
226
PHP_HASH_API void PHP_XXH3_64_Final(unsigned char digest[8], PHP_XXH3_64_CTX *ctx)
227
0
{
228
0
  XXH64_canonicalFromHash((XXH64_canonical_t*)digest, XXH3_64bits_digest(&ctx->s));
229
0
}
230
231
PHP_HASH_API zend_result PHP_XXH3_64_Copy(const php_hash_ops *ops, const PHP_XXH3_64_CTX *orig_context, PHP_XXH3_64_CTX *copy_context)
232
0
{
233
0
  copy_context->s = orig_context->s;
234
0
  return SUCCESS;
235
0
}
236
237
static hash_spec_result php_hash_xxh64_unserialize(
238
    php_hashcontext_object *hash, zend_long magic, const zval *zv)
239
93
{
240
93
  PHP_XXH64_CTX *ctx = (PHP_XXH64_CTX *) hash->context;
241
93
  hash_spec_result r = HASH_SPEC_FAILURE;
242
93
  if (magic == PHP_HASH_SERIALIZE_MAGIC_SPEC
243
77
    && (r = php_hash_unserialize_spec(hash, zv, PHP_XXH64_SPEC)) == HASH_SPEC_SUCCESS
244
73
    && ctx->s.memsize < 32) {
245
72
    return HASH_SPEC_SUCCESS;
246
72
  }
247
248
21
    return r != HASH_SPEC_SUCCESS ? r : CONTEXT_VALIDATION_FAILURE;
249
93
}
250
251
const php_hash_ops php_hash_xxh3_128_ops = {
252
  "xxh128",
253
  (php_hash_init_func_t) PHP_XXH3_128_Init,
254
  (php_hash_update_func_t) PHP_XXH3_128_Update,
255
  (php_hash_final_func_t) PHP_XXH3_128_Final,
256
  (php_hash_copy_func_t) PHP_XXH3_128_Copy,
257
  php_hash_serialize,
258
  php_hash_unserialize,
259
  NULL,
260
  16,
261
  8,
262
  sizeof(PHP_XXH3_128_CTX),
263
  0,
264
  64
265
};
266
267
PHP_HASH_API void PHP_XXH3_128_Init(PHP_XXH3_128_CTX *ctx, HashTable *args)
268
0
{
269
0
  _PHP_XXH3_Init(ctx, args, XXH3_128bits_reset_withSeed, XXH3_128bits_reset_withSecret, "xxh128");
270
0
}
271
272
PHP_HASH_API void PHP_XXH3_128_Update(PHP_XXH3_128_CTX *ctx, const unsigned char *in, size_t len)
273
0
{
274
0
  XXH3_128bits_update(&ctx->s, in, len);
275
0
}
276
277
PHP_HASH_API void PHP_XXH3_128_Final(unsigned char digest[16], PHP_XXH3_128_CTX *ctx)
278
0
{
279
0
  XXH128_canonicalFromHash((XXH128_canonical_t*)digest, XXH3_128bits_digest(&ctx->s));
280
0
}
281
282
PHP_HASH_API zend_result PHP_XXH3_128_Copy(const php_hash_ops *ops, const PHP_XXH3_128_CTX *orig_context, PHP_XXH3_128_CTX *copy_context)
283
0
{
284
0
  copy_context->s = orig_context->s;
285
0
  return SUCCESS;
286
0
}
287