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 ProtoInputStreamEnumTest 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_ENUM; 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_ENUM; 124 final long fieldId = fieldFlags | ((long) 160 & 0x0ffffffffL); 125 126 final Test.All all = new Test.All(); 127 all.outsideField = val; 128 129 final byte[] proto = MessageNano.toByteArray(all); 130 131 final ProtoInputStream pi = new ProtoInputStream(proto); 132 133 int result = 0; // start off with default value 134 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 135 switch (pi.getFieldNumber()) { 136 case (int) fieldId: 137 result = pi.readInt(fieldId); 138 break; 139 default: 140 fail("Unexpected field id " + pi.getFieldNumber()); 141 } 142 } 143 144 // Nano proto drops values that are outside the range, so compare against val 145 assertEquals(val, 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_ENUM; 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 /** 252 * Test that reading with ProtoInputStream matches, and can read the output of standard proto. 253 */ testRepeatedCompat()254 public void testRepeatedCompat() throws Exception { 255 testRepeatedCompat(new int[]{}); 256 testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); 257 } 258 259 /** 260 * Implementation of testRepeatedCompat with a given value. 261 */ testRepeatedCompat(int[] val)262 private void testRepeatedCompat(int[] val) throws Exception { 263 final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_ENUM; 264 final long fieldId = fieldFlags | ((long) 161 & 0x0ffffffffL); 265 266 final Test.All all = new Test.All(); 267 all.outsideFieldRepeated = val; 268 269 final byte[] proto = MessageNano.toByteArray(all); 270 271 final ProtoInputStream pi = new ProtoInputStream(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 // Nano proto drops values that are outside the range, so compare against val 286 assertEquals(val.length, result.length); 287 for (int i = 0; i < result.length; i++) { 288 assertEquals(val[i], result[i]); 289 } 290 } 291 testPacked()292 public void testPacked() throws IOException { 293 testPacked(0); 294 testPacked(1); 295 testPacked(5); 296 } 297 testPacked(int chunkSize)298 private void testPacked(int chunkSize) throws IOException { 299 final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_ENUM; 300 301 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 302 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 303 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 304 final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); 305 final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); 306 final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); 307 308 final byte[] protobuf = new byte[]{ 309 // 1 -> 0 - default value, written when repeated 310 (byte) 0x0a, 311 (byte) 0x02, 312 (byte) 0x00, 313 (byte) 0x00, 314 // 2 -> 1 315 (byte) 0x12, 316 (byte) 0x02, 317 (byte) 0x01, 318 (byte) 0x01, 319 320 // 6 -> MAX_VALUE 321 (byte) 0x32, 322 (byte) 0x0a, 323 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 324 325 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 326 327 // 3 -> -1 328 (byte) 0x1a, 329 (byte) 0x14, 330 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 331 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 332 333 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 334 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 335 336 // 4 -> MIN_VALUE 337 (byte) 0x22, 338 (byte) 0x14, 339 (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, 340 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 341 342 (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, 343 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01, 344 345 // 5 -> MAX_VALUE 346 (byte) 0x2a, 347 (byte) 0x0a, 348 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 349 350 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07, 351 }; 352 353 InputStream stream = new ByteArrayInputStream(protobuf); 354 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 355 int[][] results = new int[5][2]; 356 int[] indices = new int[5]; 357 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 358 359 switch (pi.getFieldNumber()) { 360 case (int) fieldId1: 361 results[0][indices[0]++] = pi.readInt(fieldId1); 362 break; 363 case (int) fieldId2: 364 results[1][indices[1]++] = pi.readInt(fieldId2); 365 break; 366 case (int) fieldId3: 367 results[2][indices[2]++] = pi.readInt(fieldId3); 368 break; 369 case (int) fieldId4: 370 results[3][indices[3]++] = pi.readInt(fieldId4); 371 break; 372 case (int) fieldId5: 373 results[4][indices[4]++] = pi.readInt(fieldId5); 374 break; 375 case (int) fieldId6: 376 // Intentionally don't read the data. Parse should continue normally 377 break; 378 default: 379 fail("Unexpected field id " + pi.getFieldNumber()); 380 } 381 } 382 stream.close(); 383 384 385 assertEquals(0, results[0][0]); 386 assertEquals(0, results[0][1]); 387 assertEquals(1, results[1][0]); 388 assertEquals(1, results[1][1]); 389 assertEquals(-1, results[2][0]); 390 assertEquals(-1, results[2][1]); 391 assertEquals(Integer.MIN_VALUE, results[3][0]); 392 assertEquals(Integer.MIN_VALUE, results[3][1]); 393 assertEquals(Integer.MAX_VALUE, results[4][0]); 394 assertEquals(Integer.MAX_VALUE, results[4][1]); 395 } 396 397 /** 398 * Test that reading with ProtoInputStream matches, and can read the output of standard proto. 399 */ testPackedCompat()400 public void testPackedCompat() throws Exception { 401 testPackedCompat(new int[]{}); 402 testPackedCompat(new int[]{0, 1}); 403 404 // Nano proto has a bug. It gets the size with computeInt32SizeNoTag (correctly) 405 // but incorrectly uses writeRawVarint32 to write the value for negative numbers. 406 //testPackedCompat(new int[] { 0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE }); 407 } 408 409 /** 410 * Implementation of testRepeatedCompat with a given value. 411 */ testPackedCompat(int[] val)412 private void testPackedCompat(int[] val) throws Exception { 413 final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_ENUM; 414 final long fieldId = fieldFlags | ((long) 162 & 0x0ffffffffL); 415 416 final Test.All all = new Test.All(); 417 all.outsideFieldPacked = val; 418 419 final byte[] proto = MessageNano.toByteArray(all); 420 421 final ProtoInputStream pi = new ProtoInputStream(proto); 422 423 int[] result = new int[val.length]; 424 int index = 0; 425 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 426 switch (pi.getFieldNumber()) { 427 case (int) fieldId: 428 result[index++] = pi.readInt(fieldId); 429 break; 430 default: 431 fail("Unexpected field id " + pi.getFieldNumber()); 432 } 433 } 434 435 // Nano proto drops values that are outside the range, so compare against val 436 assertEquals(val.length, result.length); 437 for (int i = 0; i < result.length; i++) { 438 assertEquals(val[i], result[i]); 439 } 440 } 441 442 /** 443 * Test that using the wrong read method throws an exception 444 */ testBadReadType()445 public void testBadReadType() throws IOException { 446 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM; 447 448 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 449 450 final byte[] protobuf = new byte[]{ 451 // 1 -> 1 452 (byte) 0x08, 453 (byte) 0x01, 454 }; 455 456 ProtoInputStream pi = new ProtoInputStream(protobuf); 457 pi.nextField(); 458 try { 459 pi.readFloat(fieldId1); 460 fail("Should have thrown IllegalArgumentException"); 461 } catch (IllegalArgumentException iae) { 462 // good 463 } 464 465 pi = new ProtoInputStream(protobuf); 466 pi.nextField(); 467 try { 468 pi.readDouble(fieldId1); 469 fail("Should have thrown IllegalArgumentException"); 470 } catch (IllegalArgumentException iae) { 471 // good 472 } 473 474 pi = new ProtoInputStream(protobuf); 475 pi.nextField(); 476 try { 477 pi.readBoolean(fieldId1); 478 fail("Should have thrown IllegalArgumentException"); 479 } catch (IllegalArgumentException iae) { 480 // good 481 } 482 483 pi = new ProtoInputStream(protobuf); 484 pi.nextField(); 485 try { 486 pi.readLong(fieldId1); 487 fail("Should have thrown IllegalArgumentException"); 488 } catch (IllegalArgumentException iae) { 489 // good 490 } 491 492 pi = new ProtoInputStream(protobuf); 493 pi.nextField(); 494 try { 495 pi.readBytes(fieldId1); 496 fail("Should have thrown IllegalArgumentException"); 497 } catch (IllegalArgumentException iae) { 498 // good 499 } 500 501 pi = new ProtoInputStream(protobuf); 502 pi.nextField(); 503 try { 504 pi.readString(fieldId1); 505 fail("Should have thrown IllegalArgumentException"); 506 } catch (IllegalArgumentException iae) { 507 // good 508 } 509 } 510 511 /** 512 * Test that unexpected wrong wire types will throw an exception 513 */ testBadWireType()514 public void testBadWireType() throws IOException { 515 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM; 516 517 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 518 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 519 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 520 final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); 521 522 final byte[] protobuf = new byte[]{ 523 // 1 : varint -> 1 524 (byte) 0x08, 525 (byte) 0x01, 526 // 2 : fixed64 -> 0x1 527 (byte) 0x11, 528 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 529 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 530 // 3 : length delimited -> { 1 } 531 (byte) 0x1a, 532 (byte) 0x01, 533 (byte) 0x01, 534 // 6 : fixed32 535 (byte) 0x35, 536 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 537 }; 538 539 InputStream stream = new ByteArrayInputStream(protobuf); 540 final ProtoInputStream pi = new ProtoInputStream(stream); 541 542 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 543 try { 544 switch (pi.getFieldNumber()) { 545 case (int) fieldId1: 546 pi.readInt(fieldId1); 547 // don't fail, varint is ok 548 break; 549 case (int) fieldId2: 550 pi.readInt(fieldId2); 551 fail("Should have thrown a WireTypeMismatchException"); 552 break; 553 case (int) fieldId3: 554 pi.readInt(fieldId3); 555 // don't fail, length delimited is ok (represents packed enums) 556 break; 557 case (int) fieldId6: 558 pi.readInt(fieldId6); 559 fail("Should have thrown a WireTypeMismatchException"); 560 break; 561 default: 562 fail("Unexpected field id " + pi.getFieldNumber()); 563 } 564 } catch (WireTypeMismatchException wtme) { 565 // good 566 } 567 } 568 stream.close(); 569 } 570 } 571