Home | History | Annotate | Download | only in protoinputstream
      1 /*
      2  * Copyright (C) 2019 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.test.protoinputstream;
     18 
     19 import android.util.proto.ProtoInputStream;
     20 import android.util.proto.ProtoStream;
     21 import android.util.proto.WireTypeMismatchException;
     22 
     23 import com.android.test.protoinputstream.nano.Test;
     24 
     25 import com.google.protobuf.nano.MessageNano;
     26 
     27 import junit.framework.TestCase;
     28 
     29 import java.io.ByteArrayInputStream;
     30 import java.io.IOException;
     31 import java.io.InputStream;
     32 import java.util.Arrays;
     33 
     34 public class ProtoInputStreamBytesTest extends TestCase {
     35 
     36     public void testRead() throws IOException {
     37         testRead(0);
     38         testRead(1);
     39         testRead(5);
     40     }
     41 
     42     private void testRead(int chunkSize) throws IOException {
     43         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
     44 
     45         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
     46         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
     47         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
     48         final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
     49         final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
     50 
     51         final byte[] protobuf = new byte[]{
     52                 // 1 -> null - default value, written when repeated
     53                 (byte) 0x0a,
     54                 (byte) 0x00,
     55                 // 2 -> { } - default value, written when repeated
     56                 (byte) 0x12,
     57                 (byte) 0x00,
     58                 // 5 -> { 0, 1, 2, 3, 4 }
     59                 (byte) 0x2a,
     60                 (byte) 0x05,
     61                 (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
     62                 // 3 -> { 0, 1, 2, 3, 4, 5 }
     63                 (byte) 0x1a,
     64                 (byte) 0x06,
     65                 (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
     66                 // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc }
     67                 (byte) 0x22,
     68                 (byte) 0x04,
     69                 (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc,
     70         };
     71 
     72         InputStream stream = new ByteArrayInputStream(protobuf);
     73         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
     74         byte[][] results = new byte[4][];
     75         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
     76 
     77             switch (pi.getFieldNumber()) {
     78                 case (int) fieldId1:
     79                     results[0] = pi.readBytes(fieldId1);
     80                     break;
     81                 case (int) fieldId2:
     82                     results[1] = pi.readBytes(fieldId2);
     83                     break;
     84                 case (int) fieldId3:
     85                     results[2] = pi.readBytes(fieldId3);
     86                     break;
     87                 case (int) fieldId4:
     88                     results[3] = pi.readBytes(fieldId4);
     89                     break;
     90                 case (int) fieldId5:
     91                     // Intentionally don't read the data. Parse should continue normally
     92                     break;
     93                 default:
     94                     fail("Unexpected field id " + pi.getFieldNumber());
     95             }
     96         }
     97         stream.close();
     98 
     99         assertTrue("Expected: []  Actual: " + Arrays.toString(results[0]),
    100                 Arrays.equals(new byte[]{}, results[0]));
    101         assertTrue("Expected: []  Actual: " + Arrays.toString(results[1]),
    102                 Arrays.equals(new byte[]{}, results[1]));
    103         assertTrue("Expected: [0, 1, 2, 3, 4, 5]  Actual: " + Arrays.toString(results[2]),
    104                 Arrays.equals(new byte[]{0, 1, 2, 3, 4, 5}, results[2]));
    105         assertTrue("Expected: [-1, -2, -3, -4]  Actual: " + Arrays.toString(results[3]),
    106                 Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc},
    107                         results[3]));
    108     }
    109 
    110 
    111     /**
    112      * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
    113      */
    114     public void testReadCompat() throws Exception {
    115         testReadCompat(new byte[0]);
    116         testReadCompat(new byte[]{1, 2, 3, 4});
    117         testReadCompat(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc});
    118     }
    119 
    120     /**
    121      * Implementation of testReadCompat with a given value.
    122      */
    123     private void testReadCompat(byte[] val) throws Exception {
    124         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
    125         final long fieldId = fieldFlags | ((long) 150 & 0x0ffffffffL);
    126 
    127         final Test.All all = new Test.All();
    128         all.bytesField = val;
    129 
    130         final byte[] proto = MessageNano.toByteArray(all);
    131 
    132         final ProtoInputStream pi = new ProtoInputStream(proto);
    133         final Test.All readback = Test.All.parseFrom(proto);
    134 
    135         byte[] result = new byte[val.length];
    136         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    137             switch (pi.getFieldNumber()) {
    138                 case (int) fieldId:
    139                     result = pi.readBytes(fieldId);
    140                     break;
    141                 default:
    142                     fail("Unexpected field id " + pi.getFieldNumber());
    143             }
    144         }
    145 
    146         assertEquals(readback.bytesField.length, result.length);
    147         for (int i = 0; i < result.length; i++) {
    148             assertEquals(readback.bytesField[i], result[i]);
    149         }
    150     }
    151 
    152 
    153     public void testRepeated() throws IOException {
    154         testRepeated(0);
    155         testRepeated(1);
    156         testRepeated(5);
    157     }
    158 
    159     private void testRepeated(int chunkSize) throws IOException {
    160         final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BYTES;
    161 
    162         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    163         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
    164         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
    165         final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
    166         final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
    167 
    168         final byte[] protobuf = new byte[]{
    169                 // 1 -> null - default value, written when repeated
    170                 (byte) 0x0a,
    171                 (byte) 0x00,
    172                 // 2 -> { } - default value, written when repeated
    173                 (byte) 0x12,
    174                 (byte) 0x00,
    175                 // 3 -> { 0, 1, 2, 3, 4, 5 }
    176                 (byte) 0x1a,
    177                 (byte) 0x06,
    178                 (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
    179                 // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc }
    180                 (byte) 0x22,
    181                 (byte) 0x04,
    182                 (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc,
    183 
    184                 // 5 -> { 0, 1, 2, 3, 4}
    185                 (byte) 0x2a,
    186                 (byte) 0x05,
    187                 (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
    188 
    189                 // 1 -> null - default value, written when repeated
    190                 (byte) 0x0a,
    191                 (byte) 0x00,
    192                 // 2 -> { } - default value, written when repeated
    193                 (byte) 0x12,
    194                 (byte) 0x00,
    195                 // 3 -> { 0, 1, 2, 3, 4, 5 }
    196                 (byte) 0x1a,
    197                 (byte) 0x06,
    198                 (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
    199                 // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc }
    200                 (byte) 0x22,
    201                 (byte) 0x04,
    202                 (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc,
    203         };
    204 
    205         InputStream stream = new ByteArrayInputStream(protobuf);
    206         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
    207         byte[][][] results = new byte[4][2][];
    208         int[] indices = new int[4];
    209         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    210 
    211             switch (pi.getFieldNumber()) {
    212                 case (int) fieldId1:
    213                     results[0][indices[0]++] = pi.readBytes(fieldId1);
    214                     break;
    215                 case (int) fieldId2:
    216                     results[1][indices[1]++] = pi.readBytes(fieldId2);
    217                     break;
    218                 case (int) fieldId3:
    219                     results[2][indices[2]++] = pi.readBytes(fieldId3);
    220                     break;
    221                 case (int) fieldId4:
    222                     results[3][indices[3]++] = pi.readBytes(fieldId4);
    223                     break;
    224                 case (int) fieldId5:
    225                     // Intentionally don't read the data. Parse should continue normally
    226                     break;
    227                 default:
    228                     fail("Unexpected field id " + pi.getFieldNumber());
    229             }
    230         }
    231         stream.close();
    232 
    233         assert (Arrays.equals(new byte[]{}, results[0][0]));
    234         assert (Arrays.equals(new byte[]{}, results[0][1]));
    235         assert (Arrays.equals(new byte[]{}, results[1][0]));
    236         assert (Arrays.equals(new byte[]{}, results[1][1]));
    237         assert (Arrays.equals(new byte[]{1, 2, 3, 4}, results[2][0]));
    238         assert (Arrays.equals(new byte[]{1, 2, 3, 4}, results[2][1]));
    239         assert (Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc},
    240                 results[3][0]));
    241         assert (Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc},
    242                 results[3][1]));
    243     }
    244 
    245     /**
    246      * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
    247      */
    248     public void testRepeatedCompat() throws Exception {
    249         testRepeatedCompat(new byte[0][]);
    250         testRepeatedCompat(new byte[][]{
    251                 new byte[0],
    252                 new byte[]{1, 2, 3, 4},
    253                 new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc}
    254         });
    255     }
    256 
    257     /**
    258      * Implementation of testRepeatedCompat with a given value.
    259      */
    260     private void testRepeatedCompat(byte[][] val) throws Exception {
    261         final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BYTES;
    262         final long fieldId = fieldFlags | ((long) 151 & 0x0ffffffffL);
    263 
    264         final Test.All all = new Test.All();
    265         all.bytesFieldRepeated = val;
    266 
    267         final byte[] proto = MessageNano.toByteArray(all);
    268 
    269         final ProtoInputStream pi = new ProtoInputStream(proto);
    270         final Test.All readback = Test.All.parseFrom(proto);
    271 
    272         byte[][] result = new byte[val.length][]; // start off with default value
    273         int index = 0;
    274         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    275             switch (pi.getFieldNumber()) {
    276                 case (int) fieldId:
    277                     result[index++] = pi.readBytes(fieldId);
    278                     break;
    279                 default:
    280                     fail("Unexpected field id " + pi.getFieldNumber());
    281             }
    282         }
    283 
    284         assertEquals(readback.bytesFieldRepeated.length, result.length);
    285         for (int i = 0; i < result.length; i++) {
    286             assertEquals(readback.bytesFieldRepeated[i].length, result[i].length);
    287             for (int j = 0; j < result[i].length; j++) {
    288                 assertEquals(readback.bytesFieldRepeated[i][j], result[i][j]);
    289             }
    290         }
    291     }
    292 
    293     /**
    294      * Test that using the wrong read method throws an exception
    295      */
    296     public void testBadReadType() throws IOException {
    297         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
    298 
    299         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    300 
    301         final byte[] protobuf = new byte[]{
    302                 // 1 -> {1}
    303                 (byte) 0x0a,
    304                 (byte) 0x01,
    305                 (byte) 0x01,
    306         };
    307 
    308         ProtoInputStream pi = new ProtoInputStream(protobuf);
    309         pi.isNextField(fieldId1);
    310         try {
    311             pi.readFloat(fieldId1);
    312             fail("Should have throw IllegalArgumentException");
    313         } catch (IllegalArgumentException iae) {
    314             // good
    315         }
    316 
    317         pi = new ProtoInputStream(protobuf);
    318         pi.isNextField(fieldId1);
    319         try {
    320             pi.readDouble(fieldId1);
    321             fail("Should have throw IllegalArgumentException");
    322         } catch (IllegalArgumentException iae) {
    323             // good
    324         }
    325 
    326         pi = new ProtoInputStream(protobuf);
    327         pi.isNextField(fieldId1);
    328         try {
    329             pi.readInt(fieldId1);
    330             fail("Should have throw IllegalArgumentException");
    331         } catch (IllegalArgumentException iae) {
    332             // good
    333         }
    334 
    335         pi = new ProtoInputStream(protobuf);
    336         pi.isNextField(fieldId1);
    337         try {
    338             pi.readLong(fieldId1);
    339             fail("Should have throw IllegalArgumentException");
    340         } catch (IllegalArgumentException iae) {
    341             // good
    342         }
    343 
    344         pi = new ProtoInputStream(protobuf);
    345         pi.isNextField(fieldId1);
    346         try {
    347             pi.readBoolean(fieldId1);
    348             fail("Should have throw IllegalArgumentException");
    349         } catch (IllegalArgumentException iae) {
    350             // good
    351         }
    352 
    353         pi = new ProtoInputStream(protobuf);
    354         pi.isNextField(fieldId1);
    355         try {
    356             pi.readString(fieldId1);
    357             fail("Should have throw IllegalArgumentException");
    358         } catch (IllegalArgumentException iae) {
    359             // good
    360         }
    361     }
    362 
    363     /**
    364      * Test that unexpected wrong wire types will throw an exception
    365      */
    366     public void testBadWireType() throws IOException {
    367         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
    368 
    369         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    370         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
    371         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
    372         final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
    373 
    374         final byte[] protobuf = new byte[]{
    375                 // 1 : varint -> 1
    376                 (byte) 0x08,
    377                 (byte) 0x01,
    378                 // 2 : fixed64 -> 0x1
    379                 (byte) 0x11,
    380                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    381                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    382                 // 3 : length delimited -> { 1 }
    383                 (byte) 0x1a,
    384                 (byte) 0x01,
    385                 (byte) 0x01,
    386                 // 6 : fixed32
    387                 (byte) 0x35,
    388                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    389         };
    390 
    391         InputStream stream = new ByteArrayInputStream(protobuf);
    392         final ProtoInputStream pi = new ProtoInputStream(stream);
    393 
    394         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    395             try {
    396                 switch (pi.getFieldNumber()) {
    397                     case (int) fieldId1:
    398                         pi.readBytes(fieldId1);
    399                         fail("Should have thrown a WireTypeMismatchException");
    400                         break;
    401                     case (int) fieldId2:
    402                         pi.readBytes(fieldId2);
    403                         fail("Should have thrown a WireTypeMismatchException");
    404                         break;
    405                     case (int) fieldId3:
    406                         pi.readBytes(fieldId3);
    407                         // don't fail, length delimited is ok
    408                         break;
    409                     case (int) fieldId6:
    410                         pi.readBytes(fieldId6);
    411                         fail("Should have thrown a WireTypeMismatchException");
    412                         break;
    413                     default:
    414                         fail("Unexpected field id " + pi.getFieldNumber());
    415                 }
    416             } catch (WireTypeMismatchException wtme) {
    417                 // good
    418             }
    419         }
    420         stream.close();
    421     }
    422 
    423 }
    424