Completed
Push — master ( 7ad61c...2c2f04 )
by Eugene
04:44
created

src/BufferUnpacker.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 MessagePack\Exception\InsufficientDataException;
15
use MessagePack\Exception\IntegerOverflowException;
16
use MessagePack\Exception\UnpackingFailedException;
17
use MessagePack\TypeTransformer\Unpackable;
18
19
class BufferUnpacker
20
{
21
    private $buffer;
22
    private $offset = 0;
23
    private $isBigIntAsStr;
24
    private $isBigIntAsGmp;
25
26
    /**
27
     * @var Unpackable[]|null
28
     */
29
    private $transformers;
30
31
    /**
32
     * @param string $buffer
33
     * @param UnpackOptions|int|null $options
34
     *
35
     * @throws \MessagePack\Exception\InvalidOptionException
36
     */
37 240
    public function __construct(string $buffer = '', $options = null)
38
    {
39 240
        if (null === $options) {
40 239
            $options = UnpackOptions::fromDefaults();
41 8
        } elseif (!$options instanceof PackOptions) {
42 8
            $options = UnpackOptions::fromBitmask($options);
43
        }
44
45 240
        $this->isBigIntAsStr = $options->isBigIntAsStrMode();
46 240
        $this->isBigIntAsGmp = $options->isBigIntAsGmpMode();
47
48 240
        $this->buffer = $buffer;
49 240
    }
50
51 1
    public function registerTransformer(Unpackable $transformer) : self
52
    {
53 1
        $this->transformers[$transformer->getType()] = $transformer;
54
55 1
        return $this;
56
    }
57
58 10
    public function append(string $data) : self
59
    {
60 10
        $this->buffer .= $data;
61
62 10
        return $this;
63
    }
64
65 213
    public function reset(string $buffer = '') : self
66
    {
67 213
        $this->buffer = $buffer;
68 213
        $this->offset = 0;
69
70 213
        return $this;
71
    }
72
73 3 View Code Duplication
    public function seek(int $offset) : self
0 ignored issues
show
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...
74
    {
75 3
        if ($offset < 0) {
76 1
            $offset += \strlen($this->buffer);
77
        }
78
79 3
        if (!isset($this->buffer[$offset])) {
80 1
            throw new \OutOfBoundsException("Unable to seek to position $offset.");
81
        }
82
83 2
        $this->offset = $offset;
84
85 2
        return $this;
86
    }
87
88 2 View Code Duplication
    public function skip(int $offset) : self
0 ignored issues
show
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...
89
    {
90 2
        $offset += $this->offset;
91
92 2
        if (!isset($this->buffer[$offset])) {
93 1
            throw new \OutOfBoundsException("Unable to seek to position $offset.");
94
        }
95
96 1
        $this->offset = $offset;
97
98 1
        return $this;
99
    }
100
101 1
    public function __clone()
102
    {
103 1
        $this->buffer = '';
104 1
        $this->offset = 0;
105 1
    }
106
107 3
    public function tryUnpack() : array
108
    {
109 3
        $data = [];
110 3
        $offset = $this->offset;
111
112
        try {
113
            do {
114 3
                $data[] = $this->unpack();
115 3
                $offset = $this->offset;
116 3
            } while (isset($this->buffer[$this->offset]));
117 1
        } catch (InsufficientDataException $e) {
118 1
            $this->offset = $offset;
119
        }
120
121 3
        if ($this->offset) {
122 3
            $this->buffer = isset($this->buffer[$this->offset]) ? \substr($this->buffer, $this->offset) : '';
123 3
            $this->offset = 0;
124
        }
125
126 3
        return $data;
127
    }
128
129 145
    public function unpack()
130
    {
131 145
        if (!isset($this->buffer[$this->offset])) {
132 5
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
133
        }
134
135 142
        $c = \ord($this->buffer[$this->offset]);
136 142
        ++$this->offset;
137
138
        // fixint
139 142
        if ($c <= 0x7f) {
140 31
            return $c;
141
        }
142
        // fixstr
143 135 View Code Duplication
        if ($c >= 0xa0 && $c <= 0xbf) {
144 18
            return ($c & 0x1f) ? $this->unpackStrData($c & 0x1f) : '';
145
        }
146
        // fixarray
147 128
        if ($c >= 0x90 && $c <= 0x9f) {
148 9
            return ($c & 0xf) ? $this->unpackArrayData($c & 0xf) : [];
149
        }
150
        // fixmap
151 125
        if ($c >= 0x80 && $c <= 0x8f) {
152 13
            return ($c & 0xf) ? $this->unpackMapData($c & 0xf) : [];
153
        }
154
        // negfixint
155 121
        if ($c >= 0xe0) {
156 5
            return $c - 0x100;
157
        }
158
159 116
        switch ($c) {
160 116
            case 0xc0: return null;
161 113
            case 0xc2: return false;
162 108
            case 0xc3: return true;
163
164
            // bin
165 98
            case 0xc4: return $this->unpackStrData($this->unpackUint8());
166 93
            case 0xc5: return $this->unpackStrData($this->unpackUint16());
167 92
            case 0xc6: return $this->unpackStrData($this->unpackUint32());
168
169
            // float
170 91
            case 0xca: return $this->unpackFloat32();
171 88
            case 0xcb: return $this->unpackFloat64();
172
173
            // uint
174 84
            case 0xcc: return $this->unpackUint8();
175 80
            case 0xcd: return $this->unpackUint16();
176 73
            case 0xce: return $this->unpackUint32();
177 68
            case 0xcf: return $this->unpackUint64();
178
179
            // int
180 56
            case 0xd0: return $this->unpackInt8();
181 51
            case 0xd1: return $this->unpackInt16();
182 46
            case 0xd2: return $this->unpackInt32();
183 41
            case 0xd3: return $this->unpackInt64();
184
185
            // str
186 33
            case 0xd9: return $this->unpackStrData($this->unpackUint8());
187 29
            case 0xda: return $this->unpackStrData($this->unpackUint16());
188 27
            case 0xdb: return $this->unpackStrData($this->unpackUint32());
189
190
            // array
191 26
            case 0xdc: return $this->unpackArrayData($this->unpackUint16());
192 24
            case 0xdd: return $this->unpackArrayData($this->unpackUint32());
193
194
            // map
195 23
            case 0xde: return $this->unpackMapData($this->unpackUint16());
196 21
            case 0xdf: return $this->unpackMapData($this->unpackUint32());
197
198
            // ext
199 20
            case 0xd4: return $this->unpackExtData(1);
200 17
            case 0xd5: return $this->unpackExtData(2);
201 15
            case 0xd6: return $this->unpackExtData(4);
202 13
            case 0xd7: return $this->unpackExtData(8);
203 11
            case 0xd8: return $this->unpackExtData(16);
204 9
            case 0xc7: return $this->unpackExtData($this->unpackUint8());
205 5
            case 0xc8: return $this->unpackExtData($this->unpackUint16());
206 3
            case 0xc9: return $this->unpackExtData($this->unpackUint32());
207
        }
208
209 1
        throw UnpackingFailedException::unknownCode($c);
210
    }
211
212 3
    public function unpackNil()
213
    {
214 3
        if (!isset($this->buffer[$this->offset])) {
215 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
216
        }
217
218 2
        if ("\xc0" === $this->buffer[$this->offset]) {
219 1
            ++$this->offset;
220
221 1
            return null;
222
        }
223
224 1
        throw UnpackingFailedException::unexpectedCode(\ord($this->buffer[$this->offset++]), 'nil');
225
    }
226
227 5
    public function unpackBool()
228
    {
229 5
        if (!isset($this->buffer[$this->offset])) {
230 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
231
        }
232
233 4
        $c = \ord($this->buffer[$this->offset]);
234 4
        ++$this->offset;
235
236 4
        if (0xc2 === $c) {
237 2
            return false;
238
        }
239 2
        if (0xc3 === $c) {
240 1
            return true;
241
        }
242
243 1
        throw UnpackingFailedException::unexpectedCode($c, 'bool');
244
    }
245
246 40
    public function unpackInt()
247
    {
248 40
        if (!isset($this->buffer[$this->offset])) {
249 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
250
        }
251
252 39
        $c = \ord($this->buffer[$this->offset]);
253 39
        ++$this->offset;
254
255
        // fixint
256 39
        if ($c <= 0x7f) {
257 3
            return $c;
258
        }
259
        // negfixint
260 36
        if ($c >= 0xe0) {
261 3
            return $c - 0x100;
262
        }
263
264 33
        switch ($c) {
265
            // uint
266 33
            case 0xcc: return $this->unpackUint8();
267 30
            case 0xcd: return $this->unpackUint16();
268 27
            case 0xce: return $this->unpackUint32();
269 24
            case 0xcf: return $this->unpackUint64();
270
271
            // int
272 20
            case 0xd0: return $this->unpackInt8();
273 16
            case 0xd1: return $this->unpackInt16();
274 12
            case 0xd2: return $this->unpackInt32();
275 8
            case 0xd3: return $this->unpackInt64();
276
        }
277
278 1
        throw UnpackingFailedException::unexpectedCode($c, 'int');
279
    }
280
281 7
    public function unpackFloat()
282
    {
283 7
        if (!isset($this->buffer[$this->offset])) {
284 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
285
        }
286
287 6
        $c = \ord($this->buffer[$this->offset]);
288 6
        ++$this->offset;
289
290 6
        if (0xcb === $c) {
291 3
            return $this->unpackFloat64();
292
        }
293 3
        if (0xca === $c) {
294 2
            return $this->unpackFloat32();
295
        }
296
297 1
        throw UnpackingFailedException::unexpectedCode($c, 'float');
298
    }
299
300 14
    public function unpackStr()
301
    {
302 14
        if (!isset($this->buffer[$this->offset])) {
303 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
304
        }
305
306 13
        $c = \ord($this->buffer[$this->offset]);
307 13
        ++$this->offset;
308
309 13 View Code Duplication
        if ($c >= 0xa0 && $c <= 0xbf) {
310 5
            return ($c & 0x1f) ? $this->unpackStrData($c & 0x1f) : '';
311
        }
312 8
        if (0xd9 === $c) {
313 4
            return $this->unpackStrData($this->unpackUint8());
314
        }
315 4
        if (0xda === $c) {
316 2
            return $this->unpackStrData($this->unpackUint16());
317
        }
318 2
        if (0xdb === $c) {
319 1
            return $this->unpackStrData($this->unpackUint32());
320
        }
321
322 1
        throw UnpackingFailedException::unexpectedCode($c, 'str');
323
    }
324
325 7
    public function unpackBin()
326
    {
327 7
        if (!isset($this->buffer[$this->offset])) {
328 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
329
        }
330
331 6
        $c = \ord($this->buffer[$this->offset]);
332 6
        ++$this->offset;
333
334 6
        if (0xc4 === $c) {
335 3
            return $this->unpackStrData($this->unpackUint8());
336
        }
337 3
        if (0xc5 === $c) {
338 1
            return $this->unpackStrData($this->unpackUint16());
339
        }
340 2
        if (0xc6 === $c) {
341 1
            return $this->unpackStrData($this->unpackUint32());
342
        }
343
344 1
        throw UnpackingFailedException::unexpectedCode($c, 'bin');
345
    }
346
347 9
    public function unpackArray()
348
    {
349 9
        $size = $this->unpackArrayHeader();
350
351 7
        $array = [];
352 7
        while ($size--) {
353 6
            $array[] = $this->unpack();
354
        }
355
356 7
        return $array;
357
    }
358
359 9 View Code Duplication
    public function unpackArrayHeader()
360
    {
361 9
        if (!isset($this->buffer[$this->offset])) {
362 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
363
        }
364
365 8
        $c = \ord($this->buffer[$this->offset]);
366 8
        ++$this->offset;
367
368 8
        if ($c >= 0x90 && $c <= 0x9f) {
369 4
            return $c & 0xf;
370
        }
371 4
        if (0xdc === $c) {
372 2
            return $this->unpackUint16();
373
        }
374 2
        if (0xdd === $c) {
375 1
            return $this->unpackUint32();
376
        }
377
378 1
        throw UnpackingFailedException::unexpectedCode($c, 'array header');
379
    }
380
381 13
    public function unpackMap()
382
    {
383 13
        $size = $this->unpackMapHeader();
384
385 11
        $map = [];
386 11
        while ($size--) {
387 10
            $map[$this->unpack()] = $this->unpack();
388
        }
389
390 11
        return $map;
391
    }
392
393 13 View Code Duplication
    public function unpackMapHeader()
394
    {
395 13
        if (!isset($this->buffer[$this->offset])) {
396 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
397
        }
398
399 12
        $c = \ord($this->buffer[$this->offset]);
400 12
        ++$this->offset;
401
402 12
        if ($c >= 0x80 && $c <= 0x8f) {
403 8
            return $c & 0xf;
404
        }
405 4
        if (0xde === $c) {
406 2
            return $this->unpackUint16();
407
        }
408 2
        if (0xdf === $c) {
409 1
            return $this->unpackUint32();
410
        }
411
412 1
        throw UnpackingFailedException::unexpectedCode($c, 'map header');
413
    }
414
415 10
    public function unpackExt()
416
    {
417 10
        if (!isset($this->buffer[$this->offset])) {
418 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
419
        }
420
421 9
        $c = \ord($this->buffer[$this->offset]);
422 9
        ++$this->offset;
423
424 9
        switch ($c) {
425 9
            case 0xd4: return $this->unpackExtData(1);
426 8
            case 0xd5: return $this->unpackExtData(2);
427 7
            case 0xd6: return $this->unpackExtData(4);
428 6
            case 0xd7: return $this->unpackExtData(8);
429 5
            case 0xd8: return $this->unpackExtData(16);
430 4
            case 0xc7: return $this->unpackExtData($this->unpackUint8());
431 3
            case 0xc8: return $this->unpackExtData($this->unpackUint16());
432 2
            case 0xc9: return $this->unpackExtData($this->unpackUint32());
433
        }
434
435 1
        throw UnpackingFailedException::unexpectedCode($c, 'ext header');
436
    }
437
438 34
    private function unpackUint8()
439
    {
440 34
        if (!isset($this->buffer[$this->offset])) {
441 2
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
442
        }
443
444 32
        return \ord($this->buffer[$this->offset++]);
445
    }
446
447 28
    private function unpackUint16()
448
    {
449 28
        if (!isset($this->buffer[$this->offset + 1])) {
450 2
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 2);
451
        }
452
453 26
        $hi = \ord($this->buffer[$this->offset]);
454 26
        $lo = \ord($this->buffer[++$this->offset]);
455 26
        ++$this->offset;
456
457 26
        return $hi << 8 | $lo;
458
    }
459
460 20
    private function unpackUint32()
461
    {
462 20
        if (!isset($this->buffer[$this->offset + 3])) {
463 2
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 4);
464
        }
465
466 18
        $num = \unpack('N', $this->buffer, $this->offset)[1];
467 18
        $this->offset += 4;
468
469 18
        return $num;
470
    }
471
472 16 View Code Duplication
    private function unpackUint64()
473
    {
474 16
        if (!isset($this->buffer[$this->offset + 7])) {
475 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 8);
476
        }
477
478 15
        $num = \unpack('J', $this->buffer, $this->offset)[1];
479 15
        $this->offset += 8;
480
481 15
        return $num < 0 ? $this->handleIntOverflow($num) : $num;
482
    }
483
484 9
    private function unpackInt8()
485
    {
486 9
        if (!isset($this->buffer[$this->offset])) {
487 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 1);
488
        }
489
490 8
        $num = \ord($this->buffer[$this->offset]);
491 8
        ++$this->offset;
492
493 8
        return $num > 0x7f ? $num - 0x100 : $num;
494
    }
495
496 9
    private function unpackInt16()
497
    {
498 9
        if (!isset($this->buffer[$this->offset + 1])) {
499 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 2);
500
        }
501
502 8
        $hi = \ord($this->buffer[$this->offset]);
503 8
        $lo = \ord($this->buffer[++$this->offset]);
504 8
        ++$this->offset;
505
506 8
        return $hi > 0x7f ? $hi << 8 | $lo - 0x10000 : $hi << 8 | $lo;
507
    }
508
509 9 View Code Duplication
    private function unpackInt32()
510
    {
511 9
        if (!isset($this->buffer[$this->offset + 3])) {
512 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 4);
513
        }
514
515 8
        $num = \unpack('N', $this->buffer, $this->offset)[1];
516 8
        $this->offset += 4;
517
518 8
        return $num > 0x7fffffff ? $num - 0x100000000 : $num;
519
    }
520
521 15
    private function unpackInt64()
522
    {
523 15
        if (!isset($this->buffer[$this->offset + 7])) {
524 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 8);
525
        }
526
527 14
        $num = \unpack('J', $this->buffer, $this->offset)[1];
528 14
        $this->offset += 8;
529
530 14
        return $num;
531
    }
532
533 5
    private function unpackFloat32()
534
    {
535 5
        if (!isset($this->buffer[$this->offset + 3])) {
536 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 4);
537
        }
538
539 4
        $num = \unpack('G', $this->buffer, $this->offset)[1];
540 4
        $this->offset += 4;
541
542 4
        return $num;
543
    }
544
545 7
    private function unpackFloat64()
546
    {
547 7
        if (!isset($this->buffer[$this->offset + 7])) {
548 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, 8);
549
        }
550
551 6
        $num = \unpack('E', $this->buffer, $this->offset)[1];
552 6
        $this->offset += 8;
553
554 6
        return $num;
555
    }
556
557 47
    private function unpackStrData($length)
558
    {
559 47
        if (!isset($this->buffer[$this->offset + $length - 1])) {
560 1
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, $length);
561
        }
562
563 47
        $str = \substr($this->buffer, $this->offset, $length);
564 47
        $this->offset += $length;
565
566 47
        return $str;
567
    }
568
569 11
    private function unpackArrayData($size)
570
    {
571 11
        $array = [];
572 11
        while ($size--) {
573 11
            $array[] = $this->unpack();
574
        }
575
576 11
        return $array;
577
    }
578
579 15
    private function unpackMapData($size)
580
    {
581 15
        $map = [];
582 15
        while ($size--) {
583 15
            $map[$this->unpack()] = $this->unpack();
584
        }
585
586 15
        return $map;
587
    }
588
589 24
    private function unpackExtData($length)
590
    {
591 24
        if (!isset($this->buffer[$this->offset + $length - 1])) {
592 5
            throw InsufficientDataException::unexpectedLength($this->buffer, $this->offset, $length);
593
        }
594
595
        // int8
596 19
        $type = \ord($this->buffer[$this->offset]);
597 19
        ++$this->offset;
598
599 19
        if ($type > 0x7f) {
600
            $type -= 0x100;
601
        }
602
603 19
        if (isset($this->transformers[$type])) {
604 1
            return $this->transformers[$type]->unpack($this, $length);
605
        }
606
607 18
        $data = \substr($this->buffer, $this->offset, $length);
608 18
        $this->offset += $length;
609
610 18
        return new Ext($type, $data);
611
    }
612
613 5
    private function handleIntOverflow($value)
614
    {
615 5
        if ($this->isBigIntAsStr) {
616 3
            return \sprintf('%u', $value);
617
        }
618 2
        if ($this->isBigIntAsGmp) {
619 1
            return \gmp_init(\sprintf('%u', $value));
620
        }
621
622 1
        throw new IntegerOverflowException($value);
623
    }
624
}
625