update_engine: Parse and supply EOL date for Chrome

From Omaha, the optional field |_eol_date| is to indicate the EOL of a
device. Chrome side will leverage these values to display a
notification. The value for |_eol_date| should be an integer value
indicating the days from Unix Epoch date.

If |_eol_date| does not exist in the Omaha response or have non-integer
values, the default will fallback to |kEolDateInvalid|.

BUG=chromium:998983
TEST=FEATURES="test" emerge-$B update_engine update_engine-client system_api
TEST=test_that -b $B $IP autoupdate_EOL
TEST=test_that -b $B $IP autoupdate_EOL.approaching_eol
TEST=test_that -b $B $IP autoupdate_EOL.future_eol

Cq-Depend:chromium:1783596, chromium:1811116
Change-Id: I2b1063873118ccf8fe22ba09a5961e27aa980c7b
Reviewed-on: https://chromium-review.googlesource.com/1783897
Tested-by: Jae Hoon Kim <kimjae@chromium.org>
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Legacy-Commit-Queue: Commit Bot <commit-bot@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
diff --git a/client_library/client_dbus.cc b/client_library/client_dbus.cc
index e6aba92..d1d6cc0 100644
--- a/client_library/client_dbus.cc
+++ b/client_library/client_dbus.cc
@@ -56,6 +56,7 @@
   out_status->status = static_cast<UpdateStatus>(status.current_operation());
   out_status->is_enterprise_rollback = status.is_enterprise_rollback();
   out_status->is_install = status.is_install();
+  out_status->eol_date = status.eol_date();
 }
 }  // namespace
 
diff --git a/client_library/include/update_engine/update_status.h b/client_library/include/update_engine/update_status.h
index c877df6..c1d0968 100644
--- a/client_library/include/update_engine/update_status.h
+++ b/client_library/include/update_engine/update_status.h
@@ -88,6 +88,8 @@
   bool is_enterprise_rollback;
   // Indication of install for DLC(s).
   bool is_install;
+  // The end-of-life date of the device in the number of days since Unix Epoch.
+  int64_t eol_date;
 };
 
 }  // namespace update_engine
diff --git a/common/constants.cc b/common/constants.cc
index 87bdf91..64bdf0c 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -55,6 +55,7 @@
 const char kPrefsOmahaCohort[] = "omaha-cohort";
 const char kPrefsOmahaCohortHint[] = "omaha-cohort-hint";
 const char kPrefsOmahaCohortName[] = "omaha-cohort-name";
+const char kPrefsOmahaEolDate[] = "omaha-eol-date";
 const char kPrefsOmahaEolStatus[] = "omaha-eol-status";
 const char kPrefsP2PEnabled[] = "p2p-enabled";
 const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
diff --git a/common/constants.h b/common/constants.h
index d95a56a..23c9003 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -56,6 +56,7 @@
 extern const char kPrefsOmahaCohort[];
 extern const char kPrefsOmahaCohortHint[];
 extern const char kPrefsOmahaCohortName[];
+extern const char kPrefsOmahaEolDate[];
 extern const char kPrefsOmahaEolStatus[];
 extern const char kPrefsP2PEnabled[];
 extern const char kPrefsP2PFirstAttemptTimestamp[];
diff --git a/dbus_service.cc b/dbus_service.cc
index b0dc076..065fe0c 100644
--- a/dbus_service.cc
+++ b/dbus_service.cc
@@ -49,6 +49,7 @@
   out_status->set_new_size(ue_status.new_size_bytes);
   out_status->set_is_enterprise_rollback(ue_status.is_enterprise_rollback);
   out_status->set_is_install(ue_status.is_install);
+  out_status->set_eol_date(ue_status.eol_date);
 }
 }  // namespace
 
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 4d86586..7ca4372 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -109,6 +109,7 @@
 
 // updatecheck attributes (without the underscore prefix).
 constexpr char kAttrEol[] = "eol";
+constexpr char kAttrEolDate[] = "eol_date";
 constexpr char kAttrRollback[] = "rollback";
 constexpr char kAttrFirmwareVersion[] = "firmware_version";
 constexpr char kAttrKernelVersion[] = "kernel_version";
@@ -1317,14 +1318,29 @@
 }
 
 bool OmahaRequestAction::PersistEolStatus(const map<string, string>& attrs) {
-  auto eol_attr = attrs.find(kAttrEol);
-  if (eol_attr != attrs.end()) {
-    return system_state_->prefs()->SetString(kPrefsOmahaEolStatus,
-                                             eol_attr->second);
-  } else if (system_state_->prefs()->Exists(kPrefsOmahaEolStatus)) {
-    return system_state_->prefs()->Delete(kPrefsOmahaEolStatus);
+  bool ret = true;
+
+  // Set EOL date.
+  auto eol_date_attr = attrs.find(kAttrEolDate);
+  if (eol_date_attr == attrs.end()) {
+    system_state_->prefs()->Delete(kPrefsOmahaEolDate);
+  } else if (!system_state_->prefs()->SetString(kPrefsOmahaEolDate,
+                                                eol_date_attr->second)) {
+    LOG(ERROR) << "Setting EOL date failed.";
+    ret = false;
   }
-  return true;
+
+  // Set EOL.
+  auto eol_attr = attrs.find(kAttrEol);
+  if (eol_attr == attrs.end()) {
+    system_state_->prefs()->Delete(kPrefsOmahaEolStatus);
+  } else if (!system_state_->prefs()->SetString(kPrefsOmahaEolStatus,
+                                                eol_attr->second)) {
+    LOG(ERROR) << "Setting EOL status failed.";
+    ret = false;
+  }
+
+  return ret;
 }
 
 void OmahaRequestAction::ActionCompleted(ErrorCode code) {
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 12d36d9..96f09e9 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -184,7 +184,9 @@
                          const std::string& new_value);
 
   // Parse and persist the end-of-life status flag sent back in the updatecheck
-  // tag attributes. The flag will be validated and stored in the Prefs.
+  // tag attributes. In addition, the optional end-of-life date flag will also
+  // be parsed and persisted. The flags will be validated and stored in the
+  // Prefs.
   bool PersistEolStatus(const std::map<std::string, std::string>& attrs);
 
   // If this is an update check request, initializes
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 8008e00..94d5152 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -53,6 +53,7 @@
 #include "update_engine/mock_payload_state.h"
 #include "update_engine/omaha_request_builder_xml.h"
 #include "update_engine/omaha_request_params.h"
+#include "update_engine/omaha_utils.h"
 #include "update_engine/update_manager/rollback_prefs.h"
 
 using base::Time;
@@ -2809,4 +2810,65 @@
   EXPECT_EQ(string::npos, post_str.find("requisition"));
 }
 
+TEST_F(OmahaRequestActionTest, PersistEolDatesTest) {
+  tuc_params_.http_response =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+      "protocol=\"3.0\"><app appid=\"foo\" status=\"ok\">"
+      "<ping status=\"ok\"/><updatecheck status=\"noupdate\" "
+      "_eol=\"supported\" _eol_date=\"200\" "
+      "_foo=\"bar\"/></app></response>";
+  tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+  tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+  ASSERT_TRUE(TestUpdateCheck());
+
+  string eol, eol_date;
+  EXPECT_TRUE(
+      fake_system_state_.prefs()->GetString(kPrefsOmahaEolStatus, &eol));
+  EXPECT_EQ(kEolStatusSupported, eol);
+  EXPECT_TRUE(
+      fake_system_state_.prefs()->GetString(kPrefsOmahaEolDate, &eol_date));
+  EXPECT_EQ("200", eol_date);
+}
+
+TEST_F(OmahaRequestActionTest, PersistEolMissingDatesTest) {
+  tuc_params_.http_response =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+      "protocol=\"3.0\"><app appid=\"foo\" status=\"ok\">"
+      "<ping status=\"ok\"/><updatecheck status=\"noupdate\" "
+      "_eol=\"supported\" _foo=\"bar\"/></app></response>";
+  tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+  tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+  ASSERT_TRUE(TestUpdateCheck());
+
+  string eol, eol_date;
+  EXPECT_TRUE(
+      fake_system_state_.prefs()->GetString(kPrefsOmahaEolStatus, &eol));
+  EXPECT_EQ(kEolStatusSupported, eol);
+  EXPECT_FALSE(
+      fake_system_state_.prefs()->GetString(kPrefsOmahaEolDate, &eol_date));
+}
+
+TEST_F(OmahaRequestActionTest, PersistEolBadDatesTest) {
+  tuc_params_.http_response =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+      "protocol=\"3.0\"><app appid=\"foo\" status=\"ok\">"
+      "<ping status=\"ok\"/><updatecheck status=\"noupdate\" "
+      "_eol=\"supported\" _eol_date=\"bad\" "
+      "foo=\"bar\"/></app></response>";
+  tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+  tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+  ASSERT_TRUE(TestUpdateCheck());
+
+  string eol, eol_date;
+  EXPECT_TRUE(
+      fake_system_state_.prefs()->GetString(kPrefsOmahaEolStatus, &eol));
+  EXPECT_EQ(kEolStatusSupported, eol);
+  EXPECT_TRUE(
+      fake_system_state_.prefs()->GetString(kPrefsOmahaEolDate, &eol_date));
+  EXPECT_EQ(kEolDateInvalid, StringToEolDate(eol_date));
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_utils.cc b/omaha_utils.cc
index 6bd7525..9fe425b 100644
--- a/omaha_utils.cc
+++ b/omaha_utils.cc
@@ -17,17 +17,15 @@
 #include "update_engine/omaha_utils.h"
 
 #include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
 
 namespace chromeos_update_engine {
 
-namespace {
-
-// The possible string values for the end-of-life status.
 const char kEolStatusSupported[] = "supported";
 const char kEolStatusSecurityOnly[] = "security-only";
 const char kEolStatusEol[] = "eol";
 
-}  // namespace
+const EolDate kEolDateInvalid = -9999;
 
 const char* EolStatusToString(EolStatus eol_status) {
   switch (eol_status) {
@@ -54,4 +52,21 @@
   return EolStatus::kSupported;
 }
 
+std::string EolDateToString(EolDate eol_date) {
+#if BASE_VER < 576279
+  return base::Int64ToString(eol_date);
+#else
+  return base::NumberToString(eol_date);
+#endif
+}
+
+EolDate StringToEolDate(const std::string& eol_date) {
+  EolDate date = kEolDateInvalid;
+  if (!base::StringToInt64(eol_date, &date)) {
+    LOG(WARNING) << "Invalid EOL date attribute: " << eol_date;
+    return kEolDateInvalid;
+  }
+  return date;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_utils.h b/omaha_utils.h
index 8614540..128232a 100644
--- a/omaha_utils.h
+++ b/omaha_utils.h
@@ -21,6 +21,16 @@
 
 namespace chromeos_update_engine {
 
+using EolDate = int64_t;
+
+// |EolDate| indicating an invalid end-of-life date.
+extern const EolDate kEolDateInvalid;
+
+// The possible string values for the end-of-life status.
+extern const char kEolStatusSupported[];
+extern const char kEolStatusSecurityOnly[];
+extern const char kEolStatusEol[];
+
 // The end-of-life status of the device.
 enum class EolStatus {
   kSupported = 0,
@@ -35,6 +45,14 @@
 // of an invalid string, the default "supported" value will be used instead.
 EolStatus StringToEolStatus(const std::string& eol_status);
 
+// Returns the string representation of the |eol_date|.
+std::string EolDateToString(EolDate eol_date);
+
+// Converts the end-of-life date string to an EolDate numeric value. In case
+// of an invalid string, the default |kEolDateInvalid| value will be used
+// instead.
+EolDate StringToEolDate(const std::string& eol_date);
+
 }  // namespace chromeos_update_engine
 
 #endif  // UPDATE_ENGINE_OMAHA_UTILS_H_
diff --git a/omaha_utils_unittest.cc b/omaha_utils_unittest.cc
index 8ceb76b..ccb9578 100644
--- a/omaha_utils_unittest.cc
+++ b/omaha_utils_unittest.cc
@@ -39,4 +39,17 @@
   EXPECT_EQ(EolStatus::kSupported, StringToEolStatus("hello, world!"));
 }
 
+TEST(OmahaUtilsTest, EolDateTest) {
+  // Supported values are converted back and forth properly.
+  const std::vector<EolDate> tests = {kEolDateInvalid, -1, 0, 1};
+  for (EolDate eol_date : tests) {
+    EXPECT_EQ(eol_date, StringToEolDate(EolDateToString(eol_date)))
+        << "The StringToEolDate() was " << EolDateToString(eol_date);
+  }
+
+  // Invalid values are assumed as "supported".
+  EXPECT_EQ(kEolDateInvalid, StringToEolDate(""));
+  EXPECT_EQ(kEolDateInvalid, StringToEolDate("hello, world!"));
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_attempter.cc b/update_attempter.cc
index 780ba7b..18e5088 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -54,6 +54,7 @@
 #include "update_engine/omaha_request_action.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/omaha_response_handler_action.h"
+#include "update_engine/omaha_utils.h"
 #include "update_engine/p2p_manager.h"
 #include "update_engine/payload_consumer/download_action.h"
 #include "update_engine/payload_consumer/filesystem_verifier_action.h"
@@ -1385,6 +1386,11 @@
   out_status->is_enterprise_rollback =
       install_plan_ && install_plan_->is_rollback;
   out_status->is_install = is_install_;
+
+  string str_eol_date;
+  system_state_->prefs()->GetString(kPrefsOmahaEolDate, &str_eol_date);
+  out_status->eol_date = StringToEolDate(str_eol_date);
+
   return true;
 }
 
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 0e74353..4aff897 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -18,6 +18,7 @@
 
 #include <stdint.h>
 
+#include <limits>
 #include <memory>
 #include <unordered_set>
 
@@ -48,6 +49,7 @@
 #include "update_engine/mock_p2p_manager.h"
 #include "update_engine/mock_payload_state.h"
 #include "update_engine/mock_service_observer.h"
+#include "update_engine/omaha_utils.h"
 #include "update_engine/payload_consumer/filesystem_verifier_action.h"
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/payload_consumer/payload_constants.h"
@@ -2244,4 +2246,36 @@
   EXPECT_TRUE(status.is_enterprise_rollback);
 }
 
+TEST_F(UpdateAttempterTest, FutureEolTest) {
+  EolDate eol_date = std::numeric_limits<int64_t>::max();
+  EXPECT_CALL(*prefs_, GetString(kPrefsOmahaEolDate, _))
+      .WillOnce(
+          DoAll(SetArgPointee<1>(EolDateToString(eol_date)), Return(true)));
+
+  UpdateEngineStatus status;
+  attempter_.GetStatus(&status);
+  EXPECT_EQ(eol_date, status.eol_date);
+}
+
+TEST_F(UpdateAttempterTest, PastEolTest) {
+  EolDate eol_date = 1;
+  EXPECT_CALL(*prefs_, GetString(kPrefsOmahaEolDate, _))
+      .WillOnce(
+          DoAll(SetArgPointee<1>(EolDateToString(eol_date)), Return(true)));
+
+  UpdateEngineStatus status;
+  attempter_.GetStatus(&status);
+  EXPECT_EQ(eol_date, status.eol_date);
+}
+
+TEST_F(UpdateAttempterTest, FailedEolTest) {
+  EolDate eol_date = kEolDateInvalid;
+  EXPECT_CALL(*prefs_, GetString(kPrefsOmahaEolDate, _))
+      .WillOnce(Return(false));
+
+  UpdateEngineStatus status;
+  attempter_.GetStatus(&status);
+  EXPECT_EQ(eol_date, status.eol_date);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_engine_client.cc b/update_engine_client.cc
index d78cee7..7b5c4df 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -42,6 +42,8 @@
 #include "update_engine/update_status_utils.h"
 
 using brillo::KeyValueStore;
+using chromeos_update_engine::EolDate;
+using chromeos_update_engine::EolDateToString;
 using chromeos_update_engine::EolStatus;
 using chromeos_update_engine::ErrorCode;
 using chromeos_update_engine::UpdateEngineStatusToString;
@@ -569,16 +571,14 @@
   }
 
   if (FLAGS_eol_status) {
-    int eol_status;
-    if (!client_->GetEolStatus(&eol_status)) {
-      LOG(ERROR) << "Error getting the end-of-life status.";
+    UpdateEngineStatus status;
+    if (!client_->GetStatus(&status)) {
+      LOG(ERROR) << "Error GetStatus() for getting EOL info.";
     } else {
-      EolStatus eol_status_code = static_cast<EolStatus>(eol_status);
+      EolDate eol_date_code = status.eol_date;
 
       KeyValueStore eol_status_store;
-      eol_status_store.SetString("EOL_STATUS",
-                                 EolStatusToString(eol_status_code));
-
+      eol_status_store.SetString("EOL_DATE", EolDateToString(eol_date_code));
       printf("%s", eol_status_store.SaveToString().c_str());
     }
   }