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) { |
|
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
Documentation
Bug
introduced
by
![]() |
|||
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 |