| <?php | 
 |  | 
 | // Protocol Buffers - Google's data interchange format | 
 | // Copyright 2008 Google Inc.  All rights reserved. | 
 | // | 
 | // Use of this source code is governed by a BSD-style | 
 | // license that can be found in the LICENSE file or at | 
 | // https://developers.google.com/open-source/licenses/bsd | 
 |  | 
 | namespace Google\Protobuf\Internal; | 
 |  | 
 | use Google\Protobuf\Internal\Uint64; | 
 |  | 
 | class CodedInputStream | 
 | { | 
 |  | 
 |     private $buffer; | 
 |     private $buffer_size_after_limit; | 
 |     private $buffer_end; | 
 |     private $current; | 
 |     private $current_limit; | 
 |     private $legitimate_message_end; | 
 |     private $recursion_budget; | 
 |     private $recursion_limit; | 
 |     private $total_bytes_limit; | 
 |     private $total_bytes_read; | 
 |  | 
 |     const MAX_VARINT_BYTES = 10; | 
 |     const DEFAULT_RECURSION_LIMIT = 100; | 
 |     const DEFAULT_TOTAL_BYTES_LIMIT = 33554432; // 32 << 20, 32MB | 
 |  | 
 |     public function __construct($buffer) | 
 |     { | 
 |         $start = 0; | 
 |         $end = strlen($buffer); | 
 |         $this->buffer = $buffer; | 
 |         $this->buffer_size_after_limit = 0; | 
 |         $this->buffer_end = $end; | 
 |         $this->current = $start; | 
 |         $this->current_limit = $end; | 
 |         $this->legitimate_message_end = false; | 
 |         $this->recursion_budget = self::DEFAULT_RECURSION_LIMIT; | 
 |         $this->recursion_limit = self::DEFAULT_RECURSION_LIMIT; | 
 |         $this->total_bytes_limit = self::DEFAULT_TOTAL_BYTES_LIMIT; | 
 |         $this->total_bytes_read = $end - $start; | 
 |     } | 
 |  | 
 |     private function advance($amount) | 
 |     { | 
 |         $this->current += $amount; | 
 |     } | 
 |  | 
 |     public function bufferSize() | 
 |     { | 
 |         return $this->buffer_end - $this->current; | 
 |     } | 
 |  | 
 |     public function current() | 
 |     { | 
 |         return $this->total_bytes_read - | 
 |             ($this->buffer_end - $this->current + | 
 |             $this->buffer_size_after_limit); | 
 |     } | 
 |  | 
 |     public function substr($start, $end) | 
 |     { | 
 |         return substr($this->buffer, $start, $end - $start); | 
 |     } | 
 |  | 
 |     private function recomputeBufferLimits() | 
 |     { | 
 |         $this->buffer_end += $this->buffer_size_after_limit; | 
 |         $closest_limit = min($this->current_limit, $this->total_bytes_limit); | 
 |         if ($closest_limit < $this->total_bytes_read) { | 
 |             // The limit position is in the current buffer.  We must adjust the | 
 |             // buffer size accordingly. | 
 |             $this->buffer_size_after_limit = $this->total_bytes_read - | 
 |                 $closest_limit; | 
 |             $this->buffer_end -= $this->buffer_size_after_limit; | 
 |         } else { | 
 |             $this->buffer_size_after_limit = 0; | 
 |         } | 
 |     } | 
 |  | 
 |     private function consumedEntireMessage() | 
 |     { | 
 |         return $this->legitimate_message_end; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Read uint32 into $var. Advance buffer with consumed bytes. If the | 
 |      * contained varint is larger than 32 bits, discard the high order bits. | 
 |      * @param $var | 
 |      */ | 
 |     public function readVarint32(&$var) | 
 |     { | 
 |         if (!$this->readVarint64($var)) { | 
 |             return false; | 
 |         } | 
 |  | 
 |         if (PHP_INT_SIZE == 4) { | 
 |             $var = bcmod($var, 4294967296); | 
 |         } else { | 
 |             $var &= 0xFFFFFFFF; | 
 |         } | 
 |  | 
 |         // Convert large uint32 to int32. | 
 |         if ($var > 0x7FFFFFFF) { | 
 |             if (PHP_INT_SIZE === 8) { | 
 |                 $var = $var | (0xFFFFFFFF << 32); | 
 |             } else { | 
 |                 $var = bcsub($var, 4294967296); | 
 |             } | 
 |         } | 
 |  | 
 |         $var = intval($var); | 
 |         return true; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Read Uint64 into $var. Advance buffer with consumed bytes. | 
 |      * @param $var | 
 |      */ | 
 |     public function readVarint64(&$var) | 
 |     { | 
 |         $count = 0; | 
 |  | 
 |         if (PHP_INT_SIZE == 4) { | 
 |             $high = 0; | 
 |             $low = 0; | 
 |             $b = 0; | 
 |  | 
 |             do { | 
 |                 if ($this->current === $this->buffer_end) { | 
 |                     return false; | 
 |                 } | 
 |                 if ($count === self::MAX_VARINT_BYTES) { | 
 |                     return false; | 
 |                 } | 
 |                 $b = ord($this->buffer[$this->current]); | 
 |                 $bits = 7 * $count; | 
 |                 if ($bits >= 32) { | 
 |                     $high |= (($b & 0x7F) << ($bits - 32)); | 
 |                 } else if ($bits > 25){ | 
 |                     // $bits is 28 in this case. | 
 |                     $low |= (($b & 0x7F) << 28); | 
 |                     $high = ($b & 0x7F) >> 4; | 
 |                 } else { | 
 |                     $low |= (($b & 0x7F) << $bits); | 
 |                 } | 
 |  | 
 |                 $this->advance(1); | 
 |                 $count += 1; | 
 |             } while ($b & 0x80); | 
 |  | 
 |             $var = GPBUtil::combineInt32ToInt64($high, $low); | 
 |             if (bccomp($var, 0) < 0) { | 
 |                 $var = bcadd($var, "18446744073709551616"); | 
 |             } | 
 |         } else { | 
 |             $result = 0; | 
 |             $shift = 0; | 
 |  | 
 |             do { | 
 |                 if ($this->current === $this->buffer_end) { | 
 |                     return false; | 
 |                 } | 
 |                 if ($count === self::MAX_VARINT_BYTES) { | 
 |                     return false; | 
 |                 } | 
 |  | 
 |                 $byte = ord($this->buffer[$this->current]); | 
 |                 $result |= ($byte & 0x7f) << $shift; | 
 |                 $shift += 7; | 
 |                 $this->advance(1); | 
 |                 $count += 1; | 
 |             } while ($byte > 0x7f); | 
 |  | 
 |             $var = $result; | 
 |         } | 
 |  | 
 |         return true; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Read int into $var. If the result is larger than the largest integer, $var | 
 |      * will be -1. Advance buffer with consumed bytes. | 
 |      * @param $var | 
 |      */ | 
 |     public function readVarintSizeAsInt(&$var) | 
 |     { | 
 |         if (!$this->readVarint64($var)) { | 
 |             return false; | 
 |         } | 
 |         $var = (int)$var; | 
 |         return true; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Read 32-bit unsigned integer to $var. If the buffer has less than 4 bytes, | 
 |      * return false. Advance buffer with consumed bytes. | 
 |      * @param $var | 
 |      */ | 
 |     public function readLittleEndian32(&$var) | 
 |     { | 
 |         $data = null; | 
 |         if (!$this->readRaw(4, $data)) { | 
 |             return false; | 
 |         } | 
 |         $var = unpack('V', $data); | 
 |         $var = $var[1]; | 
 |         return true; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Read 64-bit unsigned integer to $var. If the buffer has less than 8 bytes, | 
 |      * return false. Advance buffer with consumed bytes. | 
 |      * @param $var | 
 |      */ | 
 |     public function readLittleEndian64(&$var) | 
 |     { | 
 |         $data = null; | 
 |         if (!$this->readRaw(4, $data)) { | 
 |             return false; | 
 |         } | 
 |         $low = unpack('V', $data)[1]; | 
 |         if (!$this->readRaw(4, $data)) { | 
 |             return false; | 
 |         } | 
 |         $high = unpack('V', $data)[1]; | 
 |         if (PHP_INT_SIZE == 4) { | 
 |             $var = GPBUtil::combineInt32ToInt64($high, $low); | 
 |         } else { | 
 |             $var = ($high << 32) | $low; | 
 |         } | 
 |         return true; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Read tag into $var. Advance buffer with consumed bytes. | 
 |      */ | 
 |     public function readTag() | 
 |     { | 
 |         if ($this->current === $this->buffer_end) { | 
 |             // Make sure that it failed due to EOF, not because we hit | 
 |             // total_bytes_limit, which, unlike normal limits, is not a valid | 
 |             // place to end a message. | 
 |             $current_position = $this->total_bytes_read - | 
 |                 $this->buffer_size_after_limit; | 
 |             if ($current_position >= $this->total_bytes_limit) { | 
 |                 // Hit total_bytes_limit_.  But if we also hit the normal limit, | 
 |                 // we're still OK. | 
 |                 $this->legitimate_message_end = | 
 |                     ($this->current_limit === $this->total_bytes_limit); | 
 |             } else { | 
 |                 $this->legitimate_message_end = true; | 
 |             } | 
 |             return 0; | 
 |         } | 
 |  | 
 |         $result = 0; | 
 |         // The largest tag is 2^29 - 1, which can be represented by int32. | 
 |         $success = $this->readVarint32($result); | 
 |         if ($success) { | 
 |             return $result; | 
 |         } else { | 
 |             return 0; | 
 |         } | 
 |     } | 
 |  | 
 |     public function readRaw($size, &$buffer) | 
 |     { | 
 |         $current_buffer_size = 0; | 
 |         if ($this->bufferSize() < $size) { | 
 |             return false; | 
 |         } | 
 |  | 
 |         if ($size === 0) { | 
 |           $buffer = ""; | 
 |         } else { | 
 |           $buffer = substr($this->buffer, $this->current, $size); | 
 |           $this->advance($size); | 
 |         } | 
 |  | 
 |         return true; | 
 |     } | 
 |  | 
 |     /* Places a limit on the number of bytes that the stream may read, starting | 
 |      * from the current position.  Once the stream hits this limit, it will act | 
 |      * like the end of the input has been reached until popLimit() is called. | 
 |      * | 
 |      * As the names imply, the stream conceptually has a stack of limits.  The | 
 |      * shortest limit on the stack is always enforced, even if it is not the top | 
 |      * limit. | 
 |      * | 
 |      * The value returned by pushLimit() is opaque to the caller, and must be | 
 |      * passed unchanged to the corresponding call to popLimit(). | 
 |      * | 
 |      * @param integer $byte_limit | 
 |      * @throws \Exception Fail to push limit. | 
 |      */ | 
 |     public function pushLimit($byte_limit) | 
 |     { | 
 |         // Current position relative to the beginning of the stream. | 
 |         $current_position = $this->current(); | 
 |         $old_limit = $this->current_limit; | 
 |  | 
 |         // security: byte_limit is possibly evil, so check for negative values | 
 |         // and overflow. | 
 |         if ($byte_limit >= 0 && | 
 |             $byte_limit <= PHP_INT_MAX - $current_position && | 
 |             $byte_limit <= $this->current_limit - $current_position) { | 
 |             $this->current_limit = $current_position + $byte_limit; | 
 |             $this->recomputeBufferLimits(); | 
 |         } else { | 
 |             throw new GPBDecodeException("Fail to push limit."); | 
 |         } | 
 |  | 
 |         return $old_limit; | 
 |     } | 
 |  | 
 |     /* The limit passed in is actually the *old* limit, which we returned from | 
 |      * PushLimit(). | 
 |      * | 
 |      * @param integer $byte_limit | 
 |      */ | 
 |     public function popLimit($byte_limit) | 
 |     { | 
 |         $this->current_limit = $byte_limit; | 
 |         $this->recomputeBufferLimits(); | 
 |         // We may no longer be at a legitimate message end.  ReadTag() needs to | 
 |         // be called again to find out. | 
 |         $this->legitimate_message_end = false; | 
 |     } | 
 |  | 
 |     public function incrementRecursionDepthAndPushLimit( | 
 |         $byte_limit, &$old_limit, &$recursion_budget) | 
 |     { | 
 |         $old_limit = $this->pushLimit($byte_limit); | 
 |         $recursion_limit = --$this->recursion_limit; | 
 |     } | 
 |  | 
 |     public function decrementRecursionDepthAndPopLimit($byte_limit) | 
 |     { | 
 |         $result = $this->consumedEntireMessage(); | 
 |         $this->popLimit($byte_limit); | 
 |         ++$this->recursion_budget; | 
 |         return $result; | 
 |     } | 
 |  | 
 |     public function bytesUntilLimit() | 
 |     { | 
 |         if ($this->current_limit === PHP_INT_MAX) { | 
 |             return -1; | 
 |         } | 
 |         return $this->current_limit - $this->current; | 
 |     } | 
 | } |