1--TEST-- 2Random: Randomizer: getBytesFromString(): Fast Path Masking 3--FILE-- 4<?php 5 6use Random\Engine\Test\TestWrapperEngine; 7use Random\Engine\Xoshiro256StarStar; 8use Random\Randomizer; 9 10require __DIR__ . "/../../engines.inc"; 11 12$allBytes = implode('', array_map( 13 fn ($byte) => chr($byte), 14 range(0x00, 0xff) 15)); 16 17// Xoshiro256** is the fastest engine available. 18$xoshiro = new Xoshiro256StarStar(); 19 20var_dump(strlen($allBytes)); 21echo PHP_EOL; 22 23// Fast path: Inputs less than or equal to 256. 24for ($i = 1; $i <= strlen($allBytes); $i *= 2) { 25 echo "{$i}:", PHP_EOL; 26 27 $wrapper = new TestWrapperEngine($xoshiro); 28 $r = new Randomizer($wrapper); 29 $result = $r->getBytesFromString(substr($allBytes, 0, $i), 20000); 30 31 // Xoshiro256** is a 64 Bit engine and thus generates 8 bytes at once. 32 // For powers of two we expect no rejections and thus exactly 33 // 20000/8 = 2500 calls to the engine. 34 var_dump($wrapper->getCount()); 35 36 $count = []; 37 for ($j = 0; $j < strlen($result); $j++) { 38 $b = $result[$j]; 39 $count[ord($b)] ??= 0; 40 $count[ord($b)]++; 41 } 42 43 // We also expect that each possible value appears at least once, if 44 // not is is very likely that some bits were erroneously masked away. 45 var_dump(count($count)); 46 47 echo PHP_EOL; 48} 49 50// Test lengths that are one more than the powers of two. For these 51// the maximum offset will be a power of two and thus a minimal number 52// of bits will be set in the offset. 53for ($i = 1; ($i + 1) <= strlen($allBytes); $i *= 2) { 54 $oneMore = $i + 1; 55 56 echo "{$oneMore}:", PHP_EOL; 57 58 $wrapper = new TestWrapperEngine($xoshiro); 59 $r = new Randomizer($wrapper); 60 $result = $r->getBytesFromString(substr($allBytes, 0, $oneMore), 20000); 61 62 $count = []; 63 for ($j = 0; $j < strlen($result); $j++) { 64 $b = $result[$j]; 65 $count[ord($b)] ??= 0; 66 $count[ord($b)]++; 67 } 68 69 // We expect that each possible value appears at least once, if 70 // not is is very likely that some bits were erroneously masked away. 71 var_dump(count($count)); 72 73 echo PHP_EOL; 74} 75 76echo "Slow Path:", PHP_EOL; 77 78$wrapper = new TestWrapperEngine($xoshiro); 79$r = new Randomizer($wrapper); 80$result = $r->getBytesFromString($allBytes . $allBytes, 20000); 81 82// In the slow path we expect one call per byte, i.e. 20000 83var_dump($wrapper->getCount()); 84 85$count = []; 86for ($j = 0; $j < strlen($result); $j++) { 87 $b = $result[$j]; 88 $count[ord($b)] ??= 0; 89 $count[ord($b)]++; 90} 91 92// We also expect that each possible value appears at least once, if 93// not is is very likely that some bits were erroneously masked away. 94var_dump(count($count)); 95 96?> 97--EXPECT-- 98int(256) 99 1001: 101int(2500) 102int(1) 103 1042: 105int(2500) 106int(2) 107 1084: 109int(2500) 110int(4) 111 1128: 113int(2500) 114int(8) 115 11616: 117int(2500) 118int(16) 119 12032: 121int(2500) 122int(32) 123 12464: 125int(2500) 126int(64) 127 128128: 129int(2500) 130int(128) 131 132256: 133int(2500) 134int(256) 135 1362: 137int(2) 138 1393: 140int(3) 141 1425: 143int(5) 144 1459: 146int(9) 147 14817: 149int(17) 150 15133: 152int(33) 153 15465: 155int(65) 156 157129: 158int(129) 159 160Slow Path: 161int(20000) 162int(256) 163