// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // http://code.google.com/p/protobuf/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package com.revolsys.io; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; import com.google.protobuf.ByteString; /** * Reads and decodes protocol message fields. * * This class contains two kinds of methods: methods that read specific * protocol message constructs and field types (e.g. {@link #readTag()} and * {@link #readInt32()}) and methods that read low-level values (e.g. * {@link #readRawVarint32()} and {@link #readRawBytes}). If you are reading * encoded protocol messages, you should use the former methods, but if you are * reading some other format of your own design, use the latter. * * @author kenton@google.com Kenton Varda */ public final class ProtocolBufferInputStream { private static final int BUFFER_SIZE = 4096; private static final int DEFAULT_RECURSION_LIMIT = 64; private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB /** * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers * into values that can be efficiently encoded with varint. (Otherwise, * negative values must be sign-extended to 64 bits to be varint encoded, * thus always taking 10 bytes on the wire.) * * @param n An unsigned 32-bit integer, stored in a signed int because * Java has no explicit unsigned support. * @return A signed 32-bit integer. */ public static int decodeZigZag32(final int n) { return n >>> 1 ^ -(n & 1); } /** * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers * into values that can be efficiently encoded with varint. (Otherwise, * negative values must be sign-extended to 64 bits to be varint encoded, * thus always taking 10 bytes on the wire.) * * @param n An unsigned 64-bit integer, stored in a signed int because * Java has no explicit unsigned support. * @return A signed 64-bit integer. */ public static long decodeZigZag64(final long n) { return n >>> 1 ^ -(n & 1); } // ----------------------------------------------------------------- /** * Construct a new new CodedInputStream wrapping the given byte array. */ public static ProtocolBufferInputStream newInstance(final byte[] buf) { return newInstance(buf, 0, buf.length); } /** * Construct a new new CodedInputStream wrapping the given byte array slice. */ public static ProtocolBufferInputStream newInstance(final byte[] buf, final int off, final int len) { final ProtocolBufferInputStream result = new ProtocolBufferInputStream(buf, off, len); try { // Some uses of CodedInputStream can be more efficient if they know // exactly how many bytes are available. By pushing the end point of the // buffer as a limit, we allow them to get this information via // getBytesUntilLimit(). Pushing a limit that we know is at the end of // the stream can never hurt, since we can never past that point anyway. result.pushLimit(len); } catch (final InvalidProtocolBufferException ex) { // The only reason pushLimit() might throw an exception here is if len // is negative. Normally pushLimit()'s parameter comes directly off the // wire, so it's important to catch exceptions in case of corrupt or // malicious data. However, in this case, we expect that len is not a // user-supplied value, so we can assume that it being negative indicates // a programming error. Therefore, throwing an unchecked exception is // appropriate. throw new IllegalArgumentException(ex); } return result; } /** * Construct a new new CodedInputStream wrapping the given InputStream. */ public static ProtocolBufferInputStream newInstance(final InputStream input) { return new ProtocolBufferInputStream(input); } /** * Reads a varint from the input one byte at a time, so that it does not * read any bytes after the end of the varint. If you simply wrapped the * stream in a CodedInputStream and used {@link #readRawVarint32(InputStream)} * then you would probably end up reading past the end of the varint since * CodedInputStream buffers its input. */ static int readRawVarint32(final InputStream input) throws IOException { final int firstByte = input.read(); if (firstByte == -1) { throw InvalidProtocolBufferException.truncatedMessage(); } return readRawVarint32(firstByte, input); } // ----------------------------------------------------------------- /** * Like {@link #readRawVarint32(InputStream)}, but expects that the caller * has already read one byte. This allows the caller to determine if EOF * has been reached before attempting to read. */ public static int readRawVarint32(final int firstByte, final InputStream input) throws IOException { if ((firstByte & 0x80) == 0) { return firstByte; } int result = firstByte & 0x7f; int offset = 7; for (; offset < 32; offset += 7) { final int b = input.read(); if (b == -1) { throw InvalidProtocolBufferException.truncatedMessage(); } result |= (b & 0x7f) << offset; if ((b & 0x80) == 0) { return result; } } // Keep reading up to 64 bits. for (; offset < 64; offset += 7) { final int b = input.read(); if (b == -1) { throw InvalidProtocolBufferException.truncatedMessage(); } if ((b & 0x80) == 0) { return result; } } throw InvalidProtocolBufferException.malformedVarint(); } private byte[] buffer; private int bufferPos; private int bufferSize; private int bufferSizeAfterLimit; /** The absolute position of the end of the current message. */ private int currentLimit = Integer.MAX_VALUE; private InputStream input; private int lastTag; /** See setRecursionLimit() */ private int recursionDepth; // /** // * Reads a {@code group} field value from the stream and merges it into the // * given {@link UnknownFieldSet}. // * // * @deprecated UnknownFieldSet.Builder now implements MessageLite.Builder, // so // * you can just call {@link #readGroup}. // */ // @Deprecated // public void readUnknownGroup(final int fieldNumber, // final MessageLite.Builder builder) // throws IOException { // // We know that UnknownFieldSet will ignore any ExtensionRegistry so it // // is safe to pass null here. (We can't call // // ExtensionRegistry.getEmptyRegistry() because that would make this // // class depend on ExtensionRegistry, which is not part of the lite // // library.) // readGroup(fieldNumber, builder, null); // } // // /** Read an embedded message field value from the stream. */ // public void readMessage(final MessageLite.Builder builder, // final ExtensionRegistryLite extensionRegistry) // throws IOException { // final int length = readRawVarint32(); // if (recursionDepth >= recursionLimit) { // throw InvalidProtocolBufferException.recursionLimitExceeded(); // } // final int oldLimit = pushLimit(length); // ++recursionDepth; // builder.mergeFrom(this, extensionRegistry); // checkLastTagWas(0); // --recursionDepth; // popLimit(oldLimit); // } // // /** Read an embedded message field value from the stream. */ // public <T extends MessageLite> T readMessage( // final Parser<T> parser, // final ExtensionRegistryLite extensionRegistry) // throws IOException { // int length = readRawVarint32(); // if (recursionDepth >= recursionLimit) { // throw InvalidProtocolBufferException.recursionLimitExceeded(); // } // final int oldLimit = pushLimit(length); // ++recursionDepth; // T result = parser.parsePartialFrom(this, extensionRegistry); // checkLastTagWas(0); // --recursionDepth; // popLimit(oldLimit); // return result; // } private int recursionLimit = DEFAULT_RECURSION_LIMIT; /** See setSizeLimit() */ private int sizeLimit = DEFAULT_SIZE_LIMIT; /** * The total number of bytes read before the current buffer. The total * bytes read up to the current position can be computed as * {@code totalBytesRetired + bufferPos}. This value may be negative if * reading started in the middle of the current buffer (e.g. if the * constructor that takes a byte array and an offset was used). */ private int totalBytesRetired; public ProtocolBufferInputStream() { this(new byte[0]); } public ProtocolBufferInputStream(final byte[] buffer) { this(buffer, 0, buffer.length); } public ProtocolBufferInputStream(final byte[] buffer, final int off, final int len) { setBuffer(buffer, off, len); } public ProtocolBufferInputStream(final InputStream input) { setInputStream(input); } /** * Verifies that the last call to readTag() returned the given tag value. * This is used to verify that a nested group ended with the correct * end tag. * * @throws InvalidProtocolBufferException {@code value} does not match the * last tag. */ public void checkLastTagWas(final int value) throws InvalidProtocolBufferException { if (this.lastTag != value) { throw InvalidProtocolBufferException.invalidEndTag(); } } public void endLengthDelimited(final int limit) throws InvalidProtocolBufferException { checkLastTagWas(0); --this.recursionDepth; popLimit(limit); } /** * Returns the number of bytes to be read before the current limit. * If no limit is set, returns -1. */ public int getBytesUntilLimit() { if (this.currentLimit == Integer.MAX_VALUE) { return -1; } final int currentAbsolutePosition = this.totalBytesRetired + this.bufferPos; return this.currentLimit - currentAbsolutePosition; } // ================================================================= /** * The total bytes read up to the current position. Calling * {@link #resetSizeCounter()} resets this value to zero. */ public int getTotalBytesRead() { return this.totalBytesRetired + this.bufferPos; } /** * Returns true if the stream has reached the end of the input. This is the * case if either the end of the underlying input source has been reached or * if the stream has reached a limit created using {@link #pushLimit(int)}. */ public boolean isAtEnd() throws IOException { return this.bufferPos == this.bufferSize && !refillBuffer(false); } /** * Discards the current limit, returning to the previous limit. * * @param oldLimit The old limit, as returned by {@code pushLimit}. */ public void popLimit(final int limit) { this.currentLimit = limit; recomputeBufferSizeAfterLimit(); } /** * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This * is called when descending into a length-delimited embedded message. * * <p>Note that {@code pushLimit()} does NOT affect how many bytes the * {@code CodedInputStream} reads from an underlying {@code InputStream} when * refreshing its buffer. If you need to prevent reading past a certain * point in the underlying {@code InputStream} (e.g. because you expect it to * contain more data after the end of the message which you need to handle * differently) then you must place a wrapper around your {@code InputStream} * which limits the amount of data that can be read from it. * * @return the old limit. */ public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { if (byteLimit < 0) { throw InvalidProtocolBufferException.negativeSize(); } byteLimit += this.totalBytesRetired + this.bufferPos; final int oldLimit = this.currentLimit; if (byteLimit > oldLimit) { throw InvalidProtocolBufferException.truncatedMessage(); } this.currentLimit = byteLimit; recomputeBufferSizeAfterLimit(); return oldLimit; } /** Read a {@code bool} field value from the stream. */ public boolean readBool() throws IOException { return readRawVarint32() != 0; } public void readBool(final Collection<Boolean> booleans) throws IOException { final boolean bool = readBool(); booleans.add(bool); } public void readBools(final Collection<Boolean> booleans) throws IOException { final int length = readRawVarint32(); final int oldLength = pushLimit(length); while (getBytesUntilLimit() > 0) { final boolean bool = readBool(); booleans.add(bool); } popLimit(oldLength); } /** Read a {@code bytes} field value from the stream. */ public byte[] readBytes() throws IOException { final int size = readRawVarint32(); if (size == 0) { return new byte[0]; } else if (size <= this.bufferSize - this.bufferPos && size > 0) { final byte[] copy = new byte[size]; System.arraycopy(this.buffer, this.bufferPos, copy, 0, size); this.bufferPos += size; return copy; } else { return readRawBytes(size); } } /** Read a {@code double} field value from the stream. */ public double readDouble() throws IOException { return Double.longBitsToDouble(readRawLittleEndian64()); } /** * Read an enum field value from the stream. Caller is responsible * for converting the numeric value to an actual enum. */ public int readEnum() throws IOException { return readRawVarint32(); } public void readEnum(final Collection<Integer> enums) throws IOException { final int number = readEnum(); enums.add(number); } public void readEnums(final Collection<Integer> enums) throws IOException { final int length = readRawVarint32(); final int oldLength = pushLimit(length); while (getBytesUntilLimit() > 0) { final int number = readEnum(); enums.add(number); } popLimit(oldLength); } // ----------------------------------------------------------------- /** Read a {@code fixed32} field value from the stream. */ public int readFixed32() throws IOException { return readRawLittleEndian32(); } /** Read a {@code fixed64} field value from the stream. */ public long readFixed64() throws IOException { return readRawLittleEndian64(); } /** Read a {@code float} field value from the stream. */ public float readFloat() throws IOException { return Float.intBitsToFloat(readRawLittleEndian32()); } public void readInt(final Collection<Integer> ints) throws IOException { final int number = readSInt32(); ints.add(number); } /** Read an {@code int32} field value from the stream. */ public int readInt32() throws IOException { return readRawVarint32(); } /** Read an {@code int64} field value from the stream. */ public long readInt64() throws IOException { return readRawVarint64(); } public void readInts(final Collection<Integer> ints) throws IOException { final int length = readRawVarint32(); final int oldLength = pushLimit(length); while (getBytesUntilLimit() > 0) { final int number = readSInt32(); ints.add(number); } popLimit(oldLength); } public void readLong(final Collection<Long> longs) throws IOException { final long number = readSInt64(); longs.add(number); } public void readLongs(final Collection<Long> longs) throws IOException { final int length = readRawVarint32(); final int oldLength = pushLimit(length); while (getBytesUntilLimit() > 0) { final long number = readSInt64(); longs.add(number); } popLimit(oldLength); } /** * Read one byte from the input. * * @throws InvalidProtocolBufferException The end of the stream or the current * limit was reached. */ public byte readRawByte() throws IOException { if (this.bufferPos == this.bufferSize) { refillBuffer(true); } return this.buffer[this.bufferPos++]; } /** * Read a fixed size of bytes from the input. * * @throws InvalidProtocolBufferException The end of the stream or the current * limit was reached. */ public byte[] readRawBytes(final int size) throws IOException { if (size < 0) { throw InvalidProtocolBufferException.negativeSize(); } if (this.totalBytesRetired + this.bufferPos + size > this.currentLimit) { // Read to the end of the stream anyway. skipRawBytes(this.currentLimit - this.totalBytesRetired - this.bufferPos); // Then fail. throw InvalidProtocolBufferException.truncatedMessage(); } if (size <= this.bufferSize - this.bufferPos) { // We have all the bytes we need already. final byte[] bytes = new byte[size]; System.arraycopy(this.buffer, this.bufferPos, bytes, 0, size); this.bufferPos += size; return bytes; } else if (size < BUFFER_SIZE) { // Reading more bytes than are in the buffer, but not an excessive number // of bytes. We can safely allocate the resulting array ahead of time. // First copy what we have. final byte[] bytes = new byte[size]; int pos = this.bufferSize - this.bufferPos; System.arraycopy(this.buffer, this.bufferPos, bytes, 0, pos); this.bufferPos = this.bufferSize; // We want to use refillBuffer() and then copy from the buffer into our // byte array rather than reading directly into our byte array because // the input may be unbuffered. refillBuffer(true); while (size - pos > this.bufferSize) { System.arraycopy(this.buffer, 0, bytes, pos, this.bufferSize); pos += this.bufferSize; this.bufferPos = this.bufferSize; refillBuffer(true); } System.arraycopy(this.buffer, 0, bytes, pos, size - pos); this.bufferPos = size - pos; return bytes; } else { // The size is very large. For security reasons, we can't allocate the // entire byte array yet. The size comes directly from the input, so a // maliciously-crafted message could provide a bogus very large size in // order to trick the app into allocating a lot of memory. We avoid this // by allocating and reading only a small chunk at a time, so that the // malicious message must actually *be* extremely large to cause // problems. Meanwhile, we limit the allowed size of a message elsewhere. // Remember the buffer markers since we'll have to copy the bytes out of // it later. final int originalBufferPos = this.bufferPos; final int originalBufferSize = this.bufferSize; // Mark the current buffer consumed. this.totalBytesRetired += this.bufferSize; this.bufferPos = 0; this.bufferSize = 0; // Read all the rest of the bytes we need. int sizeLeft = size - (originalBufferSize - originalBufferPos); final List<byte[]> chunks = new ArrayList<>(); while (sizeLeft > 0) { final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)]; int pos = 0; while (pos < chunk.length) { final int n = this.input == null ? -1 : this.input.read(chunk, pos, chunk.length - pos); if (n == -1) { throw InvalidProtocolBufferException.truncatedMessage(); } this.totalBytesRetired += n; pos += n; } sizeLeft -= chunk.length; chunks.add(chunk); } // OK, got everything. Now concatenate it all into one buffer. final byte[] bytes = new byte[size]; // Start by copying the leftover bytes from this.buffer. int pos = originalBufferSize - originalBufferPos; System.arraycopy(this.buffer, originalBufferPos, bytes, 0, pos); // And now all the chunks. for (final byte[] chunk : chunks) { System.arraycopy(chunk, 0, bytes, pos, chunk.length); pos += chunk.length; } // Done. return bytes; } } /** Read a 32-bit little-endian integer from the stream. */ public int readRawLittleEndian32() throws IOException { final byte b1 = readRawByte(); final byte b2 = readRawByte(); final byte b3 = readRawByte(); final byte b4 = readRawByte(); return b1 & 0xff | (b2 & 0xff) << 8 | (b3 & 0xff) << 16 | (b4 & 0xff) << 24; } /** Read a 64-bit little-endian integer from the stream. */ public long readRawLittleEndian64() throws IOException { final byte b1 = readRawByte(); final byte b2 = readRawByte(); final byte b3 = readRawByte(); final byte b4 = readRawByte(); final byte b5 = readRawByte(); final byte b6 = readRawByte(); final byte b7 = readRawByte(); final byte b8 = readRawByte(); return (long)b1 & 0xff | ((long)b2 & 0xff) << 8 | ((long)b3 & 0xff) << 16 | ((long)b4 & 0xff) << 24 | ((long)b5 & 0xff) << 32 | ((long)b6 & 0xff) << 40 | ((long)b7 & 0xff) << 48 | ((long)b8 & 0xff) << 56; } /** * Read a raw Varint from the stream. If larger than 32 bits, discard the * upper bits. */ public int readRawVarint32() throws IOException { byte tmp = readRawByte(); if (tmp >= 0) { return tmp; } int result = tmp & 0x7f; if ((tmp = readRawByte()) >= 0) { result |= tmp << 7; } else { result |= (tmp & 0x7f) << 7; if ((tmp = readRawByte()) >= 0) { result |= tmp << 14; } else { result |= (tmp & 0x7f) << 14; if ((tmp = readRawByte()) >= 0) { result |= tmp << 21; } else { result |= (tmp & 0x7f) << 21; result |= (tmp = readRawByte()) << 28; if (tmp < 0) { // Discard upper 32 bits. for (int i = 0; i < 5; i++) { if (readRawByte() >= 0) { return result; } } throw InvalidProtocolBufferException.malformedVarint(); } } } } return result; } /** Read a raw Varint from the stream. */ public long readRawVarint64() throws IOException { int shift = 0; long result = 0; while (shift < 64) { final byte b = readRawByte(); result |= (long)(b & 0x7F) << shift; if ((b & 0x80) == 0) { return result; } shift += 7; } throw InvalidProtocolBufferException.malformedVarint(); } /** Read an {@code sfixed32} field value from the stream. */ public int readSFixed32() throws IOException { return readRawLittleEndian32(); } /** Read an {@code sfixed64} field value from the stream. */ public long readSFixed64() throws IOException { return readRawLittleEndian64(); } /** Read an {@code sint32} field value from the stream. */ public int readSInt32() throws IOException { return decodeZigZag32(readRawVarint32()); } /** Read an {@code sint64} field value from the stream. */ public long readSInt64() throws IOException { return decodeZigZag64(readRawVarint64()); } /** Read a {@code string} field value from the stream. */ public String readString() throws IOException { final int size = readRawVarint32(); if (size <= this.bufferSize - this.bufferPos && size > 0) { // Fast path: We already have the bytes in a contiguous buffer, so // just copy directly from it. final String result = new String(this.buffer, this.bufferPos, size, "UTF-8"); this.bufferPos += size; return result; } else { // Slow path: Build a byte array first then copy it. return new String(readRawBytes(size), "UTF-8"); } } public String readString(final String charsetName) throws IOException { final byte[] bytes = readBytes(); return new String(bytes, charsetName); } public String readStringUtf8() throws IOException { return readString("UTF-8"); } /** * Attempt to read a field tag, returning zero if we have reached EOF. * Protocol message parsers use this to read tags, since a protocol message * may legally end wherever a tag occurs, and zero is not a valid tag number. */ public int readTag() throws IOException { if (isAtEnd()) { this.lastTag = 0; return 0; } this.lastTag = readRawVarint32(); if (WireFormat.getTagFieldNumber(this.lastTag) == 0) { // If we actually read zero (or any tag number corresponding to field // number zero), that's not a valid tag. throw InvalidProtocolBufferException.invalidTag(); } return this.lastTag; } /** Read a {@code uint32} field value from the stream. */ public int readUInt32() throws IOException { return readRawVarint32(); } /** Read a {@code uint64} field value from the stream. */ public long readUInt64() throws IOException { return readRawVarint64(); } private void recomputeBufferSizeAfterLimit() { this.bufferSize += this.bufferSizeAfterLimit; final int bufferEnd = this.totalBytesRetired + this.bufferSize; if (bufferEnd > this.currentLimit) { // Limit is in current buffer. this.bufferSizeAfterLimit = bufferEnd - this.currentLimit; this.bufferSize -= this.bufferSizeAfterLimit; } else { this.bufferSizeAfterLimit = 0; } } // // /** Read a {@code group} field value from the stream. */ // public void readGroup(final int fieldNumber, // final MessageLite.Builder builder, // final ExtensionRegistryLite extensionRegistry) // throws IOException { // if (recursionDepth >= recursionLimit) { // throw InvalidProtocolBufferException.recursionLimitExceeded(); // } // ++recursionDepth; // builder.mergeFrom(this, extensionRegistry); // checkLastTagWas( // WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); // --recursionDepth; // } // // /** Read a {@code group} field value from the stream. */ // public <T extends MessageLite> T readGroup( // final int fieldNumber, // final Parser<T> parser, // final ExtensionRegistryLite extensionRegistry) // throws IOException { // if (recursionDepth >= recursionLimit) { // throw InvalidProtocolBufferException.recursionLimitExceeded(); // } // ++recursionDepth; // T result = parser.parsePartialFrom(this, extensionRegistry); // checkLastTagWas( // WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); // --recursionDepth; // return result; // } /** * Called with {@code this.buffer} is empty to read more bytes from the * input. If {@code mustSucceed} is true, refillBuffer() guarantees that * either there will be at least one byte in the buffer when it returns * or it will throw an exception. If {@code mustSucceed} is false, * refillBuffer() returns false if no more bytes were available. */ private boolean refillBuffer(final boolean mustSucceed) throws IOException { if (this.bufferPos < this.bufferSize) { throw new IllegalStateException("refillBuffer() called when buffer wasn't empty."); } if (this.totalBytesRetired + this.bufferSize == this.currentLimit) { // Oops, we hit a limit. if (mustSucceed) { throw InvalidProtocolBufferException.truncatedMessage(); } else { return false; } } this.totalBytesRetired += this.bufferSize; this.bufferPos = 0; this.bufferSize = this.input == null ? -1 : this.input.read(this.buffer); if (this.bufferSize == 0 || this.bufferSize < -1) { throw new IllegalStateException("InputStream#read(byte[]) returned invalid result: " + this.bufferSize + "\nThe InputStream implementation is buggy."); } if (this.bufferSize == -1) { this.bufferSize = 0; if (mustSucceed) { throw InvalidProtocolBufferException.truncatedMessage(); } else { return false; } } else { recomputeBufferSizeAfterLimit(); final int totalBytesRead = this.totalBytesRetired + this.bufferSize + this.bufferSizeAfterLimit; if (totalBytesRead > this.sizeLimit || totalBytesRead < 0) { throw InvalidProtocolBufferException.sizeLimitExceeded(); } return true; } } /** * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). */ public void resetSizeCounter() { this.totalBytesRetired = -this.bufferPos; } public void setBuffer(final byte[] buffer) { setBuffer(buffer, 0, buffer.length); } public void setBuffer(final byte[] buffer, final int offset, final int length) { this.buffer = buffer; this.bufferSize = offset + length; this.bufferPos = offset; this.totalBytesRetired = -offset; this.input = null; } public void setInputStream(final InputStream input) { this.buffer = new byte[BUFFER_SIZE]; this.bufferSize = 0; this.bufferPos = 0; this.totalBytesRetired = 0; this.input = input; } /** * Set the maximum message recursion depth. In order to prevent malicious * messages from causing stack overflows, {@code CodedInputStream} limits * how deeply messages may be nested. The default limit is 64. * * @return the old limit. */ public int setRecursionLimit(final int limit) { if (limit < 0) { throw new IllegalArgumentException("Recursion limit cannot be negative: " + limit); } final int oldLimit = this.recursionLimit; this.recursionLimit = limit; return oldLimit; } /** * Set the maximum message size. In order to prevent malicious * messages from exhausting memory or causing integer overflows, * {@code CodedInputStream} limits how large a message may be. * The default limit is 64MB. You should set this limit as small * as you can without harming your app's functionality. Note that * size limits only apply when reading from an {@code InputStream}, not * when constructed around a raw byte array (nor with * {@link ByteString#newCodedInput}). * <p> * If you want to read several messages from a single CodedInputStream, you * could call {@link #resetSizeCounter()} after each one to avoid hitting the * size limit. * * @return the old limit. */ public int setSizeLimit(final int limit) { if (limit < 0) { throw new IllegalArgumentException("Size limit cannot be negative: " + limit); } final int oldLimit = this.sizeLimit; this.sizeLimit = limit; return oldLimit; } /** * Reads and discards a single field, given its tag value. * * @return {@code false} if the tag is an endgroup tag, in which case * nothing is skipped. Otherwise, returns {@code true}. */ public boolean skipField(final int tag) throws IOException { switch (WireFormat.getTagWireType(tag)) { case WireFormat.WIRETYPE_VARINT: readInt32(); return true; case WireFormat.WIRETYPE_FIXED64: readRawLittleEndian64(); return true; case WireFormat.WIRETYPE_LENGTH_DELIMITED: skipRawBytes(readRawVarint32()); return true; case WireFormat.WIRETYPE_START_GROUP: skipMessage(); checkLastTagWas( WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP)); return true; case WireFormat.WIRETYPE_END_GROUP: return false; case WireFormat.WIRETYPE_FIXED32: readRawLittleEndian32(); return true; default: throw InvalidProtocolBufferException.invalidWireType(); } } /** * Reads and discards an entire message. This will read either until EOF * or until an endgroup tag, whichever comes first. */ public void skipMessage() throws IOException { while (true) { final int tag = readTag(); if (tag == 0 || !skipField(tag)) { return; } } } /** * Reads and discards {@code size} bytes. * * @throws InvalidProtocolBufferException The end of the stream or the current * limit was reached. */ public void skipRawBytes(final int size) throws IOException { if (size < 0) { throw InvalidProtocolBufferException.negativeSize(); } if (this.totalBytesRetired + this.bufferPos + size > this.currentLimit) { // Read to the end of the stream anyway. skipRawBytes(this.currentLimit - this.totalBytesRetired - this.bufferPos); // Then fail. throw InvalidProtocolBufferException.truncatedMessage(); } if (size <= this.bufferSize - this.bufferPos) { // We have all the bytes we need already. this.bufferPos += size; } else { // Skipping more bytes than are in the buffer. First skip what we have. int pos = this.bufferSize - this.bufferPos; this.bufferPos = this.bufferSize; // Keep refilling the buffer until we get to the point we wanted to skip // to. This has the side effect of ensuring the limits are updated // correctly. refillBuffer(true); while (size - pos > this.bufferSize) { pos += this.bufferSize; this.bufferPos = this.bufferSize; refillBuffer(true); } this.bufferPos = size - pos; } } public int startLengthDelimited() throws IOException { final int length = readRawVarint32(); if (this.recursionDepth >= this.recursionLimit) { throw InvalidProtocolBufferException.recursionLimitExceeded(); } final int oldLimit = pushLimit(length); ++this.recursionDepth; return oldLimit; } }