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