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 ProtoInputStreamUInt32Test 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_UINT32; 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_UINT32; 124 final long fieldId = fieldFlags | ((long) 50 & 0x0ffffffffL); 125 126 final Test.All all = new Test.All(); 127 all.uint32Field = 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.uint32Field, 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_UINT32; 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 // 6 -> MAX_VALUE 172 (byte) 0x30, 173 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 174 // 3 -> -1 175 (byte) 0x18, 176 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 177 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 178 // 4 -> MIN_VALUE 179 (byte) 0x20, 180 (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, 181 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 182 // 5 -> MAX_VALUE 183 (byte) 0x28, 184 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 185 186 // 1 -> 0 - default value, written when repeated 187 (byte) 0x08, 188 (byte) 0x00, 189 // 2 -> 1 190 (byte) 0x10, 191 (byte) 0x01, 192 // 3 -> -1 193 (byte) 0x18, 194 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 195 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 196 // 4 -> MIN_VALUE 197 (byte) 0x20, 198 (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, 199 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 200 // 5 -> MAX_VALUE 201 (byte) 0x28, 202 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 203 }; 204 205 InputStream stream = new ByteArrayInputStream(protobuf); 206 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 207 int[][] results = new int[5][2]; 208 int[] indices = new int[5]; 209 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 210 211 switch (pi.getFieldNumber()) { 212 case (int) fieldId1: 213 results[0][indices[0]++] = pi.readInt(fieldId1); 214 break; 215 case (int) fieldId2: 216 results[1][indices[1]++] = pi.readInt(fieldId2); 217 break; 218 case (int) fieldId3: 219 results[2][indices[2]++] = pi.readInt(fieldId3); 220 break; 221 case (int) fieldId4: 222 results[3][indices[3]++] = pi.readInt(fieldId4); 223 break; 224 case (int) fieldId5: 225 results[4][indices[4]++] = pi.readInt(fieldId5); 226 break; 227 case (int) fieldId6: 228 // Intentionally don't read the data. Parse should continue normally 229 break; 230 default: 231 fail("Unexpected field id " + pi.getFieldNumber()); 232 } 233 } 234 stream.close(); 235 236 237 assertEquals(0, results[0][0]); 238 assertEquals(0, results[0][1]); 239 assertEquals(1, results[1][0]); 240 assertEquals(1, results[1][1]); 241 assertEquals(-1, results[2][0]); 242 assertEquals(-1, results[2][1]); 243 assertEquals(Integer.MIN_VALUE, results[3][0]); 244 assertEquals(Integer.MIN_VALUE, results[3][1]); 245 assertEquals(Integer.MAX_VALUE, results[4][0]); 246 assertEquals(Integer.MAX_VALUE, results[4][1]); 247 } 248 249 /** 250 * Test that reading with ProtoInputStream matches, and can read the output of standard proto. 251 */ testRepeatedCompat()252 public void testRepeatedCompat() throws Exception { 253 testRepeatedCompat(new int[0]); 254 testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); 255 } 256 257 /** 258 * Implementation of testRepeatedCompat with a given value. 259 */ testRepeatedCompat(int[] val)260 private void testRepeatedCompat(int[] val) throws Exception { 261 final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32; 262 final long fieldId = fieldFlags | ((long) 51 & 0x0ffffffffL); 263 264 final Test.All all = new Test.All(); 265 all.uint32FieldRepeated = 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 int[] result = new int[val.length]; 273 int index = 0; 274 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 275 switch (pi.getFieldNumber()) { 276 case (int) fieldId: 277 result[index++] = pi.readInt(fieldId); 278 break; 279 default: 280 fail("Unexpected field id " + pi.getFieldNumber()); 281 } 282 } 283 284 assertEquals(readback.uint32FieldRepeated.length, result.length); 285 for (int i = 0; i < result.length; i++) { 286 assertEquals(readback.uint32FieldRepeated[i], result[i]); 287 } 288 } 289 testPacked()290 public void testPacked() throws IOException { 291 testPacked(0); 292 testPacked(1); 293 testPacked(5); 294 } 295 testPacked(int chunkSize)296 private void testPacked(int chunkSize) throws IOException { 297 final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_UINT32; 298 299 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 300 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 301 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 302 final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); 303 final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); 304 final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); 305 306 final byte[] protobuf = new byte[]{ 307 // 1 -> 0 - default value, written when repeated 308 (byte) 0x0a, 309 (byte) 0x02, 310 (byte) 0x00, 311 (byte) 0x00, 312 // 2 -> 1 313 (byte) 0x12, 314 (byte) 0x02, 315 (byte) 0x01, 316 (byte) 0x01, 317 318 // 6 -> MAX_VALUE 319 (byte) 0x32, 320 (byte) 0x0a, 321 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 322 323 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 324 325 // 3 -> -1 326 (byte) 0x1a, 327 (byte) 0x14, 328 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 329 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 330 331 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 332 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 333 334 // 4 -> MIN_VALUE 335 (byte) 0x22, 336 (byte) 0x14, 337 (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, 338 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 339 340 (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, 341 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 342 343 // 5 -> MAX_VALUE 344 (byte) 0x2a, 345 (byte) 0x0a, 346 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 347 348 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 349 }; 350 351 InputStream stream = new ByteArrayInputStream(protobuf); 352 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 353 int[][] results = new int[5][2]; 354 int[] indices = new int[5]; 355 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 356 357 switch (pi.getFieldNumber()) { 358 case (int) fieldId1: 359 results[0][indices[0]++] = pi.readInt(fieldId1); 360 break; 361 case (int) fieldId2: 362 results[1][indices[1]++] = pi.readInt(fieldId2); 363 break; 364 case (int) fieldId3: 365 results[2][indices[2]++] = pi.readInt(fieldId3); 366 break; 367 case (int) fieldId4: 368 results[3][indices[3]++] = pi.readInt(fieldId4); 369 break; 370 case (int) fieldId5: 371 results[4][indices[4]++] = pi.readInt(fieldId5); 372 break; 373 case (int) fieldId6: 374 // Intentionally don't read the data. Parse should continue normally 375 break; 376 default: 377 fail("Unexpected field id " + pi.getFieldNumber()); 378 } 379 } 380 stream.close(); 381 382 383 assertEquals(0, results[0][0]); 384 assertEquals(0, results[0][1]); 385 assertEquals(1, results[1][0]); 386 assertEquals(1, results[1][1]); 387 assertEquals(-1, results[2][0]); 388 assertEquals(-1, results[2][1]); 389 assertEquals(Integer.MIN_VALUE, results[3][0]); 390 assertEquals(Integer.MIN_VALUE, results[3][1]); 391 assertEquals(Integer.MAX_VALUE, results[4][0]); 392 assertEquals(Integer.MAX_VALUE, results[4][1]); 393 } 394 395 /** 396 * Test that reading with ProtoInputStream matches, and can read the output of standard proto. 397 */ testPackedCompat()398 public void testPackedCompat() throws Exception { 399 testPackedCompat(new int[0]); 400 testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); 401 } 402 403 /** 404 * Implementation of testRepeatedCompat with a given value. 405 */ testPackedCompat(int[] val)406 private void testPackedCompat(int[] val) throws Exception { 407 final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32; 408 final long fieldId = fieldFlags | ((long) 52 & 0x0ffffffffL); 409 410 final Test.All all = new Test.All(); 411 all.uint32FieldPacked = val; 412 413 final byte[] proto = MessageNano.toByteArray(all); 414 415 final ProtoInputStream pi = new ProtoInputStream(proto); 416 final Test.All readback = Test.All.parseFrom(proto); 417 418 int[] result = new int[val.length]; 419 int index = 0; 420 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 421 switch (pi.getFieldNumber()) { 422 case (int) fieldId: 423 result[index++] = pi.readInt(fieldId); 424 break; 425 default: 426 fail("Unexpected field id " + pi.getFieldNumber()); 427 } 428 } 429 430 assertEquals(readback.uint32FieldPacked.length, result.length); 431 for (int i = 0; i < result.length; i++) { 432 assertEquals(readback.uint32FieldPacked[i], result[i]); 433 } 434 } 435 436 /** 437 * Test that using the wrong read method throws an exception 438 */ testBadReadType()439 public void testBadReadType() throws IOException { 440 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; 441 442 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 443 444 final byte[] protobuf = new byte[]{ 445 // 1 -> 1 446 (byte) 0x08, 447 (byte) 0x01, 448 }; 449 450 ProtoInputStream pi = new ProtoInputStream(protobuf); 451 pi.nextField(); 452 try { 453 pi.readFloat(fieldId1); 454 fail("Should have thrown IllegalArgumentException"); 455 } catch (IllegalArgumentException iae) { 456 // good 457 } 458 459 pi = new ProtoInputStream(protobuf); 460 pi.nextField(); 461 try { 462 pi.readDouble(fieldId1); 463 fail("Should have thrown IllegalArgumentException"); 464 } catch (IllegalArgumentException iae) { 465 // good 466 } 467 468 pi = new ProtoInputStream(protobuf); 469 pi.nextField(); 470 try { 471 pi.readBoolean(fieldId1); 472 fail("Should have thrown IllegalArgumentException"); 473 } catch (IllegalArgumentException iae) { 474 // good 475 } 476 477 pi = new ProtoInputStream(protobuf); 478 pi.nextField(); 479 try { 480 pi.readLong(fieldId1); 481 fail("Should have thrown IllegalArgumentException"); 482 } catch (IllegalArgumentException iae) { 483 // good 484 } 485 486 pi = new ProtoInputStream(protobuf); 487 pi.nextField(); 488 try { 489 pi.readBytes(fieldId1); 490 fail("Should have thrown IllegalArgumentException"); 491 } catch (IllegalArgumentException iae) { 492 // good 493 } 494 495 pi = new ProtoInputStream(protobuf); 496 pi.nextField(); 497 try { 498 pi.readString(fieldId1); 499 fail("Should have thrown IllegalArgumentException"); 500 } catch (IllegalArgumentException iae) { 501 // good 502 } 503 } 504 505 /** 506 * Test that unexpected wrong wire types will throw an exception 507 */ testBadWireType()508 public void testBadWireType() throws IOException { 509 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; 510 511 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 512 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 513 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 514 final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); 515 516 final byte[] protobuf = new byte[]{ 517 // 1 : varint -> 1 518 (byte) 0x08, 519 (byte) 0x01, 520 // 2 : fixed64 -> 0x1 521 (byte) 0x11, 522 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 523 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 524 // 3 : length delimited -> { 1 } 525 (byte) 0x1a, 526 (byte) 0x01, 527 (byte) 0x01, 528 // 6 : fixed32 529 (byte) 0x35, 530 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 531 }; 532 533 InputStream stream = new ByteArrayInputStream(protobuf); 534 final ProtoInputStream pi = new ProtoInputStream(stream); 535 536 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 537 try { 538 switch (pi.getFieldNumber()) { 539 case (int) fieldId1: 540 pi.readInt(fieldId1); 541 // don't fail, varint is ok 542 break; 543 case (int) fieldId2: 544 pi.readInt(fieldId2); 545 fail("Should have thrown a WireTypeMismatchException"); 546 break; 547 case (int) fieldId3: 548 pi.readInt(fieldId3); 549 // don't fail, length delimited is ok (represents packed uint32) 550 break; 551 case (int) fieldId6: 552 pi.readInt(fieldId6); 553 fail("Should have thrown a WireTypeMismatchException"); 554 break; 555 default: 556 fail("Unexpected field id " + pi.getFieldNumber()); 557 } 558 } catch (WireTypeMismatchException wtme) { 559 // good 560 } 561 } 562 stream.close(); 563 } 564 } 565