content: /* Copyright (C) 2016-2018 Alibaba Group Holding Limited
  
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
  
      http://www.apache.org/licenses/LICENSE-2.0
  
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
  ==============================================================================*/
  
  // Protocol Buffers - Google's data interchange format
  // Copyright 2008 Google Inc.  All rights reserved.
  // https://developers.google.com/protocol-buffers/
  //
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions are
  // met:
  //
  //     * Redistributions of source code must retain the above copyright
  // notice, this list of conditions and the following disclaimer.
  //     * Redistributions in binary form must reproduce the above
  // copyright notice, this list of conditions and the following disclaimer
  // in the documentation and/or other materials provided with the
  // distribution.
  //     * Neither the name of Google Inc. nor the names of its
  // contributors may be used to endorse or promote products derived from
  // this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  package com.google.protobuf;
  
  import protobuf_unittest.UnittestProto.BoolMessage;
  import protobuf_unittest.UnittestProto.Int32Message;
  import protobuf_unittest.UnittestProto.Int64Message;
  import protobuf_unittest.UnittestProto.TestAllTypes;
  import protobuf_unittest.UnittestProto.TestRecursiveMessage;
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.FilterInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.ByteBuffer;
  import java.util.ArrayList;
  import java.util.Arrays;
  import junit.framework.TestCase;
  
  /**
   * Unit test for {@link CodedInputStream}.
   *
   * @author kenton@google.com Kenton Varda
   */
  public class CodedInputStreamTest extends TestCase {
    
    private static final int DEFAULT_BLOCK_SIZE = 4096; 
    
    private enum InputType {
      ARRAY {
        @Override
        CodedInputStream newDecoder(byte[] data, int blockSize) {
          return CodedInputStream.newInstance(data);
        }
      },
      NIO_HEAP {
        @Override
        CodedInputStream newDecoder(byte[] data, int blockSize) {
          return CodedInputStream.newInstance(ByteBuffer.wrap(data));
        }
      },
      NIO_DIRECT {
        @Override
        CodedInputStream newDecoder(byte[] data, int blockSize) {
          ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
          buffer.put(data);
          buffer.flip();
          return CodedInputStream.newInstance(buffer);
        }
      },
      STREAM {
        @Override
        CodedInputStream newDecoder(byte[] data, int blockSize) {
          return CodedInputStream.newInstance(new SmallBlockInputStream(data, blockSize));
        }
      },
      ITER_DIRECT {
        @Override
        CodedInputStream newDecoder(byte[] data, int blockSize) {
          if (blockSize > DEFAULT_BLOCK_SIZE) {
            blockSize = DEFAULT_BLOCK_SIZE;
          }
          ArrayList <ByteBuffer> input = new ArrayList <ByteBuffer>();
          for (int i = 0; i < data.length; i += blockSize) {
            int rl = Math.min(blockSize, data.length - i); 
            ByteBuffer rb = ByteBuffer.allocateDirect(rl); 
            rb.put(data, i, rl);
            rb.flip();
            input.add(rb);
          }
          return CodedInputStream.newInstance(input);
        }
      },
      STREAM_ITER_DIRECT {
        @Override
        CodedInputStream newDecoder(byte[] data, int blockSize) {
          if (blockSize > DEFAULT_BLOCK_SIZE) {
            blockSize = DEFAULT_BLOCK_SIZE;
          }
          ArrayList <ByteBuffer> input = new ArrayList <ByteBuffer>();
          for (int i = 0; i < data.length; i += blockSize) {
            int rl = Math.min(blockSize, data.length - i); 
            ByteBuffer rb = ByteBuffer.allocateDirect(rl); 
            rb.put(data, i, rl);
            rb.flip();
            input.add(rb);
          }
          return CodedInputStream.newInstance(new IterableByteBufferInputStream(input));
        }
      };
      
      
  
      CodedInputStream newDecoder(byte[] data) {
        return newDecoder(data, data.length);
      }
  
      abstract CodedInputStream newDecoder(byte[] data, int blockSize);
    }
  
    /**
     * Helper to construct a byte array from a bunch of bytes. The inputs are actually ints so that I
     * can use hex notation and not get stupid errors about precision.
     */
    private byte[] bytes(int... bytesAsInts) {
      byte[] bytes = new byte[bytesAsInts.length];
      for (int i = 0; i < bytesAsInts.length; i++) {
        bytes[i] = (byte) bytesAsInts[i];
      }
      return bytes;
    }
  
    /**
     * An InputStream which limits the number of bytes it reads at a time. We use this to make sure
     * that CodedInputStream doesn't screw up when reading in small blocks.
     */
    private static final class SmallBlockInputStream extends FilterInputStream {
      private final int blockSize;
  
      public SmallBlockInputStream(byte[] data, int blockSize) {
        super(new ByteArrayInputStream(data));
        this.blockSize = blockSize;
      }
  
      @Override
      public int read(byte[] b) throws IOException {
        return super.read(b, 0, Math.min(b.length, blockSize));
      }
  
      @Override
      public int read(byte[] b, int off, int len) throws IOException {
        return super.read(b, off, Math.min(len, blockSize));
      }
    }
  
    private void assertDataConsumed(String msg, byte[] data, CodedInputStream input)
        throws IOException {
      assertEquals(msg, data.length, input.getTotalBytesRead());
      assertTrue(msg, input.isAtEnd());
    }
  
    /**
     * Parses the given bytes using readRawVarint32() and readRawVarint64() and checks that the result
     * matches the given value.
     */
    private void assertReadVarint(byte[] data, long value) throws Exception {
      for (InputType inputType : InputType.values()) {
        // Try different block sizes.
        for (int blockSize = 1; blockSize <= 16; blockSize *= 2) {
          CodedInputStream input = inputType.newDecoder(data, blockSize);
          assertEquals(inputType.name(), (int) value, input.readRawVarint32());
          assertDataConsumed(inputType.name(), data, input);
  
          input = inputType.newDecoder(data, blockSize);
          assertEquals(inputType.name(), value, input.readRawVarint64());
          assertDataConsumed(inputType.name(), data, input);
  
          input = inputType.newDecoder(data, blockSize);
          assertEquals(inputType.name(), value, input.readRawVarint64SlowPath());
          assertDataConsumed(inputType.name(), data, input);
  
          input = inputType.newDecoder(data, blockSize);
          assertTrue(inputType.name(), input.skipField(WireFormat.WIRETYPE_VARINT));
          assertDataConsumed(inputType.name(), data, input);
        }
      }
  
      // Try reading direct from an InputStream.  We want to verify that it
      // doesn't read past the end of the input, so we copy to a new, bigger
      // array first.
      byte[] longerData = new byte[data.length + 1];
      System.arraycopy(data, 0, longerData, 0, data.length);
      InputStream rawInput = new ByteArrayInputStream(longerData);
      assertEquals((int) value, CodedInputStream.readRawVarint32(rawInput));
      assertEquals(1, rawInput.available());
    }
  
    /**
     * Parses the given bytes using readRawVarint32() and readRawVarint64() and expects them to fail
     * with an InvalidProtocolBufferException whose description matches the given one.
     */
    private void assertReadVarintFailure(InvalidProtocolBufferException expected, byte[] data)
        throws Exception {
      for (InputType inputType : InputType.values()) {
        try {
          CodedInputStream input = inputType.newDecoder(data);
          input.readRawVarint32();
          fail(inputType.name() + ": Should have thrown an exception.");
        } catch (InvalidProtocolBufferException e) {
          assertEquals(inputType.name(), expected.getMessage(), e.getMessage());
        }
        try {
          CodedInputStream input = inputType.newDecoder(data);
          input.readRawVarint64();
          fail(inputType.name() + ": Should have thrown an exception.");
        } catch (InvalidProtocolBufferException e) {
          assertEquals(inputType.name(), expected.getMessage(), e.getMessage());
        }
      }
  
      // Make sure we get the same error when reading direct from an InputStream.
      try {
        CodedInputStream.readRawVarint32(new ByteArrayInputStream(data));
        fail("Should have thrown an exception.");
      } catch (InvalidProtocolBufferException e) {
        assertEquals(expected.getMessage(), e.getMessage());
      }
    }
  
    /** Tests readRawVarint32() and readRawVarint64(). */
    public void testReadVarint() throws Exception {
      assertReadVarint(bytes(0x00), 0);
      assertReadVarint(bytes(0x01), 1);
      assertReadVarint(bytes(0x7f), 127);
      // 14882
      assertReadVarint(bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7));
      // 2961488830
      assertReadVarint(
          bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b),
          (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | (0x0bL << 28));
  
      // 64-bit
      // 7256456126
      assertReadVarint(
          bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b),
          (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | (0x1bL << 28));
      // 41256202580718336
      assertReadVarint(
          bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49),
          (0x00 << 0)
              | (0x66 << 7)
              | (0x6b << 14)
              | (0x1c << 21)
              | (0x43L << 28)
              | (0x49L << 35)
              | (0x24L << 42)
              | (0x49L << 49));
      // 11964378330978735131
      assertReadVarint(
          bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01),
          (0x1b << 0)
              | (0x28 << 7)
              | (0x79 << 14)
              | (0x42 << 21)
              | (0x3bL << 28)
              | (0x56L << 35)
              | (0x00L << 42)
              | (0x05L << 49)
              | (0x26L << 56)
              | (0x01L << 63));
  
      // Failures
      assertReadVarintFailure(
          InvalidProtocolBufferException.malformedVarint(),
          bytes(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
      assertReadVarintFailure(InvalidProtocolBufferException.truncatedMessage(), bytes(0x80));
    }
  
    /**
     * Parses the given bytes using readRawLittleEndian32() and checks that the result matches the
     * given value.
     */
    private void assertReadLittleEndian32(byte[] data, int value) throws Exception {
      for (InputType inputType : InputType.values()) {
        // Try different block sizes.
        for (int blockSize = 1; blockSize <= 16; blockSize *= 2) {
          CodedInputStream input = inputType.newDecoder(data, blockSize);
          assertEquals(inputType.name(), value, input.readRawLittleEndian32());
          assertTrue(inputType.name(), input.isAtEnd());
        }
      }
    }
  
    /**
     * Parses the given bytes using readRawLittleEndian64() and checks that the result matches the
     * given value.
     */
    private void assertReadLittleEndian64(byte[] data, long value) throws Exception {
      for (InputType inputType : InputType.values()) {
        // Try different block sizes.
        for (int blockSize = 1; blockSize <= 16; blockSize *= 2) {
          CodedInputStream input = inputType.newDecoder(data, blockSize);
          assertEquals(inputType.name(), value, input.readRawLittleEndian64());
          assertTrue(inputType.name(), input.isAtEnd());
        }
      }
    }
  
    /** Tests readRawLittleEndian32() and readRawLittleEndian64(). */
    public void testReadLittleEndian() throws Exception {
      assertReadLittleEndian32(bytes(0x78, 0x56, 0x34, 0x12), 0x12345678);
      assertReadLittleEndian32(bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0);
  
      assertReadLittleEndian64(
          bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12), 0x123456789abcdef0L);
      assertReadLittleEndian64(
          bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef012345678L);
    }
  
    /** Test decodeZigZag32() and decodeZigZag64(). */
    public void testDecodeZigZag() throws Exception {
      assertEquals(0, CodedInputStream.decodeZigZag32(0));
      assertEquals(-1, CodedInputStream.decodeZigZag32(1));
      assertEquals(1, CodedInputStream.decodeZigZag32(2));
      assertEquals(-2, CodedInputStream.decodeZigZag32(3));
      assertEquals(0x3FFFFFFF, CodedInputStream.decodeZigZag32(0x7FFFFFFE));
      assertEquals(0xC0000000, CodedInputStream.decodeZigZag32(0x7FFFFFFF));
      assertEquals(0x7FFFFFFF, CodedInputStream.decodeZigZag32(0xFFFFFFFE));
      assertEquals(0x80000000, CodedInputStream.decodeZigZag32(0xFFFFFFFF));
  
      assertEquals(0, CodedInputStream.decodeZigZag64(0));
      assertEquals(-1, CodedInputStream.decodeZigZag64(1));
      assertEquals(1, CodedInputStream.decodeZigZag64(2));
      assertEquals(-2, CodedInputStream.decodeZigZag64(3));
      assertEquals(0x000000003FFFFFFFL, CodedInputStream.decodeZigZag64(0x000000007FFFFFFEL));
      assertEquals(0xFFFFFFFFC0000000L, CodedInputStream.decodeZigZag64(0x000000007FFFFFFFL));
      assertEquals(0x000000007FFFFFFFL, CodedInputStream.decodeZigZag64(0x00000000FFFFFFFEL));
      assertEquals(0xFFFFFFFF80000000L, CodedInputStream.decodeZigZag64(0x00000000FFFFFFFFL));
      assertEquals(0x7FFFFFFFFFFFFFFFL, CodedInputStream.decodeZigZag64(0xFFFFFFFFFFFFFFFEL));
      assertEquals(0x8000000000000000L, CodedInputStream.decodeZigZag64(0xFFFFFFFFFFFFFFFFL));
    }
  
    /** Tests reading and parsing a whole message with every field type. */
    public void testReadWholeMessage() throws Exception {
      TestAllTypes message = TestUtil.getAllSet();
  
      byte[] rawBytes = message.toByteArray();
      assertEquals(rawBytes.length, message.getSerializedSize());
  
      for (InputType inputType : InputType.values()) {
        // Try different block sizes.
        for (int blockSize = 1; blockSize < 256; blockSize *= 2) {
          TestAllTypes message2 = TestAllTypes.parseFrom(inputType.newDecoder(rawBytes, blockSize));
          TestUtil.assertAllFieldsSet(message2);
        }
      }
    }
  
    /** Tests skipField(). */
    public void testSkipWholeMessage() throws Exception {
      TestAllTypes message = TestUtil.getAllSet();
      byte[] rawBytes = message.toByteArray();
  
      InputType[] inputTypes = InputType.values();
      CodedInputStream[] inputs = new CodedInputStream[inputTypes.length];
      for (int i = 0; i < inputs.length; ++i) {
        inputs[i] = inputTypes[i].newDecoder(rawBytes);
      }
      UnknownFieldSet.Builder unknownFields = UnknownFieldSet.newBuilder();
  
      while (true) {
        CodedInputStream input1 = inputs[0];
        int tag = input1.readTag();
        // Ensure that the rest match.
        for (int i = 1; i < inputs.length; ++i) {
          assertEquals(inputTypes[i].name(), tag, inputs[i].readTag());
        }
        if (tag == 0) {
          break;
        }
        unknownFields.mergeFieldFrom(tag, input1);
        // Skip the field for the rest of the inputs.
        for (int i = 1; i < inputs.length; ++i) {
          inputs[i].skipField(tag);
        }
      }
    }
  
  
    /**
     * Test that a bug in skipRawBytes() has been fixed: if the skip skips exactly up to a limit, this
     * should not break things.
     */
    public void testSkipRawBytesBug() throws Exception {
      byte[] rawBytes = new byte[] {1, 2};
      for (InputType inputType : InputType.values()) {
        CodedInputStream input = inputType.newDecoder(rawBytes);
        int limit = input.pushLimit(1);
        input.skipRawBytes(1);
        input.popLimit(limit);
        assertEquals(inputType.name(), 2, input.readRawByte());
      }
    }
  
    /**
     * Test that a bug in skipRawBytes() has been fixed: if the skip skips past the end of a buffer
     * with a limit that has been set past the end of that buffer, this should not break things.
     */
    public void testSkipRawBytesPastEndOfBufferWithLimit() throws Exception {
      byte[] rawBytes = new byte[] {1, 2, 3, 4, 5};
      for (InputType inputType : InputType.values()) {
        CodedInputStream input = inputType.newDecoder(rawBytes);
        int limit = input.pushLimit(4);
        // In order to expose the bug we need to read at least one byte to prime the
        // buffer inside the CodedInputStream.
        assertEquals(inputType.name(), 1, input.readRawByte());
        // Skip to the end of the limit.
        input.skipRawBytes(3);
        assertTrue(inputType.name(), input.isAtEnd());
        input.popLimit(limit);
        assertEquals(inputType.name(), 5, input.readRawByte());
      }
    }
  
    public void testReadHugeBlob() throws Exception {
      // Allocate and initialize a 1MB blob.
      byte[] blob = new byte[1 << 20];
      for (int i = 0; i < blob.length; i++) {
        blob[i] = (byte) i;
      }
  
      // Make a message containing it.
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      TestUtil.setAllFields(builder);
      builder.setOptionalBytes(ByteString.copyFrom(blob));
      TestAllTypes message = builder.build();
  
      byte[] data = message.toByteArray();
      for (InputType inputType : InputType.values()) {
        // Serialize and parse it.  Make sure to parse from an InputStream, not
        // directly from a ByteString, so that CodedInputStream uses buffered
        // reading.
        TestAllTypes message2 = TestAllTypes.parseFrom(inputType.newDecoder(data));
  
        assertEquals(inputType.name(), message.getOptionalBytes(), message2.getOptionalBytes());
  
        // Make sure all the other fields were parsed correctly.
        TestAllTypes message3 =
            TestAllTypes.newBuilder(message2)
                .setOptionalBytes(TestUtil.getAllSet().getOptionalBytes())
                .build();
        TestUtil.assertAllFieldsSet(message3);
      }
    }
  
    public void testReadMaliciouslyLargeBlob() throws Exception {
      ByteString.Output rawOutput = ByteString.newOutput();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
  
      int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      output.writeRawVarint32(tag);
      output.writeRawVarint32(0x7FFFFFFF);
      output.writeRawBytes(new byte[32]); // Pad with a few random bytes.
      output.flush();
  
      byte[] data = rawOutput.toByteString().toByteArray();
      for (InputType inputType : InputType.values()) {
        CodedInputStream input = inputType.newDecoder(data);
        assertEquals(tag, input.readTag());
        try {
          input.readBytes();
          fail(inputType.name() + ": Should have thrown an exception!");
        } catch (InvalidProtocolBufferException e) {
          // success.
        }
      }
    }
  
    /**
     * Test we can do messages that are up to CodedInputStream#DEFAULT_SIZE_LIMIT
     * in size (2G or Integer#MAX_SIZE).
     * @throws IOException
     */
    public void testParseMessagesCloseTo2G() throws IOException {
      byte[] serializedMessage = getBigSerializedMessage();
      // How many of these big messages do we need to take us near our 2G limit?
      int count = Integer.MAX_VALUE / serializedMessage.length;
      // Now make an inputstream that will fake a near 2G message of messages
      // returning our big serialized message 'count' times.
      InputStream is = new RepeatingInputStream(serializedMessage, count);
      // Parse should succeed!
      TestAllTypes.parseFrom(is);
    }
  
    /**
     * Test there is an exception if a message exceeds
     * CodedInputStream#DEFAULT_SIZE_LIMIT in size (2G or Integer#MAX_SIZE).
     * @throws IOException
     */
    public void testParseMessagesOver2G() throws IOException {
      byte[] serializedMessage = getBigSerializedMessage();
      // How many of these big messages do we need to take us near our 2G limit?
      int count = Integer.MAX_VALUE / serializedMessage.length;
      // Now add one to take us over the limit
      count++;
      // Now make an inputstream that will fake a near 2G message of messages
      // returning our big serialized message 'count' times.
      InputStream is = new RepeatingInputStream(serializedMessage, count);
      try {
        TestAllTypes.parseFrom(is);
        fail("Should have thrown an exception!");
      } catch (InvalidProtocolBufferException e) {
        assertTrue(e.getMessage().contains("too large"));
      }
    }
  
    /*
     * @return A serialized big message.
     */
    private static byte[] getBigSerializedMessage() {
      byte[] value = new byte[16 * 1024 * 1024]; 
      ByteString bsValue = ByteString.wrap(value);
      return TestAllTypes.newBuilder().setOptionalBytes(bsValue).build().toByteArray();
    }
  
    /*
     * An input stream that repeats a byte arrays' content a number of times.
     * Simulates really large input without consuming loads of memory. Used above
     * to test the parsing behavior when the input size exceeds 2G or close to it.
     */
    private static class RepeatingInputStream extends InputStream {
      private final byte[] serializedMessage;
      private final int count;
      private int index = 0;
      private int offset = 0;
  
      RepeatingInputStream(byte[] serializedMessage, int count) {
        this.serializedMessage = serializedMessage;
        this.count = count;
      }
  
      @Override
      public int read() throws IOException {
        if (this.offset == this.serializedMessage.length) {
          this.index++;
          this.offset = 0;
        }
        if (this.index == this.count) {
          return -1;
        }
        return this.serializedMessage[offset++];
      }
    }
  
    private TestRecursiveMessage makeRecursiveMessage(int depth) {
      if (depth == 0) {
        return TestRecursiveMessage.newBuilder().setI(5).build();
      } else {
        return TestRecursiveMessage.newBuilder().setA(makeRecursiveMessage(depth - 1)).build();
      }
    }
  
    private void assertMessageDepth(String msg, TestRecursiveMessage message, int depth) {
      if (depth == 0) {
        assertFalse(msg, message.hasA());
        assertEquals(msg, 5, message.getI());
      } else {
        assertTrue(msg, message.hasA());
        assertMessageDepth(msg, message.getA(), depth - 1);
      }
    }
  
    public void testMaliciousRecursion() throws Exception {
      byte[] data100 = makeRecursiveMessage(100).toByteArray();
      byte[] data101 = makeRecursiveMessage(101).toByteArray();
  
      for (InputType inputType : InputType.values()) {
        assertMessageDepth(
            inputType.name(), TestRecursiveMessage.parseFrom(inputType.newDecoder(data100)), 100);
  
        try {
          TestRecursiveMessage.parseFrom(inputType.newDecoder(data101));
          fail("Should have thrown an exception!");
        } catch (InvalidProtocolBufferException e) {
          // success.
        }
  
        CodedInputStream input = inputType.newDecoder(data100);
        input.setRecursionLimit(8);
        try {
          TestRecursiveMessage.parseFrom(input);
          fail(inputType.name() + ": Should have thrown an exception!");
        } catch (InvalidProtocolBufferException e) {
          // success.
        }
      }
    }
  
    private void checkSizeLimitExceeded(InvalidProtocolBufferException e) {
      assertEquals(InvalidProtocolBufferException.sizeLimitExceeded().getMessage(), e.getMessage());
    }
  
    public void testSizeLimit() throws Exception {
      // NOTE: Size limit only applies to the stream-backed CIS.
      CodedInputStream input =
          CodedInputStream.newInstance(
              new SmallBlockInputStream(TestUtil.getAllSet().toByteArray(), 16));
      input.setSizeLimit(16);
  
      try {
        TestAllTypes.parseFrom(input);
        fail("Should have thrown an exception!");
      } catch (InvalidProtocolBufferException expected) {
        checkSizeLimitExceeded(expected);
      }
    }
  
    public void testResetSizeCounter() throws Exception {
      // NOTE: Size limit only applies to the stream-backed CIS.
      CodedInputStream input =
          CodedInputStream.newInstance(new SmallBlockInputStream(new byte[256], 8));
      input.setSizeLimit(16);
      input.readRawBytes(16);
      assertEquals(16, input.getTotalBytesRead());
  
      try {
        input.readRawByte();
        fail("Should have thrown an exception!");
      } catch (InvalidProtocolBufferException expected) {
        checkSizeLimitExceeded(expected);
      }
  
      input.resetSizeCounter();
      assertEquals(0, input.getTotalBytesRead());
      input.readRawByte(); // No exception thrown.
      input.resetSizeCounter();
      assertEquals(0, input.getTotalBytesRead());
      input.readRawBytes(16);
      assertEquals(16, input.getTotalBytesRead());
      input.resetSizeCounter();
  
      try {
        input.readRawBytes(17); // Hits limit again.
        fail("Should have thrown an exception!");
      } catch (InvalidProtocolBufferException expected) {
        checkSizeLimitExceeded(expected);
      }
    }
    
    public void testRefillBufferWithCorrectSize() throws Exception {
      // NOTE: refillBuffer only applies to the stream-backed CIS.
      byte[] bytes = "123456789".getBytes("UTF-8");
      ByteArrayOutputStream rawOutput = new ByteArrayOutputStream();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput, bytes.length);
  
      int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      output.writeRawVarint32(tag);
      output.writeRawVarint32(bytes.length);
      output.writeRawBytes(bytes);
      output.writeRawVarint32(tag);
      output.writeRawVarint32(bytes.length);
      output.writeRawBytes(bytes);
      output.writeRawByte(4);
      output.flush();
  
      // Input is two string with length 9 and one raw byte.
      byte[] rawInput = rawOutput.toByteArray(); 
      for (int inputStreamBufferLength = 8; 
          inputStreamBufferLength <= rawInput.length + 1; inputStreamBufferLength++) {
        CodedInputStream input = CodedInputStream.newInstance(
                new ByteArrayInputStream(rawInput), inputStreamBufferLength);
        input.setSizeLimit(rawInput.length - 1);
        input.readString();
        input.readString(); 
        try {
          input.readRawByte(); // Hits limit.
          fail("Should have thrown an exception!");
        } catch (InvalidProtocolBufferException expected) {
          checkSizeLimitExceeded(expected);
        }
      }
    }
  
    public void testIsAtEnd() throws Exception {
      CodedInputStream input = CodedInputStream.newInstance(
          new ByteArrayInputStream(new byte[5]));
      try {   
        for (int i = 0; i < 5; i++) {
          assertEquals(false, input.isAtEnd());
          input.readRawByte();
        }
        assertEquals(true, input.isAtEnd());
      } catch (Exception e) {
        fail("Catch exception in the testIsAtEnd");
      }
    }
  
    public void testCurrentLimitExceeded() throws Exception {
      byte[] bytes = "123456789".getBytes("UTF-8");
      ByteArrayOutputStream rawOutput = new ByteArrayOutputStream();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput, bytes.length);
  
      int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      output.writeRawVarint32(tag);
      output.writeRawVarint32(bytes.length);
      output.writeRawBytes(bytes);
      output.flush();
  
      byte[] rawInput = rawOutput.toByteArray();
      CodedInputStream input = CodedInputStream.newInstance(
                new ByteArrayInputStream(rawInput));
      // The length of the whole rawInput
      input.setSizeLimit(11);
      // Some number that is smaller than the rawInput's length
      // but larger than 2 
      input.pushLimit(5);
      try {
        input.readString();
        fail("Should have thrown an exception");
      } catch (InvalidProtocolBufferException expected) {
        assertEquals(expected.getMessage(), 
            InvalidProtocolBufferException.truncatedMessage().getMessage());
      }
    }
  
    public void testSizeLimitMultipleMessages() throws Exception {
      // NOTE: Size limit only applies to the stream-backed CIS.
      byte[] bytes = new byte[256];
      for (int i = 0; i < bytes.length; i++) {
        bytes[i] = (byte) i;
      }
      CodedInputStream input = CodedInputStream.newInstance(new SmallBlockInputStream(bytes, 7));
      input.setSizeLimit(16);
      for (int i = 0; i < 256 / 16; i++) {
        byte[] message = input.readRawBytes(16);
        for (int j = 0; j < message.length; j++) {
          assertEquals(i * 16 + j, message[j] & 0xff);
        }
        assertEquals(16, input.getTotalBytesRead());
        input.resetSizeCounter();
        assertEquals(0, input.getTotalBytesRead());
      }
    }
  
    public void testReadString() throws Exception {
      String lorem = "Lorem ipsum dolor sit amet ";
      StringBuilder builder = new StringBuilder();
      for (int i = 0; i < 4096; i += lorem.length()) {
        builder.append(lorem);
      }
      lorem = builder.toString().substring(0, 4096);
      byte[] bytes = lorem.getBytes("UTF-8");
      ByteString.Output rawOutput = ByteString.newOutput();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput, bytes.length);
  
      int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      output.writeRawVarint32(tag);
      output.writeRawVarint32(bytes.length);
      output.writeRawBytes(bytes);
      output.flush();
  
      byte[] rawInput = rawOutput.toByteString().toByteArray();
      for (InputType inputType : InputType.values()) {
        CodedInputStream input = inputType.newDecoder(rawInput);
        assertEquals(inputType.name(), tag, input.readTag());
        String text = input.readString();
        assertEquals(inputType.name(), lorem, text);
      }
    }
  
    public void testReadStringRequireUtf8() throws Exception {
      String lorem = "Lorem ipsum dolor sit amet ";
      StringBuilder builder = new StringBuilder();
      for (int i = 0; i < 4096; i += lorem.length()) {
        builder.append(lorem);
      }
      lorem = builder.toString().substring(0, 4096);
      byte[] bytes = lorem.getBytes("UTF-8");
      ByteString.Output rawOutput = ByteString.newOutput();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput, bytes.length);
  
      int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      output.writeRawVarint32(tag);
      output.writeRawVarint32(bytes.length);
      output.writeRawBytes(bytes);
      output.flush();
  
      byte[] rawInput = rawOutput.toByteString().toByteArray();
      for (InputType inputType : InputType.values()) {
        CodedInputStream input = inputType.newDecoder(rawInput);
        assertEquals(inputType.name(), tag, input.readTag());
        String text = input.readStringRequireUtf8();
        assertEquals(inputType.name(), lorem, text);
      }
    }
  
    /**
     * Tests that if we readString invalid UTF-8 bytes, no exception is thrown. Instead, the invalid
     * bytes are replaced with the Unicode "replacement character" U+FFFD.
     */
    public void testReadStringInvalidUtf8() throws Exception {
      ByteString.Output rawOutput = ByteString.newOutput();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
  
      int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      output.writeRawVarint32(tag);
      output.writeRawVarint32(1);
      output.writeRawBytes(new byte[] {(byte) 0x80});
      output.flush();
  
      byte[] rawInput = rawOutput.toByteString().toByteArray();
      for (InputType inputType : InputType.values()) {
        CodedInputStream input = inputType.newDecoder(rawInput);
        assertEquals(inputType.name(), tag, input.readTag());
        String text = input.readString();
        assertEquals(inputType.name(), 0xfffd, text.charAt(0));
      }
    }
  
    /**
     * Tests that if we readStringRequireUtf8 invalid UTF-8 bytes, an InvalidProtocolBufferException
     * is thrown.
     */
    public void testReadStringRequireUtf8InvalidUtf8() throws Exception {
      ByteString.Output rawOutput = ByteString.newOutput();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
  
      int tag = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
      output.writeRawVarint32(tag);
      output.writeRawVarint32(1);
      output.writeRawBytes(new byte[] {(byte) 0x80});
      output.flush();
  
      byte[] rawInput = rawOutput.toByteString().toByteArray();
      for (InputType inputType : InputType.values()) {
        CodedInputStream input = inputType.newDecoder(rawInput);
        assertEquals(tag, input.readTag());
        try {
          input.readStringRequireUtf8();
          fail(inputType.name() + ": Expected invalid UTF-8 exception.");
        } catch (InvalidProtocolBufferException exception) {
          assertEquals(
              inputType.name(), "Protocol message had invalid UTF-8.", exception.getMessage());
        }
      }
    }
  
    public void testReadFromSlice() throws Exception {
      byte[] bytes = bytes(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
      CodedInputStream in = CodedInputStream.newInstance(bytes, 3, 5);
      assertEquals(0, in.getTotalBytesRead());
      for (int i = 3; i < 8; i++) {
        assertEquals(i, in.readRawByte());
        assertEquals(i - 2, in.getTotalBytesRead());
      }
      // eof
      assertEquals(0, in.readTag());
      assertEquals(5, in.getTotalBytesRead());
    }
  
    public void testInvalidTag() throws Exception {
      // Any tag number which corresponds to field number zero is invalid and
      // should throw InvalidProtocolBufferException.
      for (InputType inputType : InputType.values()) {
        for (int i = 0; i < 8; i++) {
          try {
            inputType.newDecoder(bytes(i)).readTag();
            fail(inputType.name() + ": Should have thrown an exception.");
          } catch (InvalidProtocolBufferException e) {
            assertEquals(
                inputType.name(),
                InvalidProtocolBufferException.invalidTag().getMessage(),
                e.getMessage());
          }
        }
      }
    }
  
    public void testReadByteArray() throws Exception {
      ByteString.Output rawOutput = ByteString.newOutput();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
      // Zero-sized bytes field.
      output.writeRawVarint32(0);
      // One one-byte bytes field
      output.writeRawVarint32(1);
      output.writeRawBytes(new byte[] {(byte) 23});
      // Another one-byte bytes field
      output.writeRawVarint32(1);
      output.writeRawBytes(new byte[] {(byte) 45});
      // A bytes field large enough that won't fit into the 4K buffer.
      final int bytesLength = 16 * 1024;
      byte[] bytes = new byte[bytesLength];
      bytes[0] = (byte) 67;
      bytes[bytesLength - 1] = (byte) 89;
      output.writeRawVarint32(bytesLength);
      output.writeRawBytes(bytes);
  
      output.flush();
  
      byte[] rawInput = rawOutput.toByteString().toByteArray();
      for (InputType inputType : InputType.values()) {
        CodedInputStream inputStream = inputType.newDecoder(rawInput);
  
        byte[] result = inputStream.readByteArray();
        assertEquals(inputType.name(), 0, result.length);
        result = inputStream.readByteArray();
        assertEquals(inputType.name(), 1, result.length);
        assertEquals(inputType.name(), (byte) 23, result[0]);
        result = inputStream.readByteArray();
        assertEquals(inputType.name(), 1, result.length);
        assertEquals(inputType.name(), (byte) 45, result[0]);
        result = inputStream.readByteArray();
        assertEquals(inputType.name(), bytesLength, result.length);
        assertEquals(inputType.name(), (byte) 67, result[0]);
        assertEquals(inputType.name(), (byte) 89, result[bytesLength - 1]);
      }
    }
  
    public void testReadLargeByteStringFromInputStream() throws Exception {
      byte[] bytes = new byte[1024 * 1024];
      for (int i = 0; i < bytes.length; i++) {
        bytes[i] = (byte) (i & 0xFF);
      }
      ByteString.Output rawOutput = ByteString.newOutput();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
      output.writeRawVarint32(bytes.length);
      output.writeRawBytes(bytes);
      output.flush();
      byte[] data = rawOutput.toByteString().toByteArray();
  
      CodedInputStream input = CodedInputStream.newInstance(
          new ByteArrayInputStream(data) {
            @Override
            public synchronized int available() {
              return 0;
            }
          });
      ByteString result = input.readBytes();
      assertEquals(ByteString.copyFrom(bytes), result);
    }
  
    public void testReadLargeByteArrayFromInputStream() throws Exception {
      byte[] bytes = new byte[1024 * 1024];
      for (int i = 0; i < bytes.length; i++) {
        bytes[i] = (byte) (i & 0xFF);
      }
      ByteString.Output rawOutput = ByteString.newOutput();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
      output.writeRawVarint32(bytes.length);
      output.writeRawBytes(bytes);
      output.flush();
      byte[] data = rawOutput.toByteString().toByteArray();
  
      CodedInputStream input = CodedInputStream.newInstance(
          new ByteArrayInputStream(data) {
            @Override
            public synchronized int available() {
              return 0;
            }
          });
      byte[] result = input.readByteArray();
      assertTrue(Arrays.equals(bytes, result));
    }
  
    public void testReadByteBuffer() throws Exception {
      ByteString.Output rawOutput = ByteString.newOutput();
      CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
      // Zero-sized bytes field.
      output.writeRawVarint32(0);
      // One one-byte bytes field
      output.writeRawVarint32(1);
      output.writeRawBytes(new byte[] {(byte) 23});
      // Another one-byte bytes field
      output.writeRawVarint32(1);
      output.writeRawBytes(new byte[] {(byte) 45});
      // A bytes field large enough that won't fit into the 4K buffer.
      final int bytesLength = 16 * 1024;
      byte[] bytes = new byte[bytesLength];
      bytes[0] = (byte) 67;
      bytes[bytesLength - 1] = (byte) 89;
      output.writeRawVarint32(bytesLength);
      output.writeRawBytes(bytes);
  
      output.flush();
  
      byte[] rawInput = rawOutput.toByteString().toByteArray();
      for (InputType inputType : InputType.values()) {
        CodedInputStream inputStream = inputType.newDecoder(rawInput);
  
        ByteBuffer result = inputStream.readByteBuffer();
        assertEquals(inputType.name(), 0, result.capacity());
        result = inputStream.readByteBuffer();
        assertEquals(inputType.name(), 1, result.capacity());
        assertEquals(inputType.name(), (byte) 23, result.get());
        result = inputStream.readByteBuffer();
        assertEquals(inputType.name(), 1, result.capacity());
        assertEquals(inputType.name(), (byte) 45, result.get());
        result = inputStream.readByteBuffer();
        assertEquals(inputType.name(), bytesLength, result.capacity());
        assertEquals(inputType.name(), (byte) 67, result.get());
        result.position(bytesLength - 1);
        assertEquals(inputType.name(), (byte) 89, result.get());
      }
    }
  
    public void testReadByteBufferAliasing() throws Exception {
      ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
      CodedOutputStream output = CodedOutputStream.newInstance(byteArrayStream);
      // Zero-sized bytes field.
      output.writeRawVarint32(0);
      // One one-byte bytes field
      output.writeRawVarint32(1);
      output.writeRawBytes(new byte[] {(byte) 23});
      // Another one-byte bytes field
      output.writeRawVarint32(1);
      output.writeRawBytes(new byte[] {(byte) 45});
      // A bytes field large enough that won't fit into the 4K buffer.
      final int bytesLength = 16 * 1024;
      byte[] bytes = new byte[bytesLength];
      bytes[0] = (byte) 67;
      bytes[bytesLength - 1] = (byte) 89;
      output.writeRawVarint32(bytesLength);
      output.writeRawBytes(bytes);
      output.flush();
  
      byte[] data = byteArrayStream.toByteArray();
  
      for (InputType inputType : InputType.values()) {
        if (inputType == InputType.STREAM 
            || inputType == InputType.STREAM_ITER_DIRECT 
            || inputType == InputType.ITER_DIRECT) {
          // Aliasing doesn't apply to stream-backed CIS.
          continue;
        }
  
        // Without aliasing
        CodedInputStream inputStream = inputType.newDecoder(data);
        ByteBuffer result = inputStream.readByteBuffer();
        assertEquals(inputType.name(), 0, result.capacity());
        result = inputStream.readByteBuffer();
        assertTrue(inputType.name(), result.array() != data);
        assertEquals(inputType.name(), 1, result.capacity());
        assertEquals(inputType.name(), (byte) 23, result.get());
        result = inputStream.readByteBuffer();
        assertTrue(inputType.name(), result.array() != data);
        assertEquals(inputType.name(), 1, result.capacity());
        assertEquals(inputType.name(), (byte) 45, result.get());
        result = inputStream.readByteBuffer();
        assertTrue(inputType.name(), result.array() != data);
        assertEquals(inputType.name(), bytesLength, result.capacity());
        assertEquals(inputType.name(), (byte) 67, result.get());
        result.position(bytesLength - 1);
        assertEquals(inputType.name(), (byte) 89, result.get());
  
        // Enable aliasing
        inputStream = inputType.newDecoder(data, data.length);
        inputStream.enableAliasing(true);
        result = inputStream.readByteBuffer();
        assertEquals(inputType.name(), 0, result.capacity());
        result = inputStream.readByteBuffer();
        if (result.hasArray()) {
          assertTrue(inputType.name(), result.array() == data);
        }
        assertEquals(inputType.name(), 1, result.capacity());
        assertEquals(inputType.name(), (byte) 23, result.get());
        result = inputStream.readByteBuffer();
        if (result.hasArray()) {
          assertTrue(inputType.name(), result.array() == data);
        }
        assertEquals(inputType.name(), 1, result.capacity());
        assertEquals(inputType.name(), (byte) 45, result.get());
        result = inputStream.readByteBuffer();
        if (result.hasArray()) {
          assertTrue(inputType.name(), result.array() == data);
        }
        assertEquals(inputType.name(), bytesLength, result.capacity());
        assertEquals(inputType.name(), (byte) 67, result.get());
        result.position(bytesLength - 1);
        assertEquals(inputType.name(), (byte) 89, result.get());
      }
    }
  
    public void testCompatibleTypes() throws Exception {
      long data = 0x100000000L;
      Int64Message message = Int64Message.newBuilder().setData(data).build();
      byte[] serialized = message.toByteArray();
      for (InputType inputType : InputType.values()) {
        CodedInputStream inputStream = inputType.newDecoder(serialized);
  
        // Test int64(long) is compatible with bool(boolean)
        BoolMessage msg2 = BoolMessage.parseFrom(inputStream);
        assertTrue(msg2.getData());
  
        // Test int64(long) is compatible with int32(int)
        inputStream = inputType.newDecoder(serialized);
        Int32Message msg3 = Int32Message.parseFrom(inputStream);
        assertEquals((int) data, msg3.getData());
      }
    }
  
    public void testSkipInvalidVarint_FastPath() throws Exception {
      // Fast path: We have >= 10 bytes available. Ensure we properly recognize a non-ending varint.
      byte[] data = new byte[] {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0};
      for (InputType inputType : InputType.values()) {
        try {
          CodedInputStream input = inputType.newDecoder(data);
          input.skipField(WireFormat.makeTag(1, WireFormat.WIRETYPE_VARINT));
          fail(inputType.name() + ": Should have thrown an exception.");
        } catch (InvalidProtocolBufferException e) {
          // Expected
        }
      }
    }
  
    public void testSkipInvalidVarint_SlowPath() throws Exception {
      // Slow path: < 10 bytes available. Ensure we properly recognize a non-ending varint.
      byte[] data = new byte[] {-1, -1, -1, -1, -1, -1, -1, -1, -1};
      for (InputType inputType : InputType.values()) {
        try {
          CodedInputStream input = inputType.newDecoder(data);
          input.skipField(WireFormat.makeTag(1, WireFormat.WIRETYPE_VARINT));
          fail(inputType.name() + ": Should have thrown an exception.");
        } catch (InvalidProtocolBufferException e) {
          // Expected
        }
      }
    }
  }
  