• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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