diff options
author | 2019-03-14 12:02:50 -0700 | |
---|---|---|
committer | 2019-04-04 20:23:29 +0000 | |
commit | d3538564e599dda0cb32d0e626bbf2285f5b52da (patch) | |
tree | e96e858a0fe6e80c8788224f650a2b011c0c9e70 | |
parent | dd07ae579c291a2b6ffe09bd576fd908eb9e5ddd (diff) |
Remove Test Api annotation from ProtoInputStream
Also move cts tests to unit tests
Test: atest ProtoInputStreamTests
Fixes: 115635242
Change-Id: I9aff1227328aad6ec2bec3471f73ae90293a028d
26 files changed, 10049 insertions, 28 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 67a26f39ebbc..2bf390075a4f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2840,31 +2840,6 @@ package android.util.proto { method public void writeRawZigZag64(long); } - public final class ProtoInputStream extends android.util.proto.ProtoStream { - ctor public ProtoInputStream(java.io.InputStream, int); - ctor public ProtoInputStream(java.io.InputStream); - ctor public ProtoInputStream(byte[]); - method public int decodeZigZag32(int); - method public long decodeZigZag64(long); - method public String dumpDebugData(); - method public void end(long); - method public int getFieldNumber(); - method public int getOffset(); - method public int getWireType(); - method public boolean isNextField(long) throws java.io.IOException; - method public int nextField() throws java.io.IOException; - method public boolean readBoolean(long) throws java.io.IOException; - method public byte[] readBytes(long) throws java.io.IOException; - method public double readDouble(long) throws java.io.IOException; - method public float readFloat(long) throws java.io.IOException; - method public int readInt(long) throws java.io.IOException; - method public long readLong(long) throws java.io.IOException; - method public String readString(long) throws java.io.IOException; - method public void skip() throws java.io.IOException; - method public long start(long) throws java.io.IOException; - field public static final int NO_MORE_FIELDS = -1; // 0xffffffff - } - public final class ProtoOutputStream extends android.util.proto.ProtoStream { ctor public ProtoOutputStream(); ctor public ProtoOutputStream(int); diff --git a/core/java/android/util/proto/ProtoInputStream.java b/core/java/android/util/proto/ProtoInputStream.java index cd2b6ce3dc67..c290dffc42c9 100644 --- a/core/java/android/util/proto/ProtoInputStream.java +++ b/core/java/android/util/proto/ProtoInputStream.java @@ -16,8 +16,6 @@ package android.util.proto; -import android.annotation.TestApi; - import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -64,7 +62,6 @@ import java.util.ArrayList; * * @hide */ -@TestApi public final class ProtoInputStream extends ProtoStream { public static final int NO_MORE_FIELDS = -1; diff --git a/core/java/android/util/proto/TEST_MAPPING b/core/java/android/util/proto/TEST_MAPPING new file mode 100644 index 000000000000..cf9f0772ac2d --- /dev/null +++ b/core/java/android/util/proto/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "ProtoInputStreamTests" + } + ] +}
\ No newline at end of file diff --git a/tests/ProtoInputStreamTests/Android.mk b/tests/ProtoInputStreamTests/Android.mk new file mode 100644 index 000000000000..eb747cc2cdcc --- /dev/null +++ b/tests/ProtoInputStreamTests/Android.mk @@ -0,0 +1,34 @@ +# Copyright (C) 2019 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_PACKAGE_NAME := ProtoInputStreamTests +LOCAL_PROTOC_OPTIMIZE_TYPE := nano +LOCAL_MODULE_TAGS := tests optional +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) \ + $(call all-proto-files-under, src) +LOCAL_PRIVATE_PLATFORM_APIS := true +LOCAL_CERTIFICATE := platform +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_STATIC_JAVA_LIBRARIES := \ + androidx.test.rules \ + frameworks-base-testutils \ + mockito-target-minus-junit4 + +include $(BUILD_PACKAGE)
\ No newline at end of file diff --git a/tests/ProtoInputStreamTests/AndroidManifest.xml b/tests/ProtoInputStreamTests/AndroidManifest.xml new file mode 100644 index 000000000000..c11aa7393431 --- /dev/null +++ b/tests/ProtoInputStreamTests/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.protoinputstream"> + <application> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.test.protoinputstream" + android:label="ProtoInputStream Tests"> + </instrumentation> +</manifest>
\ No newline at end of file diff --git a/tests/ProtoInputStreamTests/AndroidTest.xml b/tests/ProtoInputStreamTests/AndroidTest.xml new file mode 100644 index 000000000000..51ab88e4d4d0 --- /dev/null +++ b/tests/ProtoInputStreamTests/AndroidTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Configuration for ProtoInputStream Tests"> + <option name="test-suite-tag" value="ProtoInputStreamTests" /> + <option name="config-descriptor:metadata" key="component" value="metrics" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="ProtoInputStreamTests.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.test.protoinputstream" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/tests/ProtoInputStreamTests/TEST_MAPPING b/tests/ProtoInputStreamTests/TEST_MAPPING new file mode 100644 index 000000000000..cf9f0772ac2d --- /dev/null +++ b/tests/ProtoInputStreamTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "ProtoInputStreamTests" + } + ] +}
\ No newline at end of file diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java new file mode 100644 index 000000000000..c21c4033a0ff --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamBoolTest extends TestCase { + + /** + * Test reading single bool field + */ + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + /** + * Implementation of testRead with a given chunkSize. + */ + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 3 -> 1 + (byte) 0x18, + (byte) 0x01, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + boolean[] results = new boolean[2]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readBoolean(fieldId2); + break; + case (int) fieldId3: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(false, results[0]); + assertEquals(true, results[1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(false); + testReadCompat(true); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(boolean val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL; + final long fieldId = fieldFlags | ((long) 130 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.boolField = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + boolean result = false; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readBoolean(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.boolField, result); + } + + + /** + * Test reading repeated bool field + */ + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + /** + * Implementation of testRepeated with a given chunkSize. + */ + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BOOL; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + + // 4 -> 0 + (byte) 0x20, + (byte) 0x00, + // 4 -> 1 + (byte) 0x20, + (byte) 0x01, + + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + + // 3 -> 0 + (byte) 0x18, + (byte) 0x00, + // 3 -> 1 + (byte) 0x18, + (byte) 0x01, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + boolean[][] results = new boolean[3][2]; + int[] indices = new int[3]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readBoolean(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readBoolean(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readBoolean(fieldId3); + break; + case (int) fieldId4: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(false, results[0][0]); + assertEquals(false, results[0][1]); + assertEquals(true, results[1][0]); + assertEquals(true, results[1][1]); + assertEquals(false, results[2][0]); + assertEquals(true, results[2][1]); + } + + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new boolean[0]); + testRepeatedCompat(new boolean[]{false, true}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(boolean[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BOOL; + final long fieldId = fieldFlags | ((long) 131 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.boolFieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + boolean[] result = new boolean[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readBoolean(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.boolFieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.boolFieldRepeated[i], result[i]); + } + } + + /** + * Test reading packed bool field + */ + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + /** + * Implementation of testPacked with a given chunkSize. + */ + public void testPacked(int chunkSize) throws IOException { + + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_BOOL; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x02, + (byte) 0x00, + (byte) 0x00, + // 4 -> 0,1 + (byte) 0x22, + (byte) 0x02, + (byte) 0x00, + (byte) 0x01, + // 2 -> 1 + (byte) 0x12, + (byte) 0x02, + (byte) 0x01, + (byte) 0x01, + // 3 -> 0,1 + (byte) 0x1a, + (byte) 0x02, + (byte) 0x00, + (byte) 0x01, + }; + + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + boolean[][] results = new boolean[3][2]; + int[] indices = new int[3]; + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readBoolean(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readBoolean(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readBoolean(fieldId3); + break; + case (int) fieldId4: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(false, results[0][0]); + assertEquals(false, results[0][1]); + assertEquals(true, results[1][0]); + assertEquals(true, results[1][1]); + assertEquals(false, results[2][0]); + assertEquals(true, results[2][1]); + } + + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new boolean[0]); + testPackedCompat(new boolean[]{false, true}); + } + + /** + * Implementation of testPackedCompat with a given value. + */ + private void testPackedCompat(boolean[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_BOOL; + final long fieldId = fieldFlags | ((long) 132 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.boolFieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + boolean[] result = new boolean[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readBoolean(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.boolFieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.boolFieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readBoolean(fieldId1); + // don't fail, varint is ok + break; + case (int) fieldId2: + pi.readBoolean(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readBoolean(fieldId3); + // don't fail, length delimited is ok (represents packed booleans) + break; + case (int) fieldId6: + pi.readBoolean(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java new file mode 100644 index 000000000000..09fe40edda6c --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +public class ProtoInputStreamBytesTest extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> null - default value, written when repeated + (byte) 0x0a, + (byte) 0x00, + // 2 -> { } - default value, written when repeated + (byte) 0x12, + (byte) 0x00, + // 5 -> { 0, 1, 2, 3, 4 } + (byte) 0x2a, + (byte) 0x05, + (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, + // 3 -> { 0, 1, 2, 3, 4, 5 } + (byte) 0x1a, + (byte) 0x06, + (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, + // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc } + (byte) 0x22, + (byte) 0x04, + (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + byte[][] results = new byte[4][]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0] = pi.readBytes(fieldId1); + break; + case (int) fieldId2: + results[1] = pi.readBytes(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readBytes(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readBytes(fieldId4); + break; + case (int) fieldId5: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertTrue("Expected: [] Actual: " + Arrays.toString(results[0]), + Arrays.equals(new byte[]{}, results[0])); + assertTrue("Expected: [] Actual: " + Arrays.toString(results[1]), + Arrays.equals(new byte[]{}, results[1])); + assertTrue("Expected: [0, 1, 2, 3, 4, 5] Actual: " + Arrays.toString(results[2]), + Arrays.equals(new byte[]{0, 1, 2, 3, 4, 5}, results[2])); + assertTrue("Expected: [-1, -2, -3, -4] Actual: " + Arrays.toString(results[3]), + Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc}, + results[3])); + } + + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(new byte[0]); + testReadCompat(new byte[]{1, 2, 3, 4}); + testReadCompat(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc}); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(byte[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES; + final long fieldId = fieldFlags | ((long) 150 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.bytesField = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + byte[] result = new byte[val.length]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readBytes(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.bytesField.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.bytesField[i], result[i]); + } + } + + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BYTES; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> null - default value, written when repeated + (byte) 0x0a, + (byte) 0x00, + // 2 -> { } - default value, written when repeated + (byte) 0x12, + (byte) 0x00, + // 3 -> { 0, 1, 2, 3, 4, 5 } + (byte) 0x1a, + (byte) 0x06, + (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, + // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc } + (byte) 0x22, + (byte) 0x04, + (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, + + // 5 -> { 0, 1, 2, 3, 4} + (byte) 0x2a, + (byte) 0x05, + (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, + + // 1 -> null - default value, written when repeated + (byte) 0x0a, + (byte) 0x00, + // 2 -> { } - default value, written when repeated + (byte) 0x12, + (byte) 0x00, + // 3 -> { 0, 1, 2, 3, 4, 5 } + (byte) 0x1a, + (byte) 0x06, + (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, + // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc } + (byte) 0x22, + (byte) 0x04, + (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + byte[][][] results = new byte[4][2][]; + int[] indices = new int[4]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readBytes(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readBytes(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readBytes(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readBytes(fieldId4); + break; + case (int) fieldId5: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assert (Arrays.equals(new byte[]{}, results[0][0])); + assert (Arrays.equals(new byte[]{}, results[0][1])); + assert (Arrays.equals(new byte[]{}, results[1][0])); + assert (Arrays.equals(new byte[]{}, results[1][1])); + assert (Arrays.equals(new byte[]{1, 2, 3, 4}, results[2][0])); + assert (Arrays.equals(new byte[]{1, 2, 3, 4}, results[2][1])); + assert (Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc}, + results[3][0])); + assert (Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc}, + results[3][1])); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new byte[0][]); + testRepeatedCompat(new byte[][]{ + new byte[0], + new byte[]{1, 2, 3, 4}, + new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc} + }); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(byte[][] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BYTES; + final long fieldId = fieldFlags | ((long) 151 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.bytesFieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + byte[][] result = new byte[val.length][]; // start off with default value + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readBytes(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.bytesFieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.bytesFieldRepeated[i].length, result[i].length); + for (int j = 0; j < result[i].length; j++) { + assertEquals(readback.bytesFieldRepeated[i][j], result[i][j]); + } + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> {1} + (byte) 0x0a, + (byte) 0x01, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readBytes(fieldId1); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId2: + pi.readBytes(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readBytes(fieldId3); + // don't fail, length delimited is ok + break; + case (int) fieldId6: + pi.readBytes(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } + +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java new file mode 100644 index 000000000000..118fe3431e01 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java @@ -0,0 +1,728 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamDoubleTest extends TestCase { + + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL); + final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x11, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f, + // 10 -> 1 + (byte) 0x51, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f, + // 3 -> -1234.432 + (byte) 0x19, + (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e, + (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0, + // 4 -> 42.42 + (byte) 0x21, + (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f, + (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40, + // 5 -> Double.MIN_NORMAL + (byte) 0x29, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00, + // 6 -> DOUBLE.MIN_VALUE + (byte) 0x31, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 7 -> Double.NEGATIVE_INFINITY + (byte) 0x39, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff, + // 8 -> Double.NaN + (byte) 0x41, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f, + // 9 -> Double.POSITIVE_INFINITY + (byte) 0x49, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + double[] results = new double[9]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readDouble(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readDouble(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readDouble(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readDouble(fieldId5); + break; + case (int) fieldId6: + results[5] = pi.readDouble(fieldId6); + break; + case (int) fieldId7: + results[6] = pi.readDouble(fieldId7); + break; + case (int) fieldId8: + results[7] = pi.readDouble(fieldId8); + break; + case (int) fieldId9: + results[8] = pi.readDouble(fieldId9); + break; + case (int) fieldId10: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + assertEquals(0.0, results[0]); + assertEquals(1.0, results[1]); + assertEquals(-1234.432, results[2]); + assertEquals(42.42, results[3]); + assertEquals(Double.MIN_NORMAL, results[4]); + assertEquals(Double.MIN_VALUE, results[5]); + assertEquals(Double.NEGATIVE_INFINITY, results[6]); + assertEquals(Double.NaN, results[7]); + assertEquals(Double.POSITIVE_INFINITY, results[8]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1234.432); + testReadCompat(42.42); + testReadCompat(Double.MIN_NORMAL); + testReadCompat(Double.MIN_VALUE); + testReadCompat(Double.NEGATIVE_INFINITY); + testReadCompat(Double.NaN); + testReadCompat(Double.POSITIVE_INFINITY); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(double val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE; + final long fieldId = fieldFlags | ((long) 10 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.doubleField = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + double result = 0.0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readDouble(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.doubleField, result); + } + + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_DOUBLE; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL); + final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x09, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x11, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f, + // 3 -> -1234.432 + (byte) 0x19, + (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e, + (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0, + // 4 -> 42.42 + (byte) 0x21, + (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f, + (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40, + // 5 -> Double.MIN_NORMAL + (byte) 0x29, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00, + // 6 -> DOUBLE.MIN_VALUE + (byte) 0x31, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 7 -> Double.NEGATIVE_INFINITY + (byte) 0x39, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff, + // 8 -> Double.NaN + (byte) 0x41, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f, + // 9 -> Double.POSITIVE_INFINITY + (byte) 0x49, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f, + // 10 -> 1 + (byte) 0x51, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f, + + // 1 -> 0 - default value, written when repeated + (byte) 0x09, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x11, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f, + // 3 -> -1234.432 + (byte) 0x19, + (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e, + (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0, + // 4 -> 42.42 + (byte) 0x21, + (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f, + (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40, + // 5 -> Double.MIN_NORMAL + (byte) 0x29, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00, + // 6 -> DOUBLE.MIN_VALUE + (byte) 0x31, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 7 -> Double.NEGATIVE_INFINITY + (byte) 0x39, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff, + // 8 -> Double.NaN + (byte) 0x41, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f, + // 9 -> Double.POSITIVE_INFINITY + (byte) 0x49, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + double[][] results = new double[9][2]; + int[] indices = new int[9]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readDouble(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readDouble(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readDouble(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readDouble(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readDouble(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readDouble(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readDouble(fieldId7); + break; + case (int) fieldId8: + results[7][indices[7]++] = pi.readDouble(fieldId8); + break; + case (int) fieldId9: + results[8][indices[8]++] = pi.readDouble(fieldId9); + break; + case (int) fieldId10: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + assertEquals(0.0, results[0][0]); + assertEquals(0.0, results[0][1]); + assertEquals(1.0, results[1][0]); + assertEquals(1.0, results[1][1]); + assertEquals(-1234.432, results[2][0]); + assertEquals(-1234.432, results[2][1]); + assertEquals(42.42, results[3][0]); + assertEquals(42.42, results[3][1]); + assertEquals(Double.MIN_NORMAL, results[4][0]); + assertEquals(Double.MIN_NORMAL, results[4][1]); + assertEquals(Double.MIN_VALUE, results[5][0]); + assertEquals(Double.MIN_VALUE, results[5][1]); + assertEquals(Double.NEGATIVE_INFINITY, results[6][0]); + assertEquals(Double.NEGATIVE_INFINITY, results[6][1]); + assertEquals(Double.NaN, results[7][0]); + assertEquals(Double.NaN, results[7][1]); + assertEquals(Double.POSITIVE_INFINITY, results[8][0]); + assertEquals(Double.POSITIVE_INFINITY, results[8][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new double[0]); + testRepeatedCompat(new double[]{0, 1, -1234.432, 42.42, + Double.MIN_NORMAL, Double.MIN_VALUE, Double.NEGATIVE_INFINITY, Double.NaN, + Double.POSITIVE_INFINITY, + }); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(double[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_DOUBLE; + final long fieldId = fieldFlags | ((long) 11 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.doubleFieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + double[] result = new double[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readDouble(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.doubleFieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.doubleFieldRepeated[i], result[i]); + } + } + + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_DOUBLE; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL); + final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f, + // 10 -> 1 + (byte) 0x52, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f, + // 3 -> -1234.432 + (byte) 0x1a, + (byte) 0x10, + (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e, + (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0, + (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e, + (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0, + // 4 -> 42.42 + (byte) 0x22, + (byte) 0x10, + (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f, + (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40, + (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f, + (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40, + // 5 -> Double.MIN_NORMAL + (byte) 0x2a, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00, + // 6 -> DOUBLE.MIN_VALUE + (byte) 0x32, + (byte) 0x10, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 7 -> Double.NEGATIVE_INFINITY + (byte) 0x3a, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff, + // 8 -> Double.NaN + (byte) 0x42, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f, + // 9 -> Double.POSITIVE_INFINITY + (byte) 0x4a, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + double[][] results = new double[9][2]; + int[] indices = new int[9]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readDouble(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readDouble(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readDouble(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readDouble(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readDouble(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readDouble(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readDouble(fieldId7); + break; + case (int) fieldId8: + results[7][indices[7]++] = pi.readDouble(fieldId8); + break; + case (int) fieldId9: + results[8][indices[8]++] = pi.readDouble(fieldId9); + break; + case (int) fieldId10: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + assertEquals(0.0, results[0][0]); + assertEquals(0.0, results[0][1]); + assertEquals(1.0, results[1][0]); + assertEquals(1.0, results[1][1]); + assertEquals(-1234.432, results[2][0]); + assertEquals(-1234.432, results[2][1]); + assertEquals(42.42, results[3][0]); + assertEquals(42.42, results[3][1]); + assertEquals(Double.MIN_NORMAL, results[4][0]); + assertEquals(Double.MIN_NORMAL, results[4][1]); + assertEquals(Double.MIN_VALUE, results[5][0]); + assertEquals(Double.MIN_VALUE, results[5][1]); + assertEquals(Double.NEGATIVE_INFINITY, results[6][0]); + assertEquals(Double.NEGATIVE_INFINITY, results[6][1]); + assertEquals(Double.NaN, results[7][0]); + assertEquals(Double.NaN, results[7][1]); + assertEquals(Double.POSITIVE_INFINITY, results[8][0]); + assertEquals(Double.POSITIVE_INFINITY, results[8][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new double[0]); + testPackedCompat(new double[]{0, 1, -1234.432, 42.42, + Double.MIN_NORMAL, Double.MIN_VALUE, Double.NEGATIVE_INFINITY, Double.NaN, + Double.POSITIVE_INFINITY, + }); + } + + /** + * Implementation of testPackedCompat with a given value. + */ + private void testPackedCompat(double[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_DOUBLE; + final long fieldId = fieldFlags | ((long) 12 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.doubleFieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + double[] result = new double[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readDouble(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.doubleFieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.doubleFieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x09, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x08, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readDouble(fieldId1); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId2: + pi.readDouble(fieldId2); + // don't fail, fixed64 is ok + break; + case (int) fieldId3: + pi.readDouble(fieldId3); + // don't fail, length delimited is ok (represents packed doubles) + break; + case (int) fieldId6: + pi.readDouble(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java new file mode 100644 index 000000000000..f55d95129588 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamEnumTest extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 6 -> MAX_VALUE + (byte) 0x30, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[] results = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(int val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM; + final long fieldId = fieldFlags | ((long) 160 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.outsideField = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + + int result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + // Nano proto drops values that are outside the range, so compare against val + assertEquals(val, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_ENUM; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 6 -> MAX_VALUE + (byte) 0x30, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new int[]{}); + testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_ENUM; + final long fieldId = fieldFlags | ((long) 161 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.outsideFieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + // Nano proto drops values that are outside the range, so compare against val + assertEquals(val.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(val[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_ENUM; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x02, + (byte) 0x00, + (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x02, + (byte) 0x01, + (byte) 0x01, + + // 6 -> MAX_VALUE + (byte) 0x32, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 3 -> -1 + (byte) 0x1a, + (byte) 0x14, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 4 -> MIN_VALUE + (byte) 0x22, + (byte) 0x14, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 5 -> MAX_VALUE + (byte) 0x2a, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new int[]{}); + testPackedCompat(new int[]{0, 1}); + + // Nano proto has a bug. It gets the size with computeInt32SizeNoTag (correctly) + // but incorrectly uses writeRawVarint32 to write the value for negative numbers. + //testPackedCompat(new int[] { 0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE }); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_ENUM; + final long fieldId = fieldFlags | ((long) 162 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.outsideFieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + // Nano proto drops values that are outside the range, so compare against val + assertEquals(val.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(val[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readInt(fieldId1); + // don't fail, varint is ok + break; + case (int) fieldId2: + pi.readInt(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readInt(fieldId3); + // don't fail, length delimited is ok (represents packed enums) + break; + case (int) fieldId6: + pi.readInt(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java new file mode 100644 index 000000000000..df68476f0c36 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamFixed32Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x15, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> 1 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x25, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 5 -> Integer.MAX_VALUE + (byte) 0x2d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[] results = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(int val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32; + final long fieldId = fieldFlags | ((long) 90 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.fixed32Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.fixed32Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0d, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x15, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x25, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 5 -> Integer.MAX_VALUE + (byte) 0x2d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + // 6 -> 1 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + + // 1 -> 0 - default value, written when repeated + (byte) 0x0d, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x15, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x25, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 5 -> Integer.MAX_VALUE + (byte) 0x2d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new int[0]); + testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32; + final long fieldId = fieldFlags | ((long) 91 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.fixed32FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.fixed32FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.fixed32FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> 1 + (byte) 0x32, + (byte) 0x08, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x08, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1a, + (byte) 0x08, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x22, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 5 -> Integer.MAX_VALUE + (byte) 0x2a, + (byte) 0x08, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new int[0]); + testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32; + final long fieldId = fieldFlags | ((long) 92 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.fixed32FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.fixed32FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.fixed32FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x0d, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x04, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readInt(fieldId1); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId2: + pi.readInt(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readInt(fieldId3); + // don't fail, length delimited is ok (represents packed fixed32) + break; + case (int) fieldId6: + pi.readInt(fieldId6); + // don't fail, fixed32 is ok + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java new file mode 100644 index 000000000000..af4130b28cd8 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamFixed64Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> 1 + (byte) 0x41, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x19, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x21, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 5 -> Integer.MAX_VALUE + (byte) 0x29, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> Long.MIN_VALUE + (byte) 0x31, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 7 -> Long.MAX_VALUE + (byte) 0x39, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[] results = new long[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + assertEquals(Long.MIN_VALUE, results[5]); + assertEquals(Long.MAX_VALUE, results[6]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + testReadCompat(Long.MIN_VALUE); + testReadCompat(Long.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(long val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64; + final long fieldId = fieldFlags | ((long) 100 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.fixed64Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.fixed64Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x09, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x19, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x21, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 5 -> Integer.MAX_VALUE + (byte) 0x29, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> Long.MIN_VALUE + (byte) 0x31, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 7 -> Long.MAX_VALUE + (byte) 0x39, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + // 8 -> 1 + (byte) 0x41, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + + // 1 -> 0 - default value, written when repeated + (byte) 0x09, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x19, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x21, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 5 -> Integer.MAX_VALUE + (byte) 0x29, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> Long.MIN_VALUE + (byte) 0x31, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 7 -> Long.MAX_VALUE + (byte) 0x39, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new long[0]); + testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED64; + final long fieldId = fieldFlags | ((long) 101 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.fixed64FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.fixed64FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.fixed64FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x10, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 8 -> 1 + (byte) 0x42, + (byte) 0x10, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1a, + (byte) 0x10, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x22, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 5 -> Integer.MAX_VALUE + (byte) 0x2a, + (byte) 0x10, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> Long.MIN_VALUE + (byte) 0x32, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 7 -> Long.MAX_VALUE + (byte) 0x3a, + (byte) 0x10, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new long[0]); + testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED64; + final long fieldId = fieldFlags | ((long) 102 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.fixed64FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.fixed64FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.fixed64FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x09, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x08, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readLong(fieldId1); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId2: + pi.readLong(fieldId2); + // don't fail, fixed64 is ok + break; + case (int) fieldId3: + pi.readLong(fieldId3); + // don't fail, length delimited is ok (represents packed fixed64) + break; + case (int) fieldId6: + pi.readLong(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java new file mode 100644 index 000000000000..9bc07dc513e1 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java @@ -0,0 +1,679 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamFloatTest extends TestCase { + + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL); + final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x15, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f, + // 10 -> 1 + (byte) 0x55, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f, + // 3 -> -1234.432 + (byte) 0x1d, + (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4, + // 4 -> 42.42 + (byte) 0x25, + (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42, + // 5 -> Float.MIN_NORMAL + (byte) 0x2d, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00, + // 6 -> DOUBLE.MIN_VALUE + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 7 -> Float.NEGATIVE_INFINITY + (byte) 0x3d, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff, + // 8 -> Float.NaN + (byte) 0x45, + (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f, + // 9 -> Float.POSITIVE_INFINITY + (byte) 0x4d, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + float[] results = new float[9]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readFloat(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readFloat(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readFloat(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readFloat(fieldId5); + break; + case (int) fieldId6: + results[5] = pi.readFloat(fieldId6); + break; + case (int) fieldId7: + results[6] = pi.readFloat(fieldId7); + break; + case (int) fieldId8: + results[7] = pi.readFloat(fieldId8); + break; + case (int) fieldId9: + results[8] = pi.readFloat(fieldId9); + break; + case (int) fieldId10: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + assertEquals(0.0f, results[0]); + assertEquals(1.0f, results[1]); + assertEquals(-1234.432f, results[2]); + assertEquals(42.42f, results[3]); + assertEquals(Float.MIN_NORMAL, results[4]); + assertEquals(Float.MIN_VALUE, results[5]); + assertEquals(Float.NEGATIVE_INFINITY, results[6]); + assertEquals(Float.NaN, results[7]); + assertEquals(Float.POSITIVE_INFINITY, results[8]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1234.432f); + testReadCompat(42.42f); + testReadCompat(Float.MIN_NORMAL); + testReadCompat(Float.MIN_VALUE); + testReadCompat(Float.NEGATIVE_INFINITY); + testReadCompat(Float.NaN); + testReadCompat(Float.POSITIVE_INFINITY); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(float val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT; + final long fieldId = fieldFlags | ((long) 20 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.floatField = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + float result = 0.0f; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readFloat(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.floatField, result); + } + + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FLOAT; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL); + final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0d, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x15, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f, + // 3 -> -1234.432 + (byte) 0x1d, + (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4, + // 4 -> 42.42 + (byte) 0x25, + (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42, + // 5 -> Float.MIN_NORMAL + (byte) 0x2d, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00, + // 6 -> DOUBLE.MIN_VALUE + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 7 -> Float.NEGATIVE_INFINITY + (byte) 0x3d, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff, + // 8 -> Float.NaN + (byte) 0x45, + (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f, + // 9 -> Float.POSITIVE_INFINITY + (byte) 0x4d, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f, + + // 10 -> 1 + (byte) 0x55, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f, + + // 1 -> 0 - default value, written when repeated + (byte) 0x0d, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x15, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f, + // 3 -> -1234.432 + (byte) 0x1d, + (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4, + // 4 -> 42.42 + (byte) 0x25, + (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42, + // 5 -> Float.MIN_NORMAL + (byte) 0x2d, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00, + // 6 -> DOUBLE.MIN_VALUE + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 7 -> Float.NEGATIVE_INFINITY + (byte) 0x3d, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff, + // 8 -> Float.NaN + (byte) 0x45, + (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f, + // 9 -> Float.POSITIVE_INFINITY + (byte) 0x4d, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + float[][] results = new float[9][2]; + int[] indices = new int[9]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readFloat(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readFloat(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readFloat(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readFloat(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readFloat(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readFloat(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readFloat(fieldId7); + break; + case (int) fieldId8: + results[7][indices[7]++] = pi.readFloat(fieldId8); + break; + case (int) fieldId9: + results[8][indices[8]++] = pi.readFloat(fieldId9); + break; + case (int) fieldId10: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + assertEquals(0.0f, results[0][0]); + assertEquals(0.0f, results[0][1]); + assertEquals(1.0f, results[1][0]); + assertEquals(1.0f, results[1][1]); + assertEquals(-1234.432f, results[2][0]); + assertEquals(-1234.432f, results[2][1]); + assertEquals(42.42f, results[3][0]); + assertEquals(42.42f, results[3][1]); + assertEquals(Float.MIN_NORMAL, results[4][0]); + assertEquals(Float.MIN_NORMAL, results[4][1]); + assertEquals(Float.MIN_VALUE, results[5][0]); + assertEquals(Float.MIN_VALUE, results[5][1]); + assertEquals(Float.NEGATIVE_INFINITY, results[6][0]); + assertEquals(Float.NEGATIVE_INFINITY, results[6][1]); + assertEquals(Float.NaN, results[7][0]); + assertEquals(Float.NaN, results[7][1]); + assertEquals(Float.POSITIVE_INFINITY, results[8][0]); + assertEquals(Float.POSITIVE_INFINITY, results[8][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new float[0]); + testRepeatedCompat(new float[]{0, 1, -1234.432f, 42.42f, + Float.MIN_NORMAL, Float.MIN_VALUE, Float.NEGATIVE_INFINITY, Float.NaN, + Float.POSITIVE_INFINITY, + }); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(float[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FLOAT; + final long fieldId = fieldFlags | ((long) 21 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.floatFieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + float[] result = new float[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readFloat(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.floatFieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.floatFieldRepeated[i], result[i]); + } + } + + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FLOAT; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL); + final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f, + // 10 -> 1 + (byte) 0x52, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f, + // 3 -> -1234.432 + (byte) 0x1a, + (byte) 0x08, + (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4, + (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4, + // 4 -> 42.42 + (byte) 0x22, + (byte) 0x08, + (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42, + (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42, + // 5 -> Float.MIN_NORMAL + (byte) 0x2a, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00, + // 6 -> DOUBLE.MIN_VALUE + (byte) 0x32, + (byte) 0x08, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 7 -> Float.NEGATIVE_INFINITY + (byte) 0x3a, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff, + // 8 -> Float.NaN + (byte) 0x42, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f, + // 9 -> Float.POSITIVE_INFINITY + (byte) 0x4a, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + float[][] results = new float[9][2]; + int[] indices = new int[9]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readFloat(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readFloat(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readFloat(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readFloat(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readFloat(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readFloat(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readFloat(fieldId7); + break; + case (int) fieldId8: + results[7][indices[7]++] = pi.readFloat(fieldId8); + break; + case (int) fieldId9: + results[8][indices[8]++] = pi.readFloat(fieldId9); + break; + case (int) fieldId10: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + assertEquals(0.0f, results[0][0]); + assertEquals(0.0f, results[0][1]); + assertEquals(1.0f, results[1][0]); + assertEquals(1.0f, results[1][1]); + assertEquals(-1234.432f, results[2][0]); + assertEquals(-1234.432f, results[2][1]); + assertEquals(42.42f, results[3][0]); + assertEquals(42.42f, results[3][1]); + assertEquals(Float.MIN_NORMAL, results[4][0]); + assertEquals(Float.MIN_NORMAL, results[4][1]); + assertEquals(Float.MIN_VALUE, results[5][0]); + assertEquals(Float.MIN_VALUE, results[5][1]); + assertEquals(Float.NEGATIVE_INFINITY, results[6][0]); + assertEquals(Float.NEGATIVE_INFINITY, results[6][1]); + assertEquals(Float.NaN, results[7][0]); + assertEquals(Float.NaN, results[7][1]); + assertEquals(Float.POSITIVE_INFINITY, results[8][0]); + assertEquals(Float.POSITIVE_INFINITY, results[8][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new float[0]); + testPackedCompat(new float[]{0, 1, -1234.432f, 42.42f, + Float.MIN_NORMAL, Float.MIN_VALUE, Float.NEGATIVE_INFINITY, Float.NaN, + Float.POSITIVE_INFINITY, + }); + } + + /** + * Implementation of testPackedCompat with a given value. + */ + private void testPackedCompat(float[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FLOAT; + final long fieldId = fieldFlags | ((long) 22 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.floatFieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + float[] result = new float[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readFloat(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.floatFieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.floatFieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x0d, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x04, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readFloat(fieldId1); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId2: + pi.readFloat(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readFloat(fieldId3); + // don't fail, length delimited is ok (represents packed floats) + break; + case (int) fieldId6: + pi.readFloat(fieldId6); + // don't fail, fixed32 is ok + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java new file mode 100644 index 000000000000..0065870486f2 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamInt32Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 6 -> MAX_VALUE + (byte) 0x30, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[] results = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(int val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32; + final long fieldId = fieldFlags | ((long) 30 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.int32Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.int32Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 6 -> MAX_VALUE + (byte) 0x30, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new int[0]); + testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT32; + final long fieldId = fieldFlags | ((long) 31 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.int32FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.int32FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.int32FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_INT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x02, + (byte) 0x00, + (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x02, + (byte) 0x01, + (byte) 0x01, + + // 6 -> MAX_VALUE + (byte) 0x32, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 3 -> -1 + (byte) 0x1a, + (byte) 0x14, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 4 -> MIN_VALUE + (byte) 0x22, + (byte) 0x14, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 5 -> MAX_VALUE + (byte) 0x2a, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new int[0]); + testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT32; + final long fieldId = fieldFlags | ((long) 32 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.int32FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.int32FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.int32FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readInt(fieldId1); + // don't fail, varint is ok + break; + case (int) fieldId2: + pi.readInt(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readInt(fieldId3); + // don't fail, length delimited is ok (represents packed int32) + break; + case (int) fieldId6: + pi.readInt(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java new file mode 100644 index 000000000000..4d6d105e60b0 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java @@ -0,0 +1,645 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamInt64Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 8 -> Long.MAX_VALUE + (byte) 0x40, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> Integer.MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 6 -> Long.MIN_VALUE + (byte) 0x30, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x38, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[] results = new long[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + assertEquals(Long.MIN_VALUE, results[5]); + assertEquals(Long.MAX_VALUE, results[6]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + testReadCompat(Long.MIN_VALUE); + testReadCompat(Long.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(long val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64; + final long fieldId = fieldFlags | ((long) 40 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.int64Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.int64Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> Integer.MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 6 -> Long.MIN_VALUE + (byte) 0x30, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x38, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + // 8 -> Long.MAX_VALUE + (byte) 0x40, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> Integer.MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 6 -> Long.MIN_VALUE + (byte) 0x30, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x38, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new long[0]); + testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT64; + final long fieldId = fieldFlags | ((long) 41 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.int64FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.int64FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.int64FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_INT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x02, + (byte) 0x00, + (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x02, + (byte) 0x01, + (byte) 0x01, + + // 8 -> Long.MAX_VALUE + (byte) 0x42, + (byte) 0x12, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + // 3 -> -1 + (byte) 0x1a, + (byte) 0x14, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 4 -> Integer.MIN_VALUE + (byte) 0x22, + (byte) 0x14, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 5 -> Integer.MAX_VALUE + (byte) 0x2a, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 6 -> Long.MIN_VALUE + (byte) 0x32, + (byte) 0x14, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + + // 7 -> Long.MAX_VALUE + (byte) 0x3a, + (byte) 0x12, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new long[0]); + testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT64; + final long fieldId = fieldFlags | ((long) 42 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.int64FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.int64FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.int64FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readLong(fieldId1); + // don't fail, varint is ok + break; + case (int) fieldId2: + pi.readLong(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readLong(fieldId3); + // don't fail, length delimited is ok (represents packed int64) + break; + case (int) fieldId6: + pi.readLong(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java new file mode 100644 index 000000000000..5e49eeafb8af --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamObjectTest extends TestCase { + + + class SimpleObject { + public char mChar; + public char mLargeChar; + public String mString; + public SimpleObject mNested; + + void parseProto(ProtoInputStream pi) throws IOException { + final long uintFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; + final long stringFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING; + final long messageFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; + final long charId = uintFieldFlags | ((long) 2 & 0x0ffffffffL); + final long largeCharId = uintFieldFlags | ((long) 5000 & 0x0ffffffffL); + final long stringId = stringFieldFlags | ((long) 4 & 0x0ffffffffL); + final long nestedId = messageFieldFlags | ((long) 5 & 0x0ffffffffL); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) charId: + mChar = (char) pi.readInt(charId); + break; + case (int) largeCharId: + mLargeChar = (char) pi.readInt(largeCharId); + break; + case (int) stringId: + mString = pi.readString(stringId); + break; + case (int) nestedId: + long token = pi.start(nestedId); + mNested = new SimpleObject(); + mNested.parseProto(pi); + pi.end(token); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + } + + } + + /** + * Test reading an object with one char in it. + */ + public void testObjectOneChar() throws IOException { + testObjectOneChar(0); + testObjectOneChar(1); + testObjectOneChar(5); + } + + /** + * Implementation of testObjectOneChar for a given chunkSize. + */ + private void testObjectOneChar(int chunkSize) throws IOException { + final long messageFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; + + final long messageId1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL); + final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // Message 2 : { char 2 : 'c' } + (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x63, + // Message 1 : { char 2 : 'b' } + (byte) 0x0a, (byte) 0x02, (byte) 0x10, (byte) 0x62, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + + SimpleObject result = null; + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) messageId1: + final long token = pi.start(messageId1); + result = new SimpleObject(); + result.parseProto(pi); + pi.end(token); + break; + case (int) messageId2: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertNotNull(result); + assertEquals('b', result.mChar); + } + + /** + * Test reading an object with one multibyte unicode char in it. + */ + public void testObjectOneLargeChar() throws IOException { + testObjectOneLargeChar(0); + testObjectOneLargeChar(1); + testObjectOneLargeChar(5); + } + + /** + * Implementation of testObjectOneLargeChar for a given chunkSize. + */ + private void testObjectOneLargeChar(int chunkSize) throws IOException { + final long messageFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; + + final long messageId1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL); + final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // Message 2 : { char 5000 : '\u3110' } + (byte) 0x12, (byte) 0x05, (byte) 0xc0, (byte) 0xb8, + (byte) 0x02, (byte) 0x90, (byte) 0x62, + // Message 1 : { char 5000 : '\u3110' } + (byte) 0x0a, (byte) 0x05, (byte) 0xc0, (byte) 0xb8, + (byte) 0x02, (byte) 0x90, (byte) 0x62, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + + SimpleObject result = null; + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) messageId1: + final long token = pi.start(messageId1); + result = new SimpleObject(); + result.parseProto(pi); + pi.end(token); + break; + case (int) messageId2: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertNotNull(result); + assertEquals('\u3110', result.mLargeChar); + } + + /** + * Test reading a char, then an object, then a char. + */ + public void testObjectAndTwoChars() throws IOException { + testObjectAndTwoChars(0); + testObjectAndTwoChars(1); + testObjectAndTwoChars(5); + } + + /** + * Implementation of testObjectAndTwoChars for a given chunkSize. + */ + private void testObjectAndTwoChars(int chunkSize) throws IOException { + final long uintFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; + final long messageFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; + + final long charId1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL); + final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); + final long charId4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 'a' + (byte) 0x08, (byte) 0x61, + // Message 1 : { char 2 : 'b' } + (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x62, + // 4 -> 'c' + (byte) 0x20, (byte) 0x63, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + + SimpleObject obj = null; + char char1 = '\0'; + char char4 = '\0'; + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) charId1: + char1 = (char) pi.readInt(charId1); + break; + case (int) messageId2: + final long token = pi.start(messageId2); + obj = new SimpleObject(); + obj.parseProto(pi); + pi.end(token); + break; + case (int) charId4: + char4 = (char) pi.readInt(charId4); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals('a', char1); + assertNotNull(obj); + assertEquals('b', obj.mChar); + assertEquals('c', char4); + } + + /** + * Test reading a char, then an object with an int and a string in it, then a char. + */ + public void testComplexObject() throws IOException { + testComplexObject(0); + testComplexObject(1); + testComplexObject(5); + } + + /** + * Implementation of testComplexObject for a given chunkSize. + */ + private void testComplexObject(int chunkSize) throws IOException { + final long uintFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; + final long messageFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; + + final long charId1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL); + final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); + final long charId4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 'x' + (byte) 0x08, (byte) 0x78, + // begin object 2 + (byte) 0x12, (byte) 0x10, + // 2 -> 'y' + (byte) 0x10, (byte) 0x79, + // 4 -> "abcdefghijkl" + (byte) 0x22, (byte) 0x0c, + (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, + (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6a, (byte) 0x6b, (byte) 0x6c, + // 4 -> 'z' + (byte) 0x20, (byte) 0x7a, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + + SimpleObject obj = null; + char char1 = '\0'; + char char4 = '\0'; + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) charId1: + char1 = (char) pi.readInt(charId1); + break; + case (int) messageId2: + final long token = pi.start(messageId2); + obj = new SimpleObject(); + obj.parseProto(pi); + pi.end(token); + break; + case (int) charId4: + char4 = (char) pi.readInt(charId4); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals('x', char1); + assertNotNull(obj); + assertEquals('y', obj.mChar); + assertEquals("abcdefghijkl", obj.mString); + assertEquals('z', char4); + } + + /** + * Test reading 3 levels deep of objects. + */ + public void testDeepObjects() throws IOException { + testDeepObjects(0); + testDeepObjects(1); + testDeepObjects(5); + } + + /** + * Implementation of testDeepObjects for a given chunkSize. + */ + private void testDeepObjects(int chunkSize) throws IOException { + final long messageFieldFlags = + ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; + final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // begin object id 2 + (byte) 0x12, (byte) 0x1a, + // 2 -> 'a' + (byte) 0x10, (byte) 0x61, + // begin nested object id 5 + (byte) 0x2a, (byte) 0x15, + // 5000 -> '\u3110' + (byte) 0xc0, (byte) 0xb8, + (byte) 0x02, (byte) 0x90, (byte) 0x62, + // begin nested object id 5 + (byte) 0x2a, (byte) 0x0e, + // 4 -> "abcdefghijkl" + (byte) 0x22, (byte) 0x0c, + (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, + (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6a, (byte) 0x6b, (byte) 0x6c, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + + SimpleObject obj = null; + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) messageId2: + final long token = pi.start(messageId2); + obj = new SimpleObject(); + obj.parseProto(pi); + pi.end(token); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertNotNull(obj); + assertEquals('a', obj.mChar); + assertNotNull(obj.mNested); + assertEquals('\u3110', obj.mNested.mLargeChar); + assertNotNull(obj.mNested.mNested); + assertEquals("abcdefghijkl", obj.mNested.mNested.mString); + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> {1} + (byte) 0x0a, + (byte) 0x01, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readBytes(fieldId1); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId2: + pi.readBytes(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readBytes(fieldId3); + // don't fail, length delimited is ok + break; + case (int) fieldId6: + pi.readBytes(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java new file mode 100644 index 000000000000..75c88a44614b --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamSFixed32Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x15, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> 1 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x25, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 5 -> Integer.MAX_VALUE + (byte) 0x2d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[] results = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(int val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32; + final long fieldId = fieldFlags | ((long) 110 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sfixed32Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sfixed32Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0d, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x15, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x25, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 5 -> Integer.MAX_VALUE + (byte) 0x2d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + // 6 -> 1 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + + // 1 -> 0 - default value, written when repeated + (byte) 0x0d, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x15, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x25, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 5 -> Integer.MAX_VALUE + (byte) 0x2d, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new int[0]); + testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32; + final long fieldId = fieldFlags | ((long) 111 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sfixed32FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sfixed32FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.sfixed32FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SFIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x08, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> 1 + (byte) 0x32, + (byte) 0x08, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1a, + (byte) 0x08, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x22, + (byte) 0x08, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 5 -> Integer.MAX_VALUE + (byte) 0x2a, + (byte) 0x08, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new int[0]); + testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32; + final long fieldId = fieldFlags | ((long) 112 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sfixed32FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sfixed32FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.sfixed32FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x04, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readInt(fieldId1); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId2: + pi.readInt(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readInt(fieldId3); + // don't fail, length delimited is ok (represents packed sfixed32) + break; + case (int) fieldId6: + pi.readInt(fieldId6); + // don't fail, fixed32 is ok + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java new file mode 100644 index 000000000000..4c65cf49318d --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java @@ -0,0 +1,648 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamSFixed64Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 8 -> 1 + (byte) 0x41, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x19, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x21, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 5 -> Integer.MAX_VALUE + (byte) 0x29, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> Long.MIN_VALUE + (byte) 0x31, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 7 -> Long.MAX_VALUE + (byte) 0x39, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[] results = new long[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + assertEquals(Long.MIN_VALUE, results[5]); + assertEquals(Long.MAX_VALUE, results[6]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + testReadCompat(Long.MIN_VALUE); + testReadCompat(Long.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(long val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64; + final long fieldId = fieldFlags | ((long) 120 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sfixed64Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sfixed64Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x09, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x19, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x21, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 5 -> Integer.MAX_VALUE + (byte) 0x29, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> Long.MIN_VALUE + (byte) 0x31, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 7 -> Long.MAX_VALUE + (byte) 0x39, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + // 8 -> 1 + (byte) 0x41, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + + // 1 -> 0 - default value, written when repeated + (byte) 0x09, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x19, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x21, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 5 -> Integer.MAX_VALUE + (byte) 0x29, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> Long.MIN_VALUE + (byte) 0x31, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 7 -> Long.MAX_VALUE + (byte) 0x39, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new long[0]); + testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED64; + final long fieldId = fieldFlags | ((long) 121 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sfixed64FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sfixed64FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.sfixed64FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SFIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x10, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 8 -> 1 + (byte) 0x42, + (byte) 0x10, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 -> -1 + (byte) 0x1a, + (byte) 0x10, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 4 -> Integer.MIN_VALUE + (byte) 0x22, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + // 5 -> Integer.MAX_VALUE + (byte) 0x2a, + (byte) 0x10, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 -> Long.MIN_VALUE + (byte) 0x32, + (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + // 7 -> Long.MAX_VALUE + (byte) 0x3a, + (byte) 0x10, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new long[0]); + testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED64; + final long fieldId = fieldFlags | ((long) 122 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sfixed64FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sfixed64FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.sfixed64FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x08, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readLong(fieldId1); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId2: + pi.readLong(fieldId2); + // don't fail, fixed32 is ok + break; + case (int) fieldId3: + pi.readLong(fieldId3); + // don't fail, length delimited is ok (represents packed sfixed64) + break; + case (int) fieldId6: + pi.readLong(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java new file mode 100644 index 000000000000..6854cd8aad28 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamSInt32Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x10, + (byte) 0x02, + // 6 -> MAX_VALUE + (byte) 0x30, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 3 -> -1 + (byte) 0x18, + (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[] results = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(int val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32; + final long fieldId = fieldFlags | ((long) 70 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sint32Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sint32Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x02, + // 3 -> -1 + (byte) 0x18, + (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + + // 6 -> MAX_VALUE + (byte) 0x30, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x02, + // 3 -> -1 + (byte) 0x18, + (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new int[0]); + testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT32; + final long fieldId = fieldFlags | ((long) 71 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sint32FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sint32FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.sint32FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x02, + (byte) 0x00, + (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x02, + (byte) 0x02, + (byte) 0x02, + // 6 -> MAX_VALUE + (byte) 0x32, + (byte) 0x0a, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 3 -> -1 + (byte) 0x1a, + (byte) 0x02, + (byte) 0x01, + (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x22, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 5 -> MAX_VALUE + (byte) 0x2a, + (byte) 0x0a, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new int[0]); + testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT32; + final long fieldId = fieldFlags | ((long) 72 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sint32FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sint32FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.sint32FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readInt(fieldId1); + // don't fail, varint is ok + break; + case (int) fieldId2: + pi.readInt(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readInt(fieldId3); + // don't fail, length delimited is ok (represents packed sint32) + break; + case (int) fieldId6: + pi.readInt(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java new file mode 100644 index 000000000000..c53e9d72562a --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamSInt64Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x10, + (byte) 0x02, + // 8 -> Integer.MAX_VALUE + (byte) 0x40, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 3 -> -1 + (byte) 0x18, + (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x20, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 5 -> Integer.MAX_VALUE + (byte) 0x28, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 6 -> Long.MIN_VALUE + (byte) 0x30, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x38, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[] results = new long[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + assertEquals(Long.MIN_VALUE, results[5]); + assertEquals(Long.MAX_VALUE, results[6]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + testReadCompat(Long.MIN_VALUE); + testReadCompat(Long.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(long val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64; + final long fieldId = fieldFlags | ((long) 80 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sint64Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sint64Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x02, + // 3 -> -1 + (byte) 0x18, + (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x20, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 5 -> Integer.MAX_VALUE + (byte) 0x28, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 6 -> Long.MIN_VALUE + (byte) 0x30, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x38, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 8 -> Integer.MAX_VALUE + (byte) 0x40, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + + + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x02, + // 3 -> -1 + (byte) 0x18, + (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x20, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 5 -> Integer.MAX_VALUE + (byte) 0x28, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 6 -> Long.MIN_VALUE + (byte) 0x30, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x38, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new long[0]); + testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT64; + final long fieldId = fieldFlags | ((long) 81 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sint64FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sint64FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.sint64FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x02, + (byte) 0x00, + (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x02, + (byte) 0x02, + (byte) 0x02, + // 8 -> Integer.MAX_VALUE + (byte) 0x42, + (byte) 0x0a, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 3 -> -1 + (byte) 0x1a, + (byte) 0x02, + (byte) 0x01, + (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x22, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 5 -> Integer.MAX_VALUE + (byte) 0x2a, + (byte) 0x0a, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f, + // 6 -> Long.MIN_VALUE + (byte) 0x32, + (byte) 0x14, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x3a, + (byte) 0x14, + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01 + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new long[0]); + testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT64; + final long fieldId = fieldFlags | ((long) 82 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.sint64FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.sint64FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.sint64FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readLong(fieldId1); + // don't fail, varint is ok + break; + case (int) fieldId2: + pi.readLong(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readLong(fieldId3); + // don't fail, length delimited is ok (represents packed sint64) + break; + case (int) fieldId6: + pi.readLong(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java new file mode 100644 index 000000000000..816d5f900a3d --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamStringTest extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> null - default value, not written + // 2 -> "" - default value, not written + // 3 -> "abcd\u3110!" + (byte) 0x1a, + (byte) 0x08, + (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, + (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21, + // 5 -> "Hi" + (byte) 0x2a, + (byte) 0x02, + (byte) 0x48, (byte) 0x69, + // 4 -> "Hi" + (byte) 0x22, + (byte) 0x02, + (byte) 0x48, (byte) 0x69, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + String[] results = new String[4]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0] = pi.readString(fieldId1); + break; + case (int) fieldId2: + results[1] = pi.readString(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readString(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readString(fieldId4); + break; + case (int) fieldId5: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertNull(results[0]); + assertNull(results[1]); + assertEquals("abcd\u3110!", results[2]); + assertEquals("Hi", results[3]); + } + + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(""); + testReadCompat("abcd\u3110!"); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(String val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING; + final long fieldId = fieldFlags | ((long) 140 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.stringField = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + String result = ""; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readString(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.stringField, result); + } + + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> null - default value, written when repeated + (byte) 0x0a, + (byte) 0x00, + // 2 -> "" - default value, written when repeated + (byte) 0x12, + (byte) 0x00, + // 3 -> "abcd\u3110!" + (byte) 0x1a, + (byte) 0x08, + (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, + (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21, + // 4 -> "Hi" + (byte) 0x22, + (byte) 0x02, + (byte) 0x48, (byte) 0x69, + + // 5 -> "Hi" + (byte) 0x2a, + (byte) 0x02, + (byte) 0x48, (byte) 0x69, + + // 1 -> null - default value, written when repeated + (byte) 0x0a, + (byte) 0x00, + // 2 -> "" - default value, written when repeated + (byte) 0x12, + (byte) 0x00, + // 3 -> "abcd\u3110!" + (byte) 0x1a, + (byte) 0x08, + (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, + (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21, + // 4 -> "Hi" + (byte) 0x22, + (byte) 0x02, + (byte) 0x48, (byte) 0x69, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + String[][] results = new String[4][2]; + int[] indices = new int[4]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readString(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readString(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readString(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readString(fieldId4); + break; + case (int) fieldId5: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals("", results[0][0]); + assertEquals("", results[0][1]); + assertEquals("", results[1][0]); + assertEquals("", results[1][1]); + assertEquals("abcd\u3110!", results[2][0]); + assertEquals("abcd\u3110!", results[2][1]); + assertEquals("Hi", results[3][0]); + assertEquals("Hi", results[3][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new String[0]); + testRepeatedCompat(new String[]{"", "abcd\u3110!", "Hi"}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(String[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING; + final long fieldId = fieldFlags | ((long) 141 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.stringFieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + String[] result = new String[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readString(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.stringFieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.stringFieldRepeated[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> {1} + (byte) 0x0a, + (byte) 0x01, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readString(fieldId1); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId2: + pi.readString(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readString(fieldId3); + // don't fail, length delimited is ok (represents packed booleans) + break; + case (int) fieldId6: + pi.readString(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } + +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java new file mode 100644 index 000000000000..50fc537767a4 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java @@ -0,0 +1,564 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamUInt32Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 6 -> MAX_VALUE + (byte) 0x30, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[] results = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(int val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; + final long fieldId = fieldFlags | ((long) 50 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.uint32Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.uint32Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 6 -> MAX_VALUE + (byte) 0x30, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new int[0]); + testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32; + final long fieldId = fieldFlags | ((long) 51 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.uint32FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.uint32FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.uint32FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_UINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x02, + (byte) 0x00, + (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x02, + (byte) 0x01, + (byte) 0x01, + + // 6 -> MAX_VALUE + (byte) 0x32, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 3 -> -1 + (byte) 0x1a, + (byte) 0x14, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 4 -> MIN_VALUE + (byte) 0x22, + (byte) 0x14, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 5 -> MAX_VALUE + (byte) 0x2a, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + int[][] results = new int[5][2]; + int[] indices = new int[5]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readInt(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readInt(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readInt(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readInt(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readInt(fieldId5); + break; + case (int) fieldId6: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new int[0]); + testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(int[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32; + final long fieldId = fieldFlags | ((long) 52 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.uint32FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + int[] result = new int[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readInt(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.uint32FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.uint32FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readLong(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readInt(fieldId1); + // don't fail, varint is ok + break; + case (int) fieldId2: + pi.readInt(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readInt(fieldId3); + // don't fail, length delimited is ok (represents packed uint32) + break; + case (int) fieldId6: + pi.readInt(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java new file mode 100644 index 000000000000..20969e9056a9 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java @@ -0,0 +1,641 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoStream; +import android.util.proto.WireTypeMismatchException; + +import com.android.test.protoinputstream.nano.Test; + +import com.google.protobuf.nano.MessageNano; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ProtoInputStreamUInt64Test extends TestCase { + + public void testRead() throws IOException { + testRead(0); + testRead(1); + testRead(5); + } + + private void testRead(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, not written + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 8 -> Integer.MAX_VALUE + (byte) 0x40, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> Integer.MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 6 -> Long.MIN_VALUE + (byte) 0x30, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x38, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[] results = new long[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + fail("Should never reach this"); + break; + case (int) fieldId2: + results[1] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0]); + assertEquals(1, results[1]); + assertEquals(-1, results[2]); + assertEquals(Integer.MIN_VALUE, results[3]); + assertEquals(Integer.MAX_VALUE, results[4]); + assertEquals(Long.MIN_VALUE, results[5]); + assertEquals(Long.MAX_VALUE, results[6]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testReadCompat() throws Exception { + testReadCompat(0); + testReadCompat(1); + testReadCompat(-1); + testReadCompat(Integer.MIN_VALUE); + testReadCompat(Integer.MAX_VALUE); + testReadCompat(Long.MIN_VALUE); + testReadCompat(Long.MAX_VALUE); + } + + /** + * Implementation of testReadCompat with a given value. + */ + private void testReadCompat(long val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64; + final long fieldId = fieldFlags | ((long) 60 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.uint64Field = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long result = 0; // start off with default value + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.uint64Field, result); + } + + public void testRepeated() throws IOException { + testRepeated(0); + testRepeated(1); + testRepeated(5); + } + + private void testRepeated(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> Integer.MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 6 -> Long.MIN_VALUE + (byte) 0x30, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x38, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + // 8 -> Integer.MAX_VALUE + (byte) 0x40, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 1 -> 0 - default value, written when repeated + (byte) 0x08, + (byte) 0x00, + // 2 -> 1 + (byte) 0x10, + (byte) 0x01, + // 3 -> -1 + (byte) 0x18, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 4 -> Integer.MIN_VALUE + (byte) 0x20, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + // 5 -> Integer.MAX_VALUE + (byte) 0x28, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + // 6 -> Long.MIN_VALUE + (byte) 0x30, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + // 7 -> Long.MAX_VALUE + (byte) 0x38, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testRepeatedCompat() throws Exception { + testRepeatedCompat(new long[0]); + testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testRepeatedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT64; + final long fieldId = fieldFlags | ((long) 61 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.uint64FieldRepeated = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.uint64FieldRepeated.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.uint64FieldRepeated[i], result[i]); + } + } + + public void testPacked() throws IOException { + testPacked(0); + testPacked(1); + testPacked(5); + } + + private void testPacked(int chunkSize) throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_UINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); + final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL); + final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 0 - default value, written when repeated + (byte) 0x0a, + (byte) 0x02, + (byte) 0x00, + (byte) 0x00, + // 2 -> 1 + (byte) 0x12, + (byte) 0x02, + (byte) 0x01, + (byte) 0x01, + + // 8 -> Integer.MAX_VALUE + (byte) 0x42, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 3 -> -1 + (byte) 0x1a, + (byte) 0x14, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 4 -> Integer.MIN_VALUE + (byte) 0x22, + (byte) 0x14, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, + + // 5 -> Integer.MAX_VALUE + (byte) 0x2a, + (byte) 0x0a, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, + + // 6 -> Long.MIN_VALUE + (byte) 0x32, + (byte) 0x14, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, + (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01, + + // 7 -> Long.MAX_VALUE + (byte) 0x3a, + (byte) 0x12, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); + long[][] results = new long[7][2]; + int[] indices = new int[7]; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + + switch (pi.getFieldNumber()) { + case (int) fieldId1: + results[0][indices[0]++] = pi.readLong(fieldId1); + break; + case (int) fieldId2: + results[1][indices[1]++] = pi.readLong(fieldId2); + break; + case (int) fieldId3: + results[2][indices[2]++] = pi.readLong(fieldId3); + break; + case (int) fieldId4: + results[3][indices[3]++] = pi.readLong(fieldId4); + break; + case (int) fieldId5: + results[4][indices[4]++] = pi.readLong(fieldId5); + break; + case (int) fieldId6: + results[5][indices[5]++] = pi.readLong(fieldId6); + break; + case (int) fieldId7: + results[6][indices[6]++] = pi.readLong(fieldId7); + break; + case (int) fieldId8: + // Intentionally don't read the data. Parse should continue normally + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + stream.close(); + + assertEquals(0, results[0][0]); + assertEquals(0, results[0][1]); + assertEquals(1, results[1][0]); + assertEquals(1, results[1][1]); + assertEquals(-1, results[2][0]); + assertEquals(-1, results[2][1]); + assertEquals(Integer.MIN_VALUE, results[3][0]); + assertEquals(Integer.MIN_VALUE, results[3][1]); + assertEquals(Integer.MAX_VALUE, results[4][0]); + assertEquals(Integer.MAX_VALUE, results[4][1]); + assertEquals(Long.MIN_VALUE, results[5][0]); + assertEquals(Long.MIN_VALUE, results[5][1]); + assertEquals(Long.MAX_VALUE, results[6][0]); + assertEquals(Long.MAX_VALUE, results[6][1]); + } + + /** + * Test that reading with ProtoInputStream matches, and can read the output of standard proto. + */ + public void testPackedCompat() throws Exception { + testPackedCompat(new long[0]); + testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); + } + + /** + * Implementation of testRepeatedCompat with a given value. + */ + private void testPackedCompat(long[] val) throws Exception { + final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT64; + final long fieldId = fieldFlags | ((long) 62 & 0x0ffffffffL); + + final Test.All all = new Test.All(); + all.uint64FieldPacked = val; + + final byte[] proto = MessageNano.toByteArray(all); + + final ProtoInputStream pi = new ProtoInputStream(proto); + final Test.All readback = Test.All.parseFrom(proto); + + long[] result = new long[val.length]; + int index = 0; + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pi.getFieldNumber()) { + case (int) fieldId: + result[index++] = pi.readLong(fieldId); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } + + assertEquals(readback.uint64FieldPacked.length, result.length); + for (int i = 0; i < result.length; i++) { + assertEquals(readback.uint64FieldPacked[i], result[i]); + } + } + + /** + * Test that using the wrong read method throws an exception + */ + public void testBadReadType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 -> 1 + (byte) 0x08, + (byte) 0x01, + }; + + ProtoInputStream pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readFloat(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readDouble(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readInt(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBoolean(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readBytes(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + + pi = new ProtoInputStream(protobuf); + pi.isNextField(fieldId1); + try { + pi.readString(fieldId1); + fail("Should have throw IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + // good + } + } + + /** + * Test that unexpected wrong wire types will throw an exception + */ + public void testBadWireType() throws IOException { + final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64; + + final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); + final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); + final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); + final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); + + final byte[] protobuf = new byte[]{ + // 1 : varint -> 1 + (byte) 0x08, + (byte) 0x01, + // 2 : fixed64 -> 0x1 + (byte) 0x11, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // 3 : length delimited -> { 1 } + (byte) 0x1a, + (byte) 0x01, + (byte) 0x01, + // 6 : fixed32 + (byte) 0x35, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + InputStream stream = new ByteArrayInputStream(protobuf); + final ProtoInputStream pi = new ProtoInputStream(stream); + + while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + try { + switch (pi.getFieldNumber()) { + case (int) fieldId1: + pi.readLong(fieldId1); + // don't fail, varint is ok + break; + case (int) fieldId2: + pi.readLong(fieldId2); + fail("Should have thrown a WireTypeMismatchException"); + break; + case (int) fieldId3: + pi.readLong(fieldId3); + // don't fail, length delimited is ok (represents packed uint64) + break; + case (int) fieldId6: + pi.readLong(fieldId6); + fail("Should have thrown a WireTypeMismatchException"); + break; + default: + fail("Unexpected field id " + pi.getFieldNumber()); + } + } catch (WireTypeMismatchException wtme) { + // good + } + } + stream.close(); + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java new file mode 100644 index 000000000000..cdf6ae20f370 --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.protoinputstream; + +import junit.framework.TestSuite; + +public class ProtoTests { + public static TestSuite suite() { + TestSuite suite = new TestSuite(ProtoTests.class.getName()); + + suite.addTestSuite(ProtoInputStreamDoubleTest.class); + suite.addTestSuite(ProtoInputStreamFloatTest.class); + suite.addTestSuite(ProtoInputStreamInt32Test.class); + suite.addTestSuite(ProtoInputStreamInt64Test.class); + suite.addTestSuite(ProtoInputStreamUInt32Test.class); + suite.addTestSuite(ProtoInputStreamUInt64Test.class); + suite.addTestSuite(ProtoInputStreamSInt32Test.class); + suite.addTestSuite(ProtoInputStreamSInt64Test.class); + suite.addTestSuite(ProtoInputStreamFixed32Test.class); + suite.addTestSuite(ProtoInputStreamFixed64Test.class); + suite.addTestSuite(ProtoInputStreamSFixed32Test.class); + suite.addTestSuite(ProtoInputStreamSFixed64Test.class); + suite.addTestSuite(ProtoInputStreamBoolTest.class); + suite.addTestSuite(ProtoInputStreamStringTest.class); + suite.addTestSuite(ProtoInputStreamBytesTest.class); + suite.addTestSuite(ProtoInputStreamEnumTest.class); + suite.addTestSuite(ProtoInputStreamObjectTest.class); + + return suite; + } +} diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/test.proto b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/test.proto new file mode 100644 index 000000000000..9ff1d7e2d19a --- /dev/null +++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/test.proto @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package com.android.test.protoinputstream; + +/** + * Enum that outside the scope of any classes. + */ +enum Outside { + OUTSIDE_0 = 0; + OUTSIDE_1 = 1; +}; + +/** + * Message that is recursive. + */ +message Nested { + optional int32 data = 10001; + optional Nested nested = 10002; +}; + +/** + * Message with all of the field types. + */ +message All { + /** + * Enum that is inside the scope of a class. + */ + enum Inside { + option allow_alias = true; + INSIDE_0 = 0; + INSIDE_1 = 1; + INSIDE_1A = 1; + }; + + optional double double_field = 10; + repeated double double_field_repeated = 11; + repeated double double_field_packed = 12 [packed=true]; + + optional float float_field = 20; + repeated float float_field_repeated = 21; + repeated float float_field_packed = 22 [packed=true]; + + optional int32 int32_field = 30; + repeated int32 int32_field_repeated = 31; + repeated int32 int32_field_packed = 32 [packed=true]; + + optional int64 int64_field = 40; + repeated int64 int64_field_repeated = 41; + repeated int64 int64_field_packed = 42 [packed=true]; + + optional uint32 uint32_field = 50; + repeated uint32 uint32_field_repeated = 51; + repeated uint32 uint32_field_packed = 52 [packed=true]; + + optional uint64 uint64_field = 60; + repeated uint64 uint64_field_repeated = 61; + repeated uint64 uint64_field_packed = 62 [packed=true]; + + optional sint32 sint32_field = 70; + repeated sint32 sint32_field_repeated = 71; + repeated sint32 sint32_field_packed = 72 [packed=true]; + + optional sint64 sint64_field = 80; + repeated sint64 sint64_field_repeated = 81; + repeated sint64 sint64_field_packed = 82 [packed=true]; + + optional fixed32 fixed32_field = 90; + repeated fixed32 fixed32_field_repeated = 91; + repeated fixed32 fixed32_field_packed = 92 [packed=true]; + + optional fixed64 fixed64_field = 100; + repeated fixed64 fixed64_field_repeated = 101; + repeated fixed64 fixed64_field_packed = 102 [packed=true]; + + optional sfixed32 sfixed32_field = 110; + repeated sfixed32 sfixed32_field_repeated = 111; + repeated sfixed32 sfixed32_field_packed = 112 [packed=true]; + + optional sfixed64 sfixed64_field = 120; + repeated sfixed64 sfixed64_field_repeated = 121; + repeated sfixed64 sfixed64_field_packed = 122 [packed=true]; + + optional bool bool_field = 130; + repeated bool bool_field_repeated = 131; + repeated bool bool_field_packed = 132 [packed=true]; + + optional string string_field = 140; + repeated string string_field_repeated = 141; + + optional bytes bytes_field = 150; + repeated bytes bytes_field_repeated = 151; + + optional Outside outside_field = 160; + repeated Outside outside_field_repeated = 161; + repeated Outside outside_field_packed = 162 [packed=true]; + + optional Nested nested_field = 170; + repeated Nested nested_field_repeated = 171; +}; |