1<?php
2/*
3 * Copyright 2015 Google Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18namespace Google\FlatBuffers;
19
20class ByteBuffer
21{
22    /**
23     * @var string $_buffer;
24     */
25    public $_buffer;
26
27    /**
28     * @var int $_pos;
29     */
30    private $_pos;
31
32    /**
33     * @var bool $_is_little_endian
34     */
35    private static $_is_little_endian = null;
36
37    public static function wrap($bytes)
38    {
39        $bb = new ByteBuffer(0);
40        $bb->_buffer = $bytes;
41
42        return $bb;
43    }
44
45    /**
46     * @param $size
47     */
48    public function __construct($size)
49    {
50        $this->_buffer = str_repeat("\0", $size);
51    }
52
53    /**
54     * @return int
55     */
56    public function capacity()
57    {
58        return strlen($this->_buffer);
59    }
60
61    /**
62     * @return int
63     */
64    public function getPosition()
65    {
66        return $this->_pos;
67    }
68
69    /**
70     * @param $pos
71     */
72    public function setPosition($pos)
73    {
74        $this->_pos = $pos;
75    }
76
77    /**
78     *
79     */
80    public function reset()
81    {
82        $this->_pos = 0;
83    }
84
85    /**
86     * @return int
87     */
88    public function length()
89    {
90        return strlen($this->_buffer);
91    }
92
93    /**
94     * @return string
95     */
96    public function data()
97    {
98        return substr($this->_buffer, $this->_pos);
99    }
100
101    /**
102     * @return bool
103     */
104    public static function isLittleEndian()
105    {
106        if (ByteBuffer::$_is_little_endian === null) {
107            ByteBuffer::$_is_little_endian = unpack('S', "\x01\x00")[1] === 1;
108        }
109
110        return ByteBuffer::$_is_little_endian;
111    }
112
113    /**
114     * write little endian value to the buffer.
115     *
116     * @param $offset
117     * @param $count byte length
118     * @param $data actual values
119     */
120    public function writeLittleEndian($offset, $count, $data)
121    {
122        if (ByteBuffer::isLittleEndian()) {
123            for ($i = 0; $i < $count; $i++) {
124                $this->_buffer[$offset + $i] = chr($data >> $i * 8);
125            }
126        } else {
127            for ($i = 0; $i < $count; $i++) {
128                $this->_buffer[$offset + $count - 1 - $i] = chr($data >> $i * 8);
129            }
130        }
131    }
132
133    /**
134     * read little endian value from the buffer
135     *
136     * @param $offset
137     * @param $count acutal size
138     * @return int
139     */
140    public function readLittleEndian($offset, $count, $force_bigendian = false)
141    {
142        $this->assertOffsetAndLength($offset, $count);
143        $r = 0;
144
145        if (ByteBuffer::isLittleEndian() && $force_bigendian == false) {
146            for ($i = 0; $i < $count; $i++) {
147                $r |= ord($this->_buffer[$offset + $i]) << $i * 8;
148            }
149        } else {
150            for ($i = 0; $i < $count; $i++) {
151                $r |= ord($this->_buffer[$offset + $count -1 - $i]) << $i * 8;
152            }
153        }
154
155        return $r;
156    }
157
158    /**
159     * @param $offset
160     * @param $length
161     */
162    public function assertOffsetAndLength($offset, $length)
163    {
164        if ($offset < 0 ||
165            $offset >= strlen($this->_buffer) ||
166            $offset + $length > strlen($this->_buffer)) {
167            throw new \OutOfRangeException(sprintf("offset: %d, length: %d, buffer; %d", $offset, $length, strlen($this->_buffer)));
168        }
169    }
170
171    /**
172     * @param $offset
173     * @param $value
174     * @return mixed
175     */
176    public function putSbyte($offset, $value)
177    {
178        self::validateValue(-128, 127, $value, "sbyte");
179
180        $length = strlen($value);
181        $this->assertOffsetAndLength($offset, $length);
182        return $this->_buffer[$offset] = $value;
183    }
184
185    /**
186     * @param $offset
187     * @param $value
188     * @return mixed
189     */
190    public function putByte($offset, $value)
191    {
192        self::validateValue(0, 255, $value, "byte");
193
194        $length = strlen($value);
195        $this->assertOffsetAndLength($offset, $length);
196        return $this->_buffer[$offset] = $value;
197    }
198
199    /**
200     * @param $offset
201     * @param $value
202     */
203    public function put($offset, $value)
204    {
205        $length = strlen($value);
206        $this->assertOffsetAndLength($offset, $length);
207        for ($i = 0; $i < $length; $i++) {
208            $this->_buffer[$offset + $i] = $value[$i];
209        }
210    }
211
212    /**
213     * @param $offset
214     * @param $value
215     */
216    public function putShort($offset, $value)
217    {
218        self::validateValue(-32768, 32767, $value, "short");
219
220        $this->assertOffsetAndLength($offset, 2);
221        $this->writeLittleEndian($offset, 2, $value);
222    }
223
224    /**
225     * @param $offset
226     * @param $value
227     */
228    public function putUshort($offset, $value)
229    {
230        self::validateValue(0, 65535, $value, "short");
231
232        $this->assertOffsetAndLength($offset, 2);
233        $this->writeLittleEndian($offset, 2, $value);
234    }
235
236    /**
237     * @param $offset
238     * @param $value
239     */
240    public function putInt($offset, $value)
241    {
242        // 2147483647 = (1 << 31) -1 = Maximum signed 32-bit int
243        // -2147483648 = -1 << 31 = Minimum signed 32-bit int
244        self::validateValue(-2147483648, 2147483647, $value, "int");
245
246        $this->assertOffsetAndLength($offset, 4);
247        $this->writeLittleEndian($offset, 4, $value);
248    }
249
250    /**
251     * @param $offset
252     * @param $value
253     */
254    public function putUint($offset, $value)
255    {
256        // NOTE: We can't put big integer value. this is PHP limitation.
257        // 4294967295 = (1 << 32) -1 = Maximum unsigned 32-bin int
258        self::validateValue(0, 4294967295, $value, "uint",  " php has big numbers limitation. check your PHP_INT_MAX");
259
260        $this->assertOffsetAndLength($offset, 4);
261        $this->writeLittleEndian($offset, 4, $value);
262    }
263
264    /**
265     * @param $offset
266     * @param $value
267     */
268    public function putLong($offset, $value)
269    {
270        // NOTE: We can't put big integer value. this is PHP limitation.
271        self::validateValue(~PHP_INT_MAX, PHP_INT_MAX, $value, "long",  " php has big numbers limitation. check your PHP_INT_MAX");
272
273        $this->assertOffsetAndLength($offset, 8);
274        $this->writeLittleEndian($offset, 8, $value);
275    }
276
277    /**
278     * @param $offset
279     * @param $value
280     */
281    public function putUlong($offset, $value)
282    {
283        // NOTE: We can't put big integer value. this is PHP limitation.
284        self::validateValue(0, PHP_INT_MAX, $value, "long", " php has big numbers limitation. check your PHP_INT_MAX");
285
286        $this->assertOffsetAndLength($offset, 8);
287        $this->writeLittleEndian($offset, 8, $value);
288    }
289
290    /**
291     * @param $offset
292     * @param $value
293     */
294    public function putFloat($offset, $value)
295    {
296        $this->assertOffsetAndLength($offset, 4);
297
298        $floathelper = pack("f", $value);
299        $v = unpack("V", $floathelper);
300        $this->writeLittleEndian($offset, 4, $v[1]);
301    }
302
303    /**
304     * @param $offset
305     * @param $value
306     */
307    public function putDouble($offset, $value)
308    {
309        $this->assertOffsetAndLength($offset, 8);
310
311        $floathelper = pack("d", $value);
312        $v = unpack("V*", $floathelper);
313
314        $this->writeLittleEndian($offset, 4, $v[1]);
315        $this->writeLittleEndian($offset + 4, 4, $v[2]);
316    }
317
318    /**
319     * @param $index
320     * @return mixed
321     */
322    public function getByte($index)
323    {
324        return ord($this->_buffer[$index]);
325    }
326
327    /**
328     * @param $index
329     * @return mixed
330     */
331    public function getSbyte($index)
332    {
333        $v = unpack("c", $this->_buffer[$index]);
334        return $v[1];
335    }
336
337    /**
338     * @param $buffer
339     */
340    public function getX(&$buffer)
341    {
342        for ($i = $this->_pos, $j = 0; $j < strlen($buffer); $i++, $j++) {
343            $buffer[$j] = $this->_buffer[$i];
344        }
345    }
346
347    /**
348     * @param $index
349     * @return mixed
350     */
351    public function get($index)
352    {
353        $this->assertOffsetAndLength($index, 1);
354        return $this->_buffer[$index];
355    }
356
357
358    /**
359     * @param $index
360     * @return mixed
361     */
362    public function getBool($index)
363    {
364        return (bool)ord($this->_buffer[$index]);
365    }
366
367    /**
368     * @param $index
369     * @return int
370     */
371    public function getShort($index)
372    {
373        $result = $this->readLittleEndian($index, 2);
374
375        $sign = $index + (ByteBuffer::isLittleEndian() ? 1 : 0);
376        $issigned = isset($this->_buffer[$sign]) && ord($this->_buffer[$sign]) & 0x80;
377
378        // 65536 = 1 << 16 = Maximum unsigned 16-bit int
379        return $issigned ? $result - 65536 : $result;
380    }
381
382    /**
383     * @param $index
384     * @return int
385     */
386    public function getUShort($index)
387    {
388        return $this->readLittleEndian($index, 2);
389    }
390
391    /**
392     * @param $index
393     * @return int
394     */
395    public function getInt($index)
396    {
397        $result = $this->readLittleEndian($index, 4);
398
399        $sign = $index + (ByteBuffer::isLittleEndian() ? 3 : 0);
400        $issigned = isset($this->_buffer[$sign]) && ord($this->_buffer[$sign]) & 0x80;
401
402        if (PHP_INT_SIZE > 4) {
403            // 4294967296 = 1 << 32 = Maximum unsigned 32-bit int
404            return $issigned ? $result - 4294967296 : $result;
405        } else {
406            // 32bit / Windows treated number as signed integer.
407            return $result;
408        }
409    }
410
411    /**
412     * @param $index
413     * @return int
414     */
415    public function getUint($index)
416    {
417        return $this->readLittleEndian($index, 4);
418    }
419
420    /**
421     * @param $index
422     * @return int
423     */
424    public function getLong($index)
425    {
426        return $this->readLittleEndian($index, 8);
427    }
428
429    /**
430     * @param $index
431     * @return int
432     */
433    public function getUlong($index)
434    {
435        return $this->readLittleEndian($index, 8);
436    }
437
438    /**
439     * @param $index
440     * @return mixed
441     */
442    public function getFloat($index)
443    {
444        $i = $this->readLittleEndian($index, 4);
445
446        return self::convertHelper(self::__FLOAT, $i);
447    }
448
449    /**
450     * @param $index
451     * @return float
452     */
453    public function getDouble($index)
454    {
455        $i = $this->readLittleEndian($index, 4);
456        $i2 = $this->readLittleEndian($index + 4, 4);
457
458        return self::convertHelper(self::__DOUBLE, $i, $i2);
459    }
460
461    const __SHORT = 1;
462    const __INT = 2;
463    const __LONG = 3;
464    const __FLOAT = 4;
465    const __DOUBLE = 5;
466    private static function convertHelper($type, $value, $value2 = null) {
467        // readLittleEndian construct unsigned integer value from bytes. we have to encode this value to
468        // correct bytes, and decode as expected types with `unpack` function.
469        // then it returns correct type value.
470        // see also: http://php.net/manual/en/function.pack.php
471
472        switch ($type) {
473            case self::__FLOAT:
474                $inthelper = pack("V", $value);
475                $v = unpack("f", $inthelper);
476                return $v[1];
477                break;
478            case self::__DOUBLE:
479                $inthelper = pack("VV", $value, $value2);
480                $v = unpack("d", $inthelper);
481                return $v[1];
482                break;
483            default:
484                throw new \Exception(sprintf("unexpected type %d specified", $type));
485        }
486    }
487
488    private static function validateValue($min, $max, $value, $type, $additional_notes = "") {
489        if(!($min <= $value && $value <= $max)) {
490            throw new \InvalidArgumentException(sprintf("bad number %s for type %s.%s", $value, $type, $additional_notes));
491        }
492    }
493}
494