Completed
Push — master ( 15c40a...010522 )
by kacper
01:57
created

BinaryDataReader::getBinaryDataLength()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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