Home | History | Annotate | Download | only in protoinputstream
      1 /*
      2  * Copyright (C) 2018 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 junit.framework.TestCase;
     24 
     25 import java.io.ByteArrayInputStream;
     26 import java.io.IOException;
     27 import java.io.InputStream;
     28 
     29 public class ProtoInputStreamObjectTest extends TestCase {
     30 
     31 
     32     class SimpleObject {
     33         public char mChar;
     34         public char mLargeChar;
     35         public String mString;
     36         public SimpleObject mNested;
     37 
     38         void parseProto(ProtoInputStream pi) throws IOException {
     39             final long uintFieldFlags =
     40                     ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
     41             final long stringFieldFlags =
     42                     ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
     43             final long messageFieldFlags =
     44                     ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
     45             final long charId = uintFieldFlags | ((long) 2 & 0x0ffffffffL);
     46             final long largeCharId = uintFieldFlags | ((long) 5000 & 0x0ffffffffL);
     47             final long stringId = stringFieldFlags | ((long) 4 & 0x0ffffffffL);
     48             final long nestedId = messageFieldFlags | ((long) 5 & 0x0ffffffffL);
     49 
     50             while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
     51                 switch (pi.getFieldNumber()) {
     52                     case (int) charId:
     53                         mChar = (char) pi.readInt(charId);
     54                         break;
     55                     case (int) largeCharId:
     56                         mLargeChar = (char) pi.readInt(largeCharId);
     57                         break;
     58                     case (int) stringId:
     59                         mString = pi.readString(stringId);
     60                         break;
     61                     case (int) nestedId:
     62                         long token = pi.start(nestedId);
     63                         mNested = new SimpleObject();
     64                         mNested.parseProto(pi);
     65                         pi.end(token);
     66                         break;
     67                     default:
     68                         fail("Unexpected field id " + pi.getFieldNumber());
     69                 }
     70             }
     71         }
     72 
     73     }
     74 
     75     /**
     76      * Test reading an object with one char in it.
     77      */
     78     public void testObjectOneChar() throws IOException {
     79         testObjectOneChar(0);
     80         testObjectOneChar(1);
     81         testObjectOneChar(5);
     82     }
     83 
     84     /**
     85      * Implementation of testObjectOneChar for a given chunkSize.
     86      */
     87     private void testObjectOneChar(int chunkSize) throws IOException {
     88         final long messageFieldFlags =
     89                 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
     90 
     91         final long messageId1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL);
     92         final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
     93 
     94         final byte[] protobuf = new byte[]{
     95                 // Message 2 : { char 2 : 'c' }
     96                 (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x63,
     97                 // Message 1 : { char 2 : 'b' }
     98                 (byte) 0x0a, (byte) 0x02, (byte) 0x10, (byte) 0x62,
     99         };
    100 
    101         InputStream stream = new ByteArrayInputStream(protobuf);
    102         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
    103 
    104         SimpleObject result = null;
    105 
    106         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    107             switch (pi.getFieldNumber()) {
    108                 case (int) messageId1:
    109                     final long token = pi.start(messageId1);
    110                     result = new SimpleObject();
    111                     result.parseProto(pi);
    112                     pi.end(token);
    113                     break;
    114                 case (int) messageId2:
    115                     // Intentionally don't read the data. Parse should continue normally
    116                     break;
    117                 default:
    118                     fail("Unexpected field id " + pi.getFieldNumber());
    119             }
    120         }
    121         stream.close();
    122 
    123         assertNotNull(result);
    124         assertEquals('b', result.mChar);
    125     }
    126 
    127     /**
    128      * Test reading an object with one multibyte unicode char in it.
    129      */
    130     public void testObjectOneLargeChar() throws IOException {
    131         testObjectOneLargeChar(0);
    132         testObjectOneLargeChar(1);
    133         testObjectOneLargeChar(5);
    134     }
    135 
    136     /**
    137      * Implementation of testObjectOneLargeChar for a given chunkSize.
    138      */
    139     private void testObjectOneLargeChar(int chunkSize) throws IOException {
    140         final long messageFieldFlags =
    141                 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
    142 
    143         final long messageId1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL);
    144         final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
    145 
    146         final byte[] protobuf = new byte[]{
    147                 // Message 2 : { char 5000 : '\u3110' }
    148                 (byte) 0x12, (byte) 0x05, (byte) 0xc0, (byte) 0xb8,
    149                 (byte) 0x02, (byte) 0x90, (byte) 0x62,
    150                 // Message 1 : { char 5000 : '\u3110' }
    151                 (byte) 0x0a, (byte) 0x05, (byte) 0xc0, (byte) 0xb8,
    152                 (byte) 0x02, (byte) 0x90, (byte) 0x62,
    153         };
    154 
    155         InputStream stream = new ByteArrayInputStream(protobuf);
    156         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
    157 
    158         SimpleObject result = null;
    159 
    160         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    161             switch (pi.getFieldNumber()) {
    162                 case (int) messageId1:
    163                     final long token = pi.start(messageId1);
    164                     result = new SimpleObject();
    165                     result.parseProto(pi);
    166                     pi.end(token);
    167                     break;
    168                 case (int) messageId2:
    169                     // Intentionally don't read the data. Parse should continue normally
    170                     break;
    171                 default:
    172                     fail("Unexpected field id " + pi.getFieldNumber());
    173             }
    174         }
    175         stream.close();
    176 
    177         assertNotNull(result);
    178         assertEquals('\u3110', result.mLargeChar);
    179     }
    180 
    181     /**
    182      * Test reading a char, then an object, then a char.
    183      */
    184     public void testObjectAndTwoChars() throws IOException {
    185         testObjectAndTwoChars(0);
    186         testObjectAndTwoChars(1);
    187         testObjectAndTwoChars(5);
    188     }
    189 
    190     /**
    191      * Implementation of testObjectAndTwoChars for a given chunkSize.
    192      */
    193     private void testObjectAndTwoChars(int chunkSize) throws IOException  {
    194         final long uintFieldFlags =
    195                 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
    196         final long messageFieldFlags =
    197                 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
    198 
    199         final long charId1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL);
    200         final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
    201         final long charId4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL);
    202 
    203         final byte[] protobuf = new byte[]{
    204                 // 1 -> 'a'
    205                 (byte) 0x08, (byte) 0x61,
    206                 // Message 1 : { char 2 : 'b' }
    207                 (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x62,
    208                 // 4 -> 'c'
    209                 (byte) 0x20, (byte) 0x63,
    210         };
    211 
    212         InputStream stream = new ByteArrayInputStream(protobuf);
    213         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
    214 
    215         SimpleObject obj = null;
    216         char char1 = '\0';
    217         char char4 = '\0';
    218 
    219         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    220             switch (pi.getFieldNumber()) {
    221                 case (int) charId1:
    222                     char1 = (char) pi.readInt(charId1);
    223                     break;
    224                 case (int) messageId2:
    225                     final long token = pi.start(messageId2);
    226                     obj = new SimpleObject();
    227                     obj.parseProto(pi);
    228                     pi.end(token);
    229                     break;
    230                 case (int) charId4:
    231                     char4 = (char) pi.readInt(charId4);
    232                     break;
    233                 default:
    234                     fail("Unexpected field id " + pi.getFieldNumber());
    235             }
    236         }
    237         stream.close();
    238 
    239         assertEquals('a', char1);
    240         assertNotNull(obj);
    241         assertEquals('b', obj.mChar);
    242         assertEquals('c', char4);
    243     }
    244 
    245     /**
    246      * Test reading a char, then an object with an int and a string in it, then a char.
    247      */
    248     public void testComplexObject() throws IOException {
    249         testComplexObject(0);
    250         testComplexObject(1);
    251         testComplexObject(5);
    252     }
    253 
    254     /**
    255      * Implementation of testComplexObject for a given chunkSize.
    256      */
    257     private void testComplexObject(int chunkSize) throws IOException  {
    258         final long uintFieldFlags =
    259                 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
    260         final long messageFieldFlags =
    261                 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
    262 
    263         final long charId1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL);
    264         final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
    265         final long charId4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL);
    266 
    267         final byte[] protobuf = new byte[]{
    268                 // 1 -> 'x'
    269                 (byte) 0x08, (byte) 0x78,
    270                 // begin object 2
    271                 (byte) 0x12, (byte) 0x10,
    272                 // 2 -> 'y'
    273                 (byte) 0x10, (byte) 0x79,
    274                 // 4 -> "abcdefghijkl"
    275                 (byte) 0x22, (byte) 0x0c,
    276                 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66,
    277                 (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6a, (byte) 0x6b, (byte) 0x6c,
    278                 // 4 -> 'z'
    279                 (byte) 0x20, (byte) 0x7a,
    280         };
    281 
    282         InputStream stream = new ByteArrayInputStream(protobuf);
    283         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
    284 
    285         SimpleObject obj = null;
    286         char char1 = '\0';
    287         char char4 = '\0';
    288 
    289         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    290             switch (pi.getFieldNumber()) {
    291                 case (int) charId1:
    292                     char1 = (char) pi.readInt(charId1);
    293                     break;
    294                 case (int) messageId2:
    295                     final long token = pi.start(messageId2);
    296                     obj = new SimpleObject();
    297                     obj.parseProto(pi);
    298                     pi.end(token);
    299                     break;
    300                 case (int) charId4:
    301                     char4 = (char) pi.readInt(charId4);
    302                     break;
    303                 default:
    304                     fail("Unexpected field id " + pi.getFieldNumber());
    305             }
    306         }
    307         stream.close();
    308 
    309         assertEquals('x', char1);
    310         assertNotNull(obj);
    311         assertEquals('y', obj.mChar);
    312         assertEquals("abcdefghijkl", obj.mString);
    313         assertEquals('z', char4);
    314     }
    315 
    316     /**
    317      * Test reading 3 levels deep of objects.
    318      */
    319     public void testDeepObjects() throws IOException {
    320         testDeepObjects(0);
    321         testDeepObjects(1);
    322         testDeepObjects(5);
    323     }
    324 
    325     /**
    326      * Implementation of testDeepObjects for a given chunkSize.
    327      */
    328     private void testDeepObjects(int chunkSize) throws IOException  {
    329         final long messageFieldFlags =
    330                 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
    331         final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
    332 
    333         final byte[] protobuf = new byte[]{
    334                 // begin object id 2
    335                 (byte) 0x12, (byte) 0x1a,
    336                 // 2 -> 'a'
    337                 (byte) 0x10, (byte) 0x61,
    338                 // begin nested object id 5
    339                 (byte) 0x2a, (byte) 0x15,
    340                 // 5000 -> '\u3110'
    341                 (byte) 0xc0, (byte) 0xb8,
    342                 (byte) 0x02, (byte) 0x90, (byte) 0x62,
    343                 // begin nested object id 5
    344                 (byte) 0x2a, (byte) 0x0e,
    345                 // 4 -> "abcdefghijkl"
    346                 (byte) 0x22, (byte) 0x0c,
    347                 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66,
    348                 (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6a, (byte) 0x6b, (byte) 0x6c,
    349         };
    350 
    351         InputStream stream = new ByteArrayInputStream(protobuf);
    352         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
    353 
    354         SimpleObject obj = null;
    355 
    356         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    357             switch (pi.getFieldNumber()) {
    358                 case (int) messageId2:
    359                     final long token = pi.start(messageId2);
    360                     obj = new SimpleObject();
    361                     obj.parseProto(pi);
    362                     pi.end(token);
    363                     break;
    364                 default:
    365                     fail("Unexpected field id " + pi.getFieldNumber());
    366             }
    367         }
    368         stream.close();
    369 
    370         assertNotNull(obj);
    371         assertEquals('a', obj.mChar);
    372         assertNotNull(obj.mNested);
    373         assertEquals('\u3110', obj.mNested.mLargeChar);
    374         assertNotNull(obj.mNested.mNested);
    375         assertEquals("abcdefghijkl", obj.mNested.mNested.mString);
    376     }
    377 
    378     /**
    379      * Test that using the wrong read method throws an exception
    380      */
    381     public void testBadReadType() throws IOException {
    382         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
    383 
    384         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    385 
    386         final byte[] protobuf = new byte[]{
    387                 // 1 -> {1}
    388                 (byte) 0x0a,
    389                 (byte) 0x01,
    390                 (byte) 0x01,
    391         };
    392 
    393         ProtoInputStream pi = new ProtoInputStream(protobuf);
    394         pi.isNextField(fieldId1);
    395         try {
    396             pi.readFloat(fieldId1);
    397             fail("Should have throw IllegalArgumentException");
    398         } catch (IllegalArgumentException iae) {
    399             // good
    400         }
    401 
    402         pi = new ProtoInputStream(protobuf);
    403         pi.isNextField(fieldId1);
    404         try {
    405             pi.readDouble(fieldId1);
    406             fail("Should have throw IllegalArgumentException");
    407         } catch (IllegalArgumentException iae) {
    408             // good
    409         }
    410 
    411         pi = new ProtoInputStream(protobuf);
    412         pi.isNextField(fieldId1);
    413         try {
    414             pi.readInt(fieldId1);
    415             fail("Should have throw IllegalArgumentException");
    416         } catch (IllegalArgumentException iae) {
    417             // good
    418         }
    419 
    420         pi = new ProtoInputStream(protobuf);
    421         pi.isNextField(fieldId1);
    422         try {
    423             pi.readLong(fieldId1);
    424             fail("Should have throw IllegalArgumentException");
    425         } catch (IllegalArgumentException iae) {
    426             // good
    427         }
    428 
    429         pi = new ProtoInputStream(protobuf);
    430         pi.isNextField(fieldId1);
    431         try {
    432             pi.readBoolean(fieldId1);
    433             fail("Should have throw IllegalArgumentException");
    434         } catch (IllegalArgumentException iae) {
    435             // good
    436         }
    437 
    438         pi = new ProtoInputStream(protobuf);
    439         pi.isNextField(fieldId1);
    440         try {
    441             pi.readString(fieldId1);
    442             fail("Should have throw IllegalArgumentException");
    443         } catch (IllegalArgumentException iae) {
    444             // good
    445         }
    446     }
    447 
    448     /**
    449      * Test that unexpected wrong wire types will throw an exception
    450      */
    451     public void testBadWireType() throws IOException {
    452         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
    453 
    454         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    455         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
    456         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
    457         final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
    458 
    459         final byte[] protobuf = new byte[]{
    460                 // 1 : varint -> 1
    461                 (byte) 0x08,
    462                 (byte) 0x01,
    463                 // 2 : fixed64 -> 0x1
    464                 (byte) 0x11,
    465                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    466                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    467                 // 3 : length delimited -> { 1 }
    468                 (byte) 0x1a,
    469                 (byte) 0x01,
    470                 (byte) 0x01,
    471                 // 6 : fixed32
    472                 (byte) 0x35,
    473                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    474         };
    475 
    476         InputStream stream = new ByteArrayInputStream(protobuf);
    477         final ProtoInputStream pi = new ProtoInputStream(stream);
    478 
    479         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    480             try {
    481                 switch (pi.getFieldNumber()) {
    482                     case (int) fieldId1:
    483                         pi.readBytes(fieldId1);
    484                         fail("Should have thrown a WireTypeMismatchException");
    485                         break;
    486                     case (int) fieldId2:
    487                         pi.readBytes(fieldId2);
    488                         fail("Should have thrown a WireTypeMismatchException");
    489                         break;
    490                     case (int) fieldId3:
    491                         pi.readBytes(fieldId3);
    492                         // don't fail, length delimited is ok
    493                         break;
    494                     case (int) fieldId6:
    495                         pi.readBytes(fieldId6);
    496                         fail("Should have thrown a WireTypeMismatchException");
    497                         break;
    498                     default:
    499                         fail("Unexpected field id " + pi.getFieldNumber());
    500                 }
    501             } catch (WireTypeMismatchException wtme) {
    502                 // good
    503             }
    504         }
    505         stream.close();
    506     }
    507 }
    508