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