Changeset 42130 for trunk/src/wpincludes/random_compat/random_int.php
 Timestamp:
 11/08/2017 11:47:04 AM (4 years ago)
 File:

 1 edited
Legend:
 Unmodified
 Added
 Removed

trunk/src/wpincludes/random_compat/random_int.php
r36886 r42130 1 1 <?php 2 /**3 * Random_* Compatibility Library4 * for using the new PHP 7 random_* API in PHP 5 projects5 *6 * The MIT License (MIT)7 *8 * Copyright (c) 2015 Paragon Initiative Enterprises9 *10 * Permission is hereby granted, free of charge, to any person obtaining a copy11 * of this software and associated documentation files (the "Software"), to deal12 * in the Software without restriction, including without limitation the rights13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell14 * copies of the Software, and to permit persons to whom the Software is15 * furnished to do so, subject to the following conditions:16 *17 * The above copyright notice and this permission notice shall be included in18 * all copies or substantial portions of the Software.19 *20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE26 * SOFTWARE.27 */28 2 29 /** 30 * Fetch a random integer between $min and $max inclusive 31 * 32 * @param int $min 33 * @param int $max 34 * 35 * @throws Exception 36 * 37 * @return int 38 */ 39 function random_int($min, $max) 40 { 3 if (!is_callable('random_int')) { 41 4 /** 42 * Type and input logic checks 43 * 44 * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX) 45 * (noninclusive), it will sanely cast it to an int. If you it's equal to 46 * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats 47 * lose precision, so the <= and => operators might accidentally let a float 48 * through. 5 * Random_* Compatibility Library 6 * for using the new PHP 7 random_* API in PHP 5 projects 7 * 8 * The MIT License (MIT) 9 * 10 * Copyright (c) 2015  2017 Paragon Initiative Enterprises 11 * 12 * Permission is hereby granted, free of charge, to any person obtaining a copy 13 * of this software and associated documentation files (the "Software"), to deal 14 * in the Software without restriction, including without limitation the rights 15 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 * copies of the Software, and to permit persons to whom the Software is 17 * furnished to do so, subject to the following conditions: 18 * 19 * The above copyright notice and this permission notice shall be included in 20 * all copies or substantial portions of the Software. 21 * 22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 * SOFTWARE. 49 29 */ 50 51 try {52 $min = RandomCompat_intval($min);53 } catch (TypeError $ex) {54 throw new TypeError(55 'random_int(): $min must be an integer'56 );57 }58 59 try {60 $max = RandomCompat_intval($max);61 } catch (TypeError $ex) {62 throw new TypeError(63 'random_int(): $max must be an integer'64 );65 }66 67 /**68 * Now that we've verified our weak typing system has given us an integer,69 * let's validate the logic then we can move forward with generating random70 * integers along a given range.71 */72 if ($min > $max) {73 throw new Error(74 'Minimum value must be less than or equal to the maximum value'75 );76 }77 78 if ($max === $min) {79 return $min;80 }81 30 82 31 /** 83 * Initialize variables to 0 84 * 85 * We want to store: 86 * $bytes => the number of random bytes we need 87 * $mask => an integer bitmask (for use with the &) operator 88 * so we can minimize the number of discards 32 * Fetch a random integer between $min and $max inclusive 33 * 34 * @param int $min 35 * @param int $max 36 * 37 * @throws Exception 38 * 39 * @return int 89 40 */ 90 $attempts = $bits = $bytes = $mask = $valueShift = 0; 41 function random_int($min, $max) 42 { 43 /** 44 * Type and input logic checks 45 * 46 * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX) 47 * (noninclusive), it will sanely cast it to an int. If you it's equal to 48 * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats 49 * lose precision, so the <= and => operators might accidentally let a float 50 * through. 51 */ 91 52 92 /** 93 * At this point, $range is a positive number greater than 0. It might 94 * overflow, however, if $max  $min > PHP_INT_MAX. PHP will cast it to 95 * a float and we will lose some precision. 96 */ 97 $range = $max  $min; 53 try { 54 $min = RandomCompat_intval($min); 55 } catch (TypeError $ex) { 56 throw new TypeError( 57 'random_int(): $min must be an integer' 58 ); 59 } 98 60 99 /** 100 * Test for integer overflow: 101 */ 102 if (!is_int($range)) { 103 104 /** 105 * Still safely calculate wider ranges. 106 * Provided by @CodesInChaos, @oittaa 107 * 108 * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 109 * 110 * We use ~0 as a mask in this case because it generates all 1s 111 * 112 * @ref https://eval.in/400356 (32bit) 113 * @ref http://3v4l.org/XX9r5 (64bit) 114 */ 115 $bytes = PHP_INT_SIZE; 116 $mask = ~0; 117 118 } else { 119 120 /** 121 * $bits is effectively ceil(log($range, 2)) without dealing with 122 * type juggling 123 */ 124 while ($range > 0) { 125 if ($bits % 8 === 0) { 126 ++$bytes; 127 } 128 ++$bits; 129 $range >>= 1; 130 $mask = $mask << 1  1; 131 } 132 $valueShift = $min; 133 } 134 135 /** 136 * Now that we have our parameters set up, let's begin generating 137 * random integers until one falls between $min and $max 138 */ 139 do { 140 /** 141 * The rejection probability is at most 0.5, so this corresponds 142 * to a failure probability of 2^128 for a working RNG 143 */ 144 if ($attempts > 128) { 145 throw new Exception( 146 'random_int: RNG is broken  too many rejections' 61 try { 62 $max = RandomCompat_intval($max); 63 } catch (TypeError $ex) { 64 throw new TypeError( 65 'random_int(): $max must be an integer' 147 66 ); 148 67 } 149 68 150 69 /** 151 * Let's grab the necessary number of random bytes 70 * Now that we've verified our weak typing system has given us an integer, 71 * let's validate the logic then we can move forward with generating random 72 * integers along a given range. 152 73 */ 153 $randomByteString = random_bytes($bytes); 154 if ($randomByteString === false) { 155 throw new Exception( 156 'Random number generator failure' 74 if ($min > $max) { 75 throw new Error( 76 'Minimum value must be less than or equal to the maximum value' 157 77 ); 158 78 } 159 79 160 /** 161 * Let's turn $randomByteString into an integer 162 * 163 * This uses bitwise operators (<< and ) to build an integer 164 * out of the values extracted from ord() 165 * 166 * Example: [9F]  [6D]  [32]  [0C] => 167 * 159 + 27904 + 3276800 + 201326592 => 168 * 204631455 169 */ 170 $val = 0; 171 for ($i = 0; $i < $bytes; ++$i) { 172 $val = ord($randomByteString[$i]) << ($i * 8); 80 if ($max === $min) { 81 return (int) $min; 173 82 } 174 83 175 84 /** 176 * Apply mask 85 * Initialize variables to 0 86 * 87 * We want to store: 88 * $bytes => the number of random bytes we need 89 * $mask => an integer bitmask (for use with the &) operator 90 * so we can minimize the number of discards 177 91 */ 178 $val &= $mask; 179 $val += $valueShift; 92 $attempts = $bits = $bytes = $mask = $valueShift = 0; 180 93 181 ++$attempts;182 94 /** 183 * If $val overflows to a floating point number, 184 * ... or is larger than $max, 185 * ... or smaller than $min, 186 * then try again. 95 * At this point, $range is a positive number greater than 0. It might 96 * overflow, however, if $max  $min > PHP_INT_MAX. PHP will cast it to 97 * a float and we will lose some precision. 187 98 */ 188 } while (!is_int($val)  $val > $max  $val < $min);99 $range = $max  $min; 189 100 190 return (int) $val; 101 /** 102 * Test for integer overflow: 103 */ 104 if (!is_int($range)) { 105 106 /** 107 * Still safely calculate wider ranges. 108 * Provided by @CodesInChaos, @oittaa 109 * 110 * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 111 * 112 * We use ~0 as a mask in this case because it generates all 1s 113 * 114 * @ref https://eval.in/400356 (32bit) 115 * @ref http://3v4l.org/XX9r5 (64bit) 116 */ 117 $bytes = PHP_INT_SIZE; 118 $mask = ~0; 119 120 } else { 121 122 /** 123 * $bits is effectively ceil(log($range, 2)) without dealing with 124 * type juggling 125 */ 126 while ($range > 0) { 127 if ($bits % 8 === 0) { 128 ++$bytes; 129 } 130 ++$bits; 131 $range >>= 1; 132 $mask = $mask << 1  1; 133 } 134 $valueShift = $min; 135 } 136 137 $val = 0; 138 /** 139 * Now that we have our parameters set up, let's begin generating 140 * random integers until one falls between $min and $max 141 */ 142 do { 143 /** 144 * The rejection probability is at most 0.5, so this corresponds 145 * to a failure probability of 2^128 for a working RNG 146 */ 147 if ($attempts > 128) { 148 throw new Exception( 149 'random_int: RNG is broken  too many rejections' 150 ); 151 } 152 153 /** 154 * Let's grab the necessary number of random bytes 155 */ 156 $randomByteString = random_bytes($bytes); 157 158 /** 159 * Let's turn $randomByteString into an integer 160 * 161 * This uses bitwise operators (<< and ) to build an integer 162 * out of the values extracted from ord() 163 * 164 * Example: [9F]  [6D]  [32]  [0C] => 165 * 159 + 27904 + 3276800 + 201326592 => 166 * 204631455 167 */ 168 $val &= 0; 169 for ($i = 0; $i < $bytes; ++$i) { 170 $val = ord($randomByteString[$i]) << ($i * 8); 171 } 172 173 /** 174 * Apply mask 175 */ 176 $val &= $mask; 177 $val += $valueShift; 178 179 ++$attempts; 180 /** 181 * If $val overflows to a floating point number, 182 * ... or is larger than $max, 183 * ... or smaller than $min, 184 * then try again. 185 */ 186 } while (!is_int($val)  $val > $max  $val < $min); 187 188 return (int) $val; 189 } 191 190 }
Note: See TracChangeset
for help on using the changeset viewer.