1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.util.proto;
18
19import android.annotation.TestApi;
20import android.util.Log;
21
22import java.util.ArrayList;
23
24/**
25 * A stream of bytes containing a read pointer and a write pointer,
26 * backed by a set of fixed-size buffers.  There are write functions for the
27 * primitive types stored by protocol buffers, but none of the logic
28 * for tags, inner objects, or any of that.
29 *
30 * Terminology:
31 *      *Pos:       Position in the whole data set (as if it were a single buffer).
32 *      *Index:     Position within a buffer.
33 *      *BufIndex:  Index of a buffer within the mBuffers list
34 * @hide
35 */
36@TestApi
37public final class EncodedBuffer {
38    private static final String TAG = "EncodedBuffer";
39
40    private final ArrayList<byte[]> mBuffers = new ArrayList<byte[]>();
41
42    private final int mChunkSize;
43
44    /**
45     * The number of buffers in mBuffers. Stored separately to avoid the extra
46     * function call to size() everywhere for bounds checking.
47     */
48    private int mBufferCount;
49
50    /**
51     * The buffer we are currently writing to.
52     */
53    private byte[] mWriteBuffer;
54
55    /**
56     * The index into mWriteBuffer that we will write to next.
57     * It may point to the end of the buffer, in which case,
58     * the NEXT write will allocate a new buffer.
59     */
60    private int mWriteIndex;
61
62    /**
63     * The index of mWriteBuffer in mBuffers.
64     */
65    private int mWriteBufIndex;
66
67    /**
68     * The buffer we are currently reading from.
69     */
70    private byte[] mReadBuffer;
71
72    /**
73     * The index of mReadBuffer in mBuffers.
74     */
75    private int mReadBufIndex;
76
77    /**
78     * The index into mReadBuffer that we will read from next.
79     * It may point to the end of the buffer, in which case,
80     * the NEXT read will advance to the next buffer.
81     */
82    private int mReadIndex;
83
84    /**
85     * The amount of data in the last buffer.
86     */
87    private int mReadLimit = -1;
88
89    /**
90     * How much data there is total.
91     */
92    private int mReadableSize = -1;
93
94    public EncodedBuffer() {
95        this(0);
96    }
97
98    /**
99     * Construct an EncodedBuffer object.
100     *
101     * @param chunkSize The size of the buffers to use.  If chunkSize &lt;= 0, a default
102     *                  size will be used instead.
103     */
104    public EncodedBuffer(int chunkSize) {
105        if (chunkSize <= 0) {
106            chunkSize = 8 * 1024;
107        }
108        mChunkSize = chunkSize;
109        mWriteBuffer = new byte[mChunkSize];
110        mBuffers.add(mWriteBuffer);
111        mBufferCount = 1;
112    }
113
114    //
115    // Buffer management.
116    //
117
118    /**
119     * Rewind the read and write pointers, and record how much data was last written.
120     */
121    public void startEditing() {
122        mReadableSize = ((mWriteBufIndex) * mChunkSize) + mWriteIndex;
123        mReadLimit = mWriteIndex;
124
125        mWriteBuffer = mBuffers.get(0);
126        mWriteIndex = 0;
127        mWriteBufIndex = 0;
128
129        mReadBuffer = mWriteBuffer;
130        mReadBufIndex = 0;
131        mReadIndex = 0;
132    }
133
134    /**
135     * Rewind the read pointer. Don't touch the write pointer.
136     */
137    public void rewindRead() {
138        mReadBuffer = mBuffers.get(0);
139        mReadBufIndex = 0;
140        mReadIndex = 0;
141    }
142
143    /**
144     * Only valid after startEditing. Returns -1 before that.
145     */
146    public int getReadableSize() {
147        return mReadableSize;
148    }
149
150    //
151    // Reading from the read position.
152    //
153
154    /**
155     * Only valid after startEditing.
156     */
157    public int getReadPos() {
158        return ((mReadBufIndex) * mChunkSize) + mReadIndex;
159    }
160
161    /**
162     * Skip over _amount_ bytes.
163     */
164    public void skipRead(int amount) {
165        if (amount < 0) {
166            throw new RuntimeException("skipRead with negative amount=" + amount);
167        }
168        if (amount == 0) {
169            return;
170        }
171        if (amount <= mChunkSize - mReadIndex) {
172            mReadIndex += amount;
173        } else {
174            amount -= mChunkSize - mReadIndex;
175            mReadIndex = amount % mChunkSize;
176            if (mReadIndex == 0) {
177                mReadIndex = mChunkSize;
178                mReadBufIndex += (amount / mChunkSize);
179            } else {
180                mReadBufIndex += 1 + (amount / mChunkSize);
181            }
182            mReadBuffer = mBuffers.get(mReadBufIndex);
183        }
184    }
185
186    /**
187     * Read one byte from the stream and advance the read pointer.
188     *
189     * @throws IndexOutOfBoundsException if the read point is past the end of
190     * the buffer or past the read limit previously set by startEditing().
191     */
192    public byte readRawByte() {
193        if (mReadBufIndex > mBufferCount
194                || (mReadBufIndex == mBufferCount - 1 && mReadIndex >= mReadLimit)) {
195            throw new IndexOutOfBoundsException("Trying to read too much data"
196                    + " mReadBufIndex=" + mReadBufIndex + " mBufferCount=" + mBufferCount
197                    + " mReadIndex=" + mReadIndex + " mReadLimit=" + mReadLimit);
198        }
199        if (mReadIndex >= mChunkSize) {
200            mReadBufIndex++;
201            mReadBuffer = mBuffers.get(mReadBufIndex);
202            mReadIndex = 0;
203        }
204        return mReadBuffer[mReadIndex++];
205    }
206
207    /**
208     * Read an unsigned varint. The value will be returend in a java signed long.
209     */
210    public long readRawUnsigned() {
211        int bits = 0;
212        long result = 0;
213        while (true) {
214            final byte b = readRawByte();
215            result |= ((long)(b & 0x7F)) << bits;
216            if ((b & 0x80) == 0) {
217                return result;
218            }
219            bits += 7;
220            if (bits > 64) {
221                throw new ProtoParseException("Varint too long -- " + getDebugString());
222            }
223        }
224    }
225
226    /**
227     * Read 32 little endian bits from the stream.
228     */
229    public int readRawFixed32() {
230        return (readRawByte() & 0x0ff)
231                | ((readRawByte() & 0x0ff) << 8)
232                | ((readRawByte() & 0x0ff) << 16)
233                | ((readRawByte() & 0x0ff) << 24);
234    }
235
236    //
237    // Writing at a the end of the stream.
238    //
239
240    /**
241     * Advance to the next write buffer, allocating it if necessary.
242     *
243     * Must be called immediately <b>before</b> the next write, not after a write,
244     * so that a dangling empty buffer is not created.  Doing so will interfere
245     * with the expectation that mWriteIndex will point past the end of the buffer
246     * until the next read happens.
247     */
248    private void nextWriteBuffer() {
249        mWriteBufIndex++;
250        if (mWriteBufIndex >= mBufferCount) {
251            mWriteBuffer = new byte[mChunkSize];
252            mBuffers.add(mWriteBuffer);
253            mBufferCount++;
254        } else {
255            mWriteBuffer = mBuffers.get(mWriteBufIndex);
256        }
257        mWriteIndex = 0;
258    }
259
260    /**
261     * Write a single byte to the stream.
262     */
263    public void writeRawByte(byte val) {
264        if (mWriteIndex >= mChunkSize) {
265            nextWriteBuffer();
266        }
267        mWriteBuffer[mWriteIndex++] = val;
268    }
269
270    /**
271     * Return how many bytes a 32 bit unsigned varint will take when written to the stream.
272     */
273    public static int getRawVarint32Size(int val) {
274        if ((val & (0xffffffff << 7)) == 0) return 1;
275        if ((val & (0xffffffff << 14)) == 0) return 2;
276        if ((val & (0xffffffff << 21)) == 0) return 3;
277        if ((val & (0xffffffff << 28)) == 0) return 4;
278        return 5;
279    }
280
281    /**
282     * Write an unsigned varint to the stream. A signed value would need to take 10 bytes.
283     *
284     * @param val treated as unsigned.
285     */
286    public void writeRawVarint32(int val) {
287        while (true) {
288            if ((val & ~0x7F) == 0) {
289                writeRawByte((byte)val);
290                return;
291            } else {
292                writeRawByte((byte)((val & 0x7F) | 0x80));
293                val >>>= 7;
294            }
295        }
296    }
297
298    /**
299     * Return how many bytes a 32 bit signed zig zag value will take when written to the stream.
300     */
301    public static int getRawZigZag32Size(int val) {
302        return getRawVarint32Size(zigZag32(val));
303    }
304
305    /**
306     *  Write a zig-zag encoded value.
307     *
308     *  @param val treated as signed
309     */
310    public void writeRawZigZag32(int val) {
311        writeRawVarint32(zigZag32(val));
312    }
313
314    /**
315     * Return how many bytes a 64 bit varint will take when written to the stream.
316     */
317    public static int getRawVarint64Size(long val) {
318        if ((val & (0xffffffffffffffffL << 7)) == 0) return 1;
319        if ((val & (0xffffffffffffffffL << 14)) == 0) return 2;
320        if ((val & (0xffffffffffffffffL << 21)) == 0) return 3;
321        if ((val & (0xffffffffffffffffL << 28)) == 0) return 4;
322        if ((val & (0xffffffffffffffffL << 35)) == 0) return 5;
323        if ((val & (0xffffffffffffffffL << 42)) == 0) return 6;
324        if ((val & (0xffffffffffffffffL << 49)) == 0) return 7;
325        if ((val & (0xffffffffffffffffL << 56)) == 0) return 8;
326        if ((val & (0xffffffffffffffffL << 63)) == 0) return 9;
327        return 10;
328    }
329
330    /**
331     * Write a 64 bit varint to the stream.
332     */
333    public void writeRawVarint64(long val) {
334        while (true) {
335            if ((val & ~0x7FL) == 0) {
336                writeRawByte((byte)val);
337                return;
338            } else {
339                writeRawByte((byte)((val & 0x7F) | 0x80));
340                val >>>= 7;
341            }
342        }
343    }
344
345    /**
346     * Return how many bytes a signed 64 bit zig zag value will take when written to the stream.
347     */
348    public static int getRawZigZag64Size(long val) {
349        return getRawVarint64Size(zigZag64(val));
350    }
351
352    /**
353     * Write a 64 bit signed zig zag value to the stream.
354     */
355    public void writeRawZigZag64(long val) {
356        writeRawVarint64(zigZag64(val));
357    }
358
359    /**
360     * Write 4 little endian bytes to the stream.
361     */
362    public void writeRawFixed32(int val) {
363        writeRawByte((byte)(val));
364        writeRawByte((byte)(val >> 8));
365        writeRawByte((byte)(val >> 16));
366        writeRawByte((byte)(val >> 24));
367    }
368
369    /**
370     * Write 8 little endian bytes to the stream.
371     */
372    public void writeRawFixed64(long val) {
373        writeRawByte((byte)(val));
374        writeRawByte((byte)(val >> 8));
375        writeRawByte((byte)(val >> 16));
376        writeRawByte((byte)(val >> 24));
377        writeRawByte((byte)(val >> 32));
378        writeRawByte((byte)(val >> 40));
379        writeRawByte((byte)(val >> 48));
380        writeRawByte((byte)(val >> 56));
381    }
382
383    /**
384     * Write a buffer to the stream. Writes nothing if val is null or zero-length.
385     */
386    public void writeRawBuffer(byte[] val) {
387        if (val != null && val.length > 0) {
388            writeRawBuffer(val, 0, val.length);
389        }
390    }
391
392    /**
393     * Write part of an array of bytes.
394     */
395    public void writeRawBuffer(byte[] val, int offset, int length) {
396        if (val == null) {
397            return;
398        }
399        // Write up to the amount left in the first chunk to write.
400        int amt = length < (mChunkSize - mWriteIndex) ? length : (mChunkSize - mWriteIndex);
401        if (amt > 0) {
402            System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt);
403            mWriteIndex += amt;
404            length -= amt;
405            offset += amt;
406        }
407        while (length > 0) {
408            // We know we're now at the beginning of a chunk
409            nextWriteBuffer();
410            amt = length < mChunkSize ? length : mChunkSize;
411            System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt);
412            mWriteIndex += amt;
413            length -= amt;
414            offset += amt;
415        }
416    }
417
418    /**
419     * Copies data _size_ bytes of data within this buffer from _srcOffset_
420     * to the current write position. Like memmov but handles the chunked buffer.
421     */
422    public void writeFromThisBuffer(int srcOffset, int size) {
423        if (mReadLimit < 0) {
424            throw new IllegalStateException("writeFromThisBuffer before startEditing");
425        }
426        if (srcOffset < getWritePos()) {
427            throw new IllegalArgumentException("Can only move forward in the buffer --"
428                    + " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString());
429        }
430        if (srcOffset + size > mReadableSize) {
431            throw new IllegalArgumentException("Trying to move more data than there is --"
432                    + " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString());
433        }
434        if (size == 0) {
435            return;
436        }
437        if (srcOffset == ((mWriteBufIndex) * mChunkSize) + mWriteIndex /* write pos */) {
438            // Writing to the same location. Just advance the write pointer.  We already
439            // checked that size is in bounds, so we don't need to do any more range
440            // checking.
441            if (size <= mChunkSize - mWriteIndex) {
442                mWriteIndex += size;
443            } else {
444                size -= mChunkSize - mWriteIndex;
445                mWriteIndex = size % mChunkSize;
446                if (mWriteIndex == 0) {
447                    // Roll it back so nextWriteBuffer can do its job
448                    // on the next call (also makes mBuffers.get() not
449                    // fail if we're at the end).
450                    mWriteIndex = mChunkSize;
451                    mWriteBufIndex += (size / mChunkSize);
452                } else {
453                    mWriteBufIndex += 1 + (size / mChunkSize);
454                }
455                mWriteBuffer = mBuffers.get(mWriteBufIndex);
456            }
457        } else {
458            // Loop through the buffer, copying as much as we can each time.
459            // We already bounds checked so we don't need to do it again here,
460            // and nextWriteBuffer will never allocate.
461            int readBufIndex = srcOffset / mChunkSize;
462            byte[] readBuffer = mBuffers.get(readBufIndex);
463            int readIndex = srcOffset % mChunkSize;
464            while (size > 0) {
465                if (mWriteIndex >= mChunkSize) {
466                    nextWriteBuffer();
467                }
468                if (readIndex >= mChunkSize) {
469                    readBufIndex++;
470                    readBuffer = mBuffers.get(readBufIndex);
471                    readIndex = 0;
472                }
473                final int spaceInWriteBuffer = mChunkSize - mWriteIndex;
474                final int availableInReadBuffer = mChunkSize - readIndex;
475                final int amt = Math.min(size, Math.min(spaceInWriteBuffer, availableInReadBuffer));
476                System.arraycopy(readBuffer, readIndex, mWriteBuffer, mWriteIndex, amt);
477                mWriteIndex += amt;
478                readIndex += amt;
479                size -= amt;
480            }
481        }
482    }
483
484    //
485    // Writing at a particular location.
486    //
487
488    /**
489     * Returns the index into the virtual array of the write pointer.
490     */
491    public int getWritePos() {
492        return ((mWriteBufIndex) * mChunkSize) + mWriteIndex;
493    }
494
495    /**
496     * Resets the write pointer to a virtual location as returned by getWritePos.
497     */
498    public void rewindWriteTo(int writePos) {
499        if (writePos > getWritePos()) {
500            throw new RuntimeException("rewindWriteTo only can go backwards" + writePos);
501        }
502        mWriteBufIndex = writePos / mChunkSize;
503        mWriteIndex = writePos % mChunkSize;
504        if (mWriteIndex == 0 && mWriteBufIndex != 0) {
505            // Roll back so nextWriteBuffer can do its job on the next call
506            // but at the first write we're at 0.
507            mWriteIndex = mChunkSize;
508            mWriteBufIndex--;
509        }
510        mWriteBuffer = mBuffers.get(mWriteBufIndex);
511    }
512
513    /**
514     * Read a 32 bit value from the stream.
515     *
516     * Doesn't touch or affect mWritePos.
517     */
518    public int getRawFixed32At(int pos) {
519        return (0x00ff & (int)mBuffers.get(pos / mChunkSize)[pos % mChunkSize])
520                | ((0x0ff & (int)mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize]) << 8)
521                | ((0x0ff & (int)mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize]) << 16)
522                | ((0x0ff & (int)mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize]) << 24);
523    }
524
525    /**
526     * Overwrite a 32 bit value in the stream.
527     *
528     * Doesn't touch or affect mWritePos.
529     */
530    public void editRawFixed32(int pos, int val) {
531        mBuffers.get(pos / mChunkSize)[pos % mChunkSize] = (byte)(val);
532        mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize] = (byte)(val >> 8);
533        mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize] = (byte)(val >> 16);
534        mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize] = (byte)(val >> 24);
535    }
536
537    //
538    // Zigging and zagging
539    //
540
541    /**
542     * Zig-zag encode a 32 bit value.
543     */
544    private static int zigZag32(int val) {
545        return (val << 1) ^ (val >> 31);
546    }
547
548    /**
549     * Zig-zag encode a 64 bit value.
550     */
551    private static long zigZag64(long val) {
552        return (val << 1) ^ (val >> 63);
553    }
554
555    //
556    // Debugging / testing
557    //
558    // VisibleForTesting
559
560    /**
561     * Get a copy of the first _size_ bytes of data. This is not range
562     * checked, and if the bounds are outside what has been written you will
563     * get garbage and if it is outside the buffers that have been allocated,
564     * you will get an exception.
565     */
566    public byte[] getBytes(int size) {
567        final byte[] result = new byte[size];
568
569        final int bufCount = size / mChunkSize;
570        int bufIndex;
571        int writeIndex = 0;
572
573        for (bufIndex=0; bufIndex<bufCount; bufIndex++) {
574            System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, mChunkSize);
575            writeIndex += mChunkSize;
576        }
577
578        final int lastSize = size - (bufCount * mChunkSize);
579        if (lastSize > 0) {
580            System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, lastSize);
581        }
582
583        return result;
584    }
585
586    /**
587     * Get the number of chunks allocated.
588     */
589    // VisibleForTesting
590    public int getChunkCount() {
591        return mBuffers.size();
592    }
593
594    /**
595     * Get the write position inside the current write chunk.
596     */
597     // VisibleForTesting
598    public int getWriteIndex() {
599        return mWriteIndex;
600    }
601
602    /**
603     * Get the index of the current write chunk in the list of chunks.
604     */
605    // VisibleForTesting
606    public int getWriteBufIndex() {
607        return mWriteBufIndex;
608    }
609
610    /**
611     * Return debugging information about this EncodedBuffer object.
612     */
613    public String getDebugString() {
614        return "EncodedBuffer( mChunkSize=" + mChunkSize + " mBuffers.size=" + mBuffers.size()
615                + " mBufferCount=" + mBufferCount + " mWriteIndex=" + mWriteIndex
616                + " mWriteBufIndex=" + mWriteBufIndex + " mReadBufIndex=" + mReadBufIndex
617                + " mReadIndex=" + mReadIndex + " mReadableSize=" + mReadableSize
618                + " mReadLimit=" + mReadLimit + " )";
619    }
620
621    /**
622     * Print the internal buffer chunks.
623     */
624    public void dumpBuffers(String tag) {
625        final int N = mBuffers.size();
626        int start = 0;
627        for (int i=0; i<N; i++) {
628            start += dumpByteString(tag, "{" + i + "} ", start, mBuffers.get(i));
629        }
630    }
631
632    /**
633     * Print the internal buffer chunks.
634     */
635    public static void dumpByteString(String tag, String prefix, byte[] buf) {
636        dumpByteString(tag, prefix, 0, buf);
637    }
638
639    /**
640     * Print the internal buffer chunks.
641     */
642    private static int dumpByteString(String tag, String prefix, int start, byte[] buf) {
643        StringBuffer sb = new StringBuffer();
644        final int length = buf.length;
645        final int lineLen = 16;
646        int i;
647        for (i=0; i<length; i++) {
648            if (i % lineLen == 0) {
649                if (i != 0) {
650                    Log.d(tag, sb.toString());
651                    sb = new StringBuffer();
652                }
653                sb.append(prefix);
654                sb.append('[');
655                sb.append(start + i);
656                sb.append(']');
657                sb.append(' ');
658            } else {
659                sb.append(' ');
660            }
661            byte b = buf[i];
662            byte c = (byte)((b >> 4) & 0x0f);
663            if (c < 10) {
664                sb.append((char)('0' + c));
665            } else {
666                sb.append((char)('a' - 10 + c));
667            }
668            byte d = (byte)(b & 0x0f);
669            if (d < 10) {
670                sb.append((char)('0' + d));
671            } else {
672                sb.append((char)('a' - 10 + d));
673            }
674        }
675        Log.d(tag, sb.toString());
676        return length;
677    }
678}
679