|
21 | 21 | #include <stdlib.h>
|
22 | 22 | #include <sys/stat.h>
|
23 | 23 | #include <fcntl.h>
|
| 24 | +#include <math.h> |
24 | 25 |
|
25 | 26 | #include "php.h"
|
26 | 27 |
|
27 | 28 | #if PHP_WIN32
|
28 | 29 | # include "win32/winutil.h"
|
29 | 30 | #endif
|
30 | 31 |
|
| 32 | +// Big thanks to @ircmaxell for the help on this bit |
| 33 | +union rand_long_buffer { |
| 34 | + char buffer[8]; |
| 35 | + long number; |
| 36 | +}; |
| 37 | + |
31 | 38 | /*
|
32 | 39 | // Copy/pasted from string.c
|
33 | 40 | static char hexconvtab[] = "0123456789abcdef";
|
@@ -62,6 +69,7 @@ static int php_random_bytes(char *bytes, zend_long size)
|
62 | 69 | }
|
63 | 70 | n = (int)size;
|
64 | 71 | #else
|
| 72 | + // @todo Need to cache the fd for random_int() call within loop |
65 | 73 | int fd;
|
66 | 74 | size_t read_bytes = 0;
|
67 | 75 |
|
@@ -152,56 +160,40 @@ PHP_FUNCTION(random_hex)
|
152 | 160 | }
|
153 | 161 | /* }}} */
|
154 | 162 |
|
155 |
| -/* {{{ proto int random_int(int max) |
| 163 | +/* {{{ proto int random_int(int maximum) |
156 | 164 | Return an arbitrary pseudo-random integer */
|
157 | 165 | PHP_FUNCTION(random_int)
|
158 | 166 | {
|
159 |
| - zend_long max; |
| 167 | + zend_long maximum; |
160 | 168 | zend_long size;
|
161 |
| - zend_long number = 0; |
162 |
| - zend_string *bytes; |
163 | 169 | size_t i;
|
164 | 170 |
|
165 |
| - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &max) == FAILURE) { |
| 171 | + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &maximum) == FAILURE) { |
166 | 172 | return;
|
167 | 173 | }
|
168 | 174 |
|
169 |
| - if (max >= INT_MAX) { |
170 |
| - php_error_docref(NULL, E_WARNING, "Cannot use max greater than %d", INT_MAX); |
| 175 | + if (maximum <= 0 || maximum >= INT_MAX) { |
| 176 | + php_error_docref(NULL, E_WARNING, "Cannot use maximum less than 1 or greater than %d", INT_MAX); |
171 | 177 | RETURN_FALSE;
|
172 | 178 | }
|
173 | 179 |
|
174 |
| - size = sizeof(max); |
175 |
| - |
176 |
| - bytes = zend_string_alloc(size, 0); |
| 180 | + long range = (long) maximum; // @todo Support min? |
177 | 181 |
|
178 |
| - if (php_random_bytes(bytes->val, size) == FAILURE) { |
179 |
| - zend_string_release(bytes); |
180 |
| - return; |
181 |
| - } |
| 182 | + // Big thanks to @ircmaxell for the help on this bit |
| 183 | + union rand_long_buffer value; |
| 184 | + long result; |
| 185 | + int bits = (int) (log((double) range) / log(2.0)) + 1; |
| 186 | + int bytes = MAX(ceil(bits / 8), 1); |
| 187 | + long mask = (long) pow(2.0, (double) bits) - 1; |
182 | 188 |
|
183 |
| - // @todo bin-to-int: I know this is wrong but don't know how to fix |
184 |
| - for (i = 0; i < size; i++) { |
185 |
| - unsigned char c = bytes->val[i++]; |
186 |
| - unsigned char d; |
187 |
| - |
188 |
| - if (c >= '0' && c <= '9') { |
189 |
| - d = c - '0'; |
190 |
| - } else if (c >= 'a' && c <= 'f') { |
191 |
| - d = c - 'a' - 10; |
192 |
| - } else if (c >= 'A' && c <= 'F') { |
193 |
| - d = c - 'A' - 10; |
194 |
| - } else { |
195 |
| - continue; |
| 189 | + do { |
| 190 | + if (php_random_bytes(&value.buffer, 8) == FAILURE) { |
| 191 | + return; |
196 | 192 | }
|
| 193 | + result = value.number & mask; |
| 194 | + } while (result > maximum); |
197 | 195 |
|
198 |
| - // Binary = base-2 |
199 |
| - number = number * 2 + d; |
200 |
| - } |
201 |
| - |
202 |
| - zend_string_release(bytes); |
203 |
| - |
204 |
| - RETURN_LONG(number); |
| 196 | + RETURN_LONG(result); |
205 | 197 | }
|
206 | 198 | /* }}} */
|
207 | 199 |
|
|
0 commit comments