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