|
25 | 25 | */
|
26 | 26 | class ByteString extends AbstractString
|
27 | 27 | {
|
| 28 | + private const ALPHABET_ALPHANUMERIC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
| 29 | + |
28 | 30 | public function __construct(string $string = '')
|
29 | 31 | {
|
30 | 32 | $this->string = $string;
|
31 | 33 | }
|
32 | 34 |
|
33 |
| - public static function fromRandom(int $length = 16): self |
| 35 | + public static function fromRandom(int $length = 16, string $alphabet = null): self |
34 | 36 | {
|
35 |
| - $string = ''; |
| 37 | + if (0 === $length) { |
| 38 | + return new static(''); |
| 39 | + } |
| 40 | + |
| 41 | + if ($length < 0) { |
| 42 | + throw new \RuntimeException(\sprintf('Expected positive length value, got %s', $length)); |
| 43 | + } |
| 44 | + |
| 45 | + $alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC; |
| 46 | + $alphabet_size = \strlen($alphabet); |
| 47 | + $bits = (int)\ceil(\log($alphabet_size, 2.0)); |
| 48 | + if ($bits <= 0 || $bits > 56) { |
| 49 | + throw new \RuntimeException('Expected $alphabet\'s length to be in [2^1, 2^56]'); |
| 50 | + } |
36 | 51 |
|
37 |
| - do { |
38 |
| - $string .= str_replace(['/', '+', '='], '', base64_encode(random_bytes($length))); |
39 |
| - } while (\strlen($string) < $length); |
| 52 | + $ret = ''; |
| 53 | + while ($length > 0) { |
| 54 | + $urandom_length = (int)\ceil(2 * $length * $bits / 8.0); |
| 55 | + $data = \random_bytes($urandom_length); |
| 56 | + $unpacked_data = 0; |
| 57 | + $unpacked_bits = 0; |
| 58 | + for ($i = 0; $i < $urandom_length && $length > 0; ++$i) { |
| 59 | + // Unpack 8 bits |
| 60 | + $unpacked_data = ($unpacked_data << 8) | \ord($data[$i]); |
| 61 | + $unpacked_bits += 8; |
| 62 | + |
| 63 | + // While we have enough bits to select a character from the alphabet, keep |
| 64 | + // consuming the random data |
| 65 | + for (; $unpacked_bits >= $bits && $length > 0; $unpacked_bits -= $bits) { |
| 66 | + $index = ($unpacked_data & ((1 << $bits) - 1)); |
| 67 | + $unpacked_data >>= $bits; |
| 68 | + // Unfortunately, the alphabet size is not necessarily a power of two. |
| 69 | + // Worst case, it is 2^k + 1, which means we need (k+1) bits and we |
| 70 | + // have around a 50% chance of missing as k gets larger |
| 71 | + if ($index < $alphabet_size) { |
| 72 | + $ret .= $alphabet[$index]; |
| 73 | + --$length; |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + } |
40 | 78 |
|
41 |
| - return new static(substr($string, 0, $length)); |
| 79 | + return new static($ret); |
42 | 80 | }
|
43 | 81 |
|
44 | 82 | public function bytesAt(int $offset): array
|
|
0 commit comments