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