Completed
Push — master ( 74b488...57aaf2 )
by Eugene
04:25
created

BufferUnpacker::handleIntOverflow()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.4286
cc 3
eloc 6
nc 3
nop 1
crap 3
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\Collection;
18
19
class BufferUnpacker
20
{
21
    const INT_AS_EXCEPTION = 0;
22
    const INT_AS_STR = 1;
23
    const INT_AS_GMP = 2;
24
25
    /**
26
     * @var int
27
     */
28
    private $intOverflowMode = self::INT_AS_EXCEPTION;
29
30
    /**
31
     * @var string
32
     */
33
    private $buffer = '';
34
35
    /**
36
     * @var int
37
     */
38
    private $offset = 0;
39
40
    /**
41
     * @var Collection
42
     */
43
    private $transformers;
44
45
    /**
46
     * @param int|null $intOverflowMode
47
     */
48 96
    public function __construct($intOverflowMode = null)
49
    {
50 96
        if (null !== $intOverflowMode) {
51 2
            $this->intOverflowMode = $intOverflowMode;
52 2
        }
53 96
    }
54
55
    /**
56
     * @param Collection|null $transformers
57
     */
58 2
    public function setTransformers(Collection $transformers = null)
59
    {
60 2
        $this->transformers = $transformers;
61 2
    }
62
63
    /**
64
     * @return Collection|null
65
     */
66 1
    public function getTransformers()
67
    {
68 1
        return $this->transformers;
69 1
    }
70
71
    /**
72
     * @param int $intOverflowMode
73
     *
74
     * @throws \InvalidArgumentException
75
     */
76 2
    public function setIntOverflowMode($intOverflowMode)
77
    {
78 2
        if (!in_array($intOverflowMode, [
79 2
            self::INT_AS_EXCEPTION,
80 2
            self::INT_AS_STR,
81 2
            self::INT_AS_GMP,
82 2
        ], true)) {
83 1
            throw new \InvalidArgumentException(sprintf('Invalid integer overflow mode: %s.', $intOverflowMode));
84
        }
85
86 1
        $this->intOverflowMode = $intOverflowMode;
87 1
    }
88
89
    /**
90
     * @return int
91
     */
92 1
    public function getIntOverflowMode()
93
    {
94 1
        return $this->intOverflowMode;
95
    }
96
97
    /**
98
     * @param string $data
99
     *
100
     * @return $this
101
     */
102 5
    public function append($data)
103
    {
104 5
        $this->buffer .= $data;
105
106 5
        return $this;
107
    }
108
109
    /**
110
     * @param string|null $buffer
111
     *
112
     * @return $this
113
     */
114 88
    public function reset($buffer = null)
115
    {
116 88
        $this->buffer = (string) $buffer;
117 88
        $this->offset = 0;
118
119 88
        return $this;
120
    }
121
122
    /**
123
     * @return array
124
     */
125 3
    public function tryUnpack()
126
    {
127 3
        $data = [];
128 3
        $offset = $this->offset;
129
130
        try {
131
            do {
132 3
                $data[] = $this->unpack();
133 3
                $offset = $this->offset;
134 3
            } while (isset($this->buffer[$this->offset]));
135 3
        } catch (InsufficientDataException $e) {
136 1
            $this->offset = $offset;
137
        }
138
139 3
        if ($this->offset) {
140 3
            $this->buffer = (string) substr($this->buffer, $this->offset);
141 3
            $this->offset = 0;
142 3
        }
143
144 3
        return $data;
145
    }
146
147
    /**
148
     * @return mixed
149
     *
150
     * @throws UnpackingFailedException
151
     */
152 92
    public function unpack()
153
    {
154 92
        $this->ensureLength(1);
155
156 90
        $c = ord($this->buffer[$this->offset]);
157 90
        $this->offset += 1;
158
159
        // fixint
160 90
        if ($c <= 0x7f) {
161 19
            return $c;
162
        }
163
        // fixstr
164 87
        if ($c >= 0xa0 && $c <= 0xbf) {
165 12
            return $this->unpackStr($c & 0x1f);
166
        }
167
        // fixarray
168 82
        if ($c >= 0x90 && $c <= 0x9f) {
169 6
            return $this->unpackArray($c & 0xf);
170
        }
171
        // fixmap
172 79
        if ($c >= 0x80 && $c <= 0x8f) {
173 10
            return $this->unpackMap($c & 0xf);
174
        }
175
        // negfixint
176 75
        if ($c >= 0xe0) {
177 3
            return $c - 256;
178
        }
179
180
        switch ($c) {
181 72
            case 0xc0: return null;
182 71
            case 0xc2: return false;
183 69
            case 0xc3: return true;
184
185
            // MP_BIN
186 64
            case 0xc4: return $this->unpackStr($this->unpackU8());
187 60
            case 0xc5: return $this->unpackStr($this->unpackU16());
188 59
            case 0xc6: return $this->unpackStr($this->unpackU32());
189
190 58
            case 0xca: return $this->unpackFloat();
191 56
            case 0xcb: return $this->unpackDouble();
192
193
            // MP_UINT
194 53
            case 0xcc: return $this->unpackU8();
195 50
            case 0xcd: return $this->unpackU16();
196 46
            case 0xce: return $this->unpackU32();
197 43
            case 0xcf: return $this->unpackU64();
198
199
            // MP_INT
200 36
            case 0xd0: return $this->unpackI8();
201 33
            case 0xd1: return $this->unpackI16();
202 30
            case 0xd2: return $this->unpackI32();
203 27
            case 0xd3: return $this->unpackI64();
204
205
            // MP_STR
206 24
            case 0xd9: return $this->unpackStr($this->unpackU8());
207 20
            case 0xda: return $this->unpackStr($this->unpackU16());
208 18
            case 0xdb: return $this->unpackStr($this->unpackU32());
209
210
            // MP_ARRAY
211 17
            case 0xdc: return $this->unpackArray($this->unpackU16());
212 15
            case 0xdd: return $this->unpackArray($this->unpackU32());
213
214
            // MP_MAP
215 14
            case 0xde: return $this->unpackMap($this->unpackU16());
216 12
            case 0xdf: return $this->unpackMap($this->unpackU32());
217
218
            // MP_EXT
219 11
            case 0xd4: return $this->unpackExt(1);
220 9
            case 0xd5: return $this->unpackExt(2);
221 8
            case 0xd6: return $this->unpackExt(4);
222 7
            case 0xd7: return $this->unpackExt(8);
223 6
            case 0xd8: return $this->unpackExt(16);
224 5
            case 0xc7: return $this->unpackExt($this->unpackU8());
225 3
            case 0xc8: return $this->unpackExt($this->unpackU16());
226 2
            case 0xc9: return $this->unpackExt($this->unpackU32());
227
        }
228
229 1
        throw new UnpackingFailedException(sprintf('Unknown code: 0x%x.', $c));
230
    }
231
232 16
    private function unpackU8()
233
    {
234 16
        $this->ensureLength(1);
235
236 16
        $num = $this->buffer[$this->offset];
237 16
        $this->offset += 1;
238
239 16
        $num = unpack('C', $num);
240
241 16
        return $num[1];
242
    }
243
244 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...
245
    {
246 13
        $this->ensureLength(2);
247
248 13
        $num = $this->buffer[$this->offset].$this->buffer[$this->offset + 1];
249 13
        $this->offset += 2;
250
251 13
        $num = unpack('n', $num);
252
253 13
        return $num[1];
254
    }
255
256 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...
257
    {
258 9
        $this->ensureLength(4);
259
260 9
        $num = substr($this->buffer, $this->offset, 4);
261 9
        $this->offset += 4;
262
263 9
        $num = unpack('N', $num);
264
265 9
        return $num[1];
266
    }
267
268 7
    private function unpackU64()
269
    {
270 7
        $this->ensureLength(8);
271
272 7
        $num = substr($this->buffer, $this->offset, 8);
273 7
        $this->offset += 8;
274
275
        //$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...
276
277 7
        $set = unpack('N2', $num);
278 7
        $value = $set[1] << 32 | $set[2];
279
280
        // PHP does not support unsigned integers.
281
        // If a number is bigger than 2^63, it will be interpreted as a float.
282
        // @link http://php.net/manual/en/language.types.integer.php#language.types.integer.overflow
283
284 7
        return ($value < 0) ? $this->handleIntOverflow($value) : $value;
285
    }
286
287 13
    private function unpackI8()
288
    {
289 13
        $this->ensureLength(1);
290
291 13
        $num = $this->buffer[$this->offset];
292 13
        $this->offset += 1;
293
294 13
        $num = unpack('c', $num);
295
296 13
        return $num[1];
297
    }
298
299 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...
300
    {
301 3
        $this->ensureLength(2);
302
303 3
        $num = $this->buffer[$this->offset].$this->buffer[$this->offset + 1];
304 3
        $this->offset += 2;
305
306 3
        $num = unpack('s', strrev($num));
307
308 3
        return $num[1];
309
    }
310
311 3
    private function unpackI32()
312
    {
313 3
        $this->ensureLength(4);
314
315 3
        $num = substr($this->buffer, $this->offset, 4);
316 3
        $this->offset += 4;
317
318 3
        $num = unpack('i', strrev($num));
319
320 3
        return $num[1];
321
    }
322
323 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...
324
    {
325 3
        $this->ensureLength(8);
326
327 3
        $num = substr($this->buffer, $this->offset, 8);
328 3
        $this->offset += 8;
329
330 3
        $set = unpack('N2', $num);
331
332 3
        return $set[1] << 32 | $set[2];
333
    }
334
335 2
    private function unpackFloat()
336
    {
337 2
        $this->ensureLength(4);
338
339 2
        $num = substr($this->buffer, $this->offset, 4);
340 2
        $this->offset += 4;
341
342 2
        $num = unpack('f', strrev($num));
343
344 2
        return $num[1];
345
    }
346
347 3
    private function unpackDouble()
348
    {
349 3
        $this->ensureLength(8);
350
351 3
        $num = substr($this->buffer, $this->offset, 8);
352 3
        $this->offset += 8;
353
354 3
        $num = unpack('d', strrev($num));
355
356 3
        return $num[1];
357
    }
358
359 25
    private function unpackStr($length)
360
    {
361 25
        if (!$length) {
362 1
            return '';
363
        }
364
365 24
        $this->ensureLength($length);
366
367 24
        $str = substr($this->buffer, $this->offset, $length);
368 24
        $this->offset += $length;
369
370 24
        return $str;
371
    }
372
373 9
    private function unpackArray($size)
374
    {
375 9
        $array = [];
376
377 9
        for ($i = $size; $i; $i--) {
378 8
            $array[] = $this->unpack();
379 8
        }
380
381 9
        return $array;
382
    }
383
384 13
    private function unpackMap($size)
385
    {
386 13
        $map = [];
387
388 13
        for ($i = $size; $i; $i--) {
389 12
            $key = $this->unpack();
390 12
            $value = $this->unpack();
391
392 12
            $map[$key] = $value;
393 12
        }
394
395 13
        return $map;
396
    }
397
398 10
    private function unpackExt($length)
399
    {
400 10
        $this->ensureLength($length);
401
402 10
        $type = $this->unpackI8();
403 10
        $data = substr($this->buffer, $this->offset, $length);
404
405 10
        if ($this->transformers && $transformer = $this->transformers->find($type)) {
406 1
            return $transformer->reverseTransform($this->unpack());
407
        }
408
409 9
        $this->offset += $length;
410
411 9
        return new Ext($type, $data);
412
    }
413
414 92
    private function ensureLength($length)
415
    {
416 92
        if (!isset($this->buffer[$this->offset + $length - 1])) {
417 4
            throw new InsufficientDataException($length, strlen($this->buffer) - $this->offset);
418
        }
419 90
    }
420
421 3
    private function handleIntOverflow($value)
422
    {
423 3
        if (self::INT_AS_STR === $this->intOverflowMode) {
424 1
            return sprintf('%u', $value);
425
        }
426 2
        if (self::INT_AS_GMP === $this->intOverflowMode) {
427 1
            return gmp_init(sprintf('%u', $value));
428
        }
429
430 1
        throw new IntegerOverflowException($value);
431
    }
432
}
433