libresource/utils/Serialize.h (247 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #ifndef _FB_ANDROID_SERIALIZE_H #define _FB_ANDROID_SERIALIZE_H #include <map> #include <memory> #include <string> #include <unordered_map> #include <unordered_set> #include <vector> #include "androidfw/ResourceTypes.h" #include "utils/ByteOrder.h" #include "utils/Debug.h" #include "utils/Log.h" #include "utils/String16.h" #include "utils/String8.h" #include "utils/TypeHelpers.h" #include "utils/Unicode.h" #include "utils/Vector.h" namespace arsc { constexpr uint32_t PACKAGE_NAME_ARR_LENGTH = 128; void align_vec(size_t s, android::Vector<char>* vec); void push_short(uint16_t data, android::Vector<char>* vec); void push_long(uint32_t data, android::Vector<char>* vec); void push_u8_length(size_t len, android::Vector<char>* vec); void encode_string8(const android::String8& s, android::Vector<char>* vec); void encode_string16(const android::String16& s, android::Vector<char>* vec); // Returns the size of the entry and the value data structure(s) that follow it. size_t compute_entry_value_length(android::ResTable_entry* entry); // Return in device order the flags for the entry in the type uint32_t get_spec_flags(android::ResTable_typeSpec* spec, uint16_t entry_id); // Whether or not the two configs should be treated as equal (note: this is not // simply a byte by byte compare). bool are_configs_equivalent(android::ResTable_config* a, android::ResTable_config* b); enum StringKind { STD_STRING, STRING_8, STRING_16 }; struct StringHolder { StringKind kind; const char* string8; const char16_t* string16; const std::string str; size_t length; }; using SpanVector = std::vector<android::ResStringPool_span*>; struct StyleInfo { StringHolder str; SpanVector spans; }; template <typename T> using PtrLen = android::key_value_pair_t<T*, size_t>; class ResStringPoolBuilder { public: ResStringPoolBuilder(uint32_t flags) : m_flags(flags) {} // Note: in all cases, callers must be encoding string data properly, per // https://source.android.com/devices/tech/dalvik/dex-format#mutf-8 void add_string(std::string); void add_string(const char*, size_t); void add_string(const char16_t*, size_t); void add_style(std::string, SpanVector); void add_style(const char*, size_t, SpanVector); void add_style(const char16_t*, size_t, SpanVector); void serialize(android::Vector<char>* out); size_t string_count() { return non_style_string_count() + style_count(); } private: uint32_t m_flags; bool is_utf8() { return (m_flags & android::ResStringPool_header::UTF8_FLAG) != 0; } size_t non_style_string_count() { return m_strings.size(); } size_t style_count() { return m_styles.size(); } std::vector<StringHolder> m_strings; std::vector<StyleInfo> m_styles; }; using EntryValueData = PtrLen<uint8_t>; using EntryOffsetData = std::pair<EntryValueData, uint32_t>; // Helper to record identical entry/value data that has already been emitted for // a certain type. class CanonicalEntries { public: CanonicalEntries() {} // Returns true and sets the offset output parameter if identical data has // already been noted. Sets the hash output param regardless. bool find(const EntryValueData& data, size_t* out_hash, uint32_t* out_offset); void record(EntryValueData data, size_t hash, uint32_t offset); private: size_t hash(const EntryValueData& data); // Hash to pair of the entry/value bytes with the hash code, and the offset to // the serialized data. std::unordered_map<size_t, std::vector<EntryOffsetData>> m_canonical_entries; }; // Builder for serializing a ResTable_typeSpec structure with N ResTable_type // structures (and entries). As with other Builder classes, this can be used two // ways: // 1) Create new type, entry data. // 2) Project deletions over existing data structures. class ResTableTypeBuilder { public: ResTableTypeBuilder(uint32_t package_id, uint8_t type, bool enable_canonical_entries) : m_package_id(package_id), m_type(type), m_enable_canonical_entries(enable_canonical_entries) { LOG_ALWAYS_FATAL_IF((package_id & 0xFFFFFF00) != 0, "package_id expected to have low byte set; got 0x%x", package_id); } virtual ~ResTableTypeBuilder() {} uint8_t get_type_id() { return m_type; } uint32_t make_id(size_t entry) { return (m_package_id << 24) | (m_type << 16) | (entry & 0xFFFF); } virtual void serialize(android::Vector<char>* out) = 0; protected: // The (unshifted) number of the package to which this type belongs. uint32_t m_package_id; // The non-zero ID of this type uint8_t m_type; // Whether or not to check for redundant entry/value data. bool m_enable_canonical_entries; }; // Builder for projecting deletions over existing data ResTable_typeSpec and its // corresponding ResTable_type structures (as well as entries/values.) class ResTableTypeProjector : public ResTableTypeBuilder { public: ResTableTypeProjector(uint32_t package_id, android::ResTable_typeSpec* spec, std::vector<android::ResTable_type*> configs, bool enable_canonical_entries = false) : ResTableTypeBuilder(package_id, spec->id, enable_canonical_entries), m_spec(spec), m_configs(std::move(configs)) {} void remove_ids(std::unordered_set<uint32_t>& ids_to_remove) { m_ids_to_remove = ids_to_remove; } void serialize(android::Vector<char>* out) override; virtual ~ResTableTypeProjector() {} private: void serialize_type(android::ResTable_type*, android::Vector<char>* out); android::ResTable_typeSpec* m_spec; std::vector<android::ResTable_type*> m_configs; // This takes effect during file serialization std::unordered_set<uint32_t> m_ids_to_remove; }; // Builder for defining a new ResTable_typeSpec along with its ResTable_type // structures, entries, values. In all cases, given data should be in device // order. class ResTableTypeDefiner : public ResTableTypeBuilder { public: ResTableTypeDefiner(uint32_t package_id, uint8_t id, std::vector<android::ResTable_config*> configs, std::vector<uint32_t> flags, bool enable_canonical_entries = false) : ResTableTypeBuilder(package_id, id, enable_canonical_entries), m_configs(std::move(configs)), m_flags(std::move(flags)) {} // Adds a chunk of data representing an entry and value to the given config. void add(android::ResTable_config* config, EntryValueData data) { auto search = m_data.find(config); if (search == m_data.end()) { std::vector<EntryValueData> vec; m_data.emplace(config, std::move(vec)); } auto& vec = m_data.at(config); vec.emplace_back(data); } // Convenience method to add empty entry/value to the given config. void add_empty(android::ResTable_config* config) { EntryValueData ev(nullptr, 0); add(config, ev); } void serialize(android::Vector<char>* out) override; virtual ~ResTableTypeDefiner() {} private: // NOTE: size of m_configs should match the size of m_data. Inner vectors of // m_data should all have the same size, and that size should be equal to // m_flag's size. std::unordered_map<android::ResTable_config*, std::vector<EntryValueData>> m_data; const std::vector<android::ResTable_config*> m_configs; const std::vector<uint32_t> m_flags; }; // Struct for defining an existing type and the collection of entries in all // configs. struct TypeInfo { android::ResTable_typeSpec* spec; std::vector<android::ResTable_type*> configs; }; // Builder class for copying existing data to a new/modified package. // Subsequent work, to make this more full featured could be to define a // ResTypeBuilder class, and let this append either TypeInfo (to copy existing // data) or a builder to overhaul a type or define a brand new type. class ResPackageBuilder { public: ResPackageBuilder() : m_package_name{0} {} // Copies fields from the source package that will remain unchanged in the // output (i.e. id, package name, etc). ResPackageBuilder(android::ResTable_package* package); void set_id(uint32_t id) { m_id = id; }; void set_last_public_type(uint32_t last_public_type) { m_last_public_type = last_public_type; } void set_last_public_key(uint32_t last_public_key) { m_last_public_key = last_public_key; } void set_type_id_offset(uint32_t type_id_offset) { m_type_id_offset = type_id_offset; } // Copy the package name from an existing struct (in device order) void copy_package_name(android::ResTable_package* package) { for (uint32_t i = 0; i < PACKAGE_NAME_ARR_LENGTH; i++) { m_package_name[i] = dtohs(package->name[i]); } } // Adds type info which will be emitted as-is to the serialized package. void add_type(TypeInfo& info) { auto pair = std::make_pair(nullptr, info); m_id_to_type.emplace(info.spec->id, std::move(pair)); } // Delegate to the builder to emit data when serializing. void add_type(std::shared_ptr<ResTableTypeBuilder> builder) { TypeInfo empty; auto pair = std::make_pair(builder, std::move(empty)); m_id_to_type.emplace(builder->get_type_id(), std::move(pair)); } void set_key_strings(std::shared_ptr<ResStringPoolBuilder> builder) { m_key_strings.first = builder; } void set_key_strings(android::ResStringPool_header* existing_data) { m_key_strings.second = existing_data; } void set_type_strings(std::shared_ptr<ResStringPoolBuilder> builder) { m_type_strings.first = builder; } void set_type_strings(android::ResStringPool_header* existing_data) { m_type_strings.second = existing_data; } void add_chunk(android::ResChunk_header* header) { m_unknown_chunks.emplace_back(header); } void serialize(android::Vector<char>* out); private: // Pairs here are meant to be used like a union, set only one of them (defined // as a pair simply to inspect which is set). std::pair<std::shared_ptr<ResStringPoolBuilder>, android::ResStringPool_header*> m_key_strings; std::pair<std::shared_ptr<ResStringPoolBuilder>, android::ResStringPool_header*> m_type_strings; std::map<uint8_t, std::pair<std::shared_ptr<ResTableTypeBuilder>, TypeInfo>> m_id_to_type; // Chunks to emit after all type info. Meant to represent any unparsed struct // like libraries, overlay, etc. std::vector<android::ResChunk_header*> m_unknown_chunks; uint32_t m_id = 0; uint32_t m_last_public_type = 0; uint32_t m_last_public_key = 0; uint32_t m_type_id_offset = 0; uint16_t m_package_name[PACKAGE_NAME_ARR_LENGTH]; }; // Builder for a resource table, with support for either bulk appending package // data or defining a new package with builder APIs. class ResTableBuilder { public: void set_global_strings(std::shared_ptr<ResStringPoolBuilder> builder) { m_global_strings.first = builder; } void set_global_strings(android::ResStringPool_header* existing_data) { m_global_strings.second = existing_data; } void add_package(std::shared_ptr<ResPackageBuilder> builder) { m_packages.emplace_back(std::make_pair(builder, nullptr)); } void add_package(android::ResTable_package* existing_data) { m_packages.emplace_back(std::make_pair(nullptr, existing_data)); } void serialize(android::Vector<char>* out); private: // Pairs here are meant to be used like a union, set only one of them (defined // as a pair simply to inspect which is set). std::pair<std::shared_ptr<ResStringPoolBuilder>, android::ResStringPool_header*> m_global_strings; std::vector< std::pair<std::shared_ptr<ResPackageBuilder>, android::ResTable_package*>> m_packages; }; } // namespace arsc #endif