1 /* 2 * Copyright (C) 2018 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 junit.framework.TestCase; 24 25 import java.io.ByteArrayInputStream; 26 import java.io.IOException; 27 import java.io.InputStream; 28 29 public class ProtoInputStreamObjectTest extends TestCase { 30 31 32 class SimpleObject { 33 public char mChar; 34 public char mLargeChar; 35 public String mString; 36 public SimpleObject mNested; 37 parseProto(ProtoInputStream pi)38 void parseProto(ProtoInputStream pi) throws IOException { 39 final long uintFieldFlags = 40 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; 41 final long stringFieldFlags = 42 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING; 43 final long messageFieldFlags = 44 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; 45 final long charId = uintFieldFlags | ((long) 2 & 0x0ffffffffL); 46 final long largeCharId = uintFieldFlags | ((long) 5000 & 0x0ffffffffL); 47 final long stringId = stringFieldFlags | ((long) 4 & 0x0ffffffffL); 48 final long nestedId = messageFieldFlags | ((long) 5 & 0x0ffffffffL); 49 50 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 51 switch (pi.getFieldNumber()) { 52 case (int) charId: 53 mChar = (char) pi.readInt(charId); 54 break; 55 case (int) largeCharId: 56 mLargeChar = (char) pi.readInt(largeCharId); 57 break; 58 case (int) stringId: 59 mString = pi.readString(stringId); 60 break; 61 case (int) nestedId: 62 long token = pi.start(nestedId); 63 mNested = new SimpleObject(); 64 mNested.parseProto(pi); 65 pi.end(token); 66 break; 67 default: 68 fail("Unexpected field id " + pi.getFieldNumber()); 69 } 70 } 71 } 72 73 } 74 75 /** 76 * Test reading an object with one char in it. 77 */ testObjectOneChar()78 public void testObjectOneChar() throws IOException { 79 testObjectOneChar(0); 80 testObjectOneChar(1); 81 testObjectOneChar(5); 82 } 83 84 /** 85 * Implementation of testObjectOneChar for a given chunkSize. 86 */ testObjectOneChar(int chunkSize)87 private void testObjectOneChar(int chunkSize) throws IOException { 88 final long messageFieldFlags = 89 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; 90 91 final long messageId1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL); 92 final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); 93 94 final byte[] protobuf = new byte[]{ 95 // Message 2 : { char 2 : 'c' } 96 (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x63, 97 // Message 1 : { char 2 : 'b' } 98 (byte) 0x0a, (byte) 0x02, (byte) 0x10, (byte) 0x62, 99 }; 100 101 InputStream stream = new ByteArrayInputStream(protobuf); 102 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 103 104 SimpleObject result = null; 105 106 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 107 switch (pi.getFieldNumber()) { 108 case (int) messageId1: 109 final long token = pi.start(messageId1); 110 result = new SimpleObject(); 111 result.parseProto(pi); 112 pi.end(token); 113 break; 114 case (int) messageId2: 115 // Intentionally don't read the data. Parse should continue normally 116 break; 117 default: 118 fail("Unexpected field id " + pi.getFieldNumber()); 119 } 120 } 121 stream.close(); 122 123 assertNotNull(result); 124 assertEquals('b', result.mChar); 125 } 126 127 /** 128 * Test reading an object with one multibyte unicode char in it. 129 */ testObjectOneLargeChar()130 public void testObjectOneLargeChar() throws IOException { 131 testObjectOneLargeChar(0); 132 testObjectOneLargeChar(1); 133 testObjectOneLargeChar(5); 134 } 135 136 /** 137 * Implementation of testObjectOneLargeChar for a given chunkSize. 138 */ testObjectOneLargeChar(int chunkSize)139 private void testObjectOneLargeChar(int chunkSize) throws IOException { 140 final long messageFieldFlags = 141 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; 142 143 final long messageId1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL); 144 final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); 145 146 final byte[] protobuf = new byte[]{ 147 // Message 2 : { char 5000 : '\u3110' } 148 (byte) 0x12, (byte) 0x05, (byte) 0xc0, (byte) 0xb8, 149 (byte) 0x02, (byte) 0x90, (byte) 0x62, 150 // Message 1 : { char 5000 : '\u3110' } 151 (byte) 0x0a, (byte) 0x05, (byte) 0xc0, (byte) 0xb8, 152 (byte) 0x02, (byte) 0x90, (byte) 0x62, 153 }; 154 155 InputStream stream = new ByteArrayInputStream(protobuf); 156 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 157 158 SimpleObject result = null; 159 160 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 161 switch (pi.getFieldNumber()) { 162 case (int) messageId1: 163 final long token = pi.start(messageId1); 164 result = new SimpleObject(); 165 result.parseProto(pi); 166 pi.end(token); 167 break; 168 case (int) messageId2: 169 // Intentionally don't read the data. Parse should continue normally 170 break; 171 default: 172 fail("Unexpected field id " + pi.getFieldNumber()); 173 } 174 } 175 stream.close(); 176 177 assertNotNull(result); 178 assertEquals('\u3110', result.mLargeChar); 179 } 180 181 /** 182 * Test reading a char, then an object, then a char. 183 */ testObjectAndTwoChars()184 public void testObjectAndTwoChars() throws IOException { 185 testObjectAndTwoChars(0); 186 testObjectAndTwoChars(1); 187 testObjectAndTwoChars(5); 188 } 189 190 /** 191 * Implementation of testObjectAndTwoChars for a given chunkSize. 192 */ testObjectAndTwoChars(int chunkSize)193 private void testObjectAndTwoChars(int chunkSize) throws IOException { 194 final long uintFieldFlags = 195 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; 196 final long messageFieldFlags = 197 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; 198 199 final long charId1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL); 200 final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); 201 final long charId4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL); 202 203 final byte[] protobuf = new byte[]{ 204 // 1 -> 'a' 205 (byte) 0x08, (byte) 0x61, 206 // Message 1 : { char 2 : 'b' } 207 (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x62, 208 // 4 -> 'c' 209 (byte) 0x20, (byte) 0x63, 210 }; 211 212 InputStream stream = new ByteArrayInputStream(protobuf); 213 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 214 215 SimpleObject obj = null; 216 char char1 = '\0'; 217 char char4 = '\0'; 218 219 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 220 switch (pi.getFieldNumber()) { 221 case (int) charId1: 222 char1 = (char) pi.readInt(charId1); 223 break; 224 case (int) messageId2: 225 final long token = pi.start(messageId2); 226 obj = new SimpleObject(); 227 obj.parseProto(pi); 228 pi.end(token); 229 break; 230 case (int) charId4: 231 char4 = (char) pi.readInt(charId4); 232 break; 233 default: 234 fail("Unexpected field id " + pi.getFieldNumber()); 235 } 236 } 237 stream.close(); 238 239 assertEquals('a', char1); 240 assertNotNull(obj); 241 assertEquals('b', obj.mChar); 242 assertEquals('c', char4); 243 } 244 245 /** 246 * Test reading a char, then an object with an int and a string in it, then a char. 247 */ testComplexObject()248 public void testComplexObject() throws IOException { 249 testComplexObject(0); 250 testComplexObject(1); 251 testComplexObject(5); 252 } 253 254 /** 255 * Implementation of testComplexObject for a given chunkSize. 256 */ testComplexObject(int chunkSize)257 private void testComplexObject(int chunkSize) throws IOException { 258 final long uintFieldFlags = 259 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32; 260 final long messageFieldFlags = 261 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; 262 263 final long charId1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL); 264 final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); 265 final long charId4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL); 266 267 final byte[] protobuf = new byte[]{ 268 // 1 -> 'x' 269 (byte) 0x08, (byte) 0x78, 270 // begin object 2 271 (byte) 0x12, (byte) 0x10, 272 // 2 -> 'y' 273 (byte) 0x10, (byte) 0x79, 274 // 4 -> "abcdefghijkl" 275 (byte) 0x22, (byte) 0x0c, 276 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, 277 (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6a, (byte) 0x6b, (byte) 0x6c, 278 // 4 -> 'z' 279 (byte) 0x20, (byte) 0x7a, 280 }; 281 282 InputStream stream = new ByteArrayInputStream(protobuf); 283 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 284 285 SimpleObject obj = null; 286 char char1 = '\0'; 287 char char4 = '\0'; 288 289 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 290 switch (pi.getFieldNumber()) { 291 case (int) charId1: 292 char1 = (char) pi.readInt(charId1); 293 break; 294 case (int) messageId2: 295 final long token = pi.start(messageId2); 296 obj = new SimpleObject(); 297 obj.parseProto(pi); 298 pi.end(token); 299 break; 300 case (int) charId4: 301 char4 = (char) pi.readInt(charId4); 302 break; 303 default: 304 fail("Unexpected field id " + pi.getFieldNumber()); 305 } 306 } 307 stream.close(); 308 309 assertEquals('x', char1); 310 assertNotNull(obj); 311 assertEquals('y', obj.mChar); 312 assertEquals("abcdefghijkl", obj.mString); 313 assertEquals('z', char4); 314 } 315 316 /** 317 * Test reading 3 levels deep of objects. 318 */ testDeepObjects()319 public void testDeepObjects() throws IOException { 320 testDeepObjects(0); 321 testDeepObjects(1); 322 testDeepObjects(5); 323 } 324 325 /** 326 * Implementation of testDeepObjects for a given chunkSize. 327 */ testDeepObjects(int chunkSize)328 private void testDeepObjects(int chunkSize) throws IOException { 329 final long messageFieldFlags = 330 ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; 331 final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL); 332 333 final byte[] protobuf = new byte[]{ 334 // begin object id 2 335 (byte) 0x12, (byte) 0x1a, 336 // 2 -> 'a' 337 (byte) 0x10, (byte) 0x61, 338 // begin nested object id 5 339 (byte) 0x2a, (byte) 0x15, 340 // 5000 -> '\u3110' 341 (byte) 0xc0, (byte) 0xb8, 342 (byte) 0x02, (byte) 0x90, (byte) 0x62, 343 // begin nested object id 5 344 (byte) 0x2a, (byte) 0x0e, 345 // 4 -> "abcdefghijkl" 346 (byte) 0x22, (byte) 0x0c, 347 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, 348 (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6a, (byte) 0x6b, (byte) 0x6c, 349 }; 350 351 InputStream stream = new ByteArrayInputStream(protobuf); 352 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 353 354 SimpleObject obj = null; 355 356 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 357 switch (pi.getFieldNumber()) { 358 case (int) messageId2: 359 final long token = pi.start(messageId2); 360 obj = new SimpleObject(); 361 obj.parseProto(pi); 362 pi.end(token); 363 break; 364 default: 365 fail("Unexpected field id " + pi.getFieldNumber()); 366 } 367 } 368 stream.close(); 369 370 assertNotNull(obj); 371 assertEquals('a', obj.mChar); 372 assertNotNull(obj.mNested); 373 assertEquals('\u3110', obj.mNested.mLargeChar); 374 assertNotNull(obj.mNested.mNested); 375 assertEquals("abcdefghijkl", obj.mNested.mNested.mString); 376 } 377 378 /** 379 * Test that using the wrong read method throws an exception 380 */ testBadReadType()381 public void testBadReadType() throws IOException { 382 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; 383 384 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 385 386 final byte[] protobuf = new byte[]{ 387 // 1 -> {1} 388 (byte) 0x0a, 389 (byte) 0x01, 390 (byte) 0x01, 391 }; 392 393 ProtoInputStream pi = new ProtoInputStream(protobuf); 394 pi.nextField(); 395 try { 396 pi.readFloat(fieldId1); 397 fail("Should have thrown IllegalArgumentException"); 398 } catch (IllegalArgumentException iae) { 399 // good 400 } 401 402 pi = new ProtoInputStream(protobuf); 403 pi.nextField(); 404 try { 405 pi.readDouble(fieldId1); 406 fail("Should have thrown IllegalArgumentException"); 407 } catch (IllegalArgumentException iae) { 408 // good 409 } 410 411 pi = new ProtoInputStream(protobuf); 412 pi.nextField(); 413 try { 414 pi.readInt(fieldId1); 415 fail("Should have thrown IllegalArgumentException"); 416 } catch (IllegalArgumentException iae) { 417 // good 418 } 419 420 pi = new ProtoInputStream(protobuf); 421 pi.nextField(); 422 try { 423 pi.readLong(fieldId1); 424 fail("Should have thrown IllegalArgumentException"); 425 } catch (IllegalArgumentException iae) { 426 // good 427 } 428 429 pi = new ProtoInputStream(protobuf); 430 pi.nextField(); 431 try { 432 pi.readBoolean(fieldId1); 433 fail("Should have thrown IllegalArgumentException"); 434 } catch (IllegalArgumentException iae) { 435 // good 436 } 437 438 pi = new ProtoInputStream(protobuf); 439 pi.nextField(); 440 try { 441 pi.readString(fieldId1); 442 fail("Should have thrown IllegalArgumentException"); 443 } catch (IllegalArgumentException iae) { 444 // good 445 } 446 } 447 448 /** 449 * Test that unexpected wrong wire types will throw an exception 450 */ testBadWireType()451 public void testBadWireType() throws IOException { 452 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE; 453 454 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 455 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 456 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 457 final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); 458 459 final byte[] protobuf = new byte[]{ 460 // 1 : varint -> 1 461 (byte) 0x08, 462 (byte) 0x01, 463 // 2 : fixed64 -> 0x1 464 (byte) 0x11, 465 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 466 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 467 // 3 : length delimited -> { 1 } 468 (byte) 0x1a, 469 (byte) 0x01, 470 (byte) 0x01, 471 // 6 : fixed32 472 (byte) 0x35, 473 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 474 }; 475 476 InputStream stream = new ByteArrayInputStream(protobuf); 477 final ProtoInputStream pi = new ProtoInputStream(stream); 478 479 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 480 try { 481 switch (pi.getFieldNumber()) { 482 case (int) fieldId1: 483 pi.readBytes(fieldId1); 484 fail("Should have thrown a WireTypeMismatchException"); 485 break; 486 case (int) fieldId2: 487 pi.readBytes(fieldId2); 488 fail("Should have thrown a WireTypeMismatchException"); 489 break; 490 case (int) fieldId3: 491 pi.readBytes(fieldId3); 492 // don't fail, length delimited is ok 493 break; 494 case (int) fieldId6: 495 pi.readBytes(fieldId6); 496 fail("Should have thrown a WireTypeMismatchException"); 497 break; 498 default: 499 fail("Unexpected field id " + pi.getFieldNumber()); 500 } 501 } catch (WireTypeMismatchException wtme) { 502 // good 503 } 504 } 505 stream.close(); 506 } 507 } 508