| /* |
| * 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 "private/commands.h" |
| |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <functional> |
| #include <ostream> |
| #include <string> |
| #include <vector> |
| |
| #include <android-base/logging.h> |
| #include <android-base/parseint.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <openssl/sha.h> |
| |
| #include "otautil/print_sha1.h" |
| #include "otautil/rangeset.h" |
| |
| using namespace std::string_literals; |
| |
| bool Command::abort_allowed_ = false; |
| |
| Command::Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info) |
| : type_(type), |
| index_(index), |
| cmdline_(std::move(cmdline)), |
| hash_tree_info_(std::move(hash_tree_info)) { |
| CHECK(type == Type::COMPUTE_HASH_TREE); |
| } |
| |
| Command::Type Command::ParseType(const std::string& type_str) { |
| if (type_str == "abort") { |
| if (!abort_allowed_) { |
| LOG(ERROR) << "ABORT disallowed"; |
| return Type::LAST; |
| } |
| return Type::ABORT; |
| } else if (type_str == "bsdiff") { |
| return Type::BSDIFF; |
| } else if (type_str == "compute_hash_tree") { |
| return Type::COMPUTE_HASH_TREE; |
| } else if (type_str == "erase") { |
| return Type::ERASE; |
| } else if (type_str == "free") { |
| return Type::FREE; |
| } else if (type_str == "imgdiff") { |
| return Type::IMGDIFF; |
| } else if (type_str == "move") { |
| return Type::MOVE; |
| } else if (type_str == "new") { |
| return Type::NEW; |
| } else if (type_str == "stash") { |
| return Type::STASH; |
| } else if (type_str == "zero") { |
| return Type::ZERO; |
| } |
| return Type::LAST; |
| }; |
| |
| bool Command::ParseTargetInfoAndSourceInfo(const std::vector<std::string>& tokens, |
| const std::string& tgt_hash, TargetInfo* target, |
| const std::string& src_hash, SourceInfo* source, |
| std::string* err) { |
| // We expect the given args (in 'tokens' vector) in one of the following formats. |
| // |
| // <tgt_ranges> <src_block_count> - <[stash_id:location] ...> |
| // (loads data from stashes only) |
| // |
| // <tgt_ranges> <src_block_count> <src_ranges> |
| // (loads data from source image only) |
| // |
| // <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location> <[stash_id:location] ...> |
| // (loads data from both of source image and stashes) |
| |
| // At least it needs to provide three args: <tgt_ranges>, <src_block_count> and "-"/<src_ranges>. |
| if (tokens.size() < 3) { |
| *err = "invalid number of args"; |
| return false; |
| } |
| |
| size_t pos = 0; |
| RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]); |
| if (!tgt_ranges) { |
| *err = "invalid target ranges"; |
| return false; |
| } |
| *target = TargetInfo(tgt_hash, tgt_ranges); |
| |
| // <src_block_count> |
| const std::string& token = tokens[pos++]; |
| size_t src_blocks; |
| if (!android::base::ParseUint(token, &src_blocks)) { |
| *err = "invalid src_block_count \""s + token + "\""; |
| return false; |
| } |
| |
| RangeSet src_ranges; |
| RangeSet src_ranges_location; |
| // "-" or <src_ranges> [<src_ranges_location>] |
| if (tokens[pos] == "-") { |
| // no source ranges, only stashes |
| pos++; |
| } else { |
| src_ranges = RangeSet::Parse(tokens[pos++]); |
| if (!src_ranges) { |
| *err = "invalid source ranges"; |
| return false; |
| } |
| |
| if (pos >= tokens.size()) { |
| // No stashes, only source ranges. |
| SourceInfo result(src_hash, src_ranges, {}, {}); |
| |
| if (result.blocks() != src_blocks) { |
| *err = |
| android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(), |
| src_ranges.ToString().c_str(), src_blocks); |
| return false; |
| } |
| |
| *source = result; |
| return true; |
| } |
| |
| src_ranges_location = RangeSet::Parse(tokens[pos++]); |
| if (!src_ranges_location) { |
| *err = "invalid source ranges location"; |
| return false; |
| } |
| } |
| |
| // <[stash_id:stash_location]> |
| std::vector<StashInfo> stashes; |
| while (pos < tokens.size()) { |
| // Each word is a an index into the stash table, a colon, and then a RangeSet describing where |
| // in the source block that stashed data should go. |
| std::vector<std::string> pairs = android::base::Split(tokens[pos++], ":"); |
| if (pairs.size() != 2) { |
| *err = "invalid stash info"; |
| return false; |
| } |
| RangeSet stash_location = RangeSet::Parse(pairs[1]); |
| if (!stash_location) { |
| *err = "invalid stash location"; |
| return false; |
| } |
| stashes.emplace_back(pairs[0], stash_location); |
| } |
| |
| SourceInfo result(src_hash, src_ranges, src_ranges_location, stashes); |
| if (src_blocks != result.blocks()) { |
| *err = android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(), |
| src_ranges.ToString().c_str(), src_blocks); |
| return false; |
| } |
| |
| *source = result; |
| return true; |
| } |
| |
| Command Command::Parse(const std::string& line, size_t index, std::string* err) { |
| std::vector<std::string> tokens = android::base::Split(line, " "); |
| size_t pos = 0; |
| // tokens.size() will be 1 at least. |
| Type op = ParseType(tokens[pos++]); |
| if (op == Type::LAST) { |
| *err = "invalid type"; |
| return {}; |
| } |
| |
| PatchInfo patch_info; |
| TargetInfo target_info; |
| SourceInfo source_info; |
| StashInfo stash_info; |
| |
| if (op == Type::ZERO || op == Type::NEW || op == Type::ERASE) { |
| // zero/new/erase <rangeset> |
| if (pos + 1 != tokens.size()) { |
| *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)", |
| tokens.size() - pos); |
| return {}; |
| } |
| RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]); |
| if (!tgt_ranges) { |
| return {}; |
| } |
| static const std::string kUnknownHash{ "unknown-hash" }; |
| target_info = TargetInfo(kUnknownHash, tgt_ranges); |
| } else if (op == Type::STASH) { |
| // stash <stash_id> <src_ranges> |
| if (pos + 2 != tokens.size()) { |
| *err = android::base::StringPrintf("invalid number of args: %zu (expected 2)", |
| tokens.size() - pos); |
| return {}; |
| } |
| const std::string& id = tokens[pos++]; |
| RangeSet src_ranges = RangeSet::Parse(tokens[pos++]); |
| if (!src_ranges) { |
| *err = "invalid token"; |
| return {}; |
| } |
| stash_info = StashInfo(id, src_ranges); |
| } else if (op == Type::FREE) { |
| // free <stash_id> |
| if (pos + 1 != tokens.size()) { |
| *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)", |
| tokens.size() - pos); |
| return {}; |
| } |
| stash_info = StashInfo(tokens[pos++], {}); |
| } else if (op == Type::MOVE) { |
| // <hash> |
| if (pos + 1 > tokens.size()) { |
| *err = "missing hash"; |
| return {}; |
| } |
| std::string hash = tokens[pos++]; |
| if (!ParseTargetInfoAndSourceInfo( |
| std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), hash, &target_info, |
| hash, &source_info, err)) { |
| return {}; |
| } |
| } else if (op == Type::BSDIFF || op == Type::IMGDIFF) { |
| // <offset> <length> <srchash> <dsthash> |
| if (pos + 4 > tokens.size()) { |
| *err = android::base::StringPrintf("invalid number of args: %zu (expected 4+)", |
| tokens.size() - pos); |
| return {}; |
| } |
| size_t offset; |
| size_t length; |
| if (!android::base::ParseUint(tokens[pos++], &offset) || |
| !android::base::ParseUint(tokens[pos++], &length)) { |
| *err = "invalid patch offset/length"; |
| return {}; |
| } |
| patch_info = PatchInfo(offset, length); |
| |
| std::string src_hash = tokens[pos++]; |
| std::string dst_hash = tokens[pos++]; |
| if (!ParseTargetInfoAndSourceInfo( |
| std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), dst_hash, &target_info, |
| src_hash, &source_info, err)) { |
| return {}; |
| } |
| } else if (op == Type::ABORT) { |
| // Abort takes no arguments, so there's nothing else to check. |
| if (pos != tokens.size()) { |
| *err = android::base::StringPrintf("invalid number of args: %zu (expected 0)", |
| tokens.size() - pos); |
| return {}; |
| } |
| } else if (op == Type::COMPUTE_HASH_TREE) { |
| // <hash_tree_ranges> <source_ranges> <hash_algorithm> <salt_hex> <root_hash> |
| if (pos + 5 != tokens.size()) { |
| *err = android::base::StringPrintf("invalid number of args: %zu (expected 5)", |
| tokens.size() - pos); |
| return {}; |
| } |
| |
| // Expects the hash_tree data to be contiguous. |
| RangeSet hash_tree_ranges = RangeSet::Parse(tokens[pos++]); |
| if (!hash_tree_ranges || hash_tree_ranges.size() != 1) { |
| *err = "invalid hash tree ranges in: " + line; |
| return {}; |
| } |
| |
| RangeSet source_ranges = RangeSet::Parse(tokens[pos++]); |
| if (!source_ranges) { |
| *err = "invalid source ranges in: " + line; |
| return {}; |
| } |
| |
| std::string hash_algorithm = tokens[pos++]; |
| std::string salt_hex = tokens[pos++]; |
| std::string root_hash = tokens[pos++]; |
| if (hash_algorithm.empty() || salt_hex.empty() || root_hash.empty()) { |
| *err = "invalid hash tree arguments in " + line; |
| return {}; |
| } |
| |
| HashTreeInfo hash_tree_info(std::move(hash_tree_ranges), std::move(source_ranges), |
| std::move(hash_algorithm), std::move(salt_hex), |
| std::move(root_hash)); |
| return Command(op, index, line, std::move(hash_tree_info)); |
| } else { |
| *err = "invalid op"; |
| return {}; |
| } |
| |
| return Command(op, index, line, patch_info, target_info, source_info, stash_info); |
| } |
| |
| bool SourceInfo::Overlaps(const TargetInfo& target) const { |
| return ranges_.Overlaps(target.ranges()); |
| } |
| |
| // Moves blocks in the 'source' vector to the specified locations (as in 'locs') in the 'dest' |
| // vector. Note that source and dest may be the same buffer. |
| static void MoveRange(std::vector<uint8_t>* dest, const RangeSet& locs, |
| const std::vector<uint8_t>& source, size_t block_size) { |
| const uint8_t* from = source.data(); |
| uint8_t* to = dest->data(); |
| size_t start = locs.blocks(); |
| // Must do the movement backward. |
| for (auto it = locs.crbegin(); it != locs.crend(); it++) { |
| size_t blocks = it->second - it->first; |
| start -= blocks; |
| memmove(to + (it->first * block_size), from + (start * block_size), blocks * block_size); |
| } |
| } |
| |
| bool SourceInfo::ReadAll( |
| std::vector<uint8_t>* buffer, size_t block_size, |
| const std::function<int(const RangeSet&, std::vector<uint8_t>*)>& block_reader, |
| const std::function<int(const std::string&, std::vector<uint8_t>*)>& stash_reader) const { |
| if (buffer->size() < blocks() * block_size) { |
| return false; |
| } |
| |
| // Read in the source ranges. |
| if (ranges_) { |
| if (block_reader(ranges_, buffer) != 0) { |
| return false; |
| } |
| if (location_) { |
| MoveRange(buffer, location_, *buffer, block_size); |
| } |
| } |
| |
| // Read in the stashes. |
| for (const StashInfo& stash : stashes_) { |
| std::vector<uint8_t> stash_buffer(stash.blocks() * block_size); |
| if (stash_reader(stash.id(), &stash_buffer) != 0) { |
| return false; |
| } |
| MoveRange(buffer, stash.ranges(), stash_buffer, block_size); |
| } |
| return true; |
| } |
| |
| void SourceInfo::DumpBuffer(const std::vector<uint8_t>& buffer, size_t block_size) const { |
| LOG(INFO) << "Dumping hashes in hex for " << ranges_.blocks() << " source blocks"; |
| |
| const RangeSet& location = location_ ? location_ : RangeSet({ Range{ 0, ranges_.blocks() } }); |
| for (size_t i = 0; i < ranges_.blocks(); i++) { |
| size_t block_num = ranges_.GetBlockNumber(i); |
| size_t buffer_index = location.GetBlockNumber(i); |
| CHECK_LE((buffer_index + 1) * block_size, buffer.size()); |
| |
| uint8_t digest[SHA_DIGEST_LENGTH]; |
| SHA1(buffer.data() + buffer_index * block_size, block_size, digest); |
| std::string hexdigest = print_sha1(digest); |
| LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest; |
| } |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const Command& command) { |
| os << command.index() << ": " << command.cmdline(); |
| return os; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const TargetInfo& target) { |
| os << target.blocks() << " blocks (" << target.hash_ << "): " << target.ranges_.ToString(); |
| return os; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const StashInfo& stash) { |
| os << stash.blocks() << " blocks (" << stash.id_ << "): " << stash.ranges_.ToString(); |
| return os; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const SourceInfo& source) { |
| os << source.blocks_ << " blocks (" << source.hash_ << "): "; |
| if (source.ranges_) { |
| os << source.ranges_.ToString(); |
| if (source.location_) { |
| os << " (location: " << source.location_.ToString() << ")"; |
| } |
| } |
| if (!source.stashes_.empty()) { |
| os << " " << source.stashes_.size() << " stash(es)"; |
| } |
| return os; |
| } |
| |
| TransferList TransferList::Parse(const std::string& transfer_list_str, std::string* err) { |
| TransferList result{}; |
| |
| std::vector<std::string> lines = android::base::Split(transfer_list_str, "\n"); |
| if (lines.size() < kTransferListHeaderLines) { |
| *err = android::base::StringPrintf("too few lines in the transfer list [%zu]", lines.size()); |
| return TransferList{}; |
| } |
| |
| // First line in transfer list is the version number. |
| if (!android::base::ParseInt(lines[0], &result.version_, 3, 4)) { |
| *err = "unexpected transfer list version ["s + lines[0] + "]"; |
| return TransferList{}; |
| } |
| |
| // Second line in transfer list is the total number of blocks we expect to write. |
| if (!android::base::ParseUint(lines[1], &result.total_blocks_)) { |
| *err = "unexpected block count ["s + lines[1] + "]"; |
| return TransferList{}; |
| } |
| |
| // Third line is how many stash entries are needed simultaneously. |
| if (!android::base::ParseUint(lines[2], &result.stash_max_entries_)) { |
| return TransferList{}; |
| } |
| |
| // Fourth line is the maximum number of blocks that will be stashed simultaneously. |
| if (!android::base::ParseUint(lines[3], &result.stash_max_blocks_)) { |
| *err = "unexpected maximum stash blocks ["s + lines[3] + "]"; |
| return TransferList{}; |
| } |
| |
| // Subsequent lines are all individual transfer commands. |
| for (size_t i = kTransferListHeaderLines; i < lines.size(); i++) { |
| const std::string& line = lines[i]; |
| if (line.empty()) continue; |
| |
| size_t cmdindex = i - kTransferListHeaderLines; |
| std::string parsing_error; |
| Command command = Command::Parse(line, cmdindex, &parsing_error); |
| if (!command) { |
| *err = android::base::StringPrintf("Failed to parse command %zu [%s]: %s", cmdindex, |
| line.c_str(), parsing_error.c_str()); |
| return TransferList{}; |
| } |
| result.commands_.push_back(command); |
| } |
| |
| return result; |
| } |