diff options
| author | Michael Wachenschwanz <mwachens@google.com> | 2018-08-04 00:25:43 -0700 |
|---|---|---|
| committer | Michael Wachenschwanz <mwachens@google.com> | 2018-09-12 18:54:19 -0700 |
| commit | 8d38b29ba7b39b4380da569c9d6116fc1d162c97 (patch) | |
| tree | e3bfedd3ab7ad011d4a5ccb7f0a94cad048a8388 /core/java/android | |
| parent | db3e20f17fe6dc38a56a56fa5d8d16850d2822ba (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.java | 978 | ||||
| -rw-r--r-- | core/java/android/util/proto/ProtoOutputStream.java | 224 | ||||
| -rw-r--r-- | core/java/android/util/proto/ProtoStream.java | 280 | ||||
| -rw-r--r-- | core/java/android/util/proto/ProtoUtils.java | 52 | ||||
| -rw-r--r-- | core/java/android/util/proto/WireTypeMismatchException.java | 38 |
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); + } +} + |
