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 ProtoInputStreamSFixed32Test 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_SFIXED32;
     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         final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
     50 
     51         final byte[] protobuf = new byte[]{
     52                 // 1 -> 0 - default value, not written
     53                 // 2 -> 1
     54                 (byte) 0x15,
     55                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
     56                 // 6 -> 1
     57                 (byte) 0x35,
     58                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
     59                 // 3 -> -1
     60                 (byte) 0x1d,
     61                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
     62                 // 4 -> Integer.MIN_VALUE
     63                 (byte) 0x25,
     64                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
     65                 // 5 -> Integer.MAX_VALUE
     66                 (byte) 0x2d,
     67                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
     68         };
     69 
     70         InputStream stream = new ByteArrayInputStream(protobuf);
     71         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
     72         int[] results = new int[5];
     73         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
     74             switch (pi.getFieldNumber()) {
     75                 case (int) fieldId1:
     76                     fail("Should never reach this");
     77                     break;
     78                 case (int) fieldId2:
     79                     results[1] = pi.readInt(fieldId2);
     80                     break;
     81                 case (int) fieldId3:
     82                     results[2] = pi.readInt(fieldId3);
     83                     break;
     84                 case (int) fieldId4:
     85                     results[3] = pi.readInt(fieldId4);
     86                     break;
     87                 case (int) fieldId5:
     88                     results[4] = pi.readInt(fieldId5);
     89                     break;
     90                 case (int) fieldId6:
     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         assertEquals(0, results[0]);
    100         assertEquals(1, results[1]);
    101         assertEquals(-1, results[2]);
    102         assertEquals(Integer.MIN_VALUE, results[3]);
    103         assertEquals(Integer.MAX_VALUE, results[4]);
    104     }
    105 
    106     /**
    107      * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
    108      */
    109     public void testReadCompat() throws Exception {
    110         testReadCompat(0);
    111         testReadCompat(1);
    112         testReadCompat(-1);
    113         testReadCompat(Integer.MIN_VALUE);
    114         testReadCompat(Integer.MAX_VALUE);
    115     }
    116 
    117     /**
    118      * Implementation of testReadCompat with a given value.
    119      */
    120     private void testReadCompat(int val) throws Exception {
    121         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
    122         final long fieldId = fieldFlags | ((long) 110 & 0x0ffffffffL);
    123 
    124         final Test.All all = new Test.All();
    125         all.sfixed32Field = val;
    126 
    127         final byte[] proto = MessageNano.toByteArray(all);
    128 
    129         final ProtoInputStream pi = new ProtoInputStream(proto);
    130         final Test.All readback = Test.All.parseFrom(proto);
    131 
    132         int result = 0; // start off with default value
    133         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    134             switch (pi.getFieldNumber()) {
    135                 case (int) fieldId:
    136                     result = pi.readInt(fieldId);
    137                     break;
    138                 default:
    139                     fail("Unexpected field id " + pi.getFieldNumber());
    140             }
    141         }
    142 
    143         assertEquals(readback.sfixed32Field, result);
    144     }
    145 
    146     public void testRepeated() throws IOException {
    147         testRepeated(0);
    148         testRepeated(1);
    149         testRepeated(5);
    150     }
    151 
    152     private void testRepeated(int chunkSize) throws IOException {
    153         final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32;
    154 
    155         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    156         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
    157         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
    158         final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
    159         final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
    160         final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
    161 
    162         final byte[] protobuf = new byte[]{
    163                 // 1 -> 0 - default value, written when repeated
    164                 (byte) 0x0d,
    165                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    166                 // 2 -> 1
    167                 (byte) 0x15,
    168                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    169                 // 3 -> -1
    170                 (byte) 0x1d,
    171                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
    172                 // 4 -> Integer.MIN_VALUE
    173                 (byte) 0x25,
    174                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
    175                 // 5 -> Integer.MAX_VALUE
    176                 (byte) 0x2d,
    177                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
    178 
    179                 // 6 -> 1
    180                 (byte) 0x35,
    181                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    182 
    183                 // 1 -> 0 - default value, written when repeated
    184                 (byte) 0x0d,
    185                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    186                 // 2 -> 1
    187                 (byte) 0x15,
    188                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    189                 // 3 -> -1
    190                 (byte) 0x1d,
    191                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
    192                 // 4 -> Integer.MIN_VALUE
    193                 (byte) 0x25,
    194                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
    195                 // 5 -> Integer.MAX_VALUE
    196                 (byte) 0x2d,
    197                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
    198         };
    199 
    200         InputStream stream = new ByteArrayInputStream(protobuf);
    201         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
    202         int[][] results = new int[5][2];
    203         int[] indices = new int[5];
    204         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    205 
    206             switch (pi.getFieldNumber()) {
    207                 case (int) fieldId1:
    208                     results[0][indices[0]++] = pi.readInt(fieldId1);
    209                     break;
    210                 case (int) fieldId2:
    211                     results[1][indices[1]++] = pi.readInt(fieldId2);
    212                     break;
    213                 case (int) fieldId3:
    214                     results[2][indices[2]++] = pi.readInt(fieldId3);
    215                     break;
    216                 case (int) fieldId4:
    217                     results[3][indices[3]++] = pi.readInt(fieldId4);
    218                     break;
    219                 case (int) fieldId5:
    220                     results[4][indices[4]++] = pi.readInt(fieldId5);
    221                     break;
    222                 case (int) fieldId6:
    223                     // Intentionally don't read the data. Parse should continue normally
    224                     break;
    225                 default:
    226                     fail("Unexpected field id " + pi.getFieldNumber());
    227             }
    228         }
    229         stream.close();
    230 
    231 
    232         assertEquals(0, results[0][0]);
    233         assertEquals(0, results[0][1]);
    234         assertEquals(1, results[1][0]);
    235         assertEquals(1, results[1][1]);
    236         assertEquals(-1, results[2][0]);
    237         assertEquals(-1, results[2][1]);
    238         assertEquals(Integer.MIN_VALUE, results[3][0]);
    239         assertEquals(Integer.MIN_VALUE, results[3][1]);
    240         assertEquals(Integer.MAX_VALUE, results[4][0]);
    241         assertEquals(Integer.MAX_VALUE, results[4][1]);
    242     }
    243 
    244     /**
    245      * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
    246      */
    247     public void testRepeatedCompat() throws Exception {
    248         testRepeatedCompat(new int[0]);
    249         testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
    250     }
    251 
    252     /**
    253      * Implementation of testRepeatedCompat with a given value.
    254      */
    255     private void testRepeatedCompat(int[] val) throws Exception {
    256         final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32;
    257         final long fieldId = fieldFlags | ((long) 111 & 0x0ffffffffL);
    258 
    259         final Test.All all = new Test.All();
    260         all.sfixed32FieldRepeated = val;
    261 
    262         final byte[] proto = MessageNano.toByteArray(all);
    263 
    264         final ProtoInputStream pi = new ProtoInputStream(proto);
    265         final Test.All readback = Test.All.parseFrom(proto);
    266 
    267         int[] result = new int[val.length];
    268         int index = 0;
    269         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    270             switch (pi.getFieldNumber()) {
    271                 case (int) fieldId:
    272                     result[index++] = pi.readInt(fieldId);
    273                     break;
    274                 default:
    275                     fail("Unexpected field id " + pi.getFieldNumber());
    276             }
    277         }
    278 
    279         assertEquals(readback.sfixed32FieldRepeated.length, result.length);
    280         for (int i = 0; i < result.length; i++) {
    281             assertEquals(readback.sfixed32FieldRepeated[i], result[i]);
    282         }
    283     }
    284 
    285     public void testPacked() throws IOException {
    286         testPacked(0);
    287         testPacked(1);
    288         testPacked(5);
    289     }
    290 
    291     private void testPacked(int chunkSize) throws IOException {
    292         final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SFIXED32;
    293 
    294         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    295         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
    296         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
    297         final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
    298         final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
    299         final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
    300 
    301         final byte[] protobuf = new byte[]{
    302                 // 1 -> 0 - default value, written when repeated
    303                 (byte) 0x0a,
    304                 (byte) 0x08,
    305                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    306                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    307                 // 2 -> 1
    308                 (byte) 0x12,
    309                 (byte) 0x08,
    310                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    311                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    312                 // 6 -> 1
    313                 (byte) 0x32,
    314                 (byte) 0x08,
    315                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    316                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    317                 // 3 -> -1
    318                 (byte) 0x1a,
    319                 (byte) 0x08,
    320                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
    321                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
    322                 // 4 -> Integer.MIN_VALUE
    323                 (byte) 0x22,
    324                 (byte) 0x08,
    325                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
    326                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
    327                 // 5 -> Integer.MAX_VALUE
    328                 (byte) 0x2a,
    329                 (byte) 0x08,
    330                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
    331                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
    332         };
    333 
    334         InputStream stream = new ByteArrayInputStream(protobuf);
    335         final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
    336         int[][] results = new int[5][2];
    337         int[] indices = new int[5];
    338         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    339 
    340             switch (pi.getFieldNumber()) {
    341                 case (int) fieldId1:
    342                     results[0][indices[0]++] = pi.readInt(fieldId1);
    343                     break;
    344                 case (int) fieldId2:
    345                     results[1][indices[1]++] = pi.readInt(fieldId2);
    346                     break;
    347                 case (int) fieldId3:
    348                     results[2][indices[2]++] = pi.readInt(fieldId3);
    349                     break;
    350                 case (int) fieldId4:
    351                     results[3][indices[3]++] = pi.readInt(fieldId4);
    352                     break;
    353                 case (int) fieldId5:
    354                     results[4][indices[4]++] = pi.readInt(fieldId5);
    355                     break;
    356                 case (int) fieldId6:
    357                     // Intentionally don't read the data. Parse should continue normally
    358                     break;
    359                 default:
    360                     fail("Unexpected field id " + pi.getFieldNumber());
    361             }
    362         }
    363         stream.close();
    364 
    365 
    366         assertEquals(0, results[0][0]);
    367         assertEquals(0, results[0][1]);
    368         assertEquals(1, results[1][0]);
    369         assertEquals(1, results[1][1]);
    370         assertEquals(-1, results[2][0]);
    371         assertEquals(-1, results[2][1]);
    372         assertEquals(Integer.MIN_VALUE, results[3][0]);
    373         assertEquals(Integer.MIN_VALUE, results[3][1]);
    374         assertEquals(Integer.MAX_VALUE, results[4][0]);
    375         assertEquals(Integer.MAX_VALUE, results[4][1]);
    376     }
    377 
    378     /**
    379      * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
    380      */
    381     public void testPackedCompat() throws Exception {
    382         testPackedCompat(new int[0]);
    383         testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
    384     }
    385 
    386     /**
    387      * Implementation of testRepeatedCompat with a given value.
    388      */
    389     private void testPackedCompat(int[] val) throws Exception {
    390         final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32;
    391         final long fieldId = fieldFlags | ((long) 112 & 0x0ffffffffL);
    392 
    393         final Test.All all = new Test.All();
    394         all.sfixed32FieldPacked = val;
    395 
    396         final byte[] proto = MessageNano.toByteArray(all);
    397 
    398         final ProtoInputStream pi = new ProtoInputStream(proto);
    399         final Test.All readback = Test.All.parseFrom(proto);
    400 
    401         int[] result = new int[val.length];
    402         int index = 0;
    403         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    404             switch (pi.getFieldNumber()) {
    405                 case (int) fieldId:
    406                     result[index++] = pi.readInt(fieldId);
    407                     break;
    408                 default:
    409                     fail("Unexpected field id " + pi.getFieldNumber());
    410             }
    411         }
    412 
    413         assertEquals(readback.sfixed32FieldPacked.length, result.length);
    414         for (int i = 0; i < result.length; i++) {
    415             assertEquals(readback.sfixed32FieldPacked[i], result[i]);
    416         }
    417     }
    418 
    419     /**
    420      * Test that using the wrong read method throws an exception
    421      */
    422     public void testBadReadType() throws IOException {
    423         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
    424 
    425         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    426 
    427         final byte[] protobuf = new byte[]{
    428                 // 1 -> 1
    429                 (byte) 0x08,
    430                 (byte) 0x01,
    431         };
    432 
    433         ProtoInputStream pi = new ProtoInputStream(protobuf);
    434         pi.isNextField(fieldId1);
    435         try {
    436             pi.readFloat(fieldId1);
    437             fail("Should have throw IllegalArgumentException");
    438         } catch (IllegalArgumentException iae) {
    439             // good
    440         }
    441 
    442         pi = new ProtoInputStream(protobuf);
    443         pi.isNextField(fieldId1);
    444         try {
    445             pi.readDouble(fieldId1);
    446             fail("Should have throw IllegalArgumentException");
    447         } catch (IllegalArgumentException iae) {
    448             // good
    449         }
    450 
    451         pi = new ProtoInputStream(protobuf);
    452         pi.isNextField(fieldId1);
    453         try {
    454             pi.readBoolean(fieldId1);
    455             fail("Should have throw IllegalArgumentException");
    456         } catch (IllegalArgumentException iae) {
    457             // good
    458         }
    459 
    460         pi = new ProtoInputStream(protobuf);
    461         pi.isNextField(fieldId1);
    462         try {
    463             pi.readLong(fieldId1);
    464             fail("Should have throw IllegalArgumentException");
    465         } catch (IllegalArgumentException iae) {
    466             // good
    467         }
    468 
    469         pi = new ProtoInputStream(protobuf);
    470         pi.isNextField(fieldId1);
    471         try {
    472             pi.readBytes(fieldId1);
    473             fail("Should have throw IllegalArgumentException");
    474         } catch (IllegalArgumentException iae) {
    475             // good
    476         }
    477 
    478         pi = new ProtoInputStream(protobuf);
    479         pi.isNextField(fieldId1);
    480         try {
    481             pi.readString(fieldId1);
    482             fail("Should have throw IllegalArgumentException");
    483         } catch (IllegalArgumentException iae) {
    484             // good
    485         }
    486     }
    487 
    488     /**
    489      * Test that unexpected wrong wire types will throw an exception
    490      */
    491     public void testBadWireType() throws IOException {
    492         final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
    493 
    494         final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
    495         final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
    496         final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
    497         final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
    498 
    499         final byte[] protobuf = new byte[]{
    500                 // 1 : varint -> 1
    501                 (byte) 0x08,
    502                 (byte) 0x01,
    503                 // 2 : fixed64 -> 0x1
    504                 (byte) 0x11,
    505                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    506                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    507                 // 3 : length delimited -> { 1 }
    508                 (byte) 0x1a,
    509                 (byte) 0x04,
    510                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    511                 // 6 : fixed32
    512                 (byte) 0x35,
    513                 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    514         };
    515 
    516         InputStream stream = new ByteArrayInputStream(protobuf);
    517         final ProtoInputStream pi = new ProtoInputStream(stream);
    518 
    519         while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
    520             try {
    521                 switch (pi.getFieldNumber()) {
    522                     case (int) fieldId1:
    523                         pi.readInt(fieldId1);
    524                         fail("Should have thrown a WireTypeMismatchException");
    525                         break;
    526                     case (int) fieldId2:
    527                         pi.readInt(fieldId2);
    528                         fail("Should have thrown a WireTypeMismatchException");
    529                         break;
    530                     case (int) fieldId3:
    531                         pi.readInt(fieldId3);
    532                         // don't fail, length delimited is ok (represents packed sfixed32)
    533                         break;
    534                     case (int) fieldId6:
    535                         pi.readInt(fieldId6);
    536                         // don't fail, fixed32 is ok
    537                         break;
    538                     default:
    539                         fail("Unexpected field id " + pi.getFieldNumber());
    540                 }
    541             } catch (WireTypeMismatchException wtme) {
    542                 // good
    543             }
    544         }
    545         stream.close();
    546     }
    547 }
    548