1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // http://code.google.com/p/protobuf/
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
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
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.
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.
31 package com.google.protobuf;
33 import protobuf_unittest.UnittestProto.TestAllTypes;
34 import protobuf_unittest.UnittestProto.TestRecursiveMessage;
36 import junit.framework.TestCase;
38 import java.io.ByteArrayInputStream;
39 import java.io.FilterInputStream;
40 import java.io.InputStream;
41 import java.io.IOException;
44 * Unit test for {@link CodedInputStream}.
46 * @author kenton@google.com Kenton Varda
48 public class CodedInputStreamTest extends TestCase {
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
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];
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.
67 private static final class SmallBlockInputStream extends FilterInputStream {
68 private final int blockSize;
70 public SmallBlockInputStream(byte[] data, int blockSize) {
71 this(new ByteArrayInputStream(data), blockSize);
74 public SmallBlockInputStream(InputStream in, int blockSize) {
76 this.blockSize = blockSize;
79 public int read(byte[] b) throws IOException {
80 return super.read(b, 0, Math.min(b.length, blockSize));
83 public int read(byte[] b, int off, int len) throws IOException {
84 return super.read(b, off, Math.min(len, blockSize));
89 * Parses the given bytes using readRawVarint32() and readRawVarint64() and
90 * checks that the result matches the given value.
92 private void assertReadVarint(byte[] data, long value) throws Exception {
93 CodedInputStream input = CodedInputStream.newInstance(data);
94 assertEquals((int)value, input.readRawVarint32());
96 input = CodedInputStream.newInstance(data);
97 assertEquals(value, input.readRawVarint64());
98 assertTrue(input.isAtEnd());
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());
106 input = CodedInputStream.newInstance(
107 new SmallBlockInputStream(data, blockSize));
108 assertEquals(value, input.readRawVarint64());
109 assertTrue(input.isAtEnd());
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
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());
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.
127 private void assertReadVarintFailure(
128 InvalidProtocolBufferException expected, byte[] data)
130 CodedInputStream input = CodedInputStream.newInstance(data);
132 input.readRawVarint32();
133 fail("Should have thrown an exception.");
134 } catch (InvalidProtocolBufferException e) {
135 assertEquals(expected.getMessage(), e.getMessage());
138 input = CodedInputStream.newInstance(data);
140 input.readRawVarint64();
141 fail("Should have thrown an exception.");
142 } catch (InvalidProtocolBufferException e) {
143 assertEquals(expected.getMessage(), e.getMessage());
146 // Make sure we get the same error when reading direct from an InputStream.
148 CodedInputStream.readRawVarint32(new ByteArrayInputStream(data));
149 fail("Should have thrown an exception.");
150 } catch (InvalidProtocolBufferException e) {
151 assertEquals(expected.getMessage(), e.getMessage());
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);
161 assertReadVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7));
163 assertReadVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b),
164 (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
169 assertReadVarint(bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b),
170 (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
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
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));
185 assertReadVarintFailure(
186 InvalidProtocolBufferException.malformedVarint(),
187 bytes(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
189 assertReadVarintFailure(
190 InvalidProtocolBufferException.truncatedMessage(),
195 * Parses the given bytes using readRawLittleEndian32() and checks
196 * that the result matches the given value.
198 private void assertReadLittleEndian32(byte[] data, int value)
200 CodedInputStream input = CodedInputStream.newInstance(data);
201 assertEquals(value, input.readRawLittleEndian32());
202 assertTrue(input.isAtEnd());
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());
214 * Parses the given bytes using readRawLittleEndian64() and checks
215 * that the result matches the given value.
217 private void assertReadLittleEndian64(byte[] data, long value)
219 CodedInputStream input = CodedInputStream.newInstance(data);
220 assertEquals(value, input.readRawLittleEndian64());
221 assertTrue(input.isAtEnd());
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());
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);
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);
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));
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));
274 /** Tests reading and parsing a whole message with every field type. */
275 public void testReadWholeMessage() throws Exception {
276 TestAllTypes message = TestUtil.getAllSet();
278 byte[] rawBytes = message.toByteArray();
279 assertEquals(rawBytes.length, message.getSerializedSize());
281 TestAllTypes message2 = TestAllTypes.parseFrom(rawBytes);
282 TestUtil.assertAllFieldsSet(message2);
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);
292 /** Tests skipField(). */
293 public void testSkipWholeMessage() throws Exception {
294 TestAllTypes message = TestUtil.getAllSet();
295 byte[] rawBytes = message.toByteArray();
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();
304 int tag = input1.readTag();
305 assertEquals(tag, input2.readTag());
309 unknownFields.mergeFieldFrom(tag, input1);
310 input2.skipField(tag);
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.
318 public void testSkipRawBytesBug() throws Exception {
319 byte[] rawBytes = new byte[] { 1, 2 };
320 CodedInputStream input = CodedInputStream.newInstance(rawBytes);
322 int limit = input.pushLimit(1);
323 input.skipRawBytes(1);
324 input.popLimit(limit);
325 assertEquals(2, input.readRawByte());
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.
333 public void testSkipRawBytesPastEndOfBufferWithLimit() throws Exception {
334 byte[] rawBytes = new byte[] { 1, 2, 3, 4, 5 };
335 CodedInputStream input = CodedInputStream.newInstance(
336 new SmallBlockInputStream(rawBytes, 3));
338 int limit = input.pushLimit(4);
339 // In order to expose the bug we need to read at least one byte to prime the
340 // buffer inside the CodedInputStream.
341 assertEquals(1, input.readRawByte());
342 // Skip to the end of the limit.
343 input.skipRawBytes(3);
344 assertTrue(input.isAtEnd());
345 input.popLimit(limit);
346 assertEquals(5, input.readRawByte());
349 public void testReadHugeBlob() throws Exception {
350 // Allocate and initialize a 1MB blob.
351 byte[] blob = new byte[1 << 20];
352 for (int i = 0; i < blob.length; i++) {
356 // Make a message containing it.
357 TestAllTypes.Builder builder = TestAllTypes.newBuilder();
358 TestUtil.setAllFields(builder);
359 builder.setOptionalBytes(ByteString.copyFrom(blob));
360 TestAllTypes message = builder.build();
362 // Serialize and parse it. Make sure to parse from an InputStream, not
363 // directly from a ByteString, so that CodedInputStream uses buffered
365 TestAllTypes message2 =
366 TestAllTypes.parseFrom(message.toByteString().newInput());
368 assertEquals(message.getOptionalBytes(), message2.getOptionalBytes());
370 // Make sure all the other fields were parsed correctly.
371 TestAllTypes message3 = TestAllTypes.newBuilder(message2)
372 .setOptionalBytes(TestUtil.getAllSet().getOptionalBytes())
374 TestUtil.assertAllFieldsSet(message3);
377 public void testReadMaliciouslyLargeBlob() throws Exception {
378 ByteString.Output rawOutput = ByteString.newOutput();
379 CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
381 int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
382 output.writeRawVarint32(tag);
383 output.writeRawVarint32(0x7FFFFFFF);
384 output.writeRawBytes(new byte[32]); // Pad with a few random bytes.
387 CodedInputStream input = rawOutput.toByteString().newCodedInput();
388 assertEquals(tag, input.readTag());
392 fail("Should have thrown an exception!");
393 } catch (InvalidProtocolBufferException e) {
398 private TestRecursiveMessage makeRecursiveMessage(int depth) {
400 return TestRecursiveMessage.newBuilder().setI(5).build();
402 return TestRecursiveMessage.newBuilder()
403 .setA(makeRecursiveMessage(depth - 1)).build();
407 private void assertMessageDepth(TestRecursiveMessage message, int depth) {
409 assertFalse(message.hasA());
410 assertEquals(5, message.getI());
412 assertTrue(message.hasA());
413 assertMessageDepth(message.getA(), depth - 1);
417 public void testMaliciousRecursion() throws Exception {
418 ByteString data64 = makeRecursiveMessage(64).toByteString();
419 ByteString data65 = makeRecursiveMessage(65).toByteString();
421 assertMessageDepth(TestRecursiveMessage.parseFrom(data64), 64);
424 TestRecursiveMessage.parseFrom(data65);
425 fail("Should have thrown an exception!");
426 } catch (InvalidProtocolBufferException e) {
430 CodedInputStream input = data64.newCodedInput();
431 input.setRecursionLimit(8);
433 TestRecursiveMessage.parseFrom(input);
434 fail("Should have thrown an exception!");
435 } catch (InvalidProtocolBufferException e) {
440 public void testSizeLimit() throws Exception {
441 CodedInputStream input = CodedInputStream.newInstance(
442 TestUtil.getAllSet().toByteString().newInput());
443 input.setSizeLimit(16);
446 TestAllTypes.parseFrom(input);
447 fail("Should have thrown an exception!");
448 } catch (InvalidProtocolBufferException e) {
453 public void testResetSizeCounter() throws Exception {
454 CodedInputStream input = CodedInputStream.newInstance(
455 new SmallBlockInputStream(new byte[256], 8));
456 input.setSizeLimit(16);
457 input.readRawBytes(16);
458 assertEquals(16, input.getTotalBytesRead());
462 fail("Should have thrown an exception!");
463 } catch (InvalidProtocolBufferException e) {
467 input.resetSizeCounter();
468 assertEquals(0, input.getTotalBytesRead());
469 input.readRawByte(); // No exception thrown.
470 input.resetSizeCounter();
471 assertEquals(0, input.getTotalBytesRead());
474 input.readRawBytes(16); // Hits limit again.
475 fail("Should have thrown an exception!");
476 } catch (InvalidProtocolBufferException e) {
482 * Tests that if we read an string that contains invalid UTF-8, no exception
483 * is thrown. Instead, the invalid bytes are replaced with the Unicode
484 * "replacement character" U+FFFD.
486 public void testReadInvalidUtf8() throws Exception {
487 ByteString.Output rawOutput = ByteString.newOutput();
488 CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
490 int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
491 output.writeRawVarint32(tag);
492 output.writeRawVarint32(1);
493 output.writeRawBytes(new byte[] { (byte)0x80 });
496 CodedInputStream input = rawOutput.toByteString().newCodedInput();
497 assertEquals(tag, input.readTag());
498 String text = input.readString();
499 assertEquals(0xfffd, text.charAt(0));
502 public void testReadFromSlice() throws Exception {
503 byte[] bytes = bytes(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
504 CodedInputStream in = CodedInputStream.newInstance(bytes, 3, 5);
505 assertEquals(0, in.getTotalBytesRead());
506 for (int i = 3; i < 8; i++) {
507 assertEquals(i, in.readRawByte());
508 assertEquals(i-2, in.getTotalBytesRead());
511 assertEquals(0, in.readTag());
512 assertEquals(5, in.getTotalBytesRead());
515 public void testInvalidTag() throws Exception {
516 // Any tag number which corresponds to field number zero is invalid and
517 // should throw InvalidProtocolBufferException.
518 for (int i = 0; i < 8; i++) {
520 CodedInputStream.newInstance(bytes(i)).readTag();
521 fail("Should have thrown an exception.");
522 } catch (InvalidProtocolBufferException e) {
523 assertEquals(InvalidProtocolBufferException.invalidTag().getMessage(),