|
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 | + /* |
| 36 | + * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03) |
| 37 | + * |
| 38 | + * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16 |
| 39 | + * |
| 40 | + * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE). |
| 41 | + * |
| 42 | + * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/) |
| 43 | + */ |
| 44 | + |
| 45 | + public static function fromRandom(int $length = 16, string $alphabet = null): self |
34 | 46 | {
|
35 |
| - $string = ''; |
| 47 | + if ($length <= 0) { |
| 48 | + throw new InvalidArgumentException(sprintf('Expected positive length value, got "%d".', $length)); |
| 49 | + } |
| 50 | + |
| 51 | + $alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC; |
| 52 | + $alphabetSize = \strlen($alphabet); |
| 53 | + $bits = (int) ceil(log($alphabetSize, 2.0)); |
| 54 | + if ($bits <= 0 || $bits > 56) { |
| 55 | + throw new InvalidArgumentException('Expected $alphabet\'s length to be in [2^1, 2^56].'); |
| 56 | + } |
36 | 57 |
|
37 |
| - do { |
38 |
| - $string .= str_replace(['/', '+', '='], '', base64_encode(random_bytes($length))); |
39 |
| - } while (\strlen($string) < $length); |
| 58 | + $ret = ''; |
| 59 | + while ($length > 0) { |
| 60 | + $urandomLength = (int) ceil(2 * $length * $bits / 8.0); |
| 61 | + $data = random_bytes($urandomLength); |
| 62 | + $unpackedData = 0; |
| 63 | + $unpackedBits = 0; |
| 64 | + for ($i = 0; $i < $urandomLength && $length > 0; ++$i) { |
| 65 | + // Unpack 8 bits |
| 66 | + $unpackedData = ($unpackedData << 8) | \ord($data[$i]); |
| 67 | + $unpackedBits += 8; |
| 68 | + |
| 69 | + // While we have enough bits to select a character from the alphabet, keep |
| 70 | + // consuming the random data |
| 71 | + for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) { |
| 72 | + $index = ($unpackedData & ((1 << $bits) - 1)); |
| 73 | + $unpackedData >>= $bits; |
| 74 | + // Unfortunately, the alphabet size is not necessarily a power of two. |
| 75 | + // Worst case, it is 2^k + 1, which means we need (k+1) bits and we |
| 76 | + // have around a 50% chance of missing as k gets larger |
| 77 | + if ($index < $alphabetSize) { |
| 78 | + $ret .= $alphabet[$index]; |
| 79 | + --$length; |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + } |
40 | 84 |
|
41 |
| - return new static(substr($string, 0, $length)); |
| 85 | + return new static($ret); |
42 | 86 | }
|
43 | 87 |
|
44 | 88 | public function bytesAt(int $offset): array
|
|
0 commit comments