content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.util.proto;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  
  /**
   * Class to read to a protobuf stream.
   *
   * Each read method takes an ID code from the protoc generated classes
   * and return a value of the field. To read a nested object, call #start
   * and then #end when you are done.
   *
   * The ID codes have type information embedded into them, so if you call
   * the incorrect function you will get an IllegalArgumentException.
   *
   * nextField will return the field number of the next field, which can be
   * matched to the protoc generated ID code and used to determine how to
   * read the next field.
   *
   * It is STRONGLY RECOMMENDED to read from the ProtoInputStream with a switch
   * statement wrapped in a while loop. Additionally, it is worth logging or
   * storing unexpected fields or ones that do not match the expected wire type
   *
   * ex:
   * void parseFromProto(ProtoInputStream stream) {
   *     while(stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
   *         try {
   *             switch (stream.getFieldNumber()) {
   *                 case (int) DummyProto.NAME:
   *                     mName = stream.readString(DummyProto.NAME);
   *                     break;
   *                 case (int) DummyProto.VALUE:
   *                     mValue = stream.readInt(DummyProto.VALUE);
   *                     break;
   *                 default:
   *                     LOG(TAG, "Unhandled field in proto!\n"
   *                              + ProtoUtils.currentFieldToString(stream));
   *             }
   *         } catch (WireTypeMismatchException wtme) {
   *             LOG(TAG, "Wire Type mismatch in proto!\n" + ProtoUtils.currentFieldToString(stream));
   *         }
   *     }
   * }
   *
   * @hide
   */
  public final class ProtoInputStream extends ProtoStream {
  
      public static final int NO_MORE_FIELDS = -1;
  
      /**
       * Our stream.  If there is one.
       */
      private InputStream mStream;
  
      /**
       * The field number of the current field. Will be equal to NO_MORE_FIELDS if end of message is
       * reached
       */
      private int mFieldNumber;
  
      /**
       * The wire type of the current field
       */
      private int mWireType;
  
      private static final byte STATE_STARTED_FIELD_READ = 1 << 0;
      private static final byte STATE_READING_PACKED = 1 << 1;
      private static final byte STATE_FIELD_MISS = 2 << 1;
  
      /**
       * Tracks some boolean states for the proto input stream
       * bit 0: Started Field Read, true - tag has been read, ready to read field data.
       * false - field data has been read, reading to start next field.
       * bit 1: Reading Packed Field, true - currently reading values from a packed field
       * false - not reading from packed field.
       */
      private byte mState = 0;
  
      /**
       * Keeps track of the currently read nested Objects, for end object sanity checking and debug
       */
      private ArrayList<Long> mExpectedObjectTokenStack = null;
  
      /**
       * Current nesting depth of start calls.
       */
      private int mDepth = -1;
  
      /**
       * Buffer for the to be read data. If mStream is not null, it will be constantly refilled from
       * the stream.
       */
      private byte[] mBuffer;
  
      private static final int DEFAULT_BUFFER_SIZE = 8192;
  
      /**
       * Size of the buffer if reading from a stream.
       */
      private final int mBufferSize;
  
      /**
       * The number of bytes that have been skipped or dropped from the buffer.
       */
      private int mDiscardedBytes = 0;
  
      /**
       * Current offset in the buffer
       * mOffset + mDiscardedBytes = current offset in proto binary
       */
      private int mOffset = 0;
  
      /**
       * Note the offset of the last byte in the buffer. Usually will equal the size of the buffer.
       * mEnd + mDiscardedBytes = the last known byte offset + 1
       */
      private int mEnd = 0;
  
      /**
       * Packed repeated fields are not read in one go. mPackedEnd keeps track of where the packed
       * field ends in the proto binary if current field is packed.
       */
      private int mPackedEnd = 0;
  
      /**
       * Construct a ProtoInputStream on top of an InputStream to read a proto. Also specify the
       * number of bytes the ProtoInputStream will buffer from the input stream
       *
       * @param stream from which the proto is read
       */
      public ProtoInputStream(InputStream stream, int bufferSize) {
          mStream = stream;
          if (bufferSize > 0) {
              mBufferSize = bufferSize;
          } else {
              mBufferSize = DEFAULT_BUFFER_SIZE;
          }
          mBuffer = new byte[mBufferSize];
      }
  
      /**
       * Construct a ProtoInputStream on top of an InputStream to read a proto
       *
       * @param stream from which the proto is read
       */
      public ProtoInputStream(InputStream stream) {
          this(stream, DEFAULT_BUFFER_SIZE);
      }
  
      /**
       * Construct a ProtoInputStream to read a proto directly from a byte array
       *
       * @param buffer - the byte array to be parsed
       */
      public ProtoInputStream(byte[] buffer) {
          mBufferSize = buffer.length;
          mEnd = buffer.length;
          mBuffer = buffer;
          mStream = null;
      }
  
      /**
       * Get the field number of the current field.
       */
      public int getFieldNumber() {
          return mFieldNumber;
      }
  
      /**
       * Get the wire type of the current field.
       *
       * @return an int that matches one of the ProtoStream WIRE_TYPE_ constants
       */
      public int getWireType() {
          if ((mState & STATE_READING_PACKED) == STATE_READING_PACKED) {
              // mWireType got overwritten when STATE_READING_PACKED was set. Send length delimited
              // constant instead
              return WIRE_TYPE_LENGTH_DELIMITED;
          }
          return mWireType;
      }
  
      /**
       * Get the current offset in the proto binary.
       */
      public int getOffset() {
          return mOffset + mDiscardedBytes;
      }
  
      /**
       * Reads the tag of the next field from the stream. If previous field value was not read, its
       * data will be skipped over.
       *
       * @return the field number of the next field
       * @throws IOException if an I/O error occurs
       */
      public int nextField() throws IOException {
  
          if ((mState & STATE_FIELD_MISS) == STATE_FIELD_MISS) {
              // Data from the last nextField was not used, reuse the info
              mState &= ~STATE_FIELD_MISS;
              return mFieldNumber;
          }
          if ((mState & STATE_STARTED_FIELD_READ) == STATE_STARTED_FIELD_READ) {
              // Field data was not read, skip to the next field
              skip();
              mState &= ~STATE_STARTED_FIELD_READ;
          }
          if ((mState & STATE_READING_PACKED) == STATE_READING_PACKED) {
              if (getOffset() < mPackedEnd) {
                  // In the middle of a packed field, return the same tag until last packed value
                  // has been read
                  mState |= STATE_STARTED_FIELD_READ;
                  return mFieldNumber;
              } else if (getOffset() == mPackedEnd) {
                  // Reached the end of the packed field
                  mState &= ~STATE_READING_PACKED;
              } else {
                  throw new ProtoParseException(
                          "Unexpectedly reached end of packed field at offset 0x"
                                  + Integer.toHexString(mPackedEnd)
                                  + dumpDebugData());
              }
          }
  
          if ((mDepth >= 0) && (getOffset() == getOffsetFromToken(
                  mExpectedObjectTokenStack.get(mDepth)))) {
              // reached end of a embedded message
              mFieldNumber = NO_MORE_FIELDS;
          } else {
              readTag();
          }
          return mFieldNumber;
      }
  
      /**
       * Attempt to guess the next field. If there is a match, the field data will be ready to read.
       * If there is no match, nextField will need to be called to get the field number
       *
       * @return true if fieldId matches the next field, false if not
       */
      public boolean isNextField(long fieldId) throws IOException {
          if (nextField() == (int) fieldId) {
              return true;
          }
          // Note to reuse the info from the nextField call in the next call.
          mState |= STATE_FIELD_MISS;
          return false;
      }
  
      /**
       * Read a single double.
       * Will throw if the current wire type is not fixed64
       *
       * @param fieldId - must match the current field number and field type
       */
      public double readDouble(long fieldId) throws IOException {
          assertFreshData();
          assertFieldNumber(fieldId);
          checkPacked(fieldId);
  
          double value;
          switch ((int) ((fieldId & FIELD_TYPE_MASK)
                  >>> FIELD_TYPE_SHIFT)) {
              case (int) (FIELD_TYPE_DOUBLE >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_FIXED64);
                  value = Double.longBitsToDouble(readFixed64());
                  break;
              default:
                  throw new IllegalArgumentException(
                          "Requested field id (" + getFieldIdString(fieldId)
                                  + ") cannot be read as a double"
                                  + dumpDebugData());
          }
          // Successfully read the field
          mState &= ~STATE_STARTED_FIELD_READ;
          return value;
      }
  
      /**
       * Read a single float.
       * Will throw if the current wire type is not fixed32
       *
       * @param fieldId - must match the current field number and field type
       */
      public float readFloat(long fieldId) throws IOException {
          assertFreshData();
          assertFieldNumber(fieldId);
          checkPacked(fieldId);
  
          float value;
          switch ((int) ((fieldId & FIELD_TYPE_MASK)
                  >>> FIELD_TYPE_SHIFT)) {
              case (int) (FIELD_TYPE_FLOAT >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_FIXED32);
                  value = Float.intBitsToFloat(readFixed32());
                  break;
              default:
                  throw new IllegalArgumentException(
                          "Requested field id (" + getFieldIdString(fieldId) + ") is not a float"
                                  + dumpDebugData());
          }
          // Successfully read the field
          mState &= ~STATE_STARTED_FIELD_READ;
          return value;
      }
  
      /**
       * Read a single 32bit or varint proto type field as an int.
       * Will throw if the current wire type is not varint or fixed32
       *
       * @param fieldId - must match the current field number and field type
       */
      public int readInt(long fieldId) throws IOException {
          assertFreshData();
          assertFieldNumber(fieldId);
          checkPacked(fieldId);
  
          int value;
          switch ((int) ((fieldId & FIELD_TYPE_MASK)
                  >>> FIELD_TYPE_SHIFT)) {
              case (int) (FIELD_TYPE_FIXED32 >>> FIELD_TYPE_SHIFT):
              case (int) (FIELD_TYPE_SFIXED32 >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_FIXED32);
                  value = readFixed32();
                  break;
              case (int) (FIELD_TYPE_SINT32 >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_VARINT);
                  value = decodeZigZag32((int) readVarint());
                  break;
              case (int) (FIELD_TYPE_INT32 >>> FIELD_TYPE_SHIFT):
              case (int) (FIELD_TYPE_UINT32 >>> FIELD_TYPE_SHIFT):
              case (int) (FIELD_TYPE_ENUM >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_VARINT);
                  value = (int) readVarint();
                  break;
              default:
                  throw new IllegalArgumentException(
                          "Requested field id (" + getFieldIdString(fieldId) + ") is not an int"
                                  + dumpDebugData());
          }
          // Successfully read the field
          mState &= ~STATE_STARTED_FIELD_READ;
          return value;
      }
  
      /**
       * Read a single 64bit or varint proto type field as an long.
       *
       * @param fieldId - must match the current field number
       */
      public long readLong(long fieldId) throws IOException {
          assertFreshData();
          assertFieldNumber(fieldId);
          checkPacked(fieldId);
  
          long value;
          switch ((int) ((fieldId & FIELD_TYPE_MASK)
                  >>> FIELD_TYPE_SHIFT)) {
              case (int) (FIELD_TYPE_FIXED64 >>> FIELD_TYPE_SHIFT):
              case (int) (FIELD_TYPE_SFIXED64 >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_FIXED64);
                  value = readFixed64();
                  break;
              case (int) (FIELD_TYPE_SINT64 >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_VARINT);
                  value = decodeZigZag64(readVarint());
                  break;
              case (int) (FIELD_TYPE_INT64 >>> FIELD_TYPE_SHIFT):
              case (int) (FIELD_TYPE_UINT64 >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_VARINT);
                  value = readVarint();
                  break;
              default:
                  throw new IllegalArgumentException(
                          "Requested field id (" + getFieldIdString(fieldId) + ") is not an long"
                                  + dumpDebugData());
          }
          // Successfully read the field
          mState &= ~STATE_STARTED_FIELD_READ;
          return value;
      }
  
      /**
       * Read a single 32bit or varint proto type field as an boolean.
       *
       * @param fieldId - must match the current field number
       */
      public boolean readBoolean(long fieldId) throws IOException {
          assertFreshData();
          assertFieldNumber(fieldId);
          checkPacked(fieldId);
  
          boolean value;
          switch ((int) ((fieldId & FIELD_TYPE_MASK)
                  >>> FIELD_TYPE_SHIFT)) {
              case (int) (FIELD_TYPE_BOOL >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_VARINT);
                  value = readVarint() != 0;
                  break;
              default:
                  throw new IllegalArgumentException(
                          "Requested field id (" + getFieldIdString(fieldId) + ") is not an boolean"
                                  + dumpDebugData());
          }
          // Successfully read the field
          mState &= ~STATE_STARTED_FIELD_READ;
          return value;
      }
  
      /**
       * Read a string field
       *
       * @param fieldId - must match the current field number
       */
      public String readString(long fieldId) throws IOException {
          assertFreshData();
          assertFieldNumber(fieldId);
  
          String value;
          switch ((int) ((fieldId & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) {
              case (int) (FIELD_TYPE_STRING >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_LENGTH_DELIMITED);
                  int len = (int) readVarint();
                  value = readRawString(len);
                  break;
              default:
                  throw new IllegalArgumentException(
                          "Requested field id(" + getFieldIdString(fieldId)
                                  + ") is not an string"
                                  + dumpDebugData());
          }
          // Successfully read the field
          mState &= ~STATE_STARTED_FIELD_READ;
          return value;
      }
  
      /**
       * Read a bytes field
       *
       * @param fieldId - must match the current field number
       */
      public byte[] readBytes(long fieldId) throws IOException {
          assertFreshData();
          assertFieldNumber(fieldId);
  
          byte[] value;
          switch ((int) ((fieldId & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) {
              case (int) (FIELD_TYPE_MESSAGE >>> FIELD_TYPE_SHIFT):
              case (int) (FIELD_TYPE_BYTES >>> FIELD_TYPE_SHIFT):
                  assertWireType(WIRE_TYPE_LENGTH_DELIMITED);
                  int len = (int) readVarint();
                  value = readRawBytes(len);
                  break;
              default:
                  throw new IllegalArgumentException(
                          "Requested field type (" + getFieldIdString(fieldId)
                                  + ") cannot be read as raw bytes"
                                  + dumpDebugData());
          }
          // Successfully read the field
          mState &= ~STATE_STARTED_FIELD_READ;
          return value;
      }
  
      /**
       * Start the read of an embedded Object
       *
       * @param fieldId - must match the current field number
       * @return a token. The token must be handed back when finished reading embedded Object
       */
      public long start(long fieldId) throws IOException {
          assertFreshData();
          assertFieldNumber(fieldId);
          assertWireType(WIRE_TYPE_LENGTH_DELIMITED);
  
          int messageSize = (int) readVarint();
  
          if (mExpectedObjectTokenStack == null) {
              mExpectedObjectTokenStack = new ArrayList<>();
          }
          if (++mDepth == mExpectedObjectTokenStack.size()) {
              // Create a token to keep track of nested Object and extend the object stack
              mExpectedObjectTokenStack.add(makeToken(0,
                      (fieldId & FIELD_COUNT_REPEATED) == FIELD_COUNT_REPEATED, mDepth,
                      (int) fieldId, getOffset() + messageSize));
  
          } else {
              // Create a token to keep track of nested Object
              mExpectedObjectTokenStack.set(mDepth, makeToken(0,
                      (fieldId & FIELD_COUNT_REPEATED) == FIELD_COUNT_REPEATED, mDepth,
                      (int) fieldId, getOffset() + messageSize));
          }
  
          // Sanity check
          if (mDepth > 0
                  && getOffsetFromToken(mExpectedObjectTokenStack.get(mDepth))
                  > getOffsetFromToken(mExpectedObjectTokenStack.get(mDepth - 1))) {
              throw new ProtoParseException("Embedded Object ("
                      + token2String(mExpectedObjectTokenStack.get(mDepth))
                      + ") ends after of parent Objects's ("
                      + token2String(mExpectedObjectTokenStack.get(mDepth - 1))
                      + ") end"
                      + dumpDebugData());
          }
          mState &= ~STATE_STARTED_FIELD_READ;
          return mExpectedObjectTokenStack.get(mDepth);
      }
  
      /**
       * Note the end of a nested object. Must be called to continue streaming the rest of the proto.
       * end can be called mid object parse. The offset will be moved to the next field outside the
       * object.
       *
       * @param token - token
       */
      public void end(long token) {
          // Sanity check to make sure user is keeping track of their embedded messages
          if (mExpectedObjectTokenStack.get(mDepth) != token) {
              throw new ProtoParseException(
                      "end token " + token + " does not match current message token "
                              + mExpectedObjectTokenStack.get(mDepth)
                              + dumpDebugData());
          }
          if (getOffsetFromToken(mExpectedObjectTokenStack.get(mDepth)) > getOffset()) {
              // Did not read all of the message, skip to the end
              incOffset(getOffsetFromToken(mExpectedObjectTokenStack.get(mDepth)) - getOffset());
          }
          mDepth--;
          mState &= ~STATE_STARTED_FIELD_READ;
      }
  
      /**
       * Read the tag at the start of the next field and collect field number and wire type.
       * Will set mFieldNumber to NO_MORE_FIELDS if end of buffer/stream reached.
       */
      private void readTag() throws IOException {
          fillBuffer();
          if (mOffset >= mEnd) {
              // reached end of the stream
              mFieldNumber = NO_MORE_FIELDS;
              return;
          }
          int tag = (int) readVarint();
          mFieldNumber = tag >>> FIELD_ID_SHIFT;
          mWireType = tag & WIRE_TYPE_MASK;
          mState |= STATE_STARTED_FIELD_READ;
      }
  
      /**
       * Decode a 32 bit ZigZag encoded signed int.
       *
       * @param n - int to decode
       * @return the decoded signed int
       */
      public int decodeZigZag32(final int n) {
          return (n >>> 1) ^ -(n & 1);
      }
  
      /**
       * Decode a 64 bit ZigZag encoded signed long.
       *
       * @param n - long to decode
       * @return the decoded signed long
       */
      public long decodeZigZag64(final long n) {
          return (n >>> 1) ^ -(n & 1);
      }
  
      /**
       * Read a varint from the buffer
       *
       * @return the varint as a long
       */
      private long readVarint() throws IOException {
          long value = 0;
          int shift = 0;
          while (true) {
              fillBuffer();
              // Limit how much bookkeeping is done by checking how far away the end of the buffer is
              // and directly accessing buffer up until the end.
              final int fragment = mEnd - mOffset;
              for (int i = 0; i < fragment; i++) {
                  byte b = mBuffer[(mOffset + i)];
                  value |= (b & 0x7FL) << shift;
                  if ((b & 0x80) == 0) {
                      incOffset(i + 1);
                      return value;
                  }
                  shift += 7;
                  if (shift > 63) {
                      throw new ProtoParseException(
                              "Varint is too large at offset 0x"
                                      + Integer.toHexString(getOffset() + i)
                                      + dumpDebugData());
                  }
              }
              // Hit the end of the buffer, do some incrementing and checking, then continue
              incOffset(fragment);
          }
      }
  
      /**
       * Read a fixed 32 bit int from the buffer
       *
       * @return the fixed32 as a int
       */
      private int readFixed32() throws IOException {
          // check for fast path, which is likely with a reasonable buffer size
          if (mOffset + 4 <= mEnd) {
              // don't bother filling buffer since we know the end is plenty far away
              incOffset(4);
              return (mBuffer[mOffset - 4] & 0xFF)
                      | ((mBuffer[mOffset - 3] & 0xFF) << 8)
                      | ((mBuffer[mOffset - 2] & 0xFF) << 16)
                      | ((mBuffer[mOffset - 1] & 0xFF) << 24);
          }
  
          // the Fixed32 crosses the edge of a chunk, read the Fixed32 in multiple fragments.
          // There will be two fragment reads except when the chunk size is 2 or less.
          int value = 0;
          int shift = 0;
          int bytesLeft = 4;
          while (bytesLeft > 0) {
              fillBuffer();
              // Find the number of bytes available until the end of the chunk or Fixed32
              int fragment = (mEnd - mOffset) < bytesLeft ? (mEnd - mOffset) : bytesLeft;
              incOffset(fragment);
              bytesLeft -= fragment;
              while (fragment > 0) {
                  value |= ((mBuffer[mOffset - fragment] & 0xFF) << shift);
                  fragment--;
                  shift += 8;
              }
          }
          return value;
      }
  
      /**
       * Read a fixed 64 bit long from the buffer
       *
       * @return the fixed64 as a long
       */
      private long readFixed64() throws IOException {
          // check for fast path, which is likely with a reasonable buffer size
          if (mOffset + 8 <= mEnd) {
              // don't bother filling buffer since we know the end is plenty far away
              incOffset(8);
              return (mBuffer[mOffset - 8] & 0xFFL)
                      | ((mBuffer[mOffset - 7] & 0xFFL) << 8)
                      | ((mBuffer[mOffset - 6] & 0xFFL) << 16)
                      | ((mBuffer[mOffset - 5] & 0xFFL) << 24)
                      | ((mBuffer[mOffset - 4] & 0xFFL) << 32)
                      | ((mBuffer[mOffset - 3] & 0xFFL) << 40)
                      | ((mBuffer[mOffset - 2] & 0xFFL) << 48)
                      | ((mBuffer[mOffset - 1] & 0xFFL) << 56);
          }
  
          // the Fixed64 crosses the edge of a chunk, read the Fixed64 in multiple fragments.
          // There will be two fragment reads except when the chunk size is 6 or less.
          long value = 0;
          int shift = 0;
          int bytesLeft = 8;
          while (bytesLeft > 0) {
              fillBuffer();
              // Find the number of bytes available until the end of the chunk or Fixed64
              int fragment = (mEnd - mOffset) < bytesLeft ? (mEnd - mOffset) : bytesLeft;
              incOffset(fragment);
              bytesLeft -= fragment;
              while (fragment > 0) {
                  value |= ((mBuffer[(mOffset - fragment)] & 0xFFL) << shift);
                  fragment--;
                  shift += 8;
              }
          }
          return value;
      }
  
      /**
       * Read raw bytes from the buffer
       *
       * @param n - number of bytes to read
       * @return a byte array with raw bytes
       */
      private byte[] readRawBytes(int n) throws IOException {
          byte[] buffer = new byte[n];
          int pos = 0;
          while (mOffset + n - pos > mEnd) {
              int fragment = mEnd - mOffset;
              if (fragment > 0) {
                  System.arraycopy(mBuffer, mOffset, buffer, pos, fragment);
                  incOffset(fragment);
                  pos += fragment;
              }
              fillBuffer();
              if (mOffset >= mEnd) {
                  throw new ProtoParseException(
                          "Unexpectedly reached end of the InputStream at offset 0x"
                                  + Integer.toHexString(mEnd)
                                  + dumpDebugData());
              }
          }
          System.arraycopy(mBuffer, mOffset, buffer, pos, n - pos);
          incOffset(n - pos);
          return buffer;
      }
  
      /**
       * Read raw string from the buffer
       *
       * @param n - number of bytes to read
       * @return a string
       */
      private String readRawString(int n) throws IOException {
          fillBuffer();
          if (mOffset + n <= mEnd) {
              // fast path read. String is well within the current buffer
              String value = new String(mBuffer, mOffset, n, StandardCharsets.UTF_8);
              incOffset(n);
              return value;
          } else if (n <= mBufferSize) {
              // String extends past buffer, but can be encapsulated in a buffer. Copy the first chunk
              // of the string to the start of the buffer and then fill the rest of the buffer from
              // the stream.
              final int stringHead = mEnd - mOffset;
              System.arraycopy(mBuffer, mOffset, mBuffer, 0, stringHead);
              mEnd = stringHead + mStream.read(mBuffer, stringHead, n - stringHead);
  
              mDiscardedBytes += mOffset;
              mOffset = 0;
  
              String value = new String(mBuffer, mOffset, n, StandardCharsets.UTF_8);
              incOffset(n);
              return value;
          }
          // Otherwise, the string is too large to use the buffer. Create the string from a
          // separate byte array.
          return new String(readRawBytes(n), 0, n, StandardCharsets.UTF_8);
      }
  
      /**
       * Fill the buffer with a chunk from the stream if need be.
       * Will skip chunks until mOffset is reached
       */
      private void fillBuffer() throws IOException {
          if (mOffset >= mEnd && mStream != null) {
              mOffset -= mEnd;
              mDiscardedBytes += mEnd;
              if (mOffset >= mBufferSize) {
                  int skipped = (int) mStream.skip((mOffset / mBufferSize) * mBufferSize);
                  mDiscardedBytes += skipped;
                  mOffset -= skipped;
              }
              mEnd = mStream.read(mBuffer);
          }
      }
  
      /**
       * Skips the rest of current field and moves to the start of the next field. This should only be
       * called while state is STATE_STARTED_FIELD_READ
       */
      public void skip() throws IOException {
          if ((mState & STATE_READING_PACKED) == STATE_READING_PACKED) {
              incOffset(mPackedEnd - getOffset());
          } else {
              switch (mWireType) {
                  case WIRE_TYPE_VARINT:
                      byte b;
                      do {
                          fillBuffer();
                          b = mBuffer[mOffset];
                          incOffset(1);
                      } while ((b & 0x80) != 0);
                      break;
                  case WIRE_TYPE_FIXED64:
                      incOffset(8);
                      break;
                  case WIRE_TYPE_LENGTH_DELIMITED:
                      fillBuffer();
                      int length = (int) readVarint();
                      incOffset(length);
                      break;
                  /*
              case WIRE_TYPE_START_GROUP:
                  // Not implemented
                  break;
              case WIRE_TYPE_END_GROUP:
                  // Not implemented
                  break;
                  */
                  case WIRE_TYPE_FIXED32:
                      incOffset(4);
                      break;
                  default:
                      throw new ProtoParseException(
                              "Unexpected wire type: " + mWireType + " at offset 0x"
                                      + Integer.toHexString(mOffset)
                                      + dumpDebugData());
              }
          }
          mState &= ~STATE_STARTED_FIELD_READ;
      }
  
      /**
       * Increment the offset and handle all the relevant bookkeeping
       * Refilling the buffer when its end is reached will be handled elsewhere (ideally just before
       * a read, to avoid unnecessary reads from stream)
       *
       * @param n - number of bytes to increment
       */
      private void incOffset(int n) {
          mOffset += n;
  
          if (mDepth >= 0 && getOffset() > getOffsetFromToken(
                  mExpectedObjectTokenStack.get(mDepth))) {
              throw new ProtoParseException("Unexpectedly reached end of embedded object.  "
                      + token2String(mExpectedObjectTokenStack.get(mDepth))
                      + dumpDebugData());
          }
      }
  
      /**
       * Check the current wire type to determine if current numeric field is packed. If it is packed,
       * set up to deal with the field
       * This should only be called for primitive numeric field types.
       *
       * @param fieldId - used to determine what the packed wire type is.
       */
      private void checkPacked(long fieldId) throws IOException {
          if (mWireType == WIRE_TYPE_LENGTH_DELIMITED) {
              // Primitive Field is length delimited, must be a packed field.
              final int length = (int) readVarint();
              mPackedEnd = getOffset() + length;
              mState |= STATE_READING_PACKED;
  
              // Fake the wire type, based on the field type
              switch ((int) ((fieldId & FIELD_TYPE_MASK)
                      >>> FIELD_TYPE_SHIFT)) {
                  case (int) (FIELD_TYPE_FLOAT >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_FIXED32 >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_SFIXED32 >>> FIELD_TYPE_SHIFT):
                      if (length % 4 != 0) {
                          throw new IllegalArgumentException(
                                  "Requested field id (" + getFieldIdString(fieldId)
                                          + ") packed length " + length
                                          + " is not aligned for fixed32"
                                          + dumpDebugData());
                      }
                      mWireType = WIRE_TYPE_FIXED32;
                      break;
                  case (int) (FIELD_TYPE_DOUBLE >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_FIXED64 >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_SFIXED64 >>> FIELD_TYPE_SHIFT):
                      if (length % 8 != 0) {
                          throw new IllegalArgumentException(
                                  "Requested field id (" + getFieldIdString(fieldId)
                                          + ") packed length " + length
                                          + " is not aligned for fixed64"
                                          + dumpDebugData());
                      }
                      mWireType = WIRE_TYPE_FIXED64;
                      break;
                  case (int) (FIELD_TYPE_SINT32 >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_INT32 >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_UINT32 >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_SINT64 >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_INT64 >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_UINT64 >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_ENUM >>> FIELD_TYPE_SHIFT):
                  case (int) (FIELD_TYPE_BOOL >>> FIELD_TYPE_SHIFT):
                      mWireType = WIRE_TYPE_VARINT;
                      break;
                  default:
                      throw new IllegalArgumentException(
                              "Requested field id (" + getFieldIdString(fieldId)
                                      + ") is not a packable field"
                                      + dumpDebugData());
              }
          }
      }
  
  
      /**
       * Check a field id constant against current field number
       *
       * @param fieldId - throws if fieldId does not match mFieldNumber
       */
      private void assertFieldNumber(long fieldId) {
          if ((int) fieldId != mFieldNumber) {
              throw new IllegalArgumentException("Requested field id (" + getFieldIdString(fieldId)
                      + ") does not match current field number (0x" + Integer.toHexString(
                      mFieldNumber)
                      + ") at offset 0x" + Integer.toHexString(getOffset())
                      + dumpDebugData());
          }
      }
  
  
      /**
       * Check a wire type against current wire type.
       *
       * @param wireType - throws if wireType does not match mWireType.
       */
      private void assertWireType(int wireType) {
          if (wireType != mWireType) {
              throw new WireTypeMismatchException(
                      "Current wire type " + getWireTypeString(mWireType)
                              + " does not match expected wire type " + getWireTypeString(wireType)
                              + " at offset 0x" + Integer.toHexString(getOffset())
                              + dumpDebugData());
          }
      }
  
      /**
       * Check if there is data ready to be read.
       */
      private void assertFreshData() {
          if ((mState & STATE_STARTED_FIELD_READ) != STATE_STARTED_FIELD_READ) {
              throw new ProtoParseException(
                      "Attempting to read already read field at offset 0x" + Integer.toHexString(
                              getOffset()) + dumpDebugData());
          }
      }
  
      /**
       * Dump debugging data about the buffer.
       */
      public String dumpDebugData() {
          StringBuilder sb = new StringBuilder();
  
          sb.append("\nmFieldNumber : 0x" + Integer.toHexString(mFieldNumber));
          sb.append("\nmWireType : 0x" + Integer.toHexString(mWireType));
          sb.append("\nmState : 0x" + Integer.toHexString(mState));
          sb.append("\nmDiscardedBytes : 0x" + Integer.toHexString(mDiscardedBytes));
          sb.append("\nmOffset : 0x" + Integer.toHexString(mOffset));
          sb.append("\nmExpectedObjectTokenStack : ");
          if (mExpectedObjectTokenStack == null) {
              sb.append("null");
          } else {
              sb.append(mExpectedObjectTokenStack);
          }
          sb.append("\nmDepth : 0x" + Integer.toHexString(mDepth));
          sb.append("\nmBuffer : ");
          if (mBuffer == null) {
              sb.append("null");
          } else {
              sb.append(mBuffer);
          }
          sb.append("\nmBufferSize : 0x" + Integer.toHexString(mBufferSize));
          sb.append("\nmEnd : 0x" + Integer.toHexString(mEnd));
  
          return sb.toString();
      }
  }
  