Completed
Push — unpack-change ( 9f7880 )
by Riikka
02:12
created

ByteNumberGenerator::initializeByteReaders()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 26

Duplication

Lines 16
Ratio 43.24 %

Code Coverage

Tests 35
CRAP Score 1

Importance

Changes 0
Metric Value
dl 16
loc 37
ccs 35
cts 35
cp 1
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 26
nc 1
nop 0
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 \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 () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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 () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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 () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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 () {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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