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: Melissa O'Neill <oneill@pcg-random.org>          |
16    +----------------------------------------------------------------------+
17 */
18 
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22 
23 #include "php.h"
24 #include "php_random.h"
25 
26 #include "Zend/zend_exceptions.h"
27 
step(php_random_status_state_pcgoneseq128xslrr64 * s)28 static inline void step(php_random_status_state_pcgoneseq128xslrr64 *s)
29 {
30 	s->state = php_random_uint128_add(
31 		php_random_uint128_multiply(s->state, php_random_uint128_constant(2549297995355413924ULL,4865540595714422341ULL)),
32 		php_random_uint128_constant(6364136223846793005ULL,1442695040888963407ULL)
33 	);
34 }
35 
seed128(php_random_status * status,php_random_uint128_t seed)36 static inline void seed128(php_random_status *status, php_random_uint128_t seed)
37 {
38 	php_random_status_state_pcgoneseq128xslrr64 *s = status->state;
39 	s->state = php_random_uint128_constant(0ULL, 0ULL);
40 	step(s);
41 	s->state = php_random_uint128_add(s->state, seed);
42 	step(s);
43 }
44 
seed(php_random_status * status,uint64_t seed)45 static void seed(php_random_status *status, uint64_t seed)
46 {
47 	seed128(status, php_random_uint128_constant(0ULL, seed));
48 }
49 
generate(php_random_status * status)50 static uint64_t generate(php_random_status *status)
51 {
52 	php_random_status_state_pcgoneseq128xslrr64 *s = status->state;
53 
54 	step(s);
55 	return php_random_pcgoneseq128xslrr64_rotr64(s->state);
56 }
57 
range(php_random_status * status,zend_long min,zend_long max)58 static zend_long range(php_random_status *status, zend_long min, zend_long max)
59 {
60 	return php_random_range(&php_random_algo_pcgoneseq128xslrr64, status, min, max);
61 }
62 
serialize(php_random_status * status,HashTable * data)63 static bool serialize(php_random_status *status, HashTable *data)
64 {
65 	php_random_status_state_pcgoneseq128xslrr64 *s = status->state;
66 	uint64_t u;
67 	zval z;
68 
69 	u = php_random_uint128_hi(s->state);
70 	ZVAL_STR(&z, php_random_bin2hex_le(&u, sizeof(uint64_t)));
71 	zend_hash_next_index_insert(data, &z);
72 
73 	u = php_random_uint128_lo(s->state);
74 	ZVAL_STR(&z, php_random_bin2hex_le(&u, sizeof(uint64_t)));
75 	zend_hash_next_index_insert(data, &z);
76 
77 	return true;
78 }
79 
unserialize(php_random_status * status,HashTable * data)80 static bool unserialize(php_random_status *status, HashTable *data)
81 {
82 	php_random_status_state_pcgoneseq128xslrr64 *s = status->state;
83 	uint64_t u[2];
84 	zval *t;
85 
86 	/* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
87 	if (zend_hash_num_elements(data) != 2) {
88 		return false;
89 	}
90 
91 	for (uint32_t i = 0; i < 2; i++) {
92 		t = zend_hash_index_find(data, i);
93 		if (!t || Z_TYPE_P(t) != IS_STRING || Z_STRLEN_P(t) != (2 * sizeof(uint64_t))) {
94 			return false;
95 		}
96 		if (!php_random_hex2bin_le(Z_STR_P(t), &u[i])) {
97 			return false;
98 		}
99 	}
100 	s->state = php_random_uint128_constant(u[0], u[1]);
101 
102 	return true;
103 }
104 
105 const php_random_algo php_random_algo_pcgoneseq128xslrr64 = {
106 	sizeof(uint64_t),
107 	sizeof(php_random_status_state_pcgoneseq128xslrr64),
108 	seed,
109 	generate,
110 	range,
111 	serialize,
112 	unserialize
113 };
114 
115 /* {{{ php_random_pcgoneseq128xslrr64_advance */
php_random_pcgoneseq128xslrr64_advance(php_random_status_state_pcgoneseq128xslrr64 * state,uint64_t advance)116 PHPAPI void php_random_pcgoneseq128xslrr64_advance(php_random_status_state_pcgoneseq128xslrr64 *state, uint64_t advance)
117 {
118 	php_random_uint128_t
119 		cur_mult = php_random_uint128_constant(2549297995355413924ULL,4865540595714422341ULL),
120 		cur_plus = php_random_uint128_constant(6364136223846793005ULL,1442695040888963407ULL),
121 		acc_mult = php_random_uint128_constant(0ULL, 1ULL),
122 		acc_plus = php_random_uint128_constant(0ULL, 0ULL);
123 
124 	while (advance > 0) {
125 		if (advance & 1) {
126 			acc_mult = php_random_uint128_multiply(acc_mult, cur_mult);
127 			acc_plus = php_random_uint128_add(php_random_uint128_multiply(acc_plus, cur_mult), cur_plus);
128 		}
129 		cur_plus = php_random_uint128_multiply(php_random_uint128_add(cur_mult, php_random_uint128_constant(0ULL, 1ULL)), cur_plus);
130 		cur_mult = php_random_uint128_multiply(cur_mult, cur_mult);
131 		advance /= 2;
132 	}
133 
134 	state->state = php_random_uint128_add(php_random_uint128_multiply(acc_mult, state->state), acc_plus);
135 }
136 /* }}} */
137 
138 /* {{{ Random\Engine\PcgOneseq128XslRr64::__construct */
PHP_METHOD(Random_Engine_PcgOneseq128XslRr64,__construct)139 PHP_METHOD(Random_Engine_PcgOneseq128XslRr64, __construct)
140 {
141 	php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS);
142 	php_random_status_state_pcgoneseq128xslrr64 *state = engine->status->state;
143 	zend_string *str_seed = NULL;
144 	zend_long int_seed = 0;
145 	bool seed_is_null = true;
146 	uint32_t i, j;
147 	uint64_t t[2];
148 
149 	ZEND_PARSE_PARAMETERS_START(0, 1)
150 		Z_PARAM_OPTIONAL;
151 		Z_PARAM_STR_OR_LONG_OR_NULL(str_seed, int_seed, seed_is_null);
152 	ZEND_PARSE_PARAMETERS_END();
153 
154 	if (seed_is_null) {
155 		if (php_random_bytes_throw(&state->state, sizeof(php_random_uint128_t)) == FAILURE) {
156 			zend_throw_exception(random_ce_Random_RandomException, "Failed to generate a random seed", 0);
157 			RETURN_THROWS();
158 		}
159 	} else {
160 		if (str_seed) {
161 			/* char (byte: 8 bit) * 16 = 128 bits */
162 			if (ZSTR_LEN(str_seed) == 16) {
163 				/* Endianness safe copy */
164 				for (i = 0; i < 2; i++) {
165 					t[i] = 0;
166 					for (j = 0; j < 8; j++) {
167 						t[i] += ((uint64_t) (unsigned char) ZSTR_VAL(str_seed)[(i * 8) + j]) << (j * 8);
168 					}
169 				}
170 				seed128(engine->status, php_random_uint128_constant(t[0], t[1]));
171 			} else {
172 				zend_argument_value_error(1, "must be a 16 byte (128 bit) string");
173 				RETURN_THROWS();
174 			}
175 		} else {
176 			engine->algo->seed(engine->status, int_seed);
177 		}
178 	}
179 }
180 /* }}} */
181 
182 /* {{{ Random\Engine\PcgOneseq128XslRr64::jump() */
PHP_METHOD(Random_Engine_PcgOneseq128XslRr64,jump)183 PHP_METHOD(Random_Engine_PcgOneseq128XslRr64, jump)
184 {
185 	php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS);
186 	php_random_status_state_pcgoneseq128xslrr64 *state = engine->status->state;
187 	zend_long advance = 0;
188 
189 	ZEND_PARSE_PARAMETERS_START(1, 1)
190 		Z_PARAM_LONG(advance);
191 	ZEND_PARSE_PARAMETERS_END();
192 
193 	if (UNEXPECTED(advance < 0)) {
194 		zend_argument_value_error(1, "must be greater than or equal to 0");
195 		RETURN_THROWS();
196 	}
197 
198 	php_random_pcgoneseq128xslrr64_advance(state, advance);
199 }
200 /* }}} */
201