Add EC and Firmware Versions to the Omaha Response.

This CL adds 2 additional utils methods to get the version for both the
fw and ec versions. I've added a unittest to verify these work and piped
in the values into the omaha response.

BUG=chromium:219871
TEST=Unittests + on device

Change-Id: Iadf70fff858988f52797d94bcdb062bb2482bbf3
Reviewed-on: https://gerrit.chromium.org/gerrit/49713
Commit-Queue: Chris Sosa <sosa@chromium.org>
Reviewed-by: Chris Sosa <sosa@chromium.org>
Tested-by: Chris Sosa <sosa@chromium.org>
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index c28e0d6..15d3188 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -209,6 +209,8 @@
                 "board=\"" + XmlEncode(params->os_board()) + "\" " +
                 "hardware_class=\"" + XmlEncode(params->hwid()) + "\" " +
                 "delta_okay=\"" + delta_okay_str + "\" "
+                "fw_version=\"" + XmlEncode(params->fw_version()) + "\" " +
+                "ec_version=\"" + XmlEncode(params->ec_version()) + "\" " +
                 ">\n" +
                    app_body +
       "    </app>\n";
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 8ae951e..de5f49f 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -53,6 +53,8 @@
     "en-US",
     "unittest",
     "OEM MODEL 09235 7471",
+    "ChromeOSFirmware.1.0",
+    "0X0A1",
     false,  // delta okay
     false,  // interactive
     "http://url",
@@ -827,6 +829,8 @@
                             "en-US",
                             "unittest_track&lt;",
                             "<OEM MODEL>",
+                            "ChromeOSFirmware.1.0",
+                            "EC100",
                             false,  // delta okay
                             false,  // interactive
                             "http://url",
@@ -927,6 +931,10 @@
       string::npos);
   EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
             string::npos);
+  EXPECT_NE(post_str.find("fw_version=\"ChromeOSFirmware.1.0\""),
+            string::npos);
+  EXPECT_NE(post_str.find("ec_version=\"0X0A1\""),
+            string::npos);
 }
 
 
@@ -954,6 +962,10 @@
       string::npos);
   EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
             string::npos);
+  EXPECT_NE(post_str.find("fw_version=\"ChromeOSFirmware.1.0\""),
+            string::npos);
+  EXPECT_NE(post_str.find("ec_version=\"0X0A1\""),
+            string::npos);
 }
 
 TEST(OmahaRequestActionTest, FormatSuccessEventOutputTest) {
@@ -1035,6 +1047,8 @@
                               "en-US",
                               "unittest_track",
                               "OEM MODEL REV 1234",
+                              "ChromeOSFirmware.1.0",
+                              "EC100",
                               delta_okay,
                               false,  // interactive
                               "http://url",
@@ -1072,6 +1086,8 @@
                               "en-US",
                               "unittest_track",
                               "OEM MODEL REV 1234",
+                              "ChromeOSFirmware.1.0",
+                              "EC100",
                               true,  // delta_okay
                               interactive,
                               "http://url",
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
index f30e771..38262a9 100644
--- a/omaha_request_params.cc
+++ b/omaha_request_params.cc
@@ -13,6 +13,7 @@
 #include <vector>
 
 #include <base/file_util.h>
+#include <base/string_util.h>
 #include <policy/device_policy.h>
 
 #include "update_engine/constants.h"
@@ -78,6 +79,10 @@
                                stateful_override);
   app_lang_ = "en-US";
   hwid_ = utils::GetHardwareClass();
+  if (CollectECFWVersions()) {
+    fw_version_ = utils::GetFirmwareVersion();
+    ec_version_ = utils::GetECVersion(NULL);
+  }
 
   if (current_channel_ == target_channel_) {
     // deltas are only okay if the /.nodelta file does not exist.  if we don't
@@ -107,6 +112,17 @@
   return true;
 }
 
+bool OmahaRequestParams::CollectECFWVersions() const {
+  return (
+      StartsWithASCII(hwid_, string("SAMS ALEX"), true) ||
+      StartsWithASCII(hwid_, string("BUTTERFLY"), true) ||
+      StartsWithASCII(hwid_, string("LUMPY"), true) ||
+      StartsWithASCII(hwid_, string("PARROT"), true) ||
+      StartsWithASCII(hwid_, string("SPRING"), true) ||
+      StartsWithASCII(hwid_, string("SNOW"), true)
+  );
+}
+
 bool OmahaRequestParams::SetTargetChannel(const std::string& new_target_channel,
                                           bool is_powerwash_allowed) {
   LOG(INFO) << "SetTargetChannel called with " << new_target_channel
diff --git a/omaha_request_params.h b/omaha_request_params.h
index a3d08ce..7a85500 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -58,6 +58,8 @@
                      const std::string& in_app_lang,
                      const std::string& in_target_channel,
                      const std::string& in_hwid,
+                     const std::string& in_fw_version,
+                     const std::string& in_ec_version,
                      bool in_delta_okay,
                      bool in_interactive,
                      const std::string& in_update_url,
@@ -75,6 +77,8 @@
         current_channel_(in_target_channel),
         target_channel_(in_target_channel),
         hwid_(in_hwid),
+        fw_version_(in_fw_version),
+        ec_version_(in_ec_version),
         delta_okay_(in_delta_okay),
         interactive_(in_interactive),
         update_url_(in_update_url),
@@ -97,6 +101,8 @@
   inline std::string canary_app_id() const { return canary_app_id_; }
   inline std::string app_lang() const { return app_lang_; }
   inline std::string hwid() const { return hwid_; }
+  inline std::string fw_version() const { return fw_version_; }
+  inline std::string ec_version() const { return ec_version_; }
 
   inline void set_app_version(const std::string& version) {
     app_version_ = version;
@@ -219,6 +225,7 @@
   FRIEND_TEST(OmahaRequestParamsTest, ShouldLockDownTest);
   FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest);
   FRIEND_TEST(OmahaRequestParamsTest, LsbPreserveTest);
+  FRIEND_TEST(OmahaRequestParamsTest, CollectECFWVersionsTest);
 
   // Use a validator that is a non-static member of this class so that its
   // inputs can be mocked in unit tests (e.g., build type for IsValidChannel).
@@ -239,6 +246,10 @@
   // Returns the index of the given channel.
   int GetChannelIndex(const std::string& channel) const;
 
+  // Returns True if we should store the fw/ec versions based on our hwid_.
+  // Compares hwid to a set of whitelisted prefixes.
+  bool CollectECFWVersions() const;
+
   // These are individual helper methods to initialize the said properties from
   // the LSB value.
   void SetTargetChannelFromLsbValue();
@@ -300,6 +311,8 @@
   std::string download_channel_;
 
   std::string hwid_;  // Hardware Qualification ID of the client
+  std::string fw_version_;  // Chrome OS Firmware Version.
+  std::string ec_version_;  // Chrome OS EC Version.
   bool delta_okay_;  // If this client can accept a delta
   bool interactive_;   // Whether this is a user-initiated update check
 
diff --git a/omaha_request_params_unittest.cc b/omaha_request_params_unittest.cc
index c12abcb..986aacb 100644
--- a/omaha_request_params_unittest.cc
+++ b/omaha_request_params_unittest.cc
@@ -577,4 +577,23 @@
   EXPECT_EQ("r", out.GetAppId());
 }
 
+TEST_F(OmahaRequestParamsTest, CollectECFWVersionsTest) {
+  ASSERT_TRUE(WriteFileString(
+      kTestDir + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_APPID=r\n"
+      "CHROMEOS_CANARY_APPID=c\n"
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+  MockSystemState mock_system_state;
+  OmahaRequestParams out(&mock_system_state);
+  out.hwid_ = string("STUMPY ALEX 12345");
+  EXPECT_FALSE(out.CollectECFWVersions());
+
+  out.hwid_ = string("SNOW 12345");
+  EXPECT_TRUE(out.CollectECFWVersions());
+
+  out.hwid_ = string("SAMS ALEX 12345");
+  EXPECT_TRUE(out.CollectECFWVersions());
+}
+
+
 }  // namespace chromeos_update_engine
diff --git a/utils.cc b/utils.cc
index a6d8d91..d24a61f 100644
--- a/utils.cc
+++ b/utils.cc
@@ -18,6 +18,7 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <vector>
 
 #include <base/file_path.h>
 #include <base/file_util.h>
@@ -25,6 +26,7 @@
 #include <base/posix/eintr_wrapper.h>
 #include <base/rand_util.h>
 #include <base/string_number_conversions.h>
+#include <base/string_split.h>
 #include <base/string_util.h>
 #include <base/stringprintf.h>
 #include <glib.h>
@@ -41,6 +43,7 @@
 using base::Time;
 using base::TimeDelta;
 using std::min;
+using std::pair;
 using std::string;
 using std::vector;
 
@@ -82,23 +85,70 @@
   return !dev_mode;
 }
 
-string GetHardwareClass() {
-  // TODO(petkov): Convert to a library call once a crossystem library is
-  // available (crosbug.com/13291).
+string ReadValueFromCrosSystem(const string& key){
   int exit_code = 0;
   vector<string> cmd(1, "/usr/bin/crossystem");
-  cmd.push_back("hwid");
+  cmd.push_back(key);
 
-  string hwid;
-  bool success = Subprocess::SynchronousExec(cmd, &exit_code, &hwid);
+  string return_value;
+  bool success = Subprocess::SynchronousExec(cmd, &exit_code, &return_value);
   if (success && !exit_code) {
-    TrimWhitespaceASCII(hwid, TRIM_ALL, &hwid);
-    return hwid;
+    TrimWhitespaceASCII(return_value, TRIM_ALL, &return_value);
+    return return_value;
   }
-  LOG(ERROR) << "Unable to read HWID (" << exit_code << ") " << hwid;
+  LOG(ERROR) << "Unable to read " << key << " (" << exit_code << ") "
+             << return_value;
   return "";
 }
 
+string GetHardwareClass() {
+  return ReadValueFromCrosSystem("hwid");
+}
+
+string GetFirmwareVersion() {
+  return ReadValueFromCrosSystem("fwid");
+}
+
+string GetECVersion(const char* input_line) {
+  string line;
+  if(input_line == NULL) {
+    int exit_code = 0;
+    vector<string> cmd(1, "/usr/sbin/mosys");
+    cmd.push_back("-k");
+    cmd.push_back("ec");
+    cmd.push_back("info");
+
+    bool success = Subprocess::SynchronousExec(cmd, &exit_code, &line);
+    if (!success || exit_code) {
+      LOG(ERROR) << "Unable to read ec info from mosys (" << exit_code << ")";
+      return "";
+    }
+  } else {
+    line = input_line;
+  }
+
+  TrimWhitespaceASCII(line, TRIM_ALL, &line);
+
+  // At this point we want to conver the format key=value pair from mosys to
+  // a vector of key value pairs.
+  vector<pair<string, string> > kv_pairs;
+  if (base::SplitStringIntoKeyValuePairs(line, '=', ' ', &kv_pairs)) {
+    for (vector<pair<string, string> >::iterator it = kv_pairs.begin();
+         it != kv_pairs.end(); ++it) {
+      // Finally match against the fw_verion which may have quotes.
+      if (it->first == "fw_version") {
+        string output;
+        // Trim any quotes.
+        TrimString(it->second, "\"", &output);
+        return output;
+      }
+    }
+  }
+  LOG(ERROR) << "Unable to parse fwid from ec info.";
+  return "";
+}
+
+
 bool WriteFile(const char* path, const char* data, int data_len) {
   DirectFileWriter writer;
   TEST_AND_RETURN_FALSE_ERRNO(0 == writer.Open(path,
diff --git a/utils.h b/utils.h
index bc645d2..5193845 100644
--- a/utils.h
+++ b/utils.h
@@ -38,6 +38,16 @@
 // Returns the HWID or an empty string on error.
 std::string GetHardwareClass();
 
+// Returns the firmware version or an empty string if the system is not running
+// chrome os firmware.
+std::string GetFirmwareVersion();
+
+// Returns the ec version or an empty string if the system is not running a
+// custom chrome os ec. If input_line is not NULL, reads from this line,
+// otherwise polls the system for the input line. input_line should contain
+// fw_version=value.
+std::string GetECVersion(const char* input_line);
+
 // Writes the data passed to path. The file at path will be overwritten if it
 // exists. Returns true on success, false otherwise.
 bool WriteFile(const char* path, const char* data, int data_len);
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 3d2feaa..4d9dea4 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -37,6 +37,21 @@
   EXPECT_TRUE(utils::IsNormalBootMode());
 }
 
+TEST(UtilsTest, CanParseECVersion) {
+  // Chroot won't have an ec version.
+  EXPECT_EQ("", utils::GetECVersion(NULL));
+
+  // Should be able to parse and valid key value line.
+  EXPECT_EQ("12345", utils::GetECVersion("fw_version=12345"));
+  EXPECT_EQ("123456", utils::GetECVersion("b=1231a fw_version=123456 a=fasd2"));
+  EXPECT_EQ("12345", utils::GetECVersion("fw_version=12345"));
+  EXPECT_EQ("00VFA616", utils::GetECVersion(
+      "vendor=\"sam\" fw_version=\"00VFA616\""));
+
+  // For invalid entries, should return the empty string.
+  EXPECT_EQ("", utils::GetECVersion("b=1231a fw_version a=fasd2"));
+}
+
 TEST(UtilsTest, NormalizePathTest) {
   EXPECT_EQ("", utils::NormalizePath("", false));
   EXPECT_EQ("", utils::NormalizePath("", true));