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