Passed
Branch master (e090ea)
by kacper
04:22
created

BinaryDataReader::getBinarySlice()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 4
crap 1
1
<?php
2
3
namespace MySQLReplication\BinaryDataReader;
4
5
/**
6
 * Class BinaryDataReader
7
 * @package MySQLReplication\BinaryDataReader
8
 */
9
class BinaryDataReader
10
{
11
    const NULL_COLUMN = 251;
12
    const UNSIGNED_CHAR_COLUMN = 251;
13
    const UNSIGNED_SHORT_COLUMN = 252;
14
    const UNSIGNED_INT24_COLUMN = 253;
15
    const UNSIGNED_INT64_COLUMN = 254;
16
    const UNSIGNED_CHAR_LENGTH = 1;
17
    const UNSIGNED_SHORT_LENGTH = 2;
18
    const UNSIGNED_INT24_LENGTH = 3;
19
    const UNSIGNED_INT32_LENGTH = 4;
20
    const UNSIGNED_FLOAT_LENGTH = 4;
21
    const UNSIGNED_DOUBLE_LENGTH = 8;
22
    const UNSIGNED_INT40_LENGTH = 5;
23
    const UNSIGNED_INT48_LENGTH = 6;
24
    const UNSIGNED_INT56_LENGTH = 7;
25
    const UNSIGNED_INT64_LENGTH = 8;
26
27
    /**
28
     * @var int
29
     */
30
    private $readBytes = 0;
31
    /**
32
     * @var string
33
     */
34
    private $data;
35
36
    /**
37
     * Package constructor.
38
     * @param string $data
39
     */
40 84
    public function __construct($data)
41
    {
42 84
        $this->data = $data;
43 84
    }
44
45
    /**
46
     * @param int $length
47
     */
48 56
    public function advance($length)
49
    {
50 56
        $this->readBytes += $length;
51 56
        $this->data = substr($this->data, $length);
52 56
    }
53
54
    /**
55
     * @param int $length
56
     * @return string
57
     * @throws BinaryDataReaderException
58
     */
59 79
    public function read($length)
60
    {
61 79
        $return = substr($this->data, 0, $length);
62 79
        $this->readBytes += $length;
63 79
        $this->data = substr($this->data, $length);
64
65 79
        return $return;
66
    }
67
68
    /**
69
     * @return int
70
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
71
     */
72 7
    public function readInt16()
73
    {
74 7
        return unpack('s', $this->read(self::UNSIGNED_SHORT_LENGTH))[1];
75
    }
76
77
    /**
78
     * Push again data in data buffer. It's use when you want
79
     * to extract a bit from a value a let the rest of the code normally
80
     * read the data
81
     *
82
     * @param string $data
83
     */
84 11
    public function unread($data)
85
    {
86 11
        $this->readBytes -= strlen($data);
87 11
        $this->data = $data . $this->data;
88 11
    }
89
90
    /**
91
     * Read a 'Length Coded Binary' number from the data buffer.
92
     * Length coded numbers can be anywhere from 1 to 9 bytes depending
93
     * on the value of the first byte.
94
     * From PyMYSQL source code
95
     *
96
     * @return int|string
97
     * @throws BinaryDataReaderException
98
     */
99 54
    public function readCodedBinary()
100
    {
101 54
        $c = ord($this->read(self::UNSIGNED_CHAR_LENGTH));
102 54
        if ($c === self::NULL_COLUMN) {
103 1
            return '';
104
        }
105 54
        if ($c < self::UNSIGNED_CHAR_COLUMN) {
106 53
            return $c;
107 2
        } elseif ($c === self::UNSIGNED_SHORT_COLUMN) {
108 1
            return $this->readUInt16();
109 2
        } elseif ($c === self::UNSIGNED_INT24_COLUMN) {
110 1
            return $this->readUInt24();
111 2
        } elseif ($c === self::UNSIGNED_INT64_COLUMN) {
112 1
            return $this->readUInt64();
113
        }
114
115 1
        throw new BinaryDataReaderException('Column num ' . $c . ' not handled');
116
    }
117
118
    /**
119
     * @return int
120
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
121
     */
122 56
    public function readUInt16()
123
    {
124 56
        return unpack('v', $this->read(self::UNSIGNED_SHORT_LENGTH))[1];
125
    }
126
127
    /**
128
     * @return int
129
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
130
     */
131 8
    public function readUInt24()
132
    {
133 8
        $data = unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH));
134
135 8
        return $data[1] + ($data[2] << 8) + ($data[3] << 16);
136
    }
137
138
    /**
139
     * @return string
140
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
141
     */
142 6
    public function readUInt64()
143
    {
144 6
        return $this->unpackUInt64($this->read(self::UNSIGNED_INT64_LENGTH));
145
    }
146
147
    /**
148
     * @param string $binary
149
     * @return string
150
     */
151 55
    public function unpackUInt64($binary)
152
    {
153 55
        $data = unpack('V*', $binary);
154
155 55
        return bcadd($data[1], bcmul($data[2], bcpow(2, 32)));
156
    }
157
158
    /**
159
     * @return int
160
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
161
     */
162 2 View Code Duplication
    public function readInt24()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
163
    {
164 2
        $data = unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH));
165
166 2
        $res = $data[1] | ($data[2] << 8) | ($data[3] << 16);
167 2
        if ($res >= 0x800000) {
168 2
            $res -= 0x1000000;
169 2
        }
170
171 2
        return $res;
172
    }
173
174
    /**
175
     * @return string
176
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
177
     */
178 3
    public function readInt64()
179
    {
180 3
        $data = unpack('V*', $this->read(self::UNSIGNED_INT64_LENGTH));
181
182 3
        return bcadd($data[1], $data[2] << 32);
183
    }
184
185
    /**
186
     * @param int $size
187
     * @return string
188
     * @throws BinaryDataReaderException
189
     */
190 14
    public function readLengthCodedPascalString($size)
191
    {
192 14
        return $this->read($this->readUIntBySize($size));
193
    }
194
195
    /**
196
     * Read a little endian integer values based on byte number
197
     *
198
     * @param int $size
199
     * @return mixed
200
     * @throws BinaryDataReaderException
201
     */
202 25
    public function readUIntBySize($size)
203
    {
204 25
        if ($size === self::UNSIGNED_CHAR_LENGTH) {
205 12
            return $this->readUInt8();
206 13
        } elseif ($size === self::UNSIGNED_SHORT_LENGTH) {
207 3
            return $this->readUInt16();
208 10
        } elseif ($size === self::UNSIGNED_INT24_LENGTH) {
209 2
            return $this->readUInt24();
210 8
        } elseif ($size === self::UNSIGNED_INT32_LENGTH) {
211 3
            return $this->readUInt32();
212 5
        } elseif ($size === self::UNSIGNED_INT40_LENGTH) {
213 1
            return $this->readUInt40();
214 4
        } elseif ($size === self::UNSIGNED_INT48_LENGTH) {
215 1
            return $this->readUInt48();
216 3
        } elseif ($size === self::UNSIGNED_INT56_LENGTH) {
217 1
            return $this->readUInt56();
218 2
        } elseif ($size === self::UNSIGNED_INT64_LENGTH) {
219 1
            return $this->readUInt64();
220
        }
221
222 1
        throw new BinaryDataReaderException('$size ' . $size . ' not handled');
223
    }
224
225
    /**
226
     * @return int
227
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
228
     */
229 56
    public function readUInt8()
230
    {
231 56
        return unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
232
    }
233
234
    /**
235
     * @return int
236
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
237
     */
238 55
    public function readUInt32()
239
    {
240 55
        return unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
241
    }
242
243
    /**
244
     * @return mixed
245
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
246
     */
247 1 View Code Duplication
    public function readUInt40()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
248
    {
249 1
        $data1 = unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
250 1
        $data2 = unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
251
252 1
        return $data1 + ($data2 << 8);
253
    }
254
255
    /**
256
     * @return mixed
257
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
258
     */
259 1
    public function readUInt48()
260
    {
261 1
        $data = unpack('v3', $this->read(self::UNSIGNED_INT48_LENGTH));
262
263 1
        return $data[1] + ($data[2] << 16) + ($data[3] << 32);
264
    }
265
266
    /**
267
     * @return mixed
268
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
269
     */
270 1
    public function readUInt56()
271
    {
272 1
        $data1 = unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
273 1
        $data2 = unpack('S', $this->read(self::UNSIGNED_SHORT_LENGTH))[1];
274 1
        $data3 = unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
275
276 1
        return $data1 + ($data2 << 8) + ($data3 << 24);
277
    }
278
279
    /**
280
     * Read a big endian integer values based on byte number
281
     *
282
     * @param int $size
283
     * @return int
284
     * @throws BinaryDataReaderException
285
     */
286 21
    public function readIntBeBySize($size)
287
    {
288 21
        if ($size === self::UNSIGNED_CHAR_LENGTH) {
289 7
            return $this->readInt8();
290 15
        } elseif ($size === self::UNSIGNED_SHORT_LENGTH) {
291 2
            return $this->readInt16Be();
292 14
        } elseif ($size === self::UNSIGNED_INT24_LENGTH) {
293 7
            return $this->readInt24Be();
294 7
        } elseif ($size === self::UNSIGNED_INT32_LENGTH) {
295 1
            return $this->readInt32Be();
296 6
        } elseif ($size === self::UNSIGNED_INT40_LENGTH) {
297 5
            return $this->readInt40Be();
298
        }
299
300 1
        throw new BinaryDataReaderException('$size ' . $size . ' not handled');
301
    }
302
303
    /**
304
     * @return int
305
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
306
     */
307 11
    public function readInt8()
308
    {
309 11
        return unpack('c', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
310
    }
311
312
    /**
313
     * @return mixed
314
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
315
     */
316 2
    public function readInt16Be()
317
    {
318 2
        return unpack('n', $this->read(self::UNSIGNED_SHORT_LENGTH))[1];
319
    }
320
321
    /**
322
     * @return int
323
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
324
     */
325 9 View Code Duplication
    public function readInt24Be()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
326
    {
327 9
        $data = unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH));
328 9
        $res = ($data[1] << 16) | ($data[2] << 8) | $data[3];
329 9
        if ($res >= 0x800000) {
330 5
            $res -= 0x1000000;
331 5
        }
332
333 9
        return $res;
334
    }
335
336
    /**
337
     * @return int
338
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
339
     */
340 12
    public function readInt32Be()
341
    {
342 12
        return unpack('i', strrev($this->read(self::UNSIGNED_INT32_LENGTH)))[1];
343
    }
344
345
    /**
346
     * @return int
347
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
348
     */
349 5 View Code Duplication
    public function readInt40Be()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
350
    {
351 5
        $data1 = unpack('N', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
352 5
        $data2 = unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
353
354 5
        return $data2 + ($data1 << 8);
355
    }
356
357
    /**
358
     * @return int
359
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
360
     */
361 55
    public function readInt32()
362
    {
363 55
        return unpack('i', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
364
    }
365
366
    /**
367
     * @return float
368
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
369
     */
370 2
    public function readFloat()
371
    {
372 2
        return unpack('f', $this->read(self::UNSIGNED_FLOAT_LENGTH))[1];
373
    }
374
375
    /**
376
     * @return double
377
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
378
     */
379 2
    public function readDouble()
380
    {
381 2
        return unpack('d', $this->read(self::UNSIGNED_DOUBLE_LENGTH))[1];
382
    }
383
384
    /**
385
     * @return string
386
     * @throws \MySQLReplication\BinaryDataReader\BinaryDataReaderException
387
     */
388 53
    public function readTableId()
389
    {
390 53
        return $this->unpackUInt64($this->read(self::UNSIGNED_INT48_LENGTH) . chr(0) . chr(0));
391
    }
392
393
    /**
394
     * @param int $size
395
     * @return bool
396
     */
397 52
    public function isComplete($size)
398
    {
399 52
        return !($this->readBytes + 1 - 20 < $size);
400
    }
401
402
    /**
403
     * @param int $value
404
     * @return string
405
     */
406 3
    public static function pack64bit($value)
407
    {
408 3
        return pack(
409 3
            'C8', ($value >> 0) & 0xFF, ($value >> 8) & 0xFF, ($value >> 16) & 0xFF, ($value >> 24) & 0xFF,
410 3
            ($value >> 32) & 0xFF, ($value >> 40) & 0xFF, ($value >> 48) & 0xFF, ($value >> 56) & 0xFF
411 3
        );
412
    }
413
414
    /**
415
     * @return int
416
     */
417
    public function getBinaryDataLength()
418
    {
419
        return strlen($this->data);
420
    }
421
422
    /**
423
     * Read a part of binary data and extract a number
424
     *
425
     * @param int $binary
426
     * @param int $start
427
     * @param int $size
428
     * @param int $binaryLength
429
     * @return int
430
     */
431 6
    public function getBinarySlice($binary, $start, $size, $binaryLength)
432
    {
433 6
        $binary >>= $binaryLength - ($start + $size);
434 6
        $mask = ((1 << $size) - 1);
435
436 6
        return $binary & $mask;
437
    }
438
}