/src/php-src/ext/random/engine_mt19937.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 | | | Authors: Rasmus Lerdorf <rasmus@php.net> | |
12 | | | Zeev Suraski <zeev@php.net> | |
13 | | | Pedro Melo <melo@ip.pt> | |
14 | | | Sterling Hughes <sterling@php.net> | |
15 | | | Go Kudo <zeriyoshi@php.net> | |
16 | | | | |
17 | | | Based on code from: Richard J. Wagner <rjwagner@writeme.com> | |
18 | | | Makoto Matsumoto <matumoto@math.keio.ac.jp> | |
19 | | | Takuji Nishimura | |
20 | | | Shawn Cokus <Cokus@math.washington.edu> | |
21 | | +----------------------------------------------------------------------+ |
22 | | */ |
23 | | |
24 | | #ifdef HAVE_CONFIG_H |
25 | | # include "config.h" |
26 | | #endif |
27 | | |
28 | | #include "php.h" |
29 | | #include "php_random.h" |
30 | | #include "php_random_csprng.h" |
31 | | |
32 | | #include "Zend/zend_exceptions.h" |
33 | | |
34 | | /* |
35 | | The following mt19937 algorithms are based on a C++ class MTRand by |
36 | | Richard J. Wagner. For more information see the web page at |
37 | | http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/C-LANG/MersenneTwister.h |
38 | | |
39 | | Mersenne Twister random number generator -- a C++ class MTRand |
40 | | Based on code by Makoto Matsumoto, Takuji Nishimura, and Shawn Cokus |
41 | | Richard J. Wagner v1.0 15 May 2003 rjwagner@writeme.com |
42 | | |
43 | | The Mersenne Twister is an algorithm for generating random numbers. It |
44 | | was designed with consideration of the flaws in various other generators. |
45 | | The period, 2^19937-1, and the order of equidistribution, 623 dimensions, |
46 | | are far greater. The generator is also fast; it avoids multiplication and |
47 | | division, and it benefits from caches and pipelines. For more information |
48 | | see the inventors' web page at http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html |
49 | | |
50 | | Reference |
51 | | M. Matsumoto and T. Nishimura, "Mersenne Twister: A 623-Dimensionally |
52 | | Equidistributed Uniform Pseudo-Random Number Generator", ACM Transactions on |
53 | | Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3-30. |
54 | | |
55 | | Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, |
56 | | Copyright (C) 2000 - 2003, Richard J. Wagner |
57 | | All rights reserved. |
58 | | |
59 | | Redistribution and use in source and binary forms, with or without |
60 | | modification, are permitted provided that the following conditions |
61 | | are met: |
62 | | |
63 | | 1. Redistributions of source code must retain the above copyright |
64 | | notice, this list of conditions and the following disclaimer. |
65 | | |
66 | | 2. Redistributions in binary form must reproduce the above copyright |
67 | | notice, this list of conditions and the following disclaimer in the |
68 | | documentation and/or other materials provided with the distribution. |
69 | | |
70 | | 3. The names of its contributors may not be used to endorse or promote |
71 | | products derived from this software without specific prior written |
72 | | permission. |
73 | | |
74 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
75 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
76 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
77 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
78 | | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
79 | | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
80 | | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
81 | | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
82 | | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
83 | | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
84 | | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
85 | | */ |
86 | | |
87 | 13.4k | #define N 624 /* length of state vector */ |
88 | | ZEND_STATIC_ASSERT( |
89 | | N == sizeof(((php_random_status_state_mt19937*)0)->state) / sizeof(((php_random_status_state_mt19937*)0)->state[0]), |
90 | | "Assumed length of Mt19937 state vector does not match actual size." |
91 | | ); |
92 | 40 | #define M (397) /* a period parameter */ |
93 | 12.4k | #define hiBit(u) ((u) & 0x80000000U) /* mask all but highest bit of u */ |
94 | 12.4k | #define loBit(u) ((u) & 0x00000001U) /* mask all but lowest bit of u */ |
95 | 12.4k | #define loBits(u) ((u) & 0x7FFFFFFFU) /* mask the highest bit of u */ |
96 | 12.4k | #define mixBits(u, v) (hiBit(u) | loBits(v)) /* move hi bit of u to hi bit of v */ |
97 | | |
98 | 12.4k | #define twist(m,u,v) (m ^ (mixBits(u,v) >> 1) ^ ((uint32_t)(-(int32_t)(loBit(v))) & 0x9908b0dfU)) |
99 | 0 | #define twist_php(m,u,v) (m ^ (mixBits(u,v) >> 1) ^ ((uint32_t)(-(int32_t)(loBit(u))) & 0x9908b0dfU)) |
100 | | |
101 | | static inline void mt19937_reload(php_random_status_state_mt19937 *state) |
102 | 20 | { |
103 | 20 | uint32_t *p = state->state; |
104 | | |
105 | 20 | if (state->mode == MT_RAND_MT19937) { |
106 | 4.56k | for (uint32_t i = N - M; i--; ++p) { |
107 | 4.54k | *p = twist(p[M], p[0], p[1]); |
108 | 4.54k | } |
109 | 7.94k | for (uint32_t i = M; --i; ++p) { |
110 | 7.92k | *p = twist(p[M-N], p[0], p[1]); |
111 | 7.92k | } |
112 | 20 | *p = twist(p[M-N], p[0], state->state[0]); |
113 | 20 | } else { |
114 | 0 | for (uint32_t i = N - M; i--; ++p) { |
115 | 0 | *p = twist_php(p[M], p[0], p[1]); |
116 | 0 | } |
117 | 0 | for (uint32_t i = M; --i; ++p) { |
118 | 0 | *p = twist_php(p[M-N], p[0], p[1]); |
119 | 0 | } |
120 | 0 | *p = twist_php(p[M-N], p[0], state->state[0]); |
121 | 0 | } |
122 | | |
123 | 20 | state->count = 0; |
124 | 20 | } |
125 | | |
126 | | PHPAPI inline void php_random_mt19937_seed32(php_random_status_state_mt19937 *state, uint32_t seed) |
127 | 20 | { |
128 | 20 | uint32_t i, prev_state; |
129 | | |
130 | | /* Initialize generator state with seed |
131 | | See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier. |
132 | | In previous versions, most significant bits (MSBs) of the seed affect |
133 | | only MSBs of the state array. Modified 9 Jan 2002 by Makoto Matsumoto. */ |
134 | 20 | state->state[0] = seed; |
135 | 12.4k | for (i = 1; i < N; i++) { |
136 | 12.4k | prev_state = state->state[i - 1]; |
137 | 12.4k | state->state[i] = (1812433253U * (prev_state ^ (prev_state >> 30)) + i) & 0xffffffffU; |
138 | 12.4k | } |
139 | 20 | state->count = i; |
140 | | |
141 | 20 | mt19937_reload(state); |
142 | 20 | } |
143 | | |
144 | | static php_random_result generate(void *state) |
145 | 951 | { |
146 | 951 | php_random_status_state_mt19937 *s = state; |
147 | 951 | uint32_t s1; |
148 | | |
149 | 951 | if (s->count >= N) { |
150 | 0 | mt19937_reload(s); |
151 | 0 | } |
152 | | |
153 | 951 | s1 = s->state[s->count++]; |
154 | 951 | s1 ^= (s1 >> 11); |
155 | 951 | s1 ^= (s1 << 7) & 0x9d2c5680U; |
156 | 951 | s1 ^= (s1 << 15) & 0xefc60000U; |
157 | | |
158 | 951 | return (php_random_result){ |
159 | 951 | .size = sizeof(uint32_t), |
160 | 951 | .result = (uint64_t) (s1 ^ (s1 >> 18)), |
161 | 951 | }; |
162 | 951 | } |
163 | | |
164 | | static zend_long range(void *state, zend_long min, zend_long max) |
165 | 951 | { |
166 | 951 | return php_random_range((php_random_algo_with_state){ |
167 | 951 | .algo = &php_random_algo_mt19937, |
168 | 951 | .state = state, |
169 | 951 | }, min, max); |
170 | 951 | } |
171 | | |
172 | | static bool serialize(void *state, HashTable *data) |
173 | 0 | { |
174 | 0 | php_random_status_state_mt19937 *s = state; |
175 | 0 | zval t; |
176 | |
|
177 | 0 | for (uint32_t i = 0; i < N; i++) { |
178 | 0 | ZVAL_STR(&t, php_random_bin2hex_le(&s->state[i], sizeof(uint32_t))); |
179 | 0 | zend_hash_next_index_insert(data, &t); |
180 | 0 | } |
181 | 0 | ZVAL_LONG(&t, s->count); |
182 | 0 | zend_hash_next_index_insert(data, &t); |
183 | 0 | ZVAL_LONG(&t, s->mode); |
184 | 0 | zend_hash_next_index_insert(data, &t); |
185 | |
|
186 | 0 | return true; |
187 | 0 | } |
188 | | |
189 | | static bool unserialize(void *state, HashTable *data) |
190 | 0 | { |
191 | 0 | php_random_status_state_mt19937 *s = state; |
192 | 0 | zval *t; |
193 | | |
194 | | /* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */ |
195 | 0 | if (zend_hash_num_elements(data) != (N + 2)) { |
196 | 0 | return false; |
197 | 0 | } |
198 | | |
199 | 0 | for (uint32_t i = 0; i < N; i++) { |
200 | 0 | t = zend_hash_index_find(data, i); |
201 | 0 | if (!t || Z_TYPE_P(t) != IS_STRING || Z_STRLEN_P(t) != (2 * sizeof(uint32_t))) { |
202 | 0 | return false; |
203 | 0 | } |
204 | 0 | if (!php_random_hex2bin_le(Z_STR_P(t), &s->state[i])) { |
205 | 0 | return false; |
206 | 0 | } |
207 | 0 | } |
208 | 0 | t = zend_hash_index_find(data, N); |
209 | 0 | if (!t || Z_TYPE_P(t) != IS_LONG) { |
210 | 0 | return false; |
211 | 0 | } |
212 | 0 | s->count = Z_LVAL_P(t); |
213 | 0 | if (s->count > N) { |
214 | 0 | return false; |
215 | 0 | } |
216 | | |
217 | 0 | t = zend_hash_index_find(data, N + 1); |
218 | 0 | if (!t || Z_TYPE_P(t) != IS_LONG) { |
219 | 0 | return false; |
220 | 0 | } |
221 | 0 | s->mode = Z_LVAL_P(t); |
222 | 0 | if (s->mode != MT_RAND_MT19937 && s->mode != MT_RAND_PHP) { |
223 | 0 | return false; |
224 | 0 | } |
225 | | |
226 | 0 | return true; |
227 | 0 | } |
228 | | |
229 | | PHPAPI const php_random_algo php_random_algo_mt19937 = { |
230 | | sizeof(php_random_status_state_mt19937), |
231 | | generate, |
232 | | range, |
233 | | serialize, |
234 | | unserialize |
235 | | }; |
236 | | |
237 | | /* {{{ php_random_mt19937_seed_default */ |
238 | | PHPAPI void php_random_mt19937_seed_default(php_random_status_state_mt19937 *state) |
239 | 15 | { |
240 | 15 | uint32_t seed = 0; |
241 | | |
242 | 15 | if (php_random_bytes_silent(&seed, sizeof(seed)) == FAILURE) { |
243 | 0 | seed = (uint32_t)php_random_generate_fallback_seed(); |
244 | 0 | } |
245 | | |
246 | 15 | php_random_mt19937_seed32(state, seed); |
247 | 15 | } |
248 | | /* }}} */ |
249 | | |
250 | | /* {{{ Random\Engine\Mt19937::__construct() */ |
251 | | PHP_METHOD(Random_Engine_Mt19937, __construct) |
252 | 2 | { |
253 | 2 | php_random_algo_with_state engine = Z_RANDOM_ENGINE_P(ZEND_THIS)->engine; |
254 | 2 | php_random_status_state_mt19937 *state = engine.state; |
255 | 2 | zend_long seed, mode = MT_RAND_MT19937; |
256 | 2 | bool seed_is_null = true; |
257 | | |
258 | 6 | ZEND_PARSE_PARAMETERS_START(0, 2) |
259 | 6 | Z_PARAM_OPTIONAL; |
260 | 6 | Z_PARAM_LONG_OR_NULL(seed, seed_is_null); |
261 | 0 | Z_PARAM_LONG(mode); |
262 | 2 | ZEND_PARSE_PARAMETERS_END(); |
263 | | |
264 | 2 | switch (mode) { |
265 | 2 | case MT_RAND_MT19937: |
266 | 2 | state->mode = MT_RAND_MT19937; |
267 | 2 | break; |
268 | 0 | case MT_RAND_PHP: |
269 | 0 | zend_error(E_DEPRECATED, "The MT_RAND_PHP variant of Mt19937 is deprecated"); |
270 | 0 | state->mode = MT_RAND_PHP; |
271 | 0 | break; |
272 | 0 | default: |
273 | 0 | zend_argument_value_error(2, "must be either MT_RAND_MT19937 or MT_RAND_PHP"); |
274 | 0 | RETURN_THROWS(); |
275 | 2 | } |
276 | | |
277 | 2 | if (seed_is_null) { |
278 | | /* MT19937 has a very large state, uses CSPRNG for seeding only */ |
279 | 2 | if (php_random_bytes_throw(&seed, sizeof(seed)) == FAILURE) { |
280 | 0 | zend_throw_exception(random_ce_Random_RandomException, "Failed to generate a random seed", 0); |
281 | 0 | RETURN_THROWS(); |
282 | 0 | } |
283 | 2 | } |
284 | | |
285 | 2 | php_random_mt19937_seed32(state, seed); |
286 | 2 | } |
287 | | /* }}} */ |
288 | | |
289 | | /* {{{ Random\Engine\Mt19937::generate() */ |
290 | | PHP_METHOD(Random_Engine_Mt19937, generate) |
291 | 0 | { |
292 | 0 | php_random_algo_with_state engine = Z_RANDOM_ENGINE_P(ZEND_THIS)->engine; |
293 | 0 | zend_string *bytes; |
294 | |
|
295 | 0 | ZEND_PARSE_PARAMETERS_NONE(); |
296 | | |
297 | 0 | php_random_result generated = engine.algo->generate(engine.state); |
298 | 0 | if (EG(exception)) { |
299 | 0 | RETURN_THROWS(); |
300 | 0 | } |
301 | | |
302 | 0 | bytes = zend_string_alloc(generated.size, false); |
303 | | |
304 | | /* Endianness safe copy */ |
305 | 0 | for (size_t i = 0; i < generated.size; i++) { |
306 | 0 | ZSTR_VAL(bytes)[i] = (generated.result >> (i * 8)) & 0xff; |
307 | 0 | } |
308 | 0 | ZSTR_VAL(bytes)[generated.size] = '\0'; |
309 | |
|
310 | 0 | RETURN_STR(bytes); |
311 | 0 | } |
312 | | /* }}} */ |
313 | | |
314 | | /* {{{ Random\Engine\Mt19937::__serialize() */ |
315 | | PHP_METHOD(Random_Engine_Mt19937, __serialize) |
316 | 0 | { |
317 | 0 | php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS); |
318 | 0 | zval t; |
319 | |
|
320 | 0 | ZEND_PARSE_PARAMETERS_NONE(); |
321 | | |
322 | 0 | array_init(return_value); |
323 | | |
324 | | /* members */ |
325 | 0 | ZVAL_EMPTY_ARRAY(&t); |
326 | 0 | zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &t); |
327 | | |
328 | | /* state */ |
329 | 0 | array_init(&t); |
330 | 0 | zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &t); |
331 | 0 | if (!engine->engine.algo->serialize(engine->engine.state, Z_ARRVAL(t))) { |
332 | 0 | zend_throw_exception(NULL, "Engine serialize failed", 0); |
333 | 0 | RETURN_THROWS(); |
334 | 0 | } |
335 | 0 | } |
336 | | /* }}} */ |
337 | | |
338 | | /* {{{ Random\Engine\Mt19937::__unserialize() */ |
339 | | PHP_METHOD(Random_Engine_Mt19937, __unserialize) |
340 | 0 | { |
341 | 0 | php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS); |
342 | 0 | HashTable *d; |
343 | 0 | zval *t; |
344 | |
|
345 | 0 | ZEND_PARSE_PARAMETERS_START(1, 1) |
346 | 0 | Z_PARAM_ARRAY_HT(d); |
347 | 0 | ZEND_PARSE_PARAMETERS_END(); |
348 | | |
349 | | /* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */ |
350 | 0 | if (zend_hash_num_elements(d) != 2) { |
351 | 0 | zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name)); |
352 | 0 | RETURN_THROWS(); |
353 | 0 | } |
354 | | |
355 | | /* members */ |
356 | 0 | t = zend_hash_index_find(d, 0); |
357 | 0 | if (!t || Z_TYPE_P(t) != IS_ARRAY) { |
358 | 0 | zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name)); |
359 | 0 | RETURN_THROWS(); |
360 | 0 | } |
361 | 0 | object_properties_load(&engine->std, Z_ARRVAL_P(t)); |
362 | 0 | if (EG(exception)) { |
363 | 0 | zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name)); |
364 | 0 | RETURN_THROWS(); |
365 | 0 | } |
366 | | |
367 | | /* state */ |
368 | 0 | t = zend_hash_index_find(d, 1); |
369 | 0 | if (!t || Z_TYPE_P(t) != IS_ARRAY) { |
370 | 0 | zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name)); |
371 | 0 | RETURN_THROWS(); |
372 | 0 | } |
373 | 0 | if (!engine->engine.algo->unserialize(engine->engine.state, Z_ARRVAL_P(t))) { |
374 | 0 | zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name)); |
375 | 0 | RETURN_THROWS(); |
376 | 0 | } |
377 | 0 | } |
378 | | /* }}} */ |
379 | | |
380 | | /* {{{ Random\Engine\Mt19937::__debugInfo() */ |
381 | | PHP_METHOD(Random_Engine_Mt19937, __debugInfo) |
382 | 0 | { |
383 | 0 | php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS); |
384 | 0 | zval t; |
385 | |
|
386 | 0 | ZEND_PARSE_PARAMETERS_NONE(); |
387 | | |
388 | 0 | ZVAL_ARR(return_value, zend_array_dup(zend_std_get_properties_ex(&engine->std))); |
389 | |
|
390 | 0 | if (engine->engine.algo->serialize) { |
391 | 0 | array_init(&t); |
392 | 0 | zend_hash_str_add(Z_ARR_P(return_value), "__states", strlen("__states"), &t); |
393 | 0 | if (!engine->engine.algo->serialize(engine->engine.state, Z_ARRVAL(t))) { |
394 | 0 | zend_throw_exception(NULL, "Engine serialize failed", 0); |
395 | | RETURN_THROWS(); |
396 | 0 | } |
397 | 0 | } |
398 | 0 | } |
399 | | /* }}} */ |