Merge "updater: Add Command parsing codes."
am: afc2962e60
Change-Id: I24cc3beca7c75786cbfad37ab273139fd2d1ca2f
This commit is contained in:
@@ -27,6 +27,7 @@ LOCAL_STATIC_LIBRARIES := \
|
|||||||
libminui \
|
libminui \
|
||||||
libotautil \
|
libotautil \
|
||||||
libupdater \
|
libupdater \
|
||||||
|
libgtest_prod \
|
||||||
libpng \
|
libpng \
|
||||||
libziparchive \
|
libziparchive \
|
||||||
libutils \
|
libutils \
|
||||||
@@ -133,6 +134,7 @@ libupdater_static_libraries := \
|
|||||||
libfec \
|
libfec \
|
||||||
libfec_rs \
|
libfec_rs \
|
||||||
libfs_mgr \
|
libfs_mgr \
|
||||||
|
libgtest_prod \
|
||||||
liblog \
|
liblog \
|
||||||
libselinux \
|
libselinux \
|
||||||
libsparse \
|
libsparse \
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "otautil/rangeset.h"
|
||||||
#include "private/commands.h"
|
#include "private/commands.h"
|
||||||
|
|
||||||
TEST(CommandsTest, ParseType) {
|
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("foo"));
|
||||||
ASSERT_EQ(Command::Type::LAST, Command::ParseType("bar"));
|
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 \
|
||||||
libfec_rs \
|
libfec_rs \
|
||||||
libfs_mgr \
|
libfs_mgr \
|
||||||
|
libgtest_prod \
|
||||||
liblog \
|
liblog \
|
||||||
libselinux \
|
libselinux \
|
||||||
libsparse \
|
libsparse \
|
||||||
|
|||||||
+238
-13
@@ -16,28 +16,253 @@
|
|||||||
|
|
||||||
#include "private/commands.h"
|
#include "private/commands.h"
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <android-base/logging.h>
|
#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) {
|
Command::Type Command::ParseType(const std::string& type_str) {
|
||||||
if (type_str == "zero") {
|
if (type_str == "bsdiff") {
|
||||||
return Type::ZERO;
|
return Type::BSDIFF;
|
||||||
} else if (type_str == "new") {
|
|
||||||
return Type::NEW;
|
|
||||||
} else if (type_str == "erase") {
|
} else if (type_str == "erase") {
|
||||||
return Type::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") {
|
} else if (type_str == "free") {
|
||||||
return Type::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;
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
#include <string>
|
#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 {
|
enum class Type {
|
||||||
ZERO,
|
|
||||||
NEW,
|
|
||||||
ERASE,
|
|
||||||
MOVE,
|
|
||||||
BSDIFF,
|
BSDIFF,
|
||||||
IMGDIFF,
|
ERASE,
|
||||||
STASH,
|
|
||||||
FREE,
|
FREE,
|
||||||
|
IMGDIFF,
|
||||||
|
MOVE,
|
||||||
|
NEW,
|
||||||
|
STASH,
|
||||||
|
ZERO,
|
||||||
LAST, // Not a valid type.
|
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);
|
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user