Passed
Pull Request — master (#53)
by kacper
03:07
created

BinaryDataReader::readLengthString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace MySQLReplication\BinaryDataReader;
5
6
class BinaryDataReader
7
{
8
    public const NULL_COLUMN = 251;
9
    public const UNSIGNED_CHAR_COLUMN = 251;
10
    public const UNSIGNED_SHORT_COLUMN = 252;
11
    public const UNSIGNED_INT24_COLUMN = 253;
12
    public const UNSIGNED_INT64_COLUMN = 254;
13
    public const UNSIGNED_CHAR_LENGTH = 1;
14
    public const UNSIGNED_SHORT_LENGTH = 2;
15
    public const UNSIGNED_INT24_LENGTH = 3;
16
    public const UNSIGNED_INT32_LENGTH = 4;
17
    public const UNSIGNED_FLOAT_LENGTH = 4;
18
    public const UNSIGNED_DOUBLE_LENGTH = 8;
19
    public const UNSIGNED_INT40_LENGTH = 5;
20
    public const UNSIGNED_INT48_LENGTH = 6;
21
    public const UNSIGNED_INT56_LENGTH = 7;
22
    public const UNSIGNED_INT64_LENGTH = 8;
23
24
    private $data;
25
26
    /**
27
     * @var int
28
     */
29
    private $readBytes = 0;
30
31 86
    public function __construct(string $data)
32
    {
33 86
        $this->data = $data;
34 86
    }
35
36 3
    public static function pack64bit(int $value): string
37
    {
38 3
        return pack(
39 3
            'C8', ($value >> 0) & 0xFF, ($value >> 8) & 0xFF, ($value >> 16) & 0xFF, ($value >> 24) & 0xFF,
40 3
            ($value >> 32) & 0xFF, ($value >> 40) & 0xFF, ($value >> 48) & 0xFF, ($value >> 56) & 0xFF
41
        );
42
    }
43
44 58
    public function advance(int $length): void
45
    {
46 58
        $this->readBytes += $length;
47 58
        $this->data = substr($this->data, $length);
48 58
    }
49
50 8
    public function readInt16(): int
51
    {
52 8
        return unpack('s', $this->read(self::UNSIGNED_SHORT_LENGTH))[1];
53
    }
54
55 81
    public function read(int $length): string
56
    {
57 81
        $return = substr($this->data, 0, $length);
58 81
        $this->readBytes += $length;
59 81
        $this->data = substr($this->data, $length);
60
61 81
        return $return;
62
    }
63
64 11
    public function unread(string $data): void
65
    {
66 11
        $this->readBytes -= strlen($data);
67 11
        $this->data = $data . $this->data;
68 11
    }
69
70
    /**
71
     * @throws BinaryDataReaderException
72
     */
73 56
    public function readCodedBinary(): ?int
74
    {
75 56
        $c = ord($this->read(self::UNSIGNED_CHAR_LENGTH));
76 56
        if ($c === self::NULL_COLUMN) {
77 1
            return null;
78
        }
79 56
        if ($c < self::UNSIGNED_CHAR_COLUMN) {
80 55
            return $c;
81
        }
82 2
        if ($c === self::UNSIGNED_SHORT_COLUMN) {
83 1
            return $this->readUInt16();
84
        }
85 2
        if ($c === self::UNSIGNED_INT24_COLUMN) {
86 1
            return $this->readUInt24();
87
        }
88
89 1
        throw new BinaryDataReaderException('Column num ' . $c . ' not handled');
90
    }
91
92 58
    public function readUInt16(): int
93
    {
94 58
        return unpack('v', $this->read(self::UNSIGNED_SHORT_LENGTH))[1];
95
    }
96
97 8
    public function readUInt24(): int
98
    {
99 8
        $data = unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH));
100
101 8
        return $data[1] + ($data[2] << 8) + ($data[3] << 16);
102
    }
103
104 5
    public function readUInt64(): string
105
    {
106 5
        return $this->unpackUInt64($this->read(self::UNSIGNED_INT64_LENGTH));
107
    }
108
109 56
    public function unpackUInt64(string $binary): string
110
    {
111 56
        $data = unpack('V*', $binary);
112
113 56
        return bcadd((string)$data[1], bcmul((string)$data[2], bcpow('2', '32')));
114
    }
115
116 2
    public function readInt24(): int
117
    {
118 2
        $data = unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH));
119
120 2
        $res = $data[1] | ($data[2] << 8) | ($data[3] << 16);
121 2
        if ($res >= 0x800000) {
122 2
            $res -= 0x1000000;
123
        }
124
125 2
        return $res;
126
    }
127
128 3
    public function readInt64(): string
129
    {
130 3
        $data = unpack('V*', $this->read(self::UNSIGNED_INT64_LENGTH));
131
132 3
        return bcadd((string)$data[1], (string)($data[2] << 32));
133
    }
134
135
    /**
136
     * @throws BinaryDataReaderException
137
     */
138 14
    public function readLengthString(int $size): string
139
    {
140 14
        return $this->read($this->readUIntBySize($size));
141
    }
142
143
    /**
144
     * @throws BinaryDataReaderException
145
     */
146 24
    public function readUIntBySize(int $size): int
147
    {
148 24
        if ($size === self::UNSIGNED_CHAR_LENGTH) {
149 12
            return $this->readUInt8();
150
        }
151 12
        if ($size === self::UNSIGNED_SHORT_LENGTH) {
152 3
            return $this->readUInt16();
153
        }
154 9
        if ($size === self::UNSIGNED_INT24_LENGTH) {
155 2
            return $this->readUInt24();
156
        }
157 7
        if ($size === self::UNSIGNED_INT32_LENGTH) {
158 3
            return $this->readUInt32();
159
        }
160 4
        if ($size === self::UNSIGNED_INT40_LENGTH) {
161 1
            return $this->readUInt40();
162
        }
163 3
        if ($size === self::UNSIGNED_INT48_LENGTH) {
164 1
            return $this->readUInt48();
165
        }
166 2
        if ($size === self::UNSIGNED_INT56_LENGTH) {
167 1
            return $this->readUInt56();
168
        }
169
170 1
        throw new BinaryDataReaderException('$size ' . $size . ' not handled');
171
    }
172
173 58
    public function readUInt8(): int
174
    {
175 58
        return unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
176
    }
177
178 57
    public function readUInt32(): int
179
    {
180 57
        return unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
181
    }
182
183 1
    public function readUInt40(): int
184
    {
185 1
        $data1 = unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
186 1
        $data2 = unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
187
188 1
        return $data1 + ($data2 << 8);
189
    }
190
191 1
    public function readUInt48(): int
192
    {
193 1
        $data = unpack('v3', $this->read(self::UNSIGNED_INT48_LENGTH));
194
195 1
        return $data[1] + ($data[2] << 16) + ($data[3] << 32);
196
    }
197
198 1
    public function readUInt56(): int
199
    {
200 1
        $data1 = unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
201 1
        $data2 = unpack('S', $this->read(self::UNSIGNED_SHORT_LENGTH))[1];
202 1
        $data3 = unpack('I', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
203
204 1
        return $data1 + ($data2 << 8) + ($data3 << 24);
205
    }
206
207
    /**
208
     * @throws BinaryDataReaderException
209
     */
210 21
    public function readIntBeBySize(int $size): int
211
    {
212 21
        if ($size === self::UNSIGNED_CHAR_LENGTH) {
213 7
            return $this->readInt8();
214
        }
215 15
        if ($size === self::UNSIGNED_SHORT_LENGTH) {
216 2
            return $this->readInt16Be();
217
        }
218 14
        if ($size === self::UNSIGNED_INT24_LENGTH) {
219 7
            return $this->readInt24Be();
220
        }
221 7
        if ($size === self::UNSIGNED_INT32_LENGTH) {
222 1
            return $this->readInt32Be();
223
        }
224 6
        if ($size === self::UNSIGNED_INT40_LENGTH) {
225 5
            return $this->readInt40Be();
226
        }
227
228 1
        throw new BinaryDataReaderException('$size ' . $size . ' not handled');
229
    }
230
231 11
    public function readInt8(): int
232
    {
233 11
        return unpack('c', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
234
    }
235
236 2
    public function readInt16Be(): int
237
    {
238 2
        return unpack('n', $this->read(self::UNSIGNED_SHORT_LENGTH))[1];
239
    }
240
241 9
    public function readInt24Be(): int
242
    {
243 9
        $data = unpack('C3', $this->read(self::UNSIGNED_INT24_LENGTH));
244 9
        $res = ($data[1] << 16) | ($data[2] << 8) | $data[3];
245 9
        if ($res >= 0x800000) {
246 5
            $res -= 0x1000000;
247
        }
248
249 9
        return $res;
250
    }
251
252 12
    public function readInt32Be(): int
253
    {
254 12
        return unpack('i', strrev($this->read(self::UNSIGNED_INT32_LENGTH)))[1];
255
    }
256
257 5
    public function readInt40Be(): int
258
    {
259 5
        $data1 = unpack('N', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
260 5
        $data2 = unpack('C', $this->read(self::UNSIGNED_CHAR_LENGTH))[1];
261
262 5
        return $data2 + ($data1 << 8);
263
    }
264
265 57
    public function readInt32(): int
266
    {
267 57
        return unpack('i', $this->read(self::UNSIGNED_INT32_LENGTH))[1];
268
    }
269
270 2
    public function readFloat(): float
271
    {
272 2
        return unpack('f', $this->read(self::UNSIGNED_FLOAT_LENGTH))[1];
273
    }
274
275 2
    public function readDouble(): float
276
    {
277 2
        return unpack('d', $this->read(self::UNSIGNED_DOUBLE_LENGTH))[1];
278
    }
279
280 55
    public function readTableId(): string
281
    {
282 55
        return $this->unpackUInt64($this->read(self::UNSIGNED_INT48_LENGTH) . chr(0) . chr(0));
283
    }
284
285 53
    public function isComplete(int $size): bool
286
    {
287 53
        return !($this->readBytes - 20 < $size);
288
    }
289
290 1
    public function getBinaryDataLength(): int
291
    {
292 1
        return strlen($this->data);
293
    }
294
295 6
    public function getBinarySlice(int $binary, int $start, int $size, int $binaryLength): int
296
    {
297 6
        $binary >>= $binaryLength - ($start + $size);
298 6
        $mask = ((1 << $size) - 1);
299
300 6
        return $binary & $mask;
301
    }
302
}