From e0e54661f76183684dca66694967a60cbb10f04e Mon Sep 17 00:00:00 2001 From: Paul Yang Date: Thu, 15 Sep 2016 11:09:01 -0700 Subject: Check in php implementation. (#2052) This pull request includes two implementation: C extension and PHP package. Both implementations support encode/decode of singular, repeated and map fields. --- php/src/Google/Protobuf/Internal/InputStream.php | 323 +++++++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 php/src/Google/Protobuf/Internal/InputStream.php (limited to 'php/src/Google/Protobuf/Internal/InputStream.php') diff --git a/php/src/Google/Protobuf/Internal/InputStream.php b/php/src/Google/Protobuf/Internal/InputStream.php new file mode 100644 index 00000000..18d07075 --- /dev/null +++ b/php/src/Google/Protobuf/Internal/InputStream.php @@ -0,0 +1,323 @@ +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; + } + + private function bufferSize() + { + return $this->buffer_end - $this->current; + } + + private function current() + { + return $this->total_bytes_read - + ($this->buffer_end - $this->current + + $this->buffer_size_after_limit); + } + + 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; + } + $var = $var->toInteger() & 0xFFFFFFFF; + // Convert large uint32 to int32. + if (PHP_INT_SIZE === 8 && ($var > 0x7FFFFFFF)) { + $var = $var | (0xFFFFFFFF << 32); + } + return true; + } + + /** + * Read Uint64 into $var. Advance buffer with consumed bytes. + * @param $var. + */ + public function readVarint64(&$var) + { + $result = new Uint64(0); + $count = 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]); + $result->bitOr((new Uint64($b & 0x7F))->leftShift(7 * $count)); + $this->advance(1); + $count += 1; + } while ($b & 0x80); + + $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 = $var->toInteger(); + return true; + } + + /** + * Read 32-bit unsiged 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 unsiged 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]; + $var = Uint64::newValue($high, $low); + return true; + } + + /** + * Read tag into $var. Advance buffer with consumed bytes. + * @param $var. + */ + 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 larget 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; + } + + $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 + */ + 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) { + $this->current_limit = $current_position + $byte_limit; + } else { + // Negative or overflow. + $this->current_limit = PHP_INT_MAX; + } + + // We need to enforce all limits, not just the new one, so if the previous + // limit was before the new requested limit, we continue to enforce the + // previous limit. + $this->current_limit = min($this->current_limit, $old_limit); + + $this->recomputeBufferLimits(); + 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; + } +} -- cgit v1.2.3