summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorMichael Wachenschwanz <mwachens@google.com>2018-08-04 00:25:43 -0700
committerMichael Wachenschwanz <mwachens@google.com>2018-09-12 18:54:19 -0700
commit8d38b29ba7b39b4380da569c9d6116fc1d162c97 (patch)
treee3bfedd3ab7ad011d4a5ccb7f0a94cad048a8388 /core/java/android
parentdb3e20f17fe6dc38a56a56fa5d8d16850d2822ba (diff)
Intial ProtoInputStream checkin
Enable proto reading on the Android Framework with a memory efficient pull parser. Fixes: 112269636 Test: atest CtsProtoTestCases Change-Id: If8331edb1ec393acd724ffb5d27d6efad1a42a80
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/util/proto/ProtoInputStream.java978
-rw-r--r--core/java/android/util/proto/ProtoOutputStream.java224
-rw-r--r--core/java/android/util/proto/ProtoStream.java280
-rw-r--r--core/java/android/util/proto/ProtoUtils.java52
-rw-r--r--core/java/android/util/proto/WireTypeMismatchException.java38
5 files changed, 1353 insertions, 219 deletions
diff --git a/core/java/android/util/proto/ProtoInputStream.java b/core/java/android/util/proto/ProtoInputStream.java
new file mode 100644
index 000000000000..209451bcfe6a
--- /dev/null
+++ b/core/java/android/util/proto/ProtoInputStream.java
@@ -0,0 +1,978 @@
+/*
+ * 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.annotation.TestApi;
+
+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
+ */
+@TestApi
+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 = StringFactory.newStringFromBytes(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 = StringFactory.newStringFromBytes(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 StringFactory.newStringFromBytes(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();
+ }
+}
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index a94806a02fbb..a1ee61c15f24 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -100,88 +100,12 @@ import java.io.UnsupportedEncodingException;
* errors if they are not matched.
*/
@TestApi
-public final class ProtoOutputStream {
+public final class ProtoOutputStream extends ProtoStream {
+ /**
+ * @hide
+ */
public static final String TAG = "ProtoOutputStream";
- public static final int FIELD_ID_SHIFT = 3;
- public static final int WIRE_TYPE_MASK = (1<<FIELD_ID_SHIFT)-1;
- public static final int FIELD_ID_MASK = ~WIRE_TYPE_MASK;
-
- public static final int WIRE_TYPE_VARINT = 0;
- public static final int WIRE_TYPE_FIXED64 = 1;
- public static final int WIRE_TYPE_LENGTH_DELIMITED = 2;
- public static final int WIRE_TYPE_START_GROUP = 3;
- public static final int WIRE_TYPE_END_GROUP = 4;
- public static final int WIRE_TYPE_FIXED32 = 5;
-
- /**
- * Position of the field type in a (long) fieldId.
- */
- public static final int FIELD_TYPE_SHIFT = 32;
-
- /**
- * Mask for the field types stored in a fieldId. Leaves a whole
- * byte for future expansion, even though there are currently only 17 types.
- */
- public static final long FIELD_TYPE_MASK = 0x0ffL << FIELD_TYPE_SHIFT;
-
- public static final long FIELD_TYPE_UNKNOWN = 0;
-
- /**
- * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
- * so no extra mapping needs to be maintained in this case.
- */
- public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT;
-// public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated.
- public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT;
-
- private static final String[] FIELD_TYPE_NAMES = new String[] {
- "Double",
- "Float",
- "Int64",
- "UInt64",
- "Int32",
- "Fixed64",
- "Fixed32",
- "Bool",
- "String",
- "Group", // This field is deprecated but reserved here for indexing.
- "Message",
- "Bytes",
- "UInt32",
- "Enum",
- "SFixed32",
- "SFixed64",
- "SInt32",
- "SInt64",
- };
-
- //
- // FieldId flags for whether the field is single, repeated or packed.
- //
- public static final int FIELD_COUNT_SHIFT = 40;
- public static final long FIELD_COUNT_MASK = 0x0fL << FIELD_COUNT_SHIFT;
-
- public static final long FIELD_COUNT_UNKNOWN = 0;
- public static final long FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
- public static final long FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
- public static final long FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
-
/**
* Our buffer.
*/
@@ -1997,94 +1921,6 @@ public final class ProtoOutputStream {
}
}
- //
- // Child objects
- //
-
- /**
- * Make a token.
- * Bits 61-63 - tag size (So we can go backwards later if the object had not data)
- * - 3 bits, max value 7, max value needed 5
- * Bit 60 - true if the object is repeated (lets us require endObject or endRepeatedObject)
- * Bits 59-51 - depth (For error checking)
- * - 9 bits, max value 512, when checking, value is masked (if we really
- * are more than 512 levels deep)
- * Bits 32-50 - objectId (For error checking)
- * - 19 bits, max value 524,288. that's a lot of objects. IDs will wrap
- * because of the overflow, and only the tokens are compared.
- * Bits 0-31 - offset of the first size field in the buffer.
- */
- // VisibleForTesting
- public static long makeToken(int tagSize, boolean repeated, int depth, int objectId,
- int sizePos) {
- return ((0x07L & (long)tagSize) << 61)
- | (repeated ? (1L << 60) : 0)
- | (0x01ffL & (long)depth) << 51
- | (0x07ffffL & (long)objectId) << 32
- | (0x0ffffffffL & (long)sizePos);
- }
-
- /**
- * Get the encoded tag size from the token.
- */
- public static int getTagSizeFromToken(long token) {
- return (int)(0x7 & (token >> 61));
- }
-
- /**
- * Get whether this is a call to startObject (false) or startRepeatedObject (true).
- */
- public static boolean getRepeatedFromToken(long token) {
- return (0x1 & (token >> 60)) != 0;
- }
-
- /**
- * Get the nesting depth of startObject calls from the token.
- */
- public static int getDepthFromToken(long token) {
- return (int)(0x01ff & (token >> 51));
- }
-
- /**
- * Get the object ID from the token. The object ID is a serial number for the
- * startObject calls that have happened on this object. The values are truncated
- * to 9 bits, but that is sufficient for error checking.
- */
- public static int getObjectIdFromToken(long token) {
- return (int)(0x07ffff & (token >> 32));
- }
-
- /**
- * Get the location of the childRawSize (the first 32 bit size field) in this object.
- */
- public static int getSizePosFromToken(long token) {
- return (int)token;
- }
-
- /**
- * Convert the object ID to the ordinal value -- the n-th call to startObject.
- * The object IDs start at -1 and count backwards, so that the value is unlikely
- * to alias with an actual size field that had been written.
- */
- public static int convertObjectIdToOrdinal(int objectId) {
- return (-1 & 0x07ffff) - objectId;
- }
-
- /**
- * Return a debugging string of a token.
- */
- public static String token2String(long token) {
- if (token == 0L) {
- return "Token(0)";
- } else {
- return "Token(val=0x" + Long.toHexString(token)
- + " depth=" + getDepthFromToken(token)
- + " object=" + convertObjectIdToOrdinal(getObjectIdFromToken(token))
- + " tagSize=" + getTagSizeFromToken(token)
- + " sizePos=" + getSizePosFromToken(token)
- + ')';
- }
- }
/**
* Start a child object.
@@ -2175,7 +2011,7 @@ public final class ProtoOutputStream {
// at which to write the size.
final int depth = getDepthFromToken(token);
final boolean expectedRepeated = getRepeatedFromToken(token);
- final int sizePos = getSizePosFromToken(token);
+ final int sizePos = getOffsetFromToken(token);
final int childRawSize = mBuffer.getWritePos() - sizePos - 8;
if (repeated != expectedRepeated) {
@@ -2346,56 +2182,6 @@ public final class ProtoOutputStream {
}
/**
- * Get the developer-usable name of a field type.
- */
- private static String getFieldTypeString(long fieldType) {
- int index = ((int)((fieldType & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) - 1;
- if (index >= 0 && index < FIELD_TYPE_NAMES.length) {
- return FIELD_TYPE_NAMES[index];
- } else {
- return null;
- }
- }
-
- /**
- * Get the developer-usable name of a field count.
- */
- private static String getFieldCountString(long fieldCount) {
- if (fieldCount == FIELD_COUNT_SINGLE) {
- return "";
- } else if (fieldCount == FIELD_COUNT_REPEATED) {
- return "Repeated";
- } else if (fieldCount == FIELD_COUNT_PACKED) {
- return "Packed";
- } else {
- return null;
- }
- }
-
- /**
- * Get a debug string for a fieldId.
- */
- private String getFieldIdString(long fieldId) {
- final long fieldCount = fieldId & FIELD_COUNT_MASK;
- String countString = getFieldCountString(fieldCount);
- if (countString == null) {
- countString = "fieldCount=" + fieldCount;
- }
- if (countString.length() > 0) {
- countString += " ";
- }
-
- final long fieldType = fieldId & FIELD_TYPE_MASK;
- String typeString = getFieldTypeString(fieldType);
- if (typeString == null) {
- typeString = "fieldType=" + fieldType;
- }
-
- return countString + typeString + " tag=" + ((int) fieldId)
- + " fieldId=0x" + Long.toHexString(fieldId);
- }
-
- /**
* Return how many bytes an encoded field tag will require.
*/
private static int getTagSize(int id) {
diff --git a/core/java/android/util/proto/ProtoStream.java b/core/java/android/util/proto/ProtoStream.java
new file mode 100644
index 000000000000..9e2e95a5923d
--- /dev/null
+++ b/core/java/android/util/proto/ProtoStream.java
@@ -0,0 +1,280 @@
+/*
+ * 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.annotation.TestApi;
+
+/**
+ * Abstract base class for both protobuf streams.
+ *
+ * Contains a set of useful constants and methods used by both
+ * ProtoOutputStream and ProtoInputStream
+ *
+ * @hide
+ */
+@TestApi
+public abstract class ProtoStream {
+
+ public static final int FIELD_ID_SHIFT = 3;
+ public static final int WIRE_TYPE_MASK = (1 << FIELD_ID_SHIFT) - 1;
+ public static final int FIELD_ID_MASK = ~WIRE_TYPE_MASK;
+
+ public static final int WIRE_TYPE_VARINT = 0;
+ public static final int WIRE_TYPE_FIXED64 = 1;
+ public static final int WIRE_TYPE_LENGTH_DELIMITED = 2;
+ public static final int WIRE_TYPE_START_GROUP = 3;
+ public static final int WIRE_TYPE_END_GROUP = 4;
+ public static final int WIRE_TYPE_FIXED32 = 5;
+
+ /**
+ * Position of the field type in a (long) fieldId.
+ */
+ public static final int FIELD_TYPE_SHIFT = 32;
+
+ /**
+ * Mask for the field types stored in a fieldId. Leaves a whole
+ * byte for future expansion, even though there are currently only 17 types.
+ */
+ public static final long FIELD_TYPE_MASK = 0x0ffL << FIELD_TYPE_SHIFT;
+
+ public static final long FIELD_TYPE_UNKNOWN = 0;
+
+ /**
+ * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
+ * so no extra mapping needs to be maintained in this case.
+ */
+ public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT;
+ // public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated.
+ public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT;
+
+ protected static final String[] FIELD_TYPE_NAMES = new String[]{
+ "Double",
+ "Float",
+ "Int64",
+ "UInt64",
+ "Int32",
+ "Fixed64",
+ "Fixed32",
+ "Bool",
+ "String",
+ "Group", // This field is deprecated but reserved here for indexing.
+ "Message",
+ "Bytes",
+ "UInt32",
+ "Enum",
+ "SFixed32",
+ "SFixed64",
+ "SInt32",
+ "SInt64",
+ };
+
+ //
+ // FieldId flags for whether the field is single, repeated or packed.
+ //
+ public static final int FIELD_COUNT_SHIFT = 40;
+ public static final long FIELD_COUNT_MASK = 0x0fL << FIELD_COUNT_SHIFT;
+
+ public static final long FIELD_COUNT_UNKNOWN = 0;
+ public static final long FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
+ public static final long FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
+ public static final long FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
+
+
+ /**
+ * Get the developer-usable name of a field type.
+ */
+ public static String getFieldTypeString(long fieldType) {
+ int index = ((int) ((fieldType & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) - 1;
+ if (index >= 0 && index < FIELD_TYPE_NAMES.length) {
+ return FIELD_TYPE_NAMES[index];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the developer-usable name of a field count.
+ */
+ public static String getFieldCountString(long fieldCount) {
+ if (fieldCount == FIELD_COUNT_SINGLE) {
+ return "";
+ } else if (fieldCount == FIELD_COUNT_REPEATED) {
+ return "Repeated";
+ } else if (fieldCount == FIELD_COUNT_PACKED) {
+ return "Packed";
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the developer-usable name of a wire type.
+ */
+ public static String getWireTypeString(int wireType) {
+ switch (wireType) {
+ case WIRE_TYPE_VARINT:
+ return "Varint";
+ case WIRE_TYPE_FIXED64:
+ return "Fixed64";
+ case WIRE_TYPE_LENGTH_DELIMITED:
+ return "Length Delimited";
+ case WIRE_TYPE_START_GROUP:
+ return "Start Group";
+ case WIRE_TYPE_END_GROUP:
+ return "End Group";
+ case WIRE_TYPE_FIXED32:
+ return "Fixed32";
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Get a debug string for a fieldId.
+ */
+ public static String getFieldIdString(long fieldId) {
+ final long fieldCount = fieldId & FIELD_COUNT_MASK;
+ String countString = getFieldCountString(fieldCount);
+ if (countString == null) {
+ countString = "fieldCount=" + fieldCount;
+ }
+ if (countString.length() > 0) {
+ countString += " ";
+ }
+
+ final long fieldType = fieldId & FIELD_TYPE_MASK;
+ String typeString = getFieldTypeString(fieldType);
+ if (typeString == null) {
+ typeString = "fieldType=" + fieldType;
+ }
+
+ return countString + typeString + " tag=" + ((int) fieldId)
+ + " fieldId=0x" + Long.toHexString(fieldId);
+ }
+
+ /**
+ * Combine a fieldId (the field keys in the proto file) and the field flags.
+ * Mostly useful for testing because the generated code contains the fieldId
+ * constants.
+ */
+ public static long makeFieldId(int id, long fieldFlags) {
+ return fieldFlags | (((long) id) & 0x0ffffffffL);
+ }
+
+ //
+ // Child objects
+ //
+
+ /**
+ * Make a token.
+ * Bits 61-63 - tag size (So we can go backwards later if the object had not data)
+ * - 3 bits, max value 7, max value needed 5
+ * Bit 60 - true if the object is repeated (lets us require endObject or endRepeatedObject)
+ * Bits 59-51 - depth (For error checking)
+ * - 9 bits, max value 512, when checking, value is masked (if we really
+ * are more than 512 levels deep)
+ * Bits 32-50 - objectId (For error checking)
+ * - 19 bits, max value 524,288. that's a lot of objects. IDs will wrap
+ * because of the overflow, and only the tokens are compared.
+ * Bits 0-31 - offset of interest for the object.
+ */
+ public static long makeToken(int tagSize, boolean repeated, int depth, int objectId,
+ int offset) {
+ return ((0x07L & (long) tagSize) << 61)
+ | (repeated ? (1L << 60) : 0)
+ | (0x01ffL & (long) depth) << 51
+ | (0x07ffffL & (long) objectId) << 32
+ | (0x0ffffffffL & (long) offset);
+ }
+
+ /**
+ * Get the encoded tag size from the token.
+ */
+ public static int getTagSizeFromToken(long token) {
+ return (int) (0x7 & (token >> 61));
+ }
+
+ /**
+ * Get whether this is a call to startObject (false) or startRepeatedObject (true).
+ */
+ public static boolean getRepeatedFromToken(long token) {
+ return (0x1 & (token >> 60)) != 0;
+ }
+
+ /**
+ * Get the nesting depth of startObject calls from the token.
+ */
+ public static int getDepthFromToken(long token) {
+ return (int) (0x01ff & (token >> 51));
+ }
+
+ /**
+ * Get the object ID from the token. The object ID is a serial number for the
+ * startObject calls that have happened on this object. The values are truncated
+ * to 9 bits, but that is sufficient for error checking.
+ */
+ public static int getObjectIdFromToken(long token) {
+ return (int) (0x07ffff & (token >> 32));
+ }
+
+ /**
+ * Get the location of the offset recorded in the token.
+ */
+ public static int getOffsetFromToken(long token) {
+ return (int) token;
+ }
+
+ /**
+ * Convert the object ID to the ordinal value -- the n-th call to startObject.
+ * The object IDs start at -1 and count backwards, so that the value is unlikely
+ * to alias with an actual size field that had been written.
+ */
+ public static int convertObjectIdToOrdinal(int objectId) {
+ return (-1 & 0x07ffff) - objectId;
+ }
+
+ /**
+ * Return a debugging string of a token.
+ */
+ public static String token2String(long token) {
+ if (token == 0L) {
+ return "Token(0)";
+ } else {
+ return "Token(val=0x" + Long.toHexString(token)
+ + " depth=" + getDepthFromToken(token)
+ + " object=" + convertObjectIdToOrdinal(getObjectIdFromToken(token))
+ + " tagSize=" + getTagSizeFromToken(token)
+ + " offset=" + getOffsetFromToken(token)
+ + ')';
+ }
+ }
+}
diff --git a/core/java/android/util/proto/ProtoUtils.java b/core/java/android/util/proto/ProtoUtils.java
index c7bbb9f2a044..03bf590902c0 100644
--- a/core/java/android/util/proto/ProtoUtils.java
+++ b/core/java/android/util/proto/ProtoUtils.java
@@ -19,6 +19,8 @@ package android.util.proto;
import android.util.AggStats;
import android.util.Duration;
+import java.io.IOException;
+
/**
* This class contains a list of helper functions to write common proto in
* //frameworks/base/core/proto/android/base directory
@@ -70,4 +72,54 @@ public class ProtoUtils {
}
}
}
+
+ /**
+ * Provide debug data about the current field as a string
+ */
+ public static String currentFieldToString(ProtoInputStream proto) throws IOException {
+ StringBuilder sb = new StringBuilder();
+
+ final int fieldNumber = proto.getFieldNumber();
+ final int wireType = proto.getWireType();
+ long fieldConstant;
+
+ sb.append("Offset : 0x" + Integer.toHexString(proto.getOffset()));
+ sb.append("\nField Number : 0x" + Integer.toHexString(proto.getFieldNumber()));
+ sb.append("\nWire Type : ");
+ switch (wireType) {
+ case ProtoStream.WIRE_TYPE_VARINT:
+ sb.append("varint");
+ fieldConstant = ProtoStream.makeFieldId(fieldNumber,
+ ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64);
+ sb.append("\nField Value : 0x" + Long.toHexString(proto.readLong(fieldConstant)));
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED64:
+ sb.append("fixed64");
+ fieldConstant = ProtoStream.makeFieldId(fieldNumber,
+ ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64);
+ sb.append("\nField Value : 0x" + Long.toHexString(proto.readLong(fieldConstant)));
+ break;
+ case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+ sb.append("length delimited");
+ fieldConstant = ProtoStream.makeFieldId(fieldNumber,
+ ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES);
+ sb.append("\nField Bytes : " + proto.readBytes(fieldConstant));
+ break;
+ case ProtoStream.WIRE_TYPE_START_GROUP:
+ sb.append("start group");
+ break;
+ case ProtoStream.WIRE_TYPE_END_GROUP:
+ sb.append("end group");
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED32:
+ sb.append("fixed32");
+ fieldConstant = ProtoStream.makeFieldId(fieldNumber,
+ ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32);
+ sb.append("\nField Value : 0x" + Integer.toHexString(proto.readInt(fieldConstant)));
+ break;
+ default:
+ sb.append("unknown(" + proto.getWireType() + ")");
+ }
+ return sb.toString();
+ }
}
diff --git a/core/java/android/util/proto/WireTypeMismatchException.java b/core/java/android/util/proto/WireTypeMismatchException.java
new file mode 100644
index 000000000000..d90b4f8df8ae
--- /dev/null
+++ b/core/java/android/util/proto/WireTypeMismatchException.java
@@ -0,0 +1,38 @@
+/*
+ * 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.annotation.TestApi;
+
+/**
+ * Thrown when there is an error parsing protobuf data.
+ *
+ * @hide
+ */
+@TestApi
+public class WireTypeMismatchException extends ProtoParseException {
+
+ /**
+ * Construct a WireTypeMismatchException.
+ *
+ * @param msg The message.
+ */
+ public WireTypeMismatchException(String msg) {
+ super(msg);
+ }
+}
+