protobuf-lite: ProtoLiteUtils fix infinite loop
InputStream by contract can return zero if requested length equal to zero.
```
If len is zero, then no bytes are read and 0 is returned;
otherwise, there is an attempt to read at least one byte.
If no byte is available because the stream is at end of file,
the value -1 is returned; otherwise, at least one byte is read
and stored into b.
```
Close #3323
diff --git a/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
index 8914201..2a88a3f 100644
--- a/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
+++ b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
@@ -128,12 +128,19 @@
buf = new byte[size];
bufs.set(new WeakReference<byte[]>(buf));
}
- int chunkSize;
- int position = 0;
- while ((chunkSize = stream.read(buf, position, size - position)) != -1) {
- position += chunkSize;
+
+ int remaining = size;
+ while (remaining > 0) {
+ int position = size - remaining;
+ int count = stream.read(buf, position, remaining);
+ if (count == -1) {
+ break;
+ }
+ remaining -= count;
}
- if (size != position) {
+
+ if (remaining != 0) {
+ int position = size - remaining;
throw new RuntimeException("size inaccurate: " + size + " != " + position);
}
cis = CodedInputStream.newInstance(buf, 0, size);
diff --git a/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java b/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java
index d47e089..c4a4d32 100644
--- a/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java
+++ b/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java
@@ -29,6 +29,7 @@
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Type;
import io.grpc.Drainable;
+import io.grpc.KnownLength;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor.Marshaller;
import io.grpc.MethodDescriptor.PrototypeMarshaller;
@@ -215,4 +216,36 @@
ProtoLiteUtils.setExtensionRegistry(null);
}
+
+ @Test
+ public void parseFromKnowLengthInputStream() throws Exception {
+ Marshaller<Type> marshaller = ProtoLiteUtils.marshaller(Type.getDefaultInstance());
+ Type expect = Type.newBuilder().setName("expected name").build();
+
+ Type result = marshaller.parse(new CustomKnownLengthInputStream(expect.toByteArray()));
+ assertEquals(expect, result);
+ }
+
+ private static class CustomKnownLengthInputStream extends InputStream implements KnownLength {
+ private int position = 0;
+ private byte[] source;
+
+ private CustomKnownLengthInputStream(byte[] source) {
+ this.source = source;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return source.length - position;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (position == source.length) {
+ return -1;
+ }
+
+ return source[position++];
+ }
+ }
}