1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc.  All rights reserved.
3// http://code.google.com/p/protobuf/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9//     * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11//     * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15//     * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31package com.google.protobuf;
32
33import protobuf_unittest.UnittestProto.TestAllTypes;
34import protobuf_unittest.UnittestProto.TestRecursiveMessage;
35
36import junit.framework.TestCase;
37
38import java.io.ByteArrayInputStream;
39import java.io.FilterInputStream;
40import java.io.InputStream;
41import java.io.IOException;
42
43/**
44 * Unit test for {@link CodedInputStream}.
45 *
46 * @author kenton@google.com Kenton Varda
47 */
48public class CodedInputStreamTest extends TestCase {
49  /**
50   * Helper to construct a byte array from a bunch of bytes.  The inputs are
51   * actually ints so that I can use hex notation and not get stupid errors
52   * about precision.
53   */
54  private byte[] bytes(int... bytesAsInts) {
55    byte[] bytes = new byte[bytesAsInts.length];
56    for (int i = 0; i < bytesAsInts.length; i++) {
57      bytes[i] = (byte) bytesAsInts[i];
58    }
59    return bytes;
60  }
61
62  /**
63   * An InputStream which limits the number of bytes it reads at a time.
64   * We use this to make sure that CodedInputStream doesn't screw up when
65   * reading in small blocks.
66   */
67  private static final class SmallBlockInputStream extends FilterInputStream {
68    private final int blockSize;
69
70    public SmallBlockInputStream(byte[] data, int blockSize) {
71      this(new ByteArrayInputStream(data), blockSize);
72    }
73
74    public SmallBlockInputStream(InputStream in, int blockSize) {
75      super(in);
76      this.blockSize = blockSize;
77    }
78
79    public int read(byte[] b) throws IOException {
80      return super.read(b, 0, Math.min(b.length, blockSize));
81    }
82
83    public int read(byte[] b, int off, int len) throws IOException {
84      return super.read(b, off, Math.min(len, blockSize));
85    }
86  }
87
88  /**
89   * Parses the given bytes using readRawVarint32() and readRawVarint64() and
90   * checks that the result matches the given value.
91   */
92  private void assertReadVarint(byte[] data, long value) throws Exception {
93    CodedInputStream input = CodedInputStream.newInstance(data);
94    assertEquals((int)value, input.readRawVarint32());
95
96    input = CodedInputStream.newInstance(data);
97    assertEquals(value, input.readRawVarint64());
98    assertTrue(input.isAtEnd());
99
100    // Try different block sizes.
101    for (int blockSize = 1; blockSize <= 16; blockSize *= 2) {
102      input = CodedInputStream.newInstance(
103        new SmallBlockInputStream(data, blockSize));
104      assertEquals((int)value, input.readRawVarint32());
105
106      input = CodedInputStream.newInstance(
107        new SmallBlockInputStream(data, blockSize));
108      assertEquals(value, input.readRawVarint64());
109      assertTrue(input.isAtEnd());
110    }
111
112    // Try reading direct from an InputStream.  We want to verify that it
113    // doesn't read past the end of the input, so we copy to a new, bigger
114    // array first.
115    byte[] longerData = new byte[data.length + 1];
116    System.arraycopy(data, 0, longerData, 0, data.length);
117    InputStream rawInput = new ByteArrayInputStream(longerData);
118    assertEquals((int)value, CodedInputStream.readRawVarint32(rawInput));
119    assertEquals(1, rawInput.available());
120  }
121
122  /**
123   * Parses the given bytes using readRawVarint32() and readRawVarint64() and
124   * expects them to fail with an InvalidProtocolBufferException whose
125   * description matches the given one.
126   */
127  private void assertReadVarintFailure(
128      InvalidProtocolBufferException expected, byte[] data)
129      throws Exception {
130    CodedInputStream input = CodedInputStream.newInstance(data);
131    try {
132      input.readRawVarint32();
133      fail("Should have thrown an exception.");
134    } catch (InvalidProtocolBufferException e) {
135      assertEquals(expected.getMessage(), e.getMessage());
136    }
137
138    input = CodedInputStream.newInstance(data);
139    try {
140      input.readRawVarint64();
141      fail("Should have thrown an exception.");
142    } catch (InvalidProtocolBufferException e) {
143      assertEquals(expected.getMessage(), e.getMessage());
144    }
145
146    // Make sure we get the same error when reading direct from an InputStream.
147    try {
148      CodedInputStream.readRawVarint32(new ByteArrayInputStream(data));
149      fail("Should have thrown an exception.");
150    } catch (InvalidProtocolBufferException e) {
151      assertEquals(expected.getMessage(), e.getMessage());
152    }
153  }
154
155  /** Tests readRawVarint32() and readRawVarint64(). */
156  public void testReadVarint() throws Exception {
157    assertReadVarint(bytes(0x00), 0);
158    assertReadVarint(bytes(0x01), 1);
159    assertReadVarint(bytes(0x7f), 127);
160    // 14882
161    assertReadVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7));
162    // 2961488830
163    assertReadVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b),
164      (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
165      (0x0bL << 28));
166
167    // 64-bit
168    // 7256456126
169    assertReadVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b),
170      (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
171      (0x1bL << 28));
172    // 41256202580718336
173    assertReadVarint(
174      bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49),
175      (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) |
176      (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49));
177    // 11964378330978735131
178    assertReadVarint(
179      bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01),
180      (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) |
181      (0x3bL << 28) | (0x56L << 35) | (0x00L << 42) |
182      (0x05L << 49) | (0x26L << 56) | (0x01L << 63));
183
184    // Failures
185    assertReadVarintFailure(
186      InvalidProtocolBufferException.malformedVarint(),
187      bytes(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
188            0x00));
189    assertReadVarintFailure(
190      InvalidProtocolBufferException.truncatedMessage(),
191      bytes(0x80));
192  }
193
194  /**
195   * Parses the given bytes using readRawLittleEndian32() and checks
196   * that the result matches the given value.
197   */
198  private void assertReadLittleEndian32(byte[] data, int value)
199                                        throws Exception {
200    CodedInputStream input = CodedInputStream.newInstance(data);
201    assertEquals(value, input.readRawLittleEndian32());
202    assertTrue(input.isAtEnd());
203
204    // Try different block sizes.
205    for (int blockSize = 1; blockSize <= 16; blockSize *= 2) {
206      input = CodedInputStream.newInstance(
207        new SmallBlockInputStream(data, blockSize));
208      assertEquals(value, input.readRawLittleEndian32());
209      assertTrue(input.isAtEnd());
210    }
211  }
212
213  /**
214   * Parses the given bytes using readRawLittleEndian64() and checks
215   * that the result matches the given value.
216   */
217  private void assertReadLittleEndian64(byte[] data, long value)
218                                        throws Exception {
219    CodedInputStream input = CodedInputStream.newInstance(data);
220    assertEquals(value, input.readRawLittleEndian64());
221    assertTrue(input.isAtEnd());
222
223    // Try different block sizes.
224    for (int blockSize = 1; blockSize <= 16; blockSize *= 2) {
225      input = CodedInputStream.newInstance(
226        new SmallBlockInputStream(data, blockSize));
227      assertEquals(value, input.readRawLittleEndian64());
228      assertTrue(input.isAtEnd());
229    }
230  }
231
232  /** Tests readRawLittleEndian32() and readRawLittleEndian64(). */
233  public void testReadLittleEndian() throws Exception {
234    assertReadLittleEndian32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678);
235    assertReadLittleEndian32(bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0);
236
237    assertReadLittleEndian64(
238      bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12),
239      0x123456789abcdef0L);
240    assertReadLittleEndian64(
241      bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a),
242      0x9abcdef012345678L);
243  }
244
245  /** Test decodeZigZag32() and decodeZigZag64(). */
246  public void testDecodeZigZag() throws Exception {
247    assertEquals( 0, CodedInputStream.decodeZigZag32(0));
248    assertEquals(-1, CodedInputStream.decodeZigZag32(1));
249    assertEquals( 1, CodedInputStream.decodeZigZag32(2));
250    assertEquals(-2, CodedInputStream.decodeZigZag32(3));
251    assertEquals(0x3FFFFFFF, CodedInputStream.decodeZigZag32(0x7FFFFFFE));
252    assertEquals(0xC0000000, CodedInputStream.decodeZigZag32(0x7FFFFFFF));
253    assertEquals(0x7FFFFFFF, CodedInputStream.decodeZigZag32(0xFFFFFFFE));
254    assertEquals(0x80000000, CodedInputStream.decodeZigZag32(0xFFFFFFFF));
255
256    assertEquals( 0, CodedInputStream.decodeZigZag64(0));
257    assertEquals(-1, CodedInputStream.decodeZigZag64(1));
258    assertEquals( 1, CodedInputStream.decodeZigZag64(2));
259    assertEquals(-2, CodedInputStream.decodeZigZag64(3));
260    assertEquals(0x000000003FFFFFFFL,
261                 CodedInputStream.decodeZigZag64(0x000000007FFFFFFEL));
262    assertEquals(0xFFFFFFFFC0000000L,
263                 CodedInputStream.decodeZigZag64(0x000000007FFFFFFFL));
264    assertEquals(0x000000007FFFFFFFL,
265                 CodedInputStream.decodeZigZag64(0x00000000FFFFFFFEL));
266    assertEquals(0xFFFFFFFF80000000L,
267                 CodedInputStream.decodeZigZag64(0x00000000FFFFFFFFL));
268    assertEquals(0x7FFFFFFFFFFFFFFFL,
269                 CodedInputStream.decodeZigZag64(0xFFFFFFFFFFFFFFFEL));
270    assertEquals(0x8000000000000000L,
271                 CodedInputStream.decodeZigZag64(0xFFFFFFFFFFFFFFFFL));
272  }
273
274  /** Tests reading and parsing a whole message with every field type. */
275  public void testReadWholeMessage() throws Exception {
276    TestAllTypes message = TestUtil.getAllSet();
277
278    byte[] rawBytes = message.toByteArray();
279    assertEquals(rawBytes.length, message.getSerializedSize());
280
281    TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes);
282    TestUtil.assertAllFieldsSet(message2);
283
284    // Try different block sizes.
285    for (int blockSize = 1; blockSize < 256; blockSize *= 2) {
286      message2 = TestAllTypes.parseFrom(
287        new SmallBlockInputStream(rawBytes, blockSize));
288      TestUtil.assertAllFieldsSet(message2);
289    }
290  }
291
292  /** Tests skipField(). */
293  public void testSkipWholeMessage() throws Exception {
294    TestAllTypes message = TestUtil.getAllSet();
295    byte[] rawBytes = message.toByteArray();
296
297    // Create two parallel inputs.  Parse one as unknown fields while using
298    // skipField() to skip each field on the other.  Expect the same tags.
299    CodedInputStream input1 = CodedInputStream.newInstance(rawBytes);
300    CodedInputStream input2 = CodedInputStream.newInstance(rawBytes);
301    UnknownFieldSet.Builder unknownFields = UnknownFieldSet.newBuilder();
302
303    while (true) {
304      int tag = input1.readTag();
305      assertEquals(tag, input2.readTag());
306      if (tag == 0) {
307        break;
308      }
309      unknownFields.mergeFieldFrom(tag, input1);
310      input2.skipField(tag);
311    }
312  }
313
314  /**
315   * Test that a bug in skipRawBytes() has been fixed:  if the skip skips
316   * exactly up to a limit, this should not break things.
317   */
318  public void testSkipRawBytesBug() throws Exception {
319    byte[] rawBytes = new byte[] { 1, 2 };
320    CodedInputStream input = CodedInputStream.newInstance(rawBytes);
321
322    int limit = input.pushLimit(1);
323    input.skipRawBytes(1);
324    input.popLimit(limit);
325    assertEquals(2, input.readRawByte());
326  }
327
328  /**
329   * Test that a bug in skipRawBytes() has been fixed:  if the skip skips
330   * past the end of a buffer with a limit that has been set past the end of
331   * that buffer, this should not break things.
332   */
333  public void testSkipRawBytesPastEndOfBufferWithLimit() throws Exception {
334    System.out.printf("testSkipRawBytesPastEndOfBufferWithLimit: E ...\n");
335
336    byte[] rawBytes = new byte[] { 1, 2, 3, 4, 5 };
337    CodedInputStream input = CodedInputStream.newInstance(
338        new SmallBlockInputStream(rawBytes, 3));
339
340    int limit = input.pushLimit(4);
341    System.out.printf("testSkipRawBytesPastEndOfBufferWithLimit: limit=%d\n", limit);
342    // In order to expose the bug we need to read at least one byte to prime the
343    // buffer inside the CodedInputStream.
344    assertEquals(1, input.readRawByte());
345    // Skip to the end of the limit.
346    input.skipRawBytes(3);
347    assertTrue(input.isAtEnd());
348    input.popLimit(limit);
349    assertEquals(5, input.readRawByte());
350
351    System.out.printf("testSkipRawBytesPastEndOfBufferWithLimit: X ...\n");
352  }
353
354  public void testReadHugeBlob() throws Exception {
355    // Allocate and initialize a 1MB blob.
356    byte[] blob = new byte[1 << 20];
357    for (int i = 0; i < blob.length; i++) {
358      blob[i] = (byte)i;
359    }
360
361    // Make a message containing it.
362    TestAllTypes.Builder builder = TestAllTypes.newBuilder();
363    TestUtil.setAllFields(builder);
364    builder.setOptionalBytes(ByteString.copyFrom(blob));
365    TestAllTypes message = builder.build();
366
367    // Serialize and parse it.  Make sure to parse from an InputStream, not
368    // directly from a ByteString, so that CodedInputStream uses buffered
369    // reading.
370    TestAllTypes message2 =
371      TestAllTypes.parseFrom(message.toByteString().newInput());
372
373    assertEquals(message.getOptionalBytes(), message2.getOptionalBytes());
374
375    // Make sure all the other fields were parsed correctly.
376    TestAllTypes message3 = TestAllTypes.newBuilder(message2)
377      .setOptionalBytes(TestUtil.getAllSet().getOptionalBytes())
378      .build();
379    TestUtil.assertAllFieldsSet(message3);
380  }
381
382  public void testReadMaliciouslyLargeBlob() throws Exception {
383    ByteString.Output rawOutput = ByteString.newOutput();
384    CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
385
386    int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
387    output.writeRawVarint32(tag);
388    output.writeRawVarint32(0x7FFFFFFF);
389    output.writeRawBytes(new byte[32]);  // Pad with a few random bytes.
390    output.flush();
391
392    CodedInputStream input = rawOutput.toByteString().newCodedInput();
393    assertEquals(tag, input.readTag());
394
395    try {
396      input.readBytes();
397      fail("Should have thrown an exception!");
398    } catch (InvalidProtocolBufferException e) {
399      // success.
400    }
401  }
402
403  private TestRecursiveMessage makeRecursiveMessage(int depth) {
404    if (depth == 0) {
405      return TestRecursiveMessage.newBuilder().setI(5).build();
406    } else {
407      return TestRecursiveMessage.newBuilder()
408        .setA(makeRecursiveMessage(depth - 1)).build();
409    }
410  }
411
412  private void assertMessageDepth(TestRecursiveMessage message, int depth) {
413    if (depth == 0) {
414      assertFalse(message.hasA());
415      assertEquals(5, message.getI());
416    } else {
417      assertTrue(message.hasA());
418      assertMessageDepth(message.getA(), depth - 1);
419    }
420  }
421
422  public void testMaliciousRecursion() throws Exception {
423    ByteString data64 = makeRecursiveMessage(64).toByteString();
424    ByteString data65 = makeRecursiveMessage(65).toByteString();
425
426    assertMessageDepth(TestRecursiveMessage.parseFrom(data64), 64);
427
428    try {
429      TestRecursiveMessage.parseFrom(data65);
430      fail("Should have thrown an exception!");
431    } catch (InvalidProtocolBufferException e) {
432      // success.
433    }
434
435    CodedInputStream input = data64.newCodedInput();
436    input.setRecursionLimit(8);
437    try {
438      TestRecursiveMessage.parseFrom(input);
439      fail("Should have thrown an exception!");
440    } catch (InvalidProtocolBufferException e) {
441      // success.
442    }
443  }
444
445  public void testSizeLimit() throws Exception {
446    CodedInputStream input = CodedInputStream.newInstance(
447      TestUtil.getAllSet().toByteString().newInput());
448    input.setSizeLimit(16);
449
450    try {
451      TestAllTypes.parseFrom(input);
452      fail("Should have thrown an exception!");
453    } catch (InvalidProtocolBufferException e) {
454      // success.
455    }
456  }
457
458  public void testResetSizeCounter() throws Exception {
459    CodedInputStream input = CodedInputStream.newInstance(
460        new SmallBlockInputStream(new byte[256], 8));
461    input.setSizeLimit(16);
462    input.readRawBytes(16);
463    assertEquals(16, input.getTotalBytesRead());
464
465    try {
466      input.readRawByte();
467      fail("Should have thrown an exception!");
468    } catch (InvalidProtocolBufferException e) {
469      // success.
470    }
471
472    input.resetSizeCounter();
473    assertEquals(0, input.getTotalBytesRead());
474    input.readRawByte();  // No exception thrown.
475    input.resetSizeCounter();
476    assertEquals(0, input.getTotalBytesRead());
477
478    try {
479      input.readRawBytes(16);  // Hits limit again.
480      fail("Should have thrown an exception!");
481    } catch (InvalidProtocolBufferException e) {
482      // success.
483    }
484  }
485
486  /**
487   * Tests that if we read an string that contains invalid UTF-8, no exception
488   * is thrown.  Instead, the invalid bytes are replaced with the Unicode
489   * "replacement character" U+FFFD.
490   */
491  public void testReadInvalidUtf8() throws Exception {
492    ByteString.Output rawOutput = ByteString.newOutput();
493    CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
494
495    int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
496    output.writeRawVarint32(tag);
497    output.writeRawVarint32(1);
498    output.writeRawBytes(new byte[] { (byte)0x80 });
499    output.flush();
500
501    CodedInputStream input = rawOutput.toByteString().newCodedInput();
502    assertEquals(tag, input.readTag());
503    String text = input.readString();
504    assertEquals(0xfffd, text.charAt(0));
505  }
506
507  public void testReadFromSlice() throws Exception {
508    byte[] bytes = bytes(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
509    CodedInputStream in = CodedInputStream.newInstance(bytes, 3, 5);
510    assertEquals(0, in.getTotalBytesRead());
511    for (int i = 3; i < 8; i++) {
512      assertEquals(i, in.readRawByte());
513      assertEquals(i-2, in.getTotalBytesRead());
514    }
515    // eof
516    assertEquals(0, in.readTag());
517    assertEquals(5, in.getTotalBytesRead());
518  }
519
520  public void testInvalidTag() throws Exception {
521    // Any tag number which corresponds to field number zero is invalid and
522    // should throw InvalidProtocolBufferException.
523    for (int i = 0; i < 8; i++) {
524      try {
525        CodedInputStream.newInstance(bytes(i)).readTag();
526        fail("Should have thrown an exception.");
527      } catch (InvalidProtocolBufferException e) {
528        assertEquals(InvalidProtocolBufferException.invalidTag().getMessage(),
529                     e.getMessage());
530      }
531    }
532  }
533}
534