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 ProtoInputStreamStringTest 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_STRING; 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 50 final byte[] protobuf = new byte[]{ 51 // 1 -> null - default value, not written 52 // 2 -> "" - default value, not written 53 // 3 -> "abcd\u3110!" 54 (byte) 0x1a, 55 (byte) 0x08, 56 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, 57 (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21, 58 // 5 -> "Hi" 59 (byte) 0x2a, 60 (byte) 0x02, 61 (byte) 0x48, (byte) 0x69, 62 // 4 -> "Hi" 63 (byte) 0x22, 64 (byte) 0x02, 65 (byte) 0x48, (byte) 0x69, 66 }; 67 68 InputStream stream = new ByteArrayInputStream(protobuf); 69 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 70 String[] results = new String[4]; 71 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 72 73 switch (pi.getFieldNumber()) { 74 case (int) fieldId1: 75 results[0] = pi.readString(fieldId1); 76 break; 77 case (int) fieldId2: 78 results[1] = pi.readString(fieldId2); 79 break; 80 case (int) fieldId3: 81 results[2] = pi.readString(fieldId3); 82 break; 83 case (int) fieldId4: 84 results[3] = pi.readString(fieldId4); 85 break; 86 case (int) fieldId5: 87 // Intentionally don't read the data. Parse should continue normally 88 break; 89 default: 90 fail("Unexpected field id " + pi.getFieldNumber()); 91 } 92 } 93 stream.close(); 94 95 assertNull(results[0]); 96 assertNull(results[1]); 97 assertEquals("abcd\u3110!", results[2]); 98 assertEquals("Hi", results[3]); 99 } 100 101 102 /** 103 * Test that reading with ProtoInputStream matches, and can read the output of standard proto. 104 */ testReadCompat()105 public void testReadCompat() throws Exception { 106 testReadCompat(""); 107 testReadCompat("abcd\u3110!"); 108 } 109 110 /** 111 * Implementation of testReadCompat with a given value. 112 */ testReadCompat(String val)113 private void testReadCompat(String val) throws Exception { 114 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING; 115 final long fieldId = fieldFlags | ((long) 140 & 0x0ffffffffL); 116 117 final Test.All all = new Test.All(); 118 all.stringField = val; 119 120 final byte[] proto = MessageNano.toByteArray(all); 121 122 final ProtoInputStream pi = new ProtoInputStream(proto); 123 final Test.All readback = Test.All.parseFrom(proto); 124 125 String result = ""; 126 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 127 switch (pi.getFieldNumber()) { 128 case (int) fieldId: 129 result = pi.readString(fieldId); 130 break; 131 default: 132 fail("Unexpected field id " + pi.getFieldNumber()); 133 } 134 } 135 136 assertEquals(readback.stringField, result); 137 } 138 139 testRepeated()140 public void testRepeated() throws IOException { 141 testRepeated(0); 142 testRepeated(1); 143 testRepeated(5); 144 } 145 testRepeated(int chunkSize)146 private void testRepeated(int chunkSize) throws IOException { 147 final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING; 148 149 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 150 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 151 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 152 final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL); 153 final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL); 154 155 final byte[] protobuf = new byte[]{ 156 // 1 -> null - default value, written when repeated 157 (byte) 0x0a, 158 (byte) 0x00, 159 // 2 -> "" - default value, written when repeated 160 (byte) 0x12, 161 (byte) 0x00, 162 // 3 -> "abcd\u3110!" 163 (byte) 0x1a, 164 (byte) 0x08, 165 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, 166 (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21, 167 // 4 -> "Hi" 168 (byte) 0x22, 169 (byte) 0x02, 170 (byte) 0x48, (byte) 0x69, 171 172 // 5 -> "Hi" 173 (byte) 0x2a, 174 (byte) 0x02, 175 (byte) 0x48, (byte) 0x69, 176 177 // 1 -> null - default value, written when repeated 178 (byte) 0x0a, 179 (byte) 0x00, 180 // 2 -> "" - default value, written when repeated 181 (byte) 0x12, 182 (byte) 0x00, 183 // 3 -> "abcd\u3110!" 184 (byte) 0x1a, 185 (byte) 0x08, 186 (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, 187 (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21, 188 // 4 -> "Hi" 189 (byte) 0x22, 190 (byte) 0x02, 191 (byte) 0x48, (byte) 0x69, 192 }; 193 194 InputStream stream = new ByteArrayInputStream(protobuf); 195 final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize); 196 String[][] results = new String[4][2]; 197 int[] indices = new int[4]; 198 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 199 200 switch (pi.getFieldNumber()) { 201 case (int) fieldId1: 202 results[0][indices[0]++] = pi.readString(fieldId1); 203 break; 204 case (int) fieldId2: 205 results[1][indices[1]++] = pi.readString(fieldId2); 206 break; 207 case (int) fieldId3: 208 results[2][indices[2]++] = pi.readString(fieldId3); 209 break; 210 case (int) fieldId4: 211 results[3][indices[3]++] = pi.readString(fieldId4); 212 break; 213 case (int) fieldId5: 214 // Intentionally don't read the data. Parse should continue normally 215 break; 216 default: 217 fail("Unexpected field id " + pi.getFieldNumber()); 218 } 219 } 220 stream.close(); 221 222 223 assertEquals("", results[0][0]); 224 assertEquals("", results[0][1]); 225 assertEquals("", results[1][0]); 226 assertEquals("", results[1][1]); 227 assertEquals("abcd\u3110!", results[2][0]); 228 assertEquals("abcd\u3110!", results[2][1]); 229 assertEquals("Hi", results[3][0]); 230 assertEquals("Hi", results[3][1]); 231 } 232 233 /** 234 * Test that reading with ProtoInputStream matches, and can read the output of standard proto. 235 */ testRepeatedCompat()236 public void testRepeatedCompat() throws Exception { 237 testRepeatedCompat(new String[0]); 238 testRepeatedCompat(new String[]{"", "abcd\u3110!", "Hi"}); 239 } 240 241 /** 242 * Implementation of testRepeatedCompat with a given value. 243 */ testRepeatedCompat(String[] val)244 private void testRepeatedCompat(String[] val) throws Exception { 245 final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING; 246 final long fieldId = fieldFlags | ((long) 141 & 0x0ffffffffL); 247 248 final Test.All all = new Test.All(); 249 all.stringFieldRepeated = val; 250 251 final byte[] proto = MessageNano.toByteArray(all); 252 253 final ProtoInputStream pi = new ProtoInputStream(proto); 254 final Test.All readback = Test.All.parseFrom(proto); 255 256 String[] result = new String[val.length]; 257 int index = 0; 258 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 259 switch (pi.getFieldNumber()) { 260 case (int) fieldId: 261 result[index++] = pi.readString(fieldId); 262 break; 263 default: 264 fail("Unexpected field id " + pi.getFieldNumber()); 265 } 266 } 267 268 assertEquals(readback.stringFieldRepeated.length, result.length); 269 for (int i = 0; i < result.length; i++) { 270 assertEquals(readback.stringFieldRepeated[i], result[i]); 271 } 272 } 273 274 /** 275 * Test that using the wrong read method throws an exception 276 */ testBadReadType()277 public void testBadReadType() throws IOException { 278 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING; 279 280 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 281 282 final byte[] protobuf = new byte[]{ 283 // 1 -> {1} 284 (byte) 0x0a, 285 (byte) 0x01, 286 (byte) 0x01, 287 }; 288 289 ProtoInputStream pi = new ProtoInputStream(protobuf); 290 pi.nextField(); 291 try { 292 pi.readFloat(fieldId1); 293 fail("Should have thrown IllegalArgumentException"); 294 } catch (IllegalArgumentException iae) { 295 // good 296 } 297 298 pi = new ProtoInputStream(protobuf); 299 pi.nextField(); 300 try { 301 pi.readDouble(fieldId1); 302 fail("Should have thrown IllegalArgumentException"); 303 } catch (IllegalArgumentException iae) { 304 // good 305 } 306 307 pi = new ProtoInputStream(protobuf); 308 pi.nextField(); 309 try { 310 pi.readInt(fieldId1); 311 fail("Should have thrown IllegalArgumentException"); 312 } catch (IllegalArgumentException iae) { 313 // good 314 } 315 316 pi = new ProtoInputStream(protobuf); 317 pi.nextField(); 318 try { 319 pi.readLong(fieldId1); 320 fail("Should have thrown IllegalArgumentException"); 321 } catch (IllegalArgumentException iae) { 322 // good 323 } 324 325 pi = new ProtoInputStream(protobuf); 326 pi.nextField(); 327 try { 328 pi.readBytes(fieldId1); 329 fail("Should have thrown IllegalArgumentException"); 330 } catch (IllegalArgumentException iae) { 331 // good 332 } 333 334 pi = new ProtoInputStream(protobuf); 335 pi.nextField(); 336 try { 337 pi.readBoolean(fieldId1); 338 fail("Should have thrown IllegalArgumentException"); 339 } catch (IllegalArgumentException iae) { 340 // good 341 } 342 } 343 344 /** 345 * Test that unexpected wrong wire types will throw an exception 346 */ testBadWireType()347 public void testBadWireType() throws IOException { 348 final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING; 349 350 final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL); 351 final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL); 352 final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL); 353 final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL); 354 355 final byte[] protobuf = new byte[]{ 356 // 1 : varint -> 1 357 (byte) 0x08, 358 (byte) 0x01, 359 // 2 : fixed64 -> 0x1 360 (byte) 0x11, 361 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 362 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 363 // 3 : length delimited -> { 1 } 364 (byte) 0x1a, 365 (byte) 0x01, 366 (byte) 0x01, 367 // 6 : fixed32 368 (byte) 0x35, 369 (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, 370 }; 371 372 InputStream stream = new ByteArrayInputStream(protobuf); 373 final ProtoInputStream pi = new ProtoInputStream(stream); 374 375 while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 376 try { 377 switch (pi.getFieldNumber()) { 378 case (int) fieldId1: 379 pi.readString(fieldId1); 380 fail("Should have thrown a WireTypeMismatchException"); 381 break; 382 case (int) fieldId2: 383 pi.readString(fieldId2); 384 fail("Should have thrown a WireTypeMismatchException"); 385 break; 386 case (int) fieldId3: 387 pi.readString(fieldId3); 388 // don't fail, length delimited is ok (represents packed booleans) 389 break; 390 case (int) fieldId6: 391 pi.readString(fieldId6); 392 fail("Should have thrown a WireTypeMismatchException"); 393 break; 394 default: 395 fail("Unexpected field id " + pi.getFieldNumber()); 396 } 397 } catch (WireTypeMismatchException wtme) { 398 // good 399 } 400 } 401 stream.close(); 402 } 403 404 } 405