diff options
author | Mohamed Heikal <mheikal@google.com> | 2018-11-07 16:49:02 -0500 |
---|---|---|
committer | Mohamed Heikal <mheikal@google.com> | 2018-12-20 18:19:25 -0500 |
commit | c76940363197de1772b761aa38e819b55fb80cb7 (patch) | |
tree | 28c9afa0e073be59b396dbe8aebc6a118deca382 /tools/aapt2/optimize | |
parent | 9da2ff0fdc6af2153c12701ae8344f10f9a26413 (diff) |
Resource Path Obfuscation
This CL allows aapt2 to obfuscate resource paths within the output apk
and move resources to shorter obfuscated paths. This reduces apk size
when there is a large number of resources since the path metadata exists
in 4 places in the apk.
This CL adds two arguments to aapt2, one to enable resource path
obfuscation and one to point to a path to output the path map to (for
later debugging).
Test: make aapt2_tests
Bug: b/75965637
Change-Id: I9cacafe1d17800d673566b2d61b0b88f3fb8d60c
Diffstat (limited to 'tools/aapt2/optimize')
-rw-r--r-- | tools/aapt2/optimize/ResourcePathShortener.cpp | 112 | ||||
-rw-r--r-- | tools/aapt2/optimize/ResourcePathShortener.h | 44 | ||||
-rw-r--r-- | tools/aapt2/optimize/ResourcePathShortener_test.cpp | 67 |
3 files changed, 223 insertions, 0 deletions
diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/ResourcePathShortener.cpp new file mode 100644 index 000000000000..c5df3dd00db9 --- /dev/null +++ b/tools/aapt2/optimize/ResourcePathShortener.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "optimize/ResourcePathShortener.h" + +#include <math.h> +#include <unordered_set> + +#include "androidfw/StringPiece.h" + +#include "ResourceTable.h" +#include "ValueVisitor.h" + + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; + +namespace aapt { + +ResourcePathShortener::ResourcePathShortener( + std::map<std::string, std::string>& path_map_out) + : path_map_(path_map_out) { +} + +std::string ShortenFileName(const android::StringPiece& file_path, int output_length) { + std::size_t hash_num = std::hash<android::StringPiece>{}(file_path); + std::string result = ""; + // Convert to (modified) base64 so that it is a proper file path. + for (int i = 0; i < output_length; i++) { + uint8_t sextet = hash_num & 0x3f; + hash_num >>= 6; + result += base64_chars[sextet]; + } + return result; +} + + +// Calculate the optimal hash length such that an average of 10% of resources +// collide in their shortened path. +// Reference: http://matt.might.net/articles/counting-hash-collisions/ +int OptimalShortenedLength(int num_resources) { + int num_chars = 2; + double N = 64*64; // hash space when hash is 2 chars long + double max_collisions = num_resources * 0.1; + while (num_resources - N + N * pow((N - 1) / N, num_resources) > max_collisions) { + N *= 64; + num_chars++; + } + return num_chars; +} + +std::string GetShortenedPath(const android::StringPiece& shortened_filename, + const android::StringPiece& extension, int collision_count) { + std::string shortened_path = "res/" + shortened_filename.to_string(); + if (collision_count > 0) { + shortened_path += std::to_string(collision_count); + } + shortened_path += extension; + return shortened_path; +} + +bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) { + // used to detect collisions + std::unordered_set<std::string> shortened_paths; + std::unordered_set<FileReference*> file_refs; + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + for (auto& config_value : entry->values) { + FileReference* file_ref = ValueCast<FileReference>(config_value->value.get()); + if (file_ref) { + file_refs.insert(file_ref); + } + } + } + } + } + int num_chars = OptimalShortenedLength(file_refs.size()); + for (auto& file_ref : file_refs) { + android::StringPiece res_subdir, actual_filename, extension; + util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension); + + std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars); + int collision_count = 0; + std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); + while (shortened_paths.find(shortened_path) != shortened_paths.end()) { + collision_count++; + shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); + } + shortened_paths.insert(shortened_path); + path_map_.insert({*file_ref->path, shortened_path}); + file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext()); + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/optimize/ResourcePathShortener.h b/tools/aapt2/optimize/ResourcePathShortener.h new file mode 100644 index 000000000000..f1074ef083bd --- /dev/null +++ b/tools/aapt2/optimize/ResourcePathShortener.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H +#define AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H + +#include <map> + +#include "android-base/macros.h" + +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +class ResourceTable; + +// Maps resources in the apk to shortened paths. +class ResourcePathShortener : public IResourceTableConsumer { + public: + explicit ResourcePathShortener(std::map<std::string, std::string>& path_map_out); + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ResourcePathShortener); + std::map<std::string, std::string>& path_map_; +}; + +} // namespace aapt + +#endif // AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/ResourcePathShortener_test.cpp new file mode 100644 index 000000000000..88cadc76c336 --- /dev/null +++ b/tools/aapt2/optimize/ResourcePathShortener_test.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "optimize/ResourcePathShortener.h" + +#include "ResourceTable.h" +#include "test/Test.h" + +using ::aapt::test::GetValue; +using ::testing::Not; +using ::testing::NotNull; +using ::testing::Eq; + +namespace aapt { + +TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml") + .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml") + .AddString("android:string/string", "res/should/still/be/the/same.png") + .Build(); + + std::map<std::string, std::string> path_map; + ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get())); + + // Expect that the path map is populated + ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end()))); + ASSERT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end()))); + + // The file paths were changed + EXPECT_THAT(path_map.at("res/drawables/xmlfile.xml"), Not(Eq("res/drawables/xmlfile.xml"))); + EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml"))); + + // Different file paths should remain different + EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], + Not(Eq(path_map["res/drawables/xmlfile2.xml"]))); + + FileReference* ref = + GetValue<FileReference>(table.get(), "android:drawable/xmlfile"); + ASSERT_THAT(ref, NotNull()); + // The map correctly points to the new location of the file + EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], Eq(*ref->path)); + + // Strings should not be affected, only file paths + EXPECT_THAT( + *GetValue<String>(table.get(), "android:string/string")->value, + Eq("res/should/still/be/the/same.png")); + EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end())); +} + +} // namespace aapt |