1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // https://developers.google.com/protocol-buffers/ 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 com.google.protobuf.CodedOutputStream.OutOfSpaceException; 34 import protobuf_unittest.UnittestProto.SparseEnumMessage; 35 import protobuf_unittest.UnittestProto.TestAllTypes; 36 import protobuf_unittest.UnittestProto.TestPackedTypes; 37 import protobuf_unittest.UnittestProto.TestSparseEnum; 38 import java.io.ByteArrayInputStream; 39 import java.io.ByteArrayOutputStream; 40 import java.nio.ByteBuffer; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.List; 44 import junit.framework.TestCase; 45 46 /** 47 * Unit test for {@link CodedOutputStream}. 48 * 49 * @author kenton@google.com Kenton Varda 50 */ 51 public class CodedOutputStreamTest extends TestCase { 52 private interface Coder { stream()53 CodedOutputStream stream(); 54 toByteArray()55 byte[] toByteArray(); 56 getOutputType()57 OutputType getOutputType(); 58 } 59 60 private static final class OutputStreamCoder implements Coder { 61 private final CodedOutputStream stream; 62 private final ByteArrayOutputStream output; 63 OutputStreamCoder(int size)64 OutputStreamCoder(int size) { 65 output = new ByteArrayOutputStream(); 66 stream = CodedOutputStream.newInstance(output, size); 67 } 68 69 @Override stream()70 public CodedOutputStream stream() { 71 return stream; 72 } 73 74 @Override toByteArray()75 public byte[] toByteArray() { 76 return output.toByteArray(); 77 } 78 79 @Override getOutputType()80 public OutputType getOutputType() { 81 return OutputType.STREAM; 82 } 83 } 84 85 private static final class ArrayCoder implements Coder { 86 private final CodedOutputStream stream; 87 private final byte[] bytes; 88 ArrayCoder(int size)89 ArrayCoder(int size) { 90 bytes = new byte[size]; 91 stream = CodedOutputStream.newInstance(bytes); 92 } 93 94 @Override stream()95 public CodedOutputStream stream() { 96 return stream; 97 } 98 99 @Override toByteArray()100 public byte[] toByteArray() { 101 return Arrays.copyOf(bytes, stream.getTotalBytesWritten()); 102 } 103 104 @Override getOutputType()105 public OutputType getOutputType() { 106 return OutputType.ARRAY; 107 } 108 } 109 110 private static final class NioHeapCoder implements Coder { 111 private final CodedOutputStream stream; 112 private final ByteBuffer buffer; 113 private final int initialPosition; 114 NioHeapCoder(int size)115 NioHeapCoder(int size) { 116 this(size, 0); 117 } 118 NioHeapCoder(int size, int initialPosition)119 NioHeapCoder(int size, int initialPosition) { 120 this.initialPosition = initialPosition; 121 buffer = ByteBuffer.allocate(size); 122 buffer.position(initialPosition); 123 stream = CodedOutputStream.newInstance(buffer); 124 } 125 126 @Override stream()127 public CodedOutputStream stream() { 128 return stream; 129 } 130 131 @Override toByteArray()132 public byte[] toByteArray() { 133 ByteBuffer dup = buffer.duplicate(); 134 dup.position(initialPosition); 135 dup.limit(buffer.position()); 136 137 byte[] bytes = new byte[dup.remaining()]; 138 dup.get(bytes); 139 return bytes; 140 } 141 142 @Override getOutputType()143 public OutputType getOutputType() { 144 return OutputType.NIO_HEAP; 145 } 146 } 147 148 private static final class NioDirectCoder implements Coder { 149 private final int initialPosition; 150 private final CodedOutputStream stream; 151 private final ByteBuffer buffer; 152 private final boolean unsafe; 153 NioDirectCoder(int size, boolean unsafe)154 NioDirectCoder(int size, boolean unsafe) { 155 this(size, 0, unsafe); 156 } 157 NioDirectCoder(int size, int initialPosition, boolean unsafe)158 NioDirectCoder(int size, int initialPosition, boolean unsafe) { 159 this.unsafe = unsafe; 160 this.initialPosition = initialPosition; 161 buffer = ByteBuffer.allocateDirect(size); 162 buffer.position(initialPosition); 163 stream = 164 unsafe 165 ? CodedOutputStream.newUnsafeInstance(buffer) 166 : CodedOutputStream.newSafeInstance(buffer); 167 } 168 169 @Override stream()170 public CodedOutputStream stream() { 171 return stream; 172 } 173 174 @Override toByteArray()175 public byte[] toByteArray() { 176 ByteBuffer dup = buffer.duplicate(); 177 dup.position(initialPosition); 178 dup.limit(buffer.position()); 179 180 byte[] bytes = new byte[dup.remaining()]; 181 dup.get(bytes); 182 return bytes; 183 } 184 185 @Override getOutputType()186 public OutputType getOutputType() { 187 return unsafe ? OutputType.NIO_DIRECT_SAFE : OutputType.NIO_DIRECT_UNSAFE; 188 } 189 } 190 191 private enum OutputType { ARRAY()192 ARRAY() { 193 @Override 194 Coder newCoder(int size) { 195 return new ArrayCoder(size); 196 } 197 }, NIO_HEAP()198 NIO_HEAP() { 199 @Override 200 Coder newCoder(int size) { 201 return new NioHeapCoder(size); 202 } 203 }, NIO_DIRECT_SAFE()204 NIO_DIRECT_SAFE() { 205 @Override 206 Coder newCoder(int size) { 207 return new NioDirectCoder(size, false); 208 } 209 }, NIO_DIRECT_UNSAFE()210 NIO_DIRECT_UNSAFE() { 211 @Override 212 Coder newCoder(int size) { 213 return new NioDirectCoder(size, true); 214 } 215 }, STREAM()216 STREAM() { 217 @Override 218 Coder newCoder(int size) { 219 return new OutputStreamCoder(size); 220 } 221 }; 222 newCoder(int size)223 abstract Coder newCoder(int size); 224 } 225 226 /** Checks that invariants are maintained for varint round trip input and output. */ testVarintRoundTrips()227 public void testVarintRoundTrips() throws Exception { 228 for (OutputType outputType : OutputType.values()) { 229 assertVarintRoundTrip(outputType, 0L); 230 for (int bits = 0; bits < 64; bits++) { 231 long value = 1L << bits; 232 assertVarintRoundTrip(outputType, value); 233 assertVarintRoundTrip(outputType, value + 1); 234 assertVarintRoundTrip(outputType, value - 1); 235 assertVarintRoundTrip(outputType, -value); 236 } 237 } 238 } 239 240 /** Tests writeRawVarint32() and writeRawVarint64(). */ testWriteVarint()241 public void testWriteVarint() throws Exception { 242 assertWriteVarint(bytes(0x00), 0); 243 assertWriteVarint(bytes(0x01), 1); 244 assertWriteVarint(bytes(0x7f), 127); 245 // 14882 246 assertWriteVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7)); 247 // 2961488830 248 assertWriteVarint( 249 bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b), 250 (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | (0x0bL << 28)); 251 252 // 64-bit 253 // 7256456126 254 assertWriteVarint( 255 bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b), 256 (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | (0x1bL << 28)); 257 // 41256202580718336 258 assertWriteVarint( 259 bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49), 260 (0x00 << 0) 261 | (0x66 << 7) 262 | (0x6b << 14) 263 | (0x1c << 21) 264 | (0x43L << 28) 265 | (0x49L << 35) 266 | (0x24L << 42) 267 | (0x49L << 49)); 268 // 11964378330978735131 269 assertWriteVarint( 270 bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01), 271 (0x1b << 0) 272 | (0x28 << 7) 273 | (0x79 << 14) 274 | (0x42 << 21) 275 | (0x3bL << 28) 276 | (0x56L << 35) 277 | (0x00L << 42) 278 | (0x05L << 49) 279 | (0x26L << 56) 280 | (0x01L << 63)); 281 } 282 283 /** Tests writeRawLittleEndian32() and writeRawLittleEndian64(). */ testWriteLittleEndian()284 public void testWriteLittleEndian() throws Exception { 285 assertWriteLittleEndian32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678); 286 assertWriteLittleEndian32(bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0); 287 288 assertWriteLittleEndian64( 289 bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12), 0x123456789abcdef0L); 290 assertWriteLittleEndian64( 291 bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef012345678L); 292 } 293 294 /** Test encodeZigZag32() and encodeZigZag64(). */ testEncodeZigZag()295 public void testEncodeZigZag() throws Exception { 296 assertEquals(0, CodedOutputStream.encodeZigZag32(0)); 297 assertEquals(1, CodedOutputStream.encodeZigZag32(-1)); 298 assertEquals(2, CodedOutputStream.encodeZigZag32(1)); 299 assertEquals(3, CodedOutputStream.encodeZigZag32(-2)); 300 assertEquals(0x7FFFFFFE, CodedOutputStream.encodeZigZag32(0x3FFFFFFF)); 301 assertEquals(0x7FFFFFFF, CodedOutputStream.encodeZigZag32(0xC0000000)); 302 assertEquals(0xFFFFFFFE, CodedOutputStream.encodeZigZag32(0x7FFFFFFF)); 303 assertEquals(0xFFFFFFFF, CodedOutputStream.encodeZigZag32(0x80000000)); 304 305 assertEquals(0, CodedOutputStream.encodeZigZag64(0)); 306 assertEquals(1, CodedOutputStream.encodeZigZag64(-1)); 307 assertEquals(2, CodedOutputStream.encodeZigZag64(1)); 308 assertEquals(3, CodedOutputStream.encodeZigZag64(-2)); 309 assertEquals(0x000000007FFFFFFEL, CodedOutputStream.encodeZigZag64(0x000000003FFFFFFFL)); 310 assertEquals(0x000000007FFFFFFFL, CodedOutputStream.encodeZigZag64(0xFFFFFFFFC0000000L)); 311 assertEquals(0x00000000FFFFFFFEL, CodedOutputStream.encodeZigZag64(0x000000007FFFFFFFL)); 312 assertEquals(0x00000000FFFFFFFFL, CodedOutputStream.encodeZigZag64(0xFFFFFFFF80000000L)); 313 assertEquals(0xFFFFFFFFFFFFFFFEL, CodedOutputStream.encodeZigZag64(0x7FFFFFFFFFFFFFFFL)); 314 assertEquals(0xFFFFFFFFFFFFFFFFL, CodedOutputStream.encodeZigZag64(0x8000000000000000L)); 315 316 // Some easier-to-verify round-trip tests. The inputs (other than 0, 1, -1) 317 // were chosen semi-randomly via keyboard bashing. 318 assertEquals(0, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(0))); 319 assertEquals(1, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(1))); 320 assertEquals(-1, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-1))); 321 assertEquals(14927, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(14927))); 322 assertEquals(-3612, CodedOutputStream.encodeZigZag32(CodedInputStream.decodeZigZag32(-3612))); 323 324 assertEquals(0, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(0))); 325 assertEquals(1, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(1))); 326 assertEquals(-1, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-1))); 327 assertEquals(14927, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(14927))); 328 assertEquals(-3612, CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-3612))); 329 330 assertEquals( 331 856912304801416L, 332 CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(856912304801416L))); 333 assertEquals( 334 -75123905439571256L, 335 CodedOutputStream.encodeZigZag64(CodedInputStream.decodeZigZag64(-75123905439571256L))); 336 } 337 338 /** Tests writing a whole message with every field type. */ testWriteWholeMessage()339 public void testWriteWholeMessage() throws Exception { 340 final byte[] expectedBytes = TestUtil.getGoldenMessage().toByteArray(); 341 TestAllTypes message = TestUtil.getAllSet(); 342 343 for (OutputType outputType : OutputType.values()) { 344 Coder coder = outputType.newCoder(message.getSerializedSize()); 345 message.writeTo(coder.stream()); 346 coder.stream().flush(); 347 byte[] rawBytes = coder.toByteArray(); 348 assertEqualBytes(outputType, expectedBytes, rawBytes); 349 } 350 351 // Try different block sizes. 352 for (int blockSize = 1; blockSize < 256; blockSize *= 2) { 353 Coder coder = OutputType.STREAM.newCoder(blockSize); 354 message.writeTo(coder.stream()); 355 coder.stream().flush(); 356 assertEqualBytes(OutputType.STREAM, expectedBytes, coder.toByteArray()); 357 } 358 } 359 360 /** 361 * Tests writing a whole message with every packed field type. Ensures the wire format of packed 362 * fields is compatible with C++. 363 */ testWriteWholePackedFieldsMessage()364 public void testWriteWholePackedFieldsMessage() throws Exception { 365 byte[] expectedBytes = TestUtil.getGoldenPackedFieldsMessage().toByteArray(); 366 TestPackedTypes message = TestUtil.getPackedSet(); 367 368 for (OutputType outputType : OutputType.values()) { 369 Coder coder = outputType.newCoder(message.getSerializedSize()); 370 message.writeTo(coder.stream()); 371 coder.stream().flush(); 372 byte[] rawBytes = coder.toByteArray(); 373 assertEqualBytes(outputType, expectedBytes, rawBytes); 374 } 375 } 376 377 /** 378 * Test writing a message containing a negative enum value. This used to fail because the size was 379 * not properly computed as a sign-extended varint. 380 */ testWriteMessageWithNegativeEnumValue()381 public void testWriteMessageWithNegativeEnumValue() throws Exception { 382 SparseEnumMessage message = 383 SparseEnumMessage.newBuilder().setSparseEnum(TestSparseEnum.SPARSE_E).build(); 384 assertTrue(message.getSparseEnum().getNumber() < 0); 385 for (OutputType outputType : OutputType.values()) { 386 Coder coder = outputType.newCoder(message.getSerializedSize()); 387 message.writeTo(coder.stream()); 388 coder.stream().flush(); 389 byte[] rawBytes = coder.toByteArray(); 390 SparseEnumMessage message2 = SparseEnumMessage.parseFrom(rawBytes); 391 assertEquals(TestSparseEnum.SPARSE_E, message2.getSparseEnum()); 392 } 393 } 394 395 /** Test getTotalBytesWritten() */ 396 public void testGetTotalBytesWritten() throws Exception { 397 Coder coder = OutputType.STREAM.newCoder(4 * 1024); 398 399 // Write some some bytes (more than the buffer can hold) and verify that totalWritten 400 // is correct. 401 byte[] value = "abcde".getBytes(Internal.UTF_8); 402 for (int i = 0; i < 1024; ++i) { 403 coder.stream().writeRawBytes(value, 0, value.length); 404 } 405 assertEquals(value.length * 1024, coder.stream().getTotalBytesWritten()); 406 407 // Now write an encoded string. 408 String string = 409 "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; 410 // Ensure we take the slower fast path. 411 assertTrue( 412 CodedOutputStream.computeUInt32SizeNoTag(string.length()) 413 != CodedOutputStream.computeUInt32SizeNoTag(string.length() * Utf8.MAX_BYTES_PER_CHAR)); 414 415 coder.stream().writeStringNoTag(string); 416 coder.stream().flush(); 417 int stringSize = CodedOutputStream.computeStringSizeNoTag(string); 418 419 // Verify that the total bytes written is correct 420 assertEquals((value.length * 1024) + stringSize, coder.stream().getTotalBytesWritten()); 421 } 422 423 // TODO(dweis): Write a comprehensive test suite for CodedOutputStream that covers more than just 424 // this case. 425 public void testWriteStringNoTag_fastpath() throws Exception { 426 int bufferSize = 153; 427 String threeBytesPer = "\u0981"; 428 String string = threeBytesPer; 429 for (int i = 0; i < 50; i++) { 430 string += threeBytesPer; 431 } 432 // These checks ensure we will tickle the slower fast path. 433 assertEquals(1, CodedOutputStream.computeUInt32SizeNoTag(string.length())); 434 assertEquals( 435 2, CodedOutputStream.computeUInt32SizeNoTag(string.length() * Utf8.MAX_BYTES_PER_CHAR)); 436 assertEquals(bufferSize, string.length() * Utf8.MAX_BYTES_PER_CHAR); 437 438 for (OutputType outputType : OutputType.values()) { 439 Coder coder = outputType.newCoder(bufferSize + 2); 440 coder.stream().writeStringNoTag(string); 441 coder.stream().flush(); 442 } 443 } 444 445 public void testWriteToByteBuffer() throws Exception { 446 final int bufferSize = 16 * 1024; 447 ByteBuffer buffer = ByteBuffer.allocate(bufferSize); 448 CodedOutputStream codedStream = CodedOutputStream.newInstance(buffer); 449 // Write raw bytes into the ByteBuffer. 450 final int length1 = 5000; 451 for (int i = 0; i < length1; i++) { 452 codedStream.writeRawByte((byte) 1); 453 } 454 final int length2 = 8 * 1024; 455 byte[] data = new byte[length2]; 456 Arrays.fill(data, 0, length2, (byte) 2); 457 codedStream.writeRawBytes(data); 458 final int length3 = bufferSize - length1 - length2; 459 for (int i = 0; i < length3; i++) { 460 codedStream.writeRawByte((byte) 3); 461 } 462 codedStream.flush(); 463 464 // Check that data is correctly written to the ByteBuffer. 465 assertEquals(0, buffer.remaining()); 466 buffer.flip(); 467 for (int i = 0; i < length1; i++) { 468 assertEquals((byte) 1, buffer.get()); 469 } 470 for (int i = 0; i < length2; i++) { 471 assertEquals((byte) 2, buffer.get()); 472 } 473 for (int i = 0; i < length3; i++) { 474 assertEquals((byte) 3, buffer.get()); 475 } 476 } 477 478 public void testWriteByteBuffer() throws Exception { 479 byte[] value = "abcde".getBytes(Internal.UTF_8); 480 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 481 CodedOutputStream codedStream = CodedOutputStream.newInstance(outputStream); 482 ByteBuffer byteBuffer = ByteBuffer.wrap(value, 0, 1); 483 // This will actually write 5 bytes into the CodedOutputStream as the 484 // ByteBuffer's capacity() is 5. 485 codedStream.writeRawBytes(byteBuffer); 486 // The above call shouldn't affect the ByteBuffer's state. 487 assertEquals(0, byteBuffer.position()); 488 assertEquals(1, byteBuffer.limit()); 489 490 // The correct way to write part of an array using ByteBuffer. 491 codedStream.writeRawBytes(ByteBuffer.wrap(value, 2, 1).slice()); 492 493 codedStream.flush(); 494 byte[] result = outputStream.toByteArray(); 495 assertEquals(6, result.length); 496 for (int i = 0; i < 5; i++) { 497 assertEquals(value[i], result[i]); 498 } 499 assertEquals(value[2], result[5]); 500 } 501 502 public void testWriteByteArrayWithOffsets() throws Exception { 503 byte[] fullArray = bytes(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88); 504 for (OutputType type : new OutputType[] {OutputType.ARRAY}) { 505 Coder coder = type.newCoder(4); 506 coder.stream().writeByteArrayNoTag(fullArray, 2, 2); 507 assertEqualBytes(type, bytes(0x02, 0x33, 0x44), coder.toByteArray()); 508 assertEquals(3, coder.stream().getTotalBytesWritten()); 509 } 510 } 511 512 public void testSerializeUtf8_MultipleSmallWrites() throws Exception { 513 final String source = "abcdefghijklmnopqrstuvwxyz"; 514 515 // Generate the expected output if the source string is written 2 bytes at a time. 516 ByteArrayOutputStream expectedBytesStream = new ByteArrayOutputStream(); 517 for (int pos = 0; pos < source.length(); pos += 2) { 518 String substr = source.substring(pos, pos + 2); 519 expectedBytesStream.write(2); 520 expectedBytesStream.write(substr.getBytes(Internal.UTF_8)); 521 } 522 final byte[] expectedBytes = expectedBytesStream.toByteArray(); 523 524 // For each output type, write the source string 2 bytes at a time and verify the output. 525 for (OutputType outputType : OutputType.values()) { 526 Coder coder = outputType.newCoder(expectedBytes.length); 527 for (int pos = 0; pos < source.length(); pos += 2) { 528 String substr = source.substring(pos, pos + 2); 529 coder.stream().writeStringNoTag(substr); 530 } 531 coder.stream().flush(); 532 assertEqualBytes(outputType, expectedBytes, coder.toByteArray()); 533 } 534 } 535 536 public void testSerializeInvalidUtf8() throws Exception { 537 String[] invalidStrings = 538 new String[] { 539 newString(Character.MIN_HIGH_SURROGATE), 540 "foobar" + newString(Character.MIN_HIGH_SURROGATE), 541 newString(Character.MIN_LOW_SURROGATE), 542 "foobar" + newString(Character.MIN_LOW_SURROGATE), 543 newString(Character.MIN_HIGH_SURROGATE, Character.MIN_HIGH_SURROGATE) 544 }; 545 546 CodedOutputStream outputWithStream = CodedOutputStream.newInstance(new ByteArrayOutputStream()); 547 CodedOutputStream outputWithArray = CodedOutputStream.newInstance(new byte[10000]); 548 CodedOutputStream outputWithByteBuffer = 549 CodedOutputStream.newInstance(ByteBuffer.allocate(10000)); 550 for (String s : invalidStrings) { 551 // TODO(dweis): These should all fail; instead they are corrupting data. 552 CodedOutputStream.computeStringSizeNoTag(s); 553 outputWithStream.writeStringNoTag(s); 554 outputWithArray.writeStringNoTag(s); 555 outputWithByteBuffer.writeStringNoTag(s); 556 } 557 } 558 559 // TODO(nathanmittler): This test can be deleted once we properly throw IOException while 560 // encoding invalid UTF-8 strings. 561 public void testSerializeInvalidUtf8FollowedByOutOfSpace() throws Exception { 562 final int notEnoughBytes = 4; 563 CodedOutputStream outputWithArray = CodedOutputStream.newInstance(new byte[notEnoughBytes]); 564 CodedOutputStream outputWithByteBuffer = 565 CodedOutputStream.newInstance(ByteBuffer.allocate(notEnoughBytes)); 566 567 String invalidString = newString(Character.MIN_HIGH_SURROGATE, 'f', 'o', 'o', 'b', 'a', 'r'); 568 try { 569 outputWithArray.writeStringNoTag(invalidString); 570 fail("Expected OutOfSpaceException"); 571 } catch (OutOfSpaceException e) { 572 assertTrue(e.getCause() instanceof IndexOutOfBoundsException); 573 } 574 try { 575 outputWithByteBuffer.writeStringNoTag(invalidString); 576 fail("Expected OutOfSpaceException"); 577 } catch (OutOfSpaceException e) { 578 assertTrue(e.getCause() instanceof IndexOutOfBoundsException); 579 } 580 } 581 582 /** Regression test for https://github.com/protocolbuffers/protobuf/issues/292 */ 583 public void testCorrectExceptionThrowWhenEncodingStringsWithoutEnoughSpace() throws Exception { 584 String testCase = "Foooooooo"; 585 assertEquals( 586 CodedOutputStream.computeUInt32SizeNoTag(testCase.length()), 587 CodedOutputStream.computeUInt32SizeNoTag(testCase.length() * 3)); 588 assertEquals(11, CodedOutputStream.computeStringSize(1, testCase)); 589 // Tag is one byte, varint describing string length is 1 byte, string length is 9 bytes. 590 // An array of size 1 will cause a failure when trying to write the varint. 591 for (OutputType outputType : 592 new OutputType[] { 593 OutputType.ARRAY, 594 OutputType.NIO_HEAP, 595 OutputType.NIO_DIRECT_SAFE, 596 OutputType.NIO_DIRECT_UNSAFE 597 }) { 598 for (int i = 0; i < 11; i++) { 599 Coder coder = outputType.newCoder(i); 600 try { 601 coder.stream().writeString(1, testCase); 602 fail("Should have thrown an out of space exception"); 603 } catch (CodedOutputStream.OutOfSpaceException expected) { 604 } 605 } 606 } 607 } 608 609 public void testDifferentStringLengths() throws Exception { 610 // Test string serialization roundtrip using strings of the following lengths, 611 // with ASCII and Unicode characters requiring different UTF-8 byte counts per 612 // char, hence causing the length delimiter varint to sometimes require more 613 // bytes for the Unicode strings than the ASCII string of the same length. 614 int[] lengths = 615 new int[] { 616 0, 617 1, 618 (1 << 4) - 1, // 1 byte for ASCII and Unicode 619 (1 << 7) - 1, // 1 byte for ASCII, 2 bytes for Unicode 620 (1 << 11) - 1, // 2 bytes for ASCII and Unicode 621 (1 << 14) - 1, // 2 bytes for ASCII, 3 bytes for Unicode 622 (1 << 17) - 1, 623 // 3 bytes for ASCII and Unicode 624 }; 625 for (OutputType outputType : OutputType.values()) { 626 for (int i : lengths) { 627 testEncodingOfString(outputType, 'q', i); // 1 byte per char 628 testEncodingOfString(outputType, '\u07FF', i); // 2 bytes per char 629 testEncodingOfString(outputType, '\u0981', i); // 3 bytes per char 630 } 631 } 632 } 633 634 public void testNioEncodersWithInitialOffsets() throws Exception { 635 String value = "abc"; 636 for (Coder coder : 637 new Coder[] { 638 new NioHeapCoder(10, 2), new NioDirectCoder(10, 2, false), new NioDirectCoder(10, 2, true) 639 }) { 640 coder.stream().writeStringNoTag(value); 641 coder.stream().flush(); 642 assertEqualBytes(coder.getOutputType(), new byte[] {3, 'a', 'b', 'c'}, coder.toByteArray()); 643 } 644 } 645 646 /** 647 * Parses the given bytes using writeRawLittleEndian32() and checks that the result matches the 648 * given value. 649 */ 650 private static void assertWriteLittleEndian32(byte[] data, int value) throws Exception { 651 for (OutputType outputType : OutputType.values()) { 652 Coder coder = outputType.newCoder(data.length); 653 coder.stream().writeFixed32NoTag(value); 654 coder.stream().flush(); 655 assertEqualBytes(outputType, data, coder.toByteArray()); 656 } 657 658 // Try different block sizes. 659 for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { 660 Coder coder = OutputType.STREAM.newCoder(blockSize); 661 coder.stream().writeFixed32NoTag(value); 662 coder.stream().flush(); 663 assertEqualBytes(OutputType.STREAM, data, coder.toByteArray()); 664 } 665 } 666 667 /** 668 * Parses the given bytes using writeRawLittleEndian64() and checks that the result matches the 669 * given value. 670 */ 671 private static void assertWriteLittleEndian64(byte[] data, long value) throws Exception { 672 for (OutputType outputType : OutputType.values()) { 673 Coder coder = outputType.newCoder(data.length); 674 coder.stream().writeFixed64NoTag(value); 675 coder.stream().flush(); 676 assertEqualBytes(outputType, data, coder.toByteArray()); 677 } 678 679 // Try different block sizes. 680 for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { 681 Coder coder = OutputType.STREAM.newCoder(blockSize); 682 coder.stream().writeFixed64NoTag(value); 683 coder.stream().flush(); 684 assertEqualBytes(OutputType.STREAM, data, coder.toByteArray()); 685 } 686 } 687 688 private static String newString(char... chars) { 689 return new String(chars); 690 } 691 692 private static void testEncodingOfString(OutputType outputType, char c, int length) 693 throws Exception { 694 String fullString = fullString(c, length); 695 TestAllTypes testAllTypes = TestAllTypes.newBuilder().setOptionalString(fullString).build(); 696 Coder coder = outputType.newCoder(testAllTypes.getSerializedSize()); 697 testAllTypes.writeTo(coder.stream()); 698 coder.stream().flush(); 699 assertEquals( 700 "OuputType: " + outputType, 701 fullString, 702 TestAllTypes.parseFrom(coder.toByteArray()).getOptionalString()); 703 } 704 705 private static String fullString(char c, int length) { 706 char[] result = new char[length]; 707 Arrays.fill(result, c); 708 return new String(result); 709 } 710 711 /** 712 * Helper to construct a byte array from a bunch of bytes. The inputs are actually ints so that I 713 * can use hex notation and not get stupid errors about precision. 714 */ 715 private static byte[] bytes(int... bytesAsInts) { 716 byte[] bytes = new byte[bytesAsInts.length]; 717 for (int i = 0; i < bytesAsInts.length; i++) { 718 bytes[i] = (byte) bytesAsInts[i]; 719 } 720 return bytes; 721 } 722 723 /** Arrays.asList() does not work with arrays of primitives. :( */ 724 private static List<Byte> toList(byte[] bytes) { 725 List<Byte> result = new ArrayList<Byte>(); 726 for (byte b : bytes) { 727 result.add(b); 728 } 729 return result; 730 } 731 732 private static void assertEqualBytes(OutputType outputType, byte[] a, byte[] b) { 733 assertEquals(outputType.name(), toList(a), toList(b)); 734 } 735 736 /** 737 * Writes the given value using writeRawVarint32() and writeRawVarint64() and checks that the 738 * result matches the given bytes. 739 */ 740 private static void assertWriteVarint(byte[] data, long value) throws Exception { 741 for (OutputType outputType : OutputType.values()) { 742 // Only test 32-bit write if the value fits into an int. 743 if (value == (int) value) { 744 Coder coder = outputType.newCoder(10); 745 coder.stream().writeUInt32NoTag((int) value); 746 coder.stream().flush(); 747 assertEqualBytes(outputType, data, coder.toByteArray()); 748 749 // Also try computing size. 750 assertEquals(data.length, CodedOutputStream.computeUInt32SizeNoTag((int) value)); 751 } 752 753 { 754 Coder coder = outputType.newCoder(10); 755 coder.stream().writeUInt64NoTag(value); 756 coder.stream().flush(); 757 assertEqualBytes(outputType, data, coder.toByteArray()); 758 759 // Also try computing size. 760 assertEquals(data.length, CodedOutputStream.computeUInt64SizeNoTag(value)); 761 } 762 } 763 764 // Try different block sizes. 765 for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { 766 // Only test 32-bit write if the value fits into an int. 767 if (value == (int) value) { 768 Coder coder = OutputType.STREAM.newCoder(blockSize); 769 coder.stream().writeUInt64NoTag((int) value); 770 coder.stream().flush(); 771 assertEqualBytes(OutputType.STREAM, data, coder.toByteArray()); 772 773 ByteArrayOutputStream rawOutput = new ByteArrayOutputStream(); 774 CodedOutputStream output = CodedOutputStream.newInstance(rawOutput, blockSize); 775 output.writeUInt32NoTag((int) value); 776 output.flush(); 777 assertEqualBytes(OutputType.STREAM, data, rawOutput.toByteArray()); 778 } 779 780 { 781 Coder coder = OutputType.STREAM.newCoder(blockSize); 782 coder.stream().writeUInt64NoTag(value); 783 coder.stream().flush(); 784 assertEqualBytes(OutputType.STREAM, data, coder.toByteArray()); 785 } 786 } 787 } 788 789 private static void assertVarintRoundTrip(OutputType outputType, long value) throws Exception { 790 { 791 Coder coder = outputType.newCoder(10); 792 coder.stream().writeUInt64NoTag(value); 793 coder.stream().flush(); 794 byte[] bytes = coder.toByteArray(); 795 assertEquals( 796 outputType.name(), bytes.length, CodedOutputStream.computeUInt64SizeNoTag(value)); 797 CodedInputStream input = CodedInputStream.newInstance(new ByteArrayInputStream(bytes)); 798 assertEquals(outputType.name(), value, input.readRawVarint64()); 799 } 800 801 if (value == (int) value) { 802 Coder coder = outputType.newCoder(10); 803 coder.stream().writeUInt32NoTag((int) value); 804 coder.stream().flush(); 805 byte[] bytes = coder.toByteArray(); 806 assertEquals( 807 outputType.name(), bytes.length, CodedOutputStream.computeUInt32SizeNoTag((int) value)); 808 CodedInputStream input = CodedInputStream.newInstance(new ByteArrayInputStream(bytes)); 809 assertEquals(outputType.name(), value, input.readRawVarint32()); 810 } 811 } 812 } 813