Index: third_party/protobuf/java/core/src/main/java/com/google/protobuf/CodedInputStream.java |
diff --git a/third_party/protobuf/java/core/src/main/java/com/google/protobuf/CodedInputStream.java b/third_party/protobuf/java/core/src/main/java/com/google/protobuf/CodedInputStream.java |
index 14169dc4fb4147a793e86e103f3695bef39c9c11..e8860651a4fdc604a8f32c9ea1423a0a1354eddb 100644 |
--- a/third_party/protobuf/java/core/src/main/java/com/google/protobuf/CodedInputStream.java |
+++ b/third_party/protobuf/java/core/src/main/java/com/google/protobuf/CodedInputStream.java |
@@ -30,14 +30,6 @@ |
package com.google.protobuf; |
-import static com.google.protobuf.Internal.EMPTY_BYTE_ARRAY; |
-import static com.google.protobuf.Internal.EMPTY_BYTE_BUFFER; |
-import static com.google.protobuf.Internal.UTF_8; |
-import static com.google.protobuf.Internal.checkNotNull; |
-import static com.google.protobuf.WireFormat.FIXED_32_SIZE; |
-import static com.google.protobuf.WireFormat.FIXED_64_SIZE; |
-import static com.google.protobuf.WireFormat.MAX_VARINT_SIZE; |
- |
import java.io.ByteArrayOutputStream; |
import java.io.IOException; |
import java.io.InputStream; |
@@ -49,56 +41,51 @@ import java.util.List; |
/** |
* Reads and decodes protocol message fields. |
* |
- * <p>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. |
+ * 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 abstract class CodedInputStream { |
- private static final int DEFAULT_BUFFER_SIZE = 4096; |
- private static final int DEFAULT_RECURSION_LIMIT = 100; |
- // Integer.MAX_VALUE == 0x7FFFFFF == INT_MAX from limits.h |
- private static final int DEFAULT_SIZE_LIMIT = Integer.MAX_VALUE; |
- |
- /** Visible for subclasses. See setRecursionLimit() */ |
- int recursionDepth; |
- |
- int recursionLimit = DEFAULT_RECURSION_LIMIT; |
- |
- /** Visible for subclasses. See setSizeLimit() */ |
- int sizeLimit = DEFAULT_SIZE_LIMIT; |
- |
- /** Create a new CodedInputStream wrapping the given InputStream. */ |
+public final class CodedInputStream { |
+ /** |
+ * Create a new CodedInputStream wrapping the given InputStream. |
+ */ |
public static CodedInputStream newInstance(final InputStream input) { |
- return newInstance(input, DEFAULT_BUFFER_SIZE); |
+ return new CodedInputStream(input, BUFFER_SIZE); |
} |
- |
- /** Create a new CodedInputStream wrapping the given InputStream. */ |
+ |
+ /** |
+ * Create a new CodedInputStream wrapping the given InputStream. |
+ */ |
static CodedInputStream newInstance(final InputStream input, int bufferSize) { |
- if (input == null) { |
- // TODO(nathanmittler): Ideally we should throw here. This is done for backward compatibility. |
- return newInstance(EMPTY_BYTE_ARRAY); |
- } |
- return new StreamDecoder(input, bufferSize); |
+ return new CodedInputStream(input, bufferSize); |
} |
- /** Create a new CodedInputStream wrapping the given byte array. */ |
+ /** |
+ * 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) { |
+ /** |
+ * Create a new CodedInputStream wrapping the given byte array slice. |
+ */ |
+ public static CodedInputStream newInstance(final byte[] buf, final int off, |
+ final int len) { |
return newInstance(buf, off, len, false /* bufferIsImmutable */); |
} |
- |
- /** Create a new CodedInputStream wrapping the given byte array slice. */ |
+ |
+ /** |
+ * Create a new CodedInputStream wrapping the given byte array slice. |
+ */ |
static CodedInputStream newInstance( |
final byte[] buf, final int off, final int len, final boolean bufferIsImmutable) { |
- ArrayDecoder result = new ArrayDecoder(buf, off, len, bufferIsImmutable); |
+ CodedInputStream result = new CodedInputStream(buf, off, len, bufferIsImmutable); |
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 |
@@ -120,229 +107,821 @@ public abstract class CodedInputStream { |
} |
/** |
- * Create a new CodedInputStream wrapping the given ByteBuffer. The data starting from the |
- * ByteBuffer's current position to its limit will be read. The returned CodedInputStream may or |
- * may not share the underlying data in the ByteBuffer, therefore the ByteBuffer cannot be changed |
- * while the CodedInputStream is in use. Note that the ByteBuffer's position won't be changed by |
- * this function. Concurrent calls with the same ByteBuffer object are safe if no other thread is |
- * trying to alter the ByteBuffer's status. |
+ * Create a new CodedInputStream wrapping the given ByteBuffer. The data |
+ * starting from the ByteBuffer's current position to its limit will be read. |
+ * The returned CodedInputStream may or may not share the underlying data |
+ * in the ByteBuffer, therefore the ByteBuffer cannot be changed while the |
+ * CodedInputStream is in use. |
+ * Note that the ByteBuffer's position won't be changed by this function. |
+ * Concurrent calls with the same ByteBuffer object are safe if no other |
+ * thread is trying to alter the ByteBuffer's status. |
*/ |
public static CodedInputStream newInstance(ByteBuffer buf) { |
- return newInstance(buf, false /* bufferIsImmutable */); |
- } |
- |
- /** Create a new CodedInputStream wrapping the given buffer. */ |
- static CodedInputStream newInstance(ByteBuffer buf, boolean bufferIsImmutable) { |
if (buf.hasArray()) { |
- return newInstance( |
- buf.array(), buf.arrayOffset() + buf.position(), buf.remaining(), bufferIsImmutable); |
- } |
- |
- if (buf.isDirect() && UnsafeDirectNioDecoder.isSupported()) { |
- return new UnsafeDirectNioDecoder(buf, bufferIsImmutable); |
+ return newInstance(buf.array(), buf.arrayOffset() + buf.position(), |
+ buf.remaining()); |
+ } else { |
+ ByteBuffer temp = buf.duplicate(); |
+ byte[] buffer = new byte[temp.remaining()]; |
+ temp.get(buffer); |
+ return newInstance(buffer); |
} |
- |
- // The buffer is non-direct and does not expose the underlying array. Using the ByteBuffer API |
- // to access individual bytes is very slow, so just copy the buffer to an array. |
- // TODO(nathanmittler): Re-evaluate with Java 9 |
- byte[] buffer = new byte[buf.remaining()]; |
- buf.duplicate().get(buffer); |
- return newInstance(buffer, 0, buffer.length, true); |
} |
- /** Disable construction/inheritance outside of this class. */ |
- private CodedInputStream() {} |
- |
// ----------------------------------------------------------------- |
/** |
- * 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. |
+ * 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 abstract int readTag() throws IOException; |
+ 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. |
+ * 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. |
+ * @throws InvalidProtocolBufferException {@code value} does not match the |
+ * last tag. |
*/ |
- public abstract void checkLastTagWas(final int value) throws InvalidProtocolBufferException; |
+ public void checkLastTagWas(final int value) |
+ throws InvalidProtocolBufferException { |
+ if (lastTag != value) { |
+ throw InvalidProtocolBufferException.invalidEndTag(); |
+ } |
+ } |
- public abstract int getLastTag(); |
+ public int getLastTag() { |
+ return lastTag; |
+ } |
/** |
* 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}. |
+ * @return {@code false} if the tag is an endgroup tag, in which case |
+ * nothing is skipped. Otherwise, returns {@code true}. |
*/ |
- public abstract boolean skipField(final int tag) throws IOException; |
+ public boolean skipField(final int tag) throws IOException { |
+ switch (WireFormat.getTagWireType(tag)) { |
+ case WireFormat.WIRETYPE_VARINT: |
+ skipRawVarint(); |
+ return true; |
+ case WireFormat.WIRETYPE_FIXED64: |
+ skipRawBytes(8); |
+ 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: |
+ skipRawBytes(4); |
+ return true; |
+ default: |
+ throw InvalidProtocolBufferException.invalidWireType(); |
+ } |
+ } |
/** |
- * Reads a single field and writes it to output in wire format, given its tag value. |
+ * Reads a single field and writes it to output in wire format, |
+ * given its tag value. |
* |
- * @return {@code false} if the tag is an endgroup tag, in which case nothing is skipped. |
- * Otherwise, returns {@code true}. |
- * @deprecated use {@code UnknownFieldSet} or {@code UnknownFieldSetLite} to skip to an output |
- * stream. |
+ * @return {@code false} if the tag is an endgroup tag, in which case |
+ * nothing is skipped. Otherwise, returns {@code true}. |
*/ |
- @Deprecated |
- public abstract boolean skipField(final int tag, final CodedOutputStream output) |
- throws IOException; |
+ public boolean skipField(final int tag, final CodedOutputStream output) |
+ throws IOException { |
+ switch (WireFormat.getTagWireType(tag)) { |
+ case WireFormat.WIRETYPE_VARINT: { |
+ long value = readInt64(); |
+ output.writeRawVarint32(tag); |
+ output.writeUInt64NoTag(value); |
+ return true; |
+ } |
+ case WireFormat.WIRETYPE_FIXED64: { |
+ long value = readRawLittleEndian64(); |
+ output.writeRawVarint32(tag); |
+ output.writeFixed64NoTag(value); |
+ return true; |
+ } |
+ case WireFormat.WIRETYPE_LENGTH_DELIMITED: { |
+ ByteString value = readBytes(); |
+ output.writeRawVarint32(tag); |
+ output.writeBytesNoTag(value); |
+ return true; |
+ } |
+ case WireFormat.WIRETYPE_START_GROUP: { |
+ output.writeRawVarint32(tag); |
+ skipMessage(output); |
+ int endtag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), |
+ WireFormat.WIRETYPE_END_GROUP); |
+ checkLastTagWas(endtag); |
+ output.writeRawVarint32(endtag); |
+ return true; |
+ } |
+ case WireFormat.WIRETYPE_END_GROUP: { |
+ return false; |
+ } |
+ case WireFormat.WIRETYPE_FIXED32: { |
+ int value = readRawLittleEndian32(); |
+ output.writeRawVarint32(tag); |
+ output.writeFixed32NoTag(value); |
+ 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 an entire message. This will read either until EOF or until an endgroup tag, |
+ * Reads an entire message and writes it to output in wire format. |
+ * This will read either until EOF or until an endgroup tag, |
* whichever comes first. |
*/ |
- public abstract void skipMessage() throws IOException; |
+ public void skipMessage(CodedOutputStream output) throws IOException { |
+ while (true) { |
+ final int tag = readTag(); |
+ if (tag == 0 || !skipField(tag, output)) { |
+ return; |
+ } |
+ } |
+ } |
/** |
- * Reads an entire message and writes it to output in wire format. This will read either until EOF |
- * or until an endgroup tag, whichever comes first. |
+ * Collects the bytes skipped and returns the data in a ByteBuffer. |
*/ |
- public abstract void skipMessage(CodedOutputStream output) throws IOException; |
+ private class SkippedDataSink implements RefillCallback { |
+ private int lastPos = bufferPos; |
+ private ByteArrayOutputStream byteArrayStream; |
+ |
+ @Override |
+ public void onRefill() { |
+ if (byteArrayStream == null) { |
+ byteArrayStream = new ByteArrayOutputStream(); |
+ } |
+ byteArrayStream.write(buffer, lastPos, bufferPos - lastPos); |
+ lastPos = 0; |
+ } |
+ |
+ /** |
+ * Gets skipped data in a ByteBuffer. This method should only be |
+ * called once. |
+ */ |
+ ByteBuffer getSkippedData() { |
+ if (byteArrayStream == null) { |
+ return ByteBuffer.wrap(buffer, lastPos, bufferPos - lastPos); |
+ } else { |
+ byteArrayStream.write(buffer, lastPos, bufferPos); |
+ return ByteBuffer.wrap(byteArrayStream.toByteArray()); |
+ } |
+ } |
+ } |
// ----------------------------------------------------------------- |
/** Read a {@code double} field value from the stream. */ |
- public abstract double readDouble() throws IOException; |
+ public double readDouble() throws IOException { |
+ return Double.longBitsToDouble(readRawLittleEndian64()); |
+ } |
/** Read a {@code float} field value from the stream. */ |
- public abstract float readFloat() throws IOException; |
+ public float readFloat() throws IOException { |
+ return Float.intBitsToFloat(readRawLittleEndian32()); |
+ } |
/** Read a {@code uint64} field value from the stream. */ |
- public abstract long readUInt64() throws IOException; |
+ public long readUInt64() throws IOException { |
+ return readRawVarint64(); |
+ } |
/** Read an {@code int64} field value from the stream. */ |
- public abstract long readInt64() throws IOException; |
+ public long readInt64() throws IOException { |
+ return readRawVarint64(); |
+ } |
/** Read an {@code int32} field value from the stream. */ |
- public abstract int readInt32() throws IOException; |
+ public int readInt32() throws IOException { |
+ return readRawVarint32(); |
+ } |
/** Read a {@code fixed64} field value from the stream. */ |
- public abstract long readFixed64() throws IOException; |
+ public long readFixed64() throws IOException { |
+ return readRawLittleEndian64(); |
+ } |
/** Read a {@code fixed32} field value from the stream. */ |
- public abstract int readFixed32() throws IOException; |
+ public int readFixed32() throws IOException { |
+ return readRawLittleEndian32(); |
+ } |
/** Read a {@code bool} field value from the stream. */ |
- public abstract boolean readBool() throws IOException; |
+ public boolean readBool() throws IOException { |
+ return readRawVarint64() != 0; |
+ } |
/** |
- * Read a {@code string} field value from the stream. If the stream contains malformed UTF-8, |
+ * Read a {@code string} field value from the stream. |
+ * If the stream contains malformed UTF-8, |
* replace the offending bytes with the standard UTF-8 replacement character. |
*/ |
- public abstract String readString() throws IOException; |
+ 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, Internal.UTF_8); |
+ bufferPos += size; |
+ return result; |
+ } else if (size == 0) { |
+ return ""; |
+ } else if (size <= bufferSize) { |
+ refillBuffer(size); |
+ String result = new String(buffer, bufferPos, size, Internal.UTF_8); |
+ bufferPos += size; |
+ return result; |
+ } else { |
+ // Slow path: Build a byte array first then copy it. |
+ return new String(readRawBytesSlowPath(size), Internal.UTF_8); |
+ } |
+ } |
/** |
- * Read a {@code string} field value from the stream. If the stream contains malformed UTF-8, |
+ * Read a {@code string} field value from the stream. |
+ * If the stream contains malformed UTF-8, |
* throw exception {@link InvalidProtocolBufferException}. |
*/ |
- public abstract String readStringRequireUtf8() throws IOException; |
+ public String readStringRequireUtf8() throws IOException { |
+ final int size = readRawVarint32(); |
+ final byte[] bytes; |
+ final int oldPos = bufferPos; |
+ final int pos; |
+ if (size <= (bufferSize - oldPos) && size > 0) { |
+ // Fast path: We already have the bytes in a contiguous buffer, so |
+ // just copy directly from it. |
+ bytes = buffer; |
+ bufferPos = oldPos + size; |
+ pos = oldPos; |
+ } else if (size == 0) { |
+ return ""; |
+ } else if (size <= bufferSize) { |
+ refillBuffer(size); |
+ bytes = buffer; |
+ pos = 0; |
+ bufferPos = pos + size; |
+ } else { |
+ // Slow path: Build a byte array first then copy it. |
+ bytes = readRawBytesSlowPath(size); |
+ pos = 0; |
+ } |
+ // TODO(martinrb): We could save a pass by validating while decoding. |
+ if (!Utf8.isValidUtf8(bytes, pos, pos + size)) { |
+ throw InvalidProtocolBufferException.invalidUtf8(); |
+ } |
+ return new String(bytes, pos, size, Internal.UTF_8); |
+ } |
/** Read a {@code group} field value from the stream. */ |
- public abstract void readGroup( |
- final int fieldNumber, |
- final MessageLite.Builder builder, |
- final ExtensionRegistryLite extensionRegistry) |
- throws IOException; |
+ 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 abstract <T extends MessageLite> T readGroup( |
- final int fieldNumber, final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) |
- throws IOException; |
+ 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; |
+ } |
/** |
- * Reads a {@code group} field value from the stream and merges it into the given {@link |
- * UnknownFieldSet}. |
+ * 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 UnknownFieldSet.Builder now implements MessageLite.Builder, so |
+ * you can just call {@link #readGroup}. |
*/ |
@Deprecated |
- public abstract void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder) |
- throws IOException; |
+ 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 abstract void readMessage( |
- final MessageLite.Builder builder, final ExtensionRegistryLite extensionRegistry) |
- throws IOException; |
+ 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 abstract <T extends MessageLite> T readMessage( |
- final Parser<T> parser, final ExtensionRegistryLite extensionRegistry) throws IOException; |
+ 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; |
+ } |
/** Read a {@code bytes} field value from the stream. */ |
- public abstract ByteString readBytes() throws IOException; |
+ public ByteString readBytes() 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 ByteString result = bufferIsImmutable && enableAliasing |
+ ? ByteString.wrap(buffer, bufferPos, size) |
+ : ByteString.copyFrom(buffer, bufferPos, size); |
+ bufferPos += size; |
+ return result; |
+ } else if (size == 0) { |
+ return ByteString.EMPTY; |
+ } else { |
+ // Slow path: Build a byte array first then copy it. |
+ return ByteString.wrap(readRawBytesSlowPath(size)); |
+ } |
+ } |
/** Read a {@code bytes} field value from the stream. */ |
- public abstract byte[] readByteArray() throws IOException; |
+ public byte[] readByteArray() 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 byte[] result = |
+ Arrays.copyOfRange(buffer, bufferPos, bufferPos + size); |
+ bufferPos += size; |
+ return result; |
+ } else { |
+ // Slow path: Build a byte array first then copy it. |
+ return readRawBytesSlowPath(size); |
+ } |
+ } |
/** Read a {@code bytes} field value from the stream. */ |
- public abstract ByteBuffer readByteBuffer() throws IOException; |
+ public ByteBuffer readByteBuffer() throws IOException { |
+ final int size = readRawVarint32(); |
+ if (size <= (bufferSize - bufferPos) && size > 0) { |
+ // Fast path: We already have the bytes in a contiguous buffer. |
+ // When aliasing is enabled, we can return a ByteBuffer pointing directly |
+ // into the underlying byte array without copy if the CodedInputStream is |
+ // constructed from a byte array. If aliasing is disabled or the input is |
+ // from an InputStream or ByteString, we have to make a copy of the bytes. |
+ ByteBuffer result = input == null && !bufferIsImmutable && enableAliasing |
+ ? ByteBuffer.wrap(buffer, bufferPos, size).slice() |
+ : ByteBuffer.wrap(Arrays.copyOfRange( |
+ buffer, bufferPos, bufferPos + size)); |
+ bufferPos += size; |
+ return result; |
+ } else if (size == 0) { |
+ return Internal.EMPTY_BYTE_BUFFER; |
+ } else { |
+ // Slow path: Build a byte array first then copy it. |
+ return ByteBuffer.wrap(readRawBytesSlowPath(size)); |
+ } |
+ } |
/** Read a {@code uint32} field value from the stream. */ |
- public abstract int readUInt32() throws IOException; |
+ 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. |
+ * Read an enum field value from the stream. Caller is responsible |
+ * for converting the numeric value to an actual enum. |
*/ |
- public abstract int readEnum() throws IOException; |
+ public int readEnum() throws IOException { |
+ return readRawVarint32(); |
+ } |
/** Read an {@code sfixed32} field value from the stream. */ |
- public abstract int readSFixed32() throws IOException; |
+ public int readSFixed32() throws IOException { |
+ return readRawLittleEndian32(); |
+ } |
/** Read an {@code sfixed64} field value from the stream. */ |
- public abstract long readSFixed64() throws IOException; |
+ public long readSFixed64() throws IOException { |
+ return readRawLittleEndian64(); |
+ } |
/** Read an {@code sint32} field value from the stream. */ |
- public abstract int readSInt32() throws IOException; |
+ public int readSInt32() throws IOException { |
+ return decodeZigZag32(readRawVarint32()); |
+ } |
/** Read an {@code sint64} field value from the stream. */ |
- public abstract long readSInt64() throws IOException; |
+ 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 abstract int readRawVarint32() throws IOException; |
+ /** |
+ * Read a raw Varint from the stream. If larger than 32 bits, discard the |
+ * upper bits. |
+ */ |
+ public int readRawVarint32() throws IOException { |
+ // See implementation notes for readRawVarint64 |
+ fastpath: { |
+ int pos = bufferPos; |
+ |
+ if (bufferSize == pos) { |
+ break fastpath; |
+ } |
+ |
+ final byte[] buffer = this.buffer; |
+ int x; |
+ if ((x = buffer[pos++]) >= 0) { |
+ bufferPos = pos; |
+ return x; |
+ } else if (bufferSize - pos < 9) { |
+ break fastpath; |
+ } else if ((x ^= (buffer[pos++] << 7)) < 0) { |
+ x ^= (~0 << 7); |
+ } else if ((x ^= (buffer[pos++] << 14)) >= 0) { |
+ x ^= (~0 << 7) ^ (~0 << 14); |
+ } else if ((x ^= (buffer[pos++] << 21)) < 0) { |
+ x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); |
+ } else { |
+ int y = buffer[pos++]; |
+ x ^= y << 28; |
+ x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); |
+ if (y < 0 && |
+ buffer[pos++] < 0 && |
+ buffer[pos++] < 0 && |
+ buffer[pos++] < 0 && |
+ buffer[pos++] < 0 && |
+ buffer[pos++] < 0) { |
+ break fastpath; // Will throw malformedVarint() |
+ } |
+ } |
+ bufferPos = pos; |
+ return x; |
+ } |
+ return (int) readRawVarint64SlowPath(); |
+ } |
+ |
+ private void skipRawVarint() throws IOException { |
+ if (bufferSize - bufferPos >= 10) { |
+ final byte[] buffer = this.buffer; |
+ int pos = bufferPos; |
+ for (int i = 0; i < 10; i++) { |
+ if (buffer[pos++] >= 0) { |
+ bufferPos = pos; |
+ return; |
+ } |
+ } |
+ } |
+ skipRawVarintSlowPath(); |
+ } |
+ |
+ private void skipRawVarintSlowPath() throws IOException { |
+ for (int i = 0; i < 10; i++) { |
+ if (readRawByte() >= 0) { |
+ return; |
+ } |
+ } |
+ throw InvalidProtocolBufferException.malformedVarint(); |
+ } |
+ |
+ /** |
+ * 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 abstract long readRawVarint64() throws IOException; |
+ public long readRawVarint64() throws IOException { |
+ // Implementation notes: |
+ // |
+ // Optimized for one-byte values, expected to be common. |
+ // The particular code below was selected from various candidates |
+ // empirically, by winning VarintBenchmark. |
+ // |
+ // Sign extension of (signed) Java bytes is usually a nuisance, but |
+ // we exploit it here to more easily obtain the sign of bytes read. |
+ // Instead of cleaning up the sign extension bits by masking eagerly, |
+ // we delay until we find the final (positive) byte, when we clear all |
+ // accumulated bits with one xor. We depend on javac to constant fold. |
+ fastpath: { |
+ int pos = bufferPos; |
+ |
+ if (bufferSize == pos) { |
+ break fastpath; |
+ } |
+ |
+ final byte[] buffer = this.buffer; |
+ long x; |
+ int y; |
+ if ((y = buffer[pos++]) >= 0) { |
+ bufferPos = pos; |
+ return y; |
+ } else if (bufferSize - pos < 9) { |
+ break fastpath; |
+ } else if ((y ^= (buffer[pos++] << 7)) < 0) { |
+ x = y ^ (~0 << 7); |
+ } else if ((y ^= (buffer[pos++] << 14)) >= 0) { |
+ x = y ^ ((~0 << 7) ^ (~0 << 14)); |
+ } else if ((y ^= (buffer[pos++] << 21)) < 0) { |
+ x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); |
+ } else if ((x = ((long) y) ^ ((long) buffer[pos++] << 28)) >= 0L) { |
+ x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); |
+ } else if ((x ^= ((long) buffer[pos++] << 35)) < 0L) { |
+ x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); |
+ } else if ((x ^= ((long) buffer[pos++] << 42)) >= 0L) { |
+ x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); |
+ } else if ((x ^= ((long) buffer[pos++] << 49)) < 0L) { |
+ x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) |
+ ^ (~0L << 49); |
+ } else { |
+ x ^= ((long) buffer[pos++] << 56); |
+ x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) |
+ ^ (~0L << 49) ^ (~0L << 56); |
+ if (x < 0L) { |
+ if (buffer[pos++] < 0L) { |
+ break fastpath; // Will throw malformedVarint() |
+ } |
+ } |
+ } |
+ bufferPos = pos; |
+ return x; |
+ } |
+ return readRawVarint64SlowPath(); |
+ } |
/** Variant of readRawVarint64 for when uncomfortably close to the limit. */ |
/* Visible for testing */ |
- abstract long readRawVarint64SlowPath() throws IOException; |
+ long readRawVarint64SlowPath() throws IOException { |
+ long result = 0; |
+ for (int shift = 0; shift < 64; shift += 7) { |
+ final byte b = readRawByte(); |
+ result |= (long) (b & 0x7F) << shift; |
+ if ((b & 0x80) == 0) { |
+ return result; |
+ } |
+ } |
+ throw InvalidProtocolBufferException.malformedVarint(); |
+ } |
/** Read a 32-bit little-endian integer from the stream. */ |
- public abstract int readRawLittleEndian32() throws IOException; |
+ public int readRawLittleEndian32() throws IOException { |
+ int pos = bufferPos; |
+ |
+ // hand-inlined ensureAvailable(4); |
+ if (bufferSize - pos < 4) { |
+ refillBuffer(4); |
+ pos = bufferPos; |
+ } |
+ |
+ final byte[] buffer = this.buffer; |
+ bufferPos = pos + 4; |
+ return (((buffer[pos] & 0xff)) | |
+ ((buffer[pos + 1] & 0xff) << 8) | |
+ ((buffer[pos + 2] & 0xff) << 16) | |
+ ((buffer[pos + 3] & 0xff) << 24)); |
+ } |
/** Read a 64-bit little-endian integer from the stream. */ |
- public abstract long readRawLittleEndian64() throws IOException; |
+ public long readRawLittleEndian64() throws IOException { |
+ int pos = bufferPos; |
+ |
+ // hand-inlined ensureAvailable(8); |
+ if (bufferSize - pos < 8) { |
+ refillBuffer(8); |
+ pos = bufferPos; |
+ } |
+ |
+ final byte[] buffer = this.buffer; |
+ bufferPos = pos + 8; |
+ return ((((long) buffer[pos] & 0xffL)) | |
+ (((long) buffer[pos + 1] & 0xffL) << 8) | |
+ (((long) buffer[pos + 2] & 0xffL) << 16) | |
+ (((long) buffer[pos + 3] & 0xffL) << 24) | |
+ (((long) buffer[pos + 4] & 0xffL) << 32) | |
+ (((long) buffer[pos + 5] & 0xffL) << 40) | |
+ (((long) buffer[pos + 6] & 0xffL) << 48) | |
+ (((long) buffer[pos + 7] & 0xffL) << 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 final boolean bufferIsImmutable; |
+ private int bufferSize; |
+ private int bufferSizeAfterLimit; |
+ private int bufferPos; |
+ private final InputStream input; |
+ private int lastTag; |
+ private boolean enableAliasing = false; |
+ |
/** |
- * Enables {@link ByteString} aliasing of the underlying buffer, trading off on buffer pinning for |
- * data copies. Only valid for buffer-backed streams. |
+ * 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). |
*/ |
- public abstract void enableAliasing(boolean enabled); |
+ 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 = 100; |
+ 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, boolean bufferIsImmutable) { |
+ this.buffer = buffer; |
+ bufferSize = off + len; |
+ bufferPos = off; |
+ totalBytesRetired = -off; |
+ input = null; |
+ this.bufferIsImmutable = bufferIsImmutable; |
+ } |
+ |
+ private CodedInputStream(final InputStream input, int bufferSize) { |
+ buffer = new byte[bufferSize]; |
+ bufferSize = 0; |
+ bufferPos = 0; |
+ totalBytesRetired = 0; |
+ this.input = input; |
+ bufferIsImmutable = false; |
+ } |
+ |
+ public void enableAliasing(boolean enabled) { |
+ this.enableAliasing = enabled; |
+ } |
/** |
- * 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. |
+ * 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 final int setRecursionLimit(final int limit) { |
+ public int setRecursionLimit(final int limit) { |
if (limit < 0) { |
- throw new IllegalArgumentException("Recursion limit cannot be negative: " + limit); |
+ throw new IllegalArgumentException( |
+ "Recursion limit cannot be negative: " + limit); |
} |
final int oldLimit = recursionLimit; |
recursionLimit = limit; |
@@ -350,22 +929,25 @@ public abstract class CodedInputStream { |
} |
/** |
- * Only valid for {@link InputStream}-backed streams. |
- * |
- * <p>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. |
+ * 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 final int setSizeLimit(final int limit) { |
+ public int setSizeLimit(final int limit) { |
if (limit < 0) { |
- throw new IllegalArgumentException("Size limit cannot be negative: " + limit); |
+ throw new IllegalArgumentException( |
+ "Size limit cannot be negative: " + limit); |
} |
final int oldLimit = sizeLimit; |
sizeLimit = limit; |
@@ -373,2524 +955,348 @@ public abstract class CodedInputStream { |
} |
/** |
- * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). Only valid for {@link |
- * InputStream}-backed streams. |
+ * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). |
*/ |
- public abstract void resetSizeCounter(); |
+ public void resetSizeCounter() { |
+ totalBytesRetired = -bufferPos; |
+ } |
/** |
- * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This is called when |
- * descending into a length-delimited embedded message. |
+ * 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. |
+ * <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 abstract int pushLimit(int byteLimit) throws InvalidProtocolBufferException; |
+ 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 abstract void popLimit(final int oldLimit); |
+ 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. |
+ * Returns the number of bytes to be read before the current limit. |
+ * If no limit is set, returns -1. |
*/ |
- public abstract int getBytesUntilLimit(); |
+ 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)}. |
+ * 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 abstract boolean isAtEnd() throws IOException; |
+ public boolean isAtEnd() throws IOException { |
+ return bufferPos == bufferSize && !tryRefillBuffer(1); |
+ } |
/** |
- * The total bytes read up to the current position. Calling {@link #resetSizeCounter()} resets |
- * this value to zero. |
+ * The total bytes read up to the current position. Calling |
+ * {@link #resetSizeCounter()} resets this value to zero. |
*/ |
- public abstract int getTotalBytesRead(); |
+ public int getTotalBytesRead() { |
+ return totalBytesRetired + bufferPos; |
+ } |
- /** |
- * Read one byte from the input. |
- * |
- * @throws InvalidProtocolBufferException The end of the stream or the current limit was reached. |
- */ |
- public abstract byte readRawByte() throws IOException; |
- |
- /** |
- * Read a fixed size of bytes from the input. |
- * |
- * @throws InvalidProtocolBufferException The end of the stream or the current limit was reached. |
- */ |
- public abstract byte[] readRawBytes(final int size) throws IOException; |
- |
- /** |
- * Reads and discards {@code size} bytes. |
- * |
- * @throws InvalidProtocolBufferException The end of the stream or the current limit was reached. |
- */ |
- public abstract void skipRawBytes(final int size) throws IOException; |
- |
- /** |
- * 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); |
+ private interface RefillCallback { |
+ void onRefill(); |
} |
- /** |
- * 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 RefillCallback refillCallback = null; |
/** |
- * 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. |
+ * Reads more bytes from the input, making at least {@code n} bytes available |
+ * in the buffer. Caller must ensure that the requested space is not yet |
+ * available, and that the requested space is less than BUFFER_SIZE. |
+ * |
+ * @throws InvalidProtocolBufferException The end of the stream or the current |
+ * limit was reached. |
*/ |
- 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; |
- } |
+ private void refillBuffer(int n) throws IOException { |
+ if (!tryRefillBuffer(n)) { |
+ throw InvalidProtocolBufferException.truncatedMessage(); |
} |
- throw InvalidProtocolBufferException.malformedVarint(); |
} |
/** |
- * 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. |
+ * Tries to read more bytes from the input, making at least {@code n} bytes |
+ * available in the buffer. Caller must ensure that the requested space is |
+ * not yet available, and that the requested space is less than BUFFER_SIZE. |
+ * |
+ * @return {@code true} if the bytes could be made available; {@code false} |
+ * if the end of the stream or the current limit was reached. |
*/ |
- static int readRawVarint32(final InputStream input) throws IOException { |
- final int firstByte = input.read(); |
- if (firstByte == -1) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- return readRawVarint32(firstByte, input); |
- } |
- |
- /** A {@link CodedInputStream} implementation that uses a backing array as the input. */ |
- private static final class ArrayDecoder extends CodedInputStream { |
- private final byte[] buffer; |
- private final boolean immutable; |
- private int limit; |
- private int bufferSizeAfterLimit; |
- private int pos; |
- private int startPos; |
- private int lastTag; |
- private boolean enableAliasing; |
- |
- /** The absolute position of the end of the current message. */ |
- private int currentLimit = Integer.MAX_VALUE; |
- |
- private ArrayDecoder(final byte[] buffer, final int offset, final int len, boolean immutable) { |
- this.buffer = buffer; |
- limit = offset + len; |
- pos = offset; |
- startPos = pos; |
- this.immutable = immutable; |
- } |
- |
- @Override |
- 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; |
+ private boolean tryRefillBuffer(int n) throws IOException { |
+ if (bufferPos + n <= bufferSize) { |
+ throw new IllegalStateException( |
+ "refillBuffer() called when " + n + |
+ " bytes were already available in buffer"); |
} |
- @Override |
- public void checkLastTagWas(final int value) throws InvalidProtocolBufferException { |
- if (lastTag != value) { |
- throw InvalidProtocolBufferException.invalidEndTag(); |
- } |
- } |
- |
- @Override |
- public int getLastTag() { |
- return lastTag; |
- } |
- |
- @Override |
- public boolean skipField(final int tag) throws IOException { |
- switch (WireFormat.getTagWireType(tag)) { |
- case WireFormat.WIRETYPE_VARINT: |
- skipRawVarint(); |
- return true; |
- case WireFormat.WIRETYPE_FIXED64: |
- skipRawBytes(FIXED_64_SIZE); |
- 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: |
- skipRawBytes(FIXED_32_SIZE); |
- return true; |
- default: |
- throw InvalidProtocolBufferException.invalidWireType(); |
- } |
+ if (totalBytesRetired + bufferPos + n > currentLimit) { |
+ // Oops, we hit a limit. |
+ return false; |
} |
- @Override |
- public boolean skipField(final int tag, final CodedOutputStream output) throws IOException { |
- switch (WireFormat.getTagWireType(tag)) { |
- case WireFormat.WIRETYPE_VARINT: |
- { |
- long value = readInt64(); |
- output.writeRawVarint32(tag); |
- output.writeUInt64NoTag(value); |
- return true; |
- } |
- case WireFormat.WIRETYPE_FIXED64: |
- { |
- long value = readRawLittleEndian64(); |
- output.writeRawVarint32(tag); |
- output.writeFixed64NoTag(value); |
- return true; |
- } |
- case WireFormat.WIRETYPE_LENGTH_DELIMITED: |
- { |
- ByteString value = readBytes(); |
- output.writeRawVarint32(tag); |
- output.writeBytesNoTag(value); |
- return true; |
- } |
- case WireFormat.WIRETYPE_START_GROUP: |
- { |
- output.writeRawVarint32(tag); |
- skipMessage(output); |
- int endtag = |
- WireFormat.makeTag( |
- WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP); |
- checkLastTagWas(endtag); |
- output.writeRawVarint32(endtag); |
- return true; |
- } |
- case WireFormat.WIRETYPE_END_GROUP: |
- { |
- return false; |
- } |
- case WireFormat.WIRETYPE_FIXED32: |
- { |
- int value = readRawLittleEndian32(); |
- output.writeRawVarint32(tag); |
- output.writeFixed32NoTag(value); |
- return true; |
- } |
- default: |
- throw InvalidProtocolBufferException.invalidWireType(); |
- } |
+ if (refillCallback != null) { |
+ refillCallback.onRefill(); |
} |
- @Override |
- public void skipMessage() throws IOException { |
- while (true) { |
- final int tag = readTag(); |
- if (tag == 0 || !skipField(tag)) { |
- return; |
+ if (input != null) { |
+ int pos = bufferPos; |
+ if (pos > 0) { |
+ if (bufferSize > pos) { |
+ System.arraycopy(buffer, pos, buffer, 0, bufferSize - pos); |
} |
+ totalBytesRetired += pos; |
+ bufferSize -= pos; |
+ bufferPos = 0; |
} |
- } |
- @Override |
- public void skipMessage(CodedOutputStream output) throws IOException { |
- while (true) { |
- final int tag = readTag(); |
- if (tag == 0 || !skipField(tag, output)) { |
- return; |
+ int bytesRead = input.read(buffer, bufferSize, buffer.length - bufferSize); |
+ if (bytesRead == 0 || bytesRead < -1 || bytesRead > buffer.length) { |
+ throw new IllegalStateException( |
+ "InputStream#read(byte[]) returned invalid result: " + bytesRead + |
+ "\nThe InputStream implementation is buggy."); |
+ } |
+ if (bytesRead > 0) { |
+ bufferSize += bytesRead; |
+ // Integer-overflow-conscious check against sizeLimit |
+ if (totalBytesRetired + n - sizeLimit > 0) { |
+ throw InvalidProtocolBufferException.sizeLimitExceeded(); |
} |
+ recomputeBufferSizeAfterLimit(); |
+ return (bufferSize >= n) ? true : tryRefillBuffer(n); |
} |
} |
+ return false; |
+ } |
- // ----------------------------------------------------------------- |
- |
- @Override |
- public double readDouble() throws IOException { |
- return Double.longBitsToDouble(readRawLittleEndian64()); |
- } |
- |
- @Override |
- public float readFloat() throws IOException { |
- return Float.intBitsToFloat(readRawLittleEndian32()); |
- } |
- |
- @Override |
- public long readUInt64() throws IOException { |
- return readRawVarint64(); |
- } |
- |
- @Override |
- public long readInt64() throws IOException { |
- return readRawVarint64(); |
- } |
- |
- @Override |
- public int readInt32() throws IOException { |
- return readRawVarint32(); |
- } |
- |
- @Override |
- public long readFixed64() throws IOException { |
- return readRawLittleEndian64(); |
- } |
- |
- @Override |
- public int readFixed32() throws IOException { |
- return readRawLittleEndian32(); |
+ /** |
+ * 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(1); |
} |
+ return buffer[bufferPos++]; |
+ } |
- @Override |
- public boolean readBool() throws IOException { |
- return readRawVarint64() != 0; |
+ /** |
+ * 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 { |
+ final int pos = bufferPos; |
+ if (size <= (bufferSize - pos) && size > 0) { |
+ bufferPos = pos + size; |
+ return Arrays.copyOfRange(buffer, pos, pos + size); |
+ } else { |
+ return readRawBytesSlowPath(size); |
} |
+ } |
- @Override |
- public String readString() throws IOException { |
- final int size = readRawVarint32(); |
- if (size > 0 && size <= (limit - pos)) { |
- // Fast path: We already have the bytes in a contiguous buffer, so |
- // just copy directly from it. |
- final String result = new String(buffer, pos, size, UTF_8); |
- pos += size; |
- return result; |
- } |
- |
+ /** |
+ * Exactly like readRawBytes, but caller must have already checked the fast |
+ * path: (size <= (bufferSize - pos) && size > 0) |
+ */ |
+ private byte[] readRawBytesSlowPath(final int size) throws IOException { |
+ if (size <= 0) { |
if (size == 0) { |
- return ""; |
- } |
- if (size < 0) { |
+ return Internal.EMPTY_BYTE_ARRAY; |
+ } else { |
throw InvalidProtocolBufferException.negativeSize(); |
} |
- throw InvalidProtocolBufferException.truncatedMessage(); |
} |
- @Override |
- public String readStringRequireUtf8() throws IOException { |
- final int size = readRawVarint32(); |
- if (size > 0 && size <= (limit - pos)) { |
- // TODO(martinrb): We could save a pass by validating while decoding. |
- if (!Utf8.isValidUtf8(buffer, pos, pos + size)) { |
- throw InvalidProtocolBufferException.invalidUtf8(); |
- } |
- final int tempPos = pos; |
- pos += size; |
- return new String(buffer, tempPos, size, UTF_8); |
- } |
- |
- if (size == 0) { |
- return ""; |
- } |
- if (size <= 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- throw InvalidProtocolBufferException.truncatedMessage(); |
+ // Verify that the message size so far has not exceeded sizeLimit. |
+ int currentMessageSize = totalBytesRetired + bufferPos + size; |
+ if (currentMessageSize > sizeLimit) { |
+ throw InvalidProtocolBufferException.sizeLimitExceeded(); |
} |
- @Override |
- 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; |
+ // Verify that the message size so far has not exceeded currentLimit. |
+ if (currentMessageSize > currentLimit) { |
+ // Read to the end of the stream anyway. |
+ skipRawBytes(currentLimit - totalBytesRetired - bufferPos); |
+ throw InvalidProtocolBufferException.truncatedMessage(); |
} |
- |
- @Override |
- 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; |
+ // We need the input stream to proceed. |
+ if (input == null) { |
+ throw InvalidProtocolBufferException.truncatedMessage(); |
} |
- @Deprecated |
- @Override |
- public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder) |
- throws IOException { |
- readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry()); |
- } |
+ final int originalBufferPos = bufferPos; |
+ final int bufferedBytes = bufferSize - bufferPos; |
- @Override |
- 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); |
- } |
+ // Mark the current buffer consumed. |
+ totalBytesRetired += bufferSize; |
+ bufferPos = 0; |
+ bufferSize = 0; |
+ // Determine the number of bytes we need to read from the input stream. |
+ int sizeLeft = size - bufferedBytes; |
+ // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE. |
+ if (sizeLeft < BUFFER_SIZE || sizeLeft <= input.available()) { |
+ // Either the bytes we need are known to be available, or the required buffer is |
+ // within an allowed threshold - go ahead and allocate the buffer now. |
+ final byte[] bytes = new byte[size]; |
- @Override |
- 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; |
- } |
+ // Copy all of the buffered bytes to the result buffer. |
+ System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); |
- @Override |
- public ByteString readBytes() throws IOException { |
- final int size = readRawVarint32(); |
- if (size > 0 && size <= (limit - pos)) { |
- // Fast path: We already have the bytes in a contiguous buffer, so |
- // just copy directly from it. |
- final ByteString result = |
- immutable && enableAliasing |
- ? ByteString.wrap(buffer, pos, size) |
- : ByteString.copyFrom(buffer, pos, size); |
- pos += size; |
- return result; |
- } |
- if (size == 0) { |
- return ByteString.EMPTY; |
+ // Fill the remaining bytes from the input stream. |
+ int pos = bufferedBytes; |
+ while (pos < bytes.length) { |
+ int n = input.read(bytes, pos, size - pos); |
+ if (n == -1) { |
+ throw InvalidProtocolBufferException.truncatedMessage(); |
+ } |
+ totalBytesRetired += n; |
+ pos += n; |
} |
- // Slow path: Build a byte array first then copy it. |
- return ByteString.wrap(readRawBytes(size)); |
- } |
- @Override |
- public byte[] readByteArray() throws IOException { |
- final int size = readRawVarint32(); |
- return readRawBytes(size); |
+ return bytes; |
} |
- @Override |
- public ByteBuffer readByteBuffer() throws IOException { |
- final int size = readRawVarint32(); |
- if (size > 0 && size <= (limit - pos)) { |
- // Fast path: We already have the bytes in a contiguous buffer. |
- // When aliasing is enabled, we can return a ByteBuffer pointing directly |
- // into the underlying byte array without copy if the CodedInputStream is |
- // constructed from a byte array. If aliasing is disabled or the input is |
- // from an InputStream or ByteString, we have to make a copy of the bytes. |
- ByteBuffer result = |
- !immutable && enableAliasing |
- ? ByteBuffer.wrap(buffer, pos, size).slice() |
- : ByteBuffer.wrap(Arrays.copyOfRange(buffer, pos, pos + size)); |
- pos += size; |
- // TODO(nathanmittler): Investigate making the ByteBuffer be made read-only |
- return result; |
- } |
- |
- if (size == 0) { |
- return EMPTY_BYTE_BUFFER; |
- } |
- if (size < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
+ // 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. |
+ final List<byte[]> chunks = new ArrayList<byte[]>(); |
+ |
+ while (sizeLeft > 0) { |
+ // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE. |
+ final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)]; |
+ int pos = 0; |
+ while (pos < chunk.length) { |
+ final int n = input.read(chunk, pos, chunk.length - pos); |
+ if (n == -1) { |
+ throw InvalidProtocolBufferException.truncatedMessage(); |
+ } |
+ totalBytesRetired += n; |
+ pos += n; |
} |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- @Override |
- public int readUInt32() throws IOException { |
- return readRawVarint32(); |
- } |
- |
- @Override |
- public int readEnum() throws IOException { |
- return readRawVarint32(); |
- } |
- |
- @Override |
- public int readSFixed32() throws IOException { |
- return readRawLittleEndian32(); |
+ sizeLeft -= chunk.length; |
+ chunks.add(chunk); |
} |
- @Override |
- public long readSFixed64() throws IOException { |
- return readRawLittleEndian64(); |
- } |
+ // OK, got everything. Now concatenate it all into one buffer. |
+ final byte[] bytes = new byte[size]; |
- @Override |
- public int readSInt32() throws IOException { |
- return decodeZigZag32(readRawVarint32()); |
- } |
+ // Start by copying the leftover bytes from this.buffer. |
+ System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); |
- @Override |
- public long readSInt64() throws IOException { |
- return decodeZigZag64(readRawVarint64()); |
+ // And now all the chunks. |
+ int pos = bufferedBytes; |
+ for (final byte[] chunk : chunks) { |
+ System.arraycopy(chunk, 0, bytes, pos, chunk.length); |
+ pos += chunk.length; |
} |
- // ================================================================= |
- |
- @Override |
- public int readRawVarint32() throws IOException { |
- // See implementation notes for readRawVarint64 |
- fastpath: |
- { |
- int tempPos = pos; |
- |
- if (limit == tempPos) { |
- break fastpath; |
- } |
- |
- final byte[] buffer = this.buffer; |
- int x; |
- if ((x = buffer[tempPos++]) >= 0) { |
- pos = tempPos; |
- return x; |
- } else if (limit - tempPos < 9) { |
- break fastpath; |
- } else if ((x ^= (buffer[tempPos++] << 7)) < 0) { |
- x ^= (~0 << 7); |
- } else if ((x ^= (buffer[tempPos++] << 14)) >= 0) { |
- x ^= (~0 << 7) ^ (~0 << 14); |
- } else if ((x ^= (buffer[tempPos++] << 21)) < 0) { |
- x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); |
- } else { |
- int y = buffer[tempPos++]; |
- x ^= y << 28; |
- x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); |
- if (y < 0 |
- && buffer[tempPos++] < 0 |
- && buffer[tempPos++] < 0 |
- && buffer[tempPos++] < 0 |
- && buffer[tempPos++] < 0 |
- && buffer[tempPos++] < 0) { |
- break fastpath; // Will throw malformedVarint() |
- } |
- } |
- pos = tempPos; |
- return x; |
- } |
- return (int) readRawVarint64SlowPath(); |
- } |
+ // Done. |
+ return bytes; |
+ } |
- private void skipRawVarint() throws IOException { |
- if (limit - pos >= MAX_VARINT_SIZE) { |
- skipRawVarintFastPath(); |
- } else { |
- skipRawVarintSlowPath(); |
- } |
+ /** |
+ * 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 <= (bufferSize - bufferPos) && size >= 0) { |
+ // We have all the bytes we need already. |
+ bufferPos += size; |
+ } else { |
+ skipRawBytesSlowPath(size); |
} |
+ } |
- private void skipRawVarintFastPath() throws IOException { |
- for (int i = 0; i < MAX_VARINT_SIZE; i++) { |
- if (buffer[pos++] >= 0) { |
- return; |
- } |
- } |
- throw InvalidProtocolBufferException.malformedVarint(); |
+ /** |
+ * Exactly like skipRawBytes, but caller must have already checked the fast |
+ * path: (size <= (bufferSize - pos) && size >= 0) |
+ */ |
+ private void skipRawBytesSlowPath(final int size) throws IOException { |
+ if (size < 0) { |
+ throw InvalidProtocolBufferException.negativeSize(); |
} |
- private void skipRawVarintSlowPath() throws IOException { |
- for (int i = 0; i < MAX_VARINT_SIZE; i++) { |
- if (readRawByte() >= 0) { |
- return; |
- } |
- } |
- throw InvalidProtocolBufferException.malformedVarint(); |
+ if (totalBytesRetired + bufferPos + size > currentLimit) { |
+ // Read to the end of the stream anyway. |
+ skipRawBytes(currentLimit - totalBytesRetired - bufferPos); |
+ // Then fail. |
+ throw InvalidProtocolBufferException.truncatedMessage(); |
} |
- @Override |
- public long readRawVarint64() throws IOException { |
- // Implementation notes: |
- // |
- // Optimized for one-byte values, expected to be common. |
- // The particular code below was selected from various candidates |
- // empirically, by winning VarintBenchmark. |
- // |
- // Sign extension of (signed) Java bytes is usually a nuisance, but |
- // we exploit it here to more easily obtain the sign of bytes read. |
- // Instead of cleaning up the sign extension bits by masking eagerly, |
- // we delay until we find the final (positive) byte, when we clear all |
- // accumulated bits with one xor. We depend on javac to constant fold. |
- fastpath: |
- { |
- int tempPos = pos; |
- |
- if (limit == tempPos) { |
- break fastpath; |
- } |
- |
- final byte[] buffer = this.buffer; |
- long x; |
- int y; |
- if ((y = buffer[tempPos++]) >= 0) { |
- pos = tempPos; |
- return y; |
- } else if (limit - tempPos < 9) { |
- break fastpath; |
- } else if ((y ^= (buffer[tempPos++] << 7)) < 0) { |
- x = y ^ (~0 << 7); |
- } else if ((y ^= (buffer[tempPos++] << 14)) >= 0) { |
- x = y ^ ((~0 << 7) ^ (~0 << 14)); |
- } else if ((y ^= (buffer[tempPos++] << 21)) < 0) { |
- x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); |
- } else if ((x = y ^ ((long) buffer[tempPos++] << 28)) >= 0L) { |
- x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); |
- } else if ((x ^= ((long) buffer[tempPos++] << 35)) < 0L) { |
- x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); |
- } else if ((x ^= ((long) buffer[tempPos++] << 42)) >= 0L) { |
- x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); |
- } else if ((x ^= ((long) buffer[tempPos++] << 49)) < 0L) { |
- x ^= |
- (~0L << 7) |
- ^ (~0L << 14) |
- ^ (~0L << 21) |
- ^ (~0L << 28) |
- ^ (~0L << 35) |
- ^ (~0L << 42) |
- ^ (~0L << 49); |
- } else { |
- x ^= ((long) buffer[tempPos++] << 56); |
- x ^= |
- (~0L << 7) |
- ^ (~0L << 14) |
- ^ (~0L << 21) |
- ^ (~0L << 28) |
- ^ (~0L << 35) |
- ^ (~0L << 42) |
- ^ (~0L << 49) |
- ^ (~0L << 56); |
- if (x < 0L) { |
- if (buffer[tempPos++] < 0L) { |
- break fastpath; // Will throw malformedVarint() |
- } |
- } |
- } |
- pos = tempPos; |
- return x; |
- } |
- return readRawVarint64SlowPath(); |
- } |
+ // Skipping more bytes than are in the buffer. First skip what we have. |
+ int pos = bufferSize - bufferPos; |
+ bufferPos = bufferSize; |
- @Override |
- long readRawVarint64SlowPath() throws IOException { |
- long result = 0; |
- for (int shift = 0; shift < 64; shift += 7) { |
- final byte b = readRawByte(); |
- result |= (long) (b & 0x7F) << shift; |
- if ((b & 0x80) == 0) { |
- return result; |
- } |
- } |
- throw InvalidProtocolBufferException.malformedVarint(); |
+ // 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(1); |
+ while (size - pos > bufferSize) { |
+ pos += bufferSize; |
+ bufferPos = bufferSize; |
+ refillBuffer(1); |
} |
- @Override |
- public int readRawLittleEndian32() throws IOException { |
- int tempPos = pos; |
- |
- if (limit - tempPos < FIXED_32_SIZE) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- final byte[] buffer = this.buffer; |
- pos = tempPos + FIXED_32_SIZE; |
- return (((buffer[tempPos] & 0xff)) |
- | ((buffer[tempPos + 1] & 0xff) << 8) |
- | ((buffer[tempPos + 2] & 0xff) << 16) |
- | ((buffer[tempPos + 3] & 0xff) << 24)); |
- } |
- |
- @Override |
- public long readRawLittleEndian64() throws IOException { |
- int tempPos = pos; |
- |
- if (limit - tempPos < FIXED_64_SIZE) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- final byte[] buffer = this.buffer; |
- pos = tempPos + FIXED_64_SIZE; |
- return (((buffer[tempPos] & 0xffL)) |
- | ((buffer[tempPos + 1] & 0xffL) << 8) |
- | ((buffer[tempPos + 2] & 0xffL) << 16) |
- | ((buffer[tempPos + 3] & 0xffL) << 24) |
- | ((buffer[tempPos + 4] & 0xffL) << 32) |
- | ((buffer[tempPos + 5] & 0xffL) << 40) |
- | ((buffer[tempPos + 6] & 0xffL) << 48) |
- | ((buffer[tempPos + 7] & 0xffL) << 56)); |
- } |
- |
- @Override |
- public void enableAliasing(boolean enabled) { |
- this.enableAliasing = enabled; |
- } |
- |
- @Override |
- public void resetSizeCounter() { |
- startPos = pos; |
- } |
- |
- @Override |
- public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { |
- if (byteLimit < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- byteLimit += getTotalBytesRead(); |
- final int oldLimit = currentLimit; |
- if (byteLimit > oldLimit) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- currentLimit = byteLimit; |
- |
- recomputeBufferSizeAfterLimit(); |
- |
- return oldLimit; |
- } |
- |
- private void recomputeBufferSizeAfterLimit() { |
- limit += bufferSizeAfterLimit; |
- final int bufferEnd = limit - startPos; |
- if (bufferEnd > currentLimit) { |
- // Limit is in current buffer. |
- bufferSizeAfterLimit = bufferEnd - currentLimit; |
- limit -= bufferSizeAfterLimit; |
- } else { |
- bufferSizeAfterLimit = 0; |
- } |
- } |
- |
- @Override |
- public void popLimit(final int oldLimit) { |
- currentLimit = oldLimit; |
- recomputeBufferSizeAfterLimit(); |
- } |
- |
- @Override |
- public int getBytesUntilLimit() { |
- if (currentLimit == Integer.MAX_VALUE) { |
- return -1; |
- } |
- |
- return currentLimit - getTotalBytesRead(); |
- } |
- |
- @Override |
- public boolean isAtEnd() throws IOException { |
- return pos == limit; |
- } |
- |
- @Override |
- public int getTotalBytesRead() { |
- return pos - startPos; |
- } |
- |
- @Override |
- public byte readRawByte() throws IOException { |
- if (pos == limit) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- return buffer[pos++]; |
- } |
- |
- @Override |
- public byte[] readRawBytes(final int length) throws IOException { |
- if (length > 0 && length <= (limit - pos)) { |
- final int tempPos = pos; |
- pos += length; |
- return Arrays.copyOfRange(buffer, tempPos, pos); |
- } |
- |
- if (length <= 0) { |
- if (length == 0) { |
- return Internal.EMPTY_BYTE_ARRAY; |
- } else { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- } |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- @Override |
- public void skipRawBytes(final int length) throws IOException { |
- if (length >= 0 && length <= (limit - pos)) { |
- // We have all the bytes we need already. |
- pos += length; |
- return; |
- } |
- |
- if (length < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- } |
- |
- /** |
- * A {@link CodedInputStream} implementation that uses a backing direct ByteBuffer as the input. |
- * Requires the use of {@code sun.misc.Unsafe} to perform fast reads on the buffer. |
- */ |
- private static final class UnsafeDirectNioDecoder extends CodedInputStream { |
- /** The direct buffer that is backing this stream. */ |
- private final ByteBuffer buffer; |
- |
- /** |
- * If {@code true}, indicates that the buffer is backing a {@link ByteString} and is therefore |
- * considered to be an immutable input source. |
- */ |
- private final boolean immutable; |
- |
- /** The unsafe address of the content of {@link #buffer}. */ |
- private final long address; |
- |
- /** The unsafe address of the current read limit of the buffer. */ |
- private long limit; |
- |
- /** The unsafe address of the current read position of the buffer. */ |
- private long pos; |
- |
- /** The unsafe address of the starting read position. */ |
- private long startPos; |
- |
- /** The amount of available data in the buffer beyond {@link #limit}. */ |
- private int bufferSizeAfterLimit; |
- |
- /** The last tag that was read from this stream. */ |
- private int lastTag; |
- |
- /** |
- * If {@code true}, indicates that calls to read {@link ByteString} or {@code byte[]} |
- * <strong>may</strong> return slices of the underlying buffer, rather than copies. |
- */ |
- private boolean enableAliasing; |
- |
- /** The absolute position of the end of the current message. */ |
- private int currentLimit = Integer.MAX_VALUE; |
- |
- static boolean isSupported() { |
- return UnsafeUtil.hasUnsafeByteBufferOperations(); |
- } |
- |
- private UnsafeDirectNioDecoder(ByteBuffer buffer, boolean immutable) { |
- this.buffer = buffer; |
- address = UnsafeUtil.addressOffset(buffer); |
- limit = address + buffer.limit(); |
- pos = address + buffer.position(); |
- startPos = pos; |
- this.immutable = immutable; |
- } |
- |
- @Override |
- 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; |
- } |
- |
- @Override |
- public void checkLastTagWas(final int value) throws InvalidProtocolBufferException { |
- if (lastTag != value) { |
- throw InvalidProtocolBufferException.invalidEndTag(); |
- } |
- } |
- |
- @Override |
- public int getLastTag() { |
- return lastTag; |
- } |
- |
- @Override |
- public boolean skipField(final int tag) throws IOException { |
- switch (WireFormat.getTagWireType(tag)) { |
- case WireFormat.WIRETYPE_VARINT: |
- skipRawVarint(); |
- return true; |
- case WireFormat.WIRETYPE_FIXED64: |
- skipRawBytes(FIXED_64_SIZE); |
- 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: |
- skipRawBytes(FIXED_32_SIZE); |
- return true; |
- default: |
- throw InvalidProtocolBufferException.invalidWireType(); |
- } |
- } |
- |
- @Override |
- public boolean skipField(final int tag, final CodedOutputStream output) throws IOException { |
- switch (WireFormat.getTagWireType(tag)) { |
- case WireFormat.WIRETYPE_VARINT: |
- { |
- long value = readInt64(); |
- output.writeRawVarint32(tag); |
- output.writeUInt64NoTag(value); |
- return true; |
- } |
- case WireFormat.WIRETYPE_FIXED64: |
- { |
- long value = readRawLittleEndian64(); |
- output.writeRawVarint32(tag); |
- output.writeFixed64NoTag(value); |
- return true; |
- } |
- case WireFormat.WIRETYPE_LENGTH_DELIMITED: |
- { |
- ByteString value = readBytes(); |
- output.writeRawVarint32(tag); |
- output.writeBytesNoTag(value); |
- return true; |
- } |
- case WireFormat.WIRETYPE_START_GROUP: |
- { |
- output.writeRawVarint32(tag); |
- skipMessage(output); |
- int endtag = |
- WireFormat.makeTag( |
- WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP); |
- checkLastTagWas(endtag); |
- output.writeRawVarint32(endtag); |
- return true; |
- } |
- case WireFormat.WIRETYPE_END_GROUP: |
- { |
- return false; |
- } |
- case WireFormat.WIRETYPE_FIXED32: |
- { |
- int value = readRawLittleEndian32(); |
- output.writeRawVarint32(tag); |
- output.writeFixed32NoTag(value); |
- return true; |
- } |
- default: |
- throw InvalidProtocolBufferException.invalidWireType(); |
- } |
- } |
- |
- @Override |
- public void skipMessage() throws IOException { |
- while (true) { |
- final int tag = readTag(); |
- if (tag == 0 || !skipField(tag)) { |
- return; |
- } |
- } |
- } |
- |
- @Override |
- public void skipMessage(CodedOutputStream output) throws IOException { |
- while (true) { |
- final int tag = readTag(); |
- if (tag == 0 || !skipField(tag, output)) { |
- return; |
- } |
- } |
- } |
- |
- |
- // ----------------------------------------------------------------- |
- |
- @Override |
- public double readDouble() throws IOException { |
- return Double.longBitsToDouble(readRawLittleEndian64()); |
- } |
- |
- @Override |
- public float readFloat() throws IOException { |
- return Float.intBitsToFloat(readRawLittleEndian32()); |
- } |
- |
- @Override |
- public long readUInt64() throws IOException { |
- return readRawVarint64(); |
- } |
- |
- @Override |
- public long readInt64() throws IOException { |
- return readRawVarint64(); |
- } |
- |
- @Override |
- public int readInt32() throws IOException { |
- return readRawVarint32(); |
- } |
- |
- @Override |
- public long readFixed64() throws IOException { |
- return readRawLittleEndian64(); |
- } |
- |
- @Override |
- public int readFixed32() throws IOException { |
- return readRawLittleEndian32(); |
- } |
- |
- @Override |
- public boolean readBool() throws IOException { |
- return readRawVarint64() != 0; |
- } |
- |
- @Override |
- public String readString() throws IOException { |
- final int size = readRawVarint32(); |
- if (size > 0 && size <= remaining()) { |
- // TODO(nathanmittler): Is there a way to avoid this copy? |
- byte[] bytes = copyToArray(pos, pos + size); |
- String result = new String(bytes, UTF_8); |
- pos += size; |
- return result; |
- } |
- |
- if (size == 0) { |
- return ""; |
- } |
- if (size < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- @Override |
- public String readStringRequireUtf8() throws IOException { |
- final int size = readRawVarint32(); |
- if (size >= 0 && size <= remaining()) { |
- // TODO(nathanmittler): Is there a way to avoid this copy? |
- byte[] bytes = copyToArray(pos, pos + size); |
- // TODO(martinrb): We could save a pass by validating while decoding. |
- if (!Utf8.isValidUtf8(bytes)) { |
- throw InvalidProtocolBufferException.invalidUtf8(); |
- } |
- |
- String result = new String(bytes, UTF_8); |
- pos += size; |
- return result; |
- } |
- |
- if (size == 0) { |
- return ""; |
- } |
- if (size <= 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- @Override |
- 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; |
- } |
- |
- |
- @Override |
- 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; |
- } |
- |
- @Deprecated |
- @Override |
- public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder) |
- throws IOException { |
- readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry()); |
- } |
- |
- @Override |
- 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); |
- } |
- |
- |
- @Override |
- 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; |
- } |
- |
- @Override |
- public ByteString readBytes() throws IOException { |
- final int size = readRawVarint32(); |
- if (size > 0 && size <= remaining()) { |
- ByteBuffer result; |
- if (immutable && enableAliasing) { |
- result = slice(pos, pos + size); |
- } else { |
- result = copy(pos, pos + size); |
- } |
- pos += size; |
- return ByteString.wrap(result); |
- } |
- |
- if (size == 0) { |
- return ByteString.EMPTY; |
- } |
- if (size < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- @Override |
- public byte[] readByteArray() throws IOException { |
- return readRawBytes(readRawVarint32()); |
- } |
- |
- @Override |
- public ByteBuffer readByteBuffer() throws IOException { |
- final int size = readRawVarint32(); |
- if (size > 0 && size <= remaining()) { |
- ByteBuffer result; |
- // "Immutable" implies that buffer is backing a ByteString. |
- // Disallow slicing in this case to prevent the caller from modifying the contents |
- // of the ByteString. |
- if (!immutable && enableAliasing) { |
- result = slice(pos, pos + size); |
- } else { |
- result = copy(pos, pos + size); |
- } |
- pos += size; |
- // TODO(nathanmittler): Investigate making the ByteBuffer be made read-only |
- return result; |
- } |
- |
- if (size == 0) { |
- return EMPTY_BYTE_BUFFER; |
- } |
- if (size < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- @Override |
- public int readUInt32() throws IOException { |
- return readRawVarint32(); |
- } |
- |
- @Override |
- public int readEnum() throws IOException { |
- return readRawVarint32(); |
- } |
- |
- @Override |
- public int readSFixed32() throws IOException { |
- return readRawLittleEndian32(); |
- } |
- |
- @Override |
- public long readSFixed64() throws IOException { |
- return readRawLittleEndian64(); |
- } |
- |
- @Override |
- public int readSInt32() throws IOException { |
- return decodeZigZag32(readRawVarint32()); |
- } |
- |
- @Override |
- public long readSInt64() throws IOException { |
- return decodeZigZag64(readRawVarint64()); |
- } |
- |
- // ================================================================= |
- |
- @Override |
- public int readRawVarint32() throws IOException { |
- // See implementation notes for readRawVarint64 |
- fastpath: |
- { |
- long tempPos = pos; |
- |
- if (limit == tempPos) { |
- break fastpath; |
- } |
- |
- int x; |
- if ((x = UnsafeUtil.getByte(tempPos++)) >= 0) { |
- pos = tempPos; |
- return x; |
- } else if (limit - tempPos < 9) { |
- break fastpath; |
- } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) { |
- x ^= (~0 << 7); |
- } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) { |
- x ^= (~0 << 7) ^ (~0 << 14); |
- } else if ((x ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) { |
- x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); |
- } else { |
- int y = UnsafeUtil.getByte(tempPos++); |
- x ^= y << 28; |
- x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); |
- if (y < 0 |
- && UnsafeUtil.getByte(tempPos++) < 0 |
- && UnsafeUtil.getByte(tempPos++) < 0 |
- && UnsafeUtil.getByte(tempPos++) < 0 |
- && UnsafeUtil.getByte(tempPos++) < 0 |
- && UnsafeUtil.getByte(tempPos++) < 0) { |
- break fastpath; // Will throw malformedVarint() |
- } |
- } |
- pos = tempPos; |
- return x; |
- } |
- return (int) readRawVarint64SlowPath(); |
- } |
- |
- private void skipRawVarint() throws IOException { |
- if (remaining() >= MAX_VARINT_SIZE) { |
- skipRawVarintFastPath(); |
- } else { |
- skipRawVarintSlowPath(); |
- } |
- } |
- |
- private void skipRawVarintFastPath() throws IOException { |
- for (int i = 0; i < MAX_VARINT_SIZE; i++) { |
- if (UnsafeUtil.getByte(pos++) >= 0) { |
- return; |
- } |
- } |
- throw InvalidProtocolBufferException.malformedVarint(); |
- } |
- |
- private void skipRawVarintSlowPath() throws IOException { |
- for (int i = 0; i < MAX_VARINT_SIZE; i++) { |
- if (readRawByte() >= 0) { |
- return; |
- } |
- } |
- throw InvalidProtocolBufferException.malformedVarint(); |
- } |
- |
- @Override |
- public long readRawVarint64() throws IOException { |
- // Implementation notes: |
- // |
- // Optimized for one-byte values, expected to be common. |
- // The particular code below was selected from various candidates |
- // empirically, by winning VarintBenchmark. |
- // |
- // Sign extension of (signed) Java bytes is usually a nuisance, but |
- // we exploit it here to more easily obtain the sign of bytes read. |
- // Instead of cleaning up the sign extension bits by masking eagerly, |
- // we delay until we find the final (positive) byte, when we clear all |
- // accumulated bits with one xor. We depend on javac to constant fold. |
- fastpath: |
- { |
- long tempPos = pos; |
- |
- if (limit == tempPos) { |
- break fastpath; |
- } |
- |
- long x; |
- int y; |
- if ((y = UnsafeUtil.getByte(tempPos++)) >= 0) { |
- pos = tempPos; |
- return y; |
- } else if (limit - tempPos < 9) { |
- break fastpath; |
- } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 7)) < 0) { |
- x = y ^ (~0 << 7); |
- } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 14)) >= 0) { |
- x = y ^ ((~0 << 7) ^ (~0 << 14)); |
- } else if ((y ^= (UnsafeUtil.getByte(tempPos++) << 21)) < 0) { |
- x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); |
- } else if ((x = y ^ ((long) UnsafeUtil.getByte(tempPos++) << 28)) >= 0L) { |
- x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); |
- } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 35)) < 0L) { |
- x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); |
- } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 42)) >= 0L) { |
- x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); |
- } else if ((x ^= ((long) UnsafeUtil.getByte(tempPos++) << 49)) < 0L) { |
- x ^= |
- (~0L << 7) |
- ^ (~0L << 14) |
- ^ (~0L << 21) |
- ^ (~0L << 28) |
- ^ (~0L << 35) |
- ^ (~0L << 42) |
- ^ (~0L << 49); |
- } else { |
- x ^= ((long) UnsafeUtil.getByte(tempPos++) << 56); |
- x ^= |
- (~0L << 7) |
- ^ (~0L << 14) |
- ^ (~0L << 21) |
- ^ (~0L << 28) |
- ^ (~0L << 35) |
- ^ (~0L << 42) |
- ^ (~0L << 49) |
- ^ (~0L << 56); |
- if (x < 0L) { |
- if (UnsafeUtil.getByte(tempPos++) < 0L) { |
- break fastpath; // Will throw malformedVarint() |
- } |
- } |
- } |
- pos = tempPos; |
- return x; |
- } |
- return readRawVarint64SlowPath(); |
- } |
- |
- @Override |
- long readRawVarint64SlowPath() throws IOException { |
- long result = 0; |
- for (int shift = 0; shift < 64; shift += 7) { |
- final byte b = readRawByte(); |
- result |= (long) (b & 0x7F) << shift; |
- if ((b & 0x80) == 0) { |
- return result; |
- } |
- } |
- throw InvalidProtocolBufferException.malformedVarint(); |
- } |
- |
- @Override |
- public int readRawLittleEndian32() throws IOException { |
- long tempPos = pos; |
- |
- if (limit - tempPos < FIXED_32_SIZE) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- pos = tempPos + FIXED_32_SIZE; |
- return (((UnsafeUtil.getByte(tempPos) & 0xff)) |
- | ((UnsafeUtil.getByte(tempPos + 1) & 0xff) << 8) |
- | ((UnsafeUtil.getByte(tempPos + 2) & 0xff) << 16) |
- | ((UnsafeUtil.getByte(tempPos + 3) & 0xff) << 24)); |
- } |
- |
- @Override |
- public long readRawLittleEndian64() throws IOException { |
- long tempPos = pos; |
- |
- if (limit - tempPos < FIXED_64_SIZE) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- pos = tempPos + FIXED_64_SIZE; |
- return (((UnsafeUtil.getByte(tempPos) & 0xffL)) |
- | ((UnsafeUtil.getByte(tempPos + 1) & 0xffL) << 8) |
- | ((UnsafeUtil.getByte(tempPos + 2) & 0xffL) << 16) |
- | ((UnsafeUtil.getByte(tempPos + 3) & 0xffL) << 24) |
- | ((UnsafeUtil.getByte(tempPos + 4) & 0xffL) << 32) |
- | ((UnsafeUtil.getByte(tempPos + 5) & 0xffL) << 40) |
- | ((UnsafeUtil.getByte(tempPos + 6) & 0xffL) << 48) |
- | ((UnsafeUtil.getByte(tempPos + 7) & 0xffL) << 56)); |
- } |
- |
- @Override |
- public void enableAliasing(boolean enabled) { |
- this.enableAliasing = enabled; |
- } |
- |
- @Override |
- public void resetSizeCounter() { |
- startPos = pos; |
- } |
- |
- @Override |
- public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { |
- if (byteLimit < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- byteLimit += getTotalBytesRead(); |
- final int oldLimit = currentLimit; |
- if (byteLimit > oldLimit) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- currentLimit = byteLimit; |
- |
- recomputeBufferSizeAfterLimit(); |
- |
- return oldLimit; |
- } |
- |
- @Override |
- public void popLimit(final int oldLimit) { |
- currentLimit = oldLimit; |
- recomputeBufferSizeAfterLimit(); |
- } |
- |
- @Override |
- public int getBytesUntilLimit() { |
- if (currentLimit == Integer.MAX_VALUE) { |
- return -1; |
- } |
- |
- return currentLimit - getTotalBytesRead(); |
- } |
- |
- @Override |
- public boolean isAtEnd() throws IOException { |
- return pos == limit; |
- } |
- |
- @Override |
- public int getTotalBytesRead() { |
- return (int) (pos - startPos); |
- } |
- |
- @Override |
- public byte readRawByte() throws IOException { |
- if (pos == limit) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- return UnsafeUtil.getByte(pos++); |
- } |
- |
- @Override |
- public byte[] readRawBytes(final int length) throws IOException { |
- if (length >= 0 && length <= remaining()) { |
- byte[] bytes = new byte[length]; |
- slice(pos, pos + length).get(bytes); |
- pos += length; |
- return bytes; |
- } |
- |
- if (length <= 0) { |
- if (length == 0) { |
- return EMPTY_BYTE_ARRAY; |
- } else { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- } |
- |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- @Override |
- public void skipRawBytes(final int length) throws IOException { |
- if (length >= 0 && length <= remaining()) { |
- // We have all the bytes we need already. |
- pos += length; |
- return; |
- } |
- |
- if (length < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- private void recomputeBufferSizeAfterLimit() { |
- limit += bufferSizeAfterLimit; |
- final int bufferEnd = (int) (limit - startPos); |
- if (bufferEnd > currentLimit) { |
- // Limit is in current buffer. |
- bufferSizeAfterLimit = bufferEnd - currentLimit; |
- limit -= bufferSizeAfterLimit; |
- } else { |
- bufferSizeAfterLimit = 0; |
- } |
- } |
- |
- private int remaining() { |
- return (int) (limit - pos); |
- } |
- |
- private int bufferPos(long pos) { |
- return (int) (pos - address); |
- } |
- |
- private ByteBuffer slice(long begin, long end) throws IOException { |
- int prevPos = buffer.position(); |
- int prevLimit = buffer.limit(); |
- try { |
- buffer.position(bufferPos(begin)); |
- buffer.limit(bufferPos(end)); |
- return buffer.slice(); |
- } catch (IllegalArgumentException e) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } finally { |
- buffer.position(prevPos); |
- buffer.limit(prevLimit); |
- } |
- } |
- |
- private ByteBuffer copy(long begin, long end) throws IOException { |
- return ByteBuffer.wrap(copyToArray(begin, end)); |
- } |
- |
- private byte[] copyToArray(long begin, long end) throws IOException { |
- int prevPos = buffer.position(); |
- int prevLimit = buffer.limit(); |
- try { |
- buffer.position(bufferPos(begin)); |
- buffer.limit(bufferPos(end)); |
- byte[] bytes = new byte[(int) (end - begin)]; |
- buffer.get(bytes); |
- return bytes; |
- } catch (IllegalArgumentException e) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } finally { |
- buffer.position(prevPos); |
- buffer.limit(prevLimit); |
- } |
- } |
- } |
- |
- /** |
- * Implementation of {@link CodedInputStream} that uses an {@link InputStream} as the data source. |
- */ |
- private static final class StreamDecoder extends CodedInputStream { |
- private final InputStream input; |
- private final byte[] buffer; |
- /** bufferSize represents how many bytes are currently filled in the buffer */ |
- private int bufferSize; |
- |
- private int bufferSizeAfterLimit; |
- private int pos; |
- 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 + pos}. 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; |
- |
- private StreamDecoder(final InputStream input, int bufferSize) { |
- checkNotNull(input, "input"); |
- this.input = input; |
- this.buffer = new byte[bufferSize]; |
- this.bufferSize = 0; |
- pos = 0; |
- totalBytesRetired = 0; |
- } |
- |
- @Override |
- 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; |
- } |
- |
- @Override |
- public void checkLastTagWas(final int value) throws InvalidProtocolBufferException { |
- if (lastTag != value) { |
- throw InvalidProtocolBufferException.invalidEndTag(); |
- } |
- } |
- |
- @Override |
- public int getLastTag() { |
- return lastTag; |
- } |
- |
- @Override |
- public boolean skipField(final int tag) throws IOException { |
- switch (WireFormat.getTagWireType(tag)) { |
- case WireFormat.WIRETYPE_VARINT: |
- skipRawVarint(); |
- return true; |
- case WireFormat.WIRETYPE_FIXED64: |
- skipRawBytes(FIXED_64_SIZE); |
- 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: |
- skipRawBytes(FIXED_32_SIZE); |
- return true; |
- default: |
- throw InvalidProtocolBufferException.invalidWireType(); |
- } |
- } |
- |
- @Override |
- public boolean skipField(final int tag, final CodedOutputStream output) throws IOException { |
- switch (WireFormat.getTagWireType(tag)) { |
- case WireFormat.WIRETYPE_VARINT: |
- { |
- long value = readInt64(); |
- output.writeRawVarint32(tag); |
- output.writeUInt64NoTag(value); |
- return true; |
- } |
- case WireFormat.WIRETYPE_FIXED64: |
- { |
- long value = readRawLittleEndian64(); |
- output.writeRawVarint32(tag); |
- output.writeFixed64NoTag(value); |
- return true; |
- } |
- case WireFormat.WIRETYPE_LENGTH_DELIMITED: |
- { |
- ByteString value = readBytes(); |
- output.writeRawVarint32(tag); |
- output.writeBytesNoTag(value); |
- return true; |
- } |
- case WireFormat.WIRETYPE_START_GROUP: |
- { |
- output.writeRawVarint32(tag); |
- skipMessage(output); |
- int endtag = |
- WireFormat.makeTag( |
- WireFormat.getTagFieldNumber(tag), WireFormat.WIRETYPE_END_GROUP); |
- checkLastTagWas(endtag); |
- output.writeRawVarint32(endtag); |
- return true; |
- } |
- case WireFormat.WIRETYPE_END_GROUP: |
- { |
- return false; |
- } |
- case WireFormat.WIRETYPE_FIXED32: |
- { |
- int value = readRawLittleEndian32(); |
- output.writeRawVarint32(tag); |
- output.writeFixed32NoTag(value); |
- return true; |
- } |
- default: |
- throw InvalidProtocolBufferException.invalidWireType(); |
- } |
- } |
- |
- @Override |
- public void skipMessage() throws IOException { |
- while (true) { |
- final int tag = readTag(); |
- if (tag == 0 || !skipField(tag)) { |
- return; |
- } |
- } |
- } |
- |
- @Override |
- public void skipMessage(CodedOutputStream output) throws IOException { |
- while (true) { |
- final int tag = readTag(); |
- if (tag == 0 || !skipField(tag, output)) { |
- return; |
- } |
- } |
- } |
- |
- /** Collects the bytes skipped and returns the data in a ByteBuffer. */ |
- private class SkippedDataSink implements RefillCallback { |
- private int lastPos = pos; |
- private ByteArrayOutputStream byteArrayStream; |
- |
- @Override |
- public void onRefill() { |
- if (byteArrayStream == null) { |
- byteArrayStream = new ByteArrayOutputStream(); |
- } |
- byteArrayStream.write(buffer, lastPos, pos - lastPos); |
- lastPos = 0; |
- } |
- |
- /** Gets skipped data in a ByteBuffer. This method should only be called once. */ |
- ByteBuffer getSkippedData() { |
- if (byteArrayStream == null) { |
- return ByteBuffer.wrap(buffer, lastPos, pos - lastPos); |
- } else { |
- byteArrayStream.write(buffer, lastPos, pos); |
- return ByteBuffer.wrap(byteArrayStream.toByteArray()); |
- } |
- } |
- } |
- |
- |
- // ----------------------------------------------------------------- |
- |
- @Override |
- public double readDouble() throws IOException { |
- return Double.longBitsToDouble(readRawLittleEndian64()); |
- } |
- |
- @Override |
- public float readFloat() throws IOException { |
- return Float.intBitsToFloat(readRawLittleEndian32()); |
- } |
- |
- @Override |
- public long readUInt64() throws IOException { |
- return readRawVarint64(); |
- } |
- |
- @Override |
- public long readInt64() throws IOException { |
- return readRawVarint64(); |
- } |
- |
- @Override |
- public int readInt32() throws IOException { |
- return readRawVarint32(); |
- } |
- |
- @Override |
- public long readFixed64() throws IOException { |
- return readRawLittleEndian64(); |
- } |
- |
- @Override |
- public int readFixed32() throws IOException { |
- return readRawLittleEndian32(); |
- } |
- |
- @Override |
- public boolean readBool() throws IOException { |
- return readRawVarint64() != 0; |
- } |
- |
- @Override |
- public String readString() throws IOException { |
- final int size = readRawVarint32(); |
- if (size > 0 && size <= (bufferSize - pos)) { |
- // Fast path: We already have the bytes in a contiguous buffer, so |
- // just copy directly from it. |
- final String result = new String(buffer, pos, size, UTF_8); |
- pos += size; |
- return result; |
- } |
- if (size == 0) { |
- return ""; |
- } |
- if (size <= bufferSize) { |
- refillBuffer(size); |
- String result = new String(buffer, pos, size, UTF_8); |
- pos += size; |
- return result; |
- } |
- // Slow path: Build a byte array first then copy it. |
- return new String(readRawBytesSlowPath(size), UTF_8); |
- } |
- |
- @Override |
- public String readStringRequireUtf8() throws IOException { |
- final int size = readRawVarint32(); |
- final byte[] bytes; |
- final int oldPos = pos; |
- final int tempPos; |
- if (size <= (bufferSize - oldPos) && size > 0) { |
- // Fast path: We already have the bytes in a contiguous buffer, so |
- // just copy directly from it. |
- bytes = buffer; |
- pos = oldPos + size; |
- tempPos = oldPos; |
- } else if (size == 0) { |
- return ""; |
- } else if (size <= bufferSize) { |
- refillBuffer(size); |
- bytes = buffer; |
- tempPos = 0; |
- pos = tempPos + size; |
- } else { |
- // Slow path: Build a byte array first then copy it. |
- bytes = readRawBytesSlowPath(size); |
- tempPos = 0; |
- } |
- // TODO(martinrb): We could save a pass by validating while decoding. |
- if (!Utf8.isValidUtf8(bytes, tempPos, tempPos + size)) { |
- throw InvalidProtocolBufferException.invalidUtf8(); |
- } |
- return new String(bytes, tempPos, size, UTF_8); |
- } |
- |
- @Override |
- 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; |
- } |
- |
- |
- @Override |
- 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; |
- } |
- |
- @Deprecated |
- @Override |
- public void readUnknownGroup(final int fieldNumber, final MessageLite.Builder builder) |
- throws IOException { |
- readGroup(fieldNumber, builder, ExtensionRegistryLite.getEmptyRegistry()); |
- } |
- |
- @Override |
- 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); |
- } |
- |
- |
- @Override |
- 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; |
- } |
- |
- @Override |
- public ByteString readBytes() throws IOException { |
- final int size = readRawVarint32(); |
- if (size <= (bufferSize - pos) && 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, pos, size); |
- pos += size; |
- return result; |
- } |
- if (size == 0) { |
- return ByteString.EMPTY; |
- } |
- // Slow path: Build a byte array first then copy it. |
- return ByteString.wrap(readRawBytesSlowPath(size)); |
- } |
- |
- @Override |
- public byte[] readByteArray() throws IOException { |
- final int size = readRawVarint32(); |
- if (size <= (bufferSize - pos) && size > 0) { |
- // Fast path: We already have the bytes in a contiguous buffer, so |
- // just copy directly from it. |
- final byte[] result = Arrays.copyOfRange(buffer, pos, pos + size); |
- pos += size; |
- return result; |
- } else { |
- // Slow path: Build a byte array first then copy it. |
- return readRawBytesSlowPath(size); |
- } |
- } |
- |
- @Override |
- public ByteBuffer readByteBuffer() throws IOException { |
- final int size = readRawVarint32(); |
- if (size <= (bufferSize - pos) && size > 0) { |
- // Fast path: We already have the bytes in a contiguous buffer. |
- ByteBuffer result = ByteBuffer.wrap(Arrays.copyOfRange(buffer, pos, pos + size)); |
- pos += size; |
- return result; |
- } |
- if (size == 0) { |
- return Internal.EMPTY_BYTE_BUFFER; |
- } |
- // Slow path: Build a byte array first then copy it. |
- return ByteBuffer.wrap(readRawBytesSlowPath(size)); |
- } |
- |
- @Override |
- public int readUInt32() throws IOException { |
- return readRawVarint32(); |
- } |
- |
- @Override |
- public int readEnum() throws IOException { |
- return readRawVarint32(); |
- } |
- |
- @Override |
- public int readSFixed32() throws IOException { |
- return readRawLittleEndian32(); |
- } |
- |
- @Override |
- public long readSFixed64() throws IOException { |
- return readRawLittleEndian64(); |
- } |
- |
- @Override |
- public int readSInt32() throws IOException { |
- return decodeZigZag32(readRawVarint32()); |
- } |
- |
- @Override |
- public long readSInt64() throws IOException { |
- return decodeZigZag64(readRawVarint64()); |
- } |
- |
- // ================================================================= |
- |
- @Override |
- public int readRawVarint32() throws IOException { |
- // See implementation notes for readRawVarint64 |
- fastpath: |
- { |
- int tempPos = pos; |
- |
- if (bufferSize == tempPos) { |
- break fastpath; |
- } |
- |
- final byte[] buffer = this.buffer; |
- int x; |
- if ((x = buffer[tempPos++]) >= 0) { |
- pos = tempPos; |
- return x; |
- } else if (bufferSize - tempPos < 9) { |
- break fastpath; |
- } else if ((x ^= (buffer[tempPos++] << 7)) < 0) { |
- x ^= (~0 << 7); |
- } else if ((x ^= (buffer[tempPos++] << 14)) >= 0) { |
- x ^= (~0 << 7) ^ (~0 << 14); |
- } else if ((x ^= (buffer[tempPos++] << 21)) < 0) { |
- x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); |
- } else { |
- int y = buffer[tempPos++]; |
- x ^= y << 28; |
- x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); |
- if (y < 0 |
- && buffer[tempPos++] < 0 |
- && buffer[tempPos++] < 0 |
- && buffer[tempPos++] < 0 |
- && buffer[tempPos++] < 0 |
- && buffer[tempPos++] < 0) { |
- break fastpath; // Will throw malformedVarint() |
- } |
- } |
- pos = tempPos; |
- return x; |
- } |
- return (int) readRawVarint64SlowPath(); |
- } |
- |
- private void skipRawVarint() throws IOException { |
- if (bufferSize - pos >= MAX_VARINT_SIZE) { |
- skipRawVarintFastPath(); |
- } else { |
- skipRawVarintSlowPath(); |
- } |
- } |
- |
- private void skipRawVarintFastPath() throws IOException { |
- for (int i = 0; i < MAX_VARINT_SIZE; i++) { |
- if (buffer[pos++] >= 0) { |
- return; |
- } |
- } |
- throw InvalidProtocolBufferException.malformedVarint(); |
- } |
- |
- private void skipRawVarintSlowPath() throws IOException { |
- for (int i = 0; i < MAX_VARINT_SIZE; i++) { |
- if (readRawByte() >= 0) { |
- return; |
- } |
- } |
- throw InvalidProtocolBufferException.malformedVarint(); |
- } |
- |
- @Override |
- public long readRawVarint64() throws IOException { |
- // Implementation notes: |
- // |
- // Optimized for one-byte values, expected to be common. |
- // The particular code below was selected from various candidates |
- // empirically, by winning VarintBenchmark. |
- // |
- // Sign extension of (signed) Java bytes is usually a nuisance, but |
- // we exploit it here to more easily obtain the sign of bytes read. |
- // Instead of cleaning up the sign extension bits by masking eagerly, |
- // we delay until we find the final (positive) byte, when we clear all |
- // accumulated bits with one xor. We depend on javac to constant fold. |
- fastpath: |
- { |
- int tempPos = pos; |
- |
- if (bufferSize == tempPos) { |
- break fastpath; |
- } |
- |
- final byte[] buffer = this.buffer; |
- long x; |
- int y; |
- if ((y = buffer[tempPos++]) >= 0) { |
- pos = tempPos; |
- return y; |
- } else if (bufferSize - tempPos < 9) { |
- break fastpath; |
- } else if ((y ^= (buffer[tempPos++] << 7)) < 0) { |
- x = y ^ (~0 << 7); |
- } else if ((y ^= (buffer[tempPos++] << 14)) >= 0) { |
- x = y ^ ((~0 << 7) ^ (~0 << 14)); |
- } else if ((y ^= (buffer[tempPos++] << 21)) < 0) { |
- x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21)); |
- } else if ((x = y ^ ((long) buffer[tempPos++] << 28)) >= 0L) { |
- x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); |
- } else if ((x ^= ((long) buffer[tempPos++] << 35)) < 0L) { |
- x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); |
- } else if ((x ^= ((long) buffer[tempPos++] << 42)) >= 0L) { |
- x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); |
- } else if ((x ^= ((long) buffer[tempPos++] << 49)) < 0L) { |
- x ^= |
- (~0L << 7) |
- ^ (~0L << 14) |
- ^ (~0L << 21) |
- ^ (~0L << 28) |
- ^ (~0L << 35) |
- ^ (~0L << 42) |
- ^ (~0L << 49); |
- } else { |
- x ^= ((long) buffer[tempPos++] << 56); |
- x ^= |
- (~0L << 7) |
- ^ (~0L << 14) |
- ^ (~0L << 21) |
- ^ (~0L << 28) |
- ^ (~0L << 35) |
- ^ (~0L << 42) |
- ^ (~0L << 49) |
- ^ (~0L << 56); |
- if (x < 0L) { |
- if (buffer[tempPos++] < 0L) { |
- break fastpath; // Will throw malformedVarint() |
- } |
- } |
- } |
- pos = tempPos; |
- return x; |
- } |
- return readRawVarint64SlowPath(); |
- } |
- |
- @Override |
- long readRawVarint64SlowPath() throws IOException { |
- long result = 0; |
- for (int shift = 0; shift < 64; shift += 7) { |
- final byte b = readRawByte(); |
- result |= (long) (b & 0x7F) << shift; |
- if ((b & 0x80) == 0) { |
- return result; |
- } |
- } |
- throw InvalidProtocolBufferException.malformedVarint(); |
- } |
- |
- @Override |
- public int readRawLittleEndian32() throws IOException { |
- int tempPos = pos; |
- |
- if (bufferSize - tempPos < FIXED_32_SIZE) { |
- refillBuffer(FIXED_32_SIZE); |
- tempPos = pos; |
- } |
- |
- final byte[] buffer = this.buffer; |
- pos = tempPos + FIXED_32_SIZE; |
- return (((buffer[tempPos] & 0xff)) |
- | ((buffer[tempPos + 1] & 0xff) << 8) |
- | ((buffer[tempPos + 2] & 0xff) << 16) |
- | ((buffer[tempPos + 3] & 0xff) << 24)); |
- } |
- |
- @Override |
- public long readRawLittleEndian64() throws IOException { |
- int tempPos = pos; |
- |
- if (bufferSize - tempPos < FIXED_64_SIZE) { |
- refillBuffer(FIXED_64_SIZE); |
- tempPos = pos; |
- } |
- |
- final byte[] buffer = this.buffer; |
- pos = tempPos + FIXED_64_SIZE; |
- return (((buffer[tempPos] & 0xffL)) |
- | ((buffer[tempPos + 1] & 0xffL) << 8) |
- | ((buffer[tempPos + 2] & 0xffL) << 16) |
- | ((buffer[tempPos + 3] & 0xffL) << 24) |
- | ((buffer[tempPos + 4] & 0xffL) << 32) |
- | ((buffer[tempPos + 5] & 0xffL) << 40) |
- | ((buffer[tempPos + 6] & 0xffL) << 48) |
- | ((buffer[tempPos + 7] & 0xffL) << 56)); |
- } |
- |
- // ----------------------------------------------------------------- |
- |
- @Override |
- public void enableAliasing(boolean enabled) { |
- // TODO(nathanmittler): Ideally we should throw here. Do nothing for backward compatibility. |
- } |
- |
- @Override |
- public void resetSizeCounter() { |
- totalBytesRetired = -pos; |
- } |
- |
- @Override |
- public int pushLimit(int byteLimit) throws InvalidProtocolBufferException { |
- if (byteLimit < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- byteLimit += totalBytesRetired + pos; |
- 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; |
- } |
- } |
- |
- @Override |
- public void popLimit(final int oldLimit) { |
- currentLimit = oldLimit; |
- recomputeBufferSizeAfterLimit(); |
- } |
- |
- @Override |
- public int getBytesUntilLimit() { |
- if (currentLimit == Integer.MAX_VALUE) { |
- return -1; |
- } |
- |
- final int currentAbsolutePosition = totalBytesRetired + pos; |
- return currentLimit - currentAbsolutePosition; |
- } |
- |
- @Override |
- public boolean isAtEnd() throws IOException { |
- return pos == bufferSize && !tryRefillBuffer(1); |
- } |
- |
- @Override |
- public int getTotalBytesRead() { |
- return totalBytesRetired + pos; |
- } |
- |
- private interface RefillCallback { |
- void onRefill(); |
- } |
- |
- private RefillCallback refillCallback = null; |
- |
- /** |
- * Reads more bytes from the input, making at least {@code n} bytes available in the buffer. |
- * Caller must ensure that the requested space is not yet available, and that the requested |
- * space is less than BUFFER_SIZE. |
- * |
- * @throws InvalidProtocolBufferException The end of the stream or the current limit was |
- * reached. |
- */ |
- private void refillBuffer(int n) throws IOException { |
- if (!tryRefillBuffer(n)) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- } |
- |
- /** |
- * Tries to read more bytes from the input, making at least {@code n} bytes available in the |
- * buffer. Caller must ensure that the requested space is not yet available, and that the |
- * requested space is less than BUFFER_SIZE. |
- * |
- * @return {@code true} if the bytes could be made available; {@code false} if the end of the |
- * stream or the current limit was reached. |
- */ |
- private boolean tryRefillBuffer(int n) throws IOException { |
- if (pos + n <= bufferSize) { |
- throw new IllegalStateException( |
- "refillBuffer() called when " + n + " bytes were already available in buffer"); |
- } |
- |
- if (totalBytesRetired + pos + n > currentLimit) { |
- // Oops, we hit a limit. |
- return false; |
- } |
- |
- if (refillCallback != null) { |
- refillCallback.onRefill(); |
- } |
- |
- int tempPos = pos; |
- if (tempPos > 0) { |
- if (bufferSize > tempPos) { |
- System.arraycopy(buffer, tempPos, buffer, 0, bufferSize - tempPos); |
- } |
- totalBytesRetired += tempPos; |
- bufferSize -= tempPos; |
- pos = 0; |
- } |
- |
- int bytesRead = input.read(buffer, bufferSize, buffer.length - bufferSize); |
- if (bytesRead == 0 || bytesRead < -1 || bytesRead > buffer.length) { |
- throw new IllegalStateException( |
- "InputStream#read(byte[]) returned invalid result: " |
- + bytesRead |
- + "\nThe InputStream implementation is buggy."); |
- } |
- if (bytesRead > 0) { |
- bufferSize += bytesRead; |
- // Integer-overflow-conscious check against sizeLimit |
- if (totalBytesRetired + n - sizeLimit > 0) { |
- throw InvalidProtocolBufferException.sizeLimitExceeded(); |
- } |
- recomputeBufferSizeAfterLimit(); |
- return (bufferSize >= n) ? true : tryRefillBuffer(n); |
- } |
- |
- return false; |
- } |
- |
- @Override |
- public byte readRawByte() throws IOException { |
- if (pos == bufferSize) { |
- refillBuffer(1); |
- } |
- return buffer[pos++]; |
- } |
- |
- @Override |
- public byte[] readRawBytes(final int size) throws IOException { |
- final int tempPos = pos; |
- if (size <= (bufferSize - tempPos) && size > 0) { |
- pos = tempPos + size; |
- return Arrays.copyOfRange(buffer, tempPos, tempPos + size); |
- } else { |
- return readRawBytesSlowPath(size); |
- } |
- } |
- |
- /** |
- * Exactly like readRawBytes, but caller must have already checked the fast path: (size <= |
- * (bufferSize - pos) && size > 0) |
- */ |
- private byte[] readRawBytesSlowPath(final int size) throws IOException { |
- if (size == 0) { |
- return Internal.EMPTY_BYTE_ARRAY; |
- } |
- if (size < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- |
- // Integer-overflow-conscious check that the message size so far has not exceeded sizeLimit. |
- int currentMessageSize = totalBytesRetired + pos + size; |
- if (currentMessageSize - sizeLimit > 0) { |
- throw InvalidProtocolBufferException.sizeLimitExceeded(); |
- } |
- |
- // Verify that the message size so far has not exceeded currentLimit. |
- if (currentMessageSize > currentLimit) { |
- // Read to the end of the stream anyway. |
- skipRawBytes(currentLimit - totalBytesRetired - pos); |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- final int originalBufferPos = pos; |
- final int bufferedBytes = bufferSize - pos; |
- |
- // Mark the current buffer consumed. |
- totalBytesRetired += bufferSize; |
- pos = 0; |
- bufferSize = 0; |
- |
- // Determine the number of bytes we need to read from the input stream. |
- int sizeLeft = size - bufferedBytes; |
- // TODO(nathanmittler): Consider using a value larger than DEFAULT_BUFFER_SIZE. |
- if (sizeLeft < DEFAULT_BUFFER_SIZE || sizeLeft <= input.available()) { |
- // Either the bytes we need are known to be available, or the required buffer is |
- // within an allowed threshold - go ahead and allocate the buffer now. |
- final byte[] bytes = new byte[size]; |
- |
- // Copy all of the buffered bytes to the result buffer. |
- System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); |
- |
- // Fill the remaining bytes from the input stream. |
- int tempPos = bufferedBytes; |
- while (tempPos < bytes.length) { |
- int n = input.read(bytes, tempPos, size - tempPos); |
- if (n == -1) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- totalBytesRetired += n; |
- tempPos += n; |
- } |
- |
- return bytes; |
- } |
- |
- // 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. |
- final List<byte[]> chunks = new ArrayList<byte[]>(); |
- |
- while (sizeLeft > 0) { |
- // TODO(nathanmittler): Consider using a value larger than DEFAULT_BUFFER_SIZE. |
- final byte[] chunk = new byte[Math.min(sizeLeft, DEFAULT_BUFFER_SIZE)]; |
- int tempPos = 0; |
- while (tempPos < chunk.length) { |
- final int n = input.read(chunk, tempPos, chunk.length - tempPos); |
- if (n == -1) { |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- totalBytesRetired += n; |
- tempPos += 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. |
- System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes); |
- |
- // And now all the chunks. |
- int tempPos = bufferedBytes; |
- for (final byte[] chunk : chunks) { |
- System.arraycopy(chunk, 0, bytes, tempPos, chunk.length); |
- tempPos += chunk.length; |
- } |
- |
- // Done. |
- return bytes; |
- } |
- |
- @Override |
- public void skipRawBytes(final int size) throws IOException { |
- if (size <= (bufferSize - pos) && size >= 0) { |
- // We have all the bytes we need already. |
- pos += size; |
- } else { |
- skipRawBytesSlowPath(size); |
- } |
- } |
- |
- /** |
- * Exactly like skipRawBytes, but caller must have already checked the fast path: (size <= |
- * (bufferSize - pos) && size >= 0) |
- */ |
- private void skipRawBytesSlowPath(final int size) throws IOException { |
- if (size < 0) { |
- throw InvalidProtocolBufferException.negativeSize(); |
- } |
- |
- if (totalBytesRetired + pos + size > currentLimit) { |
- // Read to the end of the stream anyway. |
- skipRawBytes(currentLimit - totalBytesRetired - pos); |
- // Then fail. |
- throw InvalidProtocolBufferException.truncatedMessage(); |
- } |
- |
- // Skipping more bytes than are in the buffer. First skip what we have. |
- int tempPos = bufferSize - pos; |
- pos = 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(1); |
- while (size - tempPos > bufferSize) { |
- tempPos += bufferSize; |
- pos = bufferSize; |
- refillBuffer(1); |
- } |
- |
- pos = size - tempPos; |
- } |
+ bufferPos = size - pos; |
} |
} |