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() |
|
|
|
|
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() |
|
|
|
|
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() |
|
|
|
|
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() |
|
|
|
|
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
|
|
|
} |
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.