1<?php
2
3require_once('test_base.php');
4require_once('test_util.php');
5
6use Foo\TestEnum;
7use Foo\TestMessage;
8use Foo\TestMessage\Sub;
9use Foo\TestPackedMessage;
10use Google\Protobuf\Internal\CodedInputStream;
11use Google\Protobuf\Internal\FileDescriptorSet;
12use Google\Protobuf\Internal\GPBLabel;
13use Google\Protobuf\Internal\GPBType;
14use Google\Protobuf\Internal\GPBWire;
15use Google\Protobuf\Internal\CodedOutputStream;
16
17/**
18 * Please note, this test is only intended to be run without the protobuf C
19 * extension.
20 */
21class ImplementationTest extends TestBase
22{
23    public function setUp()
24    {
25        if (extension_loaded('protobuf')) {
26            $this->markTestSkipped();
27        }
28    }
29
30    public function testReadInt32()
31    {
32        $value = null;
33
34        // Positive number.
35        $input = new CodedInputStream(hex2bin("01"));
36        GPBWire::readInt32($input, $value);
37        $this->assertSame(1, $value);
38
39        // Negative number.
40        $input = new CodedInputStream(hex2bin("ffffffff0f"));
41        GPBWire::readInt32($input, $value);
42        $this->assertSame(-1, $value);
43
44        // Discard overflow bits.
45        $input = new CodedInputStream(hex2bin("ffffffff7f"));
46        GPBWire::readInt32($input, $value);
47        $this->assertSame(-1, $value);
48    }
49
50    public function testReadUint32()
51    {
52        $value = null;
53
54        // Positive number.
55        $input = new CodedInputStream(hex2bin("01"));
56        GPBWire::readUint32($input, $value);
57        $this->assertSame(1, $value);
58
59        // Max uint32.
60        $input = new CodedInputStream(hex2bin("ffffffff0f"));
61        GPBWire::readUint32($input, $value);
62        $this->assertSame(-1, $value);
63
64        // Discard overflow bits.
65        $input = new CodedInputStream(hex2bin("ffffffff7f"));
66        GPBWire::readUint32($input, $value);
67        $this->assertSame(-1, $value);
68    }
69
70    public function testReadInt64()
71    {
72        $value = null;
73
74        // Positive number.
75        $input = new CodedInputStream(hex2bin("01"));
76        GPBWire::readInt64($input, $value);
77        $this->assertEquals(1, $value);
78
79        // Negative number.
80        $input = new CodedInputStream(hex2bin("ffffffffffffffffff01"));
81        GPBWire::readInt64($input, $value);
82        $this->assertEquals(-1, $value);
83
84        // Discard overflow bits.
85        $input = new CodedInputStream(hex2bin("ffffffffffffffffff0f"));
86        GPBWire::readInt64($input, $value);
87        $this->assertEquals(-1, $value);
88    }
89
90    public function testReadUint64()
91    {
92        $value = null;
93
94        // Positive number.
95        $input = new CodedInputStream(hex2bin("01"));
96        GPBWire::readUint64($input, $value);
97        $this->assertEquals(1, $value);
98
99        // Negative number.
100        $input = new CodedInputStream(hex2bin("FFFFFFFFFFFFFFFFFF01"));
101        GPBWire::readUint64($input, $value);
102        $this->assertEquals(-1, $value);
103
104        // Discard overflow bits.
105        $input = new CodedInputStream(hex2bin("FFFFFFFFFFFFFFFFFF0F"));
106        GPBWire::readUint64($input, $value);
107        $this->assertEquals(-1, $value);
108    }
109
110    public function testReadSint32()
111    {
112        $value = null;
113
114        $input = new CodedInputStream(hex2bin("00"));
115        GPBWire::readSint32($input, $value);
116        $this->assertSame(0, $value);
117
118        $input = new CodedInputStream(hex2bin("01"));
119        GPBWire::readSint32($input, $value);
120        $this->assertSame(-1, $value);
121
122        $input = new CodedInputStream(hex2bin("02"));
123        GPBWire::readSint32($input, $value);
124        $this->assertSame(1, $value);
125    }
126
127    public function testReadSint64()
128    {
129        $value = null;
130
131        $input = new CodedInputStream(hex2bin("00"));
132        GPBWire::readSint64($input, $value);
133        $this->assertEquals(0, $value);
134
135        $input = new CodedInputStream(hex2bin("01"));
136        GPBWire::readSint64($input, $value);
137        $this->assertEquals(-1, $value);
138
139        $input = new CodedInputStream(hex2bin("02"));
140        GPBWire::readSint64($input, $value);
141        $this->assertEquals(1, $value);
142    }
143
144    public function testReadFixed32()
145    {
146        $value = null;
147        $input = new CodedInputStream(hex2bin("12345678"));
148        GPBWire::readFixed32($input, $value);
149        $this->assertSame(0x78563412, $value);
150    }
151
152    public function testReadFixed64()
153    {
154        $value = null;
155        $input = new CodedInputStream(hex2bin("1234567812345678"));
156        GPBWire::readFixed64($input, $value);
157        if (PHP_INT_SIZE == 4) {
158            $this->assertSame("8671175386481439762", $value);
159        } else {
160            $this->assertSame(0x7856341278563412, $value);
161        }
162    }
163
164    public function testReadSfixed32()
165    {
166        $value = null;
167        $input = new CodedInputStream(hex2bin("12345678"));
168        GPBWire::readSfixed32($input, $value);
169        $this->assertSame(0x78563412, $value);
170    }
171
172    public function testReadFloat()
173    {
174        $value = null;
175        $input = new CodedInputStream(hex2bin("0000803F"));
176        GPBWire::readFloat($input, $value);
177        $this->assertSame(1.0, $value);
178    }
179
180    public function testReadBool()
181    {
182        $value = null;
183
184        $input = new CodedInputStream(hex2bin("00"));
185        GPBWire::readBool($input, $value);
186        $this->assertSame(false, $value);
187
188        $input = new CodedInputStream(hex2bin("01"));
189        GPBWire::readBool($input, $value);
190        $this->assertSame(true, $value);
191    }
192
193    public function testReadDouble()
194    {
195        $value = null;
196        $input = new CodedInputStream(hex2bin("000000000000F03F"));
197        GPBWire::readDouble($input, $value);
198        $this->assertSame(1.0, $value);
199    }
200
201    public function testReadSfixed64()
202    {
203        $value = null;
204        $input = new CodedInputStream(hex2bin("1234567812345678"));
205        GPBWire::readSfixed64($input, $value);
206        if (PHP_INT_SIZE == 4) {
207            $this->assertSame("8671175386481439762", $value);
208        } else {
209            $this->assertSame(0x7856341278563412, $value);
210        }
211    }
212
213    public function testZigZagEncodeDecode()
214    {
215        $this->assertSame(0, GPBWire::zigZagEncode32(0));
216        $this->assertSame(1, GPBWire::zigZagEncode32(-1));
217        $this->assertSame(2, GPBWire::zigZagEncode32(1));
218        $this->assertSame(3, GPBWire::zigZagEncode32(-2));
219        $this->assertSame(0x7FFFFFFE, GPBWire::zigZagEncode32(0x3FFFFFFF));
220        $this->assertSame(0x7FFFFFFF, GPBWire::zigZagEncode32(0xC0000000));
221        $this->assertSame(0x7FFFFFFF, GPBWire::zigZagEncode32(-1073741824));
222
223        $this->assertSame(0,  GPBWire::zigZagDecode32(0));
224        $this->assertSame(-1, GPBWire::zigZagDecode32(1));
225        $this->assertSame(1,  GPBWire::zigZagDecode32(2));
226        $this->assertSame(-2, GPBWire::zigZagDecode32(3));
227        $this->assertSame(0x3FFFFFFF,  GPBWire::zigZagDecode32(0x7FFFFFFE));
228        $this->assertSame(-1073741824, GPBWire::zigZagDecode32(0x7FFFFFFF));
229        $this->assertSame(0x7FFFFFFF,  GPBWire::zigZagDecode32(0xFFFFFFFE));
230        $this->assertSame((int)-2147483648,GPBWire::zigZagDecode32(0xFFFFFFFF));
231
232        if (PHP_INT_SIZE == 4) {
233            $this->assertSame(-2, GPBWire::zigZagEncode32(0x7FFFFFFF));
234            $this->assertSame(-1, GPBWire::zigZagEncode32(0x80000000));
235            $this->assertSame('0', GPBWire::zigZagEncode64(0));
236            $this->assertSame('1', GPBWire::zigZagEncode64(-1));
237            $this->assertSame('2', GPBWire::zigZagEncode64(1));
238            $this->assertSame('3', GPBWire::zigZagEncode64(-2));
239            $this->assertSame(
240                '2147483646',  // 0x7FFFFFE
241                GPBWire::zigZagEncode64(0x3FFFFFFF));
242            $this->assertSame(
243                '2147483647',  // 0x7FFFFFF
244                GPBWire::zigZagEncode64(-1073741824));  // 0xFFFFFFFFC0000000
245            $this->assertSame(
246                '4294967294',                           // 0xFFFFFFFE
247                GPBWire::zigZagEncode64(2147483647));   // 0x7FFFFFFF
248            $this->assertSame(
249                '4294967295',                           // 0xFFFFFFFF
250                GPBWire::zigZagEncode64(-2147483648));  // 0xFFFFFFFF80000000
251            $this->assertSame(
252                '18446744073709551614',  // 0xFFFFFFFFFFFFFFFE
253                                         // 0x7FFFFFFFFFFFFFFF
254                GPBWire::zigZagEncode64("9223372036854775807"));
255            $this->assertSame(
256                '18446744073709551615',  // 0xFFFFFFFFFFFFFFFF
257                                         // 0x8000000000000000
258                GPBWire::zigZagEncode64("-9223372036854775808"));
259
260            $this->assertSame('0', GPBWire::zigZagDecode64(0));
261            $this->assertSame('-1', GPBWire::zigZagDecode64(1));
262            $this->assertSame('1', GPBWire::zigZagDecode64(2));
263            $this->assertSame('-2', GPBWire::zigZagDecode64(3));
264        } else {
265            $this->assertSame(4294967294, GPBWire::zigZagEncode32(0x7FFFFFFF));
266            $this->assertSame(4294967295, GPBWire::zigZagEncode32(0x80000000));
267            $this->assertSame(0, GPBWire::zigZagEncode64(0));
268            $this->assertSame(1, GPBWire::zigZagEncode64(-1));
269            $this->assertSame(2, GPBWire::zigZagEncode64(1));
270            $this->assertSame(3, GPBWire::zigZagEncode64(-2));
271            $this->assertSame(0x7FFFFFFE, GPBWire::zigZagEncode64(0x3FFFFFFF));
272            $this->assertSame(
273                0x7FFFFFFF,
274                GPBWire::zigZagEncode64(0xFFFFFFFFC0000000));
275            $this->assertSame(
276                0xFFFFFFFE,
277                GPBWire::zigZagEncode64(0x7FFFFFFF));
278            $this->assertSame(
279                0xFFFFFFFF,
280                GPBWire::zigZagEncode64(0xFFFFFFFF80000000));
281            $this->assertSame(
282                -2,  // 0xFFFFFFFFFFFFFFFE
283                GPBWire::zigZagEncode64(0x7FFFFFFFFFFFFFFF));
284            $this->assertSame(
285                -1,  // 0xFFFFFFFFFFFFFFFF
286                GPBWire::zigZagEncode64(0x8000000000000000));
287
288            $this->assertSame(0, GPBWire::zigZagDecode64(0));
289            $this->assertSame(-1, GPBWire::zigZagDecode64(1));
290            $this->assertSame(1, GPBWire::zigZagDecode64(2));
291            $this->assertSame(-2, GPBWire::zigZagDecode64(3));
292        }
293
294        // Round trip
295        $this->assertSame(0, GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(0)));
296        $this->assertSame(1, GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(1)));
297        $this->assertSame(-1, GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(-1)));
298        $this->assertSame(14927,
299                      GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(14927)));
300        $this->assertSame(-3612,
301                      GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(-3612)));
302    }
303
304    public function testDecode()
305    {
306        $m = new TestMessage();
307        $m->mergeFromString(TestUtil::getGoldenTestMessage());
308        TestUtil::assertTestMessage($m);
309    }
310
311    public function testDescriptorDecode()
312    {
313        $file_desc_set = new FileDescriptorSet();
314        $file_desc_set->mergeFromString(hex2bin(
315            "0a3b0a12746573745f696e636c7564652e70726f746f120362617222180a" .
316            "0b54657374496e636c75646512090a0161180120012805620670726f746f33"));
317
318        $this->assertSame(1, sizeof($file_desc_set->getFile()));
319
320        $file_desc = $file_desc_set->getFile()[0];
321        $this->assertSame("test_include.proto", $file_desc->getName());
322        $this->assertSame("bar", $file_desc->getPackage());
323        $this->assertSame(0, sizeof($file_desc->getDependency()));
324        $this->assertSame(1, sizeof($file_desc->getMessageType()));
325        $this->assertSame(0, sizeof($file_desc->getEnumType()));
326        $this->assertSame("proto3", $file_desc->getSyntax());
327
328        $desc = $file_desc->getMessageType()[0];
329        $this->assertSame("TestInclude", $desc->getName());
330        $this->assertSame(1, sizeof($desc->getField()));
331        $this->assertSame(0, sizeof($desc->getNestedType()));
332        $this->assertSame(0, sizeof($desc->getEnumType()));
333        $this->assertSame(0, sizeof($desc->getOneofDecl()));
334
335        $field = $desc->getField()[0];
336        $this->assertSame("a", $field->getName());
337        $this->assertSame(1, $field->getNumber());
338        $this->assertSame(GPBLabel::OPTIONAL, $field->getLabel());
339        $this->assertSame(GPBType::INT32, $field->getType());
340    }
341
342    public function testReadVarint64()
343    {
344        $var = 0;
345
346        // Empty buffer.
347        $input = new CodedInputStream(hex2bin(''));
348        $this->assertFalse($input->readVarint64($var));
349
350        // The largest varint is 10 bytes long.
351        $input = new CodedInputStream(hex2bin('8080808080808080808001'));
352        $this->assertFalse($input->readVarint64($var));
353
354        // Corrupted varint.
355        $input = new CodedInputStream(hex2bin('808080'));
356        $this->assertFalse($input->readVarint64($var));
357
358        // Normal case.
359        $input = new CodedInputStream(hex2bin('808001'));
360        $this->assertTrue($input->readVarint64($var));
361        if (PHP_INT_SIZE == 4) {
362            $this->assertSame('16384', $var);
363        } else {
364            $this->assertSame(16384, $var);
365        }
366        $this->assertFalse($input->readVarint64($var));
367
368        // Read two varint.
369        $input = new CodedInputStream(hex2bin('808001808002'));
370        $this->assertTrue($input->readVarint64($var));
371        if (PHP_INT_SIZE == 4) {
372            $this->assertSame('16384', $var);
373        } else {
374            $this->assertSame(16384, $var);
375        }
376        $this->assertTrue($input->readVarint64($var));
377        if (PHP_INT_SIZE == 4) {
378            $this->assertSame('32768', $var);
379        } else {
380            $this->assertSame(32768, $var);
381        }
382        $this->assertFalse($input->readVarint64($var));
383
384        // Read 64 testing
385        $testVals = array(
386            '10'                 => '0a000000000000000000',
387            '100'                => '64000000000000000000',
388            '800'                => 'a0060000000000000000',
389            '6400'               => '80320000000000000000',
390            '70400'              => '80a60400000000000000',
391            '774400'             => '80a22f00000000000000',
392            '9292800'            => '8098b704000000000000',
393            '74342400'           => '80c0b923000000000000',
394            '743424000'          => '8080bfe2020000000000',
395            '8177664000'         => '8080b5bb1e0000000000',
396            '65421312000'        => '8080a8dbf30100000000',
397            '785055744000'       => '8080e0c7ec1600000000',
398            '9420668928000'      => '808080dd969202000000',
399            '103627358208000'    => '808080fff9c717000000',
400            '1139900940288000'   => '808080f5bd9783020000',
401            '13678811283456000'  => '808080fce699a6180000',
402            '109430490267648000' => '808080e0b7ceb1c20100',
403            '984874412408832000' => '808080e0f5c1bed50d00',
404        );
405
406        foreach ($testVals as $original => $encoded) {
407            $input = new CodedInputStream(hex2bin($encoded));
408            $this->assertTrue($input->readVarint64($var));
409            $this->assertEquals($original, $var);
410        }
411    }
412
413    public function testReadVarint32()
414    {
415        $var = 0;
416
417        // Empty buffer.
418        $input = new CodedInputStream(hex2bin(''));
419        $this->assertFalse($input->readVarint32($var));
420
421        // The largest varint is 10 bytes long.
422        $input = new CodedInputStream(hex2bin('8080808080808080808001'));
423        $this->assertFalse($input->readVarint32($var));
424
425        // Corrupted varint.
426        $input = new CodedInputStream(hex2bin('808080'));
427        $this->assertFalse($input->readVarint32($var));
428
429        // Normal case.
430        $input = new CodedInputStream(hex2bin('808001'));
431        $this->assertTrue($input->readVarint32($var));
432        $this->assertSame(16384, $var);
433        $this->assertFalse($input->readVarint32($var));
434
435        // Read two varint.
436        $input = new CodedInputStream(hex2bin('808001808002'));
437        $this->assertTrue($input->readVarint32($var));
438        $this->assertSame(16384, $var);
439        $this->assertTrue($input->readVarint32($var));
440        $this->assertSame(32768, $var);
441        $this->assertFalse($input->readVarint32($var));
442
443        // Read a 64-bit integer. High-order bits should be discarded.
444        $input = new CodedInputStream(hex2bin('808081808001'));
445        $this->assertTrue($input->readVarint32($var));
446        $this->assertSame(16384, $var);
447        $this->assertFalse($input->readVarint32($var));
448    }
449
450    public function testReadTag()
451    {
452        $input = new CodedInputStream(hex2bin('808001'));
453        $tag = $input->readTag();
454        $this->assertSame(16384, $tag);
455        $tag = $input->readTag();
456        $this->assertSame(0, $tag);
457    }
458
459    public function testPushPopLimit()
460    {
461        $input = new CodedInputStream(hex2bin('808001'));
462        $old_limit = $input->pushLimit(0);
463        $tag = $input->readTag();
464        $this->assertSame(0, $tag);
465        $input->popLimit($old_limit);
466        $tag = $input->readTag();
467        $this->assertSame(16384, $tag);
468    }
469
470    public function testReadRaw()
471    {
472        $input = new CodedInputStream(hex2bin('808001'));
473        $buffer = null;
474
475        $this->assertTrue($input->readRaw(3, $buffer));
476        $this->assertSame(hex2bin('808001'), $buffer);
477
478        $this->assertFalse($input->readRaw(1, $buffer));
479    }
480
481    public function testWriteVarint32()
482    {
483        $output = new CodedOutputStream(3);
484        $output->writeVarint32(16384, true);
485        $this->assertSame(hex2bin('808001'), $output->getData());
486
487        // Negative numbers are padded to be compatible with int64.
488        $output = new CodedOutputStream(10);
489        $output->writeVarint32(-43, false);
490        $this->assertSame(hex2bin('D5FFFFFFFFFFFFFFFF01'), $output->getData());
491    }
492
493    public function testWriteVarint64()
494    {
495        $output = new CodedOutputStream(10);
496        $output->writeVarint64(-43);
497        $this->assertSame(hex2bin('D5FFFFFFFFFFFFFFFF01'), $output->getData());
498    }
499
500    public function testWriteLittleEndian32()
501    {
502        $output = new CodedOutputStream(4);
503        $output->writeLittleEndian32(46);
504        $this->assertSame(hex2bin('2E000000'), $output->getData());
505    }
506
507    public function testWriteLittleEndian64()
508    {
509        $output = new CodedOutputStream(8);
510        $output->writeLittleEndian64(47);
511        $this->assertSame(hex2bin('2F00000000000000'), $output->getData());
512    }
513
514    public function testByteSize()
515    {
516        $m = new TestMessage();
517        TestUtil::setTestMessage($m);
518        $this->assertSame(518, $m->byteSize());
519    }
520
521    public function testPackedByteSize()
522    {
523        $m = new TestPackedMessage();
524        TestUtil::setTestPackedMessage($m);
525        $this->assertSame(166, $m->byteSize());
526    }
527
528    /**
529     * @expectedException UnexpectedValueException
530     * @expectedExceptionMessage Invalid message property: optionalInt32
531     */
532    public function testArrayConstructorJsonCaseThrowsException()
533    {
534        $m = new TestMessage([
535            'optionalInt32' => -42,
536        ]);
537    }
538
539    /**
540     * @expectedException Exception
541     * @expectedExceptionMessage Expect Foo\TestMessage_Sub.
542     */
543    public function testArraysForMessagesThrowsException()
544    {
545        $m = new TestMessage([
546            'optional_message' => [
547                'a' => 33
548            ]
549        ]);
550    }
551
552    public function testArrayConstructorWithNullValues()
553    {
554        $requestData = [
555            'optional_bool' => null,
556            'optional_string' => null,
557            'optional_bytes' => null,
558            'optional_message' => null,
559        ];
560
561        $m = new TestMessage($requestData);
562
563        $this->assertSame(false, $m->getOptionalBool());
564        $this->assertSame('', $m->getOptionalString());
565        $this->assertSame('', $m->getOptionalBytes());
566        $this->assertSame(null, $m->getOptionalMessage());
567    }
568
569    /**
570     * @dataProvider provideArrayConstructorWithNullValuesThrowsException
571     * @expectedException Exception
572     */
573    public function testArrayConstructorWithNullValuesThrowsException($requestData)
574    {
575        $m = new TestMessage($requestData);
576    }
577
578    public function provideArrayConstructorWithNullValuesThrowsException()
579    {
580        return [
581            [['optional_int32' => null]],
582            [['optional_int64' => null]],
583            [['optional_uint32' => null]],
584            [['optional_uint64' => null]],
585            [['optional_sint32' => null]],
586            [['optional_sint64' => null]],
587            [['optional_fixed32' => null]],
588            [['optional_fixed64' => null]],
589            [['optional_sfixed32' => null]],
590            [['optional_sfixed64' => null]],
591            [['optional_float' => null]],
592            [['optional_double' => null]],
593            [['optional_enum' => null]],
594            [['repeated_int32' => null]],
595            [['map_int32_int32' => null]],
596        ];
597    }
598}
599