Home | History | Annotate | Download | only in protobuf
      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 
     31 package com.google.protobuf;
     32 
     33 import protobuf_unittest.UnittestProto.TestAllTypes;
     34 import protobuf_unittest.UnittestProto.TestRecursiveMessage;
     35 
     36 import junit.framework.TestCase;
     37 
     38 import java.io.ByteArrayInputStream;
     39 import java.io.FilterInputStream;
     40 import java.io.InputStream;
     41 import java.io.IOException;
     42 
     43 /**
     44  * Unit test for {@link CodedInputStream}.
     45  *
     46  * @author kenton (at) google.com Kenton Varda
     47  */
     48 public 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