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 static com.google.protobuf.Internal.EMPTY_BYTE_ARRAY;
  import static com.google.protobuf.Internal.EMPTY_BYTE_BUFFER;
  import static com.google.protobuf.Internal.UTF_8;
  import static com.google.protobuf.Internal.checkNotNull;
  import static com.google.protobuf.WireFormat.FIXED32_SIZE;
  import static com.google.protobuf.WireFormat.FIXED64_SIZE;
  import static com.google.protobuf.WireFormat.MAX_VARINT_SIZE;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.ByteBuffer;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Iterator;
  import java.util.List;
  
  /**
   * Reads and decodes protocol message fields.
   *
   * <p>This class contains two kinds of methods: methods that read specific protocol message
   * constructs and field types (e.g. {@link #readTag()} and {@link #readInt32()}) and methods that
   * read low-level values (e.g. {@link #readRawVarint32()} and {@link #readRawBytes}). If you are
   * reading encoded protocol messages, you should use the former methods, but if you are reading some
   * other format of your own design, use the latter.
   *
   * @author kenton@google.com Kenton Varda
   */
  public abstract class CodedInputStream {
    private static final int DEFAULT_BUFFER_SIZE = 4096;
    private static final int DEFAULT_RECURSION_LIMIT = 100;
    // Integer.MAX_VALUE == 0x7FFFFFF == INT_MAX from limits.h
    private static final int DEFAULT_SIZE_LIMIT = Integer.MAX_VALUE;
  
    /**
     * Whether to enable our custom UTF-8 decode codepath which does not use {@link StringCoding}.
     * Enabled by default, disable by setting
     * {@code -Dcom.google.protobuf.enableCustomutf8Decode=false} in JVM args.
     */
    private static final boolean ENABLE_CUSTOM_UTF8_DECODE
        = !"false".equals(System.getProperty("com.google.protobuf.enableCustomUtf8Decode"));
  
    /** Visible for subclasses. See setRecursionLimit() */
    int recursionDepth;
  
    int recursionLimit = DEFAULT_RECURSION_LIMIT;
  
    /** Visible for subclasses. See setSizeLimit() */
    int sizeLimit = DEFAULT_SIZE_LIMIT;
  
    /** Create a new CodedInputStream wrapping the given InputStream. */
    public static CodedInputStream newInstance(final InputStream input) {
      return newInstance(input, DEFAULT_BUFFER_SIZE);
    }
  
    /** Create a new CodedInputStream wrapping the given InputStream. */
    static CodedInputStream newInstance(final InputStream input, int bufferSize) {
      if (input == null) {
        // TODO(nathanmittler): Ideally we should throw here. This is done for backward compatibility.
        return newInstance(EMPTY_BYTE_ARRAY);
      }
      return new StreamDecoder(input, bufferSize);
    }
  
    /** Create a new CodedInputStream wrapping the given {@code Iterable <ByteBuffer>}. */
    public static CodedInputStream newInstance(final Iterable<ByteBuffer> input) {
      if (!UnsafeDirectNioDecoder.isSupported()) {
        return newInstance(new IterableByteBufferInputStream(input));
      }
      return newInstance(input, false);
    }
  
    /** Create a new CodedInputStream wrapping the given {@code Iterable <ByteBuffer>}. */
    static CodedInputStream newInstance(
        final Iterable<ByteBuffer> bufs, final boolean bufferIsImmutable) {
      // flag is to check the type of input's ByteBuffers.
      // flag equals 1: all ByteBuffers have array.
      // flag equals 2: all ByteBuffers are direct ByteBuffers.
      // flag equals 3: some ByteBuffers are direct and some have array.
      // flag greater than 3: other cases.
      int flag = 0;
      // Total size of the input
      int totalSize = 0;
      for (ByteBuffer buf : bufs) {
        totalSize += buf.remaining();
        if (buf.hasArray()) {
          flag |= 1;
        } else if (buf.isDirect()) {
          flag |= 2;
        } else {
          flag |= 4;
        }
      }
      if (flag == 2) {
        return new IterableDirectByteBufferDecoder(bufs, totalSize, bufferIsImmutable);
      } else {
        // TODO(yilunchong): add another decoders to deal case 1 and 3.
        return newInstance(new IterableByteBufferInputStream(bufs));
      }
    }
  
    /** Create a new CodedInputStream wrapping the given byte array. */
    public static CodedInputStream newInstance(final byte[] buf) {
      return newInstance(buf, 0, buf.length);
    }
  
    /** Create a new CodedInputStream wrapping the given byte array slice. */
    public static CodedInputStream newInstance(final byte[] buf, final int off, final int len) {
      return newInstance(buf, off, len, false /* bufferIsImmutable */);
    }
  
    /** Create a new CodedInputStream wrapping the given byte array slice. */
    static CodedInputStream newInstance(
        final byte[] buf, final int off, final int len, final boolean bufferIsImmutable) {
      ArrayDecoder result = new ArrayDecoder(buf, off, len, bufferIsImmutable);
      try {
        // Some uses of CodedInputStream can be more efficient if they know
        // exactly how many bytes are available.  By pushing the end point of the
        // buffer as a limit, we allow them to get this information via
        // getBytesUntilLimit().  Pushing a limit that we know is at the end of
        // the stream can never hurt, since we can never past that point anyway.
        result.pushLimit(len);
      } catch (InvalidProtocolBufferException ex) {
        // The only reason pushLimit() might throw an exception here is if len
        // is negative. Normally pushLimit()'s parameter comes directly off the
        // wire, so it's important to catch exceptions in case of corrupt or
        // malicious data. However, in this case, we expect that len is not a
        // user-supplied value, so we can assume that it being negative indicates
        // a programming error. Therefore, throwing an unchecked exception is
        // appropriate.
        throw new IllegalArgumentException(ex);
      }
      return result;
    }
  
    /**
     * Create a new CodedInputStream wrapping the given ByteBuffer. The data starting from the
     * ByteBuffer's current position to its limit will be read. The returned CodedInputStream may or
     * may not share the underlying data in the ByteBuffer, therefore the ByteBuffer cannot be changed
     * while the CodedInputStream is in use. Note that the ByteBuffer's position won't be changed by
     * this function. Concurrent calls with the same ByteBuffer object are safe if no other thread is
     * trying to alter the ByteBuffer's status.
     */
    public static CodedInputStream newInstance(ByteBuffer buf) {
      return newInstance(buf, false /* bufferIsImmutable */);
    }
  
    /** Create a new CodedInputStream wrapping the given buffer. */
    static CodedInputStream newInstance(ByteBuffer buf, boolean bufferIsImmutable) {
      if (buf.hasArray()) {
        return newInstance(
            buf.array(), buf.arrayOffset() + buf.position(), buf.remaining(), bufferIsImmutable);
      }
  
      if (buf.isDirect() && UnsafeDirectNioDecoder.isSupported()) {
        return new UnsafeDirectNioDecoder(buf, bufferIsImmutable);
      }
  
      // The buffer is non-direct and does not expose the underlying array. Using the ByteBuffer API
      // to access individual bytes is very slow, so just copy the buffer to an array.
      // TODO(nathanmittler): Re-evaluate with Java 9
      byte[] buffer = new byte[buf.remaining()];
      buf.duplicate().get(buffer);
      return newInstance(buffer, 0, buffer.length, true);
    }
  
    /** Disable construction/inheritance outside of this class. */
    private CodedInputStream() {}
  
    // -----------------------------------------------------------------
  
    /**
     * Attempt to read a field tag, returning zero if we have reached EOF. Protocol message parsers
     * use this to read tags, since a protocol message may legally end wherever a tag occurs, and zero
     * is not a valid tag number.
     */
    public abstract int readTag() throws IOException;
  
    /**
     * Verifies that the last call to readTag() returned the given tag value. This is used to verify
     * that a nested group ended with the correct end tag.
     *
     * @throws InvalidProtocolBufferException {@code value} does not match the last tag.
     */
    public abstract void checkLastTagWas(final int value) throws InvalidProtocolBufferException;
  
    public abstract int getLastTag();
  
    /**
     * Reads and discards a single field, given its tag value.
     *
     * @return {@code false} if the tag is an endgroup tag, in which case nothing is skipped.
     *     Otherwise, returns {@code true}.
     */
    public abstract boolean skipField(final int tag) throws IOException;
  
    /**
     * Reads a single field and writes it to output in wire format, given its tag value.
     *
     * @return {@code false} if the tag is an endgroup tag, in which case nothing is skipped.
     *     Otherwise, returns {@code true}.
     * @deprecated use {@code UnknownFieldSet} or {@code UnknownFieldSetLite} to skip to an output
     *     stream.
     */
    @Deprecated
    public abstract boolean skipField(final int tag, final CodedOutputStream output)
        throws IOException;
  
    /**
     * Reads and discards an entire message. This will read either until EOF or until an endgroup tag,
     * whichever comes first.
     */
    public abstract void skipMessage() throws IOException;
  
    /**
     * Reads an entire message and writes it to output in wire format. This will read either until EOF
     * or until an endgroup tag, whichever comes first.
     */
    public abstract void skipMessage(CodedOutputStream output) throws IOException;
  
  
    // -----------------------------------------------------------------
  
    /** Read a {@code double} field value from the stream. */
    public abstract double readDouble() throws IOException;
  
    /** Read a {@code float} field value from the stream. */
    public abstract float readFloat() throws IOException;
  
    /** Read a {@code uint64} field value from the stream. */
    public abstract long readUInt64() throws IOException;
  
    /** Read an {@code int64} field value from the stream. */
    public abstract long readInt64() throws IOException;
  
    /** Read an {@code int32} field value from the stream. */
    public abstract int readInt32() throws IOException;
  
    /** Read a {@code fixed64} field value from the stream. */
    public abstract long readFixed64() throws IOException;
  
    /** Read a {@code fixed32} field value from the stream. */
    public abstract int readFixed32() throws IOException;
  
    /** Read a {@code bool} field value from the stream. */
    public abstract boolean readBool() throws IOException;
  
    /**
     * Read a {@code string} field value from the stream. If the stream contains malformed UTF-8,
     * replace the offending bytes with the standard UTF-8 replacement character.
     */
    public abstract String readString() throws IOException;
  
    /**
     * Read a {@code string} field value from the stream. If the stream contains malformed UTF-8,
     * throw exception {@link InvalidProtocolBufferException}.
     */
    public abstract String readStringRequireUtf8() throws IOException;
  
    /** Read a {@code group} field value from the stream. */
    public abstract void readGroup(
        final int fieldNumber,
        final MessageLite.Builder builder,
        final ExtensionRegistryLite extensionRegistry)
        throws IOException;
  
  
    /** Read a {@code group} field value from the stream. */
    public abstract <T extends MessageLite> T readGroup(
        final int fieldNumber, final Parser<T> parser, final ExtensionRegistryLite extensionRegistry)
        throws IOException;
  
    /**
     * Reads a {@code group} field value from the stream and merges it into the given {@link
     * UnknownFieldSet}.
     *
     * @deprecated UnknownFieldSet.Builder now implements MessageLite.Builder, so you can just call
     *     {@link #readGroup}.
     */
    @Deprecated
    public abstract void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder)
        throws IOException;
  
    /** Read an embedded message field value from the stream. */
    public abstract void readMessage(
        final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry)
        throws IOException;
  
  
    /** Read an embedded message field value from the stream. */
    public abstract <T extends MessageLite> T readMessage(
        final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException;
  
    /** Read a {@code bytes} field value from the stream. */
    public abstract ByteString readBytes() throws IOException;
  
    /** Read a {@code bytes} field value from the stream. */
    public abstract byte[] readByteArray() throws IOException;
  
    /** Read a {@code bytes} field value from the stream. */
    public abstract ByteBuffer readByteBuffer() throws IOException;
  
    /** Read a {@code uint32} field value from the stream. */
    public abstract int readUInt32() throws IOException;
  
    /**
     * Read an enum field value from the stream. Caller is responsible for converting the numeric
     * value to an actual enum.
     */
    public abstract int readEnum() throws IOException;
  
    /** Read an {@code sfixed32} field value from the stream. */
    public abstract int readSFixed32() throws IOException;
  
    /** Read an {@code sfixed64} field value from the stream. */
    public abstract long readSFixed64() throws IOException;
  
    /** Read an {@code sint32} field value from the stream. */
    public abstract int readSInt32() throws IOException;
  
    /** Read an {@code sint64} field value from the stream. */
    public abstract long readSInt64() throws IOException;
  
    // =================================================================
  
    /** Read a raw Varint from the stream. If larger than 32 bits, discard the upper bits. */
    public abstract int readRawVarint32() throws IOException;
  
    /** Read a raw Varint from the stream. */
    public abstract long readRawVarint64() throws IOException;
  
    /** Variant of readRawVarint64 for when uncomfortably close to the limit. */
    /* Visible for testing */
    abstract long readRawVarint64SlowPath() throws IOException;
  
    /** Read a 32-bit little-endian integer from the stream. */
    public abstract int readRawLittleEndian32() throws IOException;
  
    /** Read a 64-bit little-endian integer from the stream. */
    public abstract long readRawLittleEndian64() throws IOException;
  
    // -----------------------------------------------------------------
  
    /**
     * Enables {@link ByteString} aliasing of the underlying buffer, trading off on buffer pinning for
     * data copies. Only valid for buffer-backed streams.
     */
    public abstract void enableAliasing(boolean enabled);
  
    /**
     * Set the maximum message recursion depth. In order to prevent malicious messages from causing
     * stack overflows, {@code CodedInputStream} limits how deeply messages may be nested. The default
     * limit is 64.
     *
     * @return the old limit.
     */
    public final int setRecursionLimit(final int limit) {
      if (limit < 0) {
        throw new IllegalArgumentException("Recursion limit cannot be negative: " + limit);
      }
      final int oldLimit = recursionLimit;
      recursionLimit = limit;
      return oldLimit;
    }
  
    /**
     * Only valid for {@link InputStream}-backed streams.
     *
     * <p>Set the maximum message size. In order to prevent malicious messages from exhausting memory
     * or causing integer overflows, {@code CodedInputStream} limits how large a message may be. The
     * default limit is {@code Integer.MAX_INT}. You should set this limit as small as you can without
     * harming your app's functionality. Note that size limits only apply when reading from an {@code
     * InputStream}, not when constructed around a raw byte array.
     *
     * <p>If you want to read several messages from a single CodedInputStream, you could call {@link
     * #resetSizeCounter()} after each one to avoid hitting the size limit.
     *
     * @return the old limit.
     */
    public final int setSizeLimit(final int limit) {
      if (limit < 0) {
        throw new IllegalArgumentException("Size limit cannot be negative: " + limit);
      }
      final int oldLimit = sizeLimit;
      sizeLimit = limit;
      return oldLimit;
    }
  
  
    private boolean explicitDiscardUnknownFields = false;
  
    private static volatile boolean proto3DiscardUnknownFieldsDefault = false;
  
    static void setProto3DiscardUnknownsByDefaultForTest() {
      proto3DiscardUnknownFieldsDefault = true;
    }
  
    static void setProto3KeepUnknownsByDefaultForTest() {
      proto3DiscardUnknownFieldsDefault = false;
    }
  
    static boolean getProto3DiscardUnknownFieldsDefault() {
      return proto3DiscardUnknownFieldsDefault;
    }
  
    /**
     * Sets this {@code CodedInputStream} to discard unknown fields. Only applies to full runtime
     * messages; lite messages will always preserve unknowns.
     *
     * <p>Note calling this function alone will have NO immediate effect on the underlying input data.
     * The unknown fields will be discarded during parsing. This affects both Proto2 and Proto3 full
     * runtime.
     */
    final void discardUnknownFields() {
      explicitDiscardUnknownFields = true;
    }
  
    /**
     * Reverts the unknown fields preservation behavior for Proto2 and Proto3 full runtime to their
     * default.
     */
    final void unsetDiscardUnknownFields() {
      explicitDiscardUnknownFields = false;
    }
  
    /**
     * Whether unknown fields in this input stream should be discarded during parsing into full
     * runtime messages.
     */
    final boolean shouldDiscardUnknownFields() {
      return explicitDiscardUnknownFields;
    }
  
    /**
     * Whether unknown fields in this input stream should be discarded during parsing for proto3 full
     * runtime messages.
     *
     * <p>This function was temporarily introduced before proto3 unknown fields behavior is changed.
     * TODO(liujisi): remove this and related code in GeneratedMessage after proto3 unknown
     * fields migration is done.
     */
    final boolean shouldDiscardUnknownFieldsProto3() {
      return explicitDiscardUnknownFields ? true : proto3DiscardUnknownFieldsDefault;
    }
  
    /**
     * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). Only valid for {@link
     * InputStream}-backed streams.
     */
    public abstract void resetSizeCounter();
  
    /**
     * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This is called when
     * descending into a length-delimited embedded message.
     *
     * <p>Note that {@code pushLimit()} does NOT affect how many bytes the {@code CodedInputStream}
     * reads from an underlying {@code InputStream} when refreshing its buffer. If you need to prevent
     * reading past a certain point in the underlying {@code InputStream} (e.g. because you expect it
     * to contain more data after the end of the message which you need to handle differently) then
     * you must place a wrapper around your {@code InputStream} which limits the amount of data that
     * can be read from it.
     *
     * @return the old limit.
     */
    public abstract int pushLimit(int byteLimit) throws InvalidProtocolBufferException;
  
    /**
     * Discards the current limit, returning to the previous limit.
     *
     * @param oldLimit The old limit, as returned by {@code pushLimit}.
     */
    public abstract void popLimit(final int oldLimit);
  
    /**
     * Returns the number of bytes to be read before the current limit. If no limit is set, returns
     * -1.
     */
    public abstract int getBytesUntilLimit();
  
    /**
     * Returns true if the stream has reached the end of the input. This is the case if either the end
     * of the underlying input source has been reached or if the stream has reached a limit created
     * using {@link #pushLimit(int)}.
     */
    public abstract boolean isAtEnd() throws IOException;
  
    /**
     * The total bytes read up to the current position. Calling {@link #resetSizeCounter()} resets
     * this value to zero.
     */
    public abstract int getTotalBytesRead();
  
    /**
     * Read one byte from the input.
     *
     * @throws InvalidProtocolBufferException The end of the stream or the current limit was reached.
     */
    public abstract byte readRawByte() throws IOException;
  
    /**
     * Read a fixed size of bytes from the input.
     *
     * @throws InvalidProtocolBufferException The end of the stream or the current limit was reached.
     */
    public abstract byte[] readRawBytes(final int size) throws IOException;
  
    /**
     * Reads and discards {@code size} bytes.
     *
     * @throws InvalidProtocolBufferException The end of the stream or the current limit was reached.
     */
    public abstract void skipRawBytes(final int size) throws IOException;
  
    /**
     * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers into values that can be
     * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
     * to be varint encoded, thus always taking 10 bytes on the wire.)
     *
     * @param n An unsigned 32-bit integer, stored in a signed int because Java has no explicit
     *     unsigned support.
     * @return A signed 32-bit integer.
     */
    public static int decodeZigZag32(final int n) {
      return (n >>> 1) ^ -(n & 1);
    }
  
    /**
     * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be
     * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
     * to be varint encoded, thus always taking 10 bytes on the wire.)
     *
     * @param n An unsigned 64-bit integer, stored in a signed int because Java has no explicit
     *     unsigned support.
     * @return A signed 64-bit integer.
     */
    public static long decodeZigZag64(final long n) {
      return (n >>> 1) ^ -(n & 1);
    }
  
    /**
     * Like {@link #readRawVarint32(InputStream)}, but expects that the caller has already read one
     * byte. This allows the caller to determine if EOF has been reached before attempting to read.
     */
    public static int readRawVarint32(final int firstByte, final InputStream input)
        throws IOException {
      if ((firstByte & 0x80) == 0) {
        return firstByte;
      }
  
      int result = firstByte & 0x7f;
      int offset = 7;
      for (; offset < 32; offset += 7) {
        final int b = input.read();
        if (b == -1) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        result |= (b & 0x7f) << offset;
        if ((b & 0x80) == 0) {
          return result;
        }
      }
      // Keep reading up to 64 bits.
      for (; offset < 64; offset += 7) {
        final int b = input.read();
        if (b == -1) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        if ((b & 0x80) == 0) {
          return result;
        }
      }
      throw InvalidProtocolBufferException.malformedVarint();
    }
  
    /**
     * Reads a varint from the input one byte at a time, so that it does not read any bytes after the
     * end of the varint. If you simply wrapped the stream in a CodedInputStream and used {@link
     * #readRawVarint32(InputStream)} then you would probably end up reading past the end of the
     * varint since CodedInputStream buffers its input.
     */
    static int readRawVarint32(final InputStream input) throws IOException {
      final int firstByte = input.read();
      if (firstByte == -1) {
        throw InvalidProtocolBufferException.truncatedMessage();
      }
      return readRawVarint32(firstByte, input);
    }
  
    /** A {@link CodedInputStream} implementation that uses a backing array as the input. */
    private static final class ArrayDecoder extends CodedInputStream {
      private final byte[] buffer;
      private final boolean immutable;
      private int limit;
      private int bufferSizeAfterLimit;
      private int pos;
      private int startPos;
      private int lastTag;
      private boolean enableAliasing;
  
      /** The absolute position of the end of the current message. */
      private int currentLimit = Integer.MAX_VALUE;
  
      private ArrayDecoder(final byte[] buffer, final int offset, final int len, boolean immutable) {
        this.buffer = buffer;
        limit = offset + len;
        pos = offset;
        startPos = pos;
        this.immutable = immutable;
      }
  
      @Override
      public int readTag() throws IOException {
        if (isAtEnd()) {
          lastTag = 0;
          return 0;
        }
  
        lastTag = readRawVarint32();
        if (WireFormat.getTagFieldNumber(lastTag) == 0) {
          // If we actually read zero (or any tag number corresponding to field
          // number zero), that's not a valid tag.
          throw InvalidProtocolBufferException.invalidTag();
        }
        return lastTag;
      }
  
      @Override
      public void checkLastTagWas(final int value) throws InvalidProtocolBufferException {
        if (lastTag != value) {
          throw InvalidProtocolBufferException.invalidEndTag();
        }
      }
  
      @Override
      public int getLastTag() {
        return lastTag;
      }
  
      @Override
      public boolean skipField(final int tag) throws IOException {
        switch (WireFormat.getTagWireType(tag)) {
          case WireFormat.WIRETYPE_VARINT:
            skipRawVarint();
            return true;
          case WireFormat.WIRETYPE_FIXED64:
            skipRawBytes(FIXED64_SIZE);
            return true;
          case WireFormat.WIRETYPE_LENGTH_DELIMITED:
            skipRawBytes(readRawVarint32());
            return true;
          case WireFormat.WIRETYPE_START_GROUP:
            skipMessage();
            checkLastTagWas(
                WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP));
            return true;
          case WireFormat.WIRETYPE_END_GROUP:
            return false;
          case WireFormat.WIRETYPE_FIXED32:
            skipRawBytes(FIXED32_SIZE);
            return true;
          default:
            throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      @Override
      public boolean skipField(final int tag, final CodedOutputStream output) throws IOException {
        switch (WireFormat.getTagWireType(tag)) {
          case WireFormat.WIRETYPE_VARINT:
            {
              long value = readInt64();
              output.writeRawVarint32(tag);
              output.writeUInt64NoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_FIXED64:
            {
              long value = readRawLittleEndian64();
              output.writeRawVarint32(tag);
              output.writeFixed64NoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_LENGTH_DELIMITED:
            {
              ByteString value = readBytes();
              output.writeRawVarint32(tag);
              output.writeBytesNoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_START_GROUP:
            {
              output.writeRawVarint32(tag);
              skipMessage(output);
              int endtag =
                  WireFormat.makeTag(
                      WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP);
              checkLastTagWas(endtag);
              output.writeRawVarint32(endtag);
              return true;
            }
          case WireFormat.WIRETYPE_END_GROUP:
            {
              return false;
            }
          case WireFormat.WIRETYPE_FIXED32:
            {
              int value = readRawLittleEndian32();
              output.writeRawVarint32(tag);
              output.writeFixed32NoTag(value);
              return true;
            }
          default:
            throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      @Override
      public void skipMessage() throws IOException {
        while (true) {
          final int tag = readTag();
          if (tag == 0 || !skipField(tag)) {
            return;
          }
        }
      }
  
      @Override
      public void skipMessage(CodedOutputStream output) throws IOException {
        while (true) {
          final int tag = readTag();
          if (tag == 0 || !skipField(tag, output)) {
            return;
          }
        }
      }
  
  
      // -----------------------------------------------------------------
  
      @Override
      public double readDouble() throws IOException {
        return Double.longBitsToDouble(readRawLittleEndian64());
      }
  
      @Override
      public float readFloat() throws IOException {
        return Float.intBitsToFloat(readRawLittleEndian32());
      }
  
      @Override
      public long readUInt64() throws IOException {
        return readRawVarint64();
      }
  
      @Override
      public long readInt64() throws IOException {
        return readRawVarint64();
      }
  
      @Override
      public int readInt32() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public long readFixed64() throws IOException {
        return readRawLittleEndian64();
      }
  
      @Override
      public int readFixed32() throws IOException {
        return readRawLittleEndian32();
      }
  
      @Override
      public boolean readBool() throws IOException {
        return readRawVarint64() != 0;
      }
  
      @Override
      public String readString() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= (limit - pos)) {
          // Fast path:  We already have the bytes in a contiguous buffer, so
          //   just copy directly from it.
          final String result = new String(buffer, pos, size, UTF_8);
          pos += size;
          return result;
        }
  
        if (size == 0) {
          return "";
        }
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public String readStringRequireUtf8() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= (limit - pos)) {
          if (ENABLE_CUSTOM_UTF8_DECODE) {
            String result = Utf8.decodeUtf8(buffer, pos, size);
            pos += size;
            return result;
          } else {
            // TODO(martinrb): We could save a pass by validating while decoding.
            if (!Utf8.isValidUtf8(buffer, pos, pos + size)) {
              throw InvalidProtocolBufferException.invalidUtf8();
            }
            final int tempPos = pos;
            pos += size;
            return new String(buffer, tempPos, size, UTF_8);
          }
        }
  
        if (size == 0) {
          return "";
        }
        if (size <= 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public void readGroup(
          final int fieldNumber,
          final MessageLite.Builder builder,
          final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        ++recursionDepth;
        builder.mergeFrom(this, extensionRegistry);
        checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
        --recursionDepth;
      }
  
  
      @Override
      public <T extends MessageLite> T readGroup(
          final int fieldNumber,
          final Parser<T> parser,
          final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        ++recursionDepth;
        T result = parser.parsePartialFrom(this, extensionRegistry);
        checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
        --recursionDepth;
        return result;
      }
  
      @Deprecated
      @Override
      public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder)
          throws IOException {
        readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry());
      }
  
      @Override
      public void readMessage(
          final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        final int length = readRawVarint32();
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        final int oldLimit = pushLimit(length);
        ++recursionDepth;
        builder.mergeFrom(this, extensionRegistry);
        checkLastTagWas(0);
        --recursionDepth;
        popLimit(oldLimit);
      }
  
  
      @Override
      public <T extends MessageLite> T readMessage(
          final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException {
        int length = readRawVarint32();
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        final int oldLimit = pushLimit(length);
        ++recursionDepth;
        T result = parser.parsePartialFrom(this, extensionRegistry);
        checkLastTagWas(0);
        --recursionDepth;
        popLimit(oldLimit);
        return result;
      }
  
      @Override
      public ByteString readBytes() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= (limit - pos)) {
          // Fast path:  We already have the bytes in a contiguous buffer, so
          //   just copy directly from it.
          final ByteString result =
              immutable && enableAliasing
                  ? ByteString.wrap(buffer, pos, size)
                  : ByteString.copyFrom(buffer, pos, size);
          pos += size;
          return result;
        }
        if (size == 0) {
          return ByteString.EMPTY;
        }
        // Slow path:  Build a byte array first then copy it.
        return ByteString.wrap(readRawBytes(size));
      }
  
      @Override
      public byte[] readByteArray() throws IOException {
        final int size = readRawVarint32();
        return readRawBytes(size);
      }
  
      @Override
      public ByteBuffer readByteBuffer() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= (limit - pos)) {
          // Fast path: We already have the bytes in a contiguous buffer.
          // When aliasing is enabled, we can return a ByteBuffer pointing directly
          // into the underlying byte array without copy if the CodedInputStream is
          // constructed from a byte array. If aliasing is disabled or the input is
          // from an InputStream or ByteString, we have to make a copy of the bytes.
          ByteBuffer result =
              !immutable && enableAliasing
                  ? ByteBuffer.wrap(buffer, pos, size).slice()
                  : ByteBuffer.wrap(Arrays.copyOfRange(buffer, pos, pos + size));
          pos += size;
          // TODO(nathanmittler): Investigate making the ByteBuffer be made read-only
          return result;
        }
  
        if (size == 0) {
          return EMPTY_BYTE_BUFFER;
        }
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public int readUInt32() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public int readEnum() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public int readSFixed32() throws IOException {
        return readRawLittleEndian32();
      }
  
      @Override
      public long readSFixed64() throws IOException {
        return readRawLittleEndian64();
      }
  
      @Override
      public int readSInt32() throws IOException {
        return decodeZigZag32(readRawVarint32());
      }
  
      @Override
      public long readSInt64() throws IOException {
        return decodeZigZag64(readRawVarint64());
      }
  
      // =================================================================
  
      @Override
      public int readRawVarint32() throws IOException {
        // See implementation notes for readRawVarint64
        fastpath:
        {
          int tempPos = pos;
  
          if (limit == tempPos) {
            break fastpath;
          }
  
          final byte[] buffer = this.buffer;
          int x;
          if ((x = buffer[tempPos++]) >= 0) {
            pos = tempPos;
            return x;
          } else if (limit - tempPos < 9) {
            break fastpath;
          } else if ((x ^= (buffer[tempPos++] << 7)) < 0) {
            x ^= (~0 << 7);
          } else if ((x ^= (buffer[tempPos++] << 14)) >= 0) {
            x ^= (~0 << 7) ^ (~0 << 14);
          } else if ((x ^= (buffer[tempPos++] << 21)) < 0) {
            x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21);
          } else {
            int y = buffer[tempPos++];
            x ^= y << 28;
            x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28);
            if (y < 0
                && buffer[tempPos++] < 0
                && buffer[tempPos++] < 0
                && buffer[tempPos++] < 0
                && buffer[tempPos++] < 0
                && buffer[tempPos++] < 0) {
              break fastpath; // Will throw malformedVarint()
            }
          }
          pos = tempPos;
          return x;
        }
        return (int) readRawVarint64SlowPath();
      }
  
      private void skipRawVarint() throws IOException {
        if (limit - pos >= MAX_VARINT_SIZE) {
          skipRawVarintFastPath();
        } else {
          skipRawVarintSlowPath();
        }
      }
  
      private void skipRawVarintFastPath() throws IOException {
        for (int i = 0; i < MAX_VARINT_SIZE; i++) {
          if (buffer[pos++] >= 0) {
            return;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      private void skipRawVarintSlowPath() throws IOException {
        for (int i = 0; i < MAX_VARINT_SIZE; i++) {
          if (readRawByte() >= 0) {
            return;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      @Override
      public long readRawVarint64() throws IOException {
        // Implementation notes:
        //
        // Optimized for one-byte values, expected to be common.
        // The particular code below was selected from various candidates
        // empirically, by winning VarintBenchmark.
        //
        // Sign extension of (signed) Java bytes is usually a nuisance, but
        // we exploit it here to more easily obtain the sign of bytes read.
        // Instead of cleaning up the sign extension bits by masking eagerly,
        // we delay until we find the final (positive) byte, when we clear all
        // accumulated bits with one xor.  We depend on javac to constant fold.
        fastpath:
        {
          int tempPos = pos;
  
          if (limit == tempPos) {
            break fastpath;
          }
  
          final byte[] buffer = this.buffer;
          long x;
          int y;
          if ((y = buffer[tempPos++]) >= 0) {
            pos = tempPos;
            return y;
          } else if (limit - tempPos < 9) {
            break fastpath;
          } else if ((y ^= (buffer[tempPos++] << 7)) < 0) {
            x = y ^ (~0 << 7);
          } else if ((y ^= (buffer[tempPos++] << 14)) >= 0) {
            x = y ^ ((~0 << 7) ^ (~0 << 14));
          } else if ((y ^= (buffer[tempPos++] << 21)) < 0) {
            x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21));
          } else if ((x = y ^ ((long) buffer[tempPos++] << 28)) >= 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28);
          } else if ((x ^= ((long) buffer[tempPos++] << 35)) < 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35);
          } else if ((x ^= ((long) buffer[tempPos++] << 42)) >= 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42);
          } else if ((x ^= ((long) buffer[tempPos++] << 49)) < 0L) {
            x ^=
                (~0L << 7)
                    ^ (~0L << 14)
                    ^ (~0L << 21)
                    ^ (~0L << 28)
                    ^ (~0L << 35)
                    ^ (~0L << 42)
                    ^ (~0L << 49);
          } else {
            x ^= ((long) buffer[tempPos++] << 56);
            x ^=
                (~0L << 7)
                    ^ (~0L << 14)
                    ^ (~0L << 21)
                    ^ (~0L << 28)
                    ^ (~0L << 35)
                    ^ (~0L << 42)
                    ^ (~0L << 49)
                    ^ (~0L << 56);
            if (x < 0L) {
              if (buffer[tempPos++] < 0L) {
                break fastpath; // Will throw malformedVarint()
              }
            }
          }
          pos = tempPos;
          return x;
        }
        return readRawVarint64SlowPath();
      }
  
      @Override
      long readRawVarint64SlowPath() throws IOException {
        long result = 0;
        for (int shift = 0; shift < 64; shift += 7) {
          final byte b = readRawByte();
          result |= (long) (b & 0x7F) << shift;
          if ((b & 0x80) == 0) {
            return result;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      @Override
      public int readRawLittleEndian32() throws IOException {
        int tempPos = pos;
  
        if (limit - tempPos < FIXED32_SIZE) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
  
        final byte[] buffer = this.buffer;
        pos = tempPos + FIXED32_SIZE;
        return (((buffer[tempPos] & 0xff))
            | ((buffer[tempPos + 1] & 0xff) << 8)
            | ((buffer[tempPos + 2] & 0xff) << 16)
            | ((buffer[tempPos + 3] & 0xff) << 24));
      }
  
      @Override
      public long readRawLittleEndian64() throws IOException {
        int tempPos = pos;
  
        if (limit - tempPos < FIXED64_SIZE) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
  
        final byte[] buffer = this.buffer;
        pos = tempPos + FIXED64_SIZE;
        return (((buffer[tempPos] & 0xffL))
            | ((buffer[tempPos + 1] & 0xffL) << 8)
            | ((buffer[tempPos + 2] & 0xffL) << 16)
            | ((buffer[tempPos + 3] & 0xffL) << 24)
            | ((buffer[tempPos + 4] & 0xffL) << 32)
            | ((buffer[tempPos + 5] & 0xffL) << 40)
            | ((buffer[tempPos + 6] & 0xffL) << 48)
            | ((buffer[tempPos + 7] & 0xffL) << 56));
      }
  
      @Override
      public void enableAliasing(boolean enabled) {
        this.enableAliasing = enabled;
      }
  
      @Override
      public void resetSizeCounter() {
        startPos = pos;
      }
  
      @Override
      public int pushLimit(int byteLimit) throws InvalidProtocolBufferException {
        if (byteLimit < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        byteLimit += getTotalBytesRead();
        final int oldLimit = currentLimit;
        if (byteLimit > oldLimit) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        currentLimit = byteLimit;
  
        recomputeBufferSizeAfterLimit();
  
        return oldLimit;
      }
  
      private void recomputeBufferSizeAfterLimit() {
        limit += bufferSizeAfterLimit;
        final int bufferEnd = limit - startPos;
        if (bufferEnd > currentLimit) {
          // Limit is in current buffer.
          bufferSizeAfterLimit = bufferEnd - currentLimit;
          limit -= bufferSizeAfterLimit;
        } else {
          bufferSizeAfterLimit = 0;
        }
      }
  
      @Override
      public void popLimit(final int oldLimit) {
        currentLimit = oldLimit;
        recomputeBufferSizeAfterLimit();
      }
  
      @Override
      public int getBytesUntilLimit() {
        if (currentLimit == Integer.MAX_VALUE) {
          return -1;
        }
  
        return currentLimit - getTotalBytesRead();
      }
  
      @Override
      public boolean isAtEnd() throws IOException {
        return pos == limit;
      }
  
      @Override
      public int getTotalBytesRead() {
        return pos - startPos;
      }
  
      @Override
      public byte readRawByte() throws IOException {
        if (pos == limit) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        return buffer[pos++];
      }
  
      @Override
      public byte[] readRawBytes(final int length) throws IOException {
        if (length > 0 && length <= (limit - pos)) {
          final int tempPos = pos;
          pos += length;
          return Arrays.copyOfRange(buffer, tempPos, pos);
        }
  
        if (length <= 0) {
          if (length == 0) {
            return Internal.EMPTY_BYTE_ARRAY;
          } else {
            throw InvalidProtocolBufferException.negativeSize();
          }
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public void skipRawBytes(final int length) throws IOException {
        if (length >= 0 && length <= (limit - pos)) {
          // We have all the bytes we need already.
          pos += length;
          return;
        }
  
        if (length < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
    }
  
    /**
     * A {@link CodedInputStream} implementation that uses a backing direct ByteBuffer as the input.
     * Requires the use of {@code sun.misc.Unsafe} to perform fast reads on the buffer.
     */
    private static final class UnsafeDirectNioDecoder extends CodedInputStream {
      /** The direct buffer that is backing this stream. */
      private final ByteBuffer buffer;
  
      /**
       * If {@code true}, indicates that the buffer is backing a {@link ByteString} and is therefore
       * considered to be an immutable input source.
       */
      private final boolean immutable;
  
      /** The unsafe address of the content of {@link #buffer}. */
      private final long address;
  
      /** The unsafe address of the current read limit of the buffer. */
      private long limit;
  
      /** The unsafe address of the current read position of the buffer. */
      private long pos;
  
      /** The unsafe address of the starting read position. */
      private long startPos;
  
      /** The amount of available data in the buffer beyond {@link #limit}. */
      private int bufferSizeAfterLimit;
  
      /** The last tag that was read from this stream. */
      private int lastTag;
  
      /**
       * If {@code true}, indicates that calls to read {@link ByteString} or {@code byte[]}
       * <strong>may</strong> return slices of the underlying buffer, rather than copies.
       */
      private boolean enableAliasing;
  
      /** The absolute position of the end of the current message. */
      private int currentLimit = Integer.MAX_VALUE;
  
      static boolean isSupported() {
        return UnsafeUtil.hasUnsafeByteBufferOperations();
      }
  
      private UnsafeDirectNioDecoder(ByteBuffer buffer, boolean immutable) {
        this.buffer = buffer;
        address = UnsafeUtil.addressOffset(buffer);
        limit = address + buffer.limit();
        pos = address + buffer.position();
        startPos = pos;
        this.immutable = immutable;
      }
  
      @Override
      public int readTag() throws IOException {
        if (isAtEnd()) {
          lastTag = 0;
          return 0;
        }
  
        lastTag = readRawVarint32();
        if (WireFormat.getTagFieldNumber(lastTag) == 0) {
          // If we actually read zero (or any tag number corresponding to field
          // number zero), that's not a valid tag.
          throw InvalidProtocolBufferException.invalidTag();
        }
        return lastTag;
      }
  
      @Override
      public void checkLastTagWas(final int value) throws InvalidProtocolBufferException {
        if (lastTag != value) {
          throw InvalidProtocolBufferException.invalidEndTag();
        }
      }
  
      @Override
      public int getLastTag() {
        return lastTag;
      }
  
      @Override
      public boolean skipField(final int tag) throws IOException {
        switch (WireFormat.getTagWireType(tag)) {
          case WireFormat.WIRETYPE_VARINT:
            skipRawVarint();
            return true;
          case WireFormat.WIRETYPE_FIXED64:
            skipRawBytes(FIXED64_SIZE);
            return true;
          case WireFormat.WIRETYPE_LENGTH_DELIMITED:
            skipRawBytes(readRawVarint32());
            return true;
          case WireFormat.WIRETYPE_START_GROUP:
            skipMessage();
            checkLastTagWas(
                WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP));
            return true;
          case WireFormat.WIRETYPE_END_GROUP:
            return false;
          case WireFormat.WIRETYPE_FIXED32:
            skipRawBytes(FIXED32_SIZE);
            return true;
          default:
            throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      @Override
      public boolean skipField(final int tag, final CodedOutputStream output) throws IOException {
        switch (WireFormat.getTagWireType(tag)) {
          case WireFormat.WIRETYPE_VARINT:
            {
              long value = readInt64();
              output.writeRawVarint32(tag);
              output.writeUInt64NoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_FIXED64:
            {
              long value = readRawLittleEndian64();
              output.writeRawVarint32(tag);
              output.writeFixed64NoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_LENGTH_DELIMITED:
            {
              ByteString value = readBytes();
              output.writeRawVarint32(tag);
              output.writeBytesNoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_START_GROUP:
            {
              output.writeRawVarint32(tag);
              skipMessage(output);
              int endtag =
                  WireFormat.makeTag(
                      WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP);
              checkLastTagWas(endtag);
              output.writeRawVarint32(endtag);
              return true;
            }
          case WireFormat.WIRETYPE_END_GROUP:
            {
              return false;
            }
          case WireFormat.WIRETYPE_FIXED32:
            {
              int value = readRawLittleEndian32();
              output.writeRawVarint32(tag);
              output.writeFixed32NoTag(value);
              return true;
            }
          default:
            throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      @Override
      public void skipMessage() throws IOException {
        while (true) {
          final int tag = readTag();
          if (tag == 0 || !skipField(tag)) {
            return;
          }
        }
      }
  
      @Override
      public void skipMessage(CodedOutputStream output) throws IOException {
        while (true) {
          final int tag = readTag();
          if (tag == 0 || !skipField(tag, output)) {
            return;
          }
        }
      }
  
  
      // -----------------------------------------------------------------
  
      @Override
      public double readDouble() throws IOException {
        return Double.longBitsToDouble(readRawLittleEndian64());
      }
  
      @Override
      public float readFloat() throws IOException {
        return Float.intBitsToFloat(readRawLittleEndian32());
      }
  
      @Override
      public long readUInt64() throws IOException {
        return readRawVarint64();
      }
  
      @Override
      public long readInt64() throws IOException {
        return readRawVarint64();
      }
  
      @Override
      public int readInt32() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public long readFixed64() throws IOException {
        return readRawLittleEndian64();
      }
  
      @Override
      public int readFixed32() throws IOException {
        return readRawLittleEndian32();
      }
  
      @Override
      public boolean readBool() throws IOException {
        return readRawVarint64() != 0;
      }
  
      @Override
      public String readString() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= remaining()) {
          // TODO(nathanmittler): Is there a way to avoid this copy?
          // TODO(anuraaga): It might be possible to share the optimized loop with
          // readStringRequireUtf8 by implementing Java replacement logic there.
          // The same as readBytes' logic
          byte[] bytes = new byte[size];
          UnsafeUtil.copyMemory(pos, bytes, 0, size);
          String result = new String(bytes, UTF_8);
          pos += size;
          return result;
        }
  
        if (size == 0) {
          return "";
        }
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public String readStringRequireUtf8() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= remaining()) {
          if (ENABLE_CUSTOM_UTF8_DECODE) {
            final int bufferPos = bufferPos(pos);
            String result = Utf8.decodeUtf8(buffer, bufferPos, size);
            pos += size;
            return result;
          } else {
            // TODO(nathanmittler): Is there a way to avoid this copy?
            // The same as readBytes' logic
            byte[] bytes = new byte[size];
            UnsafeUtil.copyMemory(pos, bytes, 0, size);
            // TODO(martinrb): We could save a pass by validating while decoding.
            if (!Utf8.isValidUtf8(bytes)) {
              throw InvalidProtocolBufferException.invalidUtf8();
            }
  
            String result = new String(bytes, UTF_8);
            pos += size;
            return result;
          }
        }
  
        if (size == 0) {
          return "";
        }
        if (size <= 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public void readGroup(
          final int fieldNumber,
          final MessageLite.Builder builder,
          final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        ++recursionDepth;
        builder.mergeFrom(this, extensionRegistry);
        checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
        --recursionDepth;
      }
  
  
      @Override
      public <T extends MessageLite> T readGroup(
          final int fieldNumber,
          final Parser<T> parser,
          final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        ++recursionDepth;
        T result = parser.parsePartialFrom(this, extensionRegistry);
        checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
        --recursionDepth;
        return result;
      }
  
      @Deprecated
      @Override
      public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder)
          throws IOException {
        readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry());
      }
  
      @Override
      public void readMessage(
          final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        final int length = readRawVarint32();
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        final int oldLimit = pushLimit(length);
        ++recursionDepth;
        builder.mergeFrom(this, extensionRegistry);
        checkLastTagWas(0);
        --recursionDepth;
        popLimit(oldLimit);
      }
  
  
      @Override
      public <T extends MessageLite> T readMessage(
          final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException {
        int length = readRawVarint32();
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        final int oldLimit = pushLimit(length);
        ++recursionDepth;
        T result = parser.parsePartialFrom(this, extensionRegistry);
        checkLastTagWas(0);
        --recursionDepth;
        popLimit(oldLimit);
        return result;
      }
  
      @Override
      public ByteString readBytes() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= remaining()) {
          if (immutable && enableAliasing) {
            final ByteBuffer result = slice(pos, pos + size);
            pos += size;
            return ByteString.wrap(result);
          } else {
            // Use UnsafeUtil to copy the memory to bytes instead of using ByteBuffer ways.
            byte[] bytes = new byte[size];
            UnsafeUtil.copyMemory(pos, bytes, 0, size);
            pos += size;
            return ByteString.wrap(bytes);
          }
        }
  
        if (size == 0) {
          return ByteString.EMPTY;
        }
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public byte[] readByteArray() throws IOException {
        return readRawBytes(readRawVarint32());
      }
  
      @Override
      public ByteBuffer readByteBuffer() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= remaining()) {
          // "Immutable" implies that buffer is backing a ByteString.
          // Disallow slicing in this case to prevent the caller from modifying the contents
          // of the ByteString.
          if (!immutable && enableAliasing) {
            final ByteBuffer result = slice(pos, pos + size);
            pos += size;
            return result;
          } else {
            // The same as readBytes' logic
            byte[] bytes = new byte[size];
            UnsafeUtil.copyMemory(pos, bytes, 0, size);
            pos += size;
            return ByteBuffer.wrap(bytes);
          }
          // TODO(nathanmittler): Investigate making the ByteBuffer be made read-only
        }
  
        if (size == 0) {
          return EMPTY_BYTE_BUFFER;
        }
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public int readUInt32() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public int readEnum() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public int readSFixed32() throws IOException {
        return readRawLittleEndian32();
      }
  
      @Override
      public long readSFixed64() throws IOException {
        return readRawLittleEndian64();
      }
  
      @Override
      public int readSInt32() throws IOException {
        return decodeZigZag32(readRawVarint32());
      }
  
      @Override
      public long readSInt64() throws IOException {
        return decodeZigZag64(readRawVarint64());
      }
  
      // =================================================================
  
      @Override
      public int readRawVarint32() throws IOException {
        // See implementation notes for readRawVarint64
        fastpath:
        {
          long tempPos = pos;
  
          if (limit == tempPos) {
            break fastpath;
          }
  
          int x;
          if ((x = UnsafeUtil.getByte(tempPos++)) >= 0) {
            pos = tempPos;
            return x;
          } else if (limit - tempPos < 9) {
            break fastpath;
          } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) {
            x ^= (~0 << 7);
          } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) {
            x ^= (~0 << 7) ^ (~0 << 14);
          } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) {
            x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21);
          } else {
            int y = UnsafeUtil.getByte(tempPos++);
            x ^= y << 28;
            x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28);
            if (y < 0
                && UnsafeUtil.getByte(tempPos++) < 0
                && UnsafeUtil.getByte(tempPos++) < 0
                && UnsafeUtil.getByte(tempPos++) < 0
                && UnsafeUtil.getByte(tempPos++) < 0
                && UnsafeUtil.getByte(tempPos++) < 0) {
              break fastpath; // Will throw malformedVarint()
            }
          }
          pos = tempPos;
          return x;
        }
        return (int) readRawVarint64SlowPath();
      }
  
      private void skipRawVarint() throws IOException {
        if (remaining() >= MAX_VARINT_SIZE) {
          skipRawVarintFastPath();
        } else {
          skipRawVarintSlowPath();
        }
      }
  
      private void skipRawVarintFastPath() throws IOException {
        for (int i = 0; i < MAX_VARINT_SIZE; i++) {
          if (UnsafeUtil.getByte(pos++) >= 0) {
            return;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      private void skipRawVarintSlowPath() throws IOException {
        for (int i = 0; i < MAX_VARINT_SIZE; i++) {
          if (readRawByte() >= 0) {
            return;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      @Override
      public long readRawVarint64() throws IOException {
        // Implementation notes:
        //
        // Optimized for one-byte values, expected to be common.
        // The particular code below was selected from various candidates
        // empirically, by winning VarintBenchmark.
        //
        // Sign extension of (signed) Java bytes is usually a nuisance, but
        // we exploit it here to more easily obtain the sign of bytes read.
        // Instead of cleaning up the sign extension bits by masking eagerly,
        // we delay until we find the final (positive) byte, when we clear all
        // accumulated bits with one xor.  We depend on javac to constant fold.
        fastpath:
        {
          long tempPos = pos;
  
          if (limit == tempPos) {
            break fastpath;
          }
  
          long x;
          int y;
          if ((y = UnsafeUtil.getByte(tempPos++)) >= 0) {
            pos = tempPos;
            return y;
          } else if (limit - tempPos < 9) {
            break fastpath;
          } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) {
            x = y ^ (~0 << 7);
          } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) {
            x = y ^ ((~0 << 7) ^ (~0 << 14));
          } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) {
            x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21));
          } else if ((x = y ^ ((long) UnsafeUtil.getByte(tempPos++) << 28)) >= 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28);
          } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 35)) < 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35);
          } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 42)) >= 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42);
          } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 49)) < 0L) {
            x ^=
                (~0L << 7)
                    ^ (~0L << 14)
                    ^ (~0L << 21)
                    ^ (~0L << 28)
                    ^ (~0L << 35)
                    ^ (~0L << 42)
                    ^ (~0L << 49);
          } else {
            x ^= ((long) UnsafeUtil.getByte(tempPos++) << 56);
            x ^=
                (~0L << 7)
                    ^ (~0L << 14)
                    ^ (~0L << 21)
                    ^ (~0L << 28)
                    ^ (~0L << 35)
                    ^ (~0L << 42)
                    ^ (~0L << 49)
                    ^ (~0L << 56);
            if (x < 0L) {
              if (UnsafeUtil.getByte(tempPos++) < 0L) {
                break fastpath; // Will throw malformedVarint()
              }
            }
          }
          pos = tempPos;
          return x;
        }
        return readRawVarint64SlowPath();
      }
  
      @Override
      long readRawVarint64SlowPath() throws IOException {
        long result = 0;
        for (int shift = 0; shift < 64; shift += 7) {
          final byte b = readRawByte();
          result |= (long) (b & 0x7F) << shift;
          if ((b & 0x80) == 0) {
            return result;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      @Override
      public int readRawLittleEndian32() throws IOException {
        long tempPos = pos;
  
        if (limit - tempPos < FIXED32_SIZE) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
  
        pos = tempPos + FIXED32_SIZE;
        return (((UnsafeUtil.getByte(tempPos) & 0xff))
            | ((UnsafeUtil.getByte(tempPos + 1) & 0xff) << 8)
            | ((UnsafeUtil.getByte(tempPos + 2) & 0xff) << 16)
            | ((UnsafeUtil.getByte(tempPos + 3) & 0xff) << 24));
      }
  
      @Override
      public long readRawLittleEndian64() throws IOException {
        long tempPos = pos;
  
        if (limit - tempPos < FIXED64_SIZE) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
  
        pos = tempPos + FIXED64_SIZE;
        return (((UnsafeUtil.getByte(tempPos) & 0xffL))
            | ((UnsafeUtil.getByte(tempPos + 1) & 0xffL) << 8)
            | ((UnsafeUtil.getByte(tempPos + 2) & 0xffL) << 16)
            | ((UnsafeUtil.getByte(tempPos + 3) & 0xffL) << 24)
            | ((UnsafeUtil.getByte(tempPos + 4) & 0xffL) << 32)
            | ((UnsafeUtil.getByte(tempPos + 5) & 0xffL) << 40)
            | ((UnsafeUtil.getByte(tempPos + 6) & 0xffL) << 48)
            | ((UnsafeUtil.getByte(tempPos + 7) & 0xffL) << 56));
      }
  
      @Override
      public void enableAliasing(boolean enabled) {
        this.enableAliasing = enabled;
      }
  
      @Override
      public void resetSizeCounter() {
        startPos = pos;
      }
  
      @Override
      public int pushLimit(int byteLimit) throws InvalidProtocolBufferException {
        if (byteLimit < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        byteLimit += getTotalBytesRead();
        final int oldLimit = currentLimit;
        if (byteLimit > oldLimit) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        currentLimit = byteLimit;
  
        recomputeBufferSizeAfterLimit();
  
        return oldLimit;
      }
  
      @Override
      public void popLimit(final int oldLimit) {
        currentLimit = oldLimit;
        recomputeBufferSizeAfterLimit();
      }
  
      @Override
      public int getBytesUntilLimit() {
        if (currentLimit == Integer.MAX_VALUE) {
          return -1;
        }
  
        return currentLimit - getTotalBytesRead();
      }
  
      @Override
      public boolean isAtEnd() throws IOException {
        return pos == limit;
      }
  
      @Override
      public int getTotalBytesRead() {
        return (int) (pos - startPos);
      }
  
      @Override
      public byte readRawByte() throws IOException {
        if (pos == limit) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        return UnsafeUtil.getByte(pos++);
      }
  
      @Override
      public byte[] readRawBytes(final int length) throws IOException {
        if (length >= 0 && length <= remaining()) {
          byte[] bytes = new byte[length];
          slice(pos, pos + length).get(bytes);
          pos += length;
          return bytes;
        }
  
        if (length <= 0) {
          if (length == 0) {
            return EMPTY_BYTE_ARRAY;
          } else {
            throw InvalidProtocolBufferException.negativeSize();
          }
        }
  
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public void skipRawBytes(final int length) throws IOException {
        if (length >= 0 && length <= remaining()) {
          // We have all the bytes we need already.
          pos += length;
          return;
        }
  
        if (length < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      private void recomputeBufferSizeAfterLimit() {
        limit += bufferSizeAfterLimit;
        final int bufferEnd = (int) (limit - startPos);
        if (bufferEnd > currentLimit) {
          // Limit is in current buffer.
          bufferSizeAfterLimit = bufferEnd - currentLimit;
          limit -= bufferSizeAfterLimit;
        } else {
          bufferSizeAfterLimit = 0;
        }
      }
  
      private int remaining() {
        return (int) (limit - pos);
      }
  
      private int bufferPos(long pos) {
        return (int) (pos - address);
      }
  
      private ByteBuffer slice(long begin, long end) throws IOException {
        int prevPos = buffer.position();
        int prevLimit = buffer.limit();
        try {
          buffer.position(bufferPos(begin));
          buffer.limit(bufferPos(end));
          return buffer.slice();
        } catch (IllegalArgumentException e) {
          throw InvalidProtocolBufferException.truncatedMessage();
        } finally {
          buffer.position(prevPos);
          buffer.limit(prevLimit);
        }
      }
    }
  
    /**
     * Implementation of {@link CodedInputStream} that uses an {@link InputStream} as the data source.
     */
    private static final class StreamDecoder extends CodedInputStream {
      private final InputStream input;
      private final byte[] buffer;
      /** bufferSize represents how many bytes are currently filled in the buffer */
      private int bufferSize;
  
      private int bufferSizeAfterLimit;
      private int pos;
      private int lastTag;
  
      /**
       * The total number of bytes read before the current buffer. The total bytes read up to the
       * current position can be computed as {@code totalBytesRetired + pos}. This value may be
       * negative if reading started in the middle of the current buffer (e.g. if the constructor that
       * takes a byte array and an offset was used).
       */
      private int totalBytesRetired;
  
      /** The absolute position of the end of the current message. */
      private int currentLimit = Integer.MAX_VALUE;
  
      private StreamDecoder(final InputStream input, int bufferSize) {
        checkNotNull(input, "input");
        this.input = input;
        this.buffer = new byte[bufferSize];
        this.bufferSize = 0;
        pos = 0;
        totalBytesRetired = 0;
      }
  
      @Override
      public int readTag() throws IOException {
        if (isAtEnd()) {
          lastTag = 0;
          return 0;
        }
  
        lastTag = readRawVarint32();
        if (WireFormat.getTagFieldNumber(lastTag) == 0) {
          // If we actually read zero (or any tag number corresponding to field
          // number zero), that's not a valid tag.
          throw InvalidProtocolBufferException.invalidTag();
        }
        return lastTag;
      }
  
      @Override
      public void checkLastTagWas(final int value) throws InvalidProtocolBufferException {
        if (lastTag != value) {
          throw InvalidProtocolBufferException.invalidEndTag();
        }
      }
  
      @Override
      public int getLastTag() {
        return lastTag;
      }
  
      @Override
      public boolean skipField(final int tag) throws IOException {
        switch (WireFormat.getTagWireType(tag)) {
          case WireFormat.WIRETYPE_VARINT:
            skipRawVarint();
            return true;
          case WireFormat.WIRETYPE_FIXED64:
            skipRawBytes(FIXED64_SIZE);
            return true;
          case WireFormat.WIRETYPE_LENGTH_DELIMITED:
            skipRawBytes(readRawVarint32());
            return true;
          case WireFormat.WIRETYPE_START_GROUP:
            skipMessage();
            checkLastTagWas(
                WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP));
            return true;
          case WireFormat.WIRETYPE_END_GROUP:
            return false;
          case WireFormat.WIRETYPE_FIXED32:
            skipRawBytes(FIXED32_SIZE);
            return true;
          default:
            throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      @Override
      public boolean skipField(final int tag, final CodedOutputStream output) throws IOException {
        switch (WireFormat.getTagWireType(tag)) {
          case WireFormat.WIRETYPE_VARINT:
            {
              long value = readInt64();
              output.writeRawVarint32(tag);
              output.writeUInt64NoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_FIXED64:
            {
              long value = readRawLittleEndian64();
              output.writeRawVarint32(tag);
              output.writeFixed64NoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_LENGTH_DELIMITED:
            {
              ByteString value = readBytes();
              output.writeRawVarint32(tag);
              output.writeBytesNoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_START_GROUP:
            {
              output.writeRawVarint32(tag);
              skipMessage(output);
              int endtag =
                  WireFormat.makeTag(
                      WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP);
              checkLastTagWas(endtag);
              output.writeRawVarint32(endtag);
              return true;
            }
          case WireFormat.WIRETYPE_END_GROUP:
            {
              return false;
            }
          case WireFormat.WIRETYPE_FIXED32:
            {
              int value = readRawLittleEndian32();
              output.writeRawVarint32(tag);
              output.writeFixed32NoTag(value);
              return true;
            }
          default:
            throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      @Override
      public void skipMessage() throws IOException {
        while (true) {
          final int tag = readTag();
          if (tag == 0 || !skipField(tag)) {
            return;
          }
        }
      }
  
      @Override
      public void skipMessage(CodedOutputStream output) throws IOException {
        while (true) {
          final int tag = readTag();
          if (tag == 0 || !skipField(tag, output)) {
            return;
          }
        }
      }
  
      /** Collects the bytes skipped and returns the data in a ByteBuffer. */
      private class SkippedDataSink implements RefillCallback {
        private int lastPos = pos;
        private ByteArrayOutputStream byteArrayStream;
  
        @Override
        public void onRefill() {
          if (byteArrayStream == null) {
            byteArrayStream = new ByteArrayOutputStream();
          }
          byteArrayStream.write(buffer, lastPos, pos - lastPos);
          lastPos = 0;
        }
  
        /** Gets skipped data in a ByteBuffer. This method should only be called once. */
        ByteBuffer getSkippedData() {
          if (byteArrayStream == null) {
            return ByteBuffer.wrap(buffer, lastPos, pos - lastPos);
          } else {
            byteArrayStream.write(buffer, lastPos, pos);
            return ByteBuffer.wrap(byteArrayStream.toByteArray());
          }
        }
      }
  
  
      // -----------------------------------------------------------------
  
      @Override
      public double readDouble() throws IOException {
        return Double.longBitsToDouble(readRawLittleEndian64());
      }
  
      @Override
      public float readFloat() throws IOException {
        return Float.intBitsToFloat(readRawLittleEndian32());
      }
  
      @Override
      public long readUInt64() throws IOException {
        return readRawVarint64();
      }
  
      @Override
      public long readInt64() throws IOException {
        return readRawVarint64();
      }
  
      @Override
      public int readInt32() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public long readFixed64() throws IOException {
        return readRawLittleEndian64();
      }
  
      @Override
      public int readFixed32() throws IOException {
        return readRawLittleEndian32();
      }
  
      @Override
      public boolean readBool() throws IOException {
        return readRawVarint64() != 0;
      }
  
      @Override
      public String readString() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= (bufferSize - pos)) {
          // Fast path:  We already have the bytes in a contiguous buffer, so
          //   just copy directly from it.
          final String result = new String(buffer, pos, size, UTF_8);
          pos += size;
          return result;
        }
        if (size == 0) {
          return "";
        }
        if (size <= bufferSize) {
          refillBuffer(size);
          String result = new String(buffer, pos, size, UTF_8);
          pos += size;
          return result;
        }
        // Slow path:  Build a byte array first then copy it.
        return new String(readRawBytesSlowPath(size), UTF_8);
      }
  
      @Override
      public String readStringRequireUtf8() throws IOException {
        final int size = readRawVarint32();
        final byte[] bytes;
        final int oldPos = pos;
        final int tempPos;
        if (size <= (bufferSize - oldPos) && size > 0) {
          // Fast path:  We already have the bytes in a contiguous buffer, so
          //   just copy directly from it.
          bytes = buffer;
          pos = oldPos + size;
          tempPos = oldPos;
        } else if (size == 0) {
          return "";
        } else if (size <= bufferSize) {
          refillBuffer(size);
          bytes = buffer;
          tempPos = 0;
          pos = tempPos + size;
        } else {
          // Slow path:  Build a byte array first then copy it.
          bytes = readRawBytesSlowPath(size);
          tempPos = 0;
        }
        if (ENABLE_CUSTOM_UTF8_DECODE) {
          return Utf8.decodeUtf8(bytes, tempPos, size);
        } else {
          // TODO(martinrb): We could save a pass by validating while decoding.
          if (!Utf8.isValidUtf8(bytes, tempPos, tempPos + size)) {
            throw InvalidProtocolBufferException.invalidUtf8();
          }
          return new String(bytes, tempPos, size, UTF_8);
        }
      }
  
      @Override
      public void readGroup(
          final int fieldNumber,
          final MessageLite.Builder builder,
          final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        ++recursionDepth;
        builder.mergeFrom(this, extensionRegistry);
        checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
        --recursionDepth;
      }
  
  
      @Override
      public <T extends MessageLite> T readGroup(
          final int fieldNumber,
          final Parser<T> parser,
          final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        ++recursionDepth;
        T result = parser.parsePartialFrom(this, extensionRegistry);
        checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
        --recursionDepth;
        return result;
      }
  
      @Deprecated
      @Override
      public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder)
          throws IOException {
        readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry());
      }
  
      @Override
      public void readMessage(
          final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        final int length = readRawVarint32();
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        final int oldLimit = pushLimit(length);
        ++recursionDepth;
        builder.mergeFrom(this, extensionRegistry);
        checkLastTagWas(0);
        --recursionDepth;
        popLimit(oldLimit);
      }
  
  
      @Override
      public <T extends MessageLite> T readMessage(
          final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException {
        int length = readRawVarint32();
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        final int oldLimit = pushLimit(length);
        ++recursionDepth;
        T result = parser.parsePartialFrom(this, extensionRegistry);
        checkLastTagWas(0);
        --recursionDepth;
        popLimit(oldLimit);
        return result;
      }
  
      @Override
      public ByteString readBytes() throws IOException {
        final int size = readRawVarint32();
        if (size <= (bufferSize - pos) && size > 0) {
          // Fast path:  We already have the bytes in a contiguous buffer, so
          //   just copy directly from it.
          final ByteString result = ByteString.copyFrom(buffer, pos, size);
          pos += size;
          return result;
        }
        if (size == 0) {
          return ByteString.EMPTY;
        }
        return readBytesSlowPath(size);
      }
  
      @Override
      public byte[] readByteArray() throws IOException {
        final int size = readRawVarint32();
        if (size <= (bufferSize - pos) && size > 0) {
          // Fast path: We already have the bytes in a contiguous buffer, so
          // just copy directly from it.
          final byte[] result = Arrays.copyOfRange(buffer, pos, pos + size);
          pos += size;
          return result;
        } else {
          // Slow path: Build a byte array first then copy it.
          return readRawBytesSlowPath(size);
        }
      }
  
      @Override
      public ByteBuffer readByteBuffer() throws IOException {
        final int size = readRawVarint32();
        if (size <= (bufferSize - pos) && size > 0) {
          // Fast path: We already have the bytes in a contiguous buffer.
          ByteBuffer result = ByteBuffer.wrap(Arrays.copyOfRange(buffer, pos, pos + size));
          pos += size;
          return result;
        }
        if (size == 0) {
          return Internal.EMPTY_BYTE_BUFFER;
        }
        // Slow path: Build a byte array first then copy it.
        return ByteBuffer.wrap(readRawBytesSlowPath(size));
      }
  
      @Override
      public int readUInt32() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public int readEnum() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public int readSFixed32() throws IOException {
        return readRawLittleEndian32();
      }
  
      @Override
      public long readSFixed64() throws IOException {
        return readRawLittleEndian64();
      }
  
      @Override
      public int readSInt32() throws IOException {
        return decodeZigZag32(readRawVarint32());
      }
  
      @Override
      public long readSInt64() throws IOException {
        return decodeZigZag64(readRawVarint64());
      }
  
      // =================================================================
  
      @Override
      public int readRawVarint32() throws IOException {
        // See implementation notes for readRawVarint64
        fastpath:
        {
          int tempPos = pos;
  
          if (bufferSize == tempPos) {
            break fastpath;
          }
  
          final byte[] buffer = this.buffer;
          int x;
          if ((x = buffer[tempPos++]) >= 0) {
            pos = tempPos;
            return x;
          } else if (bufferSize - tempPos < 9) {
            break fastpath;
          } else if ((x ^= (buffer[tempPos++] << 7)) < 0) {
            x ^= (~0 << 7);
          } else if ((x ^= (buffer[tempPos++] << 14)) >= 0) {
            x ^= (~0 << 7) ^ (~0 << 14);
          } else if ((x ^= (buffer[tempPos++] << 21)) < 0) {
            x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21);
          } else {
            int y = buffer[tempPos++];
            x ^= y << 28;
            x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28);
            if (y < 0
                && buffer[tempPos++] < 0
                && buffer[tempPos++] < 0
                && buffer[tempPos++] < 0
                && buffer[tempPos++] < 0
                && buffer[tempPos++] < 0) {
              break fastpath; // Will throw malformedVarint()
            }
          }
          pos = tempPos;
          return x;
        }
        return (int) readRawVarint64SlowPath();
      }
  
      private void skipRawVarint() throws IOException {
        if (bufferSize - pos >= MAX_VARINT_SIZE) {
          skipRawVarintFastPath();
        } else {
          skipRawVarintSlowPath();
        }
      }
  
      private void skipRawVarintFastPath() throws IOException {
        for (int i = 0; i < MAX_VARINT_SIZE; i++) {
          if (buffer[pos++] >= 0) {
            return;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      private void skipRawVarintSlowPath() throws IOException {
        for (int i = 0; i < MAX_VARINT_SIZE; i++) {
          if (readRawByte() >= 0) {
            return;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      @Override
      public long readRawVarint64() throws IOException {
        // Implementation notes:
        //
        // Optimized for one-byte values, expected to be common.
        // The particular code below was selected from various candidates
        // empirically, by winning VarintBenchmark.
        //
        // Sign extension of (signed) Java bytes is usually a nuisance, but
        // we exploit it here to more easily obtain the sign of bytes read.
        // Instead of cleaning up the sign extension bits by masking eagerly,
        // we delay until we find the final (positive) byte, when we clear all
        // accumulated bits with one xor.  We depend on javac to constant fold.
        fastpath:
        {
          int tempPos = pos;
  
          if (bufferSize == tempPos) {
            break fastpath;
          }
  
          final byte[] buffer = this.buffer;
          long x;
          int y;
          if ((y = buffer[tempPos++]) >= 0) {
            pos = tempPos;
            return y;
          } else if (bufferSize - tempPos < 9) {
            break fastpath;
          } else if ((y ^= (buffer[tempPos++] << 7)) < 0) {
            x = y ^ (~0 << 7);
          } else if ((y ^= (buffer[tempPos++] << 14)) >= 0) {
            x = y ^ ((~0 << 7) ^ (~0 << 14));
          } else if ((y ^= (buffer[tempPos++] << 21)) < 0) {
            x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21));
          } else if ((x = y ^ ((long) buffer[tempPos++] << 28)) >= 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28);
          } else if ((x ^= ((long) buffer[tempPos++] << 35)) < 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35);
          } else if ((x ^= ((long) buffer[tempPos++] << 42)) >= 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42);
          } else if ((x ^= ((long) buffer[tempPos++] << 49)) < 0L) {
            x ^=
                (~0L << 7)
                    ^ (~0L << 14)
                    ^ (~0L << 21)
                    ^ (~0L << 28)
                    ^ (~0L << 35)
                    ^ (~0L << 42)
                    ^ (~0L << 49);
          } else {
            x ^= ((long) buffer[tempPos++] << 56);
            x ^=
                (~0L << 7)
                    ^ (~0L << 14)
                    ^ (~0L << 21)
                    ^ (~0L << 28)
                    ^ (~0L << 35)
                    ^ (~0L << 42)
                    ^ (~0L << 49)
                    ^ (~0L << 56);
            if (x < 0L) {
              if (buffer[tempPos++] < 0L) {
                break fastpath; // Will throw malformedVarint()
              }
            }
          }
          pos = tempPos;
          return x;
        }
        return readRawVarint64SlowPath();
      }
  
      @Override
      long readRawVarint64SlowPath() throws IOException {
        long result = 0;
        for (int shift = 0; shift < 64; shift += 7) {
          final byte b = readRawByte();
          result |= (long) (b & 0x7F) << shift;
          if ((b & 0x80) == 0) {
            return result;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      @Override
      public int readRawLittleEndian32() throws IOException {
        int tempPos = pos;
  
        if (bufferSize - tempPos < FIXED32_SIZE) {
          refillBuffer(FIXED32_SIZE);
          tempPos = pos;
        }
  
        final byte[] buffer = this.buffer;
        pos = tempPos + FIXED32_SIZE;
        return (((buffer[tempPos] & 0xff))
            | ((buffer[tempPos + 1] & 0xff) << 8)
            | ((buffer[tempPos + 2] & 0xff) << 16)
            | ((buffer[tempPos + 3] & 0xff) << 24));
      }
  
      @Override
      public long readRawLittleEndian64() throws IOException {
        int tempPos = pos;
  
        if (bufferSize - tempPos < FIXED64_SIZE) {
          refillBuffer(FIXED64_SIZE);
          tempPos = pos;
        }
  
        final byte[] buffer = this.buffer;
        pos = tempPos + FIXED64_SIZE;
        return (((buffer[tempPos] & 0xffL))
            | ((buffer[tempPos + 1] & 0xffL) << 8)
            | ((buffer[tempPos + 2] & 0xffL) << 16)
            | ((buffer[tempPos + 3] & 0xffL) << 24)
            | ((buffer[tempPos + 4] & 0xffL) << 32)
            | ((buffer[tempPos + 5] & 0xffL) << 40)
            | ((buffer[tempPos + 6] & 0xffL) << 48)
            | ((buffer[tempPos + 7] & 0xffL) << 56));
      }
  
      // -----------------------------------------------------------------
  
      @Override
      public void enableAliasing(boolean enabled) {
        // TODO(nathanmittler): Ideally we should throw here. Do nothing for backward compatibility.
      }
  
      @Override
      public void resetSizeCounter() {
        totalBytesRetired = -pos;
      }
  
      @Override
      public int pushLimit(int byteLimit) throws InvalidProtocolBufferException {
        if (byteLimit < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        byteLimit += totalBytesRetired + pos;
        final int oldLimit = currentLimit;
        if (byteLimit > oldLimit) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        currentLimit = byteLimit;
  
        recomputeBufferSizeAfterLimit();
  
        return oldLimit;
      }
  
      private void recomputeBufferSizeAfterLimit() {
        bufferSize += bufferSizeAfterLimit;
        final int bufferEnd = totalBytesRetired + bufferSize;
        if (bufferEnd > currentLimit) {
          // Limit is in current buffer.
          bufferSizeAfterLimit = bufferEnd - currentLimit;
          bufferSize -= bufferSizeAfterLimit;
        } else {
          bufferSizeAfterLimit = 0;
        }
      }
  
      @Override
      public void popLimit(final int oldLimit) {
        currentLimit = oldLimit;
        recomputeBufferSizeAfterLimit();
      }
  
      @Override
      public int getBytesUntilLimit() {
        if (currentLimit == Integer.MAX_VALUE) {
          return -1;
        }
  
        final int currentAbsolutePosition = totalBytesRetired + pos;
        return currentLimit - currentAbsolutePosition;
      }
  
      @Override
      public boolean isAtEnd() throws IOException {
        return pos == bufferSize && !tryRefillBuffer(1);
      }
  
      @Override
      public int getTotalBytesRead() {
        return totalBytesRetired + pos;
      }
  
      private interface RefillCallback {
        void onRefill();
      }
  
      private RefillCallback refillCallback = null;
  
      /**
       * Reads more bytes from the input, making at least {@code n} bytes available in the buffer.
       * Caller must ensure that the requested space is not yet available, and that the requested
       * space is less than BUFFER_SIZE.
       *
       * @throws InvalidProtocolBufferException The end of the stream or the current limit was
       *     reached.
       */
      private void refillBuffer(int n) throws IOException {
        if (!tryRefillBuffer(n)) {
          // We have to distinguish the exception between sizeLimitExceeded and truncatedMessage. So
          // we just throw an sizeLimitExceeded exception here if it exceeds the sizeLimit
          if (n > sizeLimit - totalBytesRetired - pos) {
            throw InvalidProtocolBufferException.sizeLimitExceeded();
          } else {
            throw InvalidProtocolBufferException.truncatedMessage();
          }
        }
      }
  
      /**
       * Tries to read more bytes from the input, making at least {@code n} bytes available in the
       * buffer. Caller must ensure that the requested space is not yet available, and that the
       * requested space is less than BUFFER_SIZE.
       *
       * @return {@code true} If the bytes could be made available; {@code false} 1. Current at the
       *     end of the stream 2. The current limit was reached 3. The total size limit was reached
       */
      private boolean tryRefillBuffer(int n) throws IOException {
        if (pos + n <= bufferSize) {
          throw new IllegalStateException(
              "refillBuffer() called when " + n + " bytes were already available in buffer");
        }
  
        // Check whether the size of total message needs to read is bigger than the size limit.
        // We shouldn't throw an exception here as isAtEnd() function needs to get this function's
        // return as the result.
        if (n > sizeLimit - totalBytesRetired - pos) {
          return false;
        }
  
        // Shouldn't throw the exception here either.
        if (totalBytesRetired + pos + n > currentLimit) {
          // Oops, we hit a limit.
          return false;
        }
  
        if (refillCallback != null) {
          refillCallback.onRefill();
        }
  
        int tempPos = pos;
        if (tempPos > 0) {
          if (bufferSize > tempPos) {
            System.arraycopy(buffer, tempPos, buffer, 0, bufferSize - tempPos);
          }
          totalBytesRetired += tempPos;
          bufferSize -= tempPos;
          pos = 0;
        }
  
        // Here we should refill the buffer as many bytes as possible.
        int bytesRead =
            input.read(
                buffer,
                bufferSize,
                Math.min(
                    //  the size of allocated but unused bytes in the buffer
                    buffer.length - bufferSize,
                    //  do not exceed the total bytes limit
                    sizeLimit - totalBytesRetired - bufferSize));
        if (bytesRead == 0 || bytesRead < -1 || bytesRead > buffer.length) {
          throw new IllegalStateException(
              "InputStream#read(byte[]) returned invalid result: "
                  + bytesRead
                  + "\nThe InputStream implementation is buggy.");
        }
        if (bytesRead > 0) {
          bufferSize += bytesRead;
          recomputeBufferSizeAfterLimit();
          return (bufferSize >= n) ? true : tryRefillBuffer(n);
        }
  
        return false;
      }
  
      @Override
      public byte readRawByte() throws IOException {
        if (pos == bufferSize) {
          refillBuffer(1);
        }
        return buffer[pos++];
      }
  
      @Override
      public byte[] readRawBytes(final int size) throws IOException {
        final int tempPos = pos;
        if (size <= (bufferSize - tempPos) && size > 0) {
          pos = tempPos + size;
          return Arrays.copyOfRange(buffer, tempPos, tempPos + size);
        } else {
          return readRawBytesSlowPath(size);
        }
      }
  
      /**
       * Exactly like readRawBytes, but caller must have already checked the fast path: (size <=
       * (bufferSize - pos) && size > 0)
       */
      private byte[] readRawBytesSlowPath(final int size) throws IOException {
        // Attempt to read the data in one byte array when it's safe to do.
        byte[] result = readRawBytesSlowPathOneChunk(size);
        if (result != null) {
          return result;
        }
  
        final int originalBufferPos = pos;
        final int bufferedBytes = bufferSize - pos;
  
        // Mark the current buffer consumed.
        totalBytesRetired += bufferSize;
        pos = 0;
        bufferSize = 0;
  
        // Determine the number of bytes we need to read from the input stream.
        int sizeLeft = size - bufferedBytes;
  
        // The size is very large. For security reasons we read them in small
        // chunks.
        List<byte[]> chunks = readRawBytesSlowPathRemainingChunks(sizeLeft);
  
        // OK, got everything.  Now concatenate it all into one buffer.
        final byte[] bytes = new byte[size];
  
        // Start by copying the leftover bytes from this.buffer.
        System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes);
  
        // And now all the chunks.
        int tempPos = bufferedBytes;
        for (final byte[] chunk : chunks) {
          System.arraycopy(chunk, 0, bytes, tempPos, chunk.length);
          tempPos += chunk.length;
        }
  
        // Done.
        return bytes;
      }
  
      /**
       * Attempts to read the data in one byte array when it's safe to do. Returns null if the size to
       * read is too large and needs to be allocated in smaller chunks for security reasons.
       */
      private byte[] readRawBytesSlowPathOneChunk(final int size) throws IOException {
        if (size == 0) {
          return Internal.EMPTY_BYTE_ARRAY;
        }
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
  
        // Integer-overflow-conscious check that the message size so far has not exceeded sizeLimit.
        int currentMessageSize = totalBytesRetired + pos + size;
        if (currentMessageSize - sizeLimit > 0) {
          throw InvalidProtocolBufferException.sizeLimitExceeded();
        }
  
        // Verify that the message size so far has not exceeded currentLimit.
        if (currentMessageSize > currentLimit) {
          // Read to the end of the stream anyway.
          skipRawBytes(currentLimit - totalBytesRetired - pos);
          throw InvalidProtocolBufferException.truncatedMessage();
        }
  
        final int bufferedBytes = bufferSize - pos;
        // Determine the number of bytes we need to read from the input stream.
        int sizeLeft = size - bufferedBytes;
        // TODO(nathanmittler): Consider using a value larger than DEFAULT_BUFFER_SIZE.
        if (sizeLeft < DEFAULT_BUFFER_SIZE || sizeLeft <= input.available()) {
          // Either the bytes we need are known to be available, or the required buffer is
          // within an allowed threshold - go ahead and allocate the buffer now.
          final byte[] bytes = new byte[size];
  
          // Copy all of the buffered bytes to the result buffer.
          System.arraycopy(buffer, pos, bytes, 0, bufferedBytes);
          totalBytesRetired += bufferSize;
          pos = 0;
          bufferSize = 0;
  
          // Fill the remaining bytes from the input stream.
          int tempPos = bufferedBytes;
          while (tempPos < bytes.length) {
            int n = input.read(bytes, tempPos, size - tempPos);
            if (n == -1) {
              throw InvalidProtocolBufferException.truncatedMessage();
            }
            totalBytesRetired += n;
            tempPos += n;
          }
  
          return bytes;
        }
  
        return null;
      }
  
      /** Reads the remaining data in small chunks from the input stream. */
      private List<byte[]> readRawBytesSlowPathRemainingChunks(int sizeLeft) throws IOException {
        // The size is very large.  For security reasons, we can't allocate the
        // entire byte array yet.  The size comes directly from the input, so a
        // maliciously-crafted message could provide a bogus very large size in
        // order to trick the app into allocating a lot of memory.  We avoid this
        // by allocating and reading only a small chunk at a time, so that the
        // malicious message must actually *be* extremely large to cause
        // problems.  Meanwhile, we limit the allowed size of a message elsewhere.
        final List<byte[]> chunks = new ArrayList<byte[]>();
  
        while (sizeLeft > 0) {
          // TODO(nathanmittler): Consider using a value larger than DEFAULT_BUFFER_SIZE.
          final byte[] chunk = new byte[Math.min(sizeLeft, DEFAULT_BUFFER_SIZE)];
          int tempPos = 0;
          while (tempPos < chunk.length) {
            final int n = input.read(chunk, tempPos, chunk.length - tempPos);
            if (n == -1) {
              throw InvalidProtocolBufferException.truncatedMessage();
            }
            totalBytesRetired += n;
            tempPos += n;
          }
          sizeLeft -= chunk.length;
          chunks.add(chunk);
        }
  
        return chunks;
      }
  
      /**
       * Like readBytes, but caller must have already checked the fast path: (size <= (bufferSize -
       * pos) && size > 0 || size == 0)
       */
      private ByteString readBytesSlowPath(final int size) throws IOException {
        final byte[] result = readRawBytesSlowPathOneChunk(size);
        if (result != null) {
          return ByteString.wrap(result);
        }
  
        final int originalBufferPos = pos;
        final int bufferedBytes = bufferSize - pos;
  
        // Mark the current buffer consumed.
        totalBytesRetired += bufferSize;
        pos = 0;
        bufferSize = 0;
  
        // Determine the number of bytes we need to read from the input stream.
        int sizeLeft = size - bufferedBytes;
  
        // The size is very large. For security reasons we read them in small
        // chunks.
        List<byte[]> chunks = readRawBytesSlowPathRemainingChunks(sizeLeft);
  
        // Wrap the byte arrays into a single ByteString.
        List<ByteString> byteStrings = new ArrayList<ByteString>(1 + chunks.size());
        byteStrings.add(ByteString.copyFrom(buffer, originalBufferPos, bufferedBytes));
        for (byte[] chunk : chunks) {
          byteStrings.add(ByteString.wrap(chunk));
        }
        return ByteString.copyFrom(byteStrings);
      }
  
      @Override
      public void skipRawBytes(final int size) throws IOException {
        if (size <= (bufferSize - pos) && size >= 0) {
          // We have all the bytes we need already.
          pos += size;
        } else {
          skipRawBytesSlowPath(size);
        }
      }
  
      /**
       * Exactly like skipRawBytes, but caller must have already checked the fast path: (size <=
       * (bufferSize - pos) && size >= 0)
       */
      private void skipRawBytesSlowPath(final int size) throws IOException {
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
  
        if (totalBytesRetired + pos + size > currentLimit) {
          // Read to the end of the stream anyway.
          skipRawBytes(currentLimit - totalBytesRetired - pos);
          // Then fail.
          throw InvalidProtocolBufferException.truncatedMessage();
        }
  
        // Skipping more bytes than are in the buffer.  First skip what we have.
        int tempPos = bufferSize - pos;
        pos = bufferSize;
  
        // Keep refilling the buffer until we get to the point we wanted to skip to.
        // This has the side effect of ensuring the limits are updated correctly.
        refillBuffer(1);
        while (size - tempPos > bufferSize) {
          tempPos += bufferSize;
          pos = bufferSize;
          refillBuffer(1);
        }
  
        pos = size - tempPos;
      }
    }
  
    /**
     * Implementation of {@link CodedInputStream} that uses an {@link Iterable <ByteBuffer>} as the
     * data source. Requires the use of {@code sun.misc.Unsafe} to perform fast reads on the buffer.
     */
    private static final class IterableDirectByteBufferDecoder extends CodedInputStream {
      /** The object that need to decode. */
      private Iterable<ByteBuffer> input;
      /** The {@link Iterator} with type {@link ByteBuffer} of {@code input} */
      private Iterator<ByteBuffer> iterator;
      /** The current ByteBuffer; */
      private ByteBuffer currentByteBuffer;
      /**
       * If {@code true}, indicates that all the buffer are backing a {@link ByteString} and are
       * therefore considered to be an immutable input source.
       */
      private boolean immutable;
      /**
       * If {@code true}, indicates that calls to read {@link ByteString} or {@code byte[]}
       * <strong>may</strong> return slices of the underlying buffer, rather than copies.
       */
      private boolean enableAliasing;
      /** The global total message length limit */
      private int totalBufferSize;
      /** The amount of available data in the input beyond {@link #currentLimit}. */
      private int bufferSizeAfterCurrentLimit;
      /** The absolute position of the end of the current message. */
      private int currentLimit = Integer.MAX_VALUE;
      /** The last tag that was read from this stream. */
      private int lastTag;
      /** Total Bytes have been Read from the {@link Iterable} {@link ByteBuffer} */
      private int totalBytesRead;
      /** The start position offset of the whole message, used as to reset the totalBytesRead */
      private int startOffset;
      /** The current position for current ByteBuffer */
      private long currentByteBufferPos;
  
      private long currentByteBufferStartPos;
      /**
       * If the current ByteBuffer is unsafe-direct based, currentAddress is the start address of this
       * ByteBuffer; otherwise should be zero.
       */
      private long currentAddress;
      /** The limit position for current ByteBuffer */
      private long currentByteBufferLimit;
  
      /**
       * The constructor of {@code Iterable<ByteBuffer>} decoder.
       *
       * @param inputBufs The input data.
       * @param size The total size of the input data.
       * @param immutableFlag whether the input data is immutable.
       */
      private IterableDirectByteBufferDecoder(
          Iterable<ByteBuffer> inputBufs, int size, boolean immutableFlag) {
        totalBufferSize = size;
        input = inputBufs;
        iterator = input.iterator();
        immutable = immutableFlag;
        startOffset = totalBytesRead = 0;
        if (size == 0) {
          currentByteBuffer = EMPTY_BYTE_BUFFER;
          currentByteBufferPos = 0;
          currentByteBufferStartPos = 0;
          currentByteBufferLimit = 0;
          currentAddress = 0;
        } else {
          tryGetNextByteBuffer();
        }
      }
  
      /** To get the next ByteBuffer from {@code input}, and then update the parameters */
      private void getNextByteBuffer() throws InvalidProtocolBufferException {
        if (!iterator.hasNext()) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        tryGetNextByteBuffer();
      }
  
      private void tryGetNextByteBuffer() {
        currentByteBuffer = iterator.next();
        totalBytesRead += (int) (currentByteBufferPos - currentByteBufferStartPos);
        currentByteBufferPos = currentByteBuffer.position();
        currentByteBufferStartPos = currentByteBufferPos;
        currentByteBufferLimit = currentByteBuffer.limit();
        currentAddress = UnsafeUtil.addressOffset(currentByteBuffer);
        currentByteBufferPos += currentAddress;
        currentByteBufferStartPos += currentAddress;
        currentByteBufferLimit += currentAddress;
      }
  
      @Override
      public int readTag() throws IOException {
        if (isAtEnd()) {
          lastTag = 0;
          return 0;
        }
  
        lastTag = readRawVarint32();
        if (WireFormat.getTagFieldNumber(lastTag) == 0) {
          // If we actually read zero (or any tag number corresponding to field
          // number zero), that's not a valid tag.
          throw InvalidProtocolBufferException.invalidTag();
        }
        return lastTag;
      }
  
      @Override
      public void checkLastTagWas(final int value) throws InvalidProtocolBufferException {
        if (lastTag != value) {
          throw InvalidProtocolBufferException.invalidEndTag();
        }
      }
  
      @Override
      public int getLastTag() {
        return lastTag;
      }
  
      @Override
      public boolean skipField(final int tag) throws IOException {
        switch (WireFormat.getTagWireType(tag)) {
          case WireFormat.WIRETYPE_VARINT:
            skipRawVarint();
            return true;
          case WireFormat.WIRETYPE_FIXED64:
            skipRawBytes(FIXED64_SIZE);
            return true;
          case WireFormat.WIRETYPE_LENGTH_DELIMITED:
            skipRawBytes(readRawVarint32());
            return true;
          case WireFormat.WIRETYPE_START_GROUP:
            skipMessage();
            checkLastTagWas(
                WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP));
            return true;
          case WireFormat.WIRETYPE_END_GROUP:
            return false;
          case WireFormat.WIRETYPE_FIXED32:
            skipRawBytes(FIXED32_SIZE);
            return true;
          default:
            throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      @Override
      public boolean skipField(final int tag, final CodedOutputStream output) throws IOException {
        switch (WireFormat.getTagWireType(tag)) {
          case WireFormat.WIRETYPE_VARINT:
            {
              long value = readInt64();
              output.writeRawVarint32(tag);
              output.writeUInt64NoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_FIXED64:
            {
              long value = readRawLittleEndian64();
              output.writeRawVarint32(tag);
              output.writeFixed64NoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_LENGTH_DELIMITED:
            {
              ByteString value = readBytes();
              output.writeRawVarint32(tag);
              output.writeBytesNoTag(value);
              return true;
            }
          case WireFormat.WIRETYPE_START_GROUP:
            {
              output.writeRawVarint32(tag);
              skipMessage(output);
              int endtag =
                  WireFormat.makeTag(
                      WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP);
              checkLastTagWas(endtag);
              output.writeRawVarint32(endtag);
              return true;
            }
          case WireFormat.WIRETYPE_END_GROUP:
            {
              return false;
            }
          case WireFormat.WIRETYPE_FIXED32:
            {
              int value = readRawLittleEndian32();
              output.writeRawVarint32(tag);
              output.writeFixed32NoTag(value);
              return true;
            }
          default:
            throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      @Override
      public void skipMessage() throws IOException {
        while (true) {
          final int tag = readTag();
          if (tag == 0 || !skipField(tag)) {
            return;
          }
        }
      }
  
      @Override
      public void skipMessage(CodedOutputStream output) throws IOException {
        while (true) {
          final int tag = readTag();
          if (tag == 0 || !skipField(tag, output)) {
            return;
          }
        }
      }
  
      // -----------------------------------------------------------------
  
      @Override
      public double readDouble() throws IOException {
        return Double.longBitsToDouble(readRawLittleEndian64());
      }
  
      @Override
      public float readFloat() throws IOException {
        return Float.intBitsToFloat(readRawLittleEndian32());
      }
  
      @Override
      public long readUInt64() throws IOException {
        return readRawVarint64();
      }
  
      @Override
      public long readInt64() throws IOException {
        return readRawVarint64();
      }
  
      @Override
      public int readInt32() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public long readFixed64() throws IOException {
        return readRawLittleEndian64();
      }
  
      @Override
      public int readFixed32() throws IOException {
        return readRawLittleEndian32();
      }
  
      @Override
      public boolean readBool() throws IOException {
        return readRawVarint64() != 0;
      }
  
      @Override
      public String readString() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= currentByteBufferLimit - currentByteBufferPos) {
          byte[] bytes = new byte[size];
          UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, size);
          String result = new String(bytes, UTF_8);
          currentByteBufferPos += size;
          return result;
        } else if (size > 0 && size <= remaining()) {
          // TODO(yilunchong): To use an underlying bytes[] instead of allocating a new bytes[]
          byte[] bytes = new byte[size];
          readRawBytesTo(bytes, 0, size);
          String result = new String(bytes, UTF_8);
          return result;
        }
  
        if (size == 0) {
          return "";
        }
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public String readStringRequireUtf8() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= currentByteBufferLimit - currentByteBufferPos) {
          if (ENABLE_CUSTOM_UTF8_DECODE) {
            final int bufferPos = (int) (currentByteBufferPos - currentByteBufferStartPos);
            String result = Utf8.decodeUtf8(currentByteBuffer, bufferPos, size);
            currentByteBufferPos += size;
            return result;
          } else {
            byte[] bytes = new byte[size];
            UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, size);
            if (!Utf8.isValidUtf8(bytes)) {
              throw InvalidProtocolBufferException.invalidUtf8();
            }
            String result = new String(bytes, UTF_8);
            currentByteBufferPos += size;
            return result;
          }
        }
        if (size >= 0 && size <= remaining()) {
          byte[] bytes = new byte[size];
          readRawBytesTo(bytes, 0, size);
          if (ENABLE_CUSTOM_UTF8_DECODE) {
            return Utf8.decodeUtf8(bytes, 0, size);
          } else {
            if (!Utf8.isValidUtf8(bytes)) {
              throw InvalidProtocolBufferException.invalidUtf8();
            }
            String result = new String(bytes, UTF_8);
            return result;
          }
        }
  
        if (size == 0) {
          return "";
        }
        if (size <= 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public void readGroup(
          final int fieldNumber,
          final MessageLite.Builder builder,
          final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        ++recursionDepth;
        builder.mergeFrom(this, extensionRegistry);
        checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
        --recursionDepth;
      }
  
  
      @Override
      public <T extends MessageLite> T readGroup(
          final int fieldNumber,
          final Parser<T> parser,
          final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        ++recursionDepth;
        T result = parser.parsePartialFrom(this, extensionRegistry);
        checkLastTagWas(WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP));
        --recursionDepth;
        return result;
      }
  
      @Deprecated
      @Override
      public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder)
          throws IOException {
        readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry());
      }
  
      @Override
      public void readMessage(
          final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry)
          throws IOException {
        final int length = readRawVarint32();
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        final int oldLimit = pushLimit(length);
        ++recursionDepth;
        builder.mergeFrom(this, extensionRegistry);
        checkLastTagWas(0);
        --recursionDepth;
        popLimit(oldLimit);
      }
  
  
      @Override
      public <T extends MessageLite> T readMessage(
          final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException {
        int length = readRawVarint32();
        if (recursionDepth >= recursionLimit) {
          throw InvalidProtocolBufferException.recursionLimitExceeded();
        }
        final int oldLimit = pushLimit(length);
        ++recursionDepth;
        T result = parser.parsePartialFrom(this, extensionRegistry);
        checkLastTagWas(0);
        --recursionDepth;
        popLimit(oldLimit);
        return result;
      }
  
      @Override
      public ByteString readBytes() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= currentByteBufferLimit - currentByteBufferPos) {
          if (immutable && enableAliasing) {
            final int idx = (int) (currentByteBufferPos - currentAddress);
            final ByteString result = ByteString.wrap(slice(idx, idx + size));
            currentByteBufferPos += size;
            return result;
          } else {
            byte[] bytes;
            bytes = new byte[size];
            UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, size);
            currentByteBufferPos += size;
            return ByteString.wrap(bytes);
          }
        } else if (size > 0 && size <= remaining()) {
          byte[] temp = new byte[size];
          readRawBytesTo(temp, 0, size);
          return ByteString.wrap(temp);
        }
  
        if (size == 0) {
          return ByteString.EMPTY;
        }
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public byte[] readByteArray() throws IOException {
        return readRawBytes(readRawVarint32());
      }
  
      @Override
      public ByteBuffer readByteBuffer() throws IOException {
        final int size = readRawVarint32();
        if (size > 0 && size <= currentRemaining()) {
          if (!immutable && enableAliasing) {
            currentByteBufferPos += size;
            return slice(
                (int) (currentByteBufferPos - currentAddress - size),
                (int) (currentByteBufferPos - currentAddress));
          } else {
            byte[] bytes = new byte[size];
            UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, size);
            currentByteBufferPos += size;
            return ByteBuffer.wrap(bytes);
          }
        } else if (size > 0 && size <= remaining()) {
          byte[] temp = new byte[size];
          readRawBytesTo(temp, 0, size);
          return ByteBuffer.wrap(temp);
        }
  
        if (size == 0) {
          return EMPTY_BYTE_BUFFER;
        }
        if (size < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public int readUInt32() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public int readEnum() throws IOException {
        return readRawVarint32();
      }
  
      @Override
      public int readSFixed32() throws IOException {
        return readRawLittleEndian32();
      }
  
      @Override
      public long readSFixed64() throws IOException {
        return readRawLittleEndian64();
      }
  
      @Override
      public int readSInt32() throws IOException {
        return decodeZigZag32(readRawVarint32());
      }
  
      @Override
      public long readSInt64() throws IOException {
        return decodeZigZag64(readRawVarint64());
      }
  
      @Override
      public int readRawVarint32() throws IOException {
        fastpath:
        {
          long tempPos = currentByteBufferPos;
  
          if (currentByteBufferLimit == currentByteBufferPos) {
            break fastpath;
          }
  
          int x;
          if ((x = UnsafeUtil.getByte(tempPos++)) >= 0) {
            currentByteBufferPos++;
            return x;
          } else if (currentByteBufferLimit - currentByteBufferPos < 10) {
            break fastpath;
          } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) {
            x ^= (~0 << 7);
          } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) {
            x ^= (~0 << 7) ^ (~0 << 14);
          } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) {
            x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21);
          } else {
            int y = UnsafeUtil.getByte(tempPos++);
            x ^= y << 28;
            x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28);
            if (y < 0
                && UnsafeUtil.getByte(tempPos++) < 0
                && UnsafeUtil.getByte(tempPos++) < 0
                && UnsafeUtil.getByte(tempPos++) < 0
                && UnsafeUtil.getByte(tempPos++) < 0
                && UnsafeUtil.getByte(tempPos++) < 0) {
              break fastpath; // Will throw malformedVarint()
            }
          }
          currentByteBufferPos = tempPos;
          return x;
        }
        return (int) readRawVarint64SlowPath();
      }
  
      @Override
      public long readRawVarint64() throws IOException {
        fastpath:
        {
          long tempPos = currentByteBufferPos;
  
          if (currentByteBufferLimit == currentByteBufferPos) {
            break fastpath;
          }
  
          long x;
          int y;
          if ((y = UnsafeUtil.getByte(tempPos++)) >= 0) {
            currentByteBufferPos++;
            return y;
          } else if (currentByteBufferLimit - currentByteBufferPos < 10) {
            break fastpath;
          } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) {
            x = y ^ (~0 << 7);
          } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) {
            x = y ^ ((~0 << 7) ^ (~0 << 14));
          } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) {
            x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21));
          } else if ((x = y ^ ((long) UnsafeUtil.getByte(tempPos++) << 28)) >= 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28);
          } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 35)) < 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35);
          } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 42)) >= 0L) {
            x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42);
          } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 49)) < 0L) {
            x ^=
                (~0L << 7)
                    ^ (~0L << 14)
                    ^ (~0L << 21)
                    ^ (~0L << 28)
                    ^ (~0L << 35)
                    ^ (~0L << 42)
                    ^ (~0L << 49);
          } else {
            x ^= ((long) UnsafeUtil.getByte(tempPos++) << 56);
            x ^=
                (~0L << 7)
                    ^ (~0L << 14)
                    ^ (~0L << 21)
                    ^ (~0L << 28)
                    ^ (~0L << 35)
                    ^ (~0L << 42)
                    ^ (~0L << 49)
                    ^ (~0L << 56);
            if (x < 0L) {
              if (UnsafeUtil.getByte(tempPos++) < 0L) {
                break fastpath; // Will throw malformedVarint()
              }
            }
          }
          currentByteBufferPos = tempPos;
          return x;
        }
        return readRawVarint64SlowPath();
      }
  
      @Override
      long readRawVarint64SlowPath() throws IOException {
        long result = 0;
        for (int shift = 0; shift < 64; shift += 7) {
          final byte b = readRawByte();
          result |= (long) (b & 0x7F) << shift;
          if ((b & 0x80) == 0) {
            return result;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      @Override
      public int readRawLittleEndian32() throws IOException {
        if (currentRemaining() >= FIXED32_SIZE) {
          long tempPos = currentByteBufferPos;
          currentByteBufferPos += FIXED32_SIZE;
          return (((UnsafeUtil.getByte(tempPos) & 0xff))
              | ((UnsafeUtil.getByte(tempPos + 1) & 0xff) << 8)
              | ((UnsafeUtil.getByte(tempPos + 2) & 0xff) << 16)
              | ((UnsafeUtil.getByte(tempPos + 3) & 0xff) << 24));
        }
        return ((readRawByte() & 0xff)
            | ((readRawByte() & 0xff) << 8)
            | ((readRawByte() & 0xff) << 16)
            | ((readRawByte() & 0xff) << 24));
      }
  
      @Override
      public long readRawLittleEndian64() throws IOException {
        if (currentRemaining() >= FIXED64_SIZE) {
          long tempPos = currentByteBufferPos;
          currentByteBufferPos += FIXED64_SIZE;
          return (((UnsafeUtil.getByte(tempPos) & 0xffL))
              | ((UnsafeUtil.getByte(tempPos + 1) & 0xffL) << 8)
              | ((UnsafeUtil.getByte(tempPos + 2) & 0xffL) << 16)
              | ((UnsafeUtil.getByte(tempPos + 3) & 0xffL) << 24)
              | ((UnsafeUtil.getByte(tempPos + 4) & 0xffL) << 32)
              | ((UnsafeUtil.getByte(tempPos + 5) & 0xffL) << 40)
              | ((UnsafeUtil.getByte(tempPos + 6) & 0xffL) << 48)
              | ((UnsafeUtil.getByte(tempPos + 7) & 0xffL) << 56));
        }
        return ((readRawByte() & 0xffL)
            | ((readRawByte() & 0xffL) << 8)
            | ((readRawByte() & 0xffL) << 16)
            | ((readRawByte() & 0xffL) << 24)
            | ((readRawByte() & 0xffL) << 32)
            | ((readRawByte() & 0xffL) << 40)
            | ((readRawByte() & 0xffL) << 48)
            | ((readRawByte() & 0xffL) << 56));
      }
  
      @Override
      public void enableAliasing(boolean enabled) {
        this.enableAliasing = enabled;
      }
  
      @Override
      public void resetSizeCounter() {
        startOffset = (int) (totalBytesRead + currentByteBufferPos - currentByteBufferStartPos);
      }
  
      @Override
      public int pushLimit(int byteLimit) throws InvalidProtocolBufferException {
        if (byteLimit < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        byteLimit += getTotalBytesRead();
        final int oldLimit = currentLimit;
        if (byteLimit > oldLimit) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        currentLimit = byteLimit;
  
        recomputeBufferSizeAfterLimit();
  
        return oldLimit;
      }
  
      private void recomputeBufferSizeAfterLimit() {
        totalBufferSize += bufferSizeAfterCurrentLimit;
        final int bufferEnd = totalBufferSize - startOffset;
        if (bufferEnd > currentLimit) {
          // Limit is in current buffer.
          bufferSizeAfterCurrentLimit = bufferEnd - currentLimit;
          totalBufferSize -= bufferSizeAfterCurrentLimit;
        } else {
          bufferSizeAfterCurrentLimit = 0;
        }
      }
  
      @Override
      public void popLimit(final int oldLimit) {
        currentLimit = oldLimit;
        recomputeBufferSizeAfterLimit();
      }
  
      @Override
      public int getBytesUntilLimit() {
        if (currentLimit == Integer.MAX_VALUE) {
          return -1;
        }
  
        return currentLimit - getTotalBytesRead();
      }
  
      @Override
      public boolean isAtEnd() throws IOException {
        return totalBytesRead + currentByteBufferPos - currentByteBufferStartPos == totalBufferSize;
      }
  
      @Override
      public int getTotalBytesRead() {
        return (int)
            (totalBytesRead - startOffset + currentByteBufferPos - currentByteBufferStartPos);
      }
  
      @Override
      public byte readRawByte() throws IOException {
        if (currentRemaining() == 0) {
          getNextByteBuffer();
        }
        return UnsafeUtil.getByte(currentByteBufferPos++);
      }
  
      @Override
      public byte[] readRawBytes(final int length) throws IOException {
        if (length >= 0 && length <= currentRemaining()) {
          byte[] bytes = new byte[length];
          UnsafeUtil.copyMemory(currentByteBufferPos, bytes, 0, length);
          currentByteBufferPos += length;
          return bytes;
        }
        if (length >= 0 && length <= remaining()) {
          byte[] bytes = new byte[length];
          readRawBytesTo(bytes, 0, length);
          return bytes;
        }
  
        if (length <= 0) {
          if (length == 0) {
            return EMPTY_BYTE_ARRAY;
          } else {
            throw InvalidProtocolBufferException.negativeSize();
          }
        }
  
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      /**
       * Try to get raw bytes from {@code input} with the size of {@code length} and copy to {@code
       * bytes} array. If the size is bigger than the number of remaining bytes in the input, then
       * throw {@code truncatedMessage} exception.
       *
       * @param bytes
       * @param offset
       * @param length
       * @throws IOException
       */
      private void readRawBytesTo(byte[] bytes, int offset, final int length) throws IOException {
        if (length >= 0 && length <= remaining()) {
          int l = length;
          while (l > 0) {
            if (currentRemaining() == 0) {
              getNextByteBuffer();
            }
            int bytesToCopy = Math.min(l, (int) currentRemaining());
            UnsafeUtil.copyMemory(currentByteBufferPos, bytes, length - l + offset, bytesToCopy);
            l -= bytesToCopy;
            currentByteBufferPos += bytesToCopy;
          }
          return;
        }
  
        if (length <= 0) {
          if (length == 0) {
            return;
          } else {
            throw InvalidProtocolBufferException.negativeSize();
          }
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      @Override
      public void skipRawBytes(final int length) throws IOException {
        if (length >= 0
            && length
                <= (totalBufferSize
                    - totalBytesRead
                    - currentByteBufferPos
                    + currentByteBufferStartPos)) {
          // We have all the bytes we need already.
          int l = length;
          while (l > 0) {
            if (currentRemaining() == 0) {
              getNextByteBuffer();
            }
            int rl = Math.min(l, (int) currentRemaining());
            l -= rl;
            currentByteBufferPos += rl;
          }
          return;
        }
  
        if (length < 0) {
          throw InvalidProtocolBufferException.negativeSize();
        }
        throw InvalidProtocolBufferException.truncatedMessage();
      }
  
      // TODO: optimize to fastpath
      private void skipRawVarint() throws IOException {
        for (int i = 0; i < MAX_VARINT_SIZE; i++) {
          if (readRawByte() >= 0) {
            return;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      /**
       * Try to get the number of remaining bytes in {@code input}.
       *
       * @return the number of remaining bytes in {@code input}.
       */
      private int remaining() {
        return (int)
            (totalBufferSize - totalBytesRead - currentByteBufferPos + currentByteBufferStartPos);
      }
  
      /**
       * Try to get the number of remaining bytes in {@code currentByteBuffer}.
       *
       * @return the number of remaining bytes in {@code currentByteBuffer}
       */
      private long currentRemaining() {
        return (currentByteBufferLimit - currentByteBufferPos);
      }
  
      private ByteBuffer slice(int begin, int end) throws IOException {
        int prevPos = currentByteBuffer.position();
        int prevLimit = currentByteBuffer.limit();
        try {
          currentByteBuffer.position(begin);
          currentByteBuffer.limit(end);
          return currentByteBuffer.slice();
        } catch (IllegalArgumentException e) {
          throw InvalidProtocolBufferException.truncatedMessage();
        } finally {
          currentByteBuffer.position(prevPos);
          currentByteBuffer.limit(prevLimit);
        }
      }
    }
  }
  