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