Completed
Push — master ( 651cc4...f73fd6 )
by Eugene
04:45
created

BufferUnpacker::ensureLength()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4286
cc 2
eloc 3
nc 2
nop 1
crap 2
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
18
class BufferUnpacker
19
{
20
    const BIGINT_AS_EXCEPTION = 0;
21
    const BIGINT_AS_STR = 1;
22
    const BIGINT_AS_GMP = 2;
23
24
    /**
25
     * @var int
26
     */
27
    private $bigIntMode = self::BIGINT_AS_EXCEPTION;
28
29
    /**
30
     * @var string
31
     */
32
    private $buffer = '';
33
34
    /**
35
     * @var int
36
     */
37
    private $offset = 0;
38
39
    /**
40
     * @param int|null $bigIntMode
41
     */
42 92
    public function __construct($bigIntMode = null)
43
    {
44 92
        if (null !== $bigIntMode) {
45 2
            $this->bigIntMode = $bigIntMode;
46 2
        }
47 92
    }
48
49
    /**
50
     * @param int $bigIntMode
51
     *
52
     * @throws \InvalidArgumentException
53
     */
54 2
    public function setBigIntMode($bigIntMode)
55
    {
56 2
        if (!in_array($bigIntMode, [
57 2
            self::BIGINT_AS_EXCEPTION,
58 2
            self::BIGINT_AS_STR,
59 2
            self::BIGINT_AS_GMP,
60 2
        ], true)) {
61 1
            throw new \InvalidArgumentException(sprintf('Invalid bigint mode: %s.', $bigIntMode));
62
        }
63
64 1
        $this->bigIntMode = $bigIntMode;
65 1
    }
66
67
    /**
68
     * @return int
69
     */
70 1
    public function getBigIntMode()
71
    {
72 1
        return $this->bigIntMode;
73
    }
74
75
    /**
76
     * @param string $data
77
     *
78
     * @return $this
79
     */
80 5
    public function append($data)
81
    {
82 5
        $this->buffer .= $data;
83
84 5
        return $this;
85
    }
86
87
    /**
88
     * @param string|null $buffer
89
     *
90
     * @return $this
91
     */
92 85
    public function reset($buffer = null)
93
    {
94 85
        $this->buffer = (string) $buffer;
95 85
        $this->offset = 0;
96
97 85
        return $this;
98
    }
99
100
    /**
101
     * @return array
102
     */
103 3
    public function tryUnpack()
104
    {
105 3
        $data = [];
106 3
        $offset = $this->offset;
107
108
        try {
109
            do {
110 3
                $data[] = $this->unpack();
111 3
                $offset = $this->offset;
112 3
            } while (isset($this->buffer[$this->offset]));
113 3
        } catch (InsufficientDataException $e) {
114 1
            $this->offset = $offset;
115
        }
116
117 3
        if ($this->offset) {
118 3
            $this->buffer = (string) substr($this->buffer, $this->offset);
119 3
            $this->offset = 0;
120 3
        }
121
122 3
        return $data;
123
    }
124
125
    /**
126
     * @return mixed
127
     *
128
     * @throws UnpackingFailedException
129
     */
130 89
    public function unpack()
131
    {
132 89
        $this->ensureLength(1);
133
134 87
        $c = ord($this->buffer[$this->offset]);
135 87
        $this->offset += 1;
136
137
        // fixint
138 87
        if ($c <= 0x7f) {
139 16
            return $c;
140
        }
141
        // fixstr
142 84
        if ($c >= 0xa0 && $c <= 0xbf) {
143 12
            return $this->unpackStr($c & 0x1f);
144
        }
145
        // fixarray
146 79
        if ($c >= 0x90 && $c <= 0x9f) {
147 6
            return $this->unpackArr($c & 0xf);
148
        }
149
        // fixmap
150 76
        if ($c >= 0x80 && $c <= 0x8f) {
151 8
            return $this->unpackMap($c & 0xf);
152
        }
153
        // negfixint
154 74
        if ($c >= 0xe0) {
155 3
            return $c - 256;
156
        }
157
158
        switch ($c) {
159 71
            case 0xc0: return null;
160 70
            case 0xc2: return false;
161 68
            case 0xc3: return true;
162
163
            // MP_BIN
164 63
            case 0xc4: return $this->unpackStr($this->unpackU8());
165 59
            case 0xc5: return $this->unpackStr($this->unpackU16());
166 58
            case 0xc6: return $this->unpackStr($this->unpackU32());
167
168 57
            case 0xca: return $this->unpackFloat();
169 55
            case 0xcb: return $this->unpackDouble();
170
171
            // MP_UINT
172 52
            case 0xcc: return $this->unpackU8();
173 49
            case 0xcd: return $this->unpackU16();
174 45
            case 0xce: return $this->unpackU32();
175 42
            case 0xcf: return $this->unpackU64();
176
177
            // MP_INT
178 35
            case 0xd0: return $this->unpackI8();
179 32
            case 0xd1: return $this->unpackI16();
180 29
            case 0xd2: return $this->unpackI32();
181 26
            case 0xd3: return $this->unpackI64();
182
183
            // MP_STR
184 23
            case 0xd9: return $this->unpackStr($this->unpackU8());
185 19
            case 0xda: return $this->unpackStr($this->unpackU16());
186 17
            case 0xdb: return $this->unpackStr($this->unpackU32());
187
188
            // MP_ARRAY
189 16
            case 0xdc: return $this->unpackArr($this->unpackU16());
190 14
            case 0xdd: return $this->unpackArr($this->unpackU32());
191
192
            // MP_MAP
193 13
            case 0xde: return $this->unpackMap($this->unpackU16());
194 11
            case 0xdf: return $this->unpackMap($this->unpackU32());
195
196
            // MP_EXT
197 10
            case 0xd4: return $this->unpackExt(1);
198 9
            case 0xd5: return $this->unpackExt(2);
199 8
            case 0xd6: return $this->unpackExt(4);
200 7
            case 0xd7: return $this->unpackExt(8);
201 6
            case 0xd8: return $this->unpackExt(16);
202 5
            case 0xc7: return $this->unpackExt($this->unpackU8());
203 3
            case 0xc8: return $this->unpackExt($this->unpackU16());
204 2
            case 0xc9: return $this->unpackExt($this->unpackU32());
205
        }
206
207 1
        throw new UnpackingFailedException(sprintf('Unknown code: 0x%x.', $c));
208
    }
209
210 16
    private function unpackU8()
211
    {
212 16
        $this->ensureLength(1);
213
214 16
        $num = $this->buffer[$this->offset];
215 16
        $this->offset += 1;
216
217 16
        $num = unpack('C', $num);
218
219 16
        return $num[1];
220
    }
221
222 13 View Code Duplication
    private function unpackU16()
0 ignored issues
show
Duplication introduced by
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...
223
    {
224 13
        $this->ensureLength(2);
225
226 13
        $num = $this->buffer[$this->offset].$this->buffer[$this->offset + 1];
227 13
        $this->offset += 2;
228
229 13
        $num = unpack('n', $num);
230
231 13
        return $num[1];
232
    }
233
234 9 View Code Duplication
    private function unpackU32()
0 ignored issues
show
Duplication introduced by
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...
235
    {
236 9
        $this->ensureLength(4);
237
238 9
        $num = substr($this->buffer, $this->offset, 4);
239 9
        $this->offset += 4;
240
241 9
        $num = unpack('N', $num);
242
243 9
        return $num[1];
244
    }
245
246 7
    private function unpackU64()
247
    {
248 7
        $this->ensureLength(8);
249
250 7
        $num = substr($this->buffer, $this->offset, 8);
251 7
        $this->offset += 8;
252
253
        //$num = unpack('J', $num);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
254
255 7
        $set = unpack('N2', $num);
256 7
        $value = $set[1] << 32 | $set[2];
257
258
        // PHP does not support unsigned integers.
259
        // If a number is bigger than 2^63, it will be interpreted as a float.
260
        // @link http://php.net/manual/en/language.types.integer.php#language.types.integer.overflow
261
262 7
        return ($value < 0) ? $this->handleBigInt($value) : $value;
263
    }
264
265 12
    private function unpackI8()
266
    {
267 12
        $this->ensureLength(1);
268
269 12
        $num = $this->buffer[$this->offset];
270 12
        $this->offset += 1;
271
272 12
        $num = unpack('c', $num);
273
274 12
        return $num[1];
275
    }
276
277 3 View Code Duplication
    private function unpackI16()
0 ignored issues
show
Duplication introduced by
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...
278
    {
279 3
        $this->ensureLength(2);
280
281 3
        $num = $this->buffer[$this->offset].$this->buffer[$this->offset + 1];
282 3
        $this->offset += 2;
283
284 3
        $num = unpack('s', strrev($num));
285
286 3
        return $num[1];
287
    }
288
289 3
    private function unpackI32()
290
    {
291 3
        $this->ensureLength(4);
292
293 3
        $num = substr($this->buffer, $this->offset, 4);
294 3
        $this->offset += 4;
295
296 3
        $num = unpack('i', strrev($num));
297
298 3
        return $num[1];
299
    }
300
301 3 View Code Duplication
    private function unpackI64()
0 ignored issues
show
Duplication introduced by
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...
302
    {
303 3
        $this->ensureLength(8);
304
305 3
        $num = substr($this->buffer, $this->offset, 8);
306 3
        $this->offset += 8;
307
308 3
        $set = unpack('N2', $num);
309
310 3
        return $set[1] << 32 | $set[2];
311
    }
312
313 2
    private function unpackFloat()
314
    {
315 2
        $this->ensureLength(4);
316
317 2
        $num = substr($this->buffer, $this->offset, 4);
318 2
        $this->offset += 4;
319
320 2
        $num = unpack('f', strrev($num));
321
322 2
        return $num[1];
323
    }
324
325 3
    private function unpackDouble()
326
    {
327 3
        $this->ensureLength(8);
328
329 3
        $num = substr($this->buffer, $this->offset, 8);
330 3
        $this->offset += 8;
331
332 3
        $num = unpack('d', strrev($num));
333
334 3
        return $num[1];
335
    }
336
337 25
    private function unpackStr($length)
338
    {
339 25
        if (!$length) {
340 1
            return '';
341
        }
342
343 24
        $this->ensureLength($length);
344
345 24
        $str = substr($this->buffer, $this->offset, $length);
346 24
        $this->offset += $length;
347
348 24
        return $str;
349
    }
350
351 9
    private function unpackArr($size)
352
    {
353 9
        $array = [];
354
355 9
        for ($i = $size; $i; $i--) {
356 8
            $array[] = $this->unpack();
357 8
        }
358
359 9
        return $array;
360
    }
361
362
    /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
363
    private function unpackArrSpl($size)
364
    {
365
        $array = new \SplFixedArray($size);
366
367
        for ($i = 0; $i < $size; $i++) {
368
            $array[$i] = $this->unpack();
369
        }
370
371
        return $array;
372
    }
373
    */
374
375 11
    private function unpackMap($size)
376
    {
377 11
        $map = [];
378
379 11
        for ($i = $size; $i; $i--) {
380 10
            $key = $this->unpack();
381 10
            $value = $this->unpack();
382
383 10
            $map[$key] = $value;
384 10
        }
385
386 11
        return $map;
387
    }
388
389 9
    private function unpackExt($length)
390
    {
391 9
        $this->ensureLength($length);
392
393 9
        $type = $this->unpackI8();
394 9
        $data = substr($this->buffer, $this->offset, $length);
395 9
        $this->offset += $length;
396
397 9
        return new Ext($type, $data);
398
    }
399
400 89
    private function ensureLength($length)
401
    {
402 89
        if (!isset($this->buffer[$this->offset + $length - 1])) {
403 4
            throw new InsufficientDataException($length, strlen($this->buffer) - $this->offset);
404
        }
405 87
    }
406
407 3
    private function handleBigInt($value)
408
    {
409 3
        if (self::BIGINT_AS_STR === $this->bigIntMode) {
410 1
            return sprintf('%u', $value);
411
        }
412 2
        if (self::BIGINT_AS_GMP === $this->bigIntMode) {
413 1
            return gmp_init(sprintf('%u', $value));
414
        }
415
416 1
        throw new IntegerOverflowException($value);
417
    }
418
}
419