content: // 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}.
     * Currently disabled.
     */
    private static final boolean ENABLE_CUSTOM_UTF8_DECODE = false;
  
    /** 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);
        }
      }
    }
  }
  