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