BitManipulation::nthByte()   B
last analyzed

Complexity

Conditions 8
Paths 6

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 43
ccs 20
cts 20
cp 1
rs 7.9875
c 0
b 0
f 0
cc 8
nc 6
nop 2
crap 8
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" :)
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