update_engine: Store fingerprint value from Omaha response.

Store the unique fp value from response into prefs. Value is later sent
to Omaha to determine if there is a subsequent update available
while the system is waiting to be rebooted.

BUG=b:161259884
TEST=cros_workon_make --board=hatch --test update_engine

Change-Id: Ie37aa5da3cd8a0820e633f5ef426fb50e8a02838
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2491618
Tested-by: Vyshu Khota <vyshu@google.com>
Commit-Queue: Vyshu Khota <vyshu@google.com>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
diff --git a/common/constants.cc b/common/constants.cc
index 8883668..a9cf238 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -71,6 +71,7 @@
 const char kPrefsPingActive[] = "active";
 const char kPrefsPingLastActive[] = "date_last_active";
 const char kPrefsPingLastRollcall[] = "date_last_rollcall";
+const char kPrefsLastFp[] = "last-fp";
 const char kPrefsPostInstallSucceeded[] = "post-install-succeeded";
 const char kPrefsPreviousVersion[] = "previous-version";
 const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
diff --git a/common/constants.h b/common/constants.h
index 3685102..1e97249 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -71,6 +71,7 @@
 extern const char kPrefsPingActive[];
 extern const char kPrefsPingLastActive[];
 extern const char kPrefsPingLastRollcall[];
+extern const char kPrefsLastFp[];
 extern const char kPrefsPostInstallSucceeded[];
 extern const char kPrefsPreviousVersion[];
 extern const char kPrefsResumedUpdateFailures[];
diff --git a/common/fake_prefs.cc b/common/fake_prefs.cc
index 73559c5..275667e 100644
--- a/common/fake_prefs.cc
+++ b/common/fake_prefs.cc
@@ -106,6 +106,22 @@
   return true;
 }
 
+bool FakePrefs::Delete(const string& key, const vector<string>& nss) {
+  bool success = Delete(key);
+  for (const auto& ns : nss) {
+    vector<string> ns_keys;
+    success = GetSubKeys(ns, &ns_keys) && success;
+    for (const auto& sub_key : ns_keys) {
+      auto last_key_seperator = sub_key.find_last_of(kKeySeparator);
+      if (last_key_seperator != string::npos &&
+          key == sub_key.substr(last_key_seperator + 1)) {
+        success = Delete(sub_key) && success;
+      }
+    }
+  }
+  return success;
+}
+
 bool FakePrefs::GetSubKeys(const string& ns, vector<string>* keys) const {
   for (const auto& pr : values_)
     if (pr.first.compare(0, ns.length(), ns) == 0)
diff --git a/common/fake_prefs.h b/common/fake_prefs.h
index b24ff4d..9af2550 100644
--- a/common/fake_prefs.h
+++ b/common/fake_prefs.h
@@ -48,6 +48,8 @@
 
   bool Exists(const std::string& key) const override;
   bool Delete(const std::string& key) override;
+  bool Delete(const std::string& key,
+              const std::vector<std::string>& nss) override;
 
   bool GetSubKeys(const std::string& ns,
                   std::vector<std::string>* keys) const override;
diff --git a/common/mock_prefs.h b/common/mock_prefs.h
index 62417a8..c91664e 100644
--- a/common/mock_prefs.h
+++ b/common/mock_prefs.h
@@ -41,6 +41,9 @@
 
   MOCK_CONST_METHOD1(Exists, bool(const std::string& key));
   MOCK_METHOD1(Delete, bool(const std::string& key));
+  MOCK_METHOD2(Delete,
+               bool(const std::string& key,
+                    const std::vector<std::string>& nss));
 
   MOCK_CONST_METHOD2(GetSubKeys,
                      bool(const std::string&, std::vector<std::string>*));
diff --git a/common/prefs.cc b/common/prefs.cc
index 615014f..52a58b7 100644
--- a/common/prefs.cc
+++ b/common/prefs.cc
@@ -34,8 +34,6 @@
 
 namespace {
 
-const char kKeySeparator = '/';
-
 void DeleteEmptyDirectories(const base::FilePath& path) {
   base::FileEnumerator path_enum(
       path, false /* recursive */, base::FileEnumerator::DIRECTORIES);
@@ -112,6 +110,24 @@
   return true;
 }
 
+bool PrefsBase::Delete(const string& pref_key, const vector<string>& nss) {
+  // Delete pref key for platform.
+  bool success = Delete(pref_key);
+  // Delete pref key in each namespace.
+  for (const auto& ns : nss) {
+    vector<string> namespace_keys;
+    success = GetSubKeys(ns, &namespace_keys) && success;
+    for (const auto& key : namespace_keys) {
+      auto last_key_seperator = key.find_last_of(kKeySeparator);
+      if (last_key_seperator != string::npos &&
+          pref_key == key.substr(last_key_seperator + 1)) {
+        success = Delete(key) && success;
+      }
+    }
+  }
+  return success;
+}
+
 bool PrefsBase::GetSubKeys(const string& ns, vector<string>* keys) const {
   return storage_->GetSubKeys(ns, keys);
 }
diff --git a/common/prefs.h b/common/prefs.h
index 3fc1d89..d6ef668 100644
--- a/common/prefs.h
+++ b/common/prefs.h
@@ -74,6 +74,8 @@
 
   bool Exists(const std::string& key) const override;
   bool Delete(const std::string& key) override;
+  bool Delete(const std::string& pref_key,
+              const std::vector<std::string>& nss) override;
 
   bool GetSubKeys(const std::string& ns,
                   std::vector<std::string>* keys) const override;
diff --git a/common/prefs_interface.h b/common/prefs_interface.h
index 1311cb4..866d0ca 100644
--- a/common/prefs_interface.h
+++ b/common/prefs_interface.h
@@ -80,6 +80,12 @@
   // this key. Calling with non-existent keys does nothing.
   virtual bool Delete(const std::string& key) = 0;
 
+  // Deletes the pref key from platform and given namespace subdirectories.
+  // Keys are matched against end of pref keys in each namespace.
+  // Returns true if all deletes were successful.
+  virtual bool Delete(const std::string& pref_key,
+                      const std::vector<std::string>& nss) = 0;
+
   // Creates a key which is part of a sub preference.
   static std::string CreateSubKey(const std::vector<std::string>& ns_with_key);
 
@@ -98,6 +104,10 @@
   // anymore for future Set*() and Delete() method calls.
   virtual void RemoveObserver(const std::string& key,
                               ObserverInterface* observer) = 0;
+
+ protected:
+  // Key separator used to create sub key and get file names,
+  static const char kKeySeparator = '/';
 };
 
 }  // namespace chromeos_update_engine
diff --git a/common/prefs_unittest.cc b/common/prefs_unittest.cc
index 6dd26c0..e8efd8a 100644
--- a/common/prefs_unittest.cc
+++ b/common/prefs_unittest.cc
@@ -118,6 +118,23 @@
     for (const auto& key : keys0corner)
       EXPECT_TRUE(common_prefs_->Delete(key));
     EXPECT_FALSE(common_prefs_->Exists(key0corner));
+
+    // Test sub directory namespace.
+    const string kDlcPrefsSubDir = "foo-dir";
+    key1A = common_prefs_->CreateSubKey({kDlcPrefsSubDir, "dlc1", "keyA"});
+    EXPECT_TRUE(common_prefs_->SetString(key1A, "fp_1A"));
+    key1B = common_prefs_->CreateSubKey({kDlcPrefsSubDir, "dlc1", "keyB"});
+    EXPECT_TRUE(common_prefs_->SetString(key1B, "fp_1B"));
+    auto key2A = common_prefs_->CreateSubKey({kDlcPrefsSubDir, "dlc2", "keyA"});
+    EXPECT_TRUE(common_prefs_->SetString(key2A, "fp_A2"));
+
+    vector<string> fpKeys;
+    EXPECT_TRUE(common_prefs_->GetSubKeys(kDlcPrefsSubDir, &fpKeys));
+    EXPECT_EQ(fpKeys.size(), 3);
+    EXPECT_TRUE(common_prefs_->Delete(fpKeys[0]));
+    EXPECT_TRUE(common_prefs_->Delete(fpKeys[1]));
+    EXPECT_TRUE(common_prefs_->Delete(fpKeys[2]));
+    EXPECT_FALSE(common_prefs_->Exists(key1A));
   }
 
   PrefsInterface* common_prefs_;
@@ -423,6 +440,71 @@
   EXPECT_FALSE(base::PathExists(prefs_dir_.Append(name_space)));
 }
 
+TEST_F(PrefsTest, DeletePrefs) {
+  const string kPrefsSubDir = "foo-dir";
+  const string kFpKey = "kPrefFp";
+  const string kNotFpKey = "NotkPrefFp";
+  const string kOtherKey = "kPrefNotFp";
+
+  EXPECT_TRUE(prefs_.SetString(kFpKey, "3.000"));
+  EXPECT_TRUE(prefs_.SetString(kOtherKey, "not_fp_val"));
+
+  auto key1_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-1", kFpKey});
+  EXPECT_TRUE(prefs_.SetString(key1_fp, "3.7"));
+  auto key_not_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-1", kOtherKey});
+  EXPECT_TRUE(prefs_.SetString(key_not_fp, "not_fp_val"));
+  auto key2_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-2", kFpKey});
+  EXPECT_TRUE(prefs_.SetString(key2_fp, "3.9"));
+  auto key3_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-3", kFpKey});
+  EXPECT_TRUE(prefs_.SetString(key3_fp, "3.45"));
+
+  // Pref key does not match full subkey at end, should not delete.
+  auto key_middle_fp = prefs_.CreateSubKey({kPrefsSubDir, kFpKey, kOtherKey});
+  EXPECT_TRUE(prefs_.SetString(key_middle_fp, "not_fp_val"));
+  auto key_end_not_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-1", kNotFpKey});
+  EXPECT_TRUE(prefs_.SetString(key_end_not_fp, "not_fp_val"));
+
+  // Delete key in platform and one namespace.
+  prefs_.Delete(kFpKey, {kPrefsSubDir});
+
+  EXPECT_FALSE(prefs_.Exists(kFpKey));
+  EXPECT_FALSE(prefs_.Exists(key1_fp));
+  EXPECT_FALSE(prefs_.Exists(key2_fp));
+  EXPECT_FALSE(prefs_.Exists(key3_fp));
+
+  // Check other keys are not deleted.
+  EXPECT_TRUE(prefs_.Exists(kOtherKey));
+  EXPECT_TRUE(prefs_.Exists(key_not_fp));
+  EXPECT_TRUE(prefs_.Exists(key_middle_fp));
+  EXPECT_TRUE(prefs_.Exists(key_end_not_fp));
+}
+
+TEST_F(PrefsTest, DeleteMultipleNamespaces) {
+  const string kFirstSubDir = "foo-dir";
+  const string kSecondarySubDir = "bar-dir";
+  const string kTertiarySubDir = "ter-dir";
+  const string kFpKey = "kPrefFp";
+
+  EXPECT_TRUE(prefs_.SetString(kFpKey, "3.000"));
+  // Set pref key in different namespaces.
+  auto key1_fp = prefs_.CreateSubKey({kFirstSubDir, "id-1", kFpKey});
+  EXPECT_TRUE(prefs_.SetString(key1_fp, "3.7"));
+  auto key2_fp = prefs_.CreateSubKey({kSecondarySubDir, "id-3", kFpKey});
+  EXPECT_TRUE(prefs_.SetString(key2_fp, "7.45"));
+  auto key3_fp = prefs_.CreateSubKey({kTertiarySubDir, "id-3", kFpKey});
+  EXPECT_TRUE(prefs_.SetString(key3_fp, "7.45"));
+
+  // Delete key in platform and given namespaces.
+  prefs_.Delete(kFpKey, {kFirstSubDir, kSecondarySubDir});
+
+  EXPECT_FALSE(prefs_.Exists(kFpKey));
+  EXPECT_FALSE(prefs_.Exists(key1_fp));
+  EXPECT_FALSE(prefs_.Exists(key2_fp));
+
+  // Tertiary namespace not given to delete. Key should still exist.
+  EXPECT_TRUE(prefs_.Exists(key3_fp));
+}
+
 class MockPrefsObserver : public PrefsInterface::ObserverInterface {
  public:
   MOCK_METHOD1(OnPrefSet, void(const string&));
diff --git a/cros/omaha_request_action.cc b/cros/omaha_request_action.cc
index faa7dde..cad0c67 100644
--- a/cros/omaha_request_action.cc
+++ b/cros/omaha_request_action.cc
@@ -98,6 +98,7 @@
 constexpr char kAttrElapsedDays[] = "elapsed_days";
 constexpr char kAttrElapsedSeconds[] = "elapsed_seconds";
 constexpr char kAttrEvent[] = "event";
+constexpr char kAttrFp[] = "fp";
 constexpr char kAttrHashSha256[] = "hash_sha256";
 // Deprecated: "hash"; Although we still need to pass it from the server for
 // backward compatibility.
@@ -150,6 +151,7 @@
       string name;
       string size;
       string hash;
+      string fp;
     };
     vector<Package> packages;
   };
@@ -214,7 +216,8 @@
     if (!data->apps.empty())
       data->apps.back().packages.push_back({.name = attrs[kAttrName],
                                             .size = attrs[kAttrSize],
-                                            .hash = attrs[kAttrHashSha256]});
+                                            .hash = attrs[kAttrHashSha256],
+                                            .fp = attrs[kAttrFp]});
   } else if (data->current_path == "/response/app/updatecheck/manifest") {
     // Get the version.
     if (!data->apps.empty())
@@ -612,6 +615,8 @@
       return false;
     }
 
+    out_package.fp = package.fp;
+
     if (i < is_delta_payloads.size())
       out_package.is_delta = ParseBool(is_delta_payloads[i]);
 
diff --git a/cros/omaha_request_action_unittest.cc b/cros/omaha_request_action_unittest.cc
index 8d94195..9f9c75f 100644
--- a/cros/omaha_request_action_unittest.cc
+++ b/cros/omaha_request_action_unittest.cc
@@ -158,10 +158,10 @@
            version +
            "\">"
            "<packages><package hash=\"not-used\" name=\"" +
-           filename + "\" size=\"" + base::NumberToString(size) +
-           "\" hash_sha256=\"" + hash + "\"/>" +
-           (multi_package ? "<package name=\"package2\" size=\"222\" "
-                            "hash_sha256=\"hash2\"/>"
+           filename + "\" size=\"" + base::NumberToString(size) + "\" fp=\"" +
+           fp + "\" hash_sha256=\"" + hash + "\"/>" +
+           (multi_package ? "<package name=\"package2\" size=\"222\" fp=\"" +
+                                fp2 + "\" hash_sha256=\"hash2\"/>"
                           : "") +
            "</packages>"
            "<actions><action event=\"postinstall\" MetadataSize=\"11" +
@@ -187,8 +187,9 @@
                       "><updatecheck status=\"ok\"><urls><url codebase=\"" +
                       codebase2 + "\"/></urls><manifest version=\"" + version2 +
                       "\"><packages>"
-                      "<package name=\"package3\" size=\"333\" "
-                      "hash_sha256=\"hash3\"/></packages>"
+                      "<package name=\"package3\" size=\"333\" fp=\"" +
+                      fp2 +
+                      "\" hash_sha256=\"hash3\"/></packages>"
                       "<actions><action event=\"postinstall\" " +
                       (multi_app_self_update
                            ? "noupdate=\"true\" IsDeltaPayload=\"true\" "
@@ -215,8 +216,10 @@
                       codebase + "\"/><url codebase=\"" + codebase2 +
                       "\"/></urls><manifest version=\"" + version +
                       "\"><packages><package name=\"package3\" size=\"333\" "
-                      "hash_sha256=\"hash3\"/></packages><actions>"
-                      "<action event=\"install\" run=\".signed\"/>"
+                      "fp=\"" +
+                      fp2 +
+                      "\" hash_sha256=\"hash3\"/></packages>"
+                      "<actions><action event=\"install\" run=\".signed\"/>"
                       "<action event=\"postinstall\" MetadataSize=\"33\"/>"
                       "</actions></manifest></updatecheck></app>"
                 : "") +
@@ -248,6 +251,8 @@
   string codebase2 = "http://code/base/2/";
   string filename = "file.signed";
   string hash = "4841534831323334";
+  string fp = "3.98ba213e";
+  string fp2 = "3.755aff78e";
   uint64_t size = 123;
   string deadline = "";
   string max_days_to_scatter = "7";
@@ -670,6 +675,7 @@
   EXPECT_EQ(fake_update_response_.more_info_url, response.more_info_url);
   EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
   EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(fake_update_response_.fp, response.packages[0].fp);
   EXPECT_EQ(true, response.packages[0].is_delta);
   EXPECT_EQ(fake_update_response_.prompt == "true", response.prompt);
   EXPECT_EQ(fake_update_response_.deadline, response.deadline);
@@ -695,11 +701,13 @@
             response.packages[1].payload_urls[0]);
   EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
   EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(fake_update_response_.fp, response.packages[0].fp);
   EXPECT_EQ(true, response.packages[0].is_delta);
   EXPECT_EQ(11u, response.packages[0].metadata_size);
   ASSERT_EQ(2u, response.packages.size());
   EXPECT_EQ(string("hash2"), response.packages[1].hash);
   EXPECT_EQ(222u, response.packages[1].size);
+  EXPECT_EQ(fake_update_response_.fp2, response.packages[1].fp);
   EXPECT_EQ(22u, response.packages[1].metadata_size);
   EXPECT_EQ(false, response.packages[1].is_delta);
 }
@@ -718,11 +726,13 @@
             response.packages[1].payload_urls[0]);
   EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
   EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(fake_update_response_.fp, response.packages[0].fp);
   EXPECT_EQ(11u, response.packages[0].metadata_size);
   EXPECT_EQ(true, response.packages[0].is_delta);
   ASSERT_EQ(2u, response.packages.size());
   EXPECT_EQ(string("hash3"), response.packages[1].hash);
   EXPECT_EQ(333u, response.packages[1].size);
+  EXPECT_EQ(fake_update_response_.fp2, response.packages[1].fp);
   EXPECT_EQ(33u, response.packages[1].metadata_size);
   EXPECT_EQ(false, response.packages[1].is_delta);
 }
@@ -740,10 +750,12 @@
             response.packages[0].payload_urls[0]);
   EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
   EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(fake_update_response_.fp, response.packages[0].fp);
   EXPECT_EQ(11u, response.packages[0].metadata_size);
   ASSERT_EQ(2u, response.packages.size());
   EXPECT_EQ(string("hash3"), response.packages[1].hash);
   EXPECT_EQ(333u, response.packages[1].size);
+  EXPECT_EQ(fake_update_response_.fp2, response.packages[1].fp);
   EXPECT_EQ(33u, response.packages[1].metadata_size);
   EXPECT_EQ(true, response.packages[1].is_delta);
 }
@@ -765,15 +777,18 @@
             response.packages[2].payload_urls[0]);
   EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
   EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(fake_update_response_.fp, response.packages[0].fp);
   EXPECT_EQ(11u, response.packages[0].metadata_size);
   EXPECT_EQ(true, response.packages[0].is_delta);
   ASSERT_EQ(3u, response.packages.size());
   EXPECT_EQ(string("hash2"), response.packages[1].hash);
   EXPECT_EQ(222u, response.packages[1].size);
+  EXPECT_EQ(fake_update_response_.fp2, response.packages[1].fp);
   EXPECT_EQ(22u, response.packages[1].metadata_size);
   EXPECT_EQ(false, response.packages[1].is_delta);
   EXPECT_EQ(string("hash3"), response.packages[2].hash);
   EXPECT_EQ(333u, response.packages[2].size);
+  EXPECT_EQ(fake_update_response_.fp2, response.packages[2].fp);
   EXPECT_EQ(33u, response.packages[2].metadata_size);
   EXPECT_EQ(false, response.packages[2].is_delta);
 }
@@ -1557,7 +1572,7 @@
       "<urls><url codebase=\"http://missing/field/test/\"/></urls>"
       "<manifest version=\"10.2.3.4\">"
       "<packages><package hash=\"not-used\" name=\"f\" "
-      "size=\"587\" hash_sha256=\"lkq34j5345\"/></packages>"
+      "size=\"587\" fp=\"3.789\" hash_sha256=\"lkq34j5345\"/></packages>"
       "<actions><action event=\"postinstall\" "
       "Prompt=\"false\" "
       "IsDeltaPayload=\"false\" "
@@ -1572,6 +1587,7 @@
             response.packages[0].payload_urls[0]);
   EXPECT_EQ("", response.more_info_url);
   EXPECT_EQ("lkq34j5345", response.packages[0].hash);
+  EXPECT_EQ(string("3.789"), response.packages[0].fp);
   EXPECT_EQ(587u, response.packages[0].size);
   EXPECT_FALSE(response.prompt);
   EXPECT_TRUE(response.deadline.empty());
diff --git a/cros/omaha_response.h b/cros/omaha_response.h
index 43783d6..3b07745 100644
--- a/cros/omaha_response.h
+++ b/cros/omaha_response.h
@@ -55,6 +55,8 @@
     bool can_exclude = false;
     // The App ID associated with the package.
     std::string app_id;
+    // The unique fingerprint value associated with the package.
+    std::string fp;
   };
   std::vector<Package> packages;
 
diff --git a/cros/omaha_response_handler_action.cc b/cros/omaha_response_handler_action.cc
index b6c223f..52142a3 100644
--- a/cros/omaha_response_handler_action.cc
+++ b/cros/omaha_response_handler_action.cc
@@ -106,7 +106,9 @@
          .metadata_signature = package.metadata_signature,
          .hash = raw_hash,
          .type = package.is_delta ? InstallPayloadType::kDelta
-                                  : InstallPayloadType::kFull});
+                                  : InstallPayloadType::kFull,
+         .fp = package.fp,
+         .app_id = package.app_id});
     update_check_response_hash += package.hash + ":";
   }
   install_plan_.public_key_rsa = response.public_key_rsa;
diff --git a/cros/omaha_response_handler_action_unittest.cc b/cros/omaha_response_handler_action_unittest.cc
index b05660c..74f4d04 100644
--- a/cros/omaha_response_handler_action_unittest.cc
+++ b/cros/omaha_response_handler_action_unittest.cc
@@ -117,6 +117,9 @@
     "-the_update_a.b.c.d_DELTA_.tgz";
 const char* const kBadVersion = "don't update me";
 const char* const kPayloadHashHex = "486173682b";
+const char* const kPayloadFp1 = "1.755aff78ec73dfc7f590893ac";
+const char* const kPayloadFp2 = "1.98ba213e0ccec0d0e8cdc74a5";
+const char* const kPayloadAppId = "test_app_id";
 }  // namespace
 
 bool OmahaResponseHandlerActionTest::DoTest(const OmahaResponse& in,
@@ -185,7 +188,9 @@
     in.packages.push_back(
         {.payload_urls = {"http://foo/the_update_a.b.c.d.tgz"},
          .size = 12,
-         .hash = kPayloadHashHex});
+         .hash = kPayloadHashHex,
+         .app_id = kPayloadAppId,
+         .fp = kPayloadFp1});
     in.more_info_url = "http://more/info";
     in.prompt = false;
     in.deadline = "20101020";
@@ -193,6 +198,8 @@
     EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan));
     EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
     EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+    EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+    EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
     EXPECT_EQ(1U, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline));
@@ -211,7 +218,9 @@
     in.packages.push_back(
         {.payload_urls = {"http://foo/the_update_a.b.c.d.tgz"},
          .size = 12,
-         .hash = kPayloadHashHex});
+         .hash = kPayloadHashHex,
+         .app_id = kPayloadAppId,
+         .fp = kPayloadFp1});
     in.more_info_url = "http://more/info";
     in.prompt = true;
     InstallPlan install_plan;
@@ -220,6 +229,8 @@
     EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan));
     EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
     EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+    EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+    EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
     EXPECT_EQ(0U, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline) &&
@@ -230,8 +241,11 @@
     OmahaResponse in;
     in.update_exists = true;
     in.version = "a.b.c.d";
-    in.packages.push_back(
-        {.payload_urls = {kLongName}, .size = 12, .hash = kPayloadHashHex});
+    in.packages.push_back({.payload_urls = {kLongName},
+                           .size = 12,
+                           .hash = kPayloadHashHex,
+                           .app_id = kPayloadAppId,
+                           .fp = kPayloadFp1});
     in.more_info_url = "http://more/info";
     in.prompt = true;
     in.deadline = "some-deadline";
@@ -245,6 +259,8 @@
     EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan));
     EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
     EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+    EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+    EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
     EXPECT_EQ(1U, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline));
@@ -255,8 +271,11 @@
     OmahaResponse in;
     in.update_exists = true;
     in.version = "a.b.c.d";
-    in.packages.push_back(
-        {.payload_urls = {kLongName}, .size = 12, .hash = kPayloadHashHex});
+    in.packages.push_back({.payload_urls = {kLongName},
+                           .size = 12,
+                           .hash = kPayloadHashHex,
+                           .app_id = kPayloadAppId,
+                           .fp = kPayloadFp1});
     in.more_info_url = "http://more/info";
     in.prompt = true;
     in.deadline = "some-deadline";
@@ -268,6 +287,8 @@
     EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan));
     EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
     EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+    EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+    EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
     EXPECT_EQ(1U, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline));
@@ -309,10 +330,14 @@
   in.version = "a.b.c.d";
   in.packages.push_back({.payload_urls = {"http://package/1"},
                          .size = 1,
-                         .hash = kPayloadHashHex});
+                         .hash = kPayloadHashHex,
+                         .app_id = kPayloadAppId,
+                         .fp = kPayloadFp1});
   in.packages.push_back({.payload_urls = {"http://package/2"},
                          .size = 2,
-                         .hash = kPayloadHashHex});
+                         .hash = kPayloadHashHex,
+                         .app_id = kPayloadAppId,
+                         .fp = kPayloadFp2});
   in.more_info_url = "http://more/info";
   InstallPlan install_plan;
   EXPECT_TRUE(DoTest(in, "", &install_plan));
@@ -322,6 +347,10 @@
   EXPECT_EQ(in.packages[1].size, install_plan.payloads[1].size);
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
   EXPECT_EQ(expected_hash_, install_plan.payloads[1].hash);
+  EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+  EXPECT_EQ(in.packages[1].app_id, install_plan.payloads[1].app_id);
+  EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
+  EXPECT_EQ(in.packages[1].fp, install_plan.payloads[1].fp);
   EXPECT_EQ(in.version, install_plan.version);
 }
 
@@ -332,7 +361,9 @@
   in.packages.push_back(
       {.payload_urls = {"http://test.should/need/hash.checks.signed"},
        .size = 12,
-       .hash = kPayloadHashHex});
+       .hash = kPayloadHashHex,
+       .app_id = kPayloadAppId,
+       .fp = kPayloadFp1});
   in.more_info_url = "http://more/info";
   // Hash checks are always skipped for non-official update URLs.
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
@@ -342,6 +373,8 @@
   EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+  EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -353,7 +386,9 @@
   in.packages.push_back(
       {.payload_urls = {"http://url.normally/needs/hash.checks.signed"},
        .size = 12,
-       .hash = kPayloadHashHex});
+       .hash = kPayloadHashHex,
+       .app_id = kPayloadAppId,
+       .fp = kPayloadFp1});
   in.more_info_url = "http://more/info";
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
@@ -362,6 +397,8 @@
   EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+  EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
   EXPECT_FALSE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -375,7 +412,9 @@
   in.packages.push_back(
       {.payload_urls = {"http://url.normally/needs/hash.checks.signed"},
        .size = 12,
-       .hash = kPayloadHashHex});
+       .hash = kPayloadHashHex,
+       .app_id = kPayloadAppId,
+       .fp = kPayloadFp1});
   in.more_info_url = "http://more/info";
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
@@ -385,6 +424,8 @@
   EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+  EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
   EXPECT_FALSE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -396,7 +437,9 @@
   in.packages.push_back(
       {.payload_urls = {"https://test.should/need/hash.checks.signed"},
        .size = 12,
-       .hash = kPayloadHashHex});
+       .hash = kPayloadHashHex,
+       .app_id = kPayloadAppId,
+       .fp = kPayloadFp1});
   in.more_info_url = "http://more/info";
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
@@ -405,6 +448,8 @@
   EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+  EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -417,7 +462,9 @@
       {.payload_urls = {"http://test.should.still/need/hash.checks",
                         "https://test.should.still/need/hash.checks"},
        .size = 12,
-       .hash = kPayloadHashHex});
+       .hash = kPayloadHashHex,
+       .app_id = kPayloadAppId,
+       .fp = kPayloadFp1});
   in.more_info_url = "http://more/info";
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
@@ -426,6 +473,8 @@
   EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+  EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -675,7 +724,9 @@
   in.packages.push_back(
       {.payload_urls = {"https://would.not/cause/hash/checks"},
        .size = 12,
-       .hash = kPayloadHashHex});
+       .hash = kPayloadHashHex,
+       .app_id = kPayloadAppId,
+       .fp = kPayloadFp1});
   in.more_info_url = "http://more/info";
 
   OmahaRequestParams params(&fake_system_state_);
@@ -698,6 +749,8 @@
   InstallPlan install_plan;
   EXPECT_TRUE(DoTest(in, "", &install_plan));
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+  EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
   EXPECT_EQ(p2p_url, install_plan.download_url);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
 }
@@ -899,10 +952,14 @@
   in.version = "a.b.c.d";
   in.packages.push_back({.payload_urls = {"http://package/1"},
                          .size = 1,
-                         .hash = kPayloadHashHex});
+                         .hash = kPayloadHashHex,
+                         .app_id = kPayloadAppId,
+                         .fp = kPayloadFp1});
   in.packages.push_back({.payload_urls = {"http://package/2"},
                          .size = 2,
-                         .hash = kPayloadHashHex});
+                         .hash = kPayloadHashHex,
+                         .app_id = kPayloadAppId,
+                         .fp = kPayloadFp2});
   in.more_info_url = "http://more/info";
   InstallPlan install_plan;
   EXPECT_TRUE(DoTest(in, "", &install_plan));
@@ -912,6 +969,10 @@
   EXPECT_EQ(in.packages[1].size, install_plan.payloads[1].size);
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
   EXPECT_EQ(expected_hash_, install_plan.payloads[1].hash);
+  EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+  EXPECT_EQ(in.packages[1].app_id, install_plan.payloads[1].app_id);
+  EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
+  EXPECT_EQ(in.packages[1].fp, install_plan.payloads[1].fp);
   EXPECT_EQ(in.version, install_plan.version);
 }
 
@@ -921,7 +982,9 @@
   in.version = "a.b.c.d";
   in.packages.push_back({.payload_urls = {"http://foo/the_update_a.b.c.d.tgz"},
                          .size = 12,
-                         .hash = kPayloadHashHex});
+                         .hash = kPayloadHashHex,
+                         .app_id = kPayloadAppId,
+                         .fp = kPayloadFp1});
   // Setup the UpdateManager to disallow the update.
   FakeClock fake_clock;
   MockPolicy* mock_policy = new MockPolicy(&fake_clock);
@@ -942,6 +1005,8 @@
   install_plan = *delegate_.response_handler_action_install_plan_;
   EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id);
+  EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp);
   EXPECT_EQ(1U, install_plan.target_slot);
   EXPECT_EQ(in.version, install_plan.version);
 }
diff --git a/cros/update_attempter.cc b/cros/update_attempter.cc
index e417457..5c21d04 100644
--- a/cros/update_attempter.cc
+++ b/cros/update_attempter.cc
@@ -158,10 +158,12 @@
 
   // In case of update_engine restart without a reboot we need to restore the
   // reboot needed state.
-  if (GetBootTimeAtUpdate(nullptr))
+  if (GetBootTimeAtUpdate(nullptr)) {
     status_ = UpdateStatus::UPDATED_NEED_REBOOT;
-  else
+  } else {
     status_ = UpdateStatus::IDLE;
+    prefs_->Delete(kPrefsLastFp, {kDlcPrefsSubDir});
+  }
 }
 
 bool UpdateAttempter::ScheduleUpdates() {
@@ -646,6 +648,20 @@
   return failures.size() == 0;
 }
 
+void UpdateAttempter::SetPref(const string& pref_key,
+                              const string& pref_value,
+                              const string& payload_id) {
+  string dlc_id;
+  if (!omaha_request_params_->GetDlcId(payload_id, &dlc_id)) {
+    // Not a DLC ID, set fingerprint in perf for platform ID.
+    prefs_->SetString(pref_key, pref_value);
+  } else {
+    // Set fingerprint in pref for DLC ID.
+    auto key = prefs_->CreateSubKey({kDlcPrefsSubDir, dlc_id, pref_key});
+    prefs_->SetString(key, pref_value);
+  }
+}
+
 bool UpdateAttempter::SetDlcActiveValue(bool is_active, const string& dlc_id) {
   if (dlc_id.empty()) {
     LOG(ERROR) << "Empty DLC ID passed.";
@@ -1198,6 +1214,9 @@
     for (const auto& payload : install_plan_->payloads) {
       target_version_uid += brillo::data_encoding::Base64Encode(payload.hash) +
                             ":" + payload.metadata_signature + ":";
+      // Set fingerprint value for updates only.
+      if (!is_install_)
+        SetPref(kPrefsLastFp, payload.fp, payload.app_id);
     }
 
     // If we just downloaded a rollback image, we should preserve this fact
@@ -1419,6 +1438,7 @@
       // UpdateStatus::UPDATED_NEED_REBOOT state.
       ret_value = prefs_->Delete(kPrefsUpdateCompletedOnBootId) && ret_value;
       ret_value = prefs_->Delete(kPrefsUpdateCompletedBootTime) && ret_value;
+      ret_value = prefs_->Delete(kPrefsLastFp, {kDlcPrefsSubDir}) && ret_value;
 
       // Update the boot flags so the current slot has higher priority.
       BootControlInterface* boot_control = system_state_->boot_control();
diff --git a/cros/update_attempter.h b/cros/update_attempter.h
index bd0aef6..a201acf 100644
--- a/cros/update_attempter.h
+++ b/cros/update_attempter.h
@@ -433,6 +433,11 @@
   // Resets all the DLC prefs.
   bool ResetDlcPrefs(const std::string& dlc_id);
 
+  // Sets given pref key for DLC and platform.
+  void SetPref(const std::string& pref_key,
+               const std::string& pref_value,
+               const std::string& payload_id);
+
   // Get the integer values from the DLC metadata for |kPrefsPingLastActive|
   // or |kPrefsPingLastRollcall|.
   // The value is equal to -2 when the value cannot be read or is not numeric.
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
index 4a37836..3aae663 100644
--- a/payload_consumer/install_plan.cc
+++ b/payload_consumer/install_plan.cc
@@ -72,13 +72,16 @@
   for (const auto& payload : payloads) {
     payloads_str += base::StringPrintf(
         ", payload: (urls: %s, size: %" PRIu64 ", metadata_size: %" PRIu64
-        ", metadata signature: %s, hash: %s, payload type: %s)",
+        ", metadata signature: %s, hash: %s, payload type: %s"
+        ", fingerprint: %s, app id: %s)",
         PayloadUrlsToString(payload.payload_urls).c_str(),
         payload.size,
         payload.metadata_size,
         payload.metadata_signature.c_str(),
         base::HexEncode(payload.hash.data(), payload.hash.size()).c_str(),
-        InstallPayloadTypeToString(payload.type).c_str());
+        InstallPayloadTypeToString(payload.type).c_str(),
+        payload.fp.c_str(),
+        payload.app_id.c_str());
   }
 
   string version_str = base::StringPrintf(", version: %s", version.c_str());
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index ee1a72b..16e5674 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -62,6 +62,8 @@
     std::string metadata_signature;  // signature of the metadata in base64
     brillo::Blob hash;               // SHA256 hash of the payload
     InstallPayloadType type{InstallPayloadType::kUnknown};
+    std::string fp;      // fingerprint value unique to the payload
+    std::string app_id;  // App ID of the payload
     // Only download manifest and fill in partitions in install plan without
     // apply the payload if true. Will be set by DownloadAction when resuming
     // multi-payload.
@@ -72,7 +74,8 @@
              metadata_size == that.metadata_size &&
              metadata_signature == that.metadata_signature &&
              hash == that.hash && type == that.type &&
-             already_applied == that.already_applied;
+             already_applied == that.already_applied && fp == that.fp &&
+             app_id == that.app_id;
     }
   };
   std::vector<Payload> payloads;