blob: c290dffc42c9c0e35cb77385470aefb36def91b5 [file] [log] [blame]
/*
* 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();
}
}