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 (
490 !(
491 ($type === "byte" && $min <= ord($value) && ord($value) <= $max) ||
492 ($min <= $value && $value <= $max)
493 )
494 ) {
495 throw new \InvalidArgumentException(sprintf("bad number %s for type %s.%s", $value, $type, $additional_notes));
496 }
497 }
498}
View as plain text