/* * Copyright (C) 2012 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 android.util.Log; import java.util.ArrayList; /** * A stream of bytes containing a read pointer and a write pointer, * backed by a set of fixed-size buffers. There are write functions for the * primitive types stored by protocol buffers, but none of the logic * for tags, inner objects, or any of that. * * Terminology: * *Pos: Position in the whole data set (as if it were a single buffer). * *Index: Position within a buffer. * *BufIndex: Index of a buffer within the mBuffers list * @hide */ @TestApi public final class EncodedBuffer { private static final String TAG = "EncodedBuffer"; private final ArrayList mBuffers = new ArrayList(); private final int mChunkSize; /** * The number of buffers in mBuffers. Stored separately to avoid the extra * function call to size() everywhere for bounds checking. */ private int mBufferCount; /** * The buffer we are currently writing to. */ private byte[] mWriteBuffer; /** * The index into mWriteBuffer that we will write to next. * It may point to the end of the buffer, in which case, * the NEXT write will allocate a new buffer. */ private int mWriteIndex; /** * The index of mWriteBuffer in mBuffers. */ private int mWriteBufIndex; /** * The buffer we are currently reading from. */ private byte[] mReadBuffer; /** * The index of mReadBuffer in mBuffers. */ private int mReadBufIndex; /** * The index into mReadBuffer that we will read from next. * It may point to the end of the buffer, in which case, * the NEXT read will advance to the next buffer. */ private int mReadIndex; /** * The amount of data in the last buffer. */ private int mReadLimit = -1; /** * How much data there is total. */ private int mReadableSize = -1; public EncodedBuffer() { this(0); } /** * Construct an EncodedBuffer object. * * @param chunkSize The size of the buffers to use. If chunkSize <= 0, a default * size will be used instead. */ public EncodedBuffer(int chunkSize) { if (chunkSize <= 0) { chunkSize = 8 * 1024; } mChunkSize = chunkSize; mWriteBuffer = new byte[mChunkSize]; mBuffers.add(mWriteBuffer); mBufferCount = 1; } // // Buffer management. // /** * Rewind the read and write pointers, and record how much data was last written. */ public void startEditing() { mReadableSize = ((mWriteBufIndex) * mChunkSize) + mWriteIndex; mReadLimit = mWriteIndex; mWriteBuffer = mBuffers.get(0); mWriteIndex = 0; mWriteBufIndex = 0; mReadBuffer = mWriteBuffer; mReadBufIndex = 0; mReadIndex = 0; } /** * Rewind the read pointer. Don't touch the write pointer. */ public void rewindRead() { mReadBuffer = mBuffers.get(0); mReadBufIndex = 0; mReadIndex = 0; } /** * Only valid after startEditing. Returns -1 before that. */ public int getReadableSize() { return mReadableSize; } // // Reading from the read position. // /** * Only valid after startEditing. */ public int getReadPos() { return ((mReadBufIndex) * mChunkSize) + mReadIndex; } /** * Skip over _amount_ bytes. */ public void skipRead(int amount) { if (amount < 0) { throw new RuntimeException("skipRead with negative amount=" + amount); } if (amount == 0) { return; } if (amount <= mChunkSize - mReadIndex) { mReadIndex += amount; } else { amount -= mChunkSize - mReadIndex; mReadIndex = amount % mChunkSize; if (mReadIndex == 0) { mReadIndex = mChunkSize; mReadBufIndex += (amount / mChunkSize); } else { mReadBufIndex += 1 + (amount / mChunkSize); } mReadBuffer = mBuffers.get(mReadBufIndex); } } /** * Read one byte from the stream and advance the read pointer. * * @throws IndexOutOfBoundsException if the read point is past the end of * the buffer or past the read limit previously set by startEditing(). */ public byte readRawByte() { if (mReadBufIndex > mBufferCount || (mReadBufIndex == mBufferCount - 1 && mReadIndex >= mReadLimit)) { throw new IndexOutOfBoundsException("Trying to read too much data" + " mReadBufIndex=" + mReadBufIndex + " mBufferCount=" + mBufferCount + " mReadIndex=" + mReadIndex + " mReadLimit=" + mReadLimit); } if (mReadIndex >= mChunkSize) { mReadBufIndex++; mReadBuffer = mBuffers.get(mReadBufIndex); mReadIndex = 0; } return mReadBuffer[mReadIndex++]; } /** * Read an unsigned varint. The value will be returend in a java signed long. */ public long readRawUnsigned() { int bits = 0; long result = 0; while (true) { final byte b = readRawByte(); result |= ((long)(b & 0x7F)) << bits; if ((b & 0x80) == 0) { return result; } bits += 7; if (bits > 64) { throw new ProtoParseException("Varint too long -- " + getDebugString()); } } } /** * Read 32 little endian bits from the stream. */ public int readRawFixed32() { return (readRawByte() & 0x0ff) | ((readRawByte() & 0x0ff) << 8) | ((readRawByte() & 0x0ff) << 16) | ((readRawByte() & 0x0ff) << 24); } // // Writing at a the end of the stream. // /** * Advance to the next write buffer, allocating it if necessary. * * Must be called immediately before the next write, not after a write, * so that a dangling empty buffer is not created. Doing so will interfere * with the expectation that mWriteIndex will point past the end of the buffer * until the next read happens. */ private void nextWriteBuffer() { mWriteBufIndex++; if (mWriteBufIndex >= mBufferCount) { mWriteBuffer = new byte[mChunkSize]; mBuffers.add(mWriteBuffer); mBufferCount++; } else { mWriteBuffer = mBuffers.get(mWriteBufIndex); } mWriteIndex = 0; } /** * Write a single byte to the stream. */ public void writeRawByte(byte val) { if (mWriteIndex >= mChunkSize) { nextWriteBuffer(); } mWriteBuffer[mWriteIndex++] = val; } /** * Return how many bytes a 32 bit unsigned varint will take when written to the stream. */ public static int getRawVarint32Size(int val) { if ((val & (0xffffffff << 7)) == 0) return 1; if ((val & (0xffffffff << 14)) == 0) return 2; if ((val & (0xffffffff << 21)) == 0) return 3; if ((val & (0xffffffff << 28)) == 0) return 4; return 5; } /** * Write an unsigned varint to the stream. A signed value would need to take 10 bytes. * * @param val treated as unsigned. */ public void writeRawVarint32(int val) { while (true) { if ((val & ~0x7F) == 0) { writeRawByte((byte)val); return; } else { writeRawByte((byte)((val & 0x7F) | 0x80)); val >>>= 7; } } } /** * Return how many bytes a 32 bit signed zig zag value will take when written to the stream. */ public static int getRawZigZag32Size(int val) { return getRawVarint32Size(zigZag32(val)); } /** * Write a zig-zag encoded value. * * @param val treated as signed */ public void writeRawZigZag32(int val) { writeRawVarint32(zigZag32(val)); } /** * Return how many bytes a 64 bit varint will take when written to the stream. */ public static int getRawVarint64Size(long val) { if ((val & (0xffffffffffffffffL << 7)) == 0) return 1; if ((val & (0xffffffffffffffffL << 14)) == 0) return 2; if ((val & (0xffffffffffffffffL << 21)) == 0) return 3; if ((val & (0xffffffffffffffffL << 28)) == 0) return 4; if ((val & (0xffffffffffffffffL << 35)) == 0) return 5; if ((val & (0xffffffffffffffffL << 42)) == 0) return 6; if ((val & (0xffffffffffffffffL << 49)) == 0) return 7; if ((val & (0xffffffffffffffffL << 56)) == 0) return 8; if ((val & (0xffffffffffffffffL << 63)) == 0) return 9; return 10; } /** * Write a 64 bit varint to the stream. */ public void writeRawVarint64(long val) { while (true) { if ((val & ~0x7FL) == 0) { writeRawByte((byte)val); return; } else { writeRawByte((byte)((val & 0x7F) | 0x80)); val >>>= 7; } } } /** * Return how many bytes a signed 64 bit zig zag value will take when written to the stream. */ public static int getRawZigZag64Size(long val) { return getRawVarint64Size(zigZag64(val)); } /** * Write a 64 bit signed zig zag value to the stream. */ public void writeRawZigZag64(long val) { writeRawVarint64(zigZag64(val)); } /** * Write 4 little endian bytes to the stream. */ public void writeRawFixed32(int val) { writeRawByte((byte)(val)); writeRawByte((byte)(val >> 8)); writeRawByte((byte)(val >> 16)); writeRawByte((byte)(val >> 24)); } /** * Write 8 little endian bytes to the stream. */ public void writeRawFixed64(long val) { writeRawByte((byte)(val)); writeRawByte((byte)(val >> 8)); writeRawByte((byte)(val >> 16)); writeRawByte((byte)(val >> 24)); writeRawByte((byte)(val >> 32)); writeRawByte((byte)(val >> 40)); writeRawByte((byte)(val >> 48)); writeRawByte((byte)(val >> 56)); } /** * Write a buffer to the stream. Writes nothing if val is null or zero-length. */ public void writeRawBuffer(byte[] val) { if (val != null && val.length > 0) { writeRawBuffer(val, 0, val.length); } } /** * Write part of an array of bytes. */ public void writeRawBuffer(byte[] val, int offset, int length) { if (val == null) { return; } // Write up to the amount left in the first chunk to write. int amt = length < (mChunkSize - mWriteIndex) ? length : (mChunkSize - mWriteIndex); if (amt > 0) { System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt); mWriteIndex += amt; length -= amt; offset += amt; } while (length > 0) { // We know we're now at the beginning of a chunk nextWriteBuffer(); amt = length < mChunkSize ? length : mChunkSize; System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt); mWriteIndex += amt; length -= amt; offset += amt; } } /** * Copies data _size_ bytes of data within this buffer from _srcOffset_ * to the current write position. Like memmov but handles the chunked buffer. */ public void writeFromThisBuffer(int srcOffset, int size) { if (mReadLimit < 0) { throw new IllegalStateException("writeFromThisBuffer before startEditing"); } if (srcOffset < getWritePos()) { throw new IllegalArgumentException("Can only move forward in the buffer --" + " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString()); } if (srcOffset + size > mReadableSize) { throw new IllegalArgumentException("Trying to move more data than there is --" + " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString()); } if (size == 0) { return; } if (srcOffset == ((mWriteBufIndex) * mChunkSize) + mWriteIndex /* write pos */) { // Writing to the same location. Just advance the write pointer. We already // checked that size is in bounds, so we don't need to do any more range // checking. if (size <= mChunkSize - mWriteIndex) { mWriteIndex += size; } else { size -= mChunkSize - mWriteIndex; mWriteIndex = size % mChunkSize; if (mWriteIndex == 0) { // Roll it back so nextWriteBuffer can do its job // on the next call (also makes mBuffers.get() not // fail if we're at the end). mWriteIndex = mChunkSize; mWriteBufIndex += (size / mChunkSize); } else { mWriteBufIndex += 1 + (size / mChunkSize); } mWriteBuffer = mBuffers.get(mWriteBufIndex); } } else { // Loop through the buffer, copying as much as we can each time. // We already bounds checked so we don't need to do it again here, // and nextWriteBuffer will never allocate. int readBufIndex = srcOffset / mChunkSize; byte[] readBuffer = mBuffers.get(readBufIndex); int readIndex = srcOffset % mChunkSize; while (size > 0) { if (mWriteIndex >= mChunkSize) { nextWriteBuffer(); } if (readIndex >= mChunkSize) { readBufIndex++; readBuffer = mBuffers.get(readBufIndex); readIndex = 0; } final int spaceInWriteBuffer = mChunkSize - mWriteIndex; final int availableInReadBuffer = mChunkSize - readIndex; final int amt = Math.min(size, Math.min(spaceInWriteBuffer, availableInReadBuffer)); System.arraycopy(readBuffer, readIndex, mWriteBuffer, mWriteIndex, amt); mWriteIndex += amt; readIndex += amt; size -= amt; } } } // // Writing at a particular location. // /** * Returns the index into the virtual array of the write pointer. */ public int getWritePos() { return ((mWriteBufIndex) * mChunkSize) + mWriteIndex; } /** * Resets the write pointer to a virtual location as returned by getWritePos. */ public void rewindWriteTo(int writePos) { if (writePos > getWritePos()) { throw new RuntimeException("rewindWriteTo only can go backwards" + writePos); } mWriteBufIndex = writePos / mChunkSize; mWriteIndex = writePos % mChunkSize; if (mWriteIndex == 0 && mWriteBufIndex != 0) { // Roll back so nextWriteBuffer can do its job on the next call // but at the first write we're at 0. mWriteIndex = mChunkSize; mWriteBufIndex--; } mWriteBuffer = mBuffers.get(mWriteBufIndex); } /** * Read a 32 bit value from the stream. * * Doesn't touch or affect mWritePos. */ public int getRawFixed32At(int pos) { return (0x00ff & (int)mBuffers.get(pos / mChunkSize)[pos % mChunkSize]) | ((0x0ff & (int)mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize]) << 8) | ((0x0ff & (int)mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize]) << 16) | ((0x0ff & (int)mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize]) << 24); } /** * Overwrite a 32 bit value in the stream. * * Doesn't touch or affect mWritePos. */ public void editRawFixed32(int pos, int val) { mBuffers.get(pos / mChunkSize)[pos % mChunkSize] = (byte)(val); mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize] = (byte)(val >> 8); mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize] = (byte)(val >> 16); mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize] = (byte)(val >> 24); } // // Zigging and zagging // /** * Zig-zag encode a 32 bit value. */ private static int zigZag32(int val) { return (val << 1) ^ (val >> 31); } /** * Zig-zag encode a 64 bit value. */ private static long zigZag64(long val) { return (val << 1) ^ (val >> 63); } // // Debugging / testing // // VisibleForTesting /** * Get a copy of the first _size_ bytes of data. This is not range * checked, and if the bounds are outside what has been written you will * get garbage and if it is outside the buffers that have been allocated, * you will get an exception. */ public byte[] getBytes(int size) { final byte[] result = new byte[size]; final int bufCount = size / mChunkSize; int bufIndex; int writeIndex = 0; for (bufIndex=0; bufIndex 0) { System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, lastSize); } return result; } /** * Get the number of chunks allocated. */ // VisibleForTesting public int getChunkCount() { return mBuffers.size(); } /** * Get the write position inside the current write chunk. */ // VisibleForTesting public int getWriteIndex() { return mWriteIndex; } /** * Get the index of the current write chunk in the list of chunks. */ // VisibleForTesting public int getWriteBufIndex() { return mWriteBufIndex; } /** * Return debugging information about this EncodedBuffer object. */ public String getDebugString() { return "EncodedBuffer( mChunkSize=" + mChunkSize + " mBuffers.size=" + mBuffers.size() + " mBufferCount=" + mBufferCount + " mWriteIndex=" + mWriteIndex + " mWriteBufIndex=" + mWriteBufIndex + " mReadBufIndex=" + mReadBufIndex + " mReadIndex=" + mReadIndex + " mReadableSize=" + mReadableSize + " mReadLimit=" + mReadLimit + " )"; } /** * Print the internal buffer chunks. */ public void dumpBuffers(String tag) { final int N = mBuffers.size(); int start = 0; for (int i=0; i> 4) & 0x0f); if (c < 10) { sb.append((char)('0' + c)); } else { sb.append((char)('a' - 10 + c)); } byte d = (byte)(b & 0x0f); if (d < 10) { sb.append((char)('0' + d)); } else { sb.append((char)('a' - 10 + d)); } } Log.d(tag, sb.toString()); return length; } }