Completed
Branch master (3d6f9a)
by Eugene
02:40
created

BufferUnpacker::unpackArrayHeader()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 11
nc 5
nop 0
dl 0
loc 20
ccs 12
cts 12
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
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\InvalidOptionException;
17
use MessagePack\Exception\UnpackingFailedException;
18
use MessagePack\TypeTransformer\CanUnpackExt;
19
20
class BufferUnpacker
21
{
22
    private $buffer;
23
    private $offset = 0;
24
    private $isBigIntAsStr;
25
    private $isBigIntAsGmp;
26
27
    /**
28
     * @var CanUnpackExt[]
29
     */
30
    private $transformers = [];
31
32
    /**
33
     * @param string $buffer
34
     * @param UnpackOptions|int|null $options
35
     * @param CanUnpackExt[] $transformers
36
     *
37
     * @throws InvalidOptionException
38
     */
39 243
    public function __construct(string $buffer = '', $options = null, array $transformers = [])
40
    {
41 243
        if (null === $options) {
42 242
            $options = UnpackOptions::fromDefaults();
43 8
        } elseif (!$options instanceof PackOptions) {
0 ignored issues
show
introduced by
$options is never a sub-type of MessagePack\PackOptions.
Loading history...
44 8
            $options = UnpackOptions::fromBitmask($options);
0 ignored issues
show
Bug introduced by
It seems like $options can also be of type MessagePack\UnpackOptions; however, parameter $bitmask of MessagePack\UnpackOptions::fromBitmask() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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