1 /* 2 * Copyright (C) 2015 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 #include "format/binary/TableFlattener.h" 18 19 #include "android-base/stringprintf.h" 20 #include "androidfw/TypeWrappers.h" 21 22 #include "ResChunkPullParser.h" 23 #include "ResourceUtils.h" 24 #include "SdkConstants.h" 25 #include "format/binary/BinaryResourceParser.h" 26 #include "test/Test.h" 27 #include "util/Util.h" 28 29 using namespace android; 30 31 using ::testing::Gt; 32 using ::testing::IsNull; 33 using ::testing::NotNull; 34 35 namespace aapt { 36 37 class TableFlattenerTest : public ::testing::Test { 38 public: 39 void SetUp() override { 40 context_ = 41 test::ContextBuilder().SetCompilationPackage("com.app.test").SetPackageId(0x7f).Build(); 42 } 43 44 ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options, 45 ResourceTable* table, std::string* out_content) { 46 BigBuffer buffer(1024); 47 TableFlattener flattener(options, &buffer); 48 if (!flattener.Consume(context, table)) { 49 return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; 50 } 51 *out_content = buffer.to_string(); 52 return ::testing::AssertionSuccess(); 53 } 54 55 ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options, 56 ResourceTable* table, ResTable* out_table) { 57 std::string content; 58 auto result = Flatten(context, options, table, &content); 59 if (!result) { 60 return result; 61 } 62 63 if (out_table->add(content.data(), content.size(), 1, true) != NO_ERROR) { 64 return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; 65 } 66 return ::testing::AssertionSuccess(); 67 } 68 69 ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options, 70 ResourceTable* table, ResourceTable* out_table) { 71 std::string content; 72 auto result = Flatten(context, options, table, &content); 73 if (!result) { 74 return result; 75 } 76 77 BinaryResourceParser parser(context->GetDiagnostics(), out_table, {}, content.data(), 78 content.size()); 79 if (!parser.Parse()) { 80 return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; 81 } 82 return ::testing::AssertionSuccess(); 83 } 84 85 ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name, 86 const ResourceId& expected_id, 87 const ConfigDescription& expected_config, 88 const uint8_t expected_data_type, const uint32_t expected_data, 89 const uint32_t expected_spec_flags) { 90 const ResourceName expected_res_name = test::ParseNameOrDie(expected_name); 91 92 table->setParameters(&expected_config); 93 94 ResTable_config config; 95 Res_value val; 96 uint32_t spec_flags; 97 if (table->getResource(expected_id.id, &val, false, 0, &spec_flags, &config) < 0) { 98 return ::testing::AssertionFailure() << "could not find resource with"; 99 } 100 101 if (expected_data_type != val.dataType) { 102 return ::testing::AssertionFailure() 103 << "expected data type " << std::hex << (int)expected_data_type 104 << " but got data type " << (int)val.dataType << std::dec << " instead"; 105 } 106 107 if (expected_data != val.data) { 108 return ::testing::AssertionFailure() 109 << "expected data " << std::hex << expected_data << " but got data " << val.data 110 << std::dec << " instead"; 111 } 112 113 if (expected_spec_flags != spec_flags) { 114 return ::testing::AssertionFailure() 115 << "expected specFlags " << std::hex << expected_spec_flags << " but got specFlags " 116 << spec_flags << std::dec << " instead"; 117 } 118 119 ResTable::resource_name actual_name; 120 if (!table->getResourceName(expected_id.id, false, &actual_name)) { 121 return ::testing::AssertionFailure() << "failed to find resource name"; 122 } 123 124 Maybe<ResourceName> resName = ResourceUtils::ToResourceName(actual_name); 125 if (!resName) { 126 return ::testing::AssertionFailure() 127 << "expected name '" << expected_res_name << "' but got '" 128 << StringPiece16(actual_name.package, actual_name.packageLen) << ":" 129 << StringPiece16(actual_name.type, actual_name.typeLen) << "/" 130 << StringPiece16(actual_name.name, actual_name.nameLen) << "'"; 131 } 132 133 ResourceName actual_res_name(resName.value()); 134 135 if (expected_res_name.entry != actual_res_name.entry || 136 expected_res_name.package != actual_res_name.package || 137 expected_res_name.type != actual_res_name.type) { 138 return ::testing::AssertionFailure() << "expected resource '" << expected_res_name.to_string() 139 << "' but got '" << actual_res_name.to_string() << "'"; 140 } 141 142 if (expected_config != config) { 143 return ::testing::AssertionFailure() << "expected config '" << expected_config 144 << "' but got '" << ConfigDescription(config) << "'"; 145 } 146 return ::testing::AssertionSuccess(); 147 } 148 149 protected: 150 std::unique_ptr<IAaptContext> context_; 151 }; 152 153 TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) { 154 std::unique_ptr<ResourceTable> table = 155 test::ResourceTableBuilder() 156 .SetPackageId("com.app.test", 0x7f) 157 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) 158 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) 159 .AddValue("com.app.test:id/three", ResourceId(0x7f020002), 160 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000))) 161 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), 162 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) 163 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), 164 ResourceId(0x7f030000), 165 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) 166 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo") 167 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml") 168 .Build(); 169 170 ResTable res_table; 171 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table)); 172 173 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), {}, 174 Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); 175 176 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001), {}, 177 Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); 178 179 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {}, 180 Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); 181 182 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), {}, 183 Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION)); 184 185 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), 186 test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u, 187 ResTable_config::CONFIG_VERSION)); 188 189 std::u16string foo_str = u"foo"; 190 ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); 191 ASSERT_GE(idx, 0); 192 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {}, 193 Res_value::TYPE_STRING, (uint32_t)idx, 0u)); 194 195 std::u16string bar_path = u"res/layout/bar.xml"; 196 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); 197 ASSERT_GE(idx, 0); 198 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar", ResourceId(0x7f050000), {}, 199 Res_value::TYPE_STRING, (uint32_t)idx, 0u)); 200 } 201 202 TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) { 203 std::unique_ptr<ResourceTable> table = 204 test::ResourceTableBuilder() 205 .SetPackageId("com.app.test", 0x7f) 206 .AddSimple("com.app.test:id/one", ResourceId(0x7f020001)) 207 .AddSimple("com.app.test:id/three", ResourceId(0x7f020003)) 208 .Build(); 209 210 ResTable res_table; 211 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table)); 212 213 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001), {}, 214 Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); 215 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020003), {}, 216 Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); 217 } 218 219 TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) { 220 Attribute attr; 221 attr.type_mask = android::ResTable_map::TYPE_INTEGER; 222 attr.min_int = 10; 223 attr.max_int = 23; 224 std::unique_ptr<ResourceTable> table = 225 test::ResourceTableBuilder() 226 .SetPackageId("android", 0x01) 227 .AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>(attr)) 228 .Build(); 229 230 ResourceTable result; 231 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result)); 232 233 Attribute* actual_attr = test::GetValue<Attribute>(&result, "android:attr/foo"); 234 ASSERT_THAT(actual_attr, NotNull()); 235 EXPECT_EQ(attr.IsWeak(), actual_attr->IsWeak()); 236 EXPECT_EQ(attr.type_mask, actual_attr->type_mask); 237 EXPECT_EQ(attr.min_int, actual_attr->min_int); 238 EXPECT_EQ(attr.max_int, actual_attr->max_int); 239 } 240 241 TEST_F(TableFlattenerTest, FlattenArray) { 242 auto array = util::make_unique<Array>(); 243 array->elements.push_back(util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 244 1u)); 245 array->elements.push_back(util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 246 2u)); 247 std::unique_ptr<ResourceTable> table = 248 test::ResourceTableBuilder() 249 .SetPackageId("android", 0x01) 250 .AddValue("android:array/foo", ResourceId(0x01010000), std::move(array)) 251 .Build(); 252 253 std::string result; 254 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result)); 255 256 // Parse the flattened resource table 257 ResChunkPullParser parser(result.data(), result.size()); 258 ASSERT_TRUE(parser.IsGoodEvent(parser.Next())); 259 ASSERT_EQ(util::DeviceToHost16(parser.chunk()->type), RES_TABLE_TYPE); 260 261 // Retrieve the package of the entry 262 ResChunkPullParser table_parser(GetChunkData(parser.chunk()), GetChunkDataLen(parser.chunk())); 263 const ResChunk_header* package_chunk = nullptr; 264 while (table_parser.IsGoodEvent(table_parser.Next())) { 265 if (util::DeviceToHost16(table_parser.chunk()->type) == RES_TABLE_PACKAGE_TYPE) { 266 package_chunk = table_parser.chunk(); 267 break; 268 } 269 } 270 271 // Retrieve the type that proceeds the array entry 272 ASSERT_NE(package_chunk, nullptr); 273 ResChunkPullParser package_parser(GetChunkData(table_parser.chunk()), 274 GetChunkDataLen(table_parser.chunk())); 275 const ResChunk_header* type_chunk = nullptr; 276 while (package_parser.IsGoodEvent(package_parser.Next())) { 277 if (util::DeviceToHost16(package_parser.chunk()->type) == RES_TABLE_TYPE_TYPE) { 278 type_chunk = package_parser.chunk(); 279 break; 280 } 281 } 282 283 // Retrieve the array entry 284 ASSERT_NE(type_chunk, nullptr); 285 TypeVariant typeVariant((const ResTable_type*) type_chunk); 286 auto entry = (const ResTable_map_entry*)*typeVariant.beginEntries(); 287 ASSERT_EQ(util::DeviceToHost16(entry->count), 2u); 288 289 // Check that the value and name of the array entries are correct 290 auto values = (const ResTable_map*)(((const uint8_t *)entry) + entry->size); 291 ASSERT_EQ(values->value.data, 1u); 292 ASSERT_EQ(values->name.ident, android::ResTable_map::ATTR_MIN); 293 ASSERT_EQ((values+1)->value.data, 2u); 294 ASSERT_EQ((values+1)->name.ident, android::ResTable_map::ATTR_MIN + 1); 295 } 296 297 static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries( 298 IAaptContext* context, const ConfigDescription& sparse_config, float load) { 299 std::unique_ptr<ResourceTable> table = 300 test::ResourceTableBuilder() 301 .SetPackageId(context->GetCompilationPackage(), context->GetPackageId()) 302 .Build(); 303 304 // Add regular entries. 305 int stride = static_cast<int>(1.0f / load); 306 for (int i = 0; i < 100; i++) { 307 const ResourceName name = test::ParseNameOrDie( 308 base::StringPrintf("%s:string/foo_%d", context->GetCompilationPackage().data(), i)); 309 const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i)); 310 const auto value = 311 util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i)); 312 CHECK(table->AddResourceWithId(name, resid, ConfigDescription::DefaultConfig(), "", 313 std::unique_ptr<Value>(value->Clone(nullptr)), 314 context->GetDiagnostics())); 315 316 // Every few entries, write out a sparse_config value. This will give us the desired load. 317 if (i % stride == 0) { 318 CHECK(table->AddResourceWithId(name, resid, sparse_config, "", 319 std::unique_ptr<Value>(value->Clone(nullptr)), 320 context->GetDiagnostics())); 321 } 322 } 323 return table; 324 } 325 326 TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { 327 std::unique_ptr<IAaptContext> context = test::ContextBuilder() 328 .SetCompilationPackage("android") 329 .SetPackageId(0x01) 330 .SetMinSdkVersion(SDK_O) 331 .Build(); 332 333 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB"); 334 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); 335 336 TableFlattenerOptions options; 337 options.use_sparse_entries = true; 338 339 std::string no_sparse_contents; 340 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); 341 342 std::string sparse_contents; 343 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents)); 344 345 EXPECT_GT(no_sparse_contents.size(), sparse_contents.size()); 346 347 // Attempt to parse the sparse contents. 348 349 ResourceTable sparse_table; 350 BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"), 351 sparse_contents.data(), sparse_contents.size()); 352 ASSERT_TRUE(parser.Parse()); 353 354 auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0", 355 sparse_config); 356 ASSERT_THAT(value, NotNull()); 357 EXPECT_EQ(0u, value->value.data); 358 359 ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", 360 sparse_config), 361 IsNull()); 362 363 value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", 364 sparse_config); 365 ASSERT_THAT(value, NotNull()); 366 EXPECT_EQ(4u, value->value.data); 367 } 368 369 TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) { 370 std::unique_ptr<IAaptContext> context = test::ContextBuilder() 371 .SetCompilationPackage("android") 372 .SetPackageId(0x01) 373 .SetMinSdkVersion(SDK_LOLLIPOP) 374 .Build(); 375 376 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26"); 377 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); 378 379 TableFlattenerOptions options; 380 options.use_sparse_entries = true; 381 382 std::string no_sparse_contents; 383 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); 384 385 std::string sparse_contents; 386 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents)); 387 388 EXPECT_GT(no_sparse_contents.size(), sparse_contents.size()); 389 } 390 391 TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) { 392 std::unique_ptr<IAaptContext> context = test::ContextBuilder() 393 .SetCompilationPackage("android") 394 .SetPackageId(0x01) 395 .SetMinSdkVersion(SDK_O) 396 .Build(); 397 398 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB"); 399 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f); 400 401 TableFlattenerOptions options; 402 options.use_sparse_entries = true; 403 404 std::string no_sparse_contents; 405 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); 406 407 std::string sparse_contents; 408 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents)); 409 410 EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size()); 411 } 412 413 TEST_F(TableFlattenerTest, FlattenSharedLibrary) { 414 std::unique_ptr<IAaptContext> context = 415 test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build(); 416 std::unique_ptr<ResourceTable> table = 417 test::ResourceTableBuilder() 418 .SetPackageId("lib", 0x00) 419 .AddValue("lib:id/foo", ResourceId(0x00010000), util::make_unique<Id>()) 420 .Build(); 421 ResourceTable result; 422 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); 423 424 Maybe<ResourceTable::SearchResult> search_result = 425 result.FindResource(test::ParseNameOrDie("lib:id/foo")); 426 ASSERT_TRUE(search_result); 427 EXPECT_EQ(0x00u, search_result.value().package->id.value()); 428 429 auto iter = result.included_packages_.find(0x00); 430 ASSERT_NE(result.included_packages_.end(), iter); 431 EXPECT_EQ("lib", iter->second); 432 } 433 434 TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) { 435 std::unique_ptr<IAaptContext> context = 436 test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build(); 437 std::unique_ptr<ResourceTable> table = 438 test::ResourceTableBuilder() 439 .SetPackageId("app", 0x7f) 440 .AddValue("app:id/foo", ResourceId(0x7f010000), 441 test::BuildReference("lib_one:id/foo", ResourceId(0x02010000))) 442 .AddValue("app:id/bar", ResourceId(0x7f010001), 443 test::BuildReference("lib_two:id/bar", ResourceId(0x03010000))) 444 .Build(); 445 table->included_packages_[0x02] = "lib_one"; 446 table->included_packages_[0x03] = "lib_two"; 447 448 ResTable result; 449 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); 450 451 const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1); 452 ASSERT_THAT(dynamic_ref_table, NotNull()); 453 454 const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries(); 455 456 ssize_t idx = entries.indexOfKey(android::String16("lib_one")); 457 ASSERT_GE(idx, 0); 458 EXPECT_EQ(0x02u, entries.valueAt(idx)); 459 460 idx = entries.indexOfKey(android::String16("lib_two")); 461 ASSERT_GE(idx, 0); 462 EXPECT_EQ(0x03u, entries.valueAt(idx)); 463 } 464 465 TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) { 466 std::unique_ptr<IAaptContext> context = 467 test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x80).Build(); 468 std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() 469 .SetPackageId("app", 0x80) 470 .AddSimple("app:id/foo", ResourceId(0x80010000)) 471 .Build(); 472 473 ResTable result; 474 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); 475 476 const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1); 477 ASSERT_THAT(dynamic_ref_table, NotNull()); 478 479 const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries(); 480 ssize_t idx = entries.indexOfKey(android::String16("app")); 481 ASSERT_GE(idx, 0); 482 EXPECT_EQ(0x80u, entries.valueAt(idx)); 483 } 484 485 TEST_F(TableFlattenerTest, LongPackageNameIsTruncated) { 486 std::string kPackageName(256, 'F'); 487 488 std::unique_ptr<IAaptContext> context = 489 test::ContextBuilder().SetCompilationPackage(kPackageName).SetPackageId(0x7f).Build(); 490 std::unique_ptr<ResourceTable> table = 491 test::ResourceTableBuilder() 492 .SetPackageId(kPackageName, 0x7f) 493 .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000)) 494 .Build(); 495 496 ResTable result; 497 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); 498 499 ASSERT_EQ(1u, result.getBasePackageCount()); 500 EXPECT_EQ(127u, result.getBasePackageName(0).size()); 501 } 502 503 TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) { 504 std::string kPackageName(256, 'F'); 505 506 std::unique_ptr<IAaptContext> context = test::ContextBuilder() 507 .SetCompilationPackage(kPackageName) 508 .SetPackageId(0x7f) 509 .SetPackageType(PackageType::kSharedLib) 510 .Build(); 511 std::unique_ptr<ResourceTable> table = 512 test::ResourceTableBuilder() 513 .SetPackageId(kPackageName, 0x7f) 514 .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000)) 515 .Build(); 516 517 ResTable result; 518 ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result)); 519 } 520 521 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) { 522 std::unique_ptr<ResourceTable> table = 523 test::ResourceTableBuilder() 524 .SetPackageId("com.app.test", 0x7f) 525 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) 526 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) 527 .AddValue("com.app.test:id/three", ResourceId(0x7f020002), 528 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000))) 529 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), 530 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) 531 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), 532 ResourceId(0x7f030000), 533 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) 534 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo") 535 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml") 536 .Build(); 537 538 TableFlattenerOptions options; 539 options.collapse_key_stringpool = true; 540 541 ResTable res_table; 542 543 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); 544 545 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", 546 ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); 547 548 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", 549 ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); 550 551 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", 552 ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); 553 554 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", 555 ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, 556 ResTable_config::CONFIG_VERSION)); 557 558 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", 559 ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 560 2u, ResTable_config::CONFIG_VERSION)); 561 562 std::u16string foo_str = u"foo"; 563 ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); 564 ASSERT_GE(idx, 0); 565 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated", 566 ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); 567 568 std::u16string bar_path = u"res/layout/bar.xml"; 569 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); 570 ASSERT_GE(idx, 0); 571 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", 572 ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); 573 } 574 575 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { 576 std::unique_ptr<ResourceTable> table = 577 test::ResourceTableBuilder() 578 .SetPackageId("com.app.test", 0x7f) 579 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) 580 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) 581 .AddValue("com.app.test:id/three", ResourceId(0x7f020002), 582 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000))) 583 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), 584 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) 585 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), 586 ResourceId(0x7f030000), 587 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) 588 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo") 589 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml") 590 .Build(); 591 592 TableFlattenerOptions options; 593 options.collapse_key_stringpool = true; 594 options.whitelisted_resources.insert("test"); 595 options.whitelisted_resources.insert("three"); 596 ResTable res_table; 597 598 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); 599 600 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", 601 ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); 602 603 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", 604 ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); 605 606 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {}, 607 Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); 608 609 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", 610 ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, 611 ResTable_config::CONFIG_VERSION)); 612 613 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", 614 ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 615 2u, ResTable_config::CONFIG_VERSION)); 616 617 std::u16string foo_str = u"foo"; 618 ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); 619 ASSERT_GE(idx, 0); 620 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {}, 621 Res_value::TYPE_STRING, (uint32_t)idx, 0u)); 622 623 std::u16string bar_path = u"res/layout/bar.xml"; 624 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); 625 ASSERT_GE(idx, 0); 626 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", 627 ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); 628 } 629 630 TEST_F(TableFlattenerTest, FlattenOverlayable) { 631 OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); 632 overlayable_item.policies |= OverlayableItem::Policy::kProduct; 633 overlayable_item.policies |= OverlayableItem::Policy::kSystem; 634 overlayable_item.policies |= OverlayableItem::Policy::kVendor; 635 636 std::string name = "com.app.test:integer/overlayable"; 637 std::unique_ptr<ResourceTable> table = 638 test::ResourceTableBuilder() 639 .SetPackageId("com.app.test", 0x7f) 640 .AddSimple(name, ResourceId(0x7f020000)) 641 .SetOverlayable(name, overlayable_item) 642 .Build(); 643 644 ResourceTable output_table; 645 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table)); 646 647 auto search_result = output_table.FindResource(test::ParseNameOrDie(name)); 648 ASSERT_TRUE(search_result); 649 ASSERT_THAT(search_result.value().entry, NotNull()); 650 ASSERT_TRUE(search_result.value().entry->overlayable_item); 651 OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); 652 EXPECT_EQ(result_overlayable_item.policies, OverlayableItem::Policy::kSystem 653 | OverlayableItem::Policy::kVendor 654 | OverlayableItem::Policy::kProduct); 655 } 656 657 TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { 658 auto overlayable = std::make_shared<Overlayable>("TestName", "overlay://theme"); 659 std::string name_zero = "com.app.test:integer/overlayable_zero_item"; 660 OverlayableItem overlayable_item_zero(overlayable); 661 overlayable_item_zero.policies |= OverlayableItem::Policy::kProduct; 662 overlayable_item_zero.policies |= OverlayableItem::Policy::kSystem; 663 664 std::string name_one = "com.app.test:integer/overlayable_one_item"; 665 OverlayableItem overlayable_item_one(overlayable); 666 overlayable_item_one.policies |= OverlayableItem::Policy::kPublic; 667 668 std::string name_two = "com.app.test:integer/overlayable_two_item"; 669 OverlayableItem overlayable_item_two(overlayable); 670 overlayable_item_two.policies |= OverlayableItem::Policy::kProduct; 671 overlayable_item_two.policies |= OverlayableItem::Policy::kSystem; 672 overlayable_item_two.policies |= OverlayableItem::Policy::kVendor; 673 674 std::unique_ptr<ResourceTable> table = 675 test::ResourceTableBuilder() 676 .SetPackageId("com.app.test", 0x7f) 677 .AddSimple(name_zero, ResourceId(0x7f020000)) 678 .SetOverlayable(name_zero, overlayable_item_zero) 679 .AddSimple(name_one, ResourceId(0x7f020001)) 680 .SetOverlayable(name_one, overlayable_item_one) 681 .AddSimple(name_two, ResourceId(0x7f020002)) 682 .SetOverlayable(name_two, overlayable_item_two) 683 .Build(); 684 685 ResourceTable output_table; 686 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table)); 687 688 auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero)); 689 ASSERT_TRUE(search_result); 690 ASSERT_THAT(search_result.value().entry, NotNull()); 691 ASSERT_TRUE(search_result.value().entry->overlayable_item); 692 OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value(); 693 EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem 694 | OverlayableItem::Policy::kProduct); 695 696 search_result = output_table.FindResource(test::ParseNameOrDie(name_one)); 697 ASSERT_TRUE(search_result); 698 ASSERT_THAT(search_result.value().entry, NotNull()); 699 ASSERT_TRUE(search_result.value().entry->overlayable_item); 700 overlayable_item = search_result.value().entry->overlayable_item.value(); 701 EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic); 702 703 search_result = output_table.FindResource(test::ParseNameOrDie(name_two)); 704 ASSERT_TRUE(search_result); 705 ASSERT_THAT(search_result.value().entry, NotNull()); 706 ASSERT_TRUE(search_result.value().entry->overlayable_item); 707 overlayable_item = search_result.value().entry->overlayable_item.value(); 708 EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem 709 | OverlayableItem::Policy::kProduct 710 | OverlayableItem::Policy::kVendor); 711 } 712 713 TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { 714 auto group = std::make_shared<Overlayable>("TestName", "overlay://theme"); 715 std::string name_zero = "com.app.test:integer/overlayable_zero"; 716 OverlayableItem overlayable_item_zero(group); 717 overlayable_item_zero.policies |= OverlayableItem::Policy::kProduct; 718 overlayable_item_zero.policies |= OverlayableItem::Policy::kSystem; 719 720 auto group_one = std::make_shared<Overlayable>("OtherName", "overlay://customization"); 721 std::string name_one = "com.app.test:integer/overlayable_one"; 722 OverlayableItem overlayable_item_one(group_one); 723 overlayable_item_one.policies |= OverlayableItem::Policy::kPublic; 724 725 std::string name_two = "com.app.test:integer/overlayable_two"; 726 OverlayableItem overlayable_item_two(group); 727 overlayable_item_two.policies |= OverlayableItem::Policy::kOdm; 728 overlayable_item_two.policies |= OverlayableItem::Policy::kOem; 729 overlayable_item_two.policies |= OverlayableItem::Policy::kVendor; 730 731 std::string name_three = "com.app.test:integer/overlayable_three"; 732 OverlayableItem overlayable_item_three(group_one); 733 overlayable_item_three.policies |= OverlayableItem::Policy::kSignature; 734 735 std::unique_ptr<ResourceTable> table = 736 test::ResourceTableBuilder() 737 .SetPackageId("com.app.test", 0x7f) 738 .AddSimple(name_zero, ResourceId(0x7f020000)) 739 .SetOverlayable(name_zero, overlayable_item_zero) 740 .AddSimple(name_one, ResourceId(0x7f020001)) 741 .SetOverlayable(name_one, overlayable_item_one) 742 .AddSimple(name_two, ResourceId(0x7f020002)) 743 .SetOverlayable(name_two, overlayable_item_two) 744 .AddSimple(name_three, ResourceId(0x7f020003)) 745 .SetOverlayable(name_three, overlayable_item_three) 746 .Build(); 747 748 ResourceTable output_table; 749 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table)); 750 auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero)); 751 ASSERT_TRUE(search_result); 752 ASSERT_THAT(search_result.value().entry, NotNull()); 753 ASSERT_TRUE(search_result.value().entry->overlayable_item); 754 OverlayableItem& result_overlayable = search_result.value().entry->overlayable_item.value(); 755 EXPECT_EQ(result_overlayable.overlayable->name, "TestName"); 756 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme"); 757 EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kSystem 758 | OverlayableItem::Policy::kProduct); 759 760 search_result = output_table.FindResource(test::ParseNameOrDie(name_one)); 761 ASSERT_TRUE(search_result); 762 ASSERT_THAT(search_result.value().entry, NotNull()); 763 ASSERT_TRUE(search_result.value().entry->overlayable_item); 764 result_overlayable = search_result.value().entry->overlayable_item.value(); 765 EXPECT_EQ(result_overlayable.overlayable->name, "OtherName"); 766 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization"); 767 EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kPublic); 768 769 search_result = output_table.FindResource(test::ParseNameOrDie(name_two)); 770 ASSERT_TRUE(search_result); 771 ASSERT_THAT(search_result.value().entry, NotNull()); 772 ASSERT_TRUE(search_result.value().entry->overlayable_item); 773 result_overlayable = search_result.value().entry->overlayable_item.value(); 774 EXPECT_EQ(result_overlayable.overlayable->name, "TestName"); 775 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme"); 776 EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kOdm 777 | OverlayableItem::Policy::kOem 778 | OverlayableItem::Policy::kVendor); 779 780 search_result = output_table.FindResource(test::ParseNameOrDie(name_three)); 781 ASSERT_TRUE(search_result); 782 ASSERT_THAT(search_result.value().entry, NotNull()); 783 ASSERT_TRUE(search_result.value().entry->overlayable_item); 784 result_overlayable = search_result.value().entry->overlayable_item.value(); 785 EXPECT_EQ(result_overlayable.overlayable->name, "OtherName"); 786 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization"); 787 EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kSignature); 788 } 789 790 TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) { 791 auto group = std::make_shared<Overlayable>("TestName", "overlay://theme"); 792 std::string name_zero = "com.app.test:integer/overlayable_zero"; 793 OverlayableItem overlayable_item_zero(group); 794 795 std::unique_ptr<ResourceTable> table = 796 test::ResourceTableBuilder() 797 .SetPackageId("com.app.test", 0x7f) 798 .AddSimple(name_zero, ResourceId(0x7f020000)) 799 .SetOverlayable(name_zero, overlayable_item_zero) 800 .Build(); 801 ResourceTable output_table; 802 ASSERT_FALSE(Flatten(context_.get(), {}, table.get(), &output_table)); 803 } 804 805 } // namespace aapt 806