Completed
Push — master ( 81f456...707917 )
by Riikka
11s
created

ByteNumberGenerator::readByteNumber()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
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 string[] Formats for different numbers of bytes */
17
    private static $byteFormats = ['Ca', 'na', 'Cb/na', 'Na', 'Cc/Na', 'nc/Na', 'Cd/nc/Na', 'Ja'];
18
19
    /** @var int[] Default values for byte format values */
20
    private static $byteDefaults = ['a' => 0, 'b' => 0, 'c' => 0, 'd' => 0];
21
22
    /** @var Generator The underlying byte generator */
23
    private $byteGenerator;
24
25
    /**
26
     * NumberByteGenerator constructor.
27
     * @param Generator $generator The underlying byte generator used to generate random bytes
28
     */
29 97
    public function __construct(Generator $generator)
30
    {
31 97
        $this->byteGenerator = $generator;
32 97
    }
33
34
    /**
35
     * Tells if the underlying byte generator is supported by the system.
36
     * @return bool True if the generator is supported, false if not
37
     */
38 3
    public function isSupported()
39
    {
40 3
        return $this->byteGenerator->isSupported();
41
    }
42
43
    /**
44
     * Returns bytes read from the provided byte generator.
45
     * @param int $count The number of bytes to read
46
     * @return string A string of bytes
47
     * @throws GeneratorException If there was an error generating the bytes
48
     */
49 12
    public function getBytes($count)
50
    {
51 12
        return $this->byteGenerator->getBytes($count);
52
    }
53
54
    /**
55
     * Returns a random integer between given minimum and maximum.
56
     * @param int $min The minimum possible value to return
57
     * @param int $max The maximum possible value to return
58
     * @return int A random number between the lower and upper limit (inclusive)
59
     * @throws \InvalidArgumentException If the provided values are invalid
60
     * @throws GeneratorException If an error occurs generating the number
61
     */
62 42
    public function getNumber($min, $max)
63
    {
64 42
        $min = (int) $min;
65 42
        $max = (int) $max;
66
67 42
        if ($min > $max) {
68 3
            throw new \InvalidArgumentException('Invalid minimum and maximum value');
69
        }
70
71 39
        if ($min === $max) {
72 12
            return $min;
73
        }
74
75 30
        $difference = $max - $min;
76
77 30
        if (!is_int($difference)) {
78 3
            throw new GeneratorException('Too large difference between minimum and maximum');
79
        }
80
81 27
        return $min + $this->getByteNumber($difference);
82
    }
83
84
    /**
85
     * Returns a random number generated using the random byte generator.
86
     * @param int $limit Maximum value for the random number
87
     * @return int The generated random number between 0 and the limit
88
     * @throws GeneratorException If error occurs generating the random number
89
     */
90 27
    private function getByteNumber($limit)
91
    {
92 27
        $bits = 1;
93 27
        $mask = 1;
94
95 27
        while ($limit >> $bits > 0) {
96 27
            $mask |= 1 << $bits;
97 27
            $bits++;
98 9
        }
99
100 27
        $bytes = (int) ceil($bits / 8);
101
102
        do {
103 27
            $result = $this->readByteNumber($bytes) & $mask;
104 27
        } while ($result > $limit);
105
106 27
        return $result;
107
    }
108
109
    /**
110
     * Returns a number from byte generator based on given number of bytes.
111
     * @param int $bytes The number of bytes to read
112
     * @return int A random number read from the bytes of the byte generator
113
     * @throws GeneratorException If the errors occurs generating the bytes for the number
114
     */
115 27
    private function readByteNumber($bytes)
116
    {
117 27
        $values = unpack(self::$byteFormats[$bytes - 1], $this->byteGenerator->getBytes($bytes)) + self::$byteDefaults;
118 27
        return $values['a'] | $values['b'] << 16 | $values['c'] << 32 | $values['d'] << 48;
119
    }
120
}
121