0,0 → 1,885 |
// 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.google.protobuf; |
|
import java.io.IOException; |
import java.io.InputStream; |
import java.util.ArrayList; |
import java.util.List; |
|
/** |
* 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 CodedInputStream { |
/** |
* Create a new CodedInputStream wrapping the given InputStream. |
*/ |
public static CodedInputStream newInstance(final InputStream input) { |
return new CodedInputStream(input); |
} |
|
/** |
* Create a new CodedInputStream wrapping the given byte array. |
*/ |
public static CodedInputStream newInstance(final byte[] buf) { |
return newInstance(buf, 0, buf.length); |
} |
|
/** |
* Create a new CodedInputStream wrapping the given byte array slice. |
*/ |
public static CodedInputStream newInstance(final byte[] buf, final int off, |
final int len) { |
CodedInputStream result = new CodedInputStream(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 (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; |
} |
|
// ----------------------------------------------------------------- |
|
/** |
* 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()) { |
lastTag = 0; |
return 0; |
} |
|
lastTag = readRawVarint32(); |
if (WireFormat.getTagFieldNumber(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 lastTag; |
} |
|
/** |
* 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 (lastTag != value) { |
throw InvalidProtocolBufferException.invalidEndTag(); |
} |
} |
|
/** |
* 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; |
} |
} |
} |
|
// ----------------------------------------------------------------- |
|
/** Read a {@code double} field value from the stream. */ |
public double readDouble() throws IOException { |
return Double.longBitsToDouble(readRawLittleEndian64()); |
} |
|
/** Read a {@code float} field value from the stream. */ |
public float readFloat() throws IOException { |
return Float.intBitsToFloat(readRawLittleEndian32()); |
} |
|
/** Read a {@code uint64} field value from the stream. */ |
public long readUInt64() throws IOException { |
return readRawVarint64(); |
} |
|
/** Read an {@code int64} field value from the stream. */ |
public long readInt64() throws IOException { |
return readRawVarint64(); |
} |
|
/** Read an {@code int32} field value from the stream. */ |
public int readInt32() throws IOException { |
return readRawVarint32(); |
} |
|
/** Read a {@code fixed64} field value from the stream. */ |
public long readFixed64() throws IOException { |
return readRawLittleEndian64(); |
} |
|
/** Read a {@code fixed32} field value from the stream. */ |
public int readFixed32() throws IOException { |
return readRawLittleEndian32(); |
} |
|
/** Read a {@code bool} field value from the stream. */ |
public boolean readBool() throws IOException { |
return readRawVarint32() != 0; |
} |
|
/** Read a {@code string} field value from the stream. */ |
public String readString() throws IOException { |
final int size = readRawVarint32(); |
if (size <= (bufferSize - 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(buffer, bufferPos, size, "UTF-8"); |
bufferPos += size; |
return result; |
} else { |
// Slow path: Build a byte array first then copy it. |
return new String(readRawBytes(size), "UTF-8"); |
} |
} |
|
/** 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; |
} |
|
/** |
* 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 a {@code bytes} field value from the stream. */ |
public ByteString readBytes() throws IOException { |
final int size = readRawVarint32(); |
if (size == 0) { |
return ByteString.EMPTY; |
} else if (size <= (bufferSize - bufferPos) && size > 0) { |
// Fast path: We already have the bytes in a contiguous buffer, so |
// just copy directly from it. |
final ByteString result = ByteString.copyFrom(buffer, bufferPos, size); |
bufferPos += size; |
return result; |
} else { |
// Slow path: Build a byte array first then copy it. |
return ByteString.copyFrom(readRawBytes(size)); |
} |
} |
|
/** Read a {@code uint32} field value from the stream. */ |
public int readUInt32() throws IOException { |
return readRawVarint32(); |
} |
|
/** |
* 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(); |
} |
|
/** 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 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; |
} |
|
/** |
* 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(); |
} |
|
/** 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 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 (((int)b1 & 0xff) ) | |
(((int)b2 & 0xff) << 8) | |
(((int)b3 & 0xff) << 16) | |
(((int)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); |
} |
|
/** |
* 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); |
} |
|
// ----------------------------------------------------------------- |
|
private final byte[] buffer; |
private int bufferSize; |
private int bufferSizeAfterLimit; |
private int bufferPos; |
private final InputStream input; |
private int lastTag; |
|
/** |
* 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; |
|
/** The absolute position of the end of the current message. */ |
private int currentLimit = Integer.MAX_VALUE; |
|
/** See setRecursionLimit() */ |
private int recursionDepth; |
private int recursionLimit = DEFAULT_RECURSION_LIMIT; |
|
/** See setSizeLimit() */ |
private int sizeLimit = DEFAULT_SIZE_LIMIT; |
|
private static final int DEFAULT_RECURSION_LIMIT = 64; |
private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB |
private static final int BUFFER_SIZE = 4096; |
|
private CodedInputStream(final byte[] buffer, final int off, final int len) { |
this.buffer = buffer; |
bufferSize = off + len; |
bufferPos = off; |
totalBytesRetired = -off; |
input = null; |
} |
|
private CodedInputStream(final InputStream input) { |
buffer = new byte[BUFFER_SIZE]; |
bufferSize = 0; |
bufferPos = 0; |
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 = recursionLimit; |
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 = sizeLimit; |
sizeLimit = limit; |
return oldLimit; |
} |
|
/** |
* Resets the current size counter to zero (see {@link #setSizeLimit(int)}). |
*/ |
public void resetSizeCounter() { |
totalBytesRetired = -bufferPos; |
} |
|
/** |
* 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 you {@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 += totalBytesRetired + bufferPos; |
final int oldLimit = currentLimit; |
if (byteLimit > oldLimit) { |
throw InvalidProtocolBufferException.truncatedMessage(); |
} |
currentLimit = byteLimit; |
|
recomputeBufferSizeAfterLimit(); |
|
return oldLimit; |
} |
|
private void recomputeBufferSizeAfterLimit() { |
bufferSize += bufferSizeAfterLimit; |
final int bufferEnd = totalBytesRetired + bufferSize; |
if (bufferEnd > currentLimit) { |
// Limit is in current buffer. |
bufferSizeAfterLimit = bufferEnd - currentLimit; |
bufferSize -= bufferSizeAfterLimit; |
} else { |
bufferSizeAfterLimit = 0; |
} |
} |
|
/** |
* Discards the current limit, returning to the previous limit. |
* |
* @param oldLimit The old limit, as returned by {@code pushLimit}. |
*/ |
public void popLimit(final int oldLimit) { |
currentLimit = oldLimit; |
recomputeBufferSizeAfterLimit(); |
} |
|
/** |
* Returns the number of bytes to be read before the current limit. |
* If no limit is set, returns -1. |
*/ |
public int getBytesUntilLimit() { |
if (currentLimit == Integer.MAX_VALUE) { |
return -1; |
} |
|
final int currentAbsolutePosition = totalBytesRetired + bufferPos; |
return currentLimit - currentAbsolutePosition; |
} |
|
/** |
* 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 bufferPos == bufferSize && !refillBuffer(false); |
} |
|
/** |
* The total bytes read up to the current position. Calling |
* {@link #resetSizeCounter()} resets this value to zero. |
*/ |
public int getTotalBytesRead() { |
return totalBytesRetired + bufferPos; |
} |
|
/** |
* Called with {@code this.buffer} is empty to read more bytes from the |
* input. If {@code mustSucceed} is true, refillBuffer() gurantees 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 (bufferPos < bufferSize) { |
throw new IllegalStateException( |
"refillBuffer() called when buffer wasn't empty."); |
} |
|
if (totalBytesRetired + bufferSize == currentLimit) { |
// Oops, we hit a limit. |
if (mustSucceed) { |
throw InvalidProtocolBufferException.truncatedMessage(); |
} else { |
return false; |
} |
} |
|
totalBytesRetired += bufferSize; |
|
bufferPos = 0; |
bufferSize = (input == null) ? -1 : input.read(buffer); |
if (bufferSize == 0 || bufferSize < -1) { |
throw new IllegalStateException( |
"InputStream#read(byte[]) returned invalid result: " + bufferSize + |
"\nThe InputStream implementation is buggy."); |
} |
if (bufferSize == -1) { |
bufferSize = 0; |
if (mustSucceed) { |
throw InvalidProtocolBufferException.truncatedMessage(); |
} else { |
return false; |
} |
} else { |
recomputeBufferSizeAfterLimit(); |
final int totalBytesRead = |
totalBytesRetired + bufferSize + bufferSizeAfterLimit; |
if (totalBytesRead > sizeLimit || totalBytesRead < 0) { |
throw InvalidProtocolBufferException.sizeLimitExceeded(); |
} |
return true; |
} |
} |
|
/** |
* 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 (bufferPos == bufferSize) { |
refillBuffer(true); |
} |
return buffer[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 (totalBytesRetired + bufferPos + size > currentLimit) { |
// Read to the end of the stream anyway. |
skipRawBytes(currentLimit - totalBytesRetired - bufferPos); |
// Then fail. |
throw InvalidProtocolBufferException.truncatedMessage(); |
} |
|
if (size <= bufferSize - bufferPos) { |
// We have all the bytes we need already. |
final byte[] bytes = new byte[size]; |
System.arraycopy(buffer, bufferPos, bytes, 0, size); |
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 = bufferSize - bufferPos; |
System.arraycopy(buffer, bufferPos, bytes, 0, pos); |
bufferPos = 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 > bufferSize) { |
System.arraycopy(buffer, 0, bytes, pos, bufferSize); |
pos += bufferSize; |
bufferPos = bufferSize; |
refillBuffer(true); |
} |
|
System.arraycopy(buffer, 0, bytes, pos, size - pos); |
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 = bufferPos; |
final int originalBufferSize = bufferSize; |
|
// Mark the current buffer consumed. |
totalBytesRetired += bufferSize; |
bufferPos = 0; |
bufferSize = 0; |
|
// Read all the rest of the bytes we need. |
int sizeLeft = size - (originalBufferSize - originalBufferPos); |
final List<byte[]> chunks = new ArrayList<byte[]>(); |
|
while (sizeLeft > 0) { |
final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)]; |
int pos = 0; |
while (pos < chunk.length) { |
final int n = (input == null) ? -1 : |
input.read(chunk, pos, chunk.length - pos); |
if (n == -1) { |
throw InvalidProtocolBufferException.truncatedMessage(); |
} |
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(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; |
} |
} |
|
/** |
* 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 (totalBytesRetired + bufferPos + size > currentLimit) { |
// Read to the end of the stream anyway. |
skipRawBytes(currentLimit - totalBytesRetired - bufferPos); |
// Then fail. |
throw InvalidProtocolBufferException.truncatedMessage(); |
} |
|
if (size <= bufferSize - bufferPos) { |
// We have all the bytes we need already. |
bufferPos += size; |
} else { |
// Skipping more bytes than are in the buffer. First skip what we have. |
int pos = bufferSize - bufferPos; |
bufferPos = 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 > bufferSize) { |
pos += bufferSize; |
bufferPos = bufferSize; |
refillBuffer(true); |
} |
|
bufferPos = size - pos; |
} |
} |
} |