1 | <?php |
||
2 | |||
3 | /** |
||
4 | * This file is part of the rybakit/msgpack.php package. |
||
5 | * |
||
6 | * (c) Eugene Leonovich <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | namespace MessagePack; |
||
13 | |||
14 | use Decimal\Decimal; |
||
15 | use MessagePack\Exception\InsufficientDataException; |
||
16 | use MessagePack\Exception\InvalidOptionException; |
||
17 | use MessagePack\Exception\UnpackingFailedException; |
||
18 | use MessagePack\Type\Ext; |
||
19 | |||
20 | class BufferUnpacker |
||
21 | { |
||
22 | /** @var string */ |
||
23 | private $buffer; |
||
24 | |||
25 | /** @var int */ |
||
26 | private $offset = 0; |
||
27 | |||
28 | /** @var bool */ |
||
29 | private $isBigIntAsDec; |
||
30 | |||
31 | /** @var bool */ |
||
32 | private $isBigIntAsGmp; |
||
33 | |||
34 | /** @var Extension[] */ |
||
35 | private $extensions = []; |
||
36 | |||
37 | /** |
||
38 | * @param UnpackOptions|int|null $options |
||
39 | * @param Extension[] $extensions |
||
40 | * |
||
41 | * @throws InvalidOptionException |
||
42 | */ |
||
43 | 346 | public function __construct(string $buffer = '', $options = null, array $extensions = []) |
|
44 | { |
||
45 | 346 | if (\is_null($options)) { |
|
46 | 345 | $options = UnpackOptions::fromDefaults(); |
|
47 | 11 | } elseif (!$options instanceof UnpackOptions) { |
|
48 | 10 | $options = UnpackOptions::fromBitmask($options); |
|
49 | } |
||
50 | |||
51 | 346 | $this->isBigIntAsDec = $options->isBigIntAsDecMode(); |
|
52 | 346 | $this->isBigIntAsGmp = $options->isBigIntAsGmpMode(); |
|
53 | |||
54 | 346 | $this->buffer = $buffer; |
|
55 | |||
56 | 346 | if ($extensions) { |
|
57 | 341 | foreach ($extensions as $extension) { |
|
58 | 341 | $this->extensions[$extension->getType()] = $extension; |
|
59 | } |
||
60 | } |
||
61 | } |
||
62 | |||
63 | 1 | public function extendWith(Extension $extension, Extension ...$extensions) : self |
|
64 | { |
||
65 | 1 | $new = clone $this; |
|
66 | 1 | $new->extensions[$extension->getType()] = $extension; |
|
67 | |||
68 | 1 | if ($extensions) { |
|
0 ignored issues
–
show
|
|||
69 | 1 | foreach ($extensions as $extraExtension) { |
|
70 | 1 | $new->extensions[$extraExtension->getType()] = $extraExtension; |
|
71 | } |
||
72 | } |
||
73 | |||
74 | 1 | return $new; |
|
75 | } |
||
76 | |||
77 | 2 | public function withBuffer(string $buffer) : self |
|
78 | { |
||
79 | 2 | $new = clone $this; |
|
80 | 2 | $new->buffer = $buffer; |
|
81 | 2 | $new->offset = 0; |
|
82 | |||
83 | 2 | return $new; |
|
84 | } |
||
85 | |||
86 | 10 | public function append(string $data) : self |
|
87 | { |
||
88 | 10 | $this->buffer .= $data; |
|
89 | |||
90 | 10 | return $this; |
|
91 | } |
||
92 | |||
93 | 309 | public function reset(string $buffer = '') : self |
|
94 | { |
||
95 | 309 | $this->buffer = $buffer; |
|
96 | 309 | $this->offset = 0; |
|
97 | |||
98 | 309 | return $this; |
|
99 | } |
||
100 | |||
101 | 3 | public function seek(int $offset) : self |
|
102 | { |
||
103 | 3 | if ($offset < 0) { |
|
104 | 1 | $offset += \strlen($this->buffer); |
|
105 | } |
||
106 | |||
107 | 3 | if (!isset($this->buffer[$offset])) { |
|
108 | 1 | throw new InsufficientDataException("Unable to seek to position $offset"); |
|
109 | } |
||
110 | |||
111 | 2 | $this->offset = $offset; |
|
112 | |||
113 | 2 | return $this; |
|
114 | } |
||
115 | |||
116 | /** |
||
117 | * @param positive-int $length |
||
0 ignored issues
–
show
|
|||
118 | */ |
||
119 | 2 | public function skip(int $length) : self |
|
120 | { |
||
121 | 2 | $offset = $this->offset + $length; |
|
122 | |||
123 | 2 | if (!isset($this->buffer[$offset])) { |
|
124 | 1 | throw new InsufficientDataException("Unable to seek to position $offset"); |
|
125 | } |
||
126 | |||
127 | 1 | $this->offset = $offset; |
|
128 | |||
129 | 1 | return $this; |
|
130 | } |
||
131 | |||
132 | 2 | public function getRemainingCount() : int |
|
133 | { |
||
134 | 2 | return \strlen($this->buffer) - $this->offset; |
|
135 | } |
||
136 | |||
137 | 186 | public function hasRemaining() : bool |
|
138 | { |
||
139 | 186 | return isset($this->buffer[$this->offset]); |
|
140 | } |
||
141 | |||
142 | 1 | public function release() : int |
|
143 | { |
||
144 | 1 | if (0 === $this->offset) { |
|
145 | 1 | return 0; |
|
146 | } |
||
147 | |||
148 | 1 | $releasedBytesCount = $this->offset; |
|
149 | 1 | $this->buffer = isset($this->buffer[$this->offset]) ? \substr($this->buffer, $this->offset) : ''; |
|
150 | 1 | $this->offset = 0; |
|
151 | |||
152 | 1 | return $releasedBytesCount; |
|
153 | } |
||
154 | |||
155 | /** |
||
156 | * @param int $length |
||
157 | * |
||
158 | * @return string |
||
159 | */ |
||
160 | 58 | public function read($length) |
|
161 | { |
||
162 | 58 | if (!isset($this->buffer[$this->offset + $length - 1])) { |
|
163 | 1 | throw new InsufficientDataException(); |
|
164 | } |
||
165 | |||
166 | 58 | $data = \substr($this->buffer, $this->offset, $length); |
|
167 | 58 | $this->offset += $length; |
|
168 | |||
169 | 58 | return $data; |
|
170 | } |
||
171 | |||
172 | 3 | public function tryUnpack() : array |
|
173 | { |
||
174 | 3 | $data = []; |
|
175 | 3 | $offset = $this->offset; |
|
176 | |||
177 | try { |
||
178 | do { |
||
179 | 3 | $data[] = $this->unpack(); |
|
180 | 3 | $offset = $this->offset; |
|
181 | 3 | } while (isset($this->buffer[$this->offset])); |
|
182 | 1 | } catch (InsufficientDataException $e) { |
|
183 | 1 | $this->offset = $offset; |
|
184 | } |
||
185 | |||
186 | 3 | return $data; |
|
187 | } |
||
188 | |||
189 | /** |
||
190 | * @return mixed |
||
191 | */ |
||
192 | 210 | public function unpack() |
|
193 | { |
||
194 | 210 | if (!isset($this->buffer[$this->offset])) { |
|
195 | 4 | throw new InsufficientDataException(); |
|
196 | } |
||
197 | |||
198 | 208 | $c = \ord($this->buffer[$this->offset]); |
|
199 | 208 | ++$this->offset; |
|
200 | |||
201 | // fixint |
||
202 | 208 | if ($c <= 0x7f) { |
|
203 | 35 | return $c; |
|
204 | } |
||
205 | // fixstr |
||
206 | 195 | if ($c >= 0xa0 && $c <= 0xbf) { |
|
207 | 16 | return ($c & 0x1f) ? $this->read($c & 0x1f) : ''; |
|
208 | } |
||
209 | // negfixint |
||
210 | 189 | if ($c >= 0xe0) { |
|
211 | 5 | return $c - 0x100; |
|
212 | } |
||
213 | |||
214 | switch ($c) { |
||
215 | 185 | case 0xc0: return null; |
|
216 | 183 | case 0xc2: return false; |
|
217 | 179 | case 0xc3: return true; |
|
218 | // fixmap |
||
219 | 171 | case 0x80: return []; |
|
220 | 170 | case 0x81: return [$this->unpackMapKey() => $this->unpack()]; |
|
221 | 135 | case 0x82: return [$this->unpackMapKey() => $this->unpack(), $this->unpackMapKey() => $this->unpack()]; |
|
222 | 134 | case 0x83: return [$this->unpackMapKey() => $this->unpack(), $this->unpackMapKey() => $this->unpack(), $this->unpackMapKey() => $this->unpack()]; |
|
223 | 132 | case 0x84: return $this->unpackMapData(4); |
|
224 | 132 | case 0x85: return $this->unpackMapData(5); |
|
225 | 132 | case 0x86: return $this->unpackMapData(6); |
|
226 | 132 | case 0x87: return $this->unpackMapData(7); |
|
227 | 132 | case 0x88: return $this->unpackMapData(8); |
|
228 | 132 | case 0x89: return $this->unpackMapData(9); |
|
229 | 132 | case 0x8a: return $this->unpackMapData(10); |
|
230 | 132 | case 0x8b: return $this->unpackMapData(11); |
|
231 | 132 | case 0x8c: return $this->unpackMapData(12); |
|
232 | 132 | case 0x8d: return $this->unpackMapData(13); |
|
233 | 132 | case 0x8e: return $this->unpackMapData(14); |
|
234 | 132 | case 0x8f: return $this->unpackMapData(15); |
|
235 | // fixarray |
||
236 | 132 | case 0x90: return []; |
|
237 | 131 | case 0x91: return [$this->unpack()]; |
|
238 | 131 | case 0x92: return [$this->unpack(), $this->unpack()]; |
|
239 | 126 | case 0x93: return [$this->unpack(), $this->unpack(), $this->unpack()]; |
|
240 | 124 | case 0x94: return $this->unpackArrayData(4); |
|
241 | 124 | case 0x95: return $this->unpackArrayData(5); |
|
242 | 124 | case 0x96: return $this->unpackArrayData(6); |
|
243 | 124 | case 0x97: return $this->unpackArrayData(7); |
|
244 | 124 | case 0x98: return $this->unpackArrayData(8); |
|
245 | 124 | case 0x99: return $this->unpackArrayData(9); |
|
246 | 124 | case 0x9a: return $this->unpackArrayData(10); |
|
247 | 124 | case 0x9b: return $this->unpackArrayData(11); |
|
248 | 124 | case 0x9c: return $this->unpackArrayData(12); |
|
249 | 124 | case 0x9d: return $this->unpackArrayData(13); |
|
250 | 124 | case 0x9e: return $this->unpackArrayData(14); |
|
251 | 124 | case 0x9f: return $this->unpackArrayData(15); |
|
252 | // bin |
||
253 | 124 | case 0xc4: return $this->read($this->unpackUint8()); |
|
254 | 118 | case 0xc5: return $this->read($this->unpackUint16()); |
|
255 | 117 | case 0xc6: return $this->read($this->unpackUint32()); |
|
256 | // float |
||
257 | 116 | case 0xca: return $this->unpackFloat32(); |
|
258 | 113 | case 0xcb: return $this->unpackFloat64(); |
|
259 | // uint |
||
260 | 109 | case 0xcc: return $this->unpackUint8(); |
|
261 | 105 | case 0xcd: return $this->unpackUint16(); |
|
262 | 99 | case 0xce: return $this->unpackUint32(); |
|
263 | 95 | case 0xcf: return $this->unpackUint64(); |
|
264 | // int |
||
265 | 85 | case 0xd0: return $this->unpackInt8(); |
|
266 | 80 | case 0xd1: return $this->unpackInt16(); |
|
267 | 75 | case 0xd2: return $this->unpackInt32(); |
|
268 | 70 | case 0xd3: return $this->unpackInt64(); |
|
269 | // str |
||
270 | 62 | case 0xd9: return $this->read($this->unpackUint8()); |
|
271 | 58 | case 0xda: return $this->read($this->unpackUint16()); |
|
272 | 56 | case 0xdb: return $this->read($this->unpackUint32()); |
|
273 | // array |
||
274 | 55 | case 0xdc: return $this->unpackArrayData($this->unpackUint16()); |
|
275 | 53 | case 0xdd: return $this->unpackArrayData($this->unpackUint32()); |
|
276 | // map |
||
277 | 52 | case 0xde: return $this->unpackMapData($this->unpackUint16()); |
|
278 | 50 | case 0xdf: return $this->unpackMapData($this->unpackUint32()); |
|
279 | // ext |
||
280 | 45 | case 0xd4: return $this->unpackExtData(1); |
|
281 | 39 | case 0xd5: return $this->unpackExtData(2); |
|
282 | 36 | case 0xd6: return $this->unpackExtData(4); |
|
283 | 30 | case 0xd7: return $this->unpackExtData(8); |
|
284 | 24 | case 0xd8: return $this->unpackExtData(16); |
|
285 | 21 | case 0xc7: return $this->unpackExtData($this->unpackUint8()); |
|
286 | 11 | case 0xc8: return $this->unpackExtData($this->unpackUint16()); |
|
287 | 6 | case 0xc9: return $this->unpackExtData($this->unpackUint32()); |
|
288 | } |
||
289 | |||
290 | 1 | throw UnpackingFailedException::unknownCode($c); |
|
291 | } |
||
292 | |||
293 | /** |
||
294 | * @return null |
||
295 | */ |
||
296 | 3 | public function unpackNil() |
|
297 | { |
||
298 | 3 | if (!isset($this->buffer[$this->offset])) { |
|
299 | 1 | throw new InsufficientDataException(); |
|
300 | } |
||
301 | |||
302 | 2 | if ("\xc0" === $this->buffer[$this->offset]) { |
|
303 | 1 | ++$this->offset; |
|
304 | |||
305 | 1 | return null; |
|
306 | } |
||
307 | |||
308 | 1 | throw UnpackingFailedException::unexpectedCode(\ord($this->buffer[$this->offset++]), 'nil'); |
|
309 | } |
||
310 | |||
311 | /** |
||
312 | * @return bool |
||
313 | */ |
||
314 | 5 | public function unpackBool() |
|
315 | { |
||
316 | 5 | if (!isset($this->buffer[$this->offset])) { |
|
317 | 1 | throw new InsufficientDataException(); |
|
318 | } |
||
319 | |||
320 | 4 | $c = \ord($this->buffer[$this->offset]); |
|
321 | 4 | ++$this->offset; |
|
322 | |||
323 | 4 | if (0xc2 === $c) { |
|
324 | 2 | return false; |
|
325 | } |
||
326 | 2 | if (0xc3 === $c) { |
|
327 | 1 | return true; |
|
328 | } |
||
329 | |||
330 | 1 | throw UnpackingFailedException::unexpectedCode($c, 'bool'); |
|
331 | } |
||
332 | |||
333 | /** |
||
334 | * @return Decimal|\GMP|int|string |
||
335 | */ |
||
336 | 40 | public function unpackInt() |
|
337 | { |
||
338 | 40 | if (!isset($this->buffer[$this->offset])) { |
|
339 | 1 | throw new InsufficientDataException(); |
|
340 | } |
||
341 | |||
342 | 39 | $c = \ord($this->buffer[$this->offset]); |
|
343 | 39 | ++$this->offset; |
|
344 | |||
345 | // fixint |
||
346 | 39 | if ($c <= 0x7f) { |
|
347 | 3 | return $c; |
|
348 | } |
||
349 | // negfixint |
||
350 | 36 | if ($c >= 0xe0) { |
|
351 | 3 | return $c - 0x100; |
|
352 | } |
||
353 | |||
354 | switch ($c) { |
||
355 | // uint |
||
356 | 33 | case 0xcc: return $this->unpackUint8(); |
|
357 | 30 | case 0xcd: return $this->unpackUint16(); |
|
358 | 27 | case 0xce: return $this->unpackUint32(); |
|
359 | 24 | case 0xcf: return $this->unpackUint64(); |
|
360 | // int |
||
361 | 20 | case 0xd0: return $this->unpackInt8(); |
|
362 | 16 | case 0xd1: return $this->unpackInt16(); |
|
363 | 12 | case 0xd2: return $this->unpackInt32(); |
|
364 | 8 | case 0xd3: return $this->unpackInt64(); |
|
365 | } |
||
366 | |||
367 | 1 | throw UnpackingFailedException::unexpectedCode($c, 'int'); |
|
368 | } |
||
369 | |||
370 | /** |
||
371 | * @return float |
||
372 | */ |
||
373 | 7 | public function unpackFloat() |
|
374 | { |
||
375 | 7 | if (!isset($this->buffer[$this->offset])) { |
|
376 | 1 | throw new InsufficientDataException(); |
|
377 | } |
||
378 | |||
379 | 6 | $c = \ord($this->buffer[$this->offset]); |
|
380 | 6 | ++$this->offset; |
|
381 | |||
382 | 6 | if (0xcb === $c) { |
|
383 | 3 | return $this->unpackFloat64(); |
|
384 | } |
||
385 | 3 | if (0xca === $c) { |
|
386 | 2 | return $this->unpackFloat32(); |
|
387 | } |
||
388 | |||
389 | 1 | throw UnpackingFailedException::unexpectedCode($c, 'float'); |
|
390 | } |
||
391 | |||
392 | /** |
||
393 | * @return string |
||
394 | */ |
||
395 | 14 | public function unpackStr() |
|
396 | { |
||
397 | 14 | if (!isset($this->buffer[$this->offset])) { |
|
398 | 1 | throw new InsufficientDataException(); |
|
399 | } |
||
400 | |||
401 | 13 | $c = \ord($this->buffer[$this->offset]); |
|
402 | 13 | ++$this->offset; |
|
403 | |||
404 | 13 | if ($c >= 0xa0 && $c <= 0xbf) { |
|
405 | 5 | return ($c & 0x1f) ? $this->read($c & 0x1f) : ''; |
|
406 | } |
||
407 | 8 | if (0xd9 === $c) { |
|
408 | 4 | return $this->read($this->unpackUint8()); |
|
409 | } |
||
410 | 4 | if (0xda === $c) { |
|
411 | 2 | return $this->read($this->unpackUint16()); |
|
412 | } |
||
413 | 2 | if (0xdb === $c) { |
|
414 | 1 | return $this->read($this->unpackUint32()); |
|
415 | } |
||
416 | |||
417 | 1 | throw UnpackingFailedException::unexpectedCode($c, 'str'); |
|
418 | } |
||
419 | |||
420 | /** |
||
421 | * @return string |
||
422 | */ |
||
423 | 7 | public function unpackBin() |
|
424 | { |
||
425 | 7 | if (!isset($this->buffer[$this->offset])) { |
|
426 | 1 | throw new InsufficientDataException(); |
|
427 | } |
||
428 | |||
429 | 6 | $c = \ord($this->buffer[$this->offset]); |
|
430 | 6 | ++$this->offset; |
|
431 | |||
432 | 6 | if (0xc4 === $c) { |
|
433 | 3 | return $this->read($this->unpackUint8()); |
|
434 | } |
||
435 | 3 | if (0xc5 === $c) { |
|
436 | 1 | return $this->read($this->unpackUint16()); |
|
437 | } |
||
438 | 2 | if (0xc6 === $c) { |
|
439 | 1 | return $this->read($this->unpackUint32()); |
|
440 | } |
||
441 | |||
442 | 1 | throw UnpackingFailedException::unexpectedCode($c, 'bin'); |
|
443 | } |
||
444 | |||
445 | /** |
||
446 | * @return array |
||
447 | */ |
||
448 | 9 | public function unpackArray() |
|
449 | { |
||
450 | 9 | $size = $this->unpackArrayHeader(); |
|
451 | |||
452 | 7 | $array = []; |
|
453 | 7 | while ($size--) { |
|
454 | 6 | $array[] = $this->unpack(); |
|
455 | } |
||
456 | |||
457 | 7 | return $array; |
|
458 | } |
||
459 | |||
460 | /** |
||
461 | * @return int |
||
462 | */ |
||
463 | 9 | public function unpackArrayHeader() |
|
464 | { |
||
465 | 9 | if (!isset($this->buffer[$this->offset])) { |
|
466 | 1 | throw new InsufficientDataException(); |
|
467 | } |
||
468 | |||
469 | 8 | $c = \ord($this->buffer[$this->offset]); |
|
470 | 8 | ++$this->offset; |
|
471 | |||
472 | 8 | if ($c >= 0x90 && $c <= 0x9f) { |
|
473 | 4 | return $c & 0xf; |
|
474 | } |
||
475 | 4 | if (0xdc === $c) { |
|
476 | 2 | return $this->unpackUint16(); |
|
477 | } |
||
478 | 2 | if (0xdd === $c) { |
|
479 | 1 | return $this->unpackUint32(); |
|
480 | } |
||
481 | |||
482 | 1 | throw UnpackingFailedException::unexpectedCode($c, 'array'); |
|
483 | } |
||
484 | |||
485 | /** |
||
486 | * @return array |
||
487 | */ |
||
488 | 46 | public function unpackMap() |
|
489 | { |
||
490 | 46 | $size = $this->unpackMapHeader(); |
|
491 | |||
492 | 44 | $map = []; |
|
493 | 44 | while ($size--) { |
|
494 | 43 | $map[$this->unpackMapKey()] = $this->unpack(); |
|
495 | } |
||
496 | |||
497 | 10 | return $map; |
|
498 | } |
||
499 | |||
500 | /** |
||
501 | * @return int |
||
502 | */ |
||
503 | 46 | public function unpackMapHeader() |
|
504 | { |
||
505 | 46 | if (!isset($this->buffer[$this->offset])) { |
|
506 | 1 | throw new InsufficientDataException(); |
|
507 | } |
||
508 | |||
509 | 45 | $c = \ord($this->buffer[$this->offset]); |
|
510 | 45 | ++$this->offset; |
|
511 | |||
512 | 45 | if ($c >= 0x80 && $c <= 0x8f) { |
|
513 | 40 | return $c & 0xf; |
|
514 | } |
||
515 | 5 | if (0xde === $c) { |
|
516 | 2 | return $this->unpackUint16(); |
|
517 | } |
||
518 | 3 | if (0xdf === $c) { |
|
519 | 2 | return $this->unpackUint32(); |
|
520 | } |
||
521 | |||
522 | 1 | throw UnpackingFailedException::unexpectedCode($c, 'map'); |
|
523 | } |
||
524 | |||
525 | /** |
||
526 | * @return mixed |
||
527 | */ |
||
528 | 13 | public function unpackExt() |
|
529 | { |
||
530 | 13 | if (!isset($this->buffer[$this->offset])) { |
|
531 | 1 | throw new InsufficientDataException(); |
|
532 | } |
||
533 | |||
534 | 12 | $c = \ord($this->buffer[$this->offset]); |
|
535 | 12 | ++$this->offset; |
|
536 | |||
537 | switch ($c) { |
||
538 | 12 | case 0xd4: return $this->unpackExtData(1); |
|
539 | 11 | case 0xd5: return $this->unpackExtData(2); |
|
540 | 10 | case 0xd6: return $this->unpackExtData(4); |
|
541 | 9 | case 0xd7: return $this->unpackExtData(8); |
|
542 | 8 | case 0xd8: return $this->unpackExtData(16); |
|
543 | 7 | case 0xc7: return $this->unpackExtData($this->unpackUint8()); |
|
544 | 5 | case 0xc8: return $this->unpackExtData($this->unpackUint16()); |
|
545 | 3 | case 0xc9: return $this->unpackExtData($this->unpackUint32()); |
|
546 | } |
||
547 | |||
548 | 1 | throw UnpackingFailedException::unexpectedCode($c, 'ext'); |
|
549 | } |
||
550 | |||
551 | /** |
||
552 | * @return int |
||
553 | */ |
||
554 | 42 | private function unpackUint8() |
|
555 | { |
||
556 | 42 | if (!isset($this->buffer[$this->offset])) { |
|
557 | 2 | throw new InsufficientDataException(); |
|
558 | } |
||
559 | |||
560 | 40 | return \ord($this->buffer[$this->offset++]); |
|
561 | } |
||
562 | |||
563 | /** |
||
564 | * @return int |
||
565 | */ |
||
566 | 32 | private function unpackUint16() |
|
567 | { |
||
568 | 32 | if (!isset($this->buffer[$this->offset + 1])) { |
|
569 | 2 | throw new InsufficientDataException(); |
|
570 | } |
||
571 | |||
572 | 30 | return \ord($this->buffer[$this->offset++]) << 8 |
|
573 | 30 | | \ord($this->buffer[$this->offset++]); |
|
574 | } |
||
575 | |||
576 | /** |
||
577 | * @return int |
||
578 | */ |
||
579 | 29 | private function unpackUint32() |
|
580 | { |
||
581 | 29 | if (!isset($this->buffer[$this->offset + 3])) { |
|
582 | 2 | throw new InsufficientDataException(); |
|
583 | } |
||
584 | |||
585 | 27 | return \ord($this->buffer[$this->offset++]) << 24 |
|
586 | 27 | | \ord($this->buffer[$this->offset++]) << 16 |
|
587 | 27 | | \ord($this->buffer[$this->offset++]) << 8 |
|
588 | 27 | | \ord($this->buffer[$this->offset++]); |
|
589 | } |
||
590 | |||
591 | /** |
||
592 | * @return Decimal|\GMP|int|string |
||
593 | */ |
||
594 | 14 | private function unpackUint64() |
|
595 | { |
||
596 | 14 | if (!isset($this->buffer[$this->offset + 7])) { |
|
597 | 1 | throw new InsufficientDataException(); |
|
598 | } |
||
599 | |||
600 | 13 | $num = \unpack('J', $this->buffer, $this->offset)[1]; |
|
601 | 13 | $this->offset += 8; |
|
602 | |||
603 | 13 | if ($num >= 0) { |
|
604 | 8 | return $num; |
|
605 | } |
||
606 | 5 | if ($this->isBigIntAsDec) { |
|
607 | 1 | return new Decimal(\sprintf('%u', $num)); |
|
608 | } |
||
609 | 4 | if ($this->isBigIntAsGmp) { |
|
610 | 1 | return \gmp_import(\substr($this->buffer, $this->offset - 8, 8)); |
|
611 | } |
||
612 | |||
613 | 3 | return \sprintf('%u', $num); |
|
614 | } |
||
615 | |||
616 | /** |
||
617 | * @return int|string |
||
618 | */ |
||
619 | 6 | private function unpackUint64MapKey() |
|
620 | { |
||
621 | 6 | if (!isset($this->buffer[$this->offset + 7])) { |
|
622 | 1 | throw new InsufficientDataException(); |
|
623 | } |
||
624 | |||
625 | 5 | $num = \unpack('J', $this->buffer, $this->offset)[1]; |
|
626 | 5 | $this->offset += 8; |
|
627 | |||
628 | 5 | return $num >= 0 ? $num : \sprintf('%u', $num); |
|
629 | } |
||
630 | |||
631 | /** |
||
632 | * @return int |
||
633 | */ |
||
634 | 9 | private function unpackInt8() |
|
635 | { |
||
636 | 9 | if (!isset($this->buffer[$this->offset])) { |
|
637 | 1 | throw new InsufficientDataException(); |
|
638 | } |
||
639 | |||
640 | 8 | $num = \ord($this->buffer[$this->offset]); |
|
641 | 8 | ++$this->offset; |
|
642 | |||
643 | 8 | return $num > 0x7f ? $num - 0x100 : $num; |
|
644 | } |
||
645 | |||
646 | /** |
||
647 | * @return int |
||
648 | */ |
||
649 | 9 | private function unpackInt16() |
|
650 | { |
||
651 | 9 | if (!isset($this->buffer[$this->offset + 1])) { |
|
652 | 1 | throw new InsufficientDataException(); |
|
653 | } |
||
654 | |||
655 | 8 | $num = \ord($this->buffer[$this->offset]) << 8 |
|
656 | 8 | | \ord($this->buffer[++$this->offset]); |
|
657 | 8 | ++$this->offset; |
|
658 | |||
659 | 8 | return $num > 0x7fff ? $num - 0x10000 : $num; |
|
660 | } |
||
661 | |||
662 | /** |
||
663 | * @return int |
||
664 | */ |
||
665 | 9 | private function unpackInt32() |
|
666 | { |
||
667 | 9 | if (!isset($this->buffer[$this->offset + 3])) { |
|
668 | 1 | throw new InsufficientDataException(); |
|
669 | } |
||
670 | |||
671 | 8 | $num = \ord($this->buffer[$this->offset]) << 24 |
|
672 | 8 | | \ord($this->buffer[++$this->offset]) << 16 |
|
673 | 8 | | \ord($this->buffer[++$this->offset]) << 8 |
|
674 | 8 | | \ord($this->buffer[++$this->offset]); |
|
675 | 8 | ++$this->offset; |
|
676 | |||
677 | 8 | return $num > 0x7fffffff ? $num - 0x100000000 : $num; |
|
678 | } |
||
679 | |||
680 | /** |
||
681 | * @return int |
||
682 | */ |
||
683 | 15 | private function unpackInt64() |
|
684 | { |
||
685 | 15 | if (!isset($this->buffer[$this->offset + 7])) { |
|
686 | 1 | throw new InsufficientDataException(); |
|
687 | } |
||
688 | |||
689 | 14 | $num = \unpack('J', $this->buffer, $this->offset)[1]; |
|
690 | 14 | $this->offset += 8; |
|
691 | |||
692 | 14 | return $num; |
|
693 | } |
||
694 | |||
695 | /** |
||
696 | * @return float |
||
697 | */ |
||
698 | 5 | private function unpackFloat32() |
|
699 | { |
||
700 | 5 | if (!isset($this->buffer[$this->offset + 3])) { |
|
701 | 1 | throw new InsufficientDataException(); |
|
702 | } |
||
703 | |||
704 | 4 | $num = \unpack('G', $this->buffer, $this->offset)[1]; |
|
705 | 4 | $this->offset += 4; |
|
706 | |||
707 | 4 | return $num; |
|
708 | } |
||
709 | |||
710 | /** |
||
711 | * @return float |
||
712 | */ |
||
713 | 7 | private function unpackFloat64() |
|
714 | { |
||
715 | 7 | if (!isset($this->buffer[$this->offset + 7])) { |
|
716 | 1 | throw new InsufficientDataException(); |
|
717 | } |
||
718 | |||
719 | 6 | $num = \unpack('E', $this->buffer, $this->offset)[1]; |
|
720 | 6 | $this->offset += 8; |
|
721 | |||
722 | 6 | return $num; |
|
723 | } |
||
724 | |||
725 | /** |
||
726 | * @param int $size |
||
727 | * |
||
728 | * @return array |
||
729 | */ |
||
730 | 4 | private function unpackArrayData($size) |
|
731 | { |
||
732 | 4 | $array = []; |
|
733 | 4 | while ($size--) { |
|
734 | 4 | $array[] = $this->unpack(); |
|
735 | } |
||
736 | |||
737 | 4 | return $array; |
|
738 | } |
||
739 | |||
740 | /** |
||
741 | * @param int $size |
||
742 | * |
||
743 | * @return array |
||
744 | */ |
||
745 | 9 | private function unpackMapData($size) |
|
746 | { |
||
747 | 9 | $map = []; |
|
748 | 9 | while ($size--) { |
|
749 | 9 | $map[$this->unpackMapKey()] = $this->unpack(); |
|
750 | } |
||
751 | |||
752 | 7 | return $map; |
|
753 | } |
||
754 | |||
755 | /** |
||
756 | * @return int|string |
||
757 | */ |
||
758 | 93 | private function unpackMapKey() |
|
759 | { |
||
760 | 93 | if (!isset($this->buffer[$this->offset])) { |
|
761 | 1 | throw new InsufficientDataException(); |
|
762 | } |
||
763 | |||
764 | 92 | $c = \ord($this->buffer[$this->offset]); |
|
765 | 92 | ++$this->offset; |
|
766 | |||
767 | // fixint |
||
768 | 92 | if ($c <= 0x7f) { |
|
769 | 15 | return $c; |
|
770 | } |
||
771 | // fixstr |
||
772 | 85 | if ($c >= 0xa0 && $c <= 0xbf) { |
|
773 | 3 | return ($c & 0x1f) ? $this->read($c & 0x1f) : ''; |
|
774 | } |
||
775 | // negfixint |
||
776 | 82 | if ($c >= 0xe0) { |
|
777 | 2 | return $c - 0x100; |
|
778 | } |
||
779 | |||
780 | switch ($c) { |
||
781 | // uint |
||
782 | 80 | case 0xcc: return $this->unpackUint8(); |
|
783 | 80 | case 0xcd: return $this->unpackUint16(); |
|
784 | 78 | case 0xce: return $this->unpackUint32(); |
|
785 | 76 | case 0xcf: return $this->unpackUint64MapKey(); |
|
786 | // int |
||
787 | 70 | case 0xd0: return $this->unpackInt8(); |
|
788 | 70 | case 0xd1: return $this->unpackInt16(); |
|
789 | 70 | case 0xd2: return $this->unpackInt32(); |
|
790 | 70 | case 0xd3: return $this->unpackInt64(); |
|
791 | // str |
||
792 | 70 | case 0xd9: return $this->read($this->unpackUint8()); |
|
793 | 70 | case 0xda: return $this->read($this->unpackUint16()); |
|
794 | 70 | case 0xdb: return $this->read($this->unpackUint32()); |
|
795 | // bin |
||
796 | 70 | case 0xc4: return $this->read($this->unpackUint8()); |
|
797 | 68 | case 0xc5: return $this->read($this->unpackUint16()); |
|
798 | 68 | case 0xc6: return $this->read($this->unpackUint32()); |
|
799 | } |
||
800 | |||
801 | 68 | throw UnpackingFailedException::invalidMapKeyType($c); |
|
802 | } |
||
803 | |||
804 | /** |
||
805 | * @param int $length |
||
806 | * |
||
807 | * @return mixed |
||
808 | */ |
||
809 | 52 | private function unpackExtData($length) |
|
810 | { |
||
811 | 52 | if (!isset($this->buffer[$this->offset + $length])) { // 1 (type) + length - 1 |
|
812 | 16 | throw new InsufficientDataException(); |
|
813 | } |
||
814 | |||
815 | // int8 |
||
816 | 36 | $type = \ord($this->buffer[$this->offset]); |
|
817 | 36 | ++$this->offset; |
|
818 | |||
819 | 36 | if ($type > 0x7f) { |
|
820 | 9 | $type -= 0x100; |
|
821 | } |
||
822 | |||
823 | 36 | if (isset($this->extensions[$type])) { |
|
824 | 11 | return $this->extensions[$type]->unpackExt($this, $length); |
|
825 | } |
||
826 | |||
827 | 25 | $data = \substr($this->buffer, $this->offset, $length); |
|
828 | 25 | $this->offset += $length; |
|
829 | |||
830 | 25 | return new Ext($type, $data); |
|
831 | } |
||
832 | } |
||
833 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.