Test Setup Failed
Push — master ( f2b9b8...38e491 )
by Nikita
02:19
created

Binn.php (3 issues)

1
<?php
2
/**
3
 * Binn. Serialize to bin string.
4
 * Binn Specification: https://github.com/liteserver/binn/blob/master/spec.md
5
 *
6
 * Note! This class not support Map and Object, only List support. Sorry, i am working on this.
7
 *
8
 * Original Binn Library for C++ - https://github.com/liteserver/binn
9
 *
10
 *
11
 * @author      Nikita Kuznetsov (NiK)
12
 * @copyright   Copyright (c) 2016, Nikita Kuznetsov ([email protected])
13
 * @license     GNU GPL
14
 * @link        http://www.gameap.ru
15
 *
16
 */
17
18
namespace knik;
19
20
/**
21
 * @method Binn add_bool(boolean $value)
22
 * @method Binn add_uint8(integer $value)
23
 * @method Binn add_uint16(integer $value)
24
 * @method Binn add_uint32(integer $value)
25
 * @method Binn add_uint64(integer $value)
26
 * @method Binn add_int8(integer $value)
27
 * @method Binn add_int16(integer $value)
28
 * @method Binn add_int32(integer $value)
29
 * @method Binn add_int64(integer $value)
30
 * @method Binn add_str(string $value)
31
 * @method Binn add_list(Binn $value)
32
 * @method Binn add_map(Binn $value)
33
 * @method Binn add_object(Binn $value)
34
 *
35
 */
36
class Binn {
37
38
    // Consts from original C++ Library
39
    const BINN_LIST         = 0xE0;
40
    const BINN_MAP          = 0xE1;
41
    const BINN_OBJECT       = 0xE2;
42
43
    const BINN_UINT8        = 0x20;
44
    const BINN_INT8         = 0x21;
45
    const BINN_UINT16       = 0x40;
46
    const BINN_INT16        = 0x41;
47
    const BINN_UINT32       = 0x60;
48
    const BINN_INT32        = 0x61;
49
    const BINN_UINT64       = 0x80;
50
    const BINN_INT64        = 0x81;
51
    const BINN_STRING       = 0xA0;
52
53
    const BINN_BOOL         = 0x80061;
54
55
    const BINN_STORAGE_NOBYTES      = 0x00;
56
    const BINN_STORAGE_BYTE         = 0x20;  //  8 bits
57
    const BINN_STORAGE_WORD         = 0x40;  // 16 bits -- the endianess (byte order) is automatically corrected
58
    const BINN_STORAGE_DWORD        = 0x60;  // 32 bits -- the endianess (byte order) is automatically corrected
59
    const BINN_STORAGE_QWORD        = 0x80;  // 64 bits -- the endianess (byte order) is automatically corrected
60
    const BINN_STORAGE_STRING       = 0xA0;  // Are stored with null termination
61
    const BINN_STORAGE_BLOB         = 0xC0;
62
    const BINN_STORAGE_CONTAINER    = 0xE0;
63
64
    const BINN_NULL                 = 0x00;
65
    const BINN_TRUE                 = 0x01;
66
    const BINN_FALSE                = 0x02;
67
68
    const UINT8_MAX                 = 255;
69
    const UINT16_MAX                = 65535;
70
    const UINT32_MAX                = 4294967295;
71
    const UINT64_MAX                = 18446744073709551615;
72
73
    const INT8_MIN                  = -128;
74
    const INT8_MAX                  = 127;
75
    const INT16_MIN                 = -32768;
76
    const INT16_MAX                 = 32767;
77
    const INT32_MIN                 = -2147483648;
78
    const INT32_MAX                 = 2147483647;
79
    const INT64_MIN                 = -9223372036854775808;
80
    const INT64_MAX                 = 9223372036854775807;
81
82
    const MIN_BINN_SIZE             = 3;
83
84
    // PHP Library consts
85
    const KEY_TYPE                 = 0;
86
    const KEY_VAL                  = 1;
87
    const KEY_SIZE                 = 2;
88
89
    /**
90
     * @var array
91
     */
92
    private $methods_assignments = [
93
        'add_bool'      => self::BINN_BOOL,
94
        'add_uint8'     => self::BINN_UINT8,
95
        'add_uint16'    => self::BINN_UINT16,
96
        'add_uint32'    => self::BINN_UINT32,
97
        'add_uint64'    => self::BINN_UINT64,
98
        'add_int8'      => self::BINN_INT8,
99
        'add_int16'     => self::BINN_INT16,
100
        'add_int32'     => self::BINN_INT32,
101
        'add_int64'     => self::BINN_INT64,
102
        'add_str'       => self::BINN_STRING,
103
        'add_list'      => self::BINN_LIST,
104
        'add_map'       => self::BINN_MAP,
105
        'add_object'    => self::BINN_OBJECT,
106
    ];
107
108
    /**
109
     * Binn object type: self::BINN_LIST, self::BINN_MAP, self::BINN_OBJECT
110
     *
111
     * @var int $binn_type
112
     * @access protected
113
     */
114
    protected $binn_type;
115
116
    /**
117
     * Count elements in object
118
     *
119
     * @var int
120
     * @access protected
121
     */
122
    protected $count        = 0;
123
124
    /**
125
     * Data size in bytes
126
     *
127
     * @var int
128
     * @access protected
129
     */
130
    protected $data_size    = 0;
131
132
    /**
133
     * Meta size in bytes
134
     *
135
     * @var int
136
     */
137
    protected $meta_size    = self::MIN_BINN_SIZE;
138
139
    /**
140
     * Size bin string in bytes
141
     *
142
     * @var int
143
     * @access protected
144
     */
145
    protected $size         = 0;
146
147
    /**
148
     * Bin string
149
     *
150
     * @var string
151
     * @access protected
152
     */
153
    protected $binn_string     = "";
154
155
    /**
156
     * Object elements
157
     *
158
     * @var array
159
     * @access protected
160
     */
161
    protected $binn_arr = [];
162
163
    // -----------------------------------------------------------------
164
165
    public function __construct($binstring = '')
166
    {
167
        $this->binn_list();
168
169
        if ($binstring != '') {
170
            $this->_binn_load($binstring);
171
        }
172
    }
173
174
    // -----------------------------------------------------------------
175
176
    /**
177
     * @param int   $type
178
     * @param mixed   $val
179
     *
180
     * @return int  $type2
181
     *
182
     */
183
    protected function compress_int($type, $val)
184
    {
185
        $type2 = $type;
186
187
        if ($val >= 0) {
188
            // Convert to unsigned
189
            switch ($type) {
190
                case self::BINN_INT64:
191
                    $type = self::BINN_UINT64;
192
                    break;
193
194
                case self::BINN_INT32:
195
                    $type = self::BINN_UINT32;
196
                    break;
197
198
                case self::BINN_INT16:
199
                    $type = self::BINN_UINT16;
200
                    break;
201
            }
202
        }
203
204
        if (in_array($type, [self::BINN_INT64, self::BINN_INT32, self::BINN_INT16])) {
205
            // Signed
206
            if ($val >= self::INT8_MIN) {
207
                $type2 = self::BINN_INT8;
208
            }
209
            elseif ($val >= self::INT16_MIN) {
210
                $type2 = self::BINN_INT16;
211
            }
212
            elseif ($val >= self::INT32_MIN) {
213
                $type2 = self::BINN_INT32;
214
            }
215
        }
216
217
        if (in_array($type, [self::BINN_UINT64, self::BINN_UINT32, self::BINN_UINT16])) {
218
            // Unsigned
219
220
            if ($val <= self::UINT8_MAX) {
221
                $type2 = self::BINN_UINT8;
222
            }
223
            elseif ($val <= self::UINT16_MAX) {
224
                $type2 = self::BINN_UINT16;
225
            }
226
            elseif ($val <= self::UINT32_MAX) {
227
                $type2 = self::BINN_UINT32;
228
            }
229
        }
230
231
        return $type2;
232
    }
233
234
    // -----------------------------------------------------------------
235
236
    public function binn_free()
237
    {
238
        $this->binn_type = self::BINN_STORAGE_NOBYTES;
239
240
        $this->count        = 0;
241
        $this->data_size    = 0;
242
243
        // Initial meta size 3 bytes
244
        // Type byte + Size byte + Item counts byte
245
        $this->meta_size    = 3;
246
247
        $this->size         = 0;
248
        $this->binn_string  = "";
249
250
        $this->sub_objects  = [];
0 ignored issues
show
Bug Best Practice introduced by
The property sub_objects does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
251
        $this->binn_arr     = [];
252
253
        return $this;
254
    }
255
256
    // -----------------------------------------------------------------
257
258
    /**
259
     * @param string @bindstring
0 ignored issues
show
Documentation Bug introduced by
The doc comment @bindstring at position 0 could not be parsed: Unknown type name '@bindstring' at position 0 in @bindstring.
Loading history...
260
     * @return $this
261
     */
262
    public function binn_open($binstring = "")
263
    {
264
        $this->_binn_load($binstring);
265
        return $this;
266
    }
267
268
    // -----------------------------------------------------------------
269
270
    /**
271
     *
272
     *  @return int
273
     */
274
    private function _calculate_size()
275
    {
276
        $size = 0;
277
278
        if (($this->data_size + $this->meta_size) > 127) {
279
            $size += 3;
280
        }
281
282
        if (count($this->binn_arr) > 127) {
283
            $size += 3;
284
        }
285
286
        $this->size = ($this->data_size + $this->meta_size) + $size;
287
        return $this->size;
288
    }
289
290
    // -----------------------------------------------------------------
291
292
    /**
293
     * @param int   $type
294
     * @param mixed $value
295
     */
296
    private function _add_val($type, $value)
297
    {
298
        if (in_array($type,
299
            [self::BINN_INT64, self::BINN_INT32, self::BINN_INT16,
300
                self::BINN_UINT64,self::BINN_UINT32, self::BINN_UINT16])
301
         ) {
302
            $type = $this->compress_int($type, $value);
303
        }
304
305
        // Data size
306
        switch ($type) {
307
            case self::BINN_BOOL:
308
            case self::BINN_TRUE:
309
            case self::BINN_FALSE:
310
                $meta_size = 1;
311
                $data_size = 0;
312
                break;
313
314
            case self::BINN_INT8:
315
            case self::BINN_UINT8:
316
                $meta_size = 1;
317
                $data_size = 1;
318
                break;
319
320
            case self::BINN_INT16:
321
            case self::BINN_UINT16:
322
                $meta_size = 1;
323
                $data_size = 2;
324
                break;
325
326
            case self::BINN_INT32:
327
            case self::BINN_UINT32:
328
                $meta_size = 1;
329
                $data_size = 4;
330
                break;
331
332
            case self::BINN_INT64:
333
            case self::BINN_UINT64:
334
                $meta_size = 1;
335
                $data_size = 8;
336
                break;
337
338
            case self::BINN_STRING:
339
                $data_size = strlen($value);
340
341
                $meta_size = $data_size > 127 ? 4 : 1; // size byte
342
                $meta_size += 2; // type byte + null terminated
343
                break;
344
345
            case self::BINN_LIST:
346
                $data_size = $value->binn_size();
347
                $meta_size = 0;
348
                break;
349
350
            default:
351
                // Unknown type
352
                return false;
353
                break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
354
        }
355
356
        $this->data_size += $data_size;
357
        $this->meta_size += $meta_size;
358
359
        $this->count++;
360
361
        $this->binn_arr[] = [
362
            self::KEY_TYPE      => $type,
363
            self::KEY_VAL       => $value,
364
            self::KEY_SIZE      => $data_size
365
        ];
366
    }
367
368
    // -----------------------------------------------------------------
369
370
    /**
371
     *
372
     *  @return array
373
     */
374
    public function get_binn_arr()
375
    {
376
        $return = [];
377
378
        foreach ($this->binn_arr as &$arr) {
379
            switch ($arr[self::KEY_TYPE]) {
380
                case self::BINN_LIST:
381
                    $return[] = $arr[self::KEY_VAL]->get_binn_arr();
382
                    break;
383
384
                case self::BINN_BOOL:
385
                case self::BINN_TRUE:
386
                case self::BINN_FALSE:
387
                case self::BINN_INT64:
388
                case self::BINN_UINT64:
389
                case self::BINN_INT32:
390
                case self::BINN_UINT32:
391
                case self::BINN_INT16:
392
                case self::BINN_UINT16:
393
                case self::BINN_INT8:
394
                case self::BINN_UINT8:
395
                case self::BINN_STRING:
396
                    $return[] = $arr[self::KEY_VAL];
397
                    break;
398
            }
399
        }
400
401
        return $return;
402
    }
403
404
    // -----------------------------------------------------------------
405
406
    /**
407
     * @return int
408
     */
409
    public function binn_size()
410
    {
411
        $this->_calculate_size();
412
        return $this->size;
413
    }
414
415
    // -----------------------------------------------------------------
416
417
    /**
418
     *
419
     * @param int $int_val
420
     *
421
     * @return string   HEX string
422
     */
423
    private function _get_int32_binsize($int_val = 0)
424
    {
425
        $int_val = ($int_val | (1 << 31)); // Add byte
426
        return pack("N", $int_val);
427
    }
428
429
    // -----------------------------------------------------------------
430
431
    /**
432
     * Get binary string
433
     *
434
     * @return string
435
     */
436
    public function get_binn_val()
437
    {
438
        $this->_calculate_size();
439
440
        $this->binn_string = '';
441
        $this->binn_string .= pack("C", $this->binn_type);
442
443
        $this->binn_string .= ($this->size <= 127)
444
            ? pack("C", $this->size)
445
            : $this->_get_int32_binsize($this->size);
446
447
        $count = count($this->binn_arr);
448
        $this->binn_string .= ($count <= 127)
449
            ? pack("C", $count)
450
            : $this->_get_int32_binsize($count);
451
452
        foreach ($this->binn_arr as &$arr) {
453
            switch ($arr[self::KEY_TYPE]) {
454
                case self::BINN_BOOL:
455
                    $this->binn_string .= $arr[self::KEY_VAL] ? pack("C", self::BINN_TRUE) : pack("C", self::BINN_FALSE);
456
                    break;
457
458
                case self::BINN_TRUE:
459
                    $this->binn_string .= pack("C", self::BINN_TRUE);
460
                    break;
461
462
                case self::BINN_FALSE:
463
                    $this->binn_string .= pack("C", self::BINN_FALSE);
464
                    break;
465
466
                case self::BINN_UINT8:
467
                    $this->binn_string .= pack("C", self::BINN_UINT8);
468
                    $this->binn_string .= pack("C", $arr[self::KEY_VAL]);
469
                    break;
470
471
                case self::BINN_UINT16:
472
                    $this->binn_string .= pack("C", self::BINN_UINT16);
473
                    $this->binn_string .= pack("n", $arr[self::KEY_VAL]);
474
                    break;
475
476
                case self::BINN_UINT32:
477
                    $this->binn_string .= pack("C", self::BINN_UINT32);
478
                    $this->binn_string .= pack("N", $arr[self::KEY_VAL]);
479
                    break;
480
481
                case self::BINN_UINT64:
482
                    $this->binn_string .= pack("C", self::BINN_UINT64);
483
                    $this->binn_string .= pack("J", $arr[self::KEY_VAL]);
484
                    break;
485
486
                case self::BINN_INT8:
487
                    $this->binn_string .= pack("C", self::BINN_UINT8);
488
                    $this->binn_string .= pack("c", $arr[self::KEY_VAL]);
489
                    break;
490
491
                case self::BINN_INT16:
492
                    $this->binn_string .= pack("C", self::BINN_INT16);
493
                    $this->binn_string .= strrev(pack("s", $arr[self::KEY_VAL]));
494
                    break;
495
496
                case self::BINN_INT32:
497
                    $this->binn_string .= pack("C", self::BINN_INT32);
498
                    $this->binn_string .= strrev(pack("l", $arr[self::KEY_VAL]));
499
                    break;
500
501
                case self::BINN_INT64:
502
                    $this->binn_string .= pack("C", self::BINN_INT64);
503
                    $this->binn_string .= strrev(pack("q", $arr[self::KEY_VAL]));
504
                    break;
505
506
                case self::BINN_STRING:
507
                    $this->binn_string .= pack("C", self::BINN_STRING);
508
509
                    if ($arr[self::KEY_SIZE] <= 127) {
510
                        $this->binn_string .= pack("C", $arr[self::KEY_SIZE]);
511
                    } else {
512
                        $this->binn_string .= $this->_get_int32_binsize($arr[self::KEY_SIZE]);
513
                    }
514
515
                    $this->binn_string .= pack("a*x", $arr[self::KEY_VAL]);
516
                    break;
517
518
                case self::BINN_LIST:
519
                    $this->binn_string .= $arr[self::KEY_VAL]->get_binn_val();
520
                    break;
521
            }
522
        }
523
524
        return $this->binn_string;
525
    }
526
527
    // -----------------------------------------------------------------
528
529
    /**
530
     * @param string $name
531
     * @param mixed $arguments
532
     */
533
    public function __call($name, $arguments)
534
    {
535
        if (array_key_exists($name, $this->methods_assignments)) {
536
            $this->_add_val($this->methods_assignments[$name], $arguments[0]);
537
            return $this;
538
        }
539
540
        throw new \Exception("Call to undefined method {$name}");
541
    }
542
543
    // -----------------------------------------------------------------
544
545
    public function binn_list()
546
    {
547
        $this->binn_type = self::BINN_LIST;
548
        return $this;
549
    }
550
551
    // -----------------------------------------------------------------
552
553
    /**
554
     * @param string
555
     */
556
    private function _binn_load($binstring)
557
    {
558
        $pos = 1; // Position
559
        $size_bytes = unpack("C", $binstring[$pos])[1];
560
561
        // Size
562
        if ($size_bytes & 1 << 7) {
563
            $size_bytes = unpack("N", substr($binstring, $pos, 4))[1];
564
            $this->size = ($size_bytes &~ (1 << 31)); // Cut bit
565
            $pos += 4;
566
        } else {
567
            $this->size = $size_bytes;
568
            $pos += 1;
569
        }
570
571
        unset($size_bytes);
572
573
        $count_bytes = unpack("C", $binstring[$pos])[1];
574
575
        // Size
576
        if ($count_bytes & 1 << 7) {
577
            $count_bytes = unpack("N", substr($binstring,$pos, 4))[1];
578
            $this->count = ($count_bytes &~ (1 << 31)); // Cut bit
579
            $pos += 4;
580
        } else {
581
            $this->count = $count_bytes;
582
            $pos += 1;
583
        }
584
585
        unset($count_bytes);
586
587
        // Data
588
        $stop_while = false;
589
        while ($pos < $this->size && !$stop_while) {
590
            $byte_var_type = @unpack("C", $binstring[$pos])[1];
591
            $pos += 1;
592
593
594
            // $cur_type = strtotime(base_convert($byte_var_type, 10, 16));
595
596
            switch ($byte_var_type) {
597
                case self::BINN_TRUE:
598
                    $this->_add_val(self::BINN_BOOL, true);
599
                    break;
600
601
                case self::BINN_FALSE:
602
                    $this->_add_val(self::BINN_BOOL, false);
603
                    break;
604
605
                case self::BINN_UINT64:
606
                    $this->_add_val(self::BINN_UINT64, unpack("J", substr($binstring, $pos, 8))[1]);
607
                    $pos += 8;
608
                    break;
609
610
                case self::BINN_UINT32:
611
                    $this->_add_val(self::BINN_UINT32, unpack("N", substr($binstring, $pos, 4))[1]);
612
                    $pos += 4;
613
                    break;
614
615
                case self::BINN_UINT16:
616
                    $this->_add_val(self::BINN_UINT16, unpack("n", substr($binstring, $pos, 2))[1]);
617
                    $pos += 2;
618
                    break;
619
620
                case self::BINN_UINT8:
621
                    $this->_add_val(self::BINN_UINT8, unpack("C", substr($binstring, $pos, 1))[1]);
622
                    $pos += 1;
623
                    break;
624
625
                case self::BINN_INT8:
626
                    $this->_add_val(self::BINN_INT8, unpack("c", substr($binstring, $pos, 1))[1]);
627
                    $pos += 1;
628
                    break;
629
630
                case self::BINN_INT16:
631
                    $this->_add_val(self::BINN_INT16, unpack("s", strrev(substr($binstring, $pos, 2)))[1]);
632
                    $pos += 2;
633
                    break;
634
635
                case self::BINN_INT32:
636
                    $this->_add_val(self::BINN_INT16, unpack("i", strrev(substr($binstring, $pos, 4)))[1]);
637
                    $pos += 4;
638
                    break;
639
640
                case self::BINN_INT64:
641
                    $this->_add_val(self::BINN_INT16, unpack("q", strrev(substr($binstring, $pos, 8)))[1]);
642
                    $pos += 8;
643
                    break;
644
645
                case self::BINN_STRING:
646
                    $string_size = unpack("C", $binstring[$pos])[1];
647
648
                    // Size
649
                    if ($string_size & 1 << 7) {
650
                        $string_size = unpack("N", substr($binstring, $pos, 4))[1];
651
                        $string_size = ($string_size &~ (1 << 31)); // Cut bit
652
                        $pos += 4;
653
                    } else {
654
                        $pos += 1;
655
                    }
656
657
                    $this->_add_val(self::BINN_STRING, unpack("a*", substr($binstring, $pos, $string_size))[1]);
658
                    $pos += $string_size;
659
                    $pos += 1; // Null byte
660
                    break;
661
662
                case self::BINN_LIST:
663
                    $list_size = unpack("C", $binstring[$pos])[1];
664
665
                    // Size
666
                    if ($list_size & 1 << 7) {
667
                        $list_size = unpack("N", substr($binstring, $pos, 4))[1];
668
                        $list_size = ($list_size &~ (1 << 31)); // Cut bit
669
                    }
670
671
                    $substring = substr($binstring, $pos-1, $list_size);
672
                    $this->_add_val(self::BINN_LIST, new Binn($substring));
673
674
                    $pos += ($list_size-1);
675
676
                    break;
677
678
                default:
679
                    $stop_while = true;
680
                    break;
681
            }
682
683
        }
684
    }
685
}