updater: Add Command parsing codes.

The added codes are not used in the updater yet. The switch will happen
in subsequent CLs.

Test: Run recovery_unit_test and recovery_component_test on marlin.
Change-Id: I1ae8a233280f02c2171b43ef028bdccdacb39c59
diff --git a/tests/Android.mk b/tests/Android.mk
index a9e6491..2576ea0 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -27,6 +27,7 @@
     libminui \
     libotautil \
     libupdater \
+    libgtest_prod \
     libpng \
     libziparchive \
     libutils \
@@ -133,6 +134,7 @@
     libfec \
     libfec_rs \
     libfs_mgr \
+    libgtest_prod \
     liblog \
     libselinux \
     libsparse \
diff --git a/tests/unit/commands_test.cpp b/tests/unit/commands_test.cpp
index 18aa471..bbc83b9 100644
--- a/tests/unit/commands_test.cpp
+++ b/tests/unit/commands_test.cpp
@@ -18,6 +18,7 @@
 
 #include <gtest/gtest.h>
 
+#include "otautil/rangeset.h"
 #include "private/commands.h"
 
 TEST(CommandsTest, ParseType) {
@@ -35,3 +36,277 @@
   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());
+}
diff --git a/updater/Android.mk b/updater/Android.mk
index 46c56f4..ac9aecb 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -35,6 +35,7 @@
     libfec \
     libfec_rs \
     libfs_mgr \
+    libgtest_prod \
     liblog \
     libselinux \
     libsparse \
diff --git a/updater/commands.cpp b/updater/commands.cpp
index f798c6a..6d4b531 100644
--- a/updater/commands.cpp
+++ b/updater/commands.cpp
@@ -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;
+}
diff --git a/updater/include/private/commands.h b/updater/include/private/commands.h
index b360000..784892f 100644
--- a/updater/include/private/commands.h
+++ b/updater/include/private/commands.h
@@ -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);