1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Riimu\Kit\SecureRandom\Generator; |
4
|
|
|
|
5
|
|
|
use Riimu\Kit\SecureRandom\GeneratorException; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* A random number generator that wraps the given byte generator for generating integers. |
9
|
|
|
* |
10
|
|
|
* @author Riikka Kalliomäki <[email protected]> |
11
|
|
|
* @copyright Copyright (c) 2017 Riikka Kalliomäki |
12
|
|
|
* @license http://opensource.org/licenses/mit-license.php MIT License |
13
|
|
|
*/ |
14
|
|
|
class ByteNumberGenerator implements NumberGenerator |
15
|
|
|
{ |
16
|
|
|
/** @var \Closure[] Closures for reading bytes */ |
17
|
|
|
private $byteReaders; |
18
|
|
|
|
19
|
|
|
/** @var Generator The underlying byte generator */ |
20
|
|
|
private $byteGenerator; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* NumberByteGenerator constructor. |
24
|
|
|
* @param Generator $generator The underlying byte generator used to generate random bytes |
25
|
|
|
*/ |
26
|
97 |
|
public function __construct(Generator $generator) |
27
|
|
|
{ |
28
|
97 |
|
$this->initializeByteReaders(); |
29
|
97 |
|
$this->byteGenerator = $generator; |
30
|
97 |
|
} |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Tells if the underlying byte generator is supported by the system. |
34
|
|
|
* @return bool True if the generator is supported, false if not |
35
|
|
|
*/ |
36
|
3 |
|
public function isSupported() |
37
|
|
|
{ |
38
|
3 |
|
return $this->byteGenerator->isSupported(); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Returns bytes read from the provided byte generator. |
43
|
|
|
* @param int $count The number of bytes to read |
44
|
|
|
* @return string A string of bytes |
45
|
|
|
* @throws GeneratorException If there was an error generating the bytes |
46
|
|
|
*/ |
47
|
12 |
|
public function getBytes($count) |
48
|
|
|
{ |
49
|
12 |
|
return $this->byteGenerator->getBytes($count); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Returns a random integer between given minimum and maximum. |
54
|
|
|
* @param int $min The minimum possible value to return |
55
|
|
|
* @param int $max The maximum possible value to return |
56
|
|
|
* @return int A random number between the lower and upper limit (inclusive) |
57
|
|
|
* @throws \InvalidArgumentException If the provided values are invalid |
58
|
|
|
* @throws GeneratorException If an error occurs generating the number |
59
|
|
|
*/ |
60
|
41 |
|
public function getNumber($min, $max) |
61
|
|
|
{ |
62
|
41 |
|
$min = (int) $min; |
63
|
41 |
|
$max = (int) $max; |
64
|
|
|
|
65
|
41 |
|
if ($min > $max) { |
66
|
3 |
|
throw new \InvalidArgumentException('Invalid minimum and maximum value'); |
67
|
|
|
} |
68
|
|
|
|
69
|
38 |
|
if ($min === $max) { |
70
|
12 |
|
return $min; |
71
|
|
|
} |
72
|
|
|
|
73
|
29 |
|
$difference = $max - $min; |
74
|
|
|
|
75
|
29 |
|
if (!is_int($difference)) { |
76
|
2 |
|
throw new GeneratorException('Too large difference between minimum and maximum'); |
77
|
|
|
} |
78
|
|
|
|
79
|
27 |
|
return $min + $this->getByteNumber($difference); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Returns a random number generated using the random byte generator. |
84
|
|
|
* @param int $limit Maximum value for the random number |
85
|
|
|
* @return int The generated random number between 0 and the limit |
86
|
|
|
* @throws GeneratorException If error occurs generating the random number |
87
|
|
|
*/ |
88
|
27 |
|
private function getByteNumber($limit) |
89
|
|
|
{ |
90
|
27 |
|
$bits = 1; |
91
|
27 |
|
$mask = 1; |
92
|
|
|
|
93
|
27 |
|
while ($limit >> $bits > 0) { |
94
|
27 |
|
$mask |= 1 << $bits; |
95
|
27 |
|
$bits++; |
96
|
9 |
|
} |
97
|
|
|
|
98
|
27 |
|
$readBytes = $this->byteReaders[(int) ceil($bits / 8)]; |
99
|
|
|
|
100
|
|
|
do { |
101
|
27 |
|
$result = $readBytes() & $mask; |
102
|
27 |
|
} while ($result > $limit); |
103
|
|
|
|
104
|
27 |
|
return $result; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Initializes the callbacks used to generate different numbers of bytes. |
109
|
|
|
*/ |
110
|
97 |
|
private function initializeByteReaders() |
111
|
|
|
{ |
112
|
97 |
|
$this->byteReaders = [ |
113
|
64 |
|
1 => function () { |
114
|
24 |
|
$bytes = unpack('C', $this->byteGenerator->getBytes(1)); |
115
|
24 |
|
return $bytes[1]; |
116
|
97 |
|
}, |
117
|
64 |
|
2 => function () { |
118
|
6 |
|
$bytes = unpack('n', $this->byteGenerator->getBytes(2)); |
119
|
6 |
|
return $bytes[1]; |
120
|
97 |
|
}, |
121
|
64 |
View Code Duplication |
3 => function () { |
|
|
|
|
122
|
6 |
|
$bytes = unpack('Ca/nb', $this->byteGenerator->getBytes(3)); |
123
|
6 |
|
return $bytes['a'] << 16 | $bytes['b']; |
124
|
97 |
|
}, |
125
|
64 |
|
4 => function () { |
126
|
3 |
|
$bytes = unpack('N', $this->byteGenerator->getBytes(4)); |
127
|
3 |
|
return $bytes[1]; |
128
|
97 |
|
}, |
129
|
64 |
View Code Duplication |
5 => function () { |
|
|
|
|
130
|
3 |
|
$bytes = unpack('Ca/Nb', $this->byteGenerator->getBytes(5)); |
131
|
3 |
|
return $bytes['a'] << 32 | $bytes['b']; |
132
|
97 |
|
}, |
133
|
64 |
View Code Duplication |
6 => function () { |
|
|
|
|
134
|
3 |
|
$bytes = unpack('na/Nb', $this->byteGenerator->getBytes(6)); |
135
|
3 |
|
return $bytes['a'] << 32 | $bytes['b']; |
136
|
97 |
|
}, |
137
|
64 |
View Code Duplication |
7 => function () { |
|
|
|
|
138
|
3 |
|
$bytes = unpack('Ca/nb/Nc', $this->byteGenerator->getBytes(7)); |
139
|
3 |
|
return $bytes['a'] << 48 | $bytes['b'] << 32 | $bytes['c']; |
140
|
97 |
|
}, |
141
|
97 |
|
8 => function () { |
142
|
6 |
|
$bytes = unpack('J', $this->byteGenerator->getBytes(8)); |
143
|
6 |
|
return $bytes[1]; |
144
|
97 |
|
}, |
145
|
|
|
]; |
146
|
33 |
|
} |
147
|
|
|
} |
148
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.