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