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
33/**
34 * Defines Message, the parent class extended by all protocol message classes.
35 */
36
37namespace Google\Protobuf\Internal;
38
39use Google\Protobuf\Internal\CodedInputStream;
40use Google\Protobuf\Internal\CodedOutputStream;
41use Google\Protobuf\Internal\DescriptorPool;
42use Google\Protobuf\Internal\GPBLabel;
43use Google\Protobuf\Internal\GPBType;
44use Google\Protobuf\Internal\GPBWire;
45use Google\Protobuf\Internal\MapEntry;
46use Google\Protobuf\Internal\RepeatedField;
47use Google\Protobuf\ListValue;
48use Google\Protobuf\Value;
49use Google\Protobuf\Struct;
50use Google\Protobuf\NullValue;
51
52/**
53 * Parent class of all proto messages. Users should not instantiate this class
54 * or extend this class or its child classes by their own.  See the comment of
55 * specific functions for more details.
56 */
57class Message
58{
59
60    /**
61     * @ignore
62     */
63    private $desc;
64    private $unknown = "";
65
66    /**
67     * @ignore
68     */
69    public function __construct($data = NULL)
70    {
71        // MapEntry message is shared by all types of map fields, whose
72        // descriptors are different from each other. Thus, we cannot find a
73        // specific descriptor from the descriptor pool.
74        if ($this instanceof MapEntry) {
75            $this->initWithDescriptor($data);
76        } else {
77            $this->initWithGeneratedPool();
78            if (is_array($data)) {
79                $this->mergeFromArray($data);
80            } else if (!empty($data)) {
81                throw new \InvalidArgumentException(
82                    'Message constructor must be an array or null.'
83                );
84            }
85        }
86    }
87
88    /**
89     * @ignore
90     */
91    private function initWithGeneratedPool()
92    {
93        $pool = DescriptorPool::getGeneratedPool();
94        $this->desc = $pool->getDescriptorByClassName(get_class($this));
95        if (is_null($this->desc)) {
96            user_error(get_class($this) . " is not found in descriptor pool.");
97        }
98        foreach ($this->desc->getField() as $field) {
99            $setter = $field->getSetter();
100            if ($field->isMap()) {
101                $message_type = $field->getMessageType();
102                $key_field = $message_type->getFieldByNumber(1);
103                $value_field = $message_type->getFieldByNumber(2);
104                switch ($value_field->getType()) {
105                    case GPBType::MESSAGE:
106                    case GPBType::GROUP:
107                        $map_field = new MapField(
108                            $key_field->getType(),
109                            $value_field->getType(),
110                            $value_field->getMessageType()->getClass());
111                        $this->$setter($map_field);
112                        break;
113                    case GPBType::ENUM:
114                        $map_field = new MapField(
115                            $key_field->getType(),
116                            $value_field->getType(),
117                            $value_field->getEnumType()->getClass());
118                        $this->$setter($map_field);
119                        break;
120                    default:
121                        $map_field = new MapField(
122                            $key_field->getType(),
123                            $value_field->getType());
124                        $this->$setter($map_field);
125                        break;
126                }
127            } else if ($field->getLabel() === GPBLabel::REPEATED) {
128                switch ($field->getType()) {
129                    case GPBType::MESSAGE:
130                    case GPBType::GROUP:
131                        $repeated_field = new RepeatedField(
132                            $field->getType(),
133                            $field->getMessageType()->getClass());
134                        $this->$setter($repeated_field);
135                        break;
136                    case GPBType::ENUM:
137                        $repeated_field = new RepeatedField(
138                            $field->getType(),
139                            $field->getEnumType()->getClass());
140                        $this->$setter($repeated_field);
141                        break;
142                    default:
143                        $repeated_field = new RepeatedField($field->getType());
144                        $this->$setter($repeated_field);
145                        break;
146                }
147            } else if ($field->getOneofIndex() !== -1) {
148                $oneof = $this->desc->getOneofDecl()[$field->getOneofIndex()];
149                $oneof_name = $oneof->getName();
150                $this->$oneof_name = new OneofField($oneof);
151            } else if ($field->getLabel() === GPBLabel::OPTIONAL &&
152                       PHP_INT_SIZE == 4) {
153                switch ($field->getType()) {
154                    case GPBType::INT64:
155                    case GPBType::UINT64:
156                    case GPBType::FIXED64:
157                    case GPBType::SFIXED64:
158                    case GPBType::SINT64:
159                        $this->$setter("0");
160                }
161            }
162        }
163    }
164
165    /**
166     * @ignore
167     */
168    private function initWithDescriptor(Descriptor $desc)
169    {
170        $this->desc = $desc;
171        foreach ($desc->getField() as $field) {
172            $setter = $field->getSetter();
173            $defaultValue = $this->defaultValue($field);
174            $this->$setter($defaultValue);
175        }
176    }
177
178    protected function readOneof($number)
179    {
180        $field = $this->desc->getFieldByNumber($number);
181        $oneof = $this->desc->getOneofDecl()[$field->getOneofIndex()];
182        $oneof_name = $oneof->getName();
183        $oneof_field = $this->$oneof_name;
184        if ($number === $oneof_field->getNumber()) {
185            return $oneof_field->getValue();
186        } else {
187            return $this->defaultValue($field);
188        }
189    }
190
191    protected function writeOneof($number, $value)
192    {
193        $field = $this->desc->getFieldByNumber($number);
194        $oneof = $this->desc->getOneofDecl()[$field->getOneofIndex()];
195        $oneof_name = $oneof->getName();
196        $oneof_field = $this->$oneof_name;
197        $oneof_field->setValue($value);
198        $oneof_field->setFieldName($field->getName());
199        $oneof_field->setNumber($number);
200    }
201
202    protected function whichOneof($oneof_name)
203    {
204        $oneof_field = $this->$oneof_name;
205        $number = $oneof_field->getNumber();
206        if ($number == 0) {
207          return "";
208        }
209        $field = $this->desc->getFieldByNumber($number);
210        return $field->getName();
211    }
212
213    /**
214     * @ignore
215     */
216    private function defaultValue($field)
217    {
218        $value = null;
219
220        switch ($field->getType()) {
221            case GPBType::DOUBLE:
222            case GPBType::FLOAT:
223                return 0.0;
224            case GPBType::UINT32:
225            case GPBType::INT32:
226            case GPBType::FIXED32:
227            case GPBType::SFIXED32:
228            case GPBType::SINT32:
229            case GPBType::ENUM:
230                return 0;
231            case GPBType::INT64:
232            case GPBType::UINT64:
233            case GPBType::FIXED64:
234            case GPBType::SFIXED64:
235            case GPBType::SINT64:
236                if (PHP_INT_SIZE === 4) {
237                    return '0';
238                } else {
239                    return 0;
240                }
241            case GPBType::BOOL:
242                return false;
243            case GPBType::STRING:
244            case GPBType::BYTES:
245                return "";
246            case GPBType::GROUP:
247            case GPBType::MESSAGE:
248                return null;
249            default:
250                user_error("Unsupported type.");
251                return false;
252        }
253    }
254
255    /**
256     * @ignore
257     */
258    private function skipField($input, $tag)
259    {
260        $number = GPBWire::getTagFieldNumber($tag);
261        if ($number === 0) {
262            throw new GPBDecodeException("Illegal field number zero.");
263        }
264
265        $start = $input->current();
266        switch (GPBWire::getTagWireType($tag)) {
267            case GPBWireType::VARINT:
268                $uint64 = 0;
269                if (!$input->readVarint64($uint64)) {
270                    throw new GPBDecodeException(
271                        "Unexpected EOF inside varint.");
272                }
273                break;
274            case GPBWireType::FIXED64:
275                $uint64 = 0;
276                if (!$input->readLittleEndian64($uint64)) {
277                    throw new GPBDecodeException(
278                        "Unexpected EOF inside fixed64.");
279                }
280                break;
281            case GPBWireType::FIXED32:
282                $uint32 = 0;
283                if (!$input->readLittleEndian32($uint32)) {
284                    throw new GPBDecodeException(
285                        "Unexpected EOF inside fixed32.");
286                }
287                break;
288            case GPBWireType::LENGTH_DELIMITED:
289                $length = 0;
290                if (!$input->readVarint32($length)) {
291                    throw new GPBDecodeException(
292                        "Unexpected EOF inside length.");
293                }
294                $data = NULL;
295                if (!$input->readRaw($length, $data)) {
296                    throw new GPBDecodeException(
297                        "Unexpected EOF inside length delimited data.");
298                }
299                break;
300            case GPBWireType::START_GROUP:
301            case GPBWireType::END_GROUP:
302                throw new GPBDecodeException("Unexpected wire type.");
303            default:
304                throw new GPBDecodeException("Unexpected wire type.");
305        }
306        $end = $input->current();
307
308        $bytes = str_repeat(chr(0), CodedOutputStream::MAX_VARINT64_BYTES);
309        $size = CodedOutputStream::writeVarintToArray($tag, $bytes, true);
310        $this->unknown .= substr($bytes, 0, $size) . $input->substr($start, $end);
311    }
312
313    /**
314     * @ignore
315     */
316    private static function parseFieldFromStreamNoTag($input, $field, &$value)
317    {
318        switch ($field->getType()) {
319            case GPBType::DOUBLE:
320                if (!GPBWire::readDouble($input, $value)) {
321                    throw new GPBDecodeException(
322                        "Unexpected EOF inside double field.");
323                }
324                break;
325            case GPBType::FLOAT:
326                if (!GPBWire::readFloat($input, $value)) {
327                    throw new GPBDecodeException(
328                        "Unexpected EOF inside float field.");
329                }
330                break;
331            case GPBType::INT64:
332                if (!GPBWire::readInt64($input, $value)) {
333                    throw new GPBDecodeException(
334                        "Unexpected EOF inside int64 field.");
335                }
336                break;
337            case GPBType::UINT64:
338                if (!GPBWire::readUint64($input, $value)) {
339                    throw new GPBDecodeException(
340                        "Unexpected EOF inside uint64 field.");
341                }
342                break;
343            case GPBType::INT32:
344                if (!GPBWire::readInt32($input, $value)) {
345                    throw new GPBDecodeException(
346                        "Unexpected EOF inside int32 field.");
347                }
348                break;
349            case GPBType::FIXED64:
350                if (!GPBWire::readFixed64($input, $value)) {
351                    throw new GPBDecodeException(
352                        "Unexpected EOF inside fixed64 field.");
353                }
354                break;
355            case GPBType::FIXED32:
356                if (!GPBWire::readFixed32($input, $value)) {
357                    throw new GPBDecodeException(
358                        "Unexpected EOF inside fixed32 field.");
359                }
360                break;
361            case GPBType::BOOL:
362                if (!GPBWire::readBool($input, $value)) {
363                    throw new GPBDecodeException(
364                        "Unexpected EOF inside bool field.");
365                }
366                break;
367            case GPBType::STRING:
368                // TODO(teboring): Add utf-8 check.
369                if (!GPBWire::readString($input, $value)) {
370                    throw new GPBDecodeException(
371                        "Unexpected EOF inside string field.");
372                }
373                break;
374            case GPBType::GROUP:
375                trigger_error("Not implemented.", E_ERROR);
376                break;
377            case GPBType::MESSAGE:
378                if ($field->isMap()) {
379                    $value = new MapEntry($field->getMessageType());
380                } else {
381                    $klass = $field->getMessageType()->getClass();
382                    $value = new $klass;
383                }
384                if (!GPBWire::readMessage($input, $value)) {
385                    throw new GPBDecodeException(
386                        "Unexpected EOF inside message.");
387                }
388                break;
389            case GPBType::BYTES:
390                if (!GPBWire::readString($input, $value)) {
391                    throw new GPBDecodeException(
392                        "Unexpected EOF inside bytes field.");
393                }
394                break;
395            case GPBType::UINT32:
396                if (!GPBWire::readUint32($input, $value)) {
397                    throw new GPBDecodeException(
398                        "Unexpected EOF inside uint32 field.");
399                }
400                break;
401            case GPBType::ENUM:
402                // TODO(teboring): Check unknown enum value.
403                if (!GPBWire::readInt32($input, $value)) {
404                    throw new GPBDecodeException(
405                        "Unexpected EOF inside enum field.");
406                }
407                break;
408            case GPBType::SFIXED32:
409                if (!GPBWire::readSfixed32($input, $value)) {
410                    throw new GPBDecodeException(
411                        "Unexpected EOF inside sfixed32 field.");
412                }
413                break;
414            case GPBType::SFIXED64:
415                if (!GPBWire::readSfixed64($input, $value)) {
416                    throw new GPBDecodeException(
417                        "Unexpected EOF inside sfixed64 field.");
418                }
419                break;
420            case GPBType::SINT32:
421                if (!GPBWire::readSint32($input, $value)) {
422                    throw new GPBDecodeException(
423                        "Unexpected EOF inside sint32 field.");
424                }
425                break;
426            case GPBType::SINT64:
427                if (!GPBWire::readSint64($input, $value)) {
428                    throw new GPBDecodeException(
429                        "Unexpected EOF inside sint64 field.");
430                }
431                break;
432            default:
433                user_error("Unsupported type.");
434                return false;
435        }
436        return true;
437    }
438
439    /**
440     * @ignore
441     */
442    private function parseFieldFromStream($tag, $input, $field)
443    {
444        $value = null;
445
446        if (is_null($field)) {
447            $value_format = GPBWire::UNKNOWN;
448        } elseif (GPBWire::getTagWireType($tag) ===
449            GPBWire::getWireType($field->getType())) {
450            $value_format = GPBWire::NORMAL_FORMAT;
451        } elseif ($field->isPackable() &&
452            GPBWire::getTagWireType($tag) ===
453            GPBWire::WIRETYPE_LENGTH_DELIMITED) {
454            $value_format = GPBWire::PACKED_FORMAT;
455        } else {
456            // the wire type doesn't match. Put it in our unknown field set.
457            $value_format = GPBWire::UNKNOWN;
458        }
459
460        if ($value_format === GPBWire::UNKNOWN) {
461            $this->skipField($input, $tag);
462            return;
463        } elseif ($value_format === GPBWire::NORMAL_FORMAT) {
464            self::parseFieldFromStreamNoTag($input, $field, $value);
465        } elseif ($value_format === GPBWire::PACKED_FORMAT) {
466            $length = 0;
467            if (!GPBWire::readInt32($input, $length)) {
468                throw new GPBDecodeException(
469                    "Unexpected EOF inside packed length.");
470            }
471            $limit = $input->pushLimit($length);
472            $getter = $field->getGetter();
473            while ($input->bytesUntilLimit() > 0) {
474                self::parseFieldFromStreamNoTag($input, $field, $value);
475                $this->appendHelper($field, $value);
476            }
477            $input->popLimit($limit);
478            return;
479        } else {
480            return;
481        }
482
483        if ($field->isMap()) {
484            $this->kvUpdateHelper($field, $value->getKey(), $value->getValue());
485        } else if ($field->isRepeated()) {
486            $this->appendHelper($field, $value);
487        } else {
488            $setter = $field->getSetter();
489            $this->$setter($value);
490        }
491    }
492
493    /**
494     * Clear all containing fields.
495     * @return null.
496     */
497    public function clear()
498    {
499        $this->unknown = "";
500        foreach ($this->desc->getField() as $field) {
501            $setter = $field->getSetter();
502            if ($field->isMap()) {
503                $message_type = $field->getMessageType();
504                $key_field = $message_type->getFieldByNumber(1);
505                $value_field = $message_type->getFieldByNumber(2);
506                switch ($value_field->getType()) {
507                    case GPBType::MESSAGE:
508                    case GPBType::GROUP:
509                        $map_field = new MapField(
510                            $key_field->getType(),
511                            $value_field->getType(),
512                            $value_field->getMessageType()->getClass());
513                        $this->$setter($map_field);
514                        break;
515                    case GPBType::ENUM:
516                        $map_field = new MapField(
517                            $key_field->getType(),
518                            $value_field->getType(),
519                            $value_field->getEnumType()->getClass());
520                        $this->$setter($map_field);
521                        break;
522                    default:
523                        $map_field = new MapField(
524                            $key_field->getType(),
525                            $value_field->getType());
526                        $this->$setter($map_field);
527                        break;
528                }
529            } else if ($field->getLabel() === GPBLabel::REPEATED) {
530                switch ($field->getType()) {
531                    case GPBType::MESSAGE:
532                    case GPBType::GROUP:
533                        $repeated_field = new RepeatedField(
534                            $field->getType(),
535                            $field->getMessageType()->getClass());
536                        $this->$setter($repeated_field);
537                        break;
538                    case GPBType::ENUM:
539                        $repeated_field = new RepeatedField(
540                            $field->getType(),
541                            $field->getEnumType()->getClass());
542                        $this->$setter($repeated_field);
543                        break;
544                    default:
545                        $repeated_field = new RepeatedField($field->getType());
546                        $this->$setter($repeated_field);
547                        break;
548                }
549            } else if ($field->getOneofIndex() !== -1) {
550                $oneof = $this->desc->getOneofDecl()[$field->getOneofIndex()];
551                $oneof_name = $oneof->getName();
552                $this->$oneof_name = new OneofField($oneof);
553            } else if ($field->getLabel() === GPBLabel::OPTIONAL) {
554                switch ($field->getType()) {
555                    case GPBType::DOUBLE   :
556                    case GPBType::FLOAT    :
557                        $this->$setter(0.0);
558                        break;
559                    case GPBType::INT32    :
560                    case GPBType::FIXED32  :
561                    case GPBType::UINT32   :
562                    case GPBType::SFIXED32 :
563                    case GPBType::SINT32   :
564                    case GPBType::ENUM     :
565                        $this->$setter(0);
566                        break;
567                    case GPBType::BOOL     :
568                        $this->$setter(false);
569                        break;
570                    case GPBType::STRING   :
571                    case GPBType::BYTES    :
572                        $this->$setter("");
573                        break;
574                    case GPBType::GROUP    :
575                    case GPBType::MESSAGE  :
576                        $null = null;
577                        $this->$setter($null);
578                        break;
579                }
580                if (PHP_INT_SIZE == 4) {
581                    switch ($field->getType()) {
582                        case GPBType::INT64:
583                        case GPBType::UINT64:
584                        case GPBType::FIXED64:
585                        case GPBType::SFIXED64:
586                        case GPBType::SINT64:
587                            $this->$setter("0");
588                    }
589                } else {
590                    switch ($field->getType()) {
591                        case GPBType::INT64:
592                        case GPBType::UINT64:
593                        case GPBType::FIXED64:
594                        case GPBType::SFIXED64:
595                        case GPBType::SINT64:
596                            $this->$setter(0);
597                    }
598                }
599            }
600        }
601    }
602
603    /**
604     * Clear all unknown fields previously parsed.
605     * @return null.
606     */
607    public function discardUnknownFields()
608    {
609        $this->unknown = "";
610        foreach ($this->desc->getField() as $field) {
611            if ($field->getType() != GPBType::MESSAGE) {
612                continue;
613            }
614            if ($field->isMap()) {
615                $value_field = $field->getMessageType()->getFieldByNumber(2);
616                if ($value_field->getType() != GPBType::MESSAGE) {
617                    continue;
618                }
619                $getter = $field->getGetter();
620                $map = $this->$getter();
621                foreach ($map as $key => $value) {
622                    $value->discardUnknownFields();
623                }
624            } else if ($field->getLabel() === GPBLabel::REPEATED) {
625                $getter = $field->getGetter();
626                $arr = $this->$getter();
627                foreach ($arr as $sub) {
628                    $sub->discardUnknownFields();
629                }
630            } else if ($field->getLabel() === GPBLabel::OPTIONAL) {
631                $getter = $field->getGetter();
632                $sub = $this->$getter();
633                if (!is_null($sub)) {
634                    $sub->discardUnknownFields();
635                }
636            }
637        }
638    }
639
640    /**
641     * Merges the contents of the specified message into current message.
642     *
643     * This method merges the contents of the specified message into the
644     * current message. Singular fields that are set in the specified message
645     * overwrite the corresponding fields in the current message.  Repeated
646     * fields are appended. Map fields key-value pairs are overritten.
647     * Singular/Oneof sub-messages are recursively merged. All overritten
648     * sub-messages are deep-copied.
649     *
650     * @param object $msg Protobuf message to be merged from.
651     * @return null.
652     */
653    public function mergeFrom($msg)
654    {
655        if (get_class($this) !== get_class($msg)) {
656            user_error("Cannot merge messages with different class.");
657            return;
658        }
659
660        foreach ($this->desc->getField() as $field) {
661            $setter = $field->getSetter();
662            $getter = $field->getGetter();
663            if ($field->isMap()) {
664                if (count($msg->$getter()) != 0) {
665                    $value_field = $field->getMessageType()->getFieldByNumber(2);
666                    foreach ($msg->$getter() as $key => $value) {
667                        if ($value_field->getType() == GPBType::MESSAGE) {
668                            $klass = $value_field->getMessageType()->getClass();
669                            $copy = new $klass;
670                            $copy->mergeFrom($value);
671
672                            $this->kvUpdateHelper($field, $key, $copy);
673                        } else {
674                            $this->kvUpdateHelper($field, $key, $value);
675                        }
676                    }
677                }
678            } else if ($field->getLabel() === GPBLabel::REPEATED) {
679                if (count($msg->$getter()) != 0) {
680                    foreach ($msg->$getter() as $tmp) {
681                        if ($field->getType() == GPBType::MESSAGE) {
682                            $klass = $field->getMessageType()->getClass();
683                            $copy = new $klass;
684                            $copy->mergeFrom($tmp);
685                            $this->appendHelper($field, $copy);
686                        } else {
687                            $this->appendHelper($field, $tmp);
688                        }
689                    }
690                }
691            } else if ($field->getLabel() === GPBLabel::OPTIONAL) {
692                if($msg->$getter() !== $this->defaultValue($field)) {
693                    $tmp = $msg->$getter();
694                    if ($field->getType() == GPBType::MESSAGE) {
695                        if (is_null($this->$getter())) {
696                            $klass = $field->getMessageType()->getClass();
697                            $new_msg = new $klass;
698                            $this->$setter($new_msg);
699                        }
700                        $this->$getter()->mergeFrom($tmp);
701                    } else {
702                        $this->$setter($tmp);
703                    }
704                }
705            }
706        }
707    }
708
709    /**
710     * Parses a protocol buffer contained in a string.
711     *
712     * This function takes a string in the (non-human-readable) binary wire
713     * format, matching the encoding output by serializeToString().
714     * See mergeFrom() for merging behavior, if the field is already set in the
715     * specified message.
716     *
717     * @param string $data Binary protobuf data.
718     * @return null.
719     * @throws \Exception Invalid data.
720     */
721    public function mergeFromString($data)
722    {
723        $input = new CodedInputStream($data);
724        $this->parseFromStream($input);
725    }
726
727    /**
728     * Parses a json string to protobuf message.
729     *
730     * This function takes a string in the json wire format, matching the
731     * encoding output by serializeToJsonString().
732     * See mergeFrom() for merging behavior, if the field is already set in the
733     * specified message.
734     *
735     * @param string $data Json protobuf data.
736     * @return null.
737     * @throws \Exception Invalid data.
738     */
739    public function mergeFromJsonString($data)
740    {
741        $input = new RawInputStream($data);
742        $this->parseFromJsonStream($input);
743    }
744
745    /**
746     * @ignore
747     */
748    public function parseFromStream($input)
749    {
750        while (true) {
751            $tag = $input->readTag();
752            // End of input.  This is a valid place to end, so return true.
753            if ($tag === 0) {
754                return true;
755            }
756
757            $number = GPBWire::getTagFieldNumber($tag);
758            $field = $this->desc->getFieldByNumber($number);
759
760            $this->parseFieldFromStream($tag, $input, $field);
761        }
762    }
763
764    private function convertJsonValueToProtoValue(
765        $value,
766        $field,
767        $is_map_key = false)
768    {
769        switch ($field->getType()) {
770            case GPBType::MESSAGE:
771                $klass = $field->getMessageType()->getClass();
772                $submsg = new $klass;
773
774                if (is_a($submsg, "Google\Protobuf\Duration")) {
775                    if (is_null($value)) {
776                        return $this->defaultValue($field);
777                    } else if (!is_string($value)) {
778                        throw new GPBDecodeException("Expect string.");
779                    }
780                    return GPBUtil::parseDuration($value);
781                } else if ($field->isTimestamp()) {
782                    if (is_null($value)) {
783                        return $this->defaultValue($field);
784                    } else if (!is_string($value)) {
785                        throw new GPBDecodeException("Expect string.");
786                    }
787                    try {
788                        $timestamp = GPBUtil::parseTimestamp($value);
789                    } catch (\Exception $e) {
790                        throw new GPBDecodeException(
791                            "Invalid RFC 3339 timestamp: ".$e->getMessage());
792                    }
793
794                    $submsg->setSeconds($timestamp->getSeconds());
795                    $submsg->setNanos($timestamp->getNanos());
796                } else if (is_a($submsg, "Google\Protobuf\FieldMask")) {
797                    if (is_null($value)) {
798                        return $this->defaultValue($field);
799                    }
800                    try {
801                        return GPBUtil::parseFieldMask($value);
802                    } catch (\Exception $e) {
803                        throw new GPBDecodeException(
804                            "Invalid FieldMask: ".$e->getMessage());
805                    }
806                } else {
807                    if (is_null($value) &&
808                        !is_a($submsg, "Google\Protobuf\Value")) {
809                        return $this->defaultValue($field);
810                    }
811                    if (GPBUtil::hasSpecialJsonMapping($submsg)) {
812                    } elseif (!is_object($value) && !is_array($value)) {
813                        throw new GPBDecodeException("Expect message.");
814                    }
815                    $submsg->mergeFromJsonArray($value);
816                }
817                return $submsg;
818            case GPBType::ENUM:
819                if (is_null($value)) {
820                    return $this->defaultValue($field);
821                }
822                if (is_integer($value)) {
823                    return $value;
824                }
825                $enum_value = $field->getEnumType()->getValueByName($value);
826                if (!is_null($enum_value)) {
827                    return $enum_value->getNumber();
828                }
829                throw new GPBDecodeException(
830                        "Enum field only accepts integer or enum value name");
831            case GPBType::STRING:
832                if (is_null($value)) {
833                    return $this->defaultValue($field);
834                }
835                if (is_numeric($value)) {
836                    return strval($value);
837                }
838                if (!is_string($value)) {
839                    throw new GPBDecodeException(
840                        "String field only accepts string value");
841                }
842                return $value;
843            case GPBType::BYTES:
844                if (is_null($value)) {
845                    return $this->defaultValue($field);
846                }
847                if (!is_string($value)) {
848                    throw new GPBDecodeException(
849                        "Byte field only accepts string value");
850                }
851                $proto_value = base64_decode($value, true);
852                if ($proto_value === false) {
853                    throw new GPBDecodeException("Invalid base64 characters");
854                }
855                return $proto_value;
856            case GPBType::BOOL:
857                if (is_null($value)) {
858                    return $this->defaultValue($field);
859                }
860                if ($is_map_key) {
861                    if ($value === "true") {
862                        return true;
863                    }
864                    if ($value === "false") {
865                        return false;
866                    }
867                    throw new GPBDecodeException(
868                        "Bool field only accepts bool value");
869                }
870                if (!is_bool($value)) {
871                    throw new GPBDecodeException(
872                        "Bool field only accepts bool value");
873                }
874                return $value;
875            case GPBType::FLOAT:
876            case GPBType::DOUBLE:
877                if (is_null($value)) {
878                    return $this->defaultValue($field);
879                }
880                if ($value === "Infinity") {
881                    return INF;
882                }
883                if ($value === "-Infinity") {
884                    return -INF;
885                }
886                if ($value === "NaN") {
887                    return NAN;
888                }
889                return $value;
890            case GPBType::INT32:
891            case GPBType::SINT32:
892            case GPBType::SFIXED32:
893                if (is_null($value)) {
894                    return $this->defaultValue($field);
895                }
896                if (!is_numeric($value)) {
897                   throw new GPBDecodeException(
898                       "Invalid data type for int32 field");
899                }
900                if (bccomp($value, "2147483647") > 0) {
901                   throw new GPBDecodeException(
902                       "Int32 too large");
903                }
904                if (bccomp($value, "-2147483648") < 0) {
905                   throw new GPBDecodeException(
906                       "Int32 too small");
907                }
908                return $value;
909            case GPBType::UINT32:
910            case GPBType::FIXED32:
911                if (is_null($value)) {
912                    return $this->defaultValue($field);
913                }
914                if (!is_numeric($value)) {
915                   throw new GPBDecodeException(
916                       "Invalid data type for uint32 field");
917                }
918                if (bccomp($value, 4294967295) > 0) {
919                    throw new GPBDecodeException(
920                        "Uint32 too large");
921                }
922                return $value;
923            case GPBType::INT64:
924            case GPBType::SINT64:
925            case GPBType::SFIXED64:
926                if (is_null($value)) {
927                    return $this->defaultValue($field);
928                }
929                if (!is_numeric($value)) {
930                   throw new GPBDecodeException(
931                       "Invalid data type for int64 field");
932                }
933                if (bccomp($value, "9223372036854775807") > 0) {
934                    throw new GPBDecodeException(
935                        "Int64 too large");
936                }
937                if (bccomp($value, "-9223372036854775808") < 0) {
938                    throw new GPBDecodeException(
939                        "Int64 too small");
940                }
941                return $value;
942            case GPBType::UINT64:
943            case GPBType::FIXED64:
944                if (is_null($value)) {
945                    return $this->defaultValue($field);
946                }
947                if (!is_numeric($value)) {
948                   throw new GPBDecodeException(
949                       "Invalid data type for int64 field");
950                }
951                if (bccomp($value, "18446744073709551615") > 0) {
952                    throw new GPBDecodeException(
953                        "Uint64 too large");
954                }
955                if (bccomp($value, "9223372036854775807") > 0) {
956                    $value = bcsub($value, "18446744073709551616");
957                }
958                return $value;
959            default:
960                return $value;
961        }
962    }
963
964    /**
965     * Populates the message from a user-supplied PHP array. Array keys
966     * correspond to Message properties and nested message properties.
967     *
968     * Example:
969     * ```
970     * $message->mergeFromArray([
971     *     'name' => 'This is a message name',
972     *     'interval' => [
973     *          'startTime' => time() - 60,
974     *          'endTime' => time(),
975     *     ]
976     * ]);
977     * ```
978     *
979     * This method will trigger an error if it is passed data that cannot
980     * be converted to the correct type. For example, a StringValue field
981     * must receive data that is either a string or a StringValue object.
982     *
983     * @param array $array An array containing message properties and values.
984     * @return null.
985     */
986    protected function mergeFromArray(array $array)
987    {
988        // Just call the setters for the field names
989        foreach ($array as $key => $value) {
990            $field = $this->desc->getFieldByName($key);
991            if (is_null($field)) {
992                throw new \UnexpectedValueException(
993                    'Invalid message property: ' . $key);
994            }
995            $setter = $field->getSetter();
996            if ($field->isMap()) {
997                $valueField = $field->getMessageType()->getFieldByName('value');
998                if (!is_null($valueField) && $valueField->isWrapperType()) {
999                    self::normalizeArrayElementsToMessageType($value, $valueField->getMessageType()->getClass());
1000                }
1001            } elseif ($field->isWrapperType()) {
1002                $class = $field->getMessageType()->getClass();
1003                if ($field->isRepeated()) {
1004                    self::normalizeArrayElementsToMessageType($value, $class);
1005                } else {
1006                    self::normalizeToMessageType($value, $class);
1007                }
1008            }
1009            $this->$setter($value);
1010        }
1011    }
1012
1013    /**
1014     * Tries to normalize the elements in $value into a provided protobuf
1015     * wrapper type $class. If $value is any type other than array, we do
1016     * not do any conversion, and instead rely on the existing protobuf
1017     * type checking. If $value is an array, we process each element and
1018     * try to convert it to an instance of $class.
1019     *
1020     * @param mixed $value The array of values to normalize.
1021     * @param string $class The expected wrapper class name
1022     */
1023    private static function normalizeArrayElementsToMessageType(&$value, $class)
1024    {
1025        if (!is_array($value)) {
1026            // In the case that $value is not an array, we do not want to
1027            // attempt any conversion. Note that this includes the cases
1028            // when $value is a RepeatedField of MapField. In those cases,
1029            // we do not need to convert the elements, as they should
1030            // already be the correct types.
1031            return;
1032        } else {
1033            // Normalize each element in the array.
1034            foreach ($value as $key => &$elementValue) {
1035              self::normalizeToMessageType($elementValue, $class);
1036            }
1037        }
1038    }
1039
1040    /**
1041     * Tries to normalize $value into a provided protobuf wrapper type $class.
1042     * If $value is any type other than an object, we attempt to construct an
1043     * instance of $class and assign $value to it using the setValue method
1044     * shared by all wrapper types.
1045     *
1046     * This method will raise an error if it receives a type that cannot be
1047     * assigned to the wrapper type via setValue.
1048     *
1049     * @param mixed $value The value to normalize.
1050     * @param string $class The expected wrapper class name
1051     */
1052    private static function normalizeToMessageType(&$value, $class)
1053    {
1054        if (is_null($value) || is_object($value)) {
1055            // This handles the case that $value is an instance of $class. We
1056            // choose not to do any more strict checking here, relying on the
1057            // existing type checking done by GPBUtil.
1058            return;
1059        } else {
1060            // Try to instantiate $class and set the value
1061            try {
1062                $msg = new $class;
1063                $msg->setValue($value);
1064                $value = $msg;
1065                return;
1066            } catch (\Exception $exception) {
1067                trigger_error(
1068                    "Error normalizing value to type '$class': " . $exception->getMessage(),
1069                    E_USER_ERROR
1070                );
1071            }
1072        }
1073    }
1074
1075    protected function mergeFromJsonArray($array)
1076    {
1077        if (is_a($this, "Google\Protobuf\Any")) {
1078            $this->clear();
1079            $this->setTypeUrl($array["@type"]);
1080            $msg = $this->unpack();
1081            if (GPBUtil::hasSpecialJsonMapping($msg)) {
1082                $msg->mergeFromJsonArray($array["value"]);
1083            } else {
1084                unset($array["@type"]);
1085                $msg->mergeFromJsonArray($array);
1086            }
1087            $this->setValue($msg->serializeToString());
1088            return;
1089        }
1090        if (is_a($this, "Google\Protobuf\DoubleValue") ||
1091            is_a($this, "Google\Protobuf\FloatValue")  ||
1092            is_a($this, "Google\Protobuf\Int64Value")  ||
1093            is_a($this, "Google\Protobuf\UInt64Value") ||
1094            is_a($this, "Google\Protobuf\Int32Value")  ||
1095            is_a($this, "Google\Protobuf\UInt32Value") ||
1096            is_a($this, "Google\Protobuf\BoolValue")   ||
1097            is_a($this, "Google\Protobuf\StringValue")) {
1098            $this->setValue($array);
1099            return;
1100        }
1101        if (is_a($this, "Google\Protobuf\BytesValue")) {
1102            $this->setValue(base64_decode($array));
1103            return;
1104        }
1105        if (is_a($this, "Google\Protobuf\Duration")) {
1106            $this->mergeFrom(GPBUtil::parseDuration($array));
1107            return;
1108        }
1109        if (is_a($this, "Google\Protobuf\FieldMask")) {
1110            $this->mergeFrom(GPBUtil::parseFieldMask($array));
1111            return;
1112        }
1113        if (is_a($this, "Google\Protobuf\Timestamp")) {
1114            $this->mergeFrom(GPBUtil::parseTimestamp($array));
1115            return;
1116        }
1117        if (is_a($this, "Google\Protobuf\Struct")) {
1118            $fields = $this->getFields();
1119            foreach($array as $key => $value) {
1120                $v = new Value();
1121                $v->mergeFromJsonArray($value);
1122                $fields[$key] = $v;
1123            }
1124        }
1125        if (is_a($this, "Google\Protobuf\Value")) {
1126            if (is_bool($array)) {
1127                $this->setBoolValue($array);
1128            } elseif (is_string($array)) {
1129                $this->setStringValue($array);
1130            } elseif (is_null($array)) {
1131                $this->setNullValue(0);
1132            } elseif (is_double($array) || is_integer($array)) {
1133                $this->setNumberValue($array);
1134            } elseif (is_array($array)) {
1135                if (array_values($array) !== $array) {
1136                    // Associative array
1137                    $struct_value = $this->getStructValue();
1138                    if (is_null($struct_value)) {
1139                        $struct_value = new Struct();
1140                        $this->setStructValue($struct_value);
1141                    }
1142                    foreach ($array as $key => $v) {
1143                        $value = new Value();
1144                        $value->mergeFromJsonArray($v);
1145                        $values = $struct_value->getFields();
1146                        $values[$key]= $value;
1147                    }
1148                } else {
1149                    // Array
1150                    $list_value = $this->getListValue();
1151                    if (is_null($list_value)) {
1152                        $list_value = new ListValue();
1153                        $this->setListValue($list_value);
1154                    }
1155                    foreach ($array as $v) {
1156                        $value = new Value();
1157                        $value->mergeFromJsonArray($v);
1158                        $values = $list_value->getValues();
1159                        $values[]= $value;
1160                    }
1161                }
1162            } else {
1163                throw new GPBDecodeException("Invalid type for Value.");
1164            }
1165            return;
1166        }
1167        $this->mergeFromArrayJsonImpl($array);
1168    }
1169
1170    private function mergeFromArrayJsonImpl($array)
1171    {
1172        foreach ($array as $key => $value) {
1173            $field = $this->desc->getFieldByJsonName($key);
1174            if (is_null($field)) {
1175                $field = $this->desc->getFieldByName($key);
1176                if (is_null($field)) {
1177                    continue;
1178                }
1179            }
1180            if ($field->isMap()) {
1181                if (is_null($value)) {
1182                    continue;
1183                }
1184                $key_field = $field->getMessageType()->getFieldByNumber(1);
1185                $value_field = $field->getMessageType()->getFieldByNumber(2);
1186                foreach ($value as $tmp_key => $tmp_value) {
1187                    if (is_null($tmp_value)) {
1188                        throw new \Exception(
1189                            "Map value field element cannot be null.");
1190                    }
1191                    $proto_key = $this->convertJsonValueToProtoValue(
1192                        $tmp_key,
1193                        $key_field,
1194                        true);
1195                    $proto_value = $this->convertJsonValueToProtoValue(
1196                        $tmp_value,
1197                        $value_field);
1198                    self::kvUpdateHelper($field, $proto_key, $proto_value);
1199                }
1200            } else if ($field->isRepeated()) {
1201                if (is_null($value)) {
1202                    continue;
1203                }
1204                foreach ($value as $tmp) {
1205                    if (is_null($tmp)) {
1206                        throw new \Exception(
1207                            "Repeated field elements cannot be null.");
1208                    }
1209                    $proto_value = $this->convertJsonValueToProtoValue(
1210                        $tmp,
1211                        $field);
1212                    self::appendHelper($field, $proto_value);
1213                }
1214            } else {
1215                $setter = $field->getSetter();
1216                $proto_value = $this->convertJsonValueToProtoValue(
1217                    $value,
1218                    $field);
1219                if ($field->getType() === GPBType::MESSAGE) {
1220                    if (is_null($proto_value)) {
1221                        continue;
1222                    }
1223                    $getter = $field->getGetter();
1224                    $submsg = $this->$getter();
1225                    if (!is_null($submsg)) {
1226                        $submsg->mergeFrom($proto_value);
1227                        continue;
1228                    }
1229                }
1230                $this->$setter($proto_value);
1231            }
1232        }
1233    }
1234
1235    /**
1236     * @ignore
1237     */
1238    public function parseFromJsonStream($input)
1239    {
1240        $array = json_decode($input->getData(), true, 512, JSON_BIGINT_AS_STRING);
1241        if ($this instanceof \Google\Protobuf\ListValue) {
1242            $array = ["values"=>$array];
1243        }
1244        if (is_null($array)) {
1245            if ($this instanceof \Google\Protobuf\Value) {
1246              $this->setNullValue(\Google\Protobuf\NullValue::NULL_VALUE);
1247              return;
1248            } else {
1249              throw new GPBDecodeException(
1250                  "Cannot decode json string: " . $input->getData());
1251            }
1252        }
1253        try {
1254            $this->mergeFromJsonArray($array);
1255        } catch (\Exception $e) {
1256            throw new GPBDecodeException($e->getMessage());
1257        }
1258    }
1259
1260    /**
1261     * @ignore
1262     */
1263    private function serializeSingularFieldToStream($field, &$output)
1264    {
1265        if (!$this->existField($field)) {
1266            return true;
1267        }
1268        $getter = $field->getGetter();
1269        $value = $this->$getter();
1270        if (!GPBWire::serializeFieldToStream($value, $field, true, $output)) {
1271            return false;
1272        }
1273        return true;
1274    }
1275
1276    /**
1277     * @ignore
1278     */
1279    private function serializeRepeatedFieldToStream($field, &$output)
1280    {
1281        $getter = $field->getGetter();
1282        $values = $this->$getter();
1283        $count = count($values);
1284        if ($count === 0) {
1285            return true;
1286        }
1287
1288        $packed = $field->getPacked();
1289        if ($packed) {
1290            if (!GPBWire::writeTag(
1291                $output,
1292                GPBWire::makeTag($field->getNumber(), GPBType::STRING))) {
1293                return false;
1294            }
1295            $size = 0;
1296            foreach ($values as $value) {
1297                $size += $this->fieldDataOnlyByteSize($field, $value);
1298            }
1299            if (!$output->writeVarint32($size, true)) {
1300                return false;
1301            }
1302        }
1303
1304        foreach ($values as $value) {
1305            if (!GPBWire::serializeFieldToStream(
1306                $value,
1307                $field,
1308                !$packed,
1309                $output)) {
1310                return false;
1311            }
1312        }
1313        return true;
1314    }
1315
1316    /**
1317     * @ignore
1318     */
1319    private function serializeMapFieldToStream($field, $output)
1320    {
1321        $getter = $field->getGetter();
1322        $values = $this->$getter();
1323        $count = count($values);
1324        if ($count === 0) {
1325            return true;
1326        }
1327
1328        foreach ($values as $key => $value) {
1329            $map_entry = new MapEntry($field->getMessageType());
1330            $map_entry->setKey($key);
1331            $map_entry->setValue($value);
1332            if (!GPBWire::serializeFieldToStream(
1333                $map_entry,
1334                $field,
1335                true,
1336                $output)) {
1337                return false;
1338            }
1339        }
1340        return true;
1341    }
1342
1343    /**
1344     * @ignore
1345     */
1346    private function serializeFieldToStream(&$output, $field)
1347    {
1348        if ($field->isMap()) {
1349            return $this->serializeMapFieldToStream($field, $output);
1350        } elseif ($field->isRepeated()) {
1351            return $this->serializeRepeatedFieldToStream($field, $output);
1352        } else {
1353            return $this->serializeSingularFieldToStream($field, $output);
1354        }
1355    }
1356
1357    /**
1358     * @ignore
1359     */
1360    private function serializeFieldToJsonStream(&$output, $field)
1361    {
1362        $getter = $field->getGetter();
1363        $values = $this->$getter();
1364        return GPBJsonWire::serializeFieldToStream(
1365            $values, $field, $output, !GPBUtil::hasSpecialJsonMapping($this));
1366    }
1367
1368    /**
1369     * @ignore
1370     */
1371    public function serializeToStream(&$output)
1372    {
1373        $fields = $this->desc->getField();
1374        foreach ($fields as $field) {
1375            if (!$this->serializeFieldToStream($output, $field)) {
1376                return false;
1377            }
1378        }
1379        $output->writeRaw($this->unknown, strlen($this->unknown));
1380        return true;
1381    }
1382
1383    /**
1384     * @ignore
1385     */
1386    public function serializeToJsonStream(&$output)
1387    {
1388        if (is_a($this, 'Google\Protobuf\Any')) {
1389            $output->writeRaw("{", 1);
1390            $type_field = $this->desc->getFieldByNumber(1);
1391            $value_msg = $this->unpack();
1392
1393            // Serialize type url.
1394            $output->writeRaw("\"@type\":", 8);
1395            $output->writeRaw("\"", 1);
1396            $output->writeRaw($this->getTypeUrl(), strlen($this->getTypeUrl()));
1397            $output->writeRaw("\"", 1);
1398
1399            // Serialize value
1400            if (GPBUtil::hasSpecialJsonMapping($value_msg)) {
1401                $output->writeRaw(",\"value\":", 9);
1402                $value_msg->serializeToJsonStream($output);
1403            } else {
1404                $value_fields = $value_msg->desc->getField();
1405                foreach ($value_fields as $field) {
1406                    if ($value_msg->existField($field)) {
1407                        $output->writeRaw(",", 1);
1408                        if (!$value_msg->serializeFieldToJsonStream($output, $field)) {
1409                            return false;
1410                        }
1411                    }
1412                }
1413            }
1414
1415            $output->writeRaw("}", 1);
1416        } elseif (is_a($this, 'Google\Protobuf\FieldMask')) {
1417            $field_mask = GPBUtil::formatFieldMask($this);
1418            $output->writeRaw("\"", 1);
1419            $output->writeRaw($field_mask, strlen($field_mask));
1420            $output->writeRaw("\"", 1);
1421        } elseif (is_a($this, 'Google\Protobuf\Duration')) {
1422            $duration = GPBUtil::formatDuration($this) . "s";
1423            $output->writeRaw("\"", 1);
1424            $output->writeRaw($duration, strlen($duration));
1425            $output->writeRaw("\"", 1);
1426        } elseif (get_class($this) === 'Google\Protobuf\Timestamp') {
1427            $timestamp = GPBUtil::formatTimestamp($this);
1428            $timestamp = json_encode($timestamp);
1429            $output->writeRaw($timestamp, strlen($timestamp));
1430        } elseif (get_class($this) === 'Google\Protobuf\ListValue') {
1431            $field = $this->desc->getField()[1];
1432            if (!$this->existField($field)) {
1433                $output->writeRaw("[]", 2);
1434            } else {
1435                if (!$this->serializeFieldToJsonStream($output, $field)) {
1436                    return false;
1437                }
1438            }
1439        } elseif (get_class($this) === 'Google\Protobuf\Struct') {
1440            $field = $this->desc->getField()[1];
1441            if (!$this->existField($field)) {
1442                $output->writeRaw("{}", 2);
1443            } else {
1444                if (!$this->serializeFieldToJsonStream($output, $field)) {
1445                    return false;
1446                }
1447            }
1448        } else {
1449            if (!GPBUtil::hasSpecialJsonMapping($this)) {
1450                $output->writeRaw("{", 1);
1451            }
1452            $fields = $this->desc->getField();
1453            $first = true;
1454            foreach ($fields as $field) {
1455                if ($this->existField($field) ||
1456                    GPBUtil::hasJsonValue($this)) {
1457                    if ($first) {
1458                        $first = false;
1459                    } else {
1460                        $output->writeRaw(",", 1);
1461                    }
1462                    if (!$this->serializeFieldToJsonStream($output, $field)) {
1463                        return false;
1464                    }
1465                }
1466            }
1467            if (!GPBUtil::hasSpecialJsonMapping($this)) {
1468                $output->writeRaw("}", 1);
1469            }
1470        }
1471        return true;
1472    }
1473
1474    /**
1475     * Serialize the message to string.
1476     * @return string Serialized binary protobuf data.
1477     */
1478    public function serializeToString()
1479    {
1480        $output = new CodedOutputStream($this->byteSize());
1481        $this->serializeToStream($output);
1482        return $output->getData();
1483    }
1484
1485    /**
1486     * Serialize the message to json string.
1487     * @return string Serialized json protobuf data.
1488     */
1489    public function serializeToJsonString()
1490    {
1491        $output = new CodedOutputStream($this->jsonByteSize());
1492        $this->serializeToJsonStream($output);
1493        return $output->getData();
1494    }
1495
1496    /**
1497     * @ignore
1498     */
1499    private function existField($field)
1500    {
1501        $oneof_index = $field->getOneofIndex();
1502        if ($oneof_index !== -1) {
1503            $oneof = $this->desc->getOneofDecl()[$oneof_index];
1504            $oneof_name = $oneof->getName();
1505            return $this->$oneof_name->getNumber() === $field->getNumber();
1506        }
1507
1508        $getter = $field->getGetter();
1509        $values = $this->$getter();
1510        if ($field->isMap()) {
1511            return count($values) !== 0;
1512        } elseif ($field->isRepeated()) {
1513            return count($values) !== 0;
1514        } else {
1515            return $values !== $this->defaultValue($field);
1516        }
1517    }
1518
1519    /**
1520     * @ignore
1521     */
1522    private function repeatedFieldDataOnlyByteSize($field)
1523    {
1524        $size = 0;
1525
1526        $getter = $field->getGetter();
1527        $values = $this->$getter();
1528        $count = count($values);
1529        if ($count !== 0) {
1530            $size += $count * GPBWire::tagSize($field);
1531            foreach ($values as $value) {
1532                $size += $this->singularFieldDataOnlyByteSize($field);
1533            }
1534        }
1535    }
1536
1537    /**
1538     * @ignore
1539     */
1540    private function fieldDataOnlyByteSize($field, $value)
1541    {
1542        $size = 0;
1543
1544        switch ($field->getType()) {
1545            case GPBType::BOOL:
1546                $size += 1;
1547                break;
1548            case GPBType::FLOAT:
1549            case GPBType::FIXED32:
1550            case GPBType::SFIXED32:
1551                $size += 4;
1552                break;
1553            case GPBType::DOUBLE:
1554            case GPBType::FIXED64:
1555            case GPBType::SFIXED64:
1556                $size += 8;
1557                break;
1558            case GPBType::INT32:
1559            case GPBType::ENUM:
1560                $size += GPBWire::varint32Size($value, true);
1561                break;
1562            case GPBType::UINT32:
1563                $size += GPBWire::varint32Size($value);
1564                break;
1565            case GPBType::UINT64:
1566            case GPBType::INT64:
1567                $size += GPBWire::varint64Size($value);
1568                break;
1569            case GPBType::SINT32:
1570                $size += GPBWire::sint32Size($value);
1571                break;
1572            case GPBType::SINT64:
1573                $size += GPBWire::sint64Size($value);
1574                break;
1575            case GPBType::STRING:
1576            case GPBType::BYTES:
1577                $size += strlen($value);
1578                $size += GPBWire::varint32Size($size);
1579                break;
1580            case GPBType::MESSAGE:
1581                $size += $value->byteSize();
1582                $size += GPBWire::varint32Size($size);
1583                break;
1584            case GPBType::GROUP:
1585                // TODO(teboring): Add support.
1586                user_error("Unsupported type.");
1587                break;
1588            default:
1589                user_error("Unsupported type.");
1590                return 0;
1591        }
1592
1593        return $size;
1594    }
1595
1596    /**
1597     * @ignore
1598     */
1599    private function fieldDataOnlyJsonByteSize($field, $value)
1600    {
1601        $size = 0;
1602
1603        switch ($field->getType()) {
1604            case GPBType::SFIXED32:
1605            case GPBType::SINT32:
1606            case GPBType::INT32:
1607                $size += strlen(strval($value));
1608                break;
1609            case GPBType::FIXED32:
1610            case GPBType::UINT32:
1611                if ($value < 0) {
1612                    $value = bcadd($value, "4294967296");
1613                }
1614                $size += strlen(strval($value));
1615                break;
1616            case GPBType::FIXED64:
1617            case GPBType::UINT64:
1618                if ($value < 0) {
1619                    $value = bcadd($value, "18446744073709551616");
1620                }
1621                // Intentional fall through.
1622            case GPBType::SFIXED64:
1623            case GPBType::INT64:
1624            case GPBType::SINT64:
1625                $size += 2;  // size for ""
1626                $size += strlen(strval($value));
1627                break;
1628            case GPBType::FLOAT:
1629                if (is_nan($value)) {
1630                    $size += strlen("NaN") + 2;
1631                } elseif ($value === INF) {
1632                    $size += strlen("Infinity") + 2;
1633                } elseif ($value === -INF) {
1634                    $size += strlen("-Infinity") + 2;
1635                } else {
1636                    $size += strlen(sprintf("%.8g", $value));
1637                }
1638                break;
1639            case GPBType::DOUBLE:
1640                if (is_nan($value)) {
1641                    $size += strlen("NaN") + 2;
1642                } elseif ($value === INF) {
1643                    $size += strlen("Infinity") + 2;
1644                } elseif ($value === -INF) {
1645                    $size += strlen("-Infinity") + 2;
1646                } else {
1647                    $size += strlen(sprintf("%.17g", $value));
1648                }
1649                break;
1650            case GPBType::ENUM:
1651                $enum_desc = $field->getEnumType();
1652                if ($enum_desc->getClass() === "Google\Protobuf\NullValue") {
1653                    $size += 4;
1654                    break;
1655                }
1656                $enum_value_desc = $enum_desc->getValueByNumber($value);
1657                if (!is_null($enum_value_desc)) {
1658                    $size += 2;  // size for ""
1659                    $size += strlen($enum_value_desc->getName());
1660                } else {
1661                    $str_value = strval($value);
1662                    $size += strlen($str_value);
1663                }
1664                break;
1665            case GPBType::BOOL:
1666                if ($value) {
1667                    $size += 4;
1668                } else {
1669                    $size += 5;
1670                }
1671                break;
1672            case GPBType::STRING:
1673                $value = json_encode($value, JSON_UNESCAPED_UNICODE);
1674                $size += strlen($value);
1675                break;
1676            case GPBType::BYTES:
1677                # if (is_a($this, "Google\Protobuf\BytesValue")) {
1678                #     $size += strlen(json_encode($value));
1679                # } else {
1680                #     $size += strlen(base64_encode($value));
1681                #     $size += 2;  // size for \"\"
1682                # }
1683                $size += strlen(base64_encode($value));
1684                $size += 2;  // size for \"\"
1685                break;
1686            case GPBType::MESSAGE:
1687                $size += $value->jsonByteSize();
1688                break;
1689#             case GPBType::GROUP:
1690#                 // TODO(teboring): Add support.
1691#                 user_error("Unsupported type.");
1692#                 break;
1693            default:
1694                user_error("Unsupported type " . $field->getType());
1695                return 0;
1696        }
1697
1698        return $size;
1699    }
1700
1701    /**
1702     * @ignore
1703     */
1704    private function fieldByteSize($field)
1705    {
1706        $size = 0;
1707        if ($field->isMap()) {
1708            $getter = $field->getGetter();
1709            $values = $this->$getter();
1710            $count = count($values);
1711            if ($count !== 0) {
1712                $size += $count * GPBWire::tagSize($field);
1713                $message_type = $field->getMessageType();
1714                $key_field = $message_type->getFieldByNumber(1);
1715                $value_field = $message_type->getFieldByNumber(2);
1716                foreach ($values as $key => $value) {
1717                    $data_size = 0;
1718                    if ($key != $this->defaultValue($key_field)) {
1719                        $data_size += $this->fieldDataOnlyByteSize(
1720                            $key_field,
1721                            $key);
1722                        $data_size += GPBWire::tagSize($key_field);
1723                    }
1724                    if ($value != $this->defaultValue($value_field)) {
1725                        $data_size += $this->fieldDataOnlyByteSize(
1726                            $value_field,
1727                            $value);
1728                        $data_size += GPBWire::tagSize($value_field);
1729                    }
1730                    $size += GPBWire::varint32Size($data_size) + $data_size;
1731                }
1732            }
1733        } elseif ($field->isRepeated()) {
1734            $getter = $field->getGetter();
1735            $values = $this->$getter();
1736            $count = count($values);
1737            if ($count !== 0) {
1738                if ($field->getPacked()) {
1739                    $data_size = 0;
1740                    foreach ($values as $value) {
1741                        $data_size += $this->fieldDataOnlyByteSize($field, $value);
1742                    }
1743                    $size += GPBWire::tagSize($field);
1744                    $size += GPBWire::varint32Size($data_size);
1745                    $size += $data_size;
1746                } else {
1747                    $size += $count * GPBWire::tagSize($field);
1748                    foreach ($values as $value) {
1749                        $size += $this->fieldDataOnlyByteSize($field, $value);
1750                    }
1751                }
1752            }
1753        } elseif ($this->existField($field)) {
1754            $size += GPBWire::tagSize($field);
1755            $getter = $field->getGetter();
1756            $value = $this->$getter();
1757            $size += $this->fieldDataOnlyByteSize($field, $value);
1758        }
1759        return $size;
1760    }
1761
1762    /**
1763     * @ignore
1764     */
1765    private function fieldJsonByteSize($field)
1766    {
1767        $size = 0;
1768
1769        if ($field->isMap()) {
1770            $getter = $field->getGetter();
1771            $values = $this->$getter();
1772            $count = count($values);
1773            if ($count !== 0) {
1774                if (!GPBUtil::hasSpecialJsonMapping($this)) {
1775                    $size += 3;                              // size for "\"\":".
1776                    $size += strlen($field->getJsonName());  // size for field name
1777                }
1778                $size += 2;  // size for "{}".
1779                $size += $count - 1;                     // size for commas
1780                $getter = $field->getGetter();
1781                $map_entry = $field->getMessageType();
1782                $key_field = $map_entry->getFieldByNumber(1);
1783                $value_field = $map_entry->getFieldByNumber(2);
1784                switch ($key_field->getType()) {
1785                case GPBType::STRING:
1786                case GPBType::SFIXED64:
1787                case GPBType::INT64:
1788                case GPBType::SINT64:
1789                case GPBType::FIXED64:
1790                case GPBType::UINT64:
1791                    $additional_quote = false;
1792                    break;
1793                default:
1794                    $additional_quote = true;
1795                }
1796                foreach ($values as $key => $value) {
1797                    if ($additional_quote) {
1798                        $size += 2;  // size for ""
1799                    }
1800                    $size += $this->fieldDataOnlyJsonByteSize($key_field, $key);
1801                    $size += $this->fieldDataOnlyJsonByteSize($value_field, $value);
1802                    $size += 1;  // size for :
1803                }
1804            }
1805        } elseif ($field->isRepeated()) {
1806            $getter = $field->getGetter();
1807            $values = $this->$getter();
1808            $count = count($values);
1809            if ($count !== 0) {
1810                if (!GPBUtil::hasSpecialJsonMapping($this)) {
1811                    $size += 3;                              // size for "\"\":".
1812                    $size += strlen($field->getJsonName());  // size for field name
1813                }
1814                $size += 2;  // size for "[]".
1815                $size += $count - 1;                     // size for commas
1816                $getter = $field->getGetter();
1817                foreach ($values as $value) {
1818                    $size += $this->fieldDataOnlyJsonByteSize($field, $value);
1819                }
1820            }
1821        } elseif ($this->existField($field) || GPBUtil::hasJsonValue($this)) {
1822            if (!GPBUtil::hasSpecialJsonMapping($this)) {
1823                $size += 3;                              // size for "\"\":".
1824                $size += strlen($field->getJsonName());  // size for field name
1825            }
1826            $getter = $field->getGetter();
1827            $value = $this->$getter();
1828            $size += $this->fieldDataOnlyJsonByteSize($field, $value);
1829        }
1830        return $size;
1831    }
1832
1833    /**
1834     * @ignore
1835     */
1836    public function byteSize()
1837    {
1838        $size = 0;
1839
1840        $fields = $this->desc->getField();
1841        foreach ($fields as $field) {
1842            $size += $this->fieldByteSize($field);
1843        }
1844        $size += strlen($this->unknown);
1845        return $size;
1846    }
1847
1848    private function appendHelper($field, $append_value)
1849    {
1850        $getter = $field->getGetter();
1851        $setter = $field->getSetter();
1852
1853        $field_arr_value = $this->$getter();
1854        $field_arr_value[] = $append_value;
1855
1856        if (!is_object($field_arr_value)) {
1857            $this->$setter($field_arr_value);
1858        }
1859    }
1860
1861    private function kvUpdateHelper($field, $update_key, $update_value)
1862    {
1863        $getter = $field->getGetter();
1864        $setter = $field->getSetter();
1865
1866        $field_arr_value = $this->$getter();
1867        $field_arr_value[$update_key] = $update_value;
1868
1869        if (!is_object($field_arr_value)) {
1870            $this->$setter($field_arr_value);
1871        }
1872    }
1873
1874    /**
1875     * @ignore
1876     */
1877    public function jsonByteSize()
1878    {
1879        $size = 0;
1880        if (is_a($this, 'Google\Protobuf\Any')) {
1881            // Size for "{}".
1882            $size += 2;
1883
1884            // Size for "\"@type\":".
1885            $size += 8;
1886
1887            // Size for url. +2 for "" /.
1888            $size += strlen($this->getTypeUrl()) + 2;
1889
1890            $value_msg = $this->unpack();
1891            if (GPBUtil::hasSpecialJsonMapping($value_msg)) {
1892                // Size for "\",value\":".
1893                $size += 9;
1894                $size += $value_msg->jsonByteSize();
1895            } else {
1896                // Size for value. +1 for comma, -2 for "{}".
1897                $size += $value_msg->jsonByteSize() -1;
1898            }
1899        } elseif (get_class($this) === 'Google\Protobuf\FieldMask') {
1900            $field_mask = GPBUtil::formatFieldMask($this);
1901            $size += strlen($field_mask) + 2;  // 2 for ""
1902        } elseif (get_class($this) === 'Google\Protobuf\Duration') {
1903            $duration = GPBUtil::formatDuration($this) . "s";
1904            $size += strlen($duration) + 2;  // 2 for ""
1905        } elseif (get_class($this) === 'Google\Protobuf\Timestamp') {
1906            $timestamp = GPBUtil::formatTimestamp($this);
1907            $timestamp = json_encode($timestamp);
1908            $size += strlen($timestamp);
1909        } elseif (get_class($this) === 'Google\Protobuf\ListValue') {
1910            $field = $this->desc->getField()[1];
1911            if ($this->existField($field)) {
1912                $field_size = $this->fieldJsonByteSize($field);
1913                $size += $field_size;
1914            } else {
1915                // Size for "[]".
1916                $size += 2;
1917            }
1918        } elseif (get_class($this) === 'Google\Protobuf\Struct') {
1919            $field = $this->desc->getField()[1];
1920            if ($this->existField($field)) {
1921                $field_size = $this->fieldJsonByteSize($field);
1922                $size += $field_size;
1923            } else {
1924                // Size for "{}".
1925                $size += 2;
1926            }
1927        } else {
1928            if (!GPBUtil::hasSpecialJsonMapping($this)) {
1929                // Size for "{}".
1930                $size += 2;
1931            }
1932
1933            $fields = $this->desc->getField();
1934            $count = 0;
1935            foreach ($fields as $field) {
1936                $field_size = $this->fieldJsonByteSize($field);
1937                $size += $field_size;
1938                if ($field_size != 0) {
1939                  $count++;
1940                }
1941            }
1942            // size for comma
1943            $size += $count > 0 ? ($count - 1) : 0;
1944        }
1945        return $size;
1946    }
1947}
1948