1<?php
2
3// Protocol Buffers - Google's data interchange format
4// Copyright 2008 Google Inc.  All rights reserved.
5// https://developers.google.com/protocol-buffers/
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are
9// met:
10//
11//     * Redistributions of source code must retain the above copyright
12// notice, this list of conditions and the following disclaimer.
13//     * Redistributions in binary form must reproduce the above
14// copyright notice, this list of conditions and the following disclaimer
15// in the documentation and/or other materials provided with the
16// distribution.
17//     * Neither the name of Google Inc. nor the names of its
18// contributors may be used to endorse or promote products derived from
19// this software without specific prior written permission.
20//
21// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33namespace Google\Protobuf\Internal;
34
35class GPBWire
36{
37
38    const TAG_TYPE_BITS = 3;
39
40    const WIRETYPE_VARINT  = 0;
41    const WIRETYPE_FIXED64 = 1;
42    const WIRETYPE_LENGTH_DELIMITED = 2;
43    const WIRETYPE_START_GROUP = 3;
44    const WIRETYPE_END_GROUP = 4;
45    const WIRETYPE_FIXED32 = 5;
46
47    const UNKNOWN = 0;
48    const NORMAL_FORMAT = 1;
49    const PACKED_FORMAT = 2;
50
51    public static function getTagFieldNumber($tag)
52    {
53        return ($tag >> self::TAG_TYPE_BITS) &
54            (1 << ((PHP_INT_SIZE * 8) - self::TAG_TYPE_BITS)) - 1;
55    }
56
57    public static function getTagWireType($tag)
58    {
59        return $tag & 0x7;
60    }
61
62    public static function getWireType($type)
63    {
64        switch ($type) {
65            case GPBType::FLOAT:
66            case GPBType::FIXED32:
67            case GPBType::SFIXED32:
68                return self::WIRETYPE_FIXED32;
69            case GPBType::DOUBLE:
70            case GPBType::FIXED64:
71            case GPBType::SFIXED64:
72                return self::WIRETYPE_FIXED64;
73            case GPBType::UINT32:
74            case GPBType::UINT64:
75            case GPBType::INT32:
76            case GPBType::INT64:
77            case GPBType::SINT32:
78            case GPBType::SINT64:
79            case GPBType::ENUM:
80            case GPBType::BOOL:
81                return self::WIRETYPE_VARINT;
82            case GPBType::STRING:
83            case GPBType::BYTES:
84            case GPBType::MESSAGE:
85                return self::WIRETYPE_LENGTH_DELIMITED;
86            case GPBType::GROUP:
87                user_error("Unsupported type.");
88                return 0;
89            default:
90                user_error("Unsupported type.");
91                return 0;
92        }
93    }
94
95  // ZigZag Transform:  Encodes signed integers so that they can be effectively
96  // used with varint encoding.
97  //
98  // varint operates on unsigned integers, encoding smaller numbers into fewer
99  // bytes.  If you try to use it on a signed integer, it will treat this
100  // number as a very large unsigned integer, which means that even small
101  // signed numbers like -1 will take the maximum number of bytes (10) to
102  // encode.  zigZagEncode() maps signed integers to unsigned in such a way
103  // that those with a small absolute value will have smaller encoded values,
104  // making them appropriate for encoding using varint.
105  //
106  // int32 ->     uint32
107  // -------------------------
108  //           0 ->          0
109  //          -1 ->          1
110  //           1 ->          2
111  //          -2 ->          3
112  //         ... ->        ...
113  //  2147483647 -> 4294967294
114  // -2147483648 -> 4294967295
115  //
116  //        >> encode >>
117  //        << decode <<
118  public static function zigZagEncode32($int32)
119  {
120      if (PHP_INT_SIZE == 8) {
121          $trim_int32 = $int32 & 0xFFFFFFFF;
122          return (($trim_int32 << 1) ^ ($int32 << 32 >> 63)) & 0xFFFFFFFF;
123      } else {
124          return ($int32 << 1) ^ ($int32 >> 31);
125      }
126  }
127
128    public static function zigZagDecode32($uint32)
129    {
130        // Fill high 32 bits.
131        if (PHP_INT_SIZE === 8) {
132            $uint32 |= ($uint32 & 0xFFFFFFFF);
133        }
134
135        $int32 = (($uint32 >> 1) & 0x7FFFFFFF) ^ (-($uint32 & 1));
136
137        return $int32;
138    }
139
140    public static function zigZagEncode64($int64)
141    {
142        if (PHP_INT_SIZE == 4) {
143            if (bccomp($int64, 0) >= 0) {
144                return bcmul($int64, 2);
145            } else {
146                return bcsub(bcmul(bcsub(0, $int64), 2), 1);
147            }
148        } else {
149            return ($int64 << 1) ^ ($int64 >> 63);
150        }
151    }
152
153    public static function zigZagDecode64($uint64)
154    {
155        if (PHP_INT_SIZE == 4) {
156            if (bcmod($uint64, 2) == 0) {
157                return bcdiv($uint64, 2, 0);
158            } else {
159                return bcsub(0, bcdiv(bcadd($uint64, 1), 2, 0));
160            }
161        } else {
162            return (($uint64 >> 1) & 0x7FFFFFFFFFFFFFFF) ^ (-($uint64 & 1));
163        }
164    }
165
166    public static function readInt32(&$input, &$value)
167    {
168        return $input->readVarint32($value);
169    }
170
171    public static function readInt64(&$input, &$value)
172    {
173        $success = $input->readVarint64($value);
174        if (PHP_INT_SIZE == 4 && bccomp($value, "9223372036854775807") > 0) {
175            $value = bcsub($value, "18446744073709551616");
176        }
177        return $success;
178    }
179
180    public static function readUint32(&$input, &$value)
181    {
182        return self::readInt32($input, $value);
183    }
184
185    public static function readUint64(&$input, &$value)
186    {
187        return self::readInt64($input, $value);
188    }
189
190    public static function readSint32(&$input, &$value)
191    {
192        if (!$input->readVarint32($value)) {
193            return false;
194        }
195        $value = GPBWire::zigZagDecode32($value);
196        return true;
197    }
198
199    public static function readSint64(&$input, &$value)
200    {
201        if (!$input->readVarint64($value)) {
202            return false;
203        }
204        $value = GPBWire::zigZagDecode64($value);
205        return true;
206    }
207
208    public static function readFixed32(&$input, &$value)
209    {
210        return $input->readLittleEndian32($value);
211    }
212
213    public static function readFixed64(&$input, &$value)
214    {
215        return $input->readLittleEndian64($value);
216    }
217
218    public static function readSfixed32(&$input, &$value)
219    {
220        if (!self::readFixed32($input, $value)) {
221            return false;
222        }
223        if (PHP_INT_SIZE === 8) {
224            $value |= (-($value >> 31) << 32);
225        }
226        return true;
227    }
228
229    public static function readSfixed64(&$input, &$value)
230    {
231        $success = $input->readLittleEndian64($value);
232        if (PHP_INT_SIZE == 4 && bccomp($value, "9223372036854775807") > 0) {
233            $value = bcsub($value, "18446744073709551616");
234        }
235        return $success;
236    }
237
238    public static function readFloat(&$input, &$value)
239    {
240        $data = null;
241        if (!$input->readRaw(4, $data)) {
242            return false;
243        }
244        $value = unpack('f', $data)[1];
245        return true;
246    }
247
248    public static function readDouble(&$input, &$value)
249    {
250        $data = null;
251        if (!$input->readRaw(8, $data)) {
252            return false;
253        }
254        $value = unpack('d', $data)[1];
255        return true;
256    }
257
258    public static function readBool(&$input, &$value)
259    {
260        if (!$input->readVarint64($value)) {
261            return false;
262        }
263        if ($value == 0) {
264            $value = false;
265        } else {
266            $value = true;
267        }
268        return true;
269    }
270
271    public static function readString(&$input, &$value)
272    {
273        $length = 0;
274        return $input->readVarintSizeAsInt($length) && $input->readRaw($length, $value);
275    }
276
277    public static function readMessage(&$input, &$message)
278    {
279        $length = 0;
280        if (!$input->readVarintSizeAsInt($length)) {
281            return false;
282        }
283        $old_limit = 0;
284        $recursion_limit = 0;
285        $input->incrementRecursionDepthAndPushLimit(
286            $length,
287            $old_limit,
288            $recursion_limit);
289        if ($recursion_limit < 0 || !$message->parseFromStream($input)) {
290            return false;
291        }
292        return $input->decrementRecursionDepthAndPopLimit($old_limit);
293    }
294
295    public static function writeTag(&$output, $tag)
296    {
297        return $output->writeTag($tag);
298    }
299
300    public static function writeInt32(&$output, $value)
301    {
302        return $output->writeVarint32($value, false);
303    }
304
305    public static function writeInt64(&$output, $value)
306    {
307        return $output->writeVarint64($value);
308    }
309
310    public static function writeUint32(&$output, $value)
311    {
312        return $output->writeVarint32($value, true);
313    }
314
315    public static function writeUint64(&$output, $value)
316    {
317        return $output->writeVarint64($value);
318    }
319
320    public static function writeSint32(&$output, $value)
321    {
322        $value = GPBWire::zigZagEncode32($value);
323        return $output->writeVarint32($value, true);
324    }
325
326    public static function writeSint64(&$output, $value)
327    {
328        $value = GPBWire::zigZagEncode64($value);
329        return $output->writeVarint64($value);
330    }
331
332    public static function writeFixed32(&$output, $value)
333    {
334        return $output->writeLittleEndian32($value);
335    }
336
337    public static function writeFixed64(&$output, $value)
338    {
339        return $output->writeLittleEndian64($value);
340    }
341
342    public static function writeSfixed32(&$output, $value)
343    {
344        return $output->writeLittleEndian32($value);
345    }
346
347    public static function writeSfixed64(&$output, $value)
348    {
349        return $output->writeLittleEndian64($value);
350    }
351
352    public static function writeBool(&$output, $value)
353    {
354        if ($value) {
355            return $output->writeVarint32(1, true);
356        } else {
357            return $output->writeVarint32(0, true);
358        }
359    }
360
361    public static function writeFloat(&$output, $value)
362    {
363        $data = pack("f", $value);
364        return $output->writeRaw($data, 4);
365    }
366
367    public static function writeDouble(&$output, $value)
368    {
369        $data = pack("d", $value);
370        return $output->writeRaw($data, 8);
371    }
372
373    public static function writeString(&$output, $value)
374    {
375        return self::writeBytes($output, $value);
376    }
377
378    public static function writeBytes(&$output, $value)
379    {
380        $size = strlen($value);
381        if (!$output->writeVarint32($size, true)) {
382            return false;
383        }
384        return $output->writeRaw($value, $size);
385    }
386
387    public static function writeMessage(&$output, $value)
388    {
389        $size = $value->byteSize();
390        if (!$output->writeVarint32($size, true)) {
391            return false;
392        }
393        return $value->serializeToStream($output);
394    }
395
396    public static function makeTag($number, $type)
397    {
398        return ($number << 3) | self::getWireType($type);
399    }
400
401    public static function tagSize($field)
402    {
403        $tag = self::makeTag($field->getNumber(), $field->getType());
404        return self::varint32Size($tag);
405    }
406
407    public static function varint32Size($value, $sign_extended = false)
408    {
409        if ($value < 0) {
410            if ($sign_extended) {
411                return 10;
412            } else {
413                return 5;
414            }
415        }
416        if ($value < (1 <<  7)) {
417            return 1;
418        }
419        if ($value < (1 << 14)) {
420            return 2;
421        }
422        if ($value < (1 << 21)) {
423            return 3;
424        }
425        if ($value < (1 << 28)) {
426            return 4;
427        }
428        return 5;
429    }
430
431    public static function sint32Size($value)
432    {
433        $value = self::zigZagEncode32($value);
434        return self::varint32Size($value);
435    }
436
437    public static function sint64Size($value)
438    {
439        $value = self::zigZagEncode64($value);
440        return self::varint64Size($value);
441    }
442
443    public static function varint64Size($value)
444    {
445        if (PHP_INT_SIZE == 4) {
446            if (bccomp($value, 0) < 0 ||
447                bccomp($value, "9223372036854775807") > 0) {
448                return 10;
449            }
450            if (bccomp($value, 1 << 7) < 0) {
451                return 1;
452            }
453            if (bccomp($value, 1 << 14) < 0) {
454                return 2;
455            }
456            if (bccomp($value, 1 << 21) < 0) {
457                return 3;
458            }
459            if (bccomp($value, 1 << 28) < 0) {
460                return 4;
461            }
462            if (bccomp($value, '34359738368') < 0) {
463                return 5;
464            }
465            if (bccomp($value, '4398046511104') < 0) {
466                return 6;
467            }
468            if (bccomp($value, '562949953421312') < 0) {
469                return 7;
470            }
471            if (bccomp($value, '72057594037927936') < 0) {
472                return 8;
473            }
474            return 9;
475        } else {
476            if ($value < 0) {
477                return 10;
478            }
479            if ($value < (1 <<  7)) {
480                return 1;
481            }
482            if ($value < (1 << 14)) {
483                return 2;
484            }
485            if ($value < (1 << 21)) {
486                return 3;
487            }
488            if ($value < (1 << 28)) {
489                return 4;
490            }
491            if ($value < (1 << 35)) {
492                return 5;
493            }
494            if ($value < (1 << 42)) {
495                return 6;
496            }
497            if ($value < (1 << 49)) {
498                return 7;
499            }
500            if ($value < (1 << 56)) {
501                return 8;
502            }
503            return 9;
504        }
505    }
506
507    public static function serializeFieldToStream(
508        $value,
509        $field,
510        $need_tag,
511        &$output)
512    {
513        if ($need_tag) {
514            if (!GPBWire::writeTag(
515                $output,
516                self::makeTag(
517                    $field->getNumber(),
518                    $field->getType()))) {
519                return false;
520            }
521        }
522        switch ($field->getType()) {
523            case GPBType::DOUBLE:
524                if (!GPBWire::writeDouble($output, $value)) {
525                    return false;
526                }
527                break;
528            case GPBType::FLOAT:
529                if (!GPBWire::writeFloat($output, $value)) {
530                    return false;
531                }
532                break;
533            case GPBType::INT64:
534                if (!GPBWire::writeInt64($output, $value)) {
535                    return false;
536                }
537                break;
538            case GPBType::UINT64:
539                if (!GPBWire::writeUint64($output, $value)) {
540                    return false;
541                }
542                break;
543            case GPBType::INT32:
544                if (!GPBWire::writeInt32($output, $value)) {
545                    return false;
546                }
547                break;
548            case GPBType::FIXED32:
549                if (!GPBWire::writeFixed32($output, $value)) {
550                    return false;
551                }
552                break;
553            case GPBType::FIXED64:
554                if (!GPBWire::writeFixed64($output, $value)) {
555                    return false;
556                }
557                break;
558            case GPBType::BOOL:
559                if (!GPBWire::writeBool($output, $value)) {
560                    return false;
561                }
562                break;
563            case GPBType::STRING:
564                if (!GPBWire::writeString($output, $value)) {
565                    return false;
566                }
567                break;
568            //    case GPBType::GROUP:
569            //      echo "GROUP\xA";
570            //      trigger_error("Not implemented.", E_ERROR);
571            //      break;
572            case GPBType::MESSAGE:
573                if (!GPBWire::writeMessage($output, $value)) {
574                    return false;
575                }
576                break;
577            case GPBType::BYTES:
578                if (!GPBWire::writeBytes($output, $value)) {
579                    return false;
580                }
581                break;
582            case GPBType::UINT32:
583                if (PHP_INT_SIZE === 8 && $value < 0) {
584                    $value += 4294967296;
585                }
586                if (!GPBWire::writeUint32($output, $value)) {
587                    return false;
588                }
589                break;
590            case GPBType::ENUM:
591                if (!GPBWire::writeInt32($output, $value)) {
592                    return false;
593                }
594                break;
595            case GPBType::SFIXED32:
596                if (!GPBWire::writeSfixed32($output, $value)) {
597                    return false;
598                }
599                break;
600            case GPBType::SFIXED64:
601                if (!GPBWire::writeSfixed64($output, $value)) {
602                    return false;
603                }
604                break;
605            case GPBType::SINT32:
606                if (!GPBWire::writeSint32($output, $value)) {
607                    return false;
608                }
609                break;
610            case GPBType::SINT64:
611                if (!GPBWire::writeSint64($output, $value)) {
612                    return false;
613                }
614                break;
615            default:
616                user_error("Unsupported type.");
617                return false;
618        }
619
620        return true;
621    }
622}
623