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