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 
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_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      */
testReadCompat()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      */
testReadCompat(int val)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 
testRepeated()146     public void testRepeated() throws IOException {
147         testRepeated(0);
148         testRepeated(1);
149         testRepeated(5);
150     }
151 
testRepeated(int chunkSize)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      */
testRepeatedCompat()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      */
testRepeatedCompat(int[] val)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 
testPacked()285     public void testPacked() throws IOException {
286         testPacked(0);
287         testPacked(1);
288         testPacked(5);
289     }
290 
testPacked(int chunkSize)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      */
testPackedCompat()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      */
testPackedCompat(int[] val)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      */
testBadReadType()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.nextField();
435         try {
436             pi.readFloat(fieldId1);
437             fail("Should have thrown IllegalArgumentException");
438         } catch (IllegalArgumentException iae) {
439             // good
440         }
441 
442         pi = new ProtoInputStream(protobuf);
443         pi.nextField();
444         try {
445             pi.readDouble(fieldId1);
446             fail("Should have thrown IllegalArgumentException");
447         } catch (IllegalArgumentException iae) {
448             // good
449         }
450 
451         pi = new ProtoInputStream(protobuf);
452         pi.nextField();
453         try {
454             pi.readBoolean(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.readLong(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.readBytes(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.readString(fieldId1);
482             fail("Should have thrown IllegalArgumentException");
483         } catch (IllegalArgumentException iae) {
484             // good
485         }
486     }
487 
488     /**
489      * Test that unexpected wrong wire types will throw an exception
490      */
testBadWireType()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