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