1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpAmqpLib\Wire; |
4
|
|
|
|
5
|
|
|
use PhpAmqpLib\Exception\AMQPInvalidArgumentException; |
6
|
|
|
use PhpAmqpLib\Exception\AMQPOutOfRangeException; |
7
|
|
|
use phpseclib\Math\BigInteger; |
8
|
|
|
|
9
|
|
|
class AMQPWriter extends AbstractClient |
10
|
|
|
{ |
11
|
|
|
/** @var string */ |
12
|
|
|
protected $out = ''; |
13
|
|
|
|
14
|
|
|
/** @var array */ |
15
|
|
|
protected $bits = array(); |
16
|
|
|
|
17
|
|
|
/** @var int */ |
18
|
|
|
protected $bitcount = 0; |
19
|
|
|
|
20
|
2 |
|
private function flushbits() |
21
|
|
|
{ |
22
|
2 |
|
if (!empty($this->bits)) { |
23
|
2 |
|
$this->out .= implode('', array_map('chr', $this->bits)); |
24
|
2 |
|
$this->bits = array(); |
25
|
2 |
|
$this->bitcount = 0; |
26
|
|
|
} |
27
|
2 |
|
} |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Get what's been encoded so far. |
31
|
|
|
* |
32
|
|
|
* @return string |
33
|
|
|
*/ |
34
|
707 |
|
public function getvalue() |
35
|
|
|
{ |
36
|
|
|
/* temporarily needed for compatibility with write_bit unit tests */ |
37
|
707 |
|
if ($this->bitcount) { |
38
|
2 |
|
$this->flushbits(); |
39
|
|
|
} |
40
|
|
|
|
41
|
707 |
|
return $this->out; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Write a plain PHP string, with no special encoding. |
46
|
|
|
* |
47
|
|
|
* @param string $s |
48
|
|
|
* |
49
|
|
|
* @return $this |
50
|
|
|
*/ |
51
|
73 |
|
public function write($s) |
52
|
|
|
{ |
53
|
73 |
|
$this->out .= $s; |
54
|
|
|
|
55
|
73 |
|
return $this; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Write a boolean value. |
60
|
|
|
* (deprecated, use write_bits instead) |
61
|
|
|
* |
62
|
|
|
* @deprecated |
63
|
|
|
* @param bool $b |
64
|
|
|
* @return $this |
65
|
|
|
*/ |
66
|
2 |
|
public function write_bit($b) |
67
|
|
|
{ |
68
|
2 |
|
$b = $b ? 1 : 0; |
69
|
2 |
|
$shift = $this->bitcount % 8; |
70
|
2 |
|
$last = $shift === 0 ? 0 : array_pop($this->bits); |
71
|
2 |
|
$last |= ($b << $shift); |
72
|
2 |
|
$this->bits[] = $last; |
73
|
2 |
|
$this->bitcount++; |
74
|
|
|
|
75
|
2 |
|
return $this; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Write multiple bits as an octet |
80
|
|
|
* |
81
|
|
|
* @param bool[] $bits |
82
|
|
|
* @return $this |
83
|
|
|
*/ |
84
|
64 |
|
public function write_bits($bits) |
85
|
|
|
{ |
86
|
64 |
|
$value = 0; |
87
|
|
|
|
88
|
64 |
|
foreach ($bits as $n => $bit) { |
89
|
64 |
|
$bit = $bit ? 1 : 0; |
90
|
64 |
|
$value |= ($bit << $n); |
91
|
|
|
} |
92
|
|
|
|
93
|
64 |
|
$this->out .= chr($value); |
94
|
|
|
|
95
|
64 |
|
return $this; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Write an integer as an unsigned 8-bit value |
100
|
|
|
* |
101
|
|
|
* @param int $n |
102
|
|
|
* @return $this |
103
|
|
|
* @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException |
104
|
|
|
*/ |
105
|
345 |
View Code Duplication |
public function write_octet($n) |
|
|
|
|
106
|
|
|
{ |
107
|
345 |
|
if ($n < 0 || $n > 255) { |
108
|
2 |
|
throw new AMQPInvalidArgumentException('Octet out of range: ' . $n); |
109
|
|
|
} |
110
|
|
|
|
111
|
343 |
|
$this->out .= chr($n); |
112
|
|
|
|
113
|
343 |
|
return $this; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @param int $n |
118
|
|
|
* @return $this |
119
|
|
|
*/ |
120
|
260 |
View Code Duplication |
public function write_signed_octet($n) |
|
|
|
|
121
|
|
|
{ |
122
|
260 |
|
if (($n < -128) || ($n > 127)) { |
123
|
2 |
|
throw new AMQPInvalidArgumentException('Signed octet out of range: ' . $n); |
124
|
|
|
} |
125
|
|
|
|
126
|
258 |
|
$this->out .= pack('c', $n); |
127
|
|
|
|
128
|
258 |
|
return $this; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Write an integer as an unsigned 16-bit value |
133
|
|
|
* |
134
|
|
|
* @param int $n |
135
|
|
|
* @return $this |
136
|
|
|
* @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException |
137
|
|
|
*/ |
138
|
77 |
View Code Duplication |
public function write_short($n) |
|
|
|
|
139
|
|
|
{ |
140
|
77 |
|
if ($n < 0 || $n > 65535) { |
141
|
3 |
|
throw new AMQPInvalidArgumentException('Short out of range: ' . $n); |
142
|
|
|
} |
143
|
|
|
|
144
|
74 |
|
$this->out .= pack('n', $n); |
145
|
|
|
|
146
|
74 |
|
return $this; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* @param int $n |
151
|
|
|
* @return $this |
152
|
|
|
*/ |
153
|
8 |
View Code Duplication |
public function write_signed_short($n) |
|
|
|
|
154
|
|
|
{ |
155
|
8 |
|
if (($n < -32768) || ($n > 32767)) { |
156
|
2 |
|
throw new AMQPInvalidArgumentException('Signed short out of range: ' . $n); |
157
|
|
|
} |
158
|
|
|
|
159
|
6 |
|
$this->out .= $this->correctEndianness(pack('s', $n)); |
160
|
|
|
|
161
|
6 |
|
return $this; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Write an integer as an unsigned 32-bit value |
166
|
|
|
* |
167
|
|
|
* @param int|string $n |
168
|
|
|
* @return $this |
169
|
|
|
*/ |
170
|
88 |
|
public function write_long($n) |
171
|
|
|
{ |
172
|
88 |
|
if (($n < 0) || ($n > 4294967295)) { |
173
|
2 |
|
throw new AMQPInvalidArgumentException('Long out of range: ' . $n); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
//Numeric strings >PHP_INT_MAX on 32bit are casted to PHP_INT_MAX, damn PHP |
177
|
86 |
|
if (!self::PLATFORM_64BIT && is_string($n)) { |
178
|
|
|
$n = (float) $n; |
179
|
|
|
} |
180
|
86 |
|
$this->out .= pack('N', $n); |
181
|
|
|
|
182
|
86 |
|
return $this; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* @param int $n |
187
|
|
|
* @return $this |
188
|
|
|
*/ |
189
|
33 |
View Code Duplication |
private function writeSignedLong($n) |
|
|
|
|
190
|
|
|
{ |
191
|
33 |
|
if (($n < -2147483648) || ($n > 2147483647)) { |
192
|
2 |
|
throw new AMQPInvalidArgumentException('Signed long out of range: ' . $n); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
//on my 64bit debian this approach is slightly faster than splitIntoQuads() |
196
|
31 |
|
$this->out .= $this->correctEndianness(pack('l', $n)); |
197
|
|
|
|
198
|
31 |
|
return $this; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Write a numeric value as an unsigned 64-bit value |
203
|
|
|
* |
204
|
|
|
* @param int|string $n |
205
|
|
|
* @return $this |
206
|
|
|
* @throws AMQPOutOfRangeException |
207
|
|
|
*/ |
208
|
40 |
|
public function write_longlong($n) |
209
|
|
|
{ |
210
|
40 |
|
if (is_int($n)) { |
211
|
23 |
|
if ($n < 0) { |
212
|
|
|
throw new AMQPOutOfRangeException('Longlong out of range: ' . $n); |
213
|
|
|
} |
214
|
|
|
|
215
|
23 |
|
if (self::PLATFORM_64BIT) { |
216
|
23 |
|
$res = pack('J', $n); |
217
|
23 |
|
$this->out .= $res; |
218
|
|
|
} else { |
219
|
|
|
$this->out .= pack('NN', 0, $n); |
220
|
|
|
} |
221
|
|
|
|
222
|
23 |
|
return $this; |
223
|
|
|
} |
224
|
|
|
|
225
|
17 |
|
$value = new BigInteger($n); |
226
|
|
View Code Duplication |
if ( |
|
|
|
|
227
|
17 |
|
$value->compare(self::getBigInteger('0')) < 0 |
228
|
17 |
|
|| $value->compare(self::getBigInteger('FFFFFFFFFFFFFFFF', 16)) > 0 |
229
|
|
|
) { |
230
|
2 |
|
throw new AMQPInvalidArgumentException('Longlong out of range: ' . $n); |
231
|
|
|
} |
232
|
|
|
|
233
|
15 |
|
$value->setPrecision(64); |
234
|
15 |
|
$this->out .= $value->toBytes(); |
235
|
|
|
|
236
|
15 |
|
return $this; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @param int|string $n |
241
|
|
|
* @return $this |
242
|
|
|
*/ |
243
|
38 |
|
public function write_signed_longlong($n) |
244
|
|
|
{ |
245
|
38 |
|
if (is_int($n)) { |
246
|
12 |
|
if (self::PLATFORM_64BIT) { |
247
|
|
|
// q is for 64-bit signed machine byte order |
248
|
12 |
|
$packed = pack('q', $n); |
249
|
12 |
|
if (self::isLittleEndian()) { |
250
|
12 |
|
$packed = $this->convertByteOrder($packed); |
251
|
|
|
} |
252
|
12 |
|
$this->out .= $packed; |
253
|
|
|
} else { |
254
|
|
|
$hi = $n < 0 ? -1 : 0; |
255
|
|
|
$lo = $n; |
256
|
|
|
$this->out .= pack('NN', $hi, $lo); |
257
|
|
|
} |
258
|
|
|
|
259
|
12 |
|
return $this; |
260
|
|
|
} |
261
|
|
|
|
262
|
26 |
|
$value = new BigInteger($n); |
263
|
|
View Code Duplication |
if ( |
|
|
|
|
264
|
26 |
|
$value->compare(self::getBigInteger('-8000000000000000', 16)) < 0 |
265
|
26 |
|
|| $value->compare(self::getBigInteger('7FFFFFFFFFFFFFFF', 16)) > 0 |
266
|
|
|
) { |
267
|
2 |
|
throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n); |
268
|
|
|
} |
269
|
|
|
|
270
|
24 |
|
$value->setPrecision(64); |
271
|
24 |
|
$this->out .= substr($value->toBytes(true), -8); |
272
|
|
|
|
273
|
24 |
|
return $this; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Write a string up to 255 bytes long after encoding. |
278
|
|
|
* Assume UTF-8 encoding |
279
|
|
|
* |
280
|
|
|
* @param string $s |
281
|
|
|
* @return $this |
282
|
|
|
* @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException |
283
|
|
|
*/ |
284
|
82 |
|
public function write_shortstr($s) |
285
|
|
|
{ |
286
|
82 |
|
$len = mb_strlen($s, 'ASCII'); |
287
|
82 |
|
if ($len > 255) { |
288
|
2 |
|
throw new AMQPInvalidArgumentException('String too long'); |
289
|
|
|
} |
290
|
|
|
|
291
|
80 |
|
$this->write_octet($len); |
292
|
80 |
|
$this->out .= $s; |
293
|
|
|
|
294
|
80 |
|
return $this; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Write a string up to 2**32 bytes long. Assume UTF-8 encoding |
299
|
|
|
* |
300
|
|
|
* @param string $s |
301
|
|
|
* @return $this |
302
|
|
|
*/ |
303
|
56 |
|
public function write_longstr($s) |
304
|
|
|
{ |
305
|
56 |
|
$this->write_long(mb_strlen($s, 'ASCII')); |
306
|
56 |
|
$this->out .= $s; |
307
|
|
|
|
308
|
56 |
|
return $this; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* Supports the writing of Array types, so that you can implement |
313
|
|
|
* array methods, like Rabbitmq's HA parameters |
314
|
|
|
* |
315
|
|
|
* @param AMQPArray|array $a Instance of AMQPArray or PHP array WITHOUT format hints (unlike write_table()) |
316
|
|
|
* @return self |
317
|
|
|
*/ |
318
|
10 |
|
public function write_array($a) |
319
|
|
|
{ |
320
|
10 |
|
if (!($a instanceof AMQPArray)) { |
321
|
6 |
|
$a = new AMQPArray($a); |
322
|
|
|
} |
323
|
10 |
|
$data = new self(); |
324
|
|
|
|
325
|
10 |
|
foreach ($a as $v) { |
326
|
9 |
|
$data->writeValue($v[0], $v[1]); |
327
|
|
|
} |
328
|
|
|
|
329
|
10 |
|
$data = $data->getvalue(); |
330
|
10 |
|
$this->write_long(mb_strlen($data, 'ASCII')); |
331
|
10 |
|
$this->write($data); |
332
|
|
|
|
333
|
10 |
|
return $this; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Write unix time_t value as 64 bit timestamp |
338
|
|
|
* |
339
|
|
|
* @param int $v |
340
|
|
|
* @return $this |
341
|
|
|
*/ |
342
|
1 |
|
public function write_timestamp($v) |
343
|
|
|
{ |
344
|
1 |
|
$this->write_longlong($v); |
345
|
|
|
|
346
|
1 |
|
return $this; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* Write PHP array, as table. Input array format: keys are strings, |
351
|
|
|
* values are (type,value) tuples. |
352
|
|
|
* |
353
|
|
|
* @param AMQPTable|array $d Instance of AMQPTable or PHP array WITH format hints (unlike write_array()) |
354
|
|
|
* @return $this |
355
|
|
|
* @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException |
356
|
|
|
*/ |
357
|
63 |
|
public function write_table($d) |
358
|
|
|
{ |
359
|
63 |
|
$typeIsSym = !($d instanceof AMQPTable); //purely for back-compat purposes |
360
|
|
|
|
361
|
63 |
|
$table_data = new AMQPWriter(); |
362
|
63 |
|
foreach ($d as $k => $va) { |
363
|
56 |
|
list($ftype, $v) = $va; |
364
|
56 |
|
$table_data->write_shortstr($k); |
365
|
56 |
|
$table_data->writeValue($typeIsSym ? AMQPAbstractCollection::getDataTypeForSymbol($ftype) : $ftype, $v); |
366
|
|
|
} |
367
|
|
|
|
368
|
62 |
|
$table_data = $table_data->getvalue(); |
369
|
62 |
|
$this->write_long(mb_strlen($table_data, 'ASCII')); |
370
|
62 |
|
$this->write($table_data); |
371
|
|
|
|
372
|
62 |
|
return $this; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* for compat with method mapping used by AMQPMessage |
377
|
|
|
* |
378
|
|
|
* @param AMQPTable|array $d |
379
|
|
|
* @return $this |
380
|
|
|
*/ |
381
|
8 |
|
public function write_table_object($d) |
382
|
|
|
{ |
383
|
8 |
|
return $this->write_table($d); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* @param int $type One of AMQPAbstractCollection::T_* constants |
388
|
|
|
* @param mixed $val |
389
|
|
|
*/ |
390
|
58 |
|
private function writeValue($type, $val) |
391
|
|
|
{ |
392
|
|
|
//This will find appropriate symbol for given data type for currently selected protocol |
393
|
|
|
//Also will raise an exception on unknown type |
394
|
58 |
|
$this->write(AMQPAbstractCollection::getSymbolForDataType($type)); |
395
|
|
|
|
396
|
|
|
switch ($type) { |
397
|
58 |
|
case AMQPAbstractCollection::T_INT_SHORTSHORT: |
398
|
2 |
|
$this->write_signed_octet($val); |
399
|
2 |
|
break; |
400
|
58 |
|
case AMQPAbstractCollection::T_INT_SHORTSHORT_U: |
401
|
2 |
|
$this->write_octet($val); |
402
|
2 |
|
break; |
403
|
58 |
|
case AMQPAbstractCollection::T_INT_SHORT: |
404
|
2 |
|
$this->write_signed_short($val); |
405
|
2 |
|
break; |
406
|
58 |
|
case AMQPAbstractCollection::T_INT_SHORT_U: |
407
|
2 |
|
$this->write_short($val); |
408
|
2 |
|
break; |
409
|
58 |
|
case AMQPAbstractCollection::T_INT_LONG: |
410
|
11 |
|
$this->writeSignedLong($val); |
411
|
11 |
|
break; |
412
|
55 |
|
case AMQPAbstractCollection::T_INT_LONG_U: |
413
|
|
|
$this->write_long($val); |
414
|
|
|
break; |
415
|
55 |
|
case AMQPAbstractCollection::T_INT_LONGLONG: |
416
|
3 |
|
$this->write_signed_longlong($val); |
417
|
3 |
|
break; |
418
|
55 |
|
case AMQPAbstractCollection::T_INT_LONGLONG_U: |
419
|
|
|
$this->write_longlong($val); |
420
|
|
|
break; |
421
|
55 |
|
case AMQPAbstractCollection::T_DECIMAL: |
422
|
|
|
$this->write_octet($val->getE()); |
423
|
|
|
$this->writeSignedLong($val->getN()); |
424
|
|
|
break; |
425
|
55 |
|
case AMQPAbstractCollection::T_TIMESTAMP: |
426
|
|
|
$this->write_timestamp($val); |
427
|
|
|
break; |
428
|
55 |
|
case AMQPAbstractCollection::T_BOOL: |
429
|
50 |
|
$this->write_octet($val ? 1 : 0); |
430
|
50 |
|
break; |
431
|
55 |
|
case AMQPAbstractCollection::T_STRING_SHORT: |
432
|
2 |
|
$this->write_shortstr($val); |
433
|
2 |
|
break; |
434
|
55 |
|
case AMQPAbstractCollection::T_STRING_LONG: |
435
|
53 |
|
$this->write_longstr($val); |
436
|
53 |
|
break; |
437
|
50 |
|
case AMQPAbstractCollection::T_ARRAY: |
438
|
8 |
|
$this->write_array($val); |
439
|
8 |
|
break; |
440
|
47 |
|
case AMQPAbstractCollection::T_TABLE: |
441
|
45 |
|
$this->write_table($val); |
442
|
45 |
|
break; |
443
|
2 |
|
case AMQPAbstractCollection::T_VOID: |
444
|
1 |
|
break; |
445
|
1 |
|
case AMQPAbstractCollection::T_BYTES: |
446
|
1 |
|
$this->write_longstr($val); |
447
|
1 |
|
break; |
448
|
|
|
default: |
449
|
|
|
throw new AMQPInvalidArgumentException(sprintf( |
450
|
|
|
'Unsupported type "%s"', |
451
|
|
|
$type |
452
|
|
|
)); |
453
|
|
|
} |
454
|
58 |
|
} |
455
|
|
|
} |
456
|
|
|
|
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.