Merge "updater: Add Command parsing codes."
am: afc2962e60
Change-Id: I24cc3beca7c75786cbfad37ab273139fd2d1ca2f
This commit is contained in:
commit
e6604307f6
5 changed files with 801 additions and 20 deletions
|
@ -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 \
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ updater_common_static_libraries := \
|
|||
libfec \
|
||||
libfec_rs \
|
||||
libfs_mgr \
|
||||
libgtest_prod \
|
||||
liblog \
|
||||
libselinux \
|
||||
libsparse \
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue