• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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