Completed
Push — master ( be524f...104e68 )
by Maxime
01:59
created

BitManipulation::intToBinaryString()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 0
cts 22
cp 0
rs 8.439
c 0
b 0
f 0
cc 6
eloc 17
nc 10
nop 2
crap 42
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 = null) : string
229
    {
230
        $format = 'J*';
231
232
        if ($size !== null) {
233
            switch (true) {
234
                case $size <= 2:
235
                    $format = 'n*';
236
                    break;
237
                case $size <= 4:
238
                    $format = 'N*';
239
                    break;
240
                case $size > 4:
241
                    $format = 'J*';
242
                    break;
243
            }
244
        }
245
246
        $res = \pack($format, $frame);
247
248
        if ($size === null) {
249
            $res = \ltrim($res, "\0");
250
        }
251
252
        return $res;
253
    }
254
255
    /**
256
     * Take an string frame and transform it to a decimal frame (inside an int).
257
     *
258
     * @param string $frame
259
     * @return int
260
     */
261
    public static function binaryStringtoInt(string $frame) : int
262
    {
263
        $len = BitManipulation::frameSize($frame);
264
265
        if ($len > 8) {
266
            throw new \InvalidArgumentException(
267
                \sprintf('The string %s cannot be converted to int because an int cannot be more than 8 bytes (64b).', $frame)
268
            );
269
        }
270
271
        if (\in_array(BitManipulation::frameSize($frame), [1, 3])) {
272
            $frame = "\0" . $frame;
273
        }
274
275
        switch(true) {
276
            case $len <= 2:
277
                $format = 'n';
278
                break;
279
            case $len <= 4:
280
                $format = 'N';
281
                break;
282
            case $len > 4:
283
                $format = 'J';
284
285
                do {
286
                    $frame = "\0" . $frame;
287
                } while (BitManipulation::frameSize($frame) !== 8);
288
289
                break;
290
        }
291
292
        return \unpack($format, $frame)[1];
0 ignored issues
show
Bug introduced by
The variable $format does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
293
    }
294
295
    /**
296
     * Method that return frame as hex (more readable).
297
     * Helpful for debug !
298
     *
299
     * @param string $frame
300
     * @return string
301
     */
302
    public static function binaryStringToHex(string $frame) : string
303
    {
304
        return \unpack('H*', $frame)[1];
305
    }
306
307
    /**
308
     * Haters gonna hate. `strlen` cannot be trusted because of an option of mbstring extension, more info:
309
     * http://php.net/manual/fr/mbstring.overload.php
310
     * http://php.net/manual/fr/function.mb-strlen.php#77040
311
     *
312
     * @param string $frame
313
     * @return int
314
     */
315
    public static function frameSize(string $frame) : int
316
    {
317
        if (\extension_loaded('mbstring')) {
318
            return \mb_strlen($frame, '8bit');
319
        }
320
321
        return \strlen($frame);
322
    }
323
}
324