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 
     33 public class ProtoInputStreamStringTest extends TestCase {
     34 
     35     public void testRead() throws IOException {
     36         testRead(0);
     37         testRead(1);
     38         testRead(5);
     39     }
     40 
     41     private void testRead(int chunkSize) throws IOException {
     42         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
     43 
     44         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
     45         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
     46         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
     47         final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
     48         final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
     49 
     50         final byte[] protobuf = new byte[]{
     51                 // 1 -> null - default value, not written
     52                 // 2 -> "" - default value, not written
     53                 // 3 -> "abcd\u3110!"
     54                 (byte) 0x1a,
     55                 (byte) 0x08,
     56                 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64,
     57                 (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21,
     58                 // 5 -> "Hi"
     59                 (byte) 0x2a,
     60                 (byte) 0x02,
     61                 (byte) 0x48, (byte) 0x69,
     62                 // 4 -> "Hi"
     63                 (byte) 0x22,
     64                 (byte) 0x02,
     65                 (byte) 0x48, (byte) 0x69,
     66         };
     67 
     68         InputStream stream = new ByteArrayInputStream(protobuf);
     69         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
     70         String[] results = new String[4];
     71         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
     72 
     73             switch (pi.getFieldNumber()) {
     74                 case (int) fieldId1:
     75                     results[0] = pi.readString(fieldId1);
     76                     break;
     77                 case (int) fieldId2:
     78                     results[1] = pi.readString(fieldId2);
     79                     break;
     80                 case (int) fieldId3:
     81                     results[2] = pi.readString(fieldId3);
     82                     break;
     83                 case (int) fieldId4:
     84                     results[3] = pi.readString(fieldId4);
     85                     break;
     86                 case (int) fieldId5:
     87                     // Intentionally don't read the data. Parse should continue normally
     88                     break;
     89                 default:
     90                     fail("Unexpected field id " + pi.getFieldNumber());
     91             }
     92         }
     93         stream.close();
     94 
     95         assertNull(results[0]);
     96         assertNull(results[1]);
     97         assertEquals("abcd\u3110!", results[2]);
     98         assertEquals("Hi", results[3]);
     99     }
    100 
    101 
    102     /**
    103      * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
    104      */
    105     public void testReadCompat() throws Exception {
    106         testReadCompat("");
    107         testReadCompat("abcd\u3110!");
    108     }
    109 
    110     /**
    111      * Implementation of testReadCompat with a given value.
    112      */
    113     private void testReadCompat(String val) throws Exception {
    114         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
    115         final long fieldId = fieldFlags | ((long) 140 & 0x0ffffffffL);
    116 
    117         final Test.All all = new Test.All();
    118         all.stringField = val;
    119 
    120         final byte[] proto = MessageNano.toByteArray(all);
    121 
    122         final ProtoInputStream pi = new ProtoInputStream(proto);
    123         final Test.All readback = Test.All.parseFrom(proto);
    124 
    125         String result = "";
    126         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    127             switch (pi.getFieldNumber()) {
    128                 case (int) fieldId:
    129                     result = pi.readString(fieldId);
    130                     break;
    131                 default:
    132                     fail("Unexpected field id " + pi.getFieldNumber());
    133             }
    134         }
    135 
    136         assertEquals(readback.stringField, result);
    137     }
    138 
    139 
    140     public void testRepeated() throws IOException {
    141         testRepeated(0);
    142         testRepeated(1);
    143         testRepeated(5);
    144     }
    145 
    146     private void testRepeated(int chunkSize) throws IOException {
    147         final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING;
    148 
    149         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    150         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
    151         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
    152         final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
    153         final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
    154 
    155         final byte[] protobuf = new byte[]{
    156                 // 1 -> null - default value, written when repeated
    157                 (byte) 0x0a,
    158                 (byte) 0x00,
    159                 // 2 -> "" - default value, written when repeated
    160                 (byte) 0x12,
    161                 (byte) 0x00,
    162                 // 3 -> "abcd\u3110!"
    163                 (byte) 0x1a,
    164                 (byte) 0x08,
    165                 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64,
    166                 (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21,
    167                 // 4 -> "Hi"
    168                 (byte) 0x22,
    169                 (byte) 0x02,
    170                 (byte) 0x48, (byte) 0x69,
    171 
    172                 // 5 -> "Hi"
    173                 (byte) 0x2a,
    174                 (byte) 0x02,
    175                 (byte) 0x48, (byte) 0x69,
    176 
    177                 // 1 -> null - default value, written when repeated
    178                 (byte) 0x0a,
    179                 (byte) 0x00,
    180                 // 2 -> "" - default value, written when repeated
    181                 (byte) 0x12,
    182                 (byte) 0x00,
    183                 // 3 -> "abcd\u3110!"
    184                 (byte) 0x1a,
    185                 (byte) 0x08,
    186                 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64,
    187                 (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21,
    188                 // 4 -> "Hi"
    189                 (byte) 0x22,
    190                 (byte) 0x02,
    191                 (byte) 0x48, (byte) 0x69,
    192         };
    193 
    194         InputStream stream = new ByteArrayInputStream(protobuf);
    195         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
    196         String[][] results = new String[4][2];
    197         int[] indices = new int[4];
    198         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    199 
    200             switch (pi.getFieldNumber()) {
    201                 case (int) fieldId1:
    202                     results[0][indices[0]++] = pi.readString(fieldId1);
    203                     break;
    204                 case (int) fieldId2:
    205                     results[1][indices[1]++] = pi.readString(fieldId2);
    206                     break;
    207                 case (int) fieldId3:
    208                     results[2][indices[2]++] = pi.readString(fieldId3);
    209                     break;
    210                 case (int) fieldId4:
    211                     results[3][indices[3]++] = pi.readString(fieldId4);
    212                     break;
    213                 case (int) fieldId5:
    214                     // Intentionally don't read the data. Parse should continue normally
    215                     break;
    216                 default:
    217                     fail("Unexpected field id " + pi.getFieldNumber());
    218             }
    219         }
    220         stream.close();
    221 
    222 
    223         assertEquals("", results[0][0]);
    224         assertEquals("", results[0][1]);
    225         assertEquals("", results[1][0]);
    226         assertEquals("", results[1][1]);
    227         assertEquals("abcd\u3110!", results[2][0]);
    228         assertEquals("abcd\u3110!", results[2][1]);
    229         assertEquals("Hi", results[3][0]);
    230         assertEquals("Hi", results[3][1]);
    231     }
    232 
    233     /**
    234      * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
    235      */
    236     public void testRepeatedCompat() throws Exception {
    237         testRepeatedCompat(new String[0]);
    238         testRepeatedCompat(new String[]{"", "abcd\u3110!", "Hi"});
    239     }
    240 
    241     /**
    242      * Implementation of testRepeatedCompat with a given value.
    243      */
    244     private void testRepeatedCompat(String[] val) throws Exception {
    245         final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING;
    246         final long fieldId = fieldFlags | ((long) 141 & 0x0ffffffffL);
    247 
    248         final Test.All all = new Test.All();
    249         all.stringFieldRepeated = val;
    250 
    251         final byte[] proto = MessageNano.toByteArray(all);
    252 
    253         final ProtoInputStream pi = new ProtoInputStream(proto);
    254         final Test.All readback = Test.All.parseFrom(proto);
    255 
    256         String[] result = new String[val.length];
    257         int index = 0;
    258         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    259             switch (pi.getFieldNumber()) {
    260                 case (int) fieldId:
    261                     result[index++] = pi.readString(fieldId);
    262                     break;
    263                 default:
    264                     fail("Unexpected field id " + pi.getFieldNumber());
    265             }
    266         }
    267 
    268         assertEquals(readback.stringFieldRepeated.length, result.length);
    269         for (int i = 0; i < result.length; i++) {
    270             assertEquals(readback.stringFieldRepeated[i], result[i]);
    271         }
    272     }
    273 
    274     /**
    275      * Test that using the wrong read method throws an exception
    276      */
    277     public void testBadReadType() throws IOException {
    278         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
    279 
    280         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    281 
    282         final byte[] protobuf = new byte[]{
    283                 // 1 -> {1}
    284                 (byte) 0x0a,
    285                 (byte) 0x01,
    286                 (byte) 0x01,
    287         };
    288 
    289         ProtoInputStream pi = new ProtoInputStream(protobuf);
    290         pi.isNextField(fieldId1);
    291         try {
    292             pi.readFloat(fieldId1);
    293             fail("Should have throw IllegalArgumentException");
    294         } catch (IllegalArgumentException iae) {
    295             // good
    296         }
    297 
    298         pi = new ProtoInputStream(protobuf);
    299         pi.isNextField(fieldId1);
    300         try {
    301             pi.readDouble(fieldId1);
    302             fail("Should have throw IllegalArgumentException");
    303         } catch (IllegalArgumentException iae) {
    304             // good
    305         }
    306 
    307         pi = new ProtoInputStream(protobuf);
    308         pi.isNextField(fieldId1);
    309         try {
    310             pi.readInt(fieldId1);
    311             fail("Should have throw IllegalArgumentException");
    312         } catch (IllegalArgumentException iae) {
    313             // good
    314         }
    315 
    316         pi = new ProtoInputStream(protobuf);
    317         pi.isNextField(fieldId1);
    318         try {
    319             pi.readLong(fieldId1);
    320             fail("Should have throw IllegalArgumentException");
    321         } catch (IllegalArgumentException iae) {
    322             // good
    323         }
    324 
    325         pi = new ProtoInputStream(protobuf);
    326         pi.isNextField(fieldId1);
    327         try {
    328             pi.readBytes(fieldId1);
    329             fail("Should have throw IllegalArgumentException");
    330         } catch (IllegalArgumentException iae) {
    331             // good
    332         }
    333 
    334         pi = new ProtoInputStream(protobuf);
    335         pi.isNextField(fieldId1);
    336         try {
    337             pi.readBoolean(fieldId1);
    338             fail("Should have throw IllegalArgumentException");
    339         } catch (IllegalArgumentException iae) {
    340             // good
    341         }
    342     }
    343 
    344     /**
    345      * Test that unexpected wrong wire types will throw an exception
    346      */
    347     public void testBadWireType() throws IOException {
    348         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
    349 
    350         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    351         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
    352         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
    353         final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
    354 
    355         final byte[] protobuf = new byte[]{
    356                 // 1 : varint -> 1
    357                 (byte) 0x08,
    358                 (byte) 0x01,
    359                 // 2 : fixed64 -> 0x1
    360                 (byte) 0x11,
    361                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    362                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    363                 // 3 : length delimited -> { 1 }
    364                 (byte) 0x1a,
    365                 (byte) 0x01,
    366                 (byte) 0x01,
    367                 // 6 : fixed32
    368                 (byte) 0x35,
    369                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    370         };
    371 
    372         InputStream stream = new ByteArrayInputStream(protobuf);
    373         final ProtoInputStream pi = new ProtoInputStream(stream);
    374 
    375         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    376             try {
    377                 switch (pi.getFieldNumber()) {
    378                     case (int) fieldId1:
    379                         pi.readString(fieldId1);
    380                         fail("Should have thrown a WireTypeMismatchException");
    381                         break;
    382                     case (int) fieldId2:
    383                         pi.readString(fieldId2);
    384                         fail("Should have thrown a WireTypeMismatchException");
    385                         break;
    386                     case (int) fieldId3:
    387                         pi.readString(fieldId3);
    388                         // don't fail, length delimited is ok (represents packed booleans)
    389                         break;
    390                     case (int) fieldId6:
    391                         pi.readString(fieldId6);
    392                         fail("Should have thrown a WireTypeMismatchException");
    393                         break;
    394                     default:
    395                         fail("Unexpected field id " + pi.getFieldNumber());
    396                 }
    397             } catch (WireTypeMismatchException wtme) {
    398                 // good
    399             }
    400         }
    401         stream.close();
    402     }
    403 
    404 }
    405