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