Merge "updater: Add Command parsing codes."

am: afc2962e60

Change-Id: I24cc3beca7c75786cbfad37ab273139fd2d1ca2f
This commit is contained in:
Tao Bao 2018-06-25 20:36:08 -07:00 committed by android-build-merger
commit e6604307f6
5 changed files with 801 additions and 20 deletions

View file

@ -27,6 +27,7 @@ LOCAL_STATIC_LIBRARIES := \
libminui \
libotautil \
libupdater \
libgtest_prod \
libpng \
libziparchive \
libutils \
@ -133,6 +134,7 @@ libupdater_static_libraries := \
libfec \
libfec_rs \
libfs_mgr \
libgtest_prod \
liblog \
libselinux \
libsparse \

View file

@ -18,6 +18,7 @@
#include <gtest/gtest.h>
#include "otautil/rangeset.h"
#include "private/commands.h"
TEST(CommandsTest, ParseType) {
@ -35,3 +36,277 @@ TEST(CommandsTest, ParseType_InvalidCommand) {
ASSERT_EQ(Command::Type::LAST, Command::ParseType("foo"));
ASSERT_EQ(Command::Type::LAST, Command::ParseType("bar"));
}
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly) {
const std::vector<std::string> tokens{
"4,569884,569904,591946,592043",
"117",
"4,566779,566799,591946,592043",
};
TargetInfo target;
SourceInfo source;
std::string err;
ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
tokens, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
target);
ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), {}, {}),
source);
ASSERT_EQ(117, source.blocks());
}
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly) {
const std::vector<std::string> tokens{
"2,350729,350731",
"2",
"-",
"6ebcf8cf1f6be0bc49e7d4a864214251925d1d15:2,0,2",
};
TargetInfo target;
SourceInfo source;
std::string err;
ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
tokens, "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", &target,
"1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", &source, &err));
ASSERT_EQ(
TargetInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 350729, 350731 } })),
target);
ASSERT_EQ(
SourceInfo("1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", {}, {},
{
StashInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 0, 2 } })),
}),
source);
ASSERT_EQ(2, source.blocks());
}
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes) {
const std::vector<std::string> tokens{
"4,611641,611643,636981,637075",
"96",
"4,636981,637075,770665,770666",
"4,0,94,95,96",
"9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
};
TargetInfo target;
SourceInfo source;
std::string err;
ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
tokens, "4734d1b241eb3d0f993714aaf7d665fae43772b6", &target,
"a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", &source, &err));
ASSERT_EQ(TargetInfo("4734d1b241eb3d0f993714aaf7d665fae43772b6",
RangeSet({ { 611641, 611643 }, { 636981, 637075 } })),
target);
ASSERT_EQ(SourceInfo(
"a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
RangeSet({ { 636981, 637075 }, { 770665, 770666 } }), // source ranges
RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
{
StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", RangeSet({ { 94, 95 } })),
}),
source);
ASSERT_EQ(96, source.blocks());
}
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput) {
const std::vector<std::string> tokens{
"4,611641,611643,636981,637075",
"96",
"4,636981,637075,770665,770666",
"4,0,94,95,96",
"9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
};
TargetInfo target;
SourceInfo source;
std::string err;
// Mismatching block count.
{
std::vector<std::string> tokens_copy(tokens);
tokens_copy[1] = "97";
ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
}
// Excess stashes (causing block count mismatch).
{
std::vector<std::string> tokens_copy(tokens);
tokens_copy.push_back("e145a2f83a33334714ac65e34969c1f115e54a6f:2,0,22");
ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
}
// Invalid args.
for (size_t i = 0; i < tokens.size(); i++) {
TargetInfo target;
SourceInfo source;
std::string err;
ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
std::vector<std::string>(tokens.cbegin() + i + 1, tokens.cend()),
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
}
}
TEST(CommandsTest, Parse_EmptyInput) {
std::string err;
ASSERT_FALSE(Command::Parse("", 0, &err));
ASSERT_EQ("invalid type", err);
}
TEST(CommandsTest, Parse_BSDIFF) {
const std::string input{
"bsdiff 0 148 "
"f201a4e04bd3860da6ad47b957ef424d58a58f8c 9d5d223b4bc5c45dbd25a799c4f1a98466731599 "
"4,565704,565752,566779,566799 "
"68 4,64525,64545,565704,565752"
};
std::string err;
Command command = Command::Parse(input, 1, &err);
ASSERT_TRUE(command);
ASSERT_EQ(Command::Type::BSDIFF, command.type());
ASSERT_EQ(1, command.index());
ASSERT_EQ(input, command.cmdline());
ASSERT_EQ(TargetInfo("9d5d223b4bc5c45dbd25a799c4f1a98466731599",
RangeSet({ { 565704, 565752 }, { 566779, 566799 } })),
command.target());
ASSERT_EQ(SourceInfo("f201a4e04bd3860da6ad47b957ef424d58a58f8c",
RangeSet({ { 64525, 64545 }, { 565704, 565752 } }), RangeSet(), {}),
command.source());
ASSERT_EQ(StashInfo(), command.stash());
ASSERT_EQ(PatchInfo(0, 148), command.patch());
}
TEST(CommandsTest, Parse_ERASE) {
const std::string input{ "erase 2,5,10" };
std::string err;
Command command = Command::Parse(input, 2, &err);
ASSERT_TRUE(command);
ASSERT_EQ(Command::Type::ERASE, command.type());
ASSERT_EQ(2, command.index());
ASSERT_EQ(input, command.cmdline());
ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 5, 10 } })), command.target());
ASSERT_EQ(SourceInfo(), command.source());
ASSERT_EQ(StashInfo(), command.stash());
ASSERT_EQ(PatchInfo(), command.patch());
}
TEST(CommandsTest, Parse_FREE) {
const std::string input{ "free hash1" };
std::string err;
Command command = Command::Parse(input, 3, &err);
ASSERT_TRUE(command);
ASSERT_EQ(Command::Type::FREE, command.type());
ASSERT_EQ(3, command.index());
ASSERT_EQ(input, command.cmdline());
ASSERT_EQ(TargetInfo(), command.target());
ASSERT_EQ(SourceInfo(), command.source());
ASSERT_EQ(StashInfo("hash1", RangeSet()), command.stash());
ASSERT_EQ(PatchInfo(), command.patch());
}
TEST(CommandsTest, Parse_IMGDIFF) {
const std::string input{
"imgdiff 29629269 185 "
"a6b1c49aed1b57a2aab1ec3e1505b945540cd8db 51978f65035f584a8ef7afa941dacb6d5e862164 "
"2,90851,90852 "
"1 2,90851,90852"
};
std::string err;
Command command = Command::Parse(input, 4, &err);
ASSERT_TRUE(command);
ASSERT_EQ(Command::Type::IMGDIFF, command.type());
ASSERT_EQ(4, command.index());
ASSERT_EQ(input, command.cmdline());
ASSERT_EQ(TargetInfo("51978f65035f584a8ef7afa941dacb6d5e862164", RangeSet({ { 90851, 90852 } })),
command.target());
ASSERT_EQ(SourceInfo("a6b1c49aed1b57a2aab1ec3e1505b945540cd8db", RangeSet({ { 90851, 90852 } }),
RangeSet(), {}),
command.source());
ASSERT_EQ(StashInfo(), command.stash());
ASSERT_EQ(PatchInfo(29629269, 185), command.patch());
}
TEST(CommandsTest, Parse_MOVE) {
const std::string input{
"move 1d74d1a60332fd38cf9405f1bae67917888da6cb "
"4,569884,569904,591946,592043 117 4,566779,566799,591946,592043"
};
std::string err;
Command command = Command::Parse(input, 5, &err);
ASSERT_TRUE(command);
ASSERT_EQ(Command::Type::MOVE, command.type());
ASSERT_EQ(5, command.index());
ASSERT_EQ(input, command.cmdline());
ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
command.target());
ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), RangeSet(), {}),
command.source());
ASSERT_EQ(StashInfo(), command.stash());
ASSERT_EQ(PatchInfo(), command.patch());
}
TEST(CommandsTest, Parse_NEW) {
const std::string input{ "new 4,3,5,10,12" };
std::string err;
Command command = Command::Parse(input, 6, &err);
ASSERT_TRUE(command);
ASSERT_EQ(Command::Type::NEW, command.type());
ASSERT_EQ(6, command.index());
ASSERT_EQ(input, command.cmdline());
ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 3, 5 }, { 10, 12 } })), command.target());
ASSERT_EQ(SourceInfo(), command.source());
ASSERT_EQ(StashInfo(), command.stash());
ASSERT_EQ(PatchInfo(), command.patch());
}
TEST(CommandsTest, Parse_STASH) {
const std::string input{ "stash hash1 2,5,10" };
std::string err;
Command command = Command::Parse(input, 7, &err);
ASSERT_TRUE(command);
ASSERT_EQ(Command::Type::STASH, command.type());
ASSERT_EQ(7, command.index());
ASSERT_EQ(input, command.cmdline());
ASSERT_EQ(TargetInfo(), command.target());
ASSERT_EQ(SourceInfo(), command.source());
ASSERT_EQ(StashInfo("hash1", RangeSet({ { 5, 10 } })), command.stash());
ASSERT_EQ(PatchInfo(), command.patch());
}
TEST(CommandsTest, Parse_ZERO) {
const std::string input{ "zero 2,1,5" };
std::string err;
Command command = Command::Parse(input, 8, &err);
ASSERT_TRUE(command);
ASSERT_EQ(Command::Type::ZERO, command.type());
ASSERT_EQ(8, command.index());
ASSERT_EQ(input, command.cmdline());
ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 1, 5 } })), command.target());
ASSERT_EQ(SourceInfo(), command.source());
ASSERT_EQ(StashInfo(), command.stash());
ASSERT_EQ(PatchInfo(), command.patch());
}

View file

@ -35,6 +35,7 @@ updater_common_static_libraries := \
libfec \
libfec_rs \
libfs_mgr \
libgtest_prod \
liblog \
libselinux \
libsparse \

View file

@ -16,28 +16,253 @@
#include "private/commands.h"
#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 "otautil/rangeset.h"
using namespace std::string_literals;
Command::Type Command::ParseType(const std::string& type_str) {
if (type_str == "zero") {
return Type::ZERO;
} else if (type_str == "new") {
return Type::NEW;
if (type_str == "bsdiff") {
return Type::BSDIFF;
} else if (type_str == "erase") {
return Type::ERASE;
} else if (type_str == "move") {
return Type::MOVE;
} else if (type_str == "bsdiff") {
return Type::BSDIFF;
} else if (type_str == "imgdiff") {
return Type::IMGDIFF;
} else if (type_str == "stash") {
return Type::STASH;
} 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;
}
LOG(ERROR) << "Invalid type: " << type_str;
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 tokens parameter 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 parameters: <tgt_ranges>, <src_block_count> and
// "-"/<src_ranges>.
if (tokens.size() < 3) {
*err = "invalid number of parameters";
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, {}, {});
// Sanity check the block count.
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>
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 = "missing stash id and/or source ranges";
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 = "missing stash id in free command";
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 = "invalid number of tokens";
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 {
*err = "invalid op";
return {};
}
return Command(op, index, line, patch_info, target_info, source_info, stash_info);
}
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;
}

View file

@ -16,20 +16,298 @@
#pragma once
#include <ostream>
#include <string>
#include <vector>
struct Command {
#include <gtest/gtest_prod.h> // 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<StashInfo> 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();
}
}
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<StashInfo> 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 };
};
// 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 <tgt_ranges>
// - Fill the indicated blocks with zeros.
// - Meaningful args: TargetInfo
//
// new <tgt_ranges>
// - Fill the blocks with data read from the new_data file.
// - Meaningful args: TargetInfo
//
// erase <tgt_ranges>
// - Mark the given blocks as empty.
// - Meaningful args: TargetInfo
//
// move <hash> <...>
// - Read the source blocks, write result to target blocks.
// - Meaningful args: TargetInfo, SourceInfo
//
// See the note below for <...>.
//
// bsdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
// imgdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
// - 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:
//
// <tgt_ranges> <src_block_count> - <[stash_id:stash_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:stash_location] ...>
// (loads data from both of source image and stashes)
//
// stash <stash_id> <src_ranges>
// - Load the given source blocks and stash the data in the given slot of the stash table.
// - Meaningful args: StashInfo
//
// free <stash_id>
// - Free the given stash data.
// - Meaningful args: StashInfo
//
class Command {
public:
enum class Type {
ZERO,
NEW,
ERASE,
MOVE,
BSDIFF,
IMGDIFF,
STASH,
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)) {}
// 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_;
}
constexpr explicit operator bool() const {
return type_ != Type::LAST;
}
private:
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<std::string>& tokens,
const std::string& tgt_hash, TargetInfo* target,
const std::string& src_hash, SourceInfo* source,
std::string* err);
// 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_;
};
std::ostream& operator<<(std::ostream& os, const Command& command);