Completed
Push — master ( 1251fb...1391a2 )
by Valentin
03:06
created

BitManipulation::binaryStringtoInt()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.0585

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 15
cts 17
cp 0.8824
rs 8.439
c 0
b 0
f 0
cc 6
eloc 20
nc 7
nop 1
crap 6.0585
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 81
    public static function nthBit(int $byte, int $bitNumber) : int
38
    {
39 81
        if ($byte < 0 || $byte > 255) {
40 2
            throw new \InvalidArgumentException(
41 2
                \sprintf('The given integer %s is not a byte.', $byte)
42
            );
43
        }
44
45 79
        if ($bitNumber < 1 || $bitNumber > 8) {
46 2
            throw new \InvalidArgumentException(
47 2
                \sprintf('The bit number %s is not a correct value for a byte (1-8 required).', $bitNumber)
48
            );
49
        }
50
51 77
        $realNth = \pow(2, 8 - $bitNumber);
52
53 77
        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 88
    public static function nthByte($frame, int $byteNumber) : int
64
    {
65 88
        if (\is_string($frame)) {
66 78
            $len = BitManipulation::frameSize($frame);
67
68 78
            if ($byteNumber < 0 || $byteNumber > ($len-1)) {
69 2
                throw new \InvalidArgumentException(
70 2
                    \sprintf('The frame is only %s bytes larges but you tried to get the %sth byte.', $len, $byteNumber)
71
                );
72
            }
73
74 76
            return \ord($frame[$byteNumber]);
75
        }
76
77 10
        if (\is_int($frame)) {
78 9
            if ($frame < 0) {
79 1
                throw new \InvalidArgumentException(
80 1
                    \sprintf('This method does not support negative ints as parameter for now. %s given.', $byteNumber)
81
                );
82
            }
83 8
            $hex = \dechex($frame);
84 8
            $len = BitManipulation::frameSize($hex);
85
86
            // Index of the first octal of the wanted byte
87 8
            $realByteNth = $byteNumber * 2;
88
89 8
            if ($byteNumber < 0 || ($realByteNth + 1) > $len) {
90 3
                throw new \InvalidArgumentException(
91 3
                    \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 6
            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 1
        throw new \InvalidArgumentException(
103 1
            \sprintf('The frame must be an int or string, %s given.', gettype($frame))
104
        );
105
    }
106
107 74
    public static function partOfByte(int $byte, int $part) : int
108
    {
109 74
        if ($byte < 0 || $byte > 255) {
110
            throw new \InvalidArgumentException(sprintf('%s is not a byte', $byte));
111
        }
112
113 74
        if ($part === 1) {
114 1
            return ($byte & 240) >> 4;
115
        }
116
117 73
        if ($part === 2) {
118 72
            return $byte & 15;
119
        }
120
121 1
        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 55
    public static function hexArrayToString(...$hexArray) : string
133
    {
134 55
        if (\is_array($hexArray[0])) {
135 36
            $hexArray = $hexArray[0];
136
        }
137
138 55
        $res = '';
139 55
        foreach ($hexArray as $hexNum) {
140 55
            $res .= \chr(\hexdec($hexNum));
141
        }
142
143 55
        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 39
    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 39
        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 39
        if (\is_string($frame)) {
166 32
            if ((BitManipulation::frameSize($frame) - 1) < $to) {
167 2
                throw new NotLongEnoughException('The frame is not long enough.');
168
            }
169
170 30
            $subStringLength = $to - $from + 1;
171
            // Getting responsible bytes
172 30
            $subString = \substr($frame, $from, $subStringLength);
173 30
            $res = 0;
174
175
            // for each byte, getting ord
176 30
            for($i = 0; $i < $subStringLength; $i++) {
177 30
                $res <<= 8;
178 30
                $res += \ord($subString[$i]);
179
            }
180
181 30
            return $res;
182
        }
183
184 7
        if (!\is_int($frame)) {
185 1
            throw new \InvalidArgumentException(
186 1
                \sprintf('A frame can only be a string or int. %s given', gettype($frame))
187
            );
188
        }
189
190 6
        if ($frame < 0) {
191 1
            throw new \InvalidArgumentException('The frame cannot be a negative number');
192
        }
193
194 5
        $res = 0;
195 5
        for ($i = $from; $i <= $to; $i++) {
196 5
            $res <<= 8;
197 5
            $res += BitManipulation::nthByte($frame, $i);
198
        }
199
200 3
        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 37
    public static function bytesFromToString(string $frame, int $from, int $to, int $mode = BitManipulation::MODE_FROM_TO) : string
212
    {
213 37
        if ($mode === BitManipulation::MODE_FROM_TO) {
214 11
            return \mb_substr($frame, $from, $to - $from + 1, '8bit');
215
        }
216
217 37
        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 23
    public static function intToBinaryString(int $frame, int $size = null) : string
229
    {
230 23
        $format = 'J*';
231
232 23
        if ($size !== null) {
233
            switch (true) {
234 17
                case $size <= 2:
235 7
                    $format = 'n*';
236 7
                    break;
237 11
                case $size <= 4:
238 10
                    $format = 'N*';
239 10
                    break;
240 1
                case $size > 4:
241 1
                    $format = 'J*';
242 1
                    break;
243
            }
244
        }
245
246 23
        $res = \pack($format, $frame);
247
248 23
        if ($size === null) {
249 9
            $res = \ltrim($res, "\0");
250
        }
251
252 23
        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 2
    public static function binaryStringtoInt(string $frame) : int
262
    {
263 2
        $len = BitManipulation::frameSize($frame);
264
265 2
        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 2
        if (\in_array(BitManipulation::frameSize($frame), [1, 3])) {
272 1
            $frame = "\0" . $frame;
273
        }
274
275
        switch(true) {
276 2
            case $len <= 2:
277 1
                $format = 'n';
278 1
                break;
279 2
            case $len <= 4:
280 2
                $format = 'N';
281 2
                break;
282
            default: // also known as "$len > 4" :)
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
283 1
                $format = 'J';
284
285
                do {
286 1
                    $frame = "\0" . $frame;
287 1
                } while (BitManipulation::frameSize($frame) !== 8);
288
        }
289
290 2
        return \unpack($format, $frame)[1];
291
    }
292
293
    /**
294
     * Method that return frame as hex (more readable).
295
     * Helpful for debug !
296
     *
297
     * @param string $frame
298
     * @return string
299
     */
300 3
    public static function binaryStringToHex(string $frame) : string
301
    {
302 3
        return \unpack('H*', $frame)[1];
303
    }
304
305
    /**
306
     * Haters gonna hate. `strlen` cannot be trusted because of an option of mbstring extension, more info:
307
     * http://php.net/manual/fr/mbstring.overload.php
308
     * http://php.net/manual/fr/function.mb-strlen.php#77040
309
     *
310
     * @param string $frame
311
     * @return int
312
     */
313 101
    public static function frameSize(string $frame) : int
314
    {
315 101
        if (\extension_loaded('mbstring')) {
316 101
            return \mb_strlen($frame, '8bit');
317
        }
318
319
        return \strlen($frame);
320
    }
321
}
322