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