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