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