AMQPWriter::splitIntoQuads()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
namespace PhpAmqpLib\Wire;
3
4
use PhpAmqpLib\Exception\AMQPInvalidArgumentException;
5
use PhpAmqpLib\Exception\AMQPOutOfBoundsException;
6
7
class AMQPWriter extends AbstractClient
8
{
9
    /** @var string */
10
    protected $out;
11
12
    /** @var array */
13
    protected $bits;
14
15
    /** @var int */
16
    protected $bitcount;
17
18 435
    public function __construct()
19
    {
20 435
        parent::__construct();
21
22 435
        $this->out = '';
23 435
        $this->bits = array();
24 435
        $this->bitcount = 0;
25 435
    }
26
27
    /**
28
     * Packs integer into raw byte string in big-endian order
29
     * Supports positive and negative ints represented as PHP int or string (except scientific notation)
30
     *
31
     * Floats has some precision issues and so intentionally not supported.
32
     * Beware that floats out of PHP_INT_MAX range will be represented in scientific (exponential) notation when casted to string
33
     *
34
     * @param int|string $x Value to pack
35
     * @param int $bytes Must be multiply of 2
36
     * @return string
37
     */
38 10
    private static function packBigEndian($x, $bytes)
39
    {
40 10
        if (($bytes <= 0) || ($bytes % 2)) {
41
            throw new AMQPInvalidArgumentException(sprintf('Expected bytes count must be multiply of 2, %s given', $bytes));
42
        }
43
44 10
        $ox = $x; //purely for dbg purposes (overflow exception)
45 10
        $isNeg = false;
46
47 10
        if (is_int($x)) {
48
            if ($x < 0) {
49
                $isNeg = true;
50
                $x = abs($x);
51
            }
52 10
        } elseif (is_string($x)) {
53 10
            if (!is_numeric($x)) {
54
                throw new AMQPInvalidArgumentException(sprintf('Unknown numeric string format: %s', $x));
55
            }
56 10
            $x = preg_replace('/^-/', '', $x, 1, $isNeg);
57 8
        } else {
58
            throw new AMQPInvalidArgumentException('Only integer and numeric string values are supported');
59
        }
60
61 10
        if ($isNeg) {
62
            $x = bcadd($x, -1, 0);
63
        } //in negative domain starting point is -1, not 0
64
65 10
        $res = array();
66 10
        for ($b = 0; $b < $bytes; $b += 2) {
67 10
            $chnk = (int) bcmod($x, 65536);
68 10
            $x = bcdiv($x, 65536, 0);
69 10
            $res[] = pack('n', $isNeg ? ~$chnk : $chnk);
70 8
        }
71
72 10
        if ($x || ($isNeg && ($chnk & 0x8000))) {
0 ignored issues
show
Bug introduced by
The variable $chnk does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
73 5
            throw new AMQPOutOfBoundsException(sprintf('Overflow detected while attempting to pack %s into %s bytes', $ox, $bytes));
74
        }
75
76 5
        return implode(array_reverse($res));
77
    }
78
79 5
    private function flushbits()
80
    {
81 5
        if (!empty($this->bits)) {
82 5
            $this->out .= implode('', array_map('chr', $this->bits));
83 5
            $this->bits = array();
84 5
            $this->bitcount = 0;
85 4
        }
86 5
    }
87
88
    /**
89
     * Get what's been encoded so far.
90
     */
91 340
    public function getvalue()
92
    {
93
        /* temporarily needed for compatibility with write_bit unit tests */
94 340
        if ($this->bitcount) {
95 5
            $this->flushbits();
96 4
        }
97
98 340
        return $this->out;
99
    }
100
101
    /**
102
     * Write a plain PHP string, with no special encoding.
103
     */
104 210
    public function write($s)
105
    {
106 210
        $this->out .= $s;
107
108 210
        return $this;
109
    }
110
111
    /**
112
     * Write a boolean value.
113
     * (deprecated, use write_bits instead)
114
     *
115
     * @deprecated
116
     * @param $b
117
     * @return $this
118
     */
119 5
    public function write_bit($b)
120
    {
121 5
        $b = $b ? 1 : 0;
122 5
        $shift = $this->bitcount % 8;
123 5
        $last = $shift === 0 ? 0 : array_pop($this->bits);
124 5
        $last |= ($b << $shift);
125 5
        array_push($this->bits, $last);
126 5
        $this->bitcount += 1;
127
128 5
        return $this;
129
    }
130
131
    /**
132
     * Write multiple bits as an octet
133
     *
134
     * @param $bits
135
     * @return $this
136
     */
137 150
    public function write_bits($bits)
138
    {
139 150
        $value = 0;
140
141 150
        foreach ($bits as $n => $bit) {
142 150
            $bit = $bit ? 1 : 0;
143 150
            $value |= ($bit << $n);
144 120
        }
145
146 150
        $this->out .= chr($value);
147
148 150
        return $this;
149
    }
150
151
    /**
152
     * Write an integer as an unsigned 8-bit value
153
     *
154
     * @param $n
155
     * @return $this
156
     * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException
157
     */
158 275 View Code Duplication
    public function write_octet($n)
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...
159
    {
160 275
        if ($n < 0 || $n > 255) {
161 10
            throw new AMQPInvalidArgumentException('Octet out of range: ' . $n);
162
        }
163
164 265
        $this->out .= chr($n);
165
166 265
        return $this;
167
    }
168
169 25 View Code Duplication
    public function write_signed_octet($n)
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...
170
    {
171 25
        if (($n < -128) || ($n > 127)) {
172 10
            throw new AMQPInvalidArgumentException('Signed octet out of range: ' . $n);
173
        }
174
175 15
        $this->out .= pack('c', $n);
176
177 15
        return $this;
178
    }
179
180
    /**
181
     * Write an integer as an unsigned 16-bit value
182
     *
183
     * @param $n
184
     * @return $this
185
     * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException
186
     */
187 230 View Code Duplication
    public function write_short($n)
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...
188
    {
189 230
        if ($n < 0 || $n > 65535) {
190 10
            throw new AMQPInvalidArgumentException('Short out of range: ' . $n);
191
        }
192
193 220
        $this->out .= pack('n', $n);
194
195 220
        return $this;
196
    }
197
198 25 View Code Duplication
    public function write_signed_short($n)
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...
199
    {
200 25
        if (($n < -32768) || ($n > 32767)) {
201 10
            throw new AMQPInvalidArgumentException('Signed short out of range: ' . $n);
202
        }
203
204 15
        $this->out .= $this->correctEndianness(pack('s', $n));
205
206 15
        return $this;
207
    }
208
209
    /**
210
     * Write an integer as an unsigned 32-bit value
211
     *
212
     * @param $n
213
     * @return $this
214
     */
215 195
    public function write_long($n)
216
    {
217 195
        if (($n < 0) || ($n > 4294967295)) {
218 10
            throw new AMQPInvalidArgumentException('Long out of range: ' . $n);
219
        }
220
221
        //Numeric strings >PHP_INT_MAX on 32bit are casted to PHP_INT_MAX, damn PHP
222 185
        if (!$this->is64bits && is_string($n)) {
223
            $n = (float) $n;
224
        }
225 185
        $this->out .= pack('N', $n);
226
227 185
        return $this;
228
    }
229
230
    /**
231
     * @param $n
232
     * @return $this
233
     */
234 70 View Code Duplication
    private function write_signed_long($n)
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 70
        if (($n < -2147483648) || ($n > 2147483647)) {
237 10
            throw new AMQPInvalidArgumentException('Signed long out of range: ' . $n);
238
        }
239
240
        //on my 64bit debian this approach is slightly faster than splitIntoQuads()
241 60
        $this->out .= $this->correctEndianness(pack('l', $n));
242
243 60
        return $this;
244
    }
245
246
    /**
247
     * Write an integer as an unsigned 64-bit value
248
     *
249
     * @param $n
250
     * @return $this
251
     */
252 85
    public function write_longlong($n)
253
    {
254 85
        if ($n < 0) {
255 5
            throw new AMQPInvalidArgumentException('Longlong out of range: ' . $n);
256
        }
257
258
        // if PHP_INT_MAX is big enough for that
259
        // direct $n<=PHP_INT_MAX check is unreliable on 64bit (values close to max) due to limited float precision
260 80
        if (bcadd($n, -PHP_INT_MAX, 0) <= 0) {
261
            // trick explained in http://www.php.net/manual/fr/function.pack.php#109328
262 75 View Code Duplication
            if ($this->is64bits) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
263 75
                list($hi, $lo) = $this->splitIntoQuads($n);
264 60
            } else {
265
                $hi = 0;
266
                $lo = $n;
267
            } //on 32bits hi quad is 0 a priori
268 75
            $this->out .= pack('NN', $hi, $lo);
269 60
        } else {
270
            try {
271 10
                $this->out .= self::packBigEndian($n, 8);
272 9
            } catch (AMQPOutOfBoundsException $ex) {
273 5
                throw new AMQPInvalidArgumentException('Longlong out of range: ' . $n, 0, $ex);
274
            }
275
        }
276
277 75
        return $this;
278
    }
279
280 30
    public function write_signed_longlong($n)
281
    {
282 30
        if ((bcadd($n, PHP_INT_MAX, 0) >= -1) && (bcadd($n, -PHP_INT_MAX, 0) <= 0)) {
283 20 View Code Duplication
            if ($this->is64bits) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
284 20
                list($hi, $lo) = $this->splitIntoQuads($n);
285 16
            } else {
286
                $hi = $n < 0 ? -1 : 0;
287
                $lo = $n;
288
            } //0xffffffff for negatives
289 20
            $this->out .= pack('NN', $hi, $lo);
290 26
        } elseif ($this->is64bits) {
291 10
            throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n);
292
        } else {
293
            if (bcadd($n, '-9223372036854775807', 0) > 0) {
294
                throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n);
295
            }
296
            try {
297
                //will catch only negative overflow, as values >9223372036854775807 are valid for 8bytes range (unsigned)
298
                $this->out .= self::packBigEndian($n, 8);
299
            } catch (AMQPOutOfBoundsException $ex) {
300
                throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n, 0, $ex);
301
            }
302
        }
303
304 20
        return $this;
305
    }
306
307
    /**
308
     * @param int $n
309
     * @return array
310
     */
311 95
    private function splitIntoQuads($n)
312
    {
313 95
        $n = (int) $n;
314
315 95
        return array($n >> 32, $n & 0x00000000ffffffff);
316
    }
317
318
    /**
319
     * Write a string up to 255 bytes long after encoding.
320
     * Assume UTF-8 encoding
321
     *
322
     * @param $s
323
     * @return $this
324
     * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException
325
     */
326 235
    public function write_shortstr($s)
327
    {
328 235
        $len = mb_strlen($s, 'ASCII');
329 235
        if ($len > 255) {
330 10
            throw new AMQPInvalidArgumentException('String too long');
331
        }
332
333 225
        $this->write_octet($len);
334 225
        $this->out .= $s;
335
336 225
        return $this;
337
    }
338
339
    /**
340
     * Write a string up to 2**32 bytes long.  Assume UTF-8 encoding
341
     *
342
     * @param $s
343
     * @return $this
344
     */
345 115
    public function write_longstr($s)
346
    {
347 115
        $this->write_long(mb_strlen($s, 'ASCII'));
348 115
        $this->out .= $s;
349
350 115
        return $this;
351
    }
352
353
    /**
354
     * Supports the writing of Array types, so that you can implement
355
     * array methods, like Rabbitmq's HA parameters
356
     *
357
     * @param AMQPArray|array $a Instance of AMQPArray or PHP array WITHOUT format hints (unlike write_table())
358
     * @return self
359
     */
360 50
    public function write_array($a)
361
    {
362 50
        if (!($a instanceof AMQPArray)) {
363 30
            $a = new AMQPArray($a);
364 24
        }
365 50
        $data = new AMQPWriter();
366
367 50
        foreach ($a as $v) {
368 45
            $data->write_value($v[0], $v[1]);
369 40
        }
370
371 50
        $data = $data->getvalue();
372 50
        $this->write_long(mb_strlen($data, 'ASCII'));
373 50
        $this->write($data);
374
375 50
        return $this;
376
    }
377
378
    /**
379
     * Write unix time_t value as 64 bit timestamp
380
     *
381
     * @param $v
382
     * @return $this
383
     */
384 5
    public function write_timestamp($v)
385
    {
386 5
        $this->write_longlong($v);
387
388 5
        return $this;
389
    }
390
391
    /**
392
     * Write PHP array, as table. Input array format: keys are strings,
393
     * values are (type,value) tuples.
394
     *
395
     * @param AMQPTable|array $d Instance of AMQPTable or PHP array WITH format hints (unlike write_array())
396
     * @return self
397
     * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException
398
     */
399 160
    public function write_table($d)
400
    {
401 160
        $typeIsSym = !($d instanceof AMQPTable); //purely for back-compat purposes
402
403 160
        $table_data = new AMQPWriter();
404 160
        foreach ($d as $k => $va) {
405 125
            list($ftype, $v) = $va;
406 125
            $table_data->write_shortstr($k);
407 125
            $table_data->write_value($typeIsSym ? AMQPAbstractCollection::getDataTypeForSymbol($ftype) : $ftype, $v);
408 124
        }
409
410 155
        $table_data = $table_data->getvalue();
411 155
        $this->write_long(mb_strlen($table_data, 'ASCII'));
412 155
        $this->write($table_data);
413
414 155
        return $this;
415
    }
416
417
    /**
418
     * for compat with method mapping used by AMQPMessage
419
     */
420 40
    public function write_table_object($d)
421
    {
422 40
        return $this->write_table($d);
423
    }
424
425
    /**
426
     * @param int $type One of AMQPAbstractCollection::T_* constants
427
     * @param mixed $val
428
     */
429 135
    private function write_value($type, $val)
430
    {
431
        //This will find appropriate symbol for given data type for currently selected protocol
432
        //Also will raise an exception on unknown type
433 135
        $this->write(AMQPAbstractCollection::getSymbolForDataType($type));
434
435
        switch ($type) {
436 135
            case AMQPAbstractCollection::T_INT_SHORTSHORT:
437 10
                $this->write_signed_octet($val);
438 10
                break;
439 135
            case AMQPAbstractCollection::T_INT_SHORTSHORT_U:
440 10
                $this->write_octet($val);
441 10
                break;
442 135
            case AMQPAbstractCollection::T_INT_SHORT:
443 10
                $this->write_signed_short($val);
444 10
                break;
445 135
            case AMQPAbstractCollection::T_INT_SHORT_U:
446 10
                $this->write_short($val);
447 10
                break;
448 135
            case AMQPAbstractCollection::T_INT_LONG:
449 55
                $this->write_signed_long($val);
450 55
                break;
451 120
            case AMQPAbstractCollection::T_INT_LONG_U:
452
                $this->write_long($val);
453
                break;
454 120
            case AMQPAbstractCollection::T_INT_LONGLONG:
455 15
                $this->write_signed_longlong($val);
456 15
                break;
457 120
            case AMQPAbstractCollection::T_INT_LONGLONG_U:
458
                $this->write_longlong($val);
459
                break;
460 120
            case AMQPAbstractCollection::T_DECIMAL:
461
                $this->write_octet($val->getE());
462
                $this->write_signed_long($val->getN());
463
                break;
464 120
            case AMQPAbstractCollection::T_TIMESTAMP:
465
                $this->write_timestamp($val);
466
                break;
467 120
            case AMQPAbstractCollection::T_BOOL:
468 100
                $this->write_octet($val ? 1 : 0);
469 100
                break;
470 120
            case AMQPAbstractCollection::T_STRING_SHORT:
471 10
                $this->write_shortstr($val);
472 10
                break;
473 120
            case AMQPAbstractCollection::T_STRING_LONG:
474 110
                $this->write_longstr($val);
475 110
                break;
476 100
            case AMQPAbstractCollection::T_ARRAY:
477 40
                $this->write_array($val);
478 40
                break;
479 80
            case AMQPAbstractCollection::T_TABLE:
480 75
                $this->write_table($val);
481 75
                break;
482 5
            case AMQPAbstractCollection::T_VOID:
483 5
                break;
484
            default:
485
                throw new AMQPInvalidArgumentException(sprintf(
486
                    'Unsupported type "%s"',
487
                    $type
488
                ));
489
        }
490 135
    }
491
}
492