Home | History | Annotate | Download | only in php
      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 
     18 namespace Google\FlatBuffers;
     19 
     20 class 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