/* * 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 android.util.LongArray; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Objects; /** * 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 */ @android.ravenwood.annotation.RavenwoodKeepWholeClass 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 checking and debug */ private LongArray 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; } /** * Reads the tag of the next field from the stream. If previous field value was not read, its * data will be skipped over. If {@code fieldId} matches the next field ID, the field data will * be ready to read. If it does not match, {@link #nextField()} or {@link #nextField(long)} will * need to be called again before the field data can be read. * * @return true if fieldId matches the next field, false if not */ public boolean nextField(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 LongArray(); } 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)); } // Validation 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) { // 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; if (fragment < 0) { throw new ProtoParseException( "Incomplete varint at offset 0x" + Integer.toHexString(getOffset()) + dumpDebugData()); } 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; if (fragment < 0) { throw new ProtoParseException( "Incomplete fixed32 at offset 0x" + Integer.toHexString(getOffset()) + dumpDebugData()); } 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; if (fragment < 0) { throw new ProtoParseException( "Incomplete fixed64 at offset 0x" + Integer.toHexString(getOffset()) + dumpDebugData()); } 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").append(Integer.toHexString(mFieldNumber)); sb.append("\nmWireType : 0x").append(Integer.toHexString(mWireType)); sb.append("\nmState : 0x").append(Integer.toHexString(mState)); sb.append("\nmDiscardedBytes : 0x").append(Integer.toHexString(mDiscardedBytes)); sb.append("\nmOffset : 0x").append(Integer.toHexString(mOffset)); sb.append("\nmExpectedObjectTokenStack : ") .append(Objects.toString(mExpectedObjectTokenStack)); sb.append("\nmDepth : 0x").append(Integer.toHexString(mDepth)); sb.append("\nmBuffer : ").append(Arrays.toString(mBuffer)); sb.append("\nmBufferSize : 0x").append(Integer.toHexString(mBufferSize)); sb.append("\nmEnd : 0x").append(Integer.toHexString(mEnd)); return sb.toString(); } }