1<?php 2 3// Protocol Buffers - Google's data interchange format 4// Copyright 2008 Google Inc. All rights reserved. 5// 6// Use of this source code is governed by a BSD-style 7// license that can be found in the LICENSE file or at 8// https://developers.google.com/open-source/licenses/bsd 9 10namespace Google\Protobuf\Internal; 11 12use Google\Protobuf\Internal\Uint64; 13 14class CodedInputStream 15{ 16 17 private $buffer; 18 private $buffer_size_after_limit; 19 private $buffer_end; 20 private $current; 21 private $current_limit; 22 private $legitimate_message_end; 23 private $recursion_budget; 24 private $recursion_limit; 25 private $total_bytes_limit; 26 private $total_bytes_read; 27 28 const MAX_VARINT_BYTES = 10; 29 const DEFAULT_RECURSION_LIMIT = 100; 30 const DEFAULT_TOTAL_BYTES_LIMIT = 33554432; // 32 << 20, 32MB 31 32 public function __construct($buffer) 33 { 34 $start = 0; 35 $end = strlen($buffer); 36 $this->buffer = $buffer; 37 $this->buffer_size_after_limit = 0; 38 $this->buffer_end = $end; 39 $this->current = $start; 40 $this->current_limit = $end; 41 $this->legitimate_message_end = false; 42 $this->recursion_budget = self::DEFAULT_RECURSION_LIMIT; 43 $this->recursion_limit = self::DEFAULT_RECURSION_LIMIT; 44 $this->total_bytes_limit = self::DEFAULT_TOTAL_BYTES_LIMIT; 45 $this->total_bytes_read = $end - $start; 46 } 47 48 private function advance($amount) 49 { 50 $this->current += $amount; 51 } 52 53 public function bufferSize() 54 { 55 return $this->buffer_end - $this->current; 56 } 57 58 public function current() 59 { 60 return $this->total_bytes_read - 61 ($this->buffer_end - $this->current + 62 $this->buffer_size_after_limit); 63 } 64 65 public function substr($start, $end) 66 { 67 return substr($this->buffer, $start, $end - $start); 68 } 69 70 private function recomputeBufferLimits() 71 { 72 $this->buffer_end += $this->buffer_size_after_limit; 73 $closest_limit = min($this->current_limit, $this->total_bytes_limit); 74 if ($closest_limit < $this->total_bytes_read) { 75 // The limit position is in the current buffer. We must adjust the 76 // buffer size accordingly. 77 $this->buffer_size_after_limit = $this->total_bytes_read - 78 $closest_limit; 79 $this->buffer_end -= $this->buffer_size_after_limit; 80 } else { 81 $this->buffer_size_after_limit = 0; 82 } 83 } 84 85 private function consumedEntireMessage() 86 { 87 return $this->legitimate_message_end; 88 } 89 90 /** 91 * Read uint32 into $var. Advance buffer with consumed bytes. If the 92 * contained varint is larger than 32 bits, discard the high order bits. 93 * @param $var 94 */ 95 public function readVarint32(&$var) 96 { 97 if (!$this->readVarint64($var)) { 98 return false; 99 } 100 101 if (PHP_INT_SIZE == 4) { 102 $var = bcmod($var, 4294967296); 103 } else { 104 $var &= 0xFFFFFFFF; 105 } 106 107 // Convert large uint32 to int32. 108 if ($var > 0x7FFFFFFF) { 109 if (PHP_INT_SIZE === 8) { 110 $var = $var | (0xFFFFFFFF << 32); 111 } else { 112 $var = bcsub($var, 4294967296); 113 } 114 } 115 116 $var = intval($var); 117 return true; 118 } 119 120 /** 121 * Read Uint64 into $var. Advance buffer with consumed bytes. 122 * @param $var 123 */ 124 public function readVarint64(&$var) 125 { 126 $count = 0; 127 128 if (PHP_INT_SIZE == 4) { 129 $high = 0; 130 $low = 0; 131 $b = 0; 132 133 do { 134 if ($this->current === $this->buffer_end) { 135 return false; 136 } 137 if ($count === self::MAX_VARINT_BYTES) { 138 return false; 139 } 140 $b = ord($this->buffer[$this->current]); 141 $bits = 7 * $count; 142 if ($bits >= 32) { 143 $high |= (($b & 0x7F) << ($bits - 32)); 144 } else if ($bits > 25){ 145 // $bits is 28 in this case. 146 $low |= (($b & 0x7F) << 28); 147 $high = ($b & 0x7F) >> 4; 148 } else { 149 $low |= (($b & 0x7F) << $bits); 150 } 151 152 $this->advance(1); 153 $count += 1; 154 } while ($b & 0x80); 155 156 $var = GPBUtil::combineInt32ToInt64($high, $low); 157 if (bccomp($var, 0) < 0) { 158 $var = bcadd($var, "18446744073709551616"); 159 } 160 } else { 161 $result = 0; 162 $shift = 0; 163 164 do { 165 if ($this->current === $this->buffer_end) { 166 return false; 167 } 168 if ($count === self::MAX_VARINT_BYTES) { 169 return false; 170 } 171 172 $byte = ord($this->buffer[$this->current]); 173 $result |= ($byte & 0x7f) << $shift; 174 $shift += 7; 175 $this->advance(1); 176 $count += 1; 177 } while ($byte > 0x7f); 178 179 $var = $result; 180 } 181 182 return true; 183 } 184 185 /** 186 * Read int into $var. If the result is larger than the largest integer, $var 187 * will be -1. Advance buffer with consumed bytes. 188 * @param $var 189 */ 190 public function readVarintSizeAsInt(&$var) 191 { 192 if (!$this->readVarint64($var)) { 193 return false; 194 } 195 $var = (int)$var; 196 return true; 197 } 198 199 /** 200 * Read 32-bit unsigned integer to $var. If the buffer has less than 4 bytes, 201 * return false. Advance buffer with consumed bytes. 202 * @param $var 203 */ 204 public function readLittleEndian32(&$var) 205 { 206 $data = null; 207 if (!$this->readRaw(4, $data)) { 208 return false; 209 } 210 $var = unpack('V', $data); 211 $var = $var[1]; 212 return true; 213 } 214 215 /** 216 * Read 64-bit unsigned integer to $var. If the buffer has less than 8 bytes, 217 * return false. Advance buffer with consumed bytes. 218 * @param $var 219 */ 220 public function readLittleEndian64(&$var) 221 { 222 $data = null; 223 if (!$this->readRaw(4, $data)) { 224 return false; 225 } 226 $low = unpack('V', $data)[1]; 227 if (!$this->readRaw(4, $data)) { 228 return false; 229 } 230 $high = unpack('V', $data)[1]; 231 if (PHP_INT_SIZE == 4) { 232 $var = GPBUtil::combineInt32ToInt64($high, $low); 233 } else { 234 $var = ($high << 32) | $low; 235 } 236 return true; 237 } 238 239 /** 240 * Read tag into $var. Advance buffer with consumed bytes. 241 */ 242 public function readTag() 243 { 244 if ($this->current === $this->buffer_end) { 245 // Make sure that it failed due to EOF, not because we hit 246 // total_bytes_limit, which, unlike normal limits, is not a valid 247 // place to end a message. 248 $current_position = $this->total_bytes_read - 249 $this->buffer_size_after_limit; 250 if ($current_position >= $this->total_bytes_limit) { 251 // Hit total_bytes_limit_. But if we also hit the normal limit, 252 // we're still OK. 253 $this->legitimate_message_end = 254 ($this->current_limit === $this->total_bytes_limit); 255 } else { 256 $this->legitimate_message_end = true; 257 } 258 return 0; 259 } 260 261 $result = 0; 262 // The largest tag is 2^29 - 1, which can be represented by int32. 263 $success = $this->readVarint32($result); 264 if ($success) { 265 return $result; 266 } else { 267 return 0; 268 } 269 } 270 271 public function readRaw($size, &$buffer) 272 { 273 $current_buffer_size = 0; 274 if ($this->bufferSize() < $size) { 275 return false; 276 } 277 278 if ($size === 0) { 279 $buffer = ""; 280 } else { 281 $buffer = substr($this->buffer, $this->current, $size); 282 $this->advance($size); 283 } 284 285 return true; 286 } 287 288 /* Places a limit on the number of bytes that the stream may read, starting 289 * from the current position. Once the stream hits this limit, it will act 290 * like the end of the input has been reached until popLimit() is called. 291 * 292 * As the names imply, the stream conceptually has a stack of limits. The 293 * shortest limit on the stack is always enforced, even if it is not the top 294 * limit. 295 * 296 * The value returned by pushLimit() is opaque to the caller, and must be 297 * passed unchanged to the corresponding call to popLimit(). 298 * 299 * @param integer $byte_limit 300 * @throws \Exception Fail to push limit. 301 */ 302 public function pushLimit($byte_limit) 303 { 304 // Current position relative to the beginning of the stream. 305 $current_position = $this->current(); 306 $old_limit = $this->current_limit; 307 308 // security: byte_limit is possibly evil, so check for negative values 309 // and overflow. 310 if ($byte_limit >= 0 && 311 $byte_limit <= PHP_INT_MAX - $current_position && 312 $byte_limit <= $this->current_limit - $current_position) { 313 $this->current_limit = $current_position + $byte_limit; 314 $this->recomputeBufferLimits(); 315 } else { 316 throw new GPBDecodeException("Fail to push limit."); 317 } 318 319 return $old_limit; 320 } 321 322 /* The limit passed in is actually the *old* limit, which we returned from 323 * PushLimit(). 324 * 325 * @param integer $byte_limit 326 */ 327 public function popLimit($byte_limit) 328 { 329 $this->current_limit = $byte_limit; 330 $this->recomputeBufferLimits(); 331 // We may no longer be at a legitimate message end. ReadTag() needs to 332 // be called again to find out. 333 $this->legitimate_message_end = false; 334 } 335 336 public function incrementRecursionDepthAndPushLimit( 337 $byte_limit, &$old_limit, &$recursion_budget) 338 { 339 $old_limit = $this->pushLimit($byte_limit); 340 $recursion_limit = --$this->recursion_limit; 341 } 342 343 public function decrementRecursionDepthAndPopLimit($byte_limit) 344 { 345 $result = $this->consumedEntireMessage(); 346 $this->popLimit($byte_limit); 347 ++$this->recursion_budget; 348 return $result; 349 } 350 351 public function bytesUntilLimit() 352 { 353 if ($this->current_limit === PHP_INT_MAX) { 354 return -1; 355 } 356 return $this->current_limit - $this->current; 357 } 358} 359