update_engine: Create cros vs. aosp boundary clear
Its time to make the boundary between Chrome OS and Android code more
clear. This CL moves all CrOS only code to "chromeos" directory and the
same for Android (in "android" directory). This way we would easily know
which code is uses in which project and can keep the code cleaner and
more maintainable.
One big remaining problem is download_action* files. It seems like
DownloadAction class does a lot of things that chrome OS needs and it
depends on a lot of Chrome OS stuff, but Android is also using thie
Action in a way that circumvent the Chrome OS stuff. For example Android
checks for SystemState to be nullptr to not do things. This is really
fragile and needs to change. Probably Android Team has to implement
their own DownloadAction of some sort and not re use the Chrome OS one
in a very fragile way.
Removed a few android files that have not been used anywhere.
Changed some clang-format and lint issues in order to pass preupload.
BUG=b:171829801
TEST=cros_workon_make --board reef --test update_engine
Change-Id: I3fff1d4a100a065a5c1484a845241b5521614d9f
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2508965
Tested-by: Amin Hassani <ahassani@chromium.org>
Auto-Submit: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Jae Hoon Kim <kimjae@chromium.org>
Reviewed-by: Tianjie Xu <xunchang@google.com>
Reviewed-by: Kelvin Zhang <zhangkelvin@google.com>
Commit-Queue: Amin Hassani <ahassani@chromium.org>
diff --git a/cros/boot_control_chromeos.cc b/cros/boot_control_chromeos.cc
new file mode 100644
index 0000000..17659ae
--- /dev/null
+++ b/cros/boot_control_chromeos.cc
@@ -0,0 +1,378 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/boot_control_chromeos.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <chromeos/constants/imageloader.h>
+#include <rootdev/rootdev.h>
+
+extern "C" {
+#include <vboot/vboot_host.h>
+}
+
+#include "update_engine/common/boot_control.h"
+#include "update_engine/common/dynamic_partition_control_stub.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+const char* kChromeOSPartitionNameKernel = "kernel";
+const char* kChromeOSPartitionNameRoot = "root";
+const char* kAndroidPartitionNameKernel = "boot";
+const char* kAndroidPartitionNameRoot = "system";
+
+const char kPartitionNamePrefixDlc[] = "dlc";
+const char kPartitionNameDlcA[] = "dlc_a";
+const char kPartitionNameDlcB[] = "dlc_b";
+const char kPartitionNameDlcImage[] = "dlc.img";
+
+// Returns the currently booted rootfs partition. "/dev/sda3", for example.
+string GetBootDevice() {
+ char boot_path[PATH_MAX];
+ // Resolve the boot device path fully, including dereferencing through
+ // dm-verity.
+ int ret = rootdev(boot_path, sizeof(boot_path), true, false);
+ if (ret < 0) {
+ LOG(ERROR) << "rootdev failed to find the root device";
+ return "";
+ }
+ LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
+
+ // This local variable is used to construct the return string and is not
+ // passed around after use.
+ return boot_path;
+}
+
+// ExecCallback called when the execution of setgoodkernel finishes. Notifies
+// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
+// result.
+void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
+ int return_code,
+ const string& output) {
+ callback.Run(return_code == 0);
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace boot_control {
+
+// Factory defined in boot_control.h.
+std::unique_ptr<BootControlInterface> CreateBootControl() {
+ std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
+ new BootControlChromeOS());
+ if (!boot_control_chromeos->Init()) {
+ LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
+ }
+ return std::move(boot_control_chromeos);
+}
+
+} // namespace boot_control
+
+bool BootControlChromeOS::Init() {
+ string boot_device = GetBootDevice();
+ if (boot_device.empty())
+ return false;
+
+ int partition_num;
+ if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
+ return false;
+
+ // All installed Chrome OS devices have two slots. We don't update removable
+ // devices, so we will pretend we have only one slot in that case.
+ if (IsRemovableDevice(boot_disk_name_)) {
+ LOG(INFO)
+ << "Booted from a removable device, pretending we have only one slot.";
+ num_slots_ = 1;
+ } else {
+ // TODO(deymo): Look at the actual number of slots reported in the GPT.
+ num_slots_ = 2;
+ }
+
+ // Search through the slots to see which slot has the partition_num we booted
+ // from. This should map to one of the existing slots, otherwise something is
+ // very wrong.
+ current_slot_ = 0;
+ while (current_slot_ < num_slots_ &&
+ partition_num !=
+ GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
+ current_slot_++;
+ }
+ if (current_slot_ >= num_slots_) {
+ LOG(ERROR) << "Couldn't find the slot number corresponding to the "
+ << "partition " << boot_device
+ << ", number of slots: " << num_slots_
+ << ". This device is not updateable.";
+ num_slots_ = 1;
+ current_slot_ = BootControlInterface::kInvalidSlot;
+ return false;
+ }
+
+ dynamic_partition_control_.reset(new DynamicPartitionControlStub());
+
+ LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
+ << SlotName(current_slot_) << ") of " << num_slots_
+ << " slots present on disk " << boot_disk_name_;
+ return true;
+}
+
+unsigned int BootControlChromeOS::GetNumSlots() const {
+ return num_slots_;
+}
+
+BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
+ return current_slot_;
+}
+
+bool BootControlChromeOS::ParseDlcPartitionName(
+ const std::string partition_name,
+ std::string* dlc_id,
+ std::string* dlc_package) const {
+ CHECK_NE(dlc_id, nullptr);
+ CHECK_NE(dlc_package, nullptr);
+
+ vector<string> tokens = base::SplitString(
+ partition_name, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (tokens.size() != 3 || tokens[0] != kPartitionNamePrefixDlc) {
+ LOG(ERROR) << "DLC partition name (" << partition_name
+ << ") is not well formatted.";
+ return false;
+ }
+ if (tokens[1].empty() || tokens[2].empty()) {
+ LOG(ERROR) << " partition name does not contain valid DLC ID (" << tokens[1]
+ << ") or package (" << tokens[2] << ")";
+ return false;
+ }
+
+ *dlc_id = tokens[1];
+ *dlc_package = tokens[2];
+ return true;
+}
+
+bool BootControlChromeOS::GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ bool not_in_payload,
+ std::string* device,
+ bool* is_dynamic) const {
+ // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
+ if (base::StartsWith(partition_name,
+ kPartitionNamePrefixDlc,
+ base::CompareCase::SENSITIVE)) {
+ string dlc_id, dlc_package;
+ if (!ParseDlcPartitionName(partition_name, &dlc_id, &dlc_package))
+ return false;
+
+ *device = base::FilePath(imageloader::kDlcImageRootpath)
+ .Append(dlc_id)
+ .Append(dlc_package)
+ .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
+ .Append(kPartitionNameDlcImage)
+ .value();
+ return true;
+ }
+ int partition_num = GetPartitionNumber(partition_name, slot);
+ if (partition_num < 0)
+ return false;
+
+ string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
+ if (part_device.empty())
+ return false;
+
+ *device = part_device;
+ if (is_dynamic) {
+ *is_dynamic = false;
+ }
+ return true;
+}
+
+bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
+ BootControlInterface::Slot slot,
+ string* device) const {
+ return GetPartitionDevice(partition_name, slot, false, device, nullptr);
+}
+
+bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
+ int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+ if (partition_num < 0)
+ return false;
+
+ CgptAddParams params;
+ memset(¶ms, '\0', sizeof(params));
+ params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ params.partition = partition_num;
+
+ int retval = CgptGetPartitionDetails(¶ms);
+ if (retval != CGPT_OK)
+ return false;
+
+ return params.successful || params.tries > 0;
+}
+
+bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
+ LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
+
+ if (slot == current_slot_) {
+ LOG(ERROR) << "Refusing to mark current slot as unbootable.";
+ return false;
+ }
+
+ int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+ if (partition_num < 0)
+ return false;
+
+ CgptAddParams params;
+ memset(¶ms, 0, sizeof(params));
+
+ params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ params.partition = partition_num;
+
+ params.successful = false;
+ params.set_successful = true;
+
+ params.tries = 0;
+ params.set_tries = true;
+
+ int retval = CgptSetAttributes(¶ms);
+ if (retval != CGPT_OK) {
+ LOG(ERROR) << "Marking kernel unbootable failed.";
+ return false;
+ }
+
+ return true;
+}
+
+bool BootControlChromeOS::SetActiveBootSlot(Slot slot) {
+ LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
+
+ int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+ if (partition_num < 0)
+ return false;
+
+ CgptPrioritizeParams prio_params;
+ memset(&prio_params, 0, sizeof(prio_params));
+
+ prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ prio_params.set_partition = partition_num;
+
+ prio_params.max_priority = 0;
+
+ int retval = CgptPrioritize(&prio_params);
+ if (retval != CGPT_OK) {
+ LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
+ << " (partition " << partition_num << ").";
+ return false;
+ }
+
+ CgptAddParams add_params;
+ memset(&add_params, 0, sizeof(add_params));
+
+ add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ add_params.partition = partition_num;
+
+ add_params.tries = 6;
+ add_params.set_tries = true;
+
+ retval = CgptSetAttributes(&add_params);
+ if (retval != CGPT_OK) {
+ LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
+ << " for slot " << SlotName(slot) << " (partition "
+ << partition_num << ").";
+ return false;
+ }
+
+ return true;
+}
+
+bool BootControlChromeOS::MarkBootSuccessfulAsync(
+ base::Callback<void(bool)> callback) {
+ return Subprocess::Get().Exec(
+ {"/usr/sbin/chromeos-setgoodkernel"},
+ base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
+}
+
+// static
+string BootControlChromeOS::SysfsBlockDevice(const string& device) {
+ base::FilePath device_path(device);
+ if (device_path.DirName().value() != "/dev") {
+ return "";
+ }
+ return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
+}
+
+// static
+bool BootControlChromeOS::IsRemovableDevice(const string& device) {
+ string sysfs_block = SysfsBlockDevice(device);
+ string removable;
+ if (sysfs_block.empty() ||
+ !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
+ &removable)) {
+ return false;
+ }
+ base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
+ return removable == "1";
+}
+
+int BootControlChromeOS::GetPartitionNumber(
+ const string partition_name, BootControlInterface::Slot slot) const {
+ if (slot >= num_slots_) {
+ LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
+ << num_slots_ << " slot(s)";
+ return -1;
+ }
+
+ // In Chrome OS, the partition numbers are hard-coded:
+ // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
+ // To help compatibility between different we accept both lowercase and
+ // uppercase names in the ChromeOS or Brillo standard names.
+ // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
+ string partition_lower = base::ToLowerASCII(partition_name);
+ int base_part_num = 2 + 2 * slot;
+ if (partition_lower == kChromeOSPartitionNameKernel ||
+ partition_lower == kAndroidPartitionNameKernel)
+ return base_part_num + 0;
+ if (partition_lower == kChromeOSPartitionNameRoot ||
+ partition_lower == kAndroidPartitionNameRoot)
+ return base_part_num + 1;
+ LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
+ return -1;
+}
+
+bool BootControlChromeOS::IsSlotMarkedSuccessful(Slot slot) const {
+ LOG(ERROR) << __func__ << " not supported.";
+ return false;
+}
+
+DynamicPartitionControlInterface*
+BootControlChromeOS::GetDynamicPartitionControl() {
+ return dynamic_partition_control_.get();
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/boot_control_chromeos.h b/cros/boot_control_chromeos.h
new file mode 100644
index 0000000..0dff2c0
--- /dev/null
+++ b/cros/boot_control_chromeos.h
@@ -0,0 +1,104 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_BOOT_CONTROL_CHROMEOS_H_
+#define UPDATE_ENGINE_CROS_BOOT_CONTROL_CHROMEOS_H_
+
+#include <memory>
+#include <string>
+
+#include <base/callback.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/dynamic_partition_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// The Chrome OS implementation of the BootControlInterface. This interface
+// assumes the partition names and numbers used in Chrome OS devices.
+class BootControlChromeOS : public BootControlInterface {
+ public:
+ BootControlChromeOS() = default;
+ ~BootControlChromeOS() = default;
+
+ // Initialize the BootControl instance loading the constant values. Returns
+ // whether the operation succeeded. In case of failure, normally meaning
+ // some critical failure such as we couldn't determine the slot that we
+ // booted from, the implementation will pretend that there's only one slot and
+ // therefore A/B updates are disabled.
+ bool Init();
+
+ // BootControlInterface overrides.
+ unsigned int GetNumSlots() const override;
+ BootControlInterface::Slot GetCurrentSlot() const override;
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ bool not_in_payload,
+ std::string* device,
+ bool* is_dynamic) const override;
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ std::string* device) const override;
+ bool IsSlotBootable(BootControlInterface::Slot slot) const override;
+ bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
+ bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
+ bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+ bool IsSlotMarkedSuccessful(BootControlInterface::Slot slot) const override;
+ DynamicPartitionControlInterface* GetDynamicPartitionControl() override;
+
+ private:
+ friend class BootControlChromeOSTest;
+ FRIEND_TEST(BootControlChromeOSTest, SysfsBlockDeviceTest);
+ FRIEND_TEST(BootControlChromeOSTest, GetPartitionNumberTest);
+ FRIEND_TEST(BootControlChromeOSTest, ParseDlcPartitionNameTest);
+
+ // Returns the sysfs block device for a root block device. For example,
+ // SysfsBlockDevice("/dev/sda") returns "/sys/block/sda". Returns an empty
+ // string if the input device is not of the "/dev/xyz" form.
+ static std::string SysfsBlockDevice(const std::string& device);
+
+ // Returns true if the root |device| (e.g., "/dev/sdb") is known to be
+ // removable, false otherwise.
+ static bool IsRemovableDevice(const std::string& device);
+
+ // Return the hard-coded partition number used in Chrome OS for the passed
+ // |partition_name| and |slot|. In case of invalid data, returns -1.
+ int GetPartitionNumber(const std::string partition_name,
+ BootControlInterface::Slot slot) const;
+
+ // Extracts DLC module ID and package ID from partition name. The structure of
+ // the partition name is dlc/<dlc-id>/<dlc-package>. For example:
+ // dlc/fake-dlc/fake-package
+ bool ParseDlcPartitionName(const std::string partition_name,
+ std::string* dlc_id,
+ std::string* dlc_package) const;
+
+ // Cached values for GetNumSlots() and GetCurrentSlot().
+ BootControlInterface::Slot num_slots_{1};
+ BootControlInterface::Slot current_slot_{BootControlInterface::kInvalidSlot};
+
+ // The block device of the disk we booted from, without the partition number.
+ std::string boot_disk_name_;
+
+ std::unique_ptr<DynamicPartitionControlInterface> dynamic_partition_control_;
+
+ DISALLOW_COPY_AND_ASSIGN(BootControlChromeOS);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_BOOT_CONTROL_CHROMEOS_H_
diff --git a/cros/boot_control_chromeos_unittest.cc b/cros/boot_control_chromeos_unittest.cc
new file mode 100644
index 0000000..fc1dd1e
--- /dev/null
+++ b/cros/boot_control_chromeos_unittest.cc
@@ -0,0 +1,90 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/boot_control_chromeos.h"
+
+#include <gtest/gtest.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+class BootControlChromeOSTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // We don't run Init() for bootctl_, we set its internal values instead.
+ bootctl_.num_slots_ = 2;
+ bootctl_.current_slot_ = 0;
+ bootctl_.boot_disk_name_ = "/dev/null";
+ }
+
+ BootControlChromeOS bootctl_; // BootControlChromeOS under test.
+};
+
+TEST_F(BootControlChromeOSTest, SysfsBlockDeviceTest) {
+ EXPECT_EQ("/sys/block/sda", bootctl_.SysfsBlockDevice("/dev/sda"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("/foo/sda"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("/dev/foo/bar"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("/"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("./"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice(""));
+}
+
+TEST_F(BootControlChromeOSTest, GetPartitionNumberTest) {
+ // The partition name should not be case-sensitive.
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("kernel", 0));
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("boot", 0));
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("KERNEL", 0));
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("BOOT", 0));
+
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("ROOT", 0));
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("system", 0));
+
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("ROOT", 0));
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("system", 0));
+
+ // Slot B.
+ EXPECT_EQ(4, bootctl_.GetPartitionNumber("KERNEL", 1));
+ EXPECT_EQ(5, bootctl_.GetPartitionNumber("ROOT", 1));
+
+ // Slot C doesn't exists.
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("KERNEL", 2));
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("ROOT", 2));
+
+ // Non A/B partitions are ignored.
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("OEM", 0));
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("A little panda", 0));
+}
+
+TEST_F(BootControlChromeOSTest, ParseDlcPartitionNameTest) {
+ string id, package;
+
+ EXPECT_TRUE(bootctl_.ParseDlcPartitionName("dlc/id/package", &id, &package));
+ EXPECT_EQ(id, "id");
+ EXPECT_EQ(package, "package");
+
+ EXPECT_FALSE(
+ bootctl_.ParseDlcPartitionName("dlc-foo/id/package", &id, &package));
+ EXPECT_FALSE(
+ bootctl_.ParseDlcPartitionName("dlc-foo/id/package/", &id, &package));
+ EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc/id", &id, &package));
+ EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc/id/", &id, &package));
+ EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc//package", &id, &package));
+ EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc", &id, &package));
+ EXPECT_FALSE(bootctl_.ParseDlcPartitionName("foo", &id, &package));
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/chrome_browser_proxy_resolver.cc b/cros/chrome_browser_proxy_resolver.cc
new file mode 100644
index 0000000..3ea8a9b
--- /dev/null
+++ b/cros/chrome_browser_proxy_resolver.cc
@@ -0,0 +1,67 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/chrome_browser_proxy_resolver.h"
+
+#include <utility>
+
+#include <base/bind.h>
+#include <base/memory/ptr_util.h>
+#include <base/strings/string_util.h>
+#include <brillo/http/http_proxy.h>
+
+#include "update_engine/cros/dbus_connection.h"
+
+namespace chromeos_update_engine {
+
+ChromeBrowserProxyResolver::ChromeBrowserProxyResolver()
+ : next_request_id_(kProxyRequestIdNull + 1), weak_ptr_factory_(this) {}
+
+ChromeBrowserProxyResolver::~ChromeBrowserProxyResolver() = default;
+
+ProxyRequestId ChromeBrowserProxyResolver::GetProxiesForUrl(
+ const std::string& url, const ProxiesResolvedFn& callback) {
+ const ProxyRequestId id = next_request_id_++;
+ brillo::http::GetChromeProxyServersAsync(
+ DBusConnection::Get()->GetDBus(),
+ url,
+ base::Bind(&ChromeBrowserProxyResolver::OnGetChromeProxyServers,
+ weak_ptr_factory_.GetWeakPtr(),
+ id));
+ pending_callbacks_[id] = callback;
+ return id;
+}
+
+bool ChromeBrowserProxyResolver::CancelProxyRequest(ProxyRequestId request) {
+ return pending_callbacks_.erase(request) != 0;
+}
+
+void ChromeBrowserProxyResolver::OnGetChromeProxyServers(
+ ProxyRequestId request_id,
+ bool success,
+ const std::vector<std::string>& proxies) {
+ // If |success| is false, |proxies| will still hold the direct proxy option
+ // which is what we do in our error case.
+ auto it = pending_callbacks_.find(request_id);
+ if (it == pending_callbacks_.end())
+ return;
+
+ ProxiesResolvedFn callback = it->second;
+ pending_callbacks_.erase(it);
+ callback.Run(std::deque<std::string>(proxies.begin(), proxies.end()));
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/chrome_browser_proxy_resolver.h b/cros/chrome_browser_proxy_resolver.h
new file mode 100644
index 0000000..76848ef
--- /dev/null
+++ b/cros/chrome_browser_proxy_resolver.h
@@ -0,0 +1,66 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_CHROME_BROWSER_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_CROS_CHROME_BROWSER_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+
+#include "update_engine/common/proxy_resolver.h"
+
+namespace chromeos_update_engine {
+
+class ChromeBrowserProxyResolver : public ProxyResolver {
+ public:
+ ChromeBrowserProxyResolver();
+ ~ChromeBrowserProxyResolver() override;
+
+ // ProxyResolver:
+ ProxyRequestId GetProxiesForUrl(const std::string& url,
+ const ProxiesResolvedFn& callback) override;
+ bool CancelProxyRequest(ProxyRequestId request) override;
+
+ private:
+ // Callback for calls made by GetProxiesForUrl().
+ void OnGetChromeProxyServers(ProxyRequestId request_id,
+ bool success,
+ const std::vector<std::string>& proxies);
+
+ // Finds the callback identified by |request_id| in |pending_callbacks_|,
+ // passes |proxies| to it, and deletes it. Does nothing if the request has
+ // been cancelled.
+ void RunCallback(ProxyRequestId request_id,
+ const std::deque<std::string>& proxies);
+
+ // Next ID to return from GetProxiesForUrl().
+ ProxyRequestId next_request_id_;
+
+ // Callbacks that were passed to GetProxiesForUrl() but haven't yet been run.
+ std::map<ProxyRequestId, ProxiesResolvedFn> pending_callbacks_;
+
+ base::WeakPtrFactory<ChromeBrowserProxyResolver> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeBrowserProxyResolver);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_CHROME_BROWSER_PROXY_RESOLVER_H_
diff --git a/cros/common_service.cc b/cros/common_service.cc
new file mode 100644
index 0000000..aecad8b
--- /dev/null
+++ b/cros/common_service.cc
@@ -0,0 +1,421 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/common_service.h"
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/strings/string_utils.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/connection_manager_interface.h"
+#include "update_engine/cros/omaha_request_params.h"
+#include "update_engine/cros/omaha_utils.h"
+#include "update_engine/cros/p2p_manager.h"
+#include "update_engine/cros/payload_state_interface.h"
+#include "update_engine/cros/update_attempter.h"
+
+using base::StringPrintf;
+using brillo::ErrorPtr;
+using brillo::string_utils::ToString;
+using std::string;
+using std::vector;
+using update_engine::UpdateAttemptFlags;
+using update_engine::UpdateEngineStatus;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Log and set the error on the passed ErrorPtr.
+void LogAndSetError(ErrorPtr* error,
+ const base::Location& location,
+ const string& reason) {
+ brillo::Error::AddTo(error,
+ location,
+ UpdateEngineService::kErrorDomain,
+ UpdateEngineService::kErrorFailed,
+ reason);
+ LOG(ERROR) << "Sending Update Engine Failure: " << location.ToString() << ": "
+ << reason;
+}
+} // namespace
+
+const char* const UpdateEngineService::kErrorDomain = "update_engine";
+const char* const UpdateEngineService::kErrorFailed =
+ "org.chromium.UpdateEngine.Error.Failed";
+
+UpdateEngineService::UpdateEngineService(SystemState* system_state)
+ : system_state_(system_state) {}
+
+// org::chromium::UpdateEngineInterfaceInterface methods implementation.
+
+bool UpdateEngineService::SetUpdateAttemptFlags(ErrorPtr* /* error */,
+ int32_t in_flags_as_int) {
+ auto flags = static_cast<UpdateAttemptFlags>(in_flags_as_int);
+ LOG(INFO) << "Setting Update Attempt Flags: "
+ << "flags=0x" << std::hex << flags << " "
+ << "RestrictDownload="
+ << ((flags & UpdateAttemptFlags::kFlagRestrictDownload) ? "yes"
+ : "no");
+ system_state_->update_attempter()->SetUpdateAttemptFlags(flags);
+ return true;
+}
+
+bool UpdateEngineService::AttemptUpdate(ErrorPtr* /* error */,
+ const string& in_app_version,
+ const string& in_omaha_url,
+ int32_t in_flags_as_int,
+ bool* out_result) {
+ auto flags = static_cast<UpdateAttemptFlags>(in_flags_as_int);
+ bool interactive = !(flags & UpdateAttemptFlags::kFlagNonInteractive);
+ bool restrict_downloads = (flags & UpdateAttemptFlags::kFlagRestrictDownload);
+
+ LOG(INFO) << "Attempt update: app_version=\"" << in_app_version << "\" "
+ << "omaha_url=\"" << in_omaha_url << "\" "
+ << "flags=0x" << std::hex << flags << " "
+ << "interactive=" << (interactive ? "yes " : "no ")
+ << "RestrictDownload=" << (restrict_downloads ? "yes " : "no ");
+
+ *out_result = system_state_->update_attempter()->CheckForUpdate(
+ in_app_version, in_omaha_url, flags);
+ return true;
+}
+
+bool UpdateEngineService::AttemptInstall(brillo::ErrorPtr* error,
+ const string& omaha_url,
+ const vector<string>& dlc_ids) {
+ if (!system_state_->update_attempter()->CheckForInstall(dlc_ids, omaha_url)) {
+ // TODO(xiaochu): support more detailed error messages.
+ LogAndSetError(error, FROM_HERE, "Could not schedule install operation.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::AttemptRollback(ErrorPtr* error, bool in_powerwash) {
+ LOG(INFO) << "Attempting rollback to non-active partitions.";
+
+ if (!system_state_->update_attempter()->Rollback(in_powerwash)) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ LogAndSetError(error, FROM_HERE, "Rollback attempt failed.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::CanRollback(ErrorPtr* /* error */,
+ bool* out_can_rollback) {
+ bool can_rollback = system_state_->update_attempter()->CanRollback();
+ LOG(INFO) << "Checking to see if we can rollback . Result: " << can_rollback;
+ *out_can_rollback = can_rollback;
+ return true;
+}
+
+bool UpdateEngineService::ResetStatus(ErrorPtr* error) {
+ if (!system_state_->update_attempter()->ResetStatus()) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ LogAndSetError(error, FROM_HERE, "ResetStatus failed.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::SetDlcActiveValue(brillo::ErrorPtr* error,
+ bool is_active,
+ const string& dlc_id) {
+ if (!system_state_->update_attempter()->SetDlcActiveValue(is_active,
+ dlc_id)) {
+ LogAndSetError(error, FROM_HERE, "SetDlcActiveValue failed.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::GetStatus(ErrorPtr* error,
+ UpdateEngineStatus* out_status) {
+ if (!system_state_->update_attempter()->GetStatus(out_status)) {
+ LogAndSetError(error, FROM_HERE, "GetStatus failed.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::RebootIfNeeded(ErrorPtr* error) {
+ if (!system_state_->update_attempter()->RebootIfNeeded()) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ LogAndSetError(error, FROM_HERE, "Reboot not needed, or attempt failed.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::SetChannel(ErrorPtr* error,
+ const string& in_target_channel,
+ bool in_is_powerwash_allowed) {
+ const policy::DevicePolicy* device_policy = system_state_->device_policy();
+
+ // The device_policy is loaded in a lazy way before an update check. Load it
+ // now from the libbrillo cache if it wasn't already loaded.
+ if (!device_policy) {
+ UpdateAttempter* update_attempter = system_state_->update_attempter();
+ if (update_attempter) {
+ update_attempter->RefreshDevicePolicy();
+ device_policy = system_state_->device_policy();
+ }
+ }
+
+ bool delegated = false;
+ if (device_policy && device_policy->GetReleaseChannelDelegated(&delegated) &&
+ !delegated) {
+ LogAndSetError(error,
+ FROM_HERE,
+ "Cannot set target channel explicitly when channel "
+ "policy/settings is not delegated");
+ return false;
+ }
+
+ LOG(INFO) << "Setting destination channel to: " << in_target_channel;
+ string error_message;
+ if (!system_state_->request_params()->SetTargetChannel(
+ in_target_channel, in_is_powerwash_allowed, &error_message)) {
+ LogAndSetError(error, FROM_HERE, error_message);
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::GetChannel(ErrorPtr* /* error */,
+ bool in_get_current_channel,
+ string* out_channel) {
+ OmahaRequestParams* rp = system_state_->request_params();
+ *out_channel =
+ (in_get_current_channel ? rp->current_channel() : rp->target_channel());
+ return true;
+}
+
+bool UpdateEngineService::SetCohortHint(ErrorPtr* error,
+ const string& in_cohort_hint) {
+ PrefsInterface* prefs = system_state_->prefs();
+
+ // It is ok to override the cohort hint with an invalid value since it is
+ // stored in stateful partition. The code reading it should sanitize it
+ // anyway.
+ if (!prefs->SetString(kPrefsOmahaCohortHint, in_cohort_hint)) {
+ LogAndSetError(
+ error,
+ FROM_HERE,
+ StringPrintf("Error setting the cohort hint value to \"%s\".",
+ in_cohort_hint.c_str()));
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::GetCohortHint(ErrorPtr* error,
+ string* out_cohort_hint) {
+ PrefsInterface* prefs = system_state_->prefs();
+
+ *out_cohort_hint = "";
+ if (prefs->Exists(kPrefsOmahaCohortHint) &&
+ !prefs->GetString(kPrefsOmahaCohortHint, out_cohort_hint)) {
+ LogAndSetError(error, FROM_HERE, "Error getting the cohort hint.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::SetP2PUpdatePermission(ErrorPtr* error,
+ bool in_enabled) {
+ PrefsInterface* prefs = system_state_->prefs();
+
+ if (!prefs->SetBoolean(kPrefsP2PEnabled, in_enabled)) {
+ LogAndSetError(
+ error,
+ FROM_HERE,
+ StringPrintf("Error setting the update via p2p permission to %s.",
+ ToString(in_enabled).c_str()));
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::GetP2PUpdatePermission(ErrorPtr* error,
+ bool* out_enabled) {
+ PrefsInterface* prefs = system_state_->prefs();
+
+ bool p2p_pref = false; // Default if no setting is present.
+ if (prefs->Exists(kPrefsP2PEnabled) &&
+ !prefs->GetBoolean(kPrefsP2PEnabled, &p2p_pref)) {
+ LogAndSetError(error, FROM_HERE, "Error getting the P2PEnabled setting.");
+ return false;
+ }
+
+ *out_enabled = p2p_pref;
+ return true;
+}
+
+bool UpdateEngineService::SetUpdateOverCellularPermission(ErrorPtr* error,
+ bool in_allowed) {
+ ConnectionManagerInterface* connection_manager =
+ system_state_->connection_manager();
+
+ // Check if this setting is allowed by the device policy.
+ if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) {
+ LogAndSetError(error,
+ FROM_HERE,
+ "Ignoring the update over cellular setting since there's "
+ "a device policy enforcing this setting.");
+ return false;
+ }
+
+ // If the policy wasn't loaded yet, then it is still OK to change the local
+ // setting because the policy will be checked again during the update check.
+
+ PrefsInterface* prefs = system_state_->prefs();
+
+ if (!prefs ||
+ !prefs->SetBoolean(kPrefsUpdateOverCellularPermission, in_allowed)) {
+ LogAndSetError(error,
+ FROM_HERE,
+ string("Error setting the update over cellular to ") +
+ (in_allowed ? "true" : "false"));
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::SetUpdateOverCellularTarget(
+ brillo::ErrorPtr* error,
+ const std::string& target_version,
+ int64_t target_size) {
+ ConnectionManagerInterface* connection_manager =
+ system_state_->connection_manager();
+
+ // Check if this setting is allowed by the device policy.
+ if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) {
+ LogAndSetError(error,
+ FROM_HERE,
+ "Ignoring the update over cellular setting since there's "
+ "a device policy enforcing this setting.");
+ return false;
+ }
+
+ // If the policy wasn't loaded yet, then it is still OK to change the local
+ // setting because the policy will be checked again during the update check.
+
+ PrefsInterface* prefs = system_state_->prefs();
+
+ if (!prefs ||
+ !prefs->SetString(kPrefsUpdateOverCellularTargetVersion,
+ target_version) ||
+ !prefs->SetInt64(kPrefsUpdateOverCellularTargetSize, target_size)) {
+ LogAndSetError(
+ error, FROM_HERE, "Error setting the target for update over cellular.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::GetUpdateOverCellularPermission(ErrorPtr* error,
+ bool* out_allowed) {
+ ConnectionManagerInterface* connection_manager =
+ system_state_->connection_manager();
+
+ if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) {
+ // We have device policy, so ignore the user preferences.
+ *out_allowed = connection_manager->IsUpdateAllowedOver(
+ ConnectionType::kCellular, ConnectionTethering::kUnknown);
+ } else {
+ PrefsInterface* prefs = system_state_->prefs();
+
+ if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) {
+ // Update is not allowed as user preference is not set or not available.
+ *out_allowed = false;
+ return true;
+ }
+
+ bool is_allowed;
+
+ if (!prefs->GetBoolean(kPrefsUpdateOverCellularPermission, &is_allowed)) {
+ LogAndSetError(error,
+ FROM_HERE,
+ "Error getting the update over cellular preference.");
+ return false;
+ }
+ *out_allowed = is_allowed;
+ }
+ return true;
+}
+
+bool UpdateEngineService::GetDurationSinceUpdate(ErrorPtr* error,
+ int64_t* out_usec_wallclock) {
+ base::Time time;
+ if (!system_state_->update_attempter()->GetBootTimeAtUpdate(&time)) {
+ LogAndSetError(error, FROM_HERE, "No pending update.");
+ return false;
+ }
+
+ ClockInterface* clock = system_state_->clock();
+ *out_usec_wallclock = (clock->GetBootTime() - time).InMicroseconds();
+ return true;
+}
+
+bool UpdateEngineService::GetPrevVersion(ErrorPtr* /* error */,
+ string* out_prev_version) {
+ *out_prev_version = system_state_->update_attempter()->GetPrevVersion();
+ return true;
+}
+
+bool UpdateEngineService::GetRollbackPartition(
+ ErrorPtr* /* error */, string* out_rollback_partition_name) {
+ BootControlInterface::Slot rollback_slot =
+ system_state_->update_attempter()->GetRollbackSlot();
+
+ if (rollback_slot == BootControlInterface::kInvalidSlot) {
+ out_rollback_partition_name->clear();
+ return true;
+ }
+
+ string name;
+ if (!system_state_->boot_control()->GetPartitionDevice(
+ "KERNEL", rollback_slot, &name)) {
+ LOG(ERROR) << "Invalid rollback device";
+ return false;
+ }
+
+ LOG(INFO) << "Getting rollback partition name. Result: " << name;
+ *out_rollback_partition_name = name;
+ return true;
+}
+
+bool UpdateEngineService::GetLastAttemptError(ErrorPtr* /* error */,
+ int32_t* out_last_attempt_error) {
+ ErrorCode error_code =
+ system_state_->update_attempter()->GetAttemptErrorCode();
+ *out_last_attempt_error = static_cast<int>(error_code);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/common_service.h b/cros/common_service.h
new file mode 100644
index 0000000..6169d9c
--- /dev/null
+++ b/cros/common_service.h
@@ -0,0 +1,170 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_SERVICE_H_
+#define UPDATE_ENGINE_CROS_SERVICE_H_
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/memory/ref_counted.h>
+#include <brillo/errors/error.h>
+
+#include "update_engine/client_library/include/update_engine/update_status.h"
+#include "update_engine/common/system_state.h"
+
+namespace chromeos_update_engine {
+
+class UpdateEngineService {
+ public:
+ // Error domain for all the service errors.
+ static const char* const kErrorDomain;
+
+ // Generic service error.
+ static const char* const kErrorFailed;
+
+ explicit UpdateEngineService(SystemState* system_state);
+ virtual ~UpdateEngineService() = default;
+
+ // Set flags that influence how updates and checks are performed. These
+ // influence all future checks and updates until changed or the device
+ // reboots. The |in_flags_as_int| values are a union of values from
+ // |UpdateAttemptFlags|
+ bool SetUpdateAttemptFlags(brillo::ErrorPtr* error, int32_t in_flags_as_int);
+
+ bool AttemptUpdate(brillo::ErrorPtr* error,
+ const std::string& in_app_version,
+ const std::string& in_omaha_url,
+ int32_t in_flags_as_int,
+ bool* out_result);
+
+ // Attempts a DLC module install operation.
+ // |omaha_url|: the URL to query for update.
+ // |dlc_ids|: a list of DLC module IDs.
+ bool AttemptInstall(brillo::ErrorPtr* error,
+ const std::string& omaha_url,
+ const std::vector<std::string>& dlc_ids);
+
+ bool AttemptRollback(brillo::ErrorPtr* error, bool in_powerwash);
+
+ // Checks if the system rollback is available by verifying if the secondary
+ // system partition is valid and bootable.
+ bool CanRollback(brillo::ErrorPtr* error, bool* out_can_rollback);
+
+ // Resets the status of the update_engine to idle, ignoring any applied
+ // update. This is used for development only.
+ bool ResetStatus(brillo::ErrorPtr* error);
+
+ // Sets the DLC as active or inactive. When set to active, the ping metadata
+ // for the DLC is updated accordingly. When set to inactive, the metadata
+ // for the DLC is deleted.
+ bool SetDlcActiveValue(brillo::ErrorPtr* error,
+ bool is_active,
+ const std::string& dlc_id);
+
+ // Returns the current status of the Update Engine. If an update is in
+ // progress, the number of operations, size to download and overall progress
+ // is reported.
+ bool GetStatus(brillo::ErrorPtr* error,
+ update_engine::UpdateEngineStatus* out_status);
+
+ // Reboots the device if an update is applied and a reboot is required.
+ bool RebootIfNeeded(brillo::ErrorPtr* error);
+
+ // Changes the current channel of the device to the target channel. If the
+ // target channel is a less stable channel than the current channel, then the
+ // channel change happens immediately (at the next update check). If the
+ // target channel is a more stable channel, then if is_powerwash_allowed is
+ // set to true, then also the change happens immediately but with a powerwash
+ // if required. Otherwise, the change takes effect eventually (when the
+ // version on the target channel goes above the version number of what the
+ // device currently has).
+ bool SetChannel(brillo::ErrorPtr* error,
+ const std::string& in_target_channel,
+ bool in_is_powerwash_allowed);
+
+ // If get_current_channel is set to true, populates |channel| with the name of
+ // the channel that the device is currently on. Otherwise, it populates it
+ // with the name of the channel the device is supposed to be (in case of a
+ // pending channel change).
+ bool GetChannel(brillo::ErrorPtr* error,
+ bool in_get_current_channel,
+ std::string* out_channel);
+
+ // Sets the current "cohort hint" value to |in_cohort_hint|. The cohort hint
+ // is sent back to Omaha on every request and can be used as a hint of what
+ // cohort should we be put on.
+ bool SetCohortHint(brillo::ErrorPtr* error,
+ const std::string& in_cohort_hint);
+
+ // Return the current cohort hint. This value can be set with SetCohortHint()
+ // and can also be updated from Omaha on every update check request.
+ bool GetCohortHint(brillo::ErrorPtr* error, std::string* out_cohort_hint);
+
+ // Enables or disables the sharing and consuming updates over P2P feature
+ // according to the |enabled| argument passed.
+ bool SetP2PUpdatePermission(brillo::ErrorPtr* error, bool in_enabled);
+
+ // Returns the current value for the P2P enabled setting. This involves both
+ // sharing and consuming updates over P2P.
+ bool GetP2PUpdatePermission(brillo::ErrorPtr* error, bool* out_enabled);
+
+ // If there's no device policy installed, sets the update over cellular
+ // networks permission to the |allowed| value. Otherwise, this method returns
+ // with an error since this setting is overridden by the applied policy.
+ bool SetUpdateOverCellularPermission(brillo::ErrorPtr* error,
+ bool in_allowed);
+
+ // If there's no device policy installed, sets the update over cellular
+ // target. Otherwise, this method returns with an error.
+ bool SetUpdateOverCellularTarget(brillo::ErrorPtr* error,
+ const std::string& target_version,
+ int64_t target_size);
+
+ // Returns the current value of the update over cellular network setting,
+ // either forced by the device policy if the device is enrolled or the current
+ // user preference otherwise.
+ bool GetUpdateOverCellularPermission(brillo::ErrorPtr* error,
+ bool* out_allowed);
+
+ // Returns the duration since the last successful update, as the
+ // duration on the wallclock. Returns an error if the device has not
+ // updated.
+ bool GetDurationSinceUpdate(brillo::ErrorPtr* error,
+ int64_t* out_usec_wallclock);
+
+ // Returns the version string of OS that was used before the last reboot
+ // into an updated version. This is available only when rebooting into an
+ // update from previous version, otherwise an empty string is returned.
+ bool GetPrevVersion(brillo::ErrorPtr* error, std::string* out_prev_version);
+
+ // Returns the name of kernel partition that can be rolled back into.
+ bool GetRollbackPartition(brillo::ErrorPtr* error,
+ std::string* out_rollback_partition_name);
+
+ // Returns the last UpdateAttempt error.
+ bool GetLastAttemptError(brillo::ErrorPtr* error,
+ int32_t* out_last_attempt_error);
+
+ private:
+ SystemState* system_state_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_SERVICE_H_
diff --git a/cros/common_service_unittest.cc b/cros/common_service_unittest.cc
new file mode 100644
index 0000000..733ec0a
--- /dev/null
+++ b/cros/common_service_unittest.cc
@@ -0,0 +1,186 @@
+//
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/common_service.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include <vector>
+
+#include <brillo/errors/error.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/cros/fake_system_state.h"
+#include "update_engine/cros/omaha_utils.h"
+
+using std::string;
+using std::vector;
+using testing::_;
+using testing::Return;
+using testing::SetArgPointee;
+using update_engine::UpdateAttemptFlags;
+
+namespace chromeos_update_engine {
+
+class UpdateEngineServiceTest : public ::testing::Test {
+ protected:
+ UpdateEngineServiceTest()
+ : mock_update_attempter_(fake_system_state_.mock_update_attempter()),
+ common_service_(&fake_system_state_) {}
+
+ void SetUp() override { fake_system_state_.set_device_policy(nullptr); }
+
+ // Fake/mock infrastructure.
+ FakeSystemState fake_system_state_;
+ policy::MockDevicePolicy mock_device_policy_;
+
+ // Shortcut for fake_system_state_.mock_update_attempter().
+ MockUpdateAttempter* mock_update_attempter_;
+
+ brillo::ErrorPtr error_;
+ UpdateEngineService common_service_;
+};
+
+TEST_F(UpdateEngineServiceTest, AttemptUpdate) {
+ EXPECT_CALL(
+ *mock_update_attempter_,
+ CheckForUpdate("app_ver", "url", UpdateAttemptFlags::kFlagNonInteractive))
+ .WillOnce(Return(true));
+
+ // The non-interactive flag needs to be passed through to CheckForUpdate.
+ bool result = false;
+ EXPECT_TRUE(
+ common_service_.AttemptUpdate(&error_,
+ "app_ver",
+ "url",
+ UpdateAttemptFlags::kFlagNonInteractive,
+ &result));
+ EXPECT_EQ(nullptr, error_);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UpdateEngineServiceTest, AttemptUpdateReturnsFalse) {
+ EXPECT_CALL(*mock_update_attempter_,
+ CheckForUpdate("app_ver", "url", UpdateAttemptFlags::kNone))
+ .WillOnce(Return(false));
+ bool result = true;
+ EXPECT_TRUE(common_service_.AttemptUpdate(
+ &error_, "app_ver", "url", UpdateAttemptFlags::kNone, &result));
+ EXPECT_EQ(nullptr, error_);
+ EXPECT_FALSE(result);
+}
+
+TEST_F(UpdateEngineServiceTest, AttemptInstall) {
+ EXPECT_CALL(*mock_update_attempter_, CheckForInstall(_, _))
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(common_service_.AttemptInstall(&error_, "", {}));
+ EXPECT_EQ(nullptr, error_);
+}
+
+TEST_F(UpdateEngineServiceTest, AttemptInstallReturnsFalse) {
+ EXPECT_CALL(*mock_update_attempter_, CheckForInstall(_, _))
+ .WillOnce(Return(false));
+
+ EXPECT_FALSE(common_service_.AttemptInstall(&error_, "", {}));
+}
+
+TEST_F(UpdateEngineServiceTest, SetDlcActiveValue) {
+ EXPECT_CALL(*mock_update_attempter_, SetDlcActiveValue(_, _))
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(common_service_.SetDlcActiveValue(&error_, true, "dlc0"));
+}
+
+TEST_F(UpdateEngineServiceTest, SetDlcActiveValueReturnsFalse) {
+ EXPECT_CALL(*mock_update_attempter_, SetDlcActiveValue(_, _))
+ .WillOnce(Return(false));
+
+ EXPECT_FALSE(common_service_.SetDlcActiveValue(&error_, true, "dlc0"));
+}
+
+// SetChannel is allowed when there's no device policy (the device is not
+// enterprise enrolled).
+TEST_F(UpdateEngineServiceTest, SetChannelWithNoPolicy) {
+ EXPECT_CALL(*mock_update_attempter_, RefreshDevicePolicy());
+ // If SetTargetChannel is called it means the policy check passed.
+ EXPECT_CALL(*fake_system_state_.mock_request_params(),
+ SetTargetChannel("stable-channel", true, _))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(common_service_.SetChannel(&error_, "stable-channel", true));
+ ASSERT_EQ(nullptr, error_);
+}
+
+// When the policy is present, the delegated value should be checked.
+TEST_F(UpdateEngineServiceTest, SetChannelWithDelegatedPolicy) {
+ policy::MockDevicePolicy mock_device_policy;
+ fake_system_state_.set_device_policy(&mock_device_policy);
+ EXPECT_CALL(mock_device_policy, GetReleaseChannelDelegated(_))
+ .WillOnce(DoAll(SetArgPointee<0>(true), Return(true)));
+ EXPECT_CALL(*fake_system_state_.mock_request_params(),
+ SetTargetChannel("beta-channel", true, _))
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(common_service_.SetChannel(&error_, "beta-channel", true));
+ ASSERT_EQ(nullptr, error_);
+}
+
+// When passing an invalid value (SetTargetChannel fails) an error should be
+// raised.
+TEST_F(UpdateEngineServiceTest, SetChannelWithInvalidChannel) {
+ EXPECT_CALL(*mock_update_attempter_, RefreshDevicePolicy());
+ EXPECT_CALL(*fake_system_state_.mock_request_params(),
+ SetTargetChannel("foo-channel", true, _))
+ .WillOnce(Return(false));
+
+ EXPECT_FALSE(common_service_.SetChannel(&error_, "foo-channel", true));
+ ASSERT_NE(nullptr, error_);
+ EXPECT_TRUE(error_->HasError(UpdateEngineService::kErrorDomain,
+ UpdateEngineService::kErrorFailed));
+}
+
+TEST_F(UpdateEngineServiceTest, GetChannel) {
+ fake_system_state_.mock_request_params()->set_current_channel("current");
+ fake_system_state_.mock_request_params()->set_target_channel("target");
+ string channel;
+ EXPECT_TRUE(common_service_.GetChannel(
+ &error_, true /* get_current_channel */, &channel));
+ EXPECT_EQ(nullptr, error_);
+ EXPECT_EQ("current", channel);
+
+ EXPECT_TRUE(common_service_.GetChannel(
+ &error_, false /* get_current_channel */, &channel));
+ EXPECT_EQ(nullptr, error_);
+ EXPECT_EQ("target", channel);
+}
+
+TEST_F(UpdateEngineServiceTest, ResetStatusSucceeds) {
+ EXPECT_CALL(*mock_update_attempter_, ResetStatus()).WillOnce(Return(true));
+ EXPECT_TRUE(common_service_.ResetStatus(&error_));
+ EXPECT_EQ(nullptr, error_);
+}
+
+TEST_F(UpdateEngineServiceTest, ResetStatusFails) {
+ EXPECT_CALL(*mock_update_attempter_, ResetStatus()).WillOnce(Return(false));
+ EXPECT_FALSE(common_service_.ResetStatus(&error_));
+ ASSERT_NE(nullptr, error_);
+ EXPECT_TRUE(error_->HasError(UpdateEngineService::kErrorDomain,
+ UpdateEngineService::kErrorFailed));
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/connection_manager.cc b/cros/connection_manager.cc
new file mode 100644
index 0000000..331f76b
--- /dev/null
+++ b/cros/connection_manager.cc
@@ -0,0 +1,211 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/connection_manager.h"
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include <base/stl_util.h>
+#include <base/strings/string_util.h>
+#include <policy/device_policy.h>
+#include <shill/dbus-constants.h>
+#include <shill/dbus-proxies.h>
+
+#include "update_engine/common/connection_utils.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/shill_proxy.h"
+#include "update_engine/cros/update_attempter.h"
+
+using org::chromium::flimflam::ManagerProxyInterface;
+using org::chromium::flimflam::ServiceProxyInterface;
+using std::set;
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace connection_manager {
+std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager(
+ SystemState* system_state) {
+ return std::unique_ptr<ConnectionManagerInterface>(
+ new ConnectionManager(new ShillProxy(), system_state));
+}
+} // namespace connection_manager
+
+ConnectionManager::ConnectionManager(ShillProxyInterface* shill_proxy,
+ SystemState* system_state)
+ : shill_proxy_(shill_proxy), system_state_(system_state) {}
+
+bool ConnectionManager::IsUpdateAllowedOver(
+ ConnectionType type, ConnectionTethering tethering) const {
+ if (type != ConnectionType::kCellular) {
+ if (tethering != ConnectionTethering::kConfirmed) {
+ return true;
+ }
+
+ // Treat this connection as if it is a cellular connection.
+ LOG(INFO)
+ << "Current connection is confirmed tethered, using Cellular setting.";
+ }
+
+ const policy::DevicePolicy* device_policy = system_state_->device_policy();
+
+ // The device_policy is loaded in a lazy way before an update check. Load
+ // it now from the libbrillo cache if it wasn't already loaded.
+ if (!device_policy) {
+ UpdateAttempter* update_attempter = system_state_->update_attempter();
+ if (update_attempter) {
+ update_attempter->RefreshDevicePolicy();
+ device_policy = system_state_->device_policy();
+ }
+ }
+
+ if (!device_policy) {
+ // Device policy fails to be loaded (possibly due to guest account). We
+ // do not check the local user setting here, which should be checked by
+ // |OmahaRequestAction| during checking for update.
+ LOG(INFO) << "Allowing updates over cellular as device policy fails to be "
+ "loaded.";
+ return true;
+ }
+
+ set<string> allowed_types;
+ if (device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+ // The update setting is enforced by the device policy.
+
+ // TODO(crbug.com/1054279): Use base::Contains after uprev to r680000.
+ if (allowed_types.find(shill::kTypeCellular) == allowed_types.end()) {
+ LOG(INFO) << "Disabling updates over cellular connection as it's not "
+ "allowed in the device policy.";
+ return false;
+ }
+
+ LOG(INFO) << "Allowing updates over cellular per device policy.";
+ return true;
+ }
+
+ // If there's no update setting in the device policy, we do not check
+ // the local user setting here, which should be checked by
+ // |OmahaRequestAction| during checking for update.
+ LOG(INFO) << "Allowing updates over cellular as device policy does "
+ "not include update setting.";
+ return true;
+}
+
+bool ConnectionManager::IsAllowedConnectionTypesForUpdateSet() const {
+ const policy::DevicePolicy* device_policy = system_state_->device_policy();
+ if (!device_policy) {
+ LOG(INFO) << "There's no device policy loaded yet.";
+ return false;
+ }
+
+ set<string> allowed_types;
+ if (!device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool ConnectionManager::GetConnectionProperties(
+ ConnectionType* out_type, ConnectionTethering* out_tethering) {
+ dbus::ObjectPath default_service_path;
+ TEST_AND_RETURN_FALSE(GetDefaultServicePath(&default_service_path));
+ if (!default_service_path.IsValid())
+ return false;
+ // Shill uses the "/" service path to indicate that it is not connected.
+ if (default_service_path.value() == "/") {
+ *out_type = ConnectionType::kDisconnected;
+ *out_tethering = ConnectionTethering::kUnknown;
+ return true;
+ }
+ TEST_AND_RETURN_FALSE(
+ GetServicePathProperties(default_service_path, out_type, out_tethering));
+ return true;
+}
+
+bool ConnectionManager::GetDefaultServicePath(dbus::ObjectPath* out_path) {
+ brillo::VariantDictionary properties;
+ brillo::ErrorPtr error;
+ ManagerProxyInterface* manager_proxy = shill_proxy_->GetManagerProxy();
+ if (!manager_proxy)
+ return false;
+ TEST_AND_RETURN_FALSE(manager_proxy->GetProperties(&properties, &error));
+
+ const auto& prop_default_service =
+ properties.find(shill::kDefaultServiceProperty);
+ if (prop_default_service == properties.end())
+ return false;
+
+ *out_path = prop_default_service->second.TryGet<dbus::ObjectPath>();
+ return out_path->IsValid();
+}
+
+bool ConnectionManager::GetServicePathProperties(
+ const dbus::ObjectPath& path,
+ ConnectionType* out_type,
+ ConnectionTethering* out_tethering) {
+ // We create and dispose the ServiceProxyInterface on every request.
+ std::unique_ptr<ServiceProxyInterface> service =
+ shill_proxy_->GetServiceForPath(path);
+
+ brillo::VariantDictionary properties;
+ brillo::ErrorPtr error;
+ TEST_AND_RETURN_FALSE(service->GetProperties(&properties, &error));
+
+ // Populate the out_tethering.
+ const auto& prop_tethering = properties.find(shill::kTetheringProperty);
+ if (prop_tethering == properties.end()) {
+ // Set to Unknown if not present.
+ *out_tethering = ConnectionTethering::kUnknown;
+ } else {
+ // If the property doesn't contain a string value, the empty string will
+ // become kUnknown.
+ *out_tethering = connection_utils::ParseConnectionTethering(
+ prop_tethering->second.TryGet<string>());
+ }
+
+ // Populate the out_type property.
+ const auto& prop_type = properties.find(shill::kTypeProperty);
+ if (prop_type == properties.end()) {
+ // Set to Unknown if not present.
+ *out_type = ConnectionType::kUnknown;
+ return false;
+ }
+
+ string type_str = prop_type->second.TryGet<string>();
+ if (type_str == shill::kTypeVPN) {
+ const auto& prop_physical =
+ properties.find(shill::kPhysicalTechnologyProperty);
+ if (prop_physical == properties.end()) {
+ LOG(ERROR) << "No PhysicalTechnology property found for a VPN"
+ " connection (service: "
+ << path.value() << "). Returning default kUnknown value.";
+ *out_type = ConnectionType::kUnknown;
+ } else {
+ *out_type = connection_utils::ParseConnectionType(
+ prop_physical->second.TryGet<string>());
+ }
+ } else {
+ *out_type = connection_utils::ParseConnectionType(type_str);
+ }
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/connection_manager.h b/cros/connection_manager.h
new file mode 100644
index 0000000..b1fb961
--- /dev/null
+++ b/cros/connection_manager.h
@@ -0,0 +1,69 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_CROS_CONNECTION_MANAGER_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <dbus/object_path.h>
+
+#include "update_engine/cros/connection_manager_interface.h"
+#include "update_engine/cros/shill_proxy_interface.h"
+
+namespace chromeos_update_engine {
+
+// This class implements the concrete class that talks with the connection
+// manager (shill) over DBus.
+// TODO(deymo): Remove this class and use ShillProvider from the UpdateManager.
+class ConnectionManager : public ConnectionManagerInterface {
+ public:
+ // Constructs a new ConnectionManager object initialized with the
+ // given system state.
+ ConnectionManager(ShillProxyInterface* shill_proxy,
+ SystemState* system_state);
+ ~ConnectionManager() override = default;
+
+ // ConnectionManagerInterface overrides.
+ bool GetConnectionProperties(ConnectionType* out_type,
+ ConnectionTethering* out_tethering) override;
+ bool IsUpdateAllowedOver(ConnectionType type,
+ ConnectionTethering tethering) const override;
+ bool IsAllowedConnectionTypesForUpdateSet() const override;
+
+ private:
+ // Returns (via out_path) the default network path, or "/" if there's no
+ // network up. Returns true on success.
+ bool GetDefaultServicePath(dbus::ObjectPath* out_path);
+
+ bool GetServicePathProperties(const dbus::ObjectPath& path,
+ ConnectionType* out_type,
+ ConnectionTethering* out_tethering);
+
+ // The mockable interface to access the shill DBus proxies.
+ std::unique_ptr<ShillProxyInterface> shill_proxy_;
+
+ // The global context for update_engine.
+ SystemState* system_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectionManager);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_CONNECTION_MANAGER_H_
diff --git a/cros/connection_manager_interface.h b/cros/connection_manager_interface.h
new file mode 100644
index 0000000..6dd9fbd
--- /dev/null
+++ b/cros/connection_manager_interface.h
@@ -0,0 +1,68 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_CONNECTION_MANAGER_INTERFACE_H_
+#define UPDATE_ENGINE_CROS_CONNECTION_MANAGER_INTERFACE_H_
+
+#include <memory>
+
+#include <base/macros.h>
+
+#include "update_engine/common/connection_utils.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// This class exposes a generic interface to the connection manager
+// (e.g FlimFlam, Shill, etc.) to consolidate all connection-related
+// logic in update_engine.
+class ConnectionManagerInterface {
+ public:
+ virtual ~ConnectionManagerInterface() = default;
+
+ // Populates |out_type| with the type of the network connection
+ // that we are currently connected and |out_tethering| with the estimate of
+ // whether that network is being tethered.
+ virtual bool GetConnectionProperties(ConnectionType* out_type,
+ ConnectionTethering* out_tethering) = 0;
+
+ // Returns true if we're allowed to update the system when we're
+ // connected to the internet through the given network connection type and the
+ // given tethering state.
+ virtual bool IsUpdateAllowedOver(ConnectionType type,
+ ConnectionTethering tethering) const = 0;
+
+ // Returns true if the allowed connection types for update is set in the
+ // device policy. Otherwise, returns false.
+ virtual bool IsAllowedConnectionTypesForUpdateSet() const = 0;
+
+ protected:
+ ConnectionManagerInterface() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConnectionManagerInterface);
+};
+
+namespace connection_manager {
+// Factory function which creates a ConnectionManager.
+std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager(
+ SystemState* system_state);
+} // namespace connection_manager
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_CONNECTION_MANAGER_INTERFACE_H_
diff --git a/cros/connection_manager_unittest.cc b/cros/connection_manager_unittest.cc
new file mode 100644
index 0000000..3f1ee5a
--- /dev/null
+++ b/cros/connection_manager_unittest.cc
@@ -0,0 +1,360 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/connection_manager.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/logging.h>
+#include <brillo/any.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <shill/dbus-constants.h>
+#include <shill/dbus-proxies.h>
+#include <shill/dbus-proxy-mocks.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/cros/fake_shill_proxy.h"
+#include "update_engine/cros/fake_system_state.h"
+
+using chromeos_update_engine::connection_utils::StringForConnectionType;
+using org::chromium::flimflam::ManagerProxyMock;
+using org::chromium::flimflam::ServiceProxyMock;
+using std::set;
+using std::string;
+using testing::_;
+using testing::Return;
+using testing::SetArgPointee;
+
+namespace chromeos_update_engine {
+
+class ConnectionManagerTest : public ::testing::Test {
+ public:
+ ConnectionManagerTest() : fake_shill_proxy_(new FakeShillProxy()) {}
+
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ fake_system_state_.set_connection_manager(&cmut_);
+ }
+
+ void TearDown() override { EXPECT_FALSE(loop_.PendingTasks()); }
+
+ protected:
+ // Sets the default_service object path in the response from the
+ // ManagerProxyMock instance.
+ void SetManagerReply(const char* default_service, bool reply_succeeds);
+
+ // Sets the |service_type|, |physical_technology| and |service_tethering|
+ // properties in the mocked service |service_path|. If any of the three
+ // const char* is a nullptr, the corresponding property will not be included
+ // in the response.
+ void SetServiceReply(const string& service_path,
+ const char* service_type,
+ const char* physical_technology,
+ const char* service_tethering);
+
+ void TestWithServiceType(const char* service_type,
+ const char* physical_technology,
+ ConnectionType expected_type);
+
+ void TestWithServiceDisconnected(ConnectionType expected_type);
+
+ void TestWithServiceTethering(const char* service_tethering,
+ ConnectionTethering expected_tethering);
+
+ brillo::FakeMessageLoop loop_{nullptr};
+ FakeSystemState fake_system_state_;
+ FakeShillProxy* fake_shill_proxy_;
+
+ // ConnectionManager under test.
+ ConnectionManager cmut_{fake_shill_proxy_, &fake_system_state_};
+};
+
+void ConnectionManagerTest::SetManagerReply(const char* default_service,
+ bool reply_succeeds) {
+ ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_->GetManagerProxy();
+ if (!reply_succeeds) {
+ EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _))
+ .WillOnce(Return(false));
+ return;
+ }
+
+ // Create a dictionary of properties and optionally include the default
+ // service.
+ brillo::VariantDictionary reply_dict;
+ reply_dict["SomeOtherProperty"] = 0xC0FFEE;
+
+ if (default_service) {
+ reply_dict[shill::kDefaultServiceProperty] =
+ dbus::ObjectPath(default_service);
+ }
+ EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true)));
+}
+
+void ConnectionManagerTest::SetServiceReply(const string& service_path,
+ const char* service_type,
+ const char* physical_technology,
+ const char* service_tethering) {
+ brillo::VariantDictionary reply_dict;
+ reply_dict["SomeOtherProperty"] = 0xC0FFEE;
+
+ if (service_type)
+ reply_dict[shill::kTypeProperty] = string(service_type);
+
+ if (physical_technology) {
+ reply_dict[shill::kPhysicalTechnologyProperty] =
+ string(physical_technology);
+ }
+
+ if (service_tethering)
+ reply_dict[shill::kTetheringProperty] = string(service_tethering);
+
+ std::unique_ptr<ServiceProxyMock> service_proxy_mock(new ServiceProxyMock());
+
+ // Plumb return value into mock object.
+ EXPECT_CALL(*service_proxy_mock.get(), GetProperties(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true)));
+
+ fake_shill_proxy_->SetServiceForPath(dbus::ObjectPath(service_path),
+ std::move(service_proxy_mock));
+}
+
+void ConnectionManagerTest::TestWithServiceType(const char* service_type,
+ const char* physical_technology,
+ ConnectionType expected_type) {
+ SetManagerReply("/service/guest/network", true);
+ SetServiceReply("/service/guest/network",
+ service_type,
+ physical_technology,
+ shill::kTetheringNotDetectedState);
+
+ ConnectionType type;
+ ConnectionTethering tethering;
+ EXPECT_TRUE(cmut_.GetConnectionProperties(&type, &tethering));
+ EXPECT_EQ(expected_type, type);
+ testing::Mock::VerifyAndClearExpectations(
+ fake_shill_proxy_->GetManagerProxy());
+}
+
+void ConnectionManagerTest::TestWithServiceTethering(
+ const char* service_tethering, ConnectionTethering expected_tethering) {
+ SetManagerReply("/service/guest/network", true);
+ SetServiceReply(
+ "/service/guest/network", shill::kTypeWifi, nullptr, service_tethering);
+
+ ConnectionType type;
+ ConnectionTethering tethering;
+ EXPECT_TRUE(cmut_.GetConnectionProperties(&type, &tethering));
+ EXPECT_EQ(expected_tethering, tethering);
+ testing::Mock::VerifyAndClearExpectations(
+ fake_shill_proxy_->GetManagerProxy());
+}
+
+void ConnectionManagerTest::TestWithServiceDisconnected(
+ ConnectionType expected_type) {
+ SetManagerReply("/", true);
+
+ ConnectionType type;
+ ConnectionTethering tethering;
+ EXPECT_TRUE(cmut_.GetConnectionProperties(&type, &tethering));
+ EXPECT_EQ(expected_type, type);
+ testing::Mock::VerifyAndClearExpectations(
+ fake_shill_proxy_->GetManagerProxy());
+}
+
+TEST_F(ConnectionManagerTest, SimpleTest) {
+ TestWithServiceType(shill::kTypeEthernet, nullptr, ConnectionType::kEthernet);
+ TestWithServiceType(shill::kTypeWifi, nullptr, ConnectionType::kWifi);
+ TestWithServiceType(shill::kTypeCellular, nullptr, ConnectionType::kCellular);
+}
+
+TEST_F(ConnectionManagerTest, PhysicalTechnologyTest) {
+ TestWithServiceType(shill::kTypeVPN, nullptr, ConnectionType::kUnknown);
+ TestWithServiceType(
+ shill::kTypeVPN, shill::kTypeVPN, ConnectionType::kUnknown);
+ TestWithServiceType(shill::kTypeVPN, shill::kTypeWifi, ConnectionType::kWifi);
+}
+
+TEST_F(ConnectionManagerTest, TetheringTest) {
+ TestWithServiceTethering(shill::kTetheringConfirmedState,
+ ConnectionTethering::kConfirmed);
+ TestWithServiceTethering(shill::kTetheringNotDetectedState,
+ ConnectionTethering::kNotDetected);
+ TestWithServiceTethering(shill::kTetheringSuspectedState,
+ ConnectionTethering::kSuspected);
+ TestWithServiceTethering("I'm not a valid property value =)",
+ ConnectionTethering::kUnknown);
+}
+
+TEST_F(ConnectionManagerTest, UnknownTest) {
+ TestWithServiceType("foo", nullptr, ConnectionType::kUnknown);
+}
+
+TEST_F(ConnectionManagerTest, DisconnectTest) {
+ TestWithServiceDisconnected(ConnectionType::kDisconnected);
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverEthernetTest) {
+ // Updates over Ethernet are allowed even if there's no policy.
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
+ ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverWifiTest) {
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
+ ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOnlyOver3GPerPolicyTest) {
+ policy::MockDevicePolicy allow_3g_policy;
+
+ fake_system_state_.set_device_policy(&allow_3g_policy);
+
+ // This test tests cellular (3G) being the only connection type being allowed.
+ set<string> allowed_set;
+ allowed_set.insert(StringForConnectionType(ConnectionType::kCellular));
+
+ EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(1)
+ .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(true)));
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+ ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOver3GAndOtherTypesPerPolicyTest) {
+ policy::MockDevicePolicy allow_3g_policy;
+
+ fake_system_state_.set_device_policy(&allow_3g_policy);
+
+ // This test tests multiple connection types being allowed, with
+ // 3G one among them. Only Cellular is currently enforced by the policy
+ // setting.
+ set<string> allowed_set;
+ allowed_set.insert(StringForConnectionType(ConnectionType::kCellular));
+
+ EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(3)
+ .WillRepeatedly(DoAll(SetArgPointee<0>(allowed_set), Return(true)));
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
+ ConnectionTethering::kUnknown));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
+ ConnectionTethering::kNotDetected));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+ ConnectionTethering::kUnknown));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
+ ConnectionTethering::kUnknown));
+
+ // Tethered networks are treated in the same way as Cellular networks and
+ // thus allowed.
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
+ ConnectionTethering::kConfirmed));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
+ ConnectionTethering::kConfirmed));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverCellularByDefaultTest) {
+ policy::MockDevicePolicy device_policy;
+ // Set an empty device policy.
+ fake_system_state_.set_device_policy(&device_policy);
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+ ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverTetheredNetworkByDefaultTest) {
+ policy::MockDevicePolicy device_policy;
+ // Set an empty device policy.
+ fake_system_state_.set_device_policy(&device_policy);
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
+ ConnectionTethering::kConfirmed));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
+ ConnectionTethering::kConfirmed));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
+ ConnectionTethering::kSuspected));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOver3GPerPolicyTest) {
+ policy::MockDevicePolicy block_3g_policy;
+
+ fake_system_state_.set_device_policy(&block_3g_policy);
+
+ // Test that updates for 3G are blocked while updates are allowed
+ // over several other types.
+ set<string> allowed_set;
+ allowed_set.insert(StringForConnectionType(ConnectionType::kEthernet));
+ allowed_set.insert(StringForConnectionType(ConnectionType::kWifi));
+
+ EXPECT_CALL(block_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(1)
+ .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(true)));
+
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+ ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOver3GIfPolicyIsNotSet) {
+ policy::MockDevicePolicy device_policy;
+
+ fake_system_state_.set_device_policy(&device_policy);
+
+ // Return false for GetAllowedConnectionTypesForUpdate and see
+ // that updates are allowed as device policy is not set. Further
+ // check is left to |OmahaRequestAction|.
+ EXPECT_CALL(device_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(1)
+ .WillOnce(Return(false));
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+ ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverCellularIfPolicyFailsToBeLoaded) {
+ fake_system_state_.set_device_policy(nullptr);
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+ ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, StringForConnectionTypeTest) {
+ EXPECT_STREQ(shill::kTypeEthernet,
+ StringForConnectionType(ConnectionType::kEthernet));
+ EXPECT_STREQ(shill::kTypeWifi,
+ StringForConnectionType(ConnectionType::kWifi));
+ EXPECT_STREQ(shill::kTypeCellular,
+ StringForConnectionType(ConnectionType::kCellular));
+ EXPECT_STREQ("Unknown", StringForConnectionType(ConnectionType::kUnknown));
+ EXPECT_STREQ("Unknown",
+ StringForConnectionType(static_cast<ConnectionType>(999999)));
+}
+
+TEST_F(ConnectionManagerTest, MalformedServiceList) {
+ SetManagerReply("/service/guest/network", false);
+
+ ConnectionType type;
+ ConnectionTethering tethering;
+ EXPECT_FALSE(cmut_.GetConnectionProperties(&type, &tethering));
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/daemon_chromeos.cc b/cros/daemon_chromeos.cc
new file mode 100644
index 0000000..a7cad8c
--- /dev/null
+++ b/cros/daemon_chromeos.cc
@@ -0,0 +1,82 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/daemon_chromeos.h"
+
+#include <sysexits.h>
+
+#include <base/bind.h>
+#include <base/location.h>
+
+#include "update_engine/cros/real_system_state.h"
+
+using brillo::Daemon;
+using std::unique_ptr;
+
+namespace chromeos_update_engine {
+
+unique_ptr<DaemonBase> DaemonBase::CreateInstance() {
+ return std::make_unique<DaemonChromeOS>();
+}
+
+int DaemonChromeOS::OnInit() {
+ // Register the |subprocess_| singleton with this Daemon as the signal
+ // handler.
+ subprocess_.Init(this);
+
+ int exit_code = Daemon::OnInit();
+ if (exit_code != EX_OK)
+ return exit_code;
+
+ // Initialize update engine global state but continue if something fails.
+ // TODO(deymo): Move the daemon_state_ initialization to a factory method
+ // avoiding the explicit re-usage of the |bus| instance, shared between
+ // D-Bus service and D-Bus client calls.
+ RealSystemState* real_system_state = new RealSystemState();
+ daemon_state_.reset(real_system_state);
+ LOG_IF(ERROR, !real_system_state->Initialize())
+ << "Failed to initialize system state.";
+
+ // Create the DBus service.
+ dbus_adaptor_.reset(new UpdateEngineAdaptor(real_system_state));
+ daemon_state_->AddObserver(dbus_adaptor_.get());
+
+ dbus_adaptor_->RegisterAsync(
+ base::Bind(&DaemonChromeOS::OnDBusRegistered, base::Unretained(this)));
+ LOG(INFO) << "Waiting for DBus object to be registered.";
+ return EX_OK;
+}
+
+void DaemonChromeOS::OnDBusRegistered(bool succeeded) {
+ if (!succeeded) {
+ LOG(ERROR) << "Registering the UpdateEngineAdaptor";
+ QuitWithExitCode(1);
+ return;
+ }
+
+ // Take ownership of the service now that everything is initialized. We need
+ // to this now and not before to avoid exposing a well known DBus service
+ // path that doesn't have the service it is supposed to implement.
+ if (!dbus_adaptor_->RequestOwnership()) {
+ LOG(ERROR) << "Unable to take ownership of the DBus service, is there "
+ << "other update_engine daemon running?";
+ QuitWithExitCode(1);
+ return;
+ }
+ daemon_state_->StartUpdater();
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/daemon_chromeos.h b/cros/daemon_chromeos.h
new file mode 100644
index 0000000..5d568c7
--- /dev/null
+++ b/cros/daemon_chromeos.h
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_DAEMON_CHROMEOS_H_
+#define UPDATE_ENGINE_CROS_DAEMON_CHROMEOS_H_
+
+#include <memory>
+
+#include "update_engine/common/daemon_base.h"
+#include "update_engine/common/daemon_state_interface.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/cros/dbus_service.h"
+
+namespace chromeos_update_engine {
+
+class DaemonChromeOS : public DaemonBase {
+ public:
+ DaemonChromeOS() = default;
+
+ protected:
+ int OnInit() override;
+
+ private:
+ // Run from the main loop when the |dbus_adaptor_| object is registered. At
+ // this point we can request ownership of the DBus service name and continue
+ // initialization.
+ void OnDBusRegistered(bool succeeded);
+
+ // Main D-Bus service adaptor.
+ std::unique_ptr<UpdateEngineAdaptor> dbus_adaptor_;
+
+ // The Subprocess singleton class requires a brillo::MessageLoop in the
+ // current thread, so we need to initialize it from this class instead of
+ // the main() function.
+ Subprocess subprocess_;
+
+ // The daemon state with all the required daemon classes for the configured
+ // platform.
+ std::unique_ptr<DaemonStateInterface> daemon_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(DaemonChromeOS);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_DAEMON_CHROMEOS_H_
diff --git a/cros/dbus_connection.cc b/cros/dbus_connection.cc
new file mode 100644
index 0000000..6808bae
--- /dev/null
+++ b/cros/dbus_connection.cc
@@ -0,0 +1,55 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/dbus_connection.h"
+
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+namespace {
+const int kDBusSystemMaxWaitSeconds = 2 * 60;
+
+DBusConnection* dbus_connection_singleton = nullptr;
+} // namespace
+
+DBusConnection::DBusConnection() {
+ // We wait for the D-Bus connection for up two minutes to avoid re-spawning
+ // the daemon too fast causing thrashing if dbus-daemon is not running.
+ bus_ = dbus_connection_.ConnectWithTimeout(
+ base::TimeDelta::FromSeconds(kDBusSystemMaxWaitSeconds));
+
+ if (!bus_) {
+ // TODO(deymo): Make it possible to run update_engine even if dbus-daemon
+ // is not running or constantly crashing.
+ LOG(FATAL) << "Failed to initialize DBus, aborting.";
+ }
+
+ CHECK(bus_->SetUpAsyncOperations());
+}
+
+const scoped_refptr<dbus::Bus>& DBusConnection::GetDBus() {
+ CHECK(bus_);
+ return bus_;
+}
+
+DBusConnection* DBusConnection::Get() {
+ if (!dbus_connection_singleton)
+ dbus_connection_singleton = new DBusConnection();
+ return dbus_connection_singleton;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/dbus_connection.h b/cros/dbus_connection.h
new file mode 100644
index 0000000..8f0d6f1
--- /dev/null
+++ b/cros/dbus_connection.h
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_DBUS_CONNECTION_H_
+#define UPDATE_ENGINE_CROS_DBUS_CONNECTION_H_
+
+#include <base/memory/ref_counted.h>
+#include <brillo/dbus/dbus_connection.h>
+#include <dbus/bus.h>
+
+namespace chromeos_update_engine {
+
+class DBusConnection {
+ public:
+ DBusConnection();
+
+ const scoped_refptr<dbus::Bus>& GetDBus();
+
+ static DBusConnection* Get();
+
+ private:
+ scoped_refptr<dbus::Bus> bus_;
+
+ brillo::DBusConnection dbus_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(DBusConnection);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_DBUS_CONNECTION_H_
diff --git a/cros/dbus_service.cc b/cros/dbus_service.cc
new file mode 100644
index 0000000..d115195
--- /dev/null
+++ b/cros/dbus_service.cc
@@ -0,0 +1,223 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/dbus_service.h"
+
+#include <string>
+#include <vector>
+
+#include <update_engine/dbus-constants.h>
+
+#include "update_engine/cros/dbus_connection.h"
+#include "update_engine/proto_bindings/update_engine.pb.h"
+#include "update_engine/update_status_utils.h"
+
+namespace chromeos_update_engine {
+
+using brillo::ErrorPtr;
+using chromeos_update_engine::UpdateEngineService;
+using std::string;
+using std::vector;
+using update_engine::Operation;
+using update_engine::StatusResult;
+using update_engine::UpdateEngineStatus;
+
+namespace {
+// Converts the internal |UpdateEngineStatus| to the protobuf |StatusResult|.
+void ConvertToStatusResult(const UpdateEngineStatus& ue_status,
+ StatusResult* out_status) {
+ out_status->set_last_checked_time(ue_status.last_checked_time);
+ out_status->set_progress(ue_status.progress);
+ out_status->set_current_operation(static_cast<Operation>(ue_status.status));
+ out_status->set_new_version(ue_status.new_version);
+ 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);
+ out_status->set_will_powerwash_after_reboot(
+ ue_status.will_powerwash_after_reboot);
+}
+} // namespace
+
+DBusUpdateEngineService::DBusUpdateEngineService(SystemState* system_state)
+ : common_(new UpdateEngineService{system_state}) {}
+
+// org::chromium::UpdateEngineInterfaceInterface methods implementation.
+
+bool DBusUpdateEngineService::AttemptUpdate(ErrorPtr* error,
+ const string& in_app_version,
+ const string& in_omaha_url) {
+ return AttemptUpdateWithFlags(
+ error, in_app_version, in_omaha_url, 0 /* no flags */);
+}
+
+bool DBusUpdateEngineService::AttemptUpdateWithFlags(
+ ErrorPtr* error,
+ const string& in_app_version,
+ const string& in_omaha_url,
+ int32_t in_flags_as_int) {
+ update_engine::AttemptUpdateFlags flags =
+ static_cast<update_engine::AttemptUpdateFlags>(in_flags_as_int);
+ bool interactive = !(flags & update_engine::kAttemptUpdateFlagNonInteractive);
+ bool result;
+ return common_->AttemptUpdate(
+ error,
+ in_app_version,
+ in_omaha_url,
+ interactive ? 0 : update_engine::UpdateAttemptFlags::kFlagNonInteractive,
+ &result);
+}
+
+bool DBusUpdateEngineService::AttemptInstall(ErrorPtr* error,
+ const string& in_omaha_url,
+ const vector<string>& dlc_ids) {
+ return common_->AttemptInstall(error, in_omaha_url, dlc_ids);
+}
+
+bool DBusUpdateEngineService::AttemptRollback(ErrorPtr* error,
+ bool in_powerwash) {
+ return common_->AttemptRollback(error, in_powerwash);
+}
+
+bool DBusUpdateEngineService::CanRollback(ErrorPtr* error,
+ bool* out_can_rollback) {
+ return common_->CanRollback(error, out_can_rollback);
+}
+
+bool DBusUpdateEngineService::ResetStatus(ErrorPtr* error) {
+ return common_->ResetStatus(error);
+}
+
+bool DBusUpdateEngineService::SetDlcActiveValue(brillo::ErrorPtr* error,
+ bool is_active,
+ const string& dlc_id) {
+ return common_->SetDlcActiveValue(error, is_active, dlc_id);
+}
+
+bool DBusUpdateEngineService::GetStatusAdvanced(ErrorPtr* error,
+ StatusResult* out_status) {
+ UpdateEngineStatus status;
+ if (!common_->GetStatus(error, &status)) {
+ return false;
+ }
+
+ ConvertToStatusResult(status, out_status);
+ return true;
+}
+
+bool DBusUpdateEngineService::RebootIfNeeded(ErrorPtr* error) {
+ return common_->RebootIfNeeded(error);
+}
+
+bool DBusUpdateEngineService::SetChannel(ErrorPtr* error,
+ const string& in_target_channel,
+ bool in_is_powerwash_allowed) {
+ return common_->SetChannel(error, in_target_channel, in_is_powerwash_allowed);
+}
+
+bool DBusUpdateEngineService::GetChannel(ErrorPtr* error,
+ bool in_get_current_channel,
+ string* out_channel) {
+ return common_->GetChannel(error, in_get_current_channel, out_channel);
+}
+
+bool DBusUpdateEngineService::GetCohortHint(ErrorPtr* error,
+ string* out_cohort_hint) {
+ return common_->GetCohortHint(error, out_cohort_hint);
+}
+
+bool DBusUpdateEngineService::SetCohortHint(ErrorPtr* error,
+ const string& in_cohort_hint) {
+ return common_->SetCohortHint(error, in_cohort_hint);
+}
+
+bool DBusUpdateEngineService::SetP2PUpdatePermission(ErrorPtr* error,
+ bool in_enabled) {
+ return common_->SetP2PUpdatePermission(error, in_enabled);
+}
+
+bool DBusUpdateEngineService::GetP2PUpdatePermission(ErrorPtr* error,
+ bool* out_enabled) {
+ return common_->GetP2PUpdatePermission(error, out_enabled);
+}
+
+bool DBusUpdateEngineService::SetUpdateOverCellularPermission(ErrorPtr* error,
+ bool in_allowed) {
+ return common_->SetUpdateOverCellularPermission(error, in_allowed);
+}
+
+bool DBusUpdateEngineService::SetUpdateOverCellularTarget(
+ brillo::ErrorPtr* error,
+ const std::string& target_version,
+ int64_t target_size) {
+ return common_->SetUpdateOverCellularTarget(
+ error, target_version, target_size);
+}
+
+bool DBusUpdateEngineService::GetUpdateOverCellularPermission(
+ ErrorPtr* error, bool* out_allowed) {
+ return common_->GetUpdateOverCellularPermission(error, out_allowed);
+}
+
+bool DBusUpdateEngineService::GetDurationSinceUpdate(
+ ErrorPtr* error, int64_t* out_usec_wallclock) {
+ return common_->GetDurationSinceUpdate(error, out_usec_wallclock);
+}
+
+bool DBusUpdateEngineService::GetPrevVersion(ErrorPtr* error,
+ string* out_prev_version) {
+ return common_->GetPrevVersion(error, out_prev_version);
+}
+
+bool DBusUpdateEngineService::GetRollbackPartition(
+ ErrorPtr* error, string* out_rollback_partition_name) {
+ return common_->GetRollbackPartition(error, out_rollback_partition_name);
+}
+
+bool DBusUpdateEngineService::GetLastAttemptError(
+ ErrorPtr* error, int32_t* out_last_attempt_error) {
+ return common_->GetLastAttemptError(error, out_last_attempt_error);
+}
+
+UpdateEngineAdaptor::UpdateEngineAdaptor(SystemState* system_state)
+ : org::chromium::UpdateEngineInterfaceAdaptor(&dbus_service_),
+ bus_(DBusConnection::Get()->GetDBus()),
+ dbus_service_(system_state),
+ dbus_object_(nullptr,
+ bus_,
+ dbus::ObjectPath(update_engine::kUpdateEngineServicePath)) {}
+
+void UpdateEngineAdaptor::RegisterAsync(
+ const base::Callback<void(bool)>& completion_callback) {
+ RegisterWithDBusObject(&dbus_object_);
+ dbus_object_.RegisterAsync(completion_callback);
+}
+
+bool UpdateEngineAdaptor::RequestOwnership() {
+ return bus_->RequestOwnershipAndBlock(update_engine::kUpdateEngineServiceName,
+ dbus::Bus::REQUIRE_PRIMARY);
+}
+
+void UpdateEngineAdaptor::SendStatusUpdate(
+ const UpdateEngineStatus& update_engine_status) {
+ StatusResult status;
+ ConvertToStatusResult(update_engine_status, &status);
+
+ // Send |StatusUpdateAdvanced| signal.
+ SendStatusUpdateAdvancedSignal(status);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/dbus_service.h b/cros/dbus_service.h
new file mode 100644
index 0000000..9e4457f
--- /dev/null
+++ b/cros/dbus_service.h
@@ -0,0 +1,194 @@
+//
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_DBUS_SERVICE_H_
+#define UPDATE_ENGINE_CROS_DBUS_SERVICE_H_
+
+#include <inttypes.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/memory/ref_counted.h>
+#include <brillo/errors/error.h>
+#include <update_engine/proto_bindings/update_engine.pb.h>
+
+#include "update_engine/common/service_observer_interface.h"
+#include "update_engine/cros/common_service.h"
+#include "update_engine/cros/update_attempter.h"
+
+#include "dbus_bindings/org.chromium.UpdateEngineInterface.h"
+
+namespace chromeos_update_engine {
+
+class DBusUpdateEngineService
+ : public org::chromium::UpdateEngineInterfaceInterface {
+ public:
+ explicit DBusUpdateEngineService(SystemState* system_state);
+ virtual ~DBusUpdateEngineService() = default;
+
+ // Implementation of org::chromium::UpdateEngineInterfaceInterface.
+ bool AttemptUpdate(brillo::ErrorPtr* error,
+ const std::string& in_app_version,
+ const std::string& in_omaha_url) override;
+
+ bool AttemptUpdateWithFlags(brillo::ErrorPtr* error,
+ const std::string& in_app_version,
+ const std::string& in_omaha_url,
+ int32_t in_flags_as_int) override;
+
+ bool AttemptInstall(brillo::ErrorPtr* error,
+ const std::string& in_omaha_url,
+ const std::vector<std::string>& dlc_ids) override;
+
+ bool AttemptRollback(brillo::ErrorPtr* error, bool in_powerwash) override;
+
+ // Checks if the system rollback is available by verifying if the secondary
+ // system partition is valid and bootable.
+ bool CanRollback(brillo::ErrorPtr* error, bool* out_can_rollback) override;
+
+ // Resets the status of the update_engine to idle, ignoring any applied
+ // update. This is used for development only.
+ bool ResetStatus(brillo::ErrorPtr* error) override;
+
+ // Sets the DLC as active or inactive. When set to active, the ping metadata
+ // for the DLC is updated accordingly. When set to inactive, the metadata
+ // for the DLC is deleted.
+ bool SetDlcActiveValue(brillo::ErrorPtr* error,
+ bool is_active,
+ const std::string& dlc_id) override;
+
+ // Similar to Above, but returns a protobuffer instead. In the future it will
+ // have more features and is easily extendable.
+ bool GetStatusAdvanced(brillo::ErrorPtr* error,
+ update_engine::StatusResult* out_status) override;
+
+ // Reboots the device if an update is applied and a reboot is required.
+ bool RebootIfNeeded(brillo::ErrorPtr* error) override;
+
+ // Changes the current channel of the device to the target channel. If the
+ // target channel is a less stable channel than the current channel, then the
+ // channel change happens immediately (at the next update check). If the
+ // target channel is a more stable channel, then if is_powerwash_allowed is
+ // set to true, then also the change happens immediately but with a powerwash
+ // if required. Otherwise, the change takes effect eventually (when the
+ // version on the target channel goes above the version number of what the
+ // device currently has).
+ bool SetChannel(brillo::ErrorPtr* error,
+ const std::string& in_target_channel,
+ bool in_is_powerwash_allowed) override;
+
+ // If get_current_channel is set to true, populates |channel| with the name of
+ // the channel that the device is currently on. Otherwise, it populates it
+ // with the name of the channel the device is supposed to be (in case of a
+ // pending channel change).
+ bool GetChannel(brillo::ErrorPtr* error,
+ bool in_get_current_channel,
+ std::string* out_channel) override;
+
+ bool SetCohortHint(brillo::ErrorPtr* error,
+ const std::string& in_cohort_hint) override;
+
+ bool GetCohortHint(brillo::ErrorPtr* error,
+ std::string* out_cohort_hint) override;
+
+ // Enables or disables the sharing and consuming updates over P2P feature
+ // according to the |enabled| argument passed.
+ bool SetP2PUpdatePermission(brillo::ErrorPtr* error,
+ bool in_enabled) override;
+
+ // Returns the current value for the P2P enabled setting. This involves both
+ // sharing and consuming updates over P2P.
+ bool GetP2PUpdatePermission(brillo::ErrorPtr* error,
+ bool* out_enabled) override;
+
+ // If there's no device policy installed, sets the update over cellular
+ // networks permission to the |allowed| value. Otherwise, this method returns
+ // with an error since this setting is overridden by the applied policy.
+ bool SetUpdateOverCellularPermission(brillo::ErrorPtr* error,
+ bool in_allowed) override;
+
+ // If there's no device policy installed, sets the update over cellular
+ // target. Otherwise, this method returns with an error.
+ bool SetUpdateOverCellularTarget(brillo::ErrorPtr* error,
+ const std::string& target_version,
+ int64_t target_size) override;
+
+ // Returns the current value of the update over cellular network setting,
+ // either forced by the device policy if the device is enrolled or the current
+ // user preference otherwise.
+ bool GetUpdateOverCellularPermission(brillo::ErrorPtr* error,
+ bool* out_allowed) override;
+
+ // Returns the duration since the last successful update, as the
+ // duration on the wallclock. Returns an error if the device has not
+ // updated.
+ bool GetDurationSinceUpdate(brillo::ErrorPtr* error,
+ int64_t* out_usec_wallclock) override;
+
+ // Returns the version string of OS that was used before the last reboot
+ // into an updated version. This is available only when rebooting into an
+ // update from previous version, otherwise an empty string is returned.
+ bool GetPrevVersion(brillo::ErrorPtr* error,
+ std::string* out_prev_version) override;
+
+ // Returns the name of kernel partition that can be rolled back into.
+ bool GetRollbackPartition(brillo::ErrorPtr* error,
+ std::string* out_rollback_partition_name) override;
+
+ // Returns the last UpdateAttempt error. If not updated yet, default success
+ // ErrorCode will be returned.
+ bool GetLastAttemptError(brillo::ErrorPtr* error,
+ int32_t* out_last_attempt_error) override;
+
+ private:
+ std::unique_ptr<UpdateEngineService> common_;
+};
+
+// The UpdateEngineAdaptor class runs the UpdateEngineInterface in the fixed
+// object path, without an ObjectManager notifying the interfaces, since it is
+// all static and clients don't expect it to be implemented.
+class UpdateEngineAdaptor : public org::chromium::UpdateEngineInterfaceAdaptor,
+ public ServiceObserverInterface {
+ public:
+ explicit UpdateEngineAdaptor(SystemState* system_state);
+ ~UpdateEngineAdaptor() = default;
+
+ // Register the DBus object with the update engine service asynchronously.
+ // Calls |copmletion_callback| when done passing a boolean indicating if the
+ // registration succeeded.
+ void RegisterAsync(const base::Callback<void(bool)>& completion_callback);
+
+ // Takes ownership of the well-known DBus name and returns whether it
+ // succeeded.
+ bool RequestOwnership();
+
+ // ServiceObserverInterface overrides.
+ void SendStatusUpdate(
+ const update_engine::UpdateEngineStatus& update_engine_status) override;
+
+ void SendPayloadApplicationComplete(ErrorCode error_code) override {}
+
+ private:
+ scoped_refptr<dbus::Bus> bus_;
+ DBusUpdateEngineService dbus_service_;
+ brillo::dbus_utils::DBusObject dbus_object_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_DBUS_SERVICE_H_
diff --git a/cros/dbus_test_utils.h b/cros/dbus_test_utils.h
new file mode 100644
index 0000000..1116c52
--- /dev/null
+++ b/cros/dbus_test_utils.h
@@ -0,0 +1,91 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_DBUS_TEST_UTILS_H_
+#define UPDATE_ENGINE_CROS_DBUS_TEST_UTILS_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/bind.h>
+#include <brillo/message_loops/message_loop.h>
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+namespace dbus_test_utils {
+
+#define MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER( \
+ mock_signal_handler, mock_proxy, signal) \
+ do { \
+ EXPECT_CALL((mock_proxy), \
+ DoRegister##signal##SignalHandler(::testing::_, ::testing::_)) \
+ .WillOnce(::chromeos_update_engine::dbus_test_utils::GrabCallbacks( \
+ &(mock_signal_handler))); \
+ } while (false)
+
+template <typename T>
+class MockSignalHandler {
+ public:
+ MockSignalHandler() = default;
+ ~MockSignalHandler() {
+ if (callback_connected_task_ != brillo::MessageLoop::kTaskIdNull)
+ brillo::MessageLoop::current()->CancelTask(callback_connected_task_);
+ }
+
+ // Returns whether the signal handler is registered.
+ bool IsHandlerRegistered() const { return signal_callback_ != nullptr; }
+
+ const base::Callback<T>& signal_callback() { return *signal_callback_.get(); }
+
+ void GrabCallbacks(
+ const base::Callback<T>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
+ signal_callback_.reset(new base::Callback<T>(signal_callback));
+ on_connected_callback_.reset(new dbus::ObjectProxy::OnConnectedCallback(
+ std::move(*on_connected_callback)));
+ // Notify from the main loop that the callback was connected.
+ callback_connected_task_ = brillo::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockSignalHandler<T>::OnCallbackConnected,
+ base::Unretained(this)));
+ }
+
+ private:
+ void OnCallbackConnected() {
+ callback_connected_task_ = brillo::MessageLoop::kTaskIdNull;
+ std::move(*on_connected_callback_).Run("", "", true);
+ }
+
+ brillo::MessageLoop::TaskId callback_connected_task_{
+ brillo::MessageLoop::kTaskIdNull};
+
+ std::unique_ptr<base::Callback<T>> signal_callback_;
+ std::unique_ptr<dbus::ObjectProxy::OnConnectedCallback>
+ on_connected_callback_;
+};
+
+// Defines the action that will call MockSignalHandler<T>::GrabCallbacks for the
+// right type.
+ACTION_P(GrabCallbacks, mock_signal_handler) {
+ mock_signal_handler->GrabCallbacks(arg0, arg1);
+}
+
+} // namespace dbus_test_utils
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_DBUS_TEST_UTILS_H_
diff --git a/cros/dlcservice_chromeos.cc b/cros/dlcservice_chromeos.cc
new file mode 100644
index 0000000..e510c1d
--- /dev/null
+++ b/cros/dlcservice_chromeos.cc
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/dlcservice_chromeos.h"
+
+#include <brillo/errors/error.h>
+#include <dlcservice/proto_bindings/dlcservice.pb.h>
+// NOLINTNEXTLINE(build/include_alpha) "dbus-proxies.h" needs "dlcservice.pb.h"
+#include <dlcservice/dbus-proxies.h>
+
+#include "update_engine/cros/dbus_connection.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+org::chromium::DlcServiceInterfaceProxy GetDlcServiceProxy() {
+ return {DBusConnection::Get()->GetDBus()};
+}
+} // namespace
+
+std::unique_ptr<DlcServiceInterface> CreateDlcService() {
+ return std::make_unique<DlcServiceChromeOS>();
+}
+
+bool DlcServiceChromeOS::GetDlcsToUpdate(vector<string>* dlc_ids) {
+ if (!dlc_ids)
+ return false;
+ dlc_ids->clear();
+
+ brillo::ErrorPtr err;
+ if (!GetDlcServiceProxy().GetDlcsToUpdate(dlc_ids, &err)) {
+ LOG(ERROR) << "dlcservice failed to return DLCs that need to be updated. "
+ << "ErrorCode=" << err->GetCode()
+ << ", ErrMsg=" << err->GetMessage();
+ dlc_ids->clear();
+ return false;
+ }
+ return true;
+}
+
+bool DlcServiceChromeOS::InstallCompleted(const vector<string>& dlc_ids) {
+ brillo::ErrorPtr err;
+ if (!GetDlcServiceProxy().InstallCompleted(dlc_ids, &err)) {
+ LOG(ERROR) << "dlcservice failed to complete install. ErrCode="
+ << err->GetCode() << ", ErrMsg=" << err->GetMessage();
+ return false;
+ }
+ return true;
+}
+
+bool DlcServiceChromeOS::UpdateCompleted(const vector<string>& dlc_ids) {
+ brillo::ErrorPtr err;
+ if (!GetDlcServiceProxy().UpdateCompleted(dlc_ids, &err)) {
+ LOG(ERROR) << "dlcservice failed to complete updated. ErrCode="
+ << err->GetCode() << ", ErrMsg=" << err->GetMessage();
+ return false;
+ }
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/dlcservice_chromeos.h b/cros/dlcservice_chromeos.h
new file mode 100644
index 0000000..3f11b12
--- /dev/null
+++ b/cros/dlcservice_chromeos.h
@@ -0,0 +1,55 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_DLCSERVICE_CHROMEOS_H_
+#define UPDATE_ENGINE_CROS_DLCSERVICE_CHROMEOS_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "update_engine/common/dlcservice_interface.h"
+
+namespace chromeos_update_engine {
+
+// The Chrome OS implementation of the DlcServiceInterface. This interface
+// interacts with dlcservice via D-Bus.
+class DlcServiceChromeOS : public DlcServiceInterface {
+ public:
+ DlcServiceChromeOS() = default;
+ ~DlcServiceChromeOS() = default;
+
+ // DlcServiceInterface overrides.
+
+ // Will clear the |dlc_ids|, passed to be modified. Clearing by default has
+ // the added benefit of avoiding indeterminate behavior in the case that
+ // |dlc_ids| wasn't empty to begin which would lead to possible duplicates and
+ // cases when error was not checked it's still safe.
+ bool GetDlcsToUpdate(std::vector<std::string>* dlc_ids) override;
+
+ // Call into dlcservice for it to mark the DLC IDs as being installed.
+ bool InstallCompleted(const std::vector<std::string>& dlc_ids) override;
+
+ // Call into dlcservice for it to mark the DLC IDs as being updated.
+ bool UpdateCompleted(const std::vector<std::string>& dlc_ids) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DlcServiceChromeOS);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_DLCSERVICE_CHROMEOS_H_
diff --git a/cros/excluder_chromeos.cc b/cros/excluder_chromeos.cc
new file mode 100644
index 0000000..4796525
--- /dev/null
+++ b/cros/excluder_chromeos.cc
@@ -0,0 +1,63 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/excluder_chromeos.h"
+
+#include <memory>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_piece.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/system_state.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+std::unique_ptr<ExcluderInterface> CreateExcluder(PrefsInterface* prefs) {
+ return std::make_unique<ExcluderChromeOS>(prefs);
+}
+
+ExcluderChromeOS::ExcluderChromeOS(PrefsInterface* prefs) : prefs_(prefs) {}
+
+bool ExcluderChromeOS::Exclude(const string& name) {
+ auto key = prefs_->CreateSubKey({kExclusionPrefsSubDir, name});
+ return prefs_->SetString(key, "");
+}
+
+bool ExcluderChromeOS::IsExcluded(const string& name) {
+ auto key = prefs_->CreateSubKey({kExclusionPrefsSubDir, name});
+ return prefs_->Exists(key);
+}
+
+bool ExcluderChromeOS::Reset() {
+ bool ret = true;
+ vector<string> keys;
+ if (!prefs_->GetSubKeys(kExclusionPrefsSubDir, &keys))
+ return false;
+ for (const auto& key : keys)
+ if (!(ret &= prefs_->Delete(key)))
+ LOG(ERROR) << "Failed to delete exclusion pref for " << key;
+ return ret;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/excluder_chromeos.h b/cros/excluder_chromeos.h
new file mode 100644
index 0000000..2480066
--- /dev/null
+++ b/cros/excluder_chromeos.h
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_EXCLUDER_CHROMEOS_H_
+#define UPDATE_ENGINE_CROS_EXCLUDER_CHROMEOS_H_
+
+#include <string>
+
+#include "update_engine/common/excluder_interface.h"
+#include "update_engine/common/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// The Chrome OS implementation of the |ExcluderInterface|.
+class ExcluderChromeOS : public ExcluderInterface {
+ public:
+ explicit ExcluderChromeOS(PrefsInterface* prefs);
+ ~ExcluderChromeOS() = default;
+
+ // |ExcluderInterface| overrides.
+ bool Exclude(const std::string& name) override;
+ bool IsExcluded(const std::string& name) override;
+ bool Reset() override;
+
+ // Not copyable or movable.
+ ExcluderChromeOS(const ExcluderChromeOS&) = delete;
+ ExcluderChromeOS& operator=(const ExcluderChromeOS&) = delete;
+ ExcluderChromeOS(ExcluderChromeOS&&) = delete;
+ ExcluderChromeOS& operator=(ExcluderChromeOS&&) = delete;
+
+ private:
+ PrefsInterface* prefs_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_EXCLUDER_CHROMEOS_H_
diff --git a/cros/excluder_chromeos_unittest.cc b/cros/excluder_chromeos_unittest.cc
new file mode 100644
index 0000000..3602e56
--- /dev/null
+++ b/cros/excluder_chromeos_unittest.cc
@@ -0,0 +1,66 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/excluder_chromeos.h"
+
+#include <memory>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/prefs.h"
+
+using std::string;
+using std::unique_ptr;
+
+namespace chromeos_update_engine {
+
+constexpr char kFakeHash[] =
+ "71ff43d76e2488e394e46872f5b066cc25e394c2c3e3790dd319517883b33db1";
+
+class ExcluderChromeOSTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
+ ASSERT_TRUE(base::PathExists(tempdir_.GetPath()));
+ ASSERT_TRUE(prefs_.Init(tempdir_.GetPath()));
+ excluder_ = std::make_unique<ExcluderChromeOS>(&prefs_);
+ }
+
+ base::ScopedTempDir tempdir_;
+ Prefs prefs_;
+ unique_ptr<ExcluderChromeOS> excluder_;
+};
+
+TEST_F(ExcluderChromeOSTest, ExclusionCheck) {
+ EXPECT_FALSE(excluder_->IsExcluded(kFakeHash));
+ EXPECT_TRUE(excluder_->Exclude(kFakeHash));
+ EXPECT_TRUE(excluder_->IsExcluded(kFakeHash));
+}
+
+TEST_F(ExcluderChromeOSTest, ResetFlow) {
+ EXPECT_TRUE(excluder_->Exclude("abc"));
+ EXPECT_TRUE(excluder_->Exclude(kFakeHash));
+ EXPECT_TRUE(excluder_->IsExcluded("abc"));
+ EXPECT_TRUE(excluder_->IsExcluded(kFakeHash));
+
+ EXPECT_TRUE(excluder_->Reset());
+ EXPECT_FALSE(excluder_->IsExcluded("abc"));
+ EXPECT_FALSE(excluder_->IsExcluded(kFakeHash));
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/fake_p2p_manager.h b/cros/fake_p2p_manager.h
new file mode 100644
index 0000000..1011b7e
--- /dev/null
+++ b/cros/fake_p2p_manager.h
@@ -0,0 +1,112 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_H_
+#define UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/cros/p2p_manager.h"
+
+namespace chromeos_update_engine {
+
+// A fake implementation of P2PManager.
+class FakeP2PManager : public P2PManager {
+ public:
+ FakeP2PManager()
+ : is_p2p_enabled_(false),
+ ensure_p2p_running_result_(false),
+ ensure_p2p_not_running_result_(false),
+ perform_housekeeping_result_(false),
+ count_shared_files_result_(0) {}
+
+ // P2PManager overrides.
+ void SetDevicePolicy(const policy::DevicePolicy* device_policy) override {}
+
+ bool IsP2PEnabled() override { return is_p2p_enabled_; }
+
+ bool EnsureP2PRunning() override { return ensure_p2p_running_result_; }
+
+ bool EnsureP2PNotRunning() override { return ensure_p2p_not_running_result_; }
+
+ bool PerformHousekeeping() override { return perform_housekeeping_result_; }
+
+ void LookupUrlForFile(const std::string& file_id,
+ size_t minimum_size,
+ base::TimeDelta max_time_to_wait,
+ LookupCallback callback) override {
+ callback.Run(lookup_url_for_file_result_);
+ }
+
+ bool FileShare(const std::string& file_id, size_t expected_size) override {
+ return false;
+ }
+
+ base::FilePath FileGetPath(const std::string& file_id) override {
+ return base::FilePath();
+ }
+
+ ssize_t FileGetSize(const std::string& file_id) override { return -1; }
+
+ ssize_t FileGetExpectedSize(const std::string& file_id) override {
+ return -1;
+ }
+
+ bool FileGetVisible(const std::string& file_id, bool* out_result) override {
+ return false;
+ }
+
+ bool FileMakeVisible(const std::string& file_id) override { return false; }
+
+ int CountSharedFiles() override { return count_shared_files_result_; }
+
+ // Methods for controlling what the fake returns and how it acts.
+ void SetP2PEnabled(bool is_p2p_enabled) { is_p2p_enabled_ = is_p2p_enabled; }
+
+ void SetEnsureP2PRunningResult(bool ensure_p2p_running_result) {
+ ensure_p2p_running_result_ = ensure_p2p_running_result;
+ }
+
+ void SetEnsureP2PNotRunningResult(bool ensure_p2p_not_running_result) {
+ ensure_p2p_not_running_result_ = ensure_p2p_not_running_result;
+ }
+
+ void SetPerformHousekeepingResult(bool perform_housekeeping_result) {
+ perform_housekeeping_result_ = perform_housekeeping_result;
+ }
+
+ void SetCountSharedFilesResult(int count_shared_files_result) {
+ count_shared_files_result_ = count_shared_files_result;
+ }
+
+ void SetLookupUrlForFileResult(const std::string& url) {
+ lookup_url_for_file_result_ = url;
+ }
+
+ private:
+ bool is_p2p_enabled_;
+ bool ensure_p2p_running_result_;
+ bool ensure_p2p_not_running_result_;
+ bool perform_housekeeping_result_;
+ int count_shared_files_result_;
+ std::string lookup_url_for_file_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeP2PManager);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_H_
diff --git a/cros/fake_p2p_manager_configuration.h b/cros/fake_p2p_manager_configuration.h
new file mode 100644
index 0000000..8d50ac8
--- /dev/null
+++ b/cros/fake_p2p_manager_configuration.h
@@ -0,0 +1,102 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_CONFIGURATION_H_
+#define UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_CONFIGURATION_H_
+
+#include "update_engine/cros/p2p_manager.h"
+
+#include <string>
+#include <vector>
+
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_util.h>
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+// Configuration for P2PManager for use in unit tests. Instead of
+// /var/cache/p2p, a temporary directory is used.
+class FakeP2PManagerConfiguration : public P2PManager::Configuration {
+ public:
+ FakeP2PManagerConfiguration() { EXPECT_TRUE(p2p_dir_.CreateUniqueTempDir()); }
+
+ // P2PManager::Configuration override
+ base::FilePath GetP2PDir() override { return p2p_dir_.GetPath(); }
+
+ // P2PManager::Configuration override
+ std::vector<std::string> GetInitctlArgs(bool is_start) override {
+ return is_start ? initctl_start_args_ : initctl_stop_args_;
+ }
+
+ // P2PManager::Configuration override
+ std::vector<std::string> GetP2PClientArgs(const std::string& file_id,
+ size_t minimum_size) override {
+ std::vector<std::string> formatted_command = p2p_client_cmd_format_;
+ // Replace {variable} on the passed string.
+ std::string str_minimum_size = std::to_string(minimum_size);
+ for (std::string& arg : formatted_command) {
+ base::ReplaceSubstringsAfterOffset(&arg, 0, "{file_id}", file_id);
+ base::ReplaceSubstringsAfterOffset(
+ &arg, 0, "{minsize}", str_minimum_size);
+ }
+ return formatted_command;
+ }
+
+ // Use |command_line| instead of "initctl start p2p" when attempting
+ // to start the p2p service.
+ void SetInitctlStartCommand(const std::vector<std::string>& command) {
+ initctl_start_args_ = command;
+ }
+
+ // Use |command_line| instead of "initctl stop p2p" when attempting
+ // to stop the p2p service.
+ void SetInitctlStopCommand(const std::vector<std::string>& command) {
+ initctl_stop_args_ = command;
+ }
+
+ // Use |command_format| instead of "p2p-client --get-url={file_id}
+ // --minimum-size={minsize}" when attempting to look up a file using
+ // p2p-client(1).
+ //
+ // The passed |command_format| argument can have "{file_id}" and "{minsize}"
+ // as substrings of any of its elements, that will be replaced by the
+ // corresponding values passed to GetP2PClientArgs().
+ void SetP2PClientCommand(const std::vector<std::string>& command_format) {
+ p2p_client_cmd_format_ = command_format;
+ }
+
+ private:
+ // The temporary directory used for p2p.
+ base::ScopedTempDir p2p_dir_;
+
+ // Argument vector for starting p2p.
+ std::vector<std::string> initctl_start_args_{"initctl", "start", "p2p"};
+
+ // Argument vector for stopping p2p.
+ std::vector<std::string> initctl_stop_args_{"initctl", "stop", "p2p"};
+
+ // A string for generating the p2p-client command. See the
+ // SetP2PClientCommandLine() for details.
+ std::vector<std::string> p2p_client_cmd_format_{
+ "p2p-client", "--get-url={file_id}", "--minimum-size={minsize}"};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeP2PManagerConfiguration);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_CONFIGURATION_H_
diff --git a/cros/fake_shill_proxy.cc b/cros/fake_shill_proxy.cc
new file mode 100644
index 0000000..2d05a6b
--- /dev/null
+++ b/cros/fake_shill_proxy.cc
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/fake_shill_proxy.h"
+
+#include <utility>
+
+using org::chromium::flimflam::ManagerProxyMock;
+using org::chromium::flimflam::ServiceProxyInterface;
+
+namespace chromeos_update_engine {
+
+FakeShillProxy::FakeShillProxy()
+ : manager_proxy_mock_(new ManagerProxyMock()) {}
+
+ManagerProxyMock* FakeShillProxy::GetManagerProxy() {
+ return manager_proxy_mock_.get();
+}
+
+std::unique_ptr<ServiceProxyInterface> FakeShillProxy::GetServiceForPath(
+ const dbus::ObjectPath& path) {
+ auto it = service_proxy_mocks_.find(path.value());
+ CHECK(it != service_proxy_mocks_.end())
+ << "No ServiceProxyMock set for " << path.value();
+ std::unique_ptr<ServiceProxyInterface> result = std::move(it->second);
+ service_proxy_mocks_.erase(it);
+ return result;
+}
+
+void FakeShillProxy::SetServiceForPath(
+ const dbus::ObjectPath& path,
+ std::unique_ptr<ServiceProxyInterface> service_proxy) {
+ service_proxy_mocks_[path.value()] = std::move(service_proxy);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/fake_shill_proxy.h b/cros/fake_shill_proxy.h
new file mode 100644
index 0000000..8c15a9d
--- /dev/null
+++ b/cros/fake_shill_proxy.h
@@ -0,0 +1,66 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_FAKE_SHILL_PROXY_H_
+#define UPDATE_ENGINE_CROS_FAKE_SHILL_PROXY_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <shill/dbus-proxies.h>
+#include <shill/dbus-proxy-mocks.h>
+
+#include "update_engine/cros/shill_proxy_interface.h"
+
+namespace chromeos_update_engine {
+
+// This class implements the connection to shill using real DBus calls.
+class FakeShillProxy : public ShillProxyInterface {
+ public:
+ FakeShillProxy();
+ ~FakeShillProxy() override = default;
+
+ // ShillProxyInterface overrides.
+
+ // GetManagerProxy returns the subclass ManagerProxyMock so tests can easily
+ // use it. Mocks for the return value of GetServiceForPath() can be provided
+ // with SetServiceForPath().
+ org::chromium::flimflam::ManagerProxyMock* GetManagerProxy() override;
+ std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>
+ GetServiceForPath(const dbus::ObjectPath& path) override;
+
+ // Sets the service_proxy that will be returned by GetServiceForPath().
+ void SetServiceForPath(
+ const dbus::ObjectPath& path,
+ std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>
+ service_proxy);
+
+ private:
+ std::unique_ptr<org::chromium::flimflam::ManagerProxyMock>
+ manager_proxy_mock_;
+
+ std::map<std::string,
+ std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>>
+ service_proxy_mocks_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeShillProxy);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_FAKE_SHILL_PROXY_H_
diff --git a/cros/fake_system_state.cc b/cros/fake_system_state.cc
new file mode 100644
index 0000000..9dfdc5b
--- /dev/null
+++ b/cros/fake_system_state.cc
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/fake_system_state.h"
+
+namespace chromeos_update_engine {
+
+// Mock the SystemStateInterface so that we could lie that
+// OOBE is completed even when there's no such marker file, etc.
+FakeSystemState::FakeSystemState()
+ : mock_update_attempter_(this, nullptr),
+ mock_request_params_(this),
+ fake_update_manager_(&fake_clock_),
+ clock_(&fake_clock_),
+ connection_manager_(&mock_connection_manager_),
+ hardware_(&fake_hardware_),
+ metrics_reporter_(&mock_metrics_reporter_),
+ prefs_(&mock_prefs_),
+ powerwash_safe_prefs_(&mock_powerwash_safe_prefs_),
+ payload_state_(&mock_payload_state_),
+ update_attempter_(&mock_update_attempter_),
+ request_params_(&mock_request_params_),
+ p2p_manager_(&mock_p2p_manager_),
+ update_manager_(&fake_update_manager_),
+ device_policy_(nullptr),
+ fake_system_rebooted_(false) {
+ mock_payload_state_.Initialize(this);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/fake_system_state.h b/cros/fake_system_state.h
new file mode 100644
index 0000000..2f92b7c
--- /dev/null
+++ b/cros/fake_system_state.h
@@ -0,0 +1,281 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_FAKE_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_CROS_FAKE_SYSTEM_STATE_H_
+
+#include <base/logging.h>
+#include <gmock/gmock.h>
+#include <policy/mock_device_policy.h>
+
+#include "metrics/metrics_library_mock.h"
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/mock_metrics_reporter.h"
+#include "update_engine/common/mock_prefs.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/cros/mock_connection_manager.h"
+#include "update_engine/cros/mock_omaha_request_params.h"
+#include "update_engine/cros/mock_p2p_manager.h"
+#include "update_engine/cros/mock_payload_state.h"
+#include "update_engine/cros/mock_power_manager.h"
+#include "update_engine/cros/mock_update_attempter.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+
+namespace chromeos_update_engine {
+
+// Mock the SystemStateInterface so that we could lie that
+// OOBE is completed even when there's no such marker file, etc.
+class FakeSystemState : public SystemState {
+ public:
+ FakeSystemState();
+
+ // Base class overrides. All getters return the current implementation of
+ // various members, either the default (fake/mock) or the one set to override
+ // it by client code.
+
+ BootControlInterface* boot_control() override { return boot_control_; }
+
+ inline ClockInterface* clock() override { return clock_; }
+
+ inline void set_device_policy(
+ const policy::DevicePolicy* device_policy) override {
+ device_policy_ = device_policy;
+ }
+
+ inline const policy::DevicePolicy* device_policy() override {
+ return device_policy_;
+ }
+
+ inline ConnectionManagerInterface* connection_manager() override {
+ return connection_manager_;
+ }
+
+ inline HardwareInterface* hardware() override { return hardware_; }
+
+ inline MetricsReporterInterface* metrics_reporter() override {
+ CHECK(metrics_reporter_ != nullptr);
+ return metrics_reporter_;
+ }
+
+ inline PrefsInterface* prefs() override { return prefs_; }
+
+ inline PrefsInterface* powerwash_safe_prefs() override {
+ return powerwash_safe_prefs_;
+ }
+
+ inline PayloadStateInterface* payload_state() override {
+ return payload_state_;
+ }
+
+ inline UpdateAttempter* update_attempter() override {
+ return update_attempter_;
+ }
+
+ inline OmahaRequestParams* request_params() override {
+ return request_params_;
+ }
+
+ inline P2PManager* p2p_manager() override { return p2p_manager_; }
+
+ inline chromeos_update_manager::UpdateManager* update_manager() override {
+ return update_manager_;
+ }
+
+ inline PowerManagerInterface* power_manager() override {
+ return power_manager_;
+ }
+
+ inline DlcServiceInterface* dlcservice() override { return dlcservice_; }
+
+ inline bool system_rebooted() override { return fake_system_rebooted_; }
+
+ // Setters for the various members, can be used for overriding the default
+ // implementations. For convenience, setting to a null pointer will restore
+ // the default implementation.
+
+ void set_boot_control(BootControlInterface* boot_control) {
+ boot_control_ = boot_control ? boot_control : &fake_boot_control_;
+ }
+
+ inline void set_clock(ClockInterface* clock) {
+ clock_ = clock ? clock : &fake_clock_;
+ }
+
+ inline void set_connection_manager(
+ ConnectionManagerInterface* connection_manager) {
+ connection_manager_ =
+ (connection_manager ? connection_manager : &mock_connection_manager_);
+ }
+
+ inline void set_hardware(HardwareInterface* hardware) {
+ hardware_ = hardware ? hardware : &fake_hardware_;
+ }
+
+ inline void set_metrics_reporter(MetricsReporterInterface* metrics_reporter) {
+ metrics_reporter_ =
+ metrics_reporter ? metrics_reporter : &mock_metrics_reporter_;
+ }
+
+ inline void set_prefs(PrefsInterface* prefs) {
+ prefs_ = prefs ? prefs : &mock_prefs_;
+ }
+
+ inline void set_powerwash_safe_prefs(PrefsInterface* powerwash_safe_prefs) {
+ powerwash_safe_prefs_ =
+ (powerwash_safe_prefs ? powerwash_safe_prefs
+ : &mock_powerwash_safe_prefs_);
+ }
+
+ inline void set_payload_state(PayloadStateInterface* payload_state) {
+ payload_state_ = payload_state ? payload_state : &mock_payload_state_;
+ }
+
+ inline void set_update_attempter(UpdateAttempter* update_attempter) {
+ update_attempter_ =
+ (update_attempter ? update_attempter : &mock_update_attempter_);
+ }
+
+ inline void set_request_params(OmahaRequestParams* request_params) {
+ request_params_ = (request_params ? request_params : &mock_request_params_);
+ }
+
+ inline void set_p2p_manager(P2PManager* p2p_manager) {
+ p2p_manager_ = p2p_manager ? p2p_manager : &mock_p2p_manager_;
+ }
+
+ inline void set_update_manager(
+ chromeos_update_manager::UpdateManager* update_manager) {
+ update_manager_ = update_manager ? update_manager : &fake_update_manager_;
+ }
+
+ inline void set_system_rebooted(bool system_rebooted) {
+ fake_system_rebooted_ = system_rebooted;
+ }
+
+ inline void set_dlcservice(DlcServiceInterface* dlcservice) {
+ dlcservice_ = dlcservice;
+ }
+
+ // Getters for the built-in default implementations. These return the actual
+ // concrete type of each implementation. For additional safety, they will fail
+ // whenever the requested default was overridden by a different
+ // implementation.
+
+ inline FakeBootControl* fake_boot_control() {
+ CHECK(boot_control_ == &fake_boot_control_);
+ return &fake_boot_control_;
+ }
+
+ inline FakeClock* fake_clock() {
+ CHECK(clock_ == &fake_clock_);
+ return &fake_clock_;
+ }
+
+ inline testing::NiceMock<MockConnectionManager>* mock_connection_manager() {
+ CHECK(connection_manager_ == &mock_connection_manager_);
+ return &mock_connection_manager_;
+ }
+
+ inline FakeHardware* fake_hardware() {
+ CHECK(hardware_ == &fake_hardware_);
+ return &fake_hardware_;
+ }
+
+ inline testing::NiceMock<MockMetricsReporter>* mock_metrics_reporter() {
+ CHECK(metrics_reporter_ == &mock_metrics_reporter_);
+ return &mock_metrics_reporter_;
+ }
+
+ inline testing::NiceMock<MockPrefs>* mock_prefs() {
+ CHECK(prefs_ == &mock_prefs_);
+ return &mock_prefs_;
+ }
+
+ inline testing::NiceMock<MockPrefs>* mock_powerwash_safe_prefs() {
+ CHECK(powerwash_safe_prefs_ == &mock_powerwash_safe_prefs_);
+ return &mock_powerwash_safe_prefs_;
+ }
+
+ inline testing::NiceMock<MockPayloadState>* mock_payload_state() {
+ CHECK(payload_state_ == &mock_payload_state_);
+ return &mock_payload_state_;
+ }
+
+ inline testing::NiceMock<MockUpdateAttempter>* mock_update_attempter() {
+ CHECK(update_attempter_ == &mock_update_attempter_);
+ return &mock_update_attempter_;
+ }
+
+ inline testing::NiceMock<MockOmahaRequestParams>* mock_request_params() {
+ CHECK(request_params_ == &mock_request_params_);
+ return &mock_request_params_;
+ }
+
+ inline testing::NiceMock<MockP2PManager>* mock_p2p_manager() {
+ CHECK(p2p_manager_ == &mock_p2p_manager_);
+ return &mock_p2p_manager_;
+ }
+
+ inline chromeos_update_manager::FakeUpdateManager* fake_update_manager() {
+ CHECK(update_manager_ == &fake_update_manager_);
+ return &fake_update_manager_;
+ }
+
+ private:
+ // Default mock/fake implementations (owned).
+ FakeBootControl fake_boot_control_;
+ FakeClock fake_clock_;
+ testing::NiceMock<MockConnectionManager> mock_connection_manager_;
+ FakeHardware fake_hardware_;
+ testing::NiceMock<MockMetricsReporter> mock_metrics_reporter_;
+ testing::NiceMock<MockPrefs> mock_prefs_;
+ testing::NiceMock<MockPrefs> mock_powerwash_safe_prefs_;
+ testing::NiceMock<MockPayloadState> mock_payload_state_;
+ testing::NiceMock<MockUpdateAttempter> mock_update_attempter_;
+ testing::NiceMock<MockOmahaRequestParams> mock_request_params_;
+ testing::NiceMock<MockP2PManager> mock_p2p_manager_;
+ chromeos_update_manager::FakeUpdateManager fake_update_manager_;
+ testing::NiceMock<MockPowerManager> mock_power_manager_;
+
+ // Pointers to objects that client code can override. They are initialized to
+ // the default implementations above.
+ BootControlInterface* boot_control_{&fake_boot_control_};
+ ClockInterface* clock_;
+ ConnectionManagerInterface* connection_manager_;
+ HardwareInterface* hardware_;
+ MetricsReporterInterface* metrics_reporter_;
+ PrefsInterface* prefs_;
+ PrefsInterface* powerwash_safe_prefs_;
+ PayloadStateInterface* payload_state_;
+ UpdateAttempter* update_attempter_;
+ OmahaRequestParams* request_params_;
+ P2PManager* p2p_manager_;
+ chromeos_update_manager::UpdateManager* update_manager_;
+ PowerManagerInterface* power_manager_{&mock_power_manager_};
+ DlcServiceInterface* dlcservice_;
+
+ // Other object pointers (not preinitialized).
+ const policy::DevicePolicy* device_policy_;
+
+ // Other data members.
+ bool fake_system_rebooted_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_FAKE_SYSTEM_STATE_H_
diff --git a/cros/hardware_chromeos.cc b/cros/hardware_chromeos.cc
new file mode 100644
index 0000000..b9018ff
--- /dev/null
+++ b/cros/hardware_chromeos.cc
@@ -0,0 +1,364 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/hardware_chromeos.h"
+
+#include <utility>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <brillo/key_value_store.h>
+#include <debugd/dbus-constants.h>
+#include <vboot/crossystem.h>
+
+extern "C" {
+#include "vboot/vboot_host.h"
+}
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/hwid_override.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/dbus_connection.h"
+#if USE_CFM
+#include "update_engine/cros/requisition_util.h"
+#endif
+
+using std::string;
+using std::vector;
+
+namespace {
+
+const char kOOBECompletedMarker[] = "/home/chronos/.oobe_completed";
+
+// The stateful directory used by update_engine to store powerwash-safe files.
+// The files stored here must be added to the powerwash script allowlist.
+const char kPowerwashSafeDirectory[] =
+ "/mnt/stateful_partition/unencrypted/preserve";
+
+// The powerwash_count marker file contains the number of times the device was
+// powerwashed. This value is incremented by the clobber-state script when
+// a powerwash is performed.
+const char kPowerwashCountMarker[] = "powerwash_count";
+
+// The name of the marker file used to trigger powerwash when post-install
+// completes successfully so that the device is powerwashed on next reboot.
+const char kPowerwashMarkerFile[] =
+ "/mnt/stateful_partition/factory_install_reset";
+
+// The name of the marker file used to trigger a save of rollback data
+// during the next shutdown.
+const char kRollbackSaveMarkerFile[] =
+ "/mnt/stateful_partition/.save_rollback_data";
+
+// The contents of the powerwash marker file for the non-rollback case.
+const char kPowerwashCommand[] = "safe fast keepimg reason=update_engine\n";
+
+// The contents of the powerwas marker file for the rollback case.
+const char kRollbackPowerwashCommand[] =
+ "safe fast keepimg rollback reason=update_engine\n";
+
+// UpdateManager config path.
+const char* kConfigFilePath = "/etc/update_manager.conf";
+
+// UpdateManager config options:
+const char* kConfigOptsIsOOBEEnabled = "is_oobe_enabled";
+
+const char* kActivePingKey = "first_active_omaha_ping_sent";
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace hardware {
+
+// Factory defined in hardware.h.
+std::unique_ptr<HardwareInterface> CreateHardware() {
+ std::unique_ptr<HardwareChromeOS> hardware(new HardwareChromeOS());
+ hardware->Init();
+ return std::move(hardware);
+}
+
+} // namespace hardware
+
+void HardwareChromeOS::Init() {
+ LoadConfig("" /* root_prefix */, IsNormalBootMode());
+ debugd_proxy_.reset(
+ new org::chromium::debugdProxy(DBusConnection::Get()->GetDBus()));
+}
+
+bool HardwareChromeOS::IsOfficialBuild() const {
+ return VbGetSystemPropertyInt("debug_build") == 0;
+}
+
+bool HardwareChromeOS::IsNormalBootMode() const {
+ bool dev_mode = VbGetSystemPropertyInt("devsw_boot") != 0;
+ return !dev_mode;
+}
+
+bool HardwareChromeOS::AreDevFeaturesEnabled() const {
+ // Even though the debugd tools are also gated on devmode, checking here can
+ // save us a D-Bus call so it's worth doing explicitly.
+ if (IsNormalBootMode())
+ return false;
+
+ int32_t dev_features = debugd::DEV_FEATURES_DISABLED;
+ brillo::ErrorPtr error;
+ // Some boards may not include debugd so it's expected that this may fail,
+ // in which case we treat it as disabled.
+ if (debugd_proxy_ && debugd_proxy_->QueryDevFeatures(&dev_features, &error) &&
+ !(dev_features & debugd::DEV_FEATURES_DISABLED)) {
+ LOG(INFO) << "Debugd dev tools enabled.";
+ return true;
+ }
+ return false;
+}
+
+bool HardwareChromeOS::IsOOBEEnabled() const {
+ return is_oobe_enabled_;
+}
+
+bool HardwareChromeOS::IsOOBEComplete(base::Time* out_time_of_oobe) const {
+ if (!is_oobe_enabled_) {
+ LOG(WARNING) << "OOBE is not enabled but IsOOBEComplete() was called";
+ }
+ struct stat statbuf;
+ if (stat(kOOBECompletedMarker, &statbuf) != 0) {
+ if (errno != ENOENT) {
+ PLOG(ERROR) << "Error getting information about " << kOOBECompletedMarker;
+ }
+ return false;
+ }
+
+ if (out_time_of_oobe != nullptr)
+ *out_time_of_oobe = base::Time::FromTimeT(statbuf.st_mtime);
+ return true;
+}
+
+static string ReadValueFromCrosSystem(const string& key) {
+ char value_buffer[VB_MAX_STRING_PROPERTY];
+
+ const char* rv = VbGetSystemPropertyString(
+ key.c_str(), value_buffer, sizeof(value_buffer));
+ if (rv != nullptr) {
+ string return_value(value_buffer);
+ base::TrimWhitespaceASCII(return_value, base::TRIM_ALL, &return_value);
+ return return_value;
+ }
+
+ LOG(ERROR) << "Unable to read crossystem key " << key;
+ return "";
+}
+
+string HardwareChromeOS::GetHardwareClass() const {
+ if (USE_HWID_OVERRIDE) {
+ return HwidOverride::Read(base::FilePath("/"));
+ }
+ return ReadValueFromCrosSystem("hwid");
+}
+
+string HardwareChromeOS::GetDeviceRequisition() const {
+#if USE_CFM
+ const char* kLocalStatePath = "/home/chronos/Local State";
+ return ReadDeviceRequisition(base::FilePath(kLocalStatePath));
+#else
+ return "";
+#endif
+}
+
+int HardwareChromeOS::GetMinKernelKeyVersion() const {
+ return VbGetSystemPropertyInt("tpm_kernver");
+}
+
+int HardwareChromeOS::GetMaxFirmwareKeyRollforward() const {
+ return VbGetSystemPropertyInt("firmware_max_rollforward");
+}
+
+bool HardwareChromeOS::SetMaxFirmwareKeyRollforward(
+ int firmware_max_rollforward) {
+ // Not all devices have this field yet. So first try to read
+ // it and if there is an error just fail.
+ if (GetMaxFirmwareKeyRollforward() == -1)
+ return false;
+
+ return VbSetSystemPropertyInt("firmware_max_rollforward",
+ firmware_max_rollforward) == 0;
+}
+
+int HardwareChromeOS::GetMinFirmwareKeyVersion() const {
+ return VbGetSystemPropertyInt("tpm_fwver");
+}
+
+bool HardwareChromeOS::SetMaxKernelKeyRollforward(int kernel_max_rollforward) {
+ return VbSetSystemPropertyInt("kernel_max_rollforward",
+ kernel_max_rollforward) == 0;
+}
+
+int HardwareChromeOS::GetPowerwashCount() const {
+ int powerwash_count;
+ base::FilePath marker_path =
+ base::FilePath(kPowerwashSafeDirectory).Append(kPowerwashCountMarker);
+ string contents;
+ if (!utils::ReadFile(marker_path.value(), &contents))
+ return -1;
+ base::TrimWhitespaceASCII(contents, base::TRIM_TRAILING, &contents);
+ if (!base::StringToInt(contents, &powerwash_count))
+ return -1;
+ return powerwash_count;
+}
+
+bool HardwareChromeOS::SchedulePowerwash(bool save_rollback_data) {
+ if (save_rollback_data) {
+ if (!utils::WriteFile(kRollbackSaveMarkerFile, nullptr, 0)) {
+ PLOG(ERROR) << "Error in creating rollback save marker file: "
+ << kRollbackSaveMarkerFile << ". Rollback will not"
+ << " preserve any data.";
+ } else {
+ LOG(INFO) << "Rollback data save has been scheduled on next shutdown.";
+ }
+ }
+
+ const char* powerwash_command =
+ save_rollback_data ? kRollbackPowerwashCommand : kPowerwashCommand;
+ bool result = utils::WriteFile(
+ kPowerwashMarkerFile, powerwash_command, strlen(powerwash_command));
+ if (result) {
+ LOG(INFO) << "Created " << kPowerwashMarkerFile
+ << " to powerwash on next reboot ("
+ << "save_rollback_data=" << save_rollback_data << ")";
+ } else {
+ PLOG(ERROR) << "Error in creating powerwash marker file: "
+ << kPowerwashMarkerFile;
+ }
+
+ return result;
+}
+
+bool HardwareChromeOS::CancelPowerwash() {
+ bool result = base::DeleteFile(base::FilePath(kPowerwashMarkerFile), false);
+
+ if (result) {
+ LOG(INFO) << "Successfully deleted the powerwash marker file : "
+ << kPowerwashMarkerFile;
+ } else {
+ PLOG(ERROR) << "Could not delete the powerwash marker file : "
+ << kPowerwashMarkerFile;
+ }
+
+ // Delete the rollback save marker file if it existed.
+ if (!base::DeleteFile(base::FilePath(kRollbackSaveMarkerFile), false)) {
+ PLOG(ERROR) << "Could not remove rollback save marker";
+ }
+
+ return result;
+}
+
+bool HardwareChromeOS::GetNonVolatileDirectory(base::FilePath* path) const {
+ *path = base::FilePath(constants::kNonVolatileDirectory);
+ return true;
+}
+
+bool HardwareChromeOS::GetPowerwashSafeDirectory(base::FilePath* path) const {
+ *path = base::FilePath(kPowerwashSafeDirectory);
+ return true;
+}
+
+int64_t HardwareChromeOS::GetBuildTimestamp() const {
+ // TODO(senj): implement this in Chrome OS.
+ return 0;
+}
+
+void HardwareChromeOS::LoadConfig(const string& root_prefix, bool normal_mode) {
+ brillo::KeyValueStore store;
+
+ if (normal_mode) {
+ store.Load(base::FilePath(root_prefix + kConfigFilePath));
+ } else {
+ if (store.Load(base::FilePath(root_prefix + kStatefulPartition +
+ kConfigFilePath))) {
+ LOG(INFO) << "UpdateManager Config loaded from stateful partition.";
+ } else {
+ store.Load(base::FilePath(root_prefix + kConfigFilePath));
+ }
+ }
+
+ if (!store.GetBoolean(kConfigOptsIsOOBEEnabled, &is_oobe_enabled_))
+ is_oobe_enabled_ = true; // Default value.
+}
+
+bool HardwareChromeOS::GetFirstActiveOmahaPingSent() const {
+ string active_ping_str;
+ if (!utils::GetVpdValue(kActivePingKey, &active_ping_str)) {
+ return false;
+ }
+
+ int active_ping;
+ if (active_ping_str.empty() ||
+ !base::StringToInt(active_ping_str, &active_ping)) {
+ LOG(INFO) << "Failed to parse active_ping value: " << active_ping_str;
+ return false;
+ }
+ return static_cast<bool>(active_ping);
+}
+
+bool HardwareChromeOS::SetFirstActiveOmahaPingSent() {
+ int exit_code = 0;
+ string output, error;
+ vector<string> vpd_set_cmd = {
+ "vpd", "-i", "RW_VPD", "-s", string(kActivePingKey) + "=1"};
+ if (!Subprocess::SynchronousExec(vpd_set_cmd, &exit_code, &output, &error) ||
+ exit_code) {
+ LOG(ERROR) << "Failed to set vpd key for " << kActivePingKey
+ << " with exit code: " << exit_code << " with output: " << output
+ << " and error: " << error;
+ return false;
+ } else if (!error.empty()) {
+ LOG(INFO) << "vpd succeeded but with error logs: " << error;
+ }
+
+ vector<string> vpd_dump_cmd = {"dump_vpd_log", "--force"};
+ if (!Subprocess::SynchronousExec(vpd_dump_cmd, &exit_code, &output, &error) ||
+ exit_code) {
+ LOG(ERROR) << "Failed to cache " << kActivePingKey << " using dump_vpd_log"
+ << " with exit code: " << exit_code << " with output: " << output
+ << " and error: " << error;
+ return false;
+ } else if (!error.empty()) {
+ LOG(INFO) << "dump_vpd_log succeeded but with error logs: " << error;
+ }
+ return true;
+}
+
+void HardwareChromeOS::SetWarmReset(bool warm_reset) {}
+
+std::string HardwareChromeOS::GetVersionForLogging(
+ const std::string& partition_name) const {
+ // TODO(zhangkelvin) Implement per-partition timestamp for Chrome OS.
+ return "";
+}
+
+ErrorCode HardwareChromeOS::IsPartitionUpdateValid(
+ const std::string& partition_name, const std::string& new_version) const {
+ // TODO(zhangkelvin) Implement per-partition timestamp for Chrome OS.
+ return ErrorCode::kSuccess;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/hardware_chromeos.h b/cros/hardware_chromeos.h
new file mode 100644
index 0000000..de84d78
--- /dev/null
+++ b/cros/hardware_chromeos.h
@@ -0,0 +1,88 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_HARDWARE_CHROMEOS_H_
+#define UPDATE_ENGINE_CROS_HARDWARE_CHROMEOS_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <debugd/dbus-proxies.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements the real interface with Chrome OS verified boot and recovery
+// process.
+class HardwareChromeOS final : public HardwareInterface {
+ public:
+ HardwareChromeOS() = default;
+ ~HardwareChromeOS() override = default;
+
+ void Init();
+
+ // HardwareInterface methods.
+ bool IsOfficialBuild() const override;
+ bool IsNormalBootMode() const override;
+ bool AreDevFeaturesEnabled() const override;
+ bool IsOOBEEnabled() const override;
+ bool IsOOBEComplete(base::Time* out_time_of_oobe) const override;
+ std::string GetHardwareClass() const override;
+ std::string GetDeviceRequisition() const override;
+ int GetMinKernelKeyVersion() const override;
+ int GetMinFirmwareKeyVersion() const override;
+ int GetMaxFirmwareKeyRollforward() const override;
+ bool SetMaxFirmwareKeyRollforward(int firmware_max_rollforward) override;
+ bool SetMaxKernelKeyRollforward(int kernel_max_rollforward) override;
+ int GetPowerwashCount() const override;
+ bool SchedulePowerwash(bool save_rollback_data) override;
+ bool CancelPowerwash() override;
+ bool GetNonVolatileDirectory(base::FilePath* path) const override;
+ bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+ int64_t GetBuildTimestamp() const override;
+ bool AllowDowngrade() const override { return false; }
+ bool GetFirstActiveOmahaPingSent() const override;
+ bool SetFirstActiveOmahaPingSent() override;
+ void SetWarmReset(bool warm_reset) override;
+ std::string GetVersionForLogging(
+ const std::string& partition_name) const override;
+ ErrorCode IsPartitionUpdateValid(
+ const std::string& partition_name,
+ const std::string& new_version) const override;
+
+ private:
+ friend class HardwareChromeOSTest;
+
+ // Load the update manager config flags (is_oobe_enabled flag) from the
+ // appropriate location based on whether we are in a normal mode boot (as
+ // passed in |normal_mode|) prefixing the paths with |root_prefix|.
+ void LoadConfig(const std::string& root_prefix, bool normal_mode);
+
+ bool is_oobe_enabled_;
+
+ std::unique_ptr<org::chromium::debugdProxyInterface> debugd_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(HardwareChromeOS);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_HARDWARE_CHROMEOS_H_
diff --git a/cros/hardware_chromeos_unittest.cc b/cros/hardware_chromeos_unittest.cc
new file mode 100644
index 0000000..50bced6
--- /dev/null
+++ b/cros/hardware_chromeos_unittest.cc
@@ -0,0 +1,90 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/hardware_chromeos.h"
+
+#include <memory>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class HardwareChromeOSTest : public ::testing::Test {
+ protected:
+ void SetUp() override { ASSERT_TRUE(root_dir_.CreateUniqueTempDir()); }
+
+ void WriteStatefulConfig(const string& config) {
+ base::FilePath kFile(root_dir_.GetPath().value() + kStatefulPartition +
+ "/etc/update_manager.conf");
+ ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
+ ASSERT_TRUE(WriteFileString(kFile.value(), config));
+ }
+
+ void WriteRootfsConfig(const string& config) {
+ base::FilePath kFile(root_dir_.GetPath().value() +
+ "/etc/update_manager.conf");
+ ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
+ ASSERT_TRUE(WriteFileString(kFile.value(), config));
+ }
+
+ // Helper method to call HardwareChromeOS::LoadConfig with the test directory.
+ void CallLoadConfig(bool normal_mode) {
+ hardware_.LoadConfig(root_dir_.GetPath().value(), normal_mode);
+ }
+
+ HardwareChromeOS hardware_;
+ base::ScopedTempDir root_dir_;
+};
+
+TEST_F(HardwareChromeOSTest, NoFileFoundReturnsDefault) {
+ CallLoadConfig(true /* normal_mode */);
+ EXPECT_TRUE(hardware_.IsOOBEEnabled());
+}
+
+TEST_F(HardwareChromeOSTest, DontReadStatefulInNormalMode) {
+ WriteStatefulConfig("is_oobe_enabled=false");
+
+ CallLoadConfig(true /* normal_mode */);
+ EXPECT_TRUE(hardware_.IsOOBEEnabled());
+}
+
+TEST_F(HardwareChromeOSTest, ReadStatefulInDevMode) {
+ WriteRootfsConfig("is_oobe_enabled=true");
+ // Since the stateful is present, we should read that one.
+ WriteStatefulConfig("is_oobe_enabled=false");
+
+ CallLoadConfig(false /* normal_mode */);
+ EXPECT_FALSE(hardware_.IsOOBEEnabled());
+}
+
+TEST_F(HardwareChromeOSTest, ReadRootfsIfStatefulNotFound) {
+ WriteRootfsConfig("is_oobe_enabled=false");
+
+ CallLoadConfig(false /* normal_mode */);
+ EXPECT_FALSE(hardware_.IsOOBEEnabled());
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/image_properties.h b/cros/image_properties.h
new file mode 100644
index 0000000..4957d12
--- /dev/null
+++ b/cros/image_properties.h
@@ -0,0 +1,102 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// This module abstracts the properties tied to the current running image. These
+// properties are meant to be constant during the life of this daemon, but can
+// be modified in dev-move or non-official builds.
+
+#ifndef UPDATE_ENGINE_CROS_IMAGE_PROPERTIES_H_
+#define UPDATE_ENGINE_CROS_IMAGE_PROPERTIES_H_
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// The read-only system properties of the running image.
+struct ImageProperties {
+ // The product id of the image used for all channels, except canary.
+ std::string product_id;
+ // The canary-channel product id.
+ std::string canary_product_id;
+
+ // The product version of this image.
+ std::string version;
+
+ // The version of all product components in key values pairs.
+ std::string product_components;
+
+ // A unique string that identifies this build. Normally a combination of the
+ // the version, signing keys and build target.
+ std::string build_fingerprint;
+
+ // The Android build type, should be either 'user', 'userdebug' or 'eng'.
+ // It's empty string on other platform.
+ std::string build_type;
+
+ // The board name this image was built for.
+ std::string board;
+
+ // The release channel this image was obtained from.
+ std::string current_channel;
+
+ // Whether we allow arbitrary channels instead of just the ones listed in
+ // kChannelsByStability.
+ bool allow_arbitrary_channels = false;
+
+ // The Omaha URL this image should get updates from.
+ std::string omaha_url;
+};
+
+// The mutable image properties are read-write image properties, initialized
+// with values from the image but can be modified by storing them in the
+// stateful partition.
+struct MutableImageProperties {
+ // The release channel we are tracking.
+ std::string target_channel;
+
+ // Whether powerwash is allowed when downloading an update for the selected
+ // target_channel.
+ bool is_powerwash_allowed{false};
+};
+
+// Loads all the image properties from the running system. In case of error
+// loading any of these properties from the read-only system image a default
+// value may be returned instead.
+ImageProperties LoadImageProperties(SystemState* system_state);
+
+// Loads the mutable image properties from the stateful partition if found or
+// the system image otherwise.
+MutableImageProperties LoadMutableImageProperties(SystemState* system_state);
+
+// Stores the mutable image properties in the stateful partition. Returns
+// whether the operation succeeded.
+bool StoreMutableImageProperties(SystemState* system_state,
+ const MutableImageProperties& properties);
+
+// Logs the image properties.
+void LogImageProperties();
+
+// Sets the root_prefix used to load files from during unittests to
+// |test_root_prefix|. Passing a nullptr value resets it to the default.
+namespace test {
+void SetImagePropertiesRootPrefix(const char* test_root_prefix);
+} // namespace test
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_IMAGE_PROPERTIES_H_
diff --git a/cros/image_properties_chromeos.cc b/cros/image_properties_chromeos.cc
new file mode 100644
index 0000000..c22da7c
--- /dev/null
+++ b/cros/image_properties_chromeos.cc
@@ -0,0 +1,168 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/image_properties.h"
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <brillo/key_value_store.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/common/utils.h"
+
+namespace {
+
+const char kLsbRelease[] = "/etc/lsb-release";
+
+const char kLsbReleaseAppIdKey[] = "CHROMEOS_RELEASE_APPID";
+const char kLsbReleaseAutoUpdateServerKey[] = "CHROMEOS_AUSERVER";
+const char kLsbReleaseBoardAppIdKey[] = "CHROMEOS_BOARD_APPID";
+const char kLsbReleaseBoardKey[] = "CHROMEOS_RELEASE_BOARD";
+const char kLsbReleaseCanaryAppIdKey[] = "CHROMEOS_CANARY_APPID";
+const char kLsbReleaseIsPowerwashAllowedKey[] = "CHROMEOS_IS_POWERWASH_ALLOWED";
+const char kLsbReleaseUpdateChannelKey[] = "CHROMEOS_RELEASE_TRACK";
+const char kLsbReleaseVersionKey[] = "CHROMEOS_RELEASE_VERSION";
+
+const char kDefaultAppId[] = "{87efface-864d-49a5-9bb3-4b050a7c227a}";
+
+// A prefix added to the path, used for testing.
+const char* root_prefix = nullptr;
+
+std::string GetStringWithDefault(const brillo::KeyValueStore& store,
+ const std::string& key,
+ const std::string& default_value) {
+ std::string result;
+ if (store.GetString(key, &result))
+ return result;
+ LOG(INFO) << "Cannot load ImageProperty " << key << ", using default value "
+ << default_value;
+ return default_value;
+}
+
+enum class LsbReleaseSource {
+ kSystem,
+ kStateful,
+};
+
+// Loads the lsb-release properties into the key-value |store| reading the file
+// from either the system image or the stateful partition as specified by
+// |source|. The loaded values are added to the store, possibly overriding
+// existing values.
+void LoadLsbRelease(LsbReleaseSource source, brillo::KeyValueStore* store) {
+ std::string path;
+ if (root_prefix)
+ path = root_prefix;
+ if (source == LsbReleaseSource::kStateful)
+ path += chromeos_update_engine::kStatefulPartition;
+ store->Load(base::FilePath(path + kLsbRelease));
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace test {
+void SetImagePropertiesRootPrefix(const char* test_root_prefix) {
+ root_prefix = test_root_prefix;
+}
+} // namespace test
+
+ImageProperties LoadImageProperties(SystemState* system_state) {
+ ImageProperties result;
+
+ brillo::KeyValueStore lsb_release;
+ LoadLsbRelease(LsbReleaseSource::kSystem, &lsb_release);
+ result.current_channel = GetStringWithDefault(
+ lsb_release, kLsbReleaseUpdateChannelKey, "stable-channel");
+
+ // In dev-mode and unofficial build we can override the image properties set
+ // in the system image with the ones from the stateful partition, except the
+ // channel of the current image.
+ HardwareInterface* const hardware = system_state->hardware();
+ if (!hardware->IsOfficialBuild() || !hardware->IsNormalBootMode())
+ LoadLsbRelease(LsbReleaseSource::kStateful, &lsb_release);
+
+ // The release_app_id is used as the default appid, but can be override by
+ // the board appid in the general case or the canary appid for the canary
+ // channel only.
+ std::string release_app_id =
+ GetStringWithDefault(lsb_release, kLsbReleaseAppIdKey, kDefaultAppId);
+
+ result.product_id = GetStringWithDefault(
+ lsb_release, kLsbReleaseBoardAppIdKey, release_app_id);
+ result.canary_product_id = GetStringWithDefault(
+ lsb_release, kLsbReleaseCanaryAppIdKey, release_app_id);
+ result.board = GetStringWithDefault(lsb_release, kLsbReleaseBoardKey, "");
+ result.version = GetStringWithDefault(lsb_release, kLsbReleaseVersionKey, "");
+ result.omaha_url =
+ GetStringWithDefault(lsb_release,
+ kLsbReleaseAutoUpdateServerKey,
+ constants::kOmahaDefaultProductionURL);
+ // Build fingerprint not used in Chrome OS.
+ result.build_fingerprint = "";
+ result.allow_arbitrary_channels = false;
+
+ return result;
+}
+
+MutableImageProperties LoadMutableImageProperties(SystemState* system_state) {
+ MutableImageProperties result;
+ brillo::KeyValueStore lsb_release;
+ LoadLsbRelease(LsbReleaseSource::kSystem, &lsb_release);
+ LoadLsbRelease(LsbReleaseSource::kStateful, &lsb_release);
+ result.target_channel = GetStringWithDefault(
+ lsb_release, kLsbReleaseUpdateChannelKey, "stable-channel");
+ if (!lsb_release.GetBoolean(kLsbReleaseIsPowerwashAllowedKey,
+ &result.is_powerwash_allowed))
+ result.is_powerwash_allowed = false;
+ return result;
+}
+
+bool StoreMutableImageProperties(SystemState* system_state,
+ const MutableImageProperties& properties) {
+ brillo::KeyValueStore lsb_release;
+ LoadLsbRelease(LsbReleaseSource::kStateful, &lsb_release);
+ lsb_release.SetString(kLsbReleaseUpdateChannelKey, properties.target_channel);
+ lsb_release.SetBoolean(kLsbReleaseIsPowerwashAllowedKey,
+ properties.is_powerwash_allowed);
+
+ std::string root_prefix_str = root_prefix ? root_prefix : "";
+ base::FilePath path(root_prefix_str + kStatefulPartition + kLsbRelease);
+ if (!base::DirectoryExists(path.DirName()))
+ base::CreateDirectory(path.DirName());
+ return lsb_release.Save(path);
+}
+
+void LogImageProperties() {
+ std::string lsb_release;
+ if (utils::ReadFile(kLsbRelease, &lsb_release)) {
+ LOG(INFO) << "lsb-release inside the old rootfs:\n" << lsb_release;
+ }
+
+ std::string stateful_lsb_release;
+ if (utils::ReadFile(std::string(kStatefulPartition) + kLsbRelease,
+ &stateful_lsb_release)) {
+ LOG(INFO) << "stateful lsb-release:\n" << stateful_lsb_release;
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/image_properties_chromeos_unittest.cc b/cros/image_properties_chromeos_unittest.cc
new file mode 100644
index 0000000..4822995
--- /dev/null
+++ b/cros/image_properties_chromeos_unittest.cc
@@ -0,0 +1,172 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/image_properties.h"
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/cros/fake_system_state.h"
+
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class ImagePropertiesTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Create a uniquely named test directory.
+ ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
+ EXPECT_TRUE(base::CreateDirectory(tempdir_.GetPath().Append("etc")));
+ EXPECT_TRUE(base::CreateDirectory(base::FilePath(
+ tempdir_.GetPath().value() + kStatefulPartition + "/etc")));
+ test::SetImagePropertiesRootPrefix(tempdir_.GetPath().value().c_str());
+ SetLockDown(false);
+ }
+
+ void SetLockDown(bool locked_down) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(locked_down);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(locked_down);
+ }
+
+ FakeSystemState fake_system_state_;
+
+ base::ScopedTempDir tempdir_;
+};
+
+TEST_F(ImagePropertiesTest, SimpleTest) {
+ ASSERT_TRUE(
+ WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ ImageProperties props = LoadImageProperties(&fake_system_state_);
+ EXPECT_EQ("arm-generic", props.board);
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", props.product_id);
+ EXPECT_EQ("0.2.2.3", props.version);
+ EXPECT_EQ("dev-channel", props.current_channel);
+ EXPECT_EQ("http://www.google.com", props.omaha_url);
+}
+
+TEST_F(ImagePropertiesTest, AppIDTest) {
+ ASSERT_TRUE(WriteFileString(
+ tempdir_.GetPath().Append("etc/lsb-release").value(),
+ "CHROMEOS_RELEASE_APPID={58c35cef-9d30-476e-9098-ce20377d535d}"));
+ ImageProperties props = LoadImageProperties(&fake_system_state_);
+ EXPECT_EQ("{58c35cef-9d30-476e-9098-ce20377d535d}", props.product_id);
+}
+
+TEST_F(ImagePropertiesTest, ConfusingReleaseTest) {
+ ASSERT_TRUE(
+ WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+ "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3"));
+ ImageProperties props = LoadImageProperties(&fake_system_state_);
+ EXPECT_EQ("0.2.2.3", props.version);
+}
+
+TEST_F(ImagePropertiesTest, MissingVersionTest) {
+ ImageProperties props = LoadImageProperties(&fake_system_state_);
+ EXPECT_EQ("", props.version);
+}
+
+TEST_F(ImagePropertiesTest, OverrideTest) {
+ ASSERT_TRUE(
+ WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ tempdir_.GetPath().value() + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=beta-channel\n"
+ "CHROMEOS_AUSERVER=https://www.google.com"));
+ ImageProperties props = LoadImageProperties(&fake_system_state_);
+ EXPECT_EQ("x86-generic", props.board);
+ EXPECT_EQ("dev-channel", props.current_channel);
+ EXPECT_EQ("https://www.google.com", props.omaha_url);
+ MutableImageProperties mutable_props =
+ LoadMutableImageProperties(&fake_system_state_);
+ EXPECT_EQ("beta-channel", mutable_props.target_channel);
+}
+
+TEST_F(ImagePropertiesTest, OverrideLockDownTest) {
+ ASSERT_TRUE(
+ WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=https://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ tempdir_.GetPath().value() + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ SetLockDown(true);
+ ImageProperties props = LoadImageProperties(&fake_system_state_);
+ EXPECT_EQ("arm-generic", props.board);
+ EXPECT_EQ("dev-channel", props.current_channel);
+ EXPECT_EQ("https://www.google.com", props.omaha_url);
+ MutableImageProperties mutable_props =
+ LoadMutableImageProperties(&fake_system_state_);
+ EXPECT_EQ("stable-channel", mutable_props.target_channel);
+}
+
+TEST_F(ImagePropertiesTest, BoardAppIdUsedForNonCanaryChannelTest) {
+ ASSERT_TRUE(
+ WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_BOARD_APPID=b\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ ImageProperties props = LoadImageProperties(&fake_system_state_);
+ EXPECT_EQ("stable-channel", props.current_channel);
+ EXPECT_EQ("b", props.product_id);
+}
+
+TEST_F(ImagePropertiesTest, CanaryAppIdUsedForCanaryChannelTest) {
+ ASSERT_TRUE(
+ WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_BOARD_APPID=b\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+ ImageProperties props = LoadImageProperties(&fake_system_state_);
+ EXPECT_EQ("canary-channel", props.current_channel);
+ EXPECT_EQ("c", props.canary_product_id);
+}
+
+TEST_F(ImagePropertiesTest, ReleaseAppIdUsedAsDefaultTest) {
+ ASSERT_TRUE(
+ WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ ImageProperties props = LoadImageProperties(&fake_system_state_);
+ EXPECT_EQ("stable-channel", props.current_channel);
+ EXPECT_EQ("r", props.product_id);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/logging.cc b/cros/logging.cc
new file mode 100644
index 0000000..e09166c
--- /dev/null
+++ b/cros/logging.cc
@@ -0,0 +1,91 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/logging.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+constexpr char kSystemLogsRoot[] = "/var/log";
+
+void SetupLogSymlink(const string& symlink_path, const string& log_path) {
+ // TODO(petkov): To ensure a smooth transition between non-timestamped and
+ // timestamped logs, move an existing log to start the first timestamped
+ // one. This code can go away once all clients are switched to this version or
+ // we stop caring about the old-style logs.
+ if (utils::FileExists(symlink_path.c_str()) &&
+ !utils::IsSymlink(symlink_path.c_str())) {
+ base::ReplaceFile(
+ base::FilePath(symlink_path), base::FilePath(log_path), nullptr);
+ }
+ base::DeleteFile(base::FilePath(symlink_path), true);
+ if (symlink(log_path.c_str(), symlink_path.c_str()) == -1) {
+ PLOG(ERROR) << "Unable to create symlink " << symlink_path
+ << " pointing at " << log_path;
+ }
+}
+
+string SetupLogFile(const string& kLogsRoot) {
+ const string kLogSymlink = kLogsRoot + "/update_engine.log";
+ const string kLogsDir = kLogsRoot + "/update_engine";
+ const string kLogPath =
+ base::StringPrintf("%s/update_engine.%s",
+ kLogsDir.c_str(),
+ utils::GetTimeAsString(::time(nullptr)).c_str());
+ mkdir(kLogsDir.c_str(), 0755);
+ SetupLogSymlink(kLogSymlink, kLogPath);
+ return kLogSymlink;
+}
+
+} // namespace
+
+void SetupLogging(bool log_to_system, bool log_to_file) {
+ logging::LoggingSettings log_settings;
+ log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ log_settings.logging_dest = static_cast<logging::LoggingDestination>(
+ (log_to_system ? logging::LOG_TO_SYSTEM_DEBUG_LOG : 0) |
+ (log_to_file ? logging::LOG_TO_FILE : 0));
+ log_settings.log_file = nullptr;
+
+ string log_file;
+ if (log_to_file) {
+ log_file = SetupLogFile(kSystemLogsRoot);
+ log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+#if BASE_VER < 780000
+ log_settings.log_file = log_file.c_str();
+#else
+ log_settings.log_file_path = log_file.c_str();
+#endif
+ }
+ logging::InitLogging(log_settings);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/metrics_reporter_omaha.cc b/cros/metrics_reporter_omaha.cc
new file mode 100644
index 0000000..2cc0de5
--- /dev/null
+++ b/cros/metrics_reporter_omaha.cc
@@ -0,0 +1,600 @@
+//
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/metrics_reporter_omaha.h"
+
+#include <memory>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <metrics/metrics_library.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/omaha_request_params.h"
+#include "update_engine/metrics_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+
+namespace chromeos_update_engine {
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+const char kMetricDailyOSAgeDays[] = "UpdateEngine.Daily.OSAgeDays";
+
+// UpdateEngine.Check.* metrics.
+const char kMetricCheckDownloadErrorCode[] =
+ "UpdateEngine.Check.DownloadErrorCode";
+const char kMetricCheckReaction[] = "UpdateEngine.Check.Reaction";
+const char kMetricCheckResult[] = "UpdateEngine.Check.Result";
+const char kMetricCheckTargetVersion[] = "UpdateEngine.Check.TargetVersion";
+const char kMetricCheckRollbackTargetVersion[] =
+ "UpdateEngine.Check.RollbackTargetVersion";
+const char kMetricCheckTimeSinceLastCheckMinutes[] =
+ "UpdateEngine.Check.TimeSinceLastCheckMinutes";
+const char kMetricCheckTimeSinceLastCheckUptimeMinutes[] =
+ "UpdateEngine.Check.TimeSinceLastCheckUptimeMinutes";
+
+// UpdateEngine.Attempt.* metrics.
+const char kMetricAttemptNumber[] = "UpdateEngine.Attempt.Number";
+const char kMetricAttemptPayloadType[] = "UpdateEngine.Attempt.PayloadType";
+const char kMetricAttemptPayloadSizeMiB[] =
+ "UpdateEngine.Attempt.PayloadSizeMiB";
+const char kMetricAttemptConnectionType[] =
+ "UpdateEngine.Attempt.ConnectionType";
+const char kMetricAttemptDurationMinutes[] =
+ "UpdateEngine.Attempt.DurationMinutes";
+const char kMetricAttemptDurationUptimeMinutes[] =
+ "UpdateEngine.Attempt.DurationUptimeMinutes";
+const char kMetricAttemptTimeSinceLastAttemptMinutes[] =
+ "UpdateEngine.Attempt.TimeSinceLastAttemptMinutes";
+const char kMetricAttemptTimeSinceLastAttemptUptimeMinutes[] =
+ "UpdateEngine.Attempt.TimeSinceLastAttemptUptimeMinutes";
+const char kMetricAttemptPayloadBytesDownloadedMiB[] =
+ "UpdateEngine.Attempt.PayloadBytesDownloadedMiB";
+const char kMetricAttemptPayloadDownloadSpeedKBps[] =
+ "UpdateEngine.Attempt.PayloadDownloadSpeedKBps";
+const char kMetricAttemptDownloadSource[] =
+ "UpdateEngine.Attempt.DownloadSource";
+const char kMetricAttemptResult[] = "UpdateEngine.Attempt.Result";
+const char kMetricAttemptInternalErrorCode[] =
+ "UpdateEngine.Attempt.InternalErrorCode";
+const char kMetricAttemptDownloadErrorCode[] =
+ "UpdateEngine.Attempt.DownloadErrorCode";
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+const char kMetricSuccessfulUpdateAttemptCount[] =
+ "UpdateEngine.SuccessfulUpdate.AttemptCount";
+const char kMetricSuccessfulUpdateBytesDownloadedMiB[] =
+ "UpdateEngine.SuccessfulUpdate.BytesDownloadedMiB";
+const char kMetricSuccessfulUpdateDownloadOverheadPercentage[] =
+ "UpdateEngine.SuccessfulUpdate.DownloadOverheadPercentage";
+const char kMetricSuccessfulUpdateDownloadSourcesUsed[] =
+ "UpdateEngine.SuccessfulUpdate.DownloadSourcesUsed";
+const char kMetricSuccessfulUpdateDurationFromSeenDays[] =
+ "UpdateEngine.SuccessfulUpdate.DurationFromSeenDays.NoTimeRestriction";
+const char kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays[] =
+ "UpdateEngine.SuccessfulUpdate.DurationFromSeenDays.TimeRestricted";
+const char kMetricSuccessfulUpdatePayloadType[] =
+ "UpdateEngine.SuccessfulUpdate.PayloadType";
+const char kMetricSuccessfulUpdatePayloadSizeMiB[] =
+ "UpdateEngine.SuccessfulUpdate.PayloadSizeMiB";
+const char kMetricSuccessfulUpdateRebootCount[] =
+ "UpdateEngine.SuccessfulUpdate.RebootCount";
+const char kMetricSuccessfulUpdateTotalDurationMinutes[] =
+ "UpdateEngine.SuccessfulUpdate.TotalDurationMinutes";
+const char kMetricSuccessfulUpdateTotalDurationUptimeMinutes[] =
+ "UpdateEngine.SuccessfulUpdate.TotalDurationUptimeMinutes";
+const char kMetricSuccessfulUpdateUpdatesAbandonedCount[] =
+ "UpdateEngine.SuccessfulUpdate.UpdatesAbandonedCount";
+const char kMetricSuccessfulUpdateUrlSwitchCount[] =
+ "UpdateEngine.SuccessfulUpdate.UrlSwitchCount";
+
+// UpdateEngine.Rollback.* metric.
+const char kMetricRollbackResult[] = "UpdateEngine.Rollback.Result";
+
+// UpdateEngine.EnterpriseRollback.* metrics.
+const char kMetricEnterpriseRollbackFailure[] =
+ "UpdateEngine.EnterpriseRollback.Failure";
+const char kMetricEnterpriseRollbackSuccess[] =
+ "UpdateEngine.EnterpriseRollback.Success";
+
+// UpdateEngine.CertificateCheck.* metrics.
+const char kMetricCertificateCheckUpdateCheck[] =
+ "UpdateEngine.CertificateCheck.UpdateCheck";
+const char kMetricCertificateCheckDownload[] =
+ "UpdateEngine.CertificateCheck.Download";
+
+// UpdateEngine.KernelKey.* metrics.
+const char kMetricKernelMinVersion[] = "UpdateEngine.KernelKey.MinVersion";
+const char kMetricKernelMaxRollforwardVersion[] =
+ "UpdateEngine.KernelKey.MaxRollforwardVersion";
+const char kMetricKernelMaxRollforwardSetSuccess[] =
+ "UpdateEngine.KernelKey.MaxRollforwardSetSuccess";
+
+// UpdateEngine.* metrics.
+const char kMetricFailedUpdateCount[] = "UpdateEngine.FailedUpdateCount";
+const char kMetricInstallDateProvisioningSource[] =
+ "UpdateEngine.InstallDateProvisioningSource";
+const char kMetricTimeToRebootMinutes[] = "UpdateEngine.TimeToRebootMinutes";
+
+std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter() {
+ return std::make_unique<MetricsReporterOmaha>();
+}
+
+} // namespace metrics
+
+MetricsReporterOmaha::MetricsReporterOmaha()
+ : metrics_lib_(new MetricsLibrary()) {}
+
+void MetricsReporterOmaha::ReportDailyMetrics(base::TimeDelta os_age) {
+ string metric = metrics::kMetricDailyOSAgeDays;
+ metrics_lib_->SendToUMA(metric,
+ static_cast<int>(os_age.InDays()),
+ 0, // min: 0 days
+ 6 * 30, // max: 6 months (approx)
+ 50); // num_buckets
+}
+
+void MetricsReporterOmaha::ReportUpdateCheckMetrics(
+ SystemState* system_state,
+ metrics::CheckResult result,
+ metrics::CheckReaction reaction,
+ metrics::DownloadErrorCode download_error_code) {
+ string metric;
+ int value;
+ int max_value;
+
+ if (result != metrics::CheckResult::kUnset) {
+ metric = metrics::kMetricCheckResult;
+ value = static_cast<int>(result);
+ max_value = static_cast<int>(metrics::CheckResult::kNumConstants) - 1;
+ metrics_lib_->SendEnumToUMA(metric, value, max_value);
+ }
+ if (reaction != metrics::CheckReaction::kUnset) {
+ metric = metrics::kMetricCheckReaction;
+ value = static_cast<int>(reaction);
+ max_value = static_cast<int>(metrics::CheckReaction::kNumConstants) - 1;
+ metrics_lib_->SendEnumToUMA(metric, value, max_value);
+ }
+ if (download_error_code != metrics::DownloadErrorCode::kUnset) {
+ metric = metrics::kMetricCheckDownloadErrorCode;
+ value = static_cast<int>(download_error_code);
+ metrics_lib_->SendSparseToUMA(metric, value);
+ }
+
+ base::TimeDelta time_since_last;
+ if (WallclockDurationHelper(system_state,
+ kPrefsMetricsCheckLastReportingTime,
+ &time_since_last)) {
+ metric = metrics::kMetricCheckTimeSinceLastCheckMinutes;
+ metrics_lib_->SendToUMA(metric,
+ time_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30 * 24 * 60, // max: 30 days
+ 50); // num_buckets
+ }
+
+ base::TimeDelta uptime_since_last;
+ static int64_t uptime_since_last_storage = 0;
+ if (MonotonicDurationHelper(
+ system_state, &uptime_since_last_storage, &uptime_since_last)) {
+ metric = metrics::kMetricCheckTimeSinceLastCheckUptimeMinutes;
+ metrics_lib_->SendToUMA(metric,
+ uptime_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30 * 24 * 60, // max: 30 days
+ 50); // num_buckets
+ }
+
+ // First section of target version specified for the update.
+ if (system_state && system_state->request_params()) {
+ string target_version =
+ system_state->request_params()->target_version_prefix();
+ value = utils::VersionPrefix(target_version);
+ if (value != 0) {
+ metric = metrics::kMetricCheckTargetVersion;
+ metrics_lib_->SendSparseToUMA(metric, value);
+ if (system_state->request_params()->rollback_allowed()) {
+ metric = metrics::kMetricCheckRollbackTargetVersion;
+ metrics_lib_->SendSparseToUMA(metric, value);
+ }
+ }
+ }
+}
+
+void MetricsReporterOmaha::ReportAbnormallyTerminatedUpdateAttemptMetrics() {
+ string metric = metrics::kMetricAttemptResult;
+ metrics::AttemptResult attempt_result =
+ metrics::AttemptResult::kAbnormalTermination;
+
+ metrics_lib_->SendEnumToUMA(
+ metric,
+ static_cast<int>(attempt_result),
+ static_cast<int>(metrics::AttemptResult::kNumConstants));
+}
+
+void MetricsReporterOmaha::ReportUpdateAttemptMetrics(
+ SystemState* system_state,
+ int attempt_number,
+ PayloadType payload_type,
+ base::TimeDelta duration,
+ base::TimeDelta duration_uptime,
+ int64_t payload_size,
+ metrics::AttemptResult attempt_result,
+ ErrorCode internal_error_code) {
+ string metric = metrics::kMetricAttemptNumber;
+ metrics_lib_->SendToUMA(metric,
+ attempt_number,
+ 0, // min: 0 attempts
+ 49, // max: 49 attempts
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadType;
+ metrics_lib_->SendEnumToUMA(metric, payload_type, kNumPayloadTypes);
+
+ metric = metrics::kMetricAttemptDurationMinutes;
+ metrics_lib_->SendToUMA(metric,
+ duration.InMinutes(),
+ 0, // min: 0 min
+ 10 * 24 * 60, // max: 10 days
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptDurationUptimeMinutes;
+ metrics_lib_->SendToUMA(metric,
+ duration_uptime.InMinutes(),
+ 0, // min: 0 min
+ 10 * 24 * 60, // max: 10 days
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadSizeMiB;
+ int64_t payload_size_mib = payload_size / kNumBytesInOneMiB;
+ metrics_lib_->SendToUMA(metric,
+ payload_size_mib,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptResult;
+ metrics_lib_->SendEnumToUMA(
+ metric,
+ static_cast<int>(attempt_result),
+ static_cast<int>(metrics::AttemptResult::kNumConstants));
+
+ if (internal_error_code != ErrorCode::kSuccess) {
+ ReportInternalErrorCode(internal_error_code);
+ }
+
+ base::TimeDelta time_since_last;
+ if (WallclockDurationHelper(system_state,
+ kPrefsMetricsAttemptLastReportingTime,
+ &time_since_last)) {
+ metric = metrics::kMetricAttemptTimeSinceLastAttemptMinutes;
+ metrics_lib_->SendToUMA(metric,
+ time_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30 * 24 * 60, // max: 30 days
+ 50); // num_buckets
+ }
+
+ static int64_t uptime_since_last_storage = 0;
+ base::TimeDelta uptime_since_last;
+ if (MonotonicDurationHelper(
+ system_state, &uptime_since_last_storage, &uptime_since_last)) {
+ metric = metrics::kMetricAttemptTimeSinceLastAttemptUptimeMinutes;
+ metrics_lib_->SendToUMA(metric,
+ uptime_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30 * 24 * 60, // max: 30 days
+ 50); // num_buckets
+ }
+}
+
+void MetricsReporterOmaha::ReportUpdateAttemptDownloadMetrics(
+ int64_t payload_bytes_downloaded,
+ int64_t payload_download_speed_bps,
+ DownloadSource download_source,
+ metrics::DownloadErrorCode payload_download_error_code,
+ metrics::ConnectionType connection_type) {
+ string metric = metrics::kMetricAttemptPayloadBytesDownloadedMiB;
+ int64_t payload_bytes_downloaded_mib =
+ payload_bytes_downloaded / kNumBytesInOneMiB;
+ metrics_lib_->SendToUMA(metric,
+ payload_bytes_downloaded_mib,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadDownloadSpeedKBps;
+ int64_t payload_download_speed_kbps = payload_download_speed_bps / 1000;
+ metrics_lib_->SendToUMA(metric,
+ payload_download_speed_kbps,
+ 0, // min: 0 kB/s
+ 10 * 1000, // max: 10000 kB/s = 10 MB/s
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptDownloadSource;
+ metrics_lib_->SendEnumToUMA(metric, download_source, kNumDownloadSources);
+
+ if (payload_download_error_code != metrics::DownloadErrorCode::kUnset) {
+ metric = metrics::kMetricAttemptDownloadErrorCode;
+ metrics_lib_->SendSparseToUMA(
+ metric, static_cast<int>(payload_download_error_code));
+ }
+
+ metric = metrics::kMetricAttemptConnectionType;
+ metrics_lib_->SendEnumToUMA(
+ metric,
+ static_cast<int>(connection_type),
+ static_cast<int>(metrics::ConnectionType::kNumConstants));
+}
+
+void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics(
+ int attempt_count,
+ int updates_abandoned_count,
+ PayloadType payload_type,
+ int64_t payload_size,
+ int64_t num_bytes_downloaded[kNumDownloadSources],
+ int download_overhead_percentage,
+ base::TimeDelta total_duration,
+ base::TimeDelta total_duration_uptime,
+ int reboot_count,
+ int url_switch_count) {
+ string metric = metrics::kMetricSuccessfulUpdatePayloadSizeMiB;
+ int64_t mbs = payload_size / kNumBytesInOneMiB;
+ metrics_lib_->SendToUMA(metric,
+ mbs,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+
+ int64_t total_bytes = 0;
+ int download_sources_used = 0;
+ for (int i = 0; i < kNumDownloadSources + 1; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+
+ // Only consider this download source (and send byte counts) as
+ // having been used if we downloaded a non-trivial amount of bytes
+ // (e.g. at least 1 MiB) that contributed to the
+ // update. Otherwise we're going to end up with a lot of zero-byte
+ // events in the histogram.
+
+ metric = metrics::kMetricSuccessfulUpdateBytesDownloadedMiB;
+ if (i < kNumDownloadSources) {
+ metric += utils::ToString(source);
+ mbs = num_bytes_downloaded[i] / kNumBytesInOneMiB;
+ total_bytes += num_bytes_downloaded[i];
+ if (mbs > 0)
+ download_sources_used |= (1 << i);
+ } else {
+ mbs = total_bytes / kNumBytesInOneMiB;
+ }
+
+ if (mbs > 0) {
+ metrics_lib_->SendToUMA(metric,
+ mbs,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+ }
+ }
+
+ metric = metrics::kMetricSuccessfulUpdateDownloadSourcesUsed;
+ metrics_lib_->SendToUMA(metric,
+ download_sources_used,
+ 0, // min
+ (1 << kNumDownloadSources) - 1, // max
+ 1 << kNumDownloadSources); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage;
+ metrics_lib_->SendToUMA(metric,
+ download_overhead_percentage,
+ 0, // min: 0% overhead
+ 1000, // max: 1000% overhead
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateUrlSwitchCount;
+ metrics_lib_->SendToUMA(metric,
+ url_switch_count,
+ 0, // min: 0 URL switches
+ 49, // max: 49 URL switches
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateTotalDurationMinutes;
+ metrics_lib_->SendToUMA(metric,
+ static_cast<int>(total_duration.InMinutes()),
+ 0, // min: 0 min
+ 365 * 24 * 60, // max: 365 days ~= 1 year
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateTotalDurationUptimeMinutes;
+ metrics_lib_->SendToUMA(metric,
+ static_cast<int>(total_duration_uptime.InMinutes()),
+ 0, // min: 0 min
+ 30 * 24 * 60, // max: 30 days
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateRebootCount;
+ metrics_lib_->SendToUMA(metric,
+ reboot_count,
+ 0, // min: 0 reboots
+ 49, // max: 49 reboots
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdatePayloadType;
+ metrics_lib_->SendEnumToUMA(metric, payload_type, kNumPayloadTypes);
+
+ metric = metrics::kMetricSuccessfulUpdateAttemptCount;
+ metrics_lib_->SendToUMA(metric,
+ attempt_count,
+ 1, // min: 1 attempt
+ 50, // max: 50 attempts
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount;
+ metrics_lib_->SendToUMA(metric,
+ updates_abandoned_count,
+ 0, // min: 0 counts
+ 49, // max: 49 counts
+ 50); // num_buckets
+}
+
+void MetricsReporterOmaha::ReportRollbackMetrics(
+ metrics::RollbackResult result) {
+ string metric = metrics::kMetricRollbackResult;
+ int value = static_cast<int>(result);
+ metrics_lib_->SendEnumToUMA(
+ metric, value, static_cast<int>(metrics::RollbackResult::kNumConstants));
+}
+
+void MetricsReporterOmaha::ReportEnterpriseRollbackMetrics(
+ bool success, const string& rollback_version) {
+ int value = utils::VersionPrefix(rollback_version);
+ string metric = metrics::kMetricEnterpriseRollbackSuccess;
+ if (!success)
+ metric = metrics::kMetricEnterpriseRollbackFailure;
+ metrics_lib_->SendSparseToUMA(metric, value);
+}
+
+void MetricsReporterOmaha::ReportCertificateCheckMetrics(
+ ServerToCheck server_to_check, CertificateCheckResult result) {
+ string metric;
+ switch (server_to_check) {
+ case ServerToCheck::kUpdate:
+ metric = metrics::kMetricCertificateCheckUpdateCheck;
+ break;
+ case ServerToCheck::kDownload:
+ metric = metrics::kMetricCertificateCheckDownload;
+ break;
+ case ServerToCheck::kNone:
+ return;
+ }
+ metrics_lib_->SendEnumToUMA(
+ metric,
+ static_cast<int>(result),
+ static_cast<int>(CertificateCheckResult::kNumConstants));
+}
+
+void MetricsReporterOmaha::ReportFailedUpdateCount(int target_attempt) {
+ string metric = metrics::kMetricFailedUpdateCount;
+ metrics_lib_->SendToUMA(metric,
+ target_attempt,
+ 1, // min value
+ 50, // max value
+ kNumDefaultUmaBuckets);
+}
+
+void MetricsReporterOmaha::ReportTimeToReboot(int time_to_reboot_minutes) {
+ string metric = metrics::kMetricTimeToRebootMinutes;
+ metrics_lib_->SendToUMA(metric,
+ time_to_reboot_minutes,
+ 0, // min: 0 minute
+ 30 * 24 * 60, // max: 1 month (approx)
+ kNumDefaultUmaBuckets);
+}
+
+void MetricsReporterOmaha::ReportInstallDateProvisioningSource(int source,
+ int max) {
+ metrics_lib_->SendEnumToUMA(metrics::kMetricInstallDateProvisioningSource,
+ source, // Sample.
+ max);
+}
+
+void MetricsReporterOmaha::ReportInternalErrorCode(ErrorCode error_code) {
+ auto metric = metrics::kMetricAttemptInternalErrorCode;
+ metrics_lib_->SendEnumToUMA(metric,
+ static_cast<int>(error_code),
+ static_cast<int>(ErrorCode::kUmaReportedMax));
+}
+
+void MetricsReporterOmaha::ReportKeyVersionMetrics(
+ int kernel_min_version,
+ int kernel_max_rollforward_version,
+ bool kernel_max_rollforward_success) {
+ int value = kernel_min_version;
+ string metric = metrics::kMetricKernelMinVersion;
+ metrics_lib_->SendSparseToUMA(metric, value);
+
+ value = kernel_max_rollforward_version;
+ metric = metrics::kMetricKernelMaxRollforwardVersion;
+ metrics_lib_->SendSparseToUMA(metric, value);
+
+ bool bool_value = kernel_max_rollforward_success;
+ metric = metrics::kMetricKernelMaxRollforwardSetSuccess;
+ metrics_lib_->SendBoolToUMA(metric, bool_value);
+}
+
+void MetricsReporterOmaha::ReportEnterpriseUpdateSeenToDownloadDays(
+ bool has_time_restriction_policy, int time_to_update_days) {
+ string metric =
+ has_time_restriction_policy
+ ? metrics::kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays
+ : metrics::kMetricSuccessfulUpdateDurationFromSeenDays;
+
+ metrics_lib_->SendToUMA(metric,
+ time_to_update_days,
+ 1, // min: 1 days
+ 6 * 30, // max: 6 months (approx)
+ 50); // num_buckets
+}
+
+bool MetricsReporterOmaha::WallclockDurationHelper(
+ SystemState* system_state,
+ const std::string& state_variable_key,
+ TimeDelta* out_duration) {
+ bool ret = false;
+ Time now = system_state->clock()->GetWallclockTime();
+ int64_t stored_value;
+ if (system_state->prefs()->GetInt64(state_variable_key, &stored_value)) {
+ Time stored_time = Time::FromInternalValue(stored_value);
+ if (stored_time > now) {
+ LOG(ERROR) << "Stored time-stamp used for " << state_variable_key
+ << " is in the future.";
+ } else {
+ *out_duration = now - stored_time;
+ ret = true;
+ }
+ }
+
+ if (!system_state->prefs()->SetInt64(state_variable_key,
+ now.ToInternalValue())) {
+ LOG(ERROR) << "Error storing time-stamp in " << state_variable_key;
+ }
+
+ return ret;
+}
+
+bool MetricsReporterOmaha::MonotonicDurationHelper(SystemState* system_state,
+ int64_t* storage,
+ TimeDelta* out_duration) {
+ bool ret = false;
+ Time now = system_state->clock()->GetMonotonicTime();
+ if (*storage != 0) {
+ Time stored_time = Time::FromInternalValue(*storage);
+ *out_duration = now - stored_time;
+ ret = true;
+ }
+ *storage = now.ToInternalValue();
+
+ return ret;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/metrics_reporter_omaha.h b/cros/metrics_reporter_omaha.h
new file mode 100644
index 0000000..5b3fdb1
--- /dev/null
+++ b/cros/metrics_reporter_omaha.h
@@ -0,0 +1,208 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_METRICS_REPORTER_OMAHA_H_
+#define UPDATE_ENGINE_CROS_METRICS_REPORTER_OMAHA_H_
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+#include <metrics/metrics_library.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/metrics_constants.h"
+#include "update_engine/common/metrics_reporter_interface.h"
+#include "update_engine/common/system_state.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+extern const char kMetricDailyOSAgeDays[];
+
+// UpdateEngine.Check.* metrics.
+extern const char kMetricCheckDownloadErrorCode[];
+extern const char kMetricCheckReaction[];
+extern const char kMetricCheckResult[];
+extern const char kMetricCheckTargetVersion[];
+extern const char kMetricCheckRollbackTargetVersion[];
+extern const char kMetricCheckTimeSinceLastCheckMinutes[];
+extern const char kMetricCheckTimeSinceLastCheckUptimeMinutes[];
+
+// UpdateEngine.Attempt.* metrics.
+extern const char kMetricAttemptNumber[];
+extern const char kMetricAttemptPayloadType[];
+extern const char kMetricAttemptPayloadSizeMiB[];
+extern const char kMetricAttemptConnectionType[];
+extern const char kMetricAttemptDurationMinutes[];
+extern const char kMetricAttemptDurationUptimeMinutes[];
+extern const char kMetricAttemptTimeSinceLastAttemptMinutes[];
+extern const char kMetricAttemptTimeSinceLastAttemptUptimeMinutes[];
+extern const char kMetricAttemptPayloadBytesDownloadedMiB[];
+extern const char kMetricAttemptPayloadDownloadSpeedKBps[];
+extern const char kMetricAttemptDownloadSource[];
+extern const char kMetricAttemptResult[];
+extern const char kMetricAttemptInternalErrorCode[];
+extern const char kMetricAttemptDownloadErrorCode[];
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+extern const char kMetricSuccessfulUpdateAttemptCount[];
+extern const char kMetricSuccessfulUpdateBytesDownloadedMiB[];
+extern const char kMetricSuccessfulUpdateDownloadOverheadPercentage[];
+extern const char kMetricSuccessfulUpdateDownloadSourcesUsed[];
+extern const char kMetricSuccessfulUpdateDurationFromSeenDays[];
+extern const char kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays[];
+extern const char kMetricSuccessfulUpdatePayloadType[];
+extern const char kMetricSuccessfulUpdatePayloadSizeMiB[];
+extern const char kMetricSuccessfulUpdateRebootCount[];
+extern const char kMetricSuccessfulUpdateTotalDurationMinutes[];
+extern const char kMetricSuccessfulUpdateTotalDurationUptimeMinutes[];
+extern const char kMetricSuccessfulUpdateUpdatesAbandonedCount[];
+extern const char kMetricSuccessfulUpdateUrlSwitchCount[];
+
+// UpdateEngine.Rollback.* metric.
+extern const char kMetricRollbackResult[];
+
+// UpdateEngine.EnterpriseRollback.* metrics.
+extern const char kMetricEnterpriseRollbackFailure[];
+extern const char kMetricEnterpriseRollbackSuccess[];
+
+// UpdateEngine.CertificateCheck.* metrics.
+extern const char kMetricCertificateCheckUpdateCheck[];
+extern const char kMetricCertificateCheckDownload[];
+
+// UpdateEngine.KernelKey.* metrics.
+extern const char kMetricKernelMinVersion[];
+extern const char kMetricKernelMaxRollforwardVersion[];
+extern const char kMetricKernelMaxRollforwardSetSuccess[];
+
+// UpdateEngine.* metrics.
+extern const char kMetricFailedUpdateCount[];
+extern const char kMetricInstallDateProvisioningSource[];
+extern const char kMetricTimeToRebootMinutes[];
+
+} // namespace metrics
+
+class MetricsReporterOmaha : public MetricsReporterInterface {
+ public:
+ MetricsReporterOmaha();
+
+ ~MetricsReporterOmaha() override = default;
+
+ void ReportRollbackMetrics(metrics::RollbackResult result) override;
+
+ void ReportEnterpriseRollbackMetrics(
+ bool success, const std::string& rollback_version) override;
+
+ void ReportDailyMetrics(base::TimeDelta os_age) override;
+
+ void ReportUpdateCheckMetrics(
+ SystemState* system_state,
+ metrics::CheckResult result,
+ metrics::CheckReaction reaction,
+ metrics::DownloadErrorCode download_error_code) override;
+
+ void ReportUpdateAttemptMetrics(SystemState* system_state,
+ int attempt_number,
+ PayloadType payload_type,
+ base::TimeDelta duration,
+ base::TimeDelta duration_uptime,
+ int64_t payload_size,
+ metrics::AttemptResult attempt_result,
+ ErrorCode internal_error_code) override;
+
+ void ReportUpdateAttemptDownloadMetrics(
+ int64_t payload_bytes_downloaded,
+ int64_t payload_download_speed_bps,
+ DownloadSource download_source,
+ metrics::DownloadErrorCode payload_download_error_code,
+ metrics::ConnectionType connection_type) override;
+
+ void ReportAbnormallyTerminatedUpdateAttemptMetrics() override;
+
+ void ReportSuccessfulUpdateMetrics(
+ int attempt_count,
+ int updates_abandoned_count,
+ PayloadType payload_type,
+ int64_t payload_size,
+ int64_t num_bytes_downloaded[kNumDownloadSources],
+ int download_overhead_percentage,
+ base::TimeDelta total_duration,
+ base::TimeDelta total_duration_uptime,
+ int reboot_count,
+ int url_switch_count) override;
+
+ void ReportCertificateCheckMetrics(ServerToCheck server_to_check,
+ CertificateCheckResult result) override;
+
+ void ReportFailedUpdateCount(int target_attempt) override;
+
+ void ReportTimeToReboot(int time_to_reboot_minutes) override;
+
+ void ReportInstallDateProvisioningSource(int source, int max) override;
+
+ void ReportInternalErrorCode(ErrorCode error_code) override;
+
+ void ReportKeyVersionMetrics(int kernel_min_version,
+ int kernel_max_rollforward_version,
+ bool kernel_max_rollforward_success) override;
+
+ void ReportEnterpriseUpdateSeenToDownloadDays(
+ bool has_time_restriction_policy, int time_to_update_days) override;
+
+ private:
+ friend class MetricsReporterOmahaTest;
+ FRIEND_TEST(MetricsReporterOmahaTest, WallclockDurationHelper);
+ FRIEND_TEST(MetricsReporterOmahaTest, MonotonicDurationHelper);
+
+ // This function returns the duration on the wallclock since the last
+ // time it was called for the same |state_variable_key| value.
+ //
+ // If the function returns |true|, the duration (always non-negative)
+ // is returned in |out_duration|. If the function returns |false|
+ // something went wrong or there was no previous measurement.
+ bool WallclockDurationHelper(SystemState* system_state,
+ const std::string& state_variable_key,
+ base::TimeDelta* out_duration);
+
+ // This function returns the duration on the monotonic clock since the
+ // last time it was called for the same |storage| pointer.
+ //
+ // You should pass a pointer to a 64-bit integer in |storage| which
+ // should be initialized to 0.
+ //
+ // If the function returns |true|, the duration (always non-negative)
+ // is returned in |out_duration|. If the function returns |false|
+ // something went wrong or there was no previous measurement.
+ bool MonotonicDurationHelper(SystemState* system_state,
+ int64_t* storage,
+ base::TimeDelta* out_duration);
+
+ std::unique_ptr<MetricsLibraryInterface> metrics_lib_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsReporterOmaha);
+}; // class metrics
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_METRICS_REPORTER_OMAHA_H_
diff --git a/cros/metrics_reporter_omaha_unittest.cc b/cros/metrics_reporter_omaha_unittest.cc
new file mode 100644
index 0000000..a25472a
--- /dev/null
+++ b/cros/metrics_reporter_omaha_unittest.cc
@@ -0,0 +1,652 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/metrics_reporter_omaha.h"
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <metrics/metrics_library_mock.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/cros/fake_system_state.h"
+
+using base::TimeDelta;
+using testing::_;
+using testing::AnyNumber;
+using testing::Return;
+
+namespace chromeos_update_engine {
+class MetricsReporterOmahaTest : public ::testing::Test {
+ protected:
+ MetricsReporterOmahaTest() = default;
+
+ // Reset the metrics_lib_ to a mock library.
+ void SetUp() override {
+ mock_metrics_lib_ = new testing::NiceMock<MetricsLibraryMock>();
+ reporter_.metrics_lib_.reset(mock_metrics_lib_);
+ }
+
+ testing::NiceMock<MetricsLibraryMock>* mock_metrics_lib_;
+ MetricsReporterOmaha reporter_;
+};
+
+TEST_F(MetricsReporterOmahaTest, ReportDailyMetrics) {
+ TimeDelta age = TimeDelta::FromDays(10);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendToUMA(metrics::kMetricDailyOSAgeDays, _, _, _, _))
+ .Times(1);
+
+ reporter_.ReportDailyMetrics(age);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetrics) {
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ // We need to execute the report twice to test the time since last report.
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000));
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000));
+
+ metrics::CheckResult result = metrics::CheckResult::kUpdateAvailable;
+ metrics::CheckReaction reaction = metrics::CheckReaction::kIgnored;
+ metrics::DownloadErrorCode error_code =
+ metrics::DownloadErrorCode::kHttpStatus200;
+
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendEnumToUMA(metrics::kMetricCheckResult, static_cast<int>(result), _))
+ .Times(2);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendEnumToUMA(
+ metrics::kMetricCheckReaction, static_cast<int>(reaction), _))
+ .Times(2);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode,
+ static_cast<int>(error_code)))
+ .Times(2);
+
+ // Not pinned nor rollback
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricCheckTargetVersion, _))
+ .Times(0);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricCheckRollbackTargetVersion, _))
+ .Times(0);
+
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(metrics::kMetricCheckTimeSinceLastCheckMinutes, 1, _, _, _))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(
+ metrics::kMetricCheckTimeSinceLastCheckUptimeMinutes, 1, _, _, _))
+ .Times(1);
+
+ reporter_.ReportUpdateCheckMetrics(
+ &fake_system_state, result, reaction, error_code);
+
+ // Advance the clock by 1 minute and report the same metrics again.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(61000000));
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(61000000));
+ // Allow rollback
+ reporter_.ReportUpdateCheckMetrics(
+ &fake_system_state, result, reaction, error_code);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetricsPinned) {
+ FakeSystemState fake_system_state;
+
+ OmahaRequestParams params(&fake_system_state);
+ params.set_target_version_prefix("10575.");
+ params.set_rollback_allowed(false);
+ fake_system_state.set_request_params(¶ms);
+
+ metrics::CheckResult result = metrics::CheckResult::kUpdateAvailable;
+ metrics::CheckReaction reaction = metrics::CheckReaction::kIgnored;
+ metrics::DownloadErrorCode error_code =
+ metrics::DownloadErrorCode::kHttpStatus200;
+
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode, _));
+ // Target version set, but not a rollback.
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricCheckTargetVersion, 10575))
+ .Times(1);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricCheckRollbackTargetVersion, _))
+ .Times(0);
+
+ reporter_.ReportUpdateCheckMetrics(
+ &fake_system_state, result, reaction, error_code);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetricsRollback) {
+ FakeSystemState fake_system_state;
+
+ OmahaRequestParams params(&fake_system_state);
+ params.set_target_version_prefix("10575.");
+ params.set_rollback_allowed(true);
+ fake_system_state.set_request_params(¶ms);
+
+ metrics::CheckResult result = metrics::CheckResult::kUpdateAvailable;
+ metrics::CheckReaction reaction = metrics::CheckReaction::kIgnored;
+ metrics::DownloadErrorCode error_code =
+ metrics::DownloadErrorCode::kHttpStatus200;
+
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode, _));
+ // Rollback.
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricCheckTargetVersion, 10575))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricCheckRollbackTargetVersion, 10575))
+ .Times(1);
+
+ reporter_.ReportUpdateCheckMetrics(
+ &fake_system_state, result, reaction, error_code);
+}
+
+TEST_F(MetricsReporterOmahaTest,
+ ReportAbnormallyTerminatedUpdateAttemptMetrics) {
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendEnumToUMA(metrics::kMetricAttemptResult,
+ static_cast<int>(
+ metrics::AttemptResult::kAbnormalTermination),
+ _))
+ .Times(1);
+
+ reporter_.ReportAbnormallyTerminatedUpdateAttemptMetrics();
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateAttemptMetrics) {
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000));
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000));
+
+ int attempt_number = 1;
+ PayloadType payload_type = kPayloadTypeFull;
+ TimeDelta duration = TimeDelta::FromMinutes(1000);
+ TimeDelta duration_uptime = TimeDelta::FromMinutes(1000);
+
+ int64_t payload_size = 100 * kNumBytesInOneMiB;
+
+ metrics::AttemptResult attempt_result =
+ metrics::AttemptResult::kInternalError;
+ ErrorCode internal_error_code = ErrorCode::kDownloadInvalidMetadataSignature;
+
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendToUMA(metrics::kMetricAttemptNumber, attempt_number, _, _, _))
+ .Times(2);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendEnumToUMA(metrics::kMetricAttemptPayloadType,
+ static_cast<int>(payload_type),
+ _))
+ .Times(2);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendToUMA(metrics::kMetricAttemptDurationMinutes,
+ duration.InMinutes(),
+ _,
+ _,
+ _))
+ .Times(2);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendToUMA(metrics::kMetricAttemptDurationUptimeMinutes,
+ duration_uptime.InMinutes(),
+ _,
+ _,
+ _))
+ .Times(2);
+
+ // Check the report of attempt result.
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendEnumToUMA(
+ metrics::kMetricAttemptResult, static_cast<int>(attempt_result), _))
+ .Times(2);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendEnumToUMA(metrics::kMetricAttemptInternalErrorCode,
+ static_cast<int>(internal_error_code),
+ _))
+ .Times(2);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendToUMA(metrics::kMetricAttemptPayloadSizeMiB, 100, _, _, _))
+ .Times(2);
+
+ // Check the duration between two reports.
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(metrics::kMetricAttemptTimeSinceLastAttemptMinutes, 1, _, _, _))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(
+ metrics::kMetricAttemptTimeSinceLastAttemptUptimeMinutes, 1, _, _, _))
+ .Times(1);
+
+ reporter_.ReportUpdateAttemptMetrics(&fake_system_state,
+ attempt_number,
+ payload_type,
+ duration,
+ duration_uptime,
+ payload_size,
+ attempt_result,
+ internal_error_code);
+
+ // Advance the clock by 1 minute and report the same metrics again.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(61000000));
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(61000000));
+ reporter_.ReportUpdateAttemptMetrics(&fake_system_state,
+ attempt_number,
+ payload_type,
+ duration,
+ duration_uptime,
+ payload_size,
+ attempt_result,
+ internal_error_code);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateAttemptDownloadMetrics) {
+ int64_t payload_bytes_downloaded = 200 * kNumBytesInOneMiB;
+ int64_t payload_download_speed_bps = 100 * 1000;
+ DownloadSource download_source = kDownloadSourceHttpServer;
+ metrics::DownloadErrorCode payload_download_error_code =
+ metrics::DownloadErrorCode::kDownloadError;
+ metrics::ConnectionType connection_type = metrics::ConnectionType::kCellular;
+
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(metrics::kMetricAttemptPayloadBytesDownloadedMiB, 200, _, _, _))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(metrics::kMetricAttemptPayloadDownloadSpeedKBps, 100, _, _, _))
+ .Times(1);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendEnumToUMA(metrics::kMetricAttemptDownloadSource,
+ static_cast<int>(download_source),
+ _))
+ .Times(1);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricAttemptDownloadErrorCode,
+ static_cast<int>(payload_download_error_code)))
+ .Times(1);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendEnumToUMA(metrics::kMetricAttemptConnectionType,
+ static_cast<int>(connection_type),
+ _))
+ .Times(1);
+
+ reporter_.ReportUpdateAttemptDownloadMetrics(payload_bytes_downloaded,
+ payload_download_speed_bps,
+ download_source,
+ payload_download_error_code,
+ connection_type);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportSuccessfulUpdateMetrics) {
+ int attempt_count = 3;
+ int updates_abandoned_count = 2;
+ PayloadType payload_type = kPayloadTypeDelta;
+ int64_t payload_size = 200 * kNumBytesInOneMiB;
+ int64_t num_bytes_downloaded[kNumDownloadSources] = {};
+ // 200MiB payload downloaded from HttpsServer.
+ num_bytes_downloaded[0] = 200 * kNumBytesInOneMiB;
+ int download_overhead_percentage = 20;
+ TimeDelta total_duration = TimeDelta::FromMinutes(30);
+ TimeDelta total_duration_uptime = TimeDelta::FromMinutes(20);
+ int reboot_count = 2;
+ int url_switch_count = 2;
+
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(metrics::kMetricSuccessfulUpdatePayloadSizeMiB, 200, _, _, _))
+ .Times(1);
+
+ // Check the report to both BytesDownloadedMiBHttpsServer and
+ // BytesDownloadedMiB
+ std::string DownloadedMiBMetric =
+ metrics::kMetricSuccessfulUpdateBytesDownloadedMiB;
+ DownloadedMiBMetric += "HttpsServer";
+ EXPECT_CALL(*mock_metrics_lib_, SendToUMA(DownloadedMiBMetric, 200, _, _, _))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(
+ metrics::kMetricSuccessfulUpdateBytesDownloadedMiB, 200, _, _, _))
+ .Times(1);
+
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(
+ metrics::kMetricSuccessfulUpdateDownloadSourcesUsed, 1, _, _, _))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage,
+ 20,
+ _,
+ _,
+ _));
+
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendToUMA(metrics::kMetricSuccessfulUpdateUrlSwitchCount,
+ url_switch_count,
+ _,
+ _,
+ _))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(
+ metrics::kMetricSuccessfulUpdateTotalDurationMinutes, 30, _, _, _))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(metrics::kMetricSuccessfulUpdateTotalDurationUptimeMinutes,
+ 20,
+ _,
+ _,
+ _))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(
+ metrics::kMetricSuccessfulUpdateRebootCount, reboot_count, _, _, _))
+ .Times(1);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, payload_type, _))
+ .Times(1);
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(
+ metrics::kMetricSuccessfulUpdateAttemptCount, attempt_count, _, _, _))
+ .Times(1);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendToUMA(metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount,
+ updates_abandoned_count,
+ _,
+ _,
+ _))
+ .Times(1);
+
+ reporter_.ReportSuccessfulUpdateMetrics(attempt_count,
+ updates_abandoned_count,
+ payload_type,
+ payload_size,
+ num_bytes_downloaded,
+ download_overhead_percentage,
+ total_duration,
+ total_duration_uptime,
+ reboot_count,
+ url_switch_count);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportRollbackMetrics) {
+ metrics::RollbackResult result = metrics::RollbackResult::kSuccess;
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendEnumToUMA(
+ metrics::kMetricRollbackResult, static_cast<int>(result), _))
+ .Times(1);
+
+ reporter_.ReportRollbackMetrics(result);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportEnterpriseRollbackMetrics) {
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricEnterpriseRollbackSuccess, 10575))
+ .Times(1);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricEnterpriseRollbackFailure, 10323))
+ .Times(1);
+
+ reporter_.ReportEnterpriseRollbackMetrics(/*success=*/true, "10575.39.2");
+ reporter_.ReportEnterpriseRollbackMetrics(/*success=*/false, "10323.67.7");
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportCertificateCheckMetrics) {
+ ServerToCheck server_to_check = ServerToCheck::kUpdate;
+ CertificateCheckResult result = CertificateCheckResult::kValid;
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendEnumToUMA(metrics::kMetricCertificateCheckUpdateCheck,
+ static_cast<int>(result),
+ _))
+ .Times(1);
+
+ reporter_.ReportCertificateCheckMetrics(server_to_check, result);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportFailedUpdateCount) {
+ int target_attempt = 3;
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(metrics::kMetricFailedUpdateCount, target_attempt, _, _, _))
+ .Times(1);
+
+ reporter_.ReportFailedUpdateCount(target_attempt);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportTimeToReboot) {
+ int time_to_reboot_minutes = 1000;
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(
+ metrics::kMetricTimeToRebootMinutes, time_to_reboot_minutes, _, _, _))
+ .Times(1);
+
+ reporter_.ReportTimeToReboot(time_to_reboot_minutes);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportInstallDateProvisioningSource) {
+ int source = 2;
+ int max = 5;
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendEnumToUMA(metrics::kMetricInstallDateProvisioningSource, source, max))
+ .Times(1);
+
+ reporter_.ReportInstallDateProvisioningSource(source, max);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportKeyVersionMetrics) {
+ int kernel_min_version = 0x00040002;
+ int kernel_max_rollforward_version = 0xfffffffe;
+ bool kernel_max_rollforward_success = true;
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricKernelMinVersion, kernel_min_version))
+ .Times(1);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendSparseToUMA(metrics::kMetricKernelMaxRollforwardVersion,
+ kernel_max_rollforward_version))
+ .Times(1);
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendBoolToUMA(metrics::kMetricKernelMaxRollforwardSetSuccess,
+ kernel_max_rollforward_success))
+ .Times(1);
+
+ reporter_.ReportKeyVersionMetrics(kernel_min_version,
+ kernel_max_rollforward_version,
+ kernel_max_rollforward_success);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportEnterpriseUpdateSeenToDownloadDays) {
+ constexpr int kDaysToUpdate = 10;
+ constexpr int kMinBucket = 1;
+ constexpr int kMaxBucket = 6 * 30; // approximately 6 months
+ constexpr int kNumBuckets = 50;
+
+ EXPECT_CALL(*mock_metrics_lib_,
+ SendToUMA(metrics::kMetricSuccessfulUpdateDurationFromSeenDays,
+ kDaysToUpdate,
+ kMinBucket,
+ kMaxBucket,
+ kNumBuckets))
+ .Times(1);
+
+ reporter_.ReportEnterpriseUpdateSeenToDownloadDays(
+ false /* has_time_restriction_policy */, kDaysToUpdate);
+}
+
+TEST_F(MetricsReporterOmahaTest,
+ ReportEnterpriseTimeRestrictedUpdateSeenToDownloadTime) {
+ const int kDaysToUpdate = 15;
+ constexpr int kMinBucket = 1;
+ constexpr int kMaxBucket = 6 * 30; // approximately 6 months
+ constexpr int kNumBuckets = 50;
+
+ EXPECT_CALL(
+ *mock_metrics_lib_,
+ SendToUMA(
+ metrics::kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays,
+ kDaysToUpdate,
+ kMinBucket,
+ kMaxBucket,
+ kNumBuckets))
+ .Times(1);
+
+ reporter_.ReportEnterpriseUpdateSeenToDownloadDays(
+ true /* has_time_restriction_policy */, kDaysToUpdate);
+}
+
+TEST_F(MetricsReporterOmahaTest, WallclockDurationHelper) {
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ base::TimeDelta duration;
+ const std::string state_variable_key = "test-prefs";
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+
+ // Initialize wallclock to 1 sec.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000));
+
+ // First time called so no previous measurement available.
+ EXPECT_FALSE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+
+ // Next time, we should get zero since the clock didn't advance.
+ EXPECT_TRUE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // We can also call it as many times as we want with it being
+ // considered a failure.
+ EXPECT_TRUE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+ EXPECT_TRUE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // Advance the clock one second, then we should get 1 sec on the
+ // next call and 0 sec on the subsequent call.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(2000000));
+ EXPECT_TRUE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+ EXPECT_EQ(duration.InSeconds(), 1);
+ EXPECT_TRUE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // Advance clock two seconds and we should get 2 sec and then 0 sec.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000));
+ EXPECT_TRUE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+ EXPECT_EQ(duration.InSeconds(), 2);
+ EXPECT_TRUE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // There's a possibility that the wallclock can go backwards (NTP
+ // adjustments, for example) so check that we properly handle this
+ // case.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(3000000));
+ EXPECT_FALSE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000));
+ EXPECT_TRUE(reporter_.WallclockDurationHelper(
+ &fake_system_state, state_variable_key, &duration));
+ EXPECT_EQ(duration.InSeconds(), 1);
+}
+
+TEST_F(MetricsReporterOmahaTest, MonotonicDurationHelper) {
+ int64_t storage = 0;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ base::TimeDelta duration;
+
+ fake_system_state.set_clock(&fake_clock);
+
+ // Initialize monotonic clock to 1 sec.
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000));
+
+ // First time called so no previous measurement available.
+ EXPECT_FALSE(reporter_.MonotonicDurationHelper(
+ &fake_system_state, &storage, &duration));
+
+ // Next time, we should get zero since the clock didn't advance.
+ EXPECT_TRUE(reporter_.MonotonicDurationHelper(
+ &fake_system_state, &storage, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // We can also call it as many times as we want with it being
+ // considered a failure.
+ EXPECT_TRUE(reporter_.MonotonicDurationHelper(
+ &fake_system_state, &storage, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+ EXPECT_TRUE(reporter_.MonotonicDurationHelper(
+ &fake_system_state, &storage, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // Advance the clock one second, then we should get 1 sec on the
+ // next call and 0 sec on the subsequent call.
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(2000000));
+ EXPECT_TRUE(reporter_.MonotonicDurationHelper(
+ &fake_system_state, &storage, &duration));
+ EXPECT_EQ(duration.InSeconds(), 1);
+ EXPECT_TRUE(reporter_.MonotonicDurationHelper(
+ &fake_system_state, &storage, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // Advance clock two seconds and we should get 2 sec and then 0 sec.
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(4000000));
+ EXPECT_TRUE(reporter_.MonotonicDurationHelper(
+ &fake_system_state, &storage, &duration));
+ EXPECT_EQ(duration.InSeconds(), 2);
+ EXPECT_TRUE(reporter_.MonotonicDurationHelper(
+ &fake_system_state, &storage, &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/mock_connection_manager.h b/cros/mock_connection_manager.h
new file mode 100644
index 0000000..899a49b
--- /dev/null
+++ b/cros/mock_connection_manager.h
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_MOCK_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_CROS_MOCK_CONNECTION_MANAGER_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/cros/connection_manager_interface.h"
+
+namespace chromeos_update_engine {
+
+// This class mocks the generic interface to the connection manager
+// (e.g FlimFlam, Shill, etc.) to consolidate all connection-related
+// logic in update_engine.
+class MockConnectionManager : public ConnectionManagerInterface {
+ public:
+ MockConnectionManager() = default;
+
+ MOCK_METHOD2(GetConnectionProperties,
+ bool(ConnectionType* out_type,
+ ConnectionTethering* out_tethering));
+
+ MOCK_CONST_METHOD2(IsUpdateAllowedOver,
+ bool(ConnectionType type, ConnectionTethering tethering));
+ MOCK_CONST_METHOD0(IsAllowedConnectionTypesForUpdateSet, bool());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_MOCK_CONNECTION_MANAGER_H_
diff --git a/cros/mock_omaha_request_params.h b/cros/mock_omaha_request_params.h
new file mode 100644
index 0000000..6072d22
--- /dev/null
+++ b/cros/mock_omaha_request_params.h
@@ -0,0 +1,82 @@
+//
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_MOCK_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_CROS_MOCK_OMAHA_REQUEST_PARAMS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/cros/omaha_request_params.h"
+
+namespace chromeos_update_engine {
+
+class MockOmahaRequestParams : public OmahaRequestParams {
+ public:
+ explicit MockOmahaRequestParams(SystemState* system_state)
+ : OmahaRequestParams(system_state) {
+ // Delegate all calls to the parent instance by default. This helps the
+ // migration from tests using the real RequestParams when they should have
+ // use a fake or mock.
+ ON_CALL(*this, GetAppId())
+ .WillByDefault(
+ testing::Invoke(this, &MockOmahaRequestParams::FakeGetAppId));
+ ON_CALL(*this, SetTargetChannel(testing::_, testing::_, testing::_))
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::FakeSetTargetChannel));
+ ON_CALL(*this, UpdateDownloadChannel())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::FakeUpdateDownloadChannel));
+ ON_CALL(*this, ShouldPowerwash())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::FakeShouldPowerwash));
+ }
+
+ MOCK_CONST_METHOD0(GetAppId, std::string(void));
+ MOCK_METHOD3(SetTargetChannel,
+ bool(const std::string& channel,
+ bool is_powerwash_allowed,
+ std::string* error));
+ MOCK_CONST_METHOD0(target_version_prefix, std::string(void));
+ MOCK_METHOD0(UpdateDownloadChannel, void(void));
+ MOCK_CONST_METHOD0(IsUpdateUrlOfficial, bool(void));
+ MOCK_CONST_METHOD0(ShouldPowerwash, bool(void));
+
+ private:
+ // Wrappers to call the parent class and behave like the real object by
+ // default. See "Delegating Calls to a Parent Class" in gmock's documentation.
+ std::string FakeGetAppId() const { return OmahaRequestParams::GetAppId(); }
+
+ bool FakeSetTargetChannel(const std::string& channel,
+ bool is_powerwash_allowed,
+ std::string* error) {
+ return OmahaRequestParams::SetTargetChannel(
+ channel, is_powerwash_allowed, error);
+ }
+
+ void FakeUpdateDownloadChannel() {
+ return OmahaRequestParams::UpdateDownloadChannel();
+ }
+
+ bool FakeShouldPowerwash() const {
+ return OmahaRequestParams::ShouldPowerwash();
+ }
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_MOCK_OMAHA_REQUEST_PARAMS_H_
diff --git a/cros/mock_p2p_manager.h b/cros/mock_p2p_manager.h
new file mode 100644
index 0000000..273f7f9
--- /dev/null
+++ b/cros/mock_p2p_manager.h
@@ -0,0 +1,102 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_MOCK_P2P_MANAGER_H_
+#define UPDATE_ENGINE_CROS_MOCK_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/cros/fake_p2p_manager.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+// A mocked, fake implementation of P2PManager.
+class MockP2PManager : public P2PManager {
+ public:
+ MockP2PManager() {
+ // Delegate all calls to the fake instance
+ ON_CALL(*this, SetDevicePolicy(testing::_))
+ .WillByDefault(
+ testing::Invoke(&fake_, &FakeP2PManager::SetDevicePolicy));
+ ON_CALL(*this, IsP2PEnabled())
+ .WillByDefault(testing::Invoke(&fake_, &FakeP2PManager::IsP2PEnabled));
+ ON_CALL(*this, EnsureP2PRunning())
+ .WillByDefault(
+ testing::Invoke(&fake_, &FakeP2PManager::EnsureP2PRunning));
+ ON_CALL(*this, EnsureP2PNotRunning())
+ .WillByDefault(
+ testing::Invoke(&fake_, &FakeP2PManager::EnsureP2PNotRunning));
+ ON_CALL(*this, PerformHousekeeping())
+ .WillByDefault(
+ testing::Invoke(&fake_, &FakeP2PManager::PerformHousekeeping));
+ ON_CALL(*this,
+ LookupUrlForFile(testing::_, testing::_, testing::_, testing::_))
+ .WillByDefault(
+ testing::Invoke(&fake_, &FakeP2PManager::LookupUrlForFile));
+ ON_CALL(*this, FileShare(testing::_, testing::_))
+ .WillByDefault(testing::Invoke(&fake_, &FakeP2PManager::FileShare));
+ ON_CALL(*this, FileGetPath(testing::_))
+ .WillByDefault(testing::Invoke(&fake_, &FakeP2PManager::FileGetPath));
+ ON_CALL(*this, FileGetSize(testing::_))
+ .WillByDefault(testing::Invoke(&fake_, &FakeP2PManager::FileGetSize));
+ ON_CALL(*this, FileGetExpectedSize(testing::_))
+ .WillByDefault(
+ testing::Invoke(&fake_, &FakeP2PManager::FileGetExpectedSize));
+ ON_CALL(*this, FileGetVisible(testing::_, testing::_))
+ .WillByDefault(
+ testing::Invoke(&fake_, &FakeP2PManager::FileGetVisible));
+ ON_CALL(*this, FileMakeVisible(testing::_))
+ .WillByDefault(
+ testing::Invoke(&fake_, &FakeP2PManager::FileMakeVisible));
+ ON_CALL(*this, CountSharedFiles())
+ .WillByDefault(
+ testing::Invoke(&fake_, &FakeP2PManager::CountSharedFiles));
+ }
+
+ ~MockP2PManager() override {}
+
+ // P2PManager overrides.
+ MOCK_METHOD1(SetDevicePolicy, void(const policy::DevicePolicy*));
+ MOCK_METHOD0(IsP2PEnabled, bool());
+ MOCK_METHOD0(EnsureP2PRunning, bool());
+ MOCK_METHOD0(EnsureP2PNotRunning, bool());
+ MOCK_METHOD0(PerformHousekeeping, bool());
+ MOCK_METHOD4(
+ LookupUrlForFile,
+ void(const std::string&, size_t, base::TimeDelta, LookupCallback));
+ MOCK_METHOD2(FileShare, bool(const std::string&, size_t));
+ MOCK_METHOD1(FileGetPath, base::FilePath(const std::string&));
+ MOCK_METHOD1(FileGetSize, ssize_t(const std::string&));
+ MOCK_METHOD1(FileGetExpectedSize, ssize_t(const std::string&));
+ MOCK_METHOD2(FileGetVisible, bool(const std::string&, bool*));
+ MOCK_METHOD1(FileMakeVisible, bool(const std::string&));
+ MOCK_METHOD0(CountSharedFiles, int());
+
+ // Returns a reference to the underlying FakeP2PManager.
+ FakeP2PManager& fake() { return fake_; }
+
+ private:
+ // The underlying FakeP2PManager.
+ FakeP2PManager fake_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockP2PManager);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_MOCK_P2P_MANAGER_H_
diff --git a/cros/mock_payload_state.h b/cros/mock_payload_state.h
new file mode 100644
index 0000000..56094e6
--- /dev/null
+++ b/cros/mock_payload_state.h
@@ -0,0 +1,84 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_MOCK_PAYLOAD_STATE_H_
+#define UPDATE_ENGINE_CROS_MOCK_PAYLOAD_STATE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/system_state.h"
+#include "update_engine/cros/payload_state_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockPayloadState : public PayloadStateInterface {
+ public:
+ bool Initialize(SystemState* system_state) { return true; }
+
+ // Significant methods.
+ MOCK_METHOD1(SetResponse, void(const OmahaResponse& response));
+ MOCK_METHOD0(DownloadComplete, void());
+ MOCK_METHOD1(DownloadProgress, void(size_t count));
+ MOCK_METHOD0(UpdateResumed, void());
+ MOCK_METHOD0(UpdateRestarted, void());
+ MOCK_METHOD0(UpdateSucceeded, void());
+ MOCK_METHOD1(UpdateFailed, void(ErrorCode error));
+ MOCK_METHOD0(ResetUpdateStatus, void());
+ MOCK_METHOD0(ShouldBackoffDownload, bool());
+ MOCK_METHOD0(UpdateEngineStarted, void());
+ MOCK_METHOD0(Rollback, void());
+ MOCK_METHOD1(ExpectRebootInNewVersion,
+ void(const std::string& target_version_uid));
+ MOCK_METHOD0(P2PNewAttempt, void());
+ MOCK_METHOD0(P2PAttemptAllowed, bool());
+ MOCK_METHOD1(SetUsingP2PForDownloading, void(bool value));
+ MOCK_METHOD1(SetUsingP2PForSharing, void(bool value));
+ MOCK_METHOD1(SetScatteringWaitPeriod, void(base::TimeDelta));
+ MOCK_METHOD1(SetP2PUrl, void(const std::string&));
+ MOCK_METHOD0(NextPayload, bool());
+ MOCK_METHOD1(SetStagingWaitPeriod, void(base::TimeDelta));
+
+ // Getters.
+ MOCK_METHOD0(GetResponseSignature, std::string());
+ MOCK_METHOD0(GetPayloadAttemptNumber, int());
+ MOCK_METHOD0(GetFullPayloadAttemptNumber, int());
+ MOCK_METHOD0(GetCurrentUrl, std::string());
+ MOCK_METHOD0(GetUrlFailureCount, uint32_t());
+ MOCK_METHOD0(GetUrlSwitchCount, uint32_t());
+ MOCK_METHOD0(GetNumResponsesSeen, int());
+ MOCK_METHOD0(GetBackoffExpiryTime, base::Time());
+ MOCK_METHOD0(GetUpdateDuration, base::TimeDelta());
+ MOCK_METHOD0(GetUpdateDurationUptime, base::TimeDelta());
+ MOCK_METHOD1(GetCurrentBytesDownloaded, uint64_t(DownloadSource source));
+ MOCK_METHOD1(GetTotalBytesDownloaded, uint64_t(DownloadSource source));
+ MOCK_METHOD0(GetNumReboots, uint32_t());
+ MOCK_METHOD0(GetRollbackHappened, bool());
+ MOCK_METHOD1(SetRollbackHappened, void(bool));
+ MOCK_METHOD0(GetRollbackVersion, std::string());
+ MOCK_METHOD0(GetP2PNumAttempts, int());
+ MOCK_METHOD0(GetP2PFirstAttemptTimestamp, base::Time());
+ MOCK_CONST_METHOD0(GetUsingP2PForDownloading, bool());
+ MOCK_CONST_METHOD0(GetUsingP2PForSharing, bool());
+ MOCK_METHOD0(GetScatteringWaitPeriod, base::TimeDelta());
+ MOCK_CONST_METHOD0(GetP2PUrl, std::string());
+ MOCK_METHOD0(GetStagingWaitPeriod, base::TimeDelta());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_MOCK_PAYLOAD_STATE_H_
diff --git a/cros/mock_power_manager.h b/cros/mock_power_manager.h
new file mode 100644
index 0000000..d4a8682
--- /dev/null
+++ b/cros/mock_power_manager.h
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_MOCK_POWER_MANAGER_H_
+#define UPDATE_ENGINE_CROS_MOCK_POWER_MANAGER_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/cros/power_manager_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockPowerManager : public PowerManagerInterface {
+ public:
+ MockPowerManager() = default;
+
+ MOCK_METHOD0(RequestReboot, bool(void));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_MOCK_POWER_MANAGER_H_
diff --git a/cros/mock_update_attempter.h b/cros/mock_update_attempter.h
new file mode 100644
index 0000000..be8cfcc
--- /dev/null
+++ b/cros/mock_update_attempter.h
@@ -0,0 +1,68 @@
+//
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_MOCK_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_CROS_MOCK_UPDATE_ATTEMPTER_H_
+
+#include <string>
+#include <vector>
+
+#include "update_engine/cros/update_attempter.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+class MockUpdateAttempter : public UpdateAttempter {
+ public:
+ using UpdateAttempter::UpdateAttempter;
+
+ MOCK_METHOD(void,
+ Update,
+ (const chromeos_update_manager::UpdateCheckParams& params),
+ (override));
+
+ MOCK_METHOD1(GetStatus, bool(update_engine::UpdateEngineStatus* out_status));
+
+ MOCK_METHOD1(GetBootTimeAtUpdate, bool(base::Time* out_boot_time));
+
+ MOCK_METHOD0(ResetStatus, bool(void));
+
+ MOCK_CONST_METHOD0(GetCurrentUpdateAttemptFlags, UpdateAttemptFlags(void));
+
+ MOCK_METHOD3(CheckForUpdate,
+ bool(const std::string& app_version,
+ const std::string& omaha_url,
+ UpdateAttemptFlags flags));
+
+ MOCK_METHOD2(CheckForInstall,
+ bool(const std::vector<std::string>& dlc_ids,
+ const std::string& omaha_url));
+
+ MOCK_METHOD2(SetDlcActiveValue, bool(bool, const std::string&));
+
+ MOCK_CONST_METHOD0(GetExcluder, ExcluderInterface*(void));
+
+ MOCK_METHOD0(RefreshDevicePolicy, void(void));
+
+ MOCK_CONST_METHOD0(consecutive_failed_update_checks, unsigned int(void));
+
+ MOCK_CONST_METHOD0(server_dictated_poll_interval, unsigned int(void));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_MOCK_UPDATE_ATTEMPTER_H_
diff --git a/cros/omaha_request_action.cc b/cros/omaha_request_action.cc
new file mode 100644
index 0000000..0916f9d
--- /dev/null
+++ b/cros/omaha_request_action.cc
@@ -0,0 +1,1739 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_request_action.h"
+
+#include <inttypes.h>
+
+#include <limits>
+#include <map>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/optional.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <brillo/key_value_store.h>
+#include <expat.h>
+#include <metrics/metrics_library.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/metrics_reporter_interface.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/connection_manager_interface.h"
+#include "update_engine/cros/omaha_request_builder_xml.h"
+#include "update_engine/cros/omaha_request_params.h"
+#include "update_engine/cros/p2p_manager.h"
+#include "update_engine/cros/payload_state_interface.h"
+#include "update_engine/cros/update_attempter.h"
+#include "update_engine/metrics_utils.h"
+
+using base::Optional;
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_manager::kRollforwardInfinity;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// List of custom attributes that we interpret in the Omaha response:
+constexpr char kAttrDeadline[] = "deadline";
+constexpr char kAttrDisableP2PForDownloading[] = "DisableP2PForDownloading";
+constexpr char kAttrDisableP2PForSharing[] = "DisableP2PForSharing";
+constexpr char kAttrDisablePayloadBackoff[] = "DisablePayloadBackoff";
+constexpr char kAttrVersion[] = "version";
+// Deprecated: "IsDelta"
+constexpr char kAttrIsDeltaPayload[] = "IsDeltaPayload";
+constexpr char kAttrMaxFailureCountPerUrl[] = "MaxFailureCountPerUrl";
+constexpr char kAttrMaxDaysToScatter[] = "MaxDaysToScatter";
+// Deprecated: "ManifestSignatureRsa"
+// Deprecated: "ManifestSize"
+constexpr char kAttrMetadataSignatureRsa[] = "MetadataSignatureRsa";
+constexpr char kAttrMetadataSize[] = "MetadataSize";
+constexpr char kAttrMoreInfo[] = "MoreInfo";
+constexpr char kAttrNoUpdate[] = "noupdate";
+// Deprecated: "NeedsAdmin"
+constexpr char kAttrPollInterval[] = "PollInterval";
+constexpr char kAttrPowerwash[] = "Powerwash";
+constexpr char kAttrPrompt[] = "Prompt";
+constexpr char kAttrPublicKeyRsa[] = "PublicKeyRsa";
+
+// List of attributes that we interpret in the Omaha response:
+constexpr char kAttrAppId[] = "appid";
+constexpr char kAttrCodeBase[] = "codebase";
+constexpr char kAttrCohort[] = "cohort";
+constexpr char kAttrCohortHint[] = "cohorthint";
+constexpr char kAttrCohortName[] = "cohortname";
+constexpr char kAttrElapsedDays[] = "elapsed_days";
+constexpr char kAttrElapsedSeconds[] = "elapsed_seconds";
+constexpr char kAttrEvent[] = "event";
+constexpr char kAttrHashSha256[] = "hash_sha256";
+// Deprecated: "hash"; Although we still need to pass it from the server for
+// backward compatibility.
+constexpr char kAttrName[] = "name";
+// Deprecated: "sha256"; Although we still need to pass it from the server for
+// backward compatibility.
+constexpr char kAttrSize[] = "size";
+constexpr char kAttrStatus[] = "status";
+
+// List of values that we interpret in the Omaha response:
+constexpr char kValPostInstall[] = "postinstall";
+constexpr char kValNoUpdate[] = "noupdate";
+
+// updatecheck attributes (without the underscore prefix).
+// Deprecated: "eol"
+constexpr char kAttrEolDate[] = "eol_date";
+constexpr char kAttrRollback[] = "rollback";
+constexpr char kAttrFirmwareVersion[] = "firmware_version";
+constexpr char kAttrKernelVersion[] = "kernel_version";
+
+// Struct used for holding data obtained when parsing the XML.
+struct OmahaParserData {
+ explicit OmahaParserData(XML_Parser _xml_parser) : xml_parser(_xml_parser) {}
+
+ // Pointer to the expat XML_Parser object.
+ XML_Parser xml_parser;
+
+ // This is the state of the parser as it's processing the XML.
+ bool failed = false;
+ bool entity_decl = false;
+ string current_path;
+
+ // These are the values extracted from the XML.
+ string updatecheck_poll_interval;
+ map<string, string> updatecheck_attrs;
+ string daystart_elapsed_days;
+ string daystart_elapsed_seconds;
+
+ struct App {
+ string id;
+ vector<string> url_codebase;
+ string manifest_version;
+ map<string, string> action_postinstall_attrs;
+ string updatecheck_status;
+ Optional<string> cohort;
+ Optional<string> cohorthint;
+ Optional<string> cohortname;
+
+ struct Package {
+ string name;
+ string size;
+ string hash;
+ };
+ vector<Package> packages;
+ };
+ vector<App> apps;
+};
+
+namespace {
+
+// Callback function invoked by expat.
+void ParserHandlerStart(void* user_data,
+ const XML_Char* element,
+ const XML_Char** attr) {
+ OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+
+ if (data->failed)
+ return;
+
+ data->current_path += string("/") + element;
+
+ map<string, string> attrs;
+ if (attr != nullptr) {
+ for (int n = 0; attr[n] != nullptr && attr[n + 1] != nullptr; n += 2) {
+ string key = attr[n];
+ string value = attr[n + 1];
+ attrs[key] = value;
+ }
+ }
+
+ if (data->current_path == "/response/app") {
+ OmahaParserData::App app;
+ if (attrs.find(kAttrAppId) != attrs.end())
+ app.id = attrs[kAttrAppId];
+ if (attrs.find(kAttrCohort) != attrs.end())
+ app.cohort = attrs[kAttrCohort];
+ if (attrs.find(kAttrCohortHint) != attrs.end())
+ app.cohorthint = attrs[kAttrCohortHint];
+ if (attrs.find(kAttrCohortName) != attrs.end())
+ app.cohortname = attrs[kAttrCohortName];
+ data->apps.push_back(std::move(app));
+ } else if (data->current_path == "/response/app/updatecheck") {
+ if (!data->apps.empty())
+ data->apps.back().updatecheck_status = attrs[kAttrStatus];
+ if (data->updatecheck_poll_interval.empty())
+ data->updatecheck_poll_interval = attrs[kAttrPollInterval];
+ // Omaha sends arbitrary key-value pairs as extra attributes starting with
+ // an underscore.
+ for (const auto& attr : attrs) {
+ if (!attr.first.empty() && attr.first[0] == '_')
+ data->updatecheck_attrs[attr.first.substr(1)] = attr.second;
+ }
+ } else if (data->current_path == "/response/daystart") {
+ // Get the install-date.
+ data->daystart_elapsed_days = attrs[kAttrElapsedDays];
+ data->daystart_elapsed_seconds = attrs[kAttrElapsedSeconds];
+ } else if (data->current_path == "/response/app/updatecheck/urls/url") {
+ // Look at all <url> elements.
+ if (!data->apps.empty())
+ data->apps.back().url_codebase.push_back(attrs[kAttrCodeBase]);
+ } else if (data->current_path ==
+ "/response/app/updatecheck/manifest/packages/package") {
+ // Look at all <package> elements.
+ if (!data->apps.empty())
+ data->apps.back().packages.push_back({.name = attrs[kAttrName],
+ .size = attrs[kAttrSize],
+ .hash = attrs[kAttrHashSha256]});
+ } else if (data->current_path == "/response/app/updatecheck/manifest") {
+ // Get the version.
+ if (!data->apps.empty())
+ data->apps.back().manifest_version = attrs[kAttrVersion];
+ } else if (data->current_path ==
+ "/response/app/updatecheck/manifest/actions/action") {
+ // We only care about the postinstall action.
+ if (attrs[kAttrEvent] == kValPostInstall && !data->apps.empty()) {
+ data->apps.back().action_postinstall_attrs = std::move(attrs);
+ }
+ }
+}
+
+// Callback function invoked by expat.
+void ParserHandlerEnd(void* user_data, const XML_Char* element) {
+ OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+ if (data->failed)
+ return;
+
+ const string path_suffix = string("/") + element;
+
+ if (!base::EndsWith(
+ data->current_path, path_suffix, base::CompareCase::SENSITIVE)) {
+ LOG(ERROR) << "Unexpected end element '" << element
+ << "' with current_path='" << data->current_path << "'";
+ data->failed = true;
+ return;
+ }
+ data->current_path.resize(data->current_path.size() - path_suffix.size());
+}
+
+// Callback function invoked by expat.
+//
+// This is called for entity declarations. Since Omaha is guaranteed
+// to never return any XML with entities our course of action is to
+// just stop parsing. This avoids potential resource exhaustion
+// problems AKA the "billion laughs". CVE-2013-0340.
+void ParserHandlerEntityDecl(void* user_data,
+ const XML_Char* entity_name,
+ int is_parameter_entity,
+ const XML_Char* value,
+ int value_length,
+ const XML_Char* base,
+ const XML_Char* system_id,
+ const XML_Char* public_id,
+ const XML_Char* notation_name) {
+ OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+
+ LOG(ERROR) << "XML entities are not supported. Aborting parsing.";
+ data->failed = true;
+ data->entity_decl = true;
+ XML_StopParser(data->xml_parser, false);
+}
+
+} // namespace
+
+OmahaRequestAction::OmahaRequestAction(
+ SystemState* system_state,
+ OmahaEvent* event,
+ std::unique_ptr<HttpFetcher> http_fetcher,
+ bool ping_only,
+ const string& session_id)
+ : system_state_(system_state),
+ params_(system_state->request_params()),
+ event_(event),
+ http_fetcher_(std::move(http_fetcher)),
+ policy_provider_(std::make_unique<policy::PolicyProvider>()),
+ ping_only_(ping_only),
+ ping_active_days_(0),
+ ping_roll_call_days_(0),
+ session_id_(session_id) {
+ policy_provider_->Reload();
+}
+
+OmahaRequestAction::~OmahaRequestAction() {}
+
+// Calculates the value to use for the ping days parameter.
+int OmahaRequestAction::CalculatePingDays(const string& key) {
+ int days = kPingNeverPinged;
+ int64_t last_ping = 0;
+ if (system_state_->prefs()->GetInt64(key, &last_ping) && last_ping >= 0) {
+ days = (Time::Now() - Time::FromInternalValue(last_ping)).InDays();
+ if (days < 0) {
+ // If |days| is negative, then the system clock must have jumped
+ // back in time since the ping was sent. Mark the value so that
+ // it doesn't get sent to the server but we still update the
+ // last ping daystart preference. This way the next ping time
+ // will be correct, hopefully.
+ days = kPingTimeJump;
+ LOG(WARNING)
+ << "System clock jumped back in time. Resetting ping daystarts.";
+ }
+ }
+ return days;
+}
+
+void OmahaRequestAction::InitPingDays() {
+ // We send pings only along with update checks, not with events.
+ if (IsEvent()) {
+ return;
+ }
+ // TODO(petkov): Figure a way to distinguish active use pings
+ // vs. roll call pings. Currently, the two pings are identical. A
+ // fix needs to change this code as well as UpdateLastPingDays and ShouldPing.
+ ping_active_days_ = CalculatePingDays(kPrefsLastActivePingDay);
+ ping_roll_call_days_ = CalculatePingDays(kPrefsLastRollCallPingDay);
+}
+
+bool OmahaRequestAction::ShouldPing() const {
+ if (ping_active_days_ == kPingNeverPinged &&
+ ping_roll_call_days_ == kPingNeverPinged) {
+ int powerwash_count = system_state_->hardware()->GetPowerwashCount();
+ if (powerwash_count > 0) {
+ LOG(INFO) << "Not sending ping with a=-1 r=-1 to omaha because "
+ << "powerwash_count is " << powerwash_count;
+ return false;
+ }
+ if (system_state_->hardware()->GetFirstActiveOmahaPingSent()) {
+ LOG(INFO) << "Not sending ping with a=-1 r=-1 to omaha because "
+ << "the first_active_omaha_ping_sent is true";
+ return false;
+ }
+ return true;
+ }
+ return ping_active_days_ > 0 || ping_roll_call_days_ > 0;
+}
+
+// static
+int OmahaRequestAction::GetInstallDate(SystemState* system_state) {
+ PrefsInterface* prefs = system_state->prefs();
+ if (prefs == nullptr)
+ return -1;
+
+ // If we have the value stored on disk, just return it.
+ int64_t stored_value;
+ if (prefs->GetInt64(kPrefsInstallDateDays, &stored_value)) {
+ // Convert and validity-check.
+ int install_date_days = static_cast<int>(stored_value);
+ if (install_date_days >= 0)
+ return install_date_days;
+ LOG(ERROR) << "Dropping stored Omaha InstallData since its value num_days="
+ << install_date_days << " looks suspicious.";
+ prefs->Delete(kPrefsInstallDateDays);
+ }
+
+ // Otherwise, if OOBE is not complete then do nothing and wait for
+ // ParseResponse() to call ParseInstallDate() and then
+ // PersistInstallDate() to set the kPrefsInstallDateDays state
+ // variable. Once that is done, we'll then report back in future
+ // Omaha requests. This works exactly because OOBE triggers an
+ // update check.
+ //
+ // However, if OOBE is complete and the kPrefsInstallDateDays state
+ // variable is not set, there are two possibilities
+ //
+ // 1. The update check in OOBE failed so we never got a response
+ // from Omaha (no network etc.); or
+ //
+ // 2. OOBE was done on an older version that didn't write to the
+ // kPrefsInstallDateDays state variable.
+ //
+ // In both cases, we approximate the install date by simply
+ // inspecting the timestamp of when OOBE happened.
+
+ Time time_of_oobe;
+ if (!system_state->hardware()->IsOOBEEnabled() ||
+ !system_state->hardware()->IsOOBEComplete(&time_of_oobe)) {
+ LOG(INFO) << "Not generating Omaha InstallData as we have "
+ << "no prefs file and OOBE is not complete or not enabled.";
+ return -1;
+ }
+
+ int num_days;
+ if (!utils::ConvertToOmahaInstallDate(time_of_oobe, &num_days)) {
+ LOG(ERROR) << "Not generating Omaha InstallData from time of OOBE "
+ << "as its value '" << utils::ToString(time_of_oobe)
+ << "' looks suspicious.";
+ return -1;
+ }
+
+ // Persist this to disk, for future use.
+ if (!OmahaRequestAction::PersistInstallDate(
+ system_state, num_days, kProvisionedFromOOBEMarker))
+ return -1;
+
+ LOG(INFO) << "Set the Omaha InstallDate from OOBE time-stamp to " << num_days
+ << " days";
+
+ return num_days;
+}
+
+void OmahaRequestAction::StorePingReply(
+ const OmahaParserData& parser_data) const {
+ for (const auto& app : parser_data.apps) {
+ auto it = params_->dlc_apps_params().find(app.id);
+ if (it == params_->dlc_apps_params().end())
+ continue;
+
+ const OmahaRequestParams::AppParams& dlc_params = it->second;
+ const string& dlc_id = dlc_params.name;
+ // Skip if the ping for this DLC was not sent.
+ if (!dlc_params.send_ping)
+ continue;
+
+ PrefsInterface* prefs = system_state_->prefs();
+ // Reset the active metadata value to |kPingInactiveValue|.
+ auto active_key =
+ prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive});
+ if (!prefs->SetInt64(active_key, kPingInactiveValue))
+ LOG(ERROR) << "Failed to set the value of ping metadata '" << active_key
+ << "'.";
+
+ auto last_rollcall_key =
+ prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall});
+ if (!prefs->SetString(last_rollcall_key, parser_data.daystart_elapsed_days))
+ LOG(ERROR) << "Failed to set the value of ping metadata '"
+ << last_rollcall_key << "'.";
+
+ if (dlc_params.ping_active) {
+ // Write the value of elapsed_days into |kPrefsPingLastActive| only if
+ // the previous ping was an active one.
+ auto last_active_key =
+ prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive});
+ if (!prefs->SetString(last_active_key, parser_data.daystart_elapsed_days))
+ LOG(ERROR) << "Failed to set the value of ping metadata '"
+ << last_active_key << "'.";
+ }
+ }
+}
+
+void OmahaRequestAction::PerformAction() {
+ http_fetcher_->set_delegate(this);
+ InitPingDays();
+ if (ping_only_ && !ShouldPing()) {
+ processor_->ActionComplete(this, ErrorCode::kSuccess);
+ return;
+ }
+
+ OmahaRequestBuilderXml omaha_request(event_.get(),
+ params_,
+ ping_only_,
+ ShouldPing(), // include_ping
+ ping_active_days_,
+ ping_roll_call_days_,
+ GetInstallDate(system_state_),
+ system_state_->prefs(),
+ session_id_);
+ string request_post = omaha_request.GetRequest();
+
+ // Set X-Goog-Update headers.
+ http_fetcher_->SetHeader(kXGoogleUpdateInteractivity,
+ params_->interactive() ? "fg" : "bg");
+ http_fetcher_->SetHeader(kXGoogleUpdateAppId, params_->GetAppId());
+ http_fetcher_->SetHeader(
+ kXGoogleUpdateUpdater,
+ base::StringPrintf(
+ "%s-%s", constants::kOmahaUpdaterID, kOmahaUpdaterVersion));
+
+ http_fetcher_->SetPostData(
+ request_post.data(), request_post.size(), kHttpContentTypeTextXml);
+ LOG(INFO) << "Posting an Omaha request to " << params_->update_url();
+ LOG(INFO) << "Request: " << request_post;
+ http_fetcher_->BeginTransfer(params_->update_url());
+}
+
+void OmahaRequestAction::TerminateProcessing() {
+ http_fetcher_->TerminateTransfer();
+}
+
+// We just store the response in the buffer. Once we've received all bytes,
+// we'll look in the buffer and decide what to do.
+bool OmahaRequestAction::ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) {
+ const uint8_t* byte_ptr = reinterpret_cast<const uint8_t*>(bytes);
+ response_buffer_.insert(response_buffer_.end(), byte_ptr, byte_ptr + length);
+ return true;
+}
+
+namespace {
+
+// Parses a 64 bit base-10 int from a string and returns it. Returns 0
+// on error. If the string contains "0", that's indistinguishable from
+// error.
+off_t ParseInt(const string& str) {
+ off_t ret = 0;
+ int rc = sscanf(str.c_str(), "%" PRIi64, &ret); // NOLINT(runtime/printf)
+ if (rc < 1) {
+ // failure
+ return 0;
+ }
+ return ret;
+}
+
+// Parses |str| and returns |true| if, and only if, its value is "true".
+bool ParseBool(const string& str) {
+ return str == "true";
+}
+
+// Update the last ping day preferences based on the server daystart
+// response. Returns true on success, false otherwise.
+bool UpdateLastPingDays(OmahaParserData* parser_data, PrefsInterface* prefs) {
+ int64_t elapsed_seconds = 0;
+ TEST_AND_RETURN_FALSE(base::StringToInt64(
+ parser_data->daystart_elapsed_seconds, &elapsed_seconds));
+ TEST_AND_RETURN_FALSE(elapsed_seconds >= 0);
+
+ // Remember the local time that matches the server's last midnight
+ // time.
+ Time daystart = Time::Now() - TimeDelta::FromSeconds(elapsed_seconds);
+ prefs->SetInt64(kPrefsLastActivePingDay, daystart.ToInternalValue());
+ prefs->SetInt64(kPrefsLastRollCallPingDay, daystart.ToInternalValue());
+ return true;
+}
+
+// Parses the package node in the given XML document and populates
+// |output_object| if valid. Returns true if we should continue the parsing.
+// False otherwise, in which case it sets any error code using |completer|.
+bool ParsePackage(OmahaParserData::App* app,
+ OmahaResponse* output_object,
+ bool can_exclude,
+ ScopedActionCompleter* completer) {
+ if (app->updatecheck_status.empty() ||
+ app->updatecheck_status == kValNoUpdate) {
+ if (!app->packages.empty()) {
+ LOG(ERROR) << "No update in this <app> but <package> is not empty.";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ return true;
+ }
+ if (app->packages.empty()) {
+ LOG(ERROR) << "Omaha Response has no packages";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ if (app->url_codebase.empty()) {
+ LOG(ERROR) << "No Omaha Response URLs";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ LOG(INFO) << "Found " << app->url_codebase.size() << " url(s)";
+ vector<string> metadata_sizes =
+ base::SplitString(app->action_postinstall_attrs[kAttrMetadataSize],
+ ":",
+ base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ vector<string> metadata_signatures = base::SplitString(
+ app->action_postinstall_attrs[kAttrMetadataSignatureRsa],
+ ":",
+ base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ vector<string> is_delta_payloads =
+ base::SplitString(app->action_postinstall_attrs[kAttrIsDeltaPayload],
+ ":",
+ base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ for (size_t i = 0; i < app->packages.size(); i++) {
+ const auto& package = app->packages[i];
+ if (package.name.empty()) {
+ LOG(ERROR) << "Omaha Response has empty package name";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ LOG(INFO) << "Found package " << package.name;
+
+ OmahaResponse::Package out_package;
+ out_package.app_id = app->id;
+ out_package.can_exclude = can_exclude;
+ for (const string& codebase : app->url_codebase) {
+ if (codebase.empty()) {
+ LOG(ERROR) << "Omaha Response URL has empty codebase";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ out_package.payload_urls.push_back(codebase + package.name);
+ }
+ // Parse the payload size.
+ base::StringToUint64(package.size, &out_package.size);
+ if (out_package.size <= 0) {
+ LOG(ERROR) << "Omaha Response has invalid payload size: " << package.size;
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ LOG(INFO) << "Payload size = " << out_package.size << " bytes";
+
+ if (i < metadata_sizes.size())
+ base::StringToUint64(metadata_sizes[i], &out_package.metadata_size);
+ LOG(INFO) << "Payload metadata size = " << out_package.metadata_size
+ << " bytes";
+
+ if (i < metadata_signatures.size())
+ out_package.metadata_signature = metadata_signatures[i];
+ LOG(INFO) << "Payload metadata signature = "
+ << out_package.metadata_signature;
+
+ out_package.hash = package.hash;
+ if (out_package.hash.empty()) {
+ LOG(ERROR) << "Omaha Response has empty hash_sha256 value";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ LOG(INFO) << "Payload hash = " << out_package.hash;
+
+ if (i < is_delta_payloads.size())
+ out_package.is_delta = ParseBool(is_delta_payloads[i]);
+ LOG(INFO) << "Payload is delta = " << utils::ToString(out_package.is_delta);
+
+ output_object->packages.push_back(std::move(out_package));
+ }
+
+ return true;
+}
+
+// Removes the candidate URLs which are excluded within packages, if all the
+// candidate URLs are excluded within a package, the package will be excluded.
+void ProcessExclusions(OmahaResponse* output_object,
+ OmahaRequestParams* params,
+ ExcluderInterface* excluder) {
+ for (auto package_it = output_object->packages.begin();
+ package_it != output_object->packages.end();
+ /* Increment logic in loop */) {
+ // If package cannot be excluded, quickly continue.
+ if (!package_it->can_exclude) {
+ ++package_it;
+ continue;
+ }
+ // Remove the excluded payload URLs.
+ for (auto payload_url_it = package_it->payload_urls.begin();
+ payload_url_it != package_it->payload_urls.end();
+ /* Increment logic in loop */) {
+ auto exclusion_name = utils::GetExclusionName(*payload_url_it);
+ // If payload URL is not excluded, quickly continue.
+ if (!excluder->IsExcluded(exclusion_name)) {
+ ++payload_url_it;
+ continue;
+ }
+ LOG(INFO) << "Excluding payload URL=" << *payload_url_it
+ << " for payload hash=" << package_it->hash;
+ payload_url_it = package_it->payload_urls.erase(payload_url_it);
+ }
+ // If there are no candidate payload URLs, remove the package.
+ if (package_it->payload_urls.empty()) {
+ LOG(INFO) << "Excluding payload hash=" << package_it->hash;
+ // Need to set DLC as not updated so correct metrics can be sent when an
+ // update is completed.
+ params->SetDlcNoUpdate(package_it->app_id);
+ package_it = output_object->packages.erase(package_it);
+ continue;
+ }
+ ++package_it;
+ }
+}
+
+// Parses the 2 key version strings kernel_version and firmware_version. If the
+// field is not present, or cannot be parsed the values default to 0xffff.
+void ParseRollbackVersions(int allowed_milestones,
+ OmahaParserData* parser_data,
+ OmahaResponse* output_object) {
+ utils::ParseRollbackKeyVersion(
+ parser_data->updatecheck_attrs[kAttrFirmwareVersion],
+ &output_object->rollback_key_version.firmware_key,
+ &output_object->rollback_key_version.firmware);
+ utils::ParseRollbackKeyVersion(
+ parser_data->updatecheck_attrs[kAttrKernelVersion],
+ &output_object->rollback_key_version.kernel_key,
+ &output_object->rollback_key_version.kernel);
+
+ // Create the attribute name strings for milestone N - allowed_milestones.
+ const string firmware_max_rollforward_attr =
+ base::StringPrintf("%s_%i", kAttrFirmwareVersion, allowed_milestones);
+ const string kernel_max_rollforward_attr =
+ base::StringPrintf("%s_%i", kAttrKernelVersion, allowed_milestones);
+
+ const bool max_firmware_and_kernel_exist =
+ parser_data->updatecheck_attrs.count(firmware_max_rollforward_attr) > 0 &&
+ parser_data->updatecheck_attrs.count(kernel_max_rollforward_attr) > 0;
+
+ string firmware_version;
+ string kernel_version;
+ if (max_firmware_and_kernel_exist) {
+ firmware_version =
+ parser_data->updatecheck_attrs[firmware_max_rollforward_attr];
+ kernel_version =
+ parser_data->updatecheck_attrs[kernel_max_rollforward_attr];
+ }
+
+ LOG(INFO) << "For milestone N-" << allowed_milestones
+ << " firmware_key_version=" << firmware_version
+ << " kernel_key_version=" << kernel_version;
+
+ OmahaResponse::RollbackKeyVersion version;
+ utils::ParseRollbackKeyVersion(
+ firmware_version, &version.firmware_key, &version.firmware);
+ utils::ParseRollbackKeyVersion(
+ kernel_version, &version.kernel_key, &version.kernel);
+
+ output_object->past_rollback_key_version = std::move(version);
+}
+
+} // namespace
+
+bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ if (parser_data->apps.empty()) {
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ LOG(INFO) << "Found " << parser_data->apps.size() << " <app>.";
+
+ // chromium-os:37289: The PollInterval is not supported by Omaha server
+ // currently. But still keeping this existing code in case we ever decide to
+ // slow down the request rate from the server-side. Note that the PollInterval
+ // is not persisted, so it has to be sent by the server on every response to
+ // guarantee that the scheduler uses this value (otherwise, if the device got
+ // rebooted after the last server-indicated value, it'll revert to the default
+ // value). Also kDefaultMaxUpdateChecks value for the scattering logic is
+ // based on the assumption that we perform an update check every hour so that
+ // the max value of 8 will roughly be equivalent to one work day. If we decide
+ // to use PollInterval permanently, we should update the
+ // max_update_checks_allowed to take PollInterval into account. Note: The
+ // parsing for PollInterval happens even before parsing of the status because
+ // we may want to specify the PollInterval even when there's no update.
+ base::StringToInt(parser_data->updatecheck_poll_interval,
+ &output_object->poll_interval);
+
+ // Check for the "elapsed_days" attribute in the "daystart"
+ // element. This is the number of days since Jan 1 2007, 0:00
+ // PST. If we don't have a persisted value of the Omaha InstallDate,
+ // we'll use it to calculate it and then persist it.
+ if (ParseInstallDate(parser_data, output_object) &&
+ !HasInstallDate(system_state_)) {
+ // Since output_object->install_date_days is never negative, the
+ // elapsed_days -> install-date calculation is reduced to simply
+ // rounding down to the nearest number divisible by 7.
+ int remainder = output_object->install_date_days % 7;
+ int install_date_days_rounded =
+ output_object->install_date_days - remainder;
+ if (PersistInstallDate(system_state_,
+ install_date_days_rounded,
+ kProvisionedFromOmahaResponse)) {
+ LOG(INFO) << "Set the Omaha InstallDate from Omaha Response to "
+ << install_date_days_rounded << " days";
+ }
+ }
+
+ // We persist the cohorts sent by omaha even if the status is "noupdate".
+ for (const auto& app : parser_data->apps) {
+ if (app.id == params_->GetAppId()) {
+ if (app.cohort)
+ PersistCohortData(kPrefsOmahaCohort, app.cohort.value());
+ if (app.cohorthint)
+ PersistCohortData(kPrefsOmahaCohortHint, app.cohorthint.value());
+ if (app.cohortname)
+ PersistCohortData(kPrefsOmahaCohortName, app.cohortname.value());
+ break;
+ }
+ }
+
+ PersistEolInfo(parser_data->updatecheck_attrs);
+
+ // Rollback-related updatecheck attributes.
+ // Defaults to false if attribute is not present.
+ output_object->is_rollback =
+ ParseBool(parser_data->updatecheck_attrs[kAttrRollback]);
+
+ // Parses the rollback versions of the current image. If the fields do not
+ // exist they default to 0xffff for the 4 key versions.
+ ParseRollbackVersions(
+ params_->rollback_allowed_milestones(), parser_data, output_object);
+
+ if (!ParseStatus(parser_data, output_object, completer))
+ return false;
+
+ if (!ParseParams(parser_data, output_object, completer))
+ return false;
+
+ // Package has to be parsed after Params now because ParseParams need to make
+ // sure that postinstall action exists.
+ for (auto& app : parser_data->apps) {
+ // Only allow exclusions for a non-critical package during an update. For
+ // non-critical package installations, let the errors propagate instead
+ // of being handled inside update_engine as installations are a dlcservice
+ // specific feature.
+ bool can_exclude = !params_->is_install() && params_->IsDlcAppId(app.id);
+ if (!ParsePackage(&app, output_object, can_exclude, completer))
+ return false;
+ }
+
+ return true;
+}
+
+bool OmahaRequestAction::ParseStatus(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ output_object->update_exists = false;
+ for (const auto& app : parser_data->apps) {
+ const string& status = app.updatecheck_status;
+ if (status == kValNoUpdate) {
+ // If the app is a DLC, allow status "noupdate" to support DLC
+ // deprecations.
+ if (params_->IsDlcAppId(app.id)) {
+ LOG(INFO) << "No update for <app> " << app.id
+ << " but update continuing since a DLC.";
+ params_->SetDlcNoUpdate(app.id);
+ continue;
+ }
+ // Don't update if any app has status="noupdate".
+ LOG(INFO) << "No update for <app> " << app.id;
+ output_object->update_exists = false;
+ break;
+ } else if (status == "ok") {
+ auto const& attr_no_update =
+ app.action_postinstall_attrs.find(kAttrNoUpdate);
+ if (attr_no_update != app.action_postinstall_attrs.end() &&
+ attr_no_update->second == "true") {
+ // noupdate="true" in postinstall attributes means it's an update to
+ // self, only update if there's at least one app really have update.
+ LOG(INFO) << "Update to self for <app> " << app.id;
+ } else {
+ LOG(INFO) << "Update for <app> " << app.id;
+ output_object->update_exists = true;
+ }
+ } else if (status.empty() && params_->is_install() &&
+ params_->GetAppId() == app.id) {
+ // Skips the platform app for install operation.
+ LOG(INFO) << "No payload (and ignore) for <app> " << app.id;
+ } else {
+ LOG(ERROR) << "Unknown Omaha response status: " << status;
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ }
+ if (!output_object->update_exists) {
+ SetOutputObject(*output_object);
+ completer->set_code(ErrorCode::kSuccess);
+ }
+
+ return output_object->update_exists;
+}
+
+bool OmahaRequestAction::ParseParams(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ map<string, string> attrs;
+ for (auto& app : parser_data->apps) {
+ if (app.id == params_->GetAppId()) {
+ // this is the app (potentially the only app)
+ output_object->version = app.manifest_version;
+ } else if (params_->is_install() &&
+ app.manifest_version != params_->app_version()) {
+ LOG(WARNING) << "An app has a different version (" << app.manifest_version
+ << ") that is different than platform app version ("
+ << params_->app_version() << ")";
+ }
+ if (!app.action_postinstall_attrs.empty() && attrs.empty()) {
+ attrs = app.action_postinstall_attrs;
+ }
+ }
+ if (params_->is_install()) {
+ LOG(INFO) << "Use request version for Install operation.";
+ output_object->version = params_->app_version();
+ }
+ if (output_object->version.empty()) {
+ LOG(ERROR) << "Omaha Response does not have version in manifest!";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ LOG(INFO) << "Received omaha response to update to version "
+ << output_object->version;
+
+ if (attrs.empty()) {
+ LOG(ERROR) << "Omaha Response has no postinstall event action";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ // Get the optional properties one by one.
+ output_object->more_info_url = attrs[kAttrMoreInfo];
+ output_object->prompt = ParseBool(attrs[kAttrPrompt]);
+ output_object->deadline = attrs[kAttrDeadline];
+ output_object->max_days_to_scatter = ParseInt(attrs[kAttrMaxDaysToScatter]);
+ output_object->disable_p2p_for_downloading =
+ ParseBool(attrs[kAttrDisableP2PForDownloading]);
+ output_object->disable_p2p_for_sharing =
+ ParseBool(attrs[kAttrDisableP2PForSharing]);
+ output_object->public_key_rsa = attrs[kAttrPublicKeyRsa];
+
+ string max = attrs[kAttrMaxFailureCountPerUrl];
+ if (!base::StringToUint(max, &output_object->max_failure_count_per_url))
+ output_object->max_failure_count_per_url = kDefaultMaxFailureCountPerUrl;
+
+ output_object->disable_payload_backoff =
+ ParseBool(attrs[kAttrDisablePayloadBackoff]);
+ output_object->powerwash_required = ParseBool(attrs[kAttrPowerwash]);
+
+ return true;
+}
+
+// If the transfer was successful, this uses expat to parse the response
+// and fill in the appropriate fields of the output object. Also, notifies
+// the processor that we're done.
+void OmahaRequestAction::TransferComplete(HttpFetcher* fetcher,
+ bool successful) {
+ ScopedActionCompleter completer(processor_, this);
+ string current_response(response_buffer_.begin(), response_buffer_.end());
+ LOG(INFO) << "Omaha request response: " << current_response;
+
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+ // Set the max kernel key version based on whether rollback is allowed.
+ SetMaxKernelKeyVersionForRollback();
+
+ // Events are best effort transactions -- assume they always succeed.
+ if (IsEvent()) {
+ CHECK(!HasOutputPipe()) << "No output pipe allowed for event requests.";
+ completer.set_code(ErrorCode::kSuccess);
+ return;
+ }
+
+ ErrorCode aux_error_code = fetcher->GetAuxiliaryErrorCode();
+ if (aux_error_code != ErrorCode::kSuccess) {
+ metrics::DownloadErrorCode download_error_code =
+ metrics_utils::GetDownloadErrorCode(aux_error_code);
+ system_state_->metrics_reporter()->ReportUpdateCheckMetrics(
+ system_state_,
+ metrics::CheckResult::kUnset,
+ metrics::CheckReaction::kUnset,
+ download_error_code);
+ }
+
+ if (!successful) {
+ int code = GetHTTPResponseCode();
+ LOG(ERROR) << "Omaha request network transfer failed with HTTPResponseCode="
+ << code;
+ // Makes sure we send proper error values.
+ if (code < 0 || code >= 1000) {
+ code = 999;
+ LOG(WARNING) << "Converting to proper HTTPResponseCode=" << code;
+ }
+ completer.set_code(static_cast<ErrorCode>(
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + code));
+ return;
+ }
+
+ XML_Parser parser = XML_ParserCreate(nullptr);
+ OmahaParserData parser_data(parser);
+ XML_SetUserData(parser, &parser_data);
+ XML_SetElementHandler(parser, ParserHandlerStart, ParserHandlerEnd);
+ XML_SetEntityDeclHandler(parser, ParserHandlerEntityDecl);
+ XML_Status res =
+ XML_Parse(parser,
+ reinterpret_cast<const char*>(response_buffer_.data()),
+ response_buffer_.size(),
+ XML_TRUE);
+
+ if (res != XML_STATUS_OK || parser_data.failed) {
+ LOG(ERROR) << "Omaha response not valid XML: "
+ << XML_ErrorString(XML_GetErrorCode(parser)) << " at line "
+ << XML_GetCurrentLineNumber(parser) << " col "
+ << XML_GetCurrentColumnNumber(parser);
+ XML_ParserFree(parser);
+ ErrorCode error_code = ErrorCode::kOmahaRequestXMLParseError;
+ if (response_buffer_.empty()) {
+ error_code = ErrorCode::kOmahaRequestEmptyResponseError;
+ } else if (parser_data.entity_decl) {
+ error_code = ErrorCode::kOmahaRequestXMLHasEntityDecl;
+ }
+ completer.set_code(error_code);
+ return;
+ }
+ XML_ParserFree(parser);
+
+ // Update the last ping day preferences based on the server daystart response
+ // even if we didn't send a ping. Omaha always includes the daystart in the
+ // response, but log the error if it didn't.
+ LOG_IF(ERROR, !UpdateLastPingDays(&parser_data, system_state_->prefs()))
+ << "Failed to update the last ping day preferences!";
+
+ // Sets first_active_omaha_ping_sent to true (vpd in CrOS). We only do this if
+ // we have got a response from omaha and if its value has never been set to
+ // true before. Failure of this function should be ignored. There should be no
+ // need to check if a=-1 has been sent because older devices have already sent
+ // their a=-1 in the past and we have to set first_active_omaha_ping_sent for
+ // future checks.
+ if (!system_state_->hardware()->GetFirstActiveOmahaPingSent()) {
+ if (!system_state_->hardware()->SetFirstActiveOmahaPingSent()) {
+ system_state_->metrics_reporter()->ReportInternalErrorCode(
+ ErrorCode::kFirstActiveOmahaPingSentPersistenceError);
+ }
+ }
+
+ // Create/update the metadata files for each DLC app received.
+ StorePingReply(parser_data);
+
+ if (!HasOutputPipe()) {
+ // Just set success to whether or not the http transfer succeeded,
+ // which must be true at this point in the code.
+ completer.set_code(ErrorCode::kSuccess);
+ return;
+ }
+
+ OmahaResponse output_object;
+ if (!ParseResponse(&parser_data, &output_object, &completer))
+ return;
+ ProcessExclusions(&output_object,
+ system_state_->request_params(),
+ system_state_->update_attempter()->GetExcluder());
+ output_object.update_exists = true;
+ SetOutputObject(output_object);
+
+ LoadOrPersistUpdateFirstSeenAtPref();
+
+ ErrorCode error = ErrorCode::kSuccess;
+ if (ShouldIgnoreUpdate(output_object, &error)) {
+ // No need to change output_object.update_exists here, since the value
+ // has been output to the pipe.
+ completer.set_code(error);
+ return;
+ }
+
+ // If Omaha says to disable p2p, respect that
+ if (output_object.disable_p2p_for_downloading) {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading as "
+ << "requested by Omaha.";
+ payload_state->SetUsingP2PForDownloading(false);
+ }
+ if (output_object.disable_p2p_for_sharing) {
+ LOG(INFO) << "Forcibly disabling use of p2p for sharing as "
+ << "requested by Omaha.";
+ payload_state->SetUsingP2PForSharing(false);
+ }
+
+ // Update the payload state with the current response. The payload state
+ // will automatically reset all stale state if this response is different
+ // from what's stored already. We are updating the payload state as late
+ // as possible in this method so that if a new release gets pushed and then
+ // got pulled back due to some issues, we don't want to clear our internal
+ // state unnecessarily.
+ payload_state->SetResponse(output_object);
+
+ // It could be we've already exceeded the deadline for when p2p is
+ // allowed or that we've tried too many times with p2p. Check that.
+ if (payload_state->GetUsingP2PForDownloading()) {
+ payload_state->P2PNewAttempt();
+ if (!payload_state->P2PAttemptAllowed()) {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading because "
+ << "of previous failures when using p2p.";
+ payload_state->SetUsingP2PForDownloading(false);
+ }
+ }
+
+ // From here on, we'll complete stuff in CompleteProcessing() so
+ // disable |completer| since we'll create a new one in that
+ // function.
+ completer.set_should_complete(false);
+
+ // If we're allowed to use p2p for downloading we do not pay
+ // attention to wall-clock-based waiting if the URL is indeed
+ // available via p2p. Therefore, check if the file is available via
+ // p2p before deferring...
+ if (payload_state->GetUsingP2PForDownloading()) {
+ LookupPayloadViaP2P(output_object);
+ } else {
+ CompleteProcessing();
+ }
+}
+
+void OmahaRequestAction::CompleteProcessing() {
+ ScopedActionCompleter completer(processor_, this);
+ OmahaResponse& output_object = const_cast<OmahaResponse&>(GetOutputObject());
+ PayloadStateInterface* payload_state = system_state_->payload_state();
+
+ if (ShouldDeferDownload(&output_object)) {
+ output_object.update_exists = false;
+ LOG(INFO) << "Ignoring Omaha updates as updates are deferred by policy.";
+ completer.set_code(ErrorCode::kOmahaUpdateDeferredPerPolicy);
+ return;
+ }
+
+ if (payload_state->ShouldBackoffDownload()) {
+ output_object.update_exists = false;
+ LOG(INFO) << "Ignoring Omaha updates in order to backoff our retry "
+ << "attempts";
+ completer.set_code(ErrorCode::kOmahaUpdateDeferredForBackoff);
+ return;
+ }
+ completer.set_code(ErrorCode::kSuccess);
+}
+
+void OmahaRequestAction::OnLookupPayloadViaP2PCompleted(const string& url) {
+ LOG(INFO) << "Lookup complete, p2p-client returned URL '" << url << "'";
+ if (!url.empty()) {
+ system_state_->payload_state()->SetP2PUrl(url);
+ } else {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading "
+ << "because no suitable peer could be found.";
+ system_state_->payload_state()->SetUsingP2PForDownloading(false);
+ }
+ CompleteProcessing();
+}
+
+void OmahaRequestAction::LookupPayloadViaP2P(const OmahaResponse& response) {
+ // If the device is in the middle of an update, the state variables
+ // kPrefsUpdateStateNextDataOffset, kPrefsUpdateStateNextDataLength
+ // tracks the offset and length of the operation currently in
+ // progress. The offset is based from the end of the manifest which
+ // is kPrefsManifestMetadataSize bytes long.
+ //
+ // To make forward progress and avoid deadlocks, we need to find a
+ // peer that has at least the entire operation we're currently
+ // working on. Otherwise we may end up in a situation where two
+ // devices bounce back and forth downloading from each other,
+ // neither making any forward progress until one of them decides to
+ // stop using p2p (via kMaxP2PAttempts and kMaxP2PAttemptTimeSeconds
+ // safe-guards). See http://crbug.com/297170 for an example)
+ size_t minimum_size = 0;
+ int64_t manifest_metadata_size = 0;
+ int64_t manifest_signature_size = 0;
+ int64_t next_data_offset = 0;
+ int64_t next_data_length = 0;
+ if (system_state_ &&
+ system_state_->prefs()->GetInt64(kPrefsManifestMetadataSize,
+ &manifest_metadata_size) &&
+ manifest_metadata_size != -1 &&
+ system_state_->prefs()->GetInt64(kPrefsManifestSignatureSize,
+ &manifest_signature_size) &&
+ manifest_signature_size != -1 &&
+ system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataOffset,
+ &next_data_offset) &&
+ next_data_offset != -1 &&
+ system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataLength,
+ &next_data_length)) {
+ minimum_size = manifest_metadata_size + manifest_signature_size +
+ next_data_offset + next_data_length;
+ }
+
+ // TODO(senj): Fix P2P for multiple package.
+ brillo::Blob raw_hash;
+ if (!base::HexStringToBytes(response.packages[0].hash, &raw_hash))
+ return;
+ string file_id =
+ utils::CalculateP2PFileId(raw_hash, response.packages[0].size);
+ if (system_state_->p2p_manager()) {
+ LOG(INFO) << "Checking if payload is available via p2p, file_id=" << file_id
+ << " minimum_size=" << minimum_size;
+ system_state_->p2p_manager()->LookupUrlForFile(
+ file_id,
+ minimum_size,
+ TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds),
+ base::Bind(&OmahaRequestAction::OnLookupPayloadViaP2PCompleted,
+ base::Unretained(this)));
+ }
+}
+
+bool OmahaRequestAction::ShouldDeferDownload(OmahaResponse* output_object) {
+ if (params_->interactive()) {
+ LOG(INFO) << "Not deferring download because update is interactive.";
+ return false;
+ }
+
+ // If we're using p2p to download _and_ we have a p2p URL, we never
+ // defer the download. This is because the download will always
+ // happen from a peer on the LAN and we've been waiting in line for
+ // our turn.
+ const PayloadStateInterface* payload_state = system_state_->payload_state();
+ if (payload_state->GetUsingP2PForDownloading() &&
+ !payload_state->GetP2PUrl().empty()) {
+ LOG(INFO) << "Download not deferred because download "
+ << "will happen from a local peer (via p2p).";
+ return false;
+ }
+
+ // We should defer the downloads only if we've first satisfied the
+ // wall-clock-based-waiting period and then the update-check-based waiting
+ // period, if required.
+ if (!params_->wall_clock_based_wait_enabled()) {
+ LOG(INFO) << "Wall-clock-based waiting period is not enabled,"
+ << " so no deferring needed.";
+ return false;
+ }
+
+ switch (IsWallClockBasedWaitingSatisfied(output_object)) {
+ case kWallClockWaitNotSatisfied:
+ // We haven't even satisfied the first condition, passing the
+ // wall-clock-based waiting period, so we should defer the downloads
+ // until that happens.
+ LOG(INFO) << "wall-clock-based-wait not satisfied.";
+ return true;
+
+ case kWallClockWaitDoneButUpdateCheckWaitRequired:
+ LOG(INFO) << "wall-clock-based-wait satisfied and "
+ << "update-check-based-wait required.";
+ return !IsUpdateCheckCountBasedWaitingSatisfied();
+
+ case kWallClockWaitDoneAndUpdateCheckWaitNotRequired:
+ // Wall-clock-based waiting period is satisfied, and it's determined
+ // that we do not need the update-check-based wait. so no need to
+ // defer downloads.
+ LOG(INFO) << "wall-clock-based-wait satisfied and "
+ << "update-check-based-wait is not required.";
+ return false;
+
+ default:
+ // Returning false for this default case so we err on the
+ // side of downloading updates than deferring in case of any bugs.
+ NOTREACHED();
+ return false;
+ }
+}
+
+OmahaRequestAction::WallClockWaitResult
+OmahaRequestAction::IsWallClockBasedWaitingSatisfied(
+ OmahaResponse* output_object) {
+ Time update_first_seen_at = LoadOrPersistUpdateFirstSeenAtPref();
+ if (update_first_seen_at == base::Time()) {
+ LOG(INFO) << "Not scattering as UpdateFirstSeenAt value cannot be read or "
+ "persisted";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ TimeDelta elapsed_time =
+ system_state_->clock()->GetWallclockTime() - update_first_seen_at;
+ TimeDelta max_scatter_period =
+ TimeDelta::FromDays(output_object->max_days_to_scatter);
+ int64_t staging_wait_time_in_days = 0;
+ // Use staging and its default max value if staging is on.
+ if (system_state_->prefs()->GetInt64(kPrefsWallClockStagingWaitPeriod,
+ &staging_wait_time_in_days) &&
+ staging_wait_time_in_days > 0)
+ max_scatter_period = TimeDelta::FromDays(kMaxWaitTimeStagingInDays);
+
+ LOG(INFO) << "Waiting Period = "
+ << utils::FormatSecs(params_->waiting_period().InSeconds())
+ << ", Time Elapsed = "
+ << utils::FormatSecs(elapsed_time.InSeconds())
+ << ", MaxDaysToScatter = " << max_scatter_period.InDays();
+
+ if (!output_object->deadline.empty()) {
+ // The deadline is set for all rules which serve a delta update from a
+ // previous FSI, which means this update will be applied mostly in OOBE
+ // cases. For these cases, we shouldn't scatter so as to finish the OOBE
+ // quickly.
+ LOG(INFO) << "Not scattering as deadline flag is set";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ if (max_scatter_period.InDays() == 0) {
+ // This means the Omaha rule creator decides that this rule
+ // should not be scattered irrespective of the policy.
+ LOG(INFO) << "Not scattering as MaxDaysToScatter in rule is 0.";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ if (elapsed_time > max_scatter_period) {
+ // This means we've waited more than the upperbound wait in the rule
+ // from the time we first saw a valid update available to us.
+ // This will prevent update starvation.
+ LOG(INFO) << "Not scattering as we're past the MaxDaysToScatter limit.";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ // This means we are required to participate in scattering.
+ // See if our turn has arrived now.
+ TimeDelta remaining_wait_time = params_->waiting_period() - elapsed_time;
+ if (remaining_wait_time.InSeconds() <= 0) {
+ // Yes, it's our turn now.
+ LOG(INFO) << "Successfully passed the wall-clock-based-wait.";
+
+ // But we can't download until the update-check-count-based wait is also
+ // satisfied, so mark it as required now if update checks are enabled.
+ return params_->update_check_count_wait_enabled()
+ ? kWallClockWaitDoneButUpdateCheckWaitRequired
+ : kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ // Not our turn yet, so we have to wait until our turn to
+ // help scatter the downloads across all clients of the enterprise.
+ LOG(INFO) << "Update deferred for another "
+ << utils::FormatSecs(remaining_wait_time.InSeconds())
+ << " per policy.";
+ return kWallClockWaitNotSatisfied;
+}
+
+bool OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied() {
+ int64_t update_check_count_value;
+
+ if (system_state_->prefs()->Exists(kPrefsUpdateCheckCount)) {
+ if (!system_state_->prefs()->GetInt64(kPrefsUpdateCheckCount,
+ &update_check_count_value)) {
+ // We are unable to read the update check count from file for some reason.
+ // So let's proceed anyway so as to not stall the update.
+ LOG(ERROR) << "Unable to read update check count. "
+ << "Skipping update-check-count-based-wait.";
+ return true;
+ }
+ } else {
+ // This file does not exist. This means we haven't started our update
+ // check count down yet, so this is the right time to start the count down.
+ update_check_count_value =
+ base::RandInt(params_->min_update_checks_needed(),
+ params_->max_update_checks_allowed());
+
+ LOG(INFO) << "Randomly picked update check count value = "
+ << update_check_count_value;
+
+ // Write out the initial value of update_check_count_value.
+ if (!system_state_->prefs()->SetInt64(kPrefsUpdateCheckCount,
+ update_check_count_value)) {
+ // We weren't able to write the update check count file for some reason.
+ // So let's proceed anyway so as to not stall the update.
+ LOG(ERROR) << "Unable to write update check count. "
+ << "Skipping update-check-count-based-wait.";
+ return true;
+ }
+ }
+
+ if (update_check_count_value == 0) {
+ LOG(INFO) << "Successfully passed the update-check-based-wait.";
+ return true;
+ }
+
+ if (update_check_count_value < 0 ||
+ update_check_count_value > params_->max_update_checks_allowed()) {
+ // We err on the side of skipping scattering logic instead of stalling
+ // a machine from receiving any updates in case of any unexpected state.
+ LOG(ERROR) << "Invalid value for update check count detected. "
+ << "Skipping update-check-count-based-wait.";
+ return true;
+ }
+
+ // Legal value, we need to wait for more update checks to happen
+ // until this becomes 0.
+ LOG(INFO) << "Deferring Omaha updates for another "
+ << update_check_count_value << " update checks per policy";
+ return false;
+}
+
+// static
+bool OmahaRequestAction::ParseInstallDate(OmahaParserData* parser_data,
+ OmahaResponse* output_object) {
+ int64_t elapsed_days = 0;
+ if (!base::StringToInt64(parser_data->daystart_elapsed_days, &elapsed_days))
+ return false;
+
+ if (elapsed_days < 0)
+ return false;
+
+ output_object->install_date_days = elapsed_days;
+ return true;
+}
+
+// static
+bool OmahaRequestAction::HasInstallDate(SystemState* system_state) {
+ PrefsInterface* prefs = system_state->prefs();
+ if (prefs == nullptr)
+ return false;
+
+ return prefs->Exists(kPrefsInstallDateDays);
+}
+
+// static
+bool OmahaRequestAction::PersistInstallDate(
+ SystemState* system_state,
+ int install_date_days,
+ InstallDateProvisioningSource source) {
+ TEST_AND_RETURN_FALSE(install_date_days >= 0);
+
+ PrefsInterface* prefs = system_state->prefs();
+ if (prefs == nullptr)
+ return false;
+
+ if (!prefs->SetInt64(kPrefsInstallDateDays, install_date_days))
+ return false;
+
+ system_state->metrics_reporter()->ReportInstallDateProvisioningSource(
+ static_cast<int>(source), // Sample.
+ kProvisionedMax); // Maximum.
+ return true;
+}
+
+bool OmahaRequestAction::PersistCohortData(const string& prefs_key,
+ const string& new_value) {
+ if (new_value.empty() && system_state_->prefs()->Exists(prefs_key)) {
+ LOG(INFO) << "Removing stored " << prefs_key << " value.";
+ return system_state_->prefs()->Delete(prefs_key);
+ } else if (!new_value.empty()) {
+ LOG(INFO) << "Storing new setting " << prefs_key << " as " << new_value;
+ return system_state_->prefs()->SetString(prefs_key, new_value);
+ }
+ return true;
+}
+
+bool OmahaRequestAction::PersistEolInfo(const map<string, string>& attrs) {
+ // If EOL date attribute is not sent, don't delete the old persisted EOL
+ // date information.
+ auto eol_date_attr = attrs.find(kAttrEolDate);
+ if (eol_date_attr != attrs.end()) {
+ const auto& eol_date = eol_date_attr->second;
+ if (!system_state_->prefs()->SetString(kPrefsOmahaEolDate, eol_date)) {
+ LOG(ERROR) << "Setting EOL date failed.";
+ return false;
+ }
+ LOG(INFO) << "Set EOL date to " << eol_date;
+ }
+ return true;
+}
+
+void OmahaRequestAction::ActionCompleted(ErrorCode code) {
+ // We only want to report this on "update check".
+ if (ping_only_ || event_ != nullptr)
+ return;
+
+ metrics::CheckResult result = metrics::CheckResult::kUnset;
+ metrics::CheckReaction reaction = metrics::CheckReaction::kUnset;
+ metrics::DownloadErrorCode download_error_code =
+ metrics::DownloadErrorCode::kUnset;
+
+ // Regular update attempt.
+ switch (code) {
+ case ErrorCode::kSuccess:
+ // OK, we parsed the response successfully but that does
+ // necessarily mean that an update is available.
+ if (HasOutputPipe()) {
+ const OmahaResponse& response = GetOutputObject();
+ if (response.update_exists) {
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kUpdating;
+ } else {
+ result = metrics::CheckResult::kNoUpdateAvailable;
+ }
+ } else {
+ result = metrics::CheckResult::kNoUpdateAvailable;
+ }
+ break;
+
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateIgnoredOverCellular:
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kIgnored;
+ break;
+
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kDeferring;
+ break;
+
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kBackingOff;
+ break;
+
+ default:
+ // We report two flavors of errors, "Download errors" and "Parsing
+ // error". Try to convert to the former and if that doesn't work
+ // we know it's the latter.
+ metrics::DownloadErrorCode tmp_error =
+ metrics_utils::GetDownloadErrorCode(code);
+ if (tmp_error != metrics::DownloadErrorCode::kInputMalformed) {
+ result = metrics::CheckResult::kDownloadError;
+ download_error_code = tmp_error;
+ } else {
+ result = metrics::CheckResult::kParsingError;
+ }
+ break;
+ }
+
+ system_state_->metrics_reporter()->ReportUpdateCheckMetrics(
+ system_state_, result, reaction, download_error_code);
+}
+
+bool OmahaRequestAction::ShouldIgnoreUpdate(const OmahaResponse& response,
+ ErrorCode* error) const {
+ // Note: policy decision to not update to a version we rolled back from.
+ string rollback_version =
+ system_state_->payload_state()->GetRollbackVersion();
+ if (!rollback_version.empty()) {
+ LOG(INFO) << "Detected previous rollback from version " << rollback_version;
+ if (rollback_version == response.version) {
+ LOG(INFO) << "Received version that we rolled back from. Ignoring.";
+ *error = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
+ return true;
+ }
+ }
+
+ if (system_state_->hardware()->IsOOBEEnabled() &&
+ !system_state_->hardware()->IsOOBEComplete(nullptr) &&
+ (response.deadline.empty() ||
+ system_state_->payload_state()->GetRollbackHappened()) &&
+ params_->app_version() != "ForcedUpdate") {
+ LOG(INFO) << "Ignoring a non-critical Omaha update before OOBE completion.";
+ *error = ErrorCode::kNonCriticalUpdateInOOBE;
+ return true;
+ }
+
+ if (!IsUpdateAllowedOverCurrentConnection(error, response)) {
+ LOG(INFO) << "Update is not allowed over current connection.";
+ return true;
+ }
+
+ // Currently non-critical updates always update alongside the platform update
+ // (a critical update) so this case should never actually be hit if the
+ // request to Omaha for updates are correct. In other words, stop the update
+ // from happening as there are no packages in the response to process.
+ if (response.packages.empty()) {
+ LOG(ERROR) << "All packages were excluded.";
+ }
+
+ // Note: We could technically delete the UpdateFirstSeenAt state when we
+ // return true. If we do, it'll mean a device has to restart the
+ // UpdateFirstSeenAt and thus help scattering take effect when the AU is
+ // turned on again. On the other hand, it also increases the chance of update
+ // starvation if an admin turns AU on/off more frequently. We choose to err on
+ // the side of preventing starvation at the cost of not applying scattering in
+ // those cases.
+ return false;
+}
+
+bool OmahaRequestAction::IsUpdateAllowedOverCellularByPrefs(
+ const OmahaResponse& response) const {
+ PrefsInterface* prefs = system_state_->prefs();
+
+ if (!prefs) {
+ LOG(INFO) << "Disabling updates over cellular as the preferences are "
+ "not available.";
+ return false;
+ }
+
+ bool is_allowed;
+
+ if (prefs->Exists(kPrefsUpdateOverCellularPermission) &&
+ prefs->GetBoolean(kPrefsUpdateOverCellularPermission, &is_allowed) &&
+ is_allowed) {
+ LOG(INFO) << "Allowing updates over cellular as permission preference is "
+ "set to true.";
+ return true;
+ }
+
+ if (!prefs->Exists(kPrefsUpdateOverCellularTargetVersion) ||
+ !prefs->Exists(kPrefsUpdateOverCellularTargetSize)) {
+ LOG(INFO) << "Disabling updates over cellular as permission preference is "
+ "set to false or does not exist while target does not exist.";
+ return false;
+ }
+
+ std::string target_version;
+ int64_t target_size;
+
+ if (!prefs->GetString(kPrefsUpdateOverCellularTargetVersion,
+ &target_version) ||
+ !prefs->GetInt64(kPrefsUpdateOverCellularTargetSize, &target_size)) {
+ LOG(INFO) << "Disabling updates over cellular as the target version or "
+ "size is not accessible.";
+ return false;
+ }
+
+ uint64_t total_packages_size = 0;
+ for (const auto& package : response.packages) {
+ total_packages_size += package.size;
+ }
+ if (target_version == response.version &&
+ static_cast<uint64_t>(target_size) == total_packages_size) {
+ LOG(INFO) << "Allowing updates over cellular as the target matches the"
+ "omaha response.";
+ return true;
+ } else {
+ LOG(INFO) << "Disabling updates over cellular as the target does not"
+ "match the omaha response.";
+ return false;
+ }
+}
+
+bool OmahaRequestAction::IsUpdateAllowedOverCurrentConnection(
+ ErrorCode* error, const OmahaResponse& response) const {
+ ConnectionType type;
+ ConnectionTethering tethering;
+ ConnectionManagerInterface* connection_manager =
+ system_state_->connection_manager();
+ if (!connection_manager->GetConnectionProperties(&type, &tethering)) {
+ LOG(INFO) << "We could not determine our connection type. "
+ << "Defaulting to allow updates.";
+ return true;
+ }
+
+ bool is_allowed = connection_manager->IsUpdateAllowedOver(type, tethering);
+ bool is_device_policy_set =
+ connection_manager->IsAllowedConnectionTypesForUpdateSet();
+ // Treats tethered connection as if it is cellular connection.
+ bool is_over_cellular = type == ConnectionType::kCellular ||
+ tethering == ConnectionTethering::kConfirmed;
+
+ if (!is_over_cellular) {
+ // There's no need to further check user preferences as we are not over
+ // cellular connection.
+ if (!is_allowed)
+ *error = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
+ } else if (is_device_policy_set) {
+ // There's no need to further check user preferences as the device policy
+ // is set regarding updates over cellular.
+ if (!is_allowed)
+ *error = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
+ } else {
+ // Deivce policy is not set, so user preferences overwrite whether to
+ // allow updates over cellular.
+ is_allowed = IsUpdateAllowedOverCellularByPrefs(response);
+ if (!is_allowed)
+ *error = ErrorCode::kOmahaUpdateIgnoredOverCellular;
+ }
+
+ LOG(INFO) << "We are connected via "
+ << connection_utils::StringForConnectionType(type)
+ << ", Updates allowed: " << (is_allowed ? "Yes" : "No");
+ return is_allowed;
+}
+
+bool OmahaRequestAction::IsRollbackEnabled() const {
+ if (policy_provider_->IsConsumerDevice()) {
+ LOG(INFO) << "Rollback is not enabled for consumer devices.";
+ return false;
+ }
+
+ if (!policy_provider_->device_policy_is_loaded()) {
+ LOG(INFO) << "No device policy is loaded. Assuming rollback enabled.";
+ return true;
+ }
+
+ int allowed_milestones;
+ if (!policy_provider_->GetDevicePolicy().GetRollbackAllowedMilestones(
+ &allowed_milestones)) {
+ LOG(INFO) << "RollbackAllowedMilestones policy can't be read. "
+ "Defaulting to rollback enabled.";
+ return true;
+ }
+
+ LOG(INFO) << "Rollback allows " << allowed_milestones << " milestones.";
+ return allowed_milestones > 0;
+}
+
+void OmahaRequestAction::SetMaxKernelKeyVersionForRollback() const {
+ int max_kernel_rollforward;
+ int min_kernel_version = system_state_->hardware()->GetMinKernelKeyVersion();
+ if (IsRollbackEnabled()) {
+ // If rollback is enabled, set the max kernel key version to the current
+ // kernel key version. This has the effect of freezing kernel key roll
+ // forwards.
+ //
+ // TODO(zentaro): This behavior is temporary, and ensures that no kernel
+ // key roll forward happens until the server side components of rollback
+ // are implemented. Future changes will allow the Omaha server to return
+ // the kernel key version from max_rollback_versions in the past. At that
+ // point the max kernel key version will be set to that value, creating a
+ // sliding window of versions that can be rolled back to.
+ LOG(INFO) << "Rollback is enabled. Setting kernel_max_rollforward to "
+ << min_kernel_version;
+ max_kernel_rollforward = min_kernel_version;
+ } else {
+ // For devices that are not rollback enabled (ie. consumer devices), the
+ // max kernel key version is set to 0xfffffffe, which is logically
+ // infinity. This maintains the previous behavior that that kernel key
+ // versions roll forward each time they are incremented.
+ LOG(INFO) << "Rollback is disabled. Setting kernel_max_rollforward to "
+ << kRollforwardInfinity;
+ max_kernel_rollforward = kRollforwardInfinity;
+ }
+
+ bool max_rollforward_set =
+ system_state_->hardware()->SetMaxKernelKeyRollforward(
+ max_kernel_rollforward);
+ if (!max_rollforward_set) {
+ LOG(ERROR) << "Failed to set kernel_max_rollforward";
+ }
+ // Report metrics
+ system_state_->metrics_reporter()->ReportKeyVersionMetrics(
+ min_kernel_version, max_kernel_rollforward, max_rollforward_set);
+}
+
+base::Time OmahaRequestAction::LoadOrPersistUpdateFirstSeenAtPref() const {
+ Time update_first_seen_at;
+ int64_t update_first_seen_at_int;
+ if (system_state_->prefs()->Exists(kPrefsUpdateFirstSeenAt)) {
+ if (system_state_->prefs()->GetInt64(kPrefsUpdateFirstSeenAt,
+ &update_first_seen_at_int)) {
+ // Note: This timestamp could be that of ANY update we saw in the past
+ // (not necessarily this particular update we're considering to apply)
+ // but never got to apply because of some reason (e.g. stop AU policy,
+ // updates being pulled out from Omaha, changes in target version prefix,
+ // new update being rolled out, etc.). But for the purposes of scattering
+ // it doesn't matter which update the timestamp corresponds to. i.e.
+ // the clock starts ticking the first time we see an update and we're
+ // ready to apply when the random wait period is satisfied relative to
+ // that first seen timestamp.
+ update_first_seen_at = Time::FromInternalValue(update_first_seen_at_int);
+ LOG(INFO) << "Using persisted value of UpdateFirstSeenAt: "
+ << utils::ToString(update_first_seen_at);
+ } else {
+ // This seems like an unexpected error where the persisted value exists
+ // but it's not readable for some reason.
+ LOG(INFO) << "UpdateFirstSeenAt value cannot be read";
+ return base::Time();
+ }
+ } else {
+ update_first_seen_at = system_state_->clock()->GetWallclockTime();
+ update_first_seen_at_int = update_first_seen_at.ToInternalValue();
+ if (system_state_->prefs()->SetInt64(kPrefsUpdateFirstSeenAt,
+ update_first_seen_at_int)) {
+ LOG(INFO) << "Persisted the new value for UpdateFirstSeenAt: "
+ << utils::ToString(update_first_seen_at);
+ } else {
+ // This seems like an unexpected error where the value cannot be
+ // persisted for some reason.
+ LOG(INFO) << "UpdateFirstSeenAt value "
+ << utils::ToString(update_first_seen_at)
+ << " cannot be persisted";
+ return base::Time();
+ }
+ }
+ return update_first_seen_at;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/omaha_request_action.h b/cros/omaha_request_action.h
new file mode 100644
index 0000000..1a3a912
--- /dev/null
+++ b/cros/omaha_request_action.h
@@ -0,0 +1,320 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_OMAHA_REQUEST_ACTION_H_
+#define UPDATE_ENGINE_CROS_OMAHA_REQUEST_ACTION_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include <brillo/secure_blob.h>
+#include <curl/curl.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/http_fetcher.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/cros/omaha_request_builder_xml.h"
+#include "update_engine/cros/omaha_response.h"
+
+// The Omaha Request action makes a request to Omaha and can output
+// the response on the output ActionPipe.
+
+namespace policy {
+class PolicyProvider;
+}
+
+namespace chromeos_update_engine {
+
+class NoneType;
+class OmahaRequestAction;
+class OmahaRequestParams;
+class PrefsInterface;
+
+// This struct is declared in the .cc file.
+struct OmahaParserData;
+
+template <>
+class ActionTraits<OmahaRequestAction> {
+ public:
+ // Takes parameters on the input pipe.
+ typedef NoneType InputObjectType;
+ // On UpdateCheck success, puts the Omaha response on output. Event
+ // requests do not have an output pipe.
+ typedef OmahaResponse OutputObjectType;
+};
+
+class OmahaRequestAction : public Action<OmahaRequestAction>,
+ public HttpFetcherDelegate {
+ public:
+ static const int kPingTimeJump = -2;
+ // We choose this value of 3 as a heuristic for a work day in trying
+ // each URL, assuming we check roughly every 45 mins. This is a good time to
+ // wait so we don't give up the preferred URLs, but allow using the URL that
+ // appears earlier in list for every payload before resorting to the fallback
+ // URLs in the candiate URL list.
+ static const int kDefaultMaxFailureCountPerUrl = 3;
+
+ // If staging is enabled, set the maximum wait time to 28 days, since that is
+ // the predetermined wait time for staging.
+ static const int kMaxWaitTimeStagingInDays = 28;
+
+ // These are the possible outcome upon checking whether we satisfied
+ // the wall-clock-based-wait.
+ enum WallClockWaitResult {
+ kWallClockWaitNotSatisfied,
+ kWallClockWaitDoneButUpdateCheckWaitRequired,
+ kWallClockWaitDoneAndUpdateCheckWaitNotRequired,
+ };
+
+ // The ctor takes in all the parameters that will be used for making
+ // the request to Omaha. For some of them we have constants that
+ // should be used.
+ //
+ // Takes ownership of the passed in HttpFetcher. Useful for testing.
+ //
+ // Takes ownership of the passed in OmahaEvent. If |event| is null,
+ // this is an UpdateCheck request, otherwise it's an Event request.
+ // Event requests always succeed.
+ //
+ // A good calling pattern is:
+ // OmahaRequestAction(..., new OmahaEvent(...), new WhateverHttpFetcher);
+ // or
+ // OmahaRequestAction(..., nullptr, new WhateverHttpFetcher);
+ OmahaRequestAction(SystemState* system_state,
+ OmahaEvent* event,
+ std::unique_ptr<HttpFetcher> http_fetcher,
+ bool ping_only,
+ const std::string& session_id);
+ ~OmahaRequestAction() override;
+ typedef ActionTraits<OmahaRequestAction>::InputObjectType InputObjectType;
+ typedef ActionTraits<OmahaRequestAction>::OutputObjectType OutputObjectType;
+ void PerformAction() override;
+ void TerminateProcessing() override;
+ void ActionCompleted(ErrorCode code) override;
+
+ int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
+ // Debugging/logging
+ static std::string StaticType() { return "OmahaRequestAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ // Delegate methods (see http_fetcher.h)
+ bool ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) override;
+
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override;
+
+ // Returns true if this is an Event request, false if it's an UpdateCheck.
+ bool IsEvent() const { return event_.get() != nullptr; }
+
+ private:
+ friend class OmahaRequestActionTest;
+ friend class OmahaRequestActionTestProcessorDelegate;
+ FRIEND_TEST(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE);
+ FRIEND_TEST(OmahaRequestActionTest,
+ GetInstallDateWhenOOBECompletedWithInvalidDate);
+ FRIEND_TEST(OmahaRequestActionTest,
+ GetInstallDateWhenOOBECompletedWithValidDate);
+ FRIEND_TEST(OmahaRequestActionTest,
+ GetInstallDateWhenOOBECompletedDateChanges);
+ friend class UpdateAttempterTest;
+ FRIEND_TEST(UpdateAttempterTest, SessionIdTestEnforceEmptyStrPingOmaha);
+ FRIEND_TEST(UpdateAttempterTest, SessionIdTestConsistencyInUpdateFlow);
+
+ // Enumeration used in PersistInstallDate().
+ enum InstallDateProvisioningSource {
+ kProvisionedFromOmahaResponse,
+ kProvisionedFromOOBEMarker,
+
+ // kProvisionedMax is the count of the number of enums above. Add
+ // any new enums above this line only.
+ kProvisionedMax
+ };
+
+ // Gets the install date, expressed as the number of PST8PDT
+ // calendar weeks since January 1st 2007, times seven. Returns -1 if
+ // unknown. See http://crbug.com/336838 for details about this value.
+ static int GetInstallDate(SystemState* system_state);
+
+ // Parses the Omaha Response in |doc| and sets the
+ // |install_date_days| field of |output_object| to the value of the
+ // elapsed_days attribute of the daystart element. Returns True if
+ // the value was set, False if it wasn't found.
+ static bool ParseInstallDate(OmahaParserData* parser_data,
+ OmahaResponse* output_object);
+
+ // Returns True if the kPrefsInstallDateDays state variable is set,
+ // False otherwise.
+ static bool HasInstallDate(SystemState* system_state);
+
+ // Writes |install_date_days| into the kPrefsInstallDateDays state
+ // variable and emits an UMA stat for the |source| used. Returns
+ // True if the value was written, False if an error occurred.
+ static bool PersistInstallDate(SystemState* system_state,
+ int install_date_days,
+ InstallDateProvisioningSource source);
+
+ // Persist the new cohort* value received in the XML file in the |prefs_key|
+ // preference file. If the |new_value| is empty, the currently stored value
+ // will be deleted. Don't call this function with an empty |new_value| if the
+ // value was not set in the XML, since that would delete the stored value.
+ bool PersistCohortData(const std::string& prefs_key,
+ const std::string& new_value);
+
+ // Parses and persists the end-of-life date flag sent back in the updatecheck
+ // tag attributes. The flags will be validated and stored in the Prefs.
+ bool PersistEolInfo(const std::map<std::string, std::string>& attrs);
+
+ // If this is an update check request, initializes
+ // |ping_active_days_| and |ping_roll_call_days_| to values that may
+ // be sent as pings to Omaha.
+ void InitPingDays();
+
+ // Based on the persistent preference store values, calculates the
+ // number of days since the last ping sent for |key|.
+ int CalculatePingDays(const std::string& key);
+
+ // Returns whether we have "active_days" or "roll_call_days" ping values to
+ // send to Omaha and thus we should include them in the response.
+ bool ShouldPing() const;
+
+ // Process Omaha's response to a ping request and store the results in the DLC
+ // metadata directory.
+ void StorePingReply(const OmahaParserData& parser_data) const;
+
+ // Returns true if the download of a new update should be deferred.
+ // False if the update can be downloaded.
+ bool ShouldDeferDownload(OmahaResponse* output_object);
+
+ // Returns true if the basic wall-clock-based waiting period has been
+ // satisfied based on the scattering policy setting. False otherwise.
+ // If true, it also indicates whether the additional update-check-count-based
+ // waiting period also needs to be satisfied before the download can begin.
+ WallClockWaitResult IsWallClockBasedWaitingSatisfied(
+ OmahaResponse* output_object);
+
+ // Returns true if the update-check-count-based waiting period has been
+ // satisfied. False otherwise.
+ bool IsUpdateCheckCountBasedWaitingSatisfied();
+
+ // Parses the response from Omaha that's available in |doc| using the other
+ // helper methods below and populates the |output_object| with the relevant
+ // values. Returns true if we should continue the parsing. False otherwise,
+ // in which case it sets any error code using |completer|.
+ bool ParseResponse(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the status property in the given update_check_node and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParseStatus(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the URL nodes in the given XML document and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParseUrls(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the other parameters in the given XML document and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParseParams(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Called by TransferComplete() to complete processing, either
+ // asynchronously after looking up resources via p2p or directly.
+ void CompleteProcessing();
+
+ // Helper to asynchronously look up payload on the LAN.
+ void LookupPayloadViaP2P(const OmahaResponse& response);
+
+ // Callback used by LookupPayloadViaP2P().
+ void OnLookupPayloadViaP2PCompleted(const std::string& url);
+
+ // Returns true if the current update should be ignored.
+ bool ShouldIgnoreUpdate(const OmahaResponse& response,
+ ErrorCode* error) const;
+
+ // Return true if updates are allowed by user preferences.
+ bool IsUpdateAllowedOverCellularByPrefs(const OmahaResponse& response) const;
+
+ // Returns true if updates are allowed over the current type of connection.
+ // False otherwise.
+ bool IsUpdateAllowedOverCurrentConnection(
+ ErrorCode* error, const OmahaResponse& response) const;
+
+ // Returns true if rollback is enabled. Always returns false for consumer
+ // devices.
+ bool IsRollbackEnabled() const;
+
+ // Sets the appropriate max kernel key version based on whether rollback is
+ // enabled.
+ void SetMaxKernelKeyVersionForRollback() const;
+
+ // Reads and returns the kPrefsUpdateFirstSeenAt pref if the pref currently
+ // exists. Otherwise saves the current wallclock time to the
+ // kPrefsUpdateFirstSeenAt pref and returns it as a base::Time object.
+ base::Time LoadOrPersistUpdateFirstSeenAtPref() const;
+
+ // Global system context.
+ SystemState* system_state_;
+
+ // Contains state that is relevant in the processing of the Omaha request.
+ OmahaRequestParams* params_;
+
+ // Pointer to the OmahaEvent info. This is an UpdateCheck request if null.
+ std::unique_ptr<OmahaEvent> event_;
+
+ // pointer to the HttpFetcher that does the http work
+ std::unique_ptr<HttpFetcher> http_fetcher_;
+
+ // Used for fetching information about the device policy.
+ std::unique_ptr<policy::PolicyProvider> policy_provider_;
+
+ // If true, only include the <ping> element in the request.
+ bool ping_only_;
+
+ // Stores the response from the omaha server
+ brillo::Blob response_buffer_;
+
+ // Initialized by InitPingDays to values that may be sent to Omaha
+ // as part of a ping message. Note that only positive values and -1
+ // are sent to Omaha.
+ int ping_active_days_;
+ int ping_roll_call_days_;
+
+ std::string session_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(OmahaRequestAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_OMAHA_REQUEST_ACTION_H_
diff --git a/cros/omaha_request_action_fuzzer.cc b/cros/omaha_request_action_fuzzer.cc
new file mode 100644
index 0000000..dd02467
--- /dev/null
+++ b/cros/omaha_request_action_fuzzer.cc
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <brillo/message_loops/fake_message_loop.h>
+
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/cros/fake_system_state.h"
+#include "update_engine/cros/omaha_request_action.h"
+
+class Environment {
+ public:
+ Environment() { logging::SetMinLogLevel(logging::LOG_FATAL); }
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ static Environment env;
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ chromeos_update_engine::FakeSystemState fake_system_state;
+ auto omaha_request_action =
+ std::make_unique<chromeos_update_engine::OmahaRequestAction>(
+ &fake_system_state,
+ nullptr,
+ std::make_unique<chromeos_update_engine::MockHttpFetcher>(
+ data, size, nullptr),
+ false,
+ "" /* session_id */);
+ auto collector_action =
+ std::make_unique<chromeos_update_engine::ObjectCollectorAction<
+ chromeos_update_engine::OmahaResponse>>();
+ BondActions(omaha_request_action.get(), collector_action.get());
+ chromeos_update_engine::ActionProcessor action_processor;
+ action_processor.EnqueueAction(std::move(omaha_request_action));
+ action_processor.EnqueueAction(std::move(collector_action));
+ action_processor.StartProcessing();
+
+ loop.Run();
+ return 0;
+}
diff --git a/cros/omaha_request_action_unittest.cc b/cros/omaha_request_action_unittest.cc
new file mode 100644
index 0000000..c3842b8
--- /dev/null
+++ b/cros/omaha_request_action_unittest.cc
@@ -0,0 +1,3055 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_request_action.h"
+
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/memory/ptr_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <expat.h>
+#include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_libpolicy.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/metrics_reporter_interface.h"
+#include "update_engine/common/mock_excluder.h"
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/cros/fake_system_state.h"
+#include "update_engine/cros/mock_connection_manager.h"
+#include "update_engine/cros/mock_payload_state.h"
+#include "update_engine/cros/omaha_request_builder_xml.h"
+#include "update_engine/cros/omaha_request_params.h"
+#include "update_engine/cros/omaha_utils.h"
+#include "update_engine/update_manager/rollback_prefs.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_manager::kRollforwardInfinity;
+using std::pair;
+using std::string;
+using std::vector;
+using testing::_;
+using testing::AllOf;
+using testing::AnyNumber;
+using testing::DoAll;
+using testing::Ge;
+using testing::Le;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::ReturnRef;
+using testing::SaveArg;
+using testing::SetArgPointee;
+using testing::StrictMock;
+
+namespace {
+
+static_assert(kRollforwardInfinity == 0xfffffffe,
+ "Don't change the value of kRollforward infinity unless its "
+ "size has been changed in firmware.");
+
+const char kCurrentVersion[] = "0.1.0.0";
+const char kTestAppId[] = "test-app-id";
+const char kTestAppId2[] = "test-app2-id";
+const char kTestAppIdSkipUpdatecheck[] = "test-app-id-skip-updatecheck";
+const char kDlcId1[] = "dlc-id-1";
+const char kDlcId2[] = "dlc-id-2";
+
+// This is a helper struct to allow unit tests build an update response with the
+// values they care about.
+struct FakeUpdateResponse {
+ string GetRollbackVersionAttributes() const {
+ string num_milestones;
+ num_milestones = base::NumberToString(rollback_allowed_milestones);
+ const string rollback_version =
+ " _firmware_version_" + num_milestones + "=\"" +
+ past_rollback_key_version.first + "\"" + " _kernel_version_" +
+ num_milestones + "=\"" + past_rollback_key_version.second + "\"";
+
+ return (rollback ? " _rollback=\"true\"" : "") + rollback_version +
+ (!rollback_firmware_version.empty()
+ ? " _firmware_version=\"" + rollback_firmware_version + "\""
+ : "") +
+ (!rollback_kernel_version.empty()
+ ? " _kernel_version=\"" + rollback_kernel_version + "\""
+ : "");
+ }
+
+ string GetNoUpdateResponse() const {
+ string entity_str;
+ if (include_entity)
+ entity_str = "<!DOCTYPE response [<!ENTITY CrOS \"ChromeOS\">]>";
+ return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + entity_str +
+ "<response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"" +
+ app_id + "\" " +
+ (include_cohorts
+ ? "cohort=\"" + cohort + "\" cohorthint=\"" + cohorthint +
+ "\" cohortname=\"" + cohortname + "\" "
+ : "") +
+ " status=\"ok\">"
+ "<ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app>" +
+ (multi_app_no_update
+ ? "<app appid=\"" + app_id2 +
+ "\"><updatecheck status=\"noupdate\"/></app>"
+ : "") +
+ "</response>";
+ }
+
+ string GetUpdateResponse() const {
+ chromeos_update_engine::OmahaRequestParams request_params{nullptr};
+ request_params.set_app_id(app_id);
+ return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"" +
+ (elapsed_days.empty() ? ""
+ : (" elapsed_days=\"" + elapsed_days + "\"")) +
+ "/>"
+ "<app appid=\"" +
+ app_id + "\" " +
+ (include_cohorts
+ ? "cohort=\"" + cohort + "\" cohorthint=\"" + cohorthint +
+ "\" cohortname=\"" + cohortname + "\" "
+ : "") +
+ " status=\"ok\">"
+ "<ping status=\"ok\"/><updatecheck status=\"ok\"" +
+ GetRollbackVersionAttributes() + ">" + "<urls><url codebase=\"" +
+ codebase +
+ "\"/></urls>"
+ "<manifest version=\"" +
+ 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\"/>"
+ : "") +
+ "</packages>"
+ "<actions><action event=\"postinstall\" MetadataSize=\"11" +
+ (multi_package ? ":22" : "") + "\" MoreInfo=\"" + more_info_url +
+ "\" Prompt=\"" + prompt +
+ "\" "
+ "IsDeltaPayload=\"true" +
+ (multi_package ? ":false" : "") +
+ "\" "
+ "MaxDaysToScatter=\"" +
+ max_days_to_scatter +
+ "\" "
+ "sha256=\"not-used\" " +
+ (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) +
+ (disable_p2p_for_downloading ? "DisableP2PForDownloading=\"true\" "
+ : "") +
+ (disable_p2p_for_sharing ? "DisableP2PForSharing=\"true\" " : "") +
+ (powerwash ? "Powerwash=\"true\" " : "") +
+ "/></actions></manifest></updatecheck></app>" +
+ (multi_app
+ ? "<app appid=\"" + app_id2 + "\"" +
+ (include_cohorts ? " cohort=\"cohort2\"" : "") +
+ "><updatecheck status=\"ok\"><urls><url codebase=\"" +
+ codebase2 + "\"/></urls><manifest version=\"" + version2 +
+ "\"><packages>"
+ "<package name=\"package3\" size=\"333\" "
+ "hash_sha256=\"hash3\"/></packages>"
+ "<actions><action event=\"postinstall\" " +
+ (multi_app_self_update
+ ? "noupdate=\"true\" IsDeltaPayload=\"true\" "
+ : "IsDeltaPayload=\"false\" ") +
+ "MetadataSize=\"33\"/></actions>"
+ "</manifest></updatecheck></app>"
+ : "") +
+ (multi_app_no_update
+ ? "<app><updatecheck status=\"noupdate\"/></app>"
+ : "") +
+ (multi_app_skip_updatecheck
+ ? "<app appid=\"" + app_id_skip_updatecheck + "\"></app>"
+ : "") +
+ (dlc_app_update
+ ? "<app appid=\"" + request_params.GetDlcAppId(kDlcId1) +
+ "\" status=\"ok\">"
+ "<updatecheck status=\"ok\"><urls><url codebase=\"" +
+ codebase + "\"/><url codebase=\"" + codebase2 +
+ "\"/></urls><manifest version=\"" + version +
+ "\"><packages><package name=\"package3\" size=\"333\" "
+ "hash_sha256=\"hash3\"/></packages><actions>"
+ "<action event=\"install\" run=\".signed\"/>"
+ "<action event=\"postinstall\" MetadataSize=\"33\"/>"
+ "</actions></manifest></updatecheck></app>"
+ : "") +
+ (dlc_app_no_update
+ ? "<app appid=\"" + request_params.GetDlcAppId(kDlcId2) +
+ "\"><updatecheck status=\"noupdate\"/></app>"
+ : "") +
+ "</response>";
+ }
+
+ // Return the payload URL, which is split in two fields in the XML response.
+ string GetPayloadUrl() { return codebase + filename; }
+
+ string app_id = kTestAppId;
+ string app_id2 = kTestAppId2;
+ string app_id_skip_updatecheck = kTestAppIdSkipUpdatecheck;
+ string current_version = kCurrentVersion;
+ string version = "1.2.3.4";
+ string version2 = "2.3.4.5";
+ string more_info_url = "http://more/info";
+ string prompt = "true";
+ string codebase = "http://code/base/";
+ string codebase2 = "http://code/base/2/";
+ string filename = "file.signed";
+ string hash = "4841534831323334";
+ uint64_t size = 123;
+ string deadline = "";
+ string max_days_to_scatter = "7";
+ string elapsed_days = "42";
+
+ // P2P setting defaults to allowed.
+ bool disable_p2p_for_downloading = false;
+ bool disable_p2p_for_sharing = false;
+
+ bool powerwash = false;
+
+ // Omaha cohorts settings.
+ bool include_cohorts = false;
+ string cohort = "";
+ string cohorthint = "";
+ string cohortname = "";
+
+ // Whether to include the CrOS <!ENTITY> in the XML response.
+ bool include_entity = false;
+
+ // Whether to include more than one app.
+ bool multi_app = false;
+ // Whether to include an app with noupdate="true".
+ bool multi_app_self_update = false;
+ // Whether to include an additional app with status="noupdate".
+ bool multi_app_no_update = false;
+ // Whether to include an additional app with no updatecheck tag.
+ bool multi_app_skip_updatecheck = false;
+ // Whether to include more than one package in an app.
+ bool multi_package = false;
+ // Whether to include a DLC app with updatecheck tag.
+ bool dlc_app_update = false;
+ // Whether to include a DLC app with no updatecheck tag.
+ bool dlc_app_no_update = false;
+
+ // Whether the payload is a rollback.
+ bool rollback = false;
+ // The verified boot firmware key version for the rollback image.
+ string rollback_firmware_version = "";
+ // The verified boot kernel key version for the rollback image.
+ string rollback_kernel_version = "";
+ // The number of milestones back that the verified boot key version has been
+ // supplied.
+ uint32_t rollback_allowed_milestones = 0;
+ // The verified boot key version for the
+ // |current - rollback_allowed_milestones| most recent release.
+ // The pair contains <firmware_key_version, kernel_key_version> each
+ // of which is in the form "key_version.version".
+ pair<string, string> past_rollback_key_version;
+};
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+class OmahaRequestActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ OmahaRequestActionTestProcessorDelegate()
+ : expected_code_(ErrorCode::kSuccess),
+ interactive_(false),
+ test_http_fetcher_headers_(false) {}
+ ~OmahaRequestActionTestProcessorDelegate() override = default;
+
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) override {
+ brillo::MessageLoop::current()->BreakLoop();
+ }
+
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) override {
+ // Make sure actions always succeed.
+ if (action->Type() == OmahaRequestAction::StaticType()) {
+ EXPECT_EQ(expected_code_, code);
+ // Check that the headers were set in the fetcher during the action. Note
+ // that we set this request as "interactive".
+ auto fetcher = static_cast<const MockHttpFetcher*>(
+ static_cast<OmahaRequestAction*>(action)->http_fetcher_.get());
+
+ if (test_http_fetcher_headers_) {
+ EXPECT_EQ(interactive_ ? "fg" : "bg",
+ fetcher->GetHeader("X-Goog-Update-Interactivity"));
+ EXPECT_EQ(kTestAppId, fetcher->GetHeader("X-Goog-Update-AppId"));
+ EXPECT_NE("", fetcher->GetHeader("X-Goog-Update-Updater"));
+ }
+ post_data_ = fetcher->post_data();
+ } else if (action->Type() ==
+ ObjectCollectorAction<OmahaResponse>::StaticType()) {
+ EXPECT_EQ(ErrorCode::kSuccess, code);
+ auto collector_action =
+ static_cast<ObjectCollectorAction<OmahaResponse>*>(action);
+ omaha_response_.reset(new OmahaResponse(collector_action->object()));
+ EXPECT_TRUE(omaha_response_);
+ } else {
+ EXPECT_EQ(ErrorCode::kSuccess, code);
+ }
+ }
+ ErrorCode expected_code_;
+ brillo::Blob post_data_;
+ bool interactive_;
+ bool test_http_fetcher_headers_;
+ std::unique_ptr<OmahaResponse> omaha_response_;
+};
+
+struct TestUpdateCheckParams {
+ string http_response;
+ int fail_http_response_code;
+ bool ping_only;
+ bool is_consumer_device;
+ int rollback_allowed_milestones;
+ bool is_policy_loaded;
+ ErrorCode expected_code;
+ metrics::CheckResult expected_check_result;
+ metrics::CheckReaction expected_check_reaction;
+ metrics::DownloadErrorCode expected_download_error_code;
+ string session_id;
+};
+
+class OmahaRequestActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ request_params_.set_os_sp("service_pack");
+ request_params_.set_os_board("x86-generic");
+ request_params_.set_app_id(kTestAppId);
+ request_params_.set_app_version(kCurrentVersion);
+ request_params_.set_app_lang("en-US");
+ request_params_.set_current_channel("unittest");
+ request_params_.set_target_channel("unittest");
+ request_params_.set_hwid("OEM MODEL 09235 7471");
+ request_params_.set_delta_okay(true);
+ request_params_.set_interactive(false);
+ request_params_.set_update_url("http://url");
+ request_params_.set_target_version_prefix("");
+ request_params_.set_rollback_allowed(false);
+ request_params_.set_is_powerwash_allowed(false);
+ request_params_.set_is_install(false);
+ request_params_.set_dlc_apps_params({});
+
+ fake_system_state_.set_request_params(&request_params_);
+ fake_system_state_.set_prefs(&fake_prefs_);
+
+ // Setting the default update check params. Lookup |TestUpdateCheck()|.
+ tuc_params_ = {
+ .http_response = "",
+ .fail_http_response_code = -1,
+ .ping_only = false,
+ .is_consumer_device = true,
+ .rollback_allowed_milestones = 0,
+ .is_policy_loaded = false,
+ .expected_code = ErrorCode::kSuccess,
+ .expected_check_result = metrics::CheckResult::kUpdateAvailable,
+ .expected_check_reaction = metrics::CheckReaction::kUpdating,
+ .expected_download_error_code = metrics::DownloadErrorCode::kUnset,
+ };
+
+ ON_CALL(*fake_system_state_.mock_update_attempter(), GetExcluder())
+ .WillByDefault(Return(&mock_excluder_));
+ }
+
+ // This function uses the parameters in |tuc_params_| to do an update check.
+ // It will fill out |post_str| with the result data and |response| with
+ // |OmahaResponse|. Returns true iff an output response was obtained from the
+ // |OmahaRequestAction|. If |fail_http_response_code| is non-negative, the
+ // transfer will fail with that code. |ping_only| is passed through to the
+ // |OmahaRequestAction| constructor.
+ //
+ // The |expected_check_result|, |expected_check_reaction| and
+ // |expected_error_code| parameters are for checking expectations about
+ // reporting UpdateEngine.Check.{Result,Reaction,DownloadError} UMA
+ // statistics. Use the appropriate ::kUnset value to specify that the given
+ // metric should not be reported.
+ bool TestUpdateCheck();
+
+ // Tests events using |event| and |https_response|. It will fill up |post_str|
+ // with the result data.
+ void TestEvent(OmahaEvent* event, const string& http_response);
+
+ // Runs and checks a ping test. |ping_only| indicates whether it should send
+ // only a ping or also an updatecheck.
+ void PingTest(bool ping_only);
+
+ // InstallDate test helper function.
+ bool InstallDateParseHelper(const string& elapsed_days,
+ OmahaResponse* response);
+
+ // P2P test helper function.
+ void P2PTest(bool initial_allow_p2p_for_downloading,
+ bool initial_allow_p2p_for_sharing,
+ bool omaha_disable_p2p_for_downloading,
+ bool omaha_disable_p2p_for_sharing,
+ bool payload_state_allow_p2p_attempt,
+ bool expect_p2p_client_lookup,
+ const string& p2p_client_result_url,
+ bool expected_allow_p2p_for_downloading,
+ bool expected_allow_p2p_for_sharing,
+ const string& expected_p2p_url);
+
+ StrictMock<MockExcluder> mock_excluder_;
+ FakeSystemState fake_system_state_;
+ FakeUpdateResponse fake_update_response_;
+ // Used by all tests.
+ OmahaRequestParams request_params_{&fake_system_state_};
+
+ FakePrefs fake_prefs_;
+
+ OmahaRequestActionTestProcessorDelegate delegate_;
+
+ bool test_http_fetcher_headers_{false};
+
+ TestUpdateCheckParams tuc_params_;
+
+ // TODO(ahassani): Add trailing _ to these two variables.
+ OmahaResponse response;
+ string post_str;
+};
+
+class OmahaRequestActionDlcPingTest : public OmahaRequestActionTest {
+ protected:
+ void SetUp() override {
+ OmahaRequestActionTest::SetUp();
+ dlc_id_ = "dlc0";
+ active_key_ = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id_, kPrefsPingActive});
+ last_active_key_ = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id_, kPrefsPingLastActive});
+ last_rollcall_key_ = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id_, kPrefsPingLastRollcall});
+
+ tuc_params_.http_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\"><daystart elapsed_days=\"4763\" "
+ "elapsed_seconds=\"36540\"/><app appid=\"test-app-id\" status=\"ok\">\""
+ "<updatecheck status=\"noupdate\"/></app><app "
+ "appid=\"test-app-id_dlc0\" "
+ "status=\"ok\"><ping status=\"ok\"/><updatecheck status=\"noupdate\"/>"
+ "</app></response>";
+ tuc_params_.expected_check_result =
+ metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+ }
+
+ std::string dlc_id_;
+ std::string active_key_;
+ std::string last_active_key_;
+ std::string last_rollcall_key_;
+};
+bool OmahaRequestActionTest::TestUpdateCheck() {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+ auto fetcher =
+ std::make_unique<MockHttpFetcher>(tuc_params_.http_response.data(),
+ tuc_params_.http_response.size(),
+ nullptr);
+ if (tuc_params_.fail_http_response_code >= 0) {
+ fetcher->FailTransfer(tuc_params_.fail_http_response_code);
+ }
+ // This ensures the tests didn't forget to update fake_system_state_ if they
+ // are not using the default request_params_.
+ EXPECT_EQ(&request_params_, fake_system_state_.request_params());
+
+ auto omaha_request_action =
+ std::make_unique<OmahaRequestAction>(&fake_system_state_,
+ nullptr,
+ std::move(fetcher),
+ tuc_params_.ping_only,
+ tuc_params_.session_id);
+
+ auto mock_policy_provider =
+ std::make_unique<NiceMock<policy::MockPolicyProvider>>();
+ EXPECT_CALL(*mock_policy_provider, IsConsumerDevice())
+ .WillRepeatedly(Return(tuc_params_.is_consumer_device));
+
+ EXPECT_CALL(*mock_policy_provider, device_policy_is_loaded())
+ .WillRepeatedly(Return(tuc_params_.is_policy_loaded));
+
+ const policy::MockDevicePolicy device_policy;
+ const bool get_allowed_milestone_succeeds =
+ tuc_params_.rollback_allowed_milestones >= 0;
+ EXPECT_CALL(device_policy, GetRollbackAllowedMilestones(_))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<0>(tuc_params_.rollback_allowed_milestones),
+ Return(get_allowed_milestone_succeeds)));
+
+ EXPECT_CALL(*mock_policy_provider, GetDevicePolicy())
+ .WillRepeatedly(ReturnRef(device_policy));
+ omaha_request_action->policy_provider_ = std::move(mock_policy_provider);
+
+ delegate_.expected_code_ = tuc_params_.expected_code;
+ delegate_.interactive_ = request_params_.interactive();
+ delegate_.test_http_fetcher_headers_ = test_http_fetcher_headers_;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate_);
+
+ auto collector_action =
+ std::make_unique<ObjectCollectorAction<OmahaResponse>>();
+ BondActions(omaha_request_action.get(), collector_action.get());
+ processor.EnqueueAction(std::move(omaha_request_action));
+ processor.EnqueueAction(std::move(collector_action));
+
+ EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+ ReportUpdateCheckMetrics(_, _, _, _))
+ .Times(AnyNumber());
+
+ EXPECT_CALL(
+ *fake_system_state_.mock_metrics_reporter(),
+ ReportUpdateCheckMetrics(_,
+ tuc_params_.expected_check_result,
+ tuc_params_.expected_check_reaction,
+ tuc_params_.expected_download_error_code))
+ .Times(tuc_params_.ping_only ? 0 : 1);
+
+ loop.PostTask(base::Bind(
+ [](ActionProcessor* processor) { processor->StartProcessing(); },
+ base::Unretained(&processor)));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+ if (delegate_.omaha_response_)
+ response = *delegate_.omaha_response_;
+ post_str = string(delegate_.post_data_.begin(), delegate_.post_data_.end());
+ return delegate_.omaha_response_ != nullptr;
+}
+
+// Tests Event requests -- they should always succeed. |out_post_data| may be
+// null; if non-null, the post-data received by the mock HttpFetcher is
+// returned.
+void OmahaRequestActionTest::TestEvent(OmahaEvent* event,
+ const string& http_response) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ auto action = std::make_unique<OmahaRequestAction>(
+ &fake_system_state_,
+ event,
+ std::make_unique<MockHttpFetcher>(
+ http_response.data(), http_response.size(), nullptr),
+ false,
+ "");
+ ActionProcessor processor;
+ processor.set_delegate(&delegate_);
+ processor.EnqueueAction(std::move(action));
+
+ loop.PostTask(base::Bind(
+ [](ActionProcessor* processor) { processor->StartProcessing(); },
+ base::Unretained(&processor)));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+
+ post_str = string(delegate_.post_data_.begin(), delegate_.post_data_.end());
+}
+
+TEST_F(OmahaRequestActionTest, RejectEntities) {
+ fake_update_response_.include_entity = true;
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLHasEntityDecl;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoUpdateTest) {
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppNoUpdateTest) {
+ fake_update_response_.multi_app_no_update = true;
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppNoPartialUpdateTest) {
+ fake_update_response_.multi_app_no_update = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoSelfUpdateTest) {
+ tuc_params_.http_response =
+ "<response><app><updatecheck status=\"ok\"><manifest><actions><action "
+ "event=\"postinstall\" noupdate=\"true\"/></actions>"
+ "</manifest></updatecheck></app></response>";
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+// Test that all the values in the response are parsed in a normal update
+// response.
+TEST_F(OmahaRequestActionTest, ValidUpdateTest) {
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(fake_update_response_.version, response.version);
+ EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+ response.packages[0].payload_urls[0]);
+ 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(true, response.packages[0].is_delta);
+ EXPECT_EQ(fake_update_response_.prompt == "true", response.prompt);
+ EXPECT_EQ(fake_update_response_.deadline, response.deadline);
+ EXPECT_FALSE(response.powerwash_required);
+ // Omaha cohort attributes are not set in the response, so they should not be
+ // persisted.
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohort));
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortHint));
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortName));
+}
+
+TEST_F(OmahaRequestActionTest, MultiPackageUpdateTest) {
+ fake_update_response_.multi_package = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(fake_update_response_.version, response.version);
+ EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+ response.packages[0].payload_urls[0]);
+ EXPECT_EQ(fake_update_response_.codebase + "package2",
+ 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(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(22u, response.packages[1].metadata_size);
+ EXPECT_EQ(false, response.packages[1].is_delta);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppUpdateTest) {
+ fake_update_response_.multi_app = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(fake_update_response_.version, response.version);
+ EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+ response.packages[0].payload_urls[0]);
+ EXPECT_EQ(fake_update_response_.codebase2 + "package3",
+ 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(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(33u, response.packages[1].metadata_size);
+ EXPECT_EQ(false, response.packages[1].is_delta);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppPartialUpdateTest) {
+ fake_update_response_.multi_app = true;
+ fake_update_response_.multi_app_self_update = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(fake_update_response_.version, response.version);
+ EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+ 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(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(33u, response.packages[1].metadata_size);
+ EXPECT_EQ(true, response.packages[1].is_delta);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppMultiPackageUpdateTest) {
+ fake_update_response_.multi_app = true;
+ fake_update_response_.multi_package = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(fake_update_response_.version, response.version);
+ EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+ response.packages[0].payload_urls[0]);
+ EXPECT_EQ(fake_update_response_.codebase + "package2",
+ response.packages[1].payload_urls[0]);
+ EXPECT_EQ(fake_update_response_.codebase2 + "package3",
+ 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(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(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(33u, response.packages[2].metadata_size);
+ EXPECT_EQ(false, response.packages[2].is_delta);
+}
+
+TEST_F(OmahaRequestActionTest, PowerwashTest) {
+ fake_update_response_.powerwash = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_TRUE(response.powerwash_required);
+}
+
+TEST_F(OmahaRequestActionTest, ExtraHeadersSentInteractiveTest) {
+ request_params_.set_interactive(true);
+ test_http_fetcher_headers_ = true;
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ExtraHeadersSentNoInteractiveTest) {
+ request_params_.set_interactive(false);
+ test_http_fetcher_headers_ = true;
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByConnection) {
+ // Set up a connection manager that doesn't allow a valid update over
+ // the current ethernet connection.
+ MockConnectionManager mock_cm;
+ fake_system_state_.set_connection_manager(&mock_cm);
+
+ EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kEthernet),
+ SetArgPointee<1>(ConnectionTethering::kUnknown),
+ Return(true)));
+ EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kEthernet, _))
+ .WillRepeatedly(Return(false));
+
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kIgnored;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularAllowedByDevicePolicy) {
+ // This test tests that update over cellular is allowed as device policy
+ // says yes.
+ MockConnectionManager mock_cm;
+ fake_system_state_.set_connection_manager(&mock_cm);
+
+ EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+ SetArgPointee<1>(ConnectionTethering::kUnknown),
+ Return(true)));
+ EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+ .WillRepeatedly(Return(true));
+
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularBlockedByDevicePolicy) {
+ // This test tests that update over cellular is blocked as device policy
+ // says no.
+ MockConnectionManager mock_cm;
+ fake_system_state_.set_connection_manager(&mock_cm);
+
+ EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+ SetArgPointee<1>(ConnectionTethering::kUnknown),
+ Return(true)));
+ EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+ .WillRepeatedly(Return(false));
+
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kIgnored;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest,
+ ValidUpdateOverCellularAllowedByUserPermissionTrue) {
+ // This test tests that, when device policy is not set, update over cellular
+ // is allowed as permission for update over cellular is set to true.
+ MockConnectionManager mock_cm;
+ fake_prefs_.SetBoolean(kPrefsUpdateOverCellularPermission, true);
+ fake_system_state_.set_connection_manager(&mock_cm);
+
+ EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+ SetArgPointee<1>(ConnectionTethering::kUnknown),
+ Return(true)));
+ EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+ .WillRepeatedly(Return(true));
+
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest,
+ ValidUpdateOverCellularBlockedByUpdateTargetNotMatch) {
+ // This test tests that, when device policy is not set and permission for
+ // update over cellular is set to false or does not exist, update over
+ // cellular is blocked as update target does not match the omaha response.
+ MockConnectionManager mock_cm;
+ // A version different from the version in omaha response.
+ string diff_version = "99.99.99";
+ // A size different from the size in omaha response.
+ int64_t diff_size = 999;
+
+ fake_prefs_.SetString(kPrefsUpdateOverCellularTargetVersion, diff_version);
+ fake_prefs_.SetInt64(kPrefsUpdateOverCellularTargetSize, diff_size);
+ // This test tests cellular (3G) being the only connection type being allowed.
+ fake_system_state_.set_connection_manager(&mock_cm);
+
+ EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+ SetArgPointee<1>(ConnectionTethering::kUnknown),
+ Return(true)));
+ EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+ .WillRepeatedly(Return(true));
+
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaUpdateIgnoredOverCellular;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kIgnored;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest,
+ ValidUpdateOverCellularAllowedByUpdateTargetMatch) {
+ // This test tests that, when device policy is not set and permission for
+ // update over cellular is set to false or does not exist, update over
+ // cellular is allowed as update target matches the omaha response.
+ MockConnectionManager mock_cm;
+ // A version same as the version in omaha response.
+ string new_version = fake_update_response_.version;
+ // A size same as the size in omaha response.
+ int64_t new_size = fake_update_response_.size;
+
+ fake_prefs_.SetString(kPrefsUpdateOverCellularTargetVersion, new_version);
+ fake_prefs_.SetInt64(kPrefsUpdateOverCellularTargetSize, new_size);
+ fake_system_state_.set_connection_manager(&mock_cm);
+
+ EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+ SetArgPointee<1>(ConnectionTethering::kUnknown),
+ Return(true)));
+ EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+ .WillRepeatedly(Return(true));
+
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByRollback) {
+ string rollback_version = "1234.0.0";
+ MockPayloadState mock_payload_state;
+ fake_system_state_.set_payload_state(&mock_payload_state);
+ fake_update_response_.version = rollback_version;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kIgnored;
+
+ EXPECT_CALL(mock_payload_state, GetRollbackVersion())
+ .WillRepeatedly(Return(rollback_version));
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+// Verify that update checks called during OOBE will not try to download an
+// update if the response doesn't include the deadline field.
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBE) {
+ fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kNonCriticalUpdateInOOBE;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ // TODO(senj): set better default value for metrics::checkresult in
+ // OmahaRequestAction::ActionCompleted.
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+// Verify that the IsOOBEComplete() value is ignored when the OOBE flow is not
+// enabled.
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBEDisabled) {
+ fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+ fake_system_state_.fake_hardware()->SetIsOOBEEnabled(false);
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+// Verify that update checks called during OOBE will still try to download an
+// update if the response includes the deadline field.
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBEDeadlineSet) {
+ fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+// Verify that update checks called during OOBE will not try to download an
+// update if a rollback happened, even when the response includes the deadline
+// field.
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBERollback) {
+ fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kNonCriticalUpdateInOOBE;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetRollbackHappened())
+ .WillOnce(Return(true));
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+// Verify that non-critical updates are skipped by reporting the
+// kNonCriticalUpdateInOOBE error code when attempted over cellular network -
+// i.e. when the update would need user permission. Note that reporting
+// kOmahaUpdateIgnoredOverCellular error in this case might cause undesired UX
+// in OOBE (warning the user about an update that will be skipped).
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesInOOBEOverCellular) {
+ fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+
+ MockConnectionManager mock_cm;
+ fake_system_state_.set_connection_manager(&mock_cm);
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kNonCriticalUpdateInOOBE;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+ SetArgPointee<1>(ConnectionTethering::kUnknown),
+ Return(true)));
+ EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+ .WillRepeatedly(Return(false));
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, WallClockBasedWaitAloneCausesScattering) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_update_check_count_wait_enabled(false);
+ request_params_.set_waiting_period(TimeDelta::FromDays(2));
+ fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest,
+ WallClockBasedWaitAloneCausesScatteringInteractive) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_update_check_count_wait_enabled(false);
+ request_params_.set_waiting_period(TimeDelta::FromDays(2));
+ request_params_.set_interactive(true);
+ fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ // Verify if we are interactive check we don't defer.
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoWallClockBasedWaitCausesNoScattering) {
+ request_params_.set_wall_clock_based_wait_enabled(false);
+ request_params_.set_waiting_period(TimeDelta::FromDays(2));
+ request_params_.set_update_check_count_wait_enabled(true);
+ request_params_.set_min_update_checks_needed(1);
+ request_params_.set_max_update_checks_allowed(8);
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ZeroMaxDaysToScatterCausesNoScattering) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_waiting_period(TimeDelta::FromDays(2));
+ request_params_.set_update_check_count_wait_enabled(true);
+ request_params_.set_min_update_checks_needed(1);
+ request_params_.set_max_update_checks_allowed(8);
+ fake_update_response_.max_days_to_scatter = "0";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ZeroUpdateCheckCountCausesNoScattering) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_waiting_period(TimeDelta());
+ request_params_.set_update_check_count_wait_enabled(true);
+ request_params_.set_min_update_checks_needed(0);
+ request_params_.set_max_update_checks_allowed(0);
+ fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ int64_t count;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+ ASSERT_EQ(count, 0);
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NonZeroUpdateCheckCountCausesScattering) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_waiting_period(TimeDelta());
+ request_params_.set_update_check_count_wait_enabled(true);
+ request_params_.set_min_update_checks_needed(1);
+ request_params_.set_max_update_checks_allowed(8);
+ fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ int64_t count;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+ ASSERT_GT(count, 0);
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest,
+ NonZeroUpdateCheckCountCausesScatteringInteractive) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_waiting_period(TimeDelta());
+ request_params_.set_update_check_count_wait_enabled(true);
+ request_params_.set_min_update_checks_needed(1);
+ request_params_.set_max_update_checks_allowed(8);
+ request_params_.set_interactive(true);
+ fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ // Verify if we are interactive check we don't defer.
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ExistingUpdateCheckCountCausesScattering) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_waiting_period(TimeDelta());
+ request_params_.set_update_check_count_wait_enabled(true);
+ request_params_.set_min_update_checks_needed(1);
+ request_params_.set_max_update_checks_allowed(8);
+ fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring;
+
+ ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsUpdateCheckCount, 5));
+ ASSERT_FALSE(TestUpdateCheck());
+
+ int64_t count;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+ // |count| remains the same, as the decrementing happens in update_attempter
+ // which this test doesn't exercise.
+ ASSERT_EQ(count, 5);
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest,
+ ExistingUpdateCheckCountCausesScatteringInteractive) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_waiting_period(TimeDelta());
+ request_params_.set_update_check_count_wait_enabled(true);
+ request_params_.set_min_update_checks_needed(1);
+ request_params_.set_max_update_checks_allowed(8);
+ request_params_.set_interactive(true);
+ fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsUpdateCheckCount, 5));
+
+ // Verify if we are interactive check we don't defer.
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, StagingTurnedOnCausesScattering) {
+ // If staging is on, the value for max days to scatter should be ignored, and
+ // staging's scatter value should be used.
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_waiting_period(TimeDelta::FromDays(6));
+ request_params_.set_update_check_count_wait_enabled(false);
+ fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+
+ ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsWallClockStagingWaitPeriod, 6));
+ // This should not prevent scattering due to staging.
+ fake_update_response_.max_days_to_scatter = "0";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+
+ // Interactive updates should not be affected.
+ request_params_.set_interactive(true);
+ tuc_params_.expected_code = ErrorCode::kSuccess;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUpdating;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, CohortsArePersisted) {
+ fake_update_response_.include_cohorts = true;
+ fake_update_response_.cohort = "s/154454/8479665";
+ fake_update_response_.cohorthint = "please-put-me-on-beta";
+ fake_update_response_.cohortname = "stable";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ string value;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+ EXPECT_EQ(fake_update_response_.cohort, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+ EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+ EXPECT_EQ(fake_update_response_.cohortname, value);
+}
+
+TEST_F(OmahaRequestActionTest, CohortsAreUpdated) {
+ EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
+ EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortHint, "old_hint"));
+ EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortName, "old_name"));
+ fake_update_response_.include_cohorts = true;
+ fake_update_response_.cohort = "s/154454/8479665";
+ fake_update_response_.cohorthint = "please-put-me-on-beta";
+ fake_update_response_.cohortname = "";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ string value;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+ EXPECT_EQ(fake_update_response_.cohort, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+ EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+ EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+}
+
+TEST_F(OmahaRequestActionTest, CohortsAreNotModifiedWhenMissing) {
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
+ ASSERT_TRUE(TestUpdateCheck());
+
+ string value;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+ EXPECT_EQ("old_value", value);
+
+ EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+ EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+}
+
+TEST_F(OmahaRequestActionTest, CohortsArePersistedWhenNoUpdate) {
+ fake_update_response_.include_cohorts = true;
+ fake_update_response_.cohort = "s/154454/8479665";
+ fake_update_response_.cohorthint = "please-put-me-on-beta";
+ fake_update_response_.cohortname = "stable";
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ string value;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+ EXPECT_EQ(fake_update_response_.cohort, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+ EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+ EXPECT_EQ(fake_update_response_.cohortname, value);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppCohortTest) {
+ fake_update_response_.multi_app = true;
+ fake_update_response_.include_cohorts = true;
+ fake_update_response_.cohort = "s/154454/8479665";
+ fake_update_response_.cohorthint = "please-put-me-on-beta";
+ fake_update_response_.cohortname = "stable";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ string value;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+ EXPECT_EQ(fake_update_response_.cohort, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+ EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+ EXPECT_EQ(fake_update_response_.cohortname, value);
+}
+
+TEST_F(OmahaRequestActionTest, NoOutputPipeTest) {
+ const string http_response(fake_update_response_.GetNoUpdateResponse());
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ auto action = std::make_unique<OmahaRequestAction>(
+ &fake_system_state_,
+ nullptr,
+ std::make_unique<MockHttpFetcher>(
+ http_response.data(), http_response.size(), nullptr),
+ false,
+ "");
+ ActionProcessor processor;
+ processor.set_delegate(&delegate_);
+ processor.EnqueueAction(std::move(action));
+
+ loop.PostTask(base::Bind(
+ [](ActionProcessor* processor) { processor->StartProcessing(); },
+ base::Unretained(&processor)));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+ EXPECT_FALSE(processor.IsRunning());
+}
+
+TEST_F(OmahaRequestActionTest, InvalidXmlTest) {
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, EmptyResponseTest) {
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestEmptyResponseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingStatusTest) {
+ tuc_params_.http_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"foo\" status=\"ok\">"
+ "<ping status=\"ok\"/>"
+ "<updatecheck/></app></response>";
+ tuc_params_.expected_code = ErrorCode::kOmahaResponseInvalid;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, InvalidStatusTest) {
+ tuc_params_.http_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"foo\" status=\"ok\">"
+ "<ping status=\"ok\"/>"
+ "<updatecheck status=\"InvalidStatusTest\"/></app></response>";
+ tuc_params_.expected_code = ErrorCode::kOmahaResponseInvalid;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingNodesetTest) {
+ tuc_params_.http_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"foo\" status=\"ok\">"
+ "<ping status=\"ok\"/>"
+ "</app></response>";
+ tuc_params_.expected_code = ErrorCode::kOmahaResponseInvalid;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingFieldTest) {
+ tuc_params_.http_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ // the appid needs to match that in the request params
+ "<app appid=\"" +
+ fake_update_response_.app_id +
+ "\" status=\"ok\">"
+ "<updatecheck status=\"ok\">"
+ "<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>"
+ "<actions><action event=\"postinstall\" "
+ "Prompt=\"false\" "
+ "IsDeltaPayload=\"false\" "
+ "sha256=\"not-used\" "
+ "/></actions></manifest></updatecheck></app></response>";
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ("10.2.3.4", response.version);
+ EXPECT_EQ("http://missing/field/test/f",
+ response.packages[0].payload_urls[0]);
+ EXPECT_EQ("", response.more_info_url);
+ EXPECT_EQ("lkq34j5345", response.packages[0].hash);
+ EXPECT_EQ(587u, response.packages[0].size);
+ EXPECT_FALSE(response.prompt);
+ EXPECT_TRUE(response.deadline.empty());
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingStopped(const ActionProcessor* processor) {
+ brillo::MessageLoop::current()->BreakLoop();
+ }
+};
+
+void TerminateTransferTestStarter(ActionProcessor* processor) {
+ processor->StartProcessing();
+ CHECK(processor->IsRunning());
+ processor->StopProcessing();
+}
+} // namespace
+
+TEST_F(OmahaRequestActionTest, TerminateTransferTest) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ string http_response("doesn't matter");
+ auto action = std::make_unique<OmahaRequestAction>(
+ &fake_system_state_,
+ nullptr,
+ std::make_unique<MockHttpFetcher>(
+ http_response.data(), http_response.size(), nullptr),
+ false,
+ "");
+ TerminateEarlyTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(std::move(action));
+
+ loop.PostTask(base::Bind(&TerminateTransferTestStarter, &processor));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+}
+
+TEST_F(OmahaRequestActionTest, XmlEncodeIsUsedForParams) {
+ // Make sure XML Encode is being called on the params.
+ request_params_.set_os_sp("testtheservice_pack>");
+ request_params_.set_os_board("x86 generic<id");
+ request_params_.set_current_channel("unittest_track<");
+ request_params_.set_target_channel("unittest_track<");
+ request_params_.set_lts_tag("unittest_hint<");
+ request_params_.set_hwid("<OEM MODEL>");
+ fake_prefs_.SetString(kPrefsOmahaCohort, "evil\nstring");
+ fake_prefs_.SetString(kPrefsOmahaCohortHint, "evil&string\\");
+ fake_prefs_.SetString(
+ kPrefsOmahaCohortName,
+ base::JoinString(vector<string>(100, "My spoon is too big."), " "));
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_NE(string::npos, post_str.find("testtheservice_pack>"));
+ EXPECT_EQ(string::npos, post_str.find("testtheservice_pack>"));
+ EXPECT_NE(string::npos, post_str.find("x86 generic<id"));
+ EXPECT_EQ(string::npos, post_str.find("x86 generic<id"));
+ EXPECT_NE(string::npos, post_str.find("unittest_track&lt;"));
+ EXPECT_EQ(string::npos, post_str.find("unittest_track<"));
+ EXPECT_NE(string::npos, post_str.find("unittest_hint&lt;"));
+ EXPECT_EQ(string::npos, post_str.find("unittest_hint<"));
+ EXPECT_NE(string::npos, post_str.find("<OEM MODEL>"));
+ EXPECT_EQ(string::npos, post_str.find("<OEM MODEL>"));
+ EXPECT_NE(string::npos, post_str.find("cohort=\"evil\nstring\""));
+ EXPECT_EQ(string::npos, post_str.find("cohorthint=\"evil&string\\\""));
+ EXPECT_NE(string::npos, post_str.find("cohorthint=\"evil&string\\\""));
+ // Values from Prefs that are too big are removed from the XML instead of
+ // encoded.
+ EXPECT_EQ(string::npos, post_str.find("cohortname="));
+}
+
+TEST_F(OmahaRequestActionTest, XmlDecodeTest) {
+ fake_update_response_.deadline = "<20110101";
+ fake_update_response_.more_info_url = "testthe<url";
+ fake_update_response_.codebase = "testthe&codebase/";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_EQ("testthe<url", response.more_info_url);
+ EXPECT_EQ("testthe&codebase/file.signed",
+ response.packages[0].payload_urls[0]);
+ EXPECT_EQ("<20110101", response.deadline);
+}
+
+TEST_F(OmahaRequestActionTest, ParseIntTest) {
+ // overflows int32_t:
+ fake_update_response_.size = 123123123123123ull;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+}
+
+TEST_F(OmahaRequestActionTest, FormatUpdateCheckOutputTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ EXPECT_CALL(prefs, GetString(kPrefsPreviousVersion, _))
+ .WillOnce(DoAll(SetArgPointee<1>(string("")), Return(true)));
+ // An existing but empty previous version means that we didn't reboot to a new
+ // update, therefore, no need to update the previous version.
+ EXPECT_CALL(prefs, SetString(kPrefsPreviousVersion, _)).Times(0);
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_NE(
+ post_str.find(" <ping active=\"1\" a=\"-1\" r=\"-1\"></ping>\n"
+ " <updatecheck></updatecheck>\n"),
+ string::npos);
+ EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
+ string::npos);
+ // No <event> tag should be sent if we didn't reboot to an update.
+ EXPECT_EQ(post_str.find("<event"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, FormatSuccessEventOutputTest) {
+ TestEvent(new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
+ "invalid xml>");
+
+ string expected_event = base::StringPrintf(
+ " <event eventtype=\"%d\" eventresult=\"%d\"></event>\n",
+ OmahaEvent::kTypeUpdateDownloadStarted,
+ OmahaEvent::kResultSuccess);
+ EXPECT_NE(post_str.find(expected_event), string::npos);
+ EXPECT_EQ(post_str.find("ping"), string::npos);
+ EXPECT_EQ(post_str.find("updatecheck"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, FormatErrorEventOutputTest) {
+ TestEvent(new OmahaEvent(OmahaEvent::kTypeDownloadComplete,
+ OmahaEvent::kResultError,
+ ErrorCode::kError),
+ "invalid xml>");
+
+ string expected_event = base::StringPrintf(
+ " <event eventtype=\"%d\" eventresult=\"%d\" "
+ "errorcode=\"%d\"></event>\n",
+ OmahaEvent::kTypeDownloadComplete,
+ OmahaEvent::kResultError,
+ static_cast<int>(ErrorCode::kError));
+ EXPECT_NE(post_str.find(expected_event), string::npos);
+ EXPECT_EQ(post_str.find("updatecheck"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, IsEventTest) {
+ string http_response("doesn't matter");
+ OmahaRequestAction update_check_action(
+ &fake_system_state_,
+ nullptr,
+ std::make_unique<MockHttpFetcher>(
+ http_response.data(), http_response.size(), nullptr),
+ false,
+ "");
+ EXPECT_FALSE(update_check_action.IsEvent());
+
+ OmahaRequestAction event_action(
+ &fake_system_state_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+ std::make_unique<MockHttpFetcher>(
+ http_response.data(), http_response.size(), nullptr),
+ false,
+ "");
+ EXPECT_TRUE(event_action.IsEvent());
+}
+
+TEST_F(OmahaRequestActionTest, FormatDeltaOkayOutputTest) {
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ for (int i = 0; i < 2; i++) {
+ bool delta_okay = i == 1;
+ const char* delta_okay_str = delta_okay ? "true" : "false";
+ request_params_.set_delta_okay(delta_okay);
+
+ ASSERT_FALSE(TestUpdateCheck());
+ EXPECT_NE(
+ post_str.find(base::StringPrintf(" delta_okay=\"%s\"", delta_okay_str)),
+ string::npos)
+ << "i = " << i;
+ }
+}
+
+TEST_F(OmahaRequestActionTest, FormatInteractiveOutputTest) {
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ for (int i = 0; i < 2; i++) {
+ bool interactive = i == 1;
+ const char* interactive_str = interactive ? "ondemandupdate" : "scheduler";
+ request_params_.set_interactive(interactive);
+
+ ASSERT_FALSE(TestUpdateCheck());
+ EXPECT_NE(post_str.find(
+ base::StringPrintf("installsource=\"%s\"", interactive_str)),
+ string::npos)
+ << "i = " << i;
+ }
+}
+
+TEST_F(OmahaRequestActionTest, FormatTargetVersionPrefixOutputTest) {
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ for (int i = 0; i < 2; i++) {
+ bool target_version_set = i == 1;
+ const char* target_version_prefix = target_version_set ? "10032." : "";
+ request_params_.set_target_version_prefix(target_version_prefix);
+
+ ASSERT_FALSE(TestUpdateCheck());
+ if (target_version_set) {
+ EXPECT_NE(post_str.find("<updatecheck targetversionprefix=\"10032.\">"),
+ string::npos)
+ << "i = " << i;
+ } else {
+ EXPECT_EQ(post_str.find("targetversionprefix"), string::npos)
+ << "i = " << i;
+ }
+ }
+}
+
+TEST_F(OmahaRequestActionTest, FormatRollbackAllowedOutputTest) {
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ for (int i = 0; i < 4; i++) {
+ bool rollback_allowed = i / 2 == 0;
+ bool target_version_set = i % 2 == 0;
+ request_params_.set_target_version_prefix(target_version_set ? "10032."
+ : "");
+ request_params_.set_rollback_allowed(rollback_allowed);
+
+ ASSERT_FALSE(TestUpdateCheck());
+ if (rollback_allowed && target_version_set) {
+ EXPECT_NE(post_str.find("rollback_allowed=\"true\""), string::npos)
+ << "i = " << i;
+ } else {
+ EXPECT_EQ(post_str.find("rollback_allowed"), string::npos) << "i = " << i;
+ }
+ }
+}
+
+TEST_F(OmahaRequestActionTest, OmahaEventTest) {
+ OmahaEvent default_event;
+ EXPECT_EQ(OmahaEvent::kTypeUnknown, default_event.type);
+ EXPECT_EQ(OmahaEvent::kResultError, default_event.result);
+ EXPECT_EQ(ErrorCode::kError, default_event.error_code);
+
+ OmahaEvent success_event(OmahaEvent::kTypeUpdateDownloadStarted);
+ EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadStarted, success_event.type);
+ EXPECT_EQ(OmahaEvent::kResultSuccess, success_event.result);
+ EXPECT_EQ(ErrorCode::kSuccess, success_event.error_code);
+
+ OmahaEvent error_event(OmahaEvent::kTypeUpdateDownloadFinished,
+ OmahaEvent::kResultError,
+ ErrorCode::kError);
+ EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadFinished, error_event.type);
+ EXPECT_EQ(OmahaEvent::kResultError, error_event.result);
+ EXPECT_EQ(ErrorCode::kError, error_event.error_code);
+}
+
+TEST_F(OmahaRequestActionTest, DeviceQuickFixBuildTokenIsSetTest) {
+ // If DeviceQuickFixBuildToken value is set it takes precedence over pref
+ // value.
+ constexpr char autoupdate_token[] = "autoupdate_token>";
+ constexpr char xml_encoded_autoupdate_token[] = "autoupdate_token>";
+ constexpr char omaha_cohort_hint[] = "cohort_hint";
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+ request_params_.set_autoupdate_token(autoupdate_token);
+ fake_prefs_.SetString(kPrefsOmahaCohortHint, omaha_cohort_hint);
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_NE(string::npos,
+ post_str.find("cohorthint=\"" +
+ string(xml_encoded_autoupdate_token) + "\""));
+ EXPECT_EQ(string::npos, post_str.find(autoupdate_token));
+ EXPECT_EQ(string::npos, post_str.find(omaha_cohort_hint));
+}
+
+TEST_F(OmahaRequestActionTest, DeviceQuickFixBuildTokenIsNotSetTest) {
+ // If DeviceQuickFixBuildToken is not set, pref value will be provided in
+ // cohorthint attribute.
+ constexpr char omaha_cohort_hint[] = "evil_string>";
+ constexpr char xml_encoded_cohort_hint[] = "evil_string>";
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+ fake_prefs_.SetString(kPrefsOmahaCohortHint, omaha_cohort_hint);
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_NE(
+ string::npos,
+ post_str.find("cohorthint=\"" + string(xml_encoded_cohort_hint) + "\""));
+ EXPECT_EQ(string::npos, post_str.find(omaha_cohort_hint));
+}
+
+TEST_F(OmahaRequestActionTest, TargetChannelHintTest) {
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+ request_params_.set_lts_tag("hint>");
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_NE(string::npos, post_str.find("ltstag=\"hint>\""));
+}
+
+void OmahaRequestActionTest::PingTest(bool ping_only) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ // Add a few hours to the day difference to test no rounding, etc.
+ int64_t five_days_ago =
+ (Time::Now() - TimeDelta::FromHours(5 * 24 + 13)).ToInternalValue();
+ int64_t six_days_ago =
+ (Time::Now() - TimeDelta::FromHours(6 * 24 + 11)).ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(six_days_ago), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(five_days_ago), Return(true)));
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.ping_only = ping_only;
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_NE(post_str.find("<ping active=\"1\" a=\"6\" r=\"5\"></ping>"),
+ string::npos);
+ if (ping_only) {
+ EXPECT_EQ(post_str.find("updatecheck"), string::npos);
+ EXPECT_EQ(post_str.find("previousversion"), string::npos);
+ } else {
+ EXPECT_NE(post_str.find("updatecheck"), string::npos);
+ EXPECT_NE(post_str.find("previousversion"), string::npos);
+ }
+}
+
+TEST_F(OmahaRequestActionTest, PingTestSendOnlyAPing) {
+ PingTest(true /* ping_only */);
+}
+
+TEST_F(OmahaRequestActionTest, PingTestSendAlsoAnUpdateCheck) {
+ PingTest(false /* ping_only */);
+}
+
+TEST_F(OmahaRequestActionTest, ActivePingTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ int64_t three_days_ago =
+ (Time::Now() - TimeDelta::FromHours(3 * 24 + 12)).ToInternalValue();
+ int64_t now = Time::Now().ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(three_days_ago), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(now), Return(true)));
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_NE(post_str.find("<ping active=\"1\" a=\"3\"></ping>"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, RollCallPingTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ int64_t four_days_ago =
+ (Time::Now() - TimeDelta::FromHours(4 * 24)).ToInternalValue();
+ int64_t now = Time::Now().ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(now), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(four_days_ago), Return(true)));
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_NE(post_str.find("<ping active=\"1\" r=\"4\"></ping>\n"),
+ string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, NoPingTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ int64_t one_hour_ago =
+ (Time::Now() - TimeDelta::FromHours(1)).ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(one_hour_ago), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(one_hour_ago), Return(true)));
+ // LastActivePingDay and PrefsLastRollCallPingDay are set even if we didn't
+ // send a ping.
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(Return(true));
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_EQ(post_str.find("ping"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, IgnoreEmptyPingTest) {
+ // This test ensures that we ignore empty ping only requests.
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ int64_t now = Time::Now().ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(now), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(now), Return(true)));
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.ping_only = true;
+ tuc_params_.expected_check_result = metrics::CheckResult::kUnset;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(post_str.empty());
+}
+
+TEST_F(OmahaRequestActionTest, BackInTimePingTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ int64_t future =
+ (Time::Now() + TimeDelta::FromHours(3 * 24 + 4)).ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(future), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgPointee<1>(future), Return(true)));
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(Return(true));
+
+ tuc_params_.http_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\"><daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app></response>";
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_EQ(post_str.find("ping"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, LastPingDayUpdateTest) {
+ // This test checks that the action updates the last ping day to now
+ // minus 200 seconds with a slack of 5 seconds. Therefore, the test
+ // may fail if it runs for longer than 5 seconds. It shouldn't run
+ // that long though.
+ int64_t midnight =
+ (Time::Now() - TimeDelta::FromSeconds(200)).ToInternalValue();
+ int64_t midnight_slack =
+ (Time::Now() - TimeDelta::FromSeconds(195)).ToInternalValue();
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs,
+ SetInt64(kPrefsLastActivePingDay,
+ AllOf(Ge(midnight), Le(midnight_slack))))
+ .WillOnce(Return(true));
+ EXPECT_CALL(prefs,
+ SetInt64(kPrefsLastRollCallPingDay,
+ AllOf(Ge(midnight), Le(midnight_slack))))
+ .WillOnce(Return(true));
+
+ tuc_params_.http_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\"><daystart elapsed_seconds=\"200\"/>"
+ "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app></response>";
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+}
+
+TEST_F(OmahaRequestActionTest, NoElapsedSecondsTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+
+ tuc_params_.http_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\"><daystart blah=\"200\"/>"
+ "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app></response>";
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+}
+
+TEST_F(OmahaRequestActionTest, BadElapsedSecondsTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+
+ tuc_params_.http_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\"><daystart elapsed_seconds=\"x\"/>"
+ "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app></response>";
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+}
+
+TEST_F(OmahaRequestActionTest, NoUniqueIDTest) {
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_EQ(post_str.find("machineid="), string::npos);
+ EXPECT_EQ(post_str.find("userid="), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, NetworkFailureTest) {
+ const int http_error_code =
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 501;
+ tuc_params_.fail_http_response_code = 501;
+ tuc_params_.expected_code = static_cast<ErrorCode>(http_error_code);
+ tuc_params_.expected_check_result = metrics::CheckResult::kDownloadError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+ tuc_params_.expected_download_error_code =
+ static_cast<metrics::DownloadErrorCode>(501);
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NetworkFailureBadHTTPCodeTest) {
+ const int http_error_code =
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 999;
+
+ tuc_params_.fail_http_response_code = 1500;
+ tuc_params_.expected_code = static_cast<ErrorCode>(http_error_code);
+ tuc_params_.expected_check_result = metrics::CheckResult::kDownloadError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+ tuc_params_.expected_download_error_code =
+ metrics::DownloadErrorCode::kHttpStatusOther;
+
+ ASSERT_FALSE(TestUpdateCheck());
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsPersistedFirstTime) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_waiting_period(TimeDelta().FromDays(1));
+ request_params_.set_update_check_count_wait_enabled(false);
+
+ Time arbitrary_date;
+ ASSERT_TRUE(Time::FromString("6/4/1989", &arbitrary_date));
+ fake_system_state_.fake_clock()->SetWallclockTime(arbitrary_date);
+
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ int64_t timestamp = 0;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, ×tamp));
+ EXPECT_EQ(arbitrary_date.ToInternalValue(), timestamp);
+ EXPECT_FALSE(response.update_exists);
+
+ // Verify if we are interactive check we don't defer.
+ request_params_.set_interactive(true);
+ tuc_params_.expected_code = ErrorCode::kSuccess;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUpdating;
+
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsUsedIfAlreadyPresent) {
+ request_params_.set_wall_clock_based_wait_enabled(true);
+ request_params_.set_waiting_period(TimeDelta().FromDays(1));
+ request_params_.set_update_check_count_wait_enabled(false);
+
+ Time t1, t2;
+ ASSERT_TRUE(Time::FromString("1/1/2012", &t1));
+ ASSERT_TRUE(Time::FromString("1/3/2012", &t2));
+ ASSERT_TRUE(
+ fake_prefs_.SetInt64(kPrefsUpdateFirstSeenAt, t1.ToInternalValue()));
+ fake_system_state_.fake_clock()->SetWallclockTime(t2);
+
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+
+ // Make sure the timestamp t1 is unchanged showing that it was reused.
+ int64_t timestamp = 0;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, ×tamp));
+ ASSERT_TRUE(timestamp == t1.ToInternalValue());
+}
+
+TEST_F(OmahaRequestActionTest, TestChangingToMoreStableChannel) {
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ request_params_.set_root(tempdir.GetPath().value());
+ request_params_.set_app_id("{22222222-2222-2222-2222-222222222222}");
+ request_params_.set_app_version("1.2.3.4");
+ request_params_.set_product_components("o.bundle=1");
+ request_params_.set_current_channel("canary-channel");
+ EXPECT_TRUE(
+ request_params_.SetTargetChannel("stable-channel", true, nullptr));
+ request_params_.UpdateDownloadChannel();
+ EXPECT_TRUE(request_params_.ShouldPowerwash());
+
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_NE(
+ string::npos,
+ post_str.find("appid=\"{22222222-2222-2222-2222-222222222222}\" "
+ "version=\"0.0.0.0\" from_version=\"1.2.3.4\" "
+ "track=\"stable-channel\" from_track=\"canary-channel\" "));
+ EXPECT_EQ(string::npos, post_str.find("o.bundle"));
+}
+
+TEST_F(OmahaRequestActionTest, TestChangingToLessStableChannel) {
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ request_params_.set_root(tempdir.GetPath().value());
+ request_params_.set_app_id("{11111111-1111-1111-1111-111111111111}");
+ request_params_.set_app_version("5.6.7.8");
+ request_params_.set_product_components("o.bundle=1");
+ request_params_.set_current_channel("stable-channel");
+ EXPECT_TRUE(
+ request_params_.SetTargetChannel("canary-channel", false, nullptr));
+ request_params_.UpdateDownloadChannel();
+ EXPECT_FALSE(request_params_.ShouldPowerwash());
+
+ tuc_params_.http_response = "invalid xml>";
+ tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError;
+ tuc_params_.expected_check_result = metrics::CheckResult::kParsingError;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_FALSE(TestUpdateCheck());
+
+ EXPECT_NE(
+ string::npos,
+ post_str.find("appid=\"{11111111-1111-1111-1111-111111111111}\" "
+ "version=\"5.6.7.8\" "
+ "track=\"canary-channel\" from_track=\"stable-channel\""));
+ EXPECT_EQ(string::npos, post_str.find("from_version"));
+ EXPECT_NE(string::npos, post_str.find("o.bundle.version=\"1\""));
+}
+
+// Checks that the initial ping with a=-1 r=-1 is not send when the device
+// was powerwashed.
+TEST_F(OmahaRequestActionTest, PingWhenPowerwashed) {
+ fake_prefs_.SetString(kPrefsPreviousVersion, "");
+
+ // Flag that the device was powerwashed in the past.
+ fake_system_state_.fake_hardware()->SetPowerwashCount(1);
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ // We shouldn't send a ping in this case since powerwash > 0.
+ EXPECT_EQ(string::npos, post_str.find("<ping"));
+}
+
+// Checks that the initial ping with a=-1 r=-1 is not send when the device
+// first_active_omaha_ping_sent is set.
+TEST_F(OmahaRequestActionTest, PingWhenFirstActiveOmahaPingIsSent) {
+ fake_prefs_.SetString(kPrefsPreviousVersion, "");
+
+ // Flag that the device was not powerwashed in the past.
+ fake_system_state_.fake_hardware()->SetPowerwashCount(0);
+
+ // Flag that the device has sent first active ping in the past.
+ fake_system_state_.fake_hardware()->SetFirstActiveOmahaPingSent();
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ // We shouldn't send a ping in this case since
+ // first_active_omaha_ping_sent=true
+ EXPECT_EQ(string::npos, post_str.find("<ping"));
+}
+
+// Checks that the event 54 is sent on a reboot to a new update.
+TEST_F(OmahaRequestActionTest, RebootAfterUpdateEvent) {
+ // Flag that the device was updated in a previous boot.
+ fake_prefs_.SetString(kPrefsPreviousVersion, "1.2.3.4");
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ // An event 54 is included and has the right version.
+ EXPECT_NE(
+ string::npos,
+ post_str.find(base::StringPrintf("<event eventtype=\"%d\"",
+ OmahaEvent::kTypeRebootedAfterUpdate)));
+ EXPECT_NE(string::npos,
+ post_str.find("previousversion=\"1.2.3.4\"></event>"));
+
+ // The previous version flag should have been removed.
+ EXPECT_TRUE(fake_prefs_.Exists(kPrefsPreviousVersion));
+ string prev_version;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsPreviousVersion, &prev_version));
+ EXPECT_TRUE(prev_version.empty());
+}
+
+void OmahaRequestActionTest::P2PTest(bool initial_allow_p2p_for_downloading,
+ bool initial_allow_p2p_for_sharing,
+ bool omaha_disable_p2p_for_downloading,
+ bool omaha_disable_p2p_for_sharing,
+ bool payload_state_allow_p2p_attempt,
+ bool expect_p2p_client_lookup,
+ const string& p2p_client_result_url,
+ bool expected_allow_p2p_for_downloading,
+ bool expected_allow_p2p_for_sharing,
+ const string& expected_p2p_url) {
+ bool actual_allow_p2p_for_downloading = initial_allow_p2p_for_downloading;
+ bool actual_allow_p2p_for_sharing = initial_allow_p2p_for_sharing;
+ string actual_p2p_url;
+
+ MockPayloadState mock_payload_state;
+ fake_system_state_.set_payload_state(&mock_payload_state);
+ EXPECT_CALL(mock_payload_state, P2PAttemptAllowed())
+ .WillRepeatedly(Return(payload_state_allow_p2p_attempt));
+ EXPECT_CALL(mock_payload_state, GetUsingP2PForDownloading())
+ .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_downloading));
+ EXPECT_CALL(mock_payload_state, GetUsingP2PForSharing())
+ .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_sharing));
+ EXPECT_CALL(mock_payload_state, SetUsingP2PForDownloading(_))
+ .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_downloading));
+ EXPECT_CALL(mock_payload_state, SetUsingP2PForSharing(_))
+ .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_sharing));
+ EXPECT_CALL(mock_payload_state, SetP2PUrl(_))
+ .WillRepeatedly(SaveArg<0>(&actual_p2p_url));
+
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetLookupUrlForFileResult(p2p_client_result_url);
+
+ TimeDelta timeout = TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds);
+ EXPECT_CALL(mock_p2p_manager, LookupUrlForFile(_, _, timeout, _))
+ .Times(expect_p2p_client_lookup ? 1 : 0);
+
+ fake_update_response_.disable_p2p_for_downloading =
+ omaha_disable_p2p_for_downloading;
+ fake_update_response_.disable_p2p_for_sharing = omaha_disable_p2p_for_sharing;
+
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kUpdateAvailable;
+
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+
+ EXPECT_EQ(omaha_disable_p2p_for_downloading,
+ response.disable_p2p_for_downloading);
+ EXPECT_EQ(omaha_disable_p2p_for_sharing, response.disable_p2p_for_sharing);
+
+ EXPECT_EQ(expected_allow_p2p_for_downloading,
+ actual_allow_p2p_for_downloading);
+ EXPECT_EQ(expected_allow_p2p_for_sharing, actual_allow_p2p_for_sharing);
+ EXPECT_EQ(expected_p2p_url, actual_p2p_url);
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeer) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ false, // omaha_disable_p2p_for_downloading
+ false, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ true, // expect_p2p_client_lookup
+ "http://1.3.5.7/p2p", // p2p_client_result_url
+ true, // expected_allow_p2p_for_downloading
+ true, // expected_allow_p2p_for_sharing
+ "http://1.3.5.7/p2p"); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithoutPeer) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ false, // omaha_disable_p2p_for_downloading
+ false, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ true, // expect_p2p_client_lookup
+ "", // p2p_client_result_url
+ false, // expected_allow_p2p_for_downloading
+ true, // expected_allow_p2p_for_sharing
+ ""); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PDownloadNotAllowed) {
+ P2PTest(false, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ false, // omaha_disable_p2p_for_downloading
+ false, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ false, // expect_p2p_client_lookup
+ "unset", // p2p_client_result_url
+ false, // expected_allow_p2p_for_downloading
+ true, // expected_allow_p2p_for_sharing
+ ""); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeerDownloadDisabledByOmaha) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ true, // omaha_disable_p2p_for_downloading
+ false, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ false, // expect_p2p_client_lookup
+ "unset", // p2p_client_result_url
+ false, // expected_allow_p2p_for_downloading
+ true, // expected_allow_p2p_for_sharing
+ ""); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeerSharingDisabledByOmaha) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ false, // omaha_disable_p2p_for_downloading
+ true, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ true, // expect_p2p_client_lookup
+ "http://1.3.5.7/p2p", // p2p_client_result_url
+ true, // expected_allow_p2p_for_downloading
+ false, // expected_allow_p2p_for_sharing
+ "http://1.3.5.7/p2p"); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeerBothDisabledByOmaha) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ true, // omaha_disable_p2p_for_downloading
+ true, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ false, // expect_p2p_client_lookup
+ "unset", // p2p_client_result_url
+ false, // expected_allow_p2p_for_downloading
+ false, // expected_allow_p2p_for_sharing
+ ""); // expected_p2p_url
+}
+
+bool OmahaRequestActionTest::InstallDateParseHelper(const string& elapsed_days,
+ OmahaResponse* response) {
+ fake_update_response_.elapsed_days = elapsed_days;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ return TestUpdateCheck();
+}
+
+TEST_F(OmahaRequestActionTest, ParseInstallDateFromResponse) {
+ // Simulate a successful update check that happens during OOBE. The
+ // deadline in the response is needed to force the update attempt to
+ // occur; responses without a deadline seen during OOBE will normally
+ // return ErrorCode::kNonCriticalUpdateInOOBE.
+ fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+ fake_update_response_.deadline = "20101020";
+
+ // Check that we parse elapsed_days in the Omaha Response correctly.
+ // and that the kPrefsInstallDateDays value is written to.
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays));
+ EXPECT_TRUE(InstallDateParseHelper("42", &response));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(42, response.install_date_days);
+ EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays));
+ int64_t prefs_days;
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 42);
+
+ // If there already is a value set, we shouldn't do anything.
+ EXPECT_TRUE(InstallDateParseHelper("7", &response));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(7, response.install_date_days);
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 42);
+
+ // Note that elapsed_days is not necessarily divisible by 7 so check
+ // that we round down correctly when populating kPrefsInstallDateDays.
+ EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays));
+ EXPECT_TRUE(InstallDateParseHelper("23", &response));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(23, response.install_date_days);
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 21);
+
+ // Check that we correctly handle elapsed_days not being included in
+ // the Omaha Response.
+ EXPECT_TRUE(InstallDateParseHelper("", &response));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(-1, response.install_date_days);
+}
+
+// If there is no prefs and OOBE is not complete, we should not
+// report anything to Omaha.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE) {
+ fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1);
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays));
+}
+
+// If OOBE is complete and happened on a valid date (e.g. after Jan
+// 1 2007 0:00 PST), that date should be used and written to
+// prefs. However, first try with an invalid date and check we do
+// nothing.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithInvalidDate) {
+ Time oobe_date = Time::FromTimeT(42); // Dec 31, 1969 16:00:42 PST.
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date);
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1);
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays));
+}
+
+// Then check with a valid date. The date Jan 20, 2007 0:00 PST
+// should yield an InstallDate of 14.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithValidDate) {
+ Time oobe_date = Time::FromTimeT(1169280000); // Jan 20, 2007 0:00 PST.
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date);
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14);
+ EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays));
+
+ int64_t prefs_days;
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 14);
+}
+
+// Now that we have a valid date in prefs, check that we keep using
+// that even if OOBE date reports something else. The date Jan 30,
+// 2007 0:00 PST should yield an InstallDate of 28... but since
+// there's a prefs file, we should still get 14.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedDateChanges) {
+ // Set a valid date in the prefs first.
+ EXPECT_TRUE(fake_prefs_.SetInt64(kPrefsInstallDateDays, 14));
+
+ Time oobe_date = Time::FromTimeT(1170144000); // Jan 30, 2007 0:00 PST.
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date);
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14);
+
+ int64_t prefs_days;
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 14);
+
+ // If we delete the prefs file, we should get 28 days.
+ EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays));
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 28);
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 28);
+}
+
+// Verifies that a device with no device policy, and is not a consumer
+// device sets the max kernel key version to the current version.
+// ie. the same behavior as if rollback is enabled.
+TEST_F(OmahaRequestActionTest, NoPolicyEnterpriseDevicesSetMaxRollback) {
+ FakeHardware* fake_hw = fake_system_state_.fake_hardware();
+
+ // Setup and verify some initial default values for the kernel TPM
+ // values that control verified boot and rollback.
+ const int min_kernel_version = 4;
+ fake_hw->SetMinKernelKeyVersion(min_kernel_version);
+ fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity);
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+ EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+
+ EXPECT_CALL(
+ *fake_system_state_.mock_metrics_reporter(),
+ ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true))
+ .Times(1);
+
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.is_consumer_device = false;
+ tuc_params_.rollback_allowed_milestones = 3;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+
+ // Verify kernel_max_rollforward was set to the current minimum
+ // kernel key version. This has the effect of freezing roll
+ // forwards indefinitely. This will hold the rollback window
+ // open until a future change will be able to move this forward
+ // relative the configured window.
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMaxKernelKeyRollforward());
+}
+
+// Verifies that a conmsumer device with no device policy sets the
+// max kernel key version to the current version. ie. the same
+// behavior as if rollback is enabled.
+TEST_F(OmahaRequestActionTest, NoPolicyConsumerDevicesSetMaxRollback) {
+ FakeHardware* fake_hw = fake_system_state_.fake_hardware();
+
+ // Setup and verify some initial default values for the kernel TPM
+ // values that control verified boot and rollback.
+ const int min_kernel_version = 3;
+ fake_hw->SetMinKernelKeyVersion(min_kernel_version);
+ fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity);
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+ EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+
+ EXPECT_CALL(
+ *fake_system_state_.mock_metrics_reporter(),
+ ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true))
+ .Times(1);
+
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.is_consumer_device = true;
+ tuc_params_.rollback_allowed_milestones = 3;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+
+ // Verify that with rollback disabled that kernel_max_rollforward
+ // was set to logical infinity. This is the expected behavior for
+ // consumer devices and matches the existing behavior prior to the
+ // rollback features.
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+ EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+}
+
+// Verifies that a device with rollback enabled sets kernel_max_rollforward
+// in the TPM to prevent roll forward.
+TEST_F(OmahaRequestActionTest, RollbackEnabledDevicesSetMaxRollback) {
+ FakeHardware* fake_hw = fake_system_state_.fake_hardware();
+
+ // Setup and verify some initial default values for the kernel TPM
+ // values that control verified boot and rollback.
+ const int allowed_milestones = 4;
+ const int min_kernel_version = 3;
+ fake_hw->SetMinKernelKeyVersion(min_kernel_version);
+ fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity);
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+ EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+
+ EXPECT_CALL(
+ *fake_system_state_.mock_metrics_reporter(),
+ ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true))
+ .Times(1);
+
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.is_consumer_device = false;
+ tuc_params_.rollback_allowed_milestones = allowed_milestones;
+ tuc_params_.is_policy_loaded = true;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+
+ // Verify that with rollback enabled that kernel_max_rollforward
+ // was set to the current minimum kernel key version. This has
+ // the effect of freezing roll forwards indefinitely. This will
+ // hold the rollback window open until a future change will
+ // be able to move this forward relative the configured window.
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMaxKernelKeyRollforward());
+}
+
+// Verifies that a device with rollback disabled sets kernel_max_rollforward
+// in the TPM to logical infinity, to allow roll forward.
+TEST_F(OmahaRequestActionTest, RollbackDisabledDevicesSetMaxRollback) {
+ FakeHardware* fake_hw = fake_system_state_.fake_hardware();
+
+ // Setup and verify some initial default values for the kernel TPM
+ // values that control verified boot and rollback.
+ const int allowed_milestones = 0;
+ const int min_kernel_version = 3;
+ fake_hw->SetMinKernelKeyVersion(min_kernel_version);
+ fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity);
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+ EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+
+ EXPECT_CALL(
+ *fake_system_state_.mock_metrics_reporter(),
+ ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true))
+ .Times(1);
+
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.is_consumer_device = false;
+ tuc_params_.rollback_allowed_milestones = allowed_milestones;
+ tuc_params_.is_policy_loaded = true;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+
+ // Verify that with rollback disabled that kernel_max_rollforward
+ // was set to logical infinity.
+ EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+ EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+}
+
+TEST_F(OmahaRequestActionTest, RollbackResponseParsedNoEntries) {
+ fake_update_response_.rollback = true;
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.is_consumer_device = false;
+ tuc_params_.rollback_allowed_milestones = 4;
+ tuc_params_.is_policy_loaded = true;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_TRUE(response.is_rollback);
+}
+
+TEST_F(OmahaRequestActionTest, RollbackResponseValidVersionsParsed) {
+ fake_update_response_.rollback_firmware_version = "1.2";
+ fake_update_response_.rollback_kernel_version = "3.4";
+ fake_update_response_.rollback = true;
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.is_consumer_device = false;
+ tuc_params_.rollback_allowed_milestones = 4;
+ tuc_params_.is_policy_loaded = true;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_TRUE(response.is_rollback);
+ EXPECT_EQ(1, response.rollback_key_version.firmware_key);
+ EXPECT_EQ(2, response.rollback_key_version.firmware);
+ EXPECT_EQ(3, response.rollback_key_version.kernel_key);
+ EXPECT_EQ(4, response.rollback_key_version.kernel);
+}
+
+TEST_F(OmahaRequestActionTest,
+ TestUpdateFirstSeenAtPrefPersistedIfUpdateExists) {
+ FakeClock fake_clock;
+ Time now = Time::Now();
+ fake_clock.SetWallclockTime(now);
+ fake_system_state_.set_clock(&fake_clock);
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_TRUE(fake_prefs_.Exists(kPrefsUpdateFirstSeenAt));
+
+ int64_t stored_first_seen_at_time;
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt,
+ &stored_first_seen_at_time));
+ EXPECT_EQ(now.ToInternalValue(), stored_first_seen_at_time);
+}
+
+TEST_F(OmahaRequestActionTest,
+ TestUpdateFirstSeenAtPrefNotPersistedIfUpdateFails) {
+ FakeClock fake_clock;
+ Time now = Time::Now();
+ fake_clock.SetWallclockTime(now);
+ fake_system_state_.set_clock(&fake_clock);
+
+ tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_FALSE(response.update_exists);
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsUpdateFirstSeenAt));
+}
+
+TEST_F(OmahaRequestActionTest, InstallTest) {
+ request_params_.set_is_install(true);
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
+ {request_params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}});
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ for (const auto& it : request_params_.dlc_apps_params()) {
+ EXPECT_NE(string::npos, post_str.find("appid=\"" + it.first + "\""));
+ }
+ EXPECT_NE(string::npos,
+ post_str.find("appid=\"" + fake_update_response_.app_id + "\""));
+
+ // Count number of updatecheck tag in response.
+ int updatecheck_count = 0;
+ size_t pos = 0;
+ while ((pos = post_str.find("<updatecheck", pos)) != string::npos) {
+ updatecheck_count++;
+ pos++;
+ }
+ EXPECT_EQ(request_params_.dlc_apps_params().size(), updatecheck_count);
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, InstallMissingPlatformVersionTest) {
+ fake_update_response_.multi_app_skip_updatecheck = true;
+ fake_update_response_.multi_app_no_update = false;
+ request_params_.set_is_install(true);
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
+ {request_params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}});
+ request_params_.set_app_id(fake_update_response_.app_id_skip_updatecheck);
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(fake_update_response_.current_version, response.version);
+}
+
+TEST_F(OmahaRequestActionTest, UpdateWithDlcTest) {
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}});
+ fake_update_response_.dlc_app_update = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false));
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_EQ(response.packages.size(), 2u);
+ // Two candidate URLs.
+ EXPECT_EQ(response.packages[1].payload_urls.size(), 2u);
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, UpdateWithPartiallyExcludedDlcTest) {
+ const string kDlcAppId = request_params_.GetDlcAppId(kDlcId1);
+ request_params_.set_dlc_apps_params({{kDlcAppId, {.name = kDlcId1}}});
+ fake_update_response_.dlc_app_update = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ // The first DLC candidate URL is excluded.
+ EXPECT_CALL(mock_excluder_, IsExcluded(_))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false));
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_EQ(response.packages.size(), 2u);
+ // One candidate URL.
+ EXPECT_EQ(response.packages[1].payload_urls.size(), 1u);
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_TRUE(request_params_.dlc_apps_params().at(kDlcAppId).updated);
+}
+
+TEST_F(OmahaRequestActionTest, UpdateWithExcludedDlcTest) {
+ const string kDlcAppId = request_params_.GetDlcAppId(kDlcId1);
+ request_params_.set_dlc_apps_params({{kDlcAppId, {.name = kDlcId1}}});
+ fake_update_response_.dlc_app_update = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ // Both DLC candidate URLs are excluded.
+ EXPECT_CALL(mock_excluder_, IsExcluded(_))
+ .WillOnce(Return(true))
+ .WillOnce(Return(true));
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_EQ(response.packages.size(), 1u);
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_FALSE(request_params_.dlc_apps_params().at(kDlcAppId).updated);
+}
+
+TEST_F(OmahaRequestActionTest, UpdateWithDeprecatedDlcTest) {
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId(kDlcId2), {.name = kDlcId2}}});
+ fake_update_response_.dlc_app_no_update = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false));
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, UpdateWithDlcAndDeprecatedDlcTest) {
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}},
+ {request_params_.GetDlcAppId(kDlcId2), {.name = kDlcId2}}});
+ fake_update_response_.dlc_app_update = true;
+ fake_update_response_.dlc_app_no_update = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false));
+ ASSERT_TRUE(TestUpdateCheck());
+
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, PastRollbackVersionsNoEntries) {
+ fake_update_response_.rollback = true;
+ fake_update_response_.rollback_allowed_milestones = 4;
+ request_params_.set_rollback_allowed_milestones(4);
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.is_consumer_device = false;
+ tuc_params_.rollback_allowed_milestones = 4;
+ tuc_params_.is_policy_loaded = true;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_TRUE(response.is_rollback);
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(),
+ response.past_rollback_key_version.firmware_key);
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(),
+ response.past_rollback_key_version.firmware);
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(),
+ response.past_rollback_key_version.kernel_key);
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(),
+ response.past_rollback_key_version.kernel);
+}
+
+TEST_F(OmahaRequestActionTest, PastRollbackVersionsValidEntries) {
+ request_params_.set_rollback_allowed_milestones(4);
+ fake_update_response_.rollback = true;
+ fake_update_response_.rollback_allowed_milestones = 4;
+ fake_update_response_.rollback_firmware_version = "4.3";
+ fake_update_response_.rollback_kernel_version = "2.1";
+ fake_update_response_.past_rollback_key_version =
+ std::make_pair("16.15", "14.13");
+ fake_update_response_.deadline = "20101020";
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.is_consumer_device = false;
+ tuc_params_.rollback_allowed_milestones = 4;
+ tuc_params_.is_policy_loaded = true;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_TRUE(response.is_rollback);
+ EXPECT_EQ(16, response.past_rollback_key_version.firmware_key);
+ EXPECT_EQ(15, response.past_rollback_key_version.firmware);
+ EXPECT_EQ(14, response.past_rollback_key_version.kernel_key);
+ EXPECT_EQ(13, response.past_rollback_key_version.kernel);
+}
+
+TEST_F(OmahaRequestActionTest, MismatchNumberOfVersions) {
+ fake_update_response_.rollback = true;
+ fake_update_response_.rollback_allowed_milestones = 2;
+ fake_update_response_.deadline = "20101020";
+ request_params_.set_rollback_allowed_milestones(4);
+
+ // Since |request_params_.rollback_allowed_milestones| is 4 but the response
+ // is constructed with |fake_update_response_.rollback_allowed_milestones| set
+ // to 2, OmahaRequestAction will look for the key values of N-4 version but
+ // only the N-2 version will exist.
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ tuc_params_.is_consumer_device = false;
+ tuc_params_.rollback_allowed_milestones = 2;
+ tuc_params_.is_policy_loaded = true;
+
+ EXPECT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_TRUE(response.is_rollback);
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(),
+ response.past_rollback_key_version.firmware_key);
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(),
+ response.past_rollback_key_version.firmware);
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(),
+ response.past_rollback_key_version.kernel_key);
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(),
+ response.past_rollback_key_version.kernel);
+}
+
+TEST_F(OmahaRequestActionTest, IncludeRequisitionTest) {
+ request_params_.set_device_requisition("remora");
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_NE(string::npos, post_str.find("requisition=\"remora\""));
+}
+
+TEST_F(OmahaRequestActionTest, NoIncludeRequisitionTest) {
+ request_params_.set_device_requisition("");
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_EQ(string::npos, post_str.find("requisition"));
+}
+
+TEST_F(OmahaRequestActionTest, PersistEolDateTest) {
+ 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_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_date;
+ EXPECT_TRUE(
+ fake_system_state_.prefs()->GetString(kPrefsOmahaEolDate, &eol_date));
+ EXPECT_EQ("200", eol_date);
+}
+
+TEST_F(OmahaRequestActionTest, PersistEolMissingDateTest) {
+ 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\" "
+ "_foo=\"bar\"/></app></response>";
+ tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+ tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+
+ const string kDate = "123";
+ fake_system_state_.prefs()->SetString(kPrefsOmahaEolDate, kDate);
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ string eol_date;
+ EXPECT_TRUE(
+ fake_system_state_.prefs()->GetString(kPrefsOmahaEolDate, &eol_date));
+ EXPECT_EQ(kDate, eol_date);
+}
+
+TEST_F(OmahaRequestActionTest, PersistEolBadDateTest) {
+ 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_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_date;
+ EXPECT_TRUE(
+ fake_system_state_.prefs()->GetString(kPrefsOmahaEolDate, &eol_date));
+ EXPECT_EQ(kEolDateInvalid, StringToEolDate(eol_date));
+}
+
+TEST_F(OmahaRequestActionDlcPingTest, StorePingReplyNoPing) {
+ OmahaRequestParams::AppParams app_param = {.name = dlc_id_};
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId(dlc_id_), app_param}});
+
+ ASSERT_TRUE(TestUpdateCheck());
+
+ int64_t temp_int;
+ // If there was no ping, the metadata files shouldn't exist yet.
+ EXPECT_FALSE(fake_prefs_.GetInt64(active_key_, &temp_int));
+ EXPECT_FALSE(fake_prefs_.GetInt64(last_active_key_, &temp_int));
+ EXPECT_FALSE(fake_prefs_.GetInt64(last_rollcall_key_, &temp_int));
+}
+
+TEST_F(OmahaRequestActionDlcPingTest, StorePingReplyActiveTest) {
+ // Create Active value
+ fake_prefs_.SetInt64(active_key_, 0);
+
+ OmahaRequestParams::AppParams app_param = {
+ .active_counting_type = OmahaRequestParams::kDateBased,
+ .name = dlc_id_,
+ .ping_active = 1,
+ .send_ping = true};
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId(dlc_id_), app_param}});
+
+ int64_t temp_int;
+ string temp_str;
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(fake_prefs_.GetInt64(active_key_, &temp_int));
+ EXPECT_EQ(temp_int, kPingInactiveValue);
+ EXPECT_TRUE(fake_prefs_.GetString(last_active_key_, &temp_str));
+ EXPECT_EQ(temp_str, "4763");
+ EXPECT_TRUE(fake_prefs_.GetString(last_rollcall_key_, &temp_str));
+ EXPECT_EQ(temp_str, "4763");
+}
+
+TEST_F(OmahaRequestActionDlcPingTest, StorePingReplyInactiveTest) {
+ // Create Active value
+ fake_prefs_.SetInt64(active_key_, 0);
+
+ OmahaRequestParams::AppParams app_param = {
+ .active_counting_type = OmahaRequestParams::kDateBased,
+ .name = dlc_id_,
+ .ping_active = 0,
+ .send_ping = true};
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId(dlc_id_), app_param}});
+
+ // Set the previous active value to an older value than 4763.
+ fake_prefs_.SetString(last_active_key_, "555");
+
+ int64_t temp_int;
+ ASSERT_TRUE(TestUpdateCheck());
+ EXPECT_TRUE(fake_prefs_.GetInt64(active_key_, &temp_int));
+ EXPECT_EQ(temp_int, kPingInactiveValue);
+ string temp_str;
+ EXPECT_TRUE(fake_prefs_.GetString(last_active_key_, &temp_str));
+ EXPECT_EQ(temp_str, "555");
+ EXPECT_TRUE(fake_prefs_.GetString(last_rollcall_key_, &temp_str));
+ EXPECT_EQ(temp_str, "4763");
+}
+
+TEST_F(OmahaRequestActionTest, OmahaResponseUpdateCanExcludeCheck) {
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}});
+ fake_update_response_.dlc_app_update = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false));
+ ASSERT_TRUE(TestUpdateCheck());
+ ASSERT_TRUE(delegate_.omaha_response_);
+ const auto& packages = delegate_.omaha_response_->packages;
+ ASSERT_EQ(packages.size(), 2);
+
+ EXPECT_FALSE(packages[0].can_exclude);
+ EXPECT_TRUE(packages[1].can_exclude);
+}
+
+TEST_F(OmahaRequestActionTest, OmahaResponseInstallCannotExcludeCheck) {
+ request_params_.set_is_install(true);
+ request_params_.set_dlc_apps_params(
+ {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}});
+ fake_update_response_.dlc_app_update = true;
+ tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+
+ EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false));
+ ASSERT_TRUE(TestUpdateCheck());
+ ASSERT_TRUE(delegate_.omaha_response_);
+ const auto& packages = delegate_.omaha_response_->packages;
+ ASSERT_EQ(packages.size(), 2);
+
+ EXPECT_FALSE(packages[0].can_exclude);
+ EXPECT_FALSE(packages[1].can_exclude);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/omaha_request_builder_xml.cc b/cros/omaha_request_builder_xml.cc
new file mode 100644
index 0000000..43ee548
--- /dev/null
+++ b/cros/omaha_request_builder_xml.cc
@@ -0,0 +1,437 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_request_builder_xml.h"
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/guid.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/omaha_request_params.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char kNoVersion[] = "0.0.0.0";
+const int kPingNeverPinged = -1;
+const int kPingUnknownValue = -2;
+const int kPingActiveValue = 1;
+const int kPingInactiveValue = 0;
+
+bool XmlEncode(const string& input, string* output) {
+ if (std::find_if(input.begin(), input.end(), [](const char c) {
+ return c & 0x80;
+ }) != input.end()) {
+ LOG(WARNING) << "Invalid ASCII-7 string passed to the XML encoder:";
+ utils::HexDumpString(input);
+ return false;
+ }
+ output->clear();
+ // We need at least input.size() space in the output, but the code below will
+ // handle it if we need more.
+ output->reserve(input.size());
+ for (char c : input) {
+ switch (c) {
+ case '\"':
+ output->append(""");
+ break;
+ case '\'':
+ output->append("'");
+ break;
+ case '&':
+ output->append("&");
+ break;
+ case '<':
+ output->append("<");
+ break;
+ case '>':
+ output->append(">");
+ break;
+ default:
+ output->push_back(c);
+ }
+ }
+ return true;
+}
+
+string XmlEncodeWithDefault(const string& input, const string& default_value) {
+ string output;
+ if (XmlEncode(input, &output))
+ return output;
+ return default_value;
+}
+
+string OmahaRequestBuilderXml::GetPing() const {
+ // Returns an XML ping element attribute assignment with attribute
+ // |name| and value |ping_days| if |ping_days| has a value that needs
+ // to be sent, or an empty string otherwise.
+ auto GetPingAttribute = [](const char* name, int ping_days) -> string {
+ if (ping_days > 0 || ping_days == kPingNeverPinged)
+ return base::StringPrintf(" %s=\"%d\"", name, ping_days);
+ return "";
+ };
+
+ string ping_active = GetPingAttribute("a", ping_active_days_);
+ string ping_roll_call = GetPingAttribute("r", ping_roll_call_days_);
+ if (!ping_active.empty() || !ping_roll_call.empty()) {
+ return base::StringPrintf(" <ping active=\"1\"%s%s></ping>\n",
+ ping_active.c_str(),
+ ping_roll_call.c_str());
+ }
+ return "";
+}
+
+string OmahaRequestBuilderXml::GetPingDateBased(
+ const OmahaRequestParams::AppParams& app_params) const {
+ if (!app_params.send_ping)
+ return "";
+ string ping_active = "";
+ string ping_ad = "";
+ if (app_params.ping_active == kPingActiveValue) {
+ ping_active =
+ base::StringPrintf(" active=\"%" PRId64 "\"", app_params.ping_active);
+ ping_ad = base::StringPrintf(" ad=\"%" PRId64 "\"",
+ app_params.ping_date_last_active);
+ }
+
+ string ping_rd = base::StringPrintf(" rd=\"%" PRId64 "\"",
+ app_params.ping_date_last_rollcall);
+
+ return base::StringPrintf(" <ping%s%s%s></ping>\n",
+ ping_active.c_str(),
+ ping_ad.c_str(),
+ ping_rd.c_str());
+}
+
+string OmahaRequestBuilderXml::GetAppBody(const OmahaAppData& app_data) const {
+ string app_body;
+ if (event_ == nullptr) {
+ if (app_data.app_params.send_ping) {
+ switch (app_data.app_params.active_counting_type) {
+ case OmahaRequestParams::kDayBased:
+ app_body = GetPing();
+ break;
+ case OmahaRequestParams::kDateBased:
+ app_body = GetPingDateBased(app_data.app_params);
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+ if (!ping_only_) {
+ if (!app_data.skip_update) {
+ app_body += " <updatecheck";
+ if (!params_->target_version_prefix().empty()) {
+ app_body += base::StringPrintf(
+ " targetversionprefix=\"%s\"",
+ XmlEncodeWithDefault(params_->target_version_prefix()).c_str());
+ // Rollback requires target_version_prefix set.
+ if (params_->rollback_allowed()) {
+ app_body += " rollback_allowed=\"true\"";
+ }
+ }
+ if (!params_->lts_tag().empty()) {
+ app_body += base::StringPrintf(
+ " ltstag=\"%s\"",
+ XmlEncodeWithDefault(params_->lts_tag()).c_str());
+ }
+ app_body += "></updatecheck>\n";
+ }
+
+ // If this is the first update check after a reboot following a previous
+ // update, generate an event containing the previous version number. If
+ // the previous version preference file doesn't exist the event is still
+ // generated with a previous version of 0.0.0.0 -- this is relevant for
+ // older clients or new installs. The previous version event is not sent
+ // for ping-only requests because they come before the client has
+ // rebooted. The previous version event is also not sent if it was already
+ // sent for this new version with a previous updatecheck.
+ string prev_version;
+ if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version)) {
+ prev_version = kNoVersion;
+ }
+ // We only store a non-empty previous version value after a successful
+ // update in the previous boot. After reporting it back to the server,
+ // we clear the previous version value so it doesn't get reported again.
+ if (!prev_version.empty()) {
+ app_body += base::StringPrintf(
+ " <event eventtype=\"%d\" eventresult=\"%d\" "
+ "previousversion=\"%s\"></event>\n",
+ OmahaEvent::kTypeRebootedAfterUpdate,
+ OmahaEvent::kResultSuccess,
+ XmlEncodeWithDefault(prev_version, kNoVersion).c_str());
+ LOG_IF(WARNING, !prefs_->SetString(kPrefsPreviousVersion, ""))
+ << "Unable to reset the previous version.";
+ }
+ }
+ } else {
+ int event_result = event_->result;
+ // The error code is an optional attribute so append it only if the result
+ // is not success.
+ string error_code;
+ if (event_result != OmahaEvent::kResultSuccess) {
+ error_code = base::StringPrintf(" errorcode=\"%d\"",
+ static_cast<int>(event_->error_code));
+ } else if (app_data.is_dlc && !app_data.app_params.updated) {
+ // On a |OmahaEvent::kResultSuccess|, if the event is for an update
+ // completion and the App is a DLC, send error for excluded DLCs as they
+ // did not update.
+ event_result = OmahaEvent::Result::kResultError;
+ error_code = base::StringPrintf(
+ " errorcode=\"%d\"",
+ static_cast<int>(ErrorCode::kPackageExcludedFromUpdate));
+ }
+ app_body = base::StringPrintf(
+ " <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
+ event_->type,
+ event_result,
+ error_code.c_str());
+ }
+
+ return app_body;
+}
+
+string OmahaRequestBuilderXml::GetCohortArg(const string arg_name,
+ const string prefs_key,
+ const string override_value) const {
+ string cohort_value;
+ if (!override_value.empty()) {
+ // |override_value| take precedence over pref value.
+ cohort_value = override_value;
+ } else {
+ // There's nothing wrong with not having a given cohort setting, so we check
+ // existence first to avoid the warning log message.
+ if (!prefs_->Exists(prefs_key))
+ return "";
+ if (!prefs_->GetString(prefs_key, &cohort_value) || cohort_value.empty())
+ return "";
+ }
+ // This is a validity check to avoid sending a huge XML file back to Ohama due
+ // to a compromised stateful partition making the update check fail in low
+ // network environments envent after a reboot.
+ if (cohort_value.size() > 1024) {
+ LOG(WARNING) << "The omaha cohort setting " << arg_name
+ << " has a too big value, which must be an error or an "
+ "attacker trying to inhibit updates.";
+ return "";
+ }
+
+ string escaped_xml_value;
+ if (!XmlEncode(cohort_value, &escaped_xml_value)) {
+ LOG(WARNING) << "The omaha cohort setting " << arg_name
+ << " is ASCII-7 invalid, ignoring it.";
+ return "";
+ }
+
+ return base::StringPrintf(
+ "%s=\"%s\" ", arg_name.c_str(), escaped_xml_value.c_str());
+}
+
+bool IsValidComponentID(const string& id) {
+ for (char c : id) {
+ if (!isalnum(c) && c != '-' && c != '_' && c != '.')
+ return false;
+ }
+ return true;
+}
+
+string OmahaRequestBuilderXml::GetApp(const OmahaAppData& app_data) const {
+ string app_body = GetAppBody(app_data);
+ string app_versions;
+
+ // If we are downgrading to a more stable channel and we are allowed to do
+ // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
+ // highest-versioned payload on the destination channel.
+ if (params_->ShouldPowerwash()) {
+ LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
+ << "on downgrading to the version in the more stable channel";
+ app_versions = "version=\"" + string(kNoVersion) + "\" from_version=\"" +
+ XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" ";
+ } else {
+ app_versions = "version=\"" +
+ XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" ";
+ }
+
+ string download_channel = params_->download_channel();
+ string app_channels =
+ "track=\"" + XmlEncodeWithDefault(download_channel) + "\" ";
+ if (params_->current_channel() != download_channel) {
+ app_channels += "from_track=\"" +
+ XmlEncodeWithDefault(params_->current_channel()) + "\" ";
+ }
+
+ string delta_okay_str =
+ params_->delta_okay() && !params_->is_install() ? "true" : "false";
+
+ // If install_date_days is not set (e.g. its value is -1 ), don't
+ // include the attribute.
+ string install_date_in_days_str = "";
+ if (install_date_in_days_ >= 0) {
+ install_date_in_days_str =
+ base::StringPrintf("installdate=\"%d\" ", install_date_in_days_);
+ }
+
+ string app_cohort_args;
+ app_cohort_args += GetCohortArg("cohort", kPrefsOmahaCohort);
+ app_cohort_args += GetCohortArg("cohortname", kPrefsOmahaCohortName);
+
+ // Policy provided value overrides pref.
+ string autoupdate_token = params_->autoupdate_token();
+ app_cohort_args += GetCohortArg("cohorthint",
+ kPrefsOmahaCohortHint,
+ autoupdate_token /* override_value */);
+
+ string fingerprint_arg;
+ if (!params_->os_build_fingerprint().empty()) {
+ fingerprint_arg = "fingerprint=\"" +
+ XmlEncodeWithDefault(params_->os_build_fingerprint()) +
+ "\" ";
+ }
+
+ string buildtype_arg;
+ if (!params_->os_build_type().empty()) {
+ buildtype_arg = "os_build_type=\"" +
+ XmlEncodeWithDefault(params_->os_build_type()) + "\" ";
+ }
+
+ string product_components_args;
+ if (!params_->ShouldPowerwash() && !app_data.product_components.empty()) {
+ brillo::KeyValueStore store;
+ if (store.LoadFromString(app_data.product_components)) {
+ for (const string& key : store.GetKeys()) {
+ if (!IsValidComponentID(key)) {
+ LOG(ERROR) << "Invalid component id: " << key;
+ continue;
+ }
+ string version;
+ if (!store.GetString(key, &version)) {
+ LOG(ERROR) << "Failed to get version for " << key
+ << " in product_components.";
+ continue;
+ }
+ product_components_args +=
+ base::StringPrintf("_%s.version=\"%s\" ",
+ key.c_str(),
+ XmlEncodeWithDefault(version).c_str());
+ }
+ } else {
+ LOG(ERROR) << "Failed to parse product_components:\n"
+ << app_data.product_components;
+ }
+ }
+
+ string requisition_arg;
+ if (!params_->device_requisition().empty()) {
+ requisition_arg = "requisition=\"" +
+ XmlEncodeWithDefault(params_->device_requisition()) +
+ "\" ";
+ }
+
+ // clang-format off
+ string app_xml = " <app "
+ "appid=\"" + XmlEncodeWithDefault(app_data.id) + "\" " +
+ app_cohort_args +
+ app_versions +
+ app_channels +
+ product_components_args +
+ fingerprint_arg +
+ buildtype_arg +
+ "board=\"" + XmlEncodeWithDefault(params_->os_board()) + "\" " +
+ "hardware_class=\"" + XmlEncodeWithDefault(params_->hwid()) + "\" " +
+ "delta_okay=\"" + delta_okay_str + "\" " +
+ install_date_in_days_str +
+
+ // DLC excluded for installs and updates.
+ (app_data.is_dlc ? "" :
+ "lang=\"" + XmlEncodeWithDefault(params_->app_lang(), "en-US") + "\" " +
+ requisition_arg) +
+
+ ">\n" +
+ app_body +
+ " </app>\n";
+ // clang-format on
+ return app_xml;
+}
+
+string OmahaRequestBuilderXml::GetOs() const {
+ string os_xml =
+ " <os "
+ "version=\"" +
+ XmlEncodeWithDefault(params_->os_version()) + "\" " + "platform=\"" +
+ XmlEncodeWithDefault(params_->os_platform()) + "\" " + "sp=\"" +
+ XmlEncodeWithDefault(params_->os_sp()) +
+ "\">"
+ "</os>\n";
+ return os_xml;
+}
+
+string OmahaRequestBuilderXml::GetRequest() const {
+ string os_xml = GetOs();
+ string app_xml = GetApps();
+
+ string request_xml = base::StringPrintf(
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<request requestid=\"%s\" sessionid=\"%s\""
+ " protocol=\"3.0\" updater=\"%s\" updaterversion=\"%s\""
+ " installsource=\"%s\" ismachine=\"1\">\n%s%s</request>\n",
+ base::GenerateGUID().c_str() /* requestid */,
+ session_id_.c_str(),
+ constants::kOmahaUpdaterID,
+ kOmahaUpdaterVersion,
+ params_->interactive() ? "ondemandupdate" : "scheduler",
+ os_xml.c_str(),
+ app_xml.c_str());
+
+ return request_xml;
+}
+
+string OmahaRequestBuilderXml::GetApps() const {
+ string app_xml = "";
+ OmahaAppData product_app = {
+ .id = params_->GetAppId(),
+ .version = params_->app_version(),
+ .product_components = params_->product_components(),
+ // Skips updatecheck for platform app in case of an install operation.
+ .skip_update = params_->is_install(),
+ .is_dlc = false,
+
+ .app_params = {.active_counting_type = OmahaRequestParams::kDayBased,
+ .send_ping = include_ping_}};
+ app_xml += GetApp(product_app);
+ for (const auto& it : params_->dlc_apps_params()) {
+ OmahaAppData dlc_app_data = {
+ .id = it.first,
+ .version = params_->is_install() ? kNoVersion : params_->app_version(),
+ .skip_update = false,
+ .is_dlc = true,
+ .app_params = it.second};
+ app_xml += GetApp(dlc_app_data);
+ }
+ return app_xml;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/omaha_request_builder_xml.h b/cros/omaha_request_builder_xml.h
new file mode 100644
index 0000000..4f860dd
--- /dev/null
+++ b/cros/omaha_request_builder_xml.h
@@ -0,0 +1,199 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_OMAHA_REQUEST_BUILDER_XML_H_
+#define UPDATE_ENGINE_CROS_OMAHA_REQUEST_BUILDER_XML_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include <brillo/secure_blob.h>
+#include <curl/curl.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/http_fetcher.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/cros/omaha_request_params.h"
+#include "update_engine/cros/omaha_response.h"
+
+namespace chromeos_update_engine {
+
+extern const char kNoVersion[];
+extern const int kPingNeverPinged;
+extern const int kPingUnknownValue;
+extern const int kPingActiveValue;
+extern const int kPingInactiveValue;
+
+// This struct encapsulates the Omaha event information. For a
+// complete list of defined event types and results, see
+// http://code.google.com/p/omaha/wiki/ServerProtocol#event
+struct OmahaEvent {
+ // The Type values correspond to EVENT_TYPE values of Omaha.
+ enum Type {
+ kTypeUnknown = 0,
+ kTypeDownloadComplete = 1,
+ kTypeInstallComplete = 2,
+ kTypeUpdateComplete = 3,
+ kTypeUpdateDownloadStarted = 13,
+ kTypeUpdateDownloadFinished = 14,
+ // Chromium OS reserved type sent after the first reboot following an update
+ // completed.
+ kTypeRebootedAfterUpdate = 54,
+ };
+
+ // The Result values correspond to EVENT_RESULT values of Omaha.
+ enum Result {
+ kResultError = 0,
+ kResultSuccess = 1,
+ kResultUpdateDeferred = 9, // When we ignore/defer updates due to policy.
+ };
+
+ OmahaEvent()
+ : type(kTypeUnknown),
+ result(kResultError),
+ error_code(ErrorCode::kError) {}
+ explicit OmahaEvent(Type in_type)
+ : type(in_type),
+ result(kResultSuccess),
+ error_code(ErrorCode::kSuccess) {}
+ OmahaEvent(Type in_type, Result in_result, ErrorCode in_error_code)
+ : type(in_type), result(in_result), error_code(in_error_code) {}
+
+ Type type;
+ Result result;
+ ErrorCode error_code;
+};
+
+struct OmahaAppData {
+ std::string id;
+ std::string version;
+ std::string product_components;
+ bool skip_update;
+ bool is_dlc;
+ OmahaRequestParams::AppParams app_params;
+};
+
+// Encodes XML entities in a given string. Input must be ASCII-7 valid. If
+// the input is invalid, the default value is used instead.
+std::string XmlEncodeWithDefault(const std::string& input,
+ const std::string& default_value = "");
+
+// Escapes text so it can be included as character data and attribute
+// values. The |input| string must be valid ASCII-7, no UTF-8 supported.
+// Returns whether the |input| was valid and escaped properly in |output|.
+bool XmlEncode(const std::string& input, std::string* output);
+
+// Returns a boolean based on examining each character on whether it's a valid
+// component (meaning all characters are an alphanum excluding '-', '_', '.').
+bool IsValidComponentID(const std::string& id);
+
+class OmahaRequestBuilder {
+ public:
+ OmahaRequestBuilder() = default;
+ virtual ~OmahaRequestBuilder() = default;
+
+ virtual std::string GetRequest() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OmahaRequestBuilder);
+};
+
+class OmahaRequestBuilderXml : OmahaRequestBuilder {
+ public:
+ OmahaRequestBuilderXml(const OmahaEvent* event,
+ OmahaRequestParams* params,
+ bool ping_only,
+ bool include_ping,
+ int ping_active_days,
+ int ping_roll_call_days,
+ int install_date_in_days,
+ PrefsInterface* prefs,
+ const std::string& session_id)
+ : event_(event),
+ params_(params),
+ ping_only_(ping_only),
+ include_ping_(include_ping),
+ ping_active_days_(ping_active_days),
+ ping_roll_call_days_(ping_roll_call_days),
+ install_date_in_days_(install_date_in_days),
+ prefs_(prefs),
+ session_id_(session_id) {}
+
+ ~OmahaRequestBuilderXml() override = default;
+
+ // Returns an XML that corresponds to the entire Omaha request.
+ std::string GetRequest() const override;
+
+ private:
+ FRIEND_TEST(OmahaRequestBuilderXmlTest, PlatformGetAppTest);
+ FRIEND_TEST(OmahaRequestBuilderXmlTest, DlcGetAppTest);
+
+ // Returns an XML that corresponds to the entire <os> node of the Omaha
+ // request based on the member variables.
+ std::string GetOs() const;
+
+ // Returns an XML that corresponds to all <app> nodes of the Omaha
+ // request based on the given parameters.
+ std::string GetApps() const;
+
+ // Returns an XML that corresponds to the single <app> node of the Omaha
+ // request based on the given parameters.
+ std::string GetApp(const OmahaAppData& app_data) const;
+
+ // Returns an XML that goes into the body of the <app> element of the Omaha
+ // request based on the given parameters.
+ std::string GetAppBody(const OmahaAppData& app_data) const;
+
+ // Returns the cohort* argument to include in the <app> tag for the passed
+ // |arg_name| and |prefs_key|, if any. The return value is suitable to
+ // concatenate to the list of arguments and includes a space at the end.
+ std::string GetCohortArg(const std::string arg_name,
+ const std::string prefs_key,
+ const std::string override_value = "") const;
+
+ // Returns an XML ping element if any of the elapsed days need to be
+ // sent, or an empty string otherwise.
+ std::string GetPing() const;
+
+ // Returns an XML ping element if any of the elapsed days need to be
+ // sent, or an empty string otherwise.
+ std::string GetPingDateBased(
+ const OmahaRequestParams::AppParams& app_params) const;
+
+ const OmahaEvent* event_;
+ OmahaRequestParams* params_;
+ bool ping_only_;
+ bool include_ping_;
+ int ping_active_days_;
+ int ping_roll_call_days_;
+ int install_date_in_days_;
+ PrefsInterface* prefs_;
+ std::string session_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(OmahaRequestBuilderXml);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_OMAHA_REQUEST_BUILDER_XML_H_
diff --git a/cros/omaha_request_builder_xml_unittest.cc b/cros/omaha_request_builder_xml_unittest.cc
new file mode 100644
index 0000000..11d808b
--- /dev/null
+++ b/cros/omaha_request_builder_xml_unittest.cc
@@ -0,0 +1,401 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_request_builder_xml.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/guid.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/cros/fake_system_state.h"
+
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Helper to find key and extract value from the given string |xml|, instead
+// of using a full parser. The attribute key will be followed by "=\"" as xml
+// attribute values must be within double quotes (not single quotes).
+static string FindAttributeKeyValueInXml(const string& xml,
+ const string& key,
+ const size_t val_size) {
+ string key_with_quotes = key + "=\"";
+ const size_t val_start_pos = xml.find(key);
+ if (val_start_pos == string::npos)
+ return "";
+ return xml.substr(val_start_pos + key_with_quotes.size(), val_size);
+}
+// Helper to find the count of substring in a string.
+static size_t CountSubstringInString(const string& str, const string& substr) {
+ size_t count = 0, pos = 0;
+ while ((pos = str.find(substr, pos ? pos + 1 : 0)) != string::npos)
+ ++count;
+ return count;
+}
+} // namespace
+
+class OmahaRequestBuilderXmlTest : public ::testing::Test {
+ protected:
+ void SetUp() override {}
+ void TearDown() override {}
+
+ FakeSystemState fake_system_state_;
+ static constexpr size_t kGuidSize = 36;
+};
+
+TEST_F(OmahaRequestBuilderXmlTest, XmlEncodeTest) {
+ string output;
+ vector<pair<string, string>> xml_encode_pairs = {
+ {"ab", "ab"},
+ {"a<b", "a<b"},
+ {"<&>\"\'\\", "<&>"'\\"},
+ {"<&>", "&lt;&amp;&gt;"}};
+ for (const auto& xml_encode_pair : xml_encode_pairs) {
+ const auto& before_encoding = xml_encode_pair.first;
+ const auto& after_encoding = xml_encode_pair.second;
+ EXPECT_TRUE(XmlEncode(before_encoding, &output));
+ EXPECT_EQ(after_encoding, output);
+ }
+ // Check that unterminated UTF-8 strings are handled properly.
+ EXPECT_FALSE(XmlEncode("\xc2", &output));
+ // Fail with invalid ASCII-7 chars.
+ EXPECT_FALSE(XmlEncode("This is an 'n' with a tilde: \xc3\xb1", &output));
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, XmlEncodeWithDefaultTest) {
+ EXPECT_EQ("", XmlEncodeWithDefault(""));
+ EXPECT_EQ("<&>", XmlEncodeWithDefault("<&>", "something else"));
+ EXPECT_EQ("<not escaped>", XmlEncodeWithDefault("\xc2", "<not escaped>"));
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, PlatformGetAppTest) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ omaha_request_params.set_device_requisition("device requisition");
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ OmahaAppData dlc_app_data = {.id = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
+ .version = "",
+ .skip_update = false,
+ .is_dlc = false};
+
+ // Verify that the attributes that shouldn't be missing for Platform AppID are
+ // in fact present in the <app ...></app>.
+ const string app = omaha_request.GetApp(dlc_app_data);
+ EXPECT_NE(string::npos, app.find("lang="));
+ EXPECT_NE(string::npos, app.find("requisition="));
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, DlcGetAppTest) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ omaha_request_params.set_device_requisition("device requisition");
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ OmahaAppData dlc_app_data = {
+ .id = "_dlc_id", .version = "", .skip_update = false, .is_dlc = true};
+
+ // Verify that the attributes that should be missing for DLC AppIDs are in
+ // fact not present in the <app ...></app>.
+ const string app = omaha_request.GetApp(dlc_app_data);
+ EXPECT_EQ(string::npos, app.find("lang="));
+ EXPECT_EQ(string::npos, app.find("requisition="));
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlRequestIdTest) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ const string key = "requestid";
+ const string request_id =
+ FindAttributeKeyValueInXml(kRequestXml, key, kGuidSize);
+ // A valid |request_id| is either a GUID version 4 or empty string.
+ if (!request_id.empty())
+ EXPECT_TRUE(base::IsValidGUID(request_id));
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlSessionIdTest) {
+ const string gen_session_id = base::GenerateGUID();
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ gen_session_id};
+ const string kRequestXml = omaha_request.GetRequest();
+ const string key = "sessionid";
+ const string session_id =
+ FindAttributeKeyValueInXml(kRequestXml, key, kGuidSize);
+ // A valid |session_id| is either a GUID version 4 or empty string.
+ if (!session_id.empty()) {
+ EXPECT_TRUE(base::IsValidGUID(session_id));
+ }
+ EXPECT_EQ(gen_session_id, session_id);
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateTest) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ EXPECT_EQ(1, CountSubstringInString(kRequestXml, "<updatecheck"))
+ << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateWithDlcsTest) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ omaha_request_params.set_dlc_apps_params(
+ {{omaha_request_params.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
+ {omaha_request_params.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}});
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ EXPECT_EQ(3, CountSubstringInString(kRequestXml, "<updatecheck"))
+ << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcInstallationTest) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ const std::map<std::string, OmahaRequestParams::AppParams> dlcs = {
+ {omaha_request_params.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
+ {omaha_request_params.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}};
+ omaha_request_params.set_dlc_apps_params(dlcs);
+ omaha_request_params.set_is_install(true);
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ EXPECT_EQ(2, CountSubstringInString(kRequestXml, "<updatecheck"))
+ << kRequestXml;
+
+ auto FindAppId = [kRequestXml](size_t pos) -> size_t {
+ return kRequestXml.find("<app appid", pos);
+ };
+ // Skip over the Platform AppID, which is always first.
+ size_t pos = FindAppId(0);
+ for (auto&& _ : dlcs) {
+ (void)_;
+ EXPECT_NE(string::npos, (pos = FindAppId(pos + 1))) << kRequestXml;
+ const string dlc_app_id_version = FindAttributeKeyValueInXml(
+ kRequestXml.substr(pos), "version", string(kNoVersion).size());
+ EXPECT_EQ(kNoVersion, dlc_app_id_version);
+
+ const string false_str = "false";
+ const string dlc_app_id_delta_okay = FindAttributeKeyValueInXml(
+ kRequestXml.substr(pos), "delta_okay", false_str.length());
+ EXPECT_EQ(false_str, dlc_app_id_delta_okay);
+ }
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcNoPing) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ omaha_request_params.set_dlc_apps_params(
+ {{omaha_request_params.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}}});
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ EXPECT_EQ(0, CountSubstringInString(kRequestXml, "<ping")) << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallNoActive) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ omaha_request_params.set_dlc_apps_params(
+ {{omaha_request_params.GetDlcAppId("dlc_no_0"),
+ {.active_counting_type = OmahaRequestParams::kDateBased,
+ .name = "dlc_no_0",
+ .ping_date_last_active = 25,
+ .ping_date_last_rollcall = 36,
+ .send_ping = true}}});
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ EXPECT_EQ(1, CountSubstringInString(kRequestXml, "<ping rd=\"36\""))
+ << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallAndActive) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ omaha_request_params.set_dlc_apps_params(
+ {{omaha_request_params.GetDlcAppId("dlc_no_0"),
+ {.active_counting_type = OmahaRequestParams::kDateBased,
+ .name = "dlc_no_0",
+ .ping_active = 1,
+ .ping_date_last_active = 25,
+ .ping_date_last_rollcall = 36,
+ .send_ping = true}}});
+ OmahaRequestBuilderXml omaha_request{nullptr,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ EXPECT_EQ(1,
+ CountSubstringInString(kRequestXml,
+ "<ping active=\"1\" ad=\"25\" rd=\"36\""))
+ << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlUpdateCompleteEvent) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ OmahaEvent event(OmahaEvent::kTypeUpdateComplete);
+ OmahaRequestBuilderXml omaha_request{&event,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ LOG(INFO) << kRequestXml;
+ EXPECT_EQ(
+ 1,
+ CountSubstringInString(
+ kRequestXml, "<event eventtype=\"3\" eventresult=\"1\"></event>"))
+ << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest,
+ GetRequestXmlUpdateCompleteEventSomeDlcsExcluded) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ omaha_request_params.set_dlc_apps_params({
+ {omaha_request_params.GetDlcAppId("dlc_1"), {.updated = true}},
+ {omaha_request_params.GetDlcAppId("dlc_2"), {.updated = false}},
+ });
+ OmahaEvent event(OmahaEvent::kTypeUpdateComplete);
+ OmahaRequestBuilderXml omaha_request{&event,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ EXPECT_EQ(
+ 2,
+ CountSubstringInString(
+ kRequestXml, "<event eventtype=\"3\" eventresult=\"1\"></event>"))
+ << kRequestXml;
+ EXPECT_EQ(
+ 1,
+ CountSubstringInString(
+ kRequestXml,
+ "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>"))
+ << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest,
+ GetRequestXmlUpdateCompleteEventAllDlcsExcluded) {
+ OmahaRequestParams omaha_request_params{&fake_system_state_};
+ omaha_request_params.set_dlc_apps_params({
+ {omaha_request_params.GetDlcAppId("dlc_1"), {.updated = false}},
+ {omaha_request_params.GetDlcAppId("dlc_2"), {.updated = false}},
+ });
+ OmahaEvent event(OmahaEvent::kTypeUpdateComplete);
+ OmahaRequestBuilderXml omaha_request{&event,
+ &omaha_request_params,
+ false,
+ false,
+ 0,
+ 0,
+ 0,
+ fake_system_state_.prefs(),
+ ""};
+ const string kRequestXml = omaha_request.GetRequest();
+ EXPECT_EQ(
+ 1,
+ CountSubstringInString(
+ kRequestXml, "<event eventtype=\"3\" eventresult=\"1\"></event>"))
+ << kRequestXml;
+ EXPECT_EQ(
+ 2,
+ CountSubstringInString(
+ kRequestXml,
+ "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>"))
+ << kRequestXml;
+}
+} // namespace chromeos_update_engine
diff --git a/cros/omaha_request_params.cc b/cros/omaha_request_params.cc
new file mode 100644
index 0000000..c814e00
--- /dev/null
+++ b/cros/omaha_request_params.cc
@@ -0,0 +1,284 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_request_params.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/utsname.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/stl_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/key_value_store.h>
+#include <brillo/strings/string_utils.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/policy.h"
+
+#define CALL_MEMBER_FN(object, member) ((object).*(member))
+
+using chromeos_update_manager::UpdateCheckParams;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char OmahaRequestParams::kOsVersion[] = "Indy";
+
+const char* kChannelsByStability[] = {
+ // This list has to be sorted from least stable to most stable channel.
+ "canary-channel",
+ "dev-channel",
+ "beta-channel",
+ "stable-channel",
+};
+
+OmahaRequestParams::~OmahaRequestParams() {
+ if (!root_.empty())
+ test::SetImagePropertiesRootPrefix(nullptr);
+}
+
+bool OmahaRequestParams::Init(const string& app_version,
+ const string& update_url,
+ const UpdateCheckParams& params) {
+ LOG(INFO) << "Initializing parameters for this update attempt";
+ image_props_ = LoadImageProperties(system_state_);
+ mutable_image_props_ = LoadMutableImageProperties(system_state_);
+
+ // Validation check the channel names.
+ if (!IsValidChannel(image_props_.current_channel))
+ image_props_.current_channel = "stable-channel";
+ if (!IsValidChannel(mutable_image_props_.target_channel))
+ mutable_image_props_.target_channel = image_props_.current_channel;
+ UpdateDownloadChannel();
+
+ LOG(INFO) << "Running from channel " << image_props_.current_channel;
+
+ os_platform_ = constants::kOmahaPlatformName;
+ os_version_ = OmahaRequestParams::kOsVersion;
+ if (!app_version.empty())
+ image_props_.version = app_version;
+
+ os_sp_ = image_props_.version + "_" + GetMachineType();
+ app_lang_ = "en-US";
+ hwid_ = system_state_->hardware()->GetHardwareClass();
+ device_requisition_ = system_state_->hardware()->GetDeviceRequisition();
+
+ if (image_props_.current_channel == mutable_image_props_.target_channel) {
+ // deltas are only okay if the /.nodelta file does not exist. if we don't
+ // know (i.e. stat() returns some unexpected error), then err on the side of
+ // caution and say deltas are not okay.
+ struct stat stbuf;
+ delta_okay_ =
+ (stat((root_ + "/.nodelta").c_str(), &stbuf) < 0) && (errno == ENOENT);
+ } else {
+ LOG(INFO) << "Disabling deltas as a channel change to "
+ << mutable_image_props_.target_channel
+ << " is pending, with is_powerwash_allowed="
+ << utils::ToString(mutable_image_props_.is_powerwash_allowed);
+ // For now, disable delta updates if the current channel is different from
+ // the channel that we're sending to the update server because such updates
+ // are destined to fail -- the current rootfs hash will be different than
+ // the expected hash due to the different channel in /etc/lsb-release.
+ delta_okay_ = false;
+ }
+
+ if (update_url.empty())
+ update_url_ = image_props_.omaha_url;
+ else
+ update_url_ = update_url;
+
+ // Set the interactive flag accordingly.
+ interactive_ = params.interactive;
+
+ dlc_apps_params_.clear();
+ // Set false so it will do update by default.
+ is_install_ = false;
+
+ target_version_prefix_ = params.target_version_prefix;
+
+ lts_tag_ = params.lts_tag;
+
+ rollback_allowed_ = params.rollback_allowed;
+
+ // Set whether saving data over rollback is requested.
+ rollback_data_save_requested_ = params.rollback_data_save_requested;
+
+ // Set how many milestones of rollback are allowed.
+ rollback_allowed_milestones_ = params.rollback_allowed_milestones;
+
+ // Set the target channel, if one was provided.
+ if (params.target_channel.empty()) {
+ LOG(INFO) << "No target channel mandated by policy.";
+ } else {
+ LOG(INFO) << "Setting target channel as mandated: "
+ << params.target_channel;
+ string error_message;
+ if (!SetTargetChannel(params.target_channel,
+ params.rollback_on_channel_downgrade,
+ &error_message)) {
+ LOG(ERROR) << "Setting the channel failed: " << error_message;
+ }
+
+ // Since this is the beginning of a new attempt, update the download
+ // channel. The download channel won't be updated until the next attempt,
+ // even if target channel changes meanwhile, so that how we'll know if we
+ // should cancel the current download attempt if there's such a change in
+ // target channel.
+ UpdateDownloadChannel();
+ }
+
+ return true;
+}
+
+bool OmahaRequestParams::IsUpdateUrlOfficial() const {
+ return (update_url_ == constants::kOmahaDefaultAUTestURL ||
+ update_url_ == image_props_.omaha_url);
+}
+
+bool OmahaRequestParams::SetTargetChannel(const string& new_target_channel,
+ bool is_powerwash_allowed,
+ string* error_message) {
+ LOG(INFO) << "SetTargetChannel called with " << new_target_channel
+ << ", Is Powerwash Allowed = "
+ << utils::ToString(is_powerwash_allowed)
+ << ". Current channel = " << image_props_.current_channel
+ << ", existing target channel = "
+ << mutable_image_props_.target_channel
+ << ", download channel = " << download_channel_;
+ if (!IsValidChannel(new_target_channel, error_message)) {
+ return false;
+ }
+
+ MutableImageProperties new_props;
+ new_props.target_channel = new_target_channel;
+ new_props.is_powerwash_allowed = is_powerwash_allowed;
+
+ if (!StoreMutableImageProperties(system_state_, new_props)) {
+ if (error_message)
+ *error_message = "Error storing the new channel value.";
+ return false;
+ }
+ mutable_image_props_ = new_props;
+ return true;
+}
+
+void OmahaRequestParams::UpdateDownloadChannel() {
+ if (download_channel_ != mutable_image_props_.target_channel) {
+ download_channel_ = mutable_image_props_.target_channel;
+ LOG(INFO) << "Download channel for this attempt = " << download_channel_;
+ }
+}
+
+string OmahaRequestParams::GetMachineType() const {
+ struct utsname buf;
+ string ret;
+ if (uname(&buf) == 0)
+ ret = buf.machine;
+ return ret;
+}
+
+bool OmahaRequestParams::IsValidChannel(const string& channel,
+ string* error_message) const {
+ if (image_props_.allow_arbitrary_channels) {
+ if (!base::EndsWith(channel, "-channel", base::CompareCase::SENSITIVE)) {
+ if (error_message) {
+ *error_message = base::StringPrintf(
+ "Invalid channel name \"%s\", must ends with -channel.",
+ channel.c_str());
+ }
+ return false;
+ }
+ return true;
+ }
+ if (GetChannelIndex(channel) < 0) {
+ string valid_channels = brillo::string_utils::JoinRange(
+ ", ", std::begin(kChannelsByStability), std::end(kChannelsByStability));
+ if (error_message) {
+ *error_message =
+ base::StringPrintf("Invalid channel name \"%s\", valid names are: %s",
+ channel.c_str(),
+ valid_channels.c_str());
+ }
+ return false;
+ }
+ return true;
+}
+
+void OmahaRequestParams::set_root(const string& root) {
+ root_ = root;
+ test::SetImagePropertiesRootPrefix(root_.c_str());
+}
+
+int OmahaRequestParams::GetChannelIndex(const string& channel) const {
+ for (size_t t = 0; t < base::size(kChannelsByStability); ++t)
+ if (channel == kChannelsByStability[t])
+ return t;
+
+ return -1;
+}
+
+bool OmahaRequestParams::ToMoreStableChannel() const {
+ int current_channel_index = GetChannelIndex(image_props_.current_channel);
+ int download_channel_index = GetChannelIndex(download_channel_);
+
+ return download_channel_index > current_channel_index;
+}
+
+bool OmahaRequestParams::ShouldPowerwash() const {
+ if (!mutable_image_props_.is_powerwash_allowed)
+ return false;
+ // If arbitrary channels are allowed, always powerwash on channel change.
+ if (image_props_.allow_arbitrary_channels)
+ return image_props_.current_channel != download_channel_;
+ // Otherwise only powerwash if we are moving from less stable (higher version)
+ // to more stable channel (lower version).
+ return ToMoreStableChannel();
+}
+
+string OmahaRequestParams::GetAppId() const {
+ return download_channel_ == "canary-channel" ? image_props_.canary_product_id
+ : image_props_.product_id;
+}
+
+string OmahaRequestParams::GetDlcAppId(const std::string& dlc_id) const {
+ // Create APP ID according to |dlc_id| (sticking the current AppID to the
+ // DLC module ID with an underscode).
+ return GetAppId() + "_" + dlc_id;
+}
+
+bool OmahaRequestParams::IsDlcAppId(const std::string& app_id) const {
+ return dlc_apps_params().find(app_id) != dlc_apps_params().end();
+}
+
+void OmahaRequestParams::SetDlcNoUpdate(const string& app_id) {
+ auto itr = dlc_apps_params_.find(app_id);
+ if (itr == dlc_apps_params_.end())
+ return;
+ itr->second.updated = false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/omaha_request_params.h b/cros/omaha_request_params.h
new file mode 100644
index 0000000..26ea1c9
--- /dev/null
+++ b/cros/omaha_request_params.h
@@ -0,0 +1,420 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_CROS_OMAHA_REQUEST_PARAMS_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/cros/image_properties.h"
+#include "update_engine/update_manager/policy.h"
+
+// This gathers local system information and prepares info used by the
+// Omaha request action.
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// This class encapsulates the data Omaha gets for the request, along with
+// essential state needed for the processing of the request/response. The
+// strings in this struct should not be XML escaped.
+//
+// TODO(jaysri): chromium-os:39752 tracks the need to rename this class to
+// reflect its lifetime more appropriately.
+class OmahaRequestParams {
+ public:
+ explicit OmahaRequestParams(SystemState* system_state)
+ : system_state_(system_state),
+ os_platform_(constants::kOmahaPlatformName),
+ os_version_(kOsVersion),
+ delta_okay_(true),
+ interactive_(false),
+ rollback_allowed_(false),
+ rollback_data_save_requested_(false),
+ wall_clock_based_wait_enabled_(false),
+ update_check_count_wait_enabled_(false),
+ min_update_checks_needed_(kDefaultMinUpdateChecks),
+ max_update_checks_allowed_(kDefaultMaxUpdateChecks),
+ is_install_(false) {}
+
+ virtual ~OmahaRequestParams();
+
+ enum ActiveCountingType {
+ kDayBased = 0,
+ kDateBased,
+ };
+
+ struct AppParams {
+ ActiveCountingType active_counting_type;
+ // |name| is only used for DLCs to store the DLC ID.
+ std::string name;
+ int64_t ping_active;
+ int64_t ping_date_last_active;
+ int64_t ping_date_last_rollcall;
+ bool send_ping;
+ // |updated| is only used for DLCs to decide sending DBus message to
+ // dlcservice on an install/update completion.
+ bool updated = true;
+ };
+
+ // Setters and getters for the various properties.
+ inline std::string os_platform() const { return os_platform_; }
+ inline std::string os_version() const { return os_version_; }
+ inline std::string os_sp() const { return os_sp_; }
+ inline std::string os_board() const { return image_props_.board; }
+ inline std::string os_build_fingerprint() const {
+ return image_props_.build_fingerprint;
+ }
+ inline std::string os_build_type() const { return image_props_.build_type; }
+ inline std::string board_app_id() const { return image_props_.product_id; }
+ inline std::string canary_app_id() const {
+ return image_props_.canary_product_id;
+ }
+ inline void set_app_id(const std::string& app_id) {
+ image_props_.product_id = app_id;
+ image_props_.canary_product_id = app_id;
+ }
+ inline std::string app_lang() const { return app_lang_; }
+ inline std::string hwid() const { return hwid_; }
+ inline std::string device_requisition() const { return device_requisition_; }
+
+ inline void set_app_version(const std::string& version) {
+ image_props_.version = version;
+ }
+ inline std::string app_version() const { return image_props_.version; }
+ inline std::string product_components() const {
+ return image_props_.product_components;
+ }
+ inline void set_product_components(const std::string& product_components) {
+ image_props_.product_components = product_components;
+ }
+
+ inline std::string current_channel() const {
+ return image_props_.current_channel;
+ }
+ inline std::string target_channel() const {
+ return mutable_image_props_.target_channel;
+ }
+ inline std::string download_channel() const { return download_channel_; }
+
+ // Can client accept a delta ?
+ inline void set_delta_okay(bool ok) { delta_okay_ = ok; }
+ inline bool delta_okay() const { return delta_okay_; }
+
+ // True if this is a user-initiated update check.
+ inline void set_interactive(bool interactive) { interactive_ = interactive; }
+ inline bool interactive() const { return interactive_; }
+
+ inline void set_update_url(const std::string& url) { update_url_ = url; }
+ inline std::string update_url() const { return update_url_; }
+
+ inline void set_target_version_prefix(const std::string& prefix) {
+ target_version_prefix_ = prefix;
+ }
+
+ inline std::string target_version_prefix() const {
+ return target_version_prefix_;
+ }
+
+ inline std::string lts_tag() const { return lts_tag_; }
+
+ inline void set_lts_tag(const std::string& hint) { lts_tag_ = hint; }
+
+ inline void set_rollback_allowed(bool rollback_allowed) {
+ rollback_allowed_ = rollback_allowed;
+ }
+
+ inline bool rollback_allowed() const { return rollback_allowed_; }
+
+ inline void set_rollback_data_save_requested(
+ bool rollback_data_save_requested) {
+ rollback_data_save_requested_ = rollback_data_save_requested;
+ }
+
+ inline bool rollback_data_save_requested() const {
+ return rollback_data_save_requested_;
+ }
+
+ inline void set_rollback_allowed_milestones(int rollback_allowed_milestones) {
+ rollback_allowed_milestones_ = rollback_allowed_milestones;
+ }
+
+ inline int rollback_allowed_milestones() const {
+ return rollback_allowed_milestones_;
+ }
+
+ inline void set_wall_clock_based_wait_enabled(bool enabled) {
+ wall_clock_based_wait_enabled_ = enabled;
+ }
+ inline bool wall_clock_based_wait_enabled() const {
+ return wall_clock_based_wait_enabled_;
+ }
+
+ inline void set_waiting_period(base::TimeDelta period) {
+ waiting_period_ = period;
+ }
+ base::TimeDelta waiting_period() const { return waiting_period_; }
+
+ inline void set_update_check_count_wait_enabled(bool enabled) {
+ update_check_count_wait_enabled_ = enabled;
+ }
+
+ inline bool update_check_count_wait_enabled() const {
+ return update_check_count_wait_enabled_;
+ }
+
+ inline void set_min_update_checks_needed(int64_t min) {
+ min_update_checks_needed_ = min;
+ }
+ inline int64_t min_update_checks_needed() const {
+ return min_update_checks_needed_;
+ }
+
+ inline void set_max_update_checks_allowed(int64_t max) {
+ max_update_checks_allowed_ = max;
+ }
+ inline int64_t max_update_checks_allowed() const {
+ return max_update_checks_allowed_;
+ }
+ inline void set_dlc_apps_params(
+ const std::map<std::string, AppParams>& dlc_apps_params) {
+ dlc_apps_params_ = dlc_apps_params;
+ }
+ inline const std::map<std::string, AppParams>& dlc_apps_params() const {
+ return dlc_apps_params_;
+ }
+ inline void set_is_install(bool is_install) { is_install_ = is_install; }
+ inline bool is_install() const { return is_install_; }
+
+ inline void set_autoupdate_token(const std::string& token) {
+ autoupdate_token_ = token;
+ }
+ inline const std::string& autoupdate_token() const {
+ return autoupdate_token_;
+ }
+
+ // Returns the App ID corresponding to the current value of the
+ // download channel.
+ virtual std::string GetAppId() const;
+
+ // Returns the DLC app ID.
+ virtual std::string GetDlcAppId(const std::string& dlc_id) const;
+
+ // Returns true if the App ID is a DLC App ID that is currently part of the
+ // request parameters.
+ virtual bool IsDlcAppId(const std::string& app_id) const;
+
+ // If the App ID is a DLC App ID will set to no update.
+ void SetDlcNoUpdate(const std::string& app_id);
+
+ // Suggested defaults
+ static const char kOsVersion[];
+ static const int64_t kDefaultMinUpdateChecks = 0;
+ static const int64_t kDefaultMaxUpdateChecks = 8;
+
+ // Initializes all the data in the object. Non-empty
+ // |in_app_version| or |in_update_url| prevents automatic detection
+ // of the parameter. Returns true on success, false otherwise.
+ bool Init(const std::string& in_app_version,
+ const std::string& in_update_url,
+ const chromeos_update_manager::UpdateCheckParams& params);
+
+ // Permanently changes the release channel to |channel|. Performs a
+ // powerwash, if required and allowed.
+ // Returns true on success, false otherwise. Note: This call will fail if
+ // there's a channel change pending already. This is to serialize all the
+ // channel changes done by the user in order to avoid having to solve
+ // numerous edge cases around ensuring the powerwash happens as intended in
+ // all such cases.
+ virtual bool SetTargetChannel(const std::string& channel,
+ bool is_powerwash_allowed,
+ std::string* error_message);
+
+ // Updates the download channel for this particular attempt from the current
+ // value of target channel. This method takes a "snapshot" of the current
+ // value of target channel and uses it for all subsequent Omaha requests for
+ // this attempt (i.e. initial request as well as download progress/error
+ // event requests). The snapshot will be updated only when either this method
+ // or Init is called again.
+ virtual void UpdateDownloadChannel();
+
+ // Returns whether we should powerwash for this update. Note that this is
+ // just an indication, the final decision to powerwash or not is made in the
+ // response handler.
+ bool ShouldPowerwash() const;
+
+ // Check if the provided update URL is official, meaning either the default
+ // autoupdate server or the autoupdate autotest server.
+ virtual bool IsUpdateUrlOfficial() const;
+
+ // For unit-tests.
+ void set_root(const std::string& root);
+ void set_current_channel(const std::string& channel) {
+ image_props_.current_channel = channel;
+ }
+ void set_target_channel(const std::string& channel) {
+ mutable_image_props_.target_channel = channel;
+ }
+ void set_os_sp(const std::string& os_sp) { os_sp_ = os_sp; }
+ void set_os_board(const std::string& os_board) {
+ image_props_.board = os_board;
+ }
+ void set_app_lang(const std::string& app_lang) { app_lang_ = app_lang; }
+ void set_hwid(const std::string& hwid) { hwid_ = hwid; }
+ void set_is_powerwash_allowed(bool powerwash_allowed) {
+ mutable_image_props_.is_powerwash_allowed = powerwash_allowed;
+ }
+ bool is_powerwash_allowed() {
+ return mutable_image_props_.is_powerwash_allowed;
+ }
+
+ void set_device_requisition(const std::string& requisition) {
+ device_requisition_ = requisition;
+ }
+
+ private:
+ FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest);
+ FRIEND_TEST(OmahaRequestParamsTest, IsValidChannelTest);
+ FRIEND_TEST(OmahaRequestParamsTest, SetIsPowerwashAllowedTest);
+ FRIEND_TEST(OmahaRequestParamsTest, SetTargetChannelInvalidTest);
+ FRIEND_TEST(OmahaRequestParamsTest, SetTargetChannelTest);
+ FRIEND_TEST(OmahaRequestParamsTest, ShouldPowerwashTest);
+ FRIEND_TEST(OmahaRequestParamsTest, ToMoreStableChannelFlagTest);
+
+ // Returns true if |channel| is a valid channel, otherwise write error to
+ // |error_message| if passed and return false.
+ bool IsValidChannel(const std::string& channel,
+ std::string* error_message) const;
+ bool IsValidChannel(const std::string& channel) const {
+ return IsValidChannel(channel, nullptr);
+ }
+
+ // Returns the index of the given channel.
+ int GetChannelIndex(const std::string& channel) const;
+
+ // True if we're trying to update to a more stable channel.
+ // i.e. index(target_channel) > index(current_channel).
+ bool ToMoreStableChannel() const;
+
+ // Gets the machine type (e.g. "i686").
+ std::string GetMachineType() const;
+
+ // Global system context.
+ SystemState* system_state_;
+
+ // The system image properties.
+ ImageProperties image_props_;
+ MutableImageProperties mutable_image_props_;
+
+ // Basic properties of the OS and Application that go into the Omaha request.
+ std::string os_platform_;
+ std::string os_version_;
+ std::string os_sp_;
+ std::string app_lang_;
+
+ // There are three channel values we deal with:
+ // * The channel we got the image we are running from or "current channel"
+ // stored in |image_props_.current_channel|.
+ //
+ // * The release channel we are tracking, where we should get updates from,
+ // stored in |mutable_image_props_.target_channel|. This channel is
+ // normally the same as the current_channel, except when the user changes
+ // the channel. In that case it'll have the release channel the user
+ // switched to, regardless of whether we downloaded an update from that
+ // channel or not, or if we are in the middle of a download from a
+ // previously selected channel (as opposed to download channel
+ // which gets updated only at the start of next download).
+ //
+ // * The channel from which we're downloading the payload. This should
+ // normally be the same as target channel. But if the user made another
+ // channel change after we started the download, then they'd be different,
+ // in which case, we'd detect elsewhere that the target channel has been
+ // changed and cancel the current download attempt.
+ std::string download_channel_;
+
+ // The value defining the parameters of the LTS (Long Term Support).
+ std::string lts_tag_;
+
+ std::string hwid_; // Hardware Qualification ID of the client
+ // TODO(b:133324571) tracks removal of this field once it is no longer
+ // needed in AU requests. Remove by October 1st 2019.
+ std::string device_requisition_; // Chrome OS Requisition type.
+ bool delta_okay_; // If this client can accept a delta
+ bool interactive_; // Whether this is a user-initiated update check
+
+ // The URL to send the Omaha request to.
+ std::string update_url_;
+
+ // Prefix of the target OS version that the enterprise wants this device
+ // to be pinned to. It's empty otherwise.
+ std::string target_version_prefix_;
+
+ // Whether the client is accepting rollback images too.
+ bool rollback_allowed_;
+
+ // Whether rollbacks should preserve some system state during powerwash.
+ bool rollback_data_save_requested_;
+
+ // How many milestones the client can rollback to.
+ int rollback_allowed_milestones_;
+
+ // True if scattering or staging are enabled, in which case waiting_period_
+ // specifies the amount of absolute time that we've to wait for before sending
+ // a request to Omaha.
+ bool wall_clock_based_wait_enabled_;
+ base::TimeDelta waiting_period_;
+
+ // True if scattering or staging are enabled to denote the number of update
+ // checks we've to skip before we can send a request to Omaha. The min and max
+ // values establish the bounds for a random number to be chosen within that
+ // range to enable such a wait.
+ bool update_check_count_wait_enabled_;
+ int64_t min_update_checks_needed_;
+ int64_t max_update_checks_allowed_;
+
+ // When reading files, prepend root_ to the paths. Useful for testing.
+ std::string root_;
+
+ // A list of DLC modules to install. A mapping from DLC App ID to |AppParams|.
+ std::map<std::string, AppParams> dlc_apps_params_;
+
+ // This variable defines whether the payload is being installed in the current
+ // partition. At the moment, this is used for installing DLC modules on the
+ // current active partition instead of the inactive partition.
+ bool is_install_;
+
+ // Token used when making an update request for a specific build.
+ // For example: Token for a Quick Fix Build:
+ // https://cloud.google.com/docs/chrome-enterprise/policies/?policy=DeviceQuickFixBuildToken
+ std::string autoupdate_token_;
+
+ DISALLOW_COPY_AND_ASSIGN(OmahaRequestParams);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_OMAHA_REQUEST_PARAMS_H_
diff --git a/cros/omaha_request_params_unittest.cc b/cros/omaha_request_params_unittest.cc
new file mode 100644
index 0000000..71f3d4c
--- /dev/null
+++ b/cros/omaha_request_params_unittest.cc
@@ -0,0 +1,264 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_request_params.h"
+
+#include <stdio.h>
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/fake_system_state.h"
+
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class OmahaRequestParamsTest : public ::testing::Test {
+ public:
+ OmahaRequestParamsTest() : params_(&fake_system_state_) {}
+
+ protected:
+ void SetUp() override {
+ // Create a uniquely named test directory.
+ ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
+ params_.set_root(tempdir_.GetPath().value());
+ SetLockDown(false);
+ fake_system_state_.set_prefs(&fake_prefs_);
+ }
+
+ void SetLockDown(bool locked_down) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(locked_down);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(locked_down);
+ }
+
+ FakeSystemState fake_system_state_;
+ OmahaRequestParams params_{&fake_system_state_};
+ FakePrefs fake_prefs_;
+
+ base::ScopedTempDir tempdir_;
+};
+
+namespace {
+string GetMachineType() {
+ string machine_type;
+ if (!utils::ReadPipe("uname -m", &machine_type))
+ return "";
+ // Strip anything from the first newline char.
+ size_t newline_pos = machine_type.find('\n');
+ if (newline_pos != string::npos)
+ machine_type.erase(newline_pos);
+ return machine_type;
+}
+} // namespace
+
+TEST_F(OmahaRequestParamsTest, MissingChannelTest) {
+ EXPECT_TRUE(params_.Init("", "", {}));
+ // By default, if no channel is set, we should track the stable-channel.
+ EXPECT_EQ("stable-channel", params_.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ForceVersionTest) {
+ EXPECT_TRUE(params_.Init("ForcedVersion", "", {}));
+ EXPECT_EQ(string("ForcedVersion_") + GetMachineType(), params_.os_sp());
+ EXPECT_EQ("ForcedVersion", params_.app_version());
+}
+
+TEST_F(OmahaRequestParamsTest, ForcedURLTest) {
+ EXPECT_TRUE(params_.Init("", "http://forced.google.com", {}));
+ EXPECT_EQ("http://forced.google.com", params_.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingURLTest) {
+ EXPECT_TRUE(params_.Init("", "", {}));
+ EXPECT_EQ(constants::kOmahaDefaultProductionURL, params_.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, DeltaOKTest) {
+ EXPECT_TRUE(params_.Init("", "", {}));
+ EXPECT_TRUE(params_.delta_okay());
+}
+
+TEST_F(OmahaRequestParamsTest, NoDeltasTest) {
+ ASSERT_TRUE(
+ WriteFileString(tempdir_.GetPath().Append(".nodelta").value(), ""));
+ EXPECT_TRUE(params_.Init("", "", {}));
+ EXPECT_FALSE(params_.delta_okay());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelTest) {
+ {
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_root(tempdir_.GetPath().value());
+ EXPECT_TRUE(params.Init("", "", {}));
+ EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
+ EXPECT_FALSE(params.mutable_image_props_.is_powerwash_allowed);
+ }
+ params_.set_root(tempdir_.GetPath().value());
+ EXPECT_TRUE(params_.Init("", "", {}));
+ EXPECT_EQ("canary-channel", params_.target_channel());
+ EXPECT_FALSE(params_.mutable_image_props_.is_powerwash_allowed);
+}
+
+TEST_F(OmahaRequestParamsTest, SetIsPowerwashAllowedTest) {
+ {
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_root(tempdir_.GetPath().value());
+ EXPECT_TRUE(params.Init("", "", {}));
+ EXPECT_TRUE(params.SetTargetChannel("canary-channel", true, nullptr));
+ EXPECT_TRUE(params.mutable_image_props_.is_powerwash_allowed);
+ }
+ params_.set_root(tempdir_.GetPath().value());
+ EXPECT_TRUE(params_.Init("", "", {}));
+ EXPECT_EQ("canary-channel", params_.target_channel());
+ EXPECT_TRUE(params_.mutable_image_props_.is_powerwash_allowed);
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) {
+ {
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_root(tempdir_.GetPath().value());
+ SetLockDown(true);
+ EXPECT_TRUE(params.Init("", "", {}));
+ params.image_props_.allow_arbitrary_channels = false;
+ string error_message;
+ EXPECT_FALSE(
+ params.SetTargetChannel("dogfood-channel", true, &error_message));
+ // The error message should include a message about the valid channels.
+ EXPECT_NE(string::npos, error_message.find("stable-channel"));
+ EXPECT_FALSE(params.mutable_image_props_.is_powerwash_allowed);
+ }
+ params_.set_root(tempdir_.GetPath().value());
+ EXPECT_TRUE(params_.Init("", "", {}));
+ EXPECT_EQ("stable-channel", params_.target_channel());
+ EXPECT_FALSE(params_.mutable_image_props_.is_powerwash_allowed);
+}
+
+TEST_F(OmahaRequestParamsTest, IsValidChannelTest) {
+ EXPECT_TRUE(params_.IsValidChannel("canary-channel"));
+ EXPECT_TRUE(params_.IsValidChannel("stable-channel"));
+ EXPECT_TRUE(params_.IsValidChannel("beta-channel"));
+ EXPECT_TRUE(params_.IsValidChannel("dev-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("testimage-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("dogfood-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("some-channel"));
+ EXPECT_FALSE(params_.IsValidChannel(""));
+ params_.image_props_.allow_arbitrary_channels = true;
+ EXPECT_TRUE(params_.IsValidChannel("some-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("wrong-suffix"));
+ EXPECT_FALSE(params_.IsValidChannel(""));
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelWorks) {
+ params_.set_target_channel("dev-channel");
+ EXPECT_EQ("dev-channel", params_.target_channel());
+
+ // When an invalid value is set, it should be ignored.
+ EXPECT_FALSE(params_.SetTargetChannel("invalid-channel", false, nullptr));
+ EXPECT_EQ("dev-channel", params_.target_channel());
+
+ // When set to a valid value, it should take effect.
+ EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true, nullptr));
+ EXPECT_EQ("beta-channel", params_.target_channel());
+
+ // When set to the same value, it should be idempotent.
+ EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true, nullptr));
+ EXPECT_EQ("beta-channel", params_.target_channel());
+
+ // When set to a valid value while a change is already pending, it should
+ // succeed.
+ EXPECT_TRUE(params_.SetTargetChannel("stable-channel", true, nullptr));
+ EXPECT_EQ("stable-channel", params_.target_channel());
+
+ // Set a different channel in mutable_image_props_.
+ params_.set_target_channel("stable-channel");
+
+ // When set to a valid value while a change is already pending, it should
+ // succeed.
+ params_.Init("", "", {});
+ EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true, nullptr));
+ // The target channel should reflect the change, but the download channel
+ // should continue to retain the old value ...
+ EXPECT_EQ("beta-channel", params_.target_channel());
+ EXPECT_EQ("stable-channel", params_.download_channel());
+
+ // ... until we update the download channel explicitly.
+ params_.UpdateDownloadChannel();
+ EXPECT_EQ("beta-channel", params_.download_channel());
+ EXPECT_EQ("beta-channel", params_.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ChannelIndexTest) {
+ int canary = params_.GetChannelIndex("canary-channel");
+ int dev = params_.GetChannelIndex("dev-channel");
+ int beta = params_.GetChannelIndex("beta-channel");
+ int stable = params_.GetChannelIndex("stable-channel");
+ EXPECT_LE(canary, dev);
+ EXPECT_LE(dev, beta);
+ EXPECT_LE(beta, stable);
+
+ // testimage-channel or other names are not recognized, so index will be -1.
+ int testimage = params_.GetChannelIndex("testimage-channel");
+ int bogus = params_.GetChannelIndex("bogus-channel");
+ EXPECT_EQ(-1, testimage);
+ EXPECT_EQ(-1, bogus);
+}
+
+TEST_F(OmahaRequestParamsTest, ToMoreStableChannelFlagTest) {
+ params_.image_props_.current_channel = "canary-channel";
+ params_.download_channel_ = "stable-channel";
+ EXPECT_TRUE(params_.ToMoreStableChannel());
+ params_.image_props_.current_channel = "stable-channel";
+ EXPECT_FALSE(params_.ToMoreStableChannel());
+ params_.download_channel_ = "beta-channel";
+ EXPECT_FALSE(params_.ToMoreStableChannel());
+}
+
+TEST_F(OmahaRequestParamsTest, TargetChannelHintTest) {
+ EXPECT_TRUE(params_.Init("", "", {}));
+ const string kHint("foo-hint");
+ params_.set_lts_tag(kHint);
+ EXPECT_EQ(kHint, params_.lts_tag());
+}
+
+TEST_F(OmahaRequestParamsTest, ShouldPowerwashTest) {
+ params_.mutable_image_props_.is_powerwash_allowed = false;
+ EXPECT_FALSE(params_.ShouldPowerwash());
+ params_.mutable_image_props_.is_powerwash_allowed = true;
+ params_.image_props_.allow_arbitrary_channels = true;
+ params_.image_props_.current_channel = "foo-channel";
+ params_.download_channel_ = "bar-channel";
+ EXPECT_TRUE(params_.ShouldPowerwash());
+ params_.image_props_.allow_arbitrary_channels = false;
+ params_.image_props_.current_channel = "canary-channel";
+ params_.download_channel_ = "stable-channel";
+ EXPECT_TRUE(params_.ShouldPowerwash());
+}
+
+TEST_F(OmahaRequestParamsTest, RequisitionIsSetTest) {
+ EXPECT_TRUE(params_.Init("", "", {}));
+ EXPECT_EQ("fake_requisition", params_.device_requisition());
+}
+} // namespace chromeos_update_engine
diff --git a/cros/omaha_response.h b/cros/omaha_response.h
new file mode 100644
index 0000000..43783d6
--- /dev/null
+++ b/cros/omaha_response.h
@@ -0,0 +1,121 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_OMAHA_RESPONSE_H_
+#define UPDATE_ENGINE_CROS_OMAHA_RESPONSE_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+// This struct encapsulates the data Omaha's response for the request.
+// The strings in this struct are not XML escaped.
+struct OmahaResponse {
+ // True iff there is an update to be downloaded.
+ bool update_exists = false;
+
+ // If non-zero, server-dictated poll interval in seconds.
+ int poll_interval = 0;
+
+ // These are only valid if update_exists is true:
+ std::string version;
+
+ struct Package {
+ // The ordered list of URLs in the Omaha response. Each item is a complete
+ // URL (i.e. in terms of Omaha XML, each value is a urlBase + packageName)
+ std::vector<std::string> payload_urls;
+ uint64_t size = 0;
+ uint64_t metadata_size = 0;
+ std::string metadata_signature;
+ std::string hash;
+ // True if the payload described in this response is a delta payload.
+ // False if it's a full payload.
+ bool is_delta = false;
+ // True if the payload can be excluded from updating if consistently faulty.
+ // False if the payload is critical to update.
+ bool can_exclude = false;
+ // The App ID associated with the package.
+ std::string app_id;
+ };
+ std::vector<Package> packages;
+
+ std::string more_info_url;
+ std::string deadline;
+ int max_days_to_scatter = 0;
+ // The number of URL-related failures to tolerate before moving on to the
+ // next URL in the current pass. This is a configurable value from the
+ // Omaha Response attribute, if ever we need to fine tune the behavior.
+ uint32_t max_failure_count_per_url = 0;
+ bool prompt = false;
+
+ // True if the Omaha rule instructs us to disable the back-off logic
+ // on the client altogether. False otherwise.
+ bool disable_payload_backoff = false;
+
+ // True if the Omaha rule instructs us to disable p2p for downloading.
+ bool disable_p2p_for_downloading = false;
+
+ // True if the Omaha rule instructs us to disable p2p for sharing.
+ bool disable_p2p_for_sharing = false;
+
+ // True if the Omaha rule instructs us to powerwash.
+ bool powerwash_required = false;
+
+ // If not blank, a base-64 encoded representation of the PEM-encoded
+ // public key in the response.
+ std::string public_key_rsa;
+
+ // If not -1, the number of days since the epoch Jan 1, 2007 0:00
+ // PST, according to the Omaha Server's clock and timezone (PST8PDT,
+ // aka "Pacific Time".)
+ int install_date_days = -1;
+
+ // True if the returned image is a rollback for the device.
+ bool is_rollback = false;
+
+ struct RollbackKeyVersion {
+ // Kernel key version. 0xffff if the value is unknown.
+ uint16_t kernel_key = std::numeric_limits<uint16_t>::max();
+ // Kernel version. 0xffff if the value is unknown.
+ uint16_t kernel = std::numeric_limits<uint16_t>::max();
+ // Firmware key verison. 0xffff if the value is unknown.
+ uint16_t firmware_key = std::numeric_limits<uint16_t>::max();
+ // Firmware version. 0xffff if the value is unknown.
+ uint16_t firmware = std::numeric_limits<uint16_t>::max();
+ };
+
+ // Key versions of the returned rollback image. Values are 0xffff if the
+ // image not a rollback, or the fields were not present.
+ RollbackKeyVersion rollback_key_version;
+
+ // Key versions of the N - rollback_allowed_milestones release. For example,
+ // if the current version is 70 and rollback_allowed_milestones is 4, this
+ // will contain the key versions of version 66. This is used to ensure that
+ // the kernel and firmware keys are at most those of v66 so that v66 can be
+ // rolled back to.
+ RollbackKeyVersion past_rollback_key_version;
+};
+static_assert(sizeof(off_t) == 8, "off_t not 64 bit");
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_OMAHA_RESPONSE_H_
diff --git a/cros/omaha_response_handler_action.cc b/cros/omaha_response_handler_action.cc
new file mode 100644
index 0000000..b6c223f
--- /dev/null
+++ b/cros/omaha_response_handler_action.cc
@@ -0,0 +1,334 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_response_handler_action.h"
+
+#include <limits>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/version.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/connection_manager_interface.h"
+#include "update_engine/cros/omaha_request_params.h"
+#include "update_engine/cros/payload_state_interface.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+
+using chromeos_update_manager::kRollforwardInfinity;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateManager;
+using std::numeric_limits;
+using std::string;
+
+namespace chromeos_update_engine {
+
+OmahaResponseHandlerAction::OmahaResponseHandlerAction(
+ SystemState* system_state)
+ : system_state_(system_state),
+ deadline_file_(constants::kOmahaResponseDeadlineFile) {}
+
+void OmahaResponseHandlerAction::PerformAction() {
+ CHECK(HasInputObject());
+ ScopedActionCompleter completer(processor_, this);
+ const OmahaResponse& response = GetInputObject();
+ if (!response.update_exists) {
+ LOG(INFO) << "There are no updates. Aborting.";
+ completer.set_code(ErrorCode::kNoUpdate);
+ return;
+ }
+
+ // All decisions as to which URL should be used have already been done. So,
+ // make the current URL as the download URL.
+ string current_url = system_state_->payload_state()->GetCurrentUrl();
+ if (current_url.empty()) {
+ // This shouldn't happen as we should always supply the HTTPS backup URL.
+ // Handling this anyway, just in case.
+ LOG(ERROR) << "There are no suitable URLs in the response to use.";
+ completer.set_code(ErrorCode::kOmahaResponseInvalid);
+ return;
+ }
+
+ // This is the url to the first package, not all packages.
+ // (For updates): All |Action|s prior to this must pass in non-excluded URLs
+ // within the |OmahaResponse|, reference exlusion logic in
+ // |OmahaRequestAction| and keep the enforcement of exclusions for updates.
+ install_plan_.download_url = current_url;
+ install_plan_.version = response.version;
+
+ OmahaRequestParams* const params = system_state_->request_params();
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+ // If we're using p2p to download and there is a local peer, use it.
+ if (payload_state->GetUsingP2PForDownloading() &&
+ !payload_state->GetP2PUrl().empty()) {
+ LOG(INFO) << "Replacing URL " << install_plan_.download_url
+ << " with local URL " << payload_state->GetP2PUrl()
+ << " since p2p is enabled.";
+ install_plan_.download_url = payload_state->GetP2PUrl();
+ payload_state->SetUsingP2PForDownloading(true);
+ }
+
+ // Fill up the other properties based on the response.
+ string update_check_response_hash;
+ for (const auto& package : response.packages) {
+ brillo::Blob raw_hash;
+ if (!base::HexStringToBytes(package.hash, &raw_hash)) {
+ LOG(ERROR) << "Failed to convert payload hash from hex string to bytes: "
+ << package.hash;
+ completer.set_code(ErrorCode::kOmahaResponseInvalid);
+ return;
+ }
+ install_plan_.payloads.push_back(
+ {.payload_urls = package.payload_urls,
+ .size = package.size,
+ .metadata_size = package.metadata_size,
+ .metadata_signature = package.metadata_signature,
+ .hash = raw_hash,
+ .type = package.is_delta ? InstallPayloadType::kDelta
+ : InstallPayloadType::kFull});
+ update_check_response_hash += package.hash + ":";
+ }
+ install_plan_.public_key_rsa = response.public_key_rsa;
+ install_plan_.hash_checks_mandatory = AreHashChecksMandatory(response);
+ install_plan_.is_resume = DeltaPerformer::CanResumeUpdate(
+ system_state_->prefs(), update_check_response_hash);
+ if (install_plan_.is_resume) {
+ payload_state->UpdateResumed();
+ } else {
+ payload_state->UpdateRestarted();
+ LOG_IF(WARNING,
+ !DeltaPerformer::ResetUpdateProgress(system_state_->prefs(), false))
+ << "Unable to reset the update progress.";
+ LOG_IF(WARNING,
+ !system_state_->prefs()->SetString(kPrefsUpdateCheckResponseHash,
+ update_check_response_hash))
+ << "Unable to save the update check response hash.";
+ }
+
+ if (params->is_install()) {
+ install_plan_.target_slot = system_state_->boot_control()->GetCurrentSlot();
+ install_plan_.source_slot = BootControlInterface::kInvalidSlot;
+ } else {
+ install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot();
+ install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
+ }
+
+ // The Omaha response doesn't include the channel name for this image, so we
+ // use the download_channel we used during the request to tag the target slot.
+ // This will be used in the next boot to know the channel the image was
+ // downloaded from.
+ string current_channel_key =
+ kPrefsChannelOnSlotPrefix + std::to_string(install_plan_.target_slot);
+ system_state_->prefs()->SetString(current_channel_key,
+ params->download_channel());
+
+ // Checking whether device is able to boot up the returned rollback image.
+ if (response.is_rollback) {
+ if (!params->rollback_allowed()) {
+ LOG(ERROR) << "Received rollback image but rollback is not allowed.";
+ completer.set_code(ErrorCode::kOmahaResponseInvalid);
+ return;
+ }
+
+ // Calculate the values on the version values on current device.
+ auto min_kernel_key_version = static_cast<uint32_t>(
+ system_state_->hardware()->GetMinKernelKeyVersion());
+ auto min_firmware_key_version = static_cast<uint32_t>(
+ system_state_->hardware()->GetMinFirmwareKeyVersion());
+
+ uint32_t kernel_key_version =
+ static_cast<uint32_t>(response.rollback_key_version.kernel_key) << 16 |
+ static_cast<uint32_t>(response.rollback_key_version.kernel);
+ uint32_t firmware_key_version =
+ static_cast<uint32_t>(response.rollback_key_version.firmware_key)
+ << 16 |
+ static_cast<uint32_t>(response.rollback_key_version.firmware);
+
+ LOG(INFO) << "Rollback image versions:"
+ << " device_kernel_key_version=" << min_kernel_key_version
+ << " image_kernel_key_version=" << kernel_key_version
+ << " device_firmware_key_version=" << min_firmware_key_version
+ << " image_firmware_key_version=" << firmware_key_version;
+
+ // Don't attempt a rollback if the versions are incompatible or the
+ // target image does not specify the version information.
+ if (kernel_key_version == numeric_limits<uint32_t>::max() ||
+ firmware_key_version == numeric_limits<uint32_t>::max() ||
+ kernel_key_version < min_kernel_key_version ||
+ firmware_key_version < min_firmware_key_version) {
+ LOG(ERROR) << "Device won't be able to boot up the rollback image.";
+ completer.set_code(ErrorCode::kRollbackNotPossible);
+ return;
+ }
+ install_plan_.is_rollback = true;
+ install_plan_.rollback_data_save_requested =
+ params->rollback_data_save_requested();
+ }
+
+ // Powerwash if either the response requires it or the parameters indicated
+ // powerwash (usually because there was a channel downgrade) and we are
+ // downgrading the version. Enterprise rollback, indicated by
+ // |response.is_rollback| is dealt with separately above.
+ if (response.powerwash_required) {
+ install_plan_.powerwash_required = true;
+ } else if (params->ShouldPowerwash() && !response.is_rollback) {
+ base::Version new_version(response.version);
+ base::Version current_version(params->app_version());
+
+ if (!new_version.IsValid()) {
+ LOG(WARNING) << "Not powerwashing,"
+ << " the update's version number is unreadable."
+ << " Update's version number: " << response.version;
+ } else if (!current_version.IsValid()) {
+ LOG(WARNING) << "Not powerwashing,"
+ << " the current version number is unreadable."
+ << " Current version number: " << params->app_version();
+ } else if (new_version < current_version) {
+ install_plan_.powerwash_required = true;
+ // Always try to preserve enrollment and wifi data for enrolled devices.
+ install_plan_.rollback_data_save_requested =
+ system_state_ && system_state_->device_policy() &&
+ system_state_->device_policy()->IsEnterpriseEnrolled();
+ }
+ }
+
+ TEST_AND_RETURN(HasOutputPipe());
+ if (HasOutputPipe())
+ SetOutputObject(install_plan_);
+ LOG(INFO) << "Using this install plan:";
+ install_plan_.Dump();
+
+ // Send the deadline data (if any) to Chrome through a file. This is a pretty
+ // hacky solution but should be OK for now.
+ //
+ // TODO(petkov): Re-architect this to avoid communication through a
+ // file. Ideally, we would include this information in D-Bus's GetStatus
+ // method and UpdateStatus signal. A potential issue is that update_engine may
+ // be unresponsive during an update download.
+ if (!deadline_file_.empty()) {
+ if (payload_state->GetRollbackHappened()) {
+ // Don't do forced update if rollback has happened since the last update
+ // check where policy was present.
+ LOG(INFO) << "Not forcing update because a rollback happened.";
+ utils::WriteFile(deadline_file_.c_str(), nullptr, 0);
+ } else {
+ utils::WriteFile(deadline_file_.c_str(),
+ response.deadline.data(),
+ response.deadline.size());
+ }
+ chmod(deadline_file_.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ }
+
+ // Check the generated install-plan with the Policy to confirm that
+ // it can be applied at this time (or at all).
+ UpdateManager* const update_manager = system_state_->update_manager();
+ CHECK(update_manager);
+ auto ec = ErrorCode::kSuccess;
+ update_manager->PolicyRequest(
+ &Policy::UpdateCanBeApplied, &ec, &install_plan_);
+ completer.set_code(ec);
+
+ const auto allowed_milestones = params->rollback_allowed_milestones();
+ if (allowed_milestones > 0) {
+ auto max_firmware_rollforward = numeric_limits<uint32_t>::max();
+ auto max_kernel_rollforward = numeric_limits<uint32_t>::max();
+
+ // Determine the version to update the max rollforward verified boot
+ // value.
+ OmahaResponse::RollbackKeyVersion version =
+ response.past_rollback_key_version;
+
+ // Determine the max rollforward values to be set in the TPM.
+ max_firmware_rollforward = static_cast<uint32_t>(version.firmware_key)
+ << 16 |
+ static_cast<uint32_t>(version.firmware);
+ max_kernel_rollforward = static_cast<uint32_t>(version.kernel_key) << 16 |
+ static_cast<uint32_t>(version.kernel);
+
+ // In the case that the value is 0xffffffff, log a warning because the
+ // device should not be installing a rollback image without having version
+ // information.
+ if (max_firmware_rollforward == numeric_limits<uint32_t>::max() ||
+ max_kernel_rollforward == numeric_limits<uint32_t>::max()) {
+ LOG(WARNING)
+ << "Max rollforward values were not sent in rollback response: "
+ << " max_kernel_rollforward=" << max_kernel_rollforward
+ << " max_firmware_rollforward=" << max_firmware_rollforward
+ << " rollback_allowed_milestones="
+ << params->rollback_allowed_milestones();
+ } else {
+ LOG(INFO) << "Setting the max rollforward values: "
+ << " max_kernel_rollforward=" << max_kernel_rollforward
+ << " max_firmware_rollforward=" << max_firmware_rollforward
+ << " rollback_allowed_milestones="
+ << params->rollback_allowed_milestones();
+ system_state_->hardware()->SetMaxKernelKeyRollforward(
+ max_kernel_rollforward);
+ // TODO(crbug/783998): Set max firmware rollforward when implemented.
+ }
+ } else {
+ LOG(INFO) << "Rollback is not allowed. Setting max rollforward values"
+ << " to infinity";
+ // When rollback is not allowed, explicitly set the max roll forward to
+ // infinity.
+ system_state_->hardware()->SetMaxKernelKeyRollforward(kRollforwardInfinity);
+ // TODO(crbug/783998): Set max firmware rollforward when implemented.
+ }
+}
+
+bool OmahaResponseHandlerAction::AreHashChecksMandatory(
+ const OmahaResponse& response) {
+ // We sometimes need to waive the hash checks in order to download from
+ // sources that don't provide hashes, such as dev server.
+ // At this point UpdateAttempter::IsAnyUpdateSourceAllowed() has already been
+ // checked, so an unofficial update URL won't get this far unless it's OK to
+ // use without a hash. Additionally, we want to always waive hash checks on
+ // unofficial builds (i.e. dev/test images).
+ // The end result is this:
+ // * Base image:
+ // - Official URLs require a hash.
+ // - Unofficial URLs only get this far if the IsAnyUpdateSourceAllowed()
+ // devmode/debugd checks pass, in which case the hash is waived.
+ // * Dev/test image:
+ // - Any URL is allowed through with no hash checking.
+ if (!system_state_->request_params()->IsUpdateUrlOfficial() ||
+ !system_state_->hardware()->IsOfficialBuild()) {
+ // Still do a hash check if a public key is included.
+ if (!response.public_key_rsa.empty()) {
+ // The autoupdate_CatchBadSignatures test checks for this string
+ // in log-files. Keep in sync.
+ LOG(INFO) << "Mandating payload hash checks since Omaha Response "
+ << "for unofficial build includes public RSA key.";
+ return true;
+ } else {
+ LOG(INFO) << "Waiving payload hash checks for unofficial update URL.";
+ return false;
+ }
+ }
+
+ LOG(INFO) << "Mandating hash checks for official URL on official build.";
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/omaha_response_handler_action.h b/cros/omaha_response_handler_action.h
new file mode 100644
index 0000000..f3b821e
--- /dev/null
+++ b/cros/omaha_response_handler_action.h
@@ -0,0 +1,93 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_OMAHA_RESPONSE_HANDLER_ACTION_H_
+#define UPDATE_ENGINE_CROS_OMAHA_RESPONSE_HANDLER_ACTION_H_
+
+#include <string>
+
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/cros/omaha_request_action.h"
+#include "update_engine/payload_consumer/install_plan.h"
+
+// This class reads in an Omaha response and converts what it sees into
+// an install plan which is passed out.
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerAction;
+
+template <>
+class ActionTraits<OmahaResponseHandlerAction> {
+ public:
+ typedef OmahaResponse InputObjectType;
+ typedef InstallPlan OutputObjectType;
+};
+
+class OmahaResponseHandlerAction : public Action<OmahaResponseHandlerAction> {
+ public:
+ explicit OmahaResponseHandlerAction(SystemState* system_state);
+
+ typedef ActionTraits<OmahaResponseHandlerAction>::InputObjectType
+ InputObjectType;
+ typedef ActionTraits<OmahaResponseHandlerAction>::OutputObjectType
+ OutputObjectType;
+ void PerformAction() override;
+
+ // This is a synchronous action, and thus TerminateProcessing() should
+ // never be called
+ void TerminateProcessing() override { CHECK(false); }
+
+ const InstallPlan& install_plan() const { return install_plan_; }
+
+ // Debugging/logging
+ static std::string StaticType() { return "OmahaResponseHandlerAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ private:
+ // Returns true if payload hash checks are mandatory based on the state
+ // of the system and the contents of the Omaha response. False otherwise.
+ bool AreHashChecksMandatory(const OmahaResponse& response);
+
+ // Global system context.
+ SystemState* system_state_;
+
+ // The install plan, if we have an update.
+ InstallPlan install_plan_;
+
+ // File used for communication deadline to Chrome.
+ std::string deadline_file_;
+
+ friend class OmahaResponseHandlerActionTest;
+ friend class OmahaResponseHandlerActionProcessorDelegate;
+ FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+ FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackFailure);
+ FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess);
+ FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackFailure);
+ FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackSuccess);
+ FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
+ FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
+ FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
+
+ DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_OMAHA_RESPONSE_HANDLER_ACTION_H_
diff --git a/cros/omaha_response_handler_action_unittest.cc b/cros/omaha_response_handler_action_unittest.cc
new file mode 100644
index 0000000..8da3205
--- /dev/null
+++ b/cros/omaha_response_handler_action_unittest.cc
@@ -0,0 +1,971 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_response_handler_action.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/fake_system_state.h"
+#include "update_engine/cros/mock_payload_state.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/update_manager/mock_policy.h"
+
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::FakeUpdateManager;
+using chromeos_update_manager::kRollforwardInfinity;
+using chromeos_update_manager::MockPolicy;
+using std::string;
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerActionProcessorDelegate
+ : public ActionProcessorDelegate {
+ public:
+ OmahaResponseHandlerActionProcessorDelegate()
+ : code_(ErrorCode::kError), code_set_(false) {}
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == OmahaResponseHandlerAction::StaticType()) {
+ auto response_handler_action =
+ static_cast<OmahaResponseHandlerAction*>(action);
+ code_ = code;
+ code_set_ = true;
+ response_handler_action_install_plan_.reset(
+ new InstallPlan(response_handler_action->install_plan_));
+ } else if (action->Type() ==
+ ObjectCollectorAction<InstallPlan>::StaticType()) {
+ auto collector_action =
+ static_cast<ObjectCollectorAction<InstallPlan>*>(action);
+ collector_action_install_plan_.reset(
+ new InstallPlan(collector_action->object()));
+ }
+ }
+ ErrorCode code_;
+ bool code_set_;
+ std::unique_ptr<InstallPlan> collector_action_install_plan_;
+ std::unique_ptr<InstallPlan> response_handler_action_install_plan_;
+};
+
+class OmahaResponseHandlerActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ FakeBootControl* fake_boot_control = fake_system_state_.fake_boot_control();
+ fake_boot_control->SetPartitionDevice(kPartitionNameKernel, 0, "/dev/sdz2");
+ fake_boot_control->SetPartitionDevice(kPartitionNameRoot, 0, "/dev/sdz3");
+ fake_boot_control->SetPartitionDevice(kPartitionNameKernel, 1, "/dev/sdz4");
+ fake_boot_control->SetPartitionDevice(kPartitionNameRoot, 1, "/dev/sdz5");
+ }
+
+ // Return true iff the OmahaResponseHandlerAction succeeded.
+ // If out is non-null, it's set w/ the response from the action.
+ bool DoTest(const OmahaResponse& in,
+ const string& deadline_file,
+ InstallPlan* out);
+
+ // Delegate passed to the ActionProcessor.
+ OmahaResponseHandlerActionProcessorDelegate delegate_;
+
+ // Captures the action's result code, for tests that need to directly verify
+ // it in non-success cases.
+ ErrorCode action_result_code_;
+
+ FakeSystemState fake_system_state_;
+ // "Hash+"
+ const brillo::Blob expected_hash_ = {0x48, 0x61, 0x73, 0x68, 0x2b};
+};
+
+namespace {
+const char* const kLongName =
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "-the_update_a.b.c.d_DELTA_.tgz";
+const char* const kBadVersion = "don't update me";
+const char* const kPayloadHashHex = "486173682b";
+} // namespace
+
+bool OmahaResponseHandlerActionTest::DoTest(const OmahaResponse& in,
+ const string& test_deadline_file,
+ InstallPlan* out) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+ ActionProcessor processor;
+ processor.set_delegate(&delegate_);
+
+ auto feeder_action = std::make_unique<ObjectFeederAction<OmahaResponse>>();
+ feeder_action->set_obj(in);
+ if (in.update_exists && in.version != kBadVersion) {
+ string expected_hash;
+ for (const auto& package : in.packages)
+ expected_hash += package.hash + ":";
+ EXPECT_CALL(*(fake_system_state_.mock_prefs()),
+ SetString(kPrefsUpdateCheckResponseHash, expected_hash))
+ .WillOnce(Return(true));
+
+ int slot =
+ fake_system_state_.request_params()->is_install()
+ ? fake_system_state_.fake_boot_control()->GetCurrentSlot()
+ : 1 - fake_system_state_.fake_boot_control()->GetCurrentSlot();
+ string key = kPrefsChannelOnSlotPrefix + std::to_string(slot);
+ EXPECT_CALL(*(fake_system_state_.mock_prefs()), SetString(key, testing::_))
+ .WillOnce(Return(true));
+ }
+
+ string current_url = in.packages.size() ? in.packages[0].payload_urls[0] : "";
+ EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
+ .WillRepeatedly(Return(current_url));
+
+ auto response_handler_action =
+ std::make_unique<OmahaResponseHandlerAction>(&fake_system_state_);
+ if (!test_deadline_file.empty())
+ response_handler_action->deadline_file_ = test_deadline_file;
+
+ auto collector_action =
+ std::make_unique<ObjectCollectorAction<InstallPlan>>();
+
+ BondActions(feeder_action.get(), response_handler_action.get());
+ BondActions(response_handler_action.get(), collector_action.get());
+ processor.EnqueueAction(std::move(feeder_action));
+ processor.EnqueueAction(std::move(response_handler_action));
+ processor.EnqueueAction(std::move(collector_action));
+ processor.StartProcessing();
+ EXPECT_TRUE(!processor.IsRunning())
+ << "Update test to handle non-async actions";
+
+ if (out && delegate_.collector_action_install_plan_)
+ *out = *delegate_.collector_action_install_plan_;
+
+ EXPECT_TRUE(delegate_.code_set_);
+ action_result_code_ = delegate_.code_;
+ return delegate_.code_ == ErrorCode::kSuccess;
+}
+
+TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
+ ScopedTempFile test_deadline_file(
+ "omaha_response_handler_action_unittest-XXXXXX");
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ 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});
+ in.more_info_url = "http://more/info";
+ in.prompt = false;
+ in.deadline = "20101020";
+ InstallPlan install_plan;
+ 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(1U, install_plan.target_slot);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline));
+ EXPECT_EQ("20101020", deadline);
+ struct stat deadline_stat;
+ EXPECT_EQ(0, stat(test_deadline_file.path().c_str(), &deadline_stat));
+ EXPECT_EQ(
+ static_cast<mode_t>(S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH),
+ deadline_stat.st_mode);
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ 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});
+ in.more_info_url = "http://more/info";
+ in.prompt = true;
+ InstallPlan install_plan;
+ // Set the other slot as current.
+ fake_system_state_.fake_boot_control()->SetCurrentSlot(1);
+ 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(0U, install_plan.target_slot);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline) &&
+ deadline.empty());
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back(
+ {.payload_urls = {kLongName}, .size = 12, .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+ in.prompt = true;
+ in.deadline = "some-deadline";
+ InstallPlan install_plan;
+ fake_system_state_.fake_boot_control()->SetCurrentSlot(0);
+ // Because rollback happened, the deadline shouldn't be written into the
+ // file.
+ EXPECT_CALL(*(fake_system_state_.mock_payload_state()),
+ GetRollbackHappened())
+ .WillOnce(Return(true));
+ 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(1U, install_plan.target_slot);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline));
+ EXPECT_TRUE(deadline.empty());
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back(
+ {.payload_urls = {kLongName}, .size = 12, .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+ in.prompt = true;
+ in.deadline = "some-deadline";
+ InstallPlan install_plan;
+ fake_system_state_.fake_boot_control()->SetCurrentSlot(0);
+ EXPECT_CALL(*(fake_system_state_.mock_payload_state()),
+ GetRollbackHappened())
+ .WillOnce(Return(false));
+ 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(1U, install_plan.target_slot);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline));
+ EXPECT_EQ("some-deadline", deadline);
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+}
+
+TEST_F(OmahaResponseHandlerActionTest, NoUpdatesTest) {
+ OmahaResponse in;
+ in.update_exists = false;
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "", &install_plan));
+ EXPECT_TRUE(install_plan.partitions.empty());
+}
+
+TEST_F(OmahaResponseHandlerActionTest, InstallTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back(
+ {.payload_urls = {kLongName}, .size = 1, .hash = kPayloadHashHex});
+ in.packages.push_back(
+ {.payload_urls = {kLongName}, .size = 2, .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_is_install(true);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(install_plan.source_slot, UINT_MAX);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, MultiPackageTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back({.payload_urls = {"http://package/1"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.packages.push_back({.payload_urls = {"http://package/2"},
+ .size = 2,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(2u, install_plan.payloads.size());
+ EXPECT_EQ(in.packages[0].size, install_plan.payloads[0].size);
+ 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.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back(
+ {.payload_urls = {"http://test.should/need/hash.checks.signed"},
+ .size = 12,
+ .hash = kPayloadHashHex});
+ 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()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ InstallPlan install_plan;
+ 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_TRUE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForUnofficialUpdateUrl) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back(
+ {.payload_urls = {"http://url.normally/needs/hash.checks.signed"},
+ .size = 12,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+ EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(false));
+ InstallPlan install_plan;
+ 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_FALSE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+ HashChecksForOfficialUrlUnofficialBuildTest) {
+ // Official URLs for unofficial builds (dev/test images) don't require hash.
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back(
+ {.payload_urls = {"http://url.normally/needs/hash.checks.signed"},
+ .size = 12,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+ EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ InstallPlan install_plan;
+ 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_FALSE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpsTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back(
+ {.payload_urls = {"https://test.should/need/hash.checks.signed"},
+ .size = 12,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+ EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ InstallPlan install_plan;
+ 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_TRUE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForBothHttpAndHttpsTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back(
+ {.payload_urls = {"http://test.should.still/need/hash.checks",
+ "https://test.should.still/need/hash.checks"},
+ .size = 12,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+ EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ InstallPlan install_plan;
+ 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_TRUE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+ ChangeToMoreStableVersionAndChannelTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "1.0.0.0";
+ in.packages.push_back({.payload_urls = {"https://MoreStableChannelTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ params.set_root(tempdir.GetPath().value());
+ params.set_current_channel("canary-channel");
+ // The ImageProperties in Android uses prefs to store MutableImageProperties.
+#ifdef __ANDROID__
+ EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, true))
+ .WillOnce(Return(true));
+#endif // __ANDROID__
+ EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+ params.UpdateDownloadChannel();
+ params.set_app_version("2.0.0.0");
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_TRUE(install_plan.powerwash_required);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+ ChangeToMoreStableVersionAndChannelPowerwashNotAllowedTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "1.0.0.0";
+ in.packages.push_back({.payload_urls = {"https://MoreStableChannelTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ params.set_root(tempdir.GetPath().value());
+ params.set_current_channel("canary-channel");
+ // The |ImageProperties| in Android uses prefs to store
+ // |MutableImageProperties|.
+#ifdef __ANDROID__
+ EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, true))
+ .WillOnce(Return(true));
+#endif // __ANDROID__
+ EXPECT_TRUE(params.SetTargetChannel("stable-channel", false, nullptr));
+ params.UpdateDownloadChannel();
+ params.set_app_version("2.0.0.0");
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_FALSE(install_plan.powerwash_required);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+ ChangeToMoreStableChannelButNewerVersionTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "12345.96.0.0";
+ in.packages.push_back({.payload_urls = {"https://ChannelDownVersionUp"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ params.set_root(tempdir.GetPath().value());
+ params.set_current_channel("beta-channel");
+ // The |ImageProperties| in Android uses prefs to store
+ // |MutableImageProperties|.
+#ifdef __ANDROID__
+ EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, true))
+ .WillOnce(Return(true));
+#endif // __ANDROID__
+ EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+ params.UpdateDownloadChannel();
+ params.set_app_version("12345.48.0.0");
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_FALSE(install_plan.powerwash_required);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+ ChangeToMoreStableChannelButSameVersionTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "12345.0.0.0";
+ in.packages.push_back({.payload_urls = {"https://ChannelDownVersionUp"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ params.set_root(tempdir.GetPath().value());
+ params.set_current_channel("beta-channel");
+ EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+ params.UpdateDownloadChannel();
+ params.set_app_version("12345.0.0.0");
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_FALSE(install_plan.powerwash_required);
+ EXPECT_FALSE(install_plan.rollback_data_save_requested);
+}
+
+// On an enrolled device, the rollback data restore should be attempted when
+// doing a powerwash and channel downgrade.
+TEST_F(OmahaResponseHandlerActionTest,
+ ChangeToMoreStableChannelEnrolledDataRestore) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "12345.96.0.0";
+ in.packages.push_back({.payload_urls = {"https://ChannelDownEnrolled"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ params.set_root(tempdir.GetPath().value());
+ params.set_current_channel("beta-channel");
+ EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+ params.UpdateDownloadChannel();
+ params.set_app_version("12347.48.0.0");
+
+ testing::NiceMock<policy::MockDevicePolicy> mock_device_policy;
+ EXPECT_CALL(mock_device_policy, IsEnterpriseEnrolled())
+ .WillOnce(Return(true));
+ fake_system_state_.set_device_policy(&mock_device_policy);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_TRUE(install_plan.rollback_data_save_requested);
+}
+
+// Never attempt rollback data restore if the device is not enrolled.
+TEST_F(OmahaResponseHandlerActionTest,
+ ChangeToMoreStableChannelUnenrolledNoDataRestore) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "12345.96.0.0";
+ in.packages.push_back({.payload_urls = {"https://ChannelDownEnrolled"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ params.set_root(tempdir.GetPath().value());
+ params.set_current_channel("beta-channel");
+ EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+ params.UpdateDownloadChannel();
+ params.set_app_version("12347.48.0.0");
+
+ testing::NiceMock<policy::MockDevicePolicy> mock_device_policy;
+ EXPECT_CALL(mock_device_policy, IsEnterpriseEnrolled())
+ .WillOnce(Return(false));
+ fake_system_state_.set_device_policy(&mock_device_policy);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_FALSE(install_plan.rollback_data_save_requested);
+}
+
+// Never attempt rollback data restore if powerwash is not allowed.
+TEST_F(OmahaResponseHandlerActionTest,
+ ChangeToMoreStableChannelNoPowerwashNoDataRestore) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "12345.96.0.0";
+ in.packages.push_back(
+ {.payload_urls = {"https://URL"}, .size = 1, .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ params.set_root(tempdir.GetPath().value());
+ params.set_current_channel("beta-channel");
+ EXPECT_TRUE(params.SetTargetChannel("stable-channel", false, nullptr));
+ params.UpdateDownloadChannel();
+ params.set_app_version("12347.48.0.0");
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_FALSE(install_plan.rollback_data_save_requested);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+ ChangeToLessStableVersionAndChannelTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "2.0.0.0";
+ in.packages.push_back({.payload_urls = {"https://LessStableChannelTest"},
+ .size = 15,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ // Create a uniquely named test directory.
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ params.set_root(tempdir.GetPath().value());
+ params.set_current_channel("stable-channel");
+ // The ImageProperties in Android uses prefs to store MutableImageProperties.
+#ifdef __ANDROID__
+ EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, false))
+ .WillOnce(Return(true));
+#endif // __ANDROID__
+ EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
+ params.UpdateDownloadChannel();
+ params.set_app_version("1.0.0.0");
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_FALSE(install_plan.powerwash_required);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, P2PUrlIsUsedAndHashChecksMandatory) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back(
+ {.payload_urls = {"https://would.not/cause/hash/checks"},
+ .size = 12,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+
+ OmahaRequestParams params(&fake_system_state_);
+ // We're using a real OmahaRequestParams object here so we can't mock
+ // IsUpdateUrlOfficial(), but setting the update URL to the AutoUpdate test
+ // server will cause IsUpdateUrlOfficial() to return true.
+ params.set_update_url(constants::kOmahaDefaultAUTestURL);
+ fake_system_state_.set_request_params(¶ms);
+
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetUsingP2PForDownloading(true));
+
+ string p2p_url = "http://9.8.7.6/p2p";
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(), GetP2PUrl())
+ .WillRepeatedly(Return(p2p_url));
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ GetUsingP2PForDownloading())
+ .WillRepeatedly(Return(true));
+
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+ EXPECT_EQ(p2p_url, install_plan.download_url);
+ EXPECT_TRUE(install_plan.hash_checks_mandatory);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = true;
+
+ // The rollback payload is 2 versions behind stable.
+ in.rollback_key_version.kernel = 24;
+ in.rollback_key_version.kernel = 23;
+ in.rollback_key_version.firmware_key = 22;
+ in.rollback_key_version.firmware = 21;
+
+ OmahaResponse::RollbackKeyVersion m4;
+ m4.firmware_key = 16;
+ m4.firmware = 15;
+ m4.kernel_key = 14;
+ m4.kernel = 13;
+
+ in.past_rollback_key_version = m4;
+
+ fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+ fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+ fake_system_state_.fake_hardware()->SetMaxKernelKeyRollforward(0xaaaaaaaa);
+ // TODO(crbug/783998): Add support for firmware when implemented.
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(true);
+ params.set_rollback_allowed_milestones(4);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_TRUE(install_plan.is_rollback);
+
+ // The max rollforward should be set the values of the image
+ // rollback_allowed_milestones (4 for this test) in the past.
+ const uint32_t expected_max_kernel_rollforward =
+ static_cast<uint32_t>(m4.kernel_key) << 16 |
+ static_cast<uint32_t>(m4.kernel);
+ EXPECT_EQ(expected_max_kernel_rollforward,
+ fake_system_state_.fake_hardware()->GetMaxKernelKeyRollforward());
+ // TODO(crbug/783998): Add support for firmware when implemented.
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackKernelVersionErrorTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = true;
+ in.rollback_key_version.kernel_key = 1;
+ in.rollback_key_version.kernel = 1; // This is lower than the minimum.
+ in.rollback_key_version.firmware_key = 3;
+ in.rollback_key_version.firmware = 4;
+
+ OmahaResponse::RollbackKeyVersion m4;
+ m4.firmware_key = 16;
+ m4.firmware = 15;
+ m4.kernel_key = 14;
+ m4.kernel = 13;
+ in.past_rollback_key_version = m4;
+
+ fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+ fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+ const uint32_t current_kernel_max_rollforward = 0xaaaaaaaa;
+ fake_system_state_.fake_hardware()->SetMaxKernelKeyRollforward(
+ current_kernel_max_rollforward);
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(true);
+ params.set_rollback_allowed_milestones(4);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "", &install_plan));
+
+ // Max rollforward is not changed in error cases.
+ EXPECT_EQ(current_kernel_max_rollforward,
+ fake_system_state_.fake_hardware()->GetMaxKernelKeyRollforward());
+ // TODO(crbug/783998): Add support for firmware when implemented.
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackFirmwareVersionErrorTest) {
+ // TODO(crbug/783998): Add handling for max_firmware_rollforward when
+ // implemented.
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = true;
+ in.rollback_key_version.kernel_key = 1;
+ in.rollback_key_version.kernel = 2;
+ in.rollback_key_version.firmware_key = 3;
+ in.rollback_key_version.firmware = 3; // This is lower than the minimum.
+
+ fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+ fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(true);
+ params.set_rollback_allowed_milestones(4);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackNotRollbackTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = false;
+
+ const uint32_t current_kernel_max_rollforward = 0xaaaaaaaa;
+ fake_system_state_.fake_hardware()->SetMaxKernelKeyRollforward(
+ current_kernel_max_rollforward);
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(true);
+ params.set_rollback_allowed_milestones(4);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_FALSE(install_plan.is_rollback);
+
+ // Max rollforward is not changed for non-rollback cases.
+ EXPECT_EQ(current_kernel_max_rollforward,
+ fake_system_state_.fake_hardware()->GetMaxKernelKeyRollforward());
+ // TODO(crbug/783998): Add support for firmware when implemented.
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackNotAllowedTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = true;
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(false);
+ params.set_rollback_allowed_milestones(4);
+
+ const uint32_t current_kernel_max_rollforward = 0xaaaaaaaa;
+ fake_system_state_.fake_hardware()->SetMaxKernelKeyRollforward(
+ current_kernel_max_rollforward);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "", &install_plan));
+
+ // This case generates an error so, do not update max rollforward.
+ EXPECT_EQ(current_kernel_max_rollforward,
+ fake_system_state_.fake_hardware()->GetMaxKernelKeyRollforward());
+ // TODO(crbug/783998): Add support for firmware when implemented.
+}
+
+TEST_F(OmahaResponseHandlerActionTest, NormalUpdateWithZeroMilestonesAllowed) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.is_rollback = false;
+
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_rollback_allowed(true);
+ params.set_rollback_allowed_milestones(0);
+
+ const uint32_t current_kernel_max_rollforward = 0xaaaaaaaa;
+ fake_system_state_.fake_hardware()->SetMaxKernelKeyRollforward(
+ current_kernel_max_rollforward);
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+
+ // When allowed_milestones is 0, this is set to infinity.
+ EXPECT_EQ(kRollforwardInfinity,
+ fake_system_state_.fake_hardware()->GetMaxKernelKeyRollforward());
+ // TODO(crbug/783998): Add support for firmware when implemented.
+}
+
+TEST_F(OmahaResponseHandlerActionTest, SystemVersionTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.packages.push_back({.payload_urls = {"http://package/1"},
+ .size = 1,
+ .hash = kPayloadHashHex});
+ in.packages.push_back({.payload_urls = {"http://package/2"},
+ .size = 2,
+ .hash = kPayloadHashHex});
+ in.more_info_url = "http://more/info";
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(2u, install_plan.payloads.size());
+ EXPECT_EQ(in.packages[0].size, install_plan.payloads[0].size);
+ 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.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, TestDeferredByPolicy) {
+ OmahaResponse in;
+ in.update_exists = true;
+ 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});
+ // Setup the UpdateManager to disallow the update.
+ FakeClock fake_clock;
+ MockPolicy* mock_policy = new MockPolicy(&fake_clock);
+ FakeUpdateManager* fake_update_manager =
+ fake_system_state_.fake_update_manager();
+ fake_update_manager->set_policy(mock_policy);
+ EXPECT_CALL(*mock_policy, UpdateCanBeApplied(_, _, _, _, _))
+ .WillOnce(
+ DoAll(SetArgPointee<3>(ErrorCode::kOmahaUpdateDeferredPerPolicy),
+ Return(EvalStatus::kSucceeded)));
+ // Perform the Action. It should "fail" with kOmahaUpdateDeferredPerPolicy.
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(ErrorCode::kOmahaUpdateDeferredPerPolicy, action_result_code_);
+ // Verify that DoTest() didn't set the output install plan.
+ EXPECT_EQ("", install_plan.version);
+ // Now verify the InstallPlan that was generated.
+ 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(1U, install_plan.target_slot);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/omaha_utils.cc b/cros/omaha_utils.cc
new file mode 100644
index 0000000..fc05cb9
--- /dev/null
+++ b/cros/omaha_utils.cc
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_utils.h"
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+
+namespace chromeos_update_engine {
+
+const EolDate kEolDateInvalid = -9999;
+
+std::string EolDateToString(EolDate eol_date) {
+ return base::NumberToString(eol_date);
+}
+
+EolDate StringToEolDate(const std::string& eol_date) {
+ EolDate date = kEolDateInvalid;
+ if (!base::StringToInt64(eol_date, &date))
+ return kEolDateInvalid;
+ return date;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/omaha_utils.h b/cros/omaha_utils.h
new file mode 100644
index 0000000..6741635
--- /dev/null
+++ b/cros/omaha_utils.h
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_OMAHA_UTILS_H_
+#define UPDATE_ENGINE_CROS_OMAHA_UTILS_H_
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+using EolDate = int64_t;
+
+// |EolDate| indicating an invalid end-of-life date.
+extern const EolDate kEolDateInvalid;
+
+// 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_CROS_OMAHA_UTILS_H_
diff --git a/cros/omaha_utils_unittest.cc b/cros/omaha_utils_unittest.cc
new file mode 100644
index 0000000..f89f690
--- /dev/null
+++ b/cros/omaha_utils_unittest.cc
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/omaha_utils.h"
+
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+class OmahaUtilsTest : public ::testing::Test {};
+
+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/cros/p2p_manager.cc b/cros/p2p_manager.cc
new file mode 100644
index 0000000..dc12b35
--- /dev/null
+++ b/cros/p2p_manager.cc
@@ -0,0 +1,738 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// This provides access to timestamps with nanosecond resolution in
+// struct stat, See NOTES in stat(2) for details.
+#ifndef _DEFAULT_SOURCE
+#define _DEFAULT_SOURCE
+#endif
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include "update_engine/cros/p2p_manager.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/falloc.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_path.h>
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+
+using base::Bind;
+using base::Callback;
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateManager;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The default p2p directory.
+const char kDefaultP2PDir[] = "/var/cache/p2p";
+
+// The p2p xattr used for conveying the final size of a file - see the
+// p2p ddoc for details.
+const char kCrosP2PFileSizeXAttrName[] = "user.cros-p2p-filesize";
+
+} // namespace
+
+// The default P2PManager::Configuration implementation.
+class ConfigurationImpl : public P2PManager::Configuration {
+ public:
+ ConfigurationImpl() {}
+
+ FilePath GetP2PDir() override { return FilePath(kDefaultP2PDir); }
+
+ vector<string> GetInitctlArgs(bool is_start) override {
+ vector<string> args;
+ args.push_back("initctl");
+ args.push_back(is_start ? "start" : "stop");
+ args.push_back("p2p");
+ return args;
+ }
+
+ vector<string> GetP2PClientArgs(const string& file_id,
+ size_t minimum_size) override {
+ vector<string> args;
+ args.push_back("p2p-client");
+ args.push_back(string("--get-url=") + file_id);
+ args.push_back(StringPrintf("--minimum-size=%" PRIuS, minimum_size));
+ return args;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConfigurationImpl);
+};
+
+// The default P2PManager implementation.
+class P2PManagerImpl : public P2PManager {
+ public:
+ P2PManagerImpl(Configuration* configuration,
+ ClockInterface* clock,
+ UpdateManager* update_manager,
+ const string& file_extension,
+ const int num_files_to_keep,
+ const TimeDelta& max_file_age);
+
+ // P2PManager methods.
+ void SetDevicePolicy(const policy::DevicePolicy* device_policy) override;
+ bool IsP2PEnabled() override;
+ bool EnsureP2PRunning() override;
+ bool EnsureP2PNotRunning() override;
+ bool PerformHousekeeping() override;
+ void LookupUrlForFile(const string& file_id,
+ size_t minimum_size,
+ TimeDelta max_time_to_wait,
+ LookupCallback callback) override;
+ bool FileShare(const string& file_id, size_t expected_size) override;
+ FilePath FileGetPath(const string& file_id) override;
+ ssize_t FileGetSize(const string& file_id) override;
+ ssize_t FileGetExpectedSize(const string& file_id) override;
+ bool FileGetVisible(const string& file_id, bool* out_result) override;
+ bool FileMakeVisible(const string& file_id) override;
+ int CountSharedFiles() override;
+
+ private:
+ // Enumeration for specifying visibility.
+ enum Visibility { kVisible, kNonVisible };
+
+ // Returns "." + |file_extension_| + ".p2p" if |visibility| is
+ // |kVisible|. Returns the same concatenated with ".tmp" otherwise.
+ string GetExt(Visibility visibility);
+
+ // Gets the on-disk path for |file_id| depending on if the file
+ // is visible or not.
+ FilePath GetPath(const string& file_id, Visibility visibility);
+
+ // Utility function used by EnsureP2PRunning() and EnsureP2PNotRunning().
+ bool EnsureP2P(bool should_be_running);
+
+ // Utility function to delete a file given by |path| and log the
+ // path as well as |reason|. Returns false on failure.
+ bool DeleteP2PFile(const FilePath& path, const string& reason);
+
+ // Schedules an async request for tracking changes in P2P enabled status.
+ void ScheduleEnabledStatusChange();
+
+ // An async callback used by the above.
+ void OnEnabledStatusChange(EvalStatus status, const bool& result);
+
+ // The device policy being used or null if no policy is being used.
+ const policy::DevicePolicy* device_policy_ = nullptr;
+
+ // Configuration object.
+ unique_ptr<Configuration> configuration_;
+
+ // Object for telling the time.
+ ClockInterface* clock_;
+
+ // A pointer to the global Update Manager.
+ UpdateManager* update_manager_;
+
+ // A short string unique to the application (for example "cros_au")
+ // used to mark a file as being owned by a particular application.
+ const string file_extension_;
+
+ // If non-zero, this number denotes how many files in /var/cache/p2p
+ // owned by the application (cf. |file_extension_|) to keep after
+ // performing housekeeping.
+ const int num_files_to_keep_;
+
+ // If non-zero, files older than this will not be kept after
+ // performing housekeeping.
+ const TimeDelta max_file_age_;
+
+ // The string ".p2p".
+ static const char kP2PExtension[];
+
+ // The string ".tmp".
+ static const char kTmpExtension[];
+
+ // Whether P2P service may be running; initially, we assume it may be.
+ bool may_be_running_ = true;
+
+ // The current known enabled status of the P2P feature (initialized lazily),
+ // and whether an async status check has been scheduled.
+ bool is_enabled_;
+ bool waiting_for_enabled_status_change_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(P2PManagerImpl);
+};
+
+const char P2PManagerImpl::kP2PExtension[] = ".p2p";
+
+const char P2PManagerImpl::kTmpExtension[] = ".tmp";
+
+P2PManagerImpl::P2PManagerImpl(Configuration* configuration,
+ ClockInterface* clock,
+ UpdateManager* update_manager,
+ const string& file_extension,
+ const int num_files_to_keep,
+ const TimeDelta& max_file_age)
+ : clock_(clock),
+ update_manager_(update_manager),
+ file_extension_(file_extension),
+ num_files_to_keep_(num_files_to_keep),
+ max_file_age_(max_file_age) {
+ configuration_.reset(configuration != nullptr ? configuration
+ : new ConfigurationImpl());
+}
+
+void P2PManagerImpl::SetDevicePolicy(
+ const policy::DevicePolicy* device_policy) {
+ device_policy_ = device_policy;
+}
+
+bool P2PManagerImpl::IsP2PEnabled() {
+ if (!waiting_for_enabled_status_change_) {
+ // Get and store an initial value.
+ if (update_manager_->PolicyRequest(&Policy::P2PEnabled, &is_enabled_) ==
+ EvalStatus::kFailed) {
+ is_enabled_ = false;
+ LOG(ERROR) << "Querying P2P enabled status failed, disabling.";
+ }
+
+ // Track future changes (async).
+ ScheduleEnabledStatusChange();
+ }
+
+ return is_enabled_;
+}
+
+bool P2PManagerImpl::EnsureP2P(bool should_be_running) {
+ int return_code = 0;
+ string stderr;
+
+ may_be_running_ = true; // Unless successful, we must be conservative.
+
+ vector<string> args = configuration_->GetInitctlArgs(should_be_running);
+ if (!Subprocess::SynchronousExec(args, &return_code, nullptr, &stderr)) {
+ LOG(ERROR) << "Error spawning " << utils::StringVectorToString(args);
+ return false;
+ }
+
+ // If initctl(8) does not exit normally (exit status other than zero), ensure
+ // that the error message is not benign by scanning stderr; this is a
+ // necessity because initctl does not offer actions such as "start if not
+ // running" or "stop if running".
+ // TODO(zeuthen,chromium:277051): Avoid doing this.
+ if (return_code != 0) {
+ const char* expected_error_message =
+ should_be_running ? "initctl: Job is already running: p2p\n"
+ : "initctl: Unknown instance \n";
+ if (stderr != expected_error_message)
+ return false;
+ }
+
+ may_be_running_ = should_be_running; // Successful after all.
+ return true;
+}
+
+bool P2PManagerImpl::EnsureP2PRunning() {
+ return EnsureP2P(true);
+}
+
+bool P2PManagerImpl::EnsureP2PNotRunning() {
+ return EnsureP2P(false);
+}
+
+// Returns True if the timestamp in the first pair is greater than the
+// timestamp in the latter. If used with std::sort() this will yield a
+// sequence of elements where newer (high timestamps) elements precede
+// older ones (low timestamps).
+static bool MatchCompareFunc(const pair<FilePath, Time>& a,
+ const pair<FilePath, Time>& b) {
+ return a.second > b.second;
+}
+
+string P2PManagerImpl::GetExt(Visibility visibility) {
+ string ext = string(".") + file_extension_ + kP2PExtension;
+ switch (visibility) {
+ case kVisible:
+ break;
+ case kNonVisible:
+ ext += kTmpExtension;
+ break;
+ // Don't add a default case to let the compiler warn about newly
+ // added enum values.
+ }
+ return ext;
+}
+
+FilePath P2PManagerImpl::GetPath(const string& file_id, Visibility visibility) {
+ return configuration_->GetP2PDir().Append(file_id + GetExt(visibility));
+}
+
+bool P2PManagerImpl::DeleteP2PFile(const FilePath& path, const string& reason) {
+ LOG(INFO) << "Deleting p2p file " << path.value() << " (reason: " << reason
+ << ")";
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting p2p file " << path.value();
+ return false;
+ }
+ return true;
+}
+
+bool P2PManagerImpl::PerformHousekeeping() {
+ // Open p2p dir.
+ FilePath p2p_dir = configuration_->GetP2PDir();
+ const string ext_visible = GetExt(kVisible);
+ const string ext_non_visible = GetExt(kNonVisible);
+
+ bool deletion_failed = false;
+ vector<pair<FilePath, Time>> matches;
+
+ base::FileEnumerator dir(p2p_dir, false, base::FileEnumerator::FILES);
+ // Go through all files and collect their mtime.
+ for (FilePath name = dir.Next(); !name.empty(); name = dir.Next()) {
+ if (!(base::EndsWith(
+ name.value(), ext_visible, base::CompareCase::SENSITIVE) ||
+ base::EndsWith(
+ name.value(), ext_non_visible, base::CompareCase::SENSITIVE))) {
+ continue;
+ }
+
+ Time time = dir.GetInfo().GetLastModifiedTime();
+
+ // If instructed to keep only files younger than a given age
+ // (|max_file_age_| != 0), delete files satisfying this criteria
+ // right now. Otherwise add it to a list we'll consider for later.
+ if (clock_ != nullptr && max_file_age_ != TimeDelta() &&
+ clock_->GetWallclockTime() - time > max_file_age_) {
+ if (!DeleteP2PFile(name, "file too old"))
+ deletion_failed = true;
+ } else {
+ matches.push_back(std::make_pair(name, time));
+ }
+ }
+
+ // If instructed to only keep N files (|max_files_to_keep_ != 0),
+ // sort list of matches, newest (biggest time) to oldest (lowest
+ // time). Then delete starting at element |num_files_to_keep_|.
+ if (num_files_to_keep_ > 0) {
+ std::sort(matches.begin(), matches.end(), MatchCompareFunc);
+ vector<pair<FilePath, Time>>::const_iterator i;
+ for (i = matches.begin() + num_files_to_keep_; i < matches.end(); ++i) {
+ if (!DeleteP2PFile(i->first, "too many files"))
+ deletion_failed = true;
+ }
+ }
+
+ return !deletion_failed;
+}
+
+// Helper class for implementing LookupUrlForFile().
+class LookupData {
+ public:
+ explicit LookupData(P2PManager::LookupCallback callback)
+ : callback_(callback) {}
+
+ ~LookupData() {
+ if (timeout_task_ != MessageLoop::kTaskIdNull)
+ MessageLoop::current()->CancelTask(timeout_task_);
+ if (child_pid_)
+ Subprocess::Get().KillExec(child_pid_);
+ }
+
+ void InitiateLookup(const vector<string>& cmd, TimeDelta timeout) {
+ // NOTE: if we fail early (i.e. in this method), we need to schedule
+ // an idle to report the error. This is because we guarantee that
+ // the callback is always called from the message loop (this
+ // guarantee is useful for testing).
+
+ // We expect to run just "p2p-client" and find it in the path.
+ child_pid_ = Subprocess::Get().ExecFlags(
+ cmd,
+ Subprocess::kSearchPath,
+ {},
+ Bind(&LookupData::OnLookupDone, base::Unretained(this)));
+
+ if (!child_pid_) {
+ LOG(ERROR) << "Error spawning " << utils::StringVectorToString(cmd);
+ ReportErrorAndDeleteInIdle();
+ return;
+ }
+
+ if (timeout > TimeDelta()) {
+ timeout_task_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ Bind(&LookupData::OnTimeout, base::Unretained(this)),
+ timeout);
+ }
+ }
+
+ private:
+ void ReportErrorAndDeleteInIdle() {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&LookupData::OnIdleForReportErrorAndDelete,
+ base::Unretained(this)));
+ }
+
+ void OnIdleForReportErrorAndDelete() {
+ ReportError();
+ delete this;
+ }
+
+ void IssueCallback(const string& url) {
+ if (!callback_.is_null())
+ callback_.Run(url);
+ }
+
+ void ReportError() {
+ if (reported_)
+ return;
+ IssueCallback("");
+ reported_ = true;
+ }
+
+ void ReportSuccess(const string& output) {
+ if (reported_)
+ return;
+ string url = output;
+ size_t newline_pos = url.find('\n');
+ if (newline_pos != string::npos)
+ url.resize(newline_pos);
+
+ // Since p2p-client(1) is constructing this URL itself strictly
+ // speaking there's no need to validate it... but, anyway, can't
+ // hurt.
+ if (url.compare(0, 7, "http://") == 0) {
+ IssueCallback(url);
+ } else {
+ LOG(ERROR) << "p2p URL '" << url << "' does not look right. Ignoring.";
+ ReportError();
+ }
+ reported_ = true;
+ }
+
+ void OnLookupDone(int return_code, const string& output) {
+ child_pid_ = 0;
+ if (return_code != 0) {
+ LOG(INFO) << "Child exited with non-zero exit code " << return_code;
+ ReportError();
+ } else {
+ ReportSuccess(output);
+ }
+ delete this;
+ }
+
+ void OnTimeout() {
+ timeout_task_ = MessageLoop::kTaskIdNull;
+ ReportError();
+ delete this;
+ }
+
+ P2PManager::LookupCallback callback_;
+
+ // The Subprocess tag of the running process. A value of 0 means that the
+ // process is not running.
+ pid_t child_pid_{0};
+
+ // The timeout task_id we are waiting on, if any.
+ MessageLoop::TaskId timeout_task_{MessageLoop::kTaskIdNull};
+
+ bool reported_{false};
+};
+
+void P2PManagerImpl::LookupUrlForFile(const string& file_id,
+ size_t minimum_size,
+ TimeDelta max_time_to_wait,
+ LookupCallback callback) {
+ LookupData* lookup_data = new LookupData(callback);
+ string file_id_with_ext = file_id + "." + file_extension_;
+ vector<string> args =
+ configuration_->GetP2PClientArgs(file_id_with_ext, minimum_size);
+ lookup_data->InitiateLookup(args, max_time_to_wait);
+}
+
+bool P2PManagerImpl::FileShare(const string& file_id, size_t expected_size) {
+ // Check if file already exist.
+ FilePath path = FileGetPath(file_id);
+ if (!path.empty()) {
+ // File exists - double check its expected size though.
+ ssize_t file_expected_size = FileGetExpectedSize(file_id);
+ if (file_expected_size == -1 ||
+ static_cast<size_t>(file_expected_size) != expected_size) {
+ LOG(ERROR) << "Existing p2p file " << path.value()
+ << " with expected_size=" << file_expected_size
+ << " does not match the passed in"
+ << " expected_size=" << expected_size;
+ return false;
+ }
+ return true;
+ }
+
+ // Before creating the file, bail if statvfs(3) indicates that at
+ // least twice the size is not available in P2P_DIR.
+ struct statvfs statvfsbuf;
+ FilePath p2p_dir = configuration_->GetP2PDir();
+ if (statvfs(p2p_dir.value().c_str(), &statvfsbuf) != 0) {
+ PLOG(ERROR) << "Error calling statvfs() for dir " << p2p_dir.value();
+ return false;
+ }
+ size_t free_bytes =
+ static_cast<size_t>(statvfsbuf.f_bsize) * statvfsbuf.f_bavail;
+ if (free_bytes < 2 * expected_size) {
+ // This can easily happen and is worth reporting.
+ LOG(INFO) << "Refusing to allocate p2p file of " << expected_size
+ << " bytes since the directory " << p2p_dir.value()
+ << " only has " << free_bytes
+ << " bytes available and this is less than twice the"
+ << " requested size.";
+ return false;
+ }
+
+ // Okie-dokey looks like enough space is available - create the file.
+ path = GetPath(file_id, kNonVisible);
+ int fd = open(path.value().c_str(), O_CREAT | O_RDWR, 0644);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error creating file with path " << path.value();
+ return false;
+ }
+ ScopedFdCloser fd_closer(&fd);
+
+ // If the final size is known, allocate the file (e.g. reserve disk
+ // space) and set the user.cros-p2p-filesize xattr.
+ if (expected_size != 0) {
+ if (fallocate(fd,
+ FALLOC_FL_KEEP_SIZE, // Keep file size as 0.
+ 0,
+ expected_size) != 0) {
+ if (errno == ENOSYS || errno == EOPNOTSUPP) {
+ // If the filesystem doesn't support the fallocate, keep
+ // going. This is helpful when running unit tests on build
+ // machines with ancient filesystems and/or OSes.
+ PLOG(WARNING) << "Ignoring fallocate(2) failure";
+ } else {
+ // ENOSPC can happen (funky race though, cf. the statvfs() check
+ // above), handle it gracefully, e.g. use logging level INFO.
+ PLOG(INFO) << "Error allocating " << expected_size << " bytes for file "
+ << path.value();
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting file with path " << path.value();
+ }
+ return false;
+ }
+ }
+
+ string decimal_size = std::to_string(expected_size);
+ if (fsetxattr(fd,
+ kCrosP2PFileSizeXAttrName,
+ decimal_size.c_str(),
+ decimal_size.size(),
+ 0) != 0) {
+ PLOG(ERROR) << "Error setting xattr " << path.value();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+FilePath P2PManagerImpl::FileGetPath(const string& file_id) {
+ struct stat statbuf;
+ FilePath path;
+
+ path = GetPath(file_id, kVisible);
+ if (stat(path.value().c_str(), &statbuf) == 0) {
+ return path;
+ }
+
+ path = GetPath(file_id, kNonVisible);
+ if (stat(path.value().c_str(), &statbuf) == 0) {
+ return path;
+ }
+
+ path.clear();
+ return path;
+}
+
+bool P2PManagerImpl::FileGetVisible(const string& file_id, bool* out_result) {
+ FilePath path = FileGetPath(file_id);
+ if (path.empty()) {
+ LOG(ERROR) << "No file for id " << file_id;
+ return false;
+ }
+ if (out_result != nullptr)
+ *out_result = path.MatchesExtension(kP2PExtension);
+ return true;
+}
+
+bool P2PManagerImpl::FileMakeVisible(const string& file_id) {
+ FilePath path = FileGetPath(file_id);
+ if (path.empty()) {
+ LOG(ERROR) << "No file for id " << file_id;
+ return false;
+ }
+
+ // Already visible?
+ if (path.MatchesExtension(kP2PExtension))
+ return true;
+
+ LOG_ASSERT(path.MatchesExtension(kTmpExtension));
+ FilePath new_path = path.RemoveExtension();
+ LOG_ASSERT(new_path.MatchesExtension(kP2PExtension));
+ if (rename(path.value().c_str(), new_path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error renaming " << path.value() << " to "
+ << new_path.value();
+ return false;
+ }
+
+ return true;
+}
+
+ssize_t P2PManagerImpl::FileGetSize(const string& file_id) {
+ FilePath path = FileGetPath(file_id);
+ if (path.empty())
+ return -1;
+
+ return utils::FileSize(path.value());
+}
+
+ssize_t P2PManagerImpl::FileGetExpectedSize(const string& file_id) {
+ FilePath path = FileGetPath(file_id);
+ if (path.empty())
+ return -1;
+
+ char ea_value[64] = {0};
+ ssize_t ea_size;
+ ea_size = getxattr(path.value().c_str(),
+ kCrosP2PFileSizeXAttrName,
+ &ea_value,
+ sizeof(ea_value) - 1);
+ if (ea_size == -1) {
+ PLOG(ERROR) << "Error calling getxattr() on file " << path.value();
+ return -1;
+ }
+
+ char* endp = nullptr;
+ long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int)
+ if (*endp != '\0') {
+ LOG(ERROR) << "Error parsing the value '" << ea_value << "' of the xattr "
+ << kCrosP2PFileSizeXAttrName << " as an integer";
+ return -1;
+ }
+
+ return val;
+}
+
+int P2PManagerImpl::CountSharedFiles() {
+ int num_files = 0;
+
+ FilePath p2p_dir = configuration_->GetP2PDir();
+ const string ext_visible = GetExt(kVisible);
+ const string ext_non_visible = GetExt(kNonVisible);
+
+ base::FileEnumerator dir(p2p_dir, false, base::FileEnumerator::FILES);
+ for (FilePath name = dir.Next(); !name.empty(); name = dir.Next()) {
+ if (base::EndsWith(
+ name.value(), ext_visible, base::CompareCase::SENSITIVE) ||
+ base::EndsWith(
+ name.value(), ext_non_visible, base::CompareCase::SENSITIVE)) {
+ num_files += 1;
+ }
+ }
+
+ return num_files;
+}
+
+void P2PManagerImpl::ScheduleEnabledStatusChange() {
+ if (waiting_for_enabled_status_change_)
+ return;
+
+ Callback<void(EvalStatus, const bool&)> callback =
+ Bind(&P2PManagerImpl::OnEnabledStatusChange, base::Unretained(this));
+ update_manager_->AsyncPolicyRequest(
+ callback, &Policy::P2PEnabledChanged, is_enabled_);
+ waiting_for_enabled_status_change_ = true;
+}
+
+void P2PManagerImpl::OnEnabledStatusChange(EvalStatus status,
+ const bool& result) {
+ waiting_for_enabled_status_change_ = false;
+
+ if (status == EvalStatus::kSucceeded) {
+ if (result == is_enabled_) {
+ LOG(WARNING) << "P2P enabled status did not change, which means that it "
+ "is permanent; not scheduling further checks.";
+ waiting_for_enabled_status_change_ = true;
+ return;
+ }
+
+ is_enabled_ = result;
+
+ // If P2P is running but shouldn't be, make sure it isn't.
+ if (may_be_running_ && !is_enabled_ && !EnsureP2PNotRunning()) {
+ LOG(WARNING) << "Failed to stop P2P service.";
+ }
+ } else {
+ LOG(WARNING)
+ << "P2P enabled tracking failed (possibly timed out); retrying.";
+ }
+
+ ScheduleEnabledStatusChange();
+}
+
+P2PManager* P2PManager::Construct(Configuration* configuration,
+ ClockInterface* clock,
+ UpdateManager* update_manager,
+ const string& file_extension,
+ const int num_files_to_keep,
+ const TimeDelta& max_file_age) {
+ return new P2PManagerImpl(configuration,
+ clock,
+ update_manager,
+ file_extension,
+ num_files_to_keep,
+ max_file_age);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/p2p_manager.h b/cros/p2p_manager.h
new file mode 100644
index 0000000..bd359fa
--- /dev/null
+++ b/cros/p2p_manager.h
@@ -0,0 +1,186 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_P2P_MANAGER_H_
+#define UPDATE_ENGINE_CROS_P2P_MANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/memory/ref_counted.h>
+#include <base/time/time.h>
+#include <policy/device_policy.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/update_manager/update_manager.h"
+
+namespace chromeos_update_engine {
+
+// Interface for sharing and discovering files via p2p.
+class P2PManager {
+ public:
+ // Interface used for P2PManager implementations. The sole reason
+ // for this interface is unit testing.
+ class Configuration {
+ public:
+ virtual ~Configuration() {}
+
+ // Gets the path to the p2p dir being used, e.g. /var/cache/p2p.
+ virtual base::FilePath GetP2PDir() = 0;
+
+ // Gets the argument vector for starting (if |is_start| is True)
+ // resp. stopping (if |is_start| is False) the p2p service
+ // e.g. ["initctl", "start", "p2p"] or ["initctl", "stop", "p2p"].
+ virtual std::vector<std::string> GetInitctlArgs(bool is_start) = 0;
+
+ // Gets the argument vector for invoking p2p-client, e.g.
+ // "p2p-client --get-url=file_id_we_want --minimum-size=42123".
+ virtual std::vector<std::string> GetP2PClientArgs(
+ const std::string& file_id, size_t minimum_size) = 0;
+ };
+
+ virtual ~P2PManager() {}
+
+ // The type for the callback used in LookupUrlForFile().
+ // If the lookup failed, |url| is empty.
+ typedef base::Callback<void(const std::string& url)> LookupCallback;
+
+ // Use the device policy specified by |device_policy|. If this is
+ // null, then no device policy is used.
+ virtual void SetDevicePolicy(const policy::DevicePolicy* device_policy) = 0;
+
+ // Returns true iff P2P is currently allowed for use on this device. This
+ // value is determined and maintained by the Update Manager.
+ virtual bool IsP2PEnabled() = 0;
+
+ // Ensures that the p2p subsystem is running (e.g. starts it if it's
+ // not already running) and blocks until this is so. Returns false
+ // if an error occurred.
+ virtual bool EnsureP2PRunning() = 0;
+
+ // Ensures that the p2p subsystem is not running (e.g. stops it if
+ // it's running) and blocks until this is so. Returns false if an
+ // error occurred.
+ virtual bool EnsureP2PNotRunning() = 0;
+
+ // Cleans up files in /var/cache/p2p owned by this application as
+ // per the |file_extension| and |num_files_to_keep| values passed
+ // when the object was constructed. This may be called even if
+ // the p2p subsystem is not running.
+ virtual bool PerformHousekeeping() = 0;
+
+ // Asynchronously finds a peer that serves the file identified by
+ // |file_id|. If |minimum_size| is non-zero, will find a peer that
+ // has at least that many bytes. When the result is ready |callback|
+ // is called from the current message loop.
+ //
+ // This operation may take a very long time to complete because part
+ // of the p2p protocol involves waiting for the LAN-wide sum of all
+ // num-connections to drop below a given threshold. However, if
+ // |max_time_to_wait| is non-zero, the operation is guaranteed to
+ // not exceed this duration.
+ //
+ // If the file is not available on the LAN (or if mDNS/DNS-SD is
+ // filtered), this is guaranteed to not take longer than 5 seconds.
+ virtual void LookupUrlForFile(const std::string& file_id,
+ size_t minimum_size,
+ base::TimeDelta max_time_to_wait,
+ LookupCallback callback) = 0;
+
+ // Shares a file identified by |file_id| in the directory
+ // /var/cache/p2p. Initially the file will not be visible, that is,
+ // it will have a .tmp extension and not be shared via p2p. Use the
+ // FileMakeVisible() method to change this.
+ //
+ // If you know the final size of the file, pass it in the
+ // |expected_size| parameter. Otherwise pass zero. If non-zero, the
+ // amount of free space in /var/cache/p2p is checked and if there is
+ // less than twice the amount of space available, this method
+ // fails. Additionally, disk space will be reserved via fallocate(2)
+ // and |expected_size| is written to the user.cros-p2p-filesize
+ // xattr of the created file.
+ //
+ // If the file already exists, true is returned. Any on-disk xattr
+ // is not updated.
+ virtual bool FileShare(const std::string& file_id, size_t expected_size) = 0;
+
+ // Gets a fully qualified path for the file identified by |file_id|.
+ // If the file has not been shared already using the FileShare()
+ // method, an empty base::FilePath is returned - use FilePath::empty() to
+ // find out.
+ virtual base::FilePath FileGetPath(const std::string& file_id) = 0;
+
+ // Gets the actual size of the file identified by |file_id|. This is
+ // equivalent to reading the value of the st_size field of the
+ // struct stat on the file given by FileGetPath(). Returns -1 if an
+ // error occurs.
+ //
+ // For a file just created with FileShare() this will return 0.
+ virtual ssize_t FileGetSize(const std::string& file_id) = 0;
+
+ // Gets the expected size of the file identified by |file_id|. This
+ // is equivalent to reading the value of the user.cros-p2p-filesize
+ // xattr on the file given by FileGetPath(). Returns -1 if an error
+ // occurs.
+ //
+ // For a file just created with FileShare() this will return the
+ // value of the |expected_size| parameter passed to that method.
+ virtual ssize_t FileGetExpectedSize(const std::string& file_id) = 0;
+
+ // Gets whether the file identified by |file_id| is publicly
+ // visible. If |out_result| is not null, the result is returned
+ // there. Returns false if an error occurs.
+ virtual bool FileGetVisible(const std::string& file_id, bool* out_result) = 0;
+
+ // Makes the file identified by |file_id| publicly visible
+ // (e.g. removes the .tmp extension). If the file is already
+ // visible, this method does nothing. Returns False if
+ // the method fails or there is no file for |file_id|.
+ virtual bool FileMakeVisible(const std::string& file_id) = 0;
+
+ // Counts the number of shared files used by this application
+ // (cf. the |file_extension parameter|. Returns -1 if an error
+ // occurred.
+ virtual int CountSharedFiles() = 0;
+
+ // Creates a suitable P2PManager instance and initializes the object
+ // so it's ready for use. The |file_extension| parameter is used to
+ // identify your application, use e.g. "cros_au". If
+ // |configuration| is non-null, the P2PManager will take ownership
+ // of the Configuration object and use it (hence, it must be
+ // heap-allocated).
+ //
+ // The |num_files_to_keep| parameter specifies how many files to
+ // keep after performing housekeeping (cf. the PerformHousekeeping()
+ // method) - pass zero to allow infinitely many files. The
+ // |max_file_age| parameter specifies the maximum file age after
+ // performing housekeeping (pass zero to allow files of any age).
+ static P2PManager* Construct(
+ Configuration* configuration,
+ ClockInterface* clock,
+ chromeos_update_manager::UpdateManager* update_manager,
+ const std::string& file_extension,
+ const int num_files_to_keep,
+ const base::TimeDelta& max_file_age);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_P2P_MANAGER_H_
diff --git a/cros/p2p_manager_unittest.cc b/cros/p2p_manager_unittest.cc
new file mode 100644
index 0000000..8b6d741
--- /dev/null
+++ b/cros/p2p_manager_unittest.cc
@@ -0,0 +1,538 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/p2p_manager.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/files/file_util.h>
+#if BASE_VER < 780000 // Android
+#include <base/message_loop/message_loop.h>
+#endif // BASE_VER < 780000
+#include <base/strings/stringprintf.h>
+#if BASE_VER >= 780000 // CrOS
+#include <base/task/single_thread_task_executor.h>
+#endif // BASE_VER >= 780000
+#include <brillo/asynchronous_signal_handler.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/fake_p2p_manager_configuration.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+#include "update_engine/update_manager/mock_policy.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+
+namespace chromeos_update_engine {
+
+// Test fixture that sets up a testing configuration (with e.g. a
+// temporary p2p dir) for P2PManager and cleans up when the test is
+// done.
+class P2PManagerTest : public testing::Test {
+ protected:
+ P2PManagerTest() : fake_um_(&fake_clock_) {}
+ ~P2PManagerTest() override {}
+
+ // Derived from testing::Test.
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ async_signal_handler_.Init();
+ subprocess_.Init(&async_signal_handler_);
+ test_conf_ = new FakeP2PManagerConfiguration();
+
+ // Allocate and install a mock policy implementation in the fake Update
+ // Manager. Note that the FakeUpdateManager takes ownership of the policy
+ // object.
+ mock_policy_ = new chromeos_update_manager::MockPolicy(&fake_clock_);
+ fake_um_.set_policy(mock_policy_);
+
+ // Construct the P2P manager under test.
+ manager_.reset(P2PManager::Construct(test_conf_,
+ &fake_clock_,
+ &fake_um_,
+ "cros_au",
+ 3,
+ TimeDelta::FromDays(5)));
+ }
+
+#if BASE_VER < 780000 // Android
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+#else // CrOS
+ base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO};
+ brillo::BaseMessageLoop loop_{base_loop_.task_runner()};
+#endif // BASE_VER < 780000
+ brillo::AsynchronousSignalHandler async_signal_handler_;
+ Subprocess subprocess_;
+
+ // The P2PManager::Configuration instance used for testing.
+ FakeP2PManagerConfiguration* test_conf_;
+
+ FakeClock fake_clock_;
+ chromeos_update_manager::MockPolicy* mock_policy_ = nullptr;
+ chromeos_update_manager::FakeUpdateManager fake_um_;
+
+ unique_ptr<P2PManager> manager_;
+};
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value not
+// changing between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndNotChanged) {
+ EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+ brillo::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value changing
+// between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndChanged) {
+ EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<3>(true),
+ Return(chromeos_update_manager::EvalStatus::kSucceeded)));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, true));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+ EXPECT_TRUE(manager_->IsP2PEnabled());
+ brillo::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that we keep the $N newest files with the .$EXT.p2p extension.
+TEST_F(P2PManagerTest, HousekeepingCountLimit) {
+ // Specifically pass 0 for |max_file_age| to allow files of any age. Note that
+ // we need to reallocate the test_conf_ member, whose currently aliased object
+ // will be freed.
+ test_conf_ = new FakeP2PManagerConfiguration();
+ manager_.reset(P2PManager::Construct(test_conf_,
+ &fake_clock_,
+ &fake_um_,
+ "cros_au",
+ 3,
+ TimeDelta() /* max_file_age */));
+ EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+ base::Time start_time = base::Time::FromDoubleT(1246996800.);
+ // Generate files with different timestamps matching our pattern and generate
+ // other files not matching the pattern.
+ for (int n = 0; n < 5; n++) {
+ base::FilePath path = test_conf_->GetP2PDir().Append(
+ base::StringPrintf("file_%d.cros_au.p2p", n));
+ base::Time file_time = start_time + TimeDelta::FromMinutes(n);
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+
+ path = test_conf_->GetP2PDir().Append(
+ base::StringPrintf("file_%d.OTHER.p2p", n));
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+ EXPECT_TRUE(manager_->PerformHousekeeping());
+
+ // At this point - after HouseKeeping - we should only have
+ // eight files left.
+ for (int n = 0; n < 5; n++) {
+ string file_name;
+ bool expect;
+
+ expect = (n >= 2);
+ file_name = base::StringPrintf(
+ "%s/file_%d.cros_au.p2p", test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+ file_name = base::StringPrintf(
+ "%s/file_%d.OTHER.p2p", test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+// Check that we keep files with the .$EXT.p2p extension not older
+// than some specific age (5 days, in this test).
+TEST_F(P2PManagerTest, HousekeepingAgeLimit) {
+ // We set the cutoff time to be 1 billion seconds (01:46:40 UTC on 9
+ // September 2001 - arbitrary number, but constant to avoid test
+ // flakiness) since the epoch and then we put two files before that
+ // date and three files after.
+ base::Time cutoff_time = base::Time::FromTimeT(1000000000);
+ TimeDelta age_limit = TimeDelta::FromDays(5);
+
+ // Set the clock just so files with a timestamp before |cutoff_time|
+ // will be deleted at housekeeping.
+ fake_clock_.SetWallclockTime(cutoff_time + age_limit);
+
+ // Specifically pass 0 for |num_files_to_keep| to allow any number of files.
+ // Note that we need to reallocate the test_conf_ member, whose currently
+ // aliased object will be freed.
+ test_conf_ = new FakeP2PManagerConfiguration();
+ manager_.reset(P2PManager::Construct(test_conf_,
+ &fake_clock_,
+ &fake_um_,
+ "cros_au",
+ 0 /* num_files_to_keep */,
+ age_limit));
+ EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+ // Generate files with different timestamps matching our pattern and generate
+ // other files not matching the pattern.
+ for (int n = 0; n < 5; n++) {
+ base::FilePath path = test_conf_->GetP2PDir().Append(
+ base::StringPrintf("file_%d.cros_au.p2p", n));
+
+ // With five files and aiming for two of them to be before
+ // |cutoff_time|, we distribute it like this:
+ //
+ // -------- 0 -------- 1 -------- 2 -------- 3 -------- 4 --------
+ // |
+ // cutoff_time
+ //
+ base::Time file_date = cutoff_time + (n - 2) * TimeDelta::FromDays(1) +
+ TimeDelta::FromHours(12);
+
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+
+ path = test_conf_->GetP2PDir().Append(
+ base::StringPrintf("file_%d.OTHER.p2p", n));
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+ EXPECT_TRUE(manager_->PerformHousekeeping());
+
+ // At this point - after HouseKeeping - we should only have
+ // eight files left.
+ for (int n = 0; n < 5; n++) {
+ string file_name;
+ bool expect;
+
+ expect = (n >= 2);
+ file_name = base::StringPrintf(
+ "%s/file_%d.cros_au.p2p", test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+ file_name = base::StringPrintf(
+ "%s/file_%d.OTHER.p2p", test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+static bool CheckP2PFile(const string& p2p_dir,
+ const string& file_name,
+ ssize_t expected_size,
+ ssize_t expected_size_xattr) {
+ string path = p2p_dir + "/" + file_name;
+ char ea_value[64] = {0};
+ ssize_t ea_size;
+
+ off_t p2p_size = utils::FileSize(path);
+ if (p2p_size < 0) {
+ LOG(ERROR) << "File " << path << " does not exist";
+ return false;
+ }
+
+ if (expected_size != 0) {
+ if (p2p_size != expected_size) {
+ LOG(ERROR) << "Expected size " << expected_size << " but size was "
+ << p2p_size;
+ return false;
+ }
+ }
+
+ if (expected_size_xattr == 0) {
+ ea_size = getxattr(
+ path.c_str(), "user.cros-p2p-filesize", &ea_value, sizeof ea_value - 1);
+ if (ea_size == -1 && errno == ENODATA) {
+ // This is valid behavior as we support files without the xattr set.
+ } else {
+ PLOG(ERROR) << "getxattr() didn't fail with ENODATA as expected, "
+ << "ea_size=" << ea_size << ", errno=" << errno;
+ return false;
+ }
+ } else {
+ ea_size = getxattr(
+ path.c_str(), "user.cros-p2p-filesize", &ea_value, sizeof ea_value - 1);
+ if (ea_size < 0) {
+ LOG(ERROR) << "Error getting xattr attribute";
+ return false;
+ }
+ char* endp = nullptr;
+ long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int)
+ if (endp == nullptr || *endp != '\0') {
+ LOG(ERROR) << "Error parsing xattr '" << ea_value << "' as an integer";
+ return false;
+ }
+ if (val != expected_size_xattr) {
+ LOG(ERROR) << "Expected xattr size " << expected_size_xattr
+ << " but size was " << val;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool CreateP2PFile(string p2p_dir,
+ string file_name,
+ size_t size,
+ size_t size_xattr) {
+ string path = p2p_dir + "/" + file_name;
+
+ int fd = open(path.c_str(), O_CREAT | O_RDWR, 0644);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error creating file with path " << path;
+ return false;
+ }
+ if (ftruncate(fd, size) != 0) {
+ PLOG(ERROR) << "Error truncating " << path << " to size " << size;
+ close(fd);
+ return false;
+ }
+
+ if (size_xattr != 0) {
+ string decimal_size = std::to_string(size_xattr);
+ if (fsetxattr(fd,
+ "user.cros-p2p-filesize",
+ decimal_size.c_str(),
+ decimal_size.size(),
+ 0) != 0) {
+ PLOG(ERROR) << "Error setting xattr on " << path;
+ close(fd);
+ return false;
+ }
+ }
+
+ close(fd);
+ return true;
+}
+
+// Check that sharing a *new* file works.
+TEST_F(P2PManagerTest, ShareFile) {
+ const int kP2PTestFileSize = 1000 * 1000; // 1 MB
+
+ EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p.tmp",
+ 0,
+ kP2PTestFileSize));
+
+ // Sharing it again - with the same expected size - should return true
+ EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+
+ // ... but if we use the wrong size, it should fail
+ EXPECT_FALSE(manager_->FileShare("foo", kP2PTestFileSize + 1));
+}
+
+// Check that making a shared file visible, does what is expected.
+TEST_F(P2PManagerTest, MakeFileVisible) {
+ const int kP2PTestFileSize = 1000 * 1000; // 1 MB
+
+ // First, check that it's not visible.
+ manager_->FileShare("foo", kP2PTestFileSize);
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p.tmp",
+ 0,
+ kP2PTestFileSize));
+ // Make the file visible and check that it changed its name. Do it
+ // twice to check that FileMakeVisible() is idempotent.
+ for (int n = 0; n < 2; n++) {
+ manager_->FileMakeVisible("foo");
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p",
+ 0,
+ kP2PTestFileSize));
+ }
+}
+
+// Check that we return the right values for existing files in P2P_DIR.
+TEST_F(P2PManagerTest, ExistingFiles) {
+ bool visible;
+
+ // Check that errors are returned if the file does not exist
+ EXPECT_EQ(manager_->FileGetPath("foo"), base::FilePath());
+ EXPECT_EQ(manager_->FileGetSize("foo"), -1);
+ EXPECT_EQ(manager_->FileGetExpectedSize("foo"), -1);
+ EXPECT_FALSE(manager_->FileGetVisible("foo", nullptr));
+ // ... then create the file ...
+ EXPECT_TRUE(CreateP2PFile(
+ test_conf_->GetP2PDir().value(), "foo.cros_au.p2p", 42, 43));
+ // ... and then check that the expected values are returned
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+ EXPECT_EQ(manager_->FileGetSize("foo"), 42);
+ EXPECT_EQ(manager_->FileGetExpectedSize("foo"), 43);
+ EXPECT_TRUE(manager_->FileGetVisible("foo", &visible));
+ EXPECT_TRUE(visible);
+
+ // One more time, this time with a .tmp variant. First ensure it errors out..
+ EXPECT_EQ(manager_->FileGetPath("bar"), base::FilePath());
+ EXPECT_EQ(manager_->FileGetSize("bar"), -1);
+ EXPECT_EQ(manager_->FileGetExpectedSize("bar"), -1);
+ EXPECT_FALSE(manager_->FileGetVisible("bar", nullptr));
+ // ... then create the file ...
+ EXPECT_TRUE(CreateP2PFile(
+ test_conf_->GetP2PDir().value(), "bar.cros_au.p2p.tmp", 44, 45));
+ // ... and then check that the expected values are returned
+ EXPECT_EQ(manager_->FileGetPath("bar"),
+ test_conf_->GetP2PDir().Append("bar.cros_au.p2p.tmp"));
+ EXPECT_EQ(manager_->FileGetSize("bar"), 44);
+ EXPECT_EQ(manager_->FileGetExpectedSize("bar"), 45);
+ EXPECT_TRUE(manager_->FileGetVisible("bar", &visible));
+ EXPECT_FALSE(visible);
+}
+
+// This is a little bit ugly but short of mocking a 'p2p' service this
+// will have to do. E.g. we essentially simulate the various
+// behaviours of initctl(8) that we rely on.
+TEST_F(P2PManagerTest, StartP2P) {
+ // Check that we can start the service
+ test_conf_->SetInitctlStartCommand({"true"});
+ EXPECT_TRUE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand({"false"});
+ EXPECT_FALSE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand(
+ {"sh", "-c", "echo \"initctl: Job is already running: p2p\" >&2; false"});
+ EXPECT_TRUE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand(
+ {"sh", "-c", "echo something else >&2; false"});
+ EXPECT_FALSE(manager_->EnsureP2PRunning());
+}
+
+// Same comment as for StartP2P
+TEST_F(P2PManagerTest, StopP2P) {
+ // Check that we can start the service
+ test_conf_->SetInitctlStopCommand({"true"});
+ EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand({"false"});
+ EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand(
+ {"sh", "-c", "echo \"initctl: Unknown instance \" >&2; false"});
+ EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand(
+ {"sh", "-c", "echo something else >&2; false"});
+ EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+}
+
+static void ExpectUrl(const string& expected_url, const string& url) {
+ EXPECT_EQ(url, expected_url);
+ MessageLoop::current()->BreakLoop();
+}
+
+// Like StartP2P, we're mocking the different results that p2p-client
+// can return. It's not pretty but it works.
+TEST_F(P2PManagerTest, LookupURL) {
+ // Emulate p2p-client returning valid URL with "fooX", 42 and "cros_au"
+ // being propagated in the right places.
+ test_conf_->SetP2PClientCommand(
+ {"echo", "http://1.2.3.4/{file_id}_{minsize}"});
+ manager_->LookupUrlForFile(
+ "fooX",
+ 42,
+ TimeDelta(),
+ base::Bind(ExpectUrl, "http://1.2.3.4/fooX.cros_au_42"));
+ loop_.Run();
+
+ // Emulate p2p-client returning invalid URL.
+ test_conf_->SetP2PClientCommand({"echo", "not_a_valid_url"});
+ manager_->LookupUrlForFile(
+ "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client conveying failure.
+ test_conf_->SetP2PClientCommand({"false"});
+ manager_->LookupUrlForFile(
+ "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client not existing.
+ test_conf_->SetP2PClientCommand({"/path/to/non/existent/helper/program"});
+ manager_->LookupUrlForFile(
+ "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client crashing.
+ test_conf_->SetP2PClientCommand({"sh", "-c", "kill -SEGV $$"});
+ manager_->LookupUrlForFile(
+ "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client exceeding its timeout.
+ test_conf_->SetP2PClientCommand(
+ {"sh",
+ "-c",
+ // The 'sleep' launched below could be left behind as an orphaned
+ // process when the 'sh' process is terminated by SIGTERM. As a
+ // remedy, trap SIGTERM and kill the 'sleep' process, which requires
+ // launching 'sleep' in background and then waiting for it.
+ "cleanup() { kill \"${sleep_pid}\"; exit 0; }; "
+ "trap cleanup TERM; "
+ "sleep 5 & "
+ "sleep_pid=$!; "
+ "echo http://1.2.3.4/; "
+ "wait"});
+ manager_->LookupUrlForFile("foobar",
+ 42,
+ TimeDelta::FromMilliseconds(500),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/payload_state.cc b/cros/payload_state.cc
new file mode 100644
index 0000000..d2e6851
--- /dev/null
+++ b/cros/payload_state.cc
@@ -0,0 +1,1465 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/payload_state.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <metrics/metrics_library.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/clock.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/error_code_utils.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/metrics_reporter_interface.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/connection_manager_interface.h"
+#include "update_engine/cros/omaha_request_params.h"
+#include "update_engine/cros/update_attempter.h"
+#include "update_engine/metrics_utils.h"
+#include "update_engine/payload_consumer/install_plan.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::min;
+using std::string;
+
+namespace chromeos_update_engine {
+
+using metrics_utils::GetPersistedValue;
+
+const TimeDelta PayloadState::kDurationSlack = TimeDelta::FromSeconds(600);
+
+// We want to upperbound backoffs to 16 days
+static const int kMaxBackoffDays = 16;
+
+// We want to randomize retry attempts after the backoff by +/- 6 hours.
+static const uint32_t kMaxBackoffFuzzMinutes = 12 * 60;
+
+// Limit persisting current update duration uptime to once per second
+static const uint64_t kUptimeResolution = 1;
+
+PayloadState::PayloadState()
+ : prefs_(nullptr),
+ powerwash_safe_prefs_(nullptr),
+ excluder_(nullptr),
+ using_p2p_for_downloading_(false),
+ p2p_num_attempts_(0),
+ payload_attempt_number_(0),
+ full_payload_attempt_number_(0),
+ url_index_(0),
+ url_failure_count_(0),
+ url_switch_count_(0),
+ rollback_happened_(false),
+ attempt_num_bytes_downloaded_(0),
+ attempt_connection_type_(metrics::ConnectionType::kUnknown),
+ attempt_type_(AttemptType::kUpdate) {
+ for (int i = 0; i <= kNumDownloadSources; i++)
+ total_bytes_downloaded_[i] = current_bytes_downloaded_[i] = 0;
+}
+
+bool PayloadState::Initialize(SystemState* system_state) {
+ system_state_ = system_state;
+ prefs_ = system_state_->prefs();
+ powerwash_safe_prefs_ = system_state_->powerwash_safe_prefs();
+ excluder_ = system_state_->update_attempter()->GetExcluder();
+ LoadResponseSignature();
+ LoadPayloadAttemptNumber();
+ LoadFullPayloadAttemptNumber();
+ LoadUrlIndex();
+ LoadUrlFailureCount();
+ LoadUrlSwitchCount();
+ LoadBackoffExpiryTime();
+ LoadUpdateTimestampStart();
+ // The LoadUpdateDurationUptime() method relies on LoadUpdateTimestampStart()
+ // being called before it. Don't reorder.
+ LoadUpdateDurationUptime();
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ LoadCurrentBytesDownloaded(source);
+ LoadTotalBytesDownloaded(source);
+ }
+ LoadNumReboots();
+ LoadNumResponsesSeen();
+ LoadRollbackHappened();
+ LoadRollbackVersion();
+ LoadP2PFirstAttemptTimestamp();
+ LoadP2PNumAttempts();
+ return true;
+}
+
+void PayloadState::SetResponse(const OmahaResponse& omaha_response) {
+ // Always store the latest response.
+ response_ = omaha_response;
+
+ // Compute the candidate URLs first as they are used to calculate the
+ // response signature so that a change in enterprise policy for
+ // HTTP downloads being enabled or not could be honored as soon as the
+ // next update check happens.
+ ComputeCandidateUrls();
+
+ // Check if the "signature" of this response (i.e. the fields we care about)
+ // has changed.
+ string new_response_signature = CalculateResponseSignature();
+ bool has_response_changed = (response_signature_ != new_response_signature);
+
+ // If the response has changed, we should persist the new signature and
+ // clear away all the existing state.
+ if (has_response_changed) {
+ LOG(INFO) << "Resetting all persisted state as this is a new response";
+ SetNumResponsesSeen(num_responses_seen_ + 1);
+ SetResponseSignature(new_response_signature);
+ ResetPersistedState();
+ return;
+ }
+
+ // Always start from payload index 0, even for resume, to download partition
+ // info from previous payloads.
+ payload_index_ = 0;
+
+ // This is the earliest point at which we can validate whether the URL index
+ // we loaded from the persisted state is a valid value. If the response
+ // hasn't changed but the URL index is invalid, it's indicative of some
+ // tampering of the persisted state.
+ if (payload_index_ >= candidate_urls_.size() ||
+ url_index_ >= candidate_urls_[payload_index_].size()) {
+ LOG(INFO) << "Resetting all payload state as the url index seems to have "
+ "been tampered with";
+ ResetPersistedState();
+ return;
+ }
+
+ // Update the current download source which depends on the latest value of
+ // the response.
+ UpdateCurrentDownloadSource();
+}
+
+void PayloadState::SetUsingP2PForDownloading(bool value) {
+ using_p2p_for_downloading_ = value;
+ // Update the current download source which depends on whether we are
+ // using p2p or not.
+ UpdateCurrentDownloadSource();
+}
+
+void PayloadState::DownloadComplete() {
+ LOG(INFO) << "Payload downloaded successfully";
+ IncrementPayloadAttemptNumber();
+ IncrementFullPayloadAttemptNumber();
+}
+
+void PayloadState::DownloadProgress(size_t count) {
+ if (count == 0)
+ return;
+
+ CalculateUpdateDurationUptime();
+ UpdateBytesDownloaded(count);
+
+ // We've received non-zero bytes from a recent download operation. Since our
+ // URL failure count is meant to penalize a URL only for consecutive
+ // failures, downloading bytes successfully means we should reset the failure
+ // count (as we know at least that the URL is working). In future, we can
+ // design this to be more sophisticated to check for more intelligent failure
+ // patterns, but right now, even 1 byte downloaded will mark the URL to be
+ // good unless it hits 10 (or configured number of) consecutive failures
+ // again.
+
+ if (GetUrlFailureCount() == 0)
+ return;
+
+ LOG(INFO) << "Resetting failure count of Url" << GetUrlIndex()
+ << " to 0 as we received " << count << " bytes successfully";
+ SetUrlFailureCount(0);
+}
+
+void PayloadState::AttemptStarted(AttemptType attempt_type) {
+ // Flush previous state from abnormal attempt failure, if any.
+ ReportAndClearPersistedAttemptMetrics();
+
+ attempt_type_ = attempt_type;
+
+ ClockInterface* clock = system_state_->clock();
+ attempt_start_time_boot_ = clock->GetBootTime();
+ attempt_start_time_monotonic_ = clock->GetMonotonicTime();
+ attempt_num_bytes_downloaded_ = 0;
+
+ metrics::ConnectionType type;
+ ConnectionType network_connection_type;
+ ConnectionTethering tethering;
+ ConnectionManagerInterface* connection_manager =
+ system_state_->connection_manager();
+ if (!connection_manager->GetConnectionProperties(&network_connection_type,
+ &tethering)) {
+ LOG(ERROR) << "Failed to determine connection type.";
+ type = metrics::ConnectionType::kUnknown;
+ } else {
+ type = metrics_utils::GetConnectionType(network_connection_type, tethering);
+ }
+ attempt_connection_type_ = type;
+
+ if (attempt_type == AttemptType::kUpdate)
+ PersistAttemptMetrics();
+}
+
+void PayloadState::UpdateResumed() {
+ LOG(INFO) << "Resuming an update that was previously started.";
+ UpdateNumReboots();
+ AttemptStarted(AttemptType::kUpdate);
+}
+
+void PayloadState::UpdateRestarted() {
+ LOG(INFO) << "Starting a new update";
+ ResetDownloadSourcesOnNewUpdate();
+ SetNumReboots(0);
+ AttemptStarted(AttemptType::kUpdate);
+}
+
+void PayloadState::UpdateSucceeded() {
+ // Send the relevant metrics that are tracked in this class to UMA.
+ CalculateUpdateDurationUptime();
+ SetUpdateTimestampEnd(system_state_->clock()->GetWallclockTime());
+
+ switch (attempt_type_) {
+ case AttemptType::kUpdate:
+ CollectAndReportAttemptMetrics(ErrorCode::kSuccess);
+ CollectAndReportSuccessfulUpdateMetrics();
+ ClearPersistedAttemptMetrics();
+ break;
+
+ case AttemptType::kRollback:
+ system_state_->metrics_reporter()->ReportRollbackMetrics(
+ metrics::RollbackResult::kSuccess);
+ break;
+ }
+
+ // Reset the number of responses seen since it counts from the last
+ // successful update, e.g. now.
+ SetNumResponsesSeen(0);
+ SetPayloadIndex(0);
+
+ metrics_utils::SetSystemUpdatedMarker(system_state_->clock(), prefs_);
+}
+
+void PayloadState::UpdateFailed(ErrorCode error) {
+ ErrorCode base_error = utils::GetBaseErrorCode(error);
+ LOG(INFO) << "Updating payload state for error code: " << base_error << " ("
+ << utils::ErrorCodeToString(base_error) << ")";
+
+ if (candidate_urls_.size() == 0) {
+ // This means we got this error even before we got a valid Omaha response
+ // or don't have any valid candidates in the Omaha response.
+ // So we should not advance the url_index_ in such cases.
+ LOG(INFO) << "Ignoring failures until we get a valid Omaha response.";
+ return;
+ }
+
+ switch (attempt_type_) {
+ case AttemptType::kUpdate:
+ CollectAndReportAttemptMetrics(base_error);
+ ClearPersistedAttemptMetrics();
+ break;
+
+ case AttemptType::kRollback:
+ system_state_->metrics_reporter()->ReportRollbackMetrics(
+ metrics::RollbackResult::kFailed);
+ break;
+ }
+
+ switch (base_error) {
+ // Errors which are good indicators of a problem with a particular URL or
+ // the protocol used in the URL or entities in the communication channel
+ // (e.g. proxies). We should try the next available URL in the next update
+ // check to quickly recover from these errors.
+ case ErrorCode::kPayloadHashMismatchError:
+ case ErrorCode::kPayloadSizeMismatchError:
+ case ErrorCode::kDownloadPayloadVerificationError:
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ case ErrorCode::kDownloadManifestParseError:
+ case ErrorCode::kDownloadMetadataSignatureError:
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ case ErrorCode::kDownloadOperationExecutionError:
+ case ErrorCode::kDownloadOperationHashMismatch:
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ case ErrorCode::kDownloadOperationHashMissingError:
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ case ErrorCode::kPayloadMismatchedType:
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ case ErrorCode::kPayloadTimestampError:
+ case ErrorCode::kVerityCalculationError:
+ ExcludeCurrentPayload();
+ IncrementUrlIndex();
+ break;
+
+ // Errors which seem to be just transient network/communication related
+ // failures and do not indicate any inherent problem with the URL itself.
+ // So, we should keep the current URL but just increment the
+ // failure count to give it more chances. This way, while we maximize our
+ // chances of downloading from the URLs that appear earlier in the
+ // response (because download from a local server URL that appears earlier
+ // in a response is preferable than downloading from the next URL which
+ // could be a internet URL and thus could be more expensive).
+
+ case ErrorCode::kError:
+ case ErrorCode::kDownloadTransferError:
+ case ErrorCode::kDownloadWriteError:
+ case ErrorCode::kDownloadStateInitializationError:
+ case ErrorCode::kOmahaErrorInHTTPResponse: // Aggregate for HTTP errors.
+ IncrementFailureCount();
+ break;
+
+ // Errors which are not specific to a URL and hence shouldn't result in
+ // the URL being penalized. This can happen in two cases:
+ // 1. We haven't started downloading anything: These errors don't cost us
+ // anything in terms of actual payload bytes, so we should just do the
+ // regular retries at the next update check.
+ // 2. We have successfully downloaded the payload: In this case, the
+ // payload attempt number would have been incremented and would take care
+ // of the backoff at the next update check.
+ // In either case, there's no need to update URL index or failure count.
+ case ErrorCode::kOmahaRequestError:
+ case ErrorCode::kOmahaResponseHandlerError:
+ case ErrorCode::kPostinstallRunnerError:
+ case ErrorCode::kFilesystemCopierError:
+ case ErrorCode::kInstallDeviceOpenError:
+ case ErrorCode::kKernelDeviceOpenError:
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ case ErrorCode::kNewRootfsVerificationError:
+ case ErrorCode::kNewKernelVerificationError:
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ case ErrorCode::kOmahaRequestXMLParseError:
+ case ErrorCode::kOmahaResponseInvalid:
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kNonCriticalUpdateInOOBE:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ case ErrorCode::kPostinstallPowerwashError:
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ case ErrorCode::kFilesystemVerifierError:
+ case ErrorCode::kUserCanceled:
+ case ErrorCode::kOmahaUpdateIgnoredOverCellular:
+ case ErrorCode::kUpdatedButNotActive:
+ case ErrorCode::kNoUpdate:
+ case ErrorCode::kRollbackNotPossible:
+ case ErrorCode::kFirstActiveOmahaPingSentPersistenceError:
+ case ErrorCode::kInternalLibCurlError:
+ case ErrorCode::kUnresolvedHostError:
+ case ErrorCode::kUnresolvedHostRecovered:
+ case ErrorCode::kNotEnoughSpace:
+ case ErrorCode::kDeviceCorrupted:
+ case ErrorCode::kPackageExcludedFromUpdate:
+ LOG(INFO) << "Not incrementing URL index or failure count for this error";
+ break;
+
+ case ErrorCode::kSuccess: // success code
+ case ErrorCode::kUmaReportedMax: // not an error code
+ case ErrorCode::kOmahaRequestHTTPResponseBase: // aggregated already
+ case ErrorCode::kDevModeFlag: // not an error code
+ case ErrorCode::kResumedFlag: // not an error code
+ case ErrorCode::kTestImageFlag: // not an error code
+ case ErrorCode::kTestOmahaUrlFlag: // not an error code
+ case ErrorCode::kSpecialFlags: // not an error code
+ // These shouldn't happen. Enumerating these explicitly here so that we
+ // can let the compiler warn about new error codes that are added to
+ // action_processor.h but not added here.
+ LOG(WARNING) << "Unexpected error code for UpdateFailed";
+ break;
+
+ // Note: Not adding a default here so as to let the compiler warn us of
+ // any new enums that were added in the .h but not listed in this switch.
+ }
+}
+
+bool PayloadState::ShouldBackoffDownload() {
+ if (response_.disable_payload_backoff) {
+ LOG(INFO) << "Payload backoff logic is disabled. "
+ "Can proceed with the download";
+ return false;
+ }
+ if (GetUsingP2PForDownloading() && !GetP2PUrl().empty()) {
+ LOG(INFO) << "Payload backoff logic is disabled because download "
+ << "will happen from local peer (via p2p).";
+ return false;
+ }
+ if (system_state_->request_params()->interactive()) {
+ LOG(INFO) << "Payload backoff disabled for interactive update checks.";
+ return false;
+ }
+ for (const auto& package : response_.packages) {
+ if (package.is_delta) {
+ // If delta payloads fail, we want to fallback quickly to full payloads as
+ // they are more likely to succeed. Exponential backoffs would greatly
+ // slow down the fallback to full payloads. So we don't backoff for delta
+ // payloads.
+ LOG(INFO) << "No backoffs for delta payloads. "
+ << "Can proceed with the download";
+ return false;
+ }
+ }
+
+ if (!system_state_->hardware()->IsOfficialBuild() &&
+ !prefs_->Exists(kPrefsNoIgnoreBackoff)) {
+ // Backoffs are needed only for official builds. We do not want any delays
+ // or update failures due to backoffs during testing or development. Unless
+ // the |kPrefsNoIgnoreBackoff| is manually set.
+ LOG(INFO) << "No backoffs for test/dev images. "
+ << "Can proceed with the download";
+ return false;
+ }
+
+ if (backoff_expiry_time_.is_null()) {
+ LOG(INFO) << "No backoff expiry time has been set. "
+ << "Can proceed with the download";
+ return false;
+ }
+
+ if (backoff_expiry_time_ < Time::Now()) {
+ LOG(INFO) << "The backoff expiry time ("
+ << utils::ToString(backoff_expiry_time_)
+ << ") has elapsed. Can proceed with the download";
+ return false;
+ }
+
+ LOG(INFO) << "Cannot proceed with downloads as we need to backoff until "
+ << utils::ToString(backoff_expiry_time_);
+ return true;
+}
+
+void PayloadState::Rollback() {
+ SetRollbackVersion(system_state_->request_params()->app_version());
+ AttemptStarted(AttemptType::kRollback);
+}
+
+void PayloadState::IncrementPayloadAttemptNumber() {
+ // Update the payload attempt number for both payload types: full and delta.
+ SetPayloadAttemptNumber(GetPayloadAttemptNumber() + 1);
+}
+
+void PayloadState::IncrementFullPayloadAttemptNumber() {
+ DCHECK(payload_index_ < response_.packages.size());
+ // Update the payload attempt number for full payloads and the backoff time.
+ if (response_.packages[payload_index_].is_delta) {
+ LOG(INFO) << "Not incrementing payload attempt number for delta payloads";
+ return;
+ }
+
+ LOG(INFO) << "Incrementing the full payload attempt number";
+ SetFullPayloadAttemptNumber(GetFullPayloadAttemptNumber() + 1);
+ UpdateBackoffExpiryTime();
+}
+
+void PayloadState::IncrementUrlIndex() {
+ DCHECK(payload_index_ < candidate_urls_.size());
+ size_t next_url_index = url_index_ + 1;
+ size_t max_url_size = candidate_urls_[payload_index_].size();
+ if (next_url_index < max_url_size) {
+ LOG(INFO) << "Incrementing the URL index for next attempt";
+ SetUrlIndex(next_url_index);
+ } else {
+ LOG(INFO) << "Resetting the current URL index (" << url_index_ << ") to "
+ << "0 as we only have " << max_url_size << " candidate URL(s)";
+ SetUrlIndex(0);
+ IncrementPayloadAttemptNumber();
+ IncrementFullPayloadAttemptNumber();
+ }
+
+ // If we have multiple URLs, record that we just switched to another one
+ if (max_url_size > 1)
+ SetUrlSwitchCount(url_switch_count_ + 1);
+
+ // Whenever we update the URL index, we should also clear the URL failure
+ // count so we can start over fresh for the new URL.
+ SetUrlFailureCount(0);
+}
+
+void PayloadState::IncrementFailureCount() {
+ uint32_t next_url_failure_count = GetUrlFailureCount() + 1;
+ if (next_url_failure_count < response_.max_failure_count_per_url) {
+ LOG(INFO) << "Incrementing the URL failure count";
+ SetUrlFailureCount(next_url_failure_count);
+ } else {
+ LOG(INFO) << "Reached max number of failures for Url" << GetUrlIndex()
+ << ". Trying next available URL";
+ ExcludeCurrentPayload();
+ IncrementUrlIndex();
+ }
+}
+
+void PayloadState::ExcludeCurrentPayload() {
+ if (payload_index_ >= response_.packages.size()) {
+ LOG(INFO) << "Skipping exclusion of the current payload.";
+ return;
+ }
+ const auto& package = response_.packages[payload_index_];
+ if (!package.can_exclude) {
+ LOG(INFO) << "Not excluding as marked non-excludable for package hash="
+ << package.hash;
+ return;
+ }
+ auto exclusion_name = utils::GetExclusionName(GetCurrentUrl());
+ if (!excluder_->Exclude(exclusion_name))
+ LOG(WARNING) << "Failed to exclude "
+ << " Package Hash=" << package.hash
+ << " CurrentUrl=" << GetCurrentUrl();
+ else
+ LOG(INFO) << "Excluded "
+ << " Package Hash=" << package.hash
+ << " CurrentUrl=" << GetCurrentUrl();
+}
+
+void PayloadState::UpdateBackoffExpiryTime() {
+ if (response_.disable_payload_backoff) {
+ LOG(INFO) << "Resetting backoff expiry time as payload backoff is disabled";
+ SetBackoffExpiryTime(Time());
+ return;
+ }
+
+ if (GetFullPayloadAttemptNumber() == 0) {
+ SetBackoffExpiryTime(Time());
+ return;
+ }
+
+ // Since we're doing left-shift below, make sure we don't shift more
+ // than this. E.g. if int is 4-bytes, don't left-shift more than 30 bits,
+ // since we don't expect value of kMaxBackoffDays to be more than 100 anyway.
+ int num_days = 1; // the value to be shifted.
+ const int kMaxShifts = (sizeof(num_days) * 8) - 2;
+
+ // Normal backoff days is 2 raised to (payload_attempt_number - 1).
+ // E.g. if payload_attempt_number is over 30, limit power to 30.
+ int power = min(GetFullPayloadAttemptNumber() - 1, kMaxShifts);
+
+ // The number of days is the minimum of 2 raised to (payload_attempt_number
+ // - 1) or kMaxBackoffDays.
+ num_days = min(num_days << power, kMaxBackoffDays);
+
+ // We don't want all retries to happen exactly at the same time when
+ // retrying after backoff. So add some random minutes to fuzz.
+ int fuzz_minutes = utils::FuzzInt(0, kMaxBackoffFuzzMinutes);
+ TimeDelta next_backoff_interval =
+ TimeDelta::FromDays(num_days) + TimeDelta::FromMinutes(fuzz_minutes);
+ LOG(INFO) << "Incrementing the backoff expiry time by "
+ << utils::FormatTimeDelta(next_backoff_interval);
+ SetBackoffExpiryTime(Time::Now() + next_backoff_interval);
+}
+
+void PayloadState::UpdateCurrentDownloadSource() {
+ current_download_source_ = kNumDownloadSources;
+
+ if (using_p2p_for_downloading_) {
+ current_download_source_ = kDownloadSourceHttpPeer;
+ } else if (payload_index_ < candidate_urls_.size() &&
+ candidate_urls_[payload_index_].size() != 0) {
+ const string& current_url = candidate_urls_[payload_index_][GetUrlIndex()];
+ if (base::StartsWith(
+ current_url, "https://", base::CompareCase::INSENSITIVE_ASCII)) {
+ current_download_source_ = kDownloadSourceHttpsServer;
+ } else if (base::StartsWith(current_url,
+ "http://",
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ current_download_source_ = kDownloadSourceHttpServer;
+ }
+ }
+
+ LOG(INFO) << "Current download source: "
+ << utils::ToString(current_download_source_);
+}
+
+void PayloadState::UpdateBytesDownloaded(size_t count) {
+ SetCurrentBytesDownloaded(
+ current_download_source_,
+ GetCurrentBytesDownloaded(current_download_source_) + count,
+ false);
+ SetTotalBytesDownloaded(
+ current_download_source_,
+ GetTotalBytesDownloaded(current_download_source_) + count,
+ false);
+
+ attempt_num_bytes_downloaded_ += count;
+}
+
+PayloadType PayloadState::CalculatePayloadType() {
+ for (const auto& package : response_.packages) {
+ if (package.is_delta) {
+ return kPayloadTypeDelta;
+ }
+ }
+ OmahaRequestParams* params = system_state_->request_params();
+ if (params->delta_okay()) {
+ return kPayloadTypeFull;
+ }
+ // Full payload, delta was not allowed by request.
+ return kPayloadTypeForcedFull;
+}
+
+void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) {
+ int attempt_number = GetPayloadAttemptNumber();
+
+ PayloadType payload_type = CalculatePayloadType();
+
+ int64_t payload_size = GetPayloadSize();
+
+ int64_t payload_bytes_downloaded = attempt_num_bytes_downloaded_;
+
+ ClockInterface* clock = system_state_->clock();
+ TimeDelta duration = clock->GetBootTime() - attempt_start_time_boot_;
+ TimeDelta duration_uptime =
+ clock->GetMonotonicTime() - attempt_start_time_monotonic_;
+
+ int64_t payload_download_speed_bps = 0;
+ int64_t usec = duration_uptime.InMicroseconds();
+ if (usec > 0) {
+ double sec = static_cast<double>(usec) / Time::kMicrosecondsPerSecond;
+ double bps = static_cast<double>(payload_bytes_downloaded) / sec;
+ payload_download_speed_bps = static_cast<int64_t>(bps);
+ }
+
+ DownloadSource download_source = current_download_source_;
+
+ metrics::DownloadErrorCode payload_download_error_code =
+ metrics::DownloadErrorCode::kUnset;
+ ErrorCode internal_error_code = ErrorCode::kSuccess;
+ metrics::AttemptResult attempt_result = metrics_utils::GetAttemptResult(code);
+
+ // Add additional detail to AttemptResult
+ switch (attempt_result) {
+ case metrics::AttemptResult::kPayloadDownloadError:
+ payload_download_error_code = metrics_utils::GetDownloadErrorCode(code);
+ break;
+
+ case metrics::AttemptResult::kInternalError:
+ internal_error_code = code;
+ break;
+
+ // Explicit fall-through for cases where we do not have additional
+ // detail. We avoid the default keyword to force people adding new
+ // AttemptResult values to visit this code and examine whether
+ // additional detail is needed.
+ case metrics::AttemptResult::kUpdateSucceeded:
+ case metrics::AttemptResult::kMetadataMalformed:
+ case metrics::AttemptResult::kOperationMalformed:
+ case metrics::AttemptResult::kOperationExecutionError:
+ case metrics::AttemptResult::kMetadataVerificationFailed:
+ case metrics::AttemptResult::kPayloadVerificationFailed:
+ case metrics::AttemptResult::kVerificationFailed:
+ case metrics::AttemptResult::kPostInstallFailed:
+ case metrics::AttemptResult::kAbnormalTermination:
+ case metrics::AttemptResult::kUpdateCanceled:
+ case metrics::AttemptResult::kUpdateSucceededNotActive:
+ case metrics::AttemptResult::kUpdateSkipped:
+ case metrics::AttemptResult::kNumConstants:
+ case metrics::AttemptResult::kUnset:
+ break;
+ }
+
+ system_state_->metrics_reporter()->ReportUpdateAttemptMetrics(
+ system_state_,
+ attempt_number,
+ payload_type,
+ duration,
+ duration_uptime,
+ payload_size,
+ attempt_result,
+ internal_error_code);
+
+ system_state_->metrics_reporter()->ReportUpdateAttemptDownloadMetrics(
+ payload_bytes_downloaded,
+ payload_download_speed_bps,
+ download_source,
+ payload_download_error_code,
+ attempt_connection_type_);
+}
+
+void PayloadState::PersistAttemptMetrics() {
+ // TODO(zeuthen): For now we only persist whether an attempt was in
+ // progress and not values/metrics related to the attempt. This
+ // means that when this happens, of all the UpdateEngine.Attempt.*
+ // metrics, only UpdateEngine.Attempt.Result is reported (with the
+ // value |kAbnormalTermination|). In the future we might want to
+ // persist more data so we can report other metrics in the
+ // UpdateEngine.Attempt.* namespace when this happens.
+ prefs_->SetBoolean(kPrefsAttemptInProgress, true);
+}
+
+void PayloadState::ClearPersistedAttemptMetrics() {
+ prefs_->Delete(kPrefsAttemptInProgress);
+}
+
+void PayloadState::ReportAndClearPersistedAttemptMetrics() {
+ bool attempt_in_progress = false;
+ if (!prefs_->GetBoolean(kPrefsAttemptInProgress, &attempt_in_progress))
+ return;
+ if (!attempt_in_progress)
+ return;
+
+ system_state_->metrics_reporter()
+ ->ReportAbnormallyTerminatedUpdateAttemptMetrics();
+
+ ClearPersistedAttemptMetrics();
+}
+
+void PayloadState::CollectAndReportSuccessfulUpdateMetrics() {
+ string metric;
+
+ // Report metrics collected from all known download sources to UMA.
+ int64_t total_bytes_by_source[kNumDownloadSources];
+ int64_t successful_bytes = 0;
+ int64_t total_bytes = 0;
+ int64_t successful_mbs = 0;
+ int64_t total_mbs = 0;
+
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ int64_t bytes;
+
+ // Only consider this download source (and send byte counts) as
+ // having been used if we downloaded a non-trivial amount of bytes
+ // (e.g. at least 1 MiB) that contributed to the final success of
+ // the update. Otherwise we're going to end up with a lot of
+ // zero-byte events in the histogram.
+
+ bytes = GetCurrentBytesDownloaded(source);
+ successful_bytes += bytes;
+ successful_mbs += bytes / kNumBytesInOneMiB;
+ SetCurrentBytesDownloaded(source, 0, true);
+
+ bytes = GetTotalBytesDownloaded(source);
+ total_bytes_by_source[i] = bytes;
+ total_bytes += bytes;
+ total_mbs += bytes / kNumBytesInOneMiB;
+ SetTotalBytesDownloaded(source, 0, true);
+ }
+
+ int download_overhead_percentage = 0;
+ if (successful_bytes > 0) {
+ download_overhead_percentage =
+ (total_bytes - successful_bytes) * 100ULL / successful_bytes;
+ }
+
+ int url_switch_count = static_cast<int>(url_switch_count_);
+
+ int reboot_count = GetNumReboots();
+
+ SetNumReboots(0);
+
+ TimeDelta duration = GetUpdateDuration();
+ TimeDelta duration_uptime = GetUpdateDurationUptime();
+
+ prefs_->Delete(kPrefsUpdateTimestampStart);
+ prefs_->Delete(kPrefsUpdateDurationUptime);
+
+ PayloadType payload_type = CalculatePayloadType();
+
+ int64_t payload_size = GetPayloadSize();
+
+ int attempt_count = GetPayloadAttemptNumber();
+
+ int updates_abandoned_count = num_responses_seen_ - 1;
+
+ system_state_->metrics_reporter()->ReportSuccessfulUpdateMetrics(
+ attempt_count,
+ updates_abandoned_count,
+ payload_type,
+ payload_size,
+ total_bytes_by_source,
+ download_overhead_percentage,
+ duration,
+ duration_uptime,
+ reboot_count,
+ url_switch_count);
+}
+
+void PayloadState::UpdateNumReboots() {
+ // We only update the reboot count when the system has been detected to have
+ // been rebooted.
+ if (!system_state_->system_rebooted()) {
+ return;
+ }
+
+ SetNumReboots(GetNumReboots() + 1);
+}
+
+void PayloadState::SetNumReboots(uint32_t num_reboots) {
+ num_reboots_ = num_reboots;
+ metrics_utils::SetNumReboots(num_reboots, prefs_);
+}
+
+void PayloadState::ResetPersistedState() {
+ SetPayloadAttemptNumber(0);
+ SetFullPayloadAttemptNumber(0);
+ SetPayloadIndex(0);
+ SetUrlIndex(0);
+ SetUrlFailureCount(0);
+ SetUrlSwitchCount(0);
+ UpdateBackoffExpiryTime(); // This will reset the backoff expiry time.
+ SetUpdateTimestampStart(system_state_->clock()->GetWallclockTime());
+ SetUpdateTimestampEnd(Time()); // Set to null time
+ SetUpdateDurationUptime(TimeDelta::FromSeconds(0));
+ ResetDownloadSourcesOnNewUpdate();
+ ResetRollbackVersion();
+ SetP2PNumAttempts(0);
+ SetP2PFirstAttemptTimestamp(Time()); // Set to null time
+ SetScatteringWaitPeriod(TimeDelta());
+ SetStagingWaitPeriod(TimeDelta());
+}
+
+void PayloadState::ResetRollbackVersion() {
+ CHECK(powerwash_safe_prefs_);
+ rollback_version_ = "";
+ powerwash_safe_prefs_->Delete(kPrefsRollbackVersion);
+}
+
+void PayloadState::ResetDownloadSourcesOnNewUpdate() {
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ SetCurrentBytesDownloaded(source, 0, true);
+ // Note: Not resetting the TotalBytesDownloaded as we want that metric
+ // to count the bytes downloaded across various update attempts until
+ // we have successfully applied the update.
+ }
+}
+
+string PayloadState::CalculateResponseSignature() {
+ string response_sign;
+ for (size_t i = 0; i < response_.packages.size(); i++) {
+ const auto& package = response_.packages[i];
+ response_sign += base::StringPrintf(
+ "Payload %zu:\n"
+ " Size = %ju\n"
+ " Sha256 Hash = %s\n"
+ " Metadata Size = %ju\n"
+ " Metadata Signature = %s\n"
+ " Is Delta = %d\n"
+ " NumURLs = %zu\n",
+ i,
+ static_cast<uintmax_t>(package.size),
+ package.hash.c_str(),
+ static_cast<uintmax_t>(package.metadata_size),
+ package.metadata_signature.c_str(),
+ package.is_delta,
+ candidate_urls_[i].size());
+
+ for (size_t j = 0; j < candidate_urls_[i].size(); j++)
+ response_sign += base::StringPrintf(
+ " Candidate Url%zu = %s\n", j, candidate_urls_[i][j].c_str());
+ }
+
+ response_sign += base::StringPrintf(
+ "Max Failure Count Per Url = %d\n"
+ "Disable Payload Backoff = %d\n",
+ response_.max_failure_count_per_url,
+ response_.disable_payload_backoff);
+ return response_sign;
+}
+
+void PayloadState::LoadResponseSignature() {
+ CHECK(prefs_);
+ string stored_value;
+ if (prefs_->Exists(kPrefsCurrentResponseSignature) &&
+ prefs_->GetString(kPrefsCurrentResponseSignature, &stored_value)) {
+ SetResponseSignature(stored_value);
+ }
+}
+
+void PayloadState::SetResponseSignature(const string& response_signature) {
+ CHECK(prefs_);
+ response_signature_ = response_signature;
+ LOG(INFO) << "Current Response Signature = \n" << response_signature_;
+ prefs_->SetString(kPrefsCurrentResponseSignature, response_signature_);
+}
+
+void PayloadState::LoadPayloadAttemptNumber() {
+ SetPayloadAttemptNumber(
+ GetPersistedValue(kPrefsPayloadAttemptNumber, prefs_));
+}
+
+void PayloadState::LoadFullPayloadAttemptNumber() {
+ SetFullPayloadAttemptNumber(
+ GetPersistedValue(kPrefsFullPayloadAttemptNumber, prefs_));
+}
+
+void PayloadState::SetPayloadAttemptNumber(int payload_attempt_number) {
+ payload_attempt_number_ = payload_attempt_number;
+ metrics_utils::SetPayloadAttemptNumber(payload_attempt_number, prefs_);
+}
+
+void PayloadState::SetFullPayloadAttemptNumber(
+ int full_payload_attempt_number) {
+ CHECK(prefs_);
+ full_payload_attempt_number_ = full_payload_attempt_number;
+ LOG(INFO) << "Full Payload Attempt Number = " << full_payload_attempt_number_;
+ prefs_->SetInt64(kPrefsFullPayloadAttemptNumber,
+ full_payload_attempt_number_);
+}
+
+void PayloadState::SetPayloadIndex(size_t payload_index) {
+ CHECK(prefs_);
+ payload_index_ = payload_index;
+ LOG(INFO) << "Payload Index = " << payload_index_;
+ prefs_->SetInt64(kPrefsUpdateStatePayloadIndex, payload_index_);
+}
+
+bool PayloadState::NextPayload() {
+ if (payload_index_ >= candidate_urls_.size())
+ return false;
+ SetPayloadIndex(payload_index_ + 1);
+ if (payload_index_ >= candidate_urls_.size())
+ return false;
+ SetUrlIndex(0);
+ return true;
+}
+
+void PayloadState::LoadUrlIndex() {
+ SetUrlIndex(GetPersistedValue(kPrefsCurrentUrlIndex, prefs_));
+}
+
+void PayloadState::SetUrlIndex(uint32_t url_index) {
+ CHECK(prefs_);
+ url_index_ = url_index;
+ LOG(INFO) << "Current URL Index = " << url_index_;
+ prefs_->SetInt64(kPrefsCurrentUrlIndex, url_index_);
+
+ // Also update the download source, which is purely dependent on the
+ // current URL index alone.
+ UpdateCurrentDownloadSource();
+}
+
+void PayloadState::LoadScatteringWaitPeriod() {
+ SetScatteringWaitPeriod(TimeDelta::FromSeconds(
+ GetPersistedValue(kPrefsWallClockScatteringWaitPeriod, prefs_)));
+}
+
+void PayloadState::SetScatteringWaitPeriod(TimeDelta wait_period) {
+ CHECK(prefs_);
+ scattering_wait_period_ = wait_period;
+ LOG(INFO) << "Scattering Wait Period (seconds) = "
+ << scattering_wait_period_.InSeconds();
+ if (scattering_wait_period_.InSeconds() > 0) {
+ prefs_->SetInt64(kPrefsWallClockScatteringWaitPeriod,
+ scattering_wait_period_.InSeconds());
+ } else {
+ prefs_->Delete(kPrefsWallClockScatteringWaitPeriod);
+ }
+}
+
+void PayloadState::LoadStagingWaitPeriod() {
+ SetStagingWaitPeriod(TimeDelta::FromSeconds(
+ GetPersistedValue(kPrefsWallClockStagingWaitPeriod, prefs_)));
+}
+
+void PayloadState::SetStagingWaitPeriod(TimeDelta wait_period) {
+ CHECK(prefs_);
+ staging_wait_period_ = wait_period;
+ LOG(INFO) << "Staging Wait Period (days) =" << staging_wait_period_.InDays();
+ if (staging_wait_period_.InSeconds() > 0) {
+ prefs_->SetInt64(kPrefsWallClockStagingWaitPeriod,
+ staging_wait_period_.InSeconds());
+ } else {
+ prefs_->Delete(kPrefsWallClockStagingWaitPeriod);
+ }
+}
+
+void PayloadState::LoadUrlSwitchCount() {
+ SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount, prefs_));
+}
+
+void PayloadState::SetUrlSwitchCount(uint32_t url_switch_count) {
+ CHECK(prefs_);
+ url_switch_count_ = url_switch_count;
+ LOG(INFO) << "URL Switch Count = " << url_switch_count_;
+ prefs_->SetInt64(kPrefsUrlSwitchCount, url_switch_count_);
+}
+
+void PayloadState::LoadUrlFailureCount() {
+ SetUrlFailureCount(GetPersistedValue(kPrefsCurrentUrlFailureCount, prefs_));
+}
+
+void PayloadState::SetUrlFailureCount(uint32_t url_failure_count) {
+ CHECK(prefs_);
+ url_failure_count_ = url_failure_count;
+ LOG(INFO) << "Current URL (Url" << GetUrlIndex()
+ << ")'s Failure Count = " << url_failure_count_;
+ prefs_->SetInt64(kPrefsCurrentUrlFailureCount, url_failure_count_);
+}
+
+void PayloadState::LoadBackoffExpiryTime() {
+ CHECK(prefs_);
+ int64_t stored_value;
+ if (!prefs_->Exists(kPrefsBackoffExpiryTime))
+ return;
+
+ if (!prefs_->GetInt64(kPrefsBackoffExpiryTime, &stored_value))
+ return;
+
+ Time stored_time = Time::FromInternalValue(stored_value);
+ if (stored_time > Time::Now() + TimeDelta::FromDays(kMaxBackoffDays)) {
+ LOG(ERROR) << "Invalid backoff expiry time ("
+ << utils::ToString(stored_time)
+ << ") in persisted state. Resetting.";
+ stored_time = Time();
+ }
+ SetBackoffExpiryTime(stored_time);
+}
+
+void PayloadState::SetBackoffExpiryTime(const Time& new_time) {
+ CHECK(prefs_);
+ backoff_expiry_time_ = new_time;
+ LOG(INFO) << "Backoff Expiry Time = "
+ << utils::ToString(backoff_expiry_time_);
+ prefs_->SetInt64(kPrefsBackoffExpiryTime,
+ backoff_expiry_time_.ToInternalValue());
+}
+
+TimeDelta PayloadState::GetUpdateDuration() {
+ Time end_time = update_timestamp_end_.is_null()
+ ? system_state_->clock()->GetWallclockTime()
+ : update_timestamp_end_;
+ return end_time - update_timestamp_start_;
+}
+
+void PayloadState::LoadUpdateTimestampStart() {
+ int64_t stored_value;
+ Time stored_time;
+
+ CHECK(prefs_);
+
+ Time now = system_state_->clock()->GetWallclockTime();
+
+ if (!prefs_->Exists(kPrefsUpdateTimestampStart)) {
+ // The preference missing is not unexpected - in that case, just
+ // use the current time as start time
+ stored_time = now;
+ } else if (!prefs_->GetInt64(kPrefsUpdateTimestampStart, &stored_value)) {
+ LOG(ERROR) << "Invalid UpdateTimestampStart value. Resetting.";
+ stored_time = now;
+ } else {
+ stored_time = Time::FromInternalValue(stored_value);
+ }
+
+ // Validation check: If the time read from disk is in the future
+ // (modulo some slack to account for possible NTP drift
+ // adjustments), something is fishy and we should report and
+ // reset.
+ TimeDelta duration_according_to_stored_time = now - stored_time;
+ if (duration_according_to_stored_time < -kDurationSlack) {
+ LOG(ERROR) << "The UpdateTimestampStart value ("
+ << utils::ToString(stored_time) << ") in persisted state is "
+ << utils::FormatTimeDelta(duration_according_to_stored_time)
+ << " in the future. Resetting.";
+ stored_time = now;
+ }
+
+ SetUpdateTimestampStart(stored_time);
+}
+
+void PayloadState::SetUpdateTimestampStart(const Time& value) {
+ update_timestamp_start_ = value;
+ metrics_utils::SetUpdateTimestampStart(value, prefs_);
+}
+
+void PayloadState::SetUpdateTimestampEnd(const Time& value) {
+ update_timestamp_end_ = value;
+ LOG(INFO) << "Update Timestamp End = "
+ << utils::ToString(update_timestamp_end_);
+}
+
+TimeDelta PayloadState::GetUpdateDurationUptime() {
+ return update_duration_uptime_;
+}
+
+void PayloadState::LoadUpdateDurationUptime() {
+ int64_t stored_value;
+ TimeDelta stored_delta;
+
+ CHECK(prefs_);
+
+ if (!prefs_->Exists(kPrefsUpdateDurationUptime)) {
+ // The preference missing is not unexpected - in that case, just
+ // we'll use zero as the delta
+ } else if (!prefs_->GetInt64(kPrefsUpdateDurationUptime, &stored_value)) {
+ LOG(ERROR) << "Invalid UpdateDurationUptime value. Resetting.";
+ stored_delta = TimeDelta::FromSeconds(0);
+ } else {
+ stored_delta = TimeDelta::FromInternalValue(stored_value);
+ }
+
+ // Validation check: Uptime can never be greater than the wall-clock
+ // difference (modulo some slack). If it is, report and reset
+ // to the wall-clock difference.
+ TimeDelta diff = GetUpdateDuration() - stored_delta;
+ if (diff < -kDurationSlack) {
+ LOG(ERROR) << "The UpdateDurationUptime value ("
+ << utils::FormatTimeDelta(stored_delta)
+ << ") in persisted state is " << utils::FormatTimeDelta(diff)
+ << " larger than the wall-clock delta. Resetting.";
+ stored_delta = update_duration_current_;
+ }
+
+ SetUpdateDurationUptime(stored_delta);
+}
+
+void PayloadState::LoadNumReboots() {
+ SetNumReboots(GetPersistedValue(kPrefsNumReboots, prefs_));
+}
+
+void PayloadState::LoadRollbackHappened() {
+ CHECK(powerwash_safe_prefs_);
+ bool rollback_happened = false;
+ powerwash_safe_prefs_->GetBoolean(kPrefsRollbackHappened, &rollback_happened);
+ SetRollbackHappened(rollback_happened);
+}
+
+void PayloadState::SetRollbackHappened(bool rollback_happened) {
+ CHECK(powerwash_safe_prefs_);
+ LOG(INFO) << "Setting rollback-happened to " << rollback_happened << ".";
+ rollback_happened_ = rollback_happened;
+ if (rollback_happened) {
+ powerwash_safe_prefs_->SetBoolean(kPrefsRollbackHappened,
+ rollback_happened);
+ } else {
+ powerwash_safe_prefs_->Delete(kPrefsRollbackHappened);
+ }
+}
+
+void PayloadState::LoadRollbackVersion() {
+ CHECK(powerwash_safe_prefs_);
+ string rollback_version;
+ if (powerwash_safe_prefs_->GetString(kPrefsRollbackVersion,
+ &rollback_version)) {
+ SetRollbackVersion(rollback_version);
+ }
+}
+
+void PayloadState::SetRollbackVersion(const string& rollback_version) {
+ CHECK(powerwash_safe_prefs_);
+ LOG(INFO) << "Excluding version " << rollback_version;
+ rollback_version_ = rollback_version;
+ powerwash_safe_prefs_->SetString(kPrefsRollbackVersion, rollback_version);
+}
+
+void PayloadState::SetUpdateDurationUptimeExtended(const TimeDelta& value,
+ const Time& timestamp,
+ bool use_logging) {
+ CHECK(prefs_);
+ update_duration_uptime_ = value;
+ update_duration_uptime_timestamp_ = timestamp;
+ prefs_->SetInt64(kPrefsUpdateDurationUptime,
+ update_duration_uptime_.ToInternalValue());
+ if (use_logging) {
+ LOG(INFO) << "Update Duration Uptime = "
+ << utils::FormatTimeDelta(update_duration_uptime_);
+ }
+}
+
+void PayloadState::SetUpdateDurationUptime(const TimeDelta& value) {
+ Time now = system_state_->clock()->GetMonotonicTime();
+ SetUpdateDurationUptimeExtended(value, now, true);
+}
+
+void PayloadState::CalculateUpdateDurationUptime() {
+ Time now = system_state_->clock()->GetMonotonicTime();
+ TimeDelta uptime_since_last_update = now - update_duration_uptime_timestamp_;
+
+ if (uptime_since_last_update > TimeDelta::FromSeconds(kUptimeResolution)) {
+ TimeDelta new_uptime = update_duration_uptime_ + uptime_since_last_update;
+ // We're frequently called so avoid logging this write
+ SetUpdateDurationUptimeExtended(new_uptime, now, false);
+ }
+}
+
+string PayloadState::GetPrefsKey(const string& prefix, DownloadSource source) {
+ return prefix + "-from-" + utils::ToString(source);
+}
+
+void PayloadState::LoadCurrentBytesDownloaded(DownloadSource source) {
+ string key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source);
+ SetCurrentBytesDownloaded(source, GetPersistedValue(key, prefs_), true);
+}
+
+void PayloadState::SetCurrentBytesDownloaded(DownloadSource source,
+ uint64_t current_bytes_downloaded,
+ bool log) {
+ CHECK(prefs_);
+
+ if (source >= kNumDownloadSources)
+ return;
+
+ // Update the in-memory value.
+ current_bytes_downloaded_[source] = current_bytes_downloaded;
+
+ string prefs_key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source);
+ prefs_->SetInt64(prefs_key, current_bytes_downloaded);
+ LOG_IF(INFO, log) << "Current bytes downloaded for "
+ << utils::ToString(source) << " = "
+ << GetCurrentBytesDownloaded(source);
+}
+
+void PayloadState::LoadTotalBytesDownloaded(DownloadSource source) {
+ string key = GetPrefsKey(kPrefsTotalBytesDownloaded, source);
+ SetTotalBytesDownloaded(source, GetPersistedValue(key, prefs_), true);
+}
+
+void PayloadState::SetTotalBytesDownloaded(DownloadSource source,
+ uint64_t total_bytes_downloaded,
+ bool log) {
+ CHECK(prefs_);
+
+ if (source >= kNumDownloadSources)
+ return;
+
+ // Update the in-memory value.
+ total_bytes_downloaded_[source] = total_bytes_downloaded;
+
+ // Persist.
+ string prefs_key = GetPrefsKey(kPrefsTotalBytesDownloaded, source);
+ prefs_->SetInt64(prefs_key, total_bytes_downloaded);
+ LOG_IF(INFO, log) << "Total bytes downloaded for " << utils::ToString(source)
+ << " = " << GetTotalBytesDownloaded(source);
+}
+
+void PayloadState::LoadNumResponsesSeen() {
+ SetNumResponsesSeen(GetPersistedValue(kPrefsNumResponsesSeen, prefs_));
+}
+
+void PayloadState::SetNumResponsesSeen(int num_responses_seen) {
+ CHECK(prefs_);
+ num_responses_seen_ = num_responses_seen;
+ LOG(INFO) << "Num Responses Seen = " << num_responses_seen_;
+ prefs_->SetInt64(kPrefsNumResponsesSeen, num_responses_seen_);
+}
+
+void PayloadState::ComputeCandidateUrls() {
+ bool http_url_ok = true;
+
+ if (system_state_->hardware()->IsOfficialBuild()) {
+ const policy::DevicePolicy* policy = system_state_->device_policy();
+ if (policy && policy->GetHttpDownloadsEnabled(&http_url_ok) && !http_url_ok)
+ LOG(INFO) << "Downloads via HTTP Url are not enabled by device policy";
+ } else {
+ LOG(INFO) << "Allowing HTTP downloads for unofficial builds";
+ http_url_ok = true;
+ }
+
+ candidate_urls_.clear();
+ for (const auto& package : response_.packages) {
+ candidate_urls_.emplace_back();
+ for (const string& candidate_url : package.payload_urls) {
+ if (base::StartsWith(
+ candidate_url, "http://", base::CompareCase::INSENSITIVE_ASCII) &&
+ !http_url_ok) {
+ continue;
+ }
+ candidate_urls_.back().push_back(candidate_url);
+ LOG(INFO) << "Candidate Url" << (candidate_urls_.back().size() - 1)
+ << ": " << candidate_url;
+ }
+ LOG(INFO) << "Found " << candidate_urls_.back().size() << " candidate URLs "
+ << "out of " << package.payload_urls.size()
+ << " URLs supplied in package " << candidate_urls_.size() - 1;
+ }
+}
+
+void PayloadState::UpdateEngineStarted() {
+ // Flush previous state from abnormal attempt failure, if any.
+ ReportAndClearPersistedAttemptMetrics();
+
+ // Avoid the UpdateEngineStarted actions if this is not the first time we
+ // run the update engine since reboot.
+ if (!system_state_->system_rebooted())
+ return;
+
+ // Report time_to_reboot if we booted into a new update.
+ metrics_utils::LoadAndReportTimeToReboot(
+ system_state_->metrics_reporter(), prefs_, system_state_->clock());
+ prefs_->Delete(kPrefsSystemUpdatedMarker);
+
+ // Check if it is needed to send metrics about a failed reboot into a new
+ // version.
+ ReportFailedBootIfNeeded();
+}
+
+void PayloadState::ReportFailedBootIfNeeded() {
+ // If the kPrefsTargetVersionInstalledFrom is present, a successfully applied
+ // payload was marked as ready immediately before the last reboot, and we
+ // need to check if such payload successfully rebooted or not.
+ if (prefs_->Exists(kPrefsTargetVersionInstalledFrom)) {
+ int64_t installed_from = 0;
+ if (!prefs_->GetInt64(kPrefsTargetVersionInstalledFrom, &installed_from)) {
+ LOG(ERROR) << "Error reading TargetVersionInstalledFrom on reboot.";
+ return;
+ }
+ // Old Chrome OS devices will write 2 or 4 in this setting, with the
+ // partition number. We are now using slot numbers (0 or 1) instead, so
+ // the following comparison will not match if we are comparing an old
+ // partition number against a new slot number, which is the correct outcome
+ // since we successfully booted the new update in that case. If the boot
+ // failed, we will read this value from the same version, so it will always
+ // be compatible.
+ if (installed_from == system_state_->boot_control()->GetCurrentSlot()) {
+ // A reboot was pending, but the chromebook is again in the same
+ // BootDevice where the update was installed from.
+ int64_t target_attempt;
+ if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt)) {
+ LOG(ERROR) << "Error reading TargetVersionAttempt when "
+ "TargetVersionInstalledFrom was present.";
+ target_attempt = 1;
+ }
+
+ // Report the UMA metric of the current boot failure.
+ system_state_->metrics_reporter()->ReportFailedUpdateCount(
+ target_attempt);
+ } else {
+ prefs_->Delete(kPrefsTargetVersionAttempt);
+ prefs_->Delete(kPrefsTargetVersionUniqueId);
+ }
+ prefs_->Delete(kPrefsTargetVersionInstalledFrom);
+ }
+}
+
+void PayloadState::ExpectRebootInNewVersion(const string& target_version_uid) {
+ // Expect to boot into the new partition in the next reboot setting the
+ // TargetVersion* flags in the Prefs.
+ string stored_target_version_uid;
+ string target_version_id;
+ string target_partition;
+ int64_t target_attempt;
+
+ if (prefs_->Exists(kPrefsTargetVersionUniqueId) &&
+ prefs_->GetString(kPrefsTargetVersionUniqueId,
+ &stored_target_version_uid) &&
+ stored_target_version_uid == target_version_uid) {
+ if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt))
+ target_attempt = 0;
+ } else {
+ prefs_->SetString(kPrefsTargetVersionUniqueId, target_version_uid);
+ target_attempt = 0;
+ }
+ prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt + 1);
+
+ prefs_->SetInt64(kPrefsTargetVersionInstalledFrom,
+ system_state_->boot_control()->GetCurrentSlot());
+}
+
+void PayloadState::ResetUpdateStatus() {
+ // Remove the TargetVersionInstalledFrom pref so that if the machine is
+ // rebooted the next boot is not flagged as failed to rebooted into the
+ // new applied payload.
+ prefs_->Delete(kPrefsTargetVersionInstalledFrom);
+
+ // Also decrement the attempt number if it exists.
+ int64_t target_attempt;
+ if (prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt))
+ prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt - 1);
+}
+
+int PayloadState::GetP2PNumAttempts() {
+ return p2p_num_attempts_;
+}
+
+void PayloadState::SetP2PNumAttempts(int value) {
+ p2p_num_attempts_ = value;
+ LOG(INFO) << "p2p Num Attempts = " << p2p_num_attempts_;
+ CHECK(prefs_);
+ prefs_->SetInt64(kPrefsP2PNumAttempts, value);
+}
+
+void PayloadState::LoadP2PNumAttempts() {
+ SetP2PNumAttempts(GetPersistedValue(kPrefsP2PNumAttempts, prefs_));
+}
+
+Time PayloadState::GetP2PFirstAttemptTimestamp() {
+ return p2p_first_attempt_timestamp_;
+}
+
+void PayloadState::SetP2PFirstAttemptTimestamp(const Time& time) {
+ p2p_first_attempt_timestamp_ = time;
+ LOG(INFO) << "p2p First Attempt Timestamp = "
+ << utils::ToString(p2p_first_attempt_timestamp_);
+ CHECK(prefs_);
+ int64_t stored_value = time.ToInternalValue();
+ prefs_->SetInt64(kPrefsP2PFirstAttemptTimestamp, stored_value);
+}
+
+void PayloadState::LoadP2PFirstAttemptTimestamp() {
+ int64_t stored_value =
+ GetPersistedValue(kPrefsP2PFirstAttemptTimestamp, prefs_);
+ Time stored_time = Time::FromInternalValue(stored_value);
+ SetP2PFirstAttemptTimestamp(stored_time);
+}
+
+void PayloadState::P2PNewAttempt() {
+ CHECK(prefs_);
+ // Set timestamp, if it hasn't been set already
+ if (p2p_first_attempt_timestamp_.is_null()) {
+ SetP2PFirstAttemptTimestamp(system_state_->clock()->GetWallclockTime());
+ }
+ // Increase number of attempts
+ SetP2PNumAttempts(GetP2PNumAttempts() + 1);
+}
+
+bool PayloadState::P2PAttemptAllowed() {
+ if (p2p_num_attempts_ > kMaxP2PAttempts) {
+ LOG(INFO) << "Number of p2p attempts is " << p2p_num_attempts_
+ << " which is greater than " << kMaxP2PAttempts
+ << " - disallowing p2p.";
+ return false;
+ }
+
+ if (!p2p_first_attempt_timestamp_.is_null()) {
+ Time now = system_state_->clock()->GetWallclockTime();
+ TimeDelta time_spent_attempting_p2p = now - p2p_first_attempt_timestamp_;
+ if (time_spent_attempting_p2p.InSeconds() < 0) {
+ LOG(ERROR) << "Time spent attempting p2p is negative"
+ << " - disallowing p2p.";
+ return false;
+ }
+ if (time_spent_attempting_p2p.InSeconds() > kMaxP2PAttemptTimeSeconds) {
+ LOG(INFO) << "Time spent attempting p2p is "
+ << utils::FormatTimeDelta(time_spent_attempting_p2p)
+ << " which is greater than "
+ << utils::FormatTimeDelta(
+ TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds))
+ << " - disallowing p2p.";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int64_t PayloadState::GetPayloadSize() {
+ int64_t payload_size = 0;
+ for (const auto& package : response_.packages)
+ payload_size += package.size;
+ return payload_size;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/payload_state.h b/cros/payload_state.h
new file mode 100644
index 0000000..0827273
--- /dev/null
+++ b/cros/payload_state.h
@@ -0,0 +1,602 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_PAYLOAD_STATE_H_
+#define UPDATE_ENGINE_CROS_PAYLOAD_STATE_H_
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/excluder_interface.h"
+#include "update_engine/common/metrics_constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/cros/payload_state_interface.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// Encapsulates all the payload state required for download. This includes the
+// state necessary for handling multiple URLs in Omaha response, the backoff
+// state, etc. All state is persisted so that we use the most recently saved
+// value when resuming the update_engine process. All state is also cached in
+// memory so that we ensure we always make progress based on last known good
+// state even when there's any issue in reading/writing from the file system.
+class PayloadState : public PayloadStateInterface {
+ public:
+ PayloadState();
+ ~PayloadState() override {}
+
+ // Initializes a payload state object using the given global system state.
+ // It performs the initial loading of all persisted state into memory and
+ // dumps the initial state for debugging purposes. Note: the other methods
+ // should be called only after calling Initialize on this object.
+ bool Initialize(SystemState* system_state);
+
+ // Implementation of PayloadStateInterface methods.
+ void SetResponse(const OmahaResponse& response) override;
+ void DownloadComplete() override;
+ void DownloadProgress(size_t count) override;
+ void UpdateResumed() override;
+ void UpdateRestarted() override;
+ void UpdateSucceeded() override;
+ void UpdateFailed(ErrorCode error) override;
+ void ResetUpdateStatus() override;
+ bool ShouldBackoffDownload() override;
+ void Rollback() override;
+ void ExpectRebootInNewVersion(const std::string& target_version_uid) override;
+ void SetUsingP2PForDownloading(bool value) override;
+
+ void SetUsingP2PForSharing(bool value) override {
+ using_p2p_for_sharing_ = value;
+ }
+
+ inline std::string GetResponseSignature() override {
+ return response_signature_;
+ }
+
+ inline int GetFullPayloadAttemptNumber() override {
+ return full_payload_attempt_number_;
+ }
+
+ inline int GetPayloadAttemptNumber() override {
+ return payload_attempt_number_;
+ }
+
+ inline std::string GetCurrentUrl() override {
+ return (payload_index_ < candidate_urls_.size() &&
+ url_index_ < candidate_urls_[payload_index_].size())
+ ? candidate_urls_[payload_index_][url_index_]
+ : "";
+ }
+
+ inline uint32_t GetUrlFailureCount() override { return url_failure_count_; }
+
+ inline uint32_t GetUrlSwitchCount() override { return url_switch_count_; }
+
+ inline int GetNumResponsesSeen() override { return num_responses_seen_; }
+
+ inline base::Time GetBackoffExpiryTime() override {
+ return backoff_expiry_time_;
+ }
+
+ base::TimeDelta GetUpdateDuration() override;
+
+ base::TimeDelta GetUpdateDurationUptime() override;
+
+ inline uint64_t GetCurrentBytesDownloaded(DownloadSource source) override {
+ return source < kNumDownloadSources ? current_bytes_downloaded_[source] : 0;
+ }
+
+ inline uint64_t GetTotalBytesDownloaded(DownloadSource source) override {
+ return source < kNumDownloadSources ? total_bytes_downloaded_[source] : 0;
+ }
+
+ inline uint32_t GetNumReboots() override { return num_reboots_; }
+
+ void UpdateEngineStarted() override;
+
+ inline bool GetRollbackHappened() override { return rollback_happened_; }
+
+ void SetRollbackHappened(bool rollback_happened) override;
+
+ inline std::string GetRollbackVersion() override { return rollback_version_; }
+
+ int GetP2PNumAttempts() override;
+ base::Time GetP2PFirstAttemptTimestamp() override;
+ void P2PNewAttempt() override;
+ bool P2PAttemptAllowed() override;
+
+ bool GetUsingP2PForDownloading() const override {
+ return using_p2p_for_downloading_;
+ }
+
+ bool GetUsingP2PForSharing() const override { return using_p2p_for_sharing_; }
+
+ base::TimeDelta GetScatteringWaitPeriod() override {
+ return scattering_wait_period_;
+ }
+
+ void SetScatteringWaitPeriod(base::TimeDelta wait_period) override;
+
+ void SetStagingWaitPeriod(base::TimeDelta wait_period) override;
+
+ void SetP2PUrl(const std::string& url) override { p2p_url_ = url; }
+
+ std::string GetP2PUrl() const override { return p2p_url_; }
+
+ bool NextPayload() override;
+
+ private:
+ enum class AttemptType {
+ kUpdate,
+ kRollback,
+ };
+
+ friend class PayloadStateTest;
+ FRIEND_TEST(PayloadStateTest, RebootAfterUpdateFailedMetric);
+ FRIEND_TEST(PayloadStateTest, RebootAfterUpdateSucceed);
+ FRIEND_TEST(PayloadStateTest, RebootAfterCanceledUpdate);
+ FRIEND_TEST(PayloadStateTest, RollbackHappened);
+ FRIEND_TEST(PayloadStateTest, RollbackVersion);
+ FRIEND_TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs);
+ FRIEND_TEST(PayloadStateTest, NextPayloadResetsUrlIndex);
+ FRIEND_TEST(PayloadStateTest, ExcludeNoopForNonExcludables);
+ FRIEND_TEST(PayloadStateTest, ExcludeOnlyCanExcludables);
+ FRIEND_TEST(PayloadStateTest, IncrementFailureExclusionTest);
+ FRIEND_TEST(PayloadStateTest, HaltExclusionPostPayloadExhaustion);
+ FRIEND_TEST(PayloadStateTest, NonInfinitePayloadIndexIncrement);
+
+ // Helper called when an attempt has begun, is called by
+ // UpdateResumed(), UpdateRestarted() and Rollback().
+ void AttemptStarted(AttemptType attempt_type);
+
+ // Increments the payload attempt number used for metrics.
+ void IncrementPayloadAttemptNumber();
+
+ // Increments the payload attempt number which governs the backoff behavior
+ // at the time of the next update check.
+ void IncrementFullPayloadAttemptNumber();
+
+ // Advances the current URL index to the next available one. If all URLs have
+ // been exhausted during the current payload download attempt (as indicated
+ // by the payload attempt number), then it will increment the payload attempt
+ // number and wrap around again with the first URL in the list. This also
+ // updates the URL switch count, if needed.
+ void IncrementUrlIndex();
+
+ // Increments the failure count of the current URL. If the configured max
+ // failure count is reached for this URL, it advances the current URL index
+ // to the next URL and resets the failure count for that URL.
+ void IncrementFailureCount();
+
+ // Excludes the current payload + current candidate URL from being part of
+ // future updates/retries. Whenever |SetResponse()| or |NextPayload()| decide
+ // on the initial current URL index and the next payload respectively, it will
+ // advanced based on exclusions.
+ void ExcludeCurrentPayload();
+
+ // Updates the backoff expiry time exponentially based on the current
+ // payload attempt number.
+ void UpdateBackoffExpiryTime();
+
+ // Updates the value of current download source based on the current URL
+ // index. If the download source is not one of the known sources, it's set
+ // to kNumDownloadSources.
+ void UpdateCurrentDownloadSource();
+
+ // Updates the various metrics corresponding with the given number of bytes
+ // that were downloaded recently.
+ void UpdateBytesDownloaded(size_t count);
+
+ // Calculates the PayloadType we're using.
+ PayloadType CalculatePayloadType();
+
+ // Collects and reports the various metrics related to an update attempt.
+ void CollectAndReportAttemptMetrics(ErrorCode code);
+
+ // Persists values related to the UpdateEngine.Attempt.* metrics so
+ // we can identify later if an update attempt ends abnormally.
+ void PersistAttemptMetrics();
+
+ // Clears persistent state previously set using AttemptMetricsPersist().
+ void ClearPersistedAttemptMetrics();
+
+ // Checks if persistent state previously set using AttemptMetricsPersist()
+ // exists and, if so, emits it with |attempt_result| set to
+ // metrics::AttemptResult::kAbnormalTermination.
+ void ReportAndClearPersistedAttemptMetrics();
+
+ // Collects and reports the various metrics related to a successful update.
+ void CollectAndReportSuccessfulUpdateMetrics();
+
+ // Checks if we were expecting to be running in the new version but the
+ // boot into the new version failed for some reason. If that's the case, an
+ // UMA metric is sent reporting the number of attempts the same applied
+ // payload was attempted to reboot. This function is called by UpdateAttempter
+ // every time the update engine starts and there's no reboot pending.
+ void ReportFailedBootIfNeeded();
+
+ // Resets all the persisted state values which are maintained relative to the
+ // current response signature. The response signature itself is not reset.
+ void ResetPersistedState();
+
+ // Resets the appropriate state related to download sources that need to be
+ // reset on a new update.
+ void ResetDownloadSourcesOnNewUpdate();
+
+ // Calculates the response "signature", which is basically a string composed
+ // of the subset of the fields in the current response that affect the
+ // behavior of the PayloadState.
+ std::string CalculateResponseSignature();
+
+ // Initializes the current response signature from the persisted state.
+ void LoadResponseSignature();
+
+ // Sets the response signature to the given value. Also persists the value
+ // being set so that we resume from the save value in case of a process
+ // restart.
+ void SetResponseSignature(const std::string& response_signature);
+
+ // Initializes the payload attempt number from the persisted state.
+ void LoadPayloadAttemptNumber();
+
+ // Initializes the payload attempt number for full payloads from the persisted
+ // state.
+ void LoadFullPayloadAttemptNumber();
+
+ // Sets the payload attempt number to the given value. Also persists the
+ // value being set so that we resume from the same value in case of a process
+ // restart.
+ void SetPayloadAttemptNumber(int payload_attempt_number);
+
+ // Sets the payload attempt number for full updates to the given value. Also
+ // persists the value being set so that we resume from the same value in case
+ // of a process restart.
+ void SetFullPayloadAttemptNumber(int payload_attempt_number);
+
+ // Sets the current payload index to the given value. Also persists the value
+ // being set so that we resume from the same value in case of a process
+ // restart.
+ void SetPayloadIndex(size_t payload_index);
+
+ // Initializes the current URL index from the persisted state.
+ void LoadUrlIndex();
+
+ // Sets the current URL index to the given value. Also persists the value
+ // being set so that we resume from the same value in case of a process
+ // restart.
+ void SetUrlIndex(uint32_t url_index);
+
+ // Initializes the current URL's failure count from the persisted stae.
+ void LoadUrlFailureCount();
+
+ // Sets the current URL's failure count to the given value. Also persists the
+ // value being set so that we resume from the same value in case of a process
+ // restart.
+ void SetUrlFailureCount(uint32_t url_failure_count);
+
+ // Sets |url_switch_count_| to the given value and persists the value.
+ void SetUrlSwitchCount(uint32_t url_switch_count);
+
+ // Initializes |url_switch_count_| from the persisted stae.
+ void LoadUrlSwitchCount();
+
+ // Initializes the backoff expiry time from the persisted state.
+ void LoadBackoffExpiryTime();
+
+ // Sets the backoff expiry time to the given value. Also persists the value
+ // being set so that we resume from the same value in case of a process
+ // restart.
+ void SetBackoffExpiryTime(const base::Time& new_time);
+
+ // Initializes |update_timestamp_start_| from the persisted state.
+ void LoadUpdateTimestampStart();
+
+ // Sets |update_timestamp_start_| to the given value and persists the value.
+ void SetUpdateTimestampStart(const base::Time& value);
+
+ // Sets |update_timestamp_end_| to the given value. This is not persisted
+ // as it happens at the end of the update process where state is deleted
+ // anyway.
+ void SetUpdateTimestampEnd(const base::Time& value);
+
+ // Initializes |update_duration_uptime_| from the persisted state.
+ void LoadUpdateDurationUptime();
+
+ // Helper method used in SetUpdateDurationUptime() and
+ // CalculateUpdateDurationUptime().
+ void SetUpdateDurationUptimeExtended(const base::TimeDelta& value,
+ const base::Time& timestamp,
+ bool use_logging);
+
+ // Sets |update_duration_uptime_| to the given value and persists
+ // the value and sets |update_duration_uptime_timestamp_| to the
+ // current monotonic time.
+ void SetUpdateDurationUptime(const base::TimeDelta& value);
+
+ // Adds the difference between current monotonic time and
+ // |update_duration_uptime_timestamp_| to |update_duration_uptime_| and
+ // sets |update_duration_uptime_timestamp_| to current monotonic time.
+ void CalculateUpdateDurationUptime();
+
+ // Returns the full key for a download source given the prefix.
+ std::string GetPrefsKey(const std::string& prefix, DownloadSource source);
+
+ // Loads the number of bytes that have been currently downloaded through the
+ // previous attempts from the persisted state for the given source. It's
+ // reset to 0 every time we begin a full update and is continued from previous
+ // attempt if we're resuming the update.
+ void LoadCurrentBytesDownloaded(DownloadSource source);
+
+ // Sets the number of bytes that have been currently downloaded for the
+ // given source. This value is also persisted.
+ void SetCurrentBytesDownloaded(DownloadSource source,
+ uint64_t current_bytes_downloaded,
+ bool log);
+
+ // Loads the total number of bytes that have been downloaded (since the last
+ // successful update) from the persisted state for the given source. It's
+ // reset to 0 every time we successfully apply an update and counts the bytes
+ // downloaded for both successful and failed attempts since then.
+ void LoadTotalBytesDownloaded(DownloadSource source);
+
+ // Sets the total number of bytes that have been downloaded so far for the
+ // given source. This value is also persisted.
+ void SetTotalBytesDownloaded(DownloadSource source,
+ uint64_t total_bytes_downloaded,
+ bool log);
+
+ // Loads whether rollback has happened on this device since the last update
+ // check where policy was available. This info is preserved over powerwash.
+ void LoadRollbackHappened();
+
+ // Loads the excluded version from our prefs file.
+ void LoadRollbackVersion();
+
+ // Excludes this version from getting AU'd to until we receive a new update
+ // response.
+ void SetRollbackVersion(const std::string& rollback_version);
+
+ // Clears any excluded version.
+ void ResetRollbackVersion();
+
+ inline uint32_t GetUrlIndex() {
+ return (url_index_ != 0 && payload_index_ < candidate_urls_.size())
+ ? std::min(candidate_urls_[payload_index_].size() - 1,
+ url_index_)
+ : 0;
+ }
+
+ // Computes the list of candidate URLs from the total list of payload URLs in
+ // the Omaha response.
+ void ComputeCandidateUrls();
+
+ // Sets |num_responses_seen_| and persist it to disk.
+ void SetNumResponsesSeen(int num_responses_seen);
+
+ // Initializes |num_responses_seen_| from persisted state.
+ void LoadNumResponsesSeen();
+
+ // Initializes |num_reboots_| from the persisted state.
+ void LoadNumReboots();
+
+ // Sets |num_reboots| for the update attempt. Also persists the
+ // value being set so that we resume from the same value in case of a process
+ // restart.
+ void SetNumReboots(uint32_t num_reboots);
+
+ // Checks to see if the device rebooted since the last call and if so
+ // increments num_reboots.
+ void UpdateNumReboots();
+
+ // Loads the |kPrefsP2PFirstAttemptTimestamp| state variable from disk
+ // into |p2p_first_attempt_timestamp_|.
+ void LoadP2PFirstAttemptTimestamp();
+
+ // Loads the |kPrefsP2PNumAttempts| state variable into |p2p_num_attempts_|.
+ void LoadP2PNumAttempts();
+
+ // Sets the |kPrefsP2PNumAttempts| state variable to |value|.
+ void SetP2PNumAttempts(int value);
+
+ // Sets the |kPrefsP2PFirstAttemptTimestamp| state variable to |time|.
+ void SetP2PFirstAttemptTimestamp(const base::Time& time);
+
+ // Loads the persisted scattering wallclock-based wait period.
+ void LoadScatteringWaitPeriod();
+
+ // Loads the persisted staging wallclock-based wait period.
+ void LoadStagingWaitPeriod();
+
+ // Get the total size of all payloads.
+ int64_t GetPayloadSize();
+
+ // The global state of the system.
+ SystemState* system_state_;
+
+ // Interface object with which we read/write persisted state. This must
+ // be set by calling the Initialize method before calling any other method.
+ PrefsInterface* prefs_;
+
+ // Interface object with which we read/write persisted state. This must
+ // be set by calling the Initialize method before calling any other method.
+ // This object persists across powerwashes.
+ PrefsInterface* powerwash_safe_prefs_;
+
+ // Interface object with which we determine exclusion decisions for
+ // payloads/partitions during the update. This must be set by calling the
+ // Initialize method before calling any other method.
+ ExcluderInterface* excluder_;
+
+ // This is the current response object from Omaha.
+ OmahaResponse response_;
+
+ // Whether P2P is being used for downloading and sharing.
+ bool using_p2p_for_downloading_;
+ bool using_p2p_for_sharing_;
+
+ // Stores the P2P download URL, if one is used.
+ std::string p2p_url_;
+
+ // The cached value of |kPrefsP2PFirstAttemptTimestamp|.
+ base::Time p2p_first_attempt_timestamp_;
+
+ // The cached value of |kPrefsP2PNumAttempts|.
+ int p2p_num_attempts_;
+
+ // This stores a "signature" of the current response. The signature here
+ // refers to a subset of the current response from Omaha. Each update to
+ // this value is persisted so we resume from the same value in case of a
+ // process restart.
+ std::string response_signature_;
+
+ // The number of times we've tried to download the payload. This is
+ // incremented each time we download the payload successsfully or when we
+ // exhaust all failure limits for all URLs and are about to wrap around back
+ // to the first URL. Each update to this value is persisted so we resume from
+ // the same value in case of a process restart.
+ int payload_attempt_number_;
+
+ // The number of times we've tried to download the payload in full. This is
+ // incremented each time we download the payload in full successsfully or
+ // when we exhaust all failure limits for all URLs and are about to wrap
+ // around back to the first URL. Each update to this value is persisted so
+ // we resume from the same value in case of a process restart.
+ int full_payload_attempt_number_;
+
+ // The index of the current payload.
+ size_t payload_index_ = 0;
+
+ // The index of the current URL. This type is different from the one in the
+ // accessor methods because PrefsInterface supports only int64_t but we want
+ // to provide a stronger abstraction of uint32_t. Each update to this value
+ // is persisted so we resume from the same value in case of a process
+ // restart.
+ size_t url_index_;
+
+ // The count of failures encountered in the current attempt to download using
+ // the current URL (specified by url_index_). Each update to this value is
+ // persisted so we resume from the same value in case of a process restart.
+ int64_t url_failure_count_;
+
+ // The number of times we've switched URLs.
+ int32_t url_switch_count_;
+
+ // The current download source based on the current URL. This value is
+ // not persisted as it can be recomputed every time we update the URL.
+ // We're storing this so as not to recompute this on every few bytes of
+ // data we read from the socket.
+ DownloadSource current_download_source_;
+
+ // The number of different Omaha responses seen. Increases every time
+ // a new response is seen. Resets to 0 only when the system has been
+ // successfully updated.
+ int num_responses_seen_;
+
+ // The number of system reboots during an update attempt. Technically since
+ // we don't go out of our way to not update it when not attempting an update,
+ // also records the number of reboots before the next update attempt starts.
+ uint32_t num_reboots_;
+
+ // The timestamp until which we've to wait before attempting to download the
+ // payload again, so as to backoff repeated downloads.
+ base::Time backoff_expiry_time_;
+
+ // The most recently calculated value of the update duration.
+ base::TimeDelta update_duration_current_;
+
+ // The point in time (wall-clock) that the update was started.
+ base::Time update_timestamp_start_;
+
+ // The point in time (wall-clock) that the update ended. If the update
+ // is still in progress, this is set to the Epoch (e.g. 0).
+ base::Time update_timestamp_end_;
+
+ // The update duration uptime
+ base::TimeDelta update_duration_uptime_;
+
+ // The monotonic time when |update_duration_uptime_| was last set
+ base::Time update_duration_uptime_timestamp_;
+
+ // The number of bytes that have been downloaded for each source for each new
+ // update attempt. If we resume an update, we'll continue from the previous
+ // value, but if we get a new response or if the previous attempt failed,
+ // we'll reset this to 0 to start afresh. Each update to this value is
+ // persisted so we resume from the same value in case of a process restart.
+ // The extra index in the array is to no-op accidental access in case the
+ // return value from GetCurrentDownloadSource is used without validation.
+ uint64_t current_bytes_downloaded_[kNumDownloadSources + 1];
+
+ // The number of bytes that have been downloaded for each source since the
+ // the last successful update. This is used to compute the overhead we incur.
+ // Each update to this value is persisted so we resume from the same value in
+ // case of a process restart.
+ // The extra index in the array is to no-op accidental access in case the
+ // return value from GetCurrentDownloadSource is used without validation.
+ uint64_t total_bytes_downloaded_[kNumDownloadSources + 1];
+
+ // A small timespan used when comparing wall-clock times for coping
+ // with the fact that clocks drift and consequently are adjusted
+ // (either forwards or backwards) via NTP.
+ static const base::TimeDelta kDurationSlack;
+
+ // The ordered list of the subset of payload URL candidates which are
+ // allowed as per device policy.
+ std::vector<std::vector<std::string>> candidate_urls_;
+
+ // This stores whether rollback has happened since the last time device policy
+ // was available during update check. When this is set, we're preventing
+ // forced updates to avoid update-rollback loops.
+ bool rollback_happened_;
+
+ // This stores an excluded version set as part of rollback. When we rollback
+ // we store the version of the os from which we are rolling back from in order
+ // to guarantee that we do not re-update to it on the next au attempt after
+ // reboot.
+ std::string rollback_version_;
+
+ // The number of bytes downloaded per attempt.
+ int64_t attempt_num_bytes_downloaded_;
+
+ // The boot time when the attempt was started.
+ base::Time attempt_start_time_boot_;
+
+ // The monotonic time when the attempt was started.
+ base::Time attempt_start_time_monotonic_;
+
+ // The connection type when the attempt started.
+ metrics::ConnectionType attempt_connection_type_;
+
+ // Whether we're currently rolling back.
+ AttemptType attempt_type_;
+
+ // The current scattering wallclock-based wait period.
+ base::TimeDelta scattering_wait_period_;
+
+ // The current staging wallclock-based wait period.
+ base::TimeDelta staging_wait_period_;
+
+ DISALLOW_COPY_AND_ASSIGN(PayloadState);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_PAYLOAD_STATE_H_
diff --git a/cros/payload_state_interface.h b/cros/payload_state_interface.h
new file mode 100644
index 0000000..9ead650
--- /dev/null
+++ b/cros/payload_state_interface.h
@@ -0,0 +1,215 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_PAYLOAD_STATE_INTERFACE_H_
+#define UPDATE_ENGINE_CROS_PAYLOAD_STATE_INTERFACE_H_
+
+#include <string>
+
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/cros/omaha_response.h"
+
+namespace chromeos_update_engine {
+
+// Describes the methods that need to be implemented by the PayloadState class.
+// This interface has been carved out to support mocking of the PayloadState
+// object.
+class PayloadStateInterface {
+ public:
+ virtual ~PayloadStateInterface() = default;
+
+ // Sets the internal payload state based on the given Omaha response. This
+ // response could be the same or different from the one for which we've stored
+ // the internal state. If it's different, then this method resets all the
+ // internal state corresponding to the old response. Since the Omaha response
+ // has a lot of fields that are not related to payload state, it uses only
+ // a subset of the fields in the Omaha response to compare equality.
+ virtual void SetResponse(const OmahaResponse& response) = 0;
+
+ // This method should be called whenever we have completed downloading all
+ // the bytes of a payload and have verified that its size and hash match the
+ // expected values. We use this notificaiton to increment the payload attempt
+ // number so that the throttle the next attempt to download the same payload
+ // (in case there's an error in subsequent steps such as post-install)
+ // appropriately.
+ virtual void DownloadComplete() = 0;
+
+ // This method should be called whenever we receive new bytes from the
+ // network for the current payload. We use this notification to reset the
+ // failure count for a given URL since receipt of some bytes means we are
+ // able to make forward progress with the current URL.
+ virtual void DownloadProgress(size_t count) = 0;
+
+ // This method should be called every time we resume an update attempt.
+ virtual void UpdateResumed() = 0;
+
+ // This method should be called every time we begin a new update. This method
+ // should not be called when we resume an update from the previously
+ // downloaded point. This is used to reset the metrics for each new update.
+ virtual void UpdateRestarted() = 0;
+
+ // This method should be called once after an update attempt succeeds. This
+ // is when the relevant UMA metrics that are tracked on a per-update-basis
+ // are uploaded to the UMA server.
+ virtual void UpdateSucceeded() = 0;
+
+ // This method should be called whenever an update attempt fails with the
+ // given error code. We use this notification to update the payload state
+ // depending on the type of the error that happened.
+ virtual void UpdateFailed(ErrorCode error) = 0;
+
+ // This method should be called whenever a succeeded update is canceled, and
+ // thus can only be called after UpdateSucceeded(). This is currently used
+ // only for manual testing using the update_engine_client.
+ virtual void ResetUpdateStatus() = 0;
+
+ // This method should be called every time we initiate a Rollback.
+ virtual void Rollback() = 0;
+
+ // Sets the expectations to boot into the new version in the next reboot.
+ // This function is called every time a new update is marked as ready by
+ // UpdateSuccess(). |target_version_uid| is an unique identifier of the
+ // applied payload. It can be any string, as long as the same string is used
+ // for the same payload.
+ virtual void ExpectRebootInNewVersion(
+ const std::string& target_version_uid) = 0;
+
+ // Sets whether P2P is being used to download the update payload. This
+ // is used to keep track of download sources being used and should be called
+ // before the transfer begins.
+ virtual void SetUsingP2PForDownloading(bool value) = 0;
+
+ // Sets whether P2P is being used for sharing the update payloads.
+ virtual void SetUsingP2PForSharing(bool value) = 0;
+
+ // Returns true if we should backoff the current download attempt.
+ // False otherwise.
+ virtual bool ShouldBackoffDownload() = 0;
+
+ // Returns the currently stored response "signature". The signature is a
+ // subset of fields that are of interest to the PayloadState behavior.
+ virtual std::string GetResponseSignature() = 0;
+
+ // Returns the payload attempt number.
+ virtual int GetPayloadAttemptNumber() = 0;
+
+ // Returns the payload attempt number of the attempted full payload. Returns
+ // 0 for delta payloads.
+ virtual int GetFullPayloadAttemptNumber() = 0;
+
+ // Returns the current URL. Returns an empty string if there's no valid URL.
+ virtual std::string GetCurrentUrl() = 0;
+
+ // Returns the current URL's failure count.
+ virtual uint32_t GetUrlFailureCount() = 0;
+
+ // Returns the total number of times a new URL has been switched to
+ // for the current response.
+ virtual uint32_t GetUrlSwitchCount() = 0;
+
+ // Returns the total number of different responses seen since the
+ // last successful update.
+ virtual int GetNumResponsesSeen() = 0;
+
+ // Returns the expiry time for the current backoff period.
+ virtual base::Time GetBackoffExpiryTime() = 0;
+
+ // Returns the elapsed time used for this update, including time
+ // where the device is powered off and sleeping. If the
+ // update has not completed, returns the time spent so far.
+ virtual base::TimeDelta GetUpdateDuration() = 0;
+
+ // Returns the time used for this update not including time when
+ // the device is powered off or sleeping. If the update has not
+ // completed, returns the time spent so far.
+ virtual base::TimeDelta GetUpdateDurationUptime() = 0;
+
+ // Returns the number of bytes that have been downloaded for each source for
+ // each new update attempt. If we resume an update, we'll continue from the
+ // previous value, but if we get a new response or if the previous attempt
+ // failed, we'll reset this to 0 to start afresh.
+ virtual uint64_t GetCurrentBytesDownloaded(DownloadSource source) = 0;
+
+ // Returns the total number of bytes that have been downloaded for each
+ // source since the the last successful update. This is used to compute the
+ // overhead we incur.
+ virtual uint64_t GetTotalBytesDownloaded(DownloadSource source) = 0;
+
+ // Returns the reboot count for this update attempt.
+ virtual uint32_t GetNumReboots() = 0;
+
+ // Called at update_engine startup to do various house-keeping.
+ virtual void UpdateEngineStarted() = 0;
+
+ // Returns whether a rollback happened since the last update check with policy
+ // present.
+ virtual bool GetRollbackHappened() = 0;
+
+ // Sets whether rollback has happened on this device since the last update
+ // check where policy was available. This info is preserved over powerwash.
+ // This prevents forced updates happening on a rolled back device before
+ // device policy is available.
+ virtual void SetRollbackHappened(bool rollback_happened) = 0;
+
+ // Returns the version from before a rollback if our last update was a
+ // rollback.
+ virtual std::string GetRollbackVersion() = 0;
+
+ // Returns the value of number of attempts we've attempted to
+ // download the payload via p2p.
+ virtual int GetP2PNumAttempts() = 0;
+
+ // Returns the value of timestamp of the first time we've attempted
+ // to download the payload via p2p.
+ virtual base::Time GetP2PFirstAttemptTimestamp() = 0;
+
+ // Should be called every time we decide to use p2p for an update
+ // attempt. This is used to increase the p2p attempt counter and
+ // set the timestamp for first attempt.
+ virtual void P2PNewAttempt() = 0;
+
+ // Returns |true| if we are allowed to continue using p2p for
+ // downloading and |false| otherwise. This is done by recording
+ // and examining how many attempts have been done already as well
+ // as when the first attempt was.
+ virtual bool P2PAttemptAllowed() = 0;
+
+ // Gets the values previously set with SetUsingP2PForDownloading() and
+ // SetUsingP2PForSharing().
+ virtual bool GetUsingP2PForDownloading() const = 0;
+ virtual bool GetUsingP2PForSharing() const = 0;
+
+ // Returns the current (persisted) scattering wallclock-based wait period.
+ virtual base::TimeDelta GetScatteringWaitPeriod() = 0;
+
+ // Sets and persists the scattering wallclock-based wait period.
+ virtual void SetScatteringWaitPeriod(base::TimeDelta wait_period) = 0;
+
+ // Sets/gets the P2P download URL, if one is to be used.
+ virtual void SetP2PUrl(const std::string& url) = 0;
+ virtual std::string GetP2PUrl() const = 0;
+
+ // Switch to next payload.
+ virtual bool NextPayload() = 0;
+
+ // Sets and persists the staging wallclock-based wait period.
+ virtual void SetStagingWaitPeriod(base::TimeDelta wait_period) = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_PAYLOAD_STATE_INTERFACE_H_
diff --git a/cros/payload_state_unittest.cc b/cros/payload_state_unittest.cc
new file mode 100644
index 0000000..b48cff4
--- /dev/null
+++ b/cros/payload_state_unittest.cc
@@ -0,0 +1,1826 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/payload_state.h"
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/excluder_interface.h"
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/common/metrics_reporter_interface.h"
+#include "update_engine/common/mock_excluder.h"
+#include "update_engine/common/mock_prefs.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/fake_system_state.h"
+#include "update_engine/cros/omaha_request_action.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+using testing::_;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::StrictMock;
+
+namespace chromeos_update_engine {
+
+const char* kCurrentBytesDownloadedFromHttps =
+ "current-bytes-downloaded-from-HttpsServer";
+const char* kTotalBytesDownloadedFromHttps =
+ "total-bytes-downloaded-from-HttpsServer";
+const char* kCurrentBytesDownloadedFromHttp =
+ "current-bytes-downloaded-from-HttpServer";
+const char* kTotalBytesDownloadedFromHttp =
+ "total-bytes-downloaded-from-HttpServer";
+const char* kCurrentBytesDownloadedFromHttpPeer =
+ "current-bytes-downloaded-from-HttpPeer";
+const char* kTotalBytesDownloadedFromHttpPeer =
+ "total-bytes-downloaded-from-HttpPeer";
+
+static void SetupPayloadStateWith2Urls(string hash,
+ bool http_enabled,
+ bool is_delta_payload,
+ PayloadState* payload_state,
+ OmahaResponse* response) {
+ response->packages.clear();
+ response->packages.push_back({.payload_urls = {"http://test", "https://test"},
+ .size = 523456789,
+ .metadata_size = 558123,
+ .metadata_signature = "metasign",
+ .hash = hash,
+ .is_delta = is_delta_payload});
+ response->max_failure_count_per_url = 3;
+ payload_state->SetResponse(*response);
+ string stored_response_sign = payload_state->GetResponseSignature();
+
+ string expected_url_https_only =
+ " NumURLs = 1\n"
+ " Candidate Url0 = https://test\n";
+
+ string expected_urls_both =
+ " NumURLs = 2\n"
+ " Candidate Url0 = http://test\n"
+ " Candidate Url1 = https://test\n";
+
+ string expected_response_sign = base::StringPrintf(
+ "Payload 0:\n"
+ " Size = 523456789\n"
+ " Sha256 Hash = %s\n"
+ " Metadata Size = 558123\n"
+ " Metadata Signature = metasign\n"
+ " Is Delta = %d\n"
+ "%s"
+ "Max Failure Count Per Url = %d\n"
+ "Disable Payload Backoff = %d\n",
+ hash.c_str(),
+ response->packages[0].is_delta,
+ (http_enabled ? expected_urls_both : expected_url_https_only).c_str(),
+ response->max_failure_count_per_url,
+ response->disable_payload_backoff);
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+}
+
+class PayloadStateTest : public ::testing::Test {};
+
+TEST(PayloadStateTest, SetResponseWorksWithEmptyResponse) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1));
+ PayloadState payload_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ payload_state.SetResponse(response);
+ string stored_response_sign = payload_state.GetResponseSignature();
+ string expected_response_sign =
+ "Max Failure Count Per Url = 0\n"
+ "Disable Payload Backoff = 0\n";
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+ EXPECT_EQ("", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithSingleUrl) {
+ OmahaResponse response;
+ response.packages.push_back({.payload_urls = {"https://single.url.test"},
+ .size = 123456789,
+ .metadata_size = 58123,
+ .metadata_signature = "msign",
+ .hash = "hash"});
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1));
+ PayloadState payload_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ payload_state.SetResponse(response);
+ string stored_response_sign = payload_state.GetResponseSignature();
+ string expected_response_sign =
+ "Payload 0:\n"
+ " Size = 123456789\n"
+ " Sha256 Hash = hash\n"
+ " Metadata Size = 58123\n"
+ " Metadata Signature = msign\n"
+ " Is Delta = 0\n"
+ " NumURLs = 1\n"
+ " Candidate Url0 = https://single.url.test\n"
+ "Max Failure Count Per Url = 0\n"
+ "Disable Payload Backoff = 0\n";
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+ EXPECT_EQ("https://single.url.test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithMultipleUrls) {
+ OmahaResponse response;
+ response.packages.push_back({.payload_urls = {"http://multiple.url.test",
+ "https://multiple.url.test"},
+ .size = 523456789,
+ .metadata_size = 558123,
+ .metadata_signature = "metasign",
+ .hash = "rhash"});
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1));
+
+ PayloadState payload_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ payload_state.SetResponse(response);
+ string stored_response_sign = payload_state.GetResponseSignature();
+ string expected_response_sign =
+ "Payload 0:\n"
+ " Size = 523456789\n"
+ " Sha256 Hash = rhash\n"
+ " Metadata Size = 558123\n"
+ " Metadata Signature = metasign\n"
+ " Is Delta = 0\n"
+ " NumURLs = 2\n"
+ " Candidate Url0 = http://multiple.url.test\n"
+ " Candidate Url1 = https://multiple.url.test\n"
+ "Max Failure Count Per Url = 0\n"
+ "Disable Payload Backoff = 0\n";
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+ EXPECT_EQ("http://multiple.url.test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, CanAdvanceUrlIndexCorrectly) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ PayloadState payload_state;
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ // Payload attempt should start with 0 and then advance to 1.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(AtLeast(2));
+
+ // Reboots will be set
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, _)).Times(AtLeast(1));
+
+ // Url index should go from 0 to 1 twice.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 1)).Times(AtLeast(1));
+
+ // Failure count should be called each times url index is set, so that's
+ // 4 times for this test.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(4));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // This does a SetResponse which causes all the states to be set to 0 for
+ // the first time.
+ SetupPayloadStateWith2Urls(
+ "Hash1235", true, false, &payload_state, &response);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+ // Verify that on the first error, the URL index advances to 1.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Verify that on the next error, the URL index wraps around to 0.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+ // Verify that on the next error, it again advances to 1.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Verify that we switched URLs three times
+ EXPECT_EQ(3U, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, NewResponseResetsPayloadState) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Set the first response.
+ SetupPayloadStateWith2Urls(
+ "Hash5823", true, false, &payload_state, &response);
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+ // Advance the URL index to 1 by faking an error.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1U, payload_state.GetUrlSwitchCount());
+
+ // Now, slightly change the response and set it again.
+ SetupPayloadStateWith2Urls(
+ "Hash8225", true, false, &payload_state, &response);
+ EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+ // Fake an error again.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1U, payload_state.GetUrlSwitchCount());
+
+ // Return a third different response.
+ SetupPayloadStateWith2Urls(
+ "Hash9999", true, false, &payload_state, &response);
+ EXPECT_EQ(3, payload_state.GetNumResponsesSeen());
+
+ // Make sure the url index was reset to 0 because of the new response.
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(0U,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0U,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(
+ 0U, payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpsServer));
+ EXPECT_EQ(0U,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+}
+
+TEST(PayloadStateTest, AllCountersGetUpdatedProperlyOnErrorCodesAndEvents) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ int progress_bytes = 100;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(2));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 2))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(2));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 2))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(AtLeast(4));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(4));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 1)).Times(AtLeast(2));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(7));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 1))
+ .Times(AtLeast(2));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 2))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, progress_bytes))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kTotalBytesDownloadedFromHttp, progress_bytes))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ SetupPayloadStateWith2Urls(
+ "Hash5873", true, false, &payload_state, &response);
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+ // This should advance the URL index.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1U, payload_state.GetUrlSwitchCount());
+
+ // This should advance the failure count only.
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1U, payload_state.GetUrlSwitchCount());
+
+ // This should advance the failure count only.
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(2U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1U, payload_state.GetUrlSwitchCount());
+
+ // This should advance the URL index as we've reached the
+ // max failure count and reset the failure count for the new URL index.
+ // This should also wrap around the URL index and thus cause the payload
+ // attempt number to be incremented.
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(2U, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // This should advance the URL index.
+ payload_state.UpdateFailed(ErrorCode::kPayloadHashMismatchError);
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(3U, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // This should advance the URL index and payload attempt number due to
+ // wrap-around of URL index.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMissingError);
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(4U, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // This HTTP error code should only increase the failure count.
+ payload_state.UpdateFailed(static_cast<ErrorCode>(
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 404));
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(4U, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // And that failure count should be reset when we download some bytes
+ // afterwards.
+ payload_state.DownloadProgress(progress_bytes);
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(4U, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // Now, slightly change the response and set it again.
+ SetupPayloadStateWith2Urls(
+ "Hash8532", true, false, &payload_state, &response);
+ EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+ // Make sure the url index was reset to 0 because of the new response.
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulFullDownload) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(AtLeast(2));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ SetupPayloadStateWith2Urls(
+ "Hash8593", true, false, &payload_state, &response);
+
+ // This should just advance the payload attempt number;
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulDeltaDownload) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+
+ // kPrefsFullPayloadAttemptNumber is not incremented for delta payloads.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(1);
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ SetupPayloadStateWith2Urls("Hash8593", true, true, &payload_state, &response);
+
+ // This should just advance the payload attempt number;
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, SetResponseResetsInvalidUrlIndex) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash4427", true, false, &payload_state, &response);
+
+ // Generate enough events to advance URL index, failure count and
+ // payload attempt number all to 1.
+ payload_state.DownloadComplete();
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1U, payload_state.GetUrlSwitchCount());
+
+ // Now, simulate a corrupted url index on persisted store which gets
+ // loaded when update_engine restarts. Using a different prefs object
+ // so as to not bother accounting for the uninteresting calls above.
+ FakeSystemState fake_system_state2;
+ NiceMock<MockPrefs>* prefs2 = fake_system_state2.mock_prefs();
+ EXPECT_CALL(*prefs2, Exists(_)).WillRepeatedly(Return(true));
+ EXPECT_CALL(*prefs2, GetInt64(_, _)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsPayloadAttemptNumber, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsFullPayloadAttemptNumber, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlIndex, _))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(true)));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlFailureCount, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsUrlSwitchCount, _)).Times(AtLeast(1));
+
+ // Note: This will be a different payload object, but the response should
+ // have the same hash as before so as to not trivially reset because the
+ // response was different. We want to specifically test that even if the
+ // response is same, we should reset the state if we find it corrupted.
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state2));
+ SetupPayloadStateWith2Urls(
+ "Hash4427", true, false, &payload_state, &response);
+
+ // Make sure all counters get reset to 0 because of the corrupted URL index
+ // we supplied above.
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, NoBackoffInteractiveChecks) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state);
+ params.Init("", "", {.interactive = true});
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash6437", true, false, &payload_state, &response);
+
+ // Simulate two failures (enough to cause payload backoff) and check
+ // again that we're ready to re-download without any backoff as this is
+ // an interactive check.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, NoBackoffForP2PUpdates) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state);
+ params.Init("", "", {});
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash6437", true, false, &payload_state, &response);
+
+ // Simulate two failures (enough to cause payload backoff) and check
+ // again that we're ready to re-download without any backoff as this is
+ // an interactive check.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ // Set p2p url.
+ payload_state.SetUsingP2PForDownloading(true);
+ payload_state.SetP2PUrl("http://mypeer:52909/path/to/file");
+ // Should not backoff for p2p updates.
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+ payload_state.SetP2PUrl("");
+ // No actual p2p update if no url is provided.
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, NoBackoffForDeltaPayloads) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, true, &payload_state, &response);
+
+ // Simulate a successful download and see that we're ready to download
+ // again without any backoff as this is a delta payload.
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+ // Simulate two failures (enough to cause payload backoff) and check
+ // again that we're ready to re-download without any backoff as this is
+ // a delta payload.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+static void CheckPayloadBackoffState(PayloadState* payload_state,
+ int expected_attempt_number,
+ TimeDelta expected_days) {
+ payload_state->DownloadComplete();
+ EXPECT_EQ(expected_attempt_number,
+ payload_state->GetFullPayloadAttemptNumber());
+ EXPECT_TRUE(payload_state->ShouldBackoffDownload());
+ Time backoff_expiry_time = payload_state->GetBackoffExpiryTime();
+ // Add 1 hour extra to the 6 hour fuzz check to tolerate edge cases.
+ TimeDelta max_fuzz_delta = TimeDelta::FromHours(7);
+ Time expected_min_time = Time::Now() + expected_days - max_fuzz_delta;
+ Time expected_max_time = Time::Now() + expected_days + max_fuzz_delta;
+ EXPECT_LT(expected_min_time.ToInternalValue(),
+ backoff_expiry_time.ToInternalValue());
+ EXPECT_GT(expected_max_time.ToInternalValue(),
+ backoff_expiry_time.ToInternalValue());
+}
+
+TEST(PayloadStateTest, BackoffPeriodsAreInCorrectRange) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash8939", true, false, &payload_state, &response);
+
+ CheckPayloadBackoffState(&payload_state, 1, TimeDelta::FromDays(1));
+ CheckPayloadBackoffState(&payload_state, 2, TimeDelta::FromDays(2));
+ CheckPayloadBackoffState(&payload_state, 3, TimeDelta::FromDays(4));
+ CheckPayloadBackoffState(&payload_state, 4, TimeDelta::FromDays(8));
+ CheckPayloadBackoffState(&payload_state, 5, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 6, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 7, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 8, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 9, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 10, TimeDelta::FromDays(16));
+}
+
+TEST(PayloadStateTest, BackoffLogicCanBeDisabled) {
+ OmahaResponse response;
+ response.disable_payload_backoff = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash8939", true, false, &payload_state, &response);
+
+ // Simulate a successful download and see that we are ready to download
+ // again without any backoff.
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+ // Test again, this time by simulating two errors that would cause
+ // the payload attempt number to increment due to wrap around. And
+ // check that we are still ready to re-download without any backoff.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, BytesDownloadedMetricsGetAddedToCorrectSources) {
+ OmahaResponse response;
+ response.disable_payload_backoff = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ uint64_t https_total = 0;
+ uint64_t http_total = 0;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash3286", true, false, &payload_state, &response);
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+ // Simulate a previous attempt with in order to set an initial non-zero value
+ // for the total bytes downloaded for HTTP.
+ uint64_t prev_chunk = 323456789;
+ http_total += prev_chunk;
+ payload_state.DownloadProgress(prev_chunk);
+
+ // Ensure that the initial values for HTTP reflect this attempt.
+ EXPECT_EQ(prev_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+
+ // Change the response hash so as to simulate a new response which will
+ // reset the current bytes downloaded, but not the total bytes downloaded.
+ SetupPayloadStateWith2Urls(
+ "Hash9904", true, false, &payload_state, &response);
+ EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+ // First, simulate successful download of a few bytes over HTTP.
+ uint64_t first_chunk = 5000000;
+ http_total += first_chunk;
+ payload_state.DownloadProgress(first_chunk);
+ // Test that first all progress is made on HTTP and none on HTTPS.
+ EXPECT_EQ(first_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(
+ 0U, payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpsServer));
+ EXPECT_EQ(https_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ // Simulate an error that'll cause the url index to point to https.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+
+ // Test that no new progress is made on HTTP and new progress is on HTTPS.
+ uint64_t second_chunk = 23456789;
+ https_total += second_chunk;
+ payload_state.DownloadProgress(second_chunk);
+ EXPECT_EQ(first_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(
+ second_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpsServer));
+ EXPECT_EQ(https_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ // Simulate error to go back to http.
+ payload_state.UpdateFailed(error);
+ uint64_t third_chunk = 32345678;
+ uint64_t http_chunk = first_chunk + third_chunk;
+ http_total += third_chunk;
+ payload_state.DownloadProgress(third_chunk);
+
+ // Test that third chunk is again back on HTTP. HTTPS remains on second chunk.
+ EXPECT_EQ(http_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(
+ second_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpsServer));
+ EXPECT_EQ(https_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ // Simulate error (will cause URL switch), set p2p is to be used and
+ // then do 42MB worth of progress
+ payload_state.UpdateFailed(error);
+ payload_state.SetUsingP2PForDownloading(true);
+ uint64_t p2p_total = 42 * 1000 * 1000;
+ payload_state.DownloadProgress(p2p_total);
+
+ EXPECT_EQ(p2p_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpPeer));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportSuccessfulUpdateMetrics(
+ 1, _, kPayloadTypeFull, _, _, 314, _, _, _, 3));
+
+ payload_state.UpdateSucceeded();
+
+ // Make sure the metrics are reset after a successful update.
+ EXPECT_EQ(0U,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0U,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(
+ 0U, payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpsServer));
+ EXPECT_EQ(0U,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+ EXPECT_EQ(0, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, DownloadSourcesUsedIsCorrect) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash3286", true, false, &payload_state, &response);
+
+ // Simulate progress in order to mark HTTP as one of the sources used.
+ uint64_t num_bytes = 42 * 1000 * 1000;
+ payload_state.DownloadProgress(num_bytes);
+
+ // Check that this was done via HTTP.
+ EXPECT_EQ(num_bytes,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(num_bytes,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+
+ // Check that only HTTP is reported as a download source.
+ int64_t total_bytes[kNumDownloadSources] = {};
+ total_bytes[kDownloadSourceHttpServer] = num_bytes;
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportSuccessfulUpdateMetrics(
+ _,
+ _,
+ _,
+ _,
+ test_utils::DownloadSourceMatcher(total_bytes),
+ _,
+ _,
+ _,
+ _,
+ _))
+ .Times(1);
+
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, RestartingUpdateResetsMetrics) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Set the first response.
+ SetupPayloadStateWith2Urls(
+ "Hash5823", true, false, &payload_state, &response);
+
+ uint64_t num_bytes = 10000;
+ payload_state.DownloadProgress(num_bytes);
+ EXPECT_EQ(num_bytes,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(num_bytes,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(
+ 0U, payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpsServer));
+ EXPECT_EQ(0U,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ payload_state.UpdateRestarted();
+ // Make sure the current bytes downloaded is reset, but not the total bytes.
+ EXPECT_EQ(0U,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(num_bytes,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+}
+
+TEST(PayloadStateTest, NumRebootsIncrementsCorrectly) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AtLeast(0));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 1)).Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ payload_state.UpdateRestarted();
+ EXPECT_EQ(0U, payload_state.GetNumReboots());
+
+ fake_system_state.set_system_rebooted(true);
+ payload_state.UpdateResumed();
+ // Num reboots should be incremented because system rebooted detected.
+ EXPECT_EQ(1U, payload_state.GetNumReboots());
+
+ fake_system_state.set_system_rebooted(false);
+ payload_state.UpdateResumed();
+ // Num reboots should now be 1 as reboot was not detected.
+ EXPECT_EQ(1U, payload_state.GetNumReboots());
+
+ // Restart the update again to verify we set the num of reboots back to 0.
+ payload_state.UpdateRestarted();
+ EXPECT_EQ(0U, payload_state.GetNumReboots());
+}
+
+TEST(PayloadStateTest, RollbackHappened) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ NiceMock<MockPrefs>* mock_powerwash_safe_prefs =
+ fake_system_state.mock_powerwash_safe_prefs();
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Verify pre-conditions are good.
+ EXPECT_FALSE(payload_state.GetRollbackHappened());
+
+ // Set to true.
+ EXPECT_CALL(*mock_powerwash_safe_prefs,
+ SetBoolean(kPrefsRollbackHappened, true));
+ payload_state.SetRollbackHappened(true);
+ EXPECT_TRUE(payload_state.GetRollbackHappened());
+
+ // Set to false.
+ EXPECT_CALL(*mock_powerwash_safe_prefs, Delete(kPrefsRollbackHappened));
+ payload_state.SetRollbackHappened(false);
+ EXPECT_FALSE(payload_state.GetRollbackHappened());
+
+ // Let's verify we can reload it correctly.
+ EXPECT_CALL(*mock_powerwash_safe_prefs, GetBoolean(kPrefsRollbackHappened, _))
+ .WillOnce(DoAll(SetArgPointee<1>(true), Return(true)));
+ EXPECT_CALL(*mock_powerwash_safe_prefs,
+ SetBoolean(kPrefsRollbackHappened, true));
+ payload_state.LoadRollbackHappened();
+ EXPECT_TRUE(payload_state.GetRollbackHappened());
+}
+
+TEST(PayloadStateTest, RollbackVersion) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ NiceMock<MockPrefs>* mock_powerwash_safe_prefs =
+ fake_system_state.mock_powerwash_safe_prefs();
+
+ // Mock out the os version and make sure it's excluded correctly.
+ string rollback_version = "2345.0.0";
+ OmahaRequestParams params(&fake_system_state);
+ params.Init(rollback_version, "", {});
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Verify pre-conditions are good.
+ EXPECT_TRUE(payload_state.GetRollbackVersion().empty());
+
+ EXPECT_CALL(*mock_powerwash_safe_prefs,
+ SetString(kPrefsRollbackVersion, rollback_version));
+ payload_state.Rollback();
+
+ EXPECT_EQ(rollback_version, payload_state.GetRollbackVersion());
+
+ // Change it up a little and verify we load it correctly.
+ rollback_version = "2345.0.1";
+ // Let's verify we can reload it correctly.
+ EXPECT_CALL(*mock_powerwash_safe_prefs, GetString(kPrefsRollbackVersion, _))
+ .WillOnce(DoAll(SetArgPointee<1>(rollback_version), Return(true)));
+ EXPECT_CALL(*mock_powerwash_safe_prefs,
+ SetString(kPrefsRollbackVersion, rollback_version));
+ payload_state.LoadRollbackVersion();
+ EXPECT_EQ(rollback_version, payload_state.GetRollbackVersion());
+
+ // Check that we report only UpdateEngine.Rollback.* metrics in
+ // UpdateSucceeded().
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportRollbackMetrics(metrics::RollbackResult::kSuccess))
+ .Times(1);
+
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, DurationsAreCorrect) {
+ OmahaResponse response;
+ response.packages.resize(1);
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ // Set the clock to a well-known time - 1 second on the wall-clock
+ // and 2 seconds on the monotonic clock
+ fake_clock.SetWallclockTime(Time::FromInternalValue(1000000));
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(2000000));
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Check that durations are correct for a successful update where
+ // time has advanced 7 seconds on the wall clock and 4 seconds on
+ // the monotonic clock.
+ SetupPayloadStateWith2Urls(
+ "Hash8593", true, false, &payload_state, &response);
+ fake_clock.SetWallclockTime(Time::FromInternalValue(8000000));
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(6000000));
+ payload_state.UpdateSucceeded();
+ EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 7000000);
+ EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 4000000);
+
+ // Check that durations are reset when a new response comes in.
+ SetupPayloadStateWith2Urls(
+ "Hash8594", true, false, &payload_state, &response);
+ EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 0);
+ EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 0);
+
+ // Advance time a bit (10 secs), simulate download progress and
+ // check that durations are updated.
+ fake_clock.SetWallclockTime(Time::FromInternalValue(18000000));
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(16000000));
+ payload_state.DownloadProgress(10);
+ EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 10000000);
+ EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 10000000);
+
+ // Now simulate a reboot by resetting monotonic time (to 5000) and
+ // creating a new PayloadState object and check that we load the
+ // durations correctly (e.g. they are the same as before).
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(5000));
+ PayloadState payload_state2;
+ EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+ payload_state2.SetResponse(response);
+ EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 10000000);
+ EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(),
+ 10000000);
+
+ // Advance wall-clock by 7 seconds and monotonic clock by 6 seconds
+ // and check that the durations are increased accordingly.
+ fake_clock.SetWallclockTime(Time::FromInternalValue(25000000));
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(6005000));
+ payload_state2.UpdateSucceeded();
+ EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 17000000);
+ EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(),
+ 16000000);
+}
+
+TEST(PayloadStateTest, RebootAfterSuccessfulUpdateTest) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ // Set the clock to a well-known time (t = 30 seconds).
+ fake_clock.SetMonotonicTime(
+ Time::FromInternalValue(30 * Time::kMicrosecondsPerSecond));
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Make the update succeed.
+ SetupPayloadStateWith2Urls(
+ "Hash8593", true, false, &payload_state, &response);
+ payload_state.UpdateSucceeded();
+
+ // Check that the marker was written.
+ EXPECT_TRUE(fake_prefs.Exists(kPrefsSystemUpdatedMarker));
+
+ // Now simulate a reboot and set the wallclock time to a later point
+ // (t = 500 seconds). We do this by using a new PayloadState object
+ // and checking that it emits the right UMA metric with the right
+ // value.
+ fake_clock.SetMonotonicTime(
+ Time::FromInternalValue(500 * Time::kMicrosecondsPerSecond));
+ PayloadState payload_state2;
+ EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+
+ // Expect 500 - 30 seconds = 470 seconds ~= 7 min 50 sec
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportTimeToReboot(7));
+ fake_system_state.set_system_rebooted(true);
+
+ payload_state2.UpdateEngineStarted();
+
+ // Check that the marker was nuked.
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsSystemUpdatedMarker));
+}
+
+TEST(PayloadStateTest, RestartAfterCrash) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ testing::StrictMock<MockMetricsReporter> mock_metrics_reporter;
+ fake_system_state.set_metrics_reporter(&mock_metrics_reporter);
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Only the |kPrefsAttemptInProgress| state variable should be read.
+ EXPECT_CALL(*prefs, Exists(_)).Times(0);
+ EXPECT_CALL(*prefs, SetString(_, _)).Times(0);
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(0);
+ EXPECT_CALL(*prefs, SetBoolean(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetString(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetInt64(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetBoolean(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetBoolean(kPrefsAttemptInProgress, _));
+
+ // Simulate an update_engine restart without a reboot.
+ fake_system_state.set_system_rebooted(false);
+
+ payload_state.UpdateEngineStarted();
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsNoReporting) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ // If there's no marker at startup, ensure we don't report a metric.
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportAbnormallyTerminatedUpdateAttemptMetrics())
+ .Times(0);
+ payload_state.UpdateEngineStarted();
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsReported) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+
+ // If we have a marker at startup, ensure it's reported and the
+ // marker is then cleared.
+ fake_system_state.set_prefs(&fake_prefs);
+ fake_prefs.SetBoolean(kPrefsAttemptInProgress, true);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportAbnormallyTerminatedUpdateAttemptMetrics())
+ .Times(1);
+ payload_state.UpdateEngineStarted();
+
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsClearedOnSucceess) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+
+ // Make sure the marker is written and cleared during an attempt and
+ // also that we DO NOT emit the metric (since the attempt didn't end
+ // abnormally).
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ OmahaResponse response;
+ response.packages.resize(1);
+ payload_state.SetResponse(response);
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportAbnormallyTerminatedUpdateAttemptMetrics())
+ .Times(0);
+
+ // Attempt not in progress, should be clear.
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+
+ payload_state.UpdateRestarted();
+
+ // Attempt not in progress, should be set.
+ EXPECT_TRUE(fake_prefs.Exists(kPrefsAttemptInProgress));
+
+ payload_state.UpdateSucceeded();
+
+ // Attempt not in progress, should be clear.
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+}
+
+TEST(PayloadStateTest, CandidateUrlsComputedCorrectly) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ policy::MockDevicePolicy disable_http_policy;
+ fake_system_state.set_device_policy(&disable_http_policy);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Test with no device policy. Should default to allowing http.
+ EXPECT_CALL(disable_http_policy, GetHttpDownloadsEnabled(_))
+ .WillRepeatedly(Return(false));
+
+ // Set the first response.
+ SetupPayloadStateWith2Urls(
+ "Hash8433", true, false, &payload_state, &response);
+
+ // Check that we use the HTTP URL since there is no value set for allowing
+ // http.
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+ // Test with device policy not allowing http updates.
+ EXPECT_CALL(disable_http_policy, GetHttpDownloadsEnabled(_))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(false), Return(true)));
+
+ // Reset state and set again.
+ SetupPayloadStateWith2Urls(
+ "Hash8433", false, false, &payload_state, &response);
+
+ // Check that we skip the HTTP URL and use only the HTTPS url.
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Advance the URL index to 1 by faking an error.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+
+ // Check that we still skip the HTTP URL and use only the HTTPS url.
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
+
+ // Now, slightly change the response and set it again.
+ SetupPayloadStateWith2Urls(
+ "Hash2399", false, false, &payload_state, &response);
+
+ // Check that we still skip the HTTP URL and use only the HTTPS url.
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Now, pretend that the HTTP policy is turned on. We want to make sure
+ // the new policy is honored.
+ policy::MockDevicePolicy enable_http_policy;
+ fake_system_state.set_device_policy(&enable_http_policy);
+ EXPECT_CALL(enable_http_policy, GetHttpDownloadsEnabled(_))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(true), Return(true)));
+
+ // Now, set the same response using the same hash
+ // so that we can test that the state is reset not because of the
+ // hash but because of the policy change which results in candidate url
+ // list change.
+ SetupPayloadStateWith2Urls(
+ "Hash2399", true, false, &payload_state, &response);
+
+ // Check that we use the HTTP URL now and the failure count is reset.
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+
+ // Fake a failure and see if we're moving over to the HTTPS url and update
+ // the URL switch count properly.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1U, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsDelta) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, true, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportSuccessfulUpdateMetrics(
+ _, _, kPayloadTypeDelta, _, _, _, _, _, _, _));
+ payload_state.UpdateSucceeded();
+
+ // Mock the request to a request where the delta was disabled but Omaha sends
+ // a delta anyway and test again.
+ OmahaRequestParams params(&fake_system_state);
+ params.set_delta_okay(false);
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, true, &payload_state, &response);
+
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportSuccessfulUpdateMetrics(
+ _, _, kPayloadTypeDelta, _, _, _, _, _, _, _));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsForcedFull) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ // Mock the request to a request where the delta was disabled.
+ OmahaRequestParams params(&fake_system_state);
+ params.set_delta_okay(false);
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash6437", true, false, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportSuccessfulUpdateMetrics(
+ _, _, kPayloadTypeForcedFull, _, _, _, _, _, _, _));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsFull) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash6437", true, false, &payload_state, &response);
+
+ // Mock the request to a request where the delta is enabled, although the
+ // result is full.
+ OmahaRequestParams params(&fake_system_state);
+ params.set_delta_okay(true);
+ fake_system_state.set_request_params(¶ms);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportSuccessfulUpdateMetrics(
+ _, _, kPayloadTypeFull, _, _, _, _, _, _, _));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, RebootAfterUpdateFailedMetric) {
+ FakeSystemState fake_system_state;
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+ fake_system_state.set_prefs(&fake_prefs);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash3141", true, false, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ payload_state.UpdateSucceeded();
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ // Reboot into the same environment to get an UMA metric with a value of 1.
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportFailedUpdateCount(1));
+ payload_state.ReportFailedBootIfNeeded();
+ Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_reporter());
+
+ // Simulate a second update and reboot into the same environment, this should
+ // send a value of 2.
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportFailedUpdateCount(2));
+ payload_state.ReportFailedBootIfNeeded();
+ Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_reporter());
+
+ // Simulate a third failed reboot to new version, but this time for a
+ // different payload. This should send a value of 1 this time.
+ payload_state.ExpectRebootInNewVersion("Version:3141592");
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportFailedUpdateCount(1));
+ payload_state.ReportFailedBootIfNeeded();
+ Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_reporter());
+}
+
+TEST(PayloadStateTest, RebootAfterUpdateSucceed) {
+ FakeSystemState fake_system_state;
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+ fake_system_state.set_prefs(&fake_prefs);
+
+ FakeBootControl* fake_boot_control = fake_system_state.fake_boot_control();
+ fake_boot_control->SetCurrentSlot(0);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash3141", true, false, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ payload_state.UpdateSucceeded();
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ // Change the BootDevice to a different one, no metric should be sent.
+ fake_boot_control->SetCurrentSlot(1);
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportFailedUpdateCount(_))
+ .Times(0);
+ payload_state.ReportFailedBootIfNeeded();
+
+ // A second reboot in either partition should not send a metric.
+ payload_state.ReportFailedBootIfNeeded();
+ fake_boot_control->SetCurrentSlot(0);
+ payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, RebootAfterCanceledUpdate) {
+ FakeSystemState fake_system_state;
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash3141", true, false, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ payload_state.UpdateSucceeded();
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportFailedUpdateCount(_))
+ .Times(0);
+
+ // Cancel the applied update.
+ payload_state.ResetUpdateStatus();
+
+ // Simulate a reboot.
+ payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+ ReportFailedUpdateCount(_))
+ .Times(0);
+
+ // Simulate a reboot in this environment.
+ payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, DisallowP2PAfterTooManyAttempts) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+ fake_system_state.set_prefs(&fake_prefs);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash8593", true, false, &payload_state, &response);
+
+ // Should allow exactly kMaxP2PAttempts...
+ for (int n = 0; n < kMaxP2PAttempts; n++) {
+ payload_state.P2PNewAttempt();
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+ }
+ // ... but not more than that.
+ payload_state.P2PNewAttempt();
+ EXPECT_FALSE(payload_state.P2PAttemptAllowed());
+}
+
+TEST(PayloadStateTest, DisallowP2PAfterDeadline) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash8593", true, false, &payload_state, &response);
+
+ // Set the clock to 1 second.
+ Time epoch = Time::FromInternalValue(1000000);
+ fake_clock.SetWallclockTime(epoch);
+
+ // Do an attempt - this will set the timestamp.
+ payload_state.P2PNewAttempt();
+
+ // Check that the timestamp equals what we just set.
+ EXPECT_EQ(epoch, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Time hasn't advanced - this should work.
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+ // Set clock to half the deadline - this should work.
+ fake_clock.SetWallclockTime(
+ epoch + TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds) / 2);
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+ // Check that the first attempt timestamp hasn't changed just
+ // because the wall-clock time changed.
+ EXPECT_EQ(epoch, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Set clock to _just_ before the deadline - this should work.
+ fake_clock.SetWallclockTime(
+ epoch + TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds - 1));
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+ // Set clock to _just_ after the deadline - this should not work.
+ fake_clock.SetWallclockTime(
+ epoch + TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds + 1));
+ EXPECT_FALSE(payload_state.P2PAttemptAllowed());
+}
+
+TEST(PayloadStateTest, P2PStateVarsInitialValue) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash8593", true, false, &payload_state, &response);
+
+ Time null_time = Time();
+ EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp());
+ EXPECT_EQ(0, payload_state.GetP2PNumAttempts());
+}
+
+TEST(PayloadStateTest, P2PStateVarsArePersisted) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash8593", true, false, &payload_state, &response);
+
+ // Set the clock to something known.
+ Time time = Time::FromInternalValue(12345);
+ fake_clock.SetWallclockTime(time);
+
+ // New p2p attempt - as a side-effect this will update the p2p state vars.
+ payload_state.P2PNewAttempt();
+ EXPECT_EQ(1, payload_state.GetP2PNumAttempts());
+ EXPECT_EQ(time, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Now create a new PayloadState and check that it loads the state
+ // vars correctly.
+ PayloadState payload_state2;
+ EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+ EXPECT_EQ(1, payload_state2.GetP2PNumAttempts());
+ EXPECT_EQ(time, payload_state2.GetP2PFirstAttemptTimestamp());
+}
+
+TEST(PayloadStateTest, P2PStateVarsAreClearedOnNewResponse) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls(
+ "Hash8593", true, false, &payload_state, &response);
+
+ // Set the clock to something known.
+ Time time = Time::FromInternalValue(12345);
+ fake_clock.SetWallclockTime(time);
+
+ // New p2p attempt - as a side-effect this will update the p2p state vars.
+ payload_state.P2PNewAttempt();
+ EXPECT_EQ(1, payload_state.GetP2PNumAttempts());
+ EXPECT_EQ(time, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Set a new response...
+ SetupPayloadStateWith2Urls(
+ "Hash9904", true, false, &payload_state, &response);
+
+ // ... and check that it clears the P2P state vars.
+ Time null_time = Time();
+ EXPECT_EQ(0, payload_state.GetP2PNumAttempts());
+ EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp());
+}
+
+TEST(PayloadStateTest, NextPayloadResetsUrlIndex) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ StrictMock<MockExcluder> mock_excluder;
+ EXPECT_CALL(*fake_system_state.mock_update_attempter(), GetExcluder())
+ .WillOnce(Return(&mock_excluder));
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ OmahaResponse response;
+ response.packages.push_back(
+ {.payload_urls = {"http://test1a", "http://test2a"},
+ .size = 123456789,
+ .metadata_size = 58123,
+ .metadata_signature = "msign",
+ .hash = "hash"});
+ response.packages.push_back({.payload_urls = {"http://test1b"},
+ .size = 123456789,
+ .metadata_size = 58123,
+ .metadata_signature = "msign",
+ .hash = "hash"});
+ payload_state.SetResponse(response);
+
+ EXPECT_EQ(payload_state.GetCurrentUrl(), "http://test1a");
+ payload_state.IncrementUrlIndex();
+ EXPECT_EQ(payload_state.GetCurrentUrl(), "http://test2a");
+
+ EXPECT_TRUE(payload_state.NextPayload());
+ EXPECT_EQ(payload_state.GetCurrentUrl(), "http://test1b");
+}
+
+TEST(PayloadStateTest, ExcludeNoopForNonExcludables) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ StrictMock<MockExcluder> mock_excluder;
+ EXPECT_CALL(*fake_system_state.mock_update_attempter(), GetExcluder())
+ .WillOnce(Return(&mock_excluder));
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ OmahaResponse response;
+ response.packages.push_back(
+ {.payload_urls = {"http://test1a", "http://test2a"},
+ .size = 123456789,
+ .metadata_size = 58123,
+ .metadata_signature = "msign",
+ .hash = "hash",
+ .can_exclude = false});
+ payload_state.SetResponse(response);
+
+ EXPECT_CALL(mock_excluder, Exclude(_)).Times(0);
+ payload_state.ExcludeCurrentPayload();
+}
+
+TEST(PayloadStateTest, ExcludeOnlyCanExcludables) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ StrictMock<MockExcluder> mock_excluder;
+ EXPECT_CALL(*fake_system_state.mock_update_attempter(), GetExcluder())
+ .WillOnce(Return(&mock_excluder));
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ OmahaResponse response;
+ response.packages.push_back(
+ {.payload_urls = {"http://test1a", "http://test2a"},
+ .size = 123456789,
+ .metadata_size = 58123,
+ .metadata_signature = "msign",
+ .hash = "hash",
+ .can_exclude = true});
+ payload_state.SetResponse(response);
+
+ EXPECT_CALL(mock_excluder, Exclude(utils::GetExclusionName("http://test1a")))
+ .WillOnce(Return(true));
+ payload_state.ExcludeCurrentPayload();
+}
+
+TEST(PayloadStateTest, IncrementFailureExclusionTest) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ StrictMock<MockExcluder> mock_excluder;
+ EXPECT_CALL(*fake_system_state.mock_update_attempter(), GetExcluder())
+ .WillOnce(Return(&mock_excluder));
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ OmahaResponse response;
+ // Critical package.
+ response.packages.push_back(
+ {.payload_urls = {"http://crit-test1a", "http://crit-test2a"},
+ .size = 123456789,
+ .metadata_size = 58123,
+ .metadata_signature = "msign",
+ .hash = "hash",
+ .can_exclude = false});
+ // Non-critical package.
+ response.packages.push_back(
+ {.payload_urls = {"http://test1a", "http://test2a"},
+ .size = 123456789,
+ .metadata_size = 58123,
+ .metadata_signature = "msign",
+ .hash = "hash",
+ .can_exclude = true});
+ response.max_failure_count_per_url = 2;
+ payload_state.SetResponse(response);
+
+ // Critical package won't be excluded.
+ // Increment twice as failure count allowed per URL is set to 2.
+ payload_state.IncrementFailureCount();
+ payload_state.IncrementFailureCount();
+
+ EXPECT_TRUE(payload_state.NextPayload());
+
+ // First increment failure should not exclude.
+ payload_state.IncrementFailureCount();
+
+ // Second increment failure should exclude.
+ EXPECT_CALL(mock_excluder, Exclude(utils::GetExclusionName("http://test1a")))
+ .WillOnce(Return(true));
+ payload_state.IncrementFailureCount();
+}
+
+TEST(PayloadStateTest, HaltExclusionPostPayloadExhaustion) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ StrictMock<MockExcluder> mock_excluder;
+ EXPECT_CALL(*fake_system_state.mock_update_attempter(), GetExcluder())
+ .WillOnce(Return(&mock_excluder));
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ OmahaResponse response;
+ // Non-critical package.
+ response.packages.push_back(
+ {.payload_urls = {"http://test1a", "http://test2a"},
+ .size = 123456789,
+ .metadata_size = 58123,
+ .metadata_signature = "msign",
+ .hash = "hash",
+ .can_exclude = true});
+ payload_state.SetResponse(response);
+
+ // Exclusion should be called when excluded.
+ EXPECT_CALL(mock_excluder, Exclude(utils::GetExclusionName("http://test1a")))
+ .WillOnce(Return(true));
+ payload_state.ExcludeCurrentPayload();
+
+ // No more paylods to go through.
+ EXPECT_FALSE(payload_state.NextPayload());
+
+ // Exclusion should not be called as all |Payload|s are exhausted.
+ payload_state.ExcludeCurrentPayload();
+}
+
+TEST(PayloadStateTest, NonInfinitePayloadIndexIncrement) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ payload_state.SetResponse({});
+
+ EXPECT_FALSE(payload_state.NextPayload());
+ int payload_index = payload_state.payload_index_;
+
+ EXPECT_FALSE(payload_state.NextPayload());
+ EXPECT_EQ(payload_index, payload_state.payload_index_);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/platform_constants_chromeos.cc b/cros/platform_constants_chromeos.cc
new file mode 100644
index 0000000..fe94a45
--- /dev/null
+++ b/cros/platform_constants_chromeos.cc
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/platform_constants.h"
+
+namespace chromeos_update_engine {
+namespace constants {
+
+const char kOmahaDefaultProductionURL[] =
+ "https://tools.google.com/service/update2";
+const char kOmahaDefaultAUTestURL[] =
+ "https://omaha-qa.sandbox.google.com/service/update2";
+const char kOmahaUpdaterID[] = "ChromeOSUpdateEngine";
+const char kOmahaPlatformName[] = "Chrome OS";
+const char kUpdatePayloadPublicKeyPath[] =
+ "/usr/share/update_engine/update-payload-key.pub.pem";
+const char kUpdateCertificatesPath[] = "";
+const char kCACertificatesPath[] = "/usr/share/chromeos-ca-certificates";
+const char kOmahaResponseDeadlineFile[] = "/tmp/update-check-response-deadline";
+// This directory is wiped during powerwash.
+const char kNonVolatileDirectory[] = "/var/lib/update_engine";
+const char kPostinstallMountOptions[] = "";
+
+} // namespace constants
+} // namespace chromeos_update_engine
diff --git a/cros/power_manager_chromeos.cc b/cros/power_manager_chromeos.cc
new file mode 100644
index 0000000..c1a2859
--- /dev/null
+++ b/cros/power_manager_chromeos.cc
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/power_manager_chromeos.h"
+
+#include <memory>
+
+#include <power_manager/dbus-constants.h>
+#include <power_manager/dbus-proxies.h>
+
+#include "update_engine/cros/dbus_connection.h"
+
+namespace chromeos_update_engine {
+
+namespace power_manager {
+std::unique_ptr<PowerManagerInterface> CreatePowerManager() {
+ return std::unique_ptr<PowerManagerInterface>(new PowerManagerChromeOS());
+}
+} // namespace power_manager
+
+PowerManagerChromeOS::PowerManagerChromeOS()
+ : power_manager_proxy_(DBusConnection::Get()->GetDBus()) {}
+
+bool PowerManagerChromeOS::RequestReboot() {
+ LOG(INFO) << "Calling " << ::power_manager::kPowerManagerInterface << "."
+ << ::power_manager::kRequestRestartMethod;
+ brillo::ErrorPtr error;
+ return power_manager_proxy_.RequestRestart(
+ ::power_manager::REQUEST_RESTART_FOR_UPDATE,
+ "update_engine applying update",
+ &error);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/power_manager_chromeos.h b/cros/power_manager_chromeos.h
new file mode 100644
index 0000000..8930508
--- /dev/null
+++ b/cros/power_manager_chromeos.h
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_POWER_MANAGER_CHROMEOS_H_
+#define UPDATE_ENGINE_CROS_POWER_MANAGER_CHROMEOS_H_
+
+#include <base/macros.h>
+#include <power_manager/dbus-proxies.h>
+
+#include "update_engine/cros/power_manager_interface.h"
+
+namespace chromeos_update_engine {
+
+class PowerManagerChromeOS : public PowerManagerInterface {
+ public:
+ PowerManagerChromeOS();
+ ~PowerManagerChromeOS() override = default;
+
+ // PowerManagerInterface overrides.
+ bool RequestReboot() override;
+
+ private:
+ // Real DBus proxy using the DBus connection.
+ org::chromium::PowerManagerProxy power_manager_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerManagerChromeOS);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_POWER_MANAGER_CHROMEOS_H_
diff --git a/cros/power_manager_interface.h b/cros/power_manager_interface.h
new file mode 100644
index 0000000..1f712d2
--- /dev/null
+++ b/cros/power_manager_interface.h
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_POWER_MANAGER_INTERFACE_H_
+#define UPDATE_ENGINE_CROS_POWER_MANAGER_INTERFACE_H_
+
+#include <memory>
+
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+class PowerManagerInterface {
+ public:
+ virtual ~PowerManagerInterface() = default;
+
+ // Request the power manager to restart the device. Returns true on success.
+ virtual bool RequestReboot() = 0;
+
+ protected:
+ PowerManagerInterface() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PowerManagerInterface);
+};
+
+namespace power_manager {
+// Factory function which create a PowerManager.
+std::unique_ptr<PowerManagerInterface> CreatePowerManager();
+} // namespace power_manager
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_POWER_MANAGER_INTERFACE_H_
diff --git a/cros/real_system_state.cc b/cros/real_system_state.cc
new file mode 100644
index 0000000..4f57246
--- /dev/null
+++ b/cros/real_system_state.cc
@@ -0,0 +1,243 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/real_system_state.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/location.h>
+#include <base/time/time.h>
+#include <brillo/message_loops/message_loop.h>
+#if USE_CHROME_KIOSK_APP
+#include <chromeos/dbus/service_constants.h>
+#endif // USE_CHROME_KIOSK_APP
+
+#include "update_engine/common/boot_control.h"
+#include "update_engine/common/boot_control_stub.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/dlcservice_interface.h"
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/metrics_reporter_omaha.h"
+#if USE_DBUS
+#include "update_engine/cros/dbus_connection.h"
+#endif // USE_DBUS
+#include "update_engine/update_boot_flags_action.h"
+#include "update_engine/update_manager/state_factory.h"
+
+using brillo::MessageLoop;
+
+namespace chromeos_update_engine {
+
+RealSystemState::~RealSystemState() {
+ // Prevent any DBus communication from UpdateAttempter when shutting down the
+ // daemon.
+ if (update_attempter_)
+ update_attempter_->ClearObservers();
+}
+
+bool RealSystemState::Initialize() {
+ boot_control_ = boot_control::CreateBootControl();
+ if (!boot_control_) {
+ LOG(WARNING) << "Unable to create BootControl instance, using stub "
+ << "instead. All update attempts will fail.";
+ boot_control_ = std::make_unique<BootControlStub>();
+ }
+
+ hardware_ = hardware::CreateHardware();
+ if (!hardware_) {
+ LOG(ERROR) << "Error initializing the HardwareInterface.";
+ return false;
+ }
+
+#if USE_CHROME_KIOSK_APP
+ kiosk_app_proxy_.reset(new org::chromium::KioskAppServiceInterfaceProxy(
+ DBusConnection::Get()->GetDBus(), chromeos::kKioskAppServiceName));
+#endif // USE_CHROME_KIOSK_APP
+
+ LOG_IF(INFO, !hardware_->IsNormalBootMode()) << "Booted in dev mode.";
+ LOG_IF(INFO, !hardware_->IsOfficialBuild()) << "Booted non-official build.";
+
+ connection_manager_ = connection_manager::CreateConnectionManager(this);
+ if (!connection_manager_) {
+ LOG(ERROR) << "Error initializing the ConnectionManagerInterface.";
+ return false;
+ }
+
+ power_manager_ = power_manager::CreatePowerManager();
+ if (!power_manager_) {
+ LOG(ERROR) << "Error initializing the PowerManagerInterface.";
+ return false;
+ }
+
+ dlcservice_ = CreateDlcService();
+ if (!dlcservice_) {
+ LOG(ERROR) << "Error initializing the DlcServiceInterface.";
+ return false;
+ }
+
+ // Initialize standard and powerwash-safe prefs.
+ base::FilePath non_volatile_path;
+ // TODO(deymo): Fall back to in-memory prefs if there's no physical directory
+ // available.
+ if (!hardware_->GetNonVolatileDirectory(&non_volatile_path)) {
+ LOG(ERROR) << "Failed to get a non-volatile directory.";
+ return false;
+ }
+ Prefs* prefs;
+ prefs_.reset(prefs = new Prefs());
+ if (!prefs->Init(non_volatile_path.Append(kPrefsSubDirectory))) {
+ LOG(ERROR) << "Failed to initialize preferences.";
+ return false;
+ }
+
+ base::FilePath powerwash_safe_path;
+ if (!hardware_->GetPowerwashSafeDirectory(&powerwash_safe_path)) {
+ // TODO(deymo): Fall-back to in-memory prefs if there's no powerwash-safe
+ // directory, or disable powerwash feature.
+ powerwash_safe_path = non_volatile_path.Append("powerwash-safe");
+ LOG(WARNING) << "No powerwash-safe directory, using non-volatile one.";
+ }
+ powerwash_safe_prefs_.reset(prefs = new Prefs());
+ if (!prefs->Init(
+ powerwash_safe_path.Append(kPowerwashSafePrefsSubDirectory))) {
+ LOG(ERROR) << "Failed to initialize powerwash preferences.";
+ return false;
+ }
+
+ // Check the system rebooted marker file.
+ std::string boot_id;
+ if (utils::GetBootId(&boot_id)) {
+ std::string prev_boot_id;
+ system_rebooted_ = (!prefs_->GetString(kPrefsBootId, &prev_boot_id) ||
+ prev_boot_id != boot_id);
+ prefs_->SetString(kPrefsBootId, boot_id);
+ } else {
+ LOG(WARNING) << "Couldn't detect the bootid, assuming system was rebooted.";
+ system_rebooted_ = true;
+ }
+
+ // Initialize the OmahaRequestParams with the default settings. These settings
+ // will be re-initialized before every request using the actual request
+ // options. This initialization here pre-loads current channel and version, so
+ // the DBus service can access it.
+ if (!request_params_.Init("", "", {})) {
+ LOG(WARNING) << "Ignoring OmahaRequestParams initialization error. Some "
+ "features might not work properly.";
+ }
+
+ certificate_checker_.reset(
+ new CertificateChecker(prefs_.get(), &openssl_wrapper_));
+ certificate_checker_->Init();
+
+ update_attempter_.reset(
+ new UpdateAttempter(this, certificate_checker_.get()));
+
+ // Initialize the UpdateAttempter before the UpdateManager.
+ update_attempter_->Init();
+
+ // Initialize the Update Manager using the default state factory.
+ chromeos_update_manager::State* um_state =
+ chromeos_update_manager::DefaultStateFactory(&policy_provider_,
+#if USE_CHROME_KIOSK_APP
+ kiosk_app_proxy_.get(),
+#else
+ nullptr,
+#endif // USE_CHROME_KIOSK_APP
+ this);
+
+ if (!um_state) {
+ LOG(ERROR) << "Failed to initialize the Update Manager.";
+ return false;
+ }
+ update_manager_.reset(new chromeos_update_manager::UpdateManager(
+ &clock_,
+ base::TimeDelta::FromSeconds(5),
+ base::TimeDelta::FromHours(12),
+ um_state));
+
+ // The P2P Manager depends on the Update Manager for its initialization.
+ p2p_manager_.reset(
+ P2PManager::Construct(nullptr,
+ &clock_,
+ update_manager_.get(),
+ "cros_au",
+ kMaxP2PFilesToKeep,
+ base::TimeDelta::FromDays(kMaxP2PFileAgeDays)));
+
+ if (!payload_state_.Initialize(this)) {
+ LOG(ERROR) << "Failed to initialize the payload state object.";
+ return false;
+ }
+
+ // For images that are build for debugging purposes like test images
+ // initialize max kernel key version to 0xfffffffe, which is logical infinity.
+ if (!hardware_->IsOfficialBuild()) {
+ if (!hardware()->SetMaxKernelKeyRollforward(
+ chromeos_update_manager::kRollforwardInfinity)) {
+ LOG(ERROR) << "Failed to set kernel_max_rollforward to infinity for"
+ << " device with test/dev image.";
+ }
+ }
+
+ // All is well. Initialization successful.
+ return true;
+}
+
+bool RealSystemState::StartUpdater() {
+ // Initiate update checks.
+ update_attempter_->ScheduleUpdates();
+
+ auto update_boot_flags_action =
+ std::make_unique<UpdateBootFlagsAction>(boot_control_.get());
+ processor_.EnqueueAction(std::move(update_boot_flags_action));
+ // Update boot flags after 45 seconds.
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ActionProcessor::StartProcessing,
+ base::Unretained(&processor_)),
+ base::TimeDelta::FromSeconds(45));
+
+ // Broadcast the update engine status on startup to ensure consistent system
+ // state on crashes.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempter::BroadcastStatus,
+ base::Unretained(update_attempter_.get())));
+
+ // Run the UpdateEngineStarted() method on |update_attempter|.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempter::UpdateEngineStarted,
+ base::Unretained(update_attempter_.get())));
+ return true;
+}
+
+void RealSystemState::AddObserver(ServiceObserverInterface* observer) {
+ CHECK(update_attempter_.get());
+ update_attempter_->AddObserver(observer);
+}
+
+void RealSystemState::RemoveObserver(ServiceObserverInterface* observer) {
+ CHECK(update_attempter_.get());
+ update_attempter_->RemoveObserver(observer);
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/real_system_state.h b/cros/real_system_state.h
new file mode 100644
index 0000000..798fca0
--- /dev/null
+++ b/cros/real_system_state.h
@@ -0,0 +1,202 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_REAL_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_CROS_REAL_SYSTEM_STATE_H_
+
+#include "update_engine/common/system_state.h"
+
+#include <memory>
+#include <set>
+
+#include <policy/device_policy.h>
+
+#if USE_CHROME_KIOSK_APP
+#include <kiosk-app/dbus-proxies.h>
+#endif // USE_CHROME_KIOSK_APP
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/clock.h"
+#include "update_engine/common/daemon_state_interface.h"
+#include "update_engine/common/dlcservice_interface.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/metrics_reporter_interface.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/cros/connection_manager_interface.h"
+#include "update_engine/cros/metrics_reporter_omaha.h"
+#include "update_engine/cros/p2p_manager.h"
+#include "update_engine/cros/payload_state.h"
+#include "update_engine/cros/power_manager_interface.h"
+#include "update_engine/cros/update_attempter.h"
+#include "update_engine/update_manager/update_manager.h"
+
+namespace chromeos_update_engine {
+
+// A real implementation of the SystemStateInterface which is
+// used by the actual product code.
+class RealSystemState : public SystemState, public DaemonStateInterface {
+ public:
+ // Constructs all system objects that do not require separate initialization;
+ // see Initialize() below for the remaining ones.
+ RealSystemState() = default;
+ ~RealSystemState() override;
+
+ // Initializes and sets systems objects that require an initialization
+ // separately from construction. Returns |true| on success.
+ bool Initialize();
+
+ // DaemonStateInterface overrides.
+ // Start the periodic update attempts. Must be called at the beginning of the
+ // program to start the periodic update check process.
+ bool StartUpdater() override;
+
+ void AddObserver(ServiceObserverInterface* observer) override;
+ void RemoveObserver(ServiceObserverInterface* observer) override;
+ const std::set<ServiceObserverInterface*>& service_observers() override {
+ CHECK(update_attempter_.get());
+ return update_attempter_->service_observers();
+ }
+
+ // SystemState overrides.
+ inline void set_device_policy(
+ const policy::DevicePolicy* device_policy) override {
+ device_policy_ = device_policy;
+ }
+
+ inline const policy::DevicePolicy* device_policy() override {
+ return device_policy_;
+ }
+
+ inline BootControlInterface* boot_control() override {
+ return boot_control_.get();
+ }
+
+ inline ClockInterface* clock() override { return &clock_; }
+
+ inline ConnectionManagerInterface* connection_manager() override {
+ return connection_manager_.get();
+ }
+
+ inline HardwareInterface* hardware() override { return hardware_.get(); }
+
+ inline MetricsReporterInterface* metrics_reporter() override {
+ return &metrics_reporter_;
+ }
+
+ inline PrefsInterface* prefs() override { return prefs_.get(); }
+
+ inline PrefsInterface* powerwash_safe_prefs() override {
+ return powerwash_safe_prefs_.get();
+ }
+
+ inline PayloadStateInterface* payload_state() override {
+ return &payload_state_;
+ }
+
+ inline UpdateAttempter* update_attempter() override {
+ return update_attempter_.get();
+ }
+
+ inline OmahaRequestParams* request_params() override {
+ return &request_params_;
+ }
+
+ inline P2PManager* p2p_manager() override { return p2p_manager_.get(); }
+
+ inline chromeos_update_manager::UpdateManager* update_manager() override {
+ return update_manager_.get();
+ }
+
+ inline PowerManagerInterface* power_manager() override {
+ return power_manager_.get();
+ }
+
+ inline bool system_rebooted() override { return system_rebooted_; }
+
+ inline DlcServiceInterface* dlcservice() override {
+ return dlcservice_.get();
+ }
+
+ private:
+ // Real DBus proxies using the DBus connection.
+#if USE_CHROME_KIOSK_APP
+ std::unique_ptr<org::chromium::KioskAppServiceInterfaceProxy>
+ kiosk_app_proxy_;
+#endif // USE_CHROME_KIOSK_APP
+
+ // Interface for the power manager.
+ std::unique_ptr<PowerManagerInterface> power_manager_;
+
+ // Interface for dlcservice.
+ std::unique_ptr<DlcServiceInterface> dlcservice_;
+
+ // Interface for the bootloader control.
+ std::unique_ptr<BootControlInterface> boot_control_;
+
+ // Interface for the clock.
+ Clock clock_;
+
+ // The latest device policy object from the policy provider.
+ const policy::DevicePolicy* device_policy_{nullptr};
+
+ // The connection manager object that makes download decisions depending on
+ // the current type of connection.
+ std::unique_ptr<ConnectionManagerInterface> connection_manager_;
+
+ // Interface for the hardware functions.
+ std::unique_ptr<HardwareInterface> hardware_;
+
+ // The Metrics reporter for reporting UMA stats.
+ MetricsReporterOmaha metrics_reporter_;
+
+ // Interface for persisted store.
+ std::unique_ptr<PrefsInterface> prefs_;
+
+ // Interface for persisted store that persists across powerwashes.
+ std::unique_ptr<PrefsInterface> powerwash_safe_prefs_;
+
+ // All state pertaining to payload state such as response, URL, backoff
+ // states.
+ PayloadState payload_state_;
+
+ // OpenSSLWrapper and CertificateChecker used for checking SSL certificates.
+ OpenSSLWrapper openssl_wrapper_;
+ std::unique_ptr<CertificateChecker> certificate_checker_;
+
+ // Pointer to the update attempter object.
+ std::unique_ptr<UpdateAttempter> update_attempter_;
+
+ // Common parameters for all Omaha requests.
+ OmahaRequestParams request_params_{this};
+
+ std::unique_ptr<P2PManager> p2p_manager_;
+
+ std::unique_ptr<chromeos_update_manager::UpdateManager> update_manager_;
+
+ policy::PolicyProvider policy_provider_;
+
+ // If true, this is the first instance of the update engine since the system
+ // rebooted. Important for tracking whether you are running instance of the
+ // update engine on first boot or due to a crash/restart.
+ bool system_rebooted_{false};
+
+ ActionProcessor processor_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_REAL_SYSTEM_STATE_H_
diff --git a/cros/requisition_util.cc b/cros/requisition_util.cc
new file mode 100644
index 0000000..6296d0b
--- /dev/null
+++ b/cros/requisition_util.cc
@@ -0,0 +1,69 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/requisition_util.h"
+
+#include <memory>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/json/json_file_value_serializer.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+constexpr char kOemRequisitionKey[] = "oem_device_requisition";
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+string ReadDeviceRequisition(const base::FilePath& local_state) {
+ string requisition;
+ bool vpd_retval = utils::GetVpdValue(kOemRequisitionKey, &requisition);
+
+ // Some users manually convert non-CfM hardware at enrollment time, so VPD
+ // value may be missing. So check the Local State JSON as well.
+ if ((requisition.empty() || !vpd_retval) && base::PathExists(local_state)) {
+ int error_code;
+ std::string error_msg;
+ JSONFileValueDeserializer deserializer(local_state);
+ std::unique_ptr<base::Value> root =
+ deserializer.Deserialize(&error_code, &error_msg);
+ if (!root) {
+ if (error_code != 0) {
+ LOG(ERROR) << "Unable to deserialize Local State with exit code: "
+ << error_code << " and error: " << error_msg;
+ }
+ return "";
+ }
+ auto* path = root->FindPath({"enrollment", "device_requisition"});
+ if (!path || !path->is_string()) {
+ return "";
+ }
+ path->GetAsString(&requisition);
+ }
+ return requisition;
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/requisition_util.h b/cros/requisition_util.h
new file mode 100644
index 0000000..6ec4783
--- /dev/null
+++ b/cros/requisition_util.h
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_REQUISITION_UTIL_H_
+#define UPDATE_ENGINE_CROS_REQUISITION_UTIL_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+
+namespace chromeos_update_engine {
+
+// Checks the VPD and Local State for the device's requisition and returns it,
+// or an empty string if the device has no requisition.
+std::string ReadDeviceRequisition(const base::FilePath& local_state);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_REQUISITION_UTIL_H_
diff --git a/cros/requisition_util_unittest.cc b/cros/requisition_util_unittest.cc
new file mode 100644
index 0000000..269585e
--- /dev/null
+++ b/cros/requisition_util_unittest.cc
@@ -0,0 +1,94 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/requisition_util.h"
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace {
+
+const char kRemoraJSON[] =
+ "{\n"
+ " \"the_list\": [ \"val1\", \"val2\" ],\n"
+ " \"enrollment\": {\n"
+ " \"autostart\": true,\n"
+ " \"can_exit\": false,\n"
+ " \"device_requisition\": \"remora\"\n"
+ " },\n"
+ " \"some_String\": \"1337\",\n"
+ " \"some_int\": 42\n"
+ "}\n";
+
+const char kNoEnrollmentJSON[] =
+ "{\n"
+ " \"the_list\": [ \"val1\", \"val2\" ],\n"
+ " \"enrollment\": {\n"
+ " \"autostart\": true,\n"
+ " \"can_exit\": false,\n"
+ " \"device_requisition\": \"\"\n"
+ " },\n"
+ " \"some_String\": \"1337\",\n"
+ " \"some_int\": 42\n"
+ "}\n";
+} // namespace
+
+namespace chromeos_update_engine {
+
+class RequisitionUtilTest : public ::testing::Test {
+ protected:
+ void SetUp() override { ASSERT_TRUE(root_dir_.CreateUniqueTempDir()); }
+
+ void WriteJsonToFile(const string& json) {
+ path_ =
+ base::FilePath(root_dir_.GetPath().value() + "/chronos/Local State");
+ ASSERT_TRUE(base::CreateDirectory(path_.DirName()));
+ ASSERT_TRUE(WriteFileString(path_.value(), json));
+ }
+
+ base::ScopedTempDir root_dir_;
+ base::FilePath path_;
+};
+
+TEST_F(RequisitionUtilTest, BadJsonReturnsEmpty) {
+ WriteJsonToFile("this isn't JSON");
+ EXPECT_EQ("", ReadDeviceRequisition(path_));
+}
+
+TEST_F(RequisitionUtilTest, NoFileReturnsEmpty) {
+ EXPECT_EQ("", ReadDeviceRequisition(path_));
+}
+
+TEST_F(RequisitionUtilTest, EnrollmentRequisition) {
+ WriteJsonToFile(kRemoraJSON);
+ EXPECT_EQ("remora", ReadDeviceRequisition(path_));
+}
+
+TEST_F(RequisitionUtilTest, BlankEnrollment) {
+ WriteJsonToFile(kNoEnrollmentJSON);
+ EXPECT_EQ("", ReadDeviceRequisition(path_));
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/shill_proxy.cc b/cros/shill_proxy.cc
new file mode 100644
index 0000000..a3c8543
--- /dev/null
+++ b/cros/shill_proxy.cc
@@ -0,0 +1,42 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/shill_proxy.h"
+
+#include "update_engine/cros/dbus_connection.h"
+
+using org::chromium::flimflam::ManagerProxy;
+using org::chromium::flimflam::ManagerProxyInterface;
+using org::chromium::flimflam::ServiceProxy;
+using org::chromium::flimflam::ServiceProxyInterface;
+
+namespace chromeos_update_engine {
+
+ShillProxy::ShillProxy()
+ : bus_(DBusConnection::Get()->GetDBus()),
+ manager_proxy_(new ManagerProxy(bus_)) {}
+
+ManagerProxyInterface* ShillProxy::GetManagerProxy() {
+ return manager_proxy_.get();
+}
+
+std::unique_ptr<ServiceProxyInterface> ShillProxy::GetServiceForPath(
+ const dbus::ObjectPath& path) {
+ DCHECK(bus_.get());
+ return std::unique_ptr<ServiceProxyInterface>(new ServiceProxy(bus_, path));
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/shill_proxy.h b/cros/shill_proxy.h
new file mode 100644
index 0000000..aff428a
--- /dev/null
+++ b/cros/shill_proxy.h
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_SHILL_PROXY_H_
+#define UPDATE_ENGINE_CROS_SHILL_PROXY_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <dbus/bus.h>
+#include <dbus/object_path.h>
+#include <shill/dbus-proxies.h>
+
+#include "update_engine/cros/shill_proxy_interface.h"
+
+namespace chromeos_update_engine {
+
+// This class implements the connection to shill using real DBus calls.
+class ShillProxy : public ShillProxyInterface {
+ public:
+ ShillProxy();
+ ~ShillProxy() override = default;
+
+ // ShillProxyInterface overrides.
+ org::chromium::flimflam::ManagerProxyInterface* GetManagerProxy() override;
+ std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>
+ GetServiceForPath(const dbus::ObjectPath& path) override;
+
+ private:
+ // A reference to the main bus for creating new ServiceProxy instances.
+ scoped_refptr<dbus::Bus> bus_;
+ std::unique_ptr<org::chromium::flimflam::ManagerProxyInterface>
+ manager_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShillProxy);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_SHILL_PROXY_H_
diff --git a/cros/shill_proxy_interface.h b/cros/shill_proxy_interface.h
new file mode 100644
index 0000000..19e81f3
--- /dev/null
+++ b/cros/shill_proxy_interface.h
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_SHILL_PROXY_INTERFACE_H_
+#define UPDATE_ENGINE_CROS_SHILL_PROXY_INTERFACE_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <dbus/object_path.h>
+#include <shill/dbus-proxies.h>
+
+namespace chromeos_update_engine {
+
+// This class handles the DBus connection with shill daemon. The DBus interface
+// with shill requires to monitor or request the current service by interacting
+// with the org::chromium::flimflam::ManagerProxy and then request or monitor
+// properties on the selected org::chromium::flimflam::ServiceProxy. This class
+// provides a mockable way to access that.
+class ShillProxyInterface {
+ public:
+ virtual ~ShillProxyInterface() = default;
+
+ // Return the ManagerProxy instance of the shill daemon. The instance is owned
+ // by this ShillProxyInterface instance.
+ virtual org::chromium::flimflam::ManagerProxyInterface* GetManagerProxy() = 0;
+
+ // Return a ServiceProxy for the given path. The ownership of the returned
+ // instance is transferred to the caller.
+ virtual std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>
+ GetServiceForPath(const dbus::ObjectPath& path) = 0;
+
+ protected:
+ ShillProxyInterface() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShillProxyInterface);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_SHILL_PROXY_INTERFACE_H_
diff --git a/cros/update_attempter.cc b/cros/update_attempter.cc
new file mode 100644
index 0000000..e8cb291
--- /dev/null
+++ b/cros/update_attempter.cc
@@ -0,0 +1,1822 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/update_attempter.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/compiler_specific.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <brillo/data_encoding.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/message_loops/message_loop.h>
+#include <policy/device_policy.h>
+#include <policy/libpolicy.h>
+#include <update_engine/dbus-constants.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/dlcservice_interface.h"
+#include "update_engine/common/download_action.h"
+#include "update_engine/common/excluder_interface.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/metrics_reporter_interface.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/omaha_request_action.h"
+#include "update_engine/cros/omaha_request_params.h"
+#include "update_engine/cros/omaha_response_handler_action.h"
+#include "update_engine/cros/omaha_utils.h"
+#include "update_engine/cros/p2p_manager.h"
+#include "update_engine/cros/payload_state_interface.h"
+#include "update_engine/cros/power_manager_interface.h"
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/payload_consumer/filesystem_verifier_action.h"
+#include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/update_boot_flags_action.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/policy_utils.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/update_status_utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::FilePath;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using brillo::MessageLoop;
+using chromeos_update_manager::CalculateStagingCase;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::StagingCase;
+using chromeos_update_manager::UpdateCheckParams;
+using std::map;
+using std::string;
+using std::vector;
+using update_engine::UpdateAttemptFlags;
+using update_engine::UpdateEngineStatus;
+
+namespace chromeos_update_engine {
+
+const int UpdateAttempter::kMaxDeltaUpdateFailures = 3;
+
+namespace {
+const int kMaxConsecutiveObeyProxyRequests = 20;
+
+// Minimum threshold to broadcast an status update in progress and time.
+const double kBroadcastThresholdProgress = 0.01; // 1%
+const int kBroadcastThresholdSeconds = 10;
+
+// By default autest bypasses scattering. If we want to test scattering,
+// use kScheduledAUTestURLRequest. The URL used is same in both cases, but
+// different params are passed to CheckForUpdate().
+const char kAUTestURLRequest[] = "autest";
+const char kScheduledAUTestURLRequest[] = "autest-scheduled";
+} // namespace
+
+ErrorCode GetErrorCodeForAction(AbstractAction* action, ErrorCode code) {
+ if (code != ErrorCode::kError)
+ return code;
+
+ const string type = action->Type();
+ if (type == OmahaRequestAction::StaticType())
+ return ErrorCode::kOmahaRequestError;
+ if (type == OmahaResponseHandlerAction::StaticType())
+ return ErrorCode::kOmahaResponseHandlerError;
+ if (type == FilesystemVerifierAction::StaticType())
+ return ErrorCode::kFilesystemVerifierError;
+ if (type == PostinstallRunnerAction::StaticType())
+ return ErrorCode::kPostinstallRunnerError;
+
+ return code;
+}
+
+UpdateAttempter::UpdateAttempter(SystemState* system_state,
+ CertificateChecker* cert_checker)
+ : processor_(new ActionProcessor()),
+ system_state_(system_state),
+ cert_checker_(cert_checker),
+ is_install_(false) {}
+
+UpdateAttempter::~UpdateAttempter() {
+ // CertificateChecker might not be initialized in unittests.
+ if (cert_checker_)
+ cert_checker_->SetObserver(nullptr);
+ // Release ourselves as the ActionProcessor's delegate to prevent
+ // re-scheduling the updates due to the processing stopped.
+ processor_->set_delegate(nullptr);
+}
+
+void UpdateAttempter::Init() {
+ // Pulling from the SystemState can only be done after construction, since
+ // this is an aggregate of various objects (such as the UpdateAttempter),
+ // which requires them all to be constructed prior to it being used.
+ prefs_ = system_state_->prefs();
+ omaha_request_params_ = system_state_->request_params();
+
+ if (cert_checker_)
+ cert_checker_->SetObserver(this);
+
+ // In case of update_engine restart without a reboot we need to restore the
+ // reboot needed state.
+ if (GetBootTimeAtUpdate(nullptr))
+ status_ = UpdateStatus::UPDATED_NEED_REBOOT;
+ else
+ status_ = UpdateStatus::IDLE;
+}
+
+bool UpdateAttempter::ScheduleUpdates() {
+ if (IsBusyOrUpdateScheduled())
+ return false;
+
+ chromeos_update_manager::UpdateManager* const update_manager =
+ system_state_->update_manager();
+ CHECK(update_manager);
+ Callback<void(EvalStatus, const UpdateCheckParams&)> callback =
+ Bind(&UpdateAttempter::OnUpdateScheduled, base::Unretained(this));
+ // We limit the async policy request to a reasonably short time, to avoid a
+ // starvation due to a transient bug.
+ update_manager->AsyncPolicyRequestUpdateCheckAllowed(
+ callback, &Policy::UpdateCheckAllowed);
+ waiting_for_scheduled_check_ = true;
+ return true;
+}
+
+void UpdateAttempter::CertificateChecked(ServerToCheck server_to_check,
+ CertificateCheckResult result) {
+ system_state_->metrics_reporter()->ReportCertificateCheckMetrics(
+ server_to_check, result);
+}
+
+bool UpdateAttempter::CheckAndReportDailyMetrics() {
+ int64_t stored_value;
+ Time now = system_state_->clock()->GetWallclockTime();
+ if (system_state_->prefs()->Exists(kPrefsDailyMetricsLastReportedAt) &&
+ system_state_->prefs()->GetInt64(kPrefsDailyMetricsLastReportedAt,
+ &stored_value)) {
+ Time last_reported_at = Time::FromInternalValue(stored_value);
+ TimeDelta time_reported_since = now - last_reported_at;
+ if (time_reported_since.InSeconds() < 0) {
+ LOG(WARNING) << "Last reported daily metrics "
+ << utils::FormatTimeDelta(time_reported_since) << " ago "
+ << "which is negative. Either the system clock is wrong or "
+ << "the kPrefsDailyMetricsLastReportedAt state variable "
+ << "is wrong.";
+ // In this case, report daily metrics to reset.
+ } else {
+ if (time_reported_since.InSeconds() < 24 * 60 * 60) {
+ LOG(INFO) << "Last reported daily metrics "
+ << utils::FormatTimeDelta(time_reported_since) << " ago.";
+ return false;
+ }
+ LOG(INFO) << "Last reported daily metrics "
+ << utils::FormatTimeDelta(time_reported_since) << " ago, "
+ << "which is more than 24 hours ago.";
+ }
+ }
+
+ LOG(INFO) << "Reporting daily metrics.";
+ system_state_->prefs()->SetInt64(kPrefsDailyMetricsLastReportedAt,
+ now.ToInternalValue());
+
+ ReportOSAge();
+
+ return true;
+}
+
+void UpdateAttempter::ReportOSAge() {
+ struct stat sb;
+
+ if (system_state_ == nullptr)
+ return;
+
+ if (stat("/etc/lsb-release", &sb) != 0) {
+ PLOG(ERROR) << "Error getting file status for /etc/lsb-release "
+ << "(Note: this may happen in some unit tests)";
+ return;
+ }
+
+ Time lsb_release_timestamp = Time::FromTimeSpec(sb.st_ctim);
+ Time now = system_state_->clock()->GetWallclockTime();
+ TimeDelta age = now - lsb_release_timestamp;
+ if (age.InSeconds() < 0) {
+ LOG(ERROR) << "The OS age (" << utils::FormatTimeDelta(age)
+ << ") is negative. Maybe the clock is wrong? "
+ << "(Note: this may happen in some unit tests.)";
+ return;
+ }
+
+ system_state_->metrics_reporter()->ReportDailyMetrics(age);
+}
+
+void UpdateAttempter::Update(const UpdateCheckParams& params) {
+ // This is normally called frequently enough so it's appropriate to use as a
+ // hook for reporting daily metrics.
+ // TODO(garnold) This should be hooked to a separate (reliable and consistent)
+ // timeout event.
+ CheckAndReportDailyMetrics();
+
+ fake_update_success_ = false;
+ if (status_ == UpdateStatus::UPDATED_NEED_REBOOT) {
+ // Although we have applied an update, we still want to ping Omaha
+ // to ensure the number of active statistics is accurate.
+ //
+ // Also convey to the UpdateEngine.Check.Result metric that we're
+ // not performing an update check because of this.
+ LOG(INFO) << "Not updating b/c we already updated and we're waiting for "
+ << "reboot, we'll ping Omaha instead";
+ system_state_->metrics_reporter()->ReportUpdateCheckMetrics(
+ system_state_,
+ metrics::CheckResult::kRebootPending,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset);
+ PingOmaha();
+ return;
+ }
+ if (status_ != UpdateStatus::IDLE) {
+ // Update in progress. Do nothing
+ return;
+ }
+
+ if (!CalculateUpdateParams(params)) {
+ return;
+ }
+
+ BuildUpdateActions(params.interactive);
+
+ SetStatusAndNotify(UpdateStatus::CHECKING_FOR_UPDATE);
+
+ // Update the last check time here; it may be re-updated when an Omaha
+ // response is received, but this will prevent us from repeatedly scheduling
+ // checks in the case where a response is not received.
+ UpdateLastCheckedTime();
+
+ ScheduleProcessingStart();
+}
+
+void UpdateAttempter::RefreshDevicePolicy() {
+ // Lazy initialize the policy provider, or reload the latest policy data.
+ if (!policy_provider_.get())
+ policy_provider_.reset(new policy::PolicyProvider());
+ policy_provider_->Reload();
+
+ const policy::DevicePolicy* device_policy = nullptr;
+ if (policy_provider_->device_policy_is_loaded())
+ device_policy = &policy_provider_->GetDevicePolicy();
+
+ if (device_policy)
+ LOG(INFO) << "Device policies/settings present";
+ else
+ LOG(INFO) << "No device policies/settings present.";
+
+ system_state_->set_device_policy(device_policy);
+ system_state_->p2p_manager()->SetDevicePolicy(device_policy);
+}
+
+void UpdateAttempter::CalculateP2PParams(bool interactive) {
+ bool use_p2p_for_downloading = false;
+ bool use_p2p_for_sharing = false;
+
+ // Never use p2p for downloading in interactive checks unless the developer
+ // has opted in for it via a marker file.
+ //
+ // (Why would a developer want to opt in? If they are working on the
+ // update_engine or p2p codebases so they can actually test their code.)
+
+ if (system_state_ != nullptr) {
+ if (!system_state_->p2p_manager()->IsP2PEnabled()) {
+ LOG(INFO) << "p2p is not enabled - disallowing p2p for both"
+ << " downloading and sharing.";
+ } else {
+ // Allow p2p for sharing, even in interactive checks.
+ use_p2p_for_sharing = true;
+ if (!interactive) {
+ LOG(INFO) << "Non-interactive check - allowing p2p for downloading";
+ use_p2p_for_downloading = true;
+ } else {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading "
+ << "since this update attempt is interactive.";
+ }
+ }
+ }
+
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+ payload_state->SetUsingP2PForDownloading(use_p2p_for_downloading);
+ payload_state->SetUsingP2PForSharing(use_p2p_for_sharing);
+}
+
+bool UpdateAttempter::CalculateUpdateParams(const UpdateCheckParams& params) {
+ http_response_code_ = 0;
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+ // Refresh the policy before computing all the update parameters.
+ RefreshDevicePolicy();
+
+ // Check whether we need to clear the rollback-happened preference after
+ // policy is available again.
+ UpdateRollbackHappened();
+
+ CalculateStagingParams(params.interactive);
+ // If staging_wait_time_ wasn't set, staging is off, use scattering instead.
+ if (staging_wait_time_.InSeconds() == 0) {
+ CalculateScatteringParams(params.interactive);
+ }
+
+ CalculateP2PParams(params.interactive);
+ if (payload_state->GetUsingP2PForDownloading() ||
+ payload_state->GetUsingP2PForSharing()) {
+ // OK, p2p is to be used - start it and perform housekeeping.
+ if (!StartP2PAndPerformHousekeeping()) {
+ // If this fails, disable p2p for this attempt
+ LOG(INFO) << "Forcibly disabling use of p2p since starting p2p or "
+ << "performing housekeeping failed.";
+ payload_state->SetUsingP2PForDownloading(false);
+ payload_state->SetUsingP2PForSharing(false);
+ }
+ }
+
+ if (!omaha_request_params_->Init(
+ forced_app_version_, forced_omaha_url_, params)) {
+ LOG(ERROR) << "Unable to initialize Omaha request params.";
+ return false;
+ }
+
+ // The function |CalculateDlcParams| makes use of the function |GetAppId| from
+ // |OmahaRequestParams|, so to ensure that the return from |GetAppId|
+ // doesn't change, no changes to the values |download_channel_|,
+ // |image_props_.product_id| and |image_props_.canary_product_id| from
+ // |omaha_request_params_| shall be made below this line.
+ CalculateDlcParams();
+
+ // Set Quick Fix Build token if policy is set and the device is enterprise
+ // enrolled.
+ string token;
+ if (system_state_ && system_state_->device_policy()) {
+ if (!system_state_->device_policy()->GetDeviceQuickFixBuildToken(&token))
+ token.clear();
+ }
+ omaha_request_params_->set_autoupdate_token(token);
+
+ LOG(INFO) << "target_version_prefix = "
+ << omaha_request_params_->target_version_prefix()
+ << ", rollback_allowed = "
+ << omaha_request_params_->rollback_allowed()
+ << ", scatter_factor_in_seconds = "
+ << utils::FormatSecs(scatter_factor_.InSeconds());
+
+ LOG(INFO) << "Wall Clock Based Wait Enabled = "
+ << omaha_request_params_->wall_clock_based_wait_enabled()
+ << ", Update Check Count Wait Enabled = "
+ << omaha_request_params_->update_check_count_wait_enabled()
+ << ", Waiting Period = "
+ << utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+
+ LOG(INFO) << "Use p2p For Downloading = "
+ << payload_state->GetUsingP2PForDownloading()
+ << ", Use p2p For Sharing = "
+ << payload_state->GetUsingP2PForSharing();
+
+ obeying_proxies_ = true;
+ if (proxy_manual_checks_ == 0) {
+ LOG(INFO) << "forced to obey proxies";
+ // If forced to obey proxies, every 20th request will not use proxies
+ proxy_manual_checks_++;
+ LOG(INFO) << "proxy manual checks: " << proxy_manual_checks_;
+ if (proxy_manual_checks_ >= kMaxConsecutiveObeyProxyRequests) {
+ proxy_manual_checks_ = 0;
+ obeying_proxies_ = false;
+ }
+ } else if (base::RandInt(0, 4) == 0) {
+ obeying_proxies_ = false;
+ }
+ LOG_IF(INFO, !obeying_proxies_)
+ << "To help ensure updates work, this update check we are ignoring the "
+ << "proxy settings and using direct connections.";
+
+ DisableDeltaUpdateIfNeeded();
+ return true;
+}
+
+void UpdateAttempter::CalculateScatteringParams(bool interactive) {
+ // Take a copy of the old scatter value before we update it, as
+ // we need to update the waiting period if this value changes.
+ TimeDelta old_scatter_factor = scatter_factor_;
+ const policy::DevicePolicy* device_policy = system_state_->device_policy();
+ if (device_policy) {
+ int64_t new_scatter_factor_in_secs = 0;
+ device_policy->GetScatterFactorInSeconds(&new_scatter_factor_in_secs);
+ if (new_scatter_factor_in_secs < 0) // sanitize input, just in case.
+ new_scatter_factor_in_secs = 0;
+ scatter_factor_ = TimeDelta::FromSeconds(new_scatter_factor_in_secs);
+ }
+
+ bool is_scatter_enabled = false;
+ if (scatter_factor_.InSeconds() == 0) {
+ LOG(INFO) << "Scattering disabled since scatter factor is set to 0";
+ } else if (interactive) {
+ LOG(INFO) << "Scattering disabled as this is an interactive update check";
+ } else if (system_state_->hardware()->IsOOBEEnabled() &&
+ !system_state_->hardware()->IsOOBEComplete(nullptr)) {
+ LOG(INFO) << "Scattering disabled since OOBE is enabled but not complete "
+ "yet";
+ } else {
+ is_scatter_enabled = true;
+ LOG(INFO) << "Scattering is enabled";
+ }
+
+ if (is_scatter_enabled) {
+ // This means the scattering policy is turned on.
+ // Now check if we need to update the waiting period. The two cases
+ // in which we'd need to update the waiting period are:
+ // 1. First time in process or a scheduled check after a user-initiated one.
+ // (omaha_request_params_->waiting_period will be zero in this case).
+ // 2. Admin has changed the scattering policy value.
+ // (new scattering value will be different from old one in this case).
+ int64_t wait_period_in_secs = 0;
+ if (omaha_request_params_->waiting_period().InSeconds() == 0) {
+ // First case. Check if we have a suitable value to set for
+ // the waiting period.
+ if (prefs_->GetInt64(kPrefsWallClockScatteringWaitPeriod,
+ &wait_period_in_secs) &&
+ wait_period_in_secs > 0 &&
+ wait_period_in_secs <= scatter_factor_.InSeconds()) {
+ // This means:
+ // 1. There's a persisted value for the waiting period available.
+ // 2. And that persisted value is still valid.
+ // So, in this case, we should reuse the persisted value instead of
+ // generating a new random value to improve the chances of a good
+ // distribution for scattering.
+ omaha_request_params_->set_waiting_period(
+ TimeDelta::FromSeconds(wait_period_in_secs));
+ LOG(INFO) << "Using persisted wall-clock waiting period: "
+ << utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+ } else {
+ // This means there's no persisted value for the waiting period
+ // available or its value is invalid given the new scatter_factor value.
+ // So, we should go ahead and regenerate a new value for the
+ // waiting period.
+ LOG(INFO) << "Persisted value not present or not valid ("
+ << utils::FormatSecs(wait_period_in_secs)
+ << ") for wall-clock waiting period.";
+ GenerateNewWaitingPeriod();
+ }
+ } else if (scatter_factor_ != old_scatter_factor) {
+ // This means there's already a waiting period value, but we detected
+ // a change in the scattering policy value. So, we should regenerate the
+ // waiting period to make sure it's within the bounds of the new scatter
+ // factor value.
+ GenerateNewWaitingPeriod();
+ } else {
+ // Neither the first time scattering is enabled nor the scattering value
+ // changed. Nothing to do.
+ LOG(INFO) << "Keeping current wall-clock waiting period: "
+ << utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+ }
+
+ // The invariant at this point is that omaha_request_params_->waiting_period
+ // is non-zero no matter which path we took above.
+ LOG_IF(ERROR, omaha_request_params_->waiting_period().InSeconds() == 0)
+ << "Waiting Period should NOT be zero at this point!!!";
+
+ // Since scattering is enabled, wall clock based wait will always be
+ // enabled.
+ omaha_request_params_->set_wall_clock_based_wait_enabled(true);
+
+ // If we don't have any issues in accessing the file system to update
+ // the update check count value, we'll turn that on as well.
+ bool decrement_succeeded = DecrementUpdateCheckCount();
+ omaha_request_params_->set_update_check_count_wait_enabled(
+ decrement_succeeded);
+ } else {
+ // This means the scattering feature is turned off or disabled for
+ // this particular update check. Make sure to disable
+ // all the knobs and artifacts so that we don't invoke any scattering
+ // related code.
+ omaha_request_params_->set_wall_clock_based_wait_enabled(false);
+ omaha_request_params_->set_update_check_count_wait_enabled(false);
+ omaha_request_params_->set_waiting_period(TimeDelta::FromSeconds(0));
+ prefs_->Delete(kPrefsWallClockScatteringWaitPeriod);
+ prefs_->Delete(kPrefsUpdateCheckCount);
+ // Don't delete the UpdateFirstSeenAt file as we don't want manual checks
+ // that result in no-updates (e.g. due to server side throttling) to
+ // cause update starvation by having the client generate a new
+ // UpdateFirstSeenAt for each scheduled check that follows a manual check.
+ }
+}
+
+void UpdateAttempter::GenerateNewWaitingPeriod() {
+ omaha_request_params_->set_waiting_period(
+ TimeDelta::FromSeconds(base::RandInt(1, scatter_factor_.InSeconds())));
+
+ LOG(INFO) << "Generated new wall-clock waiting period: "
+ << utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+
+ // Do a best-effort to persist this in all cases. Even if the persistence
+ // fails, we'll still be able to scatter based on our in-memory value.
+ // The persistence only helps in ensuring a good overall distribution
+ // across multiple devices if they tend to reboot too often.
+ system_state_->payload_state()->SetScatteringWaitPeriod(
+ omaha_request_params_->waiting_period());
+}
+
+void UpdateAttempter::CalculateStagingParams(bool interactive) {
+ bool oobe_complete = system_state_->hardware()->IsOOBEEnabled() &&
+ system_state_->hardware()->IsOOBEComplete(nullptr);
+ auto device_policy = system_state_->device_policy();
+ StagingCase staging_case = StagingCase::kOff;
+ if (device_policy && !interactive && oobe_complete) {
+ staging_wait_time_ = omaha_request_params_->waiting_period();
+ staging_case = CalculateStagingCase(
+ device_policy, prefs_, &staging_wait_time_, &staging_schedule_);
+ }
+ switch (staging_case) {
+ case StagingCase::kOff:
+ // Staging is off, get rid of persisted value.
+ prefs_->Delete(kPrefsWallClockStagingWaitPeriod);
+ // Set |staging_wait_time_| to its default value so scattering can still
+ // be turned on
+ staging_wait_time_ = TimeDelta();
+ break;
+ // Let the cases fall through since they just add, and never remove, steps
+ // to turning staging on.
+ case StagingCase::kNoSavedValue:
+ prefs_->SetInt64(kPrefsWallClockStagingWaitPeriod,
+ staging_wait_time_.InDays());
+ FALLTHROUGH;
+ case StagingCase::kSetStagingFromPref:
+ omaha_request_params_->set_waiting_period(staging_wait_time_);
+ FALLTHROUGH;
+ case StagingCase::kNoAction:
+ // Staging is on, enable wallclock based wait so that its values get used.
+ omaha_request_params_->set_wall_clock_based_wait_enabled(true);
+ // Use UpdateCheckCount if possible to prevent devices updating all at
+ // once.
+ omaha_request_params_->set_update_check_count_wait_enabled(
+ DecrementUpdateCheckCount());
+ // Scattering should not be turned on if staging is on, delete the
+ // existing scattering configuration.
+ prefs_->Delete(kPrefsWallClockScatteringWaitPeriod);
+ scatter_factor_ = TimeDelta();
+ }
+}
+
+bool UpdateAttempter::ResetDlcPrefs(const string& dlc_id) {
+ vector<string> failures;
+ PrefsInterface* prefs = system_state_->prefs();
+ for (auto& sub_key :
+ {kPrefsPingActive, kPrefsPingLastActive, kPrefsPingLastRollcall}) {
+ auto key = prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, sub_key});
+ if (!prefs->Delete(key))
+ failures.emplace_back(sub_key);
+ }
+ if (failures.size() != 0)
+ PLOG(ERROR) << "Failed to delete prefs (" << base::JoinString(failures, ",")
+ << " for DLC (" << dlc_id << ").";
+
+ return failures.size() == 0;
+}
+
+bool UpdateAttempter::SetDlcActiveValue(bool is_active, const string& dlc_id) {
+ if (dlc_id.empty()) {
+ LOG(ERROR) << "Empty DLC ID passed.";
+ return false;
+ }
+ LOG(INFO) << "Set DLC (" << dlc_id << ") to "
+ << (is_active ? "Active" : "Inactive");
+ PrefsInterface* prefs = system_state_->prefs();
+ if (is_active) {
+ auto ping_active_key =
+ prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive});
+ if (!prefs->SetInt64(ping_active_key, kPingActiveValue)) {
+ LOG(ERROR) << "Failed to set the value of ping metadata '"
+ << kPrefsPingActive << "'.";
+ return false;
+ }
+ } else {
+ return ResetDlcPrefs(dlc_id);
+ }
+ return true;
+}
+
+int64_t UpdateAttempter::GetPingMetadata(const string& metadata_key) const {
+ // The first time a ping is sent, the metadata files containing the values
+ // sent back by the server still don't exist. A value of -1 is used to
+ // indicate this.
+ if (!system_state_->prefs()->Exists(metadata_key))
+ return kPingNeverPinged;
+
+ int64_t value;
+ if (system_state_->prefs()->GetInt64(metadata_key, &value))
+ return value;
+
+ // Return -2 when the file exists and there is a problem reading from it, or
+ // the value cannot be converted to an integer.
+ return kPingUnknownValue;
+}
+
+void UpdateAttempter::CalculateDlcParams() {
+ // Set the |dlc_ids_| only for an update. This is required to get the
+ // currently installed DLC(s).
+ if (!is_install_ &&
+ !system_state_->dlcservice()->GetDlcsToUpdate(&dlc_ids_)) {
+ LOG(INFO) << "Failed to retrieve DLC module IDs from dlcservice. Check the "
+ "state of dlcservice, will not update DLC modules.";
+ }
+ PrefsInterface* prefs = system_state_->prefs();
+ map<string, OmahaRequestParams::AppParams> dlc_apps_params;
+ for (const auto& dlc_id : dlc_ids_) {
+ OmahaRequestParams::AppParams dlc_params{
+ .active_counting_type = OmahaRequestParams::kDateBased,
+ .name = dlc_id,
+ .send_ping = false};
+ if (is_install_) {
+ // In some cases, |SetDlcActiveValue| might fail to reset the DLC prefs
+ // when a DLC is uninstalled. To avoid having stale values from that
+ // scenario, we reset the metadata values on a new install request.
+ // Ignore failure to delete stale prefs.
+ ResetDlcPrefs(dlc_id);
+ SetDlcActiveValue(true, dlc_id);
+ } else {
+ // Only send the ping when the request is to update DLCs. When installing
+ // DLCs, we don't want to send the ping yet, since the DLCs might fail to
+ // install or might not really be active yet.
+ dlc_params.ping_active = kPingActiveValue;
+ auto ping_active_key =
+ prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive});
+ if (!prefs->GetInt64(ping_active_key, &dlc_params.ping_active) ||
+ dlc_params.ping_active != kPingActiveValue) {
+ dlc_params.ping_active = kPingInactiveValue;
+ }
+ auto ping_last_active_key =
+ prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive});
+ dlc_params.ping_date_last_active = GetPingMetadata(ping_last_active_key);
+
+ auto ping_last_rollcall_key = prefs->CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall});
+ dlc_params.ping_date_last_rollcall =
+ GetPingMetadata(ping_last_rollcall_key);
+
+ dlc_params.send_ping = true;
+ }
+ dlc_apps_params[omaha_request_params_->GetDlcAppId(dlc_id)] = dlc_params;
+ }
+ omaha_request_params_->set_dlc_apps_params(dlc_apps_params);
+ omaha_request_params_->set_is_install(is_install_);
+}
+
+void UpdateAttempter::BuildUpdateActions(bool interactive) {
+ CHECK(!processor_->IsRunning());
+ processor_->set_delegate(this);
+
+ // The session ID needs to be kept throughout the update flow. The value
+ // of the session ID will reset/update only when it is a new update flow.
+ session_id_ = base::GenerateGUID();
+
+ // Actions:
+ auto update_check_fetcher = std::make_unique<LibcurlHttpFetcher>(
+ GetProxyResolver(), system_state_->hardware());
+ update_check_fetcher->set_server_to_check(ServerToCheck::kUpdate);
+ // Try harder to connect to the network, esp when not interactive.
+ // See comment in libcurl_http_fetcher.cc.
+ update_check_fetcher->set_no_network_max_retries(interactive ? 1 : 3);
+ update_check_fetcher->set_is_update_check(true);
+ auto update_check_action =
+ std::make_unique<OmahaRequestAction>(system_state_,
+ nullptr,
+ std::move(update_check_fetcher),
+ false,
+ session_id_);
+ auto response_handler_action =
+ std::make_unique<OmahaResponseHandlerAction>(system_state_);
+ auto update_boot_flags_action =
+ std::make_unique<UpdateBootFlagsAction>(system_state_->boot_control());
+ auto download_started_action = std::make_unique<OmahaRequestAction>(
+ system_state_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
+ std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
+ system_state_->hardware()),
+ false,
+ session_id_);
+
+ LibcurlHttpFetcher* download_fetcher =
+ new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware());
+ download_fetcher->set_server_to_check(ServerToCheck::kDownload);
+ if (interactive)
+ download_fetcher->set_max_retry_count(kDownloadMaxRetryCountInteractive);
+ download_fetcher->SetHeader(kXGoogleUpdateSessionId, session_id_);
+ auto download_action =
+ std::make_unique<DownloadAction>(prefs_,
+ system_state_->boot_control(),
+ system_state_->hardware(),
+ system_state_,
+ download_fetcher, // passes ownership
+ interactive);
+ download_action->set_delegate(this);
+
+ auto download_finished_action = std::make_unique<OmahaRequestAction>(
+ system_state_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateDownloadFinished),
+ std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
+ system_state_->hardware()),
+ false,
+ session_id_);
+ auto filesystem_verifier_action = std::make_unique<FilesystemVerifierAction>(
+ system_state_->boot_control()->GetDynamicPartitionControl());
+ auto update_complete_action = std::make_unique<OmahaRequestAction>(
+ system_state_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+ std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
+ system_state_->hardware()),
+ false,
+ session_id_);
+
+ auto postinstall_runner_action = std::make_unique<PostinstallRunnerAction>(
+ system_state_->boot_control(), system_state_->hardware());
+ postinstall_runner_action->set_delegate(this);
+
+ // Bond them together. We have to use the leaf-types when calling
+ // BondActions().
+ BondActions(update_check_action.get(), response_handler_action.get());
+ BondActions(response_handler_action.get(), download_action.get());
+ BondActions(download_action.get(), filesystem_verifier_action.get());
+ BondActions(filesystem_verifier_action.get(),
+ postinstall_runner_action.get());
+
+ processor_->EnqueueAction(std::move(update_check_action));
+ processor_->EnqueueAction(std::move(response_handler_action));
+ processor_->EnqueueAction(std::move(update_boot_flags_action));
+ processor_->EnqueueAction(std::move(download_started_action));
+ processor_->EnqueueAction(std::move(download_action));
+ processor_->EnqueueAction(std::move(download_finished_action));
+ processor_->EnqueueAction(std::move(filesystem_verifier_action));
+ processor_->EnqueueAction(std::move(postinstall_runner_action));
+ processor_->EnqueueAction(std::move(update_complete_action));
+}
+
+bool UpdateAttempter::Rollback(bool powerwash) {
+ is_install_ = false;
+ if (!CanRollback()) {
+ return false;
+ }
+
+ // Extra check for enterprise-enrolled devices since they don't support
+ // powerwash.
+ if (powerwash) {
+ // Enterprise-enrolled devices have an empty owner in their device policy.
+ string owner;
+ RefreshDevicePolicy();
+ const policy::DevicePolicy* device_policy = system_state_->device_policy();
+ if (device_policy && (!device_policy->GetOwner(&owner) || owner.empty())) {
+ LOG(ERROR) << "Enterprise device detected. "
+ << "Cannot perform a powerwash for enterprise devices.";
+ return false;
+ }
+ }
+
+ processor_->set_delegate(this);
+
+ // Initialize the default request params.
+ if (!omaha_request_params_->Init("", "", {.interactive = true})) {
+ LOG(ERROR) << "Unable to initialize Omaha request params.";
+ return false;
+ }
+
+ LOG(INFO) << "Setting rollback options.";
+ install_plan_.reset(new InstallPlan());
+ install_plan_->target_slot = GetRollbackSlot();
+ install_plan_->source_slot = system_state_->boot_control()->GetCurrentSlot();
+
+ TEST_AND_RETURN_FALSE(
+ install_plan_->LoadPartitionsFromSlots(system_state_->boot_control()));
+ install_plan_->powerwash_required = powerwash;
+
+ LOG(INFO) << "Using this install plan:";
+ install_plan_->Dump();
+
+ auto install_plan_action =
+ std::make_unique<InstallPlanAction>(*install_plan_);
+ auto postinstall_runner_action = std::make_unique<PostinstallRunnerAction>(
+ system_state_->boot_control(), system_state_->hardware());
+ postinstall_runner_action->set_delegate(this);
+ BondActions(install_plan_action.get(), postinstall_runner_action.get());
+ processor_->EnqueueAction(std::move(install_plan_action));
+ processor_->EnqueueAction(std::move(postinstall_runner_action));
+
+ // Update the payload state for Rollback.
+ system_state_->payload_state()->Rollback();
+
+ SetStatusAndNotify(UpdateStatus::ATTEMPTING_ROLLBACK);
+
+ ScheduleProcessingStart();
+ return true;
+}
+
+bool UpdateAttempter::CanRollback() const {
+ // We can only rollback if the update_engine isn't busy and we have a valid
+ // rollback partition.
+ return (status_ == UpdateStatus::IDLE &&
+ GetRollbackSlot() != BootControlInterface::kInvalidSlot);
+}
+
+BootControlInterface::Slot UpdateAttempter::GetRollbackSlot() const {
+ LOG(INFO) << "UpdateAttempter::GetRollbackSlot";
+ const unsigned int num_slots = system_state_->boot_control()->GetNumSlots();
+ const BootControlInterface::Slot current_slot =
+ system_state_->boot_control()->GetCurrentSlot();
+
+ LOG(INFO) << " Installed slots: " << num_slots;
+ LOG(INFO) << " Booted from slot: "
+ << BootControlInterface::SlotName(current_slot);
+
+ if (current_slot == BootControlInterface::kInvalidSlot || num_slots < 2) {
+ LOG(INFO) << "Device is not updateable.";
+ return BootControlInterface::kInvalidSlot;
+ }
+
+ vector<BootControlInterface::Slot> bootable_slots;
+ for (BootControlInterface::Slot slot = 0; slot < num_slots; slot++) {
+ if (slot != current_slot &&
+ system_state_->boot_control()->IsSlotBootable(slot)) {
+ LOG(INFO) << "Found bootable slot "
+ << BootControlInterface::SlotName(slot);
+ return slot;
+ }
+ }
+ LOG(INFO) << "No other bootable slot found.";
+ return BootControlInterface::kInvalidSlot;
+}
+
+bool UpdateAttempter::CheckForUpdate(const string& app_version,
+ const string& omaha_url,
+ UpdateAttemptFlags flags) {
+ if (status_ != UpdateStatus::IDLE) {
+ LOG(INFO) << "Refusing to do an update as there is an "
+ << (is_install_ ? "install" : "update")
+ << " already in progress.";
+ return false;
+ }
+
+ bool interactive = !(flags & UpdateAttemptFlags::kFlagNonInteractive);
+ is_install_ = false;
+
+ LOG(INFO) << "Forced update check requested.";
+ forced_app_version_.clear();
+ forced_omaha_url_.clear();
+
+ // Certain conditions must be met to allow setting custom version and update
+ // server URLs. However, kScheduledAUTestURLRequest and kAUTestURLRequest are
+ // always allowed regardless of device state.
+ if (IsAnyUpdateSourceAllowed()) {
+ forced_app_version_ = app_version;
+ forced_omaha_url_ = omaha_url;
+ }
+ if (omaha_url == kScheduledAUTestURLRequest) {
+ forced_omaha_url_ = constants::kOmahaDefaultAUTestURL;
+ // Pretend that it's not user-initiated even though it is,
+ // so as to test scattering logic, etc. which get kicked off
+ // only in scheduled update checks.
+ interactive = false;
+ } else if (omaha_url == kAUTestURLRequest) {
+ forced_omaha_url_ = constants::kOmahaDefaultAUTestURL;
+ }
+
+ if (interactive) {
+ // Use the passed-in update attempt flags for this update attempt instead
+ // of the previously set ones.
+ current_update_attempt_flags_ = flags;
+ // Note: The caching for non-interactive update checks happens in
+ // |OnUpdateScheduled()|.
+ }
+
+ // |forced_update_pending_callback_| should always be set, but even in the
+ // case that it is not, we still return true indicating success because the
+ // scheduled periodic check will pick up these changes.
+ if (forced_update_pending_callback_.get()) {
+ // Always call |ScheduleUpdates()| before forcing an update. This is because
+ // we need an update to be scheduled for the
+ // |forced_update_pending_callback_| to have an effect. Here we don't need
+ // to care about the return value from |ScheduleUpdate()|.
+ ScheduleUpdates();
+ forced_update_pending_callback_->Run(true, interactive);
+ }
+ return true;
+}
+
+bool UpdateAttempter::CheckForInstall(const vector<string>& dlc_ids,
+ const string& omaha_url) {
+ if (status_ != UpdateStatus::IDLE) {
+ LOG(INFO) << "Refusing to do an install as there is an "
+ << (is_install_ ? "install" : "update")
+ << " already in progress.";
+ return false;
+ }
+
+ dlc_ids_ = dlc_ids;
+ is_install_ = true;
+ forced_omaha_url_.clear();
+
+ // Certain conditions must be met to allow setting custom version and update
+ // server URLs. However, kScheduledAUTestURLRequest and kAUTestURLRequest are
+ // always allowed regardless of device state.
+ if (IsAnyUpdateSourceAllowed()) {
+ forced_omaha_url_ = omaha_url;
+ }
+
+ if (omaha_url == kScheduledAUTestURLRequest ||
+ omaha_url == kAUTestURLRequest) {
+ forced_omaha_url_ = constants::kOmahaDefaultAUTestURL;
+ }
+
+ // |forced_update_pending_callback_| should always be set, but even in the
+ // case that it is not, we still return true indicating success because the
+ // scheduled periodic check will pick up these changes.
+ if (forced_update_pending_callback_.get()) {
+ // Always call |ScheduleUpdates()| before forcing an update. This is because
+ // we need an update to be scheduled for the
+ // |forced_update_pending_callback_| to have an effect. Here we don't need
+ // to care about the return value from |ScheduleUpdate()|.
+ ScheduleUpdates();
+ forced_update_pending_callback_->Run(true, true);
+ }
+ return true;
+}
+
+bool UpdateAttempter::RebootIfNeeded() {
+#ifdef __ANDROID__
+ if (status_ != UpdateStatus::UPDATED_NEED_REBOOT) {
+ LOG(INFO) << "Reboot requested, but status is "
+ << UpdateStatusToString(status_) << ", so not rebooting.";
+ return false;
+ }
+#endif // __ANDROID__
+
+ if (system_state_->power_manager()->RequestReboot())
+ return true;
+
+ return RebootDirectly();
+}
+
+void UpdateAttempter::WriteUpdateCompletedMarker() {
+ string boot_id;
+ if (!utils::GetBootId(&boot_id))
+ return;
+ prefs_->SetString(kPrefsUpdateCompletedOnBootId, boot_id);
+
+ int64_t value = system_state_->clock()->GetBootTime().ToInternalValue();
+ prefs_->SetInt64(kPrefsUpdateCompletedBootTime, value);
+}
+
+bool UpdateAttempter::RebootDirectly() {
+ vector<string> command = {"/sbin/shutdown", "-r", "now"};
+ int rc = 0;
+ Subprocess::SynchronousExec(command, &rc, nullptr, nullptr);
+ return rc == 0;
+}
+
+void UpdateAttempter::OnUpdateScheduled(EvalStatus status,
+ const UpdateCheckParams& params) {
+ waiting_for_scheduled_check_ = false;
+
+ if (status == EvalStatus::kSucceeded) {
+ if (!params.updates_enabled) {
+ LOG(WARNING) << "Updates permanently disabled.";
+ // Signal disabled status, then switch right back to idle. This is
+ // necessary for ensuring that observers waiting for a signal change will
+ // actually notice one on subsequent calls. Note that we don't need to
+ // re-schedule a check in this case as updates are permanently disabled;
+ // further (forced) checks may still initiate a scheduling call.
+ SetStatusAndNotify(UpdateStatus::DISABLED);
+ SetStatusAndNotify(UpdateStatus::IDLE);
+ return;
+ }
+
+ LOG(INFO) << "Running " << (params.interactive ? "interactive" : "periodic")
+ << " update.";
+
+ if (!params.interactive) {
+ // Cache the update attempt flags that will be used by this update attempt
+ // so that they can't be changed mid-way through.
+ current_update_attempt_flags_ = update_attempt_flags_;
+ }
+
+ LOG(INFO) << "Update attempt flags in use = 0x" << std::hex
+ << current_update_attempt_flags_;
+
+ Update(params);
+ // Always clear the forced app_version and omaha_url after an update attempt
+ // so the next update uses the defaults.
+ forced_app_version_.clear();
+ forced_omaha_url_.clear();
+ } else {
+ LOG(WARNING)
+ << "Update check scheduling failed (possibly timed out); retrying.";
+ ScheduleUpdates();
+ }
+
+ // This check ensures that future update checks will be or are already
+ // scheduled. The check should never fail. A check failure means that there's
+ // a bug that will most likely prevent further automatic update checks. It
+ // seems better to crash in such cases and restart the update_engine daemon
+ // into, hopefully, a known good state.
+ CHECK(IsBusyOrUpdateScheduled());
+}
+
+void UpdateAttempter::UpdateLastCheckedTime() {
+ last_checked_time_ = system_state_->clock()->GetWallclockTime().ToTimeT();
+}
+
+void UpdateAttempter::UpdateRollbackHappened() {
+ DCHECK(system_state_);
+ DCHECK(system_state_->payload_state());
+ DCHECK(policy_provider_);
+ if (system_state_->payload_state()->GetRollbackHappened() &&
+ (policy_provider_->device_policy_is_loaded() ||
+ policy_provider_->IsConsumerDevice())) {
+ // Rollback happened, but we already went through OOBE and policy is
+ // present or it's a consumer device.
+ system_state_->payload_state()->SetRollbackHappened(false);
+ }
+}
+
+void UpdateAttempter::ProcessingDoneInternal(const ActionProcessor* processor,
+ ErrorCode code) {
+ // Reset cpu shares back to normal.
+ cpu_limiter_.StopLimiter();
+
+ ResetInteractivityFlags();
+
+ if (status_ == UpdateStatus::REPORTING_ERROR_EVENT) {
+ LOG(INFO) << "Error event sent.";
+
+ // Inform scheduler of new status.
+ SetStatusAndNotify(UpdateStatus::IDLE);
+ ScheduleUpdates();
+
+ if (!fake_update_success_) {
+ return;
+ }
+ LOG(INFO) << "Booted from FW B and tried to install new firmware, "
+ "so requesting reboot from user.";
+ }
+
+ attempt_error_code_ = utils::GetBaseErrorCode(code);
+
+ if (code != ErrorCode::kSuccess) {
+ if (ScheduleErrorEventAction()) {
+ return;
+ }
+ LOG(INFO) << "No update.";
+ SetStatusAndNotify(UpdateStatus::IDLE);
+ ScheduleUpdates();
+ return;
+ }
+
+ ReportTimeToUpdateAppliedMetric();
+ prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
+ prefs_->SetString(kPrefsPreviousVersion,
+ omaha_request_params_->app_version());
+ DeltaPerformer::ResetUpdateProgress(prefs_, false);
+
+ system_state_->payload_state()->UpdateSucceeded();
+
+ // Since we're done with scattering fully at this point, this is the
+ // safest point delete the state files, as we're sure that the status is
+ // set to reboot (which means no more updates will be applied until reboot)
+ // This deletion is required for correctness as we want the next update
+ // check to re-create a new random number for the update check count.
+ // Similarly, we also delete the wall-clock-wait period that was persisted
+ // so that we start with a new random value for the next update check
+ // after reboot so that the same device is not favored or punished in any
+ // way.
+ prefs_->Delete(kPrefsUpdateCheckCount);
+ system_state_->payload_state()->SetScatteringWaitPeriod(TimeDelta());
+ system_state_->payload_state()->SetStagingWaitPeriod(TimeDelta());
+ prefs_->Delete(kPrefsUpdateFirstSeenAt);
+
+ // Note: below this comment should only be on |ErrorCode::kSuccess|.
+ if (is_install_) {
+ ProcessingDoneInstall(processor, code);
+ } else {
+ ProcessingDoneUpdate(processor, code);
+ }
+}
+
+vector<string> UpdateAttempter::GetSuccessfulDlcIds() {
+ vector<string> dlc_ids;
+ for (const auto& pr : omaha_request_params_->dlc_apps_params())
+ if (pr.second.updated)
+ dlc_ids.push_back(pr.second.name);
+ return dlc_ids;
+}
+
+void UpdateAttempter::ProcessingDoneInstall(const ActionProcessor* processor,
+ ErrorCode code) {
+ if (!system_state_->dlcservice()->InstallCompleted(GetSuccessfulDlcIds()))
+ LOG(WARNING) << "dlcservice didn't successfully handle install completion.";
+ SetStatusAndNotify(UpdateStatus::IDLE);
+ ScheduleUpdates();
+ LOG(INFO) << "DLC successfully installed, no reboot needed.";
+}
+
+void UpdateAttempter::ProcessingDoneUpdate(const ActionProcessor* processor,
+ ErrorCode code) {
+ WriteUpdateCompletedMarker();
+
+ if (!system_state_->dlcservice()->UpdateCompleted(GetSuccessfulDlcIds()))
+ LOG(WARNING) << "dlcservice didn't successfully handle update completion.";
+ SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+ ScheduleUpdates();
+ LOG(INFO) << "Update successfully applied, waiting to reboot.";
+
+ // |install_plan_| is null during rollback operations, and the stats don't
+ // make much sense then anyway.
+ if (install_plan_) {
+ // Generate an unique payload identifier.
+ string target_version_uid;
+ for (const auto& payload : install_plan_->payloads) {
+ target_version_uid += brillo::data_encoding::Base64Encode(payload.hash) +
+ ":" + payload.metadata_signature + ":";
+ }
+
+ // If we just downloaded a rollback image, we should preserve this fact
+ // over the following powerwash.
+ if (install_plan_->is_rollback) {
+ system_state_->payload_state()->SetRollbackHappened(true);
+ system_state_->metrics_reporter()->ReportEnterpriseRollbackMetrics(
+ /*success=*/true, install_plan_->version);
+ }
+
+ // Expect to reboot into the new version to send the proper metric during
+ // next boot.
+ system_state_->payload_state()->ExpectRebootInNewVersion(
+ target_version_uid);
+ } else {
+ // If we just finished a rollback, then we expect to have no Omaha
+ // response. Otherwise, it's an error.
+ if (system_state_->payload_state()->GetRollbackVersion().empty()) {
+ LOG(ERROR) << "Can't send metrics because there was no Omaha response";
+ }
+ }
+}
+
+// Delegate methods:
+void UpdateAttempter::ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {
+ LOG(INFO) << "Processing Done.";
+ ProcessingDoneInternal(processor, code);
+
+ // Note: do cleanups here for any variables that need to be reset after a
+ // failure, error, update, or install.
+ is_install_ = false;
+}
+
+void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) {
+ // Reset cpu shares back to normal.
+ cpu_limiter_.StopLimiter();
+ download_progress_ = 0.0;
+
+ ResetInteractivityFlags();
+
+ SetStatusAndNotify(UpdateStatus::IDLE);
+ ScheduleUpdates();
+ error_event_.reset(nullptr);
+}
+
+// Called whenever an action has finished processing, either successfully
+// or otherwise.
+void UpdateAttempter::ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ // Reset download progress regardless of whether or not the download
+ // action succeeded. Also, get the response code from HTTP request
+ // actions (update download as well as the initial update check
+ // actions).
+ const string type = action->Type();
+ if (type == DownloadAction::StaticType()) {
+ download_progress_ = 0.0;
+ DownloadAction* download_action = static_cast<DownloadAction*>(action);
+ http_response_code_ = download_action->GetHTTPResponseCode();
+ } else if (type == OmahaRequestAction::StaticType()) {
+ OmahaRequestAction* omaha_request_action =
+ static_cast<OmahaRequestAction*>(action);
+ // If the request is not an event, then it's the update-check.
+ if (!omaha_request_action->IsEvent()) {
+ http_response_code_ = omaha_request_action->GetHTTPResponseCode();
+
+ // Record the number of consecutive failed update checks.
+ if (http_response_code_ == kHttpResponseInternalServerError ||
+ http_response_code_ == kHttpResponseServiceUnavailable) {
+ consecutive_failed_update_checks_++;
+ } else {
+ consecutive_failed_update_checks_ = 0;
+ }
+
+ const OmahaResponse& omaha_response =
+ omaha_request_action->GetOutputObject();
+ // Store the server-dictated poll interval, if any.
+ server_dictated_poll_interval_ =
+ std::max(0, omaha_response.poll_interval);
+
+ // This update is ignored by omaha request action because update over
+ // cellular connection is not allowed. Needs to ask for user's permissions
+ // to update.
+ if (code == ErrorCode::kOmahaUpdateIgnoredOverCellular) {
+ new_version_ = omaha_response.version;
+ new_payload_size_ = 0;
+ for (const auto& package : omaha_response.packages) {
+ new_payload_size_ += package.size;
+ }
+ SetStatusAndNotify(UpdateStatus::NEED_PERMISSION_TO_UPDATE);
+ }
+ }
+ } else if (type == OmahaResponseHandlerAction::StaticType()) {
+ // Depending on the returned error code, note that an update is available.
+ if (code == ErrorCode::kOmahaUpdateDeferredPerPolicy ||
+ code == ErrorCode::kSuccess) {
+ // Note that the status will be updated to DOWNLOADING when some bytes
+ // get actually downloaded from the server and the BytesReceived
+ // callback is invoked. This avoids notifying the user that a download
+ // has started in cases when the server and the client are unable to
+ // initiate the download.
+ auto omaha_response_handler_action =
+ static_cast<OmahaResponseHandlerAction*>(action);
+ install_plan_.reset(
+ new InstallPlan(omaha_response_handler_action->install_plan()));
+ UpdateLastCheckedTime();
+ new_version_ = install_plan_->version;
+ new_payload_size_ = 0;
+ for (const auto& payload : install_plan_->payloads)
+ new_payload_size_ += payload.size;
+ cpu_limiter_.StartLimiter();
+ SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
+ }
+ }
+ // General failure cases.
+ if (code != ErrorCode::kSuccess) {
+ // If the current state is at or past the download phase, count the failure
+ // in case a switch to full update becomes necessary. Ignore network
+ // transfer timeouts and failures.
+ if (code != ErrorCode::kDownloadTransferError) {
+ switch (status_) {
+ case UpdateStatus::IDLE:
+ case UpdateStatus::CHECKING_FOR_UPDATE:
+ case UpdateStatus::UPDATE_AVAILABLE:
+ case UpdateStatus::NEED_PERMISSION_TO_UPDATE:
+ break;
+ case UpdateStatus::DOWNLOADING:
+ case UpdateStatus::VERIFYING:
+ case UpdateStatus::FINALIZING:
+ case UpdateStatus::UPDATED_NEED_REBOOT:
+ case UpdateStatus::REPORTING_ERROR_EVENT:
+ case UpdateStatus::ATTEMPTING_ROLLBACK:
+ case UpdateStatus::DISABLED:
+ case UpdateStatus::CLEANUP_PREVIOUS_UPDATE:
+ MarkDeltaUpdateFailure();
+ break;
+ }
+ }
+ if (code != ErrorCode::kNoUpdate) {
+ // On failure, schedule an error event to be sent to Omaha.
+ CreatePendingErrorEvent(action, code);
+ }
+ return;
+ }
+ // Find out which action completed (successfully).
+ if (type == DownloadAction::StaticType()) {
+ SetStatusAndNotify(UpdateStatus::FINALIZING);
+ } else if (type == FilesystemVerifierAction::StaticType()) {
+ // Log the system properties before the postinst and after the file system
+ // is verified. It used to be done in the postinst itself. But postinst
+ // cannot do this anymore. On the other hand, these logs are frequently
+ // looked at and it is preferable not to scatter them in random location in
+ // the log and rather log it right before the postinst. The reason not do
+ // this in the |PostinstallRunnerAction| is to prevent dependency from
+ // libpayload_consumer to libupdate_engine.
+ LogImageProperties();
+ }
+}
+
+void UpdateAttempter::BytesReceived(uint64_t bytes_progressed,
+ uint64_t bytes_received,
+ uint64_t total) {
+ // The PayloadState keeps track of how many bytes were actually downloaded
+ // from a given URL for the URL skipping logic.
+ system_state_->payload_state()->DownloadProgress(bytes_progressed);
+
+ double progress = 0;
+ if (total)
+ progress = static_cast<double>(bytes_received) / static_cast<double>(total);
+ if (status_ != UpdateStatus::DOWNLOADING || bytes_received == total) {
+ download_progress_ = progress;
+ SetStatusAndNotify(UpdateStatus::DOWNLOADING);
+ } else {
+ ProgressUpdate(progress);
+ }
+}
+
+void UpdateAttempter::DownloadComplete() {
+ system_state_->payload_state()->DownloadComplete();
+}
+
+void UpdateAttempter::ProgressUpdate(double progress) {
+ // Self throttle based on progress. Also send notifications if progress is
+ // too slow.
+ if (progress == 1.0 ||
+ progress - download_progress_ >= kBroadcastThresholdProgress ||
+ TimeTicks::Now() - last_notify_time_ >=
+ TimeDelta::FromSeconds(kBroadcastThresholdSeconds)) {
+ download_progress_ = progress;
+ BroadcastStatus();
+ }
+}
+
+void UpdateAttempter::ResetInteractivityFlags() {
+ // Reset the state that's only valid for a single update pass.
+ current_update_attempt_flags_ = UpdateAttemptFlags::kNone;
+
+ if (forced_update_pending_callback_.get())
+ // Clear prior interactive requests once the processor is done.
+ forced_update_pending_callback_->Run(false, false);
+}
+
+bool UpdateAttempter::ResetStatus() {
+ LOG(INFO) << "Attempting to reset state from "
+ << UpdateStatusToString(status_) << " to UpdateStatus::IDLE";
+
+ switch (status_) {
+ case UpdateStatus::IDLE:
+ // no-op.
+ return true;
+
+ case UpdateStatus::UPDATED_NEED_REBOOT: {
+ bool ret_value = true;
+ status_ = UpdateStatus::IDLE;
+
+ // Remove the reboot marker so that if the machine is rebooted
+ // after resetting to idle state, it doesn't go back to
+ // UpdateStatus::UPDATED_NEED_REBOOT state.
+ ret_value = prefs_->Delete(kPrefsUpdateCompletedOnBootId) && ret_value;
+ ret_value = prefs_->Delete(kPrefsUpdateCompletedBootTime) && ret_value;
+
+ // Update the boot flags so the current slot has higher priority.
+ BootControlInterface* boot_control = system_state_->boot_control();
+ if (!boot_control->SetActiveBootSlot(boot_control->GetCurrentSlot()))
+ ret_value = false;
+
+ // Mark the current slot as successful again, since marking it as active
+ // may reset the successful bit. We ignore the result of whether marking
+ // the current slot as successful worked.
+ if (!boot_control->MarkBootSuccessfulAsync(Bind([](bool successful) {})))
+ ret_value = false;
+
+ // Notify the PayloadState that the successful payload was canceled.
+ system_state_->payload_state()->ResetUpdateStatus();
+
+ // The previous version is used to report back to omaha after reboot that
+ // we actually rebooted into the new version from this "prev-version". We
+ // need to clear out this value now to prevent it being sent on the next
+ // updatecheck request.
+ ret_value = prefs_->SetString(kPrefsPreviousVersion, "") && ret_value;
+
+ LOG(INFO) << "Reset status " << (ret_value ? "successful" : "failed");
+ return ret_value;
+ }
+
+ default:
+ LOG(ERROR) << "Reset not allowed in this state.";
+ return false;
+ }
+}
+
+bool UpdateAttempter::GetStatus(UpdateEngineStatus* out_status) {
+ out_status->last_checked_time = last_checked_time_;
+ out_status->status = status_;
+ out_status->current_version = omaha_request_params_->app_version();
+ out_status->progress = download_progress_;
+ out_status->new_size_bytes = new_payload_size_;
+ out_status->new_version = new_version_;
+ out_status->is_enterprise_rollback =
+ install_plan_ && install_plan_->is_rollback;
+ out_status->is_install = is_install_;
+
+ string str_eol_date;
+ if (system_state_->prefs()->Exists(kPrefsOmahaEolDate) &&
+ !system_state_->prefs()->GetString(kPrefsOmahaEolDate, &str_eol_date))
+ LOG(ERROR) << "Failed to retrieve kPrefsOmahaEolDate pref.";
+ out_status->eol_date = StringToEolDate(str_eol_date);
+
+ // A powerwash will take place either if the install plan says it is required
+ // or if an enterprise rollback is happening.
+ out_status->will_powerwash_after_reboot =
+ install_plan_ &&
+ (install_plan_->powerwash_required || install_plan_->is_rollback);
+
+ return true;
+}
+
+void UpdateAttempter::BroadcastStatus() {
+ UpdateEngineStatus broadcast_status;
+ // Use common method for generating the current status.
+ GetStatus(&broadcast_status);
+
+ for (const auto& observer : service_observers_) {
+ observer->SendStatusUpdate(broadcast_status);
+ }
+ last_notify_time_ = TimeTicks::Now();
+}
+
+uint32_t UpdateAttempter::GetErrorCodeFlags() {
+ uint32_t flags = 0;
+
+ if (!system_state_->hardware()->IsNormalBootMode())
+ flags |= static_cast<uint32_t>(ErrorCode::kDevModeFlag);
+
+ if (install_plan_ && install_plan_->is_resume)
+ flags |= static_cast<uint32_t>(ErrorCode::kResumedFlag);
+
+ if (!system_state_->hardware()->IsOfficialBuild())
+ flags |= static_cast<uint32_t>(ErrorCode::kTestImageFlag);
+
+ if (!omaha_request_params_->IsUpdateUrlOfficial()) {
+ flags |= static_cast<uint32_t>(ErrorCode::kTestOmahaUrlFlag);
+ }
+
+ return flags;
+}
+
+bool UpdateAttempter::ShouldCancel(ErrorCode* cancel_reason) {
+ // Check if the channel we're attempting to update to is the same as the
+ // target channel currently chosen by the user.
+ OmahaRequestParams* params = system_state_->request_params();
+ if (params->download_channel() != params->target_channel()) {
+ LOG(ERROR) << "Aborting download as target channel: "
+ << params->target_channel()
+ << " is different from the download channel: "
+ << params->download_channel();
+ *cancel_reason = ErrorCode::kUpdateCanceledByChannelChange;
+ return true;
+ }
+
+ return false;
+}
+
+void UpdateAttempter::SetStatusAndNotify(UpdateStatus status) {
+ status_ = status;
+ BroadcastStatus();
+}
+
+void UpdateAttempter::CreatePendingErrorEvent(AbstractAction* action,
+ ErrorCode code) {
+ if (error_event_.get() || status_ == UpdateStatus::REPORTING_ERROR_EVENT) {
+ // This shouldn't really happen.
+ LOG(WARNING) << "There's already an existing pending error event.";
+ return;
+ }
+
+ // Classify the code to generate the appropriate result so that
+ // the Borgmon charts show up the results correctly.
+ // Do this before calling GetErrorCodeForAction which could potentially
+ // augment the bit representation of code and thus cause no matches for
+ // the switch cases below.
+ OmahaEvent::Result event_result;
+ switch (code) {
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ event_result = OmahaEvent::kResultUpdateDeferred;
+ break;
+ default:
+ event_result = OmahaEvent::kResultError;
+ break;
+ }
+
+ code = GetErrorCodeForAction(action, code);
+ fake_update_success_ = code == ErrorCode::kPostinstallBootedFromFirmwareB;
+
+ // Compute the final error code with all the bit flags to be sent to Omaha.
+ code =
+ static_cast<ErrorCode>(static_cast<uint32_t>(code) | GetErrorCodeFlags());
+ error_event_.reset(
+ new OmahaEvent(OmahaEvent::kTypeUpdateComplete, event_result, code));
+}
+
+bool UpdateAttempter::ScheduleErrorEventAction() {
+ if (error_event_.get() == nullptr)
+ return false;
+
+ LOG(ERROR) << "Update failed.";
+ system_state_->payload_state()->UpdateFailed(error_event_->error_code);
+
+ // Send metrics if it was a rollback.
+ if (install_plan_ && install_plan_->is_rollback) {
+ system_state_->metrics_reporter()->ReportEnterpriseRollbackMetrics(
+ /*success=*/false, install_plan_->version);
+ }
+
+ // Send it to Omaha.
+ LOG(INFO) << "Reporting the error event";
+ auto error_event_action = std::make_unique<OmahaRequestAction>(
+ system_state_,
+ error_event_.release(), // Pass ownership.
+ std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
+ system_state_->hardware()),
+ false,
+ session_id_);
+ processor_->EnqueueAction(std::move(error_event_action));
+ SetStatusAndNotify(UpdateStatus::REPORTING_ERROR_EVENT);
+ processor_->StartProcessing();
+ return true;
+}
+
+void UpdateAttempter::ScheduleProcessingStart() {
+ LOG(INFO) << "Scheduling an action processor start.";
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind([](ActionProcessor* processor) { processor->StartProcessing(); },
+ base::Unretained(processor_.get())));
+}
+
+void UpdateAttempter::DisableDeltaUpdateIfNeeded() {
+ int64_t delta_failures;
+ if (omaha_request_params_->delta_okay() &&
+ prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) &&
+ delta_failures >= kMaxDeltaUpdateFailures) {
+ LOG(WARNING) << "Too many delta update failures, forcing full update.";
+ omaha_request_params_->set_delta_okay(false);
+ }
+}
+
+void UpdateAttempter::MarkDeltaUpdateFailure() {
+ // Don't try to resume a failed delta update.
+ DeltaPerformer::ResetUpdateProgress(prefs_, false);
+ int64_t delta_failures;
+ if (!prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) ||
+ delta_failures < 0) {
+ delta_failures = 0;
+ }
+ prefs_->SetInt64(kPrefsDeltaUpdateFailures, ++delta_failures);
+}
+
+void UpdateAttempter::PingOmaha() {
+ if (!processor_->IsRunning()) {
+ ResetInteractivityFlags();
+
+ auto ping_action = std::make_unique<OmahaRequestAction>(
+ system_state_,
+ nullptr,
+ std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
+ system_state_->hardware()),
+ true,
+ "" /* session_id */);
+ processor_->set_delegate(nullptr);
+ processor_->EnqueueAction(std::move(ping_action));
+ // Call StartProcessing() synchronously here to avoid any race conditions
+ // caused by multiple outstanding ping Omaha requests. If we call
+ // StartProcessing() asynchronously, the device can be suspended before we
+ // get a chance to callback to StartProcessing(). When the device resumes
+ // (assuming the device sleeps longer than the next update check period),
+ // StartProcessing() is called back and at the same time, the next update
+ // check is fired which eventually invokes StartProcessing(). A crash
+ // can occur because StartProcessing() checks to make sure that the
+ // processor is idle which it isn't due to the two concurrent ping Omaha
+ // requests.
+ processor_->StartProcessing();
+ } else {
+ LOG(WARNING) << "Action processor running, Omaha ping suppressed.";
+ }
+
+ // Update the last check time here; it may be re-updated when an Omaha
+ // response is received, but this will prevent us from repeatedly scheduling
+ // checks in the case where a response is not received.
+ UpdateLastCheckedTime();
+
+ // Update the status which will schedule the next update check
+ SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+ ScheduleUpdates();
+}
+
+bool UpdateAttempter::DecrementUpdateCheckCount() {
+ int64_t update_check_count_value;
+
+ if (!prefs_->Exists(kPrefsUpdateCheckCount)) {
+ // This file does not exist. This means we haven't started our update
+ // check count down yet, so nothing more to do. This file will be created
+ // later when we first satisfy the wall-clock-based-wait period.
+ LOG(INFO) << "No existing update check count. That's normal.";
+ return true;
+ }
+
+ if (prefs_->GetInt64(kPrefsUpdateCheckCount, &update_check_count_value)) {
+ // Only if we're able to read a proper integer value, then go ahead
+ // and decrement and write back the result in the same file, if needed.
+ LOG(INFO) << "Update check count = " << update_check_count_value;
+
+ if (update_check_count_value == 0) {
+ // It could be 0, if, for some reason, the file didn't get deleted
+ // when we set our status to waiting for reboot. so we just leave it
+ // as is so that we can prevent another update_check wait for this client.
+ LOG(INFO) << "Not decrementing update check count as it's already 0.";
+ return true;
+ }
+
+ if (update_check_count_value > 0)
+ update_check_count_value--;
+ else
+ update_check_count_value = 0;
+
+ // Write out the new value of update_check_count_value.
+ if (prefs_->SetInt64(kPrefsUpdateCheckCount, update_check_count_value)) {
+ // We successfully wrote out the new value, so enable the
+ // update check based wait.
+ LOG(INFO) << "New update check count = " << update_check_count_value;
+ return true;
+ }
+ }
+
+ LOG(INFO) << "Deleting update check count state due to read/write errors.";
+
+ // We cannot read/write to the file, so disable the update check based wait
+ // so that we don't get stuck in this OS version by any chance (which could
+ // happen if there's some bug that causes to read/write incorrectly).
+ // Also attempt to delete the file to do our best effort to cleanup.
+ prefs_->Delete(kPrefsUpdateCheckCount);
+ return false;
+}
+
+void UpdateAttempter::UpdateEngineStarted() {
+ // If we just booted into a new update, keep the previous OS version
+ // in case we rebooted because of a crash of the old version, so we
+ // can do a proper crash report with correct information.
+ // This must be done before calling
+ // system_state_->payload_state()->UpdateEngineStarted() since it will
+ // delete SystemUpdated marker file.
+ if (system_state_->system_rebooted() &&
+ prefs_->Exists(kPrefsSystemUpdatedMarker)) {
+ if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version_)) {
+ // If we fail to get the version string, make sure it stays empty.
+ prev_version_.clear();
+ }
+ }
+
+ system_state_->payload_state()->UpdateEngineStarted();
+ StartP2PAtStartup();
+
+ excluder_ = CreateExcluder(system_state_->prefs());
+}
+
+bool UpdateAttempter::StartP2PAtStartup() {
+ if (system_state_ == nullptr ||
+ !system_state_->p2p_manager()->IsP2PEnabled()) {
+ LOG(INFO) << "Not starting p2p at startup since it's not enabled.";
+ return false;
+ }
+
+ if (system_state_->p2p_manager()->CountSharedFiles() < 1) {
+ LOG(INFO) << "Not starting p2p at startup since our application "
+ << "is not sharing any files.";
+ return false;
+ }
+
+ return StartP2PAndPerformHousekeeping();
+}
+
+bool UpdateAttempter::StartP2PAndPerformHousekeeping() {
+ if (system_state_ == nullptr)
+ return false;
+
+ if (!system_state_->p2p_manager()->IsP2PEnabled()) {
+ LOG(INFO) << "Not starting p2p since it's not enabled.";
+ return false;
+ }
+
+ LOG(INFO) << "Ensuring that p2p is running.";
+ if (!system_state_->p2p_manager()->EnsureP2PRunning()) {
+ LOG(ERROR) << "Error starting p2p.";
+ return false;
+ }
+
+ LOG(INFO) << "Performing p2p housekeeping.";
+ if (!system_state_->p2p_manager()->PerformHousekeeping()) {
+ LOG(ERROR) << "Error performing housekeeping for p2p.";
+ return false;
+ }
+
+ LOG(INFO) << "Done performing p2p housekeeping.";
+ return true;
+}
+
+bool UpdateAttempter::GetBootTimeAtUpdate(Time* out_boot_time) {
+ // In case of an update_engine restart without a reboot, we stored the boot_id
+ // when the update was completed by setting a pref, so we can check whether
+ // the last update was on this boot or a previous one.
+ string boot_id;
+ TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id));
+
+ string update_completed_on_boot_id;
+ if (!prefs_->Exists(kPrefsUpdateCompletedOnBootId) ||
+ !prefs_->GetString(kPrefsUpdateCompletedOnBootId,
+ &update_completed_on_boot_id) ||
+ update_completed_on_boot_id != boot_id)
+ return false;
+
+ // Short-circuit avoiding the read in case out_boot_time is nullptr.
+ if (out_boot_time) {
+ int64_t boot_time = 0;
+ // Since the kPrefsUpdateCompletedOnBootId was correctly set, this pref
+ // should not fail.
+ TEST_AND_RETURN_FALSE(
+ prefs_->GetInt64(kPrefsUpdateCompletedBootTime, &boot_time));
+ *out_boot_time = Time::FromInternalValue(boot_time);
+ }
+ return true;
+}
+
+bool UpdateAttempter::IsBusyOrUpdateScheduled() {
+ return ((status_ != UpdateStatus::IDLE &&
+ status_ != UpdateStatus::UPDATED_NEED_REBOOT) ||
+ waiting_for_scheduled_check_);
+}
+
+bool UpdateAttempter::IsAnyUpdateSourceAllowed() const {
+ // We allow updates from any source if either of these are true:
+ // * The device is running an unofficial (dev/test) image.
+ // * The debugd dev features are accessible (i.e. in devmode with no owner).
+ // This protects users running a base image, while still allowing a specific
+ // window (gated by the debug dev features) where `cros flash` is usable.
+ if (!system_state_->hardware()->IsOfficialBuild()) {
+ LOG(INFO) << "Non-official build; allowing any update source.";
+ return true;
+ }
+
+ if (system_state_->hardware()->AreDevFeaturesEnabled()) {
+ LOG(INFO) << "Developer features enabled; allowing custom update sources.";
+ return true;
+ }
+
+ LOG(INFO)
+ << "Developer features disabled; disallowing custom update sources.";
+ return false;
+}
+
+void UpdateAttempter::ReportTimeToUpdateAppliedMetric() {
+ const policy::DevicePolicy* device_policy = system_state_->device_policy();
+ if (device_policy && device_policy->IsEnterpriseEnrolled()) {
+ vector<policy::DevicePolicy::WeeklyTimeInterval> parsed_intervals;
+ bool has_time_restrictions =
+ device_policy->GetDisallowedTimeIntervals(&parsed_intervals);
+
+ int64_t update_first_seen_at_int;
+ if (system_state_->prefs()->Exists(kPrefsUpdateFirstSeenAt)) {
+ if (system_state_->prefs()->GetInt64(kPrefsUpdateFirstSeenAt,
+ &update_first_seen_at_int)) {
+ TimeDelta update_delay =
+ system_state_->clock()->GetWallclockTime() -
+ Time::FromInternalValue(update_first_seen_at_int);
+ system_state_->metrics_reporter()
+ ->ReportEnterpriseUpdateSeenToDownloadDays(has_time_restrictions,
+ update_delay.InDays());
+ }
+ }
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/update_attempter.h b/cros/update_attempter.h
new file mode 100644
index 0000000..0f4c952
--- /dev/null
+++ b/cros/update_attempter.h
@@ -0,0 +1,576 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CROS_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_CROS_UPDATE_ATTEMPTER_H_
+
+#include <time.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/guid.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/client_library/include/update_engine/update_status.h"
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/cpu_limiter.h"
+#include "update_engine/common/download_action.h"
+#include "update_engine/common/excluder_interface.h"
+#include "update_engine/common/proxy_resolver.h"
+#include "update_engine/common/service_observer_interface.h"
+#include "update_engine/common/system_state.h"
+#if USE_CHROME_NETWORK_PROXY
+#include "update_engine/cros/chrome_browser_proxy_resolver.h"
+#endif // USE_CHROME_NETWORK_PROXY
+#include "update_engine/cros/omaha_request_builder_xml.h"
+#include "update_engine/cros/omaha_request_params.h"
+#include "update_engine/cros/omaha_response_handler_action.h"
+#include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/staging_utils.h"
+#include "update_engine/update_manager/update_manager.h"
+
+namespace policy {
+class PolicyProvider;
+}
+
+namespace chromeos_update_engine {
+
+class UpdateAttempter : public ActionProcessorDelegate,
+ public DownloadActionDelegate,
+ public CertificateChecker::Observer,
+ public PostinstallRunnerAction::DelegateInterface {
+ public:
+ using UpdateStatus = update_engine::UpdateStatus;
+ using UpdateAttemptFlags = update_engine::UpdateAttemptFlags;
+ static const int kMaxDeltaUpdateFailures;
+
+ UpdateAttempter(SystemState* system_state, CertificateChecker* cert_checker);
+ ~UpdateAttempter() override;
+
+ // Further initialization to be done post construction.
+ void Init();
+
+ // Initiates scheduling of update checks.
+ // Returns true if update check is scheduled.
+ virtual bool ScheduleUpdates();
+
+ // Checks for update and, if a newer version is available, attempts to update
+ // the system.
+ virtual void Update(const chromeos_update_manager::UpdateCheckParams& params);
+
+ // ActionProcessorDelegate methods:
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) override;
+ void ProcessingStopped(const ActionProcessor* processor) override;
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) override;
+
+ // PostinstallRunnerAction::DelegateInterface
+ void ProgressUpdate(double progress) override;
+
+ // Resets the current state to UPDATE_STATUS_IDLE.
+ // Used by update_engine_client for restarting a new update without
+ // having to reboot once the previous update has reached
+ // UPDATE_STATUS_UPDATED_NEED_REBOOT state. This is used only
+ // for testing purposes.
+ virtual bool ResetStatus();
+
+ // Returns the current status in the out param. Returns true on success.
+ virtual bool GetStatus(update_engine::UpdateEngineStatus* out_status);
+
+ UpdateStatus status() const { return status_; }
+
+ int http_response_code() const { return http_response_code_; }
+ void set_http_response_code(int code) { http_response_code_ = code; }
+
+ // Set flags that influence how updates and checks are performed. These
+ // influence all future checks and updates until changed or the device
+ // reboots.
+ void SetUpdateAttemptFlags(UpdateAttemptFlags flags) {
+ update_attempt_flags_ = flags;
+ }
+
+ // Returns the update attempt flags that are in place for the current update
+ // attempt. These are cached at the start of an update attempt so that they
+ // remain constant throughout the process.
+ virtual UpdateAttemptFlags GetCurrentUpdateAttemptFlags() const {
+ return current_update_attempt_flags_;
+ }
+
+ // This is the internal entry point for going through an
+ // update. If the current status is idle invokes Update.
+ // This is called by the DBus implementation.
+ // This returns true if an update check was started, false if a check or an
+ // update was already in progress.
+ virtual bool CheckForUpdate(const std::string& app_version,
+ const std::string& omaha_url,
+ UpdateAttemptFlags flags);
+
+ // This is the version of CheckForUpdate called by AttemptInstall API.
+ virtual bool CheckForInstall(const std::vector<std::string>& dlc_ids,
+ const std::string& omaha_url);
+
+ // This is the internal entry point for going through a rollback. This will
+ // attempt to run the postinstall on the non-active partition and set it as
+ // the partition to boot from. If |powerwash| is True, perform a powerwash
+ // as part of rollback. Returns True on success.
+ bool Rollback(bool powerwash);
+
+ // This is the internal entry point for checking if we can rollback.
+ bool CanRollback() const;
+
+ // This is the internal entry point for getting a rollback partition name,
+ // if one exists. It returns the bootable rollback kernel device partition
+ // name or empty string if none is available.
+ BootControlInterface::Slot GetRollbackSlot() const;
+
+ // Initiates a reboot if the current state is
+ // UPDATED_NEED_REBOOT. Returns true on success, false otherwise.
+ bool RebootIfNeeded();
+
+ // Sets the DLC as active or inactive. See chromeos/common_service.h
+ virtual bool SetDlcActiveValue(bool is_active, const std::string& dlc_id);
+
+ // DownloadActionDelegate methods:
+ void BytesReceived(uint64_t bytes_progressed,
+ uint64_t bytes_received,
+ uint64_t total) override;
+
+ // Returns that the update should be canceled when the download channel was
+ // changed.
+ bool ShouldCancel(ErrorCode* cancel_reason) override;
+
+ void DownloadComplete() override;
+
+ // Broadcasts the current status to all observers.
+ void BroadcastStatus();
+
+ ErrorCode GetAttemptErrorCode() const { return attempt_error_code_; }
+
+ // Called at update_engine startup to do various house-keeping.
+ void UpdateEngineStarted();
+
+ // Returns the |Excluder| that is currently held onto.
+ virtual ExcluderInterface* GetExcluder() const { return excluder_.get(); }
+
+ // Reloads the device policy from libbrillo. Note: This method doesn't
+ // cause a real-time policy fetch from the policy server. It just reloads the
+ // latest value that libbrillo has cached. libbrillo fetches the policies
+ // from the server asynchronously at its own frequency.
+ virtual void RefreshDevicePolicy();
+
+ // Stores in |out_boot_time| the boottime (CLOCK_BOOTTIME) recorded at the
+ // time of the last successful update in the current boot. Returns false if
+ // there wasn't a successful update in the current boot.
+ virtual bool GetBootTimeAtUpdate(base::Time* out_boot_time);
+
+ // Returns a version OS version that was being used before the last reboot,
+ // and if that reboot happened to be into an update (current version).
+ // This will return an empty string otherwise.
+ const std::string& GetPrevVersion() const { return prev_version_; }
+
+ // Returns the number of consecutive failed update checks.
+ virtual unsigned int consecutive_failed_update_checks() const {
+ return consecutive_failed_update_checks_;
+ }
+
+ // Returns the poll interval dictated by Omaha, if provided; zero otherwise.
+ virtual unsigned int server_dictated_poll_interval() const {
+ return server_dictated_poll_interval_;
+ }
+
+ // Sets a callback to be used when either a forced update request is received
+ // (first argument set to true) or cleared by an update attempt (first
+ // argument set to false). The callback further encodes whether the forced
+ // check is an interactive one (second argument set to true). Takes ownership
+ // of the callback object. A null value disables callback on these events.
+ // Note that only one callback can be set, so effectively at most one client
+ // can be notified.
+ virtual void set_forced_update_pending_callback(
+ base::Callback<void(bool, bool)>* callback) {
+ forced_update_pending_callback_.reset(callback);
+ }
+
+ // Returns true if we should allow updates from any source. In official builds
+ // we want to restrict updates to known safe sources, but under certain
+ // conditions it's useful to allow updating from anywhere (e.g. to allow
+ // 'cros flash' to function properly).
+ bool IsAnyUpdateSourceAllowed() const;
+
+ // Add and remove a service observer.
+ void AddObserver(ServiceObserverInterface* observer) {
+ service_observers_.insert(observer);
+ }
+ void RemoveObserver(ServiceObserverInterface* observer) {
+ service_observers_.erase(observer);
+ }
+
+ const std::set<ServiceObserverInterface*>& service_observers() {
+ return service_observers_;
+ }
+
+ // Remove all the observers.
+ void ClearObservers() { service_observers_.clear(); }
+
+ private:
+ // Friend declarations for testing purposes.
+ friend class UpdateAttempterUnderTest;
+ friend class UpdateAttempterTest;
+ FRIEND_TEST(UpdateAttempterTest, ActionCompletedDownloadTest);
+ FRIEND_TEST(UpdateAttempterTest, ActionCompletedErrorTest);
+ FRIEND_TEST(UpdateAttempterTest, ActionCompletedOmahaRequestTest);
+ FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
+ FRIEND_TEST(UpdateAttempterTest, BroadcastCompleteDownloadTest);
+ FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsInstallTest);
+ FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsNoPrefFilesTest);
+ FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsNonParseableValuesTest);
+ FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsValidValuesTest);
+ FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsRemoveStaleMetadata);
+ FRIEND_TEST(UpdateAttempterTest, ChangeToDownloadingOnReceivedBytesTest);
+ FRIEND_TEST(UpdateAttempterTest, CheckForInstallNotIdleFails);
+ FRIEND_TEST(UpdateAttempterTest, CheckForUpdateAUDlcTest);
+ FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventTest);
+ FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+ FRIEND_TEST(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest);
+ FRIEND_TEST(UpdateAttempterTest, DownloadProgressAccumulationTest);
+ FRIEND_TEST(UpdateAttempterTest, InstallSetsStatusIdle);
+ FRIEND_TEST(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusTrue);
+ FRIEND_TEST(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusFalse);
+ FRIEND_TEST(UpdateAttempterTest,
+ PowerwashInGetStatusTrueBecausePowerwashRequired);
+ FRIEND_TEST(UpdateAttempterTest, PowerwashInGetStatusTrueBecauseRollback);
+ FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
+ FRIEND_TEST(UpdateAttempterTest, PingOmahaTest);
+ FRIEND_TEST(UpdateAttempterTest, ProcessingDoneInstallError);
+ FRIEND_TEST(UpdateAttempterTest, ProcessingDoneUpdateError);
+ FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
+ FRIEND_TEST(UpdateAttempterTest, RollbackNotAllowed);
+ FRIEND_TEST(UpdateAttempterTest, RollbackAfterInstall);
+ FRIEND_TEST(UpdateAttempterTest, RollbackAllowed);
+ FRIEND_TEST(UpdateAttempterTest, RollbackAllowedSetAndReset);
+ FRIEND_TEST(UpdateAttempterTest, ChannelDowngradeNoRollback);
+ FRIEND_TEST(UpdateAttempterTest, ChannelDowngradeRollback);
+ FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackFailure);
+ FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess);
+ FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackFailure);
+ FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackSuccess);
+ FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest);
+ FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
+ FRIEND_TEST(UpdateAttempterTest, SessionIdTestEnforceEmptyStrPingOmaha);
+ FRIEND_TEST(UpdateAttempterTest, SessionIdTestOnOmahaRequestActions);
+ FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
+ FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
+ FRIEND_TEST(UpdateAttempterTest, TargetChannelHintSetAndReset);
+ FRIEND_TEST(UpdateAttempterTest, TargetVersionPrefixSetAndReset);
+ FRIEND_TEST(UpdateAttempterTest, UpdateAfterInstall);
+ FRIEND_TEST(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart);
+ FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
+ FRIEND_TEST(UpdateAttempterTest, UpdateIsNotRunningWhenUpdateAvailable);
+ FRIEND_TEST(UpdateAttempterTest, GetSuccessfulDlcIds);
+
+ // Returns the special flags to be added to ErrorCode values based on the
+ // parameters used in the current update attempt.
+ uint32_t GetErrorCodeFlags();
+
+ // ActionProcessorDelegate methods |ProcessingDone()| internal helpers.
+ void ProcessingDoneInternal(const ActionProcessor* processor, ErrorCode code);
+ void ProcessingDoneUpdate(const ActionProcessor* processor, ErrorCode code);
+ void ProcessingDoneInstall(const ActionProcessor* processor, ErrorCode code);
+
+ // CertificateChecker::Observer method.
+ // Report metrics about the certificate being checked.
+ void CertificateChecked(ServerToCheck server_to_check,
+ CertificateCheckResult result) override;
+
+ // Checks if it's more than 24 hours since daily metrics were last
+ // reported and, if so, reports daily metrics. Returns |true| if
+ // metrics were reported, |false| otherwise.
+ bool CheckAndReportDailyMetrics();
+
+ // Calculates and reports the age of the currently running OS. This
+ // is defined as the age of the /etc/lsb-release file.
+ void ReportOSAge();
+
+ // Sets the status to the given status and notifies a status update over dbus.
+ void SetStatusAndNotify(UpdateStatus status);
+
+ // Creates an error event object in |error_event_| to be included in an
+ // OmahaRequestAction once the current action processor is done.
+ void CreatePendingErrorEvent(AbstractAction* action, ErrorCode code);
+
+ // If there's a pending error event allocated in |error_event_|, schedules an
+ // OmahaRequestAction with that event in the current processor, clears the
+ // pending event, updates the status and returns true. Returns false
+ // otherwise.
+ bool ScheduleErrorEventAction();
+
+ // Schedules an event loop callback to start the action processor. This is
+ // scheduled asynchronously to unblock the event loop.
+ void ScheduleProcessingStart();
+
+ // Checks if a full update is needed and forces it by updating the Omaha
+ // request params.
+ void DisableDeltaUpdateIfNeeded();
+
+ // If this was a delta update attempt that failed, count it so that a full
+ // update can be tried when needed.
+ void MarkDeltaUpdateFailure();
+
+ ProxyResolver* GetProxyResolver() {
+#if USE_CHROME_NETWORK_PROXY
+ if (obeying_proxies_)
+ return &chrome_proxy_resolver_;
+#endif // USE_CHROME_NETWORK_PROXY
+ return &direct_proxy_resolver_;
+ }
+
+ // Sends a ping to Omaha.
+ // This is used after an update has been applied and we're waiting for the
+ // user to reboot. This ping helps keep the number of actives count
+ // accurate in case a user takes a long time to reboot the device after an
+ // update has been applied.
+ void PingOmaha();
+
+ // Helper method of Update() to calculate the update-related parameters
+ // from various sources and set the appropriate state. Please refer to
+ // Update() method for the meaning of the parameters.
+ bool CalculateUpdateParams(
+ const chromeos_update_manager::UpdateCheckParams& params);
+
+ // Calculates all the scattering related parameters (such as waiting period,
+ // which type of scattering is enabled, etc.) and also updates/deletes
+ // the corresponding prefs file used in scattering. Should be called
+ // only after the device policy has been loaded and set in the system_state_.
+ void CalculateScatteringParams(bool interactive);
+
+ // Sets a random value for the waiting period to wait for before downloading
+ // an update, if one available. This value will be upperbounded by the
+ // scatter factor value specified from policy.
+ void GenerateNewWaitingPeriod();
+
+ // Helper method of Update() to construct the sequence of actions to
+ // be performed for an update check. Please refer to
+ // Update() method for the meaning of the parameters.
+ void BuildUpdateActions(bool interactive);
+
+ // Decrements the count in the kUpdateCheckCountFilePath.
+ // Returns True if successfully decremented, false otherwise.
+ bool DecrementUpdateCheckCount();
+
+ // Starts p2p and performs housekeeping. Returns true only if p2p is
+ // running and housekeeping was done.
+ bool StartP2PAndPerformHousekeeping();
+
+ // Calculates whether peer-to-peer should be used. Sets the
+ // |use_p2p_to_download_| and |use_p2p_to_share_| parameters
+ // on the |omaha_request_params_| object.
+ void CalculateP2PParams(bool interactive);
+
+ // Starts P2P if it's enabled and there are files to actually share.
+ // Called only at program startup. Returns true only if p2p was
+ // started and housekeeping was performed.
+ bool StartP2PAtStartup();
+
+ // Writes to the processing completed marker. Does nothing if
+ // |update_completed_marker_| is empty.
+ void WriteUpdateCompletedMarker();
+
+ // Reboots the system directly by calling /sbin/shutdown. Returns true on
+ // success.
+ bool RebootDirectly();
+
+ // Callback for the async UpdateCheckAllowed policy request. If |status| is
+ // |EvalStatus::kSucceeded|, either runs or suppresses periodic update checks,
+ // based on the content of |params|. Otherwise, retries the policy request.
+ void OnUpdateScheduled(
+ chromeos_update_manager::EvalStatus status,
+ const chromeos_update_manager::UpdateCheckParams& params);
+
+ // Updates the time an update was last attempted to the current time.
+ void UpdateLastCheckedTime();
+
+ // Checks whether we need to clear the rollback-happened preference after
+ // policy is available again.
+ void UpdateRollbackHappened();
+
+ // Returns if an update is: running, applied and needs reboot, or scheduled.
+ bool IsBusyOrUpdateScheduled();
+
+ void CalculateStagingParams(bool interactive);
+
+ // Reports a metric that tracks the time from when the update was first seen
+ // to the time when the update was finally downloaded and applied. This metric
+ // will only be reported for enterprise enrolled devices.
+ void ReportTimeToUpdateAppliedMetric();
+
+ // Resets interactivity and forced update flags.
+ void ResetInteractivityFlags();
+
+ // Resets all the DLC prefs.
+ bool ResetDlcPrefs(const std::string& dlc_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.
+ // The value is equal to -1 the first time it is being sent, which is
+ // when the metadata file doesn't exist.
+ int64_t GetPingMetadata(const std::string& metadata_key) const;
+
+ // Calculates the update parameters for DLCs. Sets the |dlc_ids_|
+ // parameter on the |omaha_request_params_| object.
+ void CalculateDlcParams();
+
+ // Returns the list of DLC IDs that were installed/updated, excluding the ones
+ // which had "noupdate" in the Omaha response.
+ std::vector<std::string> GetSuccessfulDlcIds();
+
+ // Last status notification timestamp used for throttling. Use monotonic
+ // TimeTicks to ensure that notifications are sent even if the system clock is
+ // set back in the middle of an update.
+ base::TimeTicks last_notify_time_;
+
+ // Our two proxy resolvers
+ DirectProxyResolver direct_proxy_resolver_;
+#if USE_CHROME_NETWORK_PROXY
+ ChromeBrowserProxyResolver chrome_proxy_resolver_;
+#endif // USE_CHROME_NETWORK_PROXY
+
+ std::unique_ptr<ActionProcessor> processor_;
+
+ // External state of the system outside the update_engine process
+ // carved out separately to mock out easily in unit tests.
+ SystemState* system_state_;
+
+ // Pointer to the certificate checker instance to use.
+ CertificateChecker* cert_checker_;
+
+ // The list of services observing changes in the updater.
+ std::set<ServiceObserverInterface*> service_observers_;
+
+ // The install plan.
+ std::unique_ptr<InstallPlan> install_plan_;
+
+ // Pointer to the preferences store interface. This is just a cached
+ // copy of system_state->prefs() because it's used in many methods and
+ // is convenient this way.
+ PrefsInterface* prefs_ = nullptr;
+
+ // Pending error event, if any.
+ std::unique_ptr<OmahaEvent> error_event_;
+
+ // If we should request a reboot even tho we failed the update
+ bool fake_update_success_ = false;
+
+ // HTTP server response code from the last HTTP request action.
+ int http_response_code_ = 0;
+
+ // The attempt error code when the update attempt finished.
+ ErrorCode attempt_error_code_ = ErrorCode::kSuccess;
+
+ // CPU limiter during the update.
+ CPULimiter cpu_limiter_;
+
+ // For status:
+ UpdateStatus status_{UpdateStatus::IDLE};
+ double download_progress_ = 0.0;
+ int64_t last_checked_time_ = 0;
+ std::string prev_version_;
+ std::string new_version_ = "0.0.0.0";
+ uint64_t new_payload_size_ = 0;
+ // Flags influencing all periodic update checks
+ UpdateAttemptFlags update_attempt_flags_ = UpdateAttemptFlags::kNone;
+ // Flags influencing the currently in-progress check (cached at the start of
+ // the update check).
+ UpdateAttemptFlags current_update_attempt_flags_ = UpdateAttemptFlags::kNone;
+
+ // Common parameters for all Omaha requests.
+ OmahaRequestParams* omaha_request_params_ = nullptr;
+
+ // Number of consecutive manual update checks we've had where we obeyed
+ // Chrome's proxy settings.
+ int proxy_manual_checks_ = 0;
+
+ // If true, this update cycle we are obeying proxies
+ bool obeying_proxies_ = true;
+
+ // Used for fetching information about the device policy.
+ std::unique_ptr<policy::PolicyProvider> policy_provider_;
+
+ // The current scatter factor as found in the policy setting.
+ base::TimeDelta scatter_factor_;
+
+ // The number of consecutive failed update checks. Needed for calculating the
+ // next update check interval.
+ unsigned int consecutive_failed_update_checks_ = 0;
+
+ // The poll interval (in seconds) that was dictated by Omaha, if any; zero
+ // otherwise. This is needed for calculating the update check interval.
+ unsigned int server_dictated_poll_interval_ = 0;
+
+ // Tracks whether we have scheduled update checks.
+ bool waiting_for_scheduled_check_ = false;
+
+ // A callback to use when a forced update request is either received (true) or
+ // cleared by an update attempt (false). The second argument indicates whether
+ // this is an interactive update, and its value is significant iff the first
+ // argument is true.
+ std::unique_ptr<base::Callback<void(bool, bool)>>
+ forced_update_pending_callback_;
+
+ // The |app_version| and |omaha_url| parameters received during the latest
+ // forced update request. They are retrieved for use once the update is
+ // actually scheduled.
+ std::string forced_app_version_;
+ std::string forced_omaha_url_;
+
+ // A list of DLC module IDs.
+ std::vector<std::string> dlc_ids_;
+ // Whether the operation is install (write to the current slot not the
+ // inactive slot).
+ bool is_install_;
+
+ // If this is not TimeDelta(), then that means staging is turned on.
+ base::TimeDelta staging_wait_time_;
+ chromeos_update_manager::StagingSchedule staging_schedule_;
+
+ // This is the session ID used to track update flow to Omaha.
+ std::string session_id_;
+
+ // Interface for excluder.
+ std::unique_ptr<ExcluderInterface> excluder_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
+};
+
+// Turns a generic ErrorCode::kError to a generic error code specific
+// to |action| (e.g., ErrorCode::kFilesystemVerifierError). If |code| is
+// not ErrorCode::kError, or the action is not matched, returns |code|
+// unchanged.
+
+ErrorCode GetErrorCodeForAction(AbstractAction* action, ErrorCode code);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CROS_UPDATE_ATTEMPTER_H_
diff --git a/cros/update_attempter_unittest.cc b/cros/update_attempter_unittest.cc
new file mode 100644
index 0000000..f3211a0
--- /dev/null
+++ b/cros/update_attempter_unittest.cc
@@ -0,0 +1,2556 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/update_attempter.h"
+
+#include <stdint.h>
+
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_set>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/task/single_thread_task_executor.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+#include <policy/mock_libpolicy.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/dlcservice_interface.h"
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/common/mock_action.h"
+#include "update_engine/common/mock_action_processor.h"
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/mock_prefs.h"
+#include "update_engine/common/mock_service_observer.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/fake_system_state.h"
+#include "update_engine/cros/mock_p2p_manager.h"
+#include "update_engine/cros/mock_payload_state.h"
+#include "update_engine/cros/omaha_utils.h"
+#include "update_engine/libcurl_http_fetcher.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"
+#include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/update_boot_flags_action.h"
+#include "update_engine/update_manager/mock_update_manager.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::MockUpdateManager;
+using chromeos_update_manager::StagingSchedule;
+using chromeos_update_manager::UpdateCheckParams;
+using policy::DevicePolicy;
+using std::map;
+using std::string;
+using std::unique_ptr;
+using std::unordered_set;
+using std::vector;
+using testing::_;
+using testing::Contains;
+using testing::DoAll;
+using testing::ElementsAre;
+using testing::Field;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Ne;
+using testing::NiceMock;
+using testing::Pointee;
+using testing::Property;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::ReturnRef;
+using testing::SaveArg;
+using testing::SetArgPointee;
+using update_engine::UpdateAttemptFlags;
+using update_engine::UpdateEngineStatus;
+using update_engine::UpdateStatus;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const UpdateStatus kNonIdleUpdateStatuses[] = {
+ UpdateStatus::CHECKING_FOR_UPDATE,
+ UpdateStatus::UPDATE_AVAILABLE,
+ UpdateStatus::DOWNLOADING,
+ UpdateStatus::VERIFYING,
+ UpdateStatus::FINALIZING,
+ UpdateStatus::UPDATED_NEED_REBOOT,
+ UpdateStatus::REPORTING_ERROR_EVENT,
+ UpdateStatus::ATTEMPTING_ROLLBACK,
+ UpdateStatus::DISABLED,
+ UpdateStatus::NEED_PERMISSION_TO_UPDATE,
+};
+
+struct CheckForUpdateTestParams {
+ // Setups + Inputs:
+ UpdateStatus status = UpdateStatus::IDLE;
+ string app_version = "fake_app_version";
+ string omaha_url = "fake_omaha_url";
+ UpdateAttemptFlags flags = UpdateAttemptFlags::kNone;
+ bool is_official_build = true;
+ bool are_dev_features_enabled = false;
+
+ // Expects:
+ string expected_forced_app_version = "";
+ string expected_forced_omaha_url = "";
+ bool should_schedule_updates_be_called = true;
+ bool expected_result = true;
+};
+
+struct OnUpdateScheduledTestParams {
+ // Setups + Inputs:
+ UpdateCheckParams params = {};
+ EvalStatus status = EvalStatus::kFailed;
+ // Expects:
+ UpdateStatus exit_status = UpdateStatus::IDLE;
+ bool should_schedule_updates_be_called = false;
+ bool should_update_be_called = false;
+};
+
+struct ProcessingDoneTestParams {
+ // Setups + Inputs:
+ bool is_install = false;
+ UpdateStatus status = UpdateStatus::CHECKING_FOR_UPDATE;
+ ActionProcessor* processor = nullptr;
+ ErrorCode code = ErrorCode::kSuccess;
+ map<string, OmahaRequestParams::AppParams> dlc_apps_params;
+
+ // Expects:
+ const bool kExpectedIsInstall = false;
+ bool should_schedule_updates_be_called = true;
+ UpdateStatus expected_exit_status = UpdateStatus::IDLE;
+ bool should_install_completed_be_called = false;
+ bool should_update_completed_be_called = false;
+ vector<string> args_to_install_completed;
+ vector<string> args_to_update_completed;
+};
+
+class MockDlcService : public DlcServiceInterface {
+ public:
+ MOCK_METHOD1(GetDlcsToUpdate, bool(vector<string>*));
+ MOCK_METHOD1(InstallCompleted, bool(const vector<string>&));
+ MOCK_METHOD1(UpdateCompleted, bool(const vector<string>&));
+};
+
+} // namespace
+
+const char kRollbackVersion[] = "10575.39.2";
+
+// Test a subclass rather than the main class directly so that we can mock out
+// methods within the class. There're explicit unit tests for the mocked out
+// methods.
+class UpdateAttempterUnderTest : public UpdateAttempter {
+ public:
+ explicit UpdateAttempterUnderTest(SystemState* system_state)
+ : UpdateAttempter(system_state, nullptr) {}
+
+ void Update(const UpdateCheckParams& params) override {
+ update_called_ = true;
+ if (do_update_) {
+ UpdateAttempter::Update(params);
+ return;
+ }
+ LOG(INFO) << "[TEST] Update() disabled.";
+ status_ = UpdateStatus::CHECKING_FOR_UPDATE;
+ }
+
+ void DisableUpdate() { do_update_ = false; }
+
+ bool WasUpdateCalled() const { return update_called_; }
+
+ // Wrap the update scheduling method, allowing us to opt out of scheduled
+ // updates for testing purposes.
+ bool ScheduleUpdates() override {
+ schedule_updates_called_ = true;
+ if (do_schedule_updates_)
+ return UpdateAttempter::ScheduleUpdates();
+ LOG(INFO) << "[TEST] Update scheduling disabled.";
+ waiting_for_scheduled_check_ = true;
+ return true;
+ }
+
+ void DisableScheduleUpdates() { do_schedule_updates_ = false; }
+
+ // Indicates whether |ScheduleUpdates()| was called.
+ bool WasScheduleUpdatesCalled() const { return schedule_updates_called_; }
+
+ // Need to expose following private members of |UpdateAttempter| for tests.
+ const string& forced_app_version() const { return forced_app_version_; }
+ const string& forced_omaha_url() const { return forced_omaha_url_; }
+
+ // Need to expose |waiting_for_scheduled_check_| for testing.
+ void SetWaitingForScheduledCheck(bool waiting) {
+ waiting_for_scheduled_check_ = waiting;
+ }
+
+ private:
+ // Used for overrides of |Update()|.
+ bool update_called_ = false;
+ bool do_update_ = true;
+
+ // Used for overrides of |ScheduleUpdates()|.
+ bool schedule_updates_called_ = false;
+ bool do_schedule_updates_ = true;
+};
+
+class UpdateAttempterTest : public ::testing::Test {
+ protected:
+ UpdateAttempterTest()
+ : certificate_checker_(fake_system_state_.mock_prefs(),
+ &openssl_wrapper_) {
+ // Override system state members.
+ fake_system_state_.set_connection_manager(&mock_connection_manager);
+ fake_system_state_.set_update_attempter(&attempter_);
+ fake_system_state_.set_dlcservice(&mock_dlcservice_);
+ fake_system_state_.set_update_manager(&mock_update_manager_);
+ loop_.SetAsCurrent();
+
+ certificate_checker_.Init();
+
+ attempter_.set_forced_update_pending_callback(
+ new base::Callback<void(bool, bool)>(base::Bind([](bool, bool) {})));
+ // Finish initializing the attempter.
+ attempter_.Init();
+ }
+
+ void SetUp() override {
+ EXPECT_NE(nullptr, attempter_.system_state_);
+ EXPECT_NE(nullptr, attempter_.system_state_->update_manager());
+ EXPECT_EQ(0, attempter_.http_response_code_);
+ EXPECT_EQ(UpdateStatus::IDLE, attempter_.status_);
+ EXPECT_EQ(0.0, attempter_.download_progress_);
+ EXPECT_EQ(0, attempter_.last_checked_time_);
+ EXPECT_EQ("0.0.0.0", attempter_.new_version_);
+ EXPECT_EQ(0ULL, attempter_.new_payload_size_);
+ processor_ = new NiceMock<MockActionProcessor>();
+ attempter_.processor_.reset(processor_); // Transfers ownership.
+ prefs_ = fake_system_state_.mock_prefs();
+
+ // Setup store/load semantics of P2P properties via the mock |PayloadState|.
+ actual_using_p2p_for_downloading_ = false;
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetUsingP2PForDownloading(_))
+ .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_downloading_));
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ GetUsingP2PForDownloading())
+ .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_downloading_));
+ actual_using_p2p_for_sharing_ = false;
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetUsingP2PForSharing(_))
+ .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_sharing_));
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ GetUsingP2PForDownloading())
+ .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_sharing_));
+ }
+
+ public:
+ void ScheduleQuitMainLoop();
+
+ // Callbacks to run the different tests from the main loop.
+ void UpdateTestStart();
+ void UpdateTestVerify();
+ void RollbackTestStart(bool enterprise_rollback, bool valid_slot);
+ void RollbackTestVerify();
+ void PingOmahaTestStart();
+ void ReadScatterFactorFromPolicyTestStart();
+ void DecrementUpdateCheckCountTestStart();
+ void NoScatteringDoneDuringManualUpdateTestStart();
+ void P2PNotEnabledStart();
+ void P2PEnabledStart();
+ void P2PEnabledInteractiveStart();
+ void P2PEnabledStartingFailsStart();
+ void P2PEnabledHousekeepingFailsStart();
+ void SessionIdTestChange();
+ void SessionIdTestEnforceEmptyStrPingOmaha();
+ void SessionIdTestConsistencyInUpdateFlow();
+ void SessionIdTestInDownloadAction();
+ void UpdateToQuickFixBuildStart(bool set_token);
+ void ResetRollbackHappenedStart(bool is_consumer,
+ bool is_policy_available,
+ bool expected_reset);
+ // Staging related callbacks.
+ void SetUpStagingTest(const StagingSchedule& schedule, FakePrefs* prefs);
+ void CheckStagingOff();
+ void StagingSetsPrefsAndTurnsOffScatteringStart();
+ void StagingOffIfInteractiveStart();
+ void StagingOffIfOobeStart();
+
+ bool actual_using_p2p_for_downloading() {
+ return actual_using_p2p_for_downloading_;
+ }
+ bool actual_using_p2p_for_sharing() { return actual_using_p2p_for_sharing_; }
+
+ // |CheckForUpdate()| related member functions.
+ void TestCheckForUpdate();
+
+ // |OnUpdateScheduled()| related member functions.
+ void TestOnUpdateScheduled();
+
+ // |ProcessingDone()| related member functions.
+ void TestProcessingDone();
+
+ base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO};
+ brillo::BaseMessageLoop loop_{base_loop_.task_runner()};
+
+ FakeSystemState fake_system_state_;
+ UpdateAttempterUnderTest attempter_{&fake_system_state_};
+ OpenSSLWrapper openssl_wrapper_;
+ CertificateChecker certificate_checker_;
+ MockDlcService mock_dlcservice_;
+ MockUpdateManager mock_update_manager_;
+
+ NiceMock<MockActionProcessor>* processor_;
+ NiceMock<MockPrefs>*
+ prefs_; // Shortcut to |fake_system_state_->mock_prefs()|.
+ NiceMock<MockConnectionManager> mock_connection_manager;
+
+ // |CheckForUpdate()| test params.
+ CheckForUpdateTestParams cfu_params_;
+
+ // |OnUpdateScheduled()| test params.
+ OnUpdateScheduledTestParams ous_params_;
+
+ // |ProcessingDone()| test params.
+ ProcessingDoneTestParams pd_params_;
+
+ bool actual_using_p2p_for_downloading_;
+ bool actual_using_p2p_for_sharing_;
+};
+
+void UpdateAttempterTest::TestCheckForUpdate() {
+ // Setup
+ attempter_.status_ = cfu_params_.status;
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(
+ cfu_params_.is_official_build);
+ fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(
+ cfu_params_.are_dev_features_enabled);
+
+ // Invocation
+ EXPECT_EQ(
+ cfu_params_.expected_result,
+ attempter_.CheckForUpdate(
+ cfu_params_.app_version, cfu_params_.omaha_url, cfu_params_.flags));
+
+ // Verify
+ EXPECT_EQ(cfu_params_.expected_forced_app_version,
+ attempter_.forced_app_version());
+ EXPECT_EQ(cfu_params_.expected_forced_omaha_url,
+ attempter_.forced_omaha_url());
+ EXPECT_EQ(cfu_params_.should_schedule_updates_be_called,
+ attempter_.WasScheduleUpdatesCalled());
+}
+
+void UpdateAttempterTest::TestProcessingDone() {
+ // Setup
+ attempter_.DisableScheduleUpdates();
+ attempter_.is_install_ = pd_params_.is_install;
+ attempter_.status_ = pd_params_.status;
+ attempter_.omaha_request_params_->set_dlc_apps_params(
+ pd_params_.dlc_apps_params);
+
+ // Expects
+ if (pd_params_.should_install_completed_be_called)
+ EXPECT_CALL(mock_dlcservice_,
+ InstallCompleted(pd_params_.args_to_install_completed))
+ .WillOnce(Return(true));
+ else
+ EXPECT_CALL(mock_dlcservice_, InstallCompleted(_)).Times(0);
+ if (pd_params_.should_update_completed_be_called)
+ EXPECT_CALL(mock_dlcservice_,
+ UpdateCompleted(pd_params_.args_to_update_completed))
+ .WillOnce(Return(true));
+ else
+ EXPECT_CALL(mock_dlcservice_, UpdateCompleted(_)).Times(0);
+
+ // Invocation
+ attempter_.ProcessingDone(pd_params_.processor, pd_params_.code);
+
+ // Verify
+ EXPECT_EQ(pd_params_.kExpectedIsInstall, attempter_.is_install_);
+ EXPECT_EQ(pd_params_.should_schedule_updates_be_called,
+ attempter_.WasScheduleUpdatesCalled());
+ EXPECT_EQ(pd_params_.expected_exit_status, attempter_.status_);
+}
+
+void UpdateAttempterTest::ScheduleQuitMainLoop() {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind([](brillo::BaseMessageLoop* loop) { loop->BreakLoop(); },
+ base::Unretained(&loop_)));
+}
+
+void UpdateAttempterTest::SessionIdTestChange() {
+ EXPECT_NE(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status());
+ const auto old_session_id = attempter_.session_id_;
+ attempter_.Update({});
+ EXPECT_NE(old_session_id, attempter_.session_id_);
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, SessionIdTestChange) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::SessionIdTestChange,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::SessionIdTestEnforceEmptyStrPingOmaha() {
+ // The |session_id_| should not be changed and should remain as an empty
+ // string when |status_| is |UPDATED_NEED_REBOOT| (only for consistency)
+ // and |PingOmaha()| is called.
+ attempter_.DisableScheduleUpdates();
+ attempter_.status_ = UpdateStatus::UPDATED_NEED_REBOOT;
+ const auto old_session_id = attempter_.session_id_;
+ auto CheckIfEmptySessionId = [](AbstractAction* aa) {
+ if (aa->Type() == OmahaRequestAction::StaticType()) {
+ EXPECT_TRUE(static_cast<OmahaRequestAction*>(aa)->session_id_.empty());
+ }
+ };
+ EXPECT_CALL(*processor_, EnqueueAction(Pointee(_)))
+ .WillRepeatedly(Invoke(CheckIfEmptySessionId));
+ EXPECT_CALL(*processor_, StartProcessing());
+ attempter_.PingOmaha();
+ EXPECT_EQ(old_session_id, attempter_.session_id_);
+ EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status_);
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, SessionIdTestEnforceEmptyStrPingOmaha) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempterTest::SessionIdTestEnforceEmptyStrPingOmaha,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::SessionIdTestConsistencyInUpdateFlow() {
+ // All session IDs passed into |OmahaRequestActions| should be enforced to
+ // have the same value in |BuildUpdateActions()|.
+ unordered_set<string> session_ids;
+ // Gather all the session IDs being passed to |OmahaRequestActions|.
+ auto CheckSessionId = [&session_ids](AbstractAction* aa) {
+ if (aa->Type() == OmahaRequestAction::StaticType())
+ session_ids.insert(static_cast<OmahaRequestAction*>(aa)->session_id_);
+ };
+ EXPECT_CALL(*processor_, EnqueueAction(Pointee(_)))
+ .WillRepeatedly(Invoke(CheckSessionId));
+ attempter_.BuildUpdateActions(false);
+ // Validate that all the session IDs are the same.
+ EXPECT_EQ(1, session_ids.size());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, SessionIdTestConsistencyInUpdateFlow) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempterTest::SessionIdTestConsistencyInUpdateFlow,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::SessionIdTestInDownloadAction() {
+ // The session ID passed into |DownloadAction|'s |LibcurlHttpFetcher| should
+ // be enforced to be included in the HTTP header as X-Goog-Update-SessionId.
+ string header_value;
+ auto CheckSessionIdInDownloadAction = [&header_value](AbstractAction* aa) {
+ if (aa->Type() == DownloadAction::StaticType()) {
+ DownloadAction* da = static_cast<DownloadAction*>(aa);
+ EXPECT_TRUE(da->http_fetcher()->GetHeader(kXGoogleUpdateSessionId,
+ &header_value));
+ }
+ };
+ EXPECT_CALL(*processor_, EnqueueAction(Pointee(_)))
+ .WillRepeatedly(Invoke(CheckSessionIdInDownloadAction));
+ attempter_.BuildUpdateActions(false);
+ // Validate that X-Goog-Update_SessionId is set correctly in HTTP Header.
+ EXPECT_EQ(attempter_.session_id_, header_value);
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, SessionIdTestInDownloadAction) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::SessionIdTestInDownloadAction,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedDownloadTest) {
+ unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr));
+ fetcher->FailTransfer(503); // Sets the HTTP response code.
+ DownloadAction action(prefs_,
+ nullptr,
+ nullptr,
+ nullptr,
+ fetcher.release(),
+ false /* interactive */);
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
+ attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess);
+ EXPECT_EQ(UpdateStatus::FINALIZING, attempter_.status());
+ EXPECT_EQ(0.0, attempter_.download_progress_);
+ ASSERT_EQ(nullptr, attempter_.error_event_.get());
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedErrorTest) {
+ MockAction action;
+ EXPECT_CALL(action, Type()).WillRepeatedly(Return("MockAction"));
+ attempter_.status_ = UpdateStatus::DOWNLOADING;
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(Return(false));
+ attempter_.ActionCompleted(nullptr, &action, ErrorCode::kError);
+ ASSERT_NE(nullptr, attempter_.error_event_.get());
+}
+
+TEST_F(UpdateAttempterTest, DownloadProgressAccumulationTest) {
+ // Simple test case, where all the values match (nothing was skipped)
+ uint64_t bytes_progressed_1 = 1024 * 1024; // 1MB
+ uint64_t bytes_progressed_2 = 1024 * 1024; // 1MB
+ uint64_t bytes_received_1 = bytes_progressed_1;
+ uint64_t bytes_received_2 = bytes_received_1 + bytes_progressed_2;
+ uint64_t bytes_total = 20 * 1024 * 1024; // 20MB
+
+ double progress_1 =
+ static_cast<double>(bytes_received_1) / static_cast<double>(bytes_total);
+ double progress_2 =
+ static_cast<double>(bytes_received_2) / static_cast<double>(bytes_total);
+
+ EXPECT_EQ(0.0, attempter_.download_progress_);
+ // This is set via inspecting the InstallPlan payloads when the
+ // |OmahaResponseAction| is completed.
+ attempter_.new_payload_size_ = bytes_total;
+ NiceMock<MockServiceObserver> observer;
+ EXPECT_CALL(observer,
+ SendStatusUpdate(AllOf(
+ Field(&UpdateEngineStatus::progress, progress_1),
+ Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING),
+ Field(&UpdateEngineStatus::new_size_bytes, bytes_total))));
+ EXPECT_CALL(observer,
+ SendStatusUpdate(AllOf(
+ Field(&UpdateEngineStatus::progress, progress_2),
+ Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING),
+ Field(&UpdateEngineStatus::new_size_bytes, bytes_total))));
+ attempter_.AddObserver(&observer);
+ attempter_.BytesReceived(bytes_progressed_1, bytes_received_1, bytes_total);
+ EXPECT_EQ(progress_1, attempter_.download_progress_);
+ // This iteration validates that a later set of updates to the variables are
+ // properly handled (so that |getStatus()| will return the same progress info
+ // as the callback is receiving.
+ attempter_.BytesReceived(bytes_progressed_2, bytes_received_2, bytes_total);
+ EXPECT_EQ(progress_2, attempter_.download_progress_);
+}
+
+TEST_F(UpdateAttempterTest, ChangeToDownloadingOnReceivedBytesTest) {
+ // The transition into |UpdateStatus::DOWNLOADING| happens when the
+ // first bytes are received.
+ uint64_t bytes_progressed = 1024 * 1024; // 1MB
+ uint64_t bytes_received = 2 * 1024 * 1024; // 2MB
+ uint64_t bytes_total = 20 * 1024 * 1024; // 300MB
+ attempter_.status_ = UpdateStatus::CHECKING_FOR_UPDATE;
+ // This is set via inspecting the InstallPlan payloads when the
+ // |OmahaResponseAction| is completed.
+ attempter_.new_payload_size_ = bytes_total;
+ EXPECT_EQ(0.0, attempter_.download_progress_);
+ NiceMock<MockServiceObserver> observer;
+ EXPECT_CALL(observer,
+ SendStatusUpdate(AllOf(
+ Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING),
+ Field(&UpdateEngineStatus::new_size_bytes, bytes_total))));
+ attempter_.AddObserver(&observer);
+ attempter_.BytesReceived(bytes_progressed, bytes_received, bytes_total);
+ EXPECT_EQ(UpdateStatus::DOWNLOADING, attempter_.status_);
+}
+
+TEST_F(UpdateAttempterTest, BroadcastCompleteDownloadTest) {
+ // There is a special case to ensure that at 100% downloaded,
+ // |download_progress_| is updated and broadcastest.
+ uint64_t bytes_progressed = 0; // ignored
+ uint64_t bytes_received = 5 * 1024 * 1024; // ignored
+ uint64_t bytes_total = 5 * 1024 * 1024; // 300MB
+ attempter_.status_ = UpdateStatus::DOWNLOADING;
+ attempter_.new_payload_size_ = bytes_total;
+ EXPECT_EQ(0.0, attempter_.download_progress_);
+ NiceMock<MockServiceObserver> observer;
+ EXPECT_CALL(observer,
+ SendStatusUpdate(AllOf(
+ Field(&UpdateEngineStatus::progress, 1.0),
+ Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING),
+ Field(&UpdateEngineStatus::new_size_bytes, bytes_total))));
+ attempter_.AddObserver(&observer);
+ attempter_.BytesReceived(bytes_progressed, bytes_received, bytes_total);
+ EXPECT_EQ(1.0, attempter_.download_progress_);
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedOmahaRequestTest) {
+ unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr));
+ fetcher->FailTransfer(500); // Sets the HTTP response code.
+ OmahaRequestAction action(
+ &fake_system_state_, nullptr, std::move(fetcher), false, "");
+ ObjectCollectorAction<OmahaResponse> collector_action;
+ BondActions(&action, &collector_action);
+ OmahaResponse response;
+ response.poll_interval = 234;
+ action.SetOutputObject(response);
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
+ attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess);
+ EXPECT_EQ(500, attempter_.http_response_code());
+ EXPECT_EQ(UpdateStatus::IDLE, attempter_.status());
+ EXPECT_EQ(234U, attempter_.server_dictated_poll_interval_);
+ ASSERT_TRUE(attempter_.error_event_.get() == nullptr);
+}
+
+TEST_F(UpdateAttempterTest, ConstructWithUpdatedMarkerTest) {
+ FakePrefs fake_prefs;
+ string boot_id;
+ EXPECT_TRUE(utils::GetBootId(&boot_id));
+ fake_prefs.SetString(kPrefsUpdateCompletedOnBootId, boot_id);
+ fake_system_state_.set_prefs(&fake_prefs);
+ attempter_.Init();
+ EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status());
+}
+
+TEST_F(UpdateAttempterTest, GetErrorCodeForActionTest) {
+ EXPECT_EQ(ErrorCode::kSuccess,
+ GetErrorCodeForAction(nullptr, ErrorCode::kSuccess));
+
+ FakeSystemState fake_system_state;
+ OmahaRequestAction omaha_request_action(
+ &fake_system_state, nullptr, nullptr, false, "");
+ EXPECT_EQ(ErrorCode::kOmahaRequestError,
+ GetErrorCodeForAction(&omaha_request_action, ErrorCode::kError));
+ OmahaResponseHandlerAction omaha_response_handler_action(&fake_system_state_);
+ EXPECT_EQ(
+ ErrorCode::kOmahaResponseHandlerError,
+ GetErrorCodeForAction(&omaha_response_handler_action, ErrorCode::kError));
+ DynamicPartitionControlStub dynamic_control_stub;
+ FilesystemVerifierAction filesystem_verifier_action(&dynamic_control_stub);
+ EXPECT_EQ(
+ ErrorCode::kFilesystemVerifierError,
+ GetErrorCodeForAction(&filesystem_verifier_action, ErrorCode::kError));
+ PostinstallRunnerAction postinstall_runner_action(
+ fake_system_state.fake_boot_control(), fake_system_state.fake_hardware());
+ EXPECT_EQ(
+ ErrorCode::kPostinstallRunnerError,
+ GetErrorCodeForAction(&postinstall_runner_action, ErrorCode::kError));
+ MockAction action_mock;
+ EXPECT_CALL(action_mock, Type()).WillOnce(Return("MockAction"));
+ EXPECT_EQ(ErrorCode::kError,
+ GetErrorCodeForAction(&action_mock, ErrorCode::kError));
+}
+
+TEST_F(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest) {
+ attempter_.omaha_request_params_->set_delta_okay(true);
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(Return(false));
+ attempter_.DisableDeltaUpdateIfNeeded();
+ EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay());
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(
+ DoAll(SetArgPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures - 1),
+ Return(true)));
+ attempter_.DisableDeltaUpdateIfNeeded();
+ EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay());
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(
+ DoAll(SetArgPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures),
+ Return(true)));
+ attempter_.DisableDeltaUpdateIfNeeded();
+ EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay());
+ EXPECT_CALL(*prefs_, GetInt64(_, _)).Times(0);
+ attempter_.DisableDeltaUpdateIfNeeded();
+ EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay());
+}
+
+TEST_F(UpdateAttempterTest, MarkDeltaUpdateFailureTest) {
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(Return(false))
+ .WillOnce(DoAll(SetArgPointee<1>(-1), Return(true)))
+ .WillOnce(DoAll(SetArgPointee<1>(1), Return(true)))
+ .WillOnce(
+ DoAll(SetArgPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures),
+ Return(true)));
+ EXPECT_CALL(*prefs_, SetInt64(Ne(kPrefsDeltaUpdateFailures), _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures, 1)).Times(2);
+ EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures, 2));
+ EXPECT_CALL(*prefs_,
+ SetInt64(kPrefsDeltaUpdateFailures,
+ UpdateAttempter::kMaxDeltaUpdateFailures + 1));
+ for (int i = 0; i < 4; i++)
+ attempter_.MarkDeltaUpdateFailure();
+}
+
+TEST_F(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest) {
+ EXPECT_CALL(*processor_, EnqueueAction(_)).Times(0);
+ EXPECT_CALL(*processor_, StartProcessing()).Times(0);
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(), UpdateFailed(_))
+ .Times(0);
+ OmahaResponse response;
+ string url1 = "http://url1";
+ response.packages.push_back({.payload_urls = {url1, "https://url"}});
+ EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
+ .WillRepeatedly(Return(url1));
+ fake_system_state_.mock_payload_state()->SetResponse(response);
+ attempter_.ScheduleErrorEventAction();
+ EXPECT_EQ(url1, fake_system_state_.mock_payload_state()->GetCurrentUrl());
+}
+
+TEST_F(UpdateAttempterTest, ScheduleErrorEventActionTest) {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(Pointee(Property(
+ &AbstractAction::Type, OmahaRequestAction::StaticType()))));
+ EXPECT_CALL(*processor_, StartProcessing());
+ ErrorCode err = ErrorCode::kError;
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(), UpdateFailed(err));
+ attempter_.error_event_.reset(new OmahaEvent(
+ OmahaEvent::kTypeUpdateComplete, OmahaEvent::kResultError, err));
+ attempter_.ScheduleErrorEventAction();
+ EXPECT_EQ(UpdateStatus::REPORTING_ERROR_EVENT, attempter_.status());
+}
+
+namespace {
+// Actions that will be built as part of an update check.
+vector<string> GetUpdateActionTypes() {
+ return {OmahaRequestAction::StaticType(),
+ OmahaResponseHandlerAction::StaticType(),
+ UpdateBootFlagsAction::StaticType(),
+ OmahaRequestAction::StaticType(),
+ DownloadAction::StaticType(),
+ OmahaRequestAction::StaticType(),
+ FilesystemVerifierAction::StaticType(),
+ PostinstallRunnerAction::StaticType(),
+ OmahaRequestAction::StaticType()};
+}
+
+// Actions that will be built as part of a user-initiated rollback.
+vector<string> GetRollbackActionTypes() {
+ return {InstallPlanAction::StaticType(),
+ PostinstallRunnerAction::StaticType()};
+}
+
+const StagingSchedule kValidStagingSchedule = {
+ {4, 10}, {10, 40}, {19, 70}, {26, 100}};
+
+} // namespace
+
+void UpdateAttempterTest::UpdateTestStart() {
+ attempter_.set_http_response_code(200);
+
+ // Expect that the device policy is loaded by the |UpdateAttempter| at some
+ // point by calling |RefreshDevicePolicy()|.
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ EXPECT_CALL(*device_policy, LoadPolicy())
+ .Times(testing::AtLeast(1))
+ .WillRepeatedly(Return(true));
+ attempter_.policy_provider_.reset(
+ new policy::PolicyProvider(std::move(device_policy)));
+
+ {
+ InSequence s;
+ for (const auto& update_action_type : GetUpdateActionTypes()) {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(Pointee(
+ Property(&AbstractAction::Type, update_action_type))));
+ }
+ EXPECT_CALL(*processor_, StartProcessing());
+ }
+
+ attempter_.Update({});
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::UpdateTestVerify,
+ base::Unretained(this)));
+}
+
+void UpdateAttempterTest::UpdateTestVerify() {
+ EXPECT_EQ(0, attempter_.http_response_code());
+ EXPECT_EQ(&attempter_, processor_->delegate());
+ EXPECT_EQ(UpdateStatus::CHECKING_FOR_UPDATE, attempter_.status());
+ loop_.BreakLoop();
+}
+
+void UpdateAttempterTest::RollbackTestStart(bool enterprise_rollback,
+ bool valid_slot) {
+ // Create a device policy so that we can change settings.
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy.get());
+ if (enterprise_rollback) {
+ // We return an empty owner as this is an enterprise.
+ EXPECT_CALL(*device_policy, GetOwner(_))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(string("")), Return(true)));
+ } else {
+ // We return a fake owner as this is an owned consumer device.
+ EXPECT_CALL(*device_policy, GetOwner(_))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(string("fake.mail@fake.com")),
+ Return(true)));
+ }
+
+ attempter_.policy_provider_.reset(
+ new policy::PolicyProvider(std::move(device_policy)));
+
+ if (valid_slot) {
+ BootControlInterface::Slot rollback_slot = 1;
+ LOG(INFO) << "Test Mark Bootable: "
+ << BootControlInterface::SlotName(rollback_slot);
+ fake_system_state_.fake_boot_control()->SetSlotBootable(rollback_slot,
+ true);
+ }
+
+ bool is_rollback_allowed = false;
+
+ // We only allow rollback on devices that are not enterprise enrolled and
+ // which have a valid slot to rollback to.
+ if (!enterprise_rollback && valid_slot) {
+ is_rollback_allowed = true;
+ }
+
+ if (is_rollback_allowed) {
+ InSequence s;
+ for (const auto& rollback_action_type : GetRollbackActionTypes()) {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(Pointee(
+ Property(&AbstractAction::Type, rollback_action_type))));
+ }
+ EXPECT_CALL(*processor_, StartProcessing());
+
+ EXPECT_TRUE(attempter_.Rollback(true));
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::RollbackTestVerify,
+ base::Unretained(this)));
+ } else {
+ EXPECT_FALSE(attempter_.Rollback(true));
+ loop_.BreakLoop();
+ }
+}
+
+void UpdateAttempterTest::RollbackTestVerify() {
+ // Verifies the actions that were enqueued.
+ EXPECT_EQ(&attempter_, processor_->delegate());
+ EXPECT_EQ(UpdateStatus::ATTEMPTING_ROLLBACK, attempter_.status());
+ EXPECT_EQ(0U, attempter_.install_plan_->partitions.size());
+ EXPECT_EQ(attempter_.install_plan_->powerwash_required, true);
+ loop_.BreakLoop();
+}
+
+TEST_F(UpdateAttempterTest, UpdateTest) {
+ UpdateTestStart();
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, RollbackTest) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::RollbackTestStart,
+ base::Unretained(this),
+ false,
+ true));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, InvalidSlotRollbackTest) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::RollbackTestStart,
+ base::Unretained(this),
+ false,
+ false));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, EnterpriseRollbackTest) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::RollbackTestStart,
+ base::Unretained(this),
+ true,
+ true));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::PingOmahaTestStart() {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(Pointee(Property(
+ &AbstractAction::Type, OmahaRequestAction::StaticType()))));
+ EXPECT_CALL(*processor_, StartProcessing());
+ attempter_.PingOmaha();
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, PingOmahaTest) {
+ EXPECT_FALSE(attempter_.waiting_for_scheduled_check_);
+ EXPECT_FALSE(attempter_.WasScheduleUpdatesCalled());
+ // Disable scheduling of subsequnet checks; we're using the |DefaultPolicy| in
+ // testing, which is more permissive than we want to handle here.
+ attempter_.DisableScheduleUpdates();
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::PingOmahaTestStart,
+ base::Unretained(this)));
+ brillo::MessageLoopRunMaxIterations(&loop_, 100);
+ EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status());
+ EXPECT_TRUE(attempter_.WasScheduleUpdatesCalled());
+}
+
+TEST_F(UpdateAttempterTest, CreatePendingErrorEventTest) {
+ MockAction action;
+ const ErrorCode kCode = ErrorCode::kDownloadTransferError;
+ attempter_.CreatePendingErrorEvent(&action, kCode);
+ ASSERT_NE(nullptr, attempter_.error_event_.get());
+ EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type);
+ EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result);
+ EXPECT_EQ(
+ static_cast<ErrorCode>(static_cast<int>(kCode) |
+ static_cast<int>(ErrorCode::kTestOmahaUrlFlag)),
+ attempter_.error_event_->error_code);
+}
+
+TEST_F(UpdateAttempterTest, CreatePendingErrorEventResumedTest) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_resume = true;
+ MockAction action;
+ const ErrorCode kCode = ErrorCode::kInstallDeviceOpenError;
+ attempter_.CreatePendingErrorEvent(&action, kCode);
+ ASSERT_NE(nullptr, attempter_.error_event_.get());
+ EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type);
+ EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result);
+ EXPECT_EQ(
+ static_cast<ErrorCode>(static_cast<int>(kCode) |
+ static_cast<int>(ErrorCode::kResumedFlag) |
+ static_cast<int>(ErrorCode::kTestOmahaUrlFlag)),
+ attempter_.error_event_->error_code);
+}
+
+TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenNotEnabled) {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(false);
+ EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0);
+ attempter_.UpdateEngineStarted();
+}
+
+TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenEnabledButNotSharing) {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0);
+ attempter_.UpdateEngineStarted();
+}
+
+TEST_F(UpdateAttempterTest, P2PStartedAtStartupWhenEnabledAndSharing) {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetCountSharedFilesResult(1);
+ EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning());
+ attempter_.UpdateEngineStarted();
+}
+
+TEST_F(UpdateAttempterTest, P2PNotEnabled) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PNotEnabledStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PNotEnabledStart() {
+ // If P2P is not enabled, check that we do not attempt housekeeping
+ // and do not convey that P2P is to be used.
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(false);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0);
+ attempter_.Update({});
+ EXPECT_FALSE(actual_using_p2p_for_downloading_);
+ EXPECT_FALSE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabledStartingFails) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PEnabledStartingFailsStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledStartingFailsStart() {
+ // If P2P is enabled, but starting it fails ensure we don't do
+ // any housekeeping and do not convey that P2P should be used.
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetEnsureP2PRunningResult(false);
+ mock_p2p_manager.fake().SetPerformHousekeepingResult(false);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0);
+ attempter_.Update({});
+ EXPECT_FALSE(actual_using_p2p_for_downloading());
+ EXPECT_FALSE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabledHousekeepingFails) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PEnabledHousekeepingFailsStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledHousekeepingFailsStart() {
+ // If P2P is enabled, starting it works but housekeeping fails, ensure
+ // we do not convey P2P is to be used.
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
+ mock_p2p_manager.fake().SetPerformHousekeepingResult(false);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
+ attempter_.Update({});
+ EXPECT_FALSE(actual_using_p2p_for_downloading());
+ EXPECT_FALSE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabled) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PEnabledStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledStart() {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ // If P2P is enabled and starting it works, check that we performed
+ // housekeeping and that we convey P2P should be used.
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
+ mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
+ attempter_.Update({});
+ EXPECT_TRUE(actual_using_p2p_for_downloading());
+ EXPECT_TRUE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabledInteractive) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PEnabledInteractiveStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledInteractiveStart() {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ // For an interactive check, if P2P is enabled and starting it
+ // works, check that we performed housekeeping and that we convey
+ // P2P should be used for sharing but NOT for downloading.
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
+ mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
+ attempter_.Update({.interactive = true});
+ EXPECT_FALSE(actual_using_p2p_for_downloading());
+ EXPECT_TRUE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, ReadScatterFactorFromPolicy) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+// Tests that the scatter_factor_in_seconds value is properly fetched
+// from the device policy.
+void UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart() {
+ int64_t scatter_factor_in_seconds = 36000;
+
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy.get());
+
+ EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<0>(scatter_factor_in_seconds), Return(true)));
+
+ attempter_.policy_provider_.reset(
+ new policy::PolicyProvider(std::move(device_policy)));
+
+ attempter_.Update({});
+ EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
+
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, DecrementUpdateCheckCountTest) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempterTest::DecrementUpdateCheckCountTestStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::DecrementUpdateCheckCountTestStart() {
+ // Tests that the scatter_factor_in_seconds value is properly fetched
+ // from the device policy and is decremented if value > 0.
+ int64_t initial_value = 5;
+ FakePrefs fake_prefs;
+ attempter_.prefs_ = &fake_prefs;
+
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+
+ EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
+
+ int64_t scatter_factor_in_seconds = 10;
+
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy.get());
+
+ EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<0>(scatter_factor_in_seconds), Return(true)));
+
+ attempter_.policy_provider_.reset(
+ new policy::PolicyProvider(std::move(device_policy)));
+
+ attempter_.Update({});
+ EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
+
+ // Make sure the file still exists.
+ EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount));
+
+ int64_t new_value;
+ EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value));
+ EXPECT_EQ(initial_value - 1, new_value);
+
+ EXPECT_TRUE(
+ attempter_.omaha_request_params_->update_check_count_wait_enabled());
+
+ // However, if the count is already 0, it's not decremented. Test that.
+ initial_value = 0;
+ EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
+ attempter_.Update({});
+ EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount));
+ EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value));
+ EXPECT_EQ(initial_value, new_value);
+
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, NoScatteringDoneDuringManualUpdateTestStart) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(
+ &UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart() {
+ // Tests that no scattering logic is enabled if the update check
+ // is manually done (as opposed to a scheduled update check)
+ int64_t initial_value = 8;
+ FakePrefs fake_prefs;
+ attempter_.prefs_ = &fake_prefs;
+
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+ fake_system_state_.set_prefs(&fake_prefs);
+
+ EXPECT_TRUE(
+ fake_prefs.SetInt64(kPrefsWallClockScatteringWaitPeriod, initial_value));
+ EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
+
+ // make sure scatter_factor is non-zero as scattering is disabled
+ // otherwise.
+ int64_t scatter_factor_in_seconds = 50;
+
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy.get());
+
+ EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<0>(scatter_factor_in_seconds), Return(true)));
+
+ attempter_.policy_provider_.reset(
+ new policy::PolicyProvider(std::move(device_policy)));
+
+ // Trigger an interactive check so we can test that scattering is disabled.
+ attempter_.Update({.interactive = true});
+ EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
+
+ // Make sure scattering is disabled for manual (i.e. user initiated) update
+ // checks and all artifacts are removed.
+ EXPECT_FALSE(
+ attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockScatteringWaitPeriod));
+ EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InSeconds());
+ EXPECT_FALSE(
+ attempter_.omaha_request_params_->update_check_count_wait_enabled());
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsUpdateCheckCount));
+
+ ScheduleQuitMainLoop();
+}
+
+void UpdateAttempterTest::SetUpStagingTest(const StagingSchedule& schedule,
+ FakePrefs* prefs) {
+ attempter_.prefs_ = prefs;
+ fake_system_state_.set_prefs(prefs);
+
+ int64_t initial_value = 8;
+ EXPECT_TRUE(
+ prefs->SetInt64(kPrefsWallClockScatteringWaitPeriod, initial_value));
+ EXPECT_TRUE(prefs->SetInt64(kPrefsUpdateCheckCount, initial_value));
+ attempter_.scatter_factor_ = TimeDelta::FromSeconds(20);
+
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy.get());
+ EXPECT_CALL(*device_policy, GetDeviceUpdateStagingSchedule(_))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(schedule), Return(true)));
+
+ attempter_.policy_provider_.reset(
+ new policy::PolicyProvider(std::move(device_policy)));
+}
+
+TEST_F(UpdateAttempterTest, StagingSetsPrefsAndTurnsOffScattering) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(
+ &UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart() {
+ // Tests that staging sets its prefs properly and turns off scattering.
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+ FakePrefs fake_prefs;
+ SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
+
+ attempter_.Update({});
+ // Check that prefs have the correct values.
+ int64_t update_count;
+ EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &update_count));
+ int64_t waiting_time_days;
+ EXPECT_TRUE(fake_prefs.GetInt64(kPrefsWallClockStagingWaitPeriod,
+ &waiting_time_days));
+ EXPECT_GT(waiting_time_days, 0);
+ // Update count should have been decremented.
+ EXPECT_EQ(7, update_count);
+ // Check that Omaha parameters were updated correctly.
+ EXPECT_TRUE(
+ attempter_.omaha_request_params_->update_check_count_wait_enabled());
+ EXPECT_TRUE(
+ attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
+ EXPECT_EQ(waiting_time_days,
+ attempter_.omaha_request_params_->waiting_period().InDays());
+ // Check class variables.
+ EXPECT_EQ(waiting_time_days, attempter_.staging_wait_time_.InDays());
+ EXPECT_EQ(kValidStagingSchedule, attempter_.staging_schedule_);
+ // Check that scattering is turned off
+ EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds());
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockScatteringWaitPeriod));
+
+ ScheduleQuitMainLoop();
+}
+
+void UpdateAttempterTest::CheckStagingOff() {
+ // Check that all prefs were removed.
+ EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsUpdateCheckCount));
+ EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsWallClockScatteringWaitPeriod));
+ EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsWallClockStagingWaitPeriod));
+ // Check that the Omaha parameters have the correct value.
+ EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InDays());
+ EXPECT_EQ(attempter_.omaha_request_params_->waiting_period(),
+ attempter_.staging_wait_time_);
+ EXPECT_FALSE(
+ attempter_.omaha_request_params_->update_check_count_wait_enabled());
+ EXPECT_FALSE(
+ attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
+ // Check that scattering is turned off too.
+ EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds());
+}
+
+TEST_F(UpdateAttempterTest, StagingOffIfInteractive) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::StagingOffIfInteractiveStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::StagingOffIfInteractiveStart() {
+ // Tests that staging is turned off when an interactive update is requested.
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+ FakePrefs fake_prefs;
+ SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
+
+ attempter_.Update({.interactive = true});
+ CheckStagingOff();
+
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, StagingOffIfOobe) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::StagingOffIfOobeStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::StagingOffIfOobeStart() {
+ // Tests that staging is turned off if OOBE hasn't been completed.
+ fake_system_state_.fake_hardware()->SetIsOOBEEnabled(true);
+ fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+ FakePrefs fake_prefs;
+ SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
+
+ attempter_.Update({.interactive = true});
+ CheckStagingOff();
+
+ ScheduleQuitMainLoop();
+}
+
+// Checks that we only report daily metrics at most every 24 hours.
+TEST_F(UpdateAttempterTest, ReportDailyMetrics) {
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ fake_system_state_.set_clock(&fake_clock);
+ fake_system_state_.set_prefs(&fake_prefs);
+
+ Time epoch = Time::FromInternalValue(0);
+ fake_clock.SetWallclockTime(epoch);
+
+ // If there is no kPrefsDailyMetricsLastReportedAt state variable,
+ // we should report.
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ // We should not report again if no time has passed.
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // We should not report if only 10 hours has passed.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(10));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // We should not report if only 24 hours - 1 sec has passed.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(24) -
+ TimeDelta::FromSeconds(1));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // We should report if 24 hours has passed.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(24));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+
+ // But then we should not report again..
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // .. until another 24 hours has passed
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(47));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(48));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // .. and another 24 hours
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(71));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(72));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // If the span between time of reporting and present time is
+ // negative, we report. This is in order to reset the timestamp and
+ // avoid an edge condition whereby a distant point in the future is
+ // in the state variable resulting in us never ever reporting again.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(71));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // In this case we should not update until the clock reads 71 + 24 = 95.
+ // Check that.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(94));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(95));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+}
+
+TEST_F(UpdateAttempterTest, BootTimeInUpdateMarkerFile) {
+ FakeClock fake_clock;
+ fake_clock.SetBootTime(Time::FromTimeT(42));
+ fake_system_state_.set_clock(&fake_clock);
+ FakePrefs fake_prefs;
+ fake_system_state_.set_prefs(&fake_prefs);
+ attempter_.Init();
+
+ Time boot_time;
+ EXPECT_FALSE(attempter_.GetBootTimeAtUpdate(&boot_time));
+
+ attempter_.WriteUpdateCompletedMarker();
+
+ EXPECT_TRUE(attempter_.GetBootTimeAtUpdate(&boot_time));
+ EXPECT_EQ(boot_time.ToTimeT(), 42);
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedUnofficial) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedOfficialDevmode) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(true);
+ EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedOfficialNormal) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false);
+ EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+// TODO(kimjae): Follow testing pattern with params for |CheckForInstall()|.
+// When adding, remove older tests related to |CheckForInstall()|.
+TEST_F(UpdateAttempterTest, CheckForInstallNotIdleFails) {
+ for (const auto status : kNonIdleUpdateStatuses) {
+ // GIVEN a non-idle status.
+ attempter_.status_ = status;
+
+ EXPECT_FALSE(attempter_.CheckForInstall({}, ""));
+ }
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateNotIdleFails) {
+ for (const auto status : kNonIdleUpdateStatuses) {
+ // GIVEN a non-idle status.
+ cfu_params_.status = status;
+
+ // THEN |ScheduleUpdates()| should not be called.
+ cfu_params_.should_schedule_updates_be_called = false;
+ // THEN result should indicate failure.
+ cfu_params_.expected_result = false;
+
+ TestCheckForUpdate();
+ }
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateOfficalBuildClearsSource) {
+ // GIVEN a official build.
+
+ // THEN we except forced app version + forced omaha url to be cleared.
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateUnofficialBuildChangesSource) {
+ // GIVEN a nonofficial build with dev features enabled.
+ cfu_params_.is_official_build = false;
+ cfu_params_.are_dev_features_enabled = true;
+
+ // THEN the forced app version + forced omaha url changes based on input.
+ cfu_params_.expected_forced_app_version = cfu_params_.app_version;
+ cfu_params_.expected_forced_omaha_url = cfu_params_.omaha_url;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateOfficialBuildScheduledAUTest) {
+ // GIVEN a scheduled autest omaha url.
+ cfu_params_.omaha_url = "autest-scheduled";
+
+ // THEN forced app version is cleared.
+ // THEN forced omaha url changes to default constant.
+ cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateUnofficialBuildScheduledAUTest) {
+ // GIVEN a scheduled autest omaha url.
+ cfu_params_.omaha_url = "autest-scheduled";
+ // GIVEN a nonofficial build with dev features enabled.
+ cfu_params_.is_official_build = false;
+ cfu_params_.are_dev_features_enabled = true;
+
+ // THEN forced app version changes based on input.
+ cfu_params_.expected_forced_app_version = cfu_params_.app_version;
+ // THEN forced omaha url changes to default constant.
+ cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateOfficialBuildAUTest) {
+ // GIVEN a autest omaha url.
+ cfu_params_.omaha_url = "autest";
+
+ // THEN forced app version is cleared.
+ // THEN forced omaha url changes to default constant.
+ cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateUnofficialBuildAUTest) {
+ // GIVEN a autest omha url.
+ cfu_params_.omaha_url = "autest";
+ // GIVEN a nonofficial build with dev features enabled.
+ cfu_params_.is_official_build = false;
+ cfu_params_.are_dev_features_enabled = true;
+
+ // THEN forced app version changes based on input.
+ cfu_params_.expected_forced_app_version = cfu_params_.app_version;
+ // THEN forced omaha url changes to default constant.
+ cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest,
+ CheckForUpdateNonInteractiveOfficialBuildScheduledAUTest) {
+ // GIVEN a scheduled autest omaha url.
+ cfu_params_.omaha_url = "autest-scheduled";
+ // GIVEN a noninteractive update.
+ cfu_params_.flags = UpdateAttemptFlags::kFlagNonInteractive;
+
+ // THEN forced app version is cleared.
+ // THEN forced omaha url changes to default constant.
+ cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest,
+ CheckForUpdateNonInteractiveUnofficialBuildScheduledAUTest) {
+ // GIVEN a scheduled autest omaha url.
+ cfu_params_.omaha_url = "autest-scheduled";
+ // GIVEN a noninteractive update.
+ cfu_params_.flags = UpdateAttemptFlags::kFlagNonInteractive;
+ // GIVEN a nonofficial build with dev features enabled.
+ cfu_params_.is_official_build = false;
+ cfu_params_.are_dev_features_enabled = true;
+
+ // THEN forced app version changes based on input.
+ cfu_params_.expected_forced_app_version = cfu_params_.app_version;
+ // THEN forced omaha url changes to default constant.
+ cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateNonInteractiveOfficialBuildAUTest) {
+ // GIVEN a autest omaha url.
+ cfu_params_.omaha_url = "autest";
+ // GIVEN a noninteractive update.
+ cfu_params_.flags = UpdateAttemptFlags::kFlagNonInteractive;
+
+ // THEN forced app version is cleared.
+ // THEN forced omaha url changes to default constant.
+ cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateNonInteractiveUnofficialBuildAUTest) {
+ // GIVEN a autest omaha url.
+ cfu_params_.omaha_url = "autest";
+ // GIVEN a noninteractive update.
+ cfu_params_.flags = UpdateAttemptFlags::kFlagNonInteractive;
+ // GIVEN a nonofficial build with dev features enabled.
+ cfu_params_.is_official_build = false;
+ cfu_params_.are_dev_features_enabled = true;
+
+ // THEN forced app version changes based on input.
+ cfu_params_.expected_forced_app_version = cfu_params_.app_version;
+ // THEN forced omaha url changes to default constant.
+ cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateMissingForcedCallback1) {
+ // GIVEN a official build.
+ // GIVEN forced callback is not set.
+ attempter_.set_forced_update_pending_callback(nullptr);
+
+ // THEN we except forced app version + forced omaha url to be cleared.
+ // THEN |ScheduleUpdates()| should not be called.
+ cfu_params_.should_schedule_updates_be_called = false;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateMissingForcedCallback2) {
+ // GIVEN a nonofficial build with dev features enabled.
+ cfu_params_.is_official_build = false;
+ cfu_params_.are_dev_features_enabled = true;
+ // GIVEN forced callback is not set.
+ attempter_.set_forced_update_pending_callback(nullptr);
+
+ // THEN the forced app version + forced omaha url changes based on input.
+ cfu_params_.expected_forced_app_version = cfu_params_.app_version;
+ cfu_params_.expected_forced_omaha_url = cfu_params_.omaha_url;
+ // THEN |ScheduleUpdates()| should not be called.
+ cfu_params_.should_schedule_updates_be_called = false;
+
+ TestCheckForUpdate();
+}
+
+TEST_F(UpdateAttempterTest, CheckForInstallTest) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false);
+ attempter_.CheckForInstall({}, "autest");
+ EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
+
+ attempter_.CheckForInstall({}, "autest-scheduled");
+ EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
+
+ attempter_.CheckForInstall({}, "http://omaha.phishing");
+ EXPECT_EQ("", attempter_.forced_omaha_url());
+}
+
+TEST_F(UpdateAttempterTest, InstallSetsStatusIdle) {
+ attempter_.CheckForInstall({}, "http://foo.bar");
+ attempter_.status_ = UpdateStatus::DOWNLOADING;
+ EXPECT_TRUE(attempter_.is_install_);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ // Should set status to idle after an install operation.
+ EXPECT_EQ(UpdateStatus::IDLE, status.status);
+}
+
+TEST_F(UpdateAttempterTest, RollbackAfterInstall) {
+ attempter_.is_install_ = true;
+ attempter_.Rollback(false);
+ EXPECT_FALSE(attempter_.is_install_);
+}
+
+TEST_F(UpdateAttempterTest, UpdateAfterInstall) {
+ attempter_.is_install_ = true;
+ attempter_.CheckForUpdate("", "", UpdateAttemptFlags::kNone);
+ EXPECT_FALSE(attempter_.is_install_);
+}
+
+TEST_F(UpdateAttempterTest, TargetVersionPrefixSetAndReset) {
+ UpdateCheckParams params;
+ attempter_.CalculateUpdateParams({.target_version_prefix = "1234"});
+ EXPECT_EQ("1234",
+ fake_system_state_.request_params()->target_version_prefix());
+
+ attempter_.CalculateUpdateParams({});
+ EXPECT_TRUE(
+ fake_system_state_.request_params()->target_version_prefix().empty());
+}
+
+TEST_F(UpdateAttempterTest, TargetChannelHintSetAndReset) {
+ attempter_.CalculateUpdateParams({.lts_tag = "hint"});
+ EXPECT_EQ("hint", fake_system_state_.request_params()->lts_tag());
+
+ attempter_.CalculateUpdateParams({});
+ EXPECT_TRUE(fake_system_state_.request_params()->lts_tag().empty());
+}
+
+TEST_F(UpdateAttempterTest, RollbackAllowedSetAndReset) {
+ attempter_.CalculateUpdateParams({
+ .target_version_prefix = "1234",
+ .rollback_allowed = true,
+ .rollback_allowed_milestones = 4,
+ });
+ EXPECT_TRUE(fake_system_state_.request_params()->rollback_allowed());
+ EXPECT_EQ(4,
+ fake_system_state_.request_params()->rollback_allowed_milestones());
+
+ attempter_.CalculateUpdateParams({
+ .target_version_prefix = "1234",
+ .rollback_allowed_milestones = 4,
+ });
+ EXPECT_FALSE(fake_system_state_.request_params()->rollback_allowed());
+ EXPECT_EQ(4,
+ fake_system_state_.request_params()->rollback_allowed_milestones());
+}
+
+TEST_F(UpdateAttempterTest, ChannelDowngradeNoRollback) {
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+ fake_system_state_.request_params()->set_root(tempdir.GetPath().value());
+ attempter_.CalculateUpdateParams({
+ .target_channel = "stable-channel",
+ });
+ EXPECT_FALSE(fake_system_state_.request_params()->is_powerwash_allowed());
+}
+
+TEST_F(UpdateAttempterTest, ChannelDowngradeRollback) {
+ base::ScopedTempDir tempdir;
+ ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+ fake_system_state_.request_params()->set_root(tempdir.GetPath().value());
+ attempter_.CalculateUpdateParams({
+ .rollback_on_channel_downgrade = true,
+ .target_channel = "stable-channel",
+ });
+ EXPECT_TRUE(fake_system_state_.request_params()->is_powerwash_allowed());
+}
+
+TEST_F(UpdateAttempterTest, UpdateDeferredByPolicyTest) {
+ // Construct an OmahaResponseHandlerAction that has processed an InstallPlan,
+ // but the update is being deferred by the Policy.
+ OmahaResponseHandlerAction response_action(&fake_system_state_);
+ response_action.install_plan_.version = "a.b.c.d";
+ response_action.install_plan_.payloads.push_back(
+ {.size = 1234ULL, .type = InstallPayloadType::kFull});
+ // Inform the UpdateAttempter that the OmahaResponseHandlerAction has
+ // completed, with the deferred-update error code.
+ attempter_.ActionCompleted(
+ nullptr, &response_action, ErrorCode::kOmahaUpdateDeferredPerPolicy);
+ {
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_EQ(UpdateStatus::UPDATE_AVAILABLE, status.status);
+ EXPECT_TRUE(attempter_.install_plan_);
+ EXPECT_EQ(attempter_.install_plan_->version, status.new_version);
+ EXPECT_EQ(attempter_.install_plan_->payloads[0].size,
+ status.new_size_bytes);
+ }
+ // An "error" event should have been created to tell Omaha that the update is
+ // being deferred.
+ EXPECT_TRUE(nullptr != attempter_.error_event_);
+ EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type);
+ EXPECT_EQ(OmahaEvent::kResultUpdateDeferred, attempter_.error_event_->result);
+ ErrorCode expected_code = static_cast<ErrorCode>(
+ static_cast<int>(ErrorCode::kOmahaUpdateDeferredPerPolicy) |
+ static_cast<int>(ErrorCode::kTestOmahaUrlFlag));
+ EXPECT_EQ(expected_code, attempter_.error_event_->error_code);
+ // End the processing
+ attempter_.ProcessingDone(nullptr, ErrorCode::kOmahaUpdateDeferredPerPolicy);
+ // Validate the state of the attempter.
+ {
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_EQ(UpdateStatus::REPORTING_ERROR_EVENT, status.status);
+ EXPECT_EQ(response_action.install_plan_.version, status.new_version);
+ EXPECT_EQ(response_action.install_plan_.payloads[0].size,
+ status.new_size_bytes);
+ }
+}
+
+TEST_F(UpdateAttempterTest, UpdateIsNotRunningWhenUpdateAvailable) {
+ // Default construction for |waiting_for_scheduled_check_| is false.
+ EXPECT_FALSE(attempter_.IsBusyOrUpdateScheduled());
+ // Verify in-progress update with UPDATE_AVAILABLE is running
+ attempter_.status_ = UpdateStatus::UPDATE_AVAILABLE;
+ EXPECT_TRUE(attempter_.IsBusyOrUpdateScheduled());
+}
+
+TEST_F(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart) {
+ attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kFlagRestrictDownload);
+
+ UpdateCheckParams params = {.updates_enabled = true};
+ attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params);
+
+ EXPECT_EQ(UpdateAttemptFlags::kFlagRestrictDownload,
+ attempter_.GetCurrentUpdateAttemptFlags());
+}
+
+TEST_F(UpdateAttempterTest, RollbackNotAllowed) {
+ UpdateCheckParams params = {.updates_enabled = true,
+ .rollback_allowed = false};
+ attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params);
+ EXPECT_FALSE(fake_system_state_.request_params()->rollback_allowed());
+}
+
+TEST_F(UpdateAttempterTest, RollbackAllowed) {
+ UpdateCheckParams params = {.updates_enabled = true,
+ .rollback_allowed = true};
+ attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params);
+ EXPECT_TRUE(fake_system_state_.request_params()->rollback_allowed());
+}
+
+TEST_F(UpdateAttempterTest, InteractiveUpdateUsesPassedRestrictions) {
+ attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kFlagRestrictDownload);
+
+ attempter_.CheckForUpdate("", "", UpdateAttemptFlags::kNone);
+ EXPECT_EQ(UpdateAttemptFlags::kNone,
+ attempter_.GetCurrentUpdateAttemptFlags());
+}
+
+TEST_F(UpdateAttempterTest, NonInteractiveUpdateUsesSetRestrictions) {
+ attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kNone);
+
+ // This tests that when CheckForUpdate() is called with the non-interactive
+ // flag set, that it doesn't change the current UpdateAttemptFlags.
+ attempter_.CheckForUpdate("",
+ "",
+ UpdateAttemptFlags::kFlagNonInteractive |
+ UpdateAttemptFlags::kFlagRestrictDownload);
+ EXPECT_EQ(UpdateAttemptFlags::kNone,
+ attempter_.GetCurrentUpdateAttemptFlags());
+}
+
+void UpdateAttempterTest::ResetRollbackHappenedStart(bool is_consumer,
+ bool is_policy_loaded,
+ bool expected_reset) {
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(), GetRollbackHappened())
+ .WillRepeatedly(Return(true));
+ auto mock_policy_provider =
+ std::make_unique<NiceMock<policy::MockPolicyProvider>>();
+ EXPECT_CALL(*mock_policy_provider, IsConsumerDevice())
+ .WillRepeatedly(Return(is_consumer));
+ EXPECT_CALL(*mock_policy_provider, device_policy_is_loaded())
+ .WillRepeatedly(Return(is_policy_loaded));
+ const policy::MockDevicePolicy device_policy;
+ EXPECT_CALL(*mock_policy_provider, GetDevicePolicy())
+ .WillRepeatedly(ReturnRef(device_policy));
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetRollbackHappened(false))
+ .Times(expected_reset ? 1 : 0);
+ attempter_.policy_provider_ = std::move(mock_policy_provider);
+ attempter_.Update({});
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, ResetRollbackHappenedOobe) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart,
+ base::Unretained(this),
+ /*is_consumer=*/false,
+ /*is_policy_loaded=*/false,
+ /*expected_reset=*/false));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, ResetRollbackHappenedConsumer) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart,
+ base::Unretained(this),
+ /*is_consumer=*/true,
+ /*is_policy_loaded=*/false,
+ /*expected_reset=*/true));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, ResetRollbackHappenedEnterprise) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart,
+ base::Unretained(this),
+ /*is_consumer=*/false,
+ /*is_policy_loaded=*/true,
+ /*expected_reset=*/true));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, SetRollbackHappenedRollback) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_rollback = true;
+
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetRollbackHappened(true))
+ .Times(1);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, SetRollbackHappenedNotRollback) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_rollback = false;
+
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetRollbackHappened(true))
+ .Times(0);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, RollbackMetricsRollbackSuccess) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_rollback = true;
+ attempter_.install_plan_->version = kRollbackVersion;
+
+ EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+ ReportEnterpriseRollbackMetrics(true, kRollbackVersion))
+ .Times(1);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_rollback = false;
+ attempter_.install_plan_->version = kRollbackVersion;
+
+ EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+ ReportEnterpriseRollbackMetrics(_, _))
+ .Times(0);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, RollbackMetricsRollbackFailure) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_rollback = true;
+ attempter_.install_plan_->version = kRollbackVersion;
+
+ EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+ ReportEnterpriseRollbackMetrics(false, kRollbackVersion))
+ .Times(1);
+ MockAction action;
+ attempter_.CreatePendingErrorEvent(&action, ErrorCode::kRollbackNotPossible);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kRollbackNotPossible);
+}
+
+TEST_F(UpdateAttempterTest, RollbackMetricsNotRollbackFailure) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_rollback = false;
+ attempter_.install_plan_->version = kRollbackVersion;
+
+ EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+ ReportEnterpriseRollbackMetrics(_, _))
+ .Times(0);
+ MockAction action;
+ attempter_.CreatePendingErrorEvent(&action, ErrorCode::kRollbackNotPossible);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kRollbackNotPossible);
+}
+
+TEST_F(UpdateAttempterTest, TimeToUpdateAppliedMetricFailure) {
+ EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+ ReportEnterpriseUpdateSeenToDownloadDays(_, _))
+ .Times(0);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kOmahaUpdateDeferredPerPolicy);
+}
+
+TEST_F(UpdateAttempterTest, TimeToUpdateAppliedOnNonEnterprise) {
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ fake_system_state_.set_device_policy(device_policy.get());
+ // Make device policy return that this is not enterprise enrolled
+ EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(false));
+
+ // Ensure that the metric is not recorded.
+ EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+ ReportEnterpriseUpdateSeenToDownloadDays(_, _))
+ .Times(0);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest,
+ TimeToUpdateAppliedWithTimeRestrictionMetricSuccess) {
+ constexpr int kDaysToUpdate = 15;
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ fake_system_state_.set_device_policy(device_policy.get());
+ // Make device policy return that this is enterprise enrolled
+ EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(true));
+ // Pretend that there's a time restriction policy in place
+ EXPECT_CALL(*device_policy, GetDisallowedTimeIntervals(_))
+ .WillOnce(Return(true));
+
+ FakePrefs fake_prefs;
+ Time update_first_seen_at = Time::Now();
+ fake_prefs.SetInt64(kPrefsUpdateFirstSeenAt,
+ update_first_seen_at.ToInternalValue());
+
+ FakeClock fake_clock;
+ Time update_finished_at =
+ update_first_seen_at + TimeDelta::FromDays(kDaysToUpdate);
+ fake_clock.SetWallclockTime(update_finished_at);
+
+ fake_system_state_.set_clock(&fake_clock);
+ fake_system_state_.set_prefs(&fake_prefs);
+
+ EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+ ReportEnterpriseUpdateSeenToDownloadDays(true, kDaysToUpdate))
+ .Times(1);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest,
+ TimeToUpdateAppliedWithoutTimeRestrictionMetricSuccess) {
+ constexpr int kDaysToUpdate = 15;
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ fake_system_state_.set_device_policy(device_policy.get());
+ // Make device policy return that this is enterprise enrolled
+ EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(true));
+ // Pretend that there's no time restriction policy in place
+ EXPECT_CALL(*device_policy, GetDisallowedTimeIntervals(_))
+ .WillOnce(Return(false));
+
+ FakePrefs fake_prefs;
+ Time update_first_seen_at = Time::Now();
+ fake_prefs.SetInt64(kPrefsUpdateFirstSeenAt,
+ update_first_seen_at.ToInternalValue());
+
+ FakeClock fake_clock;
+ Time update_finished_at =
+ update_first_seen_at + TimeDelta::FromDays(kDaysToUpdate);
+ fake_clock.SetWallclockTime(update_finished_at);
+
+ fake_system_state_.set_clock(&fake_clock);
+ fake_system_state_.set_prefs(&fake_prefs);
+
+ EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+ ReportEnterpriseUpdateSeenToDownloadDays(false, kDaysToUpdate))
+ .Times(1);
+ attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, ProcessingDoneUpdated) {
+ // GIVEN an update finished.
+
+ // THEN update_engine should call update completion.
+ pd_params_.should_update_completed_be_called = true;
+ // THEN need reboot since update applied.
+ pd_params_.expected_exit_status = UpdateStatus::UPDATED_NEED_REBOOT;
+ // THEN install indication should be false.
+
+ TestProcessingDone();
+}
+
+TEST_F(UpdateAttempterTest, ProcessingDoneUpdatedDlcFilter) {
+ // GIVEN an update finished.
+ // GIVEN DLC |AppParams| list.
+ auto dlc_1 = "dlc_1", dlc_2 = "dlc_2";
+ pd_params_.dlc_apps_params = {{dlc_1, {.name = dlc_1, .updated = false}},
+ {dlc_2, {.name = dlc_2}}};
+
+ // THEN update_engine should call update completion.
+ pd_params_.should_update_completed_be_called = true;
+ pd_params_.args_to_update_completed = {dlc_2};
+ // THEN need reboot since update applied.
+ pd_params_.expected_exit_status = UpdateStatus::UPDATED_NEED_REBOOT;
+ // THEN install indication should be false.
+
+ TestProcessingDone();
+}
+
+TEST_F(UpdateAttempterTest, ProcessingDoneInstalled) {
+ // GIVEN an install finished.
+ pd_params_.is_install = true;
+
+ // THEN update_engine should call install completion.
+ pd_params_.should_install_completed_be_called = true;
+ // THEN go idle.
+ // THEN install indication should be false.
+
+ TestProcessingDone();
+}
+
+TEST_F(UpdateAttempterTest, ProcessingDoneInstalledDlcFilter) {
+ // GIVEN an install finished.
+ pd_params_.is_install = true;
+ // GIVEN DLC |AppParams| list.
+ auto dlc_1 = "dlc_1", dlc_2 = "dlc_2";
+ pd_params_.dlc_apps_params = {{dlc_1, {.name = dlc_1, .updated = false}},
+ {dlc_2, {.name = dlc_2}}};
+
+ // THEN update_engine should call install completion.
+ pd_params_.should_install_completed_be_called = true;
+ pd_params_.args_to_install_completed = {dlc_2};
+ // THEN go idle.
+ // THEN install indication should be false.
+
+ TestProcessingDone();
+}
+
+TEST_F(UpdateAttempterTest, ProcessingDoneInstallReportingError) {
+ // GIVEN an install finished.
+ pd_params_.is_install = true;
+ // GIVEN a reporting error occurred.
+ pd_params_.status = UpdateStatus::REPORTING_ERROR_EVENT;
+
+ // THEN update_engine should not call install completion.
+ // THEN go idle.
+ // THEN install indication should be false.
+
+ TestProcessingDone();
+}
+
+TEST_F(UpdateAttempterTest, ProcessingDoneNoUpdate) {
+ // GIVEN an update finished.
+ // GIVEN an action error occured.
+ pd_params_.code = ErrorCode::kNoUpdate;
+
+ // THEN update_engine should not call update completion.
+ // THEN go idle.
+ // THEN install indication should be false.
+
+ TestProcessingDone();
+}
+
+TEST_F(UpdateAttempterTest, ProcessingDoneNoInstall) {
+ // GIVEN an install finished.
+ pd_params_.is_install = true;
+ // GIVEN an action error occured.
+ pd_params_.code = ErrorCode::kNoUpdate;
+
+ // THEN update_engine should not call install completion.
+ // THEN go idle.
+ // THEN install indication should be false.
+
+ TestProcessingDone();
+}
+
+TEST_F(UpdateAttempterTest, ProcessingDoneUpdateError) {
+ // GIVEN an update finished.
+ // GIVEN an action error occured.
+ pd_params_.code = ErrorCode::kError;
+ // GIVEN an event error is set.
+ attempter_.error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete,
+ OmahaEvent::kResultError,
+ ErrorCode::kError));
+
+ // THEN indicate a error event.
+ pd_params_.expected_exit_status = UpdateStatus::REPORTING_ERROR_EVENT;
+ // THEN install indication should be false.
+
+ // THEN update_engine should not call update completion.
+ // THEN expect critical actions of |ScheduleErrorEventAction()|.
+ EXPECT_CALL(*processor_, EnqueueAction(Pointee(_))).Times(1);
+ EXPECT_CALL(*processor_, StartProcessing()).Times(1);
+ // THEN |ScheduleUpdates()| will be called next |ProcessingDone()| so skip.
+ pd_params_.should_schedule_updates_be_called = false;
+
+ TestProcessingDone();
+}
+
+TEST_F(UpdateAttempterTest, ProcessingDoneInstallError) {
+ // GIVEN an install finished.
+ pd_params_.is_install = true;
+ // GIVEN an action error occured.
+ pd_params_.code = ErrorCode::kError;
+ // GIVEN an event error is set.
+ attempter_.error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete,
+ OmahaEvent::kResultError,
+ ErrorCode::kError));
+
+ // THEN indicate a error event.
+ pd_params_.expected_exit_status = UpdateStatus::REPORTING_ERROR_EVENT;
+ // THEN install indication should be false.
+
+ // THEN update_engine should not call install completion.
+ // THEN expect critical actions of |ScheduleErrorEventAction()|.
+ EXPECT_CALL(*processor_, EnqueueAction(Pointee(_))).Times(1);
+ EXPECT_CALL(*processor_, StartProcessing()).Times(1);
+ // THEN |ScheduleUpdates()| will be called next |ProcessingDone()| so skip.
+ pd_params_.should_schedule_updates_be_called = false;
+
+ TestProcessingDone();
+}
+
+void UpdateAttempterTest::UpdateToQuickFixBuildStart(bool set_token) {
+ // Tests that checks if |device_quick_fix_build_token| arrives when
+ // policy is set and the device is enterprise enrolled based on |set_token|.
+ string token = set_token ? "some_token" : "";
+ auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+ fake_system_state_.set_device_policy(device_policy.get());
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+
+ if (set_token)
+ EXPECT_CALL(*device_policy, GetDeviceQuickFixBuildToken(_))
+ .WillOnce(DoAll(SetArgPointee<0>(token), Return(true)));
+ else
+ EXPECT_CALL(*device_policy, GetDeviceQuickFixBuildToken(_))
+ .WillOnce(Return(false));
+ attempter_.policy_provider_.reset(
+ new policy::PolicyProvider(std::move(device_policy)));
+ attempter_.Update({});
+
+ EXPECT_EQ(token, attempter_.omaha_request_params_->autoupdate_token());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest,
+ QuickFixTokenWhenDeviceIsEnterpriseEnrolledAndPolicyIsSet) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::UpdateToQuickFixBuildStart,
+ base::Unretained(this),
+ /*set_token=*/true));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, EmptyQuickFixToken) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::UpdateToQuickFixBuildStart,
+ base::Unretained(this),
+ /*set_token=*/false));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, ScheduleUpdateSpamHandlerTest) {
+ EXPECT_CALL(mock_update_manager_, AsyncPolicyRequestUpdateCheckAllowed(_, _))
+ .Times(1);
+ EXPECT_TRUE(attempter_.ScheduleUpdates());
+ // Now there is an update scheduled which means that all subsequent
+ // |ScheduleUpdates()| should fail.
+ EXPECT_FALSE(attempter_.ScheduleUpdates());
+ EXPECT_FALSE(attempter_.ScheduleUpdates());
+ EXPECT_FALSE(attempter_.ScheduleUpdates());
+}
+
+// Critical tests to always make sure that an update is scheduled. The following
+// unittest(s) try and cover the correctness in synergy between
+// |UpdateAttempter| and |UpdateManager|. Also it is good to remember the
+// actions that happen in the flow when |UpdateAttempter| get callbacked on
+// |OnUpdateScheduled()| -> (various cases which leads to) -> |ProcessingDone()|
+void UpdateAttempterTest::TestOnUpdateScheduled() {
+ // Setup
+ attempter_.SetWaitingForScheduledCheck(true);
+ attempter_.DisableUpdate();
+ attempter_.DisableScheduleUpdates();
+
+ // Invocation
+ attempter_.OnUpdateScheduled(ous_params_.status, ous_params_.params);
+
+ // Verify
+ EXPECT_EQ(ous_params_.exit_status, attempter_.status());
+ EXPECT_EQ(ous_params_.should_schedule_updates_be_called,
+ attempter_.WasScheduleUpdatesCalled());
+ EXPECT_EQ(ous_params_.should_update_be_called, attempter_.WasUpdateCalled());
+}
+
+TEST_F(UpdateAttempterTest, OnUpdatesScheduledFailed) {
+ // GIVEN failed status.
+
+ // THEN update should be scheduled.
+ ous_params_.should_schedule_updates_be_called = true;
+
+ TestOnUpdateScheduled();
+}
+
+TEST_F(UpdateAttempterTest, OnUpdatesScheduledAskMeAgainLater) {
+ // GIVEN ask me again later status.
+ ous_params_.status = EvalStatus::kAskMeAgainLater;
+
+ // THEN update should be scheduled.
+ ous_params_.should_schedule_updates_be_called = true;
+
+ TestOnUpdateScheduled();
+}
+
+TEST_F(UpdateAttempterTest, OnUpdatesScheduledContinue) {
+ // GIVEN continue status.
+ ous_params_.status = EvalStatus::kContinue;
+
+ // THEN update should be scheduled.
+ ous_params_.should_schedule_updates_be_called = true;
+
+ TestOnUpdateScheduled();
+}
+
+TEST_F(UpdateAttempterTest, OnUpdatesScheduledSucceededButUpdateDisabledFails) {
+ // GIVEN updates disabled.
+ ous_params_.params = {.updates_enabled = false};
+ // GIVEN succeeded status.
+ ous_params_.status = EvalStatus::kSucceeded;
+
+ // THEN update should not be scheduled.
+
+ TestOnUpdateScheduled();
+}
+
+TEST_F(UpdateAttempterTest, OnUpdatesScheduledSucceeded) {
+ // GIVEN updates enabled.
+ ous_params_.params = {.updates_enabled = true};
+ // GIVEN succeeded status.
+ ous_params_.status = EvalStatus::kSucceeded;
+
+ // THEN update should be called indicating status change.
+ ous_params_.exit_status = UpdateStatus::CHECKING_FOR_UPDATE;
+ ous_params_.should_update_be_called = true;
+
+ TestOnUpdateScheduled();
+}
+
+TEST_F(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusDefault) {
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_FALSE(status.is_enterprise_rollback);
+}
+
+TEST_F(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusFalse) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_rollback = false;
+
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_FALSE(status.is_enterprise_rollback);
+}
+
+TEST_F(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusTrue) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_rollback = true;
+
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_TRUE(status.is_enterprise_rollback);
+}
+
+TEST_F(UpdateAttempterTest, PowerwashInGetStatusDefault) {
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_FALSE(status.will_powerwash_after_reboot);
+}
+
+TEST_F(UpdateAttempterTest, PowerwashInGetStatusTrueBecausePowerwashRequired) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->powerwash_required = true;
+
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_TRUE(status.will_powerwash_after_reboot);
+}
+
+TEST_F(UpdateAttempterTest, PowerwashInGetStatusTrueBecauseRollback) {
+ attempter_.install_plan_.reset(new InstallPlan);
+ attempter_.install_plan_->is_rollback = true;
+
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_TRUE(status.will_powerwash_after_reboot);
+}
+
+TEST_F(UpdateAttempterTest, FutureEolTest) {
+ EolDate eol_date = std::numeric_limits<int64_t>::max();
+ EXPECT_CALL(*prefs_, Exists(kPrefsOmahaEolDate)).WillOnce(Return(true));
+ 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_, Exists(kPrefsOmahaEolDate)).WillOnce(Return(true));
+ 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) {
+ EXPECT_CALL(*prefs_, Exists(kPrefsOmahaEolDate)).WillOnce(Return(true));
+ EXPECT_CALL(*prefs_, GetString(kPrefsOmahaEolDate, _))
+ .WillOnce(Return(false));
+
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_EQ(kEolDateInvalid, status.eol_date);
+}
+
+TEST_F(UpdateAttempterTest, MissingEolTest) {
+ EXPECT_CALL(*prefs_, Exists(kPrefsOmahaEolDate)).WillOnce(Return(false));
+
+ UpdateEngineStatus status;
+ attempter_.GetStatus(&status);
+ EXPECT_EQ(kEolDateInvalid, status.eol_date);
+}
+
+TEST_F(UpdateAttempterTest, CalculateDlcParamsInstallTest) {
+ string dlc_id = "dlc0";
+ FakePrefs fake_prefs;
+ fake_system_state_.set_prefs(&fake_prefs);
+ attempter_.is_install_ = true;
+ attempter_.dlc_ids_ = {dlc_id};
+ attempter_.CalculateDlcParams();
+
+ OmahaRequestParams* params = fake_system_state_.request_params();
+ EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id)));
+ OmahaRequestParams::AppParams dlc_app_params =
+ params->dlc_apps_params().at(params->GetDlcAppId(dlc_id));
+ EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str());
+ EXPECT_EQ(false, dlc_app_params.send_ping);
+ // When the DLC gets installed, a ping is not sent, therefore we don't store
+ // the values sent by Omaha.
+ auto last_active_key = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive});
+ EXPECT_FALSE(fake_system_state_.prefs()->Exists(last_active_key));
+ auto last_rollcall_key = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall});
+ EXPECT_FALSE(fake_system_state_.prefs()->Exists(last_rollcall_key));
+}
+
+TEST_F(UpdateAttempterTest, CalculateDlcParamsNoPrefFilesTest) {
+ string dlc_id = "dlc0";
+ FakePrefs fake_prefs;
+ fake_system_state_.set_prefs(&fake_prefs);
+ EXPECT_CALL(mock_dlcservice_, GetDlcsToUpdate(_))
+ .WillOnce(
+ DoAll(SetArgPointee<0>(std::vector<string>({dlc_id})), Return(true)));
+
+ attempter_.is_install_ = false;
+ attempter_.CalculateDlcParams();
+
+ OmahaRequestParams* params = fake_system_state_.request_params();
+ EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id)));
+ OmahaRequestParams::AppParams dlc_app_params =
+ params->dlc_apps_params().at(params->GetDlcAppId(dlc_id));
+ EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str());
+
+ EXPECT_EQ(true, dlc_app_params.send_ping);
+ EXPECT_EQ(0, dlc_app_params.ping_active);
+ EXPECT_EQ(-1, dlc_app_params.ping_date_last_active);
+ EXPECT_EQ(-1, dlc_app_params.ping_date_last_rollcall);
+}
+
+TEST_F(UpdateAttempterTest, CalculateDlcParamsNonParseableValuesTest) {
+ string dlc_id = "dlc0";
+ MemoryPrefs prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(mock_dlcservice_, GetDlcsToUpdate(_))
+ .WillOnce(
+ DoAll(SetArgPointee<0>(std::vector<string>({dlc_id})), Return(true)));
+
+ // Write non numeric values in the metadata files.
+ auto active_key =
+ PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive});
+ auto last_active_key = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive});
+ auto last_rollcall_key = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall});
+ fake_system_state_.prefs()->SetString(active_key, "z2yz");
+ fake_system_state_.prefs()->SetString(last_active_key, "z2yz");
+ fake_system_state_.prefs()->SetString(last_rollcall_key, "z2yz");
+ attempter_.is_install_ = false;
+ attempter_.CalculateDlcParams();
+
+ OmahaRequestParams* params = fake_system_state_.request_params();
+ EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id)));
+ OmahaRequestParams::AppParams dlc_app_params =
+ params->dlc_apps_params().at(params->GetDlcAppId(dlc_id));
+ EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str());
+
+ EXPECT_EQ(true, dlc_app_params.send_ping);
+ EXPECT_EQ(0, dlc_app_params.ping_active);
+ EXPECT_EQ(-2, dlc_app_params.ping_date_last_active);
+ EXPECT_EQ(-2, dlc_app_params.ping_date_last_rollcall);
+}
+
+TEST_F(UpdateAttempterTest, CalculateDlcParamsValidValuesTest) {
+ string dlc_id = "dlc0";
+ MemoryPrefs fake_prefs;
+ fake_system_state_.set_prefs(&fake_prefs);
+ EXPECT_CALL(mock_dlcservice_, GetDlcsToUpdate(_))
+ .WillOnce(
+ DoAll(SetArgPointee<0>(std::vector<string>({dlc_id})), Return(true)));
+
+ // Write numeric values in the metadata files.
+ auto active_key =
+ PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive});
+ auto last_active_key = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive});
+ auto last_rollcall_key = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall});
+
+ fake_system_state_.prefs()->SetInt64(active_key, 1);
+ fake_system_state_.prefs()->SetInt64(last_active_key, 78);
+ fake_system_state_.prefs()->SetInt64(last_rollcall_key, 99);
+ attempter_.is_install_ = false;
+ attempter_.CalculateDlcParams();
+
+ OmahaRequestParams* params = fake_system_state_.request_params();
+ EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id)));
+ OmahaRequestParams::AppParams dlc_app_params =
+ params->dlc_apps_params().at(params->GetDlcAppId(dlc_id));
+ EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str());
+
+ EXPECT_EQ(true, dlc_app_params.send_ping);
+ EXPECT_EQ(1, dlc_app_params.ping_active);
+ EXPECT_EQ(78, dlc_app_params.ping_date_last_active);
+ EXPECT_EQ(99, dlc_app_params.ping_date_last_rollcall);
+}
+
+TEST_F(UpdateAttempterTest, CalculateDlcParamsRemoveStaleMetadata) {
+ string dlc_id = "dlc0";
+ FakePrefs fake_prefs;
+ fake_system_state_.set_prefs(&fake_prefs);
+ auto active_key =
+ PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive});
+ auto last_active_key = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive});
+ auto last_rollcall_key = PrefsInterface::CreateSubKey(
+ {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall});
+ fake_system_state_.prefs()->SetInt64(active_key, kPingInactiveValue);
+ fake_system_state_.prefs()->SetInt64(last_active_key, 0);
+ fake_system_state_.prefs()->SetInt64(last_rollcall_key, 0);
+ EXPECT_TRUE(fake_system_state_.prefs()->Exists(active_key));
+ EXPECT_TRUE(fake_system_state_.prefs()->Exists(last_active_key));
+ EXPECT_TRUE(fake_system_state_.prefs()->Exists(last_rollcall_key));
+
+ attempter_.dlc_ids_ = {dlc_id};
+ attempter_.is_install_ = true;
+ attempter_.CalculateDlcParams();
+
+ EXPECT_FALSE(fake_system_state_.prefs()->Exists(last_active_key));
+ EXPECT_FALSE(fake_system_state_.prefs()->Exists(last_rollcall_key));
+ // Active key is set on install.
+ EXPECT_TRUE(fake_system_state_.prefs()->Exists(active_key));
+ int64_t temp_int;
+ EXPECT_TRUE(fake_system_state_.prefs()->GetInt64(active_key, &temp_int));
+ EXPECT_EQ(temp_int, kPingActiveValue);
+}
+
+TEST_F(UpdateAttempterTest, SetDlcActiveValue) {
+ string dlc_id = "dlc0";
+ FakePrefs fake_prefs;
+ fake_system_state_.set_prefs(&fake_prefs);
+ attempter_.SetDlcActiveValue(true, dlc_id);
+ int64_t temp_int;
+ auto active_key =
+ PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive});
+ EXPECT_TRUE(fake_system_state_.prefs()->Exists(active_key));
+ EXPECT_TRUE(fake_system_state_.prefs()->GetInt64(active_key, &temp_int));
+ EXPECT_EQ(temp_int, kPingActiveValue);
+}
+
+TEST_F(UpdateAttempterTest, SetDlcInactive) {
+ string dlc_id = "dlc0";
+ MemoryPrefs fake_prefs;
+ fake_system_state_.set_prefs(&fake_prefs);
+ auto sub_keys = {
+ kPrefsPingActive, kPrefsPingLastActive, kPrefsPingLastRollcall};
+ for (auto& sub_key : sub_keys) {
+ auto key = PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, sub_key});
+ fake_system_state_.prefs()->SetInt64(key, 1);
+ EXPECT_TRUE(fake_system_state_.prefs()->Exists(key));
+ }
+ attempter_.SetDlcActiveValue(false, dlc_id);
+ for (auto& sub_key : sub_keys) {
+ auto key = PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, sub_key});
+ EXPECT_FALSE(fake_system_state_.prefs()->Exists(key));
+ }
+}
+
+TEST_F(UpdateAttempterTest, GetSuccessfulDlcIds) {
+ auto dlc_1 = "1", dlc_2 = "2", dlc_3 = "3";
+ attempter_.omaha_request_params_->set_dlc_apps_params(
+ {{dlc_1, {.name = dlc_1, .updated = false}},
+ {dlc_2, {.name = dlc_2}},
+ {dlc_3, {.name = dlc_3, .updated = false}}});
+ EXPECT_THAT(attempter_.GetSuccessfulDlcIds(), ElementsAre(dlc_2));
+}
+
+} // namespace chromeos_update_engine
diff --git a/cros/update_engine_client.cc b/cros/update_engine_client.cc
new file mode 100644
index 0000000..6f20f11
--- /dev/null
+++ b/cros/update_engine_client.cc
@@ -0,0 +1,601 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <inttypes.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/threading/platform_thread.h>
+#include <base/threading/thread_task_runner_handle.h>
+#include <brillo/daemons/daemon.h>
+#include <brillo/flag_helper.h>
+#include <brillo/key_value_store.h>
+
+#include "update_engine/client.h"
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/error_code_utils.h"
+#include "update_engine/cros/omaha_utils.h"
+#include "update_engine/status_update_handler.h"
+#include "update_engine/update_status.h"
+#include "update_engine/update_status_utils.h"
+
+using brillo::KeyValueStore;
+using chromeos_update_engine::EolDate;
+using chromeos_update_engine::EolDateToString;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::UpdateEngineStatusToString;
+using chromeos_update_engine::UpdateStatusToString;
+using chromeos_update_engine::utils::ErrorCodeToString;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using update_engine::UpdateEngineStatus;
+using update_engine::UpdateStatus;
+
+namespace {
+
+// Constant to signal that we need to continue running the daemon after
+// initialization.
+const int kContinueRunning = -1;
+
+// The ShowStatus request will be retried `kShowStatusRetryCount` times at
+// `kShowStatusRetryInterval` second intervals on failure.
+const int kShowStatusRetryCount = 30;
+const int kShowStatusRetryIntervalInSeconds = 2;
+
+class UpdateEngineClient : public brillo::Daemon {
+ public:
+ UpdateEngineClient(int argc, char** argv) : argc_(argc), argv_(argv) {}
+
+ ~UpdateEngineClient() override = default;
+
+ protected:
+ int OnInit() override {
+ int ret = Daemon::OnInit();
+ if (ret != EX_OK)
+ return ret;
+
+ client_ = update_engine::UpdateEngineClient::CreateInstance();
+
+ if (!client_) {
+ LOG(ERROR) << "UpdateEngineService not available.";
+ return 1;
+ }
+
+ // We can't call QuitWithExitCode from OnInit(), so we delay the execution
+ // of the ProcessFlags method after the Daemon initialization is done.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateEngineClient::ProcessFlagsAndExit,
+ base::Unretained(this)));
+ return EX_OK;
+ }
+
+ private:
+ // Show the status of the update engine in stdout.
+ bool ShowStatus();
+
+ // Return whether we need to reboot. 0 if reboot is needed, 1 if an error
+ // occurred, 2 if no reboot is needed.
+ int GetNeedReboot();
+
+ // Main method that parses and triggers all the actions based on the passed
+ // flags. Returns the exit code of the program of kContinueRunning if it
+ // should not exit.
+ int ProcessFlags();
+
+ // Processes the flags and exits the program accordingly.
+ void ProcessFlagsAndExit();
+
+ // Copy of argc and argv passed to main().
+ int argc_;
+ char** argv_;
+
+ // Library-based client
+ unique_ptr<update_engine::UpdateEngineClient> client_;
+
+ // Pointers to handlers for cleanup
+ vector<unique_ptr<update_engine::StatusUpdateHandler>> handlers_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateEngineClient);
+};
+
+class ExitingStatusUpdateHandler : public update_engine::StatusUpdateHandler {
+ public:
+ ~ExitingStatusUpdateHandler() override = default;
+
+ void IPCError(const string& error) override;
+};
+
+void ExitingStatusUpdateHandler::IPCError(const string& error) {
+ LOG(ERROR) << error;
+ exit(1);
+}
+
+class WatchingStatusUpdateHandler : public ExitingStatusUpdateHandler {
+ public:
+ ~WatchingStatusUpdateHandler() override = default;
+
+ void HandleStatusUpdate(const UpdateEngineStatus& status) override;
+};
+
+void WatchingStatusUpdateHandler::HandleStatusUpdate(
+ const UpdateEngineStatus& status) {
+ LOG(INFO) << "Got status update: " << UpdateEngineStatusToString(status);
+}
+
+bool UpdateEngineClient::ShowStatus() {
+ UpdateEngineStatus status;
+ int retry_count = kShowStatusRetryCount;
+ while (retry_count > 0) {
+ if (client_->GetStatus(&status)) {
+ break;
+ }
+ if (--retry_count == 0) {
+ return false;
+ }
+ LOG(WARNING)
+ << "Failed to get the update_engine status. This can happen when the"
+ " update_engine is busy doing a heavy operation or if the"
+ " update-engine service is down. If it doesn't resolve, a restart of"
+ " the update-engine service is needed."
+ " Will try "
+ << retry_count << " more times!";
+ base::PlatformThread::Sleep(
+ base::TimeDelta::FromSeconds(kShowStatusRetryIntervalInSeconds));
+ }
+
+ printf("%s", UpdateEngineStatusToString(status).c_str());
+
+ return true;
+}
+
+int UpdateEngineClient::GetNeedReboot() {
+ UpdateEngineStatus status;
+ if (!client_->GetStatus(&status)) {
+ return 1;
+ }
+
+ if (status.status == UpdateStatus::UPDATED_NEED_REBOOT) {
+ return 0;
+ }
+
+ return 2;
+}
+
+class UpdateWaitHandler : public ExitingStatusUpdateHandler {
+ public:
+ explicit UpdateWaitHandler(bool exit_on_error,
+ update_engine::UpdateEngineClient* client)
+ : exit_on_error_(exit_on_error), client_(client) {}
+
+ ~UpdateWaitHandler() override = default;
+
+ void HandleStatusUpdate(const UpdateEngineStatus& status) override;
+
+ private:
+ bool exit_on_error_;
+ update_engine::UpdateEngineClient* client_;
+};
+
+void UpdateWaitHandler::HandleStatusUpdate(const UpdateEngineStatus& status) {
+ if (exit_on_error_ && status.status == UpdateStatus::IDLE) {
+ int last_attempt_error = static_cast<int>(ErrorCode::kSuccess);
+ ErrorCode code = ErrorCode::kSuccess;
+ if (client_ && client_->GetLastAttemptError(&last_attempt_error))
+ code = static_cast<ErrorCode>(last_attempt_error);
+
+ LOG(ERROR) << "Update failed, current operation is "
+ << UpdateStatusToString(status.status) << ", last error code is "
+ << ErrorCodeToString(code) << "(" << last_attempt_error << ")";
+ exit(1);
+ }
+ if (status.status == UpdateStatus::UPDATED_NEED_REBOOT) {
+ LOG(INFO) << "Update succeeded -- reboot needed.";
+ exit(0);
+ }
+}
+
+int UpdateEngineClient::ProcessFlags() {
+ DEFINE_string(app_version, "", "Force the current app version.");
+ DEFINE_string(channel,
+ "",
+ "Set the target channel. The device will be powerwashed if the "
+ "target channel is more stable than the current channel unless "
+ "--nopowerwash is specified.");
+ DEFINE_bool(check_for_update, false, "Initiate check for updates.");
+ DEFINE_string(
+ cohort_hint, "", "Set the current cohort hint to the passed value.");
+ DEFINE_bool(follow,
+ false,
+ "Wait for any update operations to complete."
+ "Exit status is 0 if the update succeeded, and 1 otherwise.");
+ DEFINE_bool(interactive, true, "Mark the update request as interactive.");
+ DEFINE_string(omaha_url, "", "The URL of the Omaha update server.");
+ DEFINE_string(p2p_update,
+ "",
+ "Enables (\"yes\") or disables (\"no\") the peer-to-peer update"
+ " sharing.");
+ DEFINE_bool(powerwash,
+ true,
+ "When performing rollback or channel change, "
+ "do a powerwash or allow it respectively.");
+ DEFINE_bool(reboot, false, "Initiate a reboot if needed.");
+ DEFINE_bool(is_reboot_needed,
+ false,
+ "Exit status 0 if reboot is needed, "
+ "2 if reboot is not needed or 1 if an error occurred.");
+ DEFINE_bool(block_until_reboot_is_needed,
+ false,
+ "Blocks until reboot is "
+ "needed. Returns non-zero exit status if an error occurred.");
+ DEFINE_bool(reset_status, false, "Sets the status in update_engine to idle.");
+ DEFINE_bool(rollback,
+ false,
+ "Perform a rollback to the previous partition. The device will "
+ "be powerwashed unless --nopowerwash is specified.");
+ DEFINE_bool(can_rollback,
+ false,
+ "Shows whether rollback partition "
+ "is available.");
+ DEFINE_bool(show_channel, false, "Show the current and target channels.");
+ DEFINE_bool(show_cohort_hint, false, "Show the current cohort hint.");
+ DEFINE_bool(show_p2p_update,
+ false,
+ "Show the current setting for peer-to-peer update sharing.");
+ DEFINE_bool(show_update_over_cellular,
+ false,
+ "Show the current setting for updates over cellular networks.");
+ DEFINE_bool(status, false, "Print the status to stdout.");
+ DEFINE_bool(update,
+ false,
+ "Forces an update and waits for it to complete. "
+ "Implies --follow.");
+ DEFINE_string(update_over_cellular,
+ "",
+ "Enables (\"yes\") or disables (\"no\") the updates over "
+ "cellular networks.");
+ DEFINE_bool(watch_for_updates,
+ false,
+ "Listen for status updates and print them to the screen.");
+ DEFINE_bool(prev_version,
+ false,
+ "Show the previous OS version used before the update reboot.");
+ DEFINE_bool(last_attempt_error, false, "Show the last attempt error.");
+ DEFINE_bool(eol_status, false, "Show the current end-of-life status.");
+
+ // Boilerplate init commands.
+ base::CommandLine::Init(argc_, argv_);
+ brillo::FlagHelper::Init(argc_, argv_, "A/B Update Engine Client");
+
+ // Ensure there are no positional arguments.
+ const vector<string> positional_args =
+ base::CommandLine::ForCurrentProcess()->GetArgs();
+ if (!positional_args.empty()) {
+ LOG(ERROR) << "Found a positional argument '" << positional_args.front()
+ << "'. If you want to pass a value to a flag, pass it as "
+ "--flag=value.";
+ return 1;
+ }
+
+ // Update the status if requested.
+ if (FLAGS_reset_status) {
+ LOG(INFO) << "Setting Update Engine status to idle ...";
+
+ if (client_->ResetStatus()) {
+ LOG(INFO) << "ResetStatus succeeded; to undo partition table changes "
+ "run:\n"
+ "(D=$(rootdev -d) P=$(rootdev -s); cgpt p -i$(($(echo "
+ "${P#$D} | sed 's/^[^0-9]*//')-1)) $D;)";
+ } else {
+ LOG(ERROR) << "ResetStatus failed";
+ return 1;
+ }
+ }
+
+ // Changes the current update over cellular network setting.
+ if (!FLAGS_update_over_cellular.empty()) {
+ bool allowed = FLAGS_update_over_cellular == "yes";
+ if (!allowed && FLAGS_update_over_cellular != "no") {
+ LOG(ERROR) << "Unknown option: \"" << FLAGS_update_over_cellular
+ << "\". Please specify \"yes\" or \"no\".";
+ } else {
+ if (!client_->SetUpdateOverCellularPermission(allowed)) {
+ LOG(ERROR) << "Error setting the update over cellular setting.";
+ return 1;
+ }
+ }
+ }
+
+ // Show the current update over cellular network setting.
+ if (FLAGS_show_update_over_cellular) {
+ bool allowed;
+
+ if (!client_->GetUpdateOverCellularPermission(&allowed)) {
+ LOG(ERROR) << "Error getting the update over cellular setting.";
+ return 1;
+ }
+
+ LOG(INFO) << "Current update over cellular network setting: "
+ << (allowed ? "ENABLED" : "DISABLED");
+ }
+
+ // Change/show the cohort hint.
+ bool set_cohort_hint =
+ base::CommandLine::ForCurrentProcess()->HasSwitch("cohort_hint");
+ if (set_cohort_hint) {
+ LOG(INFO) << "Setting cohort hint to: \"" << FLAGS_cohort_hint << "\"";
+ if (!client_->SetCohortHint(FLAGS_cohort_hint)) {
+ LOG(ERROR) << "Error setting the cohort hint.";
+ return 1;
+ }
+ }
+
+ if (FLAGS_show_cohort_hint || set_cohort_hint) {
+ string cohort_hint;
+ if (!client_->GetCohortHint(&cohort_hint)) {
+ LOG(ERROR) << "Error getting the cohort hint.";
+ return 1;
+ }
+
+ LOG(INFO) << "Current cohort hint: \"" << cohort_hint << "\"";
+ }
+
+ if (!FLAGS_powerwash && !FLAGS_rollback && FLAGS_channel.empty()) {
+ LOG(ERROR) << "powerwash flag only makes sense rollback or channel change";
+ return 1;
+ }
+
+ // Change the P2P enabled setting.
+ if (!FLAGS_p2p_update.empty()) {
+ bool enabled = FLAGS_p2p_update == "yes";
+ if (!enabled && FLAGS_p2p_update != "no") {
+ LOG(ERROR) << "Unknown option: \"" << FLAGS_p2p_update
+ << "\". Please specify \"yes\" or \"no\".";
+ } else {
+ if (!client_->SetP2PUpdatePermission(enabled)) {
+ LOG(ERROR) << "Error setting the peer-to-peer update setting.";
+ return 1;
+ }
+ }
+ }
+
+ // Show the rollback availability.
+ if (FLAGS_can_rollback) {
+ string rollback_partition;
+
+ if (!client_->GetRollbackPartition(&rollback_partition)) {
+ LOG(ERROR) << "Error while querying rollback partition availability.";
+ return 1;
+ }
+
+ bool can_rollback = true;
+ if (rollback_partition.empty()) {
+ rollback_partition = "UNAVAILABLE";
+ can_rollback = false;
+ } else {
+ rollback_partition = "AVAILABLE: " + rollback_partition;
+ }
+
+ LOG(INFO) << "Rollback partition: " << rollback_partition;
+ if (!can_rollback) {
+ return 1;
+ }
+ }
+
+ // Show the current P2P enabled setting.
+ if (FLAGS_show_p2p_update) {
+ bool enabled;
+
+ if (!client_->GetP2PUpdatePermission(&enabled)) {
+ LOG(ERROR) << "Error getting the peer-to-peer update setting.";
+ return 1;
+ }
+
+ LOG(INFO) << "Current update using P2P setting: "
+ << (enabled ? "ENABLED" : "DISABLED");
+ }
+
+ // First, update the target channel if requested.
+ if (!FLAGS_channel.empty()) {
+ if (!client_->SetTargetChannel(FLAGS_channel, FLAGS_powerwash)) {
+ LOG(ERROR) << "Error setting the channel.";
+ return 1;
+ }
+
+ LOG(INFO) << "Channel permanently set to: " << FLAGS_channel;
+ }
+
+ // Show the current and target channels if requested.
+ if (FLAGS_show_channel) {
+ string current_channel;
+ string target_channel;
+
+ if (!client_->GetChannel(¤t_channel)) {
+ LOG(ERROR) << "Error getting the current channel.";
+ return 1;
+ }
+
+ if (!client_->GetTargetChannel(&target_channel)) {
+ LOG(ERROR) << "Error getting the target channel.";
+ return 1;
+ }
+
+ LOG(INFO) << "Current Channel: " << current_channel;
+
+ if (!target_channel.empty())
+ LOG(INFO) << "Target Channel (pending update): " << target_channel;
+ }
+
+ bool do_update_request = FLAGS_check_for_update || FLAGS_update ||
+ !FLAGS_app_version.empty() ||
+ !FLAGS_omaha_url.empty();
+ if (FLAGS_update)
+ FLAGS_follow = true;
+
+ if (do_update_request && FLAGS_rollback) {
+ LOG(ERROR) << "Incompatible flags specified with rollback."
+ << "Rollback should not include update-related flags.";
+ return 1;
+ }
+
+ if (FLAGS_rollback) {
+ LOG(INFO) << "Requesting rollback.";
+ if (!client_->Rollback(FLAGS_powerwash)) {
+ LOG(ERROR) << "Rollback request failed.";
+ return 1;
+ }
+ }
+
+ // Initiate an update check, if necessary.
+ if (do_update_request) {
+ LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored.";
+ string app_version = FLAGS_app_version;
+ if (FLAGS_update && app_version.empty()) {
+ app_version = "ForcedUpdate";
+ LOG(INFO) << "Forcing an update by setting app_version to ForcedUpdate.";
+ }
+ LOG(INFO) << "Initiating update check.";
+ if (!client_->AttemptUpdate(
+ app_version, FLAGS_omaha_url, FLAGS_interactive)) {
+ LOG(ERROR) << "Error checking for update.";
+ return 1;
+ }
+ }
+
+ // These final options are all mutually exclusive with one another.
+ if (FLAGS_follow + FLAGS_watch_for_updates + FLAGS_reboot + FLAGS_status +
+ FLAGS_is_reboot_needed + FLAGS_block_until_reboot_is_needed >
+ 1) {
+ LOG(ERROR) << "Multiple exclusive options selected. "
+ << "Select only one of --follow, --watch_for_updates, --reboot, "
+ << "--is_reboot_needed, --block_until_reboot_is_needed, "
+ << "or --status.";
+ return 1;
+ }
+
+ if (FLAGS_status) {
+ LOG(INFO) << "Querying Update Engine status...";
+ if (!ShowStatus()) {
+ LOG(ERROR) << "Failed to query status";
+ return 1;
+ }
+ return 0;
+ }
+
+ if (FLAGS_follow) {
+ LOG(INFO) << "Waiting for update to complete.";
+ auto handler = new UpdateWaitHandler(true, client_.get());
+ handlers_.emplace_back(handler);
+ client_->RegisterStatusUpdateHandler(handler);
+ return kContinueRunning;
+ }
+
+ if (FLAGS_watch_for_updates) {
+ LOG(INFO) << "Watching for status updates.";
+ auto handler = new WatchingStatusUpdateHandler();
+ handlers_.emplace_back(handler);
+ client_->RegisterStatusUpdateHandler(handler);
+ return kContinueRunning;
+ }
+
+ if (FLAGS_reboot) {
+ LOG(INFO) << "Requesting a reboot...";
+ client_->RebootIfNeeded();
+ return 0;
+ }
+
+ if (FLAGS_prev_version) {
+ string prev_version;
+
+ if (!client_->GetPrevVersion(&prev_version)) {
+ LOG(ERROR) << "Error getting previous version.";
+ } else {
+ LOG(INFO) << "Previous version = " << prev_version;
+ }
+ }
+
+ if (FLAGS_is_reboot_needed) {
+ int ret = GetNeedReboot();
+
+ if (ret == 1) {
+ LOG(ERROR) << "Could not query the current operation.";
+ }
+
+ return ret;
+ }
+
+ if (FLAGS_block_until_reboot_is_needed) {
+ auto handler = new UpdateWaitHandler(false, nullptr);
+ handlers_.emplace_back(handler);
+ client_->RegisterStatusUpdateHandler(handler);
+ return kContinueRunning;
+ }
+
+ if (FLAGS_last_attempt_error) {
+ int last_attempt_error;
+ if (!client_->GetLastAttemptError(&last_attempt_error)) {
+ LOG(ERROR) << "Error getting last attempt error.";
+ } else {
+ ErrorCode code = static_cast<ErrorCode>(last_attempt_error);
+
+ KeyValueStore last_attempt_error_store;
+ last_attempt_error_store.SetString(
+ "ERROR_CODE", base::NumberToString(last_attempt_error));
+ last_attempt_error_store.SetString("ERROR_MESSAGE",
+ ErrorCodeToString(code));
+ printf("%s", last_attempt_error_store.SaveToString().c_str());
+ }
+ }
+
+ if (FLAGS_eol_status) {
+ UpdateEngineStatus status;
+ if (!client_->GetStatus(&status)) {
+ LOG(ERROR) << "Error GetStatus() for getting EOL info.";
+ } else {
+ EolDate eol_date_code = status.eol_date;
+
+ KeyValueStore eol_status_store;
+ eol_status_store.SetString("EOL_DATE", EolDateToString(eol_date_code));
+ printf("%s", eol_status_store.SaveToString().c_str());
+ }
+ }
+
+ return 0;
+}
+
+void UpdateEngineClient::ProcessFlagsAndExit() {
+ int ret = ProcessFlags();
+ if (ret != kContinueRunning)
+ QuitWithExitCode(ret);
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ UpdateEngineClient client(argc, argv);
+ return client.Run();
+}