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 ProtoInputStreamFixed32Test 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_FIXED32; 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) 0x15, 55 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 56 // 6 -> 1 57 (byte) 0x35, 58 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 59 // 3 -> -1 60 (byte) 0x1d, 61 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 62 // 4 -> Integer.MIN_VALUE 63 (byte) 0x25, 64 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, 65 // 5 -> Integer.MAX_VALUE 66 (byte) 0x2d, 67 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, 68 }; 69 70 InputStream stream = new ByteArrayInputStream(protobuf); 71 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 72 int[] results = new int[5]; 73 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 74 switch (pi.getFieldNumber()) { 75 case (int) fieldId1: 76 fail("Should never reach this"); 77 break; 78 case (int) fieldId2: 79 results[1] = pi.readInt(fieldId2); 80 break; 81 case (int) fieldId3: 82 results[2] = pi.readInt(fieldId3); 83 break; 84 case (int) fieldId4: 85 results[3] = pi.readInt(fieldId4); 86 break; 87 case (int) fieldId5: 88 results[4] = pi.readInt(fieldId5); 89 break; 90 case (int) fieldId6: 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 assertEquals(0, results[0]); 100 assertEquals(1, results[1]); 101 assertEquals(-1, results[2]); 102 assertEquals(Integer.MIN_VALUE, results[3]); 103 assertEquals(Integer.MAX_VALUE, results[4]); 104 } 105 106 /** 107 * Test that reading with ProtoInputStream matches, and can read the output of standard proto. 108 */ testReadCompat()109 public void testReadCompat() throws Exception { 110 testReadCompat(0); 111 testReadCompat(1); 112 testReadCompat(-1); 113 testReadCompat(Integer.MIN_VALUE); 114 testReadCompat(Integer.MAX_VALUE); 115 } 116 117 /** 118 * Implementation of testReadCompat with a given value. 119 */ testReadCompat(int val)120 private void testReadCompat(int val) throws Exception { 121 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32; 122 final long fieldId = fieldFlags | ((long) 90 & 0x0ffffffffL); 123 124 final Test.All all = new Test.All(); 125 all.fixed32Field = val; 126 127 final byte[] proto = MessageNano.toByteArray(all); 128 129 final ProtoInputStream pi = new ProtoInputStream(proto); 130 final Test.All readback = Test.All.parseFrom(proto); 131 132 int result = 0; // start off with default value 133 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 134 switch (pi.getFieldNumber()) { 135 case (int) fieldId: 136 result = pi.readInt(fieldId); 137 break; 138 default: 139 fail("Unexpected field id " + pi.getFieldNumber()); 140 } 141 } 142 143 assertEquals(readback.fixed32Field, result); 144 } 145 testRepeated()146 public void testRepeated() throws IOException { 147 testRepeated(0); 148 testRepeated(1); 149 testRepeated(5); 150 } 151 testRepeated(int chunkSize)152 private void testRepeated(int chunkSize) throws IOException { 153 final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32; 154 155 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 156 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 157 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 158 final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); 159 final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); 160 final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); 161 162 final byte[] protobuf = new byte[]{ 163 // 1 -> 0 - default value, written when repeated 164 (byte) 0x0d, 165 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 166 // 2 -> 1 167 (byte) 0x15, 168 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 169 // 3 -> -1 170 (byte) 0x1d, 171 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 172 // 4 -> Integer.MIN_VALUE 173 (byte) 0x25, 174 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, 175 // 5 -> Integer.MAX_VALUE 176 (byte) 0x2d, 177 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, 178 179 // 6 -> 1 180 (byte) 0x35, 181 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 182 183 // 1 -> 0 - default value, written when repeated 184 (byte) 0x0d, 185 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 186 // 2 -> 1 187 (byte) 0x15, 188 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 189 // 3 -> -1 190 (byte) 0x1d, 191 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 192 // 4 -> Integer.MIN_VALUE 193 (byte) 0x25, 194 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, 195 // 5 -> Integer.MAX_VALUE 196 (byte) 0x2d, 197 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, 198 }; 199 200 InputStream stream = new ByteArrayInputStream(protobuf); 201 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 202 int[][] results = new int[5][2]; 203 int[] indices = new int[5]; 204 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 205 206 switch (pi.getFieldNumber()) { 207 case (int) fieldId1: 208 results[0][indices[0]++] = pi.readInt(fieldId1); 209 break; 210 case (int) fieldId2: 211 results[1][indices[1]++] = pi.readInt(fieldId2); 212 break; 213 case (int) fieldId3: 214 results[2][indices[2]++] = pi.readInt(fieldId3); 215 break; 216 case (int) fieldId4: 217 results[3][indices[3]++] = pi.readInt(fieldId4); 218 break; 219 case (int) fieldId5: 220 results[4][indices[4]++] = pi.readInt(fieldId5); 221 break; 222 case (int) fieldId6: 223 // Intentionally don't read the data. Parse should continue normally 224 break; 225 default: 226 fail("Unexpected field id " + pi.getFieldNumber()); 227 } 228 } 229 stream.close(); 230 231 232 assertEquals(0, results[0][0]); 233 assertEquals(0, results[0][1]); 234 assertEquals(1, results[1][0]); 235 assertEquals(1, results[1][1]); 236 assertEquals(-1, results[2][0]); 237 assertEquals(-1, results[2][1]); 238 assertEquals(Integer.MIN_VALUE, results[3][0]); 239 assertEquals(Integer.MIN_VALUE, results[3][1]); 240 assertEquals(Integer.MAX_VALUE, results[4][0]); 241 assertEquals(Integer.MAX_VALUE, results[4][1]); 242 } 243 244 /** 245 * Test that reading with ProtoInputStream matches, and can read the output of standard proto. 246 */ testRepeatedCompat()247 public void testRepeatedCompat() throws Exception { 248 testRepeatedCompat(new int[0]); 249 testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); 250 } 251 252 /** 253 * Implementation of testRepeatedCompat with a given value. 254 */ testRepeatedCompat(int[] val)255 private void testRepeatedCompat(int[] val) throws Exception { 256 final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32; 257 final long fieldId = fieldFlags | ((long) 91 & 0x0ffffffffL); 258 259 final Test.All all = new Test.All(); 260 all.fixed32FieldRepeated = val; 261 262 final byte[] proto = MessageNano.toByteArray(all); 263 264 final ProtoInputStream pi = new ProtoInputStream(proto); 265 final Test.All readback = Test.All.parseFrom(proto); 266 267 int[] result = new int[val.length]; 268 int index = 0; 269 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 270 switch (pi.getFieldNumber()) { 271 case (int) fieldId: 272 result[index++] = pi.readInt(fieldId); 273 break; 274 default: 275 fail("Unexpected field id " + pi.getFieldNumber()); 276 } 277 } 278 279 assertEquals(readback.fixed32FieldRepeated.length, result.length); 280 for (int i = 0; i < result.length; i++) { 281 assertEquals(readback.fixed32FieldRepeated[i], result[i]); 282 } 283 } 284 testPacked()285 public void testPacked() throws IOException { 286 testPacked(0); 287 testPacked(1); 288 testPacked(5); 289 } 290 testPacked(int chunkSize)291 private void testPacked(int chunkSize) throws IOException { 292 final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FIXED32; 293 294 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 295 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 296 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 297 final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); 298 final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); 299 final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); 300 301 final byte[] protobuf = new byte[]{ 302 // 1 -> 0 - default value, written when repeated 303 (byte) 0x0a, 304 (byte) 0x08, 305 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 306 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 307 // 6 -> 1 308 (byte) 0x32, 309 (byte) 0x08, 310 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 311 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 312 // 2 -> 1 313 (byte) 0x12, 314 (byte) 0x08, 315 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 316 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 317 // 3 -> -1 318 (byte) 0x1a, 319 (byte) 0x08, 320 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 321 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 322 // 4 -> Integer.MIN_VALUE 323 (byte) 0x22, 324 (byte) 0x08, 325 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, 326 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, 327 // 5 -> Integer.MAX_VALUE 328 (byte) 0x2a, 329 (byte) 0x08, 330 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, 331 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, 332 }; 333 334 InputStream stream = new ByteArrayInputStream(protobuf); 335 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 336 int[][] results = new int[5][2]; 337 int[] indices = new int[5]; 338 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 339 340 switch (pi.getFieldNumber()) { 341 case (int) fieldId1: 342 results[0][indices[0]++] = pi.readInt(fieldId1); 343 break; 344 case (int) fieldId2: 345 results[1][indices[1]++] = pi.readInt(fieldId2); 346 break; 347 case (int) fieldId3: 348 results[2][indices[2]++] = pi.readInt(fieldId3); 349 break; 350 case (int) fieldId4: 351 results[3][indices[3]++] = pi.readInt(fieldId4); 352 break; 353 case (int) fieldId5: 354 results[4][indices[4]++] = pi.readInt(fieldId5); 355 break; 356 case (int) fieldId6: 357 // Intentionally don't read the data. Parse should continue normally 358 break; 359 default: 360 fail("Unexpected field id " + pi.getFieldNumber()); 361 } 362 } 363 stream.close(); 364 365 366 assertEquals(0, results[0][0]); 367 assertEquals(0, results[0][1]); 368 assertEquals(1, results[1][0]); 369 assertEquals(1, results[1][1]); 370 assertEquals(-1, results[2][0]); 371 assertEquals(-1, results[2][1]); 372 assertEquals(Integer.MIN_VALUE, results[3][0]); 373 assertEquals(Integer.MIN_VALUE, results[3][1]); 374 assertEquals(Integer.MAX_VALUE, results[4][0]); 375 assertEquals(Integer.MAX_VALUE, results[4][1]); 376 } 377 378 /** 379 * Test that reading with ProtoInputStream matches, and can read the output of standard proto. 380 */ testPackedCompat()381 public void testPackedCompat() throws Exception { 382 testPackedCompat(new int[0]); 383 testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}); 384 } 385 386 /** 387 * Implementation of testRepeatedCompat with a given value. 388 */ testPackedCompat(int[] val)389 private void testPackedCompat(int[] val) throws Exception { 390 final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32; 391 final long fieldId = fieldFlags | ((long) 92 & 0x0ffffffffL); 392 393 final Test.All all = new Test.All(); 394 all.fixed32FieldPacked = val; 395 396 final byte[] proto = MessageNano.toByteArray(all); 397 398 final ProtoInputStream pi = new ProtoInputStream(proto); 399 final Test.All readback = Test.All.parseFrom(proto); 400 401 int[] result = new int[val.length]; 402 int index = 0; 403 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 404 switch (pi.getFieldNumber()) { 405 case (int) fieldId: 406 result[index++] = pi.readInt(fieldId); 407 break; 408 default: 409 fail("Unexpected field id " + pi.getFieldNumber()); 410 } 411 } 412 413 assertEquals(readback.fixed32FieldPacked.length, result.length); 414 for (int i = 0; i < result.length; i++) { 415 assertEquals(readback.fixed32FieldPacked[i], result[i]); 416 } 417 } 418 419 /** 420 * Test that using the wrong read method throws an exception 421 */ testBadReadType()422 public void testBadReadType() throws IOException { 423 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32; 424 425 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 426 427 final byte[] protobuf = new byte[]{ 428 // 1 -> 1 429 (byte) 0x0d, 430 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 431 }; 432 433 ProtoInputStream pi = new ProtoInputStream(protobuf); 434 pi.nextField(); 435 try { 436 pi.readFloat(fieldId1); 437 fail("Should have thrown IllegalArgumentException"); 438 } catch (IllegalArgumentException iae) { 439 // good 440 } 441 442 pi = new ProtoInputStream(protobuf); 443 pi.nextField(); 444 try { 445 pi.readDouble(fieldId1); 446 fail("Should have thrown IllegalArgumentException"); 447 } catch (IllegalArgumentException iae) { 448 // good 449 } 450 451 pi = new ProtoInputStream(protobuf); 452 pi.nextField(); 453 try { 454 pi.readBoolean(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.readLong(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.readBytes(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.readString(fieldId1); 482 fail("Should have thrown IllegalArgumentException"); 483 } catch (IllegalArgumentException iae) { 484 // good 485 } 486 } 487 488 /** 489 * Test that unexpected wrong wire types will throw an exception 490 */ testBadWireType()491 public void testBadWireType() throws IOException { 492 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32; 493 494 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 495 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 496 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 497 final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); 498 499 final byte[] protobuf = new byte[]{ 500 // 1 : varint -> 1 501 (byte) 0x08, 502 (byte) 0x01, 503 // 2 : fixed64 -> 0x1 504 (byte) 0x11, 505 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 506 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 507 // 3 : length delimited -> { 1 } 508 (byte) 0x1a, 509 (byte) 0x04, 510 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 511 // 6 : fixed32 512 (byte) 0x35, 513 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 514 }; 515 516 InputStream stream = new ByteArrayInputStream(protobuf); 517 final ProtoInputStream pi = new ProtoInputStream(stream); 518 519 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 520 try { 521 switch (pi.getFieldNumber()) { 522 case (int) fieldId1: 523 pi.readInt(fieldId1); 524 fail("Should have thrown a WireTypeMismatchException"); 525 break; 526 case (int) fieldId2: 527 pi.readInt(fieldId2); 528 fail("Should have thrown a WireTypeMismatchException"); 529 break; 530 case (int) fieldId3: 531 pi.readInt(fieldId3); 532 // don't fail, length delimited is ok (represents packed fixed32) 533 break; 534 case (int) fieldId6: 535 pi.readInt(fieldId6); 536 // don't fail, fixed32 is ok 537 break; 538 default: 539 fail("Unexpected field id " + pi.getFieldNumber()); 540 } 541 } catch (WireTypeMismatchException wtme) { 542 // good 543 } 544 } 545 stream.close(); 546 } 547 } 548