Completed
Pull Request — v3 (#389)
by Soner
06:47
created

Buffer::readInt32Signed()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * This file is part of GameQ.
4
 *
5
 * GameQ is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU Lesser General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * GameQ is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 *
18
 *
19
 */
20
21
namespace GameQ;
22
23
use GameQ\Exception\Protocol as Exception;
24
25
/**
26
 * Class Buffer
27
 *
28
 * Read specific byte sequences from a provided string or Buffer
29
 *
30
 * @package GameQ
31
 *
32
 * @author  Austin Bischoff <[email protected]>
33
 * @author  Aidan Lister <[email protected]>
34
 * @author  Tom Buskens <[email protected]>
35
 */
36
class Buffer
37
{
38
39
    /**
40
     * Constants for the byte code types we need to read as
41
     */
42
    const NUMBER_TYPE_BIGENDIAN = 'be',
43
        NUMBER_TYPE_LITTLEENDIAN = 'le',
44
        NUMBER_TYPE_MACHINE = 'm';
45
46
    /**
47
     * The number type we use for reading integers.  Defaults to little endian
48
     *
49
     * @type string
50
     */
51
    private $number_type = self::NUMBER_TYPE_LITTLEENDIAN;
52
53
    /**
54
     * The original data
55
     *
56
     * @type string
57
     */
58
    private $data;
59
60
    /**
61
     * The original data
62
     *
63
     * @type int
64
     */
65
    private $length;
66
67
    /**
68
     * Position of pointer
69
     *
70
     * @type int
71
     */
72
    private $index = 0;
73
74
    /**
75
     * Constructor
76
     *
77
     * @param string $data
78
     * @param string $number_type
79
     */
80 221
    public function __construct($data, $number_type = self::NUMBER_TYPE_LITTLEENDIAN)
81
    {
82
83 221
        $this->number_type = $number_type;
84 221
        $this->data = $data;
85 221
        $this->length = strlen($data);
86 221
    }
87
88
    /**
89
     * Return all the data
90
     *
91
     * @return  string    The data
92
     */
93 1
    public function getData()
94
    {
95
96 1
        return $this->data;
97
    }
98
99
    /**
100
     * Return data currently in the buffer
101
     *
102
     * @return  string    The data currently in the buffer
103
     */
104 140
    public function getBuffer()
105
    {
106
107 140
        return substr($this->data, $this->index);
108
    }
109
110
    /**
111
     * Returns the number of bytes in the buffer
112
     *
113
     * @return  int  Length of the buffer
114
     */
115 137
    public function getLength()
116
    {
117
118 137
        return max($this->length - $this->index, 0);
119
    }
120
121
    /**
122
     * Read from the buffer
123
     *
124
     * @param int $length
125
     *
126
     * @return string
127
     * @throws \GameQ\Exception\Protocol
128
     */
129 210
    public function read($length = 1)
130
    {
131
132 210
        if (($length + $this->index) > $this->length) {
133 2
            throw new Exception("Unable to read length={$length} from buffer.  Bad protocol format or return?");
134
        }
135
136 208
        $string = substr($this->data, $this->index, $length);
137 208
        $this->index += $length;
138
139 208
        return $string;
140
    }
141
142
    /**
143
     * Read the last character from the buffer
144
     *
145
     * Unlike the other read functions, this function actually removes
146
     * the character from the buffer.
147
     *
148
     * @return string
149
     */
150 1
    public function readLast()
151
    {
152
153 1
        $len = strlen($this->data);
154 1
        $string = $this->data{strlen($this->data) - 1};
155 1
        $this->data = substr($this->data, 0, $len - 1);
156 1
        $this->length -= 1;
157
158 1
        return $string;
159
    }
160
161
    /**
162
     * Look at the buffer, but don't remove
163
     *
164
     * @param int $length
165
     *
166
     * @return string
167
     */
168 94
    public function lookAhead($length = 1)
169
    {
170
171 94
        return substr($this->data, $this->index, $length);
172
    }
173
174
    /**
175
     * Skip forward in the buffer
176
     *
177
     * @param int $length
178
     */
179 58
    public function skip($length = 1)
180
    {
181
182 58
        $this->index += $length;
183 58
    }
184
185
    /**
186
     * Jump to a specific position in the buffer,
187
     * will not jump past end of buffer
188
     *
189
     * @param $index
190
     */
191 9
    public function jumpto($index)
192
    {
193
194 9
        $this->index = min($index, $this->length - 1);
195 9
    }
196
197
    /**
198
     * Get the current pointer position
199
     *
200
     * @return int
201
     */
202 1
    public function getPosition()
203
    {
204
205 1
        return $this->index;
206
    }
207
208
    /**
209
     * Read from buffer until delimiter is reached
210
     *
211
     * If not found, return everything
212
     *
213
     * @param string $delim
214
     *
215
     * @return string
216
     * @throws \GameQ\Exception\Protocol
217
     */
218 140
    public function readString($delim = "\x00")
219
    {
220
221
        // Get position of delimiter
222 140
        $len = strpos($this->data, $delim, min($this->index, $this->length));
223
224
        // If it is not found then return whole buffer
225 140
        if ($len === false) {
226 13
            return $this->read(strlen($this->data) - $this->index);
227
        }
228
229
        // Read the string and remove the delimiter
230 140
        $string = $this->read($len - $this->index);
231 140
        ++$this->index;
232
233 140
        return $string;
234
    }
235
236
    /**
237
     * Reads a pascal string from the buffer
238
     *
239
     * @param int  $offset      Number of bits to cut off the end
240
     * @param bool $read_offset True if the data after the offset is to be read
241
     *
242
     * @return string
243
     * @throws \GameQ\Exception\Protocol
244
     */
245 10
    public function readPascalString($offset = 0, $read_offset = false)
246
    {
247
248
        // Get the proper offset
249 10
        $len = $this->readInt8();
250 10
        $offset = max($len - $offset, 0);
251
252
        // Read the data
253 10
        if ($read_offset) {
254 5
            return $this->read($offset);
255
        } else {
256 5
            return substr($this->read($len), 0, $offset);
257
        }
258
    }
259
260
    /**
261
     * Read from buffer until any of the delimiters is reached
262
     *
263
     * If not found, return everything
264
     *
265
     * @param      $delims
266
     * @param null $delimfound
267
     *
268
     * @return string
269
     * @throws \GameQ\Exception\Protocol
270
     *
271
     * @todo: Check to see if this is even used anymore
272
     */
273 11
    public function readStringMulti($delims, &$delimfound = null)
274
    {
275
276
        // Get position of delimiters
277 11
        $pos = [];
278 11
        foreach ($delims as $delim) {
279 11
            if ($p = strpos($this->data, $delim, min($this->index, $this->length))) {
280 11
                $pos[] = $p;
281
            }
282
        }
283
284
        // If none are found then return whole buffer
285 11
        if (empty($pos)) {
286 11
            return $this->read(strlen($this->data) - $this->index);
287
        }
288
289
        // Read the string and remove the delimiter
290 11
        sort($pos);
291 11
        $string = $this->read($pos[0] - $this->index);
292 11
        $delimfound = $this->read();
293
294 11
        return $string;
295
    }
296
297
    /**
298
     * Read an 8-bit unsigned integer
299
     *
300
     * @return int
301
     * @throws \GameQ\Exception\Protocol
302
     */
303 115
    public function readInt8()
304
    {
305
306 115
        $int = unpack('Cint', $this->read(1));
307
308 115
        return $int['int'];
309
    }
310
311
    /**
312
     * Read and 8-bit signed integer
313
     *
314
     * @return int
315
     * @throws \GameQ\Exception\Protocol
316
     */
317 6
    public function readInt8Signed()
318
    {
319
320 6
        $int = unpack('cint', $this->read(1));
321
322 6
        return $int['int'];
323
    }
324
325
    /**
326
     * Read a 16-bit unsigned integer
327
     *
328
     * @return int
329
     * @throws \GameQ\Exception\Protocol
330
     */
331 96
    public function readInt16()
332
    {
333
334
        // Change the integer type we are looking up
335 96
        switch ($this->number_type) {
336 96
            case self::NUMBER_TYPE_BIGENDIAN:
337 5
                $type = 'nint';
338 5
                break;
339
340 91
            case self::NUMBER_TYPE_LITTLEENDIAN:
341 89
                $type = 'vint';
342 89
                break;
343
344
            default:
345 2
                $type = 'Sint';
346
        }
347
348 96
        $int = unpack($type, $this->read(2));
349
350 96
        return $int['int'];
351
    }
352
353
    /**
354
     * Read a 16-bit signed integer
355
     *
356
     * @return int
357
     * @throws \GameQ\Exception\Protocol
358
     */
359 92
    public function readInt16Signed()
360
    {
361
362
        // Read the data into a string
363 92
        $string = $this->read(2);
364
365
        // For big endian we need to reverse the bytes
366 92
        if ($this->number_type == self::NUMBER_TYPE_BIGENDIAN) {
367 4
            $string = strrev($string);
368
        }
369
370 92
        $int = unpack('sint', $string);
371
372 92
        unset($string);
373
374 92
        return $int['int'];
375
    }
376
377
    /**
378
     * Read a 32-bit unsigned integer
379
     *
380
     * @return int
381
     * @throws \GameQ\Exception\Protocol
382
     */
383 42
    public function readInt32()
384
    {
385
386
        // Change the integer type we are looking up
387 42
        switch ($this->number_type) {
388 42
            case self::NUMBER_TYPE_BIGENDIAN:
389 15
                $type = 'Nint';
390 15
                break;
391
392 27
            case self::NUMBER_TYPE_LITTLEENDIAN:
393 25
                $type = 'Vint';
394 25
                break;
395
396
            default:
397 2
                $type = 'Lint';
398
        }
399
400
        // Unpack the number
401 42
        $int = unpack($type, $this->read(4));
402
403 42
        return $int['int'];
404
    }
405
406
    /**
407
     * Read a 32-bit signed integer
408
     *
409
     * @return int
410
     * @throws \GameQ\Exception\Protocol
411
     */
412 94
    public function readInt32Signed()
413
    {
414
415
        // Read the data into a string
416 94
        $string = $this->read(4);
417
418
        // For big endian we need to reverse the bytes
419 94
        if ($this->number_type == self::NUMBER_TYPE_BIGENDIAN) {
420 4
            $string = strrev($string);
421
        }
422
423 94
        $int = unpack('lint', $string);
424
425 94
        unset($string);
426
427 94
        return $int['int'];
428
    }
429
430
    /**
431
     * Read a 64-bit unsigned integer
432
     *
433
     * @return int
434
     * @throws \GameQ\Exception\Protocol
435
     */
436 83
    public function readInt64()
437
    {
438
439
        // We have the pack 64-bit codes available. See: http://php.net/manual/en/function.pack.php
440 83
        if (version_compare(PHP_VERSION, '5.6.3') >= 0 && PHP_INT_SIZE == 8) {
441
            // Change the integer type we are looking up
442 83
            switch ($this->number_type) {
443 83
                case self::NUMBER_TYPE_BIGENDIAN:
444 4
                    $type = 'Jint';
445 4
                    break;
446
447 79
                case self::NUMBER_TYPE_LITTLEENDIAN:
448 77
                    $type = 'Pint';
449 77
                    break;
450
451
                default:
452 2
                    $type = 'Qint';
453
            }
454
455 83
            $int64 = unpack($type, $this->read(8));
456
457 83
            $int = $int64['int'];
458
459 83
            unset($int64);
460
        } else {
461
            if ($this->number_type == self::NUMBER_TYPE_BIGENDIAN) {
462
                $high = $this->readInt32();
463
                $low = $this->readInt32();
464
            } else {
465
                $low = $this->readInt32();
466
                $high = $this->readInt32();
467
            }
468
469
            // We have to determine the number via bitwise
470
            $int = ($high << 32) | $low;
471
472
            unset($low, $high);
473
        }
474
475 83
        return $int;
476
    }
477
478
    /**
479
     * Read a 32-bit float
480
     *
481
     * @return float
482
     * @throws \GameQ\Exception\Protocol
483
     */
484 78
    public function readFloat32()
485
    {
486
487
        // Read the data into a string
488 78
        $string = $this->read(4);
489
490
        // For big endian we need to reverse the bytes
491 78
        if ($this->number_type == self::NUMBER_TYPE_BIGENDIAN) {
492 4
            $string = strrev($string);
493
        }
494
495 78
        $float = unpack('ffloat', $string);
496
497 78
        unset($string);
498
499 78
        return $float['float'];
500
    }
501
}
502