mirror of
https://github.com/php/php-src.git
synced 2024-11-23 18:04:36 +08:00
Add Randomizer::getBytesFromString()
method (#9664)
* Add `Randomizer::getBytesFromAlphabet()` method * Rename `getBytesFromAlphabet` to `getBytesFromString` * [ci skip] Add NEWS/UPGRADING for Randomizer::getBytesFromString() Co-authored-by: Tim Düsterhus <tim@bastelstu.be>
This commit is contained in:
parent
1b503a1f52
commit
ac3ecd03af
3
NEWS
3
NEWS
@ -55,6 +55,9 @@ PHP NEWS
|
||||
- Posix:
|
||||
. Added posix_sysconf. (David Carlier)
|
||||
|
||||
- Random:
|
||||
. Added Randomizer::getBytesFromString(). (Joshua Rüsweg)
|
||||
|
||||
- Reflection:
|
||||
. Fix GH-9470 (ReflectionMethod constructor should not find private parent
|
||||
method). (ilutov)
|
||||
|
@ -62,6 +62,10 @@ PHP 8.3 UPGRADE NOTES
|
||||
- Posix:
|
||||
. Added posix_sysconf call to get runtime informations.
|
||||
|
||||
- Random:
|
||||
. Added Randomizer::getBytesFromString().
|
||||
RFC: https://wiki.php.net/rfc/randomizer_additions
|
||||
|
||||
- Sockets:
|
||||
. Added socket_atmark to checks if the socket is OOB marked.
|
||||
|
||||
|
@ -74,6 +74,8 @@ PHPAPI double php_combined_lcg(void);
|
||||
|
||||
# define MT_N (624)
|
||||
|
||||
#define PHP_RANDOM_RANGE_ATTEMPTS (50)
|
||||
|
||||
PHPAPI void php_mt_srand(uint32_t seed);
|
||||
PHPAPI uint32_t php_mt_rand(void);
|
||||
PHPAPI zend_long php_mt_rand_range(zend_long min, zend_long max);
|
||||
|
@ -86,8 +86,6 @@ static zend_object_handlers random_engine_xoshiro256starstar_object_handlers;
|
||||
static zend_object_handlers random_engine_secure_object_handlers;
|
||||
static zend_object_handlers random_randomizer_object_handlers;
|
||||
|
||||
#define RANDOM_RANGE_ATTEMPTS (50)
|
||||
|
||||
static inline uint32_t rand_range32(const php_random_algo *algo, php_random_status *status, uint32_t umax)
|
||||
{
|
||||
uint32_t result, limit;
|
||||
@ -124,8 +122,8 @@ static inline uint32_t rand_range32(const php_random_algo *algo, php_random_stat
|
||||
/* Discard numbers over the limit to avoid modulo bias */
|
||||
while (UNEXPECTED(result > limit)) {
|
||||
/* If the requirements cannot be met in a cycles, return fail */
|
||||
if (++count > RANDOM_RANGE_ATTEMPTS) {
|
||||
zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", RANDOM_RANGE_ATTEMPTS);
|
||||
if (++count > PHP_RANDOM_RANGE_ATTEMPTS) {
|
||||
zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", PHP_RANDOM_RANGE_ATTEMPTS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -180,8 +178,8 @@ static inline uint64_t rand_range64(const php_random_algo *algo, php_random_stat
|
||||
/* Discard numbers over the limit to avoid modulo bias */
|
||||
while (UNEXPECTED(result > limit)) {
|
||||
/* If the requirements cannot be met in a cycles, return fail */
|
||||
if (++count > RANDOM_RANGE_ATTEMPTS) {
|
||||
zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", RANDOM_RANGE_ATTEMPTS);
|
||||
if (++count > PHP_RANDOM_RANGE_ATTEMPTS) {
|
||||
zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", PHP_RANDOM_RANGE_ATTEMPTS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -137,6 +137,8 @@ namespace Random
|
||||
|
||||
public function getBytes(int $length): string {}
|
||||
|
||||
public function getBytesFromString(string $string, int $length): string {}
|
||||
|
||||
public function shuffleArray(array $array): array {}
|
||||
|
||||
public function shuffleBytes(string $bytes): string {}
|
||||
|
9
ext/random/random_arginfo.h
generated
9
ext/random/random_arginfo.h
generated
@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 6cc9022516ce23c2e95af30606db43e9fc28e38a */
|
||||
* Stub hash: a4226bc7838eba98c5a935b279f681a7d083c0b2 */
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_lcg_value, 0, 0, IS_DOUBLE, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
@ -94,6 +94,11 @@ ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_class_Random_Randomizer_getBytes arginfo_random_bytes
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Random_Randomizer_getBytesFromString, 0, 2, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Random_Randomizer_shuffleArray, 0, 1, IS_ARRAY, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
@ -133,6 +138,7 @@ ZEND_METHOD(Random_Randomizer, __construct);
|
||||
ZEND_METHOD(Random_Randomizer, nextInt);
|
||||
ZEND_METHOD(Random_Randomizer, getInt);
|
||||
ZEND_METHOD(Random_Randomizer, getBytes);
|
||||
ZEND_METHOD(Random_Randomizer, getBytesFromString);
|
||||
ZEND_METHOD(Random_Randomizer, shuffleArray);
|
||||
ZEND_METHOD(Random_Randomizer, shuffleBytes);
|
||||
ZEND_METHOD(Random_Randomizer, pickArrayKeys);
|
||||
@ -209,6 +215,7 @@ static const zend_function_entry class_Random_Randomizer_methods[] = {
|
||||
ZEND_ME(Random_Randomizer, nextInt, arginfo_class_Random_Randomizer_nextInt, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Random_Randomizer, getInt, arginfo_class_Random_Randomizer_getInt, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Random_Randomizer, getBytes, arginfo_class_Random_Randomizer_getBytes, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Random_Randomizer, getBytesFromString, arginfo_class_Random_Randomizer_getBytesFromString, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Random_Randomizer, shuffleArray, arginfo_class_Random_Randomizer_shuffleArray, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Random_Randomizer, shuffleBytes, arginfo_class_Random_Randomizer_shuffleBytes, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Random_Randomizer, pickArrayKeys, arginfo_class_Random_Randomizer_pickArrayKeys, ZEND_ACC_PUBLIC)
|
||||
|
@ -258,6 +258,102 @@ PHP_METHOD(Random_Randomizer, pickArrayKeys)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Get Random Bytes for String */
|
||||
PHP_METHOD(Random_Randomizer, getBytesFromString)
|
||||
{
|
||||
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
|
||||
zend_long length;
|
||||
zend_string *source, *retval;
|
||||
size_t total_size = 0;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(2, 2);
|
||||
Z_PARAM_STR(source)
|
||||
Z_PARAM_LONG(length)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
const size_t source_length = ZSTR_LEN(source);
|
||||
|
||||
if (source_length < 1) {
|
||||
zend_argument_value_error(1, "cannot be empty");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (length < 1) {
|
||||
zend_argument_value_error(2, "must be greater than 0");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
retval = zend_string_alloc(length, 0);
|
||||
|
||||
if (source_length > 0xFF) {
|
||||
while (total_size < length) {
|
||||
uint64_t offset = randomizer->algo->range(randomizer->status, 0, source_length - 1);
|
||||
|
||||
if (EG(exception)) {
|
||||
zend_string_free(retval);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
ZSTR_VAL(retval)[total_size++] = ZSTR_VAL(source)[offset];
|
||||
}
|
||||
} else {
|
||||
uint64_t mask;
|
||||
if (source_length <= 0x1) {
|
||||
mask = 0x0;
|
||||
} else if (source_length <= 0x2) {
|
||||
mask = 0x1;
|
||||
} else if (source_length <= 0x4) {
|
||||
mask = 0x3;
|
||||
} else if (source_length <= 0x8) {
|
||||
mask = 0x7;
|
||||
} else if (source_length <= 0x10) {
|
||||
mask = 0xF;
|
||||
} else if (source_length <= 0x20) {
|
||||
mask = 0x1F;
|
||||
} else if (source_length <= 0x40) {
|
||||
mask = 0x3F;
|
||||
} else if (source_length <= 0x80) {
|
||||
mask = 0x7F;
|
||||
} else {
|
||||
mask = 0xFF;
|
||||
}
|
||||
|
||||
int failures = 0;
|
||||
while (total_size < length) {
|
||||
uint64_t result = randomizer->algo->generate(randomizer->status);
|
||||
if (EG(exception)) {
|
||||
zend_string_free(retval);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < randomizer->status->last_generated_size; i++) {
|
||||
uint64_t offset = (result >> (i * 8)) & mask;
|
||||
|
||||
if (offset >= source_length) {
|
||||
if (++failures > PHP_RANDOM_RANGE_ATTEMPTS) {
|
||||
zend_string_free(retval);
|
||||
zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", PHP_RANDOM_RANGE_ATTEMPTS);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
failures = 0;
|
||||
|
||||
ZSTR_VAL(retval)[total_size++] = ZSTR_VAL(source)[offset];
|
||||
if (total_size >= length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ZSTR_VAL(retval)[length] = '\0';
|
||||
RETURN_STR(retval);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Random\Randomizer::__serialize() */
|
||||
PHP_METHOD(Random_Randomizer, __serialize)
|
||||
{
|
||||
|
@ -49,6 +49,18 @@ try {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump(randomizer()->getBytesFromString('123', 10));
|
||||
} catch (Random\BrokenRandomEngineError $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump(randomizer()->getBytesFromString(str_repeat('a', 500), 10));
|
||||
} catch (Random\BrokenRandomEngineError $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Failed to generate an acceptable random number in 50 attempts
|
||||
@ -56,3 +68,5 @@ int(%d)
|
||||
string(2) "ff"
|
||||
Failed to generate an acceptable random number in 50 attempts
|
||||
Failed to generate an acceptable random number in 50 attempts
|
||||
Failed to generate an acceptable random number in 50 attempts
|
||||
Failed to generate an acceptable random number in 50 attempts
|
||||
|
@ -49,6 +49,18 @@ try {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump(randomizer()->getBytesFromString('123', 10));
|
||||
} catch (Random\BrokenRandomEngineError $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump(randomizer()->getBytesFromString(str_repeat('a', 500), 10));
|
||||
} catch (Random\BrokenRandomEngineError $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
A random engine must return a non-empty string
|
||||
@ -56,3 +68,5 @@ A random engine must return a non-empty string
|
||||
A random engine must return a non-empty string
|
||||
A random engine must return a non-empty string
|
||||
A random engine must return a non-empty string
|
||||
A random engine must return a non-empty string
|
||||
A random engine must return a non-empty string
|
||||
|
@ -0,0 +1,63 @@
|
||||
--TEST--
|
||||
Random: Randomizer: getBytesFromString(): Basic functionality
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
use Random\Engine;
|
||||
use Random\Engine\Mt19937;
|
||||
use Random\Engine\PcgOneseq128XslRr64;
|
||||
use Random\Engine\Secure;
|
||||
use Random\Engine\Test\TestShaEngine;
|
||||
use Random\Engine\Xoshiro256StarStar;
|
||||
use Random\Randomizer;
|
||||
|
||||
require __DIR__ . "/../../engines.inc";
|
||||
|
||||
$engines = [];
|
||||
$engines[] = new Mt19937(null, MT_RAND_MT19937);
|
||||
$engines[] = new Mt19937(null, MT_RAND_PHP);
|
||||
$engines[] = new PcgOneseq128XslRr64();
|
||||
$engines[] = new Xoshiro256StarStar();
|
||||
$engines[] = new Secure();
|
||||
$engines[] = new TestShaEngine();
|
||||
|
||||
foreach ($engines as $engine) {
|
||||
echo $engine::class, PHP_EOL;
|
||||
|
||||
$randomizer = new Randomizer($engine);
|
||||
var_dump($randomizer->getBytesFromString('a', 10));
|
||||
var_dump($randomizer->getBytesFromString(str_repeat('a', 256), 5));
|
||||
|
||||
for ($i = 1; $i < 250; $i++) {
|
||||
$output = $randomizer->getBytesFromString(str_repeat('ab', $i), 500);
|
||||
|
||||
// This check can theoretically fail with a chance of 0.5**500.
|
||||
if (!str_contains($output, 'a') || !str_contains($output, 'b')) {
|
||||
die("failure: didn't see both a and b at {$i}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
die('success');
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Random\Engine\Mt19937
|
||||
string(10) "aaaaaaaaaa"
|
||||
string(5) "aaaaa"
|
||||
Random\Engine\Mt19937
|
||||
string(10) "aaaaaaaaaa"
|
||||
string(5) "aaaaa"
|
||||
Random\Engine\PcgOneseq128XslRr64
|
||||
string(10) "aaaaaaaaaa"
|
||||
string(5) "aaaaa"
|
||||
Random\Engine\Xoshiro256StarStar
|
||||
string(10) "aaaaaaaaaa"
|
||||
string(5) "aaaaa"
|
||||
Random\Engine\Secure
|
||||
string(10) "aaaaaaaaaa"
|
||||
string(5) "aaaaa"
|
||||
Random\Engine\Test\TestShaEngine
|
||||
string(10) "aaaaaaaaaa"
|
||||
string(5) "aaaaa"
|
||||
success
|
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Random: Randomizer: getBytesFromString(): Parameters are correctly validated
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
use Random\Randomizer;
|
||||
|
||||
function randomizer(): Randomizer
|
||||
{
|
||||
return new Randomizer();
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump(randomizer()->getBytesFromString("", 2));
|
||||
} catch (ValueError $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump(randomizer()->getBytesFromString("abc", 0));
|
||||
} catch (ValueError $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Random\Randomizer::getBytesFromString(): Argument #1 ($string) cannot be empty
|
||||
Random\Randomizer::getBytesFromString(): Argument #2 ($length) must be greater than 0
|
Loading…
Reference in New Issue
Block a user