content: /*
   * 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<byte[]> mBuffers = new ArrayList<byte[]>();
  
      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 &lt;= 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;
      }
  
      /**
       * Returns the buffer size
       * @return the buffer size
       */
      public int getSize() {
          return ((mBufferCount - 1) * mChunkSize) + mWriteIndex;
      }
  
      //
      // 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 <b>before</b> 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<bufCount; bufIndex++) {
              System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, mChunkSize);
              writeIndex += mChunkSize;
          }
  
          final int lastSize = size - (bufCount * mChunkSize);
          if (lastSize > 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<N; i++) {
              start += dumpByteString(tag, "{" + i + "} ", start, mBuffers.get(i));
          }
      }
  
      /**
       * Print the internal buffer chunks.
       */
      public static void dumpByteString(String tag, String prefix, byte[] buf) {
          dumpByteString(tag, prefix, 0, buf);
      }
  
      /**
       * Print the internal buffer chunks.
       */
      private static int dumpByteString(String tag, String prefix, int start, byte[] buf) {
          StringBuffer sb = new StringBuffer();
          final int length = buf.length;
          final int lineLen = 16;
          int i;
          for (i=0; i<length; i++) {
              if (i % lineLen == 0) {
                  if (i != 0) {
                      Log.d(tag, sb.toString());
                      sb = new StringBuffer();
                  }
                  sb.append(prefix);
                  sb.append('[');
                  sb.append(start + i);
                  sb.append(']');
                  sb.append(' ');
              } else {
                  sb.append(' ');
              }
              byte b = buf[i];
              byte c = (byte)((b >> 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;
      }
  }
  