Skip to content

Commit 23590f7

Browse files
committed
hash: Implement xxHash
The implementation bundles the xxHash v0.8.0 release and includes all the variants - xxh32, 32-bit wide - xxh64, 64-bit wide - xxh3, 64-bit wide - xxh128, 128-bit wide An initial hash state can be passed through the options arrray. An additional functionality not targeted in this implementation is the secret support in xxh3 and xxh128. That can be added at a later point. The serialization for xxh3 and xxh128 should not be implemented, as the state would contain the secret. Despite the xxHash is a non crypto algorithm, the secret would be serialized as plain text which would be insecure. Closes GH-6524 Signed-off-by: Anatol Belski <ab@php.net>
1 parent c139122 commit 23590f7

13 files changed

+5213
-7
lines changed

ext/hash/config.m4

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,17 @@ else
3434
fi
3535

3636
PHP_ADD_BUILD_DIR(ext/hash/murmur, 1)
37+
PHP_HASH_CFLAGS="$PHP_HASH_CFLAGS -I@ext_srcdir@/xxhash"
3738

3839
EXT_HASH_SOURCES="hash.c hash_md.c hash_sha.c hash_ripemd.c hash_haval.c \
3940
hash_tiger.c hash_gost.c hash_snefru.c hash_whirlpool.c hash_adler32.c \
4041
hash_crc32.c hash_fnv.c hash_joaat.c $EXT_HASH_SHA3_SOURCES
41-
murmur/PMurHash.c murmur/PMurHash128.c hash_murmur.c"
42+
murmur/PMurHash.c murmur/PMurHash128.c hash_murmur.c hash_xxhash.c"
4243
EXT_HASH_HEADERS="php_hash.h php_hash_md.h php_hash_sha.h php_hash_ripemd.h \
4344
php_hash_haval.h php_hash_tiger.h php_hash_gost.h php_hash_snefru.h \
4445
php_hash_whirlpool.h php_hash_adler32.h php_hash_crc32.h \
45-
php_hash_fnv.h php_hash_joaat.h php_hash_sha3.h php_hash_murmur.h"
46+
php_hash_fnv.h php_hash_joaat.h php_hash_sha3.h php_hash_murmur.h \
47+
php_hash_xxhash.h"
4648

4749
PHP_NEW_EXTENSION(hash, $EXT_HASH_SOURCES, 0,,$PHP_HASH_CFLAGS)
4850
PHP_INSTALL_HEADERS(ext/hash, $EXT_HASH_HEADERS)

ext/hash/config.w32

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ PHP_HASH = 'yes';
1111
EXTENSION('hash', 'hash.c hash_md.c hash_sha.c hash_ripemd.c hash_haval.c ' +
1212
'hash_tiger.c hash_gost.c hash_snefru.c hash_whirlpool.c ' +
1313
'hash_adler32.c hash_crc32.c hash_joaat.c hash_fnv.c ' +
14-
'hash_sha3.c hash_murmur.c', false);
14+
'hash_sha3.c hash_murmur.c hash_xxhash.c', false);
1515

1616
var hash_sha3_dir = 'ext/hash/sha3/generic' + (X64 ? '64' : '32') + 'lc';
1717

@@ -34,8 +34,13 @@ if (!CHECK_HEADER_ADD_INCLUDE('PMurHash.h', 'CFLAGS_HASH', hash_murmur_dir)) {
3434
}
3535
ADD_SOURCES(hash_murmur_dir, 'PMurHash.c PMurHash128.c', 'hash');
3636

37+
var hash_xxhash_dir = 'ext/hash/xxhash';
38+
if (!CHECK_HEADER_ADD_INCLUDE('xxhash.h', 'CFLAGS_HASH', hash_xxhash_dir)) {
39+
ERROR('Unable to locate xxhash headers');
40+
}
41+
3742
PHP_INSTALL_HEADERS('ext/hash/', 'php_hash.h php_hash_md.h php_hash_sha.h ' +
3843
'php_hash_ripemd.h php_hash_haval.h php_hash_tiger.h ' +
3944
'php_hash_gost.h php_hash_snefru.h php_hash_whirlpool.h ' +
4045
'php_hash_adler32.h php_hash_crc32.h php_hash_sha3.h ' +
41-
'php_hash_murmur.h');
46+
'php_hash_murmur.h php_hash_xxhash.h');

ext/hash/hash.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ struct mhash_bc_entry {
5252
int value;
5353
};
5454

55-
#define MHASH_NUM_ALGOS 38
55+
#define MHASH_NUM_ALGOS 42
5656

5757
static struct mhash_bc_entry mhash_to_hash[MHASH_NUM_ALGOS] = {
5858
{"CRC32", "crc32", 0}, /* used by bzip */
@@ -93,6 +93,10 @@ static struct mhash_bc_entry mhash_to_hash[MHASH_NUM_ALGOS] = {
9393
{"MURMUR3A", "murmur3a", 35},
9494
{"MURMUR3C", "murmur3c", 36},
9595
{"MURMUR3F", "murmur3f", 37},
96+
{"XXH32", "xxh32", 38},
97+
{"XXH64", "xxh64", 39},
98+
{"XXH3", "xxh3", 40},
99+
{"XXH128", "xxh128", 41},
96100
};
97101
#endif
98102

@@ -1598,6 +1602,10 @@ PHP_MINIT_FUNCTION(hash)
15981602
php_hash_register_algo("murmur3a", &php_hash_murmur3a_ops);
15991603
php_hash_register_algo("murmur3c", &php_hash_murmur3c_ops);
16001604
php_hash_register_algo("murmur3f", &php_hash_murmur3f_ops);
1605+
php_hash_register_algo("xxh32", &php_hash_xxh32_ops);
1606+
php_hash_register_algo("xxh64", &php_hash_xxh64_ops);
1607+
php_hash_register_algo("xxh3", &php_hash_xxh3_64_ops);
1608+
php_hash_register_algo("xxh128", &php_hash_xxh3_128_ops);
16011609

16021610
PHP_HASH_HAVAL_REGISTER(3,128);
16031611
PHP_HASH_HAVAL_REGISTER(3,160);

ext/hash/hash_xxhash.c

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
| http://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+
const php_hash_ops php_hash_xxh32_ops = {
21+
"xxh32",
22+
(php_hash_init_func_t) PHP_XXH32Init,
23+
(php_hash_update_func_t) PHP_XXH32Update,
24+
(php_hash_final_func_t) PHP_XXH32Final,
25+
(php_hash_copy_func_t) PHP_XXH32Copy,
26+
php_hash_serialize,
27+
php_hash_unserialize,
28+
PHP_XXH32_SPEC,
29+
4,
30+
4,
31+
sizeof(PHP_XXH32_CTX),
32+
0
33+
};
34+
35+
PHP_HASH_API void PHP_XXH32Init(PHP_XXH32_CTX *ctx, HashTable *args)
36+
{
37+
/* XXH32_createState() is not used intentionally. */
38+
memset(&ctx->s, 0, sizeof ctx->s);
39+
40+
if (args) {
41+
zval *seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
42+
/* This might be a bit too restrictive, but thinking that a seed might be set
43+
once and for all, it should be done a clean way. */
44+
if (seed && IS_LONG == Z_TYPE_P(seed)) {
45+
XXH32_reset(&ctx->s, (XXH32_hash_t)Z_LVAL_P(seed));
46+
} else {
47+
XXH32_reset(&ctx->s, 0);
48+
}
49+
} else {
50+
XXH32_reset(&ctx->s, 0);
51+
}
52+
}
53+
54+
PHP_HASH_API void PHP_XXH32Update(PHP_XXH32_CTX *ctx, const unsigned char *in, size_t len)
55+
{
56+
XXH32_update(&ctx->s, in, len);
57+
}
58+
59+
PHP_HASH_API void PHP_XXH32Final(unsigned char digest[4], PHP_XXH32_CTX *ctx)
60+
{
61+
XXH32_hash_t const h = XXH32_digest(&ctx->s);
62+
63+
digest[0] = (unsigned char)((h >> 24) & 0xff);
64+
digest[1] = (unsigned char)((h >> 16) & 0xff);
65+
digest[2] = (unsigned char)((h >> 8) & 0xff);
66+
digest[3] = (unsigned char)(h & 0xff);
67+
}
68+
69+
PHP_HASH_API int PHP_XXH32Copy(const php_hash_ops *ops, PHP_XXH32_CTX *orig_context, PHP_XXH32_CTX *copy_context)
70+
{
71+
copy_context->s = orig_context->s;
72+
return SUCCESS;
73+
}
74+
75+
const php_hash_ops php_hash_xxh64_ops = {
76+
"xxh64",
77+
(php_hash_init_func_t) PHP_XXH64Init,
78+
(php_hash_update_func_t) PHP_XXH64Update,
79+
(php_hash_final_func_t) PHP_XXH64Final,
80+
(php_hash_copy_func_t) PHP_XXH64Copy,
81+
php_hash_serialize,
82+
php_hash_unserialize,
83+
PHP_XXH64_SPEC,
84+
8,
85+
8,
86+
sizeof(PHP_XXH64_CTX),
87+
0
88+
};
89+
90+
PHP_HASH_API void PHP_XXH64Init(PHP_XXH64_CTX *ctx, HashTable *args)
91+
{
92+
/* XXH64_createState() is not used intentionally. */
93+
memset(&ctx->s, 0, sizeof ctx->s);
94+
95+
if (args) {
96+
zval *seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
97+
/* This might be a bit too restrictive, but thinking that a seed might be set
98+
once and for all, it should be done a clean way. */
99+
if (seed && IS_LONG == Z_TYPE_P(seed)) {
100+
XXH64_reset(&ctx->s, (XXH64_hash_t)Z_LVAL_P(seed));
101+
} else {
102+
XXH64_reset(&ctx->s, 0);
103+
}
104+
} else {
105+
XXH64_reset(&ctx->s, 0);
106+
}
107+
}
108+
109+
PHP_HASH_API void PHP_XXH64Update(PHP_XXH64_CTX *ctx, const unsigned char *in, size_t len)
110+
{
111+
XXH64_update(&ctx->s, in, len);
112+
}
113+
114+
PHP_HASH_API void PHP_XXH64Final(unsigned char digest[8], PHP_XXH64_CTX *ctx)
115+
{
116+
XXH64_hash_t const h = XXH64_digest(&ctx->s);
117+
118+
digest[0] = (unsigned char)((h >> 56) & 0xff);
119+
digest[1] = (unsigned char)((h >> 48) & 0xff);
120+
digest[2] = (unsigned char)((h >> 40) & 0xff);
121+
digest[3] = (unsigned char)((h >> 32) & 0xff);
122+
digest[4] = (unsigned char)((h >> 24) & 0xff);
123+
digest[5] = (unsigned char)((h >> 16) & 0xff);
124+
digest[6] = (unsigned char)((h >> 8) & 0xff);
125+
digest[7] = (unsigned char)(h & 0xff);
126+
}
127+
128+
PHP_HASH_API int PHP_XXH64Copy(const php_hash_ops *ops, PHP_XXH64_CTX *orig_context, PHP_XXH64_CTX *copy_context)
129+
{
130+
copy_context->s = orig_context->s;
131+
return SUCCESS;
132+
}
133+
134+
const php_hash_ops php_hash_xxh3_64_ops = {
135+
"xxh3",
136+
(php_hash_init_func_t) PHP_XXH3_64_Init,
137+
(php_hash_update_func_t) PHP_XXH3_64_Update,
138+
(php_hash_final_func_t) PHP_XXH3_64_Final,
139+
(php_hash_copy_func_t) PHP_XXH3_64_Copy,
140+
php_hash_serialize,
141+
php_hash_unserialize,
142+
NULL,
143+
8,
144+
8,
145+
sizeof(PHP_XXH3_64_CTX),
146+
0
147+
};
148+
149+
PHP_HASH_API void PHP_XXH3_64_Init(PHP_XXH3_64_CTX *ctx, HashTable *args)
150+
{
151+
/* TODO integrate also XXH3_64bits_reset_withSecret(). */
152+
XXH64_hash_t seed = 0;
153+
154+
if (args) {
155+
zval *_seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
156+
/* This might be a bit too restrictive, but thinking that a seed might be set
157+
once and for all, it should be done a clean way. */
158+
if (_seed && IS_LONG == Z_TYPE_P(_seed)) {
159+
seed = (XXH64_hash_t)Z_LVAL_P(_seed);
160+
}
161+
}
162+
163+
XXH3_64bits_reset_withSeed(&ctx->s, seed);
164+
}
165+
166+
PHP_HASH_API void PHP_XXH3_64_Update(PHP_XXH3_64_CTX *ctx, const unsigned char *in, size_t len)
167+
{
168+
XXH3_64bits_update(&ctx->s, in, len);
169+
}
170+
171+
PHP_HASH_API void PHP_XXH3_64_Final(unsigned char digest[8], PHP_XXH3_64_CTX *ctx)
172+
{
173+
XXH64_hash_t const h = XXH3_64bits_digest(&ctx->s);
174+
175+
digest[0] = (unsigned char)((h >> 56) & 0xff);
176+
digest[1] = (unsigned char)((h >> 48) & 0xff);
177+
digest[2] = (unsigned char)((h >> 40) & 0xff);
178+
digest[3] = (unsigned char)((h >> 32) & 0xff);
179+
digest[4] = (unsigned char)((h >> 24) & 0xff);
180+
digest[5] = (unsigned char)((h >> 16) & 0xff);
181+
digest[6] = (unsigned char)((h >> 8) & 0xff);
182+
digest[7] = (unsigned char)(h & 0xff);
183+
}
184+
185+
PHP_HASH_API int PHP_XXH3_64_Copy(const php_hash_ops *ops, PHP_XXH3_64_CTX *orig_context, PHP_XXH3_64_CTX *copy_context)
186+
{
187+
copy_context->s = orig_context->s;
188+
return SUCCESS;
189+
}
190+
191+
const php_hash_ops php_hash_xxh3_128_ops = {
192+
"xxh128",
193+
(php_hash_init_func_t) PHP_XXH3_128_Init,
194+
(php_hash_update_func_t) PHP_XXH3_128_Update,
195+
(php_hash_final_func_t) PHP_XXH3_128_Final,
196+
(php_hash_copy_func_t) PHP_XXH3_128_Copy,
197+
php_hash_serialize,
198+
php_hash_unserialize,
199+
NULL,
200+
16,
201+
8,
202+
sizeof(PHP_XXH3_128_CTX),
203+
0
204+
};
205+
206+
PHP_HASH_API void PHP_XXH3_128_Init(PHP_XXH3_128_CTX *ctx, HashTable *args)
207+
{
208+
/* TODO integrate also XXH3_128__64bits_reset_withSecret(). */
209+
XXH64_hash_t seed = 0;
210+
211+
if (args) {
212+
zval *_seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
213+
/* This might be a bit too restrictive, but thinking that a seed might be set
214+
once and for all, it should be done a clean way. */
215+
if (_seed && IS_LONG == Z_TYPE_P(_seed)) {
216+
seed = (XXH64_hash_t)Z_LVAL_P(_seed);
217+
}
218+
}
219+
220+
XXH3_128bits_reset_withSeed(&ctx->s, seed);
221+
}
222+
223+
PHP_HASH_API void PHP_XXH3_128_Update(PHP_XXH3_128_CTX *ctx, const unsigned char *in, size_t len)
224+
{
225+
XXH3_128bits_update(&ctx->s, in, len);
226+
}
227+
228+
PHP_HASH_API void PHP_XXH3_128_Final(unsigned char digest[16], PHP_XXH3_128_CTX *ctx)
229+
{
230+
XXH128_hash_t const h = XXH3_128bits_digest(&ctx->s);
231+
232+
digest[0] = (unsigned char)((h.high64 >> 56) & 0xff);
233+
digest[1] = (unsigned char)((h.high64 >> 48) & 0xff);
234+
digest[2] = (unsigned char)((h.high64 >> 40) & 0xff);
235+
digest[3] = (unsigned char)((h.high64 >> 32) & 0xff);
236+
digest[4] = (unsigned char)((h.high64 >> 24) & 0xff);
237+
digest[5] = (unsigned char)((h.high64 >> 16) & 0xff);
238+
digest[6] = (unsigned char)((h.high64 >> 8) & 0xff);
239+
digest[7] = (unsigned char)(h.high64 & 0xff);
240+
digest[8] = (unsigned char)((h.low64 >> 56) & 0xff);
241+
digest[9] = (unsigned char)((h.low64 >> 48) & 0xff);
242+
digest[10] = (unsigned char)((h.low64 >> 40) & 0xff);
243+
digest[11] = (unsigned char)((h.low64 >> 32) & 0xff);
244+
digest[12] = (unsigned char)((h.low64 >> 24) & 0xff);
245+
digest[13] = (unsigned char)((h.low64 >> 16) & 0xff);
246+
digest[14] = (unsigned char)((h.low64 >> 8) & 0xff);
247+
digest[15] = (unsigned char)(h.low64 & 0xff);
248+
}
249+
250+
PHP_HASH_API int PHP_XXH3_128_Copy(const php_hash_ops *ops, PHP_XXH3_128_CTX *orig_context, PHP_XXH3_128_CTX *copy_context)
251+
{
252+
copy_context->s = orig_context->s;
253+
return SUCCESS;
254+
}
255+

ext/hash/php_hash.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ extern const php_hash_ops php_hash_joaat_ops;
108108
extern const php_hash_ops php_hash_murmur3a_ops;
109109
extern const php_hash_ops php_hash_murmur3c_ops;
110110
extern const php_hash_ops php_hash_murmur3f_ops;
111+
extern const php_hash_ops php_hash_xxh32_ops;
112+
extern const php_hash_ops php_hash_xxh64_ops;
113+
extern const php_hash_ops php_hash_xxh3_64_ops;
114+
extern const php_hash_ops php_hash_xxh3_128_ops;
111115

112116
#define PHP_HASH_HAVAL_OPS(p,b) extern const php_hash_ops php_hash_##p##haval##b##_ops;
113117

0 commit comments

Comments
 (0)