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: Go Kudo <zeriyoshi@php.net> |
14 | |
15 | Based on code from: David Blackman |
16 | Sebastiano Vigna <vigna@acm.org> |
17 +----------------------------------------------------------------------+
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include "php.h"
25 #include "php_random.h"
26 #include "php_random_csprng.h"
27
28 #include "Zend/zend_exceptions.h"
29
splitmix64(uint64_t * seed)30 static inline uint64_t splitmix64(uint64_t *seed)
31 {
32 uint64_t r;
33
34 r = (*seed += 0x9e3779b97f4a7c15ULL);
35 r = (r ^ (r >> 30)) * 0xbf58476d1ce4e5b9ULL;
36 r = (r ^ (r >> 27)) * 0x94d049bb133111ebULL;
37 return (r ^ (r >> 31));
38 }
39
rotl(const uint64_t x,int k)40 ZEND_ATTRIBUTE_CONST static inline uint64_t rotl(const uint64_t x, int k)
41 {
42 return (x << k) | (x >> (64 - k));
43 }
44
generate_state(php_random_status_state_xoshiro256starstar * s)45 static inline uint64_t generate_state(php_random_status_state_xoshiro256starstar *s)
46 {
47 const uint64_t r = rotl(s->state[1] * 5, 7) * 9;
48 const uint64_t t = s->state[1] << 17;
49
50 s->state[2] ^= s->state[0];
51 s->state[3] ^= s->state[1];
52 s->state[1] ^= s->state[2];
53 s->state[0] ^= s->state[3];
54
55 s->state[2] ^= t;
56
57 s->state[3] = rotl(s->state[3], 45);
58
59 return r;
60 }
61
jump(php_random_status_state_xoshiro256starstar * state,const uint64_t * jmp)62 static inline void jump(php_random_status_state_xoshiro256starstar *state, const uint64_t *jmp)
63 {
64 uint64_t s0 = 0, s1 = 0, s2 = 0, s3 = 0;
65
66 for (uint32_t i = 0; i < 4; i++) {
67 for (uint32_t j = 0; j < 64; j++) {
68 if (jmp[i] & 1ULL << j) {
69 s0 ^= state->state[0];
70 s1 ^= state->state[1];
71 s2 ^= state->state[2];
72 s3 ^= state->state[3];
73 }
74
75 generate_state(state);
76 }
77 }
78
79 state->state[0] = s0;
80 state->state[1] = s1;
81 state->state[2] = s2;
82 state->state[3] = s3;
83 }
84
php_random_xoshiro256starstar_seed256(php_random_status_state_xoshiro256starstar * state,uint64_t s0,uint64_t s1,uint64_t s2,uint64_t s3)85 PHPAPI inline void php_random_xoshiro256starstar_seed256(php_random_status_state_xoshiro256starstar *state, uint64_t s0, uint64_t s1, uint64_t s2, uint64_t s3)
86 {
87 state->state[0] = s0;
88 state->state[1] = s1;
89 state->state[2] = s2;
90 state->state[3] = s3;
91 }
92
php_random_xoshiro256starstar_seed64(php_random_status_state_xoshiro256starstar * state,uint64_t seed)93 PHPAPI inline void php_random_xoshiro256starstar_seed64(php_random_status_state_xoshiro256starstar *state, uint64_t seed)
94 {
95 uint64_t s[4];
96
97 s[0] = splitmix64(&seed);
98 s[1] = splitmix64(&seed);
99 s[2] = splitmix64(&seed);
100 s[3] = splitmix64(&seed);
101
102 php_random_xoshiro256starstar_seed256(state, s[0], s[1], s[2], s[3]);
103 }
104
generate(void * state)105 static php_random_result generate(void *state)
106 {
107 return (php_random_result){
108 .size = sizeof(uint64_t),
109 .result = generate_state(state),
110 };
111 }
112
range(void * state,zend_long min,zend_long max)113 static zend_long range(void *state, zend_long min, zend_long max)
114 {
115 return php_random_range((php_random_algo_with_state){
116 .algo = &php_random_algo_xoshiro256starstar,
117 .state = state,
118 }, min, max);
119 }
120
serialize(void * state,HashTable * data)121 static bool serialize(void *state, HashTable *data)
122 {
123 php_random_status_state_xoshiro256starstar *s = state;
124 zval t;
125
126 for (uint32_t i = 0; i < 4; i++) {
127 ZVAL_STR(&t, php_random_bin2hex_le(&s->state[i], sizeof(uint64_t)));
128 zend_hash_next_index_insert(data, &t);
129 }
130
131 return true;
132 }
133
unserialize(void * state,HashTable * data)134 static bool unserialize(void *state, HashTable *data)
135 {
136 php_random_status_state_xoshiro256starstar *s = state;
137 zval *t;
138
139 /* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
140 if (zend_hash_num_elements(data) != 4) {
141 return false;
142 }
143
144 for (uint32_t i = 0; i < 4; i++) {
145 t = zend_hash_index_find(data, i);
146 if (!t || Z_TYPE_P(t) != IS_STRING || Z_STRLEN_P(t) != (2 * sizeof(uint64_t))) {
147 return false;
148 }
149 if (!php_random_hex2bin_le(Z_STR_P(t), &s->state[i])) {
150 return false;
151 }
152 }
153
154 return true;
155 }
156
157 PHPAPI const php_random_algo php_random_algo_xoshiro256starstar = {
158 sizeof(php_random_status_state_xoshiro256starstar),
159 generate,
160 range,
161 serialize,
162 unserialize
163 };
164
php_random_xoshiro256starstar_jump(php_random_status_state_xoshiro256starstar * state)165 PHPAPI void php_random_xoshiro256starstar_jump(php_random_status_state_xoshiro256starstar *state)
166 {
167 static const uint64_t jmp[] = {0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c};
168 jump(state, jmp);
169 }
170
php_random_xoshiro256starstar_jump_long(php_random_status_state_xoshiro256starstar * state)171 PHPAPI void php_random_xoshiro256starstar_jump_long(php_random_status_state_xoshiro256starstar *state)
172 {
173 static const uint64_t jmp[] = {0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635};
174 jump(state, jmp);
175 }
176
177 /* {{{ Random\Engine\Xoshiro256StarStar::jump() */
PHP_METHOD(Random_Engine_Xoshiro256StarStar,jump)178 PHP_METHOD(Random_Engine_Xoshiro256StarStar, jump)
179 {
180 php_random_algo_with_state engine = Z_RANDOM_ENGINE_P(ZEND_THIS)->engine;
181 php_random_status_state_xoshiro256starstar *state = engine.state;
182
183 ZEND_PARSE_PARAMETERS_NONE();
184
185 php_random_xoshiro256starstar_jump(state);
186 }
187 /* }}} */
188
189 /* {{{ Random\Engine\Xoshiro256StarStar::jumpLong() */
PHP_METHOD(Random_Engine_Xoshiro256StarStar,jumpLong)190 PHP_METHOD(Random_Engine_Xoshiro256StarStar, jumpLong)
191 {
192 php_random_algo_with_state engine = Z_RANDOM_ENGINE_P(ZEND_THIS)->engine;
193 php_random_status_state_xoshiro256starstar *state = engine.state;
194
195 ZEND_PARSE_PARAMETERS_NONE();
196
197 php_random_xoshiro256starstar_jump_long(state);
198 }
199 /* }}} */
200
201 /* {{{ Random\Engine\Xoshiro256StarStar::__construct */
PHP_METHOD(Random_Engine_Xoshiro256StarStar,__construct)202 PHP_METHOD(Random_Engine_Xoshiro256StarStar, __construct)
203 {
204 php_random_algo_with_state engine = Z_RANDOM_ENGINE_P(ZEND_THIS)->engine;
205 php_random_status_state_xoshiro256starstar *state = engine.state;
206 zend_string *str_seed = NULL;
207 zend_long int_seed = 0;
208 bool seed_is_null = true;
209
210 ZEND_PARSE_PARAMETERS_START(0, 1)
211 Z_PARAM_OPTIONAL;
212 Z_PARAM_STR_OR_LONG_OR_NULL(str_seed, int_seed, seed_is_null);
213 ZEND_PARSE_PARAMETERS_END();
214
215 if (seed_is_null) {
216 uint64_t t[4];
217
218 do {
219 if (php_random_bytes_throw(&t, sizeof(t)) == FAILURE) {
220 zend_throw_exception(random_ce_Random_RandomException, "Failed to generate a random seed", 0);
221 RETURN_THROWS();
222 }
223 } while (UNEXPECTED(t[0] == 0 && t[1] == 0 && t[2] == 0 && t[3] == 0));
224
225 php_random_xoshiro256starstar_seed256(state, t[0], t[1], t[2], t[3]);
226 } else {
227 if (str_seed) {
228 /* char (byte: 8 bit) * 32 = 256 bits */
229 if (ZSTR_LEN(str_seed) == 32) {
230 uint64_t t[4];
231
232 /* Endianness safe copy */
233 for (uint32_t i = 0; i < 4; i++) {
234 t[i] = 0;
235 for (uint32_t j = 0; j < 8; j++) {
236 t[i] += ((uint64_t) (unsigned char) ZSTR_VAL(str_seed)[(i * 8) + j]) << (j * 8);
237 }
238 }
239
240 if (UNEXPECTED(t[0] == 0 && t[1] == 0 && t[2] == 0 && t[3] == 0)) {
241 zend_argument_value_error(1, "must not consist entirely of NUL bytes");
242 RETURN_THROWS();
243 }
244
245 php_random_xoshiro256starstar_seed256(state, t[0], t[1], t[2], t[3]);
246 } else {
247 zend_argument_value_error(1, "must be a 32 byte (256 bit) string");
248 RETURN_THROWS();
249 }
250 } else {
251 php_random_xoshiro256starstar_seed64(state, (uint64_t) int_seed);
252 }
253 }
254 }
255 /* }}} */
256