Completed
Pull Request — master (#118)
by Charlotte
02:02
created

BitManipulation::binaryToInt()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 0
cts 19
cp 0
rs 8.5906
c 0
b 0
f 0
cc 5
eloc 14
nc 5
nop 1
crap 30
1
<?php
2
3
/**
4
 * This file is a part of Woketo package.
5
 *
6
 * (c) Nekland <[email protected]>
7
 *
8
 * For the full license, take a look to the LICENSE file
9
 * on the root directory of this project
10
 */
11
declare(strict_types=1);
12
13
namespace Nekland\Woketo\Utils;
14
use Nekland\Woketo\Exception\Utils\NotLongEnoughException;
15
16
/**
17
 * Class BitManipulation
18
 *
19
 * Glossary:
20
 *   - in this context a "frame" is an assembly of bytes represented by a "byte-string" or a (signed) int.
21
 */
22
class BitManipulation
23
{
24
    /**
25
     * Mode from to is the default mode of inspection of frames. But PHP usually uses from and length to inspect frames.
26
     */
27
    const MODE_FROM_TO = 0;
28
    const MODE_PHP = 1;
29
30
    /**
31
     * Get a specific bit from a byte.
32
     *
33
     * @param int $byte
34
     * @param int $bitNumber
35
     * @return int
36
     */
37
    public static function nthBit(int $byte, int $bitNumber) : int
38
    {
39
        if ($byte < 0 || $byte > 255) {
40
            throw new \InvalidArgumentException(
41
                \sprintf('The given integer %s is not a byte.', $byte)
42
            );
43
        }
44
45
        if ($bitNumber < 1 || $bitNumber > 8) {
46
            throw new \InvalidArgumentException(
47
                \sprintf('The bit number %s is not a correct value for a byte (1-8 required).', $bitNumber)
48
            );
49
        }
50
51
        $realNth = \pow(2, 8 - $bitNumber);
52
53
        return (int) ($realNth === ($byte & $realNth));
54
    }
55
56
    /**
57
     * Get a specific byte inside a frame represented by an int or a string.
58
     *
59
     * @param string|int $frame      Non utf8 string (this should be more precisely a bytes-string).
60
     * @param int        $byteNumber Starting at 0.
61
     * @return int
62
     */
63
    public static function nthByte($frame, int $byteNumber) : int
64
    {
65
        if (\is_string($frame)) {
66
            $len = BitManipulation::frameSize($frame);
67
68
            if ($byteNumber < 0 || $byteNumber > ($len-1)) {
69
                throw new \InvalidArgumentException(
70
                    \sprintf('The frame is only %s bytes larges but you tried to get the %sth byte.', $len, $byteNumber)
71
                );
72
            }
73
74
            return \ord($frame[$byteNumber]);
75
        }
76
77
        if (\is_int($frame)) {
78
            if ($frame < 0) {
79
                throw new \InvalidArgumentException(
80
                    \sprintf('This method does not support negative ints as parameter for now. %s given.', $byteNumber)
81
                );
82
            }
83
            $hex = \dechex($frame);
84
            $len = BitManipulation::frameSize($hex);
85
86
            // Index of the first octal of the wanted byte
87
            $realByteNth = $byteNumber * 2;
88
89
            if ($byteNumber < 0 || ($realByteNth + 1) > $len) {
90
                throw new \InvalidArgumentException(
91
                    \sprintf('Impossible to get the byte %s from the frame %s.', $byteNumber, $frame)
92
                );
93
            }
94
95
            // Considering FF12AB (number) if you want the byte represented by AB you need to get the
96
            // first letter, shift it of 4 and add the next letter.
97
            // This may seems weird but that's because you read numbers from right to left.
98
            return (\hexdec($hex[$realByteNth]) << 4) + \hexdec($hex[$realByteNth + 1]);
99
            // _Notice that if the number is from right to left, your data is still from left to right_
100
        }
101
102
        throw new \InvalidArgumentException(
103
            \sprintf('The frame must be an int or string, %s given.', gettype($frame))
104
        );
105
    }
106
107
    public static function partOfByte(int $byte, int $part) : int
108
    {
109
        if ($byte < 0 || $byte > 255) {
110
            throw new \InvalidArgumentException(sprintf('%s is not a byte', $byte));
111
        }
112
113
        if ($part === 1) {
114
            return ($byte & 240) >> 4;
115
        }
116
117
        if ($part === 2) {
118
            return $byte & 15;
119
        }
120
121
        throw new \InvalidArgumentException(sprintf('A byte have only 2 parts. %s asked.', $part));
122
    }
123
124
    /**
125
     * Because strings are the best way to store many bytes in PHP it can
126
     * be useful to make the conversion between hex (which are strings)
127
     * array to string.
128
     *
129
     * @param array $hexArray
130
     * @return string
131
     */
132
    public static function hexArrayToString(...$hexArray) : string
133
    {
134
        if (\is_array($hexArray[0])) {
135
            $hexArray = $hexArray[0];
136
        }
137
        
138
        $res = '';
139
        foreach ($hexArray as $hexNum) {
140
            $res .= \chr(\hexdec($hexNum));
141
        }
142
143
        return $res;
144
    }
145
146
    /**
147
     * @param string|int $frame
148
     * @param int        $from        Byte where to start (should be inferior to $to).
149
     * @param int        $to          Byte where to stop (considering it starts at 0). The `to` value include the target
150
     *                                byte.
151
     * @param bool       $force8bytes By default PHP have a wrong behavior with 8 bytes variables. If you have 8 bytes
152
     *                                the returned int will be negative (because unsigned integers does not exists in PHP)
153
     * @return int
154
     */
155
    public static function bytesFromTo($frame, int $from, int $to, bool $force8bytes = false) : int
156
    {
157
        // No more than 64b (which return negative number when the first bit is specified)
158
        if (($to - $from) > 7 && (!$force8bytes && ($to - $from) !== 8)) {
159
            if ($force8bytes) {
160
                throw new \InvalidArgumentException(sprintf('Not more than 8 bytes (64bit) is supported by this method and you asked for %s bytes.', $to - $from));
161
            }
162
            throw new \InvalidArgumentException('PHP limitation: getting more than 7 bytes will return a negative number because unsigned int does not exist.');
163
        }
164
165
        if (\is_string($frame)) {
166
            if ((BitManipulation::frameSize($frame) - 1) < $to) {
167
                throw new NotLongEnoughException('The frame is not long enough.');
168
            }
169
170
            $subStringLength = $to - $from + 1;
171
            // Getting responsible bytes
172
            $subString = \substr($frame, $from, $subStringLength);
173
            $res = 0;
174
175
            // for each byte, getting ord
176
            for($i = 0; $i < $subStringLength; $i++) {
177
                $res <<= 8;
178
                $res += \ord($subString[$i]);
179
            }
180
181
            return $res;
182
        }
183
184
        if (!\is_int($frame)) {
185
            throw new \InvalidArgumentException(
186
                \sprintf('A frame can only be a string or int. %s given', gettype($frame))
187
            );
188
        }
189
190
        if ($frame < 0) {
191
            throw new \InvalidArgumentException('The frame cannot be a negative number');
192
        }
193
194
        $res = 0;
195
        for ($i = $from; $i <= $to; $i++) {
196
            $res <<= 8;
197
            $res += BitManipulation::nthByte($frame, $i);
198
        }
199
200
        return $res;
201
    }
202
203
    /**
204
     * Proxy to the substr to be sure to be use the right method (mb_substr)
205
     *
206
     * @param string $frame
207
     * @param int    $from
208
     * @param int    $to
209
     * @return string
210
     */
211
    public static function bytesFromToString(string $frame, int $from, int $to, int $mode = BitManipulation::MODE_FROM_TO) : string
212
    {
213
        if ($mode === BitManipulation::MODE_FROM_TO) {
214
            return \mb_substr($frame, $from, $to - $from + 1, '8bit');
215
        }
216
217
        return \mb_substr($frame, $from, $to, '8bit');
218
    }
219
220
    /**
221
     * Take a frame represented by a decimal int to transform it in a string.
222
     * Notice that any int is a frame and cannot be more than 8 bytes
223
     *
224
     * @param int      $frame
225
     * @param int|null $size  In bytes. This value should always be precise. Be careful if you don't !
226
     * @return string
227
     */
228
    public static function intToBinaryString(int $frame, int $size = 0) : string
229
    {
230
        $format = '*';
231
232
        switch(true) {
233
            case $size <= 2:
234
                $format = 'n*';
235
                break;
236
            case $size <= 4:
237
                $format = 'N*';
238
                break;
239
            case $size > 4:
240
                $format = 'J*';
241
                break;
242
        }
243
244
        return pack($format, $frame);
245
    }
246
247
    /**
248
     * Take an string frame and transform it to a decimal frame (inside an int).
249
     *
250
     * @param string $frame
251
     * @return int
252
     */
253
    public static function binaryToInt(string $frame) : int
254
    {
255
        $len = BitManipulation::frameSize($frame);
256
257
        if ($len > 8) {
258
            throw new \InvalidArgumentException(
259
                \sprintf('The string %s cannot be converted to int because an int cannot be more than 8 bytes (64b).', $frame)
260
            );
261
        }
262
263
        $format = '*';
264
265
        switch(true) {
266
            case $len <= 2:
267
                $format = 'n';break;
0 ignored issues
show
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
268
            case $len <= 4:
269
                $format = 'N';break;
0 ignored issues
show
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
270
            case $len > 4:
271
                $format = 'J';break;
0 ignored issues
show
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
272
        }
273
274
        return unpack($format, $frame)[1];
275
    }
276
277
    /**
278
     * Method that return frame as hex (more readable).
279
     * Helpful for debug !
280
     *
281
     * @param string $frame
282
     * @return string
283
     */
284
    public static function binaryToHex(string $frame) : string
285
    {
286
        return unpack('H*', $frame)[1];
287
    }
288
289
    /**
290
     * Haters gonna hate. `strlen` cannot be trusted because of an option of mbstring extension, more info:
291
     * http://php.net/manual/fr/mbstring.overload.php
292
     * http://php.net/manual/fr/function.mb-strlen.php#77040
293
     *
294
     * @param string $frame
295
     * @return int
296
     */
297
    public static function frameSize(string $frame) : int
298
    {
299
        if (\extension_loaded('mbstring')) {
300
            return \mb_strlen($frame, '8bit');
301
        }
302
303
        return \strlen($frame);
304
    }
305
}
306