/* * 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. */ #pragma once #include #include #include #include #include #include // FRIEND_TEST #include "otautil/rangeset.h" // Represents the target info used in a Command. TargetInfo contains the ranges of the blocks and // the expected hash. class TargetInfo { public: TargetInfo() = default; TargetInfo(std::string hash, RangeSet ranges) : hash_(std::move(hash)), ranges_(std::move(ranges)) {} const std::string& hash() const { return hash_; } const RangeSet& ranges() const { return ranges_; } size_t blocks() const { return ranges_.blocks(); } bool operator==(const TargetInfo& other) const { return hash_ == other.hash_ && ranges_ == other.ranges_; } private: friend std::ostream& operator<<(std::ostream& os, const TargetInfo& source); // The hash of the data represented by the object. std::string hash_; // The block ranges that the data should be written to. RangeSet ranges_; }; std::ostream& operator<<(std::ostream& os, const TargetInfo& source); // Represents the stash info used in a Command. class StashInfo { public: StashInfo() = default; StashInfo(std::string id, RangeSet ranges) : id_(std::move(id)), ranges_(std::move(ranges)) {} size_t blocks() const { return ranges_.blocks(); } const std::string& id() const { return id_; } const RangeSet& ranges() const { return ranges_; } bool operator==(const StashInfo& other) const { return id_ == other.id_ && ranges_ == other.ranges_; } private: friend std::ostream& operator<<(std::ostream& os, const StashInfo& stash); // The id (i.e. hash) of the stash. std::string id_; // The matching location of the stash. RangeSet ranges_; }; std::ostream& operator<<(std::ostream& os, const StashInfo& stash); // Represents the source info in a Command, whose data could come from source image, stashed blocks, // or both. class SourceInfo { public: SourceInfo() = default; SourceInfo(std::string hash, RangeSet ranges, RangeSet location, std::vector stashes) : hash_(std::move(hash)), ranges_(std::move(ranges)), location_(std::move(location)), stashes_(std::move(stashes)) { blocks_ = ranges_.blocks(); for (const auto& stash : stashes_) { blocks_ += stash.ranges().blocks(); } } // Reads all the data specified by this SourceInfo object into the given 'buffer', by calling the // given readers. Caller needs to specify the block size for the represented blocks. The given // buffer needs to be sufficiently large. Otherwise it returns false. 'block_reader' and // 'stash_reader' read the specified data into the given buffer (guaranteed to be large enough) // respectively. The readers should return 0 on success, or -1 on error. bool ReadAll( std::vector* buffer, size_t block_size, const std::function*)>& block_reader, const std::function*)>& stash_reader) const; // Whether this SourceInfo overlaps with the given TargetInfo object. bool Overlaps(const TargetInfo& target) const; // Dumps the hashes in hex for the given buffer that's loaded from this SourceInfo object // (excluding the stashed blocks which are handled separately). void DumpBuffer(const std::vector& buffer, size_t block_size) const; const std::string& hash() const { return hash_; } size_t blocks() const { return blocks_; } bool operator==(const SourceInfo& other) const { return hash_ == other.hash_ && ranges_ == other.ranges_ && location_ == other.location_ && stashes_ == other.stashes_; } private: friend std::ostream& operator<<(std::ostream& os, const SourceInfo& source); // The hash of the data represented by the object. std::string hash_; // The block ranges from the source image to read data from. This could be a subset of all the // blocks represented by the object, or empty if all the data should be loaded from stash. RangeSet ranges_; // The location in the buffer to load ranges_ into. Empty if ranges_ alone covers all the blocks // (i.e. nothing needs to be loaded from stash). RangeSet location_; // The info for the stashed blocks that are part of the source. Empty if there's none. std::vector stashes_; // Total number of blocks represented by the object. size_t blocks_{ 0 }; }; std::ostream& operator<<(std::ostream& os, const SourceInfo& source); class PatchInfo { public: PatchInfo() = default; PatchInfo(size_t offset, size_t length) : offset_(offset), length_(length) {} size_t offset() const { return offset_; } size_t length() const { return length_; } bool operator==(const PatchInfo& other) const { return offset_ == other.offset_ && length_ == other.length_; } private: size_t offset_{ 0 }; size_t length_{ 0 }; }; // The arguments to build a hash tree from blocks on the block device. class HashTreeInfo { public: HashTreeInfo() = default; HashTreeInfo(RangeSet hash_tree_ranges, RangeSet source_ranges, std::string hash_algorithm, std::string salt_hex, std::string root_hash) : hash_tree_ranges_(std::move(hash_tree_ranges)), source_ranges_(std::move(source_ranges)), hash_algorithm_(std::move(hash_algorithm)), salt_hex_(std::move(salt_hex)), root_hash_(std::move(root_hash)) {} const RangeSet& hash_tree_ranges() const { return hash_tree_ranges_; } const RangeSet& source_ranges() const { return source_ranges_; } const std::string& hash_algorithm() const { return hash_algorithm_; } const std::string& salt_hex() const { return salt_hex_; } const std::string& root_hash() const { return root_hash_; } bool operator==(const HashTreeInfo& other) const { return hash_tree_ranges_ == other.hash_tree_ranges_ && source_ranges_ == other.source_ranges_ && hash_algorithm_ == other.hash_algorithm_ && salt_hex_ == other.salt_hex_ && root_hash_ == other.root_hash_; } private: RangeSet hash_tree_ranges_; RangeSet source_ranges_; std::string hash_algorithm_; std::string salt_hex_; std::string root_hash_; }; // Command class holds the info for an update command that performs block-based OTA (BBOTA). Each // command consists of one or several args, namely TargetInfo, SourceInfo, StashInfo and PatchInfo. // The currently used BBOTA version is v4. // // zero // - Fill the indicated blocks with zeros. // - Meaningful args: TargetInfo // // new // - Fill the blocks with data read from the new_data file. // - Meaningful args: TargetInfo // // erase // - Mark the given blocks as empty. // - Meaningful args: TargetInfo // // move <...> // - Read the source blocks, write result to target blocks. // - Meaningful args: TargetInfo, SourceInfo // // See the note below for <...>. // // bsdiff <...> // imgdiff <...> // - Read the source blocks, apply a patch, and write result to target blocks. // - Meaningful args: PatchInfo, TargetInfo, SourceInfo // // It expects <...> in one of the following formats: // // - <[stash_id:stash_location] ...> // (loads data from stashes only) // // // (loads data from source image only) // // // <[stash_id:stash_location] ...> // (loads data from both of source image and stashes) // // stash // - Load the given source blocks and stash the data in the given slot of the stash table. // - Meaningful args: StashInfo // // free // - Free the given stash data. // - Meaningful args: StashInfo // // compute_hash_tree // - Computes the hash_tree bytes and writes the result to the specified range on the // block_device. // // abort // - Abort the current update. Allowed for testing code only. // class Command { public: enum class Type { ABORT, BSDIFF, COMPUTE_HASH_TREE, ERASE, FREE, IMGDIFF, MOVE, NEW, STASH, ZERO, LAST, // Not a valid type. }; Command() = default; Command(Type type, size_t index, std::string cmdline, PatchInfo patch, TargetInfo target, SourceInfo source, StashInfo stash) : type_(type), index_(index), cmdline_(std::move(cmdline)), patch_(std::move(patch)), target_(std::move(target)), source_(std::move(source)), stash_(std::move(stash)) {} Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info); // Parses the given command 'line' into a Command object and returns it. The 'index' is specified // by the caller to index the object. On parsing error, it returns an empty Command object that // evaluates to false, and the specific error message will be set in 'err'. static Command Parse(const std::string& line, size_t index, std::string* err); // Parses the command type from the given string. static Type ParseType(const std::string& type_str); Type type() const { return type_; } size_t index() const { return index_; } const std::string& cmdline() const { return cmdline_; } const PatchInfo& patch() const { return patch_; } const TargetInfo& target() const { return target_; } const SourceInfo& source() const { return source_; } const StashInfo& stash() const { return stash_; } const HashTreeInfo& hash_tree_info() const { return hash_tree_info_; } size_t block_size() const { return block_size_; } constexpr explicit operator bool() const { return type_ != Type::LAST; } private: friend class ResumableUpdaterTest; friend class UpdaterTest; FRIEND_TEST(CommandsTest, Parse_ABORT_Allowed); FRIEND_TEST(CommandsTest, Parse_InvalidNumberOfArgs); FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput); FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly); FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes); FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly); // Parses the target and source info from the given 'tokens' vector. Saves the parsed info into // 'target' and 'source' objects. Returns the parsing result. Error message will be set in 'err' // on parsing error, and the contents in 'target' and 'source' will be undefined. static bool ParseTargetInfoAndSourceInfo(const std::vector& tokens, const std::string& tgt_hash, TargetInfo* target, const std::string& src_hash, SourceInfo* source, std::string* err); // Allows parsing ABORT command, which should be used for testing purpose only. static bool abort_allowed_; // The type of the command. Type type_{ Type::LAST }; // The index of the Command object, which is specified by the caller. size_t index_{ 0 }; // The input string that the Command object is parsed from. std::string cmdline_; // The patch info. Only meaningful for BSDIFF and IMGDIFF commands. PatchInfo patch_; // The target info, where the command should be written to. TargetInfo target_; // The source info to load the source blocks for the command. SourceInfo source_; // The stash info. Only meaningful for STASH and FREE commands. Note that although SourceInfo may // also load data from stash, such info will be owned and managed by SourceInfo (i.e. in source_). StashInfo stash_; // The hash_tree info. Only meaningful for COMPUTE_HASH_TREE. HashTreeInfo hash_tree_info_; // The unit size of each block to be used in this command. size_t block_size_{ 4096 }; }; std::ostream& operator<<(std::ostream& os, const Command& command); // TransferList represents the info for a transfer list, which is parsed from input text lines // containing commands to transfer data from one place to another on the target partition. // // The creator of the transfer list will guarantee that no block is read (i.e., used as the source // for a patch or move) after it has been written. // // The creator will guarantee that a given stash is loaded (with a stash command) before it's used // in a move/bsdiff/imgdiff command. // // Within one command the source and target ranges may overlap so in general we need to read the // entire source into memory before writing anything to the target blocks. // // All the patch data is concatenated into one patch_data file in the update package. It must be // stored uncompressed because we memory-map it in directly from the archive. (Since patches are // already compressed, we lose very little by not compressing their concatenation.) // // Commands that read data from the partition (i.e. move/bsdiff/imgdiff/stash) have one or more // additional hashes before the range parameters, which are used to check if the command has // already been completed and verify the integrity of the source data. class TransferList { public: // Number of header lines. static constexpr size_t kTransferListHeaderLines = 4; TransferList() = default; // Parses the given input string and returns a TransferList object. Sets error message if any. static TransferList Parse(const std::string& transfer_list_str, std::string* err); int version() const { return version_; } size_t total_blocks() const { return total_blocks_; } size_t stash_max_entries() const { return stash_max_entries_; } size_t stash_max_blocks() const { return stash_max_blocks_; } const std::vector& commands() const { return commands_; } // Returns whether the TransferList is valid. constexpr explicit operator bool() const { return version_ != 0; } private: // BBOTA version. int version_{ 0 }; // Total number of blocks to be written in this transfer. size_t total_blocks_; // Maximum number of stashes that exist at the same time. size_t stash_max_entries_; // Maximum number of blocks to be stashed. size_t stash_max_blocks_; // Commands in this transfer. std::vector commands_; };