Project import
diff --git a/update_engine/.clang-format b/update_engine/.clang-format
new file mode 120000
index 0000000..f412743
--- /dev/null
+++ b/update_engine/.clang-format
@@ -0,0 +1 @@
+../../build/tools/brillo-clang-format
\ No newline at end of file
diff --git a/update_engine/Android.mk b/update_engine/Android.mk
new file mode 100644
index 0000000..1cba534
--- /dev/null
+++ b/update_engine/Android.mk
@@ -0,0 +1,1152 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(my-dir)
+
+# Default values for the USE flags. Override these USE flags from your product
+# by setting BRILLO_USE_* values. Note that we define local variables like
+# local_use_* to prevent leaking our default setting for other packages.
+local_use_binder := $(if $(BRILLO_USE_BINDER),$(BRILLO_USE_BINDER),1)
+local_use_dbus := $(if $(BRILLO_USE_DBUS),$(BRILLO_USE_DBUS),0)
+local_use_hwid_override := \
+    $(if $(BRILLO_USE_HWID_OVERRIDE),$(BRILLO_USE_HWID_OVERRIDE),0)
+# "libcros" gates the LibCrosService exposed by the Chrome OS' chrome browser to
+# the system layer.
+local_use_libcros := $(if $(BRILLO_USE_LIBCROS),$(BRILLO_USE_LIBCROS),0)
+local_use_mtd := $(if $(BRILLO_USE_MTD),$(BRILLO_USE_MTD),0)
+local_use_omaha := $(if $(BRILLO_USE_OMAHA),$(BRILLO_USE_OMAHA),0)
+local_use_shill := $(if $(BRILLO_USE_SHILL),$(BRILLO_USE_SHILL),0)
+local_use_weave := $(if $(BRILLO_USE_WEAVE),$(BRILLO_USE_WEAVE),0)
+local_use_nestlabs := $(if $(BRILLO_USE_NESTLABS),$(BRILLO_USE_NESTLABS),0)
+
+ifeq ($(local_use_shill),1)
+ifneq ($(local_use_dbus),1)
+$(error USE_SHILL depends on USE_DBUS.)
+endif  # local_use_dbus != 1
+endif  # local_use_shill == 1
+
+ue_common_cflags := \
+    -DUSE_BINDER=$(local_use_binder) \
+    -DUSE_DBUS=$(local_use_dbus) \
+    -DUSE_HWID_OVERRIDE=$(local_use_hwid_override) \
+    -DUSE_LIBCROS=$(local_use_libcros) \
+    -DUSE_MTD=$(local_use_mtd) \
+    -DUSE_OMAHA=$(local_use_omaha) \
+    -DUSE_SHILL=$(local_use_shill) \
+    -DUSE_WEAVE=$(local_use_weave) \
+    -DUSE_NESTLABS=$(local_use_nestlabs) \
+    -D_FILE_OFFSET_BITS=64 \
+    -D_POSIX_C_SOURCE=199309L \
+    -Wa,--noexecstack \
+    -Wall \
+    -Werror \
+    -Wextra \
+    -Wformat=2 \
+    -Wno-psabi \
+    -Wno-unused-parameter \
+    -ffunction-sections \
+    -fstack-protector-strong \
+    -fvisibility=hidden
+ue_common_cppflags := \
+    -Wnon-virtual-dtor \
+    -fno-strict-aliasing
+ue_common_ldflags := \
+    -Wl,--gc-sections
+ue_common_c_includes := \
+    $(LOCAL_PATH)/client_library/include \
+    system
+ue_common_shared_libraries := \
+    libbrillo-stream \
+    libbrillo \
+    libchrome \
+    libbase
+ue_common_static_libraries := \
+    libgtest_prod \
+
+ifeq ($(local_use_dbus),1)
+
+# update_engine_client-dbus-proxies (from generate-dbus-proxies.gypi)
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine_client-dbus-proxies
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+ifeq ($(local_use_nestlabs),1)
+LOCAL_SRC_FILES += \
+    dbus_bindings/dbus-service-config-nestlabs.json \
+    dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml
+else
+LOCAL_SRC_FILES += \
+    dbus_bindings/dbus-service-config.json \
+    dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
+endif
+LOCAL_DBUS_PROXY_PREFIX := update_engine
+include $(BUILD_STATIC_LIBRARY)
+
+endif  # local_use_dbus == 1
+
+# update_metadata-protos (type: static_library)
+# ========================================================
+# Protobufs.
+ue_update_metadata_protos_exported_static_libraries := \
+    update_metadata-protos
+ue_update_metadata_protos_exported_shared_libraries := \
+    libprotobuf-cpp-lite
+
+ue_update_metadata_protos_src_files := \
+    update_metadata.proto
+
+# Build for the host.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_metadata-protos
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_IS_HOST_MODULE := true
+generated_sources_dir := $(call local-generated-sources-dir)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(generated_sources_dir)/proto/system
+LOCAL_SRC_FILES := $(ue_update_metadata_protos_src_files)
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+# Build for the target.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_metadata-protos
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+generated_sources_dir := $(call local-generated-sources-dir)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(generated_sources_dir)/proto/system
+LOCAL_SRC_FILES := $(ue_update_metadata_protos_src_files)
+include $(BUILD_STATIC_LIBRARY)
+
+ifeq ($(local_use_dbus),1)
+
+# update_engine-dbus-adaptor (from generate-dbus-adaptors.gypi)
+# ========================================================
+# Chrome D-Bus bindings.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine-dbus-adaptor
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+ifeq ($(local_use_nestlabs),1)
+LOCAL_SRC_FILES := \
+    dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml
+else
+LOCAL_SRC_FILES := \
+    dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
+endif
+include $(BUILD_STATIC_LIBRARY)
+
+# update_engine-dbus-libcros-client (from generate-dbus-proxies.gypi)
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine-dbus-libcros-client
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_SRC_FILES := \
+    dbus_bindings/org.chromium.LibCrosService.dbus-xml
+LOCAL_DBUS_PROXY_PREFIX := libcros
+include $(BUILD_STATIC_LIBRARY)
+
+endif  # local_use_dbus == 1
+
+# libpayload_consumer (type: static_library)
+# ========================================================
+# The payload application component and common dependencies.
+ue_libpayload_consumer_exported_static_libraries := \
+    update_metadata-protos \
+    libxz-host \
+    libbz \
+    libimgpatch \
+    libz \
+    $(ue_update_metadata_protos_exported_static_libraries)
+ue_libpayload_consumer_exported_shared_libraries := \
+    libcrypto \
+    $(ue_update_metadata_protos_exported_shared_libraries)
+
+ue_libpayload_consumer_src_files := \
+    common/action_processor.cc \
+    common/boot_control_stub.cc \
+    common/clock.cc \
+    common/constants.cc \
+    common/cpu_limiter.cc \
+    common/error_code_utils.cc \
+    common/file_fetcher.cc \
+    common/hash_calculator.cc \
+    common/http_common.cc \
+    common/http_fetcher.cc \
+    common/hwid_override.cc \
+    common/multi_range_http_fetcher.cc \
+    common/prefs.cc \
+    common/subprocess.cc \
+    common/terminator.cc \
+    common/utils.cc \
+    payload_consumer/bzip_extent_writer.cc \
+    payload_consumer/delta_performer.cc \
+    payload_consumer/download_action.cc \
+    payload_consumer/extent_writer.cc \
+    payload_consumer/file_descriptor.cc \
+    payload_consumer/file_writer.cc \
+    payload_consumer/filesystem_verifier_action.cc \
+    payload_consumer/install_plan.cc \
+    payload_consumer/payload_constants.cc \
+    payload_consumer/payload_verifier.cc \
+    payload_consumer/postinstall_runner_action.cc \
+    payload_consumer/xz_extent_writer.cc
+ifeq ($(local_use_nestlabs),1)
+ue_libpayload_consumer_src_files += \
+    common/platform_constants_nestlabs.cc
+else # local_use_nestlabs == 1
+ue_libpayload_consumer_src_files += \
+    common/platform_constants_android.cc
+endif # local_use_nestlabs != 1
+
+ifeq ($(HOST_OS),linux)
+# Build for the host.
+include $(CLEAR_VARS)
+LOCAL_MODULE := libpayload_consumer
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+    $(ue_common_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+    update_metadata-protos \
+    $(ue_common_static_libraries) \
+    $(ue_libpayload_consumer_exported_static_libraries) \
+    $(ue_update_metadata_protos_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+    $(ue_common_shared_libraries) \
+    $(ue_libpayload_consumer_exported_shared_libraries) \
+    $(ue_update_metadata_protos_exported_shared_libraries)
+LOCAL_SRC_FILES := $(ue_libpayload_consumer_src_files)
+include $(BUILD_HOST_STATIC_LIBRARY)
+endif  # HOST_OS == linux
+
+# Build for the target.
+include $(CLEAR_VARS)
+LOCAL_MODULE := libpayload_consumer
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+    $(ue_common_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+    update_metadata-protos \
+    $(ue_common_static_libraries) \
+    $(ue_libpayload_consumer_exported_static_libraries:-host=) \
+    $(ue_update_metadata_protos_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+    $(ue_common_shared_libraries) \
+    $(ue_libpayload_consumer_exported_shared_libraries:-host=) \
+    $(ue_update_metadata_protos_exported_shared_libraries)
+LOCAL_SRC_FILES := $(ue_libpayload_consumer_src_files)
+include $(BUILD_STATIC_LIBRARY)
+
+ifeq ($(local_use_omaha),1)
+
+# libupdate_engine (type: static_library)
+# ========================================================
+# The main daemon static_library with all the code used to check for updates
+# with Omaha and expose a DBus daemon.
+ue_libupdate_engine_exported_c_includes := \
+    $(LOCAL_PATH)/include \
+    external/cros/system_api/dbus
+ue_libupdate_engine_exported_static_libraries := \
+    libpayload_consumer \
+    update_metadata-protos \
+    libbz \
+    libfs_mgr \
+    $(ue_libpayload_consumer_exported_static_libraries) \
+    $(ue_update_metadata_protos_exported_static_libraries)
+ue_libupdate_engine_exported_shared_libraries := \
+    libmetrics \
+    libexpat \
+    libbrillo-policy \
+    libhardware \
+    libcurl \
+    libcutils \
+    libssl \
+    $(ue_libpayload_consumer_exported_shared_libraries) \
+    $(ue_update_metadata_protos_exported_shared_libraries)
+ifeq ($(local_use_dbus),1)
+ue_libupdate_engine_exported_static_libraries += \
+    update_engine-dbus-adaptor \
+    update_engine-dbus-libcros-client \
+    update_engine_client-dbus-proxies
+ue_libupdate_engine_exported_shared_libraries += \
+    libdbus \
+    libbrillo-dbus \
+    libchrome-dbus
+endif  # local_use_dbus == 1
+ifeq ($(local_use_shill),1)
+ue_libupdate_engine_exported_shared_libraries += \
+    libshill-client
+endif  # local_use_shill == 1
+ifeq ($(local_use_binder),1)
+ue_libupdate_engine_exported_shared_libraries += \
+    libbinder \
+    libbinderwrapper \
+    libbrillo-binder \
+    libutils
+endif  # local_use_binder == 1
+ifeq ($(local_use_weave),1)
+ue_libupdate_engine_exported_shared_libraries += \
+    libbinderwrapper \
+    libbrillo-binder \
+    libweaved
+endif  # local_use_weave == 1
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libupdate_engine
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(ue_libupdate_engine_exported_c_includes)
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+    $(ue_common_c_includes) \
+    $(ue_libupdate_engine_exported_c_includes) \
+    bootable/recovery
+LOCAL_STATIC_LIBRARIES := \
+    libpayload_consumer \
+    update_metadata-protos \
+    $(ue_common_static_libraries) \
+    $(ue_libupdate_engine_exported_static_libraries:-host=) \
+    $(ue_libpayload_consumer_exported_static_libraries:-host=) \
+    $(ue_update_metadata_protos_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+    $(ue_common_shared_libraries) \
+    $(ue_libupdate_engine_exported_shared_libraries:-host=) \
+    $(ue_libpayload_consumer_exported_shared_libraries:-host=) \
+    $(ue_update_metadata_protos_exported_shared_libraries)
+LOCAL_SRC_FILES := \
+    boot_control_android.cc \
+    certificate_checker.cc \
+    common_service.cc \
+    connection_utils.cc \
+    daemon.cc \
+    hardware_android.cc \
+    image_properties_android.cc \
+    libcurl_http_fetcher.cc \
+    metrics.cc \
+    metrics_utils.cc \
+    p2p_manager.cc \
+    payload_state.cc \
+    power_manager_android.cc \
+    proxy_resolver.cc \
+    real_system_state.cc \
+    update_manager/boxed_value.cc \
+    update_manager/chromeos_policy.cc \
+    update_manager/default_policy.cc \
+    update_manager/evaluation_context.cc \
+    update_manager/policy.cc \
+    update_manager/real_config_provider.cc \
+    update_manager/real_device_policy_provider.cc \
+    update_manager/real_random_provider.cc \
+    update_manager/real_system_provider.cc \
+    update_manager/real_time_provider.cc \
+    update_manager/real_updater_provider.cc \
+    update_manager/state_factory.cc \
+    update_manager/update_manager.cc \
+    update_status_utils.cc \
+    utils_android.cc \
+    weave_service_factory.cc
+ifeq ($(local_use_nestlabs),1)
+LOCAL_SRC_FILES += \
+    omaha_request_params.cc \
+    omaha_utils.cc \
+    payload_properties_request_action.cc \
+    payload_properties_handler_action.cc \
+    update_attempter_nestlabs.cc
+else
+LOCAL_SRC_FILES += \
+    omaha_request_action.cc \
+    omaha_response_handler_action.cc \
+    update_attempter.cc
+endif # local_use_nestlabs == 1
+ifeq ($(local_use_dbus),1)
+LOCAL_SRC_FILES += \
+    dbus_connection.cc \
+    libcros_proxy.cc
+ifeq ($(local_use_nestlabs),1)
+LOCAL_SRC_FILES += \
+    dbus_service_nestlabs.cc
+else
+LOCAL_SRC_FILES += \
+    dbus_service.cc
+endif
+endif  # local_use_dbus == 1
+ifeq ($(local_use_shill),1)
+LOCAL_SRC_FILES += \
+    connection_manager.cc \
+    shill_proxy.cc \
+    update_manager/real_shill_provider.cc
+else   # local_use_shill != 1
+LOCAL_SRC_FILES += \
+    connection_manager_android.cc
+endif  # local_use_shill == 1
+ifeq ($(local_use_binder),1)
+LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/binder_bindings
+LOCAL_SRC_FILES += \
+    binder_bindings/android/brillo/IUpdateEngine.aidl \
+    binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl \
+    binder_service_brillo.cc \
+    parcelable_update_engine_status.cc
+endif  # local_use_binder == 1
+ifeq ($(local_use_weave),1)
+LOCAL_SRC_FILES += \
+    weave_service.cc
+endif  # local_use_weave == 1
+ifeq ($(local_use_libcros),1)
+LOCAL_SRC_FILES += \
+    chrome_browser_proxy_resolver.cc
+endif  # local_use_libcros == 1
+include $(BUILD_STATIC_LIBRARY)
+
+else  # local_use_omaha == 1
+
+ifneq ($(local_use_binder),1)
+$(error USE_BINDER is disabled but is required in non-Brillo devices.)
+endif  # local_use_binder == 1
+
+# libupdate_engine_android (type: static_library)
+# ========================================================
+# The main daemon static_library used in Android (non-Brillo). This only has a
+# loop to apply payloads provided by the upper layer via a Binder interface.
+ue_libupdate_engine_android_exported_static_libraries := \
+    libpayload_consumer \
+    libfs_mgr \
+    $(ue_libpayload_consumer_exported_static_libraries)
+ue_libupdate_engine_android_exported_shared_libraries := \
+    $(ue_libpayload_consumer_exported_shared_libraries) \
+    libandroid \
+    libbinder \
+    libbinderwrapper \
+    libbrillo-binder \
+    libcutils \
+    libcurl \
+    libhardware \
+    libssl \
+    libutils
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libupdate_engine_android
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+    $(ue_common_c_includes) \
+    bootable/recovery
+#TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved
+# out of the DBus interface.
+LOCAL_C_INCLUDES += \
+    external/cros/system_api/dbus
+LOCAL_STATIC_LIBRARIES := \
+    $(ue_common_static_libraries) \
+    $(ue_libupdate_engine_android_exported_static_libraries:-host=)
+LOCAL_SHARED_LIBRARIES += \
+    $(ue_common_shared_libraries) \
+    $(ue_libupdate_engine_android_exported_shared_libraries:-host=)
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder_bindings
+LOCAL_SRC_FILES += \
+    binder_bindings/android/os/IUpdateEngine.aidl \
+    binder_bindings/android/os/IUpdateEngineCallback.aidl \
+    binder_service_android.cc \
+    boot_control_android.cc \
+    certificate_checker.cc \
+    daemon.cc \
+    daemon_state_android.cc \
+    hardware_android.cc \
+    libcurl_http_fetcher.cc \
+    network_selector_android.cc \
+    proxy_resolver.cc \
+    update_attempter_android.cc \
+    update_status_utils.cc \
+    utils_android.cc
+include $(BUILD_STATIC_LIBRARY)
+
+endif  # local_use_omaha == 1
+
+# update_engine (type: executable)
+# ========================================================
+# update_engine daemon.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_REQUIRED_MODULES := \
+    bspatch \
+    cacerts_google
+ifeq ($(local_use_weave),1)
+LOCAL_REQUIRED_MODULES += updater.json
+endif  # local_use_weave == 1
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+    $(ue_common_c_includes)
+LOCAL_SHARED_LIBRARIES := \
+    $(ue_common_shared_libraries)
+LOCAL_STATIC_LIBRARIES := \
+    $(ue_common_static_libraries)
+LOCAL_SRC_FILES := \
+    main.cc
+
+ifeq ($(local_use_omaha),1)
+LOCAL_C_INCLUDES += \
+    $(ue_libupdate_engine_exported_c_includes)
+LOCAL_STATIC_LIBRARIES += \
+    libupdate_engine \
+    $(ue_libupdate_engine_exported_static_libraries:-host=)
+LOCAL_SHARED_LIBRARIES += \
+    $(ue_libupdate_engine_exported_shared_libraries:-host=)
+else  # local_use_omaha == 1
+LOCAL_STATIC_LIBRARIES += \
+    libupdate_engine_android \
+    $(ue_libupdate_engine_android_exported_static_libraries:-host=)
+LOCAL_SHARED_LIBRARIES += \
+    $(ue_libupdate_engine_android_exported_shared_libraries:-host=)
+endif  # local_use_omaha == 1
+
+LOCAL_INIT_RC := update_engine.rc
+include $(BUILD_EXECUTABLE)
+
+# update_engine_sideload (type: executable)
+# ========================================================
+# A static binary equivalent to update_engine daemon that installs an update
+# from a local file directly instead of running in the background.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine_sideload
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_REQUIRED_MODULES := \
+    bspatch_recovery
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := \
+    $(ue_common_cflags) \
+    -D_UE_SIDELOAD
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+    $(ue_common_c_includes) \
+    bootable/recovery
+#TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved
+# out of the DBus interface.
+LOCAL_C_INCLUDES += \
+    external/cros/system_api/dbus
+LOCAL_SRC_FILES := \
+    boot_control_android.cc \
+    hardware_android.cc \
+    network_selector_stub.cc \
+    proxy_resolver.cc \
+    sideload_main.cc \
+    update_attempter_android.cc \
+    update_status_utils.cc \
+    utils_android.cc
+LOCAL_STATIC_LIBRARIES := \
+    libfs_mgr \
+    libpayload_consumer \
+    update_metadata-protos \
+    libmodpb64 \
+    libgtest_prod \
+    $(ue_common_static_libraries) \
+    $(ue_libpayload_consumer_exported_static_libraries:-host=) \
+    $(ue_update_metadata_protos_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES += \
+    $(ue_common_shared_libraries) \
+    libcutils \
+    liblog \
+    libevent \
+    libprotobuf-cpp-lite \
+
+LOCAL_SHARED_LIBRARIES += \
+    $(ue_libupdate_engine_exported_shared_libraries:-host=)
+
+include $(BUILD_EXECUTABLE)
+
+# libupdate_engine_client (type: shared_library)
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libupdate_engine_client
+LOCAL_CFLAGS := \
+    -Wall \
+    -Werror \
+    -Wno-unused-parameter \
+    -DUSE_DBUS=$(local_use_dbus) \
+    -DUSE_BINDER=$(local_use_binder) \
+    -DUSE_NESTLABS=$(local_use_nestlabs)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := .cc
+# TODO(deymo): Remove "external/cros/system_api/dbus" when dbus is not used.
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/client_library/include \
+    external/cros/system_api/dbus \
+    system
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/client_library/include
+LOCAL_SHARED_LIBRARIES := \
+    libchrome \
+    libbrillo
+LOCAL_SRC_FILES := \
+    client_library/client.cc \
+    update_status_utils.cc
+
+# We can only compile support for one IPC mechanism. If both "binder" and "dbus"
+# are defined, we prefer binder.
+ifeq ($(local_use_binder),1)
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder_bindings
+LOCAL_SHARED_LIBRARIES += \
+    libbinder \
+    libbrillo-binder \
+    libutils
+LOCAL_SRC_FILES += \
+    binder_bindings/android/brillo/IUpdateEngine.aidl \
+    binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl \
+    client_library/client_binder.cc \
+    parcelable_update_engine_status.cc
+else  # local_use_binder != 1
+LOCAL_STATIC_LIBRARIES := \
+    update_engine_client-dbus-proxies
+LOCAL_SHARED_LIBRARIES += \
+    libchrome-dbus \
+    libbrillo-dbus
+ifeq ($(local_use_nestlabs),1)
+LOCAL_SRC_FILES += \
+    client_library/client_dbus_nestlabs.cc
+else
+LOCAL_SRC_FILES += \
+    client_library/client_dbus.cc
+endif
+endif  # local_use_binder == 1
+
+include $(BUILD_SHARED_LIBRARY)
+
+# update_engine_client (type: executable)
+# ========================================================
+# update_engine console client.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine_client
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := $(ue_common_c_includes)
+LOCAL_SHARED_LIBRARIES := $(ue_common_shared_libraries)
+LOCAL_STATIC_LIBRARIES := $(ue_common_static_libraries)
+ifeq ($(local_use_omaha),1)
+LOCAL_SHARED_LIBRARIES += \
+    libupdate_engine_client
+LOCAL_SRC_FILES := \
+    update_engine_client.cc \
+    common/error_code_utils.cc \
+    omaha_utils.cc
+else  # local_use_omaha == 1
+#TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved
+# out of the DBus interface.
+LOCAL_C_INCLUDES += \
+    external/cros/system_api/dbus
+LOCAL_SHARED_LIBRARIES += \
+    libbinder \
+    libbinderwrapper \
+    libbrillo-binder \
+    libutils
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder_bindings
+LOCAL_SRC_FILES := \
+    binder_bindings/android/os/IUpdateEngine.aidl \
+    binder_bindings/android/os/IUpdateEngineCallback.aidl \
+    common/error_code_utils.cc \
+    update_engine_client_android.cc \
+    update_status_utils.cc
+endif  # local_use_omaha == 1
+include $(BUILD_EXECUTABLE)
+
+# libpayload_generator (type: static_library)
+# ========================================================
+# server-side code. This is used for delta_generator and unittests but not
+# for any client code.
+ue_libpayload_generator_exported_static_libraries := \
+    libpayload_consumer \
+    update_metadata-protos \
+    liblzma \
+    $(ue_libpayload_consumer_exported_static_libraries) \
+    $(ue_update_metadata_protos_exported_static_libraries)
+ue_libpayload_generator_exported_shared_libraries := \
+    libext2fs-host \
+    $(ue_libpayload_consumer_exported_shared_libraries) \
+    $(ue_update_metadata_protos_exported_shared_libraries)
+
+ue_libpayload_generator_src_files := \
+    payload_generator/ab_generator.cc \
+    payload_generator/annotated_operation.cc \
+    payload_generator/blob_file_writer.cc \
+    payload_generator/block_mapping.cc \
+    payload_generator/bzip.cc \
+    payload_generator/cycle_breaker.cc \
+    payload_generator/delta_diff_generator.cc \
+    payload_generator/delta_diff_utils.cc \
+    payload_generator/ext2_filesystem.cc \
+    payload_generator/extent_ranges.cc \
+    payload_generator/extent_utils.cc \
+    payload_generator/full_update_generator.cc \
+    payload_generator/graph_types.cc \
+    payload_generator/graph_utils.cc \
+    payload_generator/inplace_generator.cc \
+    payload_generator/payload_file.cc \
+    payload_generator/payload_generation_config.cc \
+    payload_generator/payload_signer.cc \
+    payload_generator/raw_filesystem.cc \
+    payload_generator/tarjan.cc \
+    payload_generator/topological_sort.cc \
+    payload_generator/xz_android.cc
+
+ifeq ($(HOST_OS),linux)
+# Build for the host.
+include $(CLEAR_VARS)
+LOCAL_MODULE := libpayload_generator
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := $(ue_common_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+    libpayload_consumer \
+    update_metadata-protos \
+    liblzma \
+    $(ue_common_static_libraries) \
+    $(ue_libpayload_consumer_exported_static_libraries) \
+    $(ue_update_metadata_protos_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+    $(ue_common_shared_libraries) \
+    $(ue_libpayload_generator_exported_shared_libraries) \
+    $(ue_libpayload_consumer_exported_shared_libraries) \
+    $(ue_update_metadata_protos_exported_shared_libraries)
+LOCAL_SRC_FILES := $(ue_libpayload_generator_src_files)
+include $(BUILD_HOST_STATIC_LIBRARY)
+endif  # HOST_OS == linux
+
+# Build for the target.
+include $(CLEAR_VARS)
+LOCAL_MODULE := libpayload_generator
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := $(ue_common_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+    libpayload_consumer \
+    update_metadata-protos \
+    liblzma \
+    $(ue_common_static_libraries) \
+    $(ue_libpayload_consumer_exported_static_libraries:-host=) \
+    $(ue_update_metadata_protos_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+    $(ue_common_shared_libraries) \
+    $(ue_libpayload_generator_exported_shared_libraries:-host=) \
+    $(ue_libpayload_consumer_exported_shared_libraries:-host=) \
+    $(ue_update_metadata_protos_exported_shared_libraries)
+LOCAL_SRC_FILES := $(ue_libpayload_generator_src_files)
+include $(BUILD_STATIC_LIBRARY)
+
+# delta_generator (type: executable)
+# ========================================================
+# server-side delta generator.
+ue_delta_generator_src_files := \
+    payload_generator/generate_delta_main.cc
+
+ifeq ($(HOST_OS),linux)
+# Build for the host.
+include $(CLEAR_VARS)
+LOCAL_MODULE := delta_generator
+LOCAL_REQUIRED_MODULES := \
+    bsdiff \
+    imgdiff
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := $(ue_common_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+    libpayload_consumer \
+    libpayload_generator \
+    $(ue_common_static_libraries) \
+    $(ue_libpayload_consumer_exported_static_libraries) \
+    $(ue_libpayload_generator_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+    $(ue_common_shared_libraries) \
+    $(ue_libpayload_consumer_exported_shared_libraries) \
+    $(ue_libpayload_generator_exported_shared_libraries)
+LOCAL_SRC_FILES := $(ue_delta_generator_src_files)
+include $(BUILD_HOST_EXECUTABLE)
+endif  # HOST_OS == linux
+
+# Build for the target.
+include $(CLEAR_VARS)
+LOCAL_MODULE := ue_unittest_delta_generator
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests
+LOCAL_MODULE_STEM := delta_generator
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := $(ue_common_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+    libpayload_consumer \
+    libpayload_generator \
+    $(ue_common_static_libraries) \
+    $(ue_libpayload_consumer_exported_static_libraries:-host=) \
+    $(ue_libpayload_generator_exported_static_libraries:-host=)
+LOCAL_SHARED_LIBRARIES := \
+    $(ue_common_shared_libraries) \
+    $(ue_libpayload_consumer_exported_shared_libraries:-host=) \
+    $(ue_libpayload_generator_exported_shared_libraries:-host=)
+LOCAL_SRC_FILES := $(ue_delta_generator_src_files)
+include $(BUILD_EXECUTABLE)
+
+# Private and public keys for unittests.
+# ========================================================
+# Generate a module that installs a prebuilt private key and a module that
+# installs a public key generated from the private key.
+#
+# $(1): The path to the private key in pem format.
+define ue-unittest-keys
+    $(eval include $(CLEAR_VARS)) \
+    $(eval LOCAL_MODULE := ue_$(1).pem) \
+    $(eval LOCAL_MODULE_CLASS := ETC) \
+    $(eval $(ifeq $(BRILLO), 1, LOCAL_MODULE_TAGS := eng)) \
+    $(eval LOCAL_SRC_FILES := $(1).pem) \
+    $(eval LOCAL_MODULE_PATH := \
+        $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests) \
+    $(eval LOCAL_MODULE_STEM := $(1).pem) \
+    $(eval include $(BUILD_PREBUILT)) \
+    \
+    $(eval include $(CLEAR_VARS)) \
+    $(eval LOCAL_MODULE := ue_$(1).pub.pem) \
+    $(eval LOCAL_MODULE_CLASS := ETC) \
+    $(eval $(ifeq $(BRILLO), 1, LOCAL_MODULE_TAGS := eng)) \
+    $(eval LOCAL_MODULE_PATH := \
+        $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests) \
+    $(eval LOCAL_MODULE_STEM := $(1).pub.pem) \
+    $(eval include $(BUILD_SYSTEM)/base_rules.mk) \
+    $(eval $(LOCAL_BUILT_MODULE) : $(LOCAL_PATH)/$(1).pem ; \
+        openssl rsa -in $$< -pubout -out $$@)
+endef
+
+$(call ue-unittest-keys,unittest_key)
+$(call ue-unittest-keys,unittest_key2)
+
+# Sample images for unittests.
+# ========================================================
+# Generate a prebuilt module that installs a sample image from the compressed
+# sample_images.tar.bz2 file used by the unittests.
+#
+# $(1): The filename in the sample_images.tar.bz2
+define ue-unittest-sample-image
+    $(eval include $(CLEAR_VARS)) \
+    $(eval LOCAL_MODULE := ue_unittest_$(1)) \
+    $(eval LOCAL_MODULE_CLASS := EXECUTABLES) \
+    $(eval $(ifeq $(BRILLO), 1, LOCAL_MODULE_TAGS := eng)) \
+    $(eval LOCAL_MODULE_PATH := \
+        $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests/gen) \
+    $(eval LOCAL_MODULE_STEM := $(1)) \
+    $(eval include $(BUILD_SYSTEM)/base_rules.mk) \
+    $(eval $(LOCAL_BUILT_MODULE) : \
+        $(LOCAL_PATH)/sample_images/sample_images.tar.bz2 ; \
+        tar -jxf $$< -C $$(dir $$@) $$(notdir $$@) && touch $$@)
+endef
+
+$(call ue-unittest-sample-image,disk_ext2_1k.img)
+$(call ue-unittest-sample-image,disk_ext2_4k.img)
+$(call ue-unittest-sample-image,disk_ext2_4k_empty.img)
+$(call ue-unittest-sample-image,disk_ext2_unittest.img)
+
+# Zlib Fingerprint
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := zlib_fingerprint
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests
+LOCAL_PREBUILT_MODULE_FILE := $(TARGET_OUT_COMMON_GEN)/zlib_fingerprint
+include $(BUILD_PREBUILT)
+
+# update_engine.conf
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := ue_unittest_update_engine.conf
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests
+LOCAL_MODULE_STEM := update_engine.conf
+LOCAL_SRC_FILES := update_engine.conf
+include $(BUILD_PREBUILT)
+
+# test_http_server (type: executable)
+# ========================================================
+# Test HTTP Server.
+include $(CLEAR_VARS)
+LOCAL_MODULE := test_http_server
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := $(ue_common_c_includes)
+LOCAL_SHARED_LIBRARIES := $(ue_common_shared_libraries)
+LOCAL_STATIC_LIBRARIES := $(ue_common_static_libraries)
+LOCAL_SRC_FILES := \
+    common/http_common.cc \
+    test_http_server.cc
+include $(BUILD_EXECUTABLE)
+
+# bsdiff (type: executable)
+# ========================================================
+# We need bsdiff in the update_engine_unittests directory, so we build it here.
+include $(CLEAR_VARS)
+LOCAL_MODULE := ue_unittest_bsdiff
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests
+LOCAL_MODULE_STEM := bsdiff
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_SRC_FILES := ../../external/bsdiff/bsdiff_main.cc
+LOCAL_CFLAGS := \
+    -D_FILE_OFFSET_BITS=64 \
+    -Wall \
+    -Werror \
+    -Wextra \
+    -Wno-unused-parameter
+LOCAL_STATIC_LIBRARIES := \
+    libbsdiff \
+    libbz \
+    libdivsufsort64 \
+    libdivsufsort
+include $(BUILD_EXECUTABLE)
+
+# test_subprocess (type: executable)
+# ========================================================
+# Test helper subprocess program.
+include $(CLEAR_VARS)
+LOCAL_MODULE := test_subprocess
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := $(ue_common_c_includes)
+LOCAL_SHARED_LIBRARIES := $(ue_common_shared_libraries)
+LOCAL_STATIC_LIBRARIES := $(ue_common_static_libraries)
+LOCAL_SRC_FILES := test_subprocess.cc
+include $(BUILD_EXECUTABLE)
+
+# update_engine_unittests (type: executable)
+# ========================================================
+# Main unittest file.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine_unittests
+LOCAL_REQUIRED_MODULES := \
+    test_http_server \
+    test_subprocess \
+    ue_unittest_bsdiff \
+    ue_unittest_delta_generator \
+    ue_unittest_disk_ext2_1k.img \
+    ue_unittest_disk_ext2_4k.img \
+    ue_unittest_disk_ext2_4k_empty.img \
+    ue_unittest_disk_ext2_unittest.img \
+    ue_unittest_key.pem \
+    ue_unittest_key.pub.pem \
+    ue_unittest_key2.pem \
+    ue_unittest_key2.pub.pem \
+    ue_unittest_update_engine.conf \
+    zlib_fingerprint
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+    $(ue_common_c_includes) \
+    $(ue_libupdate_engine_exported_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+    libpayload_generator \
+    libbrillo-test-helpers \
+    libgmock \
+    libchrome_test_helpers \
+    $(ue_common_static_libraries) \
+    $(ue_libpayload_generator_exported_static_libraries:-host=)
+LOCAL_SHARED_LIBRARIES := \
+    $(ue_common_shared_libraries) \
+    $(ue_libpayload_generator_exported_shared_libraries:-host=)
+LOCAL_SRC_FILES := \
+    certificate_checker_unittest.cc \
+    common/action_pipe_unittest.cc \
+    common/action_processor_unittest.cc \
+    common/action_unittest.cc \
+    common/cpu_limiter_unittest.cc \
+    common/fake_prefs.cc \
+    common/file_fetcher_unittest.cc \
+    common/hash_calculator_unittest.cc \
+    common/http_fetcher_unittest.cc \
+    common/hwid_override_unittest.cc \
+    common/mock_http_fetcher.cc \
+    common/prefs_unittest.cc \
+    common/subprocess_unittest.cc \
+    common/terminator_unittest.cc \
+    common/test_utils.cc \
+    common/utils_unittest.cc \
+    payload_consumer/bzip_extent_writer_unittest.cc \
+    payload_consumer/delta_performer_integration_test.cc \
+    payload_consumer/delta_performer_unittest.cc \
+    payload_consumer/extent_writer_unittest.cc \
+    payload_consumer/file_writer_unittest.cc \
+    payload_consumer/filesystem_verifier_action_unittest.cc \
+    payload_consumer/postinstall_runner_action_unittest.cc \
+    payload_consumer/xz_extent_writer_unittest.cc \
+    payload_generator/ab_generator_unittest.cc \
+    payload_generator/blob_file_writer_unittest.cc \
+    payload_generator/block_mapping_unittest.cc \
+    payload_generator/cycle_breaker_unittest.cc \
+    payload_generator/delta_diff_utils_unittest.cc \
+    payload_generator/ext2_filesystem_unittest.cc \
+    payload_generator/extent_ranges_unittest.cc \
+    payload_generator/extent_utils_unittest.cc \
+    payload_generator/fake_filesystem.cc \
+    payload_generator/full_update_generator_unittest.cc \
+    payload_generator/graph_utils_unittest.cc \
+    payload_generator/inplace_generator_unittest.cc \
+    payload_generator/payload_file_unittest.cc \
+    payload_generator/payload_generation_config_unittest.cc \
+    payload_generator/payload_signer_unittest.cc \
+    payload_generator/tarjan_unittest.cc \
+    payload_generator/topological_sort_unittest.cc \
+    payload_generator/zip_unittest.cc \
+    testrunner.cc
+ifeq ($(local_use_omaha),1)
+LOCAL_C_INCLUDES += \
+    $(ue_libupdate_engine_exported_c_includes)
+LOCAL_STATIC_LIBRARIES += \
+    libupdate_engine \
+    $(ue_libupdate_engine_exported_static_libraries:-host=)
+LOCAL_SHARED_LIBRARIES += \
+    $(ue_libupdate_engine_exported_shared_libraries:-host=)
+LOCAL_SRC_FILES += \
+    common_service_unittest.cc \
+    fake_system_state.cc \
+    metrics_utils_unittest.cc
+ifneq ($(local_use_nestlabs),1)
+LOCAL_SRC_FILES += \
+    omaha_request_action_unittest.cc \
+    omaha_request_params_unittest.cc \
+    omaha_response_handler_action_unittest.cc \
+    omaha_utils_unittest.cc
+endif # local_use_nestlabs != 1
+LOCAL_SRC_FILES += \
+    p2p_manager_unittest.cc \
+    payload_consumer/download_action_unittest.cc \
+    payload_state_unittest.cc \
+    update_manager/boxed_value_unittest.cc \
+    update_manager/chromeos_policy_unittest.cc \
+    update_manager/evaluation_context_unittest.cc \
+    update_manager/generic_variables_unittest.cc \
+    update_manager/prng_unittest.cc \
+    update_manager/real_device_policy_provider_unittest.cc \
+    update_manager/real_random_provider_unittest.cc \
+    update_manager/real_system_provider_unittest.cc \
+    update_manager/real_time_provider_unittest.cc \
+    update_manager/real_updater_provider_unittest.cc \
+    update_manager/umtest_utils.cc \
+    update_manager/update_manager_unittest.cc \
+    update_manager/variable_unittest.cc
+else  # local_use_omaha == 1
+LOCAL_STATIC_LIBRARIES += \
+    libupdate_engine_android \
+    $(ue_libupdate_engine_android_exported_static_libraries:-host=)
+LOCAL_SHARED_LIBRARIES += \
+    $(ue_libupdate_engine_android_exported_shared_libraries:-host=)
+endif  # local_use_omaha == 1
+ifeq ($(local_use_shill),1)
+LOCAL_SRC_FILES += \
+    connection_manager_unittest.cc \
+    fake_shill_proxy.cc \
+    update_manager/real_shill_provider_unittest.cc
+endif  # local_use_shill == 1
+ifeq ($(local_use_libcros),1)
+LOCAL_SRC_FILES += \
+    chrome_browser_proxy_resolver_unittest.cc
+endif  # local_use_libcros == 1
+include $(BUILD_NATIVE_TEST)
+
+# Weave schema files
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := updater.json
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/weaved/traits
+LOCAL_SRC_FILES := weaved/traits/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+# Update payload signing public key.
+# ========================================================
+ifneq ($(local_use_nestlabs),1)
+ifdef BRILLO
+include $(CLEAR_VARS)
+LOCAL_MODULE := brillo-update-payload-key
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/update_engine
+LOCAL_MODULE_STEM := update-payload-key.pub.pem
+LOCAL_SRC_FILES := update_payload_key/brillo-update-payload-key.pub.pem
+LOCAL_BUILT_MODULE_STEM := update_payload_key/brillo-update-payload-key.pub.pem
+include $(BUILD_PREBUILT)
+endif  # BRILLO
+endif # local_use_nestlabs != 1
+
+# Brillo update payload generation script
+# ========================================================
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := scripts/brillo_update_payload
+LOCAL_MODULE := brillo_update_payload
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := \
+    delta_generator \
+    shflags
+include $(BUILD_PREBUILT)
+endif  # HOST_OS == linux
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := scripts/payload_apply.sh
+LOCAL_MODULE := payload_apply.sh
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := \
+	update_engine_sideload
+include $(BUILD_PREBUILT)
diff --git a/update_engine/MODULE_LICENSE_APACHE2 b/update_engine/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/update_engine/MODULE_LICENSE_APACHE2
diff --git a/update_engine/NOTICE b/update_engine/NOTICE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/update_engine/NOTICE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/update_engine/OWNERS b/update_engine/OWNERS
new file mode 100644
index 0000000..fafbecc
--- /dev/null
+++ b/update_engine/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+deymo@chromium.org
+garnold@chromium.org
+zeuthen@chromium.org
diff --git a/update_engine/PRESUBMIT.cfg b/update_engine/PRESUBMIT.cfg
new file mode 100644
index 0000000..087dfa3
--- /dev/null
+++ b/update_engine/PRESUBMIT.cfg
@@ -0,0 +1,3 @@
+[Hook Overrides]
+cros_license_check: false
+aosp_license_check: true
diff --git a/update_engine/UpdateEngine.conf b/update_engine/UpdateEngine.conf
new file mode 100644
index 0000000..58cca09
--- /dev/null
+++ b/update_engine/UpdateEngine.conf
@@ -0,0 +1,76 @@
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+  "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+  <policy user="root">
+    <allow own="org.chromium.UpdateEngine" />
+    <allow send_destination="org.chromium.UpdateEngine" />
+  </policy>
+  <policy user="chronos">
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="AttemptUpdate"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="AttemptUpdateWithFlags"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="AttemptRollback"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="CanRollback"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetRollbackPartition"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="ResetStatus"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetStatus"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="RebootIfNeeded"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="SetChannel"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetChannel"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="SetCohortHint"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetCohortHint"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="SetP2PUpdatePermission"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetP2PUpdatePermission"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="SetUpdateOverCellularPermission"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetUpdateOverCellularPermission"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetDurationSinceUpdate"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetPrevVersion"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetLastAttemptError"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetEolStatus"/>
+    <allow send_interface="org.chromium.UpdateEngineLibcrosProxyResolvedInterface" />
+  </policy>
+  <policy user="power">
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetStatus"/>
+  </policy>
+</busconfig>
diff --git a/update_engine/WATCHLISTS b/update_engine/WATCHLISTS
new file mode 100644
index 0000000..bcce0de
--- /dev/null
+++ b/update_engine/WATCHLISTS
@@ -0,0 +1,14 @@
+# See http://dev.chromium.org/developers/contributing-code/watchlists for
+# a description of this file's format.
+# Please keep these keys in alphabetical order.
+
+{
+  'WATCHLIST_DEFINITIONS': {
+    'all': {
+      'filepath': '.',
+    },
+  },
+  'WATCHLISTS': {
+    'all': ['adlr@chromium.org', 'petkov@chromium.org']
+  },
+}
diff --git a/update_engine/binder_bindings/android/brillo/IUpdateEngine.aidl b/update_engine/binder_bindings/android/brillo/IUpdateEngine.aidl
new file mode 100644
index 0000000..b1a1b4f
--- /dev/null
+++ b/update_engine/binder_bindings/android/brillo/IUpdateEngine.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package android.brillo;
+
+import android.brillo.IUpdateEngineStatusCallback;
+import android.brillo.ParcelableUpdateEngineStatus;
+
+interface IUpdateEngine {
+  void AttemptUpdate(in String app_version, in String omaha_url, in int flags);
+  void AttemptRollback(in boolean powerwash);
+  boolean CanRollback();
+  void ResetStatus();
+  ParcelableUpdateEngineStatus GetStatus();
+  void RebootIfNeeded();
+  void SetChannel(in String target_channel, in boolean powewash);
+  String GetChannel(in boolean get_current_channel);
+  void SetCohortHint(in String cohort_hint);
+  String GetCohortHint();
+  void SetP2PUpdatePermission(in boolean enabled);
+  boolean GetP2PUpdatePermission();
+  void SetUpdateOverCellularPermission(in boolean enabled);
+  boolean GetUpdateOverCellularPermission();
+  long GetDurationSinceUpdate();
+  String GetPrevVersion();
+  String GetRollbackPartition();
+  void RegisterStatusCallback(in IUpdateEngineStatusCallback callback);
+  int GetLastAttemptError();
+  int GetEolStatus();
+}
diff --git a/update_engine/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl b/update_engine/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl
new file mode 100644
index 0000000..42b6438
--- /dev/null
+++ b/update_engine/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+package android.brillo;
+
+interface IUpdateEngineStatusCallback {
+  oneway
+  void HandleStatusUpdate(in long last_checked_time, in double progress,
+      in String current_operation, in String new_version, in long new_size);
+}
diff --git a/update_engine/binder_bindings/android/brillo/ParcelableUpdateEngineStatus.aidl b/update_engine/binder_bindings/android/brillo/ParcelableUpdateEngineStatus.aidl
new file mode 100644
index 0000000..fc10505
--- /dev/null
+++ b/update_engine/binder_bindings/android/brillo/ParcelableUpdateEngineStatus.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+package android.brillo;
+
+parcelable ParcelableUpdateEngineStatus cpp_header
+    "update_engine/parcelable_update_engine_status.h";
diff --git a/update_engine/binder_bindings/android/os/IUpdateEngine.aidl b/update_engine/binder_bindings/android/os/IUpdateEngine.aidl
new file mode 100644
index 0000000..67f828a
--- /dev/null
+++ b/update_engine/binder_bindings/android/os/IUpdateEngine.aidl
@@ -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.
+ */
+
+package android.os;
+
+import android.os.IUpdateEngineCallback;
+
+/** @hide */
+interface IUpdateEngine {
+  /** @hide */
+  void applyPayload(String url,
+                    in long payload_offset,
+                    in long payload_size,
+                    in String[] headerKeyValuePairs);
+  /** @hide */
+  boolean bind(IUpdateEngineCallback callback);
+  /** @hide */
+  void suspend();
+  /** @hide */
+  void resume();
+  /** @hide */
+  void cancel();
+  /** @hide */
+  void resetStatus();
+}
diff --git a/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl b/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl
new file mode 100644
index 0000000..ee15c8b
--- /dev/null
+++ b/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package android.os;
+
+/** @hide */
+oneway interface IUpdateEngineCallback {
+  /** @hide */
+  void onStatusUpdate(int status_code, float percentage);
+  /** @hide */
+  void onPayloadApplicationComplete(int error_code);
+}
diff --git a/update_engine/binder_service_android.cc b/update_engine/binder_service_android.cc
new file mode 100644
index 0000000..872f64c
--- /dev/null
+++ b/update_engine/binder_service_android.cc
@@ -0,0 +1,146 @@
+//
+// 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/binder_service_android.h"
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <binderwrapper/binder_wrapper.h>
+#include <brillo/errors/error.h>
+#include <utils/String8.h>
+
+using android::binder::Status;
+using android::os::IUpdateEngineCallback;
+
+namespace {
+Status ErrorPtrToStatus(const brillo::ErrorPtr& error) {
+  return Status::fromServiceSpecificError(
+      1, android::String8{error->GetMessage().c_str()});
+}
+}  // namespace
+
+namespace chromeos_update_engine {
+
+BinderUpdateEngineAndroidService::BinderUpdateEngineAndroidService(
+    ServiceDelegateAndroidInterface* service_delegate)
+    : service_delegate_(service_delegate) {
+}
+
+void BinderUpdateEngineAndroidService::SendStatusUpdate(
+    int64_t /* last_checked_time */,
+    double progress,
+    update_engine::UpdateStatus status,
+    const std::string& /* new_version  */,
+    int64_t /* new_size */) {
+  last_status_ = static_cast<int>(status);
+  last_progress_ = progress;
+  for (auto& callback : callbacks_) {
+    callback->onStatusUpdate(last_status_, last_progress_);
+  }
+}
+
+void BinderUpdateEngineAndroidService::SendPayloadApplicationComplete(
+    ErrorCode error_code) {
+  for (auto& callback : callbacks_) {
+    callback->onPayloadApplicationComplete(static_cast<int>(error_code));
+  }
+}
+
+Status BinderUpdateEngineAndroidService::bind(
+    const android::sp<IUpdateEngineCallback>& callback, bool* return_value) {
+  callbacks_.emplace_back(callback);
+
+  auto binder_wrapper = android::BinderWrapper::Get();
+  binder_wrapper->RegisterForDeathNotifications(
+      IUpdateEngineCallback::asBinder(callback),
+      base::Bind(&BinderUpdateEngineAndroidService::UnbindCallback,
+                 base::Unretained(this),
+                 base::Unretained(callback.get())));
+
+  // Send an status update on connection (except when no update sent so far),
+  // since the status update is oneway and we don't need to wait for the
+  // response.
+  if (last_status_ != -1)
+    callback->onStatusUpdate(last_status_, last_progress_);
+
+  *return_value = true;
+  return Status::ok();
+}
+
+Status BinderUpdateEngineAndroidService::applyPayload(
+    const android::String16& url,
+    int64_t payload_offset,
+    int64_t payload_size,
+    const std::vector<android::String16>& header_kv_pairs) {
+  const std::string payload_url{android::String8{url}.string()};
+  std::vector<std::string> str_headers;
+  str_headers.reserve(header_kv_pairs.size());
+  for (const auto& header : header_kv_pairs) {
+    str_headers.emplace_back(android::String8{header}.string());
+  }
+
+  brillo::ErrorPtr error;
+  if (!service_delegate_->ApplyPayload(
+          payload_url, payload_offset, payload_size, str_headers, &error)) {
+    return ErrorPtrToStatus(error);
+  }
+  return Status::ok();
+}
+
+Status BinderUpdateEngineAndroidService::suspend() {
+  brillo::ErrorPtr error;
+  if (!service_delegate_->SuspendUpdate(&error))
+    return ErrorPtrToStatus(error);
+  return Status::ok();
+}
+
+Status BinderUpdateEngineAndroidService::resume() {
+  brillo::ErrorPtr error;
+  if (!service_delegate_->ResumeUpdate(&error))
+    return ErrorPtrToStatus(error);
+  return Status::ok();
+}
+
+Status BinderUpdateEngineAndroidService::cancel() {
+  brillo::ErrorPtr error;
+  if (!service_delegate_->CancelUpdate(&error))
+    return ErrorPtrToStatus(error);
+  return Status::ok();
+}
+
+Status BinderUpdateEngineAndroidService::resetStatus() {
+  brillo::ErrorPtr error;
+  if (!service_delegate_->ResetStatus(&error))
+    return ErrorPtrToStatus(error);
+  return Status::ok();
+}
+
+void BinderUpdateEngineAndroidService::UnbindCallback(
+    IUpdateEngineCallback* callback) {
+  auto it =
+      std::find_if(callbacks_.begin(),
+                   callbacks_.end(),
+                   [&callback](const android::sp<IUpdateEngineCallback>& elem) {
+                     return elem.get() == callback;
+                   });
+  if (it == callbacks_.end()) {
+    LOG(ERROR) << "Got death notification for unknown callback.";
+    return;
+  }
+  callbacks_.erase(it);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/binder_service_android.h b/update_engine/binder_service_android.h
new file mode 100644
index 0000000..3fb38bc
--- /dev/null
+++ b/update_engine/binder_service_android.h
@@ -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.
+//
+
+#ifndef UPDATE_ENGINE_BINDER_SERVICE_ANDROID_H_
+#define UPDATE_ENGINE_BINDER_SERVICE_ANDROID_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <utils/Errors.h>
+#include <utils/String16.h>
+#include <utils/StrongPointer.h>
+
+#include "android/os/BnUpdateEngine.h"
+#include "android/os/IUpdateEngineCallback.h"
+#include "update_engine/service_delegate_android_interface.h"
+#include "update_engine/service_observer_interface.h"
+
+namespace chromeos_update_engine {
+
+class BinderUpdateEngineAndroidService : public android::os::BnUpdateEngine,
+                                         public ServiceObserverInterface {
+ public:
+  explicit BinderUpdateEngineAndroidService(
+      ServiceDelegateAndroidInterface* service_delegate);
+  ~BinderUpdateEngineAndroidService() override = default;
+
+  const char* ServiceName() const {
+    return "android.os.UpdateEngineService";
+  }
+
+  // ServiceObserverInterface overrides.
+  void SendStatusUpdate(int64_t last_checked_time,
+                        double progress,
+                        update_engine::UpdateStatus status,
+                        const std::string& new_version,
+                        int64_t new_size) override;
+  void SendPayloadApplicationComplete(ErrorCode error_code) override;
+
+  // Channel tracking changes are ignored.
+  void SendChannelChangeUpdate(const std::string& tracking_channel) override {}
+
+  // android::os::BnUpdateEngine overrides.
+  android::binder::Status applyPayload(
+      const android::String16& url,
+      int64_t payload_offset,
+      int64_t payload_size,
+      const std::vector<android::String16>& header_kv_pairs) override;
+  android::binder::Status bind(
+      const android::sp<android::os::IUpdateEngineCallback>& callback,
+      bool* return_value) override;
+  android::binder::Status suspend() override;
+  android::binder::Status resume() override;
+  android::binder::Status cancel() override;
+  android::binder::Status resetStatus() override;
+
+ private:
+  // Remove the passed |callback| from the list of registered callbacks. Called
+  // whenever the callback object is destroyed.
+  void UnbindCallback(android::os::IUpdateEngineCallback* callback);
+
+  // List of currently bound callbacks.
+  std::vector<android::sp<android::os::IUpdateEngineCallback>> callbacks_;
+
+  // Cached copy of the last status update sent. Used to send an initial
+  // notification when bind() is called from the client.
+  int last_status_{-1};
+  double last_progress_{0.0};
+
+  ServiceDelegateAndroidInterface* service_delegate_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_BINDER_SERVICE_ANDROID_H_
diff --git a/update_engine/binder_service_brillo.cc b/update_engine/binder_service_brillo.cc
new file mode 100644
index 0000000..5e74159
--- /dev/null
+++ b/update_engine/binder_service_brillo.cc
@@ -0,0 +1,246 @@
+//
+// 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/binder_service_brillo.h"
+
+#include <base/bind.h>
+
+#include <binderwrapper/binder_wrapper.h>
+
+#include <utils/String16.h>
+#include <utils/StrongPointer.h>
+
+#include "update_engine/update_status_utils.h"
+
+using android::String16;
+using android::String8;
+using android::binder::Status;
+using android::brillo::IUpdateEngineStatusCallback;
+using android::brillo::ParcelableUpdateEngineStatus;
+using android::sp;
+using brillo::ErrorPtr;
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+string NormalString(const String16& in) {
+  return string{String8{in}.string()};
+}
+
+Status ToStatus(ErrorPtr* error) {
+  return Status::fromServiceSpecificError(
+      1, String8{error->get()->GetMessage().c_str()});
+}
+}  // namespace
+
+template <typename... Parameters, typename... Arguments>
+Status BinderUpdateEngineBrilloService::CallCommonHandler(
+    bool (UpdateEngineService::*Handler)(ErrorPtr*, Parameters...),
+    Arguments... arguments) {
+  ErrorPtr error;
+  if (((common_.get())->*Handler)(&error, arguments...))
+    return Status::ok();
+  return ToStatus(&error);
+}
+
+Status BinderUpdateEngineBrilloService::AttemptUpdate(
+    const String16& app_version, const String16& omaha_url, int flags) {
+  return CallCommonHandler(&UpdateEngineService::AttemptUpdate,
+                           NormalString(app_version),
+                           NormalString(omaha_url),
+                           flags);
+}
+
+Status BinderUpdateEngineBrilloService::AttemptRollback(bool powerwash) {
+  return CallCommonHandler(&UpdateEngineService::AttemptRollback, powerwash);
+}
+
+Status BinderUpdateEngineBrilloService::CanRollback(bool* out_can_rollback) {
+  return CallCommonHandler(&UpdateEngineService::CanRollback, out_can_rollback);
+}
+
+Status BinderUpdateEngineBrilloService::ResetStatus() {
+  return CallCommonHandler(&UpdateEngineService::ResetStatus);
+}
+
+Status BinderUpdateEngineBrilloService::GetStatus(
+    ParcelableUpdateEngineStatus* status) {
+  string current_op;
+  string new_version;
+
+  auto ret = CallCommonHandler(&UpdateEngineService::GetStatus,
+                               &status->last_checked_time_,
+                               &status->progress_,
+                               &current_op,
+                               &new_version,
+                               &status->new_size_);
+
+  if (ret.isOk()) {
+    status->current_operation_ = String16{current_op.c_str()};
+    status->new_version_ = String16{new_version.c_str()};
+  }
+
+  return ret;
+}
+
+Status BinderUpdateEngineBrilloService::RebootIfNeeded() {
+  return CallCommonHandler(&UpdateEngineService::RebootIfNeeded);
+}
+
+Status BinderUpdateEngineBrilloService::SetChannel(
+    const String16& target_channel, bool powerwash) {
+  return CallCommonHandler(&UpdateEngineService::SetChannel,
+                           NormalString(target_channel),
+                           powerwash);
+}
+
+Status BinderUpdateEngineBrilloService::GetChannel(bool get_current_channel,
+                                                   String16* out_channel) {
+  string channel_string;
+  auto ret = CallCommonHandler(
+      &UpdateEngineService::GetChannel, get_current_channel, &channel_string);
+
+  *out_channel = String16(channel_string.c_str());
+  return ret;
+}
+
+Status BinderUpdateEngineBrilloService::SetCohortHint(
+    const String16& in_cohort_hint) {
+  return CallCommonHandler(&UpdateEngineService::SetCohortHint,
+                           NormalString(in_cohort_hint));
+}
+
+Status BinderUpdateEngineBrilloService::GetCohortHint(
+    String16* out_cohort_hint) {
+  string cohort_hint;
+  auto ret =
+      CallCommonHandler(&UpdateEngineService::GetCohortHint, &cohort_hint);
+
+  *out_cohort_hint = String16(cohort_hint.c_str());
+  return ret;
+}
+
+Status BinderUpdateEngineBrilloService::SetP2PUpdatePermission(bool enabled) {
+  return CallCommonHandler(&UpdateEngineService::SetP2PUpdatePermission,
+                           enabled);
+}
+
+Status BinderUpdateEngineBrilloService::GetP2PUpdatePermission(
+    bool* out_p2p_permission) {
+  return CallCommonHandler(&UpdateEngineService::GetP2PUpdatePermission,
+                           out_p2p_permission);
+}
+
+Status BinderUpdateEngineBrilloService::SetUpdateOverCellularPermission(
+    bool enabled) {
+  return CallCommonHandler(
+      &UpdateEngineService::SetUpdateOverCellularPermission, enabled);
+}
+
+Status BinderUpdateEngineBrilloService::GetUpdateOverCellularPermission(
+    bool* out_cellular_permission) {
+  return CallCommonHandler(
+      &UpdateEngineService::GetUpdateOverCellularPermission,
+      out_cellular_permission);
+}
+
+Status BinderUpdateEngineBrilloService::GetDurationSinceUpdate(
+    int64_t* out_duration) {
+  return CallCommonHandler(&UpdateEngineService::GetDurationSinceUpdate,
+                           out_duration);
+}
+
+Status BinderUpdateEngineBrilloService::GetPrevVersion(
+    String16* out_prev_version) {
+  string version_string;
+  auto ret =
+      CallCommonHandler(&UpdateEngineService::GetPrevVersion, &version_string);
+
+  *out_prev_version = String16(version_string.c_str());
+  return ret;
+}
+
+Status BinderUpdateEngineBrilloService::GetRollbackPartition(
+    String16* out_rollback_partition) {
+  string partition_string;
+  auto ret = CallCommonHandler(&UpdateEngineService::GetRollbackPartition,
+                               &partition_string);
+
+  if (ret.isOk()) {
+    *out_rollback_partition = String16(partition_string.c_str());
+  }
+
+  return ret;
+}
+
+Status BinderUpdateEngineBrilloService::RegisterStatusCallback(
+    const sp<IUpdateEngineStatusCallback>& callback) {
+  callbacks_.emplace_back(callback);
+
+  auto binder_wrapper = android::BinderWrapper::Get();
+
+  binder_wrapper->RegisterForDeathNotifications(
+      IUpdateEngineStatusCallback::asBinder(callback),
+      base::Bind(&BinderUpdateEngineBrilloService::UnregisterStatusCallback,
+                 base::Unretained(this),
+                 base::Unretained(callback.get())));
+
+  return Status::ok();
+}
+
+Status BinderUpdateEngineBrilloService::GetLastAttemptError(
+    int* out_last_attempt_error) {
+  return CallCommonHandler(&UpdateEngineService::GetLastAttemptError,
+                           out_last_attempt_error);
+}
+
+Status BinderUpdateEngineBrilloService::GetEolStatus(int* out_eol_status) {
+  return CallCommonHandler(&UpdateEngineService::GetEolStatus, out_eol_status);
+}
+
+void BinderUpdateEngineBrilloService::UnregisterStatusCallback(
+    IUpdateEngineStatusCallback* callback) {
+  auto it = callbacks_.begin();
+  while (it != callbacks_.end() && it->get() != callback)
+    it++;
+
+  if (it == callbacks_.end()) {
+    LOG(ERROR) << "Got death notification for unknown callback.";
+    return;
+  }
+
+  LOG(INFO) << "Erasing orphan callback";
+  callbacks_.erase(it);
+}
+
+void BinderUpdateEngineBrilloService::SendStatusUpdate(
+    int64_t last_checked_time,
+    double progress,
+    update_engine::UpdateStatus status,
+    const string& new_version,
+    int64_t new_size) {
+  const string str_status = UpdateStatusToString(status);
+  for (auto& callback : callbacks_) {
+    callback->HandleStatusUpdate(last_checked_time,
+                                 progress,
+                                 String16{str_status.c_str()},
+                                 String16{new_version.c_str()},
+                                 new_size);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/binder_service_brillo.h b/update_engine/binder_service_brillo.h
new file mode 100644
index 0000000..fad0f8c
--- /dev/null
+++ b/update_engine/binder_service_brillo.h
@@ -0,0 +1,115 @@
+//
+// 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_BINDER_SERVICE_BRILLO_H_
+#define UPDATE_ENGINE_BINDER_SERVICE_BRILLO_H_
+
+#include <utils/Errors.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <utils/RefBase.h>
+
+#include "update_engine/common_service.h"
+#include "update_engine/parcelable_update_engine_status.h"
+#include "update_engine/service_observer_interface.h"
+
+#include "android/brillo/BnUpdateEngine.h"
+#include "android/brillo/IUpdateEngineStatusCallback.h"
+
+namespace chromeos_update_engine {
+
+class BinderUpdateEngineBrilloService : public android::brillo::BnUpdateEngine,
+                                        public ServiceObserverInterface {
+ public:
+  explicit BinderUpdateEngineBrilloService(SystemState* system_state)
+      : common_(new UpdateEngineService(system_state)) {}
+  virtual ~BinderUpdateEngineBrilloService() = default;
+
+  const char* ServiceName() const {
+    return "android.brillo.UpdateEngineService";
+  }
+
+  // ServiceObserverInterface overrides.
+  void SendStatusUpdate(int64_t last_checked_time,
+                        double progress,
+                        update_engine::UpdateStatus status,
+                        const std::string& new_version,
+                        int64_t new_size) override;
+  void SendPayloadApplicationComplete(ErrorCode error_code) override {}
+  // Channel tracking changes are ignored.
+  void SendChannelChangeUpdate(const std::string& tracking_channel) override {}
+
+  // android::brillo::BnUpdateEngine overrides.
+  android::binder::Status AttemptUpdate(const android::String16& app_version,
+                                        const android::String16& omaha_url,
+                                        int flags) override;
+  android::binder::Status AttemptRollback(bool powerwash) override;
+  android::binder::Status CanRollback(bool* out_can_rollback) override;
+  android::binder::Status ResetStatus() override;
+  android::binder::Status GetStatus(
+      android::brillo::ParcelableUpdateEngineStatus* status);
+  android::binder::Status RebootIfNeeded() override;
+  android::binder::Status SetChannel(const android::String16& target_channel,
+                                     bool powerwash) override;
+  android::binder::Status GetChannel(bool get_current_channel,
+                                     android::String16* out_channel) override;
+  android::binder::Status SetCohortHint(
+      const android::String16& cohort_hint) override;
+  android::binder::Status GetCohortHint(
+      android::String16* out_cohort_hint) override;
+  android::binder::Status SetP2PUpdatePermission(bool enabled) override;
+  android::binder::Status GetP2PUpdatePermission(
+      bool* out_p2p_permission) override;
+  android::binder::Status SetUpdateOverCellularPermission(
+      bool enabled) override;
+  android::binder::Status GetUpdateOverCellularPermission(
+      bool* out_cellular_permission) override;
+  android::binder::Status GetDurationSinceUpdate(
+      int64_t* out_duration) override;
+  android::binder::Status GetPrevVersion(
+      android::String16* out_prev_version) override;
+  android::binder::Status GetRollbackPartition(
+      android::String16* out_rollback_partition) override;
+  android::binder::Status RegisterStatusCallback(
+      const android::sp<android::brillo::IUpdateEngineStatusCallback>& callback)
+      override;
+  android::binder::Status GetLastAttemptError(
+      int* out_last_attempt_error) override;
+  android::binder::Status GetEolStatus(int* out_eol_status) override;
+
+ private:
+  // Generic function for dispatching to the common service.
+  template <typename... Parameters, typename... Arguments>
+  android::binder::Status CallCommonHandler(
+      bool (UpdateEngineService::*Handler)(brillo::ErrorPtr*, Parameters...),
+      Arguments... arguments);
+
+  // To be used as a death notification handler only.
+  void UnregisterStatusCallback(
+      android::brillo::IUpdateEngineStatusCallback* callback);
+
+  std::unique_ptr<UpdateEngineService> common_;
+
+  std::vector<android::sp<android::brillo::IUpdateEngineStatusCallback>>
+      callbacks_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_BINDER_SERVICE_BRILLO_H_
diff --git a/update_engine/boot_control_android.cc b/update_engine/boot_control_android.cc
new file mode 100644
index 0000000..b3b7630
--- /dev/null
+++ b/update_engine/boot_control_android.cc
@@ -0,0 +1,164 @@
+//
+// 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/boot_control_android.h"
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/utils_android.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace boot_control {
+
+// Factory defined in boot_control.h.
+std::unique_ptr<BootControlInterface> CreateBootControl() {
+  std::unique_ptr<BootControlAndroid> boot_control(new BootControlAndroid());
+  if (!boot_control->Init()) {
+    return nullptr;
+  }
+  return std::move(boot_control);
+}
+
+}  // namespace boot_control
+
+bool BootControlAndroid::Init() {
+  const hw_module_t* hw_module;
+  int ret;
+
+  ret = hw_get_module(BOOT_CONTROL_HARDWARE_MODULE_ID, &hw_module);
+  if (ret != 0) {
+    LOG(ERROR) << "Error loading boot_control HAL implementation.";
+    return false;
+  }
+
+  module_ = reinterpret_cast<boot_control_module_t*>(const_cast<hw_module_t*>(hw_module));
+  module_->init(module_);
+
+  LOG(INFO) << "Loaded boot_control HAL "
+            << "'" << hw_module->name << "' "
+            << "version " << (hw_module->module_api_version>>8) << "."
+            << (hw_module->module_api_version&0xff) << " "
+            << "authored by '" << hw_module->author << "'.";
+  return true;
+}
+
+unsigned int BootControlAndroid::GetNumSlots() const {
+  return module_->getNumberSlots(module_);
+}
+
+BootControlInterface::Slot BootControlAndroid::GetCurrentSlot() const {
+  return module_->getCurrentSlot(module_);
+}
+
+bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
+                                            Slot slot,
+                                            string* device) const {
+  // We can't use fs_mgr to look up |partition_name| because fstab
+  // doesn't list every slot partition (it uses the slotselect option
+  // to mask the suffix).
+  //
+  // We can however assume that there's an entry for the /misc mount
+  // point and use that to get the device file for the misc
+  // partition. This helps us locate the disk that |partition_name|
+  // resides on. From there we'll assume that a by-name scheme is used
+  // so we can just replace the trailing "misc" by the given
+  // |partition_name| and suffix corresponding to |slot|, e.g.
+  //
+  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
+  //
+  // If needed, it's possible to relax the by-name assumption in the
+  // future by trawling /sys/block looking for the appropriate sibling
+  // of misc and then finding an entry in /dev matching the sysfs
+  // entry.
+
+  base::FilePath misc_device;
+  if (!utils::DeviceForMountPoint("/misc", &misc_device))
+    return false;
+
+  if (!utils::IsSymlink(misc_device.value().c_str())) {
+    LOG(ERROR) << "Device file " << misc_device.value() << " for /misc "
+               << "is not a symlink.";
+    return false;
+  }
+
+  const char* suffix = module_->getSuffix(module_, slot);
+  if (suffix == nullptr) {
+    LOG(ERROR) << "boot_control impl returned no suffix for slot "
+               << SlotName(slot);
+    return false;
+  }
+
+  base::FilePath path = misc_device.DirName().Append(partition_name + suffix);
+  if (!base::PathExists(path)) {
+    LOG(ERROR) << "Device file " << path.value() << " does not exist.";
+    return false;
+  }
+
+  *device = path.value();
+  return true;
+}
+
+bool BootControlAndroid::IsSlotBootable(Slot slot) const {
+  int ret = module_->isSlotBootable(module_, slot);
+  if (ret < 0) {
+    LOG(ERROR) << "Unable to determine if slot " << SlotName(slot)
+               << " is bootable: " << strerror(-ret);
+    return false;
+  }
+  return ret == 1;
+}
+
+bool BootControlAndroid::MarkSlotUnbootable(Slot slot) {
+  int ret = module_->setSlotAsUnbootable(module_, slot);
+  if (ret < 0) {
+    LOG(ERROR) << "Unable to mark slot " << SlotName(slot)
+               << " as bootable: " << strerror(-ret);
+    return false;
+  }
+  return ret == 0;
+}
+
+bool BootControlAndroid::SetActiveBootSlot(Slot slot) {
+  int ret = module_->setActiveBootSlot(module_, slot);
+  if (ret < 0) {
+    LOG(ERROR) << "Unable to set the active slot to slot " << SlotName(slot)
+               << ": " << strerror(-ret);
+  }
+  return ret == 0;
+}
+
+bool BootControlAndroid::MarkBootSuccessfulAsync(
+    base::Callback<void(bool)> callback) {
+  int ret = module_->markBootSuccessful(module_);
+  if (ret < 0) {
+    LOG(ERROR) << "Unable to mark boot successful: " << strerror(-ret);
+  }
+  return brillo::MessageLoop::current()->PostTask(
+             FROM_HERE, base::Bind(callback, ret == 0)) !=
+         brillo::MessageLoop::kTaskIdNull;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/boot_control_android.h b/update_engine/boot_control_android.h
new file mode 100644
index 0000000..a5a6255
--- /dev/null
+++ b/update_engine/boot_control_android.h
@@ -0,0 +1,61 @@
+//
+// 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_BOOT_CONTROL_ANDROID_H_
+#define UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_
+
+#include <string>
+
+#include <hardware/boot_control.h>
+#include <hardware/hardware.h>
+
+#include "update_engine/common/boot_control.h"
+
+namespace chromeos_update_engine {
+
+// The Android implementation of the BootControlInterface. This implementation
+// uses the libhardware's boot_control HAL to access the bootloader.
+class BootControlAndroid : public BootControlInterface {
+ public:
+  BootControlAndroid() = default;
+  ~BootControlAndroid() = default;
+
+  // Load boot_control HAL implementation using libhardware and
+  // initializes it. Returns false if an error occurred.
+  bool Init();
+
+  // BootControlInterface overrides.
+  unsigned int GetNumSlots() const override;
+  BootControlInterface::Slot GetCurrentSlot() 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;
+
+ private:
+  // NOTE: There is no way to release/unload HAL implementations so
+  // this is essentially leaked on object destruction.
+  boot_control_module_t* module_;
+
+  DISALLOW_COPY_AND_ASSIGN(BootControlAndroid);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_
diff --git a/update_engine/boot_control_chromeos.cc b/update_engine/boot_control_chromeos.cc
new file mode 100644
index 0000000..e9ad698
--- /dev/null
+++ b/update_engine/boot_control_chromeos.cc
@@ -0,0 +1,304 @@
+//
+// 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/boot_control_chromeos.h"
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <rootdev/rootdev.h>
+
+extern "C" {
+#include <vboot/vboot_host.h>
+}
+
+#include "update_engine/common/boot_control.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace {
+
+const char* kChromeOSPartitionNameKernel = "kernel";
+const char* kChromeOSPartitionNameRoot = "root";
+const char* kAndroidPartitionNameKernel = "boot";
+const char* kAndroidPartitionNameRoot = "system";
+
+// 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;
+  }
+
+  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::GetPartitionDevice(const string& partition_name,
+                                             unsigned int slot,
+                                             string* device) const {
+  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;
+  return true;
+}
+
+bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
+  int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+  if (partition_num < 0)
+    return false;
+
+  CgptAddParams params;
+  memset(&params, '\0', sizeof(params));
+  params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+  params.partition = partition_num;
+
+  int retval = CgptGetPartitionDetails(&params);
+  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(&params, 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(&params);
+  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;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/boot_control_chromeos.h b/update_engine/boot_control_chromeos.h
new file mode 100644
index 0000000..a1d57fe
--- /dev/null
+++ b/update_engine/boot_control_chromeos.h
@@ -0,0 +1,85 @@
+//
+// 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_BOOT_CONTROL_CHROMEOS_H_
+#define UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_
+
+#include <string>
+
+#include <base/callback.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/common/boot_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,
+                          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;
+
+ private:
+  friend class BootControlChromeOSTest;
+  FRIEND_TEST(BootControlChromeOSTest, SysfsBlockDeviceTest);
+  FRIEND_TEST(BootControlChromeOSTest, GetPartitionNumberTest);
+
+  // 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;
+
+  // 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_;
+
+  DISALLOW_COPY_AND_ASSIGN(BootControlChromeOS);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_
diff --git a/update_engine/boot_control_chromeos_unittest.cc b/update_engine/boot_control_chromeos_unittest.cc
new file mode 100644
index 0000000..6a60009
--- /dev/null
+++ b/update_engine/boot_control_chromeos_unittest.cc
@@ -0,0 +1,70 @@
+//
+// 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/boot_control_chromeos.h"
+
+#include <gtest/gtest.h>
+
+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));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/boot_control_recovery_stub.cc b/update_engine/boot_control_recovery_stub.cc
new file mode 100644
index 0000000..129c5d0
--- /dev/null
+++ b/update_engine/boot_control_recovery_stub.cc
@@ -0,0 +1,21 @@
+//
+// 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 <hardware/hardware.h>
+
+hw_module_t HAL_MODULE_INFO_SYM = {
+  .id = "stub",
+};
diff --git a/update_engine/certificate_checker.cc b/update_engine/certificate_checker.cc
new file mode 100644
index 0000000..6e886e7
--- /dev/null
+++ b/update_engine/certificate_checker.cc
@@ -0,0 +1,204 @@
+//
+// 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/certificate_checker.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <curl/curl.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+                                          int* out_depth,
+                                          unsigned int* out_digest_length,
+                                          uint8_t* out_digest) const {
+  TEST_AND_RETURN_FALSE(out_digest);
+  X509* certificate = X509_STORE_CTX_get_current_cert(x509_ctx);
+  TEST_AND_RETURN_FALSE(certificate);
+  int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
+  if (out_depth)
+    *out_depth = depth;
+
+  unsigned int len;
+  const EVP_MD* digest_function = EVP_sha256();
+  bool success = X509_digest(certificate, digest_function, out_digest, &len);
+
+  if (success && out_digest_length)
+    *out_digest_length = len;
+  return success;
+}
+
+// static
+CertificateChecker* CertificateChecker::cert_checker_singleton_ = nullptr;
+
+CertificateChecker::CertificateChecker(PrefsInterface* prefs,
+                                       OpenSSLWrapper* openssl_wrapper)
+    : prefs_(prefs), openssl_wrapper_(openssl_wrapper) {
+}
+
+CertificateChecker::~CertificateChecker() {
+  if (cert_checker_singleton_ == this)
+    cert_checker_singleton_ = nullptr;
+}
+
+void CertificateChecker::Init() {
+  CHECK(cert_checker_singleton_ == nullptr);
+  cert_checker_singleton_ = this;
+}
+
+// static
+CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
+                                               SSL_CTX* ssl_ctx,
+                                               void* ptr) {
+  ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
+
+  if (!cert_checker_singleton_) {
+    DLOG(WARNING) << "No CertificateChecker singleton initialized.";
+    return CURLE_FAILED_INIT;
+  }
+
+  // From here we set the SSL_CTX to another callback, from the openssl library,
+  // which will be called after each server certificate is validated. However,
+  // since openssl does not allow us to pass our own data pointer to the
+  // callback, the certificate check will have to be done statically. Since we
+  // need to know which update server we are using in order to check the
+  // certificate, we hardcode Chrome OS's two known update servers here, and
+  // define a different static callback for each. Since this code should only
+  // run in official builds, this should not be a problem. However, if an update
+  // server different from the ones listed here is used, the check will not
+  // take place.
+  int (*verify_callback)(int, X509_STORE_CTX*);
+  switch (*server_to_check) {
+    case ServerToCheck::kDownload:
+      verify_callback = &CertificateChecker::VerifySSLCallbackDownload;
+      break;
+    case ServerToCheck::kUpdate:
+      verify_callback = &CertificateChecker::VerifySSLCallbackUpdate;
+      break;
+    case ServerToCheck::kNone:
+      verify_callback = nullptr;
+      break;
+  }
+
+  SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);
+  return CURLE_OK;
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
+                                                  X509_STORE_CTX* x509_ctx) {
+  return VerifySSLCallback(preverify_ok, x509_ctx, ServerToCheck::kDownload);
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackUpdate(int preverify_ok,
+                                                X509_STORE_CTX* x509_ctx) {
+  return VerifySSLCallback(preverify_ok, x509_ctx, ServerToCheck::kUpdate);
+}
+
+// static
+int CertificateChecker::VerifySSLCallback(int preverify_ok,
+                                          X509_STORE_CTX* x509_ctx,
+                                          ServerToCheck server_to_check) {
+  CHECK(cert_checker_singleton_ != nullptr);
+  return cert_checker_singleton_->CheckCertificateChange(
+      preverify_ok, x509_ctx, server_to_check) ? 1 : 0;
+}
+
+bool CertificateChecker::CheckCertificateChange(int preverify_ok,
+                                                X509_STORE_CTX* x509_ctx,
+                                                ServerToCheck server_to_check) {
+  TEST_AND_RETURN_FALSE(prefs_ != nullptr);
+
+  // If pre-verification failed, we are not interested in the current
+  // certificate. We store a report to UMA and just propagate the fail result.
+  if (!preverify_ok) {
+    NotifyCertificateChecked(server_to_check, CertificateCheckResult::kFailed);
+    return false;
+  }
+
+  int depth;
+  unsigned int digest_length;
+  uint8_t digest[EVP_MAX_MD_SIZE];
+
+  if (!openssl_wrapper_->GetCertificateDigest(x509_ctx,
+                                              &depth,
+                                              &digest_length,
+                                              digest)) {
+    LOG(WARNING) << "Failed to generate digest of X509 certificate "
+                 << "from update server.";
+    NotifyCertificateChecked(server_to_check, CertificateCheckResult::kValid);
+    return true;
+  }
+
+  // We convert the raw bytes of the digest to an hex string, for storage in
+  // prefs.
+  string digest_string = base::HexEncode(digest, digest_length);
+
+  string storage_key =
+      base::StringPrintf("%s-%d-%d", kPrefsUpdateServerCertificate,
+                         static_cast<int>(server_to_check), depth);
+  string stored_digest;
+  // If there's no stored certificate, we just store the current one and return.
+  if (!prefs_->GetString(storage_key, &stored_digest)) {
+    if (!prefs_->SetString(storage_key, digest_string)) {
+      LOG(WARNING) << "Failed to store server certificate on storage key "
+                   << storage_key;
+    }
+    NotifyCertificateChecked(server_to_check, CertificateCheckResult::kValid);
+    return true;
+  }
+
+  // Certificate changed, we store a report to UMA and store the most recent
+  // certificate.
+  if (stored_digest != digest_string) {
+    if (!prefs_->SetString(storage_key, digest_string)) {
+      LOG(WARNING) << "Failed to store server certificate on storage key "
+                   << storage_key;
+    }
+    LOG(INFO) << "Certificate changed from " << stored_digest << " to "
+              << digest_string << ".";
+    NotifyCertificateChecked(server_to_check,
+                             CertificateCheckResult::kValidChanged);
+    return true;
+  }
+
+  NotifyCertificateChecked(server_to_check, CertificateCheckResult::kValid);
+  // Since we don't perform actual SSL verification, we return success.
+  return true;
+}
+
+void CertificateChecker::NotifyCertificateChecked(
+    ServerToCheck server_to_check,
+    CertificateCheckResult result) {
+  if (observer_)
+    observer_->CertificateChecked(server_to_check, result);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/certificate_checker.h b/update_engine/certificate_checker.h
new file mode 100644
index 0000000..5d0b5ba
--- /dev/null
+++ b/update_engine/certificate_checker.h
@@ -0,0 +1,175 @@
+//
+// 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_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
+
+#include <curl/curl.h>
+#include <openssl/ssl.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace chromeos_update_engine {
+
+class PrefsInterface;
+
+// Wrapper for openssl operations with the certificates.
+class OpenSSLWrapper {
+ public:
+  OpenSSLWrapper() = default;
+  virtual ~OpenSSLWrapper() = default;
+
+  // Takes an openssl X509_STORE_CTX, extracts the corresponding certificate
+  // from it and calculates its fingerprint (SHA256 digest). Returns true on
+  // success and false otherwise.
+  //
+  // |x509_ctx| is the pointer to the openssl object that holds the certificate.
+  // |out_depth| is the depth of the current certificate, in the certificate
+  // chain.
+  // |out_digest_length| is the length of the generated digest.
+  // |out_digest| is the byte array where the digest itself will be written.
+  // It should be big enough to hold a SHA1 digest (e.g. EVP_MAX_MD_SIZE).
+  virtual bool GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+                                    int* out_depth,
+                                    unsigned int* out_digest_length,
+                                    uint8_t* out_digest) const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OpenSSLWrapper);
+};
+
+// The values in this enum are replicated in the metrics server. See metrics.h
+// for instructions on how to update these values in the server side.
+enum class CertificateCheckResult {
+  // The certificate is valid and the same as seen before or the first time we
+  // see a certificate.
+  kValid,
+  // The certificate is valid, but is different than a previously seen
+  // certificate for the selected server.
+  kValidChanged,
+  // The certificate validation failed.
+  kFailed,
+
+  // This value must be the last entry.
+  kNumConstants
+};
+
+// These values are used to generate the keys of files persisted via prefs.
+// This means that changing these will cause loss of information on metrics
+// reporting, during the transition. These values are also mapped to a metric
+// name in metrics.cc, so adding values here requires to assign a new metric
+// name in that file.
+enum class ServerToCheck {
+  kUpdate = 0,
+  kDownload,
+
+  // Ignore this server.
+  kNone,
+};
+
+// Responsible for checking whether update server certificates change, and
+// reporting to UMA when this happens. Since all state information is persisted,
+// and openssl forces us to use a static callback with no data pointer, this
+// class is entirely static.
+class CertificateChecker {
+ public:
+  class Observer {
+   public:
+    virtual ~Observer() = default;
+
+    // Called whenever a certificate is checked for the server |server_to_check|
+    // passing the result of said certificate check.
+    virtual void CertificateChecked(ServerToCheck server_to_check,
+                                    CertificateCheckResult result) = 0;
+  };
+
+  CertificateChecker(PrefsInterface* prefs, OpenSSLWrapper* openssl_wrapper);
+  ~CertificateChecker();
+
+  // This callback is called by libcurl just before the initialization of an
+  // SSL connection after having processed all other SSL related options. Used
+  // to check if server certificates change. |cert_checker| is expected to be a
+  // pointer to the CertificateChecker instance.
+  static CURLcode ProcessSSLContext(CURL* curl_handle,
+                                    SSL_CTX* ssl_ctx,
+                                    void* cert_checker);
+
+  // Initialize this class as the singleton instance. Only one instance can be
+  // initialized at a time and it should be initialized before other methods
+  // can be used.
+  void Init();
+
+  // Set the certificate observer to the passed instance. To remove the
+  // observer, pass a nullptr. The |observer| instance must be valid while this
+  // CertificateChecker verifies certificates.
+  void SetObserver(Observer* observer) { observer_ = observer; }
+
+ private:
+  FRIEND_TEST(CertificateCheckerTest, NewCertificate);
+  FRIEND_TEST(CertificateCheckerTest, SameCertificate);
+  FRIEND_TEST(CertificateCheckerTest, ChangedCertificate);
+  FRIEND_TEST(CertificateCheckerTest, FailedCertificate);
+
+  // These callbacks are asynchronously called by openssl after initial SSL
+  // verification. They are used to perform any additional security verification
+  // on the connection, but we use them here to get hold of the server
+  // certificate, in order to determine if it has changed since the last
+  // connection. Since openssl forces us to do this statically, we define two
+  // different callbacks for the two different official update servers, and only
+  // assign the correspondent one. The assigned callback is then called once per
+  // each certificate on the server and returns 1 for success and 0 for failure.
+  static int VerifySSLCallbackDownload(int preverify_ok,
+                                       X509_STORE_CTX* x509_ctx);
+  static int VerifySSLCallbackUpdate(int preverify_ok,
+                                     X509_STORE_CTX* x509_ctx);
+  static int VerifySSLCallback(int preverify_ok,
+                               X509_STORE_CTX* x509_ctx,
+                               ServerToCheck server_to_check);
+
+  // Checks if server certificate stored in |x509_ctx| has changed since last
+  // connection to that same server, specified by |server_to_check|.
+  // This is called by the callbacks defined above. The result of the
+  // certificate check is passed to the observer, if any. Returns true on
+  // success and false otherwise.
+  bool CheckCertificateChange(int preverify_ok,
+                              X509_STORE_CTX* x509_ctx,
+                              ServerToCheck server_to_check);
+
+  // Notifies the observer, if any, of a certificate checking.
+  void NotifyCertificateChecked(ServerToCheck server_to_check,
+                                CertificateCheckResult result);
+
+  // The CertificateChecker singleton instance.
+  static CertificateChecker* cert_checker_singleton_;
+
+  // Prefs instance used to store the certificates seen in the past.
+  PrefsInterface* prefs_;
+
+  // The wrapper for openssl operations.
+  OpenSSLWrapper* openssl_wrapper_;
+
+  // The observer called whenever a certificate is checked, if not null.
+  Observer* observer_{nullptr};
+
+  DISALLOW_COPY_AND_ASSIGN(CertificateChecker);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
diff --git a/update_engine/certificate_checker_unittest.cc b/update_engine/certificate_checker_unittest.cc
new file mode 100644
index 0000000..20efce9
--- /dev/null
+++ b/update_engine/certificate_checker_unittest.cc
@@ -0,0 +1,140 @@
+//
+// 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/certificate_checker.h"
+
+#include <string>
+
+#include <base/strings/string_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/mock_prefs.h"
+#include "update_engine/mock_certificate_checker.h"
+
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::SetArrayArgument;
+using ::testing::_;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class MockCertificateCheckObserver : public CertificateChecker::Observer {
+ public:
+  MOCK_METHOD2(CertificateChecked,
+               void(ServerToCheck server_to_check,
+                    CertificateCheckResult result));
+};
+
+class CertificateCheckerTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    cert_key_ = base::StringPrintf("%s-%d-%d",
+                                   cert_key_prefix_.c_str(),
+                                   static_cast<int>(server_to_check_),
+                                   depth_);
+    cert_checker.Init();
+    cert_checker.SetObserver(&observer_);
+  }
+
+  void TearDown() override {
+    cert_checker.SetObserver(nullptr);
+  }
+
+  MockPrefs prefs_;
+  MockOpenSSLWrapper openssl_wrapper_;
+  // Parameters of our mock certificate digest.
+  int depth_{0};
+  unsigned int length_{4};
+  uint8_t digest_[4]{0x17, 0x7D, 0x07, 0x5F};
+  string digest_hex_{"177D075F"};
+  string diff_digest_hex_{"1234ABCD"};
+  string cert_key_prefix_{kPrefsUpdateServerCertificate};
+  ServerToCheck server_to_check_{ServerToCheck::kUpdate};
+  string cert_key_;
+
+  testing::StrictMock<MockCertificateCheckObserver> observer_;
+  CertificateChecker cert_checker{&prefs_, &openssl_wrapper_};
+};
+
+// check certificate change, new
+TEST_F(CertificateCheckerTest, NewCertificate) {
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(depth_),
+          SetArgumentPointee<2>(length_),
+          SetArrayArgument<3>(digest_, digest_ + 4),
+          Return(true)));
+  EXPECT_CALL(prefs_, GetString(cert_key_, _)).WillOnce(Return(false));
+  EXPECT_CALL(prefs_, SetString(cert_key_, digest_hex_)).WillOnce(Return(true));
+  EXPECT_CALL(observer_,
+              CertificateChecked(server_to_check_,
+                                 CertificateCheckResult::kValid));
+  ASSERT_TRUE(
+      cert_checker.CheckCertificateChange(1, nullptr, server_to_check_));
+}
+
+// check certificate change, unchanged
+TEST_F(CertificateCheckerTest, SameCertificate) {
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(depth_),
+          SetArgumentPointee<2>(length_),
+          SetArrayArgument<3>(digest_, digest_ + 4),
+          Return(true)));
+  EXPECT_CALL(prefs_, GetString(cert_key_, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(digest_hex_), Return(true)));
+  EXPECT_CALL(prefs_, SetString(_, _)).Times(0);
+  EXPECT_CALL(observer_,
+              CertificateChecked(server_to_check_,
+                                 CertificateCheckResult::kValid));
+  ASSERT_TRUE(
+      cert_checker.CheckCertificateChange(1, nullptr, server_to_check_));
+}
+
+// check certificate change, changed
+TEST_F(CertificateCheckerTest, ChangedCertificate) {
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(depth_),
+          SetArgumentPointee<2>(length_),
+          SetArrayArgument<3>(digest_, digest_ + 4),
+          Return(true)));
+  EXPECT_CALL(prefs_, GetString(cert_key_, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(diff_digest_hex_), Return(true)));
+  EXPECT_CALL(observer_,
+              CertificateChecked(server_to_check_,
+                                 CertificateCheckResult::kValidChanged));
+  EXPECT_CALL(prefs_, SetString(cert_key_, digest_hex_)).WillOnce(Return(true));
+  ASSERT_TRUE(
+      cert_checker.CheckCertificateChange(1, nullptr, server_to_check_));
+}
+
+// check certificate change, failed
+TEST_F(CertificateCheckerTest, FailedCertificate) {
+  EXPECT_CALL(observer_, CertificateChecked(server_to_check_,
+                                            CertificateCheckResult::kFailed));
+  EXPECT_CALL(prefs_, GetString(_, _)).Times(0);
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(_, _, _, _)).Times(0);
+  ASSERT_FALSE(
+      cert_checker.CheckCertificateChange(0, nullptr, server_to_check_));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/chrome_browser_proxy_resolver.cc b/update_engine/chrome_browser_proxy_resolver.cc
new file mode 100644
index 0000000..da57e1d
--- /dev/null
+++ b/update_engine/chrome_browser_proxy_resolver.cc
@@ -0,0 +1,193 @@
+//
+// 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/chrome_browser_proxy_resolver.h"
+
+#include <deque>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/strings/string_tokenizer.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+using base::StringTokenizer;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::deque;
+using std::make_pair;
+using std::pair;
+using std::string;
+
+const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
+const char kLibCrosProxyResolveName[] = "ProxyResolved";
+const char kLibCrosProxyResolveSignalInterface[] =
+    "org.chromium.UpdateEngineLibcrosProxyResolvedInterface";
+
+namespace {
+
+const int kTimeout = 5;  // seconds
+
+}  // namespace
+
+ChromeBrowserProxyResolver::ChromeBrowserProxyResolver(
+    LibCrosProxy* libcros_proxy)
+    : libcros_proxy_(libcros_proxy), timeout_(kTimeout) {}
+
+bool ChromeBrowserProxyResolver::Init() {
+  libcros_proxy_->ue_proxy_resolved_interface()
+      ->RegisterProxyResolvedSignalHandler(
+          base::Bind(&ChromeBrowserProxyResolver::OnProxyResolvedSignal,
+                     base::Unretained(this)),
+          base::Bind(&ChromeBrowserProxyResolver::OnSignalConnected,
+                     base::Unretained(this)));
+  return true;
+}
+
+ChromeBrowserProxyResolver::~ChromeBrowserProxyResolver() {
+  // Kill outstanding timers.
+  for (auto& timer : timers_) {
+    MessageLoop::current()->CancelTask(timer.second);
+    timer.second = MessageLoop::kTaskIdNull;
+  }
+}
+
+bool ChromeBrowserProxyResolver::GetProxiesForUrl(const string& url,
+                                                  ProxiesResolvedFn callback,
+                                                  void* data) {
+  int timeout = timeout_;
+  brillo::ErrorPtr error;
+  if (!libcros_proxy_->service_interface_proxy()->ResolveNetworkProxy(
+          url.c_str(),
+          kLibCrosProxyResolveSignalInterface,
+          kLibCrosProxyResolveName,
+          &error)) {
+    LOG(WARNING) << "Can't resolve the proxy. Continuing with no proxy.";
+    timeout = 0;
+  }
+
+  callbacks_.insert(make_pair(url, make_pair(callback, data)));
+  MessageLoop::TaskId timer = MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&ChromeBrowserProxyResolver::HandleTimeout,
+                 base::Unretained(this),
+                 url),
+      TimeDelta::FromSeconds(timeout));
+  timers_.insert(make_pair(url, timer));
+  return true;
+}
+
+bool ChromeBrowserProxyResolver::DeleteUrlState(
+    const string& source_url,
+    bool delete_timer,
+    pair<ProxiesResolvedFn, void*>* callback) {
+  {
+    CallbacksMap::iterator it = callbacks_.lower_bound(source_url);
+    TEST_AND_RETURN_FALSE(it != callbacks_.end());
+    TEST_AND_RETURN_FALSE(it->first == source_url);
+    if (callback)
+      *callback = it->second;
+    callbacks_.erase(it);
+  }
+  {
+    TimeoutsMap::iterator it = timers_.lower_bound(source_url);
+    TEST_AND_RETURN_FALSE(it != timers_.end());
+    TEST_AND_RETURN_FALSE(it->first == source_url);
+    if (delete_timer)
+      MessageLoop::current()->CancelTask(it->second);
+    timers_.erase(it);
+  }
+  return true;
+}
+
+void ChromeBrowserProxyResolver::OnSignalConnected(const string& interface_name,
+                                                   const string& signal_name,
+                                                   bool successful) {
+  if (!successful) {
+    LOG(ERROR) << "Couldn't connect to the signal " << interface_name << "."
+               << signal_name;
+  }
+}
+
+void ChromeBrowserProxyResolver::OnProxyResolvedSignal(
+    const string& source_url,
+    const string& proxy_info,
+    const string& error_message) {
+  pair<ProxiesResolvedFn, void*> callback;
+  TEST_AND_RETURN(DeleteUrlState(source_url, true, &callback));
+  if (!error_message.empty()) {
+    LOG(WARNING) << "ProxyResolved error: " << error_message;
+  }
+  (*callback.first)(ParseProxyString(proxy_info), callback.second);
+}
+
+void ChromeBrowserProxyResolver::HandleTimeout(string source_url) {
+  LOG(INFO) << "Timeout handler called. Seems Chrome isn't responding.";
+  pair<ProxiesResolvedFn, void*> callback;
+  TEST_AND_RETURN(DeleteUrlState(source_url, false, &callback));
+  deque<string> proxies;
+  proxies.push_back(kNoProxy);
+  (*callback.first)(proxies, callback.second);
+}
+
+deque<string> ChromeBrowserProxyResolver::ParseProxyString(
+    const string& input) {
+  deque<string> ret;
+  // Some of this code taken from
+  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
+  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
+  StringTokenizer entry_tok(input, ";");
+  while (entry_tok.GetNext()) {
+    string token = entry_tok.token();
+    base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
+
+    // Start by finding the first space (if any).
+    string::iterator space;
+    for (space = token.begin(); space != token.end(); ++space) {
+      if (base::IsAsciiWhitespace(*space)) {
+        break;
+      }
+    }
+
+    string scheme = base::ToLowerASCII(string(token.begin(), space));
+    // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
+    if (scheme == "socks")
+      scheme += "4";
+    else if (scheme == "proxy")
+      scheme = "http";
+    else if (scheme != "https" &&
+             scheme != "socks4" &&
+             scheme != "socks5" &&
+             scheme != "direct")
+      continue;  // Invalid proxy scheme
+
+    string host_and_port = string(space, token.end());
+    base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
+    if (scheme != "direct" && host_and_port.empty())
+      continue;  // Must supply host/port when non-direct proxy used.
+    ret.push_back(scheme + "://" + host_and_port);
+  }
+  if (ret.empty() || *ret.rbegin() != kNoProxy)
+    ret.push_back(kNoProxy);
+  return ret;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/chrome_browser_proxy_resolver.h b/update_engine/chrome_browser_proxy_resolver.h
new file mode 100644
index 0000000..84b0c28
--- /dev/null
+++ b/update_engine/chrome_browser_proxy_resolver.h
@@ -0,0 +1,98 @@
+//
+// 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_CHROME_BROWSER_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/libcros_proxy.h"
+#include "update_engine/proxy_resolver.h"
+
+namespace chromeos_update_engine {
+
+extern const char kLibCrosServiceName[];
+extern const char kLibCrosProxyResolveName[];
+extern const char kLibCrosProxyResolveSignalInterface[];
+
+class ChromeBrowserProxyResolver : public ProxyResolver {
+ public:
+  explicit ChromeBrowserProxyResolver(LibCrosProxy* libcros_proxy);
+  ~ChromeBrowserProxyResolver() override;
+
+  // Initialize the ProxyResolver using the provided DBus proxies.
+  bool Init();
+
+  bool GetProxiesForUrl(const std::string& url,
+                        ProxiesResolvedFn callback,
+                        void* data) override;
+
+ private:
+  FRIEND_TEST(ChromeBrowserProxyResolverTest, ParseTest);
+  FRIEND_TEST(ChromeBrowserProxyResolverTest, SuccessTest);
+  typedef std::multimap<std::string, std::pair<ProxiesResolvedFn, void*>>
+      CallbacksMap;
+  typedef std::multimap<std::string, brillo::MessageLoop::TaskId> TimeoutsMap;
+
+  // Called when the signal in UpdateEngineLibcrosProxyResolvedInterface is
+  // connected.
+  void OnSignalConnected(const std::string& interface_name,
+                         const std::string& signal_name,
+                         bool successful);
+
+  // Handle a reply from Chrome:
+  void OnProxyResolvedSignal(const std::string& source_url,
+                             const std::string& proxy_info,
+                             const std::string& error_message);
+
+  // Handle no reply:
+  void HandleTimeout(std::string source_url);
+
+  // Parses a string-encoded list of proxies and returns a deque
+  // of individual proxies. The last one will always be kNoProxy.
+  static std::deque<std::string> ParseProxyString(const std::string& input);
+
+  // Deletes internal state for the first instance of url in the state.
+  // If delete_timer is set, calls CancelTask on the timer id.
+  // Returns the callback in an out parameter. Returns true on success.
+  bool DeleteUrlState(const std::string& url,
+                      bool delete_timer,
+                      std::pair<ProxiesResolvedFn, void*>* callback);
+
+  // Shutdown the dbus proxy object.
+  void Shutdown();
+
+  // DBus proxies to request a HTTP proxy resolution. The request is done in the
+  // service_interface_proxy() interface and the response is received as a
+  // signal in the ue_proxy_resolved_interface().
+  LibCrosProxy* libcros_proxy_;
+
+  int timeout_;
+  TimeoutsMap timers_;
+  CallbacksMap callbacks_;
+  DISALLOW_COPY_AND_ASSIGN(ChromeBrowserProxyResolver);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
diff --git a/update_engine/chrome_browser_proxy_resolver_unittest.cc b/update_engine/chrome_browser_proxy_resolver_unittest.cc
new file mode 100644
index 0000000..bb5193e
--- /dev/null
+++ b/update_engine/chrome_browser_proxy_resolver_unittest.cc
@@ -0,0 +1,211 @@
+//
+// 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/chrome_browser_proxy_resolver.h"
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <base/bind.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/fake_message_loop.h>
+
+#include "libcros/dbus-proxies.h"
+#include "libcros/dbus-proxy-mocks.h"
+#include "update_engine/dbus_test_utils.h"
+
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::_;
+using brillo::MessageLoop;
+using org::chromium::LibCrosServiceInterfaceProxyMock;
+using org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyMock;
+using std::deque;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ChromeBrowserProxyResolverTest : public ::testing::Test {
+ protected:
+  ChromeBrowserProxyResolverTest()
+      : service_interface_mock_(new LibCrosServiceInterfaceProxyMock()),
+        ue_proxy_resolved_interface_mock_(
+            new UpdateEngineLibcrosProxyResolvedInterfaceProxyMock()),
+        libcros_proxy_(
+            brillo::make_unique_ptr(service_interface_mock_),
+            brillo::make_unique_ptr(ue_proxy_resolved_interface_mock_)) {}
+
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    // The ProxyResolved signal should be subscribed to.
+    MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER(
+        ue_proxy_resolved_signal_,
+        *ue_proxy_resolved_interface_mock_,
+        ProxyResolved);
+
+    EXPECT_TRUE(resolver_.Init());
+    // Run the loop once to dispatch the successfully registered signal handler.
+    EXPECT_TRUE(loop_.RunOnce(false));
+  }
+
+  void TearDown() override {
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  // Send the signal to the callback passed during registration of the
+  // ProxyResolved.
+  void SendReplySignal(const string& source_url,
+                       const string& proxy_info,
+                       const string& error_message);
+
+  void RunTest(bool chrome_replies, bool chrome_alive);
+
+ private:
+  brillo::FakeMessageLoop loop_{nullptr};
+
+  // Local pointers to the mocks. The instances are owned by the
+  // |libcros_proxy_|.
+  LibCrosServiceInterfaceProxyMock* service_interface_mock_;
+  UpdateEngineLibcrosProxyResolvedInterfaceProxyMock*
+      ue_proxy_resolved_interface_mock_;
+
+  // The registered signal handler for the signal
+  // UpdateEngineLibcrosProxyResolvedInterface.ProxyResolved.
+  chromeos_update_engine::dbus_test_utils::MockSignalHandler<
+      void(const string&, const string&, const string&)>
+      ue_proxy_resolved_signal_;
+
+  LibCrosProxy libcros_proxy_;
+  ChromeBrowserProxyResolver resolver_{&libcros_proxy_};
+};
+
+
+void ChromeBrowserProxyResolverTest::SendReplySignal(
+    const string& source_url,
+    const string& proxy_info,
+    const string& error_message) {
+  ASSERT_TRUE(ue_proxy_resolved_signal_.IsHandlerRegistered());
+  ue_proxy_resolved_signal_.signal_callback().Run(
+      source_url, proxy_info, error_message);
+}
+
+namespace {
+void CheckResponseResolved(const deque<string>& proxies,
+                           void* /* pirv_data */) {
+  EXPECT_EQ(2U, proxies.size());
+  EXPECT_EQ("socks5://192.168.52.83:5555", proxies[0]);
+  EXPECT_EQ(kNoProxy, proxies[1]);
+  MessageLoop::current()->BreakLoop();
+}
+
+void CheckResponseNoReply(const deque<string>& proxies, void* /* pirv_data */) {
+  EXPECT_EQ(1U, proxies.size());
+  EXPECT_EQ(kNoProxy, proxies[0]);
+  MessageLoop::current()->BreakLoop();
+}
+}  // namespace
+
+// chrome_replies should be set to whether or not we fake a reply from
+// chrome. If there's no reply, the resolver should time out.
+// If chrome_alive is false, assume that sending to chrome fails.
+void ChromeBrowserProxyResolverTest::RunTest(bool chrome_replies,
+                                             bool chrome_alive) {
+  char kUrl[] = "http://example.com/blah";
+  char kProxyConfig[] = "SOCKS5 192.168.52.83:5555;DIRECT";
+
+  EXPECT_CALL(*service_interface_mock_,
+              ResolveNetworkProxy(StrEq(kUrl),
+                                  StrEq(kLibCrosProxyResolveSignalInterface),
+                                  StrEq(kLibCrosProxyResolveName),
+                                  _,
+                                  _))
+      .WillOnce(Return(chrome_alive));
+
+  ProxiesResolvedFn get_proxies_response = &CheckResponseNoReply;
+  if (chrome_replies) {
+    get_proxies_response = &CheckResponseResolved;
+    MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&ChromeBrowserProxyResolverTest::SendReplySignal,
+                   base::Unretained(this),
+                   kUrl,
+                   kProxyConfig,
+                   ""),
+        base::TimeDelta::FromSeconds(1));
+  }
+
+  EXPECT_TRUE(resolver_.GetProxiesForUrl(kUrl, get_proxies_response, nullptr));
+  MessageLoop::current()->Run();
+}
+
+
+TEST_F(ChromeBrowserProxyResolverTest, ParseTest) {
+  // Test ideas from
+  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list_unittest.cc
+  vector<string> inputs = {
+      "PROXY foopy:10",
+      " DIRECT",  // leading space.
+      "PROXY foopy1 ; proxy foopy2;\t DIRECT",
+      "proxy foopy1 ; SOCKS foopy2",
+      "DIRECT ; proxy foopy1 ; DIRECT ; SOCKS5 foopy2;DIRECT ",
+      "DIRECT ; proxy foopy1:80; DIRECT ; DIRECT",
+      "PROXY-foopy:10",
+      "PROXY",
+      "PROXY foopy1 ; JUNK ; JUNK ; SOCKS5 foopy2 ; ;",
+      "HTTP foopy1; SOCKS5 foopy2"};
+  vector<deque<string>> outputs = {
+      {"http://foopy:10", kNoProxy},
+      {kNoProxy},
+      {"http://foopy1", "http://foopy2", kNoProxy},
+      {"http://foopy1", "socks4://foopy2", kNoProxy},
+      {kNoProxy, "http://foopy1", kNoProxy, "socks5://foopy2", kNoProxy},
+      {kNoProxy, "http://foopy1:80", kNoProxy, kNoProxy},
+      {kNoProxy},
+      {kNoProxy},
+      {"http://foopy1", "socks5://foopy2", kNoProxy},
+      {"socks5://foopy2", kNoProxy}};
+  ASSERT_EQ(inputs.size(), outputs.size());
+
+  for (size_t i = 0; i < inputs.size(); i++) {
+    deque<string> results =
+        ChromeBrowserProxyResolver::ParseProxyString(inputs[i]);
+    deque<string>& expected = outputs[i];
+    EXPECT_EQ(results.size(), expected.size()) << "i = " << i;
+    if (expected.size() != results.size())
+      continue;
+    for (size_t j = 0; j < expected.size(); j++) {
+      EXPECT_EQ(expected[j], results[j]) << "i = " << i;
+    }
+  }
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, SuccessTest) {
+  RunTest(true, true);
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, NoReplyTest) {
+  RunTest(false, true);
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, NoChromeTest) {
+  RunTest(false, false);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/client_library/client.cc b/update_engine/client_library/client.cc
new file mode 100644
index 0000000..348219f
--- /dev/null
+++ b/update_engine/client_library/client.cc
@@ -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.
+//
+
+#include "update_engine/client_library/include/update_engine/client.h"
+
+#include <memory>
+
+#if USE_BINDER
+#include "update_engine/client_library/client_binder.h"
+#else  // !USE_BINDER
+#ifdef USE_NESTLABS
+#include "update_engine/client_library/client_dbus_nestlabs.h"
+#else
+#include "update_engine/client_library/client_dbus.h"
+#endif
+#endif  // USE_BINDER
+
+using std::unique_ptr;
+
+namespace update_engine {
+
+unique_ptr<UpdateEngineClient> UpdateEngineClient::CreateInstance() {
+#if USE_BINDER
+  auto update_engine_client_impl = new internal::BinderUpdateEngineClient{};
+#else  // !USE_BINDER
+#if USE_NESTLABS
+  auto update_engine_client_impl = new internal::DBusUpdateEngineNestlabsClient{};
+#else
+  auto update_engine_client_impl = new internal::DBusUpdateEngineClient{};
+#endif
+#endif  // USE_BINDER
+  auto ret = unique_ptr<UpdateEngineClient>{update_engine_client_impl};
+
+  if (!update_engine_client_impl->Init()) {
+      ret.reset();
+  }
+
+  return ret;
+}
+
+}  // namespace update_engine
diff --git a/update_engine/client_library/client_binder.cc b/update_engine/client_library/client_binder.cc
new file mode 100644
index 0000000..e98c225
--- /dev/null
+++ b/update_engine/client_library/client_binder.cc
@@ -0,0 +1,251 @@
+//
+// 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/client_library/client_binder.h"
+
+#include <binder/IServiceManager.h>
+
+#include <base/message_loop/message_loop.h>
+#include <utils/String8.h>
+
+#include "update_engine/common_service.h"
+#include "update_engine/parcelable_update_engine_status.h"
+#include "update_engine/update_status_utils.h"
+
+using android::OK;
+using android::String16;
+using android::String8;
+using android::binder::Status;
+using android::brillo::ParcelableUpdateEngineStatus;
+using android::getService;
+using chromeos_update_engine::StringToUpdateStatus;
+using chromeos_update_engine::UpdateEngineService;
+using std::string;
+
+namespace update_engine {
+namespace internal {
+
+bool BinderUpdateEngineClient::Init() {
+  if (!binder_watcher_.Init()) return false;
+
+  return getService(String16{"android.brillo.UpdateEngineService"},
+      &service_) == OK;
+}
+
+bool BinderUpdateEngineClient::AttemptUpdate(const string& in_app_version,
+                                             const string& in_omaha_url,
+                                             bool at_user_request) {
+  return service_->AttemptUpdate(String16{in_app_version.c_str()},
+      String16{in_omaha_url.c_str()},
+      at_user_request ? 0 :
+          UpdateEngineService::kAttemptUpdateFlagNonInteractive).isOk();
+}
+
+bool BinderUpdateEngineClient::GetStatus(int64_t* out_last_checked_time,
+                                         double* out_progress,
+                                         UpdateStatus* out_update_status,
+                                         string* out_new_version,
+                                         int64_t* out_new_size) const {
+  ParcelableUpdateEngineStatus status;
+
+  if (!service_->GetStatus(&status).isOk())
+    return false;
+
+  *out_last_checked_time = status.last_checked_time_;
+  *out_progress = status.progress_;
+  StringToUpdateStatus(String8{status.current_operation_}.string(),
+                       out_update_status);
+  *out_new_version = String8{status.new_version_}.string();
+  *out_new_size = status.new_size_;
+  return true;
+}
+
+bool BinderUpdateEngineClient::SetCohortHint(const string& in_cohort_hint) {
+  return service_->SetCohortHint(String16{in_cohort_hint.c_str()}).isOk();
+}
+
+bool BinderUpdateEngineClient::GetCohortHint(string* out_cohort_hint) const {
+  String16 out_as_string16;
+
+  if (!service_->GetCohortHint(&out_as_string16).isOk())
+    return false;
+
+  *out_cohort_hint = String8{out_as_string16}.string();
+  return true;
+}
+
+bool BinderUpdateEngineClient::SetUpdateOverCellularPermission(bool allowed) {
+  return service_->SetUpdateOverCellularPermission(allowed).isOk();
+}
+
+bool BinderUpdateEngineClient::GetUpdateOverCellularPermission(
+    bool* allowed) const {
+  return service_->GetUpdateOverCellularPermission(allowed).isOk();
+}
+
+bool BinderUpdateEngineClient::SetP2PUpdatePermission(bool enabled) {
+  return service_->SetP2PUpdatePermission(enabled).isOk();
+}
+
+bool BinderUpdateEngineClient::GetP2PUpdatePermission(bool* enabled) const {
+  return service_->GetP2PUpdatePermission(enabled).isOk();
+}
+
+bool BinderUpdateEngineClient::Rollback(bool powerwash) {
+  return service_->AttemptRollback(powerwash).isOk();
+}
+
+bool BinderUpdateEngineClient::GetRollbackPartition(
+    string* rollback_partition) const {
+  String16 out_as_string16;
+
+  if (!service_->GetRollbackPartition(&out_as_string16).isOk())
+    return false;
+
+  *rollback_partition = String8{out_as_string16}.string();
+  return true;
+}
+
+bool BinderUpdateEngineClient::GetPrevVersion(string* prev_version) const {
+  String16 out_as_string16;
+
+  if (!service_->GetPrevVersion(&out_as_string16).isOk())
+    return false;
+
+  *prev_version = String8{out_as_string16}.string();
+  return true;
+}
+
+void BinderUpdateEngineClient::RebootIfNeeded() {
+  if (!service_->RebootIfNeeded().isOk()) {
+    // Reboot error code doesn't necessarily mean that a reboot
+    // failed. For example, D-Bus may be shutdown before we receive the
+    // result.
+    LOG(INFO) << "RebootIfNeeded() failure ignored.";
+  }
+}
+
+bool BinderUpdateEngineClient::ResetStatus() {
+  return service_->ResetStatus().isOk();
+}
+
+Status BinderUpdateEngineClient::StatusUpdateCallback::HandleStatusUpdate(
+    int64_t last_checked_time,
+    double progress,
+    const String16& current_operation,
+    const String16& new_version,
+    int64_t new_size) {
+  UpdateStatus update_status;
+
+  StringToUpdateStatus(String8{current_operation}.string(), &update_status);
+
+  for (auto& handler : client_->handlers_) {
+    handler->HandleStatusUpdate(last_checked_time, progress, update_status,
+                                String8{new_version}.string(), new_size);
+  }
+
+  return Status::ok();
+}
+
+bool BinderUpdateEngineClient::RegisterStatusUpdateHandler(
+    StatusUpdateHandler* handler) {
+  if (!status_callback_.get()) {
+    status_callback_ =
+        new BinderUpdateEngineClient::StatusUpdateCallback(this);
+    if (!service_->RegisterStatusCallback(status_callback_).isOk()) {
+      return false;
+    }
+  }
+
+  handlers_.push_back(handler);
+
+  int64_t last_checked_time;
+  double progress;
+  UpdateStatus update_status;
+  string new_version;
+  int64_t new_size;
+
+  if (!GetStatus(&last_checked_time, &progress, &update_status,
+                 &new_version, &new_size)) {
+    handler->IPCError("Could not get status from binder service");
+  }
+
+  handler->HandleStatusUpdate(last_checked_time, progress, update_status,
+                              new_version, new_size);
+
+  return true;
+}
+
+bool BinderUpdateEngineClient::UnregisterStatusUpdateHandler(
+    StatusUpdateHandler* handler) {
+  auto it = std::find(handlers_.begin(), handlers_.end(), handler);
+  if (it != handlers_.end()) {
+    handlers_.erase(it);
+    return true;
+  }
+
+  return false;
+}
+
+bool BinderUpdateEngineClient::SetTargetChannel(const string& in_target_channel,
+                                                bool allow_powerwash) {
+  return service_->SetChannel(String16{in_target_channel.c_str()},
+                              allow_powerwash).isOk();
+}
+
+bool BinderUpdateEngineClient::GetTargetChannel(string* out_channel) const {
+  String16 out_as_string16;
+
+  if (!service_->GetChannel(false, &out_as_string16).isOk())
+    return false;
+
+  *out_channel = String8{out_as_string16}.string();
+  return true;
+}
+
+bool BinderUpdateEngineClient::GetChannel(string* out_channel) const {
+  String16 out_as_string16;
+
+  if (!service_->GetChannel(true, &out_as_string16).isOk())
+    return false;
+
+  *out_channel = String8{out_as_string16}.string();
+  return true;
+}
+
+bool BinderUpdateEngineClient::GetLastAttemptError(
+    int32_t* last_attempt_error) const {
+  int out_as_int;
+
+  if (!service_->GetLastAttemptError(&out_as_int).isOk())
+    return false;
+
+  *last_attempt_error = out_as_int;
+  return true;
+}
+
+bool BinderUpdateEngineClient::GetEolStatus(int32_t* eol_status) const {
+  int out_as_int;
+
+  if (!service_->GetEolStatus(&out_as_int).isOk())
+    return false;
+
+  *eol_status = out_as_int;
+  return true;
+}
+
+}  // namespace internal
+}  // namespace update_engine
diff --git a/update_engine/client_library/client_binder.h b/update_engine/client_library/client_binder.h
new file mode 100644
index 0000000..b1b34da
--- /dev/null
+++ b/update_engine/client_library/client_binder.h
@@ -0,0 +1,118 @@
+//
+// 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_CLIENT_LIBRARY_CLIENT_BINDER_H_
+#define UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_BINDER_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <utils/String16.h>
+#include <utils/StrongPointer.h>
+
+#include <brillo/binder_watcher.h>
+
+#include "android/brillo/BnUpdateEngineStatusCallback.h"
+#include "android/brillo/IUpdateEngine.h"
+
+#include "update_engine/client_library/include/update_engine/client.h"
+
+namespace update_engine {
+namespace internal {
+
+class BinderUpdateEngineClient : public UpdateEngineClient {
+ public:
+  BinderUpdateEngineClient() = default;
+  bool Init();
+
+  virtual ~BinderUpdateEngineClient() = default;
+
+  bool AttemptUpdate(const std::string& app_version,
+                     const std::string& omaha_url,
+                     bool at_user_request) override;
+
+  bool GetStatus(int64_t* out_last_checked_time,
+                 double* out_progress,
+                 UpdateStatus* out_update_status,
+                 std::string* out_new_version,
+                 int64_t* out_new_size) const override;
+
+  bool SetCohortHint(const std::string& in_cohort_hint) override;
+  bool GetCohortHint(std::string* out_cohort_hint) const override;
+
+  bool SetUpdateOverCellularPermission(bool allowed) override;
+  bool GetUpdateOverCellularPermission(bool* allowed) const override;
+
+  bool SetP2PUpdatePermission(bool enabled) override;
+  bool GetP2PUpdatePermission(bool* enabled) const override;
+
+  bool Rollback(bool powerwash) override;
+
+  bool GetRollbackPartition(std::string* rollback_partition) const override;
+
+  void RebootIfNeeded() override;
+
+  bool GetPrevVersion(std::string* prev_version) const override;
+
+  bool ResetStatus() override;
+
+  bool SetTargetChannel(const std::string& target_channel,
+                        bool allow_powerwash) override;
+
+  bool GetTargetChannel(std::string* out_channel) const override;
+
+  bool GetChannel(std::string* out_channel) const override;
+
+  bool RegisterStatusUpdateHandler(StatusUpdateHandler* handler) override;
+  bool UnregisterStatusUpdateHandler(StatusUpdateHandler* handler) override;
+
+  bool GetLastAttemptError(int32_t* last_attempt_error) const override;
+
+  bool GetEolStatus(int32_t* eol_status) const override;
+
+ private:
+  class StatusUpdateCallback :
+      public android::brillo::BnUpdateEngineStatusCallback {
+   public:
+    explicit StatusUpdateCallback(BinderUpdateEngineClient* client)
+        : client_(client) {}
+
+    android::binder::Status HandleStatusUpdate(
+        int64_t last_checked_time,
+        double progress,
+        const android::String16& current_operation,
+        const android::String16& new_version,
+        int64_t new_size) override;
+
+   private:
+    BinderUpdateEngineClient* client_;
+  };
+
+  android::sp<android::brillo::IUpdateEngine> service_;
+  android::sp<android::brillo::IUpdateEngineStatusCallback> status_callback_;
+  std::vector<update_engine::StatusUpdateHandler*> handlers_;
+  brillo::BinderWatcher binder_watcher_;
+
+  DISALLOW_COPY_AND_ASSIGN(BinderUpdateEngineClient);
+};  // class BinderUpdateEngineClient
+
+}  // namespace internal
+}  // namespace update_engine
+
+#endif  // UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_BINDER_H_
diff --git a/update_engine/client_library/client_dbus.cc b/update_engine/client_library/client_dbus.cc
new file mode 100644
index 0000000..bebea6a
--- /dev/null
+++ b/update_engine/client_library/client_dbus.cc
@@ -0,0 +1,249 @@
+//
+// 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/client_library/client_dbus.h"
+
+#include <base/message_loop/message_loop.h>
+
+#include <dbus/bus.h>
+#include <update_engine/dbus-constants.h>
+
+#include "update_engine/update_status_utils.h"
+
+using chromeos_update_engine::StringToUpdateStatus;
+using dbus::Bus;
+using org::chromium::UpdateEngineInterfaceProxy;
+using std::string;
+
+namespace update_engine {
+namespace internal {
+
+bool DBusUpdateEngineClient::Init() {
+  Bus::Options options;
+  options.bus_type = Bus::SYSTEM;
+  scoped_refptr<Bus> bus{new Bus{options}};
+
+  if (!bus->Connect())
+    return false;
+
+  proxy_.reset(new UpdateEngineInterfaceProxy{bus});
+  return true;
+}
+
+bool DBusUpdateEngineClient::AttemptUpdate(const string& in_app_version,
+                                           const string& in_omaha_url,
+                                           bool at_user_request) {
+  return proxy_->AttemptUpdateWithFlags(
+      in_app_version,
+      in_omaha_url,
+      (at_user_request) ? 0 : kAttemptUpdateFlagNonInteractive,
+      nullptr);
+}
+
+bool DBusUpdateEngineClient::GetStatus(int64_t* out_last_checked_time,
+                                       double* out_progress,
+                                       UpdateStatus* out_update_status,
+                                       string* out_new_version,
+                                       int64_t* out_new_size) const {
+  string status_as_string;
+  const bool success = proxy_->GetStatus(out_last_checked_time,
+                                         out_progress,
+                                         &status_as_string,
+                                         out_new_version,
+                                         out_new_size,
+                                         nullptr);
+  if (!success) {
+    return false;
+  }
+
+  return StringToUpdateStatus(status_as_string, out_update_status);
+}
+
+bool DBusUpdateEngineClient::SetCohortHint(const string& cohort_hint) {
+  return proxy_->SetCohortHint(cohort_hint, nullptr);
+}
+
+bool DBusUpdateEngineClient::GetCohortHint(string* cohort_hint) const {
+  return proxy_->GetCohortHint(cohort_hint, nullptr);
+}
+
+bool DBusUpdateEngineClient::SetUpdateOverCellularPermission(bool allowed) {
+  return proxy_->SetUpdateOverCellularPermission(allowed, nullptr);
+}
+
+bool DBusUpdateEngineClient::GetUpdateOverCellularPermission(
+    bool* allowed) const {
+  return proxy_->GetUpdateOverCellularPermission(allowed, nullptr);
+}
+
+bool DBusUpdateEngineClient::SetP2PUpdatePermission(bool enabled) {
+  return proxy_->SetP2PUpdatePermission(enabled, nullptr);
+}
+
+bool DBusUpdateEngineClient::GetP2PUpdatePermission(bool* enabled) const {
+  return proxy_->GetP2PUpdatePermission(enabled, nullptr);
+}
+
+bool DBusUpdateEngineClient::Rollback(bool powerwash) {
+  return proxy_->AttemptRollback(powerwash, nullptr);
+}
+
+bool DBusUpdateEngineClient::GetRollbackPartition(
+    string* rollback_partition) const {
+  return proxy_->GetRollbackPartition(rollback_partition, nullptr);
+}
+
+bool DBusUpdateEngineClient::GetPrevVersion(string* prev_version) const {
+  return proxy_->GetPrevVersion(prev_version, nullptr);
+}
+
+void DBusUpdateEngineClient::RebootIfNeeded() {
+  bool ret = proxy_->RebootIfNeeded(nullptr);
+  if (!ret) {
+    // Reboot error code doesn't necessarily mean that a reboot
+    // failed. For example, D-Bus may be shutdown before we receive the
+    // result.
+    LOG(INFO) << "RebootIfNeeded() failure ignored.";
+  }
+}
+
+bool DBusUpdateEngineClient::ResetStatus() {
+  return proxy_->ResetStatus(nullptr);
+}
+
+void DBusUpdateEngineClient::DBusStatusHandlersRegistered(
+    const string& interface,
+    const string& signal_name,
+    bool success) const {
+  if (!success) {
+    for (auto handler : handlers_) {
+      handler->IPCError("Could not connect to" + signal_name +
+                        " on " + interface);
+    }
+  } else {
+    StatusUpdateHandlersRegistered(nullptr);
+  }
+}
+
+void DBusUpdateEngineClient::StatusUpdateHandlersRegistered(
+    StatusUpdateHandler* handler) const {
+  int64_t last_checked_time;
+  double progress;
+  UpdateStatus update_status;
+  string new_version;
+  int64_t new_size;
+
+  if (!GetStatus(&last_checked_time,
+                 &progress,
+                 &update_status,
+                 &new_version,
+                 &new_size)) {
+    handler->IPCError("Could not query current status");
+    return;
+  }
+
+  std::vector<update_engine::StatusUpdateHandler*> just_handler = {handler};
+  for (auto h : handler ? just_handler : handlers_) {
+    h->HandleStatusUpdate(
+        last_checked_time, progress, update_status, new_version, new_size);
+  }
+}
+
+void DBusUpdateEngineClient::RunStatusUpdateHandlers(
+    int64_t last_checked_time,
+    double progress,
+    const string& current_operation,
+    const string& new_version,
+    int64_t new_size) {
+  UpdateStatus status;
+  StringToUpdateStatus(current_operation, &status);
+
+  for (auto handler : handlers_) {
+    handler->HandleStatusUpdate(
+        last_checked_time, progress, status, new_version, new_size);
+  }
+}
+
+bool DBusUpdateEngineClient::UnregisterStatusUpdateHandler(
+    StatusUpdateHandler* handler) {
+  auto it = std::find(handlers_.begin(), handlers_.end(), handler);
+  if (it != handlers_.end()) {
+    handlers_.erase(it);
+    return true;
+  }
+
+  return false;
+}
+
+bool DBusUpdateEngineClient::RegisterStatusUpdateHandler(
+    StatusUpdateHandler* handler) {
+  if (!base::MessageLoopForIO::current()) {
+    LOG(FATAL) << "Cannot get UpdateEngineClient outside of message loop.";
+    return false;
+  }
+
+  handlers_.push_back(handler);
+
+  if (dbus_handler_registered_) {
+    StatusUpdateHandlersRegistered(handler);
+    return true;
+  }
+
+  proxy_->RegisterStatusUpdateSignalHandler(
+      base::Bind(&DBusUpdateEngineClient::RunStatusUpdateHandlers,
+                 base::Unretained(this)),
+      base::Bind(&DBusUpdateEngineClient::DBusStatusHandlersRegistered,
+                 base::Unretained(this)));
+
+  dbus_handler_registered_ = true;
+
+  return true;
+}
+
+bool DBusUpdateEngineClient::SetTargetChannel(const string& in_target_channel,
+                                              bool allow_powerwash) {
+  return proxy_->SetChannel(in_target_channel, allow_powerwash, nullptr);
+}
+
+bool DBusUpdateEngineClient::GetTargetChannel(string* out_channel) const {
+  return proxy_->GetChannel(false,  // Get the target channel.
+                            out_channel,
+                            nullptr);
+}
+
+bool DBusUpdateEngineClient::GetChannel(string* out_channel) const {
+  return proxy_->GetChannel(true,  // Get the current channel.
+                            out_channel,
+                            nullptr);
+}
+
+bool DBusUpdateEngineClient::GetLastAttemptError(
+    int32_t* last_attempt_error) const {
+  return proxy_->GetLastAttemptError(last_attempt_error, nullptr);
+}
+
+bool DBusUpdateEngineClient::GetEolStatus(int32_t* eol_status) const {
+  return proxy_->GetEolStatus(eol_status, nullptr);
+}
+
+#ifdef USE_NESTLABS
+bool DBusUpdateEngineClient::MarkBootSuccessful() const {
+  return proxy_->MarkBootSuccessful(nullptr);
+}
+#endif // USE_NESTLABS
+
+}  // namespace internal
+}  // namespace update_engine
diff --git a/update_engine/client_library/client_dbus.h b/update_engine/client_library/client_dbus.h
new file mode 100644
index 0000000..cec1665
--- /dev/null
+++ b/update_engine/client_library/client_dbus.h
@@ -0,0 +1,109 @@
+//
+// 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_CLIENT_LIBRARY_CLIENT_DBUS_H_
+#define UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_DBUS_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/client_library/include/update_engine/client.h"
+#include "update_engine/dbus-proxies.h"
+
+namespace update_engine {
+namespace internal {
+
+class DBusUpdateEngineClient : public UpdateEngineClient {
+ public:
+  DBusUpdateEngineClient() = default;
+  bool Init();
+
+  virtual ~DBusUpdateEngineClient() = default;
+
+  bool AttemptUpdate(const std::string& app_version,
+                     const std::string& omaha_url,
+                     bool at_user_request) override;
+
+  bool GetStatus(int64_t* out_last_checked_time,
+                 double* out_progress,
+                 UpdateStatus* out_update_status,
+                 std::string* out_new_version,
+                 int64_t* out_new_size) const override;
+
+  bool SetCohortHint(const std::string& cohort_hint) override;
+  bool GetCohortHint(std::string* cohort_hint) const override;
+
+  bool SetUpdateOverCellularPermission(bool allowed) override;
+  bool GetUpdateOverCellularPermission(bool* allowed) const override;
+
+  bool SetP2PUpdatePermission(bool enabled) override;
+  bool GetP2PUpdatePermission(bool* enabled) const override;
+
+  bool Rollback(bool powerwash) override;
+
+  bool GetRollbackPartition(std::string* rollback_partition) const override;
+
+  void RebootIfNeeded() override;
+
+  bool GetPrevVersion(std::string* prev_version) const override;
+
+  bool ResetStatus() override;
+
+  bool SetTargetChannel(const std::string& target_channel,
+                        bool allow_powerwash) override;
+
+  bool GetTargetChannel(std::string* out_channel) const override;
+
+  bool GetChannel(std::string* out_channel) const override;
+
+  bool RegisterStatusUpdateHandler(StatusUpdateHandler* handler) override;
+  bool UnregisterStatusUpdateHandler(StatusUpdateHandler* handler) override;
+
+  bool GetLastAttemptError(int32_t* last_attempt_error) const override;
+
+  bool GetEolStatus(int32_t* eol_status) const override;
+
+ private:
+  void DBusStatusHandlersRegistered(const std::string& interface,
+                                    const std::string& signal_name,
+                                    bool success) const;
+
+  // Send an initial event to new StatusUpdateHandlers. If the handler argument
+  // is not nullptr, only that handler receives the event. Otherwise all
+  // registered handlers receive the event.
+  void StatusUpdateHandlersRegistered(StatusUpdateHandler* handler) const;
+
+  void RunStatusUpdateHandlers(int64_t last_checked_time,
+                               double progress,
+                               const std::string& current_operation,
+                               const std::string& new_version,
+                               int64_t new_size);
+
+  std::unique_ptr<org::chromium::UpdateEngineInterfaceProxy> proxy_;
+  std::vector<update_engine::StatusUpdateHandler*> handlers_;
+  bool dbus_handler_registered_{false};
+
+  DISALLOW_COPY_AND_ASSIGN(DBusUpdateEngineClient);
+};  // class DBusUpdateEngineClient
+
+}  // namespace internal
+}  // namespace update_engine
+
+#endif  // UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_DBUS_H_
diff --git a/update_engine/client_library/client_dbus_nestlabs.cc b/update_engine/client_library/client_dbus_nestlabs.cc
new file mode 100644
index 0000000..64ede0e
--- /dev/null
+++ b/update_engine/client_library/client_dbus_nestlabs.cc
@@ -0,0 +1,200 @@
+//
+// 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/client_library/client_dbus_nestlabs.h"
+
+#include <base/message_loop/message_loop.h>
+
+#include <dbus/bus.h>
+#include <update_engine/dbus-constants-nestlabs.h>
+
+#include "update_engine/update_status_utils.h"
+
+using chromeos_update_engine::StringToUpdateStatus;
+using dbus::Bus;
+using com::nestlabs::UpdateEngineInterfaceProxy;
+using std::string;
+
+namespace update_engine {
+namespace internal {
+
+bool DBusUpdateEngineNestlabsClient::Init() {
+  Bus::Options options;
+  options.bus_type = Bus::SYSTEM;
+  scoped_refptr<Bus> bus{new Bus{options}};
+
+  if (!bus->Connect())
+    return false;
+
+  proxy_.reset(new UpdateEngineInterfaceProxy{bus, update_engine::kUpdateEngineServiceName});
+  return true;
+}
+
+bool DBusUpdateEngineNestlabsClient::AttemptUpdate(const string& in_app_version,
+                                           const string& in_url,
+                                           bool at_user_request) {
+  return proxy_->AttemptUpdate(
+      in_app_version,
+      in_url,
+      nullptr);
+}
+
+bool DBusUpdateEngineNestlabsClient::GetStatus(int64_t* out_last_checked_time,
+                                       double* out_progress,
+                                       UpdateStatus* out_update_status,
+                                       string* out_new_version,
+                                       int64_t* out_new_size) const {
+  string status_as_string;
+  const bool success = proxy_->GetStatus(out_last_checked_time,
+                                         out_progress,
+                                         &status_as_string,
+                                         out_new_version,
+                                         out_new_size,
+                                         nullptr);
+  if (!success) {
+    return false;
+  }
+
+  return StringToUpdateStatus(status_as_string, out_update_status);
+}
+
+bool DBusUpdateEngineNestlabsClient::Rollback(bool powerwash) {
+  return proxy_->AttemptRollback(powerwash, nullptr);
+}
+
+bool DBusUpdateEngineNestlabsClient::GetRollbackPartition(
+    string* rollback_partition) const {
+  return proxy_->GetRollbackPartition(rollback_partition, nullptr);
+}
+
+bool DBusUpdateEngineNestlabsClient::GetPrevVersion(string* prev_version) const {
+  return proxy_->GetPrevVersion(prev_version, nullptr);
+}
+
+void DBusUpdateEngineNestlabsClient::RebootIfNeeded() {
+  bool ret = proxy_->RebootIfNeeded(nullptr);
+  if (!ret) {
+    // Reboot error code doesn't necessarily mean that a reboot
+    // failed. For example, D-Bus may be shutdown before we receive the
+    // result.
+    LOG(INFO) << "RebootIfNeeded() failure ignored.";
+  }
+}
+
+bool DBusUpdateEngineNestlabsClient::ResetStatus() {
+  return proxy_->ResetStatus(nullptr);
+}
+
+void DBusUpdateEngineNestlabsClient::DBusStatusHandlersRegistered(
+    const string& interface,
+    const string& signal_name,
+    bool success) const {
+  if (!success) {
+    for (auto handler : handlers_) {
+      handler->IPCError("Could not connect to" + signal_name +
+                        " on " + interface);
+    }
+  } else {
+    StatusUpdateHandlersRegistered(nullptr);
+  }
+}
+
+void DBusUpdateEngineNestlabsClient::StatusUpdateHandlersRegistered(
+    StatusUpdateHandler* handler) const {
+  int64_t last_checked_time;
+  double progress;
+  UpdateStatus update_status;
+  string new_version;
+  int64_t new_size;
+
+  if (!GetStatus(&last_checked_time,
+                 &progress,
+                 &update_status,
+                 &new_version,
+                 &new_size)) {
+    handler->IPCError("Could not query current status");
+    return;
+  }
+
+  std::vector<update_engine::StatusUpdateHandler*> just_handler = {handler};
+  for (auto h : handler ? just_handler : handlers_) {
+    h->HandleStatusUpdate(
+        last_checked_time, progress, update_status, new_version, new_size);
+  }
+}
+
+void DBusUpdateEngineNestlabsClient::RunStatusUpdateHandlers(
+    int64_t last_checked_time,
+    double progress,
+    const string& current_operation,
+    const string& new_version,
+    int64_t new_size) {
+  UpdateStatus status;
+  StringToUpdateStatus(current_operation, &status);
+
+  for (auto handler : handlers_) {
+    handler->HandleStatusUpdate(
+        last_checked_time, progress, status, new_version, new_size);
+  }
+}
+
+bool DBusUpdateEngineNestlabsClient::UnregisterStatusUpdateHandler(
+    StatusUpdateHandler* handler) {
+  auto it = std::find(handlers_.begin(), handlers_.end(), handler);
+  if (it != handlers_.end()) {
+    handlers_.erase(it);
+    return true;
+  }
+
+  return false;
+}
+
+bool DBusUpdateEngineNestlabsClient::RegisterStatusUpdateHandler(
+    StatusUpdateHandler* handler) {
+  if (!base::MessageLoopForIO::current()) {
+    LOG(FATAL) << "Cannot get UpdateEngineClient outside of message loop.";
+    return false;
+  }
+
+  handlers_.push_back(handler);
+
+  if (dbus_handler_registered_) {
+    StatusUpdateHandlersRegistered(handler);
+    return true;
+  }
+
+  proxy_->RegisterStatusUpdateSignalHandler(
+      base::Bind(&DBusUpdateEngineNestlabsClient::RunStatusUpdateHandlers,
+                 base::Unretained(this)),
+      base::Bind(&DBusUpdateEngineNestlabsClient::DBusStatusHandlersRegistered,
+                 base::Unretained(this)));
+
+  dbus_handler_registered_ = true;
+
+  return true;
+}
+
+bool DBusUpdateEngineNestlabsClient::GetLastAttemptError(
+    int32_t* last_attempt_error) const {
+  return proxy_->GetLastAttemptError(last_attempt_error, nullptr);
+}
+
+bool DBusUpdateEngineNestlabsClient::MarkBootSuccessful() const {
+  return proxy_->MarkBootSuccessful(nullptr);
+}
+
+}  // namespace internal
+}  // namespace update_engine
diff --git a/update_engine/client_library/client_dbus_nestlabs.h b/update_engine/client_library/client_dbus_nestlabs.h
new file mode 100644
index 0000000..5d3fc1b
--- /dev/null
+++ b/update_engine/client_library/client_dbus_nestlabs.h
@@ -0,0 +1,113 @@
+//
+// 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_CLIENT_LIBRARY_CLIENT_DBUS_NESTLABS_H_
+#define UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_DBUS_NESTLABS_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/client_library/include/update_engine/client.h"
+#include "update_engine/dbus-proxies.h"
+
+namespace update_engine {
+namespace internal {
+
+class DBusUpdateEngineNestlabsClient : public UpdateEngineClient {
+ public:
+  DBusUpdateEngineNestlabsClient() = default;
+  bool Init();
+
+  virtual ~DBusUpdateEngineNestlabsClient() = default;
+
+  bool AttemptUpdate(const std::string& app_version,
+                     const std::string& url,
+                     bool at_user_request) override;
+
+  bool GetStatus(int64_t* out_last_checked_time,
+                 double* out_progress,
+                 UpdateStatus* out_update_status,
+                 std::string* out_new_version,
+                 int64_t* out_new_size) const override;
+
+  bool Rollback(bool powerwash) override;
+
+  bool GetRollbackPartition(std::string* rollback_partition) const override;
+
+  void RebootIfNeeded() override;
+
+  bool GetPrevVersion(std::string* prev_version) const override;
+
+  bool ResetStatus() override;
+
+  bool RegisterStatusUpdateHandler(StatusUpdateHandler* handler) override;
+  bool UnregisterStatusUpdateHandler(StatusUpdateHandler* handler) override;
+
+  bool GetLastAttemptError(int32_t* last_attempt_error) const override;
+
+  // not supported API
+  bool SetCohortHint(const std::string& cohort_hint) override { return false; };
+  bool GetCohortHint(std::string* cohort_hint) const override { return false; };
+
+  bool SetUpdateOverCellularPermission(bool allowed) override { return false; };
+  bool GetUpdateOverCellularPermission(bool* allowed) const override { return false; };
+
+  bool SetP2PUpdatePermission(bool enabled) override { return false; };
+  bool GetP2PUpdatePermission(bool* enabled) const override { return false; };
+
+
+  bool SetTargetChannel(const std::string& target_channel,
+                        bool allow_powerwash) override { return false; };
+
+  bool GetTargetChannel(std::string* out_channel) const override { return false; };
+
+  bool GetChannel(std::string* out_channel) const override { return false; };
+
+  bool GetEolStatus(int32_t* eol_status) const override { return false; };
+
+  bool MarkBootSuccessful() const override;
+
+ private:
+  void DBusStatusHandlersRegistered(const std::string& interface,
+                                    const std::string& signal_name,
+                                    bool success) const;
+
+  // Send an initial event to new StatusUpdateHandlers. If the handler argument
+  // is not nullptr, only that handler receives the event. Otherwise all
+  // registered handlers receive the event.
+  void StatusUpdateHandlersRegistered(StatusUpdateHandler* handler) const;
+
+  void RunStatusUpdateHandlers(int64_t last_checked_time,
+                               double progress,
+                               const std::string& current_operation,
+                               const std::string& new_version,
+                               int64_t new_size);
+
+  std::unique_ptr<com::nestlabs::UpdateEngineInterfaceProxy> proxy_;
+  std::vector<update_engine::StatusUpdateHandler*> handlers_;
+  bool dbus_handler_registered_{false};
+
+  DISALLOW_COPY_AND_ASSIGN(DBusUpdateEngineNestlabsClient);
+};  // class DBusUpdateEngineNestlabsClient
+
+}  // namespace internal
+}  // namespace update_engine
+
+#endif  // UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_DBUS_NESTLABS_H_
diff --git a/update_engine/client_library/include/update_engine/client.h b/update_engine/client_library/include/update_engine/client.h
new file mode 100644
index 0000000..7fe946e
--- /dev/null
+++ b/update_engine/client_library/include/update_engine/client.h
@@ -0,0 +1,140 @@
+//
+// 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_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_CLIENT_H_
+#define UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_CLIENT_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "update_engine/status_update_handler.h"
+#include "update_engine/update_status.h"
+
+namespace update_engine {
+
+class UpdateEngineClient {
+ public:
+  static std::unique_ptr<UpdateEngineClient> CreateInstance();
+
+  virtual ~UpdateEngineClient() = default;
+
+  // Force the update_engine to attempt an update.
+  // |app_version|
+  //     Attempt to update to this version.  An empty string indicates that
+  //     update engine should pick the most recent image on the current channel.
+  // |omaha_url|
+  //     Force update_engine to look for updates from the given server.  Passing
+  //     empty indicates update_engine should get this parameter from its
+  //     config.  Note that update_engine will ignore this parameter in
+  //     production mode to avoid pulling untrusted updates.
+  // |at_user_request|
+  //     This update was directly requested by the user.
+  virtual bool AttemptUpdate(const std::string& app_version,
+                             const std::string& omaha_url,
+                             bool at_user_request) = 0;
+
+  // Returns the current status of the Update Engine.
+  //
+  // |out_last_checked_time|
+  //     the last time the update engine checked for an update in seconds since
+  //     the epoc.
+  // |out_progress|
+  //     when downloading an update, this is calculated as
+  //     (number of bytes received) / (total bytes).
+  // |out_update_status|
+  //     See update_status.h.
+  // |out_new_version|
+  //     string version of the new system image.
+  // |out_new_size|
+  //     number of bytes in the new system image.
+  virtual bool GetStatus(int64_t* out_last_checked_time,
+                         double* out_progress,
+                         UpdateStatus* out_update_status,
+                         std::string* out_new_version,
+                         int64_t* out_new_size) const = 0;
+
+  // Getter and setter for the cohort hint.
+  virtual bool SetCohortHint(const std::string& cohort_hint) = 0;
+  virtual bool GetCohortHint(std::string* cohort_hint) const = 0;
+
+  // Getter and setter for the updates over cellular connections.
+  virtual bool SetUpdateOverCellularPermission(bool allowed) = 0;
+  virtual bool GetUpdateOverCellularPermission(bool* allowed) const = 0;
+
+  // Getter and setter for the updates from P2P permission.
+  virtual bool SetP2PUpdatePermission(bool enabled) = 0;
+  virtual bool GetP2PUpdatePermission(bool* enabled) const = 0;
+
+  // Attempt a rollback. Set 'powerwash' to reset the device while rolling
+  // back.
+  virtual bool Rollback(bool powerwash) = 0;
+
+  // Get the rollback partition if available. Gives empty string if not.
+  virtual bool GetRollbackPartition(std::string* rollback_partition) const = 0;
+
+  // Reboot the system if needed.
+  virtual void RebootIfNeeded() = 0;
+
+  // Get the previous version
+  virtual bool GetPrevVersion(std::string* prev_version) const = 0;
+
+  // Resets the status of the Update Engine
+  virtual bool ResetStatus() = 0;
+
+  // Changes the current channel of the device to the target channel.
+  virtual bool SetTargetChannel(const std::string& target_channel,
+                                bool allow_powerwash) = 0;
+
+  // Get the channel the device will switch to on reboot.
+  virtual bool GetTargetChannel(std::string* out_channel) const = 0;
+
+  // Get the channel the device is currently on.
+  virtual bool GetChannel(std::string* out_channel) const = 0;
+
+  // Handle status updates. The handler must exist until the client is
+  // destroyed or UnregisterStatusUpdateHandler is called for it. Its IPCError
+  // method will be called if the handler could not be registered. Otherwise
+  // its HandleStatusUpdate method will be called every time update_engine's
+  // status changes. Will always report the status on registration to prevent
+  // race conditions.
+  virtual bool RegisterStatusUpdateHandler(StatusUpdateHandler* handler) = 0;
+
+  // Unregister a status update handler
+  virtual bool UnregisterStatusUpdateHandler(StatusUpdateHandler* handler) = 0;
+
+  // Get the last UpdateAttempt error code.
+  virtual bool GetLastAttemptError(int32_t* last_attempt_error) const = 0;
+
+  // Get the current end-of-life status code. See EolStatus enum for details.
+  virtual bool GetEolStatus(int32_t* eol_status) const = 0;
+
+#ifdef USE_NESTLABS
+  virtual bool MarkBootSuccessful() const = 0;
+#endif // USE_NESTLABS
+
+ protected:
+  // Use CreateInstance().
+  UpdateEngineClient() = default;
+
+ private:
+  UpdateEngineClient(const UpdateEngineClient&) = delete;
+  void operator=(const UpdateEngineClient&) = delete;
+};  // class UpdateEngineClient
+
+}  // namespace update_engine
+
+#endif  // UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_CLIENT_H_
diff --git a/update_engine/client_library/include/update_engine/status_update_handler.h b/update_engine/client_library/include/update_engine/status_update_handler.h
new file mode 100644
index 0000000..d5b8cdb
--- /dev/null
+++ b/update_engine/client_library/include/update_engine/status_update_handler.h
@@ -0,0 +1,47 @@
+//
+// 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_CLIENT_LIBRARY_INCLUDE_STATUS_UPDATE_HANDLER_H_
+#define UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_STATUS_UPDATE_HANDLER_H_
+
+#include <string>
+
+#include "update_engine/client.h"
+#include "update_engine/update_status.h"
+
+namespace update_engine {
+
+// Handles update_engine status changes. An instance of this class can be
+// registered with UpdateEngineClient and will respond to any update_engine
+// status changes.
+class StatusUpdateHandler {
+ public:
+  virtual ~StatusUpdateHandler() = default;
+
+  // Runs when we fail to register the handler due to an IPC error.
+  virtual void IPCError(const std::string& error) = 0;
+
+  // Runs every time update_engine reports a status change.
+  virtual void HandleStatusUpdate(int64_t last_checked_time,
+                                  double progress,
+                                  UpdateStatus current_operation,
+                                  const std::string& new_version,
+                                  int64_t new_size) = 0;
+};
+
+}  // namespace update_engine
+
+#endif  // UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_STATUS_UPDATE_HANDLER_H_
diff --git a/update_engine/client_library/include/update_engine/update_status.h b/update_engine/client_library/include/update_engine/update_status.h
new file mode 100644
index 0000000..3e9af5b
--- /dev/null
+++ b/update_engine/client_library/include/update_engine/update_status.h
@@ -0,0 +1,37 @@
+//
+// 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_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_UPDATE_STATUS_H_
+#define UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_UPDATE_STATUS_H_
+
+namespace update_engine {
+
+enum class UpdateStatus {
+  IDLE = 0,
+  CHECKING_FOR_UPDATE,
+  UPDATE_AVAILABLE,
+  DOWNLOADING,
+  VERIFYING,
+  FINALIZING,
+  UPDATED_NEED_REBOOT,
+  REPORTING_ERROR_EVENT,
+  ATTEMPTING_ROLLBACK,
+  DISABLED,
+};
+
+}  // namespace update_engine
+
+#endif  // UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_UPDATE_STATUS_H_
diff --git a/update_engine/common/action.h b/update_engine/common/action.h
new file mode 100644
index 0000000..6c88216
--- /dev/null
+++ b/update_engine/common/action.h
@@ -0,0 +1,227 @@
+//
+// Copyright (C) 2009 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_COMMON_ACTION_H_
+#define UPDATE_ENGINE_COMMON_ACTION_H_
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/action_processor.h"
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+//
+// Readers may want to consult this wiki page from the Update Engine site:
+// http://code.google.com/p/update-engine/wiki/ActionProcessor
+// Although it's referring to the Objective-C KSAction* classes, much
+// applies here as well.
+//
+// How it works:
+//
+// First off, there is only one thread and all I/O should be asynchronous.
+// A message loop blocks whenever there is no work to be done. This happens
+// where there is no CPU work to be done and no I/O ready to transfer in or
+// out. Two kinds of events can wake up the message loop: timer alarm or file
+// descriptors. If either of these happens, the message loop finds out the owner
+// of what fired and calls the appropriate code to handle it. As such, all the
+// code in the Action* classes and the code that is calls is non-blocking.
+//
+// An ActionProcessor contains a queue of Actions to perform. When
+// ActionProcessor::StartProcessing() is called, it executes the first action.
+// Each action tells the processor when it has completed, which causes the
+// Processor to execute the next action. ActionProcessor may have a delegate
+// (an object of type ActionProcessorDelegate). If it does, the delegate
+// is called to be notified of events as they happen.
+//
+// ActionPipe classes
+//
+// See action_pipe.h
+//
+// ActionTraits
+//
+// We need to use an extra class ActionTraits. ActionTraits is a simple
+// templated class that contains only two typedefs: OutputObjectType and
+// InputObjectType. Each action class also has two typedefs of the same name
+// that are of the same type. So, to get the input/output types of, e.g., the
+// DownloadAction class, we look at the type of
+// DownloadAction::InputObjectType.
+//
+// Each concrete Action class derives from Action<T>. This means that during
+// template instantiation of Action<T>, T is declared but not defined, which
+// means that T::InputObjectType (and OutputObjectType) is not defined.
+// However, the traits class is constructed in such a way that it will be
+// template instantiated first, so Action<T> *can* find the types it needs by
+// consulting ActionTraits<T>::InputObjectType (and OutputObjectType).
+// This is why the ActionTraits classes are needed.
+
+namespace chromeos_update_engine {
+
+// It is handy to have a non-templated base class of all Actions.
+class AbstractAction {
+ public:
+  AbstractAction() : processor_(nullptr) {}
+  virtual ~AbstractAction() = default;
+
+  // Begin performing the action. Since this code is asynchronous, when this
+  // method returns, it means only that the action has started, not necessarily
+  // completed. However, it's acceptable for this method to perform the
+  // action synchronously; Action authors should understand the implications
+  // of synchronously performing, though, because this is a single-threaded
+  // app, the entire process will be blocked while the action performs.
+  //
+  // When the action is complete, it must call
+  // ActionProcessor::ActionComplete(this); to notify the processor that it's
+  // done.
+  virtual void PerformAction() = 0;
+
+  // Called on ActionProcess::ActionComplete() by ActionProcessor.
+  virtual void ActionCompleted(ErrorCode code) {}
+
+  // Called by the ActionProcessor to tell this Action which processor
+  // it belongs to.
+  void SetProcessor(ActionProcessor* processor) {
+    if (processor)
+      CHECK(!processor_);
+    else
+      CHECK(processor_);
+    processor_ = processor;
+  }
+
+  // Returns true iff the action is the current action of its ActionProcessor.
+  bool IsRunning() const {
+    if (!processor_)
+      return false;
+    return processor_->current_action() == this;
+  }
+
+  // Called on asynchronous actions if canceled. Actions may implement if
+  // there's any cleanup to do. There is no need to call
+  // ActionProcessor::ActionComplete() because the processor knows this
+  // action is terminating.
+  // Only the ActionProcessor should call this.
+  virtual void TerminateProcessing() {}
+
+  // Called on asynchronous actions if the processing is suspended and resumed,
+  // respectively. These methods are called by the ActionProcessor and should
+  // not be explicitly called.
+  // The action may still call ActionCompleted() once the action is completed
+  // while the processing is suspended, for example if suspend/resume is not
+  // implemented for the given action.
+  virtual void SuspendAction() {}
+  virtual void ResumeAction() {}
+
+  // These methods are useful for debugging. TODO(adlr): consider using
+  // std::type_info for this?
+  // Type() returns a string of the Action type. I.e., for DownloadAction,
+  // Type() would return "DownloadAction".
+  virtual std::string Type() const = 0;
+
+ protected:
+  // A weak pointer to the processor that owns this Action.
+  ActionProcessor* processor_;
+};
+
+// Forward declare a couple classes we use.
+template<typename T>
+class ActionPipe;
+template<typename T>
+class ActionTraits;
+
+template<typename SubClass>
+class Action : public AbstractAction {
+ public:
+  ~Action() override {}
+
+  // Attaches an input pipe to this Action. This is optional; an Action
+  // doesn't need to have an input pipe. The input pipe must be of the type
+  // of object that this class expects.
+  // This is generally called by ActionPipe::Bond()
+  void set_in_pipe(
+      // this type is a fancy way of saying: a shared_ptr to an
+      // ActionPipe<InputObjectType>.
+      const std::shared_ptr<ActionPipe<
+          typename ActionTraits<SubClass>::InputObjectType>>& in_pipe) {
+    in_pipe_ = in_pipe;
+  }
+
+  // Attaches an output pipe to this Action. This is optional; an Action
+  // doesn't need to have an output pipe. The output pipe must be of the type
+  // of object that this class expects.
+  // This is generally called by ActionPipe::Bond()
+  void set_out_pipe(
+      // this type is a fancy way of saying: a shared_ptr to an
+      // ActionPipe<OutputObjectType>.
+      const std::shared_ptr<ActionPipe<
+          typename ActionTraits<SubClass>::OutputObjectType>>& out_pipe) {
+    out_pipe_ = out_pipe;
+  }
+
+  // Returns true iff there is an associated input pipe. If there's an input
+  // pipe, there's an input object, but it may have been constructed with the
+  // default ctor if the previous action didn't call SetOutputObject().
+  bool HasInputObject() const { return in_pipe_.get(); }
+
+  // returns a const reference to the object in the input pipe.
+  const typename ActionTraits<SubClass>::InputObjectType& GetInputObject()
+      const {
+    CHECK(HasInputObject());
+    return in_pipe_->contents();
+  }
+
+  // Returns true iff there's an output pipe.
+  bool HasOutputPipe() const {
+    return out_pipe_.get();
+  }
+
+  // Copies the object passed into the output pipe. It will be accessible to
+  // the next Action via that action's input pipe (which is the same as this
+  // Action's output pipe).
+  void SetOutputObject(
+      const typename ActionTraits<SubClass>::OutputObjectType& out_obj) {
+    CHECK(HasOutputPipe());
+    out_pipe_->set_contents(out_obj);
+  }
+
+  // Returns a reference to the object sitting in the output pipe.
+  const typename ActionTraits<SubClass>::OutputObjectType& GetOutputObject() {
+    CHECK(HasOutputPipe());
+    return out_pipe_->contents();
+  }
+
+ protected:
+  // We use a shared_ptr to the pipe. shared_ptr objects destroy what they
+  // point to when the last such shared_ptr object dies. We consider the
+  // Actions on either end of a pipe to "own" the pipe. When the last Action
+  // of the two dies, the ActionPipe will die, too.
+  std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::InputObjectType>>
+      in_pipe_;
+  std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::OutputObjectType>>
+      out_pipe_;
+};
+
+};  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_ACTION_H_
diff --git a/update_engine/common/action_pipe.h b/update_engine/common/action_pipe.h
new file mode 100644
index 0000000..376c2f1
--- /dev/null
+++ b/update_engine/common/action_pipe.h
@@ -0,0 +1,101 @@
+//
+// Copyright (C) 2009 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_COMMON_ACTION_PIPE_H_
+#define UPDATE_ENGINE_COMMON_ACTION_PIPE_H_
+
+#include <stdio.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+
+// This class serves as a temporary holding area for an object passed out
+// from one Action and into another Action. It's templated so that it may
+// contain any type of object that an Action outputs/inputs. Actions
+// cannot be bonded (i.e., connected with a pipe) if their output/input
+// object types differ (a compiler error will result).
+//
+// An ActionPipe is generally created with the Bond() method and owned by
+// the two Action objects. a shared_ptr is used so that when the last Action
+// pointing to an ActionPipe dies, the ActionPipe dies, too.
+
+namespace chromeos_update_engine {
+
+// Used by Actions an InputObjectType or OutputObjectType to specify that
+// for that type, no object is taken/given.
+class NoneType {};
+
+template<typename T>
+class Action;
+
+template<typename ObjectType>
+class ActionPipe {
+ public:
+  virtual ~ActionPipe() {}
+
+  // This should be called by an Action on its input pipe.
+  // Returns a reference to the stored object.
+  const ObjectType& contents() const { return contents_; }
+
+  // This should be called by an Action on its output pipe.
+  // Stores a copy of the passed object in this pipe.
+  void set_contents(const ObjectType& contents) { contents_ = contents; }
+
+  // Bonds two Actions together with a new ActionPipe. The ActionPipe is
+  // jointly owned by the two Actions and will be automatically destroyed
+  // when the last Action is destroyed.
+  template<typename FromAction, typename ToAction>
+  static void Bond(FromAction* from, ToAction* to) {
+    std::shared_ptr<ActionPipe<ObjectType>> pipe(new ActionPipe<ObjectType>);
+    from->set_out_pipe(pipe);
+
+    to->set_in_pipe(pipe);  // If you get an error on this line, then
+    // it most likely means that the From object's OutputObjectType is
+    // different from the To object's InputObjectType.
+  }
+
+ private:
+  ObjectType contents_;
+
+  // The ctor is private. This is because this class should construct itself
+  // via the static Bond() method.
+  ActionPipe() {}
+  DISALLOW_COPY_AND_ASSIGN(ActionPipe);
+};
+
+// Utility function
+template<typename FromAction, typename ToAction>
+void BondActions(FromAction* from, ToAction* to) {
+  static_assert(
+      std::is_same<typename FromAction::OutputObjectType,
+                   typename ToAction::InputObjectType>::value,
+      "FromAction::OutputObjectType doesn't match ToAction::InputObjectType");
+  ActionPipe<typename FromAction::OutputObjectType>::Bond(from, to);
+}
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_ACTION_PIPE_H_
diff --git a/update_engine/common/action_pipe_unittest.cc b/update_engine/common/action_pipe_unittest.cc
new file mode 100644
index 0000000..9bfbc83
--- /dev/null
+++ b/update_engine/common/action_pipe_unittest.cc
@@ -0,0 +1,60 @@
+//
+// Copyright (C) 2009 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/action_pipe.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include "update_engine/common/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionPipeTestAction;
+
+template<>
+class ActionTraits<ActionPipeTestAction> {
+ public:
+  typedef string OutputObjectType;
+  typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionPipeTestAction : public Action<ActionPipeTestAction> {
+ public:
+  typedef string InputObjectType;
+  typedef string OutputObjectType;
+  ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+  ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+  void PerformAction() {}
+  string Type() const { return "ActionPipeTestAction"; }
+};
+
+class ActionPipeTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionPipeTest, SimpleTest) {
+  ActionPipeTestAction a, b;
+  BondActions(&a, &b);
+  a.out_pipe()->set_contents("foo");
+  EXPECT_EQ("foo", b.in_pipe()->contents());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/action_processor.cc b/update_engine/common/action_processor.cc
new file mode 100644
index 0000000..3549e08
--- /dev/null
+++ b/update_engine/common/action_processor.cc
@@ -0,0 +1,147 @@
+//
+// Copyright (C) 2009 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/action_processor.h"
+
+#include <string>
+
+#include <base/logging.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/error_code_utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+ActionProcessor::~ActionProcessor() {
+  if (IsRunning())
+    StopProcessing();
+  for (auto action : actions_)
+    action->SetProcessor(nullptr);
+}
+
+void ActionProcessor::EnqueueAction(AbstractAction* action) {
+  actions_.push_back(action);
+  action->SetProcessor(this);
+}
+
+void ActionProcessor::StartProcessing() {
+  CHECK(!IsRunning());
+  if (!actions_.empty()) {
+    current_action_ = actions_.front();
+    LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
+    actions_.pop_front();
+    current_action_->PerformAction();
+  }
+}
+
+void ActionProcessor::StopProcessing() {
+  CHECK(IsRunning());
+  if (current_action_) {
+    current_action_->TerminateProcessing();
+    current_action_->SetProcessor(nullptr);
+  }
+  LOG(INFO) << "ActionProcessor: aborted "
+            << (current_action_ ? current_action_->Type() : "")
+            << (suspended_ ? " while suspended" : "");
+  current_action_ = nullptr;
+  suspended_ = false;
+  // Delete all the actions before calling the delegate.
+  for (auto action : actions_)
+    action->SetProcessor(nullptr);
+  actions_.clear();
+  if (delegate_)
+    delegate_->ProcessingStopped(this);
+}
+
+void ActionProcessor::SuspendProcessing() {
+  // No current_action_ when not suspended means that the action processor was
+  // never started or already finished.
+  if (suspended_ || !current_action_) {
+    LOG(WARNING) << "Called SuspendProcessing while not processing.";
+    return;
+  }
+  suspended_ = true;
+
+  // If there's a current action we should notify it that it should suspend, but
+  // the action can ignore that and terminate at any point.
+  LOG(INFO) << "ActionProcessor: suspending " << current_action_->Type();
+  current_action_->SuspendAction();
+}
+
+void ActionProcessor::ResumeProcessing() {
+  if (!suspended_) {
+    LOG(WARNING) << "Called ResumeProcessing while not suspended.";
+    return;
+  }
+  suspended_ = false;
+  if (current_action_) {
+    // The current_action_ did not call ActionComplete while suspended, so we
+    // should notify it of the resume operation.
+    LOG(INFO) << "ActionProcessor: resuming " << current_action_->Type();
+    current_action_->ResumeAction();
+  } else {
+    // The last action called ActionComplete while suspended, so there is
+    // already a log message with the type of the finished action. We simply
+    // state that we are resuming processing and the next function will log the
+    // start of the next action or processing completion.
+    LOG(INFO) << "ActionProcessor: resuming processing";
+    StartNextActionOrFinish(suspended_error_code_);
+  }
+}
+
+void ActionProcessor::ActionComplete(AbstractAction* actionptr,
+                                     ErrorCode code) {
+  CHECK_EQ(actionptr, current_action_);
+  if (delegate_)
+    delegate_->ActionCompleted(this, actionptr, code);
+  string old_type = current_action_->Type();
+  current_action_->ActionCompleted(code);
+  current_action_->SetProcessor(nullptr);
+  current_action_ = nullptr;
+  LOG(INFO) << "ActionProcessor: finished "
+            << (actions_.empty() ? "last action " : "") << old_type
+            << (suspended_ ? " while suspended" : "")
+            << " with code " << utils::ErrorCodeToString(code);
+  if (!actions_.empty() && code != ErrorCode::kSuccess) {
+    LOG(INFO) << "ActionProcessor: Aborting processing due to failure.";
+    actions_.clear();
+  }
+  if (suspended_) {
+    // If an action finished while suspended we don't start the next action (or
+    // terminate the processing) until the processor is resumed. This condition
+    // will be flagged by a nullptr current_action_ while suspended_ is true.
+    suspended_error_code_ = code;
+    return;
+  }
+  StartNextActionOrFinish(code);
+}
+
+void ActionProcessor::StartNextActionOrFinish(ErrorCode code) {
+  if (actions_.empty()) {
+    if (delegate_) {
+      delegate_->ProcessingDone(this, code);
+    }
+    return;
+  }
+  current_action_ = actions_.front();
+  actions_.pop_front();
+  LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
+  current_action_->PerformAction();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/action_processor.h b/update_engine/common/action_processor.h
new file mode 100644
index 0000000..c9c179e
--- /dev/null
+++ b/update_engine/common/action_processor.h
@@ -0,0 +1,144 @@
+//
+// 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_COMMON_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_COMMON_ACTION_PROCESSOR_H_
+
+#include <deque>
+
+#include <base/macros.h>
+#include <brillo/errors/error.h>
+
+#include "update_engine/common/error_code.h"
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+
+// See action.h for an overview of this class and other Action* classes.
+
+// An ActionProcessor keeps a queue of Actions and processes them in order.
+
+namespace chromeos_update_engine {
+
+class AbstractAction;
+class ActionProcessorDelegate;
+
+class ActionProcessor {
+ public:
+  ActionProcessor() = default;
+
+  virtual ~ActionProcessor();
+
+  // Starts processing the first Action in the queue. If there's a delegate,
+  // when all processing is complete, ProcessingDone() will be called on the
+  // delegate.
+  virtual void StartProcessing();
+
+  // Aborts processing. If an Action is running, it will have
+  // TerminateProcessing() called on it. The Action that was running and all the
+  // remaining actions will be lost and must be re-enqueued if this Processor is
+  // to use it.
+  void StopProcessing();
+
+  // Suspend the processing. If an Action is running, it will have the
+  // SuspendProcessing() called on it, and it should suspend operations until
+  // ResumeProcessing() is called on this class to continue. While suspended,
+  // no new actions will be started. Calling SuspendProcessing while the
+  // processing is suspended or not running this method performs no action.
+  void SuspendProcessing();
+
+  // Resume the suspended processing. If the ActionProcessor is not suspended
+  // or not running in the first place this method performs no action.
+  void ResumeProcessing();
+
+  // Returns true iff the processing was started but not yet completed nor
+  // stopped.
+  bool IsRunning() const { return current_action_ != nullptr || suspended_; }
+
+  // Adds another Action to the end of the queue.
+  virtual void EnqueueAction(AbstractAction* action);
+
+  // Sets/gets the current delegate. Set to null to remove a delegate.
+  ActionProcessorDelegate* delegate() const { return delegate_; }
+  void set_delegate(ActionProcessorDelegate *delegate) {
+    delegate_ = delegate;
+  }
+
+  // Returns a pointer to the current Action that's processing.
+  AbstractAction* current_action() const {
+    return current_action_;
+  }
+
+  // Called by an action to notify processor that it's done. Caller passes self.
+  void ActionComplete(AbstractAction* actionptr, ErrorCode code);
+
+ private:
+  // Continue processing actions (if any) after the last action terminated with
+  // the passed error code. If there are no more actions to process, the
+  // processing will terminate.
+  void StartNextActionOrFinish(ErrorCode code);
+
+  // Actions that have not yet begun processing, in the order in which
+  // they'll be processed.
+  std::deque<AbstractAction*> actions_;
+
+  // A pointer to the currently processing Action, if any.
+  AbstractAction* current_action_{nullptr};
+
+  // The ErrorCode reported by an action that was suspended but finished while
+  // being suspended. This error code is stored here to be reported back to the
+  // delegate once the processor is resumed.
+  ErrorCode suspended_error_code_{ErrorCode::kSuccess};
+
+  // Whether the action processor is or should be suspended.
+  bool suspended_{false};
+
+  // A pointer to the delegate, or null if none.
+  ActionProcessorDelegate* delegate_{nullptr};
+
+  DISALLOW_COPY_AND_ASSIGN(ActionProcessor);
+};
+
+// A delegate object can be used to be notified of events that happen
+// in an ActionProcessor. An instance of this class can be passed to an
+// ActionProcessor to register itself.
+class ActionProcessorDelegate {
+ public:
+  virtual ~ActionProcessorDelegate() = default;
+
+  // Called when all processing in an ActionProcessor has completed. A pointer
+  // to the ActionProcessor is passed. |code| is set to the exit code of the
+  // last completed action.
+  virtual void ProcessingDone(const ActionProcessor* processor,
+                              ErrorCode code) {}
+
+  // Called when processing has stopped. Does not mean that all Actions have
+  // completed. If/when all Actions complete, ProcessingDone() will be called.
+  virtual void ProcessingStopped(const ActionProcessor* processor) {}
+
+  // Called whenever an action has finished processing, either successfully
+  // or otherwise.
+  virtual void ActionCompleted(ActionProcessor* processor,
+                               AbstractAction* action,
+                               ErrorCode code) {}
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_ACTION_PROCESSOR_H_
diff --git a/update_engine/common/action_processor_unittest.cc b/update_engine/common/action_processor_unittest.cc
new file mode 100644
index 0000000..631e42d
--- /dev/null
+++ b/update_engine/common/action_processor_unittest.cc
@@ -0,0 +1,264 @@
+//
+// Copyright (C) 2009 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/action_processor.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/mock_action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionProcessorTestAction;
+
+template<>
+class ActionTraits<ActionProcessorTestAction> {
+ public:
+  typedef string OutputObjectType;
+  typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionProcessorTestAction : public Action<ActionProcessorTestAction> {
+ public:
+  typedef string InputObjectType;
+  typedef string OutputObjectType;
+  ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+  ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+  ActionProcessor* processor() { return processor_; }
+  void PerformAction() {}
+  void CompleteAction() {
+    ASSERT_TRUE(processor());
+    processor()->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  string Type() const { return "ActionProcessorTestAction"; }
+};
+
+namespace {
+class MyActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  explicit MyActionProcessorDelegate(const ActionProcessor* processor)
+      : processor_(processor),
+        processing_done_called_(false),
+        processing_stopped_called_(false),
+        action_completed_called_(false),
+        action_exit_code_(ErrorCode::kError) {}
+
+  virtual void ProcessingDone(const ActionProcessor* processor,
+                              ErrorCode code) {
+    EXPECT_EQ(processor_, processor);
+    EXPECT_FALSE(processing_done_called_);
+    processing_done_called_ = true;
+  }
+  virtual void ProcessingStopped(const ActionProcessor* processor) {
+    EXPECT_EQ(processor_, processor);
+    EXPECT_FALSE(processing_stopped_called_);
+    processing_stopped_called_ = true;
+  }
+  virtual void ActionCompleted(ActionProcessor* processor,
+                               AbstractAction* action,
+                               ErrorCode code) {
+    EXPECT_EQ(processor_, processor);
+    EXPECT_FALSE(action_completed_called_);
+    action_completed_called_ = true;
+    action_exit_code_ = code;
+  }
+
+  const ActionProcessor* processor_;
+  bool processing_done_called_;
+  bool processing_stopped_called_;
+  bool action_completed_called_;
+  ErrorCode action_exit_code_;
+};
+}  // namespace
+
+class ActionProcessorTest : public ::testing::Test {
+  void SetUp() override {
+    action_processor_.set_delegate(&delegate_);
+    // Silence Type() calls used for logging.
+    EXPECT_CALL(mock_action_, Type()).Times(testing::AnyNumber());
+  }
+
+  void TearDown() override {
+    action_processor_.set_delegate(nullptr);
+  }
+
+ protected:
+  // The ActionProcessor under test.
+  ActionProcessor action_processor_;
+
+  MyActionProcessorDelegate delegate_{&action_processor_};
+
+  // Common actions used during most tests.
+  testing::StrictMock<MockAction> mock_action_;
+  ActionProcessorTestAction action_;
+};
+
+TEST_F(ActionProcessorTest, SimpleTest) {
+  EXPECT_FALSE(action_processor_.IsRunning());
+  action_processor_.EnqueueAction(&action_);
+  EXPECT_FALSE(action_processor_.IsRunning());
+  EXPECT_FALSE(action_.IsRunning());
+  action_processor_.StartProcessing();
+  EXPECT_TRUE(action_processor_.IsRunning());
+  EXPECT_TRUE(action_.IsRunning());
+  EXPECT_EQ(action_processor_.current_action(), &action_);
+  action_.CompleteAction();
+  EXPECT_FALSE(action_processor_.IsRunning());
+  EXPECT_FALSE(action_.IsRunning());
+}
+
+TEST_F(ActionProcessorTest, DelegateTest) {
+  action_processor_.EnqueueAction(&action_);
+  action_processor_.StartProcessing();
+  action_.CompleteAction();
+  EXPECT_TRUE(delegate_.processing_done_called_);
+  EXPECT_TRUE(delegate_.action_completed_called_);
+}
+
+TEST_F(ActionProcessorTest, StopProcessingTest) {
+  action_processor_.EnqueueAction(&action_);
+  action_processor_.StartProcessing();
+  action_processor_.StopProcessing();
+  EXPECT_TRUE(delegate_.processing_stopped_called_);
+  EXPECT_FALSE(delegate_.action_completed_called_);
+  EXPECT_FALSE(action_processor_.IsRunning());
+  EXPECT_EQ(nullptr, action_processor_.current_action());
+}
+
+TEST_F(ActionProcessorTest, ChainActionsTest) {
+  // This test doesn't use a delegate since it terminates several actions.
+  action_processor_.set_delegate(nullptr);
+
+  ActionProcessorTestAction action1, action2;
+  action_processor_.EnqueueAction(&action1);
+  action_processor_.EnqueueAction(&action2);
+  action_processor_.StartProcessing();
+  EXPECT_EQ(&action1, action_processor_.current_action());
+  EXPECT_TRUE(action_processor_.IsRunning());
+  action1.CompleteAction();
+  EXPECT_EQ(&action2, action_processor_.current_action());
+  EXPECT_TRUE(action_processor_.IsRunning());
+  action2.CompleteAction();
+  EXPECT_EQ(nullptr, action_processor_.current_action());
+  EXPECT_FALSE(action_processor_.IsRunning());
+}
+
+TEST_F(ActionProcessorTest, DtorTest) {
+  ActionProcessorTestAction action1, action2;
+  {
+    ActionProcessor action_processor;
+    action_processor.EnqueueAction(&action1);
+    action_processor.EnqueueAction(&action2);
+    action_processor.StartProcessing();
+  }
+  EXPECT_EQ(nullptr, action1.processor());
+  EXPECT_FALSE(action1.IsRunning());
+  EXPECT_EQ(nullptr, action2.processor());
+  EXPECT_FALSE(action2.IsRunning());
+}
+
+TEST_F(ActionProcessorTest, DefaultDelegateTest) {
+  // Just make sure it doesn't crash
+  action_processor_.EnqueueAction(&action_);
+  action_processor_.StartProcessing();
+  action_.CompleteAction();
+
+  action_processor_.EnqueueAction(&action_);
+  action_processor_.StartProcessing();
+  action_processor_.StopProcessing();
+}
+
+// This test suspends and resume the action processor while running one action_.
+TEST_F(ActionProcessorTest, SuspendResumeTest) {
+  action_processor_.EnqueueAction(&mock_action_);
+
+  testing::InSequence s;
+  EXPECT_CALL(mock_action_, PerformAction());
+  action_processor_.StartProcessing();
+
+  EXPECT_CALL(mock_action_, SuspendAction());
+  action_processor_.SuspendProcessing();
+  // Suspending the processor twice should not suspend the action twice.
+  action_processor_.SuspendProcessing();
+
+  // IsRunning should return whether there's is an action doing some work, even
+  // if it is suspended.
+  EXPECT_TRUE(action_processor_.IsRunning());
+  EXPECT_EQ(&mock_action_, action_processor_.current_action());
+
+  EXPECT_CALL(mock_action_, ResumeAction());
+  action_processor_.ResumeProcessing();
+
+  // Calling ResumeProcessing twice should not affect the action_.
+  action_processor_.ResumeProcessing();
+
+  action_processor_.ActionComplete(&mock_action_, ErrorCode::kSuccess);
+}
+
+// This test suspends an action that presumably doesn't support suspend/resume
+// and it finished before being resumed.
+TEST_F(ActionProcessorTest, ActionCompletedWhileSuspendedTest) {
+  action_processor_.EnqueueAction(&mock_action_);
+
+  testing::InSequence s;
+  EXPECT_CALL(mock_action_, PerformAction());
+  action_processor_.StartProcessing();
+
+  EXPECT_CALL(mock_action_, SuspendAction());
+  action_processor_.SuspendProcessing();
+
+  // Simulate the action completion while suspended. No other call to
+  // |mock_action_| is expected at this point.
+  action_processor_.ActionComplete(&mock_action_, ErrorCode::kSuccess);
+
+  // The processing should not be done since the ActionProcessor is suspended
+  // and the processing is considered to be still running until resumed.
+  EXPECT_FALSE(delegate_.processing_done_called_);
+  EXPECT_TRUE(action_processor_.IsRunning());
+
+  action_processor_.ResumeProcessing();
+  EXPECT_TRUE(delegate_.processing_done_called_);
+  EXPECT_FALSE(delegate_.processing_stopped_called_);
+}
+
+TEST_F(ActionProcessorTest, StoppedWhileSuspendedTest) {
+  action_processor_.EnqueueAction(&mock_action_);
+
+  testing::InSequence s;
+  EXPECT_CALL(mock_action_, PerformAction());
+  action_processor_.StartProcessing();
+  EXPECT_CALL(mock_action_, SuspendAction());
+  action_processor_.SuspendProcessing();
+
+  EXPECT_CALL(mock_action_, TerminateProcessing());
+  action_processor_.StopProcessing();
+  // Stopping the processing should abort the current execution no matter what.
+  EXPECT_TRUE(delegate_.processing_stopped_called_);
+  EXPECT_FALSE(delegate_.processing_done_called_);
+  EXPECT_FALSE(delegate_.action_completed_called_);
+  EXPECT_FALSE(action_processor_.IsRunning());
+  EXPECT_EQ(nullptr, action_processor_.current_action());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/action_unittest.cc b/update_engine/common/action_unittest.cc
new file mode 100644
index 0000000..dcdce17
--- /dev/null
+++ b/update_engine/common/action_unittest.cc
@@ -0,0 +1,76 @@
+//
+// 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.
+//
+
+#include "update_engine/common/action.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include "update_engine/common/action_processor.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionTestAction;
+
+template<>
+class ActionTraits<ActionTestAction> {
+ public:
+  typedef string OutputObjectType;
+  typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionTestAction : public Action<ActionTestAction> {
+ public:
+  typedef string InputObjectType;
+  typedef string OutputObjectType;
+  ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+  ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+  ActionProcessor* processor() { return processor_; }
+  void PerformAction() {}
+  void CompleteAction() {
+    ASSERT_TRUE(processor());
+    processor()->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  string Type() const { return "ActionTestAction"; }
+};
+
+class ActionTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionTest, SimpleTest) {
+  ActionTestAction action;
+
+  EXPECT_FALSE(action.in_pipe());
+  EXPECT_FALSE(action.out_pipe());
+  EXPECT_FALSE(action.processor());
+  EXPECT_FALSE(action.IsRunning());
+
+  ActionProcessor action_processor;
+  action_processor.EnqueueAction(&action);
+  EXPECT_EQ(&action_processor, action.processor());
+
+  action_processor.StartProcessing();
+  EXPECT_TRUE(action.IsRunning());
+  action.CompleteAction();
+  EXPECT_FALSE(action.IsRunning());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/boot_control.h b/update_engine/common/boot_control.h
new file mode 100644
index 0000000..1463015
--- /dev/null
+++ b/update_engine/common/boot_control.h
@@ -0,0 +1,35 @@
+//
+// 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_COMMON_BOOT_CONTROL_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_H_
+
+#include <memory>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+namespace boot_control {
+
+// The real BootControlInterface is platform-specific. This factory function
+// creates a new BootControlInterface instance for the current platform. If
+// this fails nullptr is returned.
+std::unique_ptr<BootControlInterface> CreateBootControl();
+
+}  // namespace boot_control
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_BOOT_CONTROL_H_
diff --git a/update_engine/common/boot_control_interface.h b/update_engine/common/boot_control_interface.h
new file mode 100644
index 0000000..659b388
--- /dev/null
+++ b/update_engine/common/boot_control_interface.h
@@ -0,0 +1,98 @@
+//
+// 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_COMMON_BOOT_CONTROL_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_INTERFACE_H_
+
+#include <climits>
+#include <string>
+
+#include <base/callback.h>
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+// The abstract boot control interface defines the interaction with the
+// platform's bootloader hiding vendor-specific details from the rest of
+// update_engine. This interface is used for controlling where the device should
+// boot from.
+class BootControlInterface {
+ public:
+  using Slot = unsigned int;
+
+  static const Slot kInvalidSlot = UINT_MAX;
+
+  virtual ~BootControlInterface() = default;
+
+  // Return the number of update slots in the system. A system will normally
+  // have two slots, named "A" and "B" in the documentation, but sometimes
+  // images running from other media can have only one slot, like some USB
+  // image. Systems with only one slot won't be able to update.
+  virtual unsigned int GetNumSlots() const = 0;
+
+  // Return the slot where we are running the system from. On success, the
+  // result is a number between 0 and GetNumSlots() - 1. Otherwise, log an error
+  // and return kInvalidSlot.
+  virtual Slot GetCurrentSlot() const = 0;
+
+  // Determines the block device for the given partition name and slot number.
+  // The |slot| number must be between 0 and GetNumSlots() - 1 and the
+  // |partition_name| is a platform-specific name that identifies a partition on
+  // every slot. On success, returns true and stores the block device in
+  // |device|.
+  virtual bool GetPartitionDevice(const std::string& partition_name,
+                                  Slot slot,
+                                  std::string* device) const = 0;
+
+  // Returns whether the passed |slot| is marked as bootable. Returns false if
+  // the slot is invalid.
+  virtual bool IsSlotBootable(Slot slot) const = 0;
+
+  // Mark the specified slot unbootable. No other slot flags are modified.
+  // Returns true on success.
+  virtual bool MarkSlotUnbootable(Slot slot) = 0;
+
+  // Set the passed |slot| as the preferred boot slot. Returns whether it
+  // succeeded setting the active slot. If succeeded, on next boot the
+  // bootloader will attempt to load the |slot| marked as active. Note that this
+  // method doesn't change the value of GetCurrentSlot() on the current boot.
+  virtual bool SetActiveBootSlot(Slot slot) = 0;
+
+  // Mark the current slot as successfully booted asynchronously. No other slot
+  // flags are modified. Returns false if it was not able to schedule the
+  // operation, otherwise, returns true and calls the |callback| with the result
+  // of the operation.
+  virtual bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) = 0;
+
+  // Return a human-readable slot name used for logging.
+  static std::string SlotName(Slot slot) {
+    if (slot == kInvalidSlot)
+      return "INVALID";
+    if (slot < 26)
+      return std::string(1, 'A' + slot);
+    return "TOO_BIG";
+  }
+
+ protected:
+  BootControlInterface() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BootControlInterface);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_BOOT_CONTROL_INTERFACE_H_
diff --git a/update_engine/common/boot_control_stub.cc b/update_engine/common/boot_control_stub.cc
new file mode 100644
index 0000000..2de0c82
--- /dev/null
+++ b/update_engine/common/boot_control_stub.cc
@@ -0,0 +1,62 @@
+//
+// 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/boot_control_stub.h"
+
+#include <base/logging.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+unsigned int BootControlStub::GetNumSlots() const {
+  return 0;
+}
+
+BootControlInterface::Slot BootControlStub::GetCurrentSlot() const {
+  LOG(ERROR) << __FUNCTION__ << " should never be called.";
+  return 0;
+}
+
+bool BootControlStub::GetPartitionDevice(const string& partition_name,
+                                         Slot slot,
+                                         string* device) const {
+  LOG(ERROR) << __FUNCTION__ << " should never be called.";
+  return false;
+}
+
+bool BootControlStub::IsSlotBootable(Slot slot) const {
+  LOG(ERROR) << __FUNCTION__ << " should never be called.";
+  return false;
+}
+
+bool BootControlStub::MarkSlotUnbootable(Slot slot) {
+  LOG(ERROR) << __FUNCTION__ << " should never be called.";
+  return false;
+}
+
+bool BootControlStub::SetActiveBootSlot(Slot slot) {
+  LOG(ERROR) << __FUNCTION__ << " should never be called.";
+  return false;
+}
+
+bool BootControlStub::MarkBootSuccessfulAsync(
+    base::Callback<void(bool)> callback) {
+  // This is expected to be called on update_engine startup.
+  return false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/boot_control_stub.h b/update_engine/common/boot_control_stub.h
new file mode 100644
index 0000000..7832adc
--- /dev/null
+++ b/update_engine/common/boot_control_stub.h
@@ -0,0 +1,55 @@
+//
+// 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_COMMON_BOOT_CONTROL_STUB_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_
+
+#include <string>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// An implementation of the BootControlInterface that does nothing,
+// typically used when e.g. an underlying HAL implementation cannot be
+// loaded or doesn't exist.
+//
+// You are gauranteed that the implementation of GetNumSlots() method
+// always returns 0. This can be used to identify that this
+// implementation is in use.
+class BootControlStub : public BootControlInterface {
+ public:
+  BootControlStub() = default;
+  ~BootControlStub() = default;
+
+  // BootControlInterface overrides.
+  unsigned int GetNumSlots() const override;
+  BootControlInterface::Slot GetCurrentSlot() 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;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BootControlStub);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_
diff --git a/update_engine/common/clock.cc b/update_engine/common/clock.cc
new file mode 100644
index 0000000..f0eff44
--- /dev/null
+++ b/update_engine/common/clock.cc
@@ -0,0 +1,58 @@
+//
+// 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/common/clock.h"
+
+#include <time.h>
+
+namespace chromeos_update_engine {
+
+base::Time Clock::GetWallclockTime() {
+  return base::Time::Now();
+}
+
+base::Time Clock::GetMonotonicTime() {
+  struct timespec now_ts;
+  if (clock_gettime(CLOCK_MONOTONIC_RAW, &now_ts) != 0) {
+    // Avoid logging this as an error as call-sites may call this very
+    // often and we don't want to fill up the disk. Note that this
+    // only fails if running on ancient kernels (CLOCK_MONOTONIC_RAW
+    // was added in Linux 2.6.28) so it never fails on a ChromeOS
+    // device.
+    return base::Time();
+  }
+  struct timeval now_tv;
+  now_tv.tv_sec = now_ts.tv_sec;
+  now_tv.tv_usec = now_ts.tv_nsec/base::Time::kNanosecondsPerMicrosecond;
+  return base::Time::FromTimeVal(now_tv);
+}
+
+base::Time Clock::GetBootTime() {
+  struct timespec now_ts;
+  if (clock_gettime(CLOCK_BOOTTIME, &now_ts) != 0) {
+    // Avoid logging this as an error as call-sites may call this very
+    // often and we don't want to fill up the disk. Note that this
+    // only fails if running on ancient kernels (CLOCK_BOOTTIME was
+    // added in Linux 2.6.39) so it never fails on a ChromeOS device.
+    return base::Time();
+  }
+  struct timeval now_tv;
+  now_tv.tv_sec = now_ts.tv_sec;
+  now_tv.tv_usec = now_ts.tv_nsec/base::Time::kNanosecondsPerMicrosecond;
+  return base::Time::FromTimeVal(now_tv);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/clock.h b/update_engine/common/clock.h
new file mode 100644
index 0000000..2f373a7
--- /dev/null
+++ b/update_engine/common/clock.h
@@ -0,0 +1,41 @@
+//
+// 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_COMMON_CLOCK_H_
+#define UPDATE_ENGINE_COMMON_CLOCK_H_
+
+#include "update_engine/common/clock_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a clock.
+class Clock : public ClockInterface {
+ public:
+  Clock() {}
+
+  base::Time GetWallclockTime() override;
+
+  base::Time GetMonotonicTime() override;
+
+  base::Time GetBootTime() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Clock);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_CLOCK_H_
diff --git a/update_engine/common/clock_interface.h b/update_engine/common/clock_interface.h
new file mode 100644
index 0000000..2228983
--- /dev/null
+++ b/update_engine/common/clock_interface.h
@@ -0,0 +1,54 @@
+//
+// 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_COMMON_CLOCK_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_CLOCK_INTERFACE_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+// The clock interface allows access to various system clocks. The
+// sole reason for providing this as an interface is unit testing.
+// Additionally, the sole reason for the methods not being static
+// is also unit testing.
+class ClockInterface {
+ public:
+  virtual ~ClockInterface() = default;
+
+  // Gets the current time e.g. similar to base::Time::Now().
+  virtual base::Time GetWallclockTime() = 0;
+
+  // Returns monotonic time since some unspecified starting point. It
+  // is not increased when the system is sleeping nor is it affected
+  // by NTP or the user changing the time.
+  //
+  // (This is a simple wrapper around clock_gettime(2) / CLOCK_MONOTONIC_RAW.)
+  virtual base::Time GetMonotonicTime() = 0;
+
+  // Returns monotonic time since some unspecified starting point. It
+  // is increased when the system is sleeping but it's not affected
+  // by NTP or the user changing the time.
+  //
+  // (This is a simple wrapper around clock_gettime(2) / CLOCK_BOOTTIME.)
+  virtual base::Time GetBootTime() = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_CLOCK_INTERFACE_H_
diff --git a/update_engine/common/constants.cc b/update_engine/common/constants.cc
new file mode 100644
index 0000000..324bdc5
--- /dev/null
+++ b/update_engine/common/constants.cc
@@ -0,0 +1,98 @@
+//
+// 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/common/constants.h"
+
+namespace chromeos_update_engine {
+
+const char kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs";
+
+const char kPrefsSubDirectory[] = "prefs";
+
+const char kStatefulPartition[] = "/mnt/stateful_partition";
+
+const char kPostinstallDefaultScript[] = "postinst";
+
+// Constants defining keys for the persisted state of update engine.
+const char kPrefsAttemptInProgress[] = "attempt-in-progress";
+const char kPrefsBackoffExpiryTime[] = "backoff-expiry-time";
+const char kPrefsBootId[] = "boot-id";
+const char kPrefsCurrentBytesDownloaded[] = "current-bytes-downloaded";
+const char kPrefsCurrentResponseSignature[] = "current-response-signature";
+const char kPrefsCurrentUrlFailureCount[] = "current-url-failure-count";
+const char kPrefsCurrentUrlIndex[] = "current-url-index";
+const char kPrefsDailyMetricsLastReportedAt[] =
+    "daily-metrics-last-reported-at";
+const char kPrefsDeltaUpdateFailures[] = "delta-update-failures";
+const char kPrefsFullPayloadAttemptNumber[] = "full-payload-attempt-number";
+const char kPrefsInstallDateDays[] = "install-date-days";
+const char kPrefsLastActivePingDay[] = "last-active-ping-day";
+const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day";
+const char kPrefsManifestMetadataSize[] = "manifest-metadata-size";
+const char kPrefsManifestSignatureSize[] = "manifest-signature-size";
+const char kPrefsMetricsAttemptLastReportingTime[] =
+    "metrics-attempt-last-reporting-time";
+const char kPrefsMetricsCheckLastReportingTime[] =
+    "metrics-check-last-reporting-time";
+const char kPrefsNumReboots[] = "num-reboots";
+const char kPrefsNumResponsesSeen[] = "num-responses-seen";
+const char kPrefsOmahaCohort[] = "omaha-cohort";
+const char kPrefsOmahaCohortHint[] = "omaha-cohort-hint";
+const char kPrefsOmahaCohortName[] = "omaha-cohort-name";
+const char kPrefsOmahaEolStatus[] = "omaha-eol-status";
+const char kPrefsP2PEnabled[] = "p2p-enabled";
+const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
+const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
+const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
+const char kPrefsPreviousVersion[] = "previous-version";
+const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
+const char kPrefsRollbackVersion[] = "rollback-version";
+const char kPrefsChannelOnSlotPrefix[] = "channel-on-slot-";
+const char kPrefsSystemUpdatedMarker[] = "system-updated-marker";
+const char kPrefsTargetVersionAttempt[] = "target-version-attempt";
+const char kPrefsTargetVersionInstalledFrom[] = "target-version-installed-from";
+const char kPrefsTargetVersionUniqueId[] = "target-version-unique-id";
+const char kPrefsTotalBytesDownloaded[] = "total-bytes-downloaded";
+const char kPrefsUpdateCheckCount[] = "update-check-count";
+const char kPrefsUpdateCheckResponseHash[] = "update-check-response-hash";
+const char kPrefsUpdateCompletedBootTime[] = "update-completed-boot-time";
+const char kPrefsUpdateCompletedOnBootId[] = "update-completed-on-boot-id";
+const char kPrefsUpdateDurationUptime[] = "update-duration-uptime";
+const char kPrefsUpdateFirstSeenAt[] = "update-first-seen-at";
+const char kPrefsUpdateOverCellularPermission[] =
+    "update-over-cellular-permission";
+const char kPrefsUpdateServerCertificate[] = "update-server-cert";
+const char kPrefsUpdateStateNextDataLength[] = "update-state-next-data-length";
+const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
+const char kPrefsUpdateStateNextOperation[] = "update-state-next-operation";
+const char kPrefsUpdateStateSHA256Context[] = "update-state-sha-256-context";
+const char kPrefsUpdateStateSignatureBlob[] = "update-state-signature-blob";
+const char kPrefsUpdateStateSignedSHA256Context[] =
+    "update-state-signed-sha-256-context";
+const char kPrefsUpdateTimestampStart[] = "update-timestamp-start";
+const char kPrefsUrlSwitchCount[] = "url-switch-count";
+const char kPrefsWallClockWaitPeriod[] = "wall-clock-wait-period";
+
+const char kPayloadPropertyFileSize[] = "FILE_SIZE";
+const char kPayloadPropertyFileHash[] = "FILE_HASH";
+const char kPayloadPropertyMetadataSize[] = "METADATA_SIZE";
+const char kPayloadPropertyMetadataHash[] = "METADATA_HASH";
+const char kPayloadPropertyAuthorization[] = "AUTHORIZATION";
+const char kPayloadPropertyUserAgent[] = "USER_AGENT";
+const char kPayloadPropertyPowerwash[] = "POWERWASH";
+const char kPayloadPropertyNetworkId[] = "NETWORK_ID";
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/constants.h b/update_engine/common/constants.h
new file mode 100644
index 0000000..ab66921
--- /dev/null
+++ b/update_engine/common/constants.h
@@ -0,0 +1,188 @@
+//
+// 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_COMMON_CONSTANTS_H_
+#define UPDATE_ENGINE_COMMON_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+
+// Directory for AU prefs that are preserved across powerwash.
+extern const char kPowerwashSafePrefsSubDirectory[];
+
+// The location where we store the AU preferences (state etc).
+extern const char kPrefsSubDirectory[];
+
+// Path to the post install command, relative to the partition.
+extern const char kPostinstallDefaultScript[];
+
+// Path to the stateful partition on the root filesystem.
+extern const char kStatefulPartition[];
+
+// Constants related to preferences.
+extern const char kPrefsAttemptInProgress[];
+extern const char kPrefsBackoffExpiryTime[];
+extern const char kPrefsBootId[];
+extern const char kPrefsCurrentBytesDownloaded[];
+extern const char kPrefsCurrentResponseSignature[];
+extern const char kPrefsCurrentUrlFailureCount[];
+extern const char kPrefsCurrentUrlIndex[];
+extern const char kPrefsDailyMetricsLastReportedAt[];
+extern const char kPrefsDeltaUpdateFailures[];
+extern const char kPrefsFullPayloadAttemptNumber[];
+extern const char kPrefsInstallDateDays[];
+extern const char kPrefsLastActivePingDay[];
+extern const char kPrefsLastRollCallPingDay[];
+extern const char kPrefsManifestMetadataSize[];
+extern const char kPrefsManifestSignatureSize[];
+extern const char kPrefsMetricsAttemptLastReportingTime[];
+extern const char kPrefsMetricsCheckLastReportingTime[];
+extern const char kPrefsNumReboots[];
+extern const char kPrefsNumResponsesSeen[];
+extern const char kPrefsOmahaCohort[];
+extern const char kPrefsOmahaCohortHint[];
+extern const char kPrefsOmahaCohortName[];
+extern const char kPrefsOmahaEolStatus[];
+extern const char kPrefsP2PEnabled[];
+extern const char kPrefsP2PFirstAttemptTimestamp[];
+extern const char kPrefsP2PNumAttempts[];
+extern const char kPrefsPayloadAttemptNumber[];
+extern const char kPrefsPreviousVersion[];
+extern const char kPrefsResumedUpdateFailures[];
+extern const char kPrefsRollbackVersion[];
+extern const char kPrefsChannelOnSlotPrefix[];
+extern const char kPrefsSystemUpdatedMarker[];
+extern const char kPrefsTargetVersionAttempt[];
+extern const char kPrefsTargetVersionInstalledFrom[];
+extern const char kPrefsTargetVersionUniqueId[];
+extern const char kPrefsTotalBytesDownloaded[];
+extern const char kPrefsUpdateCheckCount[];
+extern const char kPrefsUpdateCheckResponseHash[];
+extern const char kPrefsUpdateCompletedBootTime[];
+extern const char kPrefsUpdateCompletedOnBootId[];
+extern const char kPrefsUpdateDurationUptime[];
+extern const char kPrefsUpdateFirstSeenAt[];
+extern const char kPrefsUpdateOverCellularPermission[];
+extern const char kPrefsUpdateServerCertificate[];
+extern const char kPrefsUpdateStateNextDataLength[];
+extern const char kPrefsUpdateStateNextDataOffset[];
+extern const char kPrefsUpdateStateNextOperation[];
+extern const char kPrefsUpdateStateSHA256Context[];
+extern const char kPrefsUpdateStateSignatureBlob[];
+extern const char kPrefsUpdateStateSignedSHA256Context[];
+extern const char kPrefsUpdateTimestampStart[];
+extern const char kPrefsUrlSwitchCount[];
+extern const char kPrefsWallClockWaitPeriod[];
+
+// Keys used when storing and loading payload properties.
+extern const char kPayloadPropertyFileSize[];
+extern const char kPayloadPropertyFileHash[];
+extern const char kPayloadPropertyMetadataSize[];
+extern const char kPayloadPropertyMetadataHash[];
+extern const char kPayloadPropertyAuthorization[];
+extern const char kPayloadPropertyUserAgent[];
+extern const char kPayloadPropertyPowerwash[];
+extern const char kPayloadPropertyNetworkId[];
+
+// A download source is any combination of protocol and server (that's of
+// interest to us when looking at UMA metrics) using which we may download
+// the payload.
+typedef enum {
+  kDownloadSourceHttpsServer,  // UMA Binary representation: 0001
+  kDownloadSourceHttpServer,   // UMA Binary representation: 0010
+  kDownloadSourceHttpPeer,     // UMA Binary representation: 0100
+
+  // Note: Add new sources only above this line.
+  kNumDownloadSources
+} DownloadSource;
+
+// A payload can be a Full or Delta payload. In some cases, a Full payload is
+// used even when a Delta payload was available for the update, called here
+// ForcedFull. The PayloadType enum is only used to send UMA metrics about the
+// successfully applied payload.
+typedef enum {
+  kPayloadTypeFull,
+  kPayloadTypeDelta,
+  kPayloadTypeForcedFull,
+
+  // Note: Add new payload types only above this line.
+  kNumPayloadTypes
+} PayloadType;
+
+// Maximum number of times we'll allow using p2p for the same update payload.
+const int kMaxP2PAttempts = 10;
+
+// Maximum wallclock time we allow attempting to update using p2p for
+// the same update payload - five days.
+const int kMaxP2PAttemptTimeSeconds = 5 * 24 * 60 * 60;
+
+// The maximum amount of time to spend waiting for p2p-client(1) to
+// return while waiting in line to use the LAN - six hours.
+const int kMaxP2PNetworkWaitTimeSeconds = 6 * 60 * 60;
+
+// The maximum number of payload files to keep in /var/cache/p2p.
+const int kMaxP2PFilesToKeep = 3;
+
+// The maximum number of days to keep a p2p file;
+const int kMaxP2PFileAgeDays = 5;
+
+// The default number of UMA buckets for metrics.
+const int kNumDefaultUmaBuckets = 50;
+
+// General constants
+const int kNumBytesInOneMiB = 1024 * 1024;
+
+// Number of redirects allowed when downloading.
+const int kDownloadMaxRedirects = 10;
+
+// The minimum average speed that downloads must sustain...
+//
+// This is set low because some devices may have very poor
+// connectivity and we want to make as much forward progress as
+// possible. For p2p this is high (25 kB/second) since we can assume
+// high bandwidth (same LAN) and we want to fail fast.
+const int kDownloadLowSpeedLimitBps = 1;
+const int kDownloadP2PLowSpeedLimitBps = 25 * 1000;
+
+// ... measured over this period.
+//
+// For non-official builds (e.g. typically built on a developer's
+// workstation and served via devserver) bump this since it takes time
+// for the workstation to generate the payload. For p2p, make this
+// relatively low since we want to fail fast.
+const int kDownloadLowSpeedTimeSeconds = 90;
+const int kDownloadDevModeLowSpeedTimeSeconds = 180;
+const int kDownloadP2PLowSpeedTimeSeconds = 60;
+
+// The maximum amount of HTTP server reconnect attempts.
+//
+// This is set high in order to maximize the attempt's chance of
+// succeeding. When using p2p, this is low in order to fail fast.
+const int kDownloadMaxRetryCount = 20;
+const int kDownloadMaxRetryCountOobeNotComplete = 3;
+const int kDownloadP2PMaxRetryCount = 5;
+
+// The connect timeout, in seconds.
+//
+// This is set high because some devices may have very poor
+// connectivity and we may be using HTTPS which involves complicated
+// multi-roundtrip setup. For p2p, this is set low because we can
+// the server is on the same LAN and we want to fail fast.
+const int kDownloadConnectTimeoutSeconds = 30;
+const int kDownloadP2PConnectTimeoutSeconds = 5;
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_CONSTANTS_H_
diff --git a/update_engine/common/cpu_limiter.cc b/update_engine/common/cpu_limiter.cc
new file mode 100644
index 0000000..32068d4
--- /dev/null
+++ b/update_engine/common/cpu_limiter.cc
@@ -0,0 +1,92 @@
+//
+// 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/common/cpu_limiter.h"
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/utils.h"
+
+namespace {
+
+// Cgroup container is created in update-engine's upstart script located at
+// /etc/init/update-engine.conf.
+#ifdef USE_NESTLABS
+const char kCGroupSharesPath[] = "/dev/cpuctl/update_engine/cpu.shares";
+#else
+const char kCGroupSharesPath[] = "/sys/fs/cgroup/cpu/update-engine/cpu.shares";
+#endif
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+CPULimiter::~CPULimiter() {
+  // Set everything back to normal on destruction.
+  CPULimiter::SetCpuShares(CpuShares::kNormal);
+}
+
+void CPULimiter::StartLimiter() {
+  if (manage_shares_id_ != brillo::MessageLoop::kTaskIdNull) {
+    LOG(ERROR) << "Cpu shares timeout source hasn't been destroyed.";
+    StopLimiter();
+  }
+  manage_shares_id_ = brillo::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&CPULimiter::StopLimiterCallback, base::Unretained(this)),
+      base::TimeDelta::FromHours(2));
+  SetCpuShares(CpuShares::kLow);
+}
+
+void CPULimiter::StopLimiter() {
+  if (manage_shares_id_ != brillo::MessageLoop::kTaskIdNull) {
+    // If the shares were never set and there isn't a message loop instance,
+    // we avoid calling CancelTask(), which otherwise would have been a no-op.
+    brillo::MessageLoop::current()->CancelTask(manage_shares_id_);
+    manage_shares_id_ = brillo::MessageLoop::kTaskIdNull;
+  }
+  SetCpuShares(CpuShares::kNormal);
+}
+
+bool CPULimiter::SetCpuShares(CpuShares shares) {
+  // Short-circuit to avoid re-setting the shares.
+  if (shares_ == shares)
+    return true;
+
+  std::string string_shares = base::IntToString(static_cast<int>(shares));
+  LOG(INFO) << "Setting cgroup cpu shares to  " << string_shares;
+  if (!utils::WriteFile(
+          kCGroupSharesPath, string_shares.c_str(), string_shares.size())) {
+    LOG(ERROR) << "Failed to change cgroup cpu shares to " << string_shares
+               << " using " << kCGroupSharesPath;
+    return false;
+  }
+  shares_ = shares;
+  LOG(INFO) << "CPU shares = " << static_cast<int>(shares_);
+  return true;
+}
+
+void CPULimiter::StopLimiterCallback() {
+  SetCpuShares(CpuShares::kNormal);
+  manage_shares_id_ = brillo::MessageLoop::kTaskIdNull;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/cpu_limiter.h b/update_engine/common/cpu_limiter.h
new file mode 100644
index 0000000..c7add89
--- /dev/null
+++ b/update_engine/common/cpu_limiter.h
@@ -0,0 +1,67 @@
+//
+// 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_COMMON_CPU_LIMITER_H_
+#define UPDATE_ENGINE_COMMON_CPU_LIMITER_H_
+
+#include <brillo/message_loops/message_loop.h>
+
+namespace chromeos_update_engine {
+
+// Cgroups cpu shares constants. 1024 is the default shares a standard process
+// gets and 2 is the minimum value. We set High as a value that gives the
+// update-engine 2x the cpu share of a standard process.
+enum class CpuShares : int {
+  kHigh = 2048,
+  kNormal = 1024,
+  kLow = 2,
+};
+
+// Sets the current process shares to |shares|. Returns true on
+// success, false otherwise.
+bool SetCpuShares(CpuShares shares);
+
+class CPULimiter {
+ public:
+  CPULimiter() = default;
+  ~CPULimiter();
+
+  // Sets the cpu shares to low and sets up timeout events to stop the limiter.
+  void StartLimiter();
+
+  // Resets the cpu shares to normal and destroys any scheduled timeout sources.
+  void StopLimiter();
+
+  // Sets the cpu shares to |shares|. This method can be user at any time, but
+  // if the limiter is not running, the shares won't be reset to normal.
+  bool SetCpuShares(CpuShares shares);
+
+ private:
+  // The cpu shares timeout source callback sets the current cpu shares to
+  // normal.
+  void StopLimiterCallback();
+
+  // Current cpu shares.
+  CpuShares shares_ = CpuShares::kNormal;
+
+  // The cpu shares management timeout task id.
+  brillo::MessageLoop::TaskId manage_shares_id_{
+      brillo::MessageLoop::kTaskIdNull};
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_CPU_LIMITER_H_
diff --git a/update_engine/common/cpu_limiter_unittest.cc b/update_engine/common/cpu_limiter_unittest.cc
new file mode 100644
index 0000000..d549b4c
--- /dev/null
+++ b/update_engine/common/cpu_limiter_unittest.cc
@@ -0,0 +1,42 @@
+//
+// 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/common/cpu_limiter.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+class CPULimiterTest : public ::testing::Test {};
+
+namespace {
+// Compares cpu shares and returns an integer that is less
+// than, equal to or greater than 0 if |shares_lhs| is,
+// respectively, lower than, same as or higher than |shares_rhs|.
+int CompareCpuShares(CpuShares shares_lhs, CpuShares shares_rhs) {
+  return static_cast<int>(shares_lhs) - static_cast<int>(shares_rhs);
+}
+}  // namespace
+
+// Tests the CPU shares enum is in the order we expect it.
+TEST(CPULimiterTest, CompareCpuSharesTest) {
+  EXPECT_LT(CompareCpuShares(CpuShares::kLow, CpuShares::kNormal), 0);
+  EXPECT_GT(CompareCpuShares(CpuShares::kNormal, CpuShares::kLow), 0);
+  EXPECT_EQ(CompareCpuShares(CpuShares::kNormal, CpuShares::kNormal), 0);
+  EXPECT_GT(CompareCpuShares(CpuShares::kHigh, CpuShares::kNormal), 0);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/error_code.h b/update_engine/common/error_code.h
new file mode 100644
index 0000000..bc50589
--- /dev/null
+++ b/update_engine/common/error_code.h
@@ -0,0 +1,138 @@
+//
+// 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_COMMON_ERROR_CODE_H_
+#define UPDATE_ENGINE_COMMON_ERROR_CODE_H_
+
+#include <ostream>  // NOLINT(readability/streams)
+
+namespace chromeos_update_engine {
+
+// Action exit codes.
+enum class ErrorCode : int {
+  kSuccess = 0,
+  kError = 1,
+  kOmahaRequestError = 2,
+  kOmahaResponseHandlerError = 3,
+  kFilesystemCopierError = 4,
+  kPostinstallRunnerError = 5,
+  kPayloadMismatchedType = 6,
+  kInstallDeviceOpenError = 7,
+  kKernelDeviceOpenError = 8,
+  kDownloadTransferError = 9,
+  kPayloadHashMismatchError = 10,
+  kPayloadSizeMismatchError = 11,
+  kDownloadPayloadVerificationError = 12,
+  kDownloadNewPartitionInfoError = 13,
+  kDownloadWriteError = 14,
+  kNewRootfsVerificationError = 15,
+  kNewKernelVerificationError = 16,
+  kSignedDeltaPayloadExpectedError = 17,
+  kDownloadPayloadPubKeyVerificationError = 18,
+  kPostinstallBootedFromFirmwareB = 19,
+  kDownloadStateInitializationError = 20,
+  kDownloadInvalidMetadataMagicString = 21,
+  kDownloadSignatureMissingInManifest = 22,
+  kDownloadManifestParseError = 23,
+  kDownloadMetadataSignatureError = 24,
+  kDownloadMetadataSignatureVerificationError = 25,
+  kDownloadMetadataSignatureMismatch = 26,
+  kDownloadOperationHashVerificationError = 27,
+  kDownloadOperationExecutionError = 28,
+  kDownloadOperationHashMismatch = 29,
+  kOmahaRequestEmptyResponseError = 30,
+  kOmahaRequestXMLParseError = 31,
+  kDownloadInvalidMetadataSize = 32,
+  kDownloadInvalidMetadataSignature = 33,
+  kOmahaResponseInvalid = 34,
+  kOmahaUpdateIgnoredPerPolicy = 35,
+  kOmahaUpdateDeferredPerPolicy = 36,
+  kOmahaErrorInHTTPResponse = 37,
+  kDownloadOperationHashMissingError = 38,
+  kDownloadMetadataSignatureMissingError = 39,
+  kOmahaUpdateDeferredForBackoff = 40,
+  kPostinstallPowerwashError = 41,
+  kUpdateCanceledByChannelChange = 42,
+  kPostinstallFirmwareRONotUpdatable = 43,
+  kUnsupportedMajorPayloadVersion = 44,
+  kUnsupportedMinorPayloadVersion = 45,
+  kOmahaRequestXMLHasEntityDecl = 46,
+  kFilesystemVerifierError = 47,
+  kUserCanceled = 48,
+  kNonCriticalUpdateInOOBE = 49,
+
+  // VERY IMPORTANT! When adding new error codes:
+  //
+  // 1) Update tools/metrics/histograms/histograms.xml in Chrome.
+  //
+  // 2) Update the assorted switch statements in update_engine which won't
+  //    build until this case is added.
+
+  // Any code above this is sent to both Omaha and UMA as-is, except
+  // kOmahaErrorInHTTPResponse (see error code 2000 for more details).
+  // Codes/flags below this line is sent only to Omaha and not to UMA.
+
+  // kUmaReportedMax is not an error code per se, it's just the count
+  // of the number of enums above.  Add any new errors above this line if you
+  // want them to show up on UMA. Stuff below this line will not be sent to UMA
+  // but is used for other errors that are sent to Omaha. We don't assign any
+  // particular value for this enum so that it's just one more than the last
+  // one above and thus always represents the correct count of UMA metrics
+  // buckets, even when new enums are added above this line in future. See
+  // metrics::ReportUpdateAttemptMetrics() on how this enum is used.
+  kUmaReportedMax,
+
+  // use the 2xxx range to encode HTTP errors. These errors are available in
+  // Dremel with the individual granularity. But for UMA purposes, all these
+  // errors are aggregated into one: kOmahaErrorInHTTPResponse.
+  kOmahaRequestHTTPResponseBase = 2000,  // + HTTP response code
+  kHTTPResponseBase = kOmahaRequestHTTPResponseBase, // alias for kOmahaRequestHTTPResponseBase
+
+  // TODO(jaysri): Move out all the bit masks into separate constants
+  // outside the enum as part of fixing bug 34369.
+  // Bit flags. Remember to update the mask below for new bits.
+
+  // Set if boot mode not normal.
+  // TODO(garnold) This is very debatable value to use, knowing that the
+  // underlying type is a signed int (often, 32-bit). However, at this point
+  // there are parts of the ecosystem that expect this to be a negative value,
+  // so we preserve this semantics. This should be reconsidered if/when we
+  // modify the implementation of ErrorCode into a properly encapsulated class.
+  kDevModeFlag = 1 << 31,
+
+  // Set if resuming an interruped update.
+  kResumedFlag = 1 << 30,
+
+  // Set if using a dev/test image as opposed to an MP-signed image.
+  kTestImageFlag = 1 << 29,
+
+  // Set if using devserver or Omaha sandbox (using crosh autest).
+  kTestOmahaUrlFlag = 1 << 28,
+
+  // Mask that indicates bit positions that are used to indicate special flags
+  // that are embedded in the error code to provide additional context about
+  // the system in which the error was encountered.
+  kSpecialFlags =
+      (kDevModeFlag | kResumedFlag | kTestImageFlag | kTestOmahaUrlFlag)
+};
+
+inline std::ostream& operator<<(std::ostream& os, ErrorCode val) {
+  return os << static_cast<int>(val);
+}
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_ERROR_CODE_H_
diff --git a/update_engine/common/error_code_utils.cc b/update_engine/common/error_code_utils.cc
new file mode 100644
index 0000000..ad4a00f
--- /dev/null
+++ b/update_engine/common/error_code_utils.cc
@@ -0,0 +1,163 @@
+//
+// 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/common/error_code_utils.h"
+
+#include <base/strings/string_number_conversions.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+namespace utils {
+
+string ErrorCodeToString(ErrorCode code) {
+  // If the given code has both parts (i.e. the error code part and the flags
+  // part) then strip off the flags part since the switch statement below
+  // has case statements only for the base error code or a single flag but
+  // doesn't support any combinations of those.
+  if ((static_cast<int>(code) & static_cast<int>(ErrorCode::kSpecialFlags)) &&
+      (static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags)))
+    code = static_cast<ErrorCode>(
+        static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+  switch (code) {
+    case ErrorCode::kSuccess: return "ErrorCode::kSuccess";
+    case ErrorCode::kError: return "ErrorCode::kError";
+    case ErrorCode::kOmahaRequestError: return "ErrorCode::kOmahaRequestError";
+    case ErrorCode::kOmahaResponseHandlerError:
+      return "ErrorCode::kOmahaResponseHandlerError";
+    case ErrorCode::kFilesystemCopierError:
+      return "ErrorCode::kFilesystemCopierError";
+    case ErrorCode::kPostinstallRunnerError:
+      return "ErrorCode::kPostinstallRunnerError";
+    case ErrorCode::kPayloadMismatchedType:
+      return "ErrorCode::kPayloadMismatchedType";
+    case ErrorCode::kInstallDeviceOpenError:
+      return "ErrorCode::kInstallDeviceOpenError";
+    case ErrorCode::kKernelDeviceOpenError:
+      return "ErrorCode::kKernelDeviceOpenError";
+    case ErrorCode::kDownloadTransferError:
+      return "ErrorCode::kDownloadTransferError";
+    case ErrorCode::kPayloadHashMismatchError:
+      return "ErrorCode::kPayloadHashMismatchError";
+    case ErrorCode::kPayloadSizeMismatchError:
+      return "ErrorCode::kPayloadSizeMismatchError";
+    case ErrorCode::kDownloadPayloadVerificationError:
+      return "ErrorCode::kDownloadPayloadVerificationError";
+    case ErrorCode::kDownloadNewPartitionInfoError:
+      return "ErrorCode::kDownloadNewPartitionInfoError";
+    case ErrorCode::kDownloadWriteError:
+      return "ErrorCode::kDownloadWriteError";
+    case ErrorCode::kNewRootfsVerificationError:
+      return "ErrorCode::kNewRootfsVerificationError";
+    case ErrorCode::kNewKernelVerificationError:
+      return "ErrorCode::kNewKernelVerificationError";
+    case ErrorCode::kSignedDeltaPayloadExpectedError:
+      return "ErrorCode::kSignedDeltaPayloadExpectedError";
+    case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+      return "ErrorCode::kDownloadPayloadPubKeyVerificationError";
+    case ErrorCode::kPostinstallBootedFromFirmwareB:
+      return "ErrorCode::kPostinstallBootedFromFirmwareB";
+    case ErrorCode::kDownloadStateInitializationError:
+      return "ErrorCode::kDownloadStateInitializationError";
+    case ErrorCode::kDownloadInvalidMetadataMagicString:
+      return "ErrorCode::kDownloadInvalidMetadataMagicString";
+    case ErrorCode::kDownloadSignatureMissingInManifest:
+      return "ErrorCode::kDownloadSignatureMissingInManifest";
+    case ErrorCode::kDownloadManifestParseError:
+      return "ErrorCode::kDownloadManifestParseError";
+    case ErrorCode::kDownloadMetadataSignatureError:
+      return "ErrorCode::kDownloadMetadataSignatureError";
+    case ErrorCode::kDownloadMetadataSignatureVerificationError:
+      return "ErrorCode::kDownloadMetadataSignatureVerificationError";
+    case ErrorCode::kDownloadMetadataSignatureMismatch:
+      return "ErrorCode::kDownloadMetadataSignatureMismatch";
+    case ErrorCode::kDownloadOperationHashVerificationError:
+      return "ErrorCode::kDownloadOperationHashVerificationError";
+    case ErrorCode::kDownloadOperationExecutionError:
+      return "ErrorCode::kDownloadOperationExecutionError";
+    case ErrorCode::kDownloadOperationHashMismatch:
+      return "ErrorCode::kDownloadOperationHashMismatch";
+    case ErrorCode::kOmahaRequestEmptyResponseError:
+      return "ErrorCode::kOmahaRequestEmptyResponseError";
+    case ErrorCode::kOmahaRequestXMLParseError:
+      return "ErrorCode::kOmahaRequestXMLParseError";
+    case ErrorCode::kDownloadInvalidMetadataSize:
+      return "ErrorCode::kDownloadInvalidMetadataSize";
+    case ErrorCode::kDownloadInvalidMetadataSignature:
+      return "ErrorCode::kDownloadInvalidMetadataSignature";
+    case ErrorCode::kOmahaResponseInvalid:
+      return "ErrorCode::kOmahaResponseInvalid";
+    case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+      return "ErrorCode::kOmahaUpdateIgnoredPerPolicy";
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+      return "ErrorCode::kOmahaUpdateDeferredPerPolicy";
+    case ErrorCode::kOmahaErrorInHTTPResponse:
+#ifdef USE_NESTLABS
+      return "ErrorCode::kErrorInHTTPResponse";
+#else
+      return "ErrorCode::kOmahaErrorInHTTPResponse";
+#endif
+    case ErrorCode::kDownloadOperationHashMissingError:
+      return "ErrorCode::kDownloadOperationHashMissingError";
+    case ErrorCode::kDownloadMetadataSignatureMissingError:
+      return "ErrorCode::kDownloadMetadataSignatureMissingError";
+    case ErrorCode::kOmahaUpdateDeferredForBackoff:
+      return "ErrorCode::kOmahaUpdateDeferredForBackoff";
+    case ErrorCode::kPostinstallPowerwashError:
+      return "ErrorCode::kPostinstallPowerwashError";
+    case ErrorCode::kUpdateCanceledByChannelChange:
+      return "ErrorCode::kUpdateCanceledByChannelChange";
+    case ErrorCode::kUmaReportedMax:
+      return "ErrorCode::kUmaReportedMax";
+    case ErrorCode::kOmahaRequestHTTPResponseBase:
+#ifdef USE_NESTLABS
+      return "ErrorCode::kRequestHTTPResponseBase";
+#else
+      return "ErrorCode::kOmahaRequestHTTPResponseBase";
+#endif
+    case ErrorCode::kResumedFlag:
+      return "Resumed";
+    case ErrorCode::kDevModeFlag:
+      return "DevMode";
+    case ErrorCode::kTestImageFlag:
+      return "TestImage";
+    case ErrorCode::kTestOmahaUrlFlag:
+      return "TestOmahaUrl";
+    case ErrorCode::kSpecialFlags:
+      return "ErrorCode::kSpecialFlags";
+    case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+      return "ErrorCode::kPostinstallFirmwareRONotUpdatable";
+    case ErrorCode::kUnsupportedMajorPayloadVersion:
+      return "ErrorCode::kUnsupportedMajorPayloadVersion";
+    case ErrorCode::kUnsupportedMinorPayloadVersion:
+      return "ErrorCode::kUnsupportedMinorPayloadVersion";
+    case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+      return "ErrorCode::kOmahaRequestXMLHasEntityDecl";
+    case ErrorCode::kFilesystemVerifierError:
+      return "ErrorCode::kFilesystemVerifierError";
+    case ErrorCode::kUserCanceled:
+      return "ErrorCode::kUserCanceled";
+    case ErrorCode::kNonCriticalUpdateInOOBE:
+      return "ErrorCode::kNonCriticalUpdateInOOBE";
+    // Don't add a default case to let the compiler warn about newly added
+    // error codes which should be added here.
+  }
+
+  return "Unknown error: " + base::UintToString(static_cast<unsigned>(code));
+}
+
+}  // namespace utils
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/error_code_utils.h b/update_engine/common/error_code_utils.h
new file mode 100644
index 0000000..ae3958e
--- /dev/null
+++ b/update_engine/common/error_code_utils.h
@@ -0,0 +1,34 @@
+//
+// 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_COMMON_ERROR_CODE_UTILS_H_
+#define UPDATE_ENGINE_COMMON_ERROR_CODE_UTILS_H_
+
+#include <string>
+
+#include "update_engine/common/error_code.h"
+
+namespace chromeos_update_engine {
+namespace utils {
+
+// Returns a string representation of the ErrorCodes (either the base
+// error codes or the bit flags) for logging purposes.
+std::string ErrorCodeToString(ErrorCode code);
+
+}  // namespace utils
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_ERROR_CODE_UTILS_H_
diff --git a/update_engine/common/fake_boot_control.h b/update_engine/common/fake_boot_control.h
new file mode 100644
index 0000000..3eccc80
--- /dev/null
+++ b/update_engine/common/fake_boot_control.h
@@ -0,0 +1,112 @@
+//
+// 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_COMMON_FAKE_BOOT_CONTROL_H_
+#define UPDATE_ENGINE_COMMON_FAKE_BOOT_CONTROL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake bootloader control interface used for testing.
+class FakeBootControl : public BootControlInterface {
+ public:
+  FakeBootControl() {
+    SetNumSlots(num_slots_);
+    // The current slot should be bootable.
+    is_bootable_[current_slot_] = true;
+  }
+
+  // BootControlInterface overrides.
+  unsigned int GetNumSlots() const override { return num_slots_; }
+  BootControlInterface::Slot GetCurrentSlot() const override {
+    return current_slot_;
+  }
+
+  bool GetPartitionDevice(const std::string& partition_name,
+                          BootControlInterface::Slot slot,
+                          std::string* device) const override {
+    if (slot >= num_slots_)
+      return false;
+    auto part_it = devices_[slot].find(partition_name);
+    if (part_it == devices_[slot].end())
+      return false;
+    *device = part_it->second;
+    return true;
+  }
+
+  bool IsSlotBootable(BootControlInterface::Slot slot) const override {
+    return slot < num_slots_ && is_bootable_[slot];
+  }
+
+  bool MarkSlotUnbootable(BootControlInterface::Slot slot) override {
+    if (slot >= num_slots_)
+      return false;
+    is_bootable_[slot] = false;
+    return true;
+  }
+
+  bool SetActiveBootSlot(Slot slot) override { return true; }
+
+  bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override {
+    // We run the callback directly from here to avoid having to setup a message
+    // loop in the test environment.
+    callback.Run(true);
+    return true;
+  }
+
+  // Setters
+  void SetNumSlots(unsigned int num_slots) {
+    num_slots_ = num_slots;
+    is_bootable_.resize(num_slots_, false);
+    devices_.resize(num_slots_);
+  }
+
+  void SetCurrentSlot(BootControlInterface::Slot slot) {
+    current_slot_ = slot;
+  }
+
+  void SetPartitionDevice(const std::string& partition_name,
+                          BootControlInterface::Slot slot,
+                          const std::string& device) {
+    DCHECK(slot < num_slots_);
+    devices_[slot][partition_name] = device;
+  }
+
+  void SetSlotBootable(BootControlInterface::Slot slot, bool bootable) {
+    DCHECK(slot < num_slots_);
+    is_bootable_[slot] = bootable;
+  }
+
+ private:
+  BootControlInterface::Slot num_slots_{2};
+  BootControlInterface::Slot current_slot_{0};
+
+  std::vector<bool> is_bootable_;
+  std::vector<std::map<std::string, std::string>> devices_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeBootControl);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_FAKE_BOOT_CONTROL_H_
diff --git a/update_engine/common/fake_clock.h b/update_engine/common/fake_clock.h
new file mode 100644
index 0000000..3d3bad8
--- /dev/null
+++ b/update_engine/common/fake_clock.h
@@ -0,0 +1,63 @@
+//
+// 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_COMMON_FAKE_CLOCK_H_
+#define UPDATE_ENGINE_COMMON_FAKE_CLOCK_H_
+
+#include "update_engine/common/clock_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a clock that can be made to tell any time you want.
+class FakeClock : public ClockInterface {
+ public:
+  FakeClock() {}
+
+  base::Time GetWallclockTime() override {
+    return wallclock_time_;
+  }
+
+  base::Time GetMonotonicTime() override {
+    return monotonic_time_;
+  }
+
+  base::Time GetBootTime() override {
+    return boot_time_;
+  }
+
+  void SetWallclockTime(const base::Time &time) {
+    wallclock_time_ = time;
+  }
+
+  void SetMonotonicTime(const base::Time &time) {
+    monotonic_time_ = time;
+  }
+
+  void SetBootTime(const base::Time &time) {
+    boot_time_ = time;
+  }
+
+ private:
+  base::Time wallclock_time_;
+  base::Time monotonic_time_;
+  base::Time boot_time_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeClock);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_FAKE_CLOCK_H_
diff --git a/update_engine/common/fake_hardware.h b/update_engine/common/fake_hardware.h
new file mode 100644
index 0000000..5d0fca3
--- /dev/null
+++ b/update_engine/common/fake_hardware.h
@@ -0,0 +1,146 @@
+//
+// 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_COMMON_FAKE_HARDWARE_H_
+#define UPDATE_ENGINE_COMMON_FAKE_HARDWARE_H_
+
+#include <map>
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake hardware interface used for testing.
+class FakeHardware : public HardwareInterface {
+ public:
+  // Value used to signal that the powerwash_count file is not present. When
+  // this value is used in SetPowerwashCount(), GetPowerwashCount() will return
+  // false.
+  static const int kPowerwashCountNotSet = -1;
+
+  FakeHardware() = default;
+
+  // HardwareInterface methods.
+  bool IsOfficialBuild() const override { return is_official_build_; }
+
+  bool IsNormalBootMode() const override { return is_normal_boot_mode_; }
+
+  bool AreDevFeaturesEnabled() const override {
+    return are_dev_features_enabled_;
+  }
+
+  bool IsOOBEEnabled() const override { return is_oobe_enabled_; }
+
+  bool IsOOBEComplete(base::Time* out_time_of_oobe) const override {
+    if (out_time_of_oobe != nullptr)
+      *out_time_of_oobe = oobe_timestamp_;
+    return is_oobe_complete_;
+  }
+
+  std::string GetHardwareClass() const override { return hardware_class_; }
+
+  std::string GetFirmwareVersion() const override { return firmware_version_; }
+
+  std::string GetECVersion() const override { return ec_version_; }
+
+  int GetPowerwashCount() const override { return powerwash_count_; }
+
+  bool SchedulePowerwash() override {
+    powerwash_scheduled_ = true;
+    return true;
+  }
+
+  bool CancelPowerwash() override {
+    powerwash_scheduled_ = false;
+    return true;
+  }
+
+  bool IsPowerwashScheduled() { return powerwash_scheduled_; }
+
+  bool GetNonVolatileDirectory(base::FilePath* path) const override {
+    return false;
+  }
+
+  bool GetPowerwashSafeDirectory(base::FilePath* path) const override {
+    return false;
+  }
+
+  // Setters
+  void SetIsOfficialBuild(bool is_official_build) {
+    is_official_build_ = is_official_build;
+  }
+
+  void SetIsNormalBootMode(bool is_normal_boot_mode) {
+    is_normal_boot_mode_ = is_normal_boot_mode;
+  }
+
+  void SetAreDevFeaturesEnabled(bool are_dev_features_enabled) {
+    are_dev_features_enabled_ = are_dev_features_enabled;
+  }
+
+  // Sets the SetIsOOBEEnabled to |is_oobe_enabled|.
+  void SetIsOOBEEnabled(bool is_oobe_enabled) {
+    is_oobe_enabled_ = is_oobe_enabled;
+  }
+
+  // Sets the IsOOBEComplete to True with the given timestamp.
+  void SetIsOOBEComplete(base::Time oobe_timestamp) {
+    is_oobe_complete_ = true;
+    oobe_timestamp_ = oobe_timestamp;
+  }
+
+  void UnsetIsOOBEComplete() {
+    is_oobe_complete_ = false;
+  }
+
+  void SetHardwareClass(const std::string& hardware_class) {
+    hardware_class_ = hardware_class;
+  }
+
+  void SetFirmwareVersion(const std::string& firmware_version) {
+    firmware_version_ = firmware_version;
+  }
+
+  void SetECVersion(const std::string& ec_version) {
+    ec_version_ = ec_version;
+  }
+
+  void SetPowerwashCount(int powerwash_count) {
+    powerwash_count_ = powerwash_count;
+  }
+
+ private:
+  bool is_official_build_{true};
+  bool is_normal_boot_mode_{true};
+  bool are_dev_features_enabled_{false};
+  bool is_oobe_enabled_{true};
+  bool is_oobe_complete_{true};
+  base::Time oobe_timestamp_{base::Time::FromTimeT(1169280000)}; // Jan 20, 2007
+  std::string hardware_class_{"Fake HWID BLAH-1234"};
+  std::string firmware_version_{"Fake Firmware v1.0.1"};
+  std::string ec_version_{"Fake EC v1.0a"};
+  int powerwash_count_{kPowerwashCountNotSet};
+  bool powerwash_scheduled_{false};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeHardware);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_FAKE_HARDWARE_H_
diff --git a/update_engine/common/fake_prefs.cc b/update_engine/common/fake_prefs.cc
new file mode 100644
index 0000000..5a0a3af
--- /dev/null
+++ b/update_engine/common/fake_prefs.cc
@@ -0,0 +1,168 @@
+//
+// 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/common/fake_prefs.h"
+
+#include <algorithm>
+
+#include <gtest/gtest.h>
+
+using std::string;
+
+using chromeos_update_engine::FakePrefs;
+
+namespace {
+
+void CheckNotNull(const string& key, void* ptr) {
+  EXPECT_NE(nullptr, ptr)
+      << "Called Get*() for key \"" << key << "\" with a null parameter.";
+}
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+FakePrefs::~FakePrefs() {
+  EXPECT_TRUE(observers_.empty());
+}
+
+// Compile-time type-dependent constants definitions.
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<string>::type =
+    FakePrefs::PrefType::kString;
+template<>
+string FakePrefs::PrefValue::* const  // NOLINT(runtime/string), not static str.
+    FakePrefs::PrefConsts<string>::member = &FakePrefs::PrefValue::as_str;
+
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<int64_t>::type =
+    FakePrefs::PrefType::kInt64;
+template<>
+int64_t FakePrefs::PrefValue::* const FakePrefs::PrefConsts<int64_t>::member =
+    &FakePrefs::PrefValue::as_int64;
+
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<bool>::type =
+    FakePrefs::PrefType::kBool;
+template<>
+bool FakePrefs::PrefValue::* const FakePrefs::PrefConsts<bool>::member =
+    &FakePrefs::PrefValue::as_bool;
+
+bool FakePrefs::GetString(const string& key, string* value) const {
+  return GetValue(key, value);
+}
+
+bool FakePrefs::SetString(const string& key, const string& value) {
+  SetValue(key, value);
+  return true;
+}
+
+bool FakePrefs::GetInt64(const string& key, int64_t* value) const {
+  return GetValue(key, value);
+}
+
+bool FakePrefs::SetInt64(const string& key, const int64_t value) {
+  SetValue(key, value);
+  return true;
+}
+
+bool FakePrefs::GetBoolean(const string& key, bool* value) const {
+  return GetValue(key, value);
+}
+
+bool FakePrefs::SetBoolean(const string& key, const bool value) {
+  SetValue(key, value);
+  return true;
+}
+
+bool FakePrefs::Exists(const string& key) const {
+  return values_.find(key) != values_.end();
+}
+
+bool FakePrefs::Delete(const string& key) {
+  if (values_.find(key) == values_.end())
+    return false;
+  values_.erase(key);
+  const auto observers_for_key = observers_.find(key);
+  if (observers_for_key != observers_.end()) {
+    std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+    for (ObserverInterface* observer : copy_observers)
+      observer->OnPrefDeleted(key);
+  }
+  return true;
+}
+
+string FakePrefs::GetTypeName(PrefType type) {
+  switch (type) {
+    case PrefType::kString:
+      return "string";
+    case PrefType::kInt64:
+      return "int64_t";
+    case PrefType::kBool:
+      return "bool";
+  }
+  return "Unknown";
+}
+
+void FakePrefs::CheckKeyType(const string& key, PrefType type) const {
+  auto it = values_.find(key);
+  EXPECT_TRUE(it == values_.end() || it->second.type == type)
+      << "Key \"" << key << "\" if defined as " << GetTypeName(it->second.type)
+      << " but is accessed as a " << GetTypeName(type);
+}
+
+template<typename T>
+void FakePrefs::SetValue(const string& key, const T& value) {
+  CheckKeyType(key, PrefConsts<T>::type);
+  values_[key].type = PrefConsts<T>::type;
+  values_[key].value.*(PrefConsts<T>::member) = value;
+  const auto observers_for_key = observers_.find(key);
+  if (observers_for_key != observers_.end()) {
+    std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+    for (ObserverInterface* observer : copy_observers)
+      observer->OnPrefSet(key);
+  }
+}
+
+template<typename T>
+bool FakePrefs::GetValue(const string& key, T* value) const {
+  CheckKeyType(key, PrefConsts<T>::type);
+  auto it = values_.find(key);
+  if (it == values_.end())
+    return false;
+  CheckNotNull(key, value);
+  *value = it->second.value.*(PrefConsts<T>::member);
+  return true;
+}
+
+void FakePrefs::AddObserver(const string& key, ObserverInterface* observer) {
+  observers_[key].push_back(observer);
+}
+
+void FakePrefs::RemoveObserver(const string& key, ObserverInterface* observer) {
+  std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+  auto observer_it =
+      std::find(observers_for_key.begin(), observers_for_key.end(), observer);
+  EXPECT_NE(observer_it, observers_for_key.end())
+      << "Trying to remove an observer instance not watching the key "
+      << key;
+  if (observer_it != observers_for_key.end())
+    observers_for_key.erase(observer_it);
+  if (observers_for_key.empty())
+    observers_.erase(key);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/fake_prefs.h b/update_engine/common/fake_prefs.h
new file mode 100644
index 0000000..d194060
--- /dev/null
+++ b/update_engine/common/fake_prefs.h
@@ -0,0 +1,113 @@
+//
+// 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_COMMON_FAKE_PREFS_H_
+#define UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/common/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake preference store by storing the value associated with
+// a key in a std::map, suitable for testing. It doesn't allow to set a value on
+// a key with a different type than the previously set type. This enforces the
+// type of a given key to be fixed. Also the class checks that the Get*()
+// methods aren't called on a key set with a different type.
+
+class FakePrefs : public PrefsInterface {
+ public:
+  FakePrefs() = default;
+  ~FakePrefs();
+
+  // PrefsInterface methods.
+  bool GetString(const std::string& key, std::string* value) const override;
+  bool SetString(const std::string& key, const std::string& value) override;
+  bool GetInt64(const std::string& key, int64_t* value) const override;
+  bool SetInt64(const std::string& key, const int64_t value) override;
+  bool GetBoolean(const std::string& key, bool* value) const override;
+  bool SetBoolean(const std::string& key, const bool value) override;
+
+  bool Exists(const std::string& key) const override;
+  bool Delete(const std::string& key) override;
+
+  void AddObserver(const std::string& key,
+                   ObserverInterface* observer) override;
+  void RemoveObserver(const std::string& key,
+                      ObserverInterface* observer) override;
+
+ private:
+  enum class PrefType {
+    kString,
+    kInt64,
+    kBool,
+  };
+  struct PrefValue {
+    std::string as_str;
+    int64_t as_int64;
+    bool as_bool;
+  };
+
+  struct PrefTypeValue {
+    PrefType type;
+    PrefValue value;
+  };
+
+  // Class to store compile-time type-dependent constants.
+  template<typename T>
+  class PrefConsts {
+   public:
+    // The PrefType associated with T.
+    static FakePrefs::PrefType const type;
+
+    // The data member pointer to PrefValue associated with T.
+    static T FakePrefs::PrefValue::* const member;
+  };
+
+  // Returns a string representation of the PrefType useful for logging.
+  static std::string GetTypeName(PrefType type);
+
+  // Checks that the |key| is either not present or has the given |type|.
+  void CheckKeyType(const std::string& key, PrefType type) const;
+
+  // Helper function to set a value of the passed |key|. It sets the type based
+  // on the template parameter T.
+  template<typename T>
+  void SetValue(const std::string& key, const T& value);
+
+  // Helper function to get a value from the map checking for invalid calls.
+  // The function fails the test if you attempt to read a value  defined as a
+  // different type. Returns whether the get succeeded.
+  template<typename T>
+  bool GetValue(const std::string& key, T* value) const;
+
+  // Container for all the key/value pairs.
+  std::map<std::string, PrefTypeValue> values_;
+
+  // The registered observers watching for changes.
+  std::map<std::string, std::vector<ObserverInterface*>> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakePrefs);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
diff --git a/update_engine/common/file_fetcher.cc b/update_engine/common/file_fetcher.cc
new file mode 100644
index 0000000..d0a109b
--- /dev/null
+++ b/update_engine/common/file_fetcher.cc
@@ -0,0 +1,186 @@
+//
+// 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/common/file_fetcher.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/bind.h>
+#include <base/format_macros.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/streams/file_stream.h>
+
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/platform_constants.h"
+
+using std::string;
+
+namespace {
+
+size_t kReadBufferSize = 16 * 1024;
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+// static
+bool FileFetcher::SupportedUrl(const string& url) {
+  // Note that we require the file path to start with a "/".
+  return base::StartsWith(
+      url, "file:///", base::CompareCase::INSENSITIVE_ASCII);
+}
+
+FileFetcher::~FileFetcher() {
+  LOG_IF(ERROR, transfer_in_progress_)
+      << "Destroying the fetcher while a transfer is in progress.";
+  CleanUp();
+}
+
+// Begins the transfer, which must not have already been started.
+void FileFetcher::BeginTransfer(const string& url) {
+  CHECK(!transfer_in_progress_);
+
+  if (!SupportedUrl(url)) {
+    LOG(ERROR) << "Unsupported file URL: " << url;
+    // No HTTP error code when the URL is not supported.
+    http_response_code_ = 0;
+    CleanUp();
+    if (delegate_)
+      delegate_->TransferComplete(this, false);
+    return;
+  }
+
+  string file_path = url.substr(strlen("file://"));
+  stream_ =
+      brillo::FileStream::Open(base::FilePath(file_path),
+                               brillo::Stream::AccessMode::READ,
+                               brillo::FileStream::Disposition::OPEN_EXISTING,
+                               nullptr);
+
+  if (!stream_) {
+    LOG(ERROR) << "Couldn't open " << file_path;
+    http_response_code_ = kHttpResponseNotFound;
+    CleanUp();
+    if (delegate_)
+      delegate_->TransferComplete(this, false);
+    return;
+  }
+  http_response_code_ = kHttpResponseOk;
+
+  if (offset_)
+    stream_->SetPosition(offset_, nullptr);
+  bytes_copied_ = 0;
+  transfer_in_progress_ = true;
+  ScheduleRead();
+}
+
+void FileFetcher::TerminateTransfer() {
+  CleanUp();
+  if (delegate_) {
+    // Note that after the callback returns this object may be destroyed.
+    delegate_->TransferTerminated(this);
+  }
+}
+
+void FileFetcher::ScheduleRead() {
+  if (transfer_paused_ || ongoing_read_ || !transfer_in_progress_)
+    return;
+
+  buffer_.resize(kReadBufferSize);
+  size_t bytes_to_read = buffer_.size();
+  if (data_length_ >= 0) {
+    bytes_to_read = std::min(static_cast<uint64_t>(bytes_to_read),
+                             data_length_ - bytes_copied_);
+  }
+
+  if (!bytes_to_read) {
+    OnReadDoneCallback(0);
+    return;
+  }
+
+  ongoing_read_ = stream_->ReadAsync(
+      buffer_.data(),
+      bytes_to_read,
+      base::Bind(&FileFetcher::OnReadDoneCallback, base::Unretained(this)),
+      base::Bind(&FileFetcher::OnReadErrorCallback, base::Unretained(this)),
+      nullptr);
+
+  if (!ongoing_read_) {
+    LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
+    CleanUp();
+    if (delegate_)
+      delegate_->TransferComplete(this, false);
+  }
+}
+
+void FileFetcher::OnReadDoneCallback(size_t bytes_read) {
+  ongoing_read_ = false;
+  if (bytes_read == 0) {
+    CleanUp();
+    if (delegate_)
+      delegate_->TransferComplete(this, true);
+  } else {
+    bytes_copied_ += bytes_read;
+    if (delegate_)
+      delegate_->ReceivedBytes(this, buffer_.data(), bytes_read);
+    ScheduleRead();
+  }
+}
+
+void FileFetcher::OnReadErrorCallback(const brillo::Error* error) {
+  LOG(ERROR) << "Asynchronous read failed: " << error->GetMessage();
+  CleanUp();
+  if (delegate_)
+    delegate_->TransferComplete(this, false);
+}
+
+void FileFetcher::Pause() {
+  if (transfer_paused_) {
+    LOG(ERROR) << "Fetcher already paused.";
+    return;
+  }
+  transfer_paused_ = true;
+}
+
+void FileFetcher::Unpause() {
+  if (!transfer_paused_) {
+    LOG(ERROR) << "Resume attempted when fetcher not paused.";
+    return;
+  }
+  transfer_paused_ = false;
+  ScheduleRead();
+}
+
+void FileFetcher::CleanUp() {
+  if (stream_) {
+    stream_->CancelPendingAsyncOperations();
+    stream_->CloseBlocking(nullptr);
+    stream_.reset();
+  }
+  // Destroying the |stream_| releases the callback, so we don't have any
+  // ongoing read at this point.
+  ongoing_read_ = false;
+  buffer_ = brillo::Blob();
+
+  transfer_in_progress_ = false;
+  transfer_paused_ = false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/file_fetcher.h b/update_engine/common/file_fetcher.h
new file mode 100644
index 0000000..2368b1d
--- /dev/null
+++ b/update_engine/common/file_fetcher.h
@@ -0,0 +1,119 @@
+//
+// 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_COMMON_FILE_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_FILE_FETCHER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/streams/stream.h>
+
+#include "update_engine/common/http_fetcher.h"
+
+// This is a concrete implementation of HttpFetcher that reads files
+// asynchronously.
+
+namespace chromeos_update_engine {
+
+class FileFetcher : public HttpFetcher {
+ public:
+  // Returns whether the passed url is supported.
+  static bool SupportedUrl(const std::string& url);
+
+  FileFetcher() : HttpFetcher(nullptr) {}
+
+  // Cleans up all internal state. Does not notify delegate.
+  ~FileFetcher() override;
+
+  // HttpFetcher overrides.
+  void SetOffset(off_t offset) override { offset_ = offset; }
+  void SetLength(size_t length) override { data_length_ = length; }
+  void UnsetLength() override { SetLength(0); }
+
+  // Begins the transfer if it hasn't already begun.
+  void BeginTransfer(const std::string& url) override;
+
+  // If the transfer is in progress, aborts the transfer early. The transfer
+  // cannot be resumed.
+  void TerminateTransfer() override;
+
+  // Ignore all extra headers for files.
+  void SetHeader(const std::string& header_name,
+                 const std::string& header_value) override {};
+
+  // Suspend the asynchronous file read.
+  void Pause() override;
+
+  // Resume the suspended file read.
+  void Unpause() override;
+
+  size_t GetBytesDownloaded() override {
+    return static_cast<size_t>(bytes_copied_);
+  }
+
+  // Ignore all the time limits for files.
+  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {}
+  void set_connect_timeout(int connect_timeout_seconds) override {}
+  void set_max_retry_count(int max_retry_count) override {}
+
+ private:
+  // Cleans up the fetcher, resetting its status to a newly constructed one.
+  void CleanUp();
+
+  // Schedule a new asynchronous read if the stream is not paused and no other
+  // read is in process. This method can be called at any point.
+  void ScheduleRead();
+
+  // Called from the main loop when a single read from |stream_| succeeds or
+  // fails, calling OnReadDoneCallback() and OnReadErrorCallback() respectively.
+  void OnReadDoneCallback(size_t bytes_read);
+  void OnReadErrorCallback(const brillo::Error* error);
+
+  // Whether the transfer was started and didn't finish yet.
+  bool transfer_in_progress_{false};
+
+  // Whether the transfer is paused.
+  bool transfer_paused_{false};
+
+  // Whether there's an ongoing asynchronous read. When this value is true, the
+  // the |buffer_| is being used by the |stream_|.
+  bool ongoing_read_{false};
+
+  // Total number of bytes copied.
+  uint64_t bytes_copied_{0};
+
+  // The offset inside the file where the read should start.
+  uint64_t offset_{0};
+
+  // The length of the data or -1 if unknown (will read until EOF).
+  int64_t data_length_{-1};
+
+  brillo::StreamPtr stream_;
+
+  // The buffer used for reading from the stream.
+  brillo::Blob buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_FILE_FETCHER_H_
diff --git a/update_engine/common/file_fetcher_unittest.cc b/update_engine/common/file_fetcher_unittest.cc
new file mode 100644
index 0000000..9c6b0ec
--- /dev/null
+++ b/update_engine/common/file_fetcher_unittest.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/common/file_fetcher.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+
+namespace chromeos_update_engine {
+
+class FileFetcherUnitTest : public ::testing::Test {};
+
+TEST_F(FileFetcherUnitTest, SupporterUrlsTest) {
+  EXPECT_TRUE(FileFetcher::SupportedUrl("file:///path/to/somewhere.bin"));
+  EXPECT_TRUE(FileFetcher::SupportedUrl("FILE:///I/LIKE/TO/SHOUT"));
+
+  EXPECT_FALSE(FileFetcher::SupportedUrl("file://relative"));
+  EXPECT_FALSE(FileFetcher::SupportedUrl("http:///no_http_here"));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/hardware.h b/update_engine/common/hardware.h
new file mode 100644
index 0000000..f1365e0
--- /dev/null
+++ b/update_engine/common/hardware.h
@@ -0,0 +1,34 @@
+//
+// 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_COMMON_HARDWARE_H_
+#define UPDATE_ENGINE_COMMON_HARDWARE_H_
+
+#include <memory>
+
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+namespace hardware {
+
+// The real HardwareInterface is platform-specific. This factory function
+// creates a new HardwareInterface instance for the current platform.
+std::unique_ptr<HardwareInterface> CreateHardware();
+
+}  // namespace hardware
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_HARDWARE_H_
diff --git a/update_engine/common/hardware_interface.h b/update_engine/common/hardware_interface.h
new file mode 100644
index 0000000..316ad3d
--- /dev/null
+++ b/update_engine/common/hardware_interface.h
@@ -0,0 +1,96 @@
+//
+// 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_COMMON_HARDWARE_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+// The hardware interface allows access to the crossystem exposed properties,
+// such as the firmware version, hwid, verified boot mode.
+// These stateless functions are tied together in this interface to facilitate
+// unit testing.
+class HardwareInterface {
+ public:
+  virtual ~HardwareInterface() {}
+
+  // Returns whether this is an official build. Official build means that the
+  // server maintains and updates the build, so update_engine should run and
+  // periodically check for updates.
+  virtual bool IsOfficialBuild() const = 0;
+
+  // Returns true if the boot mode is normal or if it's unable to
+  // determine the boot mode. Returns false if the boot mode is
+  // developer. A dev-mode boot will allow the user to access developer-only
+  // features.
+  virtual bool IsNormalBootMode() const = 0;
+
+  // Returns whether the developer features are enabled.
+  virtual bool AreDevFeaturesEnabled() const = 0;
+
+  // Returns whether the device has an OOBE flow that the user must go through
+  // before getting non-critical updates. Use IsOOBEComplete() to determine if
+  // that flow is complete.
+  virtual bool IsOOBEEnabled() const = 0;
+
+  // Returns true if the OOBE process has been completed and EULA accepted,
+  // False otherwise. If True is returned, and |out_time_of_oobe| isn't null,
+  // the time-stamp of when OOBE happened is stored at |out_time_of_oobe|.
+  virtual bool IsOOBEComplete(base::Time* out_time_of_oobe) const = 0;
+
+  // Returns the HWID or an empty string on error.
+  virtual std::string GetHardwareClass() const = 0;
+
+  // Returns the firmware version or an empty string if the system is
+  // not running chrome os firmware.
+  virtual std::string GetFirmwareVersion() const = 0;
+
+  // Returns the ec version or an empty string if the system is not
+  // running a custom chrome os ec.
+  virtual std::string GetECVersion() const = 0;
+
+  // Returns the powerwash_count from the stateful. If the file is not found
+  // or is invalid, returns -1. Brand new machines out of the factory or after
+  // recovery don't have this value set.
+  virtual int GetPowerwashCount() const = 0;
+
+  // Signals that a powerwash (stateful partition wipe) should be performed
+  // after reboot.
+  virtual bool SchedulePowerwash() = 0;
+
+  // Cancel the powerwash operation scheduled to be performed on next boot.
+  virtual bool CancelPowerwash() = 0;
+
+  // Store in |path| the path to a non-volatile directory (persisted across
+  // reboots) available for this daemon. In case of an error, such as no
+  // directory available, returns false.
+  virtual bool GetNonVolatileDirectory(base::FilePath* path) const = 0;
+
+  // Store in |path| the path to a non-volatile directory persisted across
+  // powerwash cycles. In case of an error, such as no directory available,
+  // returns false.
+  virtual bool GetPowerwashSafeDirectory(base::FilePath* path) const = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
diff --git a/update_engine/common/hash_calculator.cc b/update_engine/common/hash_calculator.cc
new file mode 100644
index 0000000..de6e0f9
--- /dev/null
+++ b/update_engine/common/hash_calculator.cc
@@ -0,0 +1,143 @@
+//
+// Copyright (C) 2009 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/hash_calculator.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <brillo/data_encoding.h>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+HashCalculator::HashCalculator() : valid_(false) {
+  valid_ = (SHA256_Init(&ctx_) == 1);
+  LOG_IF(ERROR, !valid_) << "SHA256_Init failed";
+}
+
+// Update is called with all of the data that should be hashed in order.
+// Mostly just passes the data through to OpenSSL's SHA256_Update()
+bool HashCalculator::Update(const void* data, size_t length) {
+  TEST_AND_RETURN_FALSE(valid_);
+  TEST_AND_RETURN_FALSE(hash_.empty());
+  static_assert(sizeof(size_t) <= sizeof(unsigned long),  // NOLINT(runtime/int)
+                "length param may be truncated in SHA256_Update");
+  TEST_AND_RETURN_FALSE(SHA256_Update(&ctx_, data, length) == 1);
+  return true;
+}
+
+off_t HashCalculator::UpdateFile(const string& name, off_t length) {
+  int fd = HANDLE_EINTR(open(name.c_str(), O_RDONLY));
+  if (fd < 0) {
+    return -1;
+  }
+
+  const int kBufferSize = 128 * 1024;  // 128 KiB
+  brillo::Blob buffer(kBufferSize);
+  off_t bytes_processed = 0;
+  while (length < 0 || bytes_processed < length) {
+    off_t bytes_to_read = buffer.size();
+    if (length >= 0 && bytes_to_read > length - bytes_processed) {
+      bytes_to_read = length - bytes_processed;
+    }
+    ssize_t rc = HANDLE_EINTR(read(fd, buffer.data(), bytes_to_read));
+    if (rc == 0) {  // EOF
+      break;
+    }
+    if (rc < 0 || !Update(buffer.data(), rc)) {
+      bytes_processed = -1;
+      break;
+    }
+    bytes_processed += rc;
+  }
+  IGNORE_EINTR(close(fd));
+  return bytes_processed;
+}
+
+// Call Finalize() when all data has been passed in. This mostly just
+// calls OpenSSL's SHA256_Final() and then base64 encodes the hash.
+bool HashCalculator::Finalize() {
+  TEST_AND_RETURN_FALSE(hash_.empty());
+  TEST_AND_RETURN_FALSE(raw_hash_.empty());
+  raw_hash_.resize(SHA256_DIGEST_LENGTH);
+  TEST_AND_RETURN_FALSE(SHA256_Final(raw_hash_.data(), &ctx_) == 1);
+
+  // Convert raw_hash_ to base64 encoding and store it in hash_.
+  hash_ = brillo::data_encoding::Base64Encode(raw_hash_.data(),
+                                              raw_hash_.size());
+  return true;
+}
+
+bool HashCalculator::RawHashOfBytes(const void* data,
+                                    size_t length,
+                                    brillo::Blob* out_hash) {
+  HashCalculator calc;
+  TEST_AND_RETURN_FALSE(calc.Update(data, length));
+  TEST_AND_RETURN_FALSE(calc.Finalize());
+  *out_hash = calc.raw_hash();
+  return true;
+}
+
+bool HashCalculator::RawHashOfData(const brillo::Blob& data,
+                                   brillo::Blob* out_hash) {
+  return RawHashOfBytes(data.data(), data.size(), out_hash);
+}
+
+off_t HashCalculator::RawHashOfFile(const string& name, off_t length,
+                                    brillo::Blob* out_hash) {
+  HashCalculator calc;
+  off_t res = calc.UpdateFile(name, length);
+  if (res < 0) {
+    return res;
+  }
+  if (!calc.Finalize()) {
+    return -1;
+  }
+  *out_hash = calc.raw_hash();
+  return res;
+}
+
+string HashCalculator::HashOfBytes(const void* data, size_t length) {
+  HashCalculator calc;
+  calc.Update(data, length);
+  calc.Finalize();
+  return calc.hash();
+}
+
+string HashCalculator::HashOfString(const string& str) {
+  return HashOfBytes(str.data(), str.size());
+}
+
+string HashCalculator::HashOfData(const brillo::Blob& data) {
+  return HashOfBytes(data.data(), data.size());
+}
+
+string HashCalculator::GetContext() const {
+  return string(reinterpret_cast<const char*>(&ctx_), sizeof(ctx_));
+}
+
+bool HashCalculator::SetContext(const string& context) {
+  TEST_AND_RETURN_FALSE(context.size() == sizeof(ctx_));
+  memcpy(&ctx_, context.data(), sizeof(ctx_));
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/hash_calculator.h b/update_engine/common/hash_calculator.h
new file mode 100644
index 0000000..f749585
--- /dev/null
+++ b/update_engine/common/hash_calculator.h
@@ -0,0 +1,107 @@
+//
+// Copyright (C) 2009 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_COMMON_HASH_CALCULATOR_H_
+#define UPDATE_ENGINE_COMMON_HASH_CALCULATOR_H_
+
+#include <openssl/sha.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+// Omaha uses base64 encoded SHA-256 as the hash. This class provides a simple
+// wrapper around OpenSSL providing such a formatted hash of data passed in.
+// The methods of this class must be called in a very specific order: First the
+// ctor (of course), then 0 or more calls to Update(), then Finalize(), then 0
+// or more calls to hash().
+
+namespace chromeos_update_engine {
+
+class HashCalculator {
+ public:
+  HashCalculator();
+
+  // Update is called with all of the data that should be hashed in order.
+  // Update will read |length| bytes of |data|.
+  // Returns true on success.
+  bool Update(const void* data, size_t length);
+
+  // Updates the hash with up to |length| bytes of data from |file|. If |length|
+  // is negative, reads in and updates with the whole file. Returns the number
+  // of bytes that the hash was updated with, or -1 on error.
+  off_t UpdateFile(const std::string& name, off_t length);
+
+  // Call Finalize() when all data has been passed in. This method tells
+  // OpenSSl that no more data will come in and base64 encodes the resulting
+  // hash.
+  // Returns true on success.
+  bool Finalize();
+
+  // Gets the hash. Finalize() must have been called.
+  const std::string& hash() const {
+    DCHECK(!hash_.empty()) << "Call Finalize() first";
+    return hash_;
+  }
+
+  const brillo::Blob& raw_hash() const {
+    DCHECK(!raw_hash_.empty()) << "Call Finalize() first";
+    return raw_hash_;
+  }
+
+  // Gets the current hash context. Note that the string will contain binary
+  // data (including \0 characters).
+  std::string GetContext() const;
+
+  // Sets the current hash context. |context| must the string returned by a
+  // previous HashCalculator::GetContext method call. Returns true on success,
+  // and false otherwise.
+  bool SetContext(const std::string& context);
+
+  static bool RawHashOfBytes(const void* data,
+                             size_t length,
+                             brillo::Blob* out_hash);
+  static bool RawHashOfData(const brillo::Blob& data,
+                            brillo::Blob* out_hash);
+  static off_t RawHashOfFile(const std::string& name, off_t length,
+                             brillo::Blob* out_hash);
+
+  // Used by tests
+  static std::string HashOfBytes(const void* data, size_t length);
+  static std::string HashOfString(const std::string& str);
+  static std::string HashOfData(const brillo::Blob& data);
+
+ private:
+  // If non-empty, the final base64 encoded hash and the raw hash. Will only be
+  // set to non-empty when Finalize is called.
+  std::string hash_;
+  brillo::Blob raw_hash_;
+
+  // Init success
+  bool valid_;
+
+  // The hash state used by OpenSSL
+  SHA256_CTX ctx_;
+  DISALLOW_COPY_AND_ASSIGN(HashCalculator);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_HASH_CALCULATOR_H_
diff --git a/update_engine/common/hash_calculator_unittest.cc b/update_engine/common/hash_calculator_unittest.cc
new file mode 100644
index 0000000..436e6a7
--- /dev/null
+++ b/update_engine/common/hash_calculator_unittest.cc
@@ -0,0 +1,173 @@
+//
+// Copyright (C) 2009 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/hash_calculator.h"
+
+#include <math.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// Generated by running this on a linux shell:
+// $ echo -n hi | openssl dgst -sha256 -binary | openssl base64
+static const char kExpectedHash[] =
+    "j0NDRmSPa5bfid2pAcUXaxCm2Dlh3TwayItZstwyeqQ=";
+static const uint8_t kExpectedRawHash[] = {
+  0x8f, 0x43, 0x43, 0x46, 0x64, 0x8f, 0x6b, 0x96,
+  0xdf, 0x89, 0xdd, 0xa9, 0x01, 0xc5, 0x17, 0x6b,
+  0x10, 0xa6, 0xd8, 0x39, 0x61, 0xdd, 0x3c, 0x1a,
+  0xc8, 0x8b, 0x59, 0xb2, 0xdc, 0x32, 0x7a, 0xa4
+};
+
+class HashCalculatorTest : public ::testing::Test {
+ public:
+  HashCalculatorTest() {}
+};
+
+TEST_F(HashCalculatorTest, SimpleTest) {
+  HashCalculator calc;
+  calc.Update("hi", 2);
+  calc.Finalize();
+  EXPECT_EQ(kExpectedHash, calc.hash());
+  brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+                        std::end(kExpectedRawHash));
+  EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(HashCalculatorTest, MultiUpdateTest) {
+  HashCalculator calc;
+  calc.Update("h", 1);
+  calc.Update("i", 1);
+  calc.Finalize();
+  EXPECT_EQ(kExpectedHash, calc.hash());
+  brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+                        std::end(kExpectedRawHash));
+  EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(HashCalculatorTest, ContextTest) {
+  HashCalculator calc;
+  calc.Update("h", 1);
+  string calc_context = calc.GetContext();
+  calc.Finalize();
+  HashCalculator calc_next;
+  calc_next.SetContext(calc_context);
+  calc_next.Update("i", 1);
+  calc_next.Finalize();
+  EXPECT_EQ(kExpectedHash, calc_next.hash());
+  brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+                        std::end(kExpectedRawHash));
+  EXPECT_TRUE(raw_hash == calc_next.raw_hash());
+}
+
+TEST_F(HashCalculatorTest, BigTest) {
+  HashCalculator calc;
+
+  int digit_count = 1;
+  int next_overflow = 10;
+  for (int i = 0; i < 1000000; i++) {
+    char buf[8];
+    if (i == next_overflow) {
+      next_overflow *= 10;
+      digit_count++;
+    }
+    ASSERT_EQ(digit_count, snprintf(buf, sizeof(buf), "%d", i)) << " i = " << i;
+    calc.Update(buf, strlen(buf));
+  }
+  calc.Finalize();
+
+  // Hash constant generated by running this on a linux shell:
+  // $ C=0
+  // $ while [ $C -lt 1000000 ]; do
+  //     echo -n $C
+  //     let C=C+1
+  //   done | openssl dgst -sha256 -binary | openssl base64
+  EXPECT_EQ("NZf8k6SPBkYMvhaX8YgzuMgbkLP1XZ+neM8K5wcSsf8=", calc.hash());
+}
+
+TEST_F(HashCalculatorTest, UpdateFileSimpleTest) {
+  string data_path;
+  ASSERT_TRUE(
+      utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+  ScopedPathUnlinker data_path_unlinker(data_path);
+  ASSERT_TRUE(utils::WriteFile(data_path.c_str(), "hi", 2));
+
+  static const int kLengths[] = { -1, 2, 10 };
+  for (size_t i = 0; i < arraysize(kLengths); i++) {
+    HashCalculator calc;
+    EXPECT_EQ(2, calc.UpdateFile(data_path, kLengths[i]));
+    EXPECT_TRUE(calc.Finalize());
+    EXPECT_EQ(kExpectedHash, calc.hash());
+    brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+                          std::end(kExpectedRawHash));
+    EXPECT_TRUE(raw_hash == calc.raw_hash());
+  }
+
+  HashCalculator calc;
+  EXPECT_EQ(0, calc.UpdateFile(data_path, 0));
+  EXPECT_EQ(1, calc.UpdateFile(data_path, 1));
+  EXPECT_TRUE(calc.Finalize());
+  // echo -n h | openssl dgst -sha256 -binary | openssl base64
+  EXPECT_EQ("qqlAJmTxpB9A67xSyZk+tmrrNmYClY/fqig7ceZNsSM=", calc.hash());
+}
+
+TEST_F(HashCalculatorTest, RawHashOfFileSimpleTest) {
+  string data_path;
+  ASSERT_TRUE(
+      utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+  ScopedPathUnlinker data_path_unlinker(data_path);
+  ASSERT_TRUE(utils::WriteFile(data_path.c_str(), "hi", 2));
+
+  static const int kLengths[] = { -1, 2, 10 };
+  for (size_t i = 0; i < arraysize(kLengths); i++) {
+    brillo::Blob exp_raw_hash(std::begin(kExpectedRawHash),
+                              std::end(kExpectedRawHash));
+    brillo::Blob raw_hash;
+    EXPECT_EQ(2, HashCalculator::RawHashOfFile(data_path,
+                                               kLengths[i],
+                                               &raw_hash));
+    EXPECT_TRUE(exp_raw_hash == raw_hash);
+  }
+}
+
+TEST_F(HashCalculatorTest, UpdateFileNonexistentTest) {
+  HashCalculator calc;
+  EXPECT_EQ(-1, calc.UpdateFile("/some/non-existent/file", -1));
+}
+
+TEST_F(HashCalculatorTest, AbortTest) {
+  // Just make sure we don't crash and valgrind doesn't detect memory leaks
+  {
+    HashCalculator calc;
+  }
+  {
+    HashCalculator calc;
+    calc.Update("h", 1);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/http_common.cc b/update_engine/common/http_common.cc
new file mode 100644
index 0000000..d07ced3
--- /dev/null
+++ b/update_engine/common/http_common.cc
@@ -0,0 +1,88 @@
+//
+// Copyright (C) 2009 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.
+//
+
+// Implementation of common HTTP related functions.
+
+#include "update_engine/common/http_common.h"
+
+#include <cstdlib>
+
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+const char *GetHttpResponseDescription(HttpResponseCode code) {
+  static const struct {
+    HttpResponseCode code;
+    const char* description;
+  } http_response_table[] = {
+    { kHttpResponseOk,                  "OK" },
+    { kHttpResponseCreated,             "Created" },
+    { kHttpResponseAccepted,            "Accepted" },
+    { kHttpResponseNonAuthInfo,         "Non-Authoritative Information" },
+    { kHttpResponseNoContent,           "No Content" },
+    { kHttpResponseResetContent,        "Reset Content" },
+    { kHttpResponsePartialContent,      "Partial Content" },
+    { kHttpResponseMultipleChoices,     "Multiple Choices" },
+    { kHttpResponseMovedPermanently,    "Moved Permanently" },
+    { kHttpResponseFound,               "Found" },
+    { kHttpResponseSeeOther,            "See Other" },
+    { kHttpResponseNotModified,         "Not Modified" },
+    { kHttpResponseUseProxy,            "Use Proxy" },
+    { kHttpResponseTempRedirect,        "Temporary Redirect" },
+    { kHttpResponseBadRequest,          "Bad Request" },
+    { kHttpResponseUnauth,              "Unauthorized" },
+    { kHttpResponseForbidden,           "Forbidden" },
+    { kHttpResponseNotFound,            "Not Found" },
+    { kHttpResponseRequestTimeout,      "Request Timeout" },
+    { kHttpResponseInternalServerError, "Internal Server Error" },
+    { kHttpResponseNotImplemented,      "Not Implemented" },
+    { kHttpResponseServiceUnavailable,  "Service Unavailable" },
+    { kHttpResponseVersionNotSupported, "HTTP Version Not Supported" },
+  };
+
+  bool is_found = false;
+  size_t i;
+  for (i = 0; i < arraysize(http_response_table); i++)
+    if ((is_found = (http_response_table[i].code == code)))
+      break;
+
+  return (is_found ? http_response_table[i].description : "(unsupported)");
+}
+
+HttpResponseCode StringToHttpResponseCode(const char *s) {
+  return static_cast<HttpResponseCode>(strtoul(s, nullptr, 10));
+}
+
+
+const char *GetHttpContentTypeString(HttpContentType type) {
+  static const struct {
+    HttpContentType type;
+    const char* str;
+  } http_content_type_table[] = {
+    { kHttpContentTypeTextXml, "text/xml" },
+  };
+
+  bool is_found = false;
+  size_t i;
+  for (i = 0; i < arraysize(http_content_type_table); i++)
+    if ((is_found = (http_content_type_table[i].type == type)))
+      break;
+
+  return (is_found ? http_content_type_table[i].str : nullptr);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/http_common.h b/update_engine/common/http_common.h
new file mode 100644
index 0000000..6d444ed
--- /dev/null
+++ b/update_engine/common/http_common.h
@@ -0,0 +1,72 @@
+//
+// Copyright (C) 2009 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 file contains general definitions used in implementing, testing and
+// emulating communication over HTTP.
+
+#ifndef UPDATE_ENGINE_COMMON_HTTP_COMMON_H_
+#define UPDATE_ENGINE_COMMON_HTTP_COMMON_H_
+
+namespace chromeos_update_engine {
+
+// Enumeration type for HTTP response codes.
+enum HttpResponseCode {
+  kHttpResponseUndefined           = 0,
+  kHttpResponseOk                  = 200,
+  kHttpResponseCreated             = 201,
+  kHttpResponseAccepted            = 202,
+  kHttpResponseNonAuthInfo         = 203,
+  kHttpResponseNoContent           = 204,
+  kHttpResponseResetContent        = 205,
+  kHttpResponsePartialContent      = 206,
+  kHttpResponseMultipleChoices     = 300,
+  kHttpResponseMovedPermanently    = 301,
+  kHttpResponseFound               = 302,
+  kHttpResponseSeeOther            = 303,
+  kHttpResponseNotModified         = 304,
+  kHttpResponseUseProxy            = 305,
+  kHttpResponseTempRedirect        = 307,
+  kHttpResponseBadRequest          = 400,
+  kHttpResponseUnauth              = 401,
+  kHttpResponseForbidden           = 403,
+  kHttpResponseNotFound            = 404,
+  kHttpResponseRequestTimeout      = 408,
+  kHttpResponseReqRangeNotSat      = 416,
+  kHttpResponseInternalServerError = 500,
+  kHttpResponseNotImplemented      = 501,
+  kHttpResponseServiceUnavailable  = 503,
+  kHttpResponseVersionNotSupported = 505,
+};
+
+// Returns a standard HTTP status line string for a given response code.
+const char *GetHttpResponseDescription(HttpResponseCode code);
+
+// Converts a string beginning with an HTTP error code into numerical value.
+HttpResponseCode StringToHttpResponseCode(const char *s);
+
+
+// Enumeration type for HTTP Content-Type.
+enum HttpContentType {
+  kHttpContentTypeUnspecified = 0,
+  kHttpContentTypeTextXml,
+};
+
+// Returns a standard HTTP Content-Type string.
+const char *GetHttpContentTypeString(HttpContentType type);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_HTTP_COMMON_H_
diff --git a/update_engine/common/http_fetcher.cc b/update_engine/common/http_fetcher.cc
new file mode 100644
index 0000000..400b43c
--- /dev/null
+++ b/update_engine/common/http_fetcher.cc
@@ -0,0 +1,82 @@
+//
+// Copyright (C) 2009 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/http_fetcher.h"
+
+#include <base/bind.h>
+
+using base::Closure;
+using brillo::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+HttpFetcher::~HttpFetcher() {
+  if (no_resolver_idle_id_ != MessageLoop::kTaskIdNull) {
+    MessageLoop::current()->CancelTask(no_resolver_idle_id_);
+    no_resolver_idle_id_ = MessageLoop::kTaskIdNull;
+  }
+}
+
+void HttpFetcher::SetPostData(const void* data, size_t size,
+                              HttpContentType type) {
+  post_data_set_ = true;
+  post_data_.clear();
+  const char* char_data = reinterpret_cast<const char*>(data);
+  post_data_.insert(post_data_.end(), char_data, char_data + size);
+  post_content_type_ = type;
+}
+
+void HttpFetcher::SetPostData(const void* data, size_t size) {
+  SetPostData(data, size, kHttpContentTypeUnspecified);
+}
+
+// Proxy methods to set the proxies, then to pop them off.
+bool HttpFetcher::ResolveProxiesForUrl(const string& url,
+                                       const Closure& callback) {
+  CHECK_EQ(static_cast<Closure*>(nullptr), callback_.get());
+  callback_.reset(new Closure(callback));
+
+  if (!proxy_resolver_) {
+    LOG(INFO) << "Not resolving proxies (no proxy resolver).";
+    no_resolver_idle_id_ = MessageLoop::current()->PostTask(
+        FROM_HERE,
+        base::Bind(&HttpFetcher::NoProxyResolverCallback,
+                   base::Unretained(this)));
+    return true;
+  }
+  return proxy_resolver_->GetProxiesForUrl(url,
+                                           &HttpFetcher::StaticProxiesResolved,
+                                           this);
+}
+
+void HttpFetcher::NoProxyResolverCallback() {
+  ProxiesResolved(deque<string>());
+}
+
+void HttpFetcher::ProxiesResolved(const deque<string>& proxies) {
+  no_resolver_idle_id_ = MessageLoop::kTaskIdNull;
+  if (!proxies.empty())
+    SetProxies(proxies);
+  CHECK_NE(static_cast<Closure*>(nullptr), callback_.get());
+  Closure* callback = callback_.release();
+  // This may indirectly call back into ResolveProxiesForUrl():
+  callback->Run();
+  delete callback;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/http_fetcher.h b/update_engine/common/http_fetcher.h
new file mode 100644
index 0000000..d2499eb
--- /dev/null
+++ b/update_engine/common/http_fetcher.h
@@ -0,0 +1,203 @@
+//
+// Copyright (C) 2009 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_COMMON_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_HTTP_FETCHER_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/http_common.h"
+#include "update_engine/proxy_resolver.h"
+
+// This class is a simple wrapper around an HTTP library (libcurl). We can
+// easily mock out this interface for testing.
+
+// Implementations of this class should use asynchronous i/o. They can access
+// the MessageLoop to request callbacks when timers or file descriptors change.
+
+namespace chromeos_update_engine {
+
+class HttpFetcherDelegate;
+
+class HttpFetcher {
+ public:
+  // |proxy_resolver| is the resolver that will be consulted for proxy
+  // settings. It may be null, in which case direct connections will
+  // be used. Does not take ownership of the resolver.
+  explicit HttpFetcher(ProxyResolver* proxy_resolver)
+      : post_data_set_(false),
+        http_response_code_(0),
+        delegate_(nullptr),
+        proxies_(1, kNoProxy),
+        proxy_resolver_(proxy_resolver),
+        callback_(nullptr) {}
+  virtual ~HttpFetcher();
+
+  void set_delegate(HttpFetcherDelegate* delegate) { delegate_ = delegate; }
+  HttpFetcherDelegate* delegate() const { return delegate_; }
+  int http_response_code() const { return http_response_code_; }
+
+  // Optional: Post data to the server. The HttpFetcher should make a copy
+  // of this data and upload it via HTTP POST during the transfer. The type of
+  // the data is necessary for properly setting the Content-Type HTTP header.
+  void SetPostData(const void* data, size_t size, HttpContentType type);
+
+  // Same without a specified Content-Type.
+  void SetPostData(const void* data, size_t size);
+
+  // Proxy methods to set the proxies, then to pop them off.
+  // Returns true on success.
+  bool ResolveProxiesForUrl(const std::string& url,
+                            const base::Closure& callback);
+
+  void SetProxies(const std::deque<std::string>& proxies) {
+    proxies_ = proxies;
+  }
+  const std::string& GetCurrentProxy() const {
+    return proxies_.front();
+  }
+  bool HasProxy() const { return !proxies_.empty(); }
+  void PopProxy() { proxies_.pop_front(); }
+
+  // Downloading should resume from this offset
+  virtual void SetOffset(off_t offset) = 0;
+
+  // Set/unset the length of the range to be downloaded.
+  virtual void SetLength(size_t length) = 0;
+  virtual void UnsetLength() = 0;
+
+  // Begins the transfer to the specified URL. This fetcher instance should not
+  // be destroyed until either TransferComplete, or TransferTerminated is
+  // called.
+  virtual void BeginTransfer(const std::string& url) = 0;
+
+  // Aborts the transfer. The transfer may not abort right away -- delegate's
+  // TransferTerminated() will be called when the transfer is actually done.
+  virtual void TerminateTransfer() = 0;
+
+  // Add or update a custom header to be sent with every request. If the same
+  // |header_name| is passed twice, the second |header_value| would override the
+  // previous value.
+  virtual void SetHeader(const std::string& header_name,
+                         const std::string& header_value) = 0;
+
+  // If data is coming in too quickly, you can call Pause() to pause the
+  // transfer. The delegate will not have ReceivedBytes() called while
+  // an HttpFetcher is paused.
+  virtual void Pause() = 0;
+
+  // Used to unpause an HttpFetcher and let the bytes stream in again.
+  // If a delegate is set, ReceivedBytes() may be called on it before
+  // Unpause() returns
+  virtual void Unpause() = 0;
+
+  // These two function are overloaded in LibcurlHttp fetcher to speed
+  // testing.
+  virtual void set_idle_seconds(int seconds) {}
+  virtual void set_retry_seconds(int seconds) {}
+
+  // Sets the values used to time out the connection if the transfer
+  // rate is less than |low_speed_bps| bytes/sec for more than
+  // |low_speed_sec| seconds.
+  virtual void set_low_speed_limit(int low_speed_bps, int low_speed_sec) = 0;
+
+  // Sets the connect timeout, e.g. the maximum amount of time willing
+  // to wait for establishing a connection to the server.
+  virtual void set_connect_timeout(int connect_timeout_seconds) = 0;
+
+  // Sets the number of allowed retries.
+  virtual void set_max_retry_count(int max_retry_count) = 0;
+
+  // Get the total number of bytes downloaded by fetcher.
+  virtual size_t GetBytesDownloaded() = 0;
+
+  ProxyResolver* proxy_resolver() const { return proxy_resolver_; }
+
+ protected:
+  // The URL we're actively fetching from
+  std::string url_;
+
+  // POST data for the transfer, and whether or not it was ever set
+  bool post_data_set_;
+  brillo::Blob post_data_;
+  HttpContentType post_content_type_;
+
+  // The server's HTTP response code from the last transfer. This
+  // field should be set to 0 when a new transfer is initiated, and
+  // set to the response code when the transfer is complete.
+  int http_response_code_;
+
+  // The delegate; may be null.
+  HttpFetcherDelegate* delegate_;
+
+  // Proxy servers
+  std::deque<std::string> proxies_;
+
+  ProxyResolver* const proxy_resolver_;
+
+  // The ID of the idle callback, used when we have no proxy resolver.
+  brillo::MessageLoop::TaskId no_resolver_idle_id_{
+      brillo::MessageLoop::kTaskIdNull};
+
+  // Callback for when we are resolving proxies
+  std::unique_ptr<base::Closure> callback_;
+
+ private:
+  // Callback from the proxy resolver
+  void ProxiesResolved(const std::deque<std::string>& proxies);
+  static void StaticProxiesResolved(const std::deque<std::string>& proxies,
+                                    void* data) {
+    reinterpret_cast<HttpFetcher*>(data)->ProxiesResolved(proxies);
+  }
+
+  // Callback used to run the proxy resolver callback when there is no
+  // |proxy_resolver_|.
+  void NoProxyResolverCallback();
+
+  DISALLOW_COPY_AND_ASSIGN(HttpFetcher);
+};
+
+// Interface for delegates
+class HttpFetcherDelegate {
+ public:
+  virtual ~HttpFetcherDelegate() = default;
+
+  // Called every time bytes are received.
+  virtual void ReceivedBytes(HttpFetcher* fetcher,
+                             const void* bytes,
+                             size_t length) = 0;
+
+  // Called if the fetcher seeks to a particular offset.
+  virtual void SeekToOffset(off_t offset) {}
+
+  // When a transfer has completed, exactly one of these two methods will be
+  // called. TransferTerminated is called when the transfer has been aborted
+  // through TerminateTransfer. TransferComplete is called in all other
+  // situations. It's OK to destroy the |fetcher| object in this callback.
+  virtual void TransferComplete(HttpFetcher* fetcher, bool successful) = 0;
+  virtual void TransferTerminated(HttpFetcher* fetcher) {}
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_HTTP_FETCHER_H_
diff --git a/update_engine/common/http_fetcher_unittest.cc b/update_engine/common/http_fetcher_unittest.cc
new file mode 100644
index 0000000..0f34475
--- /dev/null
+++ b/update_engine/common/http_fetcher_unittest.cc
@@ -0,0 +1,1240 @@
+//
+// 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 <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.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/bind_lambda.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 <brillo/process.h>
+#include <brillo/streams/file_stream.h>
+#include <brillo/streams/stream.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/file_fetcher.h"
+#include "update_engine/common/http_common.h"
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/mock_proxy_resolver.h"
+#include "update_engine/proxy_resolver.h"
+
+using brillo::MessageLoop;
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::DoAll;
+using testing::Return;
+using testing::SaveArg;
+using testing::_;
+
+namespace {
+
+const int kBigLength           = 100000;
+const int kMediumLength        = 1000;
+const int kFlakyTruncateLength = 29000;
+const int kFlakySleepEvery     = 3;
+const int kFlakySleepSecs      = 10;
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+static const char *kUnusedUrl = "unused://unused";
+
+static inline string LocalServerUrlForPath(in_port_t port,
+                                           const string& path) {
+  string port_str = (port ? base::StringPrintf(":%hu", port) : "");
+  return base::StringPrintf("http://127.0.0.1%s%s", port_str.c_str(),
+                            path.c_str());
+}
+
+//
+// Class hierarchy for HTTP server implementations.
+//
+
+class HttpServer {
+ public:
+  // This makes it an abstract class (dirty but works).
+  virtual ~HttpServer() = 0;
+
+  virtual in_port_t GetPort() const {
+    return 0;
+  }
+
+  bool started_;
+};
+
+HttpServer::~HttpServer() {}
+
+
+class NullHttpServer : public HttpServer {
+ public:
+  NullHttpServer() {
+    started_ = true;
+  }
+};
+
+
+class PythonHttpServer : public HttpServer {
+ public:
+  PythonHttpServer() : port_(0) {
+    started_ = false;
+
+    // Spawn the server process.
+    unique_ptr<brillo::Process> http_server(new brillo::ProcessImpl());
+    http_server->AddArg(test_utils::GetBuildArtifactsPath("test_http_server"));
+    http_server->RedirectUsingPipe(STDOUT_FILENO, false);
+
+    if (!http_server->Start()) {
+      ADD_FAILURE() << "failed to spawn http server process";
+      return;
+    }
+    LOG(INFO) << "started http server with pid " << http_server->pid();
+
+    // Wait for server to begin accepting connections, obtain its port.
+    brillo::StreamPtr stdout = brillo::FileStream::FromFileDescriptor(
+        http_server->GetPipe(STDOUT_FILENO), false /* own */, nullptr);
+    if (!stdout)
+      return;
+
+    vector<char> buf(128);
+    string line;
+    while (line.find('\n') == string::npos) {
+      size_t read;
+      if (!stdout->ReadBlocking(buf.data(), buf.size(), &read, nullptr)) {
+        ADD_FAILURE() << "error reading http server stdout";
+        return;
+      }
+      line.append(buf.data(), read);
+      if (read == 0)
+        break;
+    }
+    // Parse the port from the output line.
+    const size_t listening_msg_prefix_len = strlen(kServerListeningMsgPrefix);
+    if (line.size() < listening_msg_prefix_len) {
+      ADD_FAILURE() << "server output too short";
+      return;
+    }
+
+    EXPECT_EQ(kServerListeningMsgPrefix,
+              line.substr(0, listening_msg_prefix_len));
+    string port_str = line.substr(listening_msg_prefix_len);
+    port_str.resize(port_str.find('\n'));
+    EXPECT_TRUE(base::StringToUint(port_str, &port_));
+
+    started_ = true;
+    LOG(INFO) << "server running, listening on port " << port_;
+
+    // Any failure before this point will SIGKILL the test server if started
+    // when the |http_server| goes out of scope.
+    http_server_ = std::move(http_server);
+  }
+
+  ~PythonHttpServer() {
+    // If there's no process, do nothing.
+    if (!http_server_)
+      return;
+    // Wait up to 10 seconds for the process to finish. Destroying the process
+    // will kill it with a SIGKILL otherwise.
+    http_server_->Kill(SIGTERM, 10);
+  }
+
+  in_port_t GetPort() const override {
+    return port_;
+  }
+
+ private:
+  static const char* kServerListeningMsgPrefix;
+
+  unique_ptr<brillo::Process> http_server_;
+  unsigned int port_;
+};
+
+const char* PythonHttpServer::kServerListeningMsgPrefix = "listening on port ";
+
+//
+// Class hierarchy for HTTP fetcher test wrappers.
+//
+
+class AnyHttpFetcherTest {
+ public:
+  AnyHttpFetcherTest() {}
+  virtual ~AnyHttpFetcherTest() {}
+
+  virtual HttpFetcher* NewLargeFetcher(ProxyResolver* proxy_resolver) = 0;
+  HttpFetcher* NewLargeFetcher(size_t num_proxies) {
+    proxy_resolver_.set_num_proxies(num_proxies);
+    return NewLargeFetcher(&proxy_resolver_);
+  }
+  HttpFetcher* NewLargeFetcher() {
+    return NewLargeFetcher(1);
+  }
+
+  virtual HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) = 0;
+  HttpFetcher* NewSmallFetcher() {
+    proxy_resolver_.set_num_proxies(1);
+    return NewSmallFetcher(&proxy_resolver_);
+  }
+
+  virtual string BigUrl(in_port_t port) const { return kUnusedUrl; }
+  virtual string SmallUrl(in_port_t port) const { return kUnusedUrl; }
+  virtual string ErrorUrl(in_port_t port) const { return kUnusedUrl; }
+
+  virtual bool IsMock() const = 0;
+  virtual bool IsMulti() const = 0;
+  virtual bool IsHttpSupported() const = 0;
+
+  virtual void IgnoreServerAborting(HttpServer* server) const {}
+
+  virtual HttpServer* CreateServer() = 0;
+
+  FakeHardware* fake_hardware() {
+    return &fake_hardware_;
+  }
+
+ protected:
+  DirectProxyResolver proxy_resolver_;
+  FakeHardware fake_hardware_;
+};
+
+class MockHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewLargeFetcher;
+  HttpFetcher* NewLargeFetcher(ProxyResolver* proxy_resolver) override {
+    brillo::Blob big_data(1000000);
+    return new MockHttpFetcher(
+        big_data.data(), big_data.size(), proxy_resolver);
+  }
+
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewSmallFetcher;
+  HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override {
+    return new MockHttpFetcher("x", 1, proxy_resolver);
+  }
+
+  bool IsMock() const override { return true; }
+  bool IsMulti() const override { return false; }
+  bool IsHttpSupported() const override { return true; }
+
+  HttpServer* CreateServer() override {
+    return new NullHttpServer;
+  }
+};
+
+class LibcurlHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewLargeFetcher;
+  HttpFetcher* NewLargeFetcher(ProxyResolver* proxy_resolver) override {
+    LibcurlHttpFetcher* ret =
+        new LibcurlHttpFetcher(proxy_resolver, &fake_hardware_);
+    // Speed up test execution.
+    ret->set_idle_seconds(1);
+    ret->set_retry_seconds(1);
+    fake_hardware_.SetIsOfficialBuild(false);
+    return ret;
+  }
+
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewSmallFetcher;
+  HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override {
+    return NewLargeFetcher(proxy_resolver);
+  }
+
+  string BigUrl(in_port_t port) const override {
+    return LocalServerUrlForPath(port,
+                                 base::StringPrintf("/download/%d",
+                                                    kBigLength));
+  }
+  string SmallUrl(in_port_t port) const override {
+    return LocalServerUrlForPath(port, "/foo");
+  }
+  string ErrorUrl(in_port_t port) const override {
+    return LocalServerUrlForPath(port, "/error");
+  }
+
+  bool IsMock() const override { return false; }
+  bool IsMulti() const override { return false; }
+  bool IsHttpSupported() const override { return true; }
+
+  void IgnoreServerAborting(HttpServer* server) const override {
+    // Nothing to do.
+  }
+
+  HttpServer* CreateServer() override {
+    return new PythonHttpServer;
+  }
+};
+
+class MultiRangeHttpFetcherTest : public LibcurlHttpFetcherTest {
+ public:
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewLargeFetcher;
+  HttpFetcher* NewLargeFetcher(ProxyResolver* proxy_resolver) override {
+    MultiRangeHttpFetcher* ret = new MultiRangeHttpFetcher(
+        new LibcurlHttpFetcher(proxy_resolver, &fake_hardware_));
+    ret->ClearRanges();
+    ret->AddRange(0);
+    // Speed up test execution.
+    ret->set_idle_seconds(1);
+    ret->set_retry_seconds(1);
+    fake_hardware_.SetIsOfficialBuild(false);
+    return ret;
+  }
+
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewSmallFetcher;
+  HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override {
+    return NewLargeFetcher(proxy_resolver);
+  }
+
+  bool IsMulti() const override { return true; }
+};
+
+class FileFetcherTest : public AnyHttpFetcherTest {
+ public:
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewLargeFetcher;
+  HttpFetcher* NewLargeFetcher(ProxyResolver* /* proxy_resolver */) override {
+    return new FileFetcher();
+  }
+
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewSmallFetcher;
+  HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override {
+    return NewLargeFetcher(proxy_resolver);
+  }
+
+  string BigUrl(in_port_t port) const override {
+    return "file://" + temp_file_.path();
+  }
+  string SmallUrl(in_port_t port) const override {
+    test_utils::WriteFileString(temp_file_.path(), "small contents");
+    return "file://" + temp_file_.path();
+  }
+  string ErrorUrl(in_port_t port) const override {
+    return "file:///path/to/non-existing-file";
+  }
+
+  bool IsMock() const override { return false; }
+  bool IsMulti() const override { return false; }
+  bool IsHttpSupported() const override { return false; }
+
+  void IgnoreServerAborting(HttpServer* server) const override {}
+
+  HttpServer* CreateServer() override { return new NullHttpServer; }
+
+ private:
+  test_utils::ScopedTempFile temp_file_{"ue_file_fetcher.XXXXXX"};
+};
+
+//
+// Infrastructure for type tests of HTTP fetcher.
+// See: http://code.google.com/p/googletest/wiki/AdvancedGuide#Typed_Tests
+//
+
+// Fixture class template. We use an explicit constraint to guarantee that it
+// can only be instantiated with an AnyHttpFetcherTest type, see:
+// http://www2.research.att.com/~bs/bs_faq2.html#constraints
+template <typename T>
+class HttpFetcherTest : public ::testing::Test {
+ public:
+  base::MessageLoopForIO base_loop_;
+  brillo::BaseMessageLoop loop_{&base_loop_};
+
+  T test_;
+
+ protected:
+  HttpFetcherTest() {
+    loop_.SetAsCurrent();
+  }
+
+  void TearDown() override {
+    EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
+  }
+
+ private:
+  static void TypeConstraint(T* a) {
+    AnyHttpFetcherTest *b = a;
+    if (b == 0)  // Silence compiler warning of unused variable.
+      *b = a;
+  }
+};
+
+// Test case types list.
+typedef ::testing::Types<LibcurlHttpFetcherTest,
+                         MockHttpFetcherTest,
+                         MultiRangeHttpFetcherTest,
+                         FileFetcherTest>
+    HttpFetcherTestTypes;
+TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes);
+
+
+namespace {
+class HttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  HttpFetcherTestDelegate() = default;
+
+  void ReceivedBytes(HttpFetcher* /* fetcher */,
+                     const void* bytes,
+                     size_t length) override {
+    data.append(reinterpret_cast<const char*>(bytes), length);
+    // Update counters
+    times_received_bytes_called_++;
+  }
+
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    if (is_expect_error_)
+      EXPECT_EQ(kHttpResponseNotFound, fetcher->http_response_code());
+    else
+      EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+    MessageLoop::current()->BreakLoop();
+
+    // Update counter
+    times_transfer_complete_called_++;
+  }
+
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+    times_transfer_terminated_called_++;
+    MessageLoop::current()->BreakLoop();
+  }
+
+  // Are we expecting an error response? (default: no)
+  bool is_expect_error_{false};
+
+  // Counters for callback invocations.
+  int times_transfer_complete_called_{0};
+  int times_transfer_terminated_called_{0};
+  int times_received_bytes_called_{0};
+
+  // The received data bytes.
+  string data;
+};
+
+
+void StartTransfer(HttpFetcher* http_fetcher, const string& url) {
+  http_fetcher->BeginTransfer(url);
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleTest) {
+  HttpFetcherTestDelegate delegate;
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+  fetcher->set_delegate(&delegate);
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  this->loop_.PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      this->test_.SmallUrl(server->GetPort())));
+  this->loop_.Run();
+}
+
+TYPED_TEST(HttpFetcherTest, SimpleBigTest) {
+  HttpFetcherTestDelegate delegate;
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+  fetcher->set_delegate(&delegate);
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  this->loop_.PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      this->test_.BigUrl(server->GetPort())));
+  this->loop_.Run();
+}
+
+// Issue #9648: when server returns an error HTTP response, the fetcher needs to
+// terminate transfer prematurely, rather than try to process the error payload.
+TYPED_TEST(HttpFetcherTest, ErrorTest) {
+  if (this->test_.IsMock() || this->test_.IsMulti())
+    return;
+  HttpFetcherTestDelegate delegate;
+
+  // Delegate should expect an error response.
+  delegate.is_expect_error_ = true;
+
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+  fetcher->set_delegate(&delegate);
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  this->loop_.PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      this->test_.ErrorUrl(server->GetPort())));
+  this->loop_.Run();
+
+  // Make sure that no bytes were received.
+  CHECK_EQ(delegate.times_received_bytes_called_, 0);
+  CHECK_EQ(fetcher->GetBytesDownloaded(), static_cast<size_t>(0));
+
+  // Make sure that transfer completion was signaled once, and no termination
+  // was signaled.
+  CHECK_EQ(delegate.times_transfer_complete_called_, 1);
+  CHECK_EQ(delegate.times_transfer_terminated_called_, 0);
+}
+
+TYPED_TEST(HttpFetcherTest, ExtraHeadersInRequestTest) {
+  if (this->test_.IsMock() || !this->test_.IsHttpSupported())
+    return;
+
+  HttpFetcherTestDelegate delegate;
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+  fetcher->set_delegate(&delegate);
+  fetcher->SetHeader("User-Agent", "MyTest");
+  fetcher->SetHeader("user-agent", "Override that header");
+  fetcher->SetHeader("Authorization", "Basic user:passwd");
+
+  // Invalid headers.
+  fetcher->SetHeader("X-Foo", "Invalid\nHeader\nIgnored");
+  fetcher->SetHeader("X-Bar: ", "I do not know how to parse");
+
+  // Hide Accept header normally added by default.
+  fetcher->SetHeader("Accept", "");
+
+  PythonHttpServer server;
+  int port = server.GetPort();
+  ASSERT_TRUE(server.started_);
+
+  this->loop_.PostTask(
+      FROM_HERE,
+      base::Bind(StartTransfer,
+                 fetcher.get(),
+                 LocalServerUrlForPath(port, "/echo-headers")));
+  this->loop_.Run();
+
+  EXPECT_NE(string::npos,
+            delegate.data.find("user-agent: Override that header\r\n"));
+  EXPECT_NE(string::npos,
+            delegate.data.find("Authorization: Basic user:passwd\r\n"));
+
+  EXPECT_EQ(string::npos, delegate.data.find("\nAccept:"));
+  EXPECT_EQ(string::npos, delegate.data.find("X-Foo: Invalid"));
+  EXPECT_EQ(string::npos, delegate.data.find("X-Bar: I do not"));
+}
+
+namespace {
+class PausingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* /* bytes */, size_t /* length */) override {
+    CHECK(!paused_);
+    paused_ = true;
+    fetcher->Pause();
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+  void Unpause() {
+    CHECK(paused_);
+    paused_ = false;
+    fetcher_->Unpause();
+  }
+  bool paused_;
+  HttpFetcher* fetcher_;
+};
+
+void UnpausingTimeoutCallback(PausingHttpFetcherTestDelegate* delegate,
+                              MessageLoop::TaskId* my_id) {
+  if (delegate->paused_)
+    delegate->Unpause();
+  // Update the task id with the new scheduled callback.
+  *my_id = MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&UnpausingTimeoutCallback, delegate, my_id),
+      base::TimeDelta::FromMilliseconds(200));
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, PauseTest) {
+  PausingHttpFetcherTestDelegate delegate;
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+  delegate.paused_ = false;
+  delegate.fetcher_ = fetcher.get();
+  fetcher->set_delegate(&delegate);
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  MessageLoop::TaskId callback_id;
+  callback_id = this->loop_.PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&UnpausingTimeoutCallback, &delegate, &callback_id),
+      base::TimeDelta::FromMilliseconds(200));
+  fetcher->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+  this->loop_.Run();
+  EXPECT_TRUE(this->loop_.CancelTask(callback_id));
+}
+
+// This test will pause the fetcher while the download is not yet started
+// because it is waiting for the proxy to be resolved.
+TYPED_TEST(HttpFetcherTest, PauseWhileResolvingProxyTest) {
+  if (this->test_.IsMock() || !this->test_.IsHttpSupported())
+    return;
+  MockProxyResolver mock_resolver;
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher(&mock_resolver));
+
+  // Saved arguments from the proxy call.
+  ProxiesResolvedFn proxy_callback = nullptr;
+  void* proxy_data = nullptr;
+
+  EXPECT_CALL(mock_resolver, GetProxiesForUrl("http://fake_url", _, _))
+      .WillOnce(DoAll(
+          SaveArg<1>(&proxy_callback), SaveArg<2>(&proxy_data), Return(true)));
+  fetcher->BeginTransfer("http://fake_url");
+  testing::Mock::VerifyAndClearExpectations(&mock_resolver);
+
+  // Pausing and unpausing while resolving the proxy should not affect anything.
+  fetcher->Pause();
+  fetcher->Unpause();
+  fetcher->Pause();
+  // Proxy resolver comes back after we paused the fetcher.
+  ASSERT_TRUE(proxy_callback);
+  (*proxy_callback)({1, kNoProxy}, proxy_data);
+}
+
+namespace {
+class AbortingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {}
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    ADD_FAILURE();  // We should never get here
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    EXPECT_EQ(fetcher, fetcher_.get());
+    EXPECT_FALSE(once_);
+    EXPECT_TRUE(callback_once_);
+    callback_once_ = false;
+    // The fetcher could have a callback scheduled on the ProxyResolver that
+    // can fire after this callback. We wait until the end of the test to
+    // delete the fetcher.
+  }
+  void TerminateTransfer() {
+    CHECK(once_);
+    once_ = false;
+    fetcher_->TerminateTransfer();
+  }
+  void EndLoop() {
+    MessageLoop::current()->BreakLoop();
+  }
+  bool once_;
+  bool callback_once_;
+  unique_ptr<HttpFetcher> fetcher_;
+};
+
+void AbortingTimeoutCallback(AbortingHttpFetcherTestDelegate* delegate,
+                             MessageLoop::TaskId* my_id) {
+  if (delegate->once_) {
+    delegate->TerminateTransfer();
+    *my_id = MessageLoop::current()->PostTask(
+        FROM_HERE,
+        base::Bind(AbortingTimeoutCallback, delegate, my_id));
+  } else {
+    delegate->EndLoop();
+    *my_id = MessageLoop::kTaskIdNull;
+  }
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, AbortTest) {
+  AbortingHttpFetcherTestDelegate delegate;
+  delegate.fetcher_.reset(this->test_.NewLargeFetcher());
+  delegate.once_ = true;
+  delegate.callback_once_ = true;
+  delegate.fetcher_->set_delegate(&delegate);
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  this->test_.IgnoreServerAborting(server.get());
+  ASSERT_TRUE(server->started_);
+
+  MessageLoop::TaskId task_id = MessageLoop::kTaskIdNull;
+
+  task_id = this->loop_.PostTask(
+      FROM_HERE,
+      base::Bind(AbortingTimeoutCallback, &delegate, &task_id));
+  delegate.fetcher_->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+  this->loop_.Run();
+  CHECK(!delegate.once_);
+  CHECK(!delegate.callback_once_);
+  this->loop_.CancelTask(task_id);
+}
+
+namespace {
+class FlakyHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    data.append(reinterpret_cast<const char*>(bytes), length);
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_TRUE(successful);
+    EXPECT_EQ(kHttpResponsePartialContent, fetcher->http_response_code());
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+  string data;
+};
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, FlakyTest) {
+  if (this->test_.IsMock() || !this->test_.IsHttpSupported())
+    return;
+  {
+    FlakyHttpFetcherTestDelegate delegate;
+    unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+    fetcher->set_delegate(&delegate);
+
+    unique_ptr<HttpServer> server(this->test_.CreateServer());
+    ASSERT_TRUE(server->started_);
+
+    this->loop_.PostTask(FROM_HERE, base::Bind(
+        &StartTransfer,
+        fetcher.get(),
+        LocalServerUrlForPath(server->GetPort(),
+                              base::StringPrintf("/flaky/%d/%d/%d/%d",
+                                                 kBigLength,
+                                                 kFlakyTruncateLength,
+                                                 kFlakySleepEvery,
+                                                 kFlakySleepSecs))));
+    this->loop_.Run();
+
+    // verify the data we get back
+    ASSERT_EQ(kBigLength, static_cast<int>(delegate.data.size()));
+    for (int i = 0; i < kBigLength; i += 10) {
+      // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+      ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+    }
+  }
+}
+
+namespace {
+// This delegate kills the server attached to it after receiving any bytes.
+// This can be used for testing what happens when you try to fetch data and
+// the server dies.
+class FailureHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  explicit FailureHttpFetcherTestDelegate(PythonHttpServer* server)
+      : server_(server) {}
+
+  ~FailureHttpFetcherTestDelegate() override {
+    if (server_) {
+      LOG(INFO) << "Stopping server in destructor";
+      delete server_;
+      LOG(INFO) << "server stopped";
+    }
+  }
+
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    if (server_) {
+      LOG(INFO) << "Stopping server in ReceivedBytes";
+      delete server_;
+      LOG(INFO) << "server stopped";
+      server_ = nullptr;
+    }
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_FALSE(successful);
+    EXPECT_EQ(0, fetcher->http_response_code());
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+  PythonHttpServer* server_;
+};
+}  // namespace
+
+
+TYPED_TEST(HttpFetcherTest, FailureTest) {
+  // This test ensures that a fetcher responds correctly when a server isn't
+  // available at all.
+  if (this->test_.IsMock())
+    return;
+  {
+    FailureHttpFetcherTestDelegate delegate(nullptr);
+    unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+    fetcher->set_delegate(&delegate);
+
+    this->loop_.PostTask(FROM_HERE,
+                         base::Bind(StartTransfer,
+                                    fetcher.get(),
+                                    "http://host_doesnt_exist99999999"));
+    this->loop_.Run();
+
+    // Exiting and testing happens in the delegate
+  }
+}
+
+TYPED_TEST(HttpFetcherTest, NoResponseTest) {
+  // This test starts a new http server but the server doesn't respond and just
+  // closes the connection.
+  if (this->test_.IsMock())
+    return;
+
+  PythonHttpServer* server = new PythonHttpServer();
+  int port = server->GetPort();
+  ASSERT_TRUE(server->started_);
+
+  // Handles destruction and claims ownership.
+  FailureHttpFetcherTestDelegate delegate(server);
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+  fetcher->set_delegate(&delegate);
+  // The server will not reply at all, so we can limit the execution time of the
+  // test by reducing the low-speed timeout to something small. The test will
+  // finish once the TimeoutCallback() triggers (every second) and the timeout
+  // expired.
+  fetcher->set_low_speed_limit(kDownloadLowSpeedLimitBps, 1);
+
+  this->loop_.PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      LocalServerUrlForPath(port, "/hang")));
+  this->loop_.Run();
+
+  // Check that no other callback runs in the next two seconds. That would
+  // indicate a leaked callback.
+  bool timeout = false;
+  auto callback = base::Bind([](bool* timeout) { *timeout = true; },
+                             base::Unretained(&timeout));
+  this->loop_.PostDelayedTask(FROM_HERE, callback,
+                              base::TimeDelta::FromSeconds(2));
+  EXPECT_TRUE(this->loop_.RunOnce(true));
+  EXPECT_TRUE(timeout);
+}
+
+TYPED_TEST(HttpFetcherTest, ServerDiesTest) {
+  // This test starts a new http server and kills it after receiving its first
+  // set of bytes. It test whether or not our fetcher eventually gives up on
+  // retries and aborts correctly.
+  if (this->test_.IsMock())
+    return;
+  {
+    PythonHttpServer* server = new PythonHttpServer();
+    int port = server->GetPort();
+    ASSERT_TRUE(server->started_);
+
+    // Handles destruction and claims ownership.
+    FailureHttpFetcherTestDelegate delegate(server);
+    unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+    fetcher->set_delegate(&delegate);
+
+    this->loop_.PostTask(FROM_HERE, base::Bind(
+        StartTransfer,
+        fetcher.get(),
+        LocalServerUrlForPath(port,
+                              base::StringPrintf("/flaky/%d/%d/%d/%d",
+                                                 kBigLength,
+                                                 kFlakyTruncateLength,
+                                                 kFlakySleepEvery,
+                                                 kFlakySleepSecs))));
+    this->loop_.Run();
+
+    // Exiting and testing happens in the delegate
+  }
+}
+
+namespace {
+const HttpResponseCode kRedirectCodes[] = {
+  kHttpResponseMovedPermanently, kHttpResponseFound, kHttpResponseSeeOther,
+  kHttpResponseTempRedirect
+};
+
+class RedirectHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  explicit RedirectHttpFetcherTestDelegate(bool expected_successful)
+      : expected_successful_(expected_successful) {}
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    data.append(reinterpret_cast<const char*>(bytes), length);
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_EQ(expected_successful_, successful);
+    if (expected_successful_) {
+      EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+    } else {
+      EXPECT_GE(fetcher->http_response_code(), kHttpResponseMovedPermanently);
+      EXPECT_LE(fetcher->http_response_code(), kHttpResponseTempRedirect);
+    }
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+  bool expected_successful_;
+  string data;
+};
+
+// RedirectTest takes ownership of |http_fetcher|.
+void RedirectTest(const HttpServer* server,
+                  bool expected_successful,
+                  const string& url,
+                  HttpFetcher* http_fetcher) {
+  RedirectHttpFetcherTestDelegate delegate(expected_successful);
+  unique_ptr<HttpFetcher> fetcher(http_fetcher);
+  fetcher->set_delegate(&delegate);
+
+  MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      LocalServerUrlForPath(server->GetPort(), url)));
+  MessageLoop::current()->Run();
+  if (expected_successful) {
+    // verify the data we get back
+    ASSERT_EQ(static_cast<size_t>(kMediumLength), delegate.data.size());
+    for (int i = 0; i < kMediumLength; i += 10) {
+      // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+      ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+    }
+  }
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleRedirectTest) {
+  if (this->test_.IsMock() || !this->test_.IsHttpSupported())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  for (size_t c = 0; c < arraysize(kRedirectCodes); ++c) {
+    const string url = base::StringPrintf("/redirect/%d/download/%d",
+                                          kRedirectCodes[c],
+                                          kMediumLength);
+    RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+  }
+}
+
+TYPED_TEST(HttpFetcherTest, MaxRedirectTest) {
+  if (this->test_.IsMock() || !this->test_.IsHttpSupported())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  string url;
+  for (int r = 0; r < kDownloadMaxRedirects; r++) {
+    url += base::StringPrintf("/redirect/%d",
+                              kRedirectCodes[r % arraysize(kRedirectCodes)]);
+  }
+  url += base::StringPrintf("/download/%d", kMediumLength);
+  RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+}
+
+TYPED_TEST(HttpFetcherTest, BeyondMaxRedirectTest) {
+  if (this->test_.IsMock() || !this->test_.IsHttpSupported())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  string url;
+  for (int r = 0; r < kDownloadMaxRedirects + 1; r++) {
+    url += base::StringPrintf("/redirect/%d",
+                              kRedirectCodes[r % arraysize(kRedirectCodes)]);
+  }
+  url += base::StringPrintf("/download/%d", kMediumLength);
+  RedirectTest(server.get(), false, url, this->test_.NewLargeFetcher());
+}
+
+namespace {
+class MultiHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  explicit MultiHttpFetcherTestDelegate(int expected_response_code)
+      : expected_response_code_(expected_response_code) {}
+
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    EXPECT_EQ(fetcher, fetcher_.get());
+    data.append(reinterpret_cast<const char*>(bytes), length);
+  }
+
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_EQ(fetcher, fetcher_.get());
+    EXPECT_EQ(expected_response_code_ != kHttpResponseUndefined, successful);
+    if (expected_response_code_ != 0)
+      EXPECT_EQ(expected_response_code_, fetcher->http_response_code());
+    // Destroy the fetcher (because we're allowed to).
+    fetcher_.reset(nullptr);
+    MessageLoop::current()->BreakLoop();
+  }
+
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+
+  unique_ptr<HttpFetcher> fetcher_;
+  int expected_response_code_;
+  string data;
+};
+
+void MultiTest(HttpFetcher* fetcher_in,
+               FakeHardware* fake_hardware,
+               const string& url,
+               const vector<pair<off_t, off_t>>& ranges,
+               const string& expected_prefix,
+               size_t expected_size,
+               HttpResponseCode expected_response_code) {
+  MultiHttpFetcherTestDelegate delegate(expected_response_code);
+  delegate.fetcher_.reset(fetcher_in);
+
+  MultiRangeHttpFetcher* multi_fetcher =
+      static_cast<MultiRangeHttpFetcher*>(fetcher_in);
+  ASSERT_TRUE(multi_fetcher);
+  multi_fetcher->ClearRanges();
+  for (vector<pair<off_t, off_t>>::const_iterator it = ranges.begin(),
+           e = ranges.end(); it != e; ++it) {
+    string tmp_str = base::StringPrintf("%jd+", it->first);
+    if (it->second > 0) {
+      base::StringAppendF(&tmp_str, "%jd", it->second);
+      multi_fetcher->AddRange(it->first, it->second);
+    } else {
+      base::StringAppendF(&tmp_str, "?");
+      multi_fetcher->AddRange(it->first);
+    }
+    LOG(INFO) << "added range: " << tmp_str;
+  }
+  fake_hardware->SetIsOfficialBuild(false);
+  multi_fetcher->set_delegate(&delegate);
+
+  MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(StartTransfer, multi_fetcher, url));
+  MessageLoop::current()->Run();
+
+  EXPECT_EQ(expected_size, delegate.data.size());
+  EXPECT_EQ(expected_prefix,
+            string(delegate.data.data(), expected_prefix.size()));
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherSimpleTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(0, 25));
+  ranges.push_back(make_pair(99, 0));
+  MultiTest(this->test_.NewLargeFetcher(),
+            this->test_.fake_hardware(),
+            this->test_.BigUrl(server->GetPort()),
+            ranges,
+            "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+            kBigLength - (99 - 25),
+            kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherLengthLimitTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(0, 24));
+  MultiTest(this->test_.NewLargeFetcher(),
+            this->test_.fake_hardware(),
+            this->test_.BigUrl(server->GetPort()),
+            ranges,
+            "abcdefghijabcdefghijabcd",
+            24,
+            kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherMultiEndTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(kBigLength - 2, 0));
+  ranges.push_back(make_pair(kBigLength - 3, 0));
+  MultiTest(this->test_.NewLargeFetcher(),
+            this->test_.fake_hardware(),
+            this->test_.BigUrl(server->GetPort()),
+            ranges,
+            "ijhij",
+            5,
+            kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherInsufficientTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(kBigLength - 2, 4));
+  for (int i = 0; i < 2; ++i) {
+    LOG(INFO) << "i = " << i;
+    MultiTest(this->test_.NewLargeFetcher(),
+              this->test_.fake_hardware(),
+              this->test_.BigUrl(server->GetPort()),
+              ranges,
+              "ij",
+              2,
+              kHttpResponseUndefined);
+    ranges.push_back(make_pair(0, 5));
+  }
+}
+
+// Issue #18143: when a fetch of a secondary chunk out of a chain, then it
+// should retry with other proxies listed before giving up.
+//
+// (1) successful recovery: The offset fetch will fail twice but succeed with
+// the third proxy.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetRecoverableTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(0, 25));
+  ranges.push_back(make_pair(99, 0));
+  MultiTest(this->test_.NewLargeFetcher(3),
+            this->test_.fake_hardware(),
+            LocalServerUrlForPath(server->GetPort(),
+                                  base::StringPrintf("/error-if-offset/%d/2",
+                                                     kBigLength)),
+            ranges,
+            "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+            kBigLength - (99 - 25),
+            kHttpResponsePartialContent);
+}
+
+// (2) unsuccessful recovery: The offset fetch will fail repeatedly.  The
+// fetcher will signal a (failed) completed transfer to the delegate.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetUnrecoverableTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(0, 25));
+  ranges.push_back(make_pair(99, 0));
+  MultiTest(this->test_.NewLargeFetcher(2),
+            this->test_.fake_hardware(),
+            LocalServerUrlForPath(server->GetPort(),
+                                  base::StringPrintf("/error-if-offset/%d/3",
+                                                     kBigLength)),
+            ranges,
+            "abcdefghijabcdefghijabcde",  // only received the first chunk
+            25,
+            kHttpResponseUndefined);
+}
+
+
+
+namespace {
+class BlockedTransferTestDelegate : public HttpFetcherDelegate {
+ public:
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    ADD_FAILURE();
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_FALSE(successful);
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+};
+
+void BlockedTransferTestHelper(AnyHttpFetcherTest* fetcher_test,
+                               bool is_official_build) {
+  if (fetcher_test->IsMock() || fetcher_test->IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(fetcher_test->CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  BlockedTransferTestDelegate delegate;
+  unique_ptr<HttpFetcher> fetcher(fetcher_test->NewLargeFetcher());
+  LOG(INFO) << "is_official_build: " << is_official_build;
+  // NewLargeFetcher creates the HttpFetcher* with a FakeSystemState.
+  fetcher_test->fake_hardware()->SetIsOfficialBuild(is_official_build);
+  fetcher->set_delegate(&delegate);
+
+  MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      LocalServerUrlForPath(server->GetPort(),
+                            fetcher_test->SmallUrl(server->GetPort()))));
+  MessageLoop::current()->Run();
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferTest) {
+  BlockedTransferTestHelper(&this->test_, false);
+}
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferOfficialBuildTest) {
+  BlockedTransferTestHelper(&this->test_, true);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/hwid_override.cc b/update_engine/common/hwid_override.cc
new file mode 100644
index 0000000..8800e94
--- /dev/null
+++ b/update_engine/common/hwid_override.cc
@@ -0,0 +1,46 @@
+//
+// 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/common/hwid_override.h"
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <brillo/key_value_store.h>
+
+using std::map;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char HwidOverride::kHwidOverrideKey[] = "HWID_OVERRIDE";
+
+HwidOverride::HwidOverride() {}
+
+HwidOverride::~HwidOverride() {}
+
+string HwidOverride::Read(const base::FilePath& root) {
+  brillo::KeyValueStore lsb_release;
+  lsb_release.Load(base::FilePath(root.value() + "/etc/lsb-release"));
+  string result;
+  if (lsb_release.GetString(kHwidOverrideKey, &result))
+    return result;
+  return "";
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/hwid_override.h b/update_engine/common/hwid_override.h
new file mode 100644
index 0000000..d39b572
--- /dev/null
+++ b/update_engine/common/hwid_override.h
@@ -0,0 +1,46 @@
+//
+// 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_COMMON_HWID_OVERRIDE_H_
+#define UPDATE_ENGINE_COMMON_HWID_OVERRIDE_H_
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+// Class that allows HWID to be read from <root>/etc/lsb-release.
+class HwidOverride {
+ public:
+  HwidOverride();
+  ~HwidOverride();
+
+  // Read HWID from an /etc/lsb-release file under given root.
+  // An empty string is returned if there is any error.
+  static std::string Read(const base::FilePath& root);
+
+  static const char kHwidOverrideKey[];
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HwidOverride);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_HWID_OVERRIDE_H_
diff --git a/update_engine/common/hwid_override_unittest.cc b/update_engine/common/hwid_override_unittest.cc
new file mode 100644
index 0000000..26ef30a
--- /dev/null
+++ b/update_engine/common/hwid_override_unittest.cc
@@ -0,0 +1,67 @@
+//
+// 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/common/hwid_override.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>
+
+namespace chromeos_update_engine {
+
+class HwidOverrideTest : public ::testing::Test {
+ public:
+  HwidOverrideTest() {}
+  ~HwidOverrideTest() override = default;
+
+  void SetUp() override {
+    ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
+    ASSERT_TRUE(base::CreateDirectory(tempdir_.path().Append("etc")));
+  }
+
+ protected:
+  base::ScopedTempDir tempdir_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HwidOverrideTest);
+};
+
+TEST_F(HwidOverrideTest, ReadGood) {
+  std::string expected_hwid("expected");
+  std::string keyval(HwidOverride::kHwidOverrideKey);
+  keyval += ("=" + expected_hwid);
+  ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
+                            keyval.c_str(), keyval.length()),
+            static_cast<int>(keyval.length()));
+  EXPECT_EQ(expected_hwid, HwidOverride::Read(tempdir_.path()));
+}
+
+TEST_F(HwidOverrideTest, ReadNothing) {
+  std::string keyval("SOMETHING_ELSE=UNINTERESTING");
+  ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
+                            keyval.c_str(), keyval.length()),
+            static_cast<int>(keyval.length()));
+  EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+}
+
+TEST_F(HwidOverrideTest, ReadFailure) {
+  EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/mock_action.h b/update_engine/common/mock_action.h
new file mode 100644
index 0000000..06acad1
--- /dev/null
+++ b/update_engine/common/mock_action.h
@@ -0,0 +1,52 @@
+//
+// 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_COMMON_MOCK_ACTION_H_
+#define UPDATE_ENGINE_COMMON_MOCK_ACTION_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/action.h"
+
+namespace chromeos_update_engine {
+
+class MockAction;
+
+template <>
+class ActionTraits<MockAction> {
+ public:
+  typedef NoneType OutputObjectType;
+  typedef NoneType InputObjectType;
+};
+
+class MockAction : public Action<MockAction> {
+ public:
+  MockAction() {
+    ON_CALL(*this, Type()).WillByDefault(testing::Return("MockAction"));
+  }
+
+  MOCK_METHOD0(PerformAction, void());
+  MOCK_METHOD0(TerminateProcessing, void());
+  MOCK_METHOD0(SuspendAction, void());
+  MOCK_METHOD0(ResumeAction, void());
+  MOCK_CONST_METHOD0(Type, std::string());
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_MOCK_ACTION_H_
diff --git a/update_engine/common/mock_action_processor.h b/update_engine/common/mock_action_processor.h
new file mode 100644
index 0000000..04275c1
--- /dev/null
+++ b/update_engine/common/mock_action_processor.h
@@ -0,0 +1,34 @@
+//
+// 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_COMMON_MOCK_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_COMMON_MOCK_ACTION_PROCESSOR_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/action.h"
+
+namespace chromeos_update_engine {
+
+class MockActionProcessor : public ActionProcessor {
+ public:
+  MOCK_METHOD0(StartProcessing, void());
+  MOCK_METHOD1(EnqueueAction, void(AbstractAction* action));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_MOCK_ACTION_PROCESSOR_H_
diff --git a/update_engine/common/mock_hardware.h b/update_engine/common/mock_hardware.h
new file mode 100644
index 0000000..1c4253a
--- /dev/null
+++ b/update_engine/common/mock_hardware.h
@@ -0,0 +1,97 @@
+//
+// 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_COMMON_MOCK_HARDWARE_H_
+#define UPDATE_ENGINE_COMMON_MOCK_HARDWARE_H_
+
+#include <string>
+
+#include "update_engine/common/fake_hardware.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+// A mocked, fake implementation of HardwareInterface.
+class MockHardware : public HardwareInterface {
+ public:
+  MockHardware() {
+    // Delegate all calls to the fake instance
+    ON_CALL(*this, IsOfficialBuild())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsOfficialBuild));
+    ON_CALL(*this, IsNormalBootMode())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsNormalBootMode));
+    ON_CALL(*this, AreDevFeaturesEnabled())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::AreDevFeaturesEnabled));
+    ON_CALL(*this, IsOOBEEnabled())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsOOBEEnabled));
+    ON_CALL(*this, IsOOBEComplete(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsOOBEComplete));
+    ON_CALL(*this, GetHardwareClass())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetHardwareClass));
+    ON_CALL(*this, GetFirmwareVersion())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetFirmwareVersion));
+    ON_CALL(*this, GetECVersion())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetECVersion));
+    ON_CALL(*this, GetPowerwashCount())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetPowerwashCount));
+    ON_CALL(*this, GetNonVolatileDirectory(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetNonVolatileDirectory));
+    ON_CALL(*this, GetPowerwashSafeDirectory(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetPowerwashSafeDirectory));
+  }
+
+  ~MockHardware() override = default;
+
+  // Hardware overrides.
+  MOCK_CONST_METHOD0(IsOfficialBuild, bool());
+  MOCK_CONST_METHOD0(IsNormalBootMode, bool());
+  MOCK_CONST_METHOD0(IsOOBEEnabled, bool());
+  MOCK_CONST_METHOD1(IsOOBEComplete, bool(base::Time* out_time_of_oobe));
+  MOCK_CONST_METHOD0(GetHardwareClass, std::string());
+  MOCK_CONST_METHOD0(GetFirmwareVersion, std::string());
+  MOCK_CONST_METHOD0(GetECVersion, std::string());
+  MOCK_CONST_METHOD0(GetPowerwashCount, int());
+  MOCK_CONST_METHOD1(GetNonVolatileDirectory, bool(base::FilePath*));
+  MOCK_CONST_METHOD1(GetPowerwashSafeDirectory, bool(base::FilePath*));
+
+  // Returns a reference to the underlying FakeHardware.
+  FakeHardware& fake() {
+    return fake_;
+  }
+
+ private:
+  // The underlying FakeHardware.
+  FakeHardware fake_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockHardware);
+};
+
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_MOCK_HARDWARE_H_
diff --git a/update_engine/common/mock_http_fetcher.cc b/update_engine/common/mock_http_fetcher.cc
new file mode 100644
index 0000000..f1ae72a
--- /dev/null
+++ b/update_engine/common/mock_http_fetcher.cc
@@ -0,0 +1,162 @@
+//
+// Copyright (C) 2009 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/mock_http_fetcher.h"
+
+#include <algorithm>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+// This is a mock implementation of HttpFetcher which is useful for testing.
+
+using brillo::MessageLoop;
+using std::min;
+
+namespace chromeos_update_engine {
+
+MockHttpFetcher::~MockHttpFetcher() {
+  CHECK(timeout_id_ == MessageLoop::kTaskIdNull) <<
+      "Call TerminateTransfer() before dtor.";
+}
+
+void MockHttpFetcher::BeginTransfer(const std::string& url) {
+  EXPECT_FALSE(never_use_);
+  if (fail_transfer_ || data_.empty()) {
+    // No data to send, just notify of completion..
+    SignalTransferComplete();
+    return;
+  }
+  if (sent_size_ < data_.size())
+    SendData(true);
+}
+
+// Returns false on one condition: If timeout_id_ was already set
+// and it needs to be deleted by the caller. If timeout_id_ is null
+// when this function is called, this function will always return true.
+bool MockHttpFetcher::SendData(bool skip_delivery) {
+  if (fail_transfer_) {
+    SignalTransferComplete();
+    return timeout_id_ != MessageLoop::kTaskIdNull;
+  }
+
+  CHECK_LT(sent_size_, data_.size());
+  if (!skip_delivery) {
+    const size_t chunk_size = min(kMockHttpFetcherChunkSize,
+                                  data_.size() - sent_size_);
+    CHECK(delegate_);
+    delegate_->ReceivedBytes(this, &data_[sent_size_], chunk_size);
+    // We may get terminated in the callback.
+    if (sent_size_ == data_.size()) {
+      LOG(INFO) << "Terminated in the ReceivedBytes callback.";
+      return timeout_id_ != MessageLoop::kTaskIdNull;
+    }
+    sent_size_ += chunk_size;
+    CHECK_LE(sent_size_, data_.size());
+    if (sent_size_ == data_.size()) {
+      // We've sent all the data. Notify of success.
+      SignalTransferComplete();
+    }
+  }
+
+  if (paused_) {
+    // If we're paused, we should return true if timeout_id_ is set,
+    // since we need the caller to delete it.
+    return timeout_id_ != MessageLoop::kTaskIdNull;
+  }
+
+  if (timeout_id_ != MessageLoop::kTaskIdNull) {
+    // we still need a timeout if there's more data to send
+    return sent_size_ < data_.size();
+  } else if (sent_size_ < data_.size()) {
+    // we don't have a timeout source and we need one
+    timeout_id_ = MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
+        base::TimeDelta::FromMilliseconds(10));
+  }
+  return true;
+}
+
+void MockHttpFetcher::TimeoutCallback() {
+  CHECK(!paused_);
+  if (SendData(false)) {
+    // We need to re-schedule the timeout.
+    timeout_id_ = MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
+        base::TimeDelta::FromMilliseconds(10));
+  } else {
+    timeout_id_ = MessageLoop::kTaskIdNull;
+  }
+}
+
+// If the transfer is in progress, aborts the transfer early.
+// The transfer cannot be resumed.
+void MockHttpFetcher::TerminateTransfer() {
+  LOG(INFO) << "Terminating transfer.";
+  sent_size_ = data_.size();
+  // Kill any timeout, it is ok to call with kTaskIdNull.
+  MessageLoop::current()->CancelTask(timeout_id_);
+  timeout_id_ = MessageLoop::kTaskIdNull;
+  delegate_->TransferTerminated(this);
+}
+
+void MockHttpFetcher::SetHeader(const std::string& header_name,
+                                const std::string& header_value) {
+  extra_headers_[base::ToLowerASCII(header_name)] = header_value;
+}
+
+std::string MockHttpFetcher::GetHeader(const std::string& header_name) const {
+  const auto it = extra_headers_.find(base::ToLowerASCII(header_name));
+  if (it == extra_headers_.end())
+    return "";
+  return it->second;
+}
+
+void MockHttpFetcher::Pause() {
+  CHECK(!paused_);
+  paused_ = true;
+  MessageLoop::current()->CancelTask(timeout_id_);
+  timeout_id_ = MessageLoop::kTaskIdNull;
+}
+
+void MockHttpFetcher::Unpause() {
+  CHECK(paused_) << "You must pause before unpause.";
+  paused_ = false;
+  if (sent_size_ < data_.size()) {
+    SendData(false);
+  }
+}
+
+void MockHttpFetcher::FailTransfer(int http_response_code) {
+  fail_transfer_ = true;
+  http_response_code_ = http_response_code;
+}
+
+void MockHttpFetcher::SignalTransferComplete() {
+  // If the transfer has been failed, the HTTP response code should be set
+  // already.
+  if (!fail_transfer_) {
+    http_response_code_ = 200;
+  }
+  delegate_->TransferComplete(this, !fail_transfer_);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/mock_http_fetcher.h b/update_engine/common/mock_http_fetcher.h
new file mode 100644
index 0000000..367802e
--- /dev/null
+++ b/update_engine/common/mock_http_fetcher.h
@@ -0,0 +1,157 @@
+//
+// Copyright (C) 2009 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_COMMON_MOCK_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_MOCK_HTTP_FETCHER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/http_fetcher.h"
+
+// This is a mock implementation of HttpFetcher which is useful for testing.
+// All data must be passed into the ctor. When started, MockHttpFetcher will
+// deliver the data in chunks of size kMockHttpFetcherChunkSize. To simulate
+// a network failure, you can call FailTransfer().
+
+namespace chromeos_update_engine {
+
+// MockHttpFetcher will send a chunk of data down in each call to BeginTransfer
+// and Unpause. For the other chunks of data, a callback is put on the run
+// loop and when that's called, another chunk is sent down.
+const size_t kMockHttpFetcherChunkSize(65536);
+
+class MockHttpFetcher : public HttpFetcher {
+ public:
+  // The data passed in here is copied and then passed to the delegate after
+  // the transfer begins.
+  MockHttpFetcher(const uint8_t* data,
+                  size_t size,
+                  ProxyResolver* proxy_resolver)
+      : HttpFetcher(proxy_resolver),
+        sent_size_(0),
+        timeout_id_(brillo::MessageLoop::kTaskIdNull),
+        paused_(false),
+        fail_transfer_(false),
+        never_use_(false) {
+    data_.insert(data_.end(), data, data + size);
+  }
+
+  // Constructor overload for string data.
+  MockHttpFetcher(const char* data, size_t size, ProxyResolver* proxy_resolver)
+      : MockHttpFetcher(reinterpret_cast<const uint8_t*>(data), size,
+                        proxy_resolver) {}
+
+  // Cleans up all internal state. Does not notify delegate
+  ~MockHttpFetcher() override;
+
+  // Ignores this.
+  void SetOffset(off_t offset) override {
+    sent_size_ = offset;
+    if (delegate_)
+      delegate_->SeekToOffset(offset);
+  }
+
+  // Do nothing.
+  void SetLength(size_t length) override {}
+  void UnsetLength() override {}
+  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {}
+  void set_connect_timeout(int connect_timeout_seconds) override {}
+  void set_max_retry_count(int max_retry_count) override {}
+
+  // Dummy: no bytes were downloaded.
+  size_t GetBytesDownloaded() override {
+    return sent_size_;
+  }
+
+  // Begins the transfer if it hasn't already begun.
+  void BeginTransfer(const std::string& url) override;
+
+  // If the transfer is in progress, aborts the transfer early.
+  // The transfer cannot be resumed.
+  void TerminateTransfer() override;
+
+  void SetHeader(const std::string& header_name,
+                 const std::string& header_value) override;
+
+  // Return the value of the header |header_name| or the empty string if not
+  // set.
+  std::string GetHeader(const std::string& header_name) const;
+
+  // Suspend the mock transfer.
+  void Pause() override;
+
+  // Resume the mock transfer.
+  void Unpause() override;
+
+  // Fail the transfer. This simulates a network failure.
+  void FailTransfer(int http_response_code);
+
+  // If set to true, this will EXPECT fail on BeginTransfer
+  void set_never_use(bool never_use) { never_use_ = never_use; }
+
+  const brillo::Blob& post_data() const {
+    return post_data_;
+  }
+
+ private:
+  // Sends data to the delegate and sets up a timeout callback if needed.
+  // There must be a delegate and there must be data to send. If there is
+  // already a timeout callback, and it should be deleted by the caller,
+  // this will return false; otherwise true is returned.
+  // If skip_delivery is true, no bytes will be delivered, but the callbacks
+  // still be set if needed.
+  bool SendData(bool skip_delivery);
+
+  // Callback for when our message loop timeout expires.
+  void TimeoutCallback();
+
+  // Sets the HTTP response code and signals to the delegate that the transfer
+  // is complete.
+  void SignalTransferComplete();
+
+  // A full copy of the data we'll return to the delegate
+  brillo::Blob data_;
+
+  // The number of bytes we've sent so far
+  size_t sent_size_;
+
+  // The extra headers set.
+  std::map<std::string, std::string> extra_headers_;
+
+  // The TaskId of the timeout callback. After each chunk of data sent, we
+  // time out for 0s just to make sure that run loop services other clients.
+  brillo::MessageLoop::TaskId timeout_id_;
+
+  // True iff the fetcher is paused.
+  bool paused_;
+
+  // Set to true if the transfer should fail.
+  bool fail_transfer_;
+
+  // Set to true if BeginTransfer should EXPECT fail.
+  bool never_use_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockHttpFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_MOCK_HTTP_FETCHER_H_
diff --git a/update_engine/common/mock_prefs.h b/update_engine/common/mock_prefs.h
new file mode 100644
index 0000000..0e639a2
--- /dev/null
+++ b/update_engine/common/mock_prefs.h
@@ -0,0 +1,51 @@
+//
+// 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_COMMON_MOCK_PREFS_H_
+#define UPDATE_ENGINE_COMMON_MOCK_PREFS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockPrefs : public PrefsInterface {
+ public:
+  MOCK_CONST_METHOD2(GetString,
+                     bool(const std::string& key, std::string* value));
+  MOCK_METHOD2(SetString, bool(const std::string& key,
+                               const std::string& value));
+  MOCK_CONST_METHOD2(GetInt64, bool(const std::string& key, int64_t* value));
+  MOCK_METHOD2(SetInt64, bool(const std::string& key, const int64_t value));
+
+  MOCK_CONST_METHOD2(GetBoolean, bool(const std::string& key, bool* value));
+  MOCK_METHOD2(SetBoolean, bool(const std::string& key, const bool value));
+
+  MOCK_CONST_METHOD1(Exists, bool(const std::string& key));
+  MOCK_METHOD1(Delete, bool(const std::string& key));
+
+  MOCK_METHOD2(AddObserver, void(const std::string& key, ObserverInterface*));
+  MOCK_METHOD2(RemoveObserver,
+               void(const std::string& key, ObserverInterface*));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_MOCK_PREFS_H_
diff --git a/update_engine/common/multi_range_http_fetcher.cc b/update_engine/common/multi_range_http_fetcher.cc
new file mode 100644
index 0000000..0a97b6e
--- /dev/null
+++ b/update_engine/common/multi_range_http_fetcher.cc
@@ -0,0 +1,190 @@
+//
+// 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.
+//
+
+#include "update_engine/common/multi_range_http_fetcher.h"
+
+#include <base/strings/stringprintf.h>
+
+#include <algorithm>
+#include <string>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+// Begins the transfer to the specified URL.
+// State change: Stopped -> Downloading
+// (corner case: Stopped -> Stopped for an empty request)
+void MultiRangeHttpFetcher::BeginTransfer(const std::string& url) {
+  CHECK(!base_fetcher_active_) << "BeginTransfer but already active.";
+  CHECK(!pending_transfer_ended_) << "BeginTransfer but pending.";
+  CHECK(!terminating_) << "BeginTransfer but terminating.";
+
+  if (ranges_.empty()) {
+    // Note that after the callback returns this object may be destroyed.
+    if (delegate_)
+      delegate_->TransferComplete(this, true);
+    return;
+  }
+  url_ = url;
+  current_index_ = 0;
+  bytes_received_this_range_ = 0;
+  LOG(INFO) << "starting first transfer";
+  base_fetcher_->set_delegate(this);
+  StartTransfer();
+}
+
+// State change: Downloading -> Pending transfer ended
+void MultiRangeHttpFetcher::TerminateTransfer() {
+  if (!base_fetcher_active_) {
+    LOG(INFO) << "Called TerminateTransfer but not active.";
+    // Note that after the callback returns this object may be destroyed.
+    if (delegate_)
+      delegate_->TransferTerminated(this);
+    return;
+  }
+  terminating_ = true;
+
+  if (!pending_transfer_ended_) {
+    base_fetcher_->TerminateTransfer();
+  }
+}
+
+// State change: Stopped or Downloading -> Downloading
+void MultiRangeHttpFetcher::StartTransfer() {
+  if (current_index_ >= ranges_.size()) {
+    return;
+  }
+
+  Range range = ranges_[current_index_];
+  LOG(INFO) << "starting transfer of range " << range.ToString();
+
+  bytes_received_this_range_ = 0;
+  base_fetcher_->SetOffset(range.offset());
+  if (range.HasLength())
+    base_fetcher_->SetLength(range.length());
+  else
+    base_fetcher_->UnsetLength();
+  if (delegate_)
+    delegate_->SeekToOffset(range.offset());
+  base_fetcher_active_ = true;
+  base_fetcher_->BeginTransfer(url_);
+}
+
+// State change: Downloading -> Downloading or Pending transfer ended
+void MultiRangeHttpFetcher::ReceivedBytes(HttpFetcher* fetcher,
+                                          const void* bytes,
+                                          size_t length) {
+  CHECK_LT(current_index_, ranges_.size());
+  CHECK_EQ(fetcher, base_fetcher_.get());
+  CHECK(!pending_transfer_ended_);
+  size_t next_size = length;
+  Range range = ranges_[current_index_];
+  if (range.HasLength()) {
+    next_size = std::min(next_size,
+                         range.length() - bytes_received_this_range_);
+  }
+  LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
+  if (delegate_) {
+    delegate_->ReceivedBytes(this, bytes, next_size);
+  }
+  bytes_received_this_range_ += length;
+  if (range.HasLength() && bytes_received_this_range_ >= range.length()) {
+    // Terminates the current fetcher. Waits for its TransferTerminated
+    // callback before starting the next range so that we don't end up
+    // signalling the delegate that the whole multi-transfer is complete
+    // before all fetchers are really done and cleaned up.
+    pending_transfer_ended_ = true;
+    LOG(INFO) << "terminating transfer";
+    fetcher->TerminateTransfer();
+  }
+}
+
+// State change: Downloading or Pending transfer ended -> Stopped
+void MultiRangeHttpFetcher::TransferEnded(HttpFetcher* fetcher,
+                                          bool successful) {
+  CHECK(base_fetcher_active_) << "Transfer ended unexpectedly.";
+  CHECK_EQ(fetcher, base_fetcher_.get());
+  pending_transfer_ended_ = false;
+  http_response_code_ = fetcher->http_response_code();
+  LOG(INFO) << "TransferEnded w/ code " << http_response_code_;
+  if (terminating_) {
+    LOG(INFO) << "Terminating.";
+    Reset();
+    // Note that after the callback returns this object may be destroyed.
+    if (delegate_)
+      delegate_->TransferTerminated(this);
+    return;
+  }
+
+  // If we didn't get enough bytes, it's failure
+  Range range = ranges_[current_index_];
+  if (range.HasLength()) {
+    if (bytes_received_this_range_ < range.length()) {
+      // Failure
+      LOG(INFO) << "Didn't get enough bytes. Ending w/ failure.";
+      Reset();
+      // Note that after the callback returns this object may be destroyed.
+      if (delegate_)
+        delegate_->TransferComplete(this, false);
+      return;
+    }
+    // We got enough bytes and there were bytes specified, so this is success.
+    successful = true;
+  }
+
+  // If we have another transfer, do that.
+  if (current_index_ + 1 < ranges_.size()) {
+    current_index_++;
+    LOG(INFO) << "Starting next transfer (" << current_index_ << ").";
+    StartTransfer();
+    return;
+  }
+
+  LOG(INFO) << "Done w/ all transfers";
+  Reset();
+  // Note that after the callback returns this object may be destroyed.
+  if (delegate_)
+    delegate_->TransferComplete(this, successful);
+}
+
+void MultiRangeHttpFetcher::TransferComplete(HttpFetcher* fetcher,
+                                             bool successful) {
+  LOG(INFO) << "Received transfer complete.";
+  TransferEnded(fetcher, successful);
+}
+
+void MultiRangeHttpFetcher::TransferTerminated(HttpFetcher* fetcher) {
+  LOG(INFO) << "Received transfer terminated.";
+  TransferEnded(fetcher, false);
+}
+
+void MultiRangeHttpFetcher::Reset() {
+  base_fetcher_active_ = pending_transfer_ended_ = terminating_ = false;
+  current_index_ = 0;
+  bytes_received_this_range_ = 0;
+}
+
+std::string MultiRangeHttpFetcher::Range::ToString() const {
+  std::string range_str = base::StringPrintf("%jd+", offset());
+  if (HasLength())
+    range_str += std::to_string(length());
+  else
+    range_str += "?";
+  return range_str;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/multi_range_http_fetcher.h b/update_engine/common/multi_range_http_fetcher.h
new file mode 100644
index 0000000..8a91ead
--- /dev/null
+++ b/update_engine/common/multi_range_http_fetcher.h
@@ -0,0 +1,184 @@
+//
+// 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_COMMON_MULTI_RANGE_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_MULTI_RANGE_HTTP_FETCHER_H_
+
+#include <deque>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "update_engine/common/http_fetcher.h"
+
+// This class is a simple wrapper around an HttpFetcher. The client
+// specifies a vector of byte ranges. MultiRangeHttpFetcher will fetch bytes
+// from those offsets, using the same bash fetcher for all ranges. Thus, the
+// fetcher must support beginning a transfer after one has stopped. Pass -1
+// as a length to specify unlimited length. It really only would make sense
+// for the last range specified to have unlimited length, tho it is legal for
+// other entries to have unlimited length.
+
+// There are three states a MultiRangeHttpFetcher object will be in:
+// - Stopped (start state)
+// - Downloading
+// - Pending transfer ended
+// Various functions below that might change state indicate possible
+// state changes.
+
+namespace chromeos_update_engine {
+
+class MultiRangeHttpFetcher : public HttpFetcher, public HttpFetcherDelegate {
+ public:
+  // Takes ownership of the passed in fetcher.
+  explicit MultiRangeHttpFetcher(HttpFetcher* base_fetcher)
+      : HttpFetcher(base_fetcher->proxy_resolver()),
+        base_fetcher_(base_fetcher),
+        base_fetcher_active_(false),
+        pending_transfer_ended_(false),
+        terminating_(false),
+        current_index_(0),
+        bytes_received_this_range_(0) {}
+  ~MultiRangeHttpFetcher() override {}
+
+  void ClearRanges() { ranges_.clear(); }
+
+  void AddRange(off_t offset, size_t size) {
+    CHECK_GT(size, static_cast<size_t>(0));
+    ranges_.push_back(Range(offset, size));
+  }
+
+  void AddRange(off_t offset) {
+    ranges_.push_back(Range(offset));
+  }
+
+  // HttpFetcher overrides.
+  void SetOffset(off_t offset) override {}  // for now, doesn't support this
+
+  void SetLength(size_t length) override {}  // unsupported
+  void UnsetLength() override {}
+
+  // Begins the transfer to the specified URL.
+  // State change: Stopped -> Downloading
+  // (corner case: Stopped -> Stopped for an empty request)
+  void BeginTransfer(const std::string& url) override;
+
+  // State change: Downloading -> Pending transfer ended
+  void TerminateTransfer() override;
+
+  void SetHeader(const std::string& header_name,
+                 const std::string& header_value) override {
+    base_fetcher_->SetHeader(header_name, header_value);
+  }
+
+  void Pause() override { base_fetcher_->Pause(); }
+
+  void Unpause() override { base_fetcher_->Unpause(); }
+
+  // These functions are overloaded in LibcurlHttp fetcher for testing purposes.
+  void set_idle_seconds(int seconds) override {
+    base_fetcher_->set_idle_seconds(seconds);
+  }
+  void set_retry_seconds(int seconds) override {
+    base_fetcher_->set_retry_seconds(seconds);
+  }
+  // TODO(deymo): Determine if this method should be virtual in HttpFetcher so
+  // this call is sent to the base_fetcher_.
+  virtual void SetProxies(const std::deque<std::string>& proxies) {
+    base_fetcher_->SetProxies(proxies);
+  }
+
+  inline size_t GetBytesDownloaded() override {
+    return base_fetcher_->GetBytesDownloaded();
+  }
+
+  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
+    base_fetcher_->set_low_speed_limit(low_speed_bps, low_speed_sec);
+  }
+
+  void set_connect_timeout(int connect_timeout_seconds) override {
+    base_fetcher_->set_connect_timeout(connect_timeout_seconds);
+  }
+
+  void set_max_retry_count(int max_retry_count) override {
+    base_fetcher_->set_max_retry_count(max_retry_count);
+  }
+
+ private:
+  // A range object defining the offset and length of a download chunk.  Zero
+  // length indicates an unspecified end offset (note that it is impossible to
+  // request a zero-length range in HTTP).
+  class Range {
+   public:
+    Range(off_t offset, size_t length) : offset_(offset), length_(length) {}
+    explicit Range(off_t offset) : offset_(offset), length_(0) {}
+
+    inline off_t offset() const { return offset_; }
+    inline size_t length() const { return length_; }
+
+    inline bool HasLength() const { return (length_ > 0); }
+
+    std::string ToString() const;
+
+   private:
+    off_t offset_;
+    size_t length_;
+  };
+
+  typedef std::vector<Range> RangesVect;
+
+  // State change: Stopped or Downloading -> Downloading
+  void StartTransfer();
+
+  // HttpFetcherDelegate overrides.
+  // State change: Downloading -> Downloading or Pending transfer ended
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override;
+
+  // State change: Pending transfer ended -> Stopped
+  void TransferEnded(HttpFetcher* fetcher, bool successful);
+  // These two call TransferEnded():
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override;
+  void TransferTerminated(HttpFetcher* fetcher) override;
+
+  void Reset();
+
+  std::unique_ptr<HttpFetcher> base_fetcher_;
+
+  // If true, do not send any more data or TransferComplete to the delegate.
+  bool base_fetcher_active_;
+
+  // If true, the next fetcher needs to be started when TransferTerminated is
+  // received from the current fetcher.
+  bool pending_transfer_ended_;
+
+  // True if we are waiting for base fetcher to terminate b/c we are
+  // ourselves terminating.
+  bool terminating_;
+
+  RangesVect ranges_;
+
+  RangesVect::size_type current_index_;  // index into ranges_
+  size_t bytes_received_this_range_;
+
+  DISALLOW_COPY_AND_ASSIGN(MultiRangeHttpFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_MULTI_RANGE_HTTP_FETCHER_H_
diff --git a/update_engine/common/platform_constants.h b/update_engine/common/platform_constants.h
new file mode 100644
index 0000000..419f322
--- /dev/null
+++ b/update_engine/common/platform_constants.h
@@ -0,0 +1,64 @@
+//
+// 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_COMMON_PLATFORM_CONSTANTS_H_
+#define UPDATE_ENGINE_COMMON_PLATFORM_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+namespace constants {
+
+// The default URL used by all products when running in normal mode. The AUTest
+// URL is used when testing normal images against the alternative AUTest server.
+// Note that the URL can be override in run-time in certain cases.
+extern const char kOmahaDefaultProductionURL[];
+extern const char kOmahaDefaultAUTestURL[];
+
+// Our product name used in Omaha. This value must match the one configured in
+// the server side and is sent on every request.
+extern const char kOmahaUpdaterID[];
+
+// The name of the platform as sent to Omaha.
+extern const char kOmahaPlatformName[];
+
+// Path to the location of the public half of the payload key. The payload key
+// is used to sign the contents of the payload binary file: the manifest and the
+// whole payload.
+extern const char kUpdatePayloadPublicKeyPath[];
+
+// Path to the directory containing all the SSL certificates accepted by
+// update_engine when sending requests to Omaha and the download server (if
+// HTTPS is used for that as well).
+extern const char kCACertificatesPath[];
+
+extern const char kCACertificatesFilePath[];
+extern const char kSSLCertPath[];
+extern const char kSSLKeyPath[];
+
+// Path to the file used to notify chrome about the deadline of the last omaha
+// response. Empty if not supported.
+extern const char kOmahaResponseDeadlineFile[];
+
+// The stateful directory used by update_engine.
+extern const char kNonVolatileDirectory[];
+
+// Options passed to the filesystem when mounting the new partition during
+// postinstall.
+extern const char kPostinstallMountOptions[];
+
+}  // namespace constants
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_PLATFORM_CONSTANTS_H_
diff --git a/update_engine/common/platform_constants_android.cc b/update_engine/common/platform_constants_android.cc
new file mode 100644
index 0000000..371fe26
--- /dev/null
+++ b/update_engine/common/platform_constants_android.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://clients2.google.com/service/update2/brillo";
+const char kOmahaDefaultAUTestURL[] =
+    "https://clients2.google.com/service/update2/brillo";
+const char kOmahaUpdaterID[] = "Brillo";
+const char kOmahaPlatformName[] = "Brillo";
+const char kUpdatePayloadPublicKeyPath[] =
+    "/etc/update_engine/update-payload-key.pub.pem";
+const char kCACertificatesPath[] = "/system/etc/security/cacerts_google";
+// No deadline file API support on Android.
+const char kOmahaResponseDeadlineFile[] = "";
+const char kNonVolatileDirectory[] = "/data/misc/update_engine";
+const char kPostinstallMountOptions[] =
+  "context=u:object_r:postinstall_file:s0";
+
+}  // namespace constants
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/platform_constants_chromeos.cc b/update_engine/common/platform_constants_chromeos.cc
new file mode 100644
index 0000000..3ebcf8a
--- /dev/null
+++ b/update_engine/common/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.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 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/update_engine/common/platform_constants_nestlabs.cc b/update_engine/common/platform_constants_nestlabs.cc
new file mode 100644
index 0000000..42cd2f5
--- /dev/null
+++ b/update_engine/common/platform_constants_nestlabs.cc
@@ -0,0 +1,40 @@
+//
+// 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[] = "";
+const char kOmahaDefaultAUTestURL[] = "";
+const char kOmahaUpdaterID[] = "";
+const char kOmahaPlatformName[] = "";
+const char kOmahaResponseDeadlineFile[] = "";
+
+const char kUpdatePayloadPublicKeyPath[] =
+    "/etc/update_engine/update-payload-key.pub.pem";
+const char kCACertificatesPath[] = "/system/etc/security/cacerts_google";
+const char kCACertificatesFilePath[] = "/etc/dropcam_calist.pem";
+const char kSSLCertPath[] = "/data/misc/certs/device_cert.pem";
+const char kSSLKeyPath[] =  "/data/misc/certs/device_cert.key";
+// This directory is wiped during powerwash.
+const char kNonVolatileDirectory[] = "/data/misc/update_engine";
+const char kPostinstallMountOptions[] =
+  "context=u:object_r:postinstall_file:s0";
+
+}  // namespace constants
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/prefs.cc b/update_engine/common/prefs.cc
new file mode 100644
index 0000000..12d06c0
--- /dev/null
+++ b/update_engine/common/prefs.cc
@@ -0,0 +1,196 @@
+//
+// 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/common/prefs.h"
+
+#include <algorithm>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+bool PrefsBase::GetString(const string& key, string* value) const {
+  return storage_->GetKey(key, value);
+}
+
+bool PrefsBase::SetString(const string& key, const string& value) {
+  TEST_AND_RETURN_FALSE(storage_->SetKey(key, value));
+  const auto observers_for_key = observers_.find(key);
+  if (observers_for_key != observers_.end()) {
+    std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+    for (ObserverInterface* observer : copy_observers)
+      observer->OnPrefSet(key);
+  }
+  return true;
+}
+
+bool PrefsBase::GetInt64(const string& key, int64_t* value) const {
+  string str_value;
+  if (!GetString(key, &str_value))
+    return false;
+  base::TrimWhitespaceASCII(str_value, base::TRIM_ALL, &str_value);
+  TEST_AND_RETURN_FALSE(base::StringToInt64(str_value, value));
+  return true;
+}
+
+bool PrefsBase::SetInt64(const string& key, const int64_t value) {
+  return SetString(key, base::Int64ToString(value));
+}
+
+bool PrefsBase::GetBoolean(const string& key, bool* value) const {
+  string str_value;
+  if (!GetString(key, &str_value))
+    return false;
+  base::TrimWhitespaceASCII(str_value, base::TRIM_ALL, &str_value);
+  if (str_value == "false") {
+    *value = false;
+    return true;
+  }
+  if (str_value == "true") {
+    *value = true;
+    return true;
+  }
+  return false;
+}
+
+bool PrefsBase::SetBoolean(const string& key, const bool value) {
+  return SetString(key, value ? "true" : "false");
+}
+
+bool PrefsBase::Exists(const string& key) const {
+  return storage_->KeyExists(key);
+}
+
+bool PrefsBase::Delete(const string& key) {
+  TEST_AND_RETURN_FALSE(storage_->DeleteKey(key));
+  const auto observers_for_key = observers_.find(key);
+  if (observers_for_key != observers_.end()) {
+    std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+    for (ObserverInterface* observer : copy_observers)
+      observer->OnPrefDeleted(key);
+  }
+  return true;
+}
+
+void PrefsBase::AddObserver(const string& key, ObserverInterface* observer) {
+  observers_[key].push_back(observer);
+}
+
+void PrefsBase::RemoveObserver(const string& key, ObserverInterface* observer) {
+  std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+  auto observer_it =
+      std::find(observers_for_key.begin(), observers_for_key.end(), observer);
+  if (observer_it != observers_for_key.end())
+    observers_for_key.erase(observer_it);
+}
+
+// Prefs
+
+bool Prefs::Init(const base::FilePath& prefs_dir) {
+  return file_storage_.Init(prefs_dir);
+}
+
+bool Prefs::FileStorage::Init(const base::FilePath& prefs_dir) {
+  prefs_dir_ = prefs_dir;
+  return true;
+}
+
+bool Prefs::FileStorage::GetKey(const string& key, string* value) const {
+  base::FilePath filename;
+  TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+  if (!base::ReadFileToString(filename, value)) {
+    LOG(INFO) << key << " not present in " << prefs_dir_.value();
+    return false;
+  }
+  return true;
+}
+
+bool Prefs::FileStorage::SetKey(const string& key, const string& value) {
+  base::FilePath filename;
+  TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+  if (!base::DirectoryExists(filename.DirName())) {
+    // Only attempt to create the directory if it doesn't exist to avoid calls
+    // to parent directories where we might not have permission to write to.
+    TEST_AND_RETURN_FALSE(base::CreateDirectory(filename.DirName()));
+  }
+  TEST_AND_RETURN_FALSE(base::WriteFile(filename, value.data(), value.size()) ==
+                        static_cast<int>(value.size()));
+  return true;
+}
+
+bool Prefs::FileStorage::KeyExists(const string& key) const {
+  base::FilePath filename;
+  TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+  return base::PathExists(filename);
+}
+
+bool Prefs::FileStorage::DeleteKey(const string& key) {
+  base::FilePath filename;
+  TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+  TEST_AND_RETURN_FALSE(base::DeleteFile(filename, false));
+  return true;
+}
+
+bool Prefs::FileStorage::GetFileNameForKey(const string& key,
+                                           base::FilePath* filename) const {
+  // Allows only non-empty keys containing [A-Za-z0-9_-].
+  TEST_AND_RETURN_FALSE(!key.empty());
+  for (size_t i = 0; i < key.size(); ++i) {
+    char c = key.at(i);
+    TEST_AND_RETURN_FALSE(base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) ||
+                          c == '_' || c == '-');
+  }
+  *filename = prefs_dir_.Append(key);
+  return true;
+}
+
+// MemoryPrefs
+
+bool MemoryPrefs::MemoryStorage::GetKey(const string& key,
+                                        string* value) const {
+  auto it = values_.find(key);
+  if (it == values_.end())
+    return false;
+  *value = it->second;
+  return true;
+}
+
+bool MemoryPrefs::MemoryStorage::SetKey(const string& key,
+                                        const string& value) {
+  values_[key] = value;
+  return true;
+}
+
+bool MemoryPrefs::MemoryStorage::KeyExists(const string& key) const {
+  return values_.find(key) != values_.end();
+}
+
+bool MemoryPrefs::MemoryStorage::DeleteKey(const string& key) {
+  auto it = values_.find(key);
+  if (it == values_.end())
+    return false;
+  values_.erase(it);
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/prefs.h b/update_engine/common/prefs.h
new file mode 100644
index 0000000..0116454
--- /dev/null
+++ b/update_engine/common/prefs.h
@@ -0,0 +1,168 @@
+//
+// 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_COMMON_PREFS_H_
+#define UPDATE_ENGINE_COMMON_PREFS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+
+#include "gtest/gtest_prod.h"  // for FRIEND_TEST
+#include "update_engine/common/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a preference store by storing the value associated with a key
+// in a given storage passed during construction.
+class PrefsBase : public PrefsInterface {
+ public:
+  // Storage interface used to set and retrieve keys.
+  class StorageInterface {
+   public:
+    StorageInterface() = default;
+    virtual ~StorageInterface() = default;
+
+    // Get the key named |key| and store its value in the referenced |value|.
+    // Returns whether the operation succeeded.
+    virtual bool GetKey(const std::string& key, std::string* value) const = 0;
+
+    // Set the value of the key named |key| to |value| regardless of the
+    // previous value. Returns whether the operation succeeded.
+    virtual bool SetKey(const std::string& key, const std::string& value) = 0;
+
+    // Returns whether the key named |key| exists.
+    virtual bool KeyExists(const std::string& key) const = 0;
+
+    // Deletes the value associated with the key name |key|. Returns whether the
+    // key was deleted.
+    virtual bool DeleteKey(const std::string& key) = 0;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(StorageInterface);
+  };
+
+  explicit PrefsBase(StorageInterface* storage) : storage_(storage) {}
+
+  // PrefsInterface methods.
+  bool GetString(const std::string& key, std::string* value) const override;
+  bool SetString(const std::string& key, const std::string& value) override;
+  bool GetInt64(const std::string& key, int64_t* value) const override;
+  bool SetInt64(const std::string& key, const int64_t value) override;
+  bool GetBoolean(const std::string& key, bool* value) const override;
+  bool SetBoolean(const std::string& key, const bool value) override;
+
+  bool Exists(const std::string& key) const override;
+  bool Delete(const std::string& key) override;
+
+  void AddObserver(const std::string& key,
+                   ObserverInterface* observer) override;
+  void RemoveObserver(const std::string& key,
+                      ObserverInterface* observer) override;
+
+ private:
+  // The registered observers watching for changes.
+  std::map<std::string, std::vector<ObserverInterface*>> observers_;
+
+  // The concrete implementation of the storage used for the keys.
+  StorageInterface* storage_;
+
+  DISALLOW_COPY_AND_ASSIGN(PrefsBase);
+};
+
+// Implements a preference store by storing the value associated with
+// a key in a separate file named after the key under a preference
+// store directory.
+
+class Prefs : public PrefsBase {
+ public:
+  Prefs() : PrefsBase(&file_storage_) {}
+
+  // Initializes the store by associating this object with |prefs_dir|
+  // as the preference store directory. Returns true on success, false
+  // otherwise.
+  bool Init(const base::FilePath& prefs_dir);
+
+ private:
+  FRIEND_TEST(PrefsTest, GetFileNameForKey);
+  FRIEND_TEST(PrefsTest, GetFileNameForKeyBadCharacter);
+  FRIEND_TEST(PrefsTest, GetFileNameForKeyEmpty);
+
+  class FileStorage : public PrefsBase::StorageInterface {
+   public:
+    FileStorage() = default;
+
+    bool Init(const base::FilePath& prefs_dir);
+
+    // PrefsBase::StorageInterface overrides.
+    bool GetKey(const std::string& key, std::string* value) const override;
+    bool SetKey(const std::string& key, const std::string& value) override;
+    bool KeyExists(const std::string& key) const override;
+    bool DeleteKey(const std::string& key) override;
+
+   private:
+    FRIEND_TEST(PrefsTest, GetFileNameForKey);
+    FRIEND_TEST(PrefsTest, GetFileNameForKeyBadCharacter);
+    FRIEND_TEST(PrefsTest, GetFileNameForKeyEmpty);
+
+    // Sets |filename| to the full path to the file containing the data
+    // associated with |key|. Returns true on success, false otherwise.
+    bool GetFileNameForKey(const std::string& key,
+                           base::FilePath* filename) const;
+
+    // Preference store directory.
+    base::FilePath prefs_dir_;
+  };
+
+  // The concrete file storage implementation.
+  FileStorage file_storage_;
+
+  DISALLOW_COPY_AND_ASSIGN(Prefs);
+};
+
+// Implements a preference store in memory. The stored values are lost when the
+// object is destroyed.
+
+class MemoryPrefs : public PrefsBase {
+ public:
+  MemoryPrefs() : PrefsBase(&mem_storage_) {}
+
+ private:
+  class MemoryStorage : public PrefsBase::StorageInterface {
+   public:
+    MemoryStorage() = default;
+
+    // PrefsBase::StorageInterface overrides.
+    bool GetKey(const std::string& key, std::string* value) const override;
+    bool SetKey(const std::string& key, const std::string& value) override;
+    bool KeyExists(const std::string& key) const override;
+    bool DeleteKey(const std::string& key) override;
+
+   private:
+    // The std::map holding the values in memory.
+    std::map<std::string, std::string> values_;
+  };
+
+  // The concrete memory storage implementation.
+  MemoryStorage mem_storage_;
+
+  DISALLOW_COPY_AND_ASSIGN(MemoryPrefs);
+};
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_PREFS_H_
diff --git a/update_engine/common/prefs_interface.h b/update_engine/common/prefs_interface.h
new file mode 100644
index 0000000..03ae3ec
--- /dev/null
+++ b/update_engine/common/prefs_interface.h
@@ -0,0 +1,97 @@
+//
+// 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_COMMON_PREFS_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_PREFS_INTERFACE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+// The prefs interface allows access to a persistent preferences
+// store. The two reasons for providing this as an interface are
+// testing as well as easier switching to a new implementation in the
+// future, if necessary.
+
+class PrefsInterface {
+ public:
+  // Observer class to be notified about key value changes.
+  class ObserverInterface {
+   public:
+    virtual ~ObserverInterface() = default;
+
+    // Called when the value is set for the observed |key|.
+    virtual void OnPrefSet(const std::string& key) = 0;
+
+    // Called when the observed |key| is deleted.
+    virtual void OnPrefDeleted(const std::string& key) = 0;
+  };
+
+  virtual ~PrefsInterface() = default;
+
+  // Gets a string |value| associated with |key|. Returns true on
+  // success, false on failure (including when the |key| is not
+  // present in the store).
+  virtual bool GetString(const std::string& key, std::string* value) const = 0;
+
+  // Associates |key| with a string |value|. Returns true on success,
+  // false otherwise.
+  virtual bool SetString(const std::string& key, const std::string& value) = 0;
+
+  // Gets an int64_t |value| associated with |key|. Returns true on
+  // success, false on failure (including when the |key| is not
+  // present in the store).
+  virtual bool GetInt64(const std::string& key, int64_t* value) const = 0;
+
+  // Associates |key| with an int64_t |value|. Returns true on success,
+  // false otherwise.
+  virtual bool SetInt64(const std::string& key, const int64_t value) = 0;
+
+  // Gets a boolean |value| associated with |key|. Returns true on
+  // success, false on failure (including when the |key| is not
+  // present in the store).
+  virtual bool GetBoolean(const std::string& key, bool* value) const = 0;
+
+  // Associates |key| with a boolean |value|. Returns true on success,
+  // false otherwise.
+  virtual bool SetBoolean(const std::string& key, const bool value) = 0;
+
+  // Returns true if the setting exists (i.e. a file with the given key
+  // exists in the prefs directory)
+  virtual bool Exists(const std::string& key) const = 0;
+
+  // Returns true if successfully deleted the file corresponding to
+  // this key. Calling with non-existent keys does nothing.
+  virtual bool Delete(const std::string& key) = 0;
+
+  // Add an observer to watch whenever the given |key| is modified. The
+  // OnPrefSet() and OnPrefDelete() methods will be called whenever any of the
+  // Set*() methods or the Delete() method are called on the given key,
+  // respectively.
+  virtual void AddObserver(const std::string& key,
+                           ObserverInterface* observer) = 0;
+
+  // Remove an observer added with AddObserver(). The observer won't be called
+  // anymore for future Set*() and Delete() method calls.
+  virtual void RemoveObserver(const std::string& key,
+                              ObserverInterface* observer) = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_PREFS_INTERFACE_H_
diff --git a/update_engine/common/prefs_unittest.cc b/update_engine/common/prefs_unittest.cc
new file mode 100644
index 0000000..73ceb00
--- /dev/null
+++ b/update_engine/common/prefs_unittest.cc
@@ -0,0 +1,361 @@
+//
+// 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/common/prefs.h"
+
+#include <inttypes.h>
+
+#include <limits>
+#include <string>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using std::string;
+using testing::Eq;
+using testing::_;
+
+namespace {
+// Test key used along the tests.
+const char kKey[] = "test-key";
+}
+
+namespace chromeos_update_engine {
+
+class PrefsTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    prefs_dir_ = temp_dir_.path();
+    ASSERT_TRUE(prefs_.Init(prefs_dir_));
+  }
+
+  bool SetValue(const string& key, const string& value) {
+    return base::WriteFile(prefs_dir_.Append(key), value.data(),
+                           value.length()) == static_cast<int>(value.length());
+  }
+
+  base::ScopedTempDir temp_dir_;
+  base::FilePath prefs_dir_;
+  Prefs prefs_;
+};
+
+TEST_F(PrefsTest, GetFileNameForKey) {
+  const char kAllvalidCharsKey[] =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-";
+  base::FilePath path;
+  EXPECT_TRUE(prefs_.file_storage_.GetFileNameForKey(kAllvalidCharsKey, &path));
+  EXPECT_EQ(prefs_dir_.Append(kAllvalidCharsKey).value(), path.value());
+}
+
+TEST_F(PrefsTest, GetFileNameForKeyBadCharacter) {
+  base::FilePath path;
+  EXPECT_FALSE(prefs_.file_storage_.GetFileNameForKey("ABC abc", &path));
+}
+
+TEST_F(PrefsTest, GetFileNameForKeyEmpty) {
+  base::FilePath path;
+  EXPECT_FALSE(prefs_.file_storage_.GetFileNameForKey("", &path));
+}
+
+TEST_F(PrefsTest, GetString) {
+  const string test_data = "test data";
+  ASSERT_TRUE(SetValue(kKey, test_data));
+  string value;
+  EXPECT_TRUE(prefs_.GetString(kKey, &value));
+  EXPECT_EQ(test_data, value);
+}
+
+TEST_F(PrefsTest, GetStringBadKey) {
+  string value;
+  EXPECT_FALSE(prefs_.GetString(",bad", &value));
+}
+
+TEST_F(PrefsTest, GetStringNonExistentKey) {
+  string value;
+  EXPECT_FALSE(prefs_.GetString("non-existent-key", &value));
+}
+
+TEST_F(PrefsTest, SetString) {
+  const char kValue[] = "some test value\non 2 lines";
+  EXPECT_TRUE(prefs_.SetString(kKey, kValue));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ(kValue, value);
+}
+
+TEST_F(PrefsTest, SetStringBadKey) {
+  const char kKeyWithDots[] = ".no-dots";
+  EXPECT_FALSE(prefs_.SetString(kKeyWithDots, "some value"));
+  EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKeyWithDots)));
+}
+
+TEST_F(PrefsTest, SetStringCreateDir) {
+  const char kValue[] = "test value";
+  base::FilePath subdir = prefs_dir_.Append("subdir1").Append("subdir2");
+  EXPECT_TRUE(prefs_.Init(subdir));
+  EXPECT_TRUE(prefs_.SetString(kKey, kValue));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(subdir.Append(kKey), &value));
+  EXPECT_EQ(kValue, value);
+}
+
+TEST_F(PrefsTest, SetStringDirCreationFailure) {
+  EXPECT_TRUE(prefs_.Init(base::FilePath("/dev/null")));
+  EXPECT_FALSE(prefs_.SetString(kKey, "test value"));
+}
+
+TEST_F(PrefsTest, SetStringFileCreationFailure) {
+  base::CreateDirectory(prefs_dir_.Append(kKey));
+  EXPECT_FALSE(prefs_.SetString(kKey, "test value"));
+  EXPECT_TRUE(base::DirectoryExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, GetInt64) {
+  ASSERT_TRUE(SetValue(kKey, " \n 25 \t "));
+  int64_t value;
+  EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+  EXPECT_EQ(25, value);
+}
+
+TEST_F(PrefsTest, GetInt64BadValue) {
+  ASSERT_TRUE(SetValue(kKey, "30a"));
+  int64_t value;
+  EXPECT_FALSE(prefs_.GetInt64(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetInt64Max) {
+  ASSERT_TRUE(SetValue(kKey, base::StringPrintf(
+      "%" PRIi64, std::numeric_limits<int64_t>::max())));
+  int64_t value;
+  EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+  EXPECT_EQ(std::numeric_limits<int64_t>::max(), value);
+}
+
+TEST_F(PrefsTest, GetInt64Min) {
+  ASSERT_TRUE(SetValue(kKey, base::StringPrintf(
+        "%" PRIi64, std::numeric_limits<int64_t>::min())));
+  int64_t value;
+  EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+  EXPECT_EQ(std::numeric_limits<int64_t>::min(), value);
+}
+
+TEST_F(PrefsTest, GetInt64Negative) {
+  ASSERT_TRUE(SetValue(kKey, " \t -100 \n "));
+  int64_t value;
+  EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+  EXPECT_EQ(-100, value);
+}
+
+TEST_F(PrefsTest, GetInt64NonExistentKey) {
+  int64_t value;
+  EXPECT_FALSE(prefs_.GetInt64("random-key", &value));
+}
+
+TEST_F(PrefsTest, SetInt64) {
+  EXPECT_TRUE(prefs_.SetInt64(kKey, -123));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ("-123", value);
+}
+
+TEST_F(PrefsTest, SetInt64BadKey) {
+  const char kKeyWithSpaces[] = "s p a c e s";
+  EXPECT_FALSE(prefs_.SetInt64(kKeyWithSpaces, 20));
+  EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKeyWithSpaces)));
+}
+
+TEST_F(PrefsTest, SetInt64Max) {
+  EXPECT_TRUE(prefs_.SetInt64(kKey, std::numeric_limits<int64_t>::max()));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ(base::StringPrintf("%" PRIi64, std::numeric_limits<int64_t>::max()),
+            value);
+}
+
+TEST_F(PrefsTest, SetInt64Min) {
+  EXPECT_TRUE(prefs_.SetInt64(kKey, std::numeric_limits<int64_t>::min()));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ(base::StringPrintf("%" PRIi64, std::numeric_limits<int64_t>::min()),
+            value);
+}
+
+TEST_F(PrefsTest, GetBooleanFalse) {
+  ASSERT_TRUE(SetValue(kKey, " \n false \t "));
+  bool value;
+  EXPECT_TRUE(prefs_.GetBoolean(kKey, &value));
+  EXPECT_FALSE(value);
+}
+
+TEST_F(PrefsTest, GetBooleanTrue) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, " \t true \n "));
+  bool value;
+  EXPECT_TRUE(prefs_.GetBoolean(kKey, &value));
+  EXPECT_TRUE(value);
+}
+
+TEST_F(PrefsTest, GetBooleanBadValue) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, "1"));
+  bool value;
+  EXPECT_FALSE(prefs_.GetBoolean(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetBooleanBadEmptyValue) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, ""));
+  bool value;
+  EXPECT_FALSE(prefs_.GetBoolean(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetBooleanNonExistentKey) {
+  bool value;
+  EXPECT_FALSE(prefs_.GetBoolean("random-key", &value));
+}
+
+TEST_F(PrefsTest, SetBooleanTrue) {
+  const char kKey[] = "test-bool";
+  EXPECT_TRUE(prefs_.SetBoolean(kKey, true));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ("true", value);
+}
+
+TEST_F(PrefsTest, SetBooleanFalse) {
+  const char kKey[] = "test-bool";
+  EXPECT_TRUE(prefs_.SetBoolean(kKey, false));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ("false", value);
+}
+
+TEST_F(PrefsTest, SetBooleanBadKey) {
+  const char kKey[] = "s p a c e s";
+  EXPECT_FALSE(prefs_.SetBoolean(kKey, true));
+  EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, ExistsWorks) {
+  // test that the key doesn't exist before we set it.
+  EXPECT_FALSE(prefs_.Exists(kKey));
+
+  // test that the key exists after we set it.
+  ASSERT_TRUE(prefs_.SetInt64(kKey, 8));
+  EXPECT_TRUE(prefs_.Exists(kKey));
+}
+
+TEST_F(PrefsTest, DeleteWorks) {
+  // test that it's alright to delete a non-existent key.
+  EXPECT_TRUE(prefs_.Delete(kKey));
+
+  // delete the key after we set it.
+  ASSERT_TRUE(prefs_.SetInt64(kKey, 0));
+  EXPECT_TRUE(prefs_.Delete(kKey));
+
+  // make sure it doesn't exist anymore.
+  EXPECT_FALSE(prefs_.Exists(kKey));
+}
+
+class MockPrefsObserver : public PrefsInterface::ObserverInterface {
+ public:
+  MOCK_METHOD1(OnPrefSet, void(const string&));
+  MOCK_METHOD1(OnPrefDeleted, void(const string& key));
+};
+
+TEST_F(PrefsTest, ObserversCalled) {
+  MockPrefsObserver mock_obserser;
+  prefs_.AddObserver(kKey, &mock_obserser);
+
+  EXPECT_CALL(mock_obserser, OnPrefSet(Eq(kKey)));
+  EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+  prefs_.SetString(kKey, "value");
+  testing::Mock::VerifyAndClearExpectations(&mock_obserser);
+
+  EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+  EXPECT_CALL(mock_obserser, OnPrefDeleted(Eq(kKey)));
+  prefs_.Delete(kKey);
+  testing::Mock::VerifyAndClearExpectations(&mock_obserser);
+
+  prefs_.RemoveObserver(kKey, &mock_obserser);
+}
+
+TEST_F(PrefsTest, OnlyCalledOnObservedKeys) {
+  MockPrefsObserver mock_obserser;
+  const char kUnusedKey[] = "unused-key";
+  prefs_.AddObserver(kUnusedKey, &mock_obserser);
+
+  EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+  EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+  prefs_.SetString(kKey, "value");
+  prefs_.Delete(kKey);
+
+  prefs_.RemoveObserver(kUnusedKey, &mock_obserser);
+}
+
+TEST_F(PrefsTest, RemovedObserversNotCalled) {
+  MockPrefsObserver mock_obserser_a, mock_obserser_b;
+  prefs_.AddObserver(kKey, &mock_obserser_a);
+  prefs_.AddObserver(kKey, &mock_obserser_b);
+  EXPECT_CALL(mock_obserser_a, OnPrefSet(_)).Times(2);
+  EXPECT_CALL(mock_obserser_b, OnPrefSet(_)).Times(1);
+  EXPECT_TRUE(prefs_.SetString(kKey, "value"));
+  prefs_.RemoveObserver(kKey, &mock_obserser_b);
+  EXPECT_TRUE(prefs_.SetString(kKey, "other value"));
+  prefs_.RemoveObserver(kKey, &mock_obserser_a);
+  EXPECT_TRUE(prefs_.SetString(kKey, "yet another value"));
+}
+
+TEST_F(PrefsTest, UnsuccessfulCallsNotObserved) {
+  MockPrefsObserver mock_obserser;
+  const char kInvalidKey[] = "no spaces or .";
+  prefs_.AddObserver(kInvalidKey, &mock_obserser);
+
+  EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+  EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+  EXPECT_FALSE(prefs_.SetString(kInvalidKey, "value"));
+  EXPECT_FALSE(prefs_.Delete(kInvalidKey));
+
+  prefs_.RemoveObserver(kInvalidKey, &mock_obserser);
+}
+
+class MemoryPrefsTest : public ::testing::Test {
+ protected:
+  MemoryPrefs prefs_;
+};
+
+TEST_F(MemoryPrefsTest, BasicTest) {
+  EXPECT_FALSE(prefs_.Exists(kKey));
+  int64_t value = 0;
+  EXPECT_FALSE(prefs_.GetInt64(kKey, &value));
+
+  EXPECT_TRUE(prefs_.SetInt64(kKey, 1234));
+  EXPECT_TRUE(prefs_.Exists(kKey));
+  EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+  EXPECT_EQ(1234, value);
+
+  EXPECT_TRUE(prefs_.Delete(kKey));
+  EXPECT_FALSE(prefs_.Exists(kKey));
+  EXPECT_FALSE(prefs_.Delete(kKey));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/subprocess.cc b/update_engine/common/subprocess.cc
new file mode 100644
index 0000000..4e6d352
--- /dev/null
+++ b/update_engine/common/subprocess.cc
@@ -0,0 +1,294 @@
+//
+// 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/common/subprocess.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/process.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/utils.h"
+
+using brillo::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+bool SetupChild(const std::map<string, string>& env, uint32_t flags) {
+  // Setup the environment variables.
+  clearenv();
+  for (const auto& key_value : env) {
+    setenv(key_value.first.c_str(), key_value.second.c_str(), 0);
+  }
+
+  if ((flags & Subprocess::kRedirectStderrToStdout) != 0) {
+    if (HANDLE_EINTR(dup2(STDOUT_FILENO, STDERR_FILENO)) != STDERR_FILENO)
+      return false;
+  }
+
+  int fd = HANDLE_EINTR(open("/dev/null", O_RDONLY));
+  if (fd < 0)
+    return false;
+  if (HANDLE_EINTR(dup2(fd, STDIN_FILENO)) != STDIN_FILENO)
+    return false;
+  IGNORE_EINTR(close(fd));
+
+  return true;
+}
+
+// Helper function to launch a process with the given Subprocess::Flags.
+// This function only sets up and starts the process according to the |flags|.
+// The caller is responsible for watching the termination of the subprocess.
+// Return whether the process was successfully launched and fills in the |proc|
+// Process.
+bool LaunchProcess(const vector<string>& cmd,
+                   uint32_t flags,
+                   const vector<int>& output_pipes,
+                   brillo::Process* proc) {
+  for (const string& arg : cmd)
+    proc->AddArg(arg);
+  proc->SetSearchPath((flags & Subprocess::kSearchPath) != 0);
+
+  // Create an environment for the child process with just the required PATHs.
+  std::map<string, string> env;
+  for (const char* key : {"LD_LIBRARY_PATH", "PATH"}) {
+    const char* value = getenv(key);
+    if (value)
+      env.emplace(key, value);
+  }
+
+  for (const int fd : output_pipes) {
+    proc->RedirectUsingPipe(fd, false);
+  }
+  proc->SetCloseUnusedFileDescriptors(true);
+  proc->RedirectUsingPipe(STDOUT_FILENO, false);
+  proc->SetPreExecCallback(base::Bind(&SetupChild, env, flags));
+
+  return proc->Start();
+}
+
+}  // namespace
+
+void Subprocess::Init(
+      brillo::AsynchronousSignalHandlerInterface* async_signal_handler) {
+  if (subprocess_singleton_ == this)
+    return;
+  CHECK(subprocess_singleton_ == nullptr);
+  subprocess_singleton_ = this;
+
+  process_reaper_.Register(async_signal_handler);
+}
+
+Subprocess::~Subprocess() {
+  if (subprocess_singleton_ == this)
+    subprocess_singleton_ = nullptr;
+}
+
+void Subprocess::OnStdoutReady(SubprocessRecord* record) {
+  char buf[1024];
+  size_t bytes_read;
+  do {
+    bytes_read = 0;
+    bool eof;
+    bool ok = utils::ReadAll(
+        record->stdout_fd, buf, arraysize(buf), &bytes_read, &eof);
+    record->stdout.append(buf, bytes_read);
+    if (!ok || eof) {
+      // There was either an error or an EOF condition, so we are done watching
+      // the file descriptor.
+      MessageLoop::current()->CancelTask(record->stdout_task_id);
+      record->stdout_task_id = MessageLoop::kTaskIdNull;
+      return;
+    }
+  } while (bytes_read);
+}
+
+void Subprocess::ChildExitedCallback(const siginfo_t& info) {
+  auto pid_record = subprocess_records_.find(info.si_pid);
+  if (pid_record == subprocess_records_.end())
+    return;
+  SubprocessRecord* record = pid_record->second.get();
+
+  // Make sure we read any remaining process output and then close the pipe.
+  OnStdoutReady(record);
+
+  MessageLoop::current()->CancelTask(record->stdout_task_id);
+  record->stdout_task_id = MessageLoop::kTaskIdNull;
+
+  // Don't print any log if the subprocess exited with exit code 0.
+  if (info.si_code != CLD_EXITED) {
+    LOG(INFO) << "Subprocess terminated with si_code " << info.si_code;
+  } else if (info.si_status != 0) {
+    LOG(INFO) << "Subprocess exited with si_status: " << info.si_status;
+  }
+
+  if (!record->stdout.empty()) {
+    LOG(INFO) << "Subprocess output:\n" << record->stdout;
+  }
+  if (!record->callback.is_null()) {
+    record->callback.Run(info.si_status, record->stdout);
+  }
+  // Release and close all the pipes after calling the callback so our
+  // redirected pipes are still alive. Releasing the process first makes
+  // Reset(0) not attempt to kill the process, which is already a zombie at this
+  // point.
+  record->proc.Release();
+  record->proc.Reset(0);
+
+  subprocess_records_.erase(pid_record);
+}
+
+pid_t Subprocess::Exec(const vector<string>& cmd,
+                       const ExecCallback& callback) {
+  return ExecFlags(cmd, kRedirectStderrToStdout, {}, callback);
+}
+
+pid_t Subprocess::ExecFlags(const vector<string>& cmd,
+                            uint32_t flags,
+                            const vector<int>& output_pipes,
+                            const ExecCallback& callback) {
+  unique_ptr<SubprocessRecord> record(new SubprocessRecord(callback));
+
+  if (!LaunchProcess(cmd, flags, output_pipes, &record->proc)) {
+    LOG(ERROR) << "Failed to launch subprocess";
+    return 0;
+  }
+
+  pid_t pid = record->proc.pid();
+  CHECK(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind(
+      &Subprocess::ChildExitedCallback,
+      base::Unretained(this))));
+
+  record->stdout_fd = record->proc.GetPipe(STDOUT_FILENO);
+  // Capture the subprocess output. Make our end of the pipe non-blocking.
+  int fd_flags = fcntl(record->stdout_fd, F_GETFL, 0) | O_NONBLOCK;
+  if (HANDLE_EINTR(fcntl(record->stdout_fd, F_SETFL, fd_flags)) < 0) {
+    LOG(ERROR) << "Unable to set non-blocking I/O mode on fd "
+               << record->stdout_fd << ".";
+  }
+
+  record->stdout_task_id = MessageLoop::current()->WatchFileDescriptor(
+      FROM_HERE,
+      record->stdout_fd,
+      MessageLoop::WatchMode::kWatchRead,
+      true,
+      base::Bind(&Subprocess::OnStdoutReady, record.get()));
+
+  subprocess_records_[pid] = std::move(record);
+  return pid;
+}
+
+void Subprocess::KillExec(pid_t pid) {
+  auto pid_record = subprocess_records_.find(pid);
+  if (pid_record == subprocess_records_.end())
+    return;
+  pid_record->second->callback.Reset();
+  // We don't care about output/return code, so we use SIGKILL here to ensure it
+  // will be killed, SIGTERM might lead to leaked subprocess.
+  if (kill(pid, SIGKILL) != 0) {
+    PLOG(WARNING) << "Error sending SIGKILL to " << pid;
+  }
+  // Release the pid now so we don't try to kill it if Subprocess is destroyed
+  // before the corresponding ChildExitedCallback() is called.
+  pid_record->second->proc.Release();
+}
+
+int Subprocess::GetPipeFd(pid_t pid, int fd) const {
+  auto pid_record = subprocess_records_.find(pid);
+  if (pid_record == subprocess_records_.end())
+    return -1;
+  return pid_record->second->proc.GetPipe(fd);
+}
+
+bool Subprocess::SynchronousExec(const vector<string>& cmd,
+                                 int* return_code,
+                                 string* stdout) {
+  // The default for SynchronousExec is to use kSearchPath since the code relies
+  // on that.
+  return SynchronousExecFlags(
+      cmd,
+      kRedirectStderrToStdout | kSearchPath,
+      return_code,
+      stdout);
+}
+
+bool Subprocess::SynchronousExecFlags(const vector<string>& cmd,
+                                      uint32_t flags,
+                                      int* return_code,
+                                      string* stdout) {
+  brillo::ProcessImpl proc;
+  // It doesn't make sense to redirect some pipes in the synchronous case
+  // because we won't be reading on our end, so we don't expose the output_pipes
+  // in this case.
+  if (!LaunchProcess(cmd, flags, {}, &proc)) {
+    LOG(ERROR) << "Failed to launch subprocess";
+    return false;
+  }
+
+  if (stdout) {
+    stdout->clear();
+  }
+
+  int fd = proc.GetPipe(STDOUT_FILENO);
+  vector<char> buffer(32 * 1024);
+  while (true) {
+    int rc = HANDLE_EINTR(read(fd, buffer.data(), buffer.size()));
+    if (rc < 0) {
+      PLOG(ERROR) << "Reading from child's output";
+      break;
+    } else if (rc == 0) {
+      break;
+    } else {
+      if (stdout)
+        stdout->append(buffer.data(), rc);
+    }
+  }
+  // At this point, the subprocess already closed the output, so we only need to
+  // wait for it to finish.
+  int proc_return_code = proc.Wait();
+  if (return_code)
+    *return_code = proc_return_code;
+  return proc_return_code != brillo::Process::kErrorExitStatus;
+}
+
+bool Subprocess::SubprocessInFlight() {
+  for (const auto& pid_record : subprocess_records_) {
+    if (!pid_record.second->callback.is_null())
+      return true;
+  }
+  return false;
+}
+
+Subprocess* Subprocess::subprocess_singleton_ = nullptr;
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/subprocess.h b/update_engine/common/subprocess.h
new file mode 100644
index 0000000..b655fb7
--- /dev/null
+++ b/update_engine/common/subprocess.h
@@ -0,0 +1,152 @@
+//
+// 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_COMMON_SUBPROCESS_H_
+#define UPDATE_ENGINE_COMMON_SUBPROCESS_H_
+
+#include <unistd.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/asynchronous_signal_handler_interface.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/process.h>
+#include <brillo/process_reaper.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+// The Subprocess class is a singleton. It's used to spawn off a subprocess
+// and get notified when the subprocess exits. The result of Exec() can
+// be saved and used to cancel the callback request and kill your process. If
+// you know you won't call KillExec(), you may safely lose the return value
+// from Exec().
+
+// To create the Subprocess singleton just instantiate it with and call Init().
+// You can't have two Subprocess instances initialized at the same time.
+
+namespace chromeos_update_engine {
+
+class Subprocess {
+ public:
+  enum Flags {
+    kSearchPath = 1 << 0,
+    kRedirectStderrToStdout = 1 << 1,
+  };
+
+  // Callback type used when an async process terminates. It receives the exit
+  // code and the stdout output (and stderr if redirected).
+  using ExecCallback = base::Callback<void(int, const std::string&)>;
+
+  Subprocess() = default;
+
+  // Destroy and unregister the Subprocess singleton.
+  ~Subprocess();
+
+  // Initialize and register the Subprocess singleton.
+  void Init(brillo::AsynchronousSignalHandlerInterface* async_signal_handler);
+
+  // Launches a process in the background and calls the passed |callback| when
+  // the process exits. The file descriptors specified in |output_pipes| will
+  // be available in the child as the writer end of a pipe. Use GetPipeFd() to
+  // know the reader end in the parent. Only stdin, stdout, stderr and the file
+  // descriptors in |output_pipes| will be open in the child.
+  // Returns the process id of the new launched process or 0 in case of failure.
+  pid_t Exec(const std::vector<std::string>& cmd, const ExecCallback& callback);
+  pid_t ExecFlags(const std::vector<std::string>& cmd,
+                  uint32_t flags,
+                  const std::vector<int>& output_pipes,
+                  const ExecCallback& callback);
+
+  // Kills the running process with SIGTERM and ignores the callback.
+  void KillExec(pid_t pid);
+
+  // Return the parent end of the pipe mapped onto |fd| in the child |pid|. This
+  // file descriptor is available until the callback for the child |pid|
+  // returns. After that the file descriptor will be closed. The passed |fd|
+  // must be one of the file descriptors passed to ExecFlags() in
+  // |output_pipes|, otherwise returns -1.
+  int GetPipeFd(pid_t pid, int fd) const;
+
+  // Executes a command synchronously. Returns true on success. If |stdout| is
+  // non-null, the process output is stored in it, otherwise the output is
+  // logged. Note that stderr is redirected to stdout.
+  static bool SynchronousExec(const std::vector<std::string>& cmd,
+                              int* return_code,
+                              std::string* stdout);
+  static bool SynchronousExecFlags(const std::vector<std::string>& cmd,
+                                   uint32_t flags,
+                                   int* return_code,
+                                   std::string* stdout);
+
+  // Gets the one instance.
+  static Subprocess& Get() {
+    return *subprocess_singleton_;
+  }
+
+  // Returns true iff there is at least one subprocess we're waiting on.
+  bool SubprocessInFlight();
+
+ private:
+  FRIEND_TEST(SubprocessTest, CancelTest);
+
+  struct SubprocessRecord {
+    explicit SubprocessRecord(const ExecCallback& callback)
+      : callback(callback) {}
+
+    // The callback supplied by the caller.
+    ExecCallback callback;
+
+    // The ProcessImpl instance managing the child process. Destroying this
+    // will close our end of the pipes we have open.
+    brillo::ProcessImpl proc;
+
+    // These are used to monitor the stdout of the running process, including
+    // the stderr if it was redirected.
+    brillo::MessageLoop::TaskId stdout_task_id{
+        brillo::MessageLoop::kTaskIdNull};
+    int stdout_fd{-1};
+    std::string stdout;
+  };
+
+  // Callback which runs whenever there is input available on the subprocess
+  // stdout pipe.
+  static void OnStdoutReady(SubprocessRecord* record);
+
+  // Callback for when any subprocess terminates. This calls the user
+  // requested callback.
+  void ChildExitedCallback(const siginfo_t& info);
+
+  // The global instance.
+  static Subprocess* subprocess_singleton_;
+
+  // A map from the asynchronous subprocess tag (see Exec) to the subprocess
+  // record structure for all active asynchronous subprocesses.
+  std::map<pid_t, std::unique_ptr<SubprocessRecord>> subprocess_records_;
+
+  // Used to watch for child processes.
+  brillo::ProcessReaper process_reaper_;
+
+  DISALLOW_COPY_AND_ASSIGN(Subprocess);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_SUBPROCESS_H_
diff --git a/update_engine/common/subprocess_unittest.cc b/update_engine/common/subprocess_unittest.cc
new file mode 100644
index 0000000..7dbdf98
--- /dev/null
+++ b/update_engine/common/subprocess_unittest.cc
@@ -0,0 +1,273 @@
+//
+// 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/common/subprocess.h"
+
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/location.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <brillo/bind_lambda.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 <brillo/strings/string_utils.h>
+#include <brillo/unittest_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::string;
+using std::vector;
+
+namespace {
+
+#ifdef __ANDROID__
+#define kBinPath "/system/bin"
+#define kUsrBinPath "/system/bin"
+#else
+#define kBinPath "/bin"
+#define kUsrBinPath "/usr/bin"
+#endif  // __ANDROID__
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+class SubprocessTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    async_signal_handler_.Init();
+    subprocess_.Init(&async_signal_handler_);
+  }
+
+  base::MessageLoopForIO base_loop_;
+  brillo::BaseMessageLoop loop_{&base_loop_};
+  brillo::AsynchronousSignalHandler async_signal_handler_;
+  Subprocess subprocess_;
+};
+
+namespace {
+
+void ExpectedResults(int expected_return_code, const string& expected_output,
+                     int return_code, const string& output) {
+  EXPECT_EQ(expected_return_code, return_code);
+  EXPECT_EQ(expected_output, output);
+  MessageLoop::current()->BreakLoop();
+}
+
+void ExpectedEnvVars(int return_code, const string& output) {
+  EXPECT_EQ(0, return_code);
+  const std::set<string> allowed_envs = {"LD_LIBRARY_PATH", "PATH"};
+  for (const string& key_value : brillo::string_utils::Split(output, "\n")) {
+    auto key_value_pair = brillo::string_utils::SplitAtFirst(
+        key_value, "=", true);
+    EXPECT_NE(allowed_envs.end(), allowed_envs.find(key_value_pair.first));
+  }
+  MessageLoop::current()->BreakLoop();
+}
+
+void ExpectedDataOnPipe(const Subprocess* subprocess,
+                        pid_t* pid,
+                        int child_fd,
+                        const string& child_fd_data,
+                        int expected_return_code,
+                        int return_code,
+                        const string& /* output */) {
+  EXPECT_EQ(expected_return_code, return_code);
+
+  // Verify that we can read the data from our end of |child_fd|.
+  int fd = subprocess->GetPipeFd(*pid, child_fd);
+  EXPECT_NE(-1, fd);
+  vector<char> buf(child_fd_data.size() + 1);
+  EXPECT_EQ(static_cast<ssize_t>(child_fd_data.size()),
+            HANDLE_EINTR(read(fd, buf.data(), buf.size())));
+  EXPECT_EQ(child_fd_data,
+            string(buf.begin(), buf.begin() + child_fd_data.size()));
+
+  MessageLoop::current()->BreakLoop();
+}
+
+}  // namespace
+
+TEST_F(SubprocessTest, IsASingleton) {
+  EXPECT_EQ(&subprocess_, &Subprocess::Get());
+}
+
+TEST_F(SubprocessTest, InactiveInstancesDontChangeTheSingleton) {
+  std::unique_ptr<Subprocess> another_subprocess(new Subprocess());
+  EXPECT_EQ(&subprocess_, &Subprocess::Get());
+  another_subprocess.reset();
+  EXPECT_EQ(&subprocess_, &Subprocess::Get());
+}
+
+TEST_F(SubprocessTest, SimpleTest) {
+  EXPECT_TRUE(subprocess_.Exec({kBinPath "/false"},
+                               base::Bind(&ExpectedResults, 1, "")));
+  loop_.Run();
+}
+
+TEST_F(SubprocessTest, EchoTest) {
+  EXPECT_TRUE(subprocess_.Exec(
+      {kBinPath "/sh", "-c", "echo this is stdout; echo this is stderr >&2"},
+      base::Bind(&ExpectedResults, 0, "this is stdout\nthis is stderr\n")));
+  loop_.Run();
+}
+
+TEST_F(SubprocessTest, StderrNotIncludedInOutputTest) {
+  EXPECT_TRUE(subprocess_.ExecFlags(
+      {kBinPath "/sh", "-c", "echo on stdout; echo on stderr >&2"},
+      0,
+      {},
+      base::Bind(&ExpectedResults, 0, "on stdout\n")));
+  loop_.Run();
+}
+
+TEST_F(SubprocessTest, PipeRedirectFdTest) {
+  pid_t pid;
+  pid = subprocess_.ExecFlags(
+      {kBinPath "/sh", "-c", "echo on pipe >&3"},
+      0,
+      {3},
+      base::Bind(&ExpectedDataOnPipe, &subprocess_, &pid, 3, "on pipe\n", 0));
+  EXPECT_NE(0, pid);
+
+  // Wrong file descriptor values should return -1.
+  EXPECT_EQ(-1, subprocess_.GetPipeFd(pid, 123));
+  loop_.Run();
+  // Calling GetPipeFd() after the callback runs is invalid.
+  EXPECT_EQ(-1, subprocess_.GetPipeFd(pid, 3));
+}
+
+// Test that a pipe file descriptor open in the parent is not open in the child.
+TEST_F(SubprocessTest, PipeClosedWhenNotRedirectedTest) {
+  brillo::ScopedPipe pipe;
+
+  // test_subprocess will return with the errno of fstat, which should be EBADF
+  // if the passed file descriptor is closed in the child.
+  const vector<string> cmd = {
+      test_utils::GetBuildArtifactsPath("test_subprocess"),
+      "fstat",
+      std::to_string(pipe.writer)};
+  EXPECT_TRUE(subprocess_.ExecFlags(
+      cmd, 0, {}, base::Bind(&ExpectedResults, EBADF, "")));
+  loop_.Run();
+}
+
+TEST_F(SubprocessTest, EnvVarsAreFiltered) {
+  EXPECT_TRUE(
+      subprocess_.Exec({kUsrBinPath "/env"}, base::Bind(&ExpectedEnvVars)));
+  loop_.Run();
+}
+
+TEST_F(SubprocessTest, SynchronousTrueSearchsOnPath) {
+  int rc = -1;
+  EXPECT_TRUE(Subprocess::SynchronousExecFlags(
+      {"true"}, Subprocess::kSearchPath, &rc, nullptr));
+  EXPECT_EQ(0, rc);
+}
+
+TEST_F(SubprocessTest, SynchronousEchoTest) {
+  vector<string> cmd = {
+      kBinPath "/sh",
+      "-c",
+      "echo -n stdout-here; echo -n stderr-there >&2"};
+  int rc = -1;
+  string stdout;
+  ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, &stdout));
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ("stdout-herestderr-there", stdout);
+}
+
+TEST_F(SubprocessTest, SynchronousEchoNoOutputTest) {
+  int rc = -1;
+  ASSERT_TRUE(Subprocess::SynchronousExec(
+      {kBinPath "/sh", "-c", "echo test"}, &rc, nullptr));
+  EXPECT_EQ(0, rc);
+}
+
+namespace {
+void CallbackBad(int return_code, const string& output) {
+  ADD_FAILURE() << "should never be called.";
+}
+}  // namespace
+
+// Test that you can cancel a program that's already running.
+TEST_F(SubprocessTest, CancelTest) {
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+  string fifo_path = tempdir.path().Append("fifo").value();
+  EXPECT_EQ(0, mkfifo(fifo_path.c_str(), 0666));
+
+  // Start a process, make sure it is running and try to cancel it. We write
+  // two bytes to the fifo, the first one marks that the program is running and
+  // the second one marks that the process waited for a timeout and was not
+  // killed. We should read the first byte but not the second one.
+  vector<string> cmd = {
+      kBinPath "/sh",
+      "-c",
+      base::StringPrintf(
+          "echo -n  X >\"%s\"; sleep 60; echo -n  Y >\"%s\"; exit 1",
+          fifo_path.c_str(),
+          fifo_path.c_str())};
+  uint32_t tag = Subprocess::Get().Exec(cmd, base::Bind(&CallbackBad));
+  EXPECT_NE(0U, tag);
+
+  int fifo_fd = HANDLE_EINTR(open(fifo_path.c_str(), O_RDONLY));
+  EXPECT_GE(fifo_fd, 0);
+
+  loop_.WatchFileDescriptor(FROM_HERE,
+                            fifo_fd,
+                            MessageLoop::WatchMode::kWatchRead,
+                            false,
+                            base::Bind([](int fifo_fd, uint32_t tag) {
+                              char c;
+                              EXPECT_EQ(1, HANDLE_EINTR(read(fifo_fd, &c, 1)));
+                              EXPECT_EQ('X', c);
+                              LOG(INFO) << "Killing tag " << tag;
+                              Subprocess::Get().KillExec(tag);
+                            }, fifo_fd, tag));
+
+  // This test would leak a callback that runs when the child process exits
+  // unless we wait for it to run.
+  brillo::MessageLoopRunUntil(
+      &loop_,
+      TimeDelta::FromSeconds(120),
+      base::Bind([] { return Subprocess::Get().subprocess_records_.empty(); }));
+  EXPECT_TRUE(Subprocess::Get().subprocess_records_.empty());
+  // Check that there isn't anything else to read from the pipe.
+  char c;
+  EXPECT_EQ(0, HANDLE_EINTR(read(fifo_fd, &c, 1)));
+  IGNORE_EINTR(close(fifo_fd));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/terminator.cc b/update_engine/common/terminator.cc
new file mode 100644
index 0000000..62adafd
--- /dev/null
+++ b/update_engine/common/terminator.cc
@@ -0,0 +1,56 @@
+//
+// 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/common/terminator.h"
+
+#include <cstdlib>
+
+namespace chromeos_update_engine {
+
+volatile sig_atomic_t Terminator::exit_status_ = 1;  // default exit status
+volatile sig_atomic_t Terminator::exit_blocked_ = 0;
+volatile sig_atomic_t Terminator::exit_requested_ = 0;
+
+void Terminator::Init() {
+  exit_blocked_ = 0;
+  exit_requested_ = 0;
+  signal(SIGTERM, HandleSignal);
+}
+
+void Terminator::Init(int exit_status) {
+  exit_status_ = exit_status;
+  Init();
+}
+
+void Terminator::Exit() {
+  exit(exit_status_);
+}
+
+void Terminator::HandleSignal(int signum) {
+  if (exit_blocked_ == 0) {
+    Exit();
+  }
+  exit_requested_ = 1;
+}
+
+ScopedTerminatorExitUnblocker::~ScopedTerminatorExitUnblocker() {
+  Terminator::set_exit_blocked(false);
+  if (Terminator::exit_requested()) {
+    Terminator::Exit();
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/terminator.h b/update_engine/common/terminator.h
new file mode 100644
index 0000000..20616f6
--- /dev/null
+++ b/update_engine/common/terminator.h
@@ -0,0 +1,65 @@
+//
+// 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_COMMON_TERMINATOR_H_
+#define UPDATE_ENGINE_COMMON_TERMINATOR_H_
+
+#include <signal.h>
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace chromeos_update_engine {
+
+// A class allowing graceful delayed exit.
+class Terminator {
+ public:
+  // Initializes the terminator and sets up signal handlers.
+  static void Init();
+  static void Init(int exit_status);
+
+  // Terminates the current process.
+  static void Exit();
+
+  // Set to true if the terminator should block termination requests in an
+  // attempt to block exiting.
+  static void set_exit_blocked(bool block) { exit_blocked_ = block ? 1 : 0; }
+  static bool exit_blocked() { return exit_blocked_ != 0; }
+
+  // Returns true if the system is trying to terminate the process, false
+  // otherwise. Returns true only if exit was blocked when the termination
+  // request arrived.
+  static bool exit_requested() { return exit_requested_ != 0; }
+
+ private:
+  FRIEND_TEST(TerminatorTest, HandleSignalTest);
+  FRIEND_TEST(TerminatorDeathTest, ScopedTerminatorExitUnblockerExitTest);
+
+  // The signal handler.
+  static void HandleSignal(int signum);
+
+  static volatile sig_atomic_t exit_status_;
+  static volatile sig_atomic_t exit_blocked_;
+  static volatile sig_atomic_t exit_requested_;
+};
+
+class ScopedTerminatorExitUnblocker {
+ public:
+  ~ScopedTerminatorExitUnblocker();
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_TERMINATOR_H_
diff --git a/update_engine/common/terminator_unittest.cc b/update_engine/common/terminator_unittest.cc
new file mode 100644
index 0000000..5e8302f
--- /dev/null
+++ b/update_engine/common/terminator_unittest.cc
@@ -0,0 +1,85 @@
+//
+// 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/common/terminator.h"
+
+#include <gtest/gtest.h>
+#include <gtest/gtest-spi.h>
+
+using testing::ExitedWithCode;
+
+namespace chromeos_update_engine {
+
+class TerminatorTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    Terminator::Init();
+    ASSERT_FALSE(Terminator::exit_blocked());
+    ASSERT_FALSE(Terminator::exit_requested());
+  }
+  void TearDown() override {
+    // Makes sure subsequent non-Terminator tests don't get accidentally
+    // terminated.
+    Terminator::Init();
+  }
+};
+
+typedef TerminatorTest TerminatorDeathTest;
+
+namespace {
+void UnblockExitThroughUnblocker() {
+  ScopedTerminatorExitUnblocker unblocker = ScopedTerminatorExitUnblocker();
+}
+
+void RaiseSIGTERM() {
+  ASSERT_EXIT(raise(SIGTERM), ExitedWithCode(2), "");
+}
+}  // namespace
+
+TEST_F(TerminatorTest, HandleSignalTest) {
+  Terminator::set_exit_blocked(true);
+  Terminator::HandleSignal(SIGTERM);
+  ASSERT_TRUE(Terminator::exit_requested());
+}
+
+TEST_F(TerminatorTest, ScopedTerminatorExitUnblockerTest) {
+  Terminator::set_exit_blocked(true);
+  ASSERT_TRUE(Terminator::exit_blocked());
+  ASSERT_FALSE(Terminator::exit_requested());
+  UnblockExitThroughUnblocker();
+  ASSERT_FALSE(Terminator::exit_blocked());
+  ASSERT_FALSE(Terminator::exit_requested());
+}
+
+TEST_F(TerminatorDeathTest, ExitTest) {
+  ASSERT_EXIT(Terminator::Exit(), ExitedWithCode(2), "");
+  Terminator::set_exit_blocked(true);
+  ASSERT_EXIT(Terminator::Exit(), ExitedWithCode(2), "");
+}
+
+TEST_F(TerminatorDeathTest, RaiseSignalTest) {
+  RaiseSIGTERM();
+  Terminator::set_exit_blocked(true);
+  EXPECT_FATAL_FAILURE(RaiseSIGTERM(), "");
+}
+
+TEST_F(TerminatorDeathTest, ScopedTerminatorExitUnblockerExitTest) {
+  Terminator::set_exit_blocked(true);
+  Terminator::exit_requested_ = 1;
+  ASSERT_EXIT(UnblockExitThroughUnblocker(), ExitedWithCode(2), "");
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/test_utils.cc b/update_engine/common/test_utils.cc
new file mode 100644
index 0000000..dfdc6b8
--- /dev/null
+++ b/update_engine/common/test_utils.cc
@@ -0,0 +1,271 @@
+//
+// 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/common/test_utils.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/loop.h>
+#include <linux/major.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.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/error_code_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/file_writer.h"
+
+using base::StringPrintf;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void PrintTo(const Extent& extent, ::std::ostream* os) {
+  *os << "(" << extent.start_block() << ", " << extent.num_blocks() << ")";
+}
+
+void PrintTo(const ErrorCode& error_code, ::std::ostream* os) {
+  *os << utils::ErrorCodeToString(error_code);
+}
+
+namespace test_utils {
+
+const uint8_t kRandomString[] = {
+  0xf2, 0xb7, 0x55, 0x92, 0xea, 0xa6, 0xc9, 0x57,
+  0xe0, 0xf8, 0xeb, 0x34, 0x93, 0xd9, 0xc4, 0x8f,
+  0xcb, 0x20, 0xfa, 0x37, 0x4b, 0x40, 0xcf, 0xdc,
+  0xa5, 0x08, 0x70, 0x89, 0x79, 0x35, 0xe2, 0x3d,
+  0x56, 0xa4, 0x75, 0x73, 0xa3, 0x6d, 0xd1, 0xd5,
+  0x26, 0xbb, 0x9c, 0x60, 0xbd, 0x2f, 0x5a, 0xfa,
+  0xb7, 0xd4, 0x3a, 0x50, 0xa7, 0x6b, 0x3e, 0xfd,
+  0x61, 0x2b, 0x3a, 0x31, 0x30, 0x13, 0x33, 0x53,
+  0xdb, 0xd0, 0x32, 0x71, 0x5c, 0x39, 0xed, 0xda,
+  0xb4, 0x84, 0xca, 0xbc, 0xbd, 0x78, 0x1c, 0x0c,
+  0xd8, 0x0b, 0x41, 0xe8, 0xe1, 0xe0, 0x41, 0xad,
+  0x03, 0x12, 0xd3, 0x3d, 0xb8, 0x75, 0x9b, 0xe6,
+  0xd9, 0x01, 0xd0, 0x87, 0xf4, 0x36, 0xfa, 0xa7,
+  0x0a, 0xfa, 0xc5, 0x87, 0x65, 0xab, 0x9a, 0x7b,
+  0xeb, 0x58, 0x23, 0xf0, 0xa8, 0x0a, 0xf2, 0x33,
+  0x3a, 0xe2, 0xe3, 0x35, 0x74, 0x95, 0xdd, 0x3c,
+  0x59, 0x5a, 0xd9, 0x52, 0x3a, 0x3c, 0xac, 0xe5,
+  0x15, 0x87, 0x6d, 0x82, 0xbc, 0xf8, 0x7d, 0xbe,
+  0xca, 0xd3, 0x2c, 0xd6, 0xec, 0x38, 0xeb, 0xe4,
+  0x53, 0xb0, 0x4c, 0x3f, 0x39, 0x29, 0xf7, 0xa4,
+  0x73, 0xa8, 0xcb, 0x32, 0x50, 0x05, 0x8c, 0x1c,
+  0x1c, 0xca, 0xc9, 0x76, 0x0b, 0x8f, 0x6b, 0x57,
+  0x1f, 0x24, 0x2b, 0xba, 0x82, 0xba, 0xed, 0x58,
+  0xd8, 0xbf, 0xec, 0x06, 0x64, 0x52, 0x6a, 0x3f,
+  0xe4, 0xad, 0xce, 0x84, 0xb4, 0x27, 0x55, 0x14,
+  0xe3, 0x75, 0x59, 0x73, 0x71, 0x51, 0xea, 0xe8,
+  0xcc, 0xda, 0x4f, 0x09, 0xaf, 0xa4, 0xbc, 0x0e,
+  0xa6, 0x1f, 0xe2, 0x3a, 0xf8, 0x96, 0x7d, 0x30,
+  0x23, 0xc5, 0x12, 0xb5, 0xd8, 0x73, 0x6b, 0x71,
+  0xab, 0xf1, 0xd7, 0x43, 0x58, 0xa7, 0xc9, 0xf0,
+  0xe4, 0x85, 0x1c, 0xd6, 0x92, 0x50, 0x2c, 0x98,
+  0x36, 0xfe, 0x87, 0xaf, 0x43, 0x8f, 0x8f, 0xf5,
+  0x88, 0x48, 0x18, 0x42, 0xcf, 0x42, 0xc1, 0xa8,
+  0xe8, 0x05, 0x08, 0xa1, 0x45, 0x70, 0x5b, 0x8c,
+  0x39, 0x28, 0xab, 0xe9, 0x6b, 0x51, 0xd2, 0xcb,
+  0x30, 0x04, 0xea, 0x7d, 0x2f, 0x6e, 0x6c, 0x3b,
+  0x5f, 0x82, 0xd9, 0x5b, 0x89, 0x37, 0x65, 0x65,
+  0xbe, 0x9f, 0xa3, 0x5d,
+};
+
+string Readlink(const string& path) {
+  vector<char> buf(PATH_MAX + 1);
+  ssize_t r = readlink(path.c_str(), buf.data(), buf.size());
+  if (r < 0)
+    return "";
+  CHECK_LT(r, static_cast<ssize_t>(buf.size()));
+  return string(buf.begin(), buf.begin() + r);
+}
+
+bool IsXAttrSupported(const base::FilePath& dir_path) {
+  char *path = strdup(dir_path.Append("xattr_test_XXXXXX").value().c_str());
+
+  int fd = mkstemp(path);
+  if (fd == -1) {
+    PLOG(ERROR) << "Error creating temporary file in " << dir_path.value();
+    free(path);
+    return false;
+  }
+
+  if (unlink(path) != 0) {
+    PLOG(ERROR) << "Error unlinking temporary file " << path;
+    close(fd);
+    free(path);
+    return false;
+  }
+
+  int xattr_res = fsetxattr(fd, "user.xattr-test", "value", strlen("value"), 0);
+  if (xattr_res != 0) {
+    if (errno == ENOTSUP) {
+      // Leave it to call-sites to warn about non-support.
+    } else {
+      PLOG(ERROR) << "Error setting xattr on " << path;
+    }
+  }
+  close(fd);
+  free(path);
+  return xattr_res == 0;
+}
+
+bool WriteFileVector(const string& path, const brillo::Blob& data) {
+  return utils::WriteFile(path.c_str(), data.data(), data.size());
+}
+
+bool WriteFileString(const string& path, const string& data) {
+  return utils::WriteFile(path.c_str(), data.data(), data.size());
+}
+
+bool BindToUnusedLoopDevice(const string& filename,
+                            bool writable,
+                            string* out_lo_dev_name) {
+  CHECK(out_lo_dev_name);
+  // Get the next available loop-device.
+  int control_fd =
+      HANDLE_EINTR(open("/dev/loop-control", O_RDWR | O_LARGEFILE));
+  TEST_AND_RETURN_FALSE_ERRNO(control_fd >= 0);
+  int loop_number = ioctl(control_fd, LOOP_CTL_GET_FREE);
+  IGNORE_EINTR(close(control_fd));
+  *out_lo_dev_name = StringPrintf("/dev/loop%d", loop_number);
+
+  // Double check that the loop exists and is free.
+  int loop_device_fd =
+      HANDLE_EINTR(open(out_lo_dev_name->c_str(), O_RDWR | O_LARGEFILE));
+  if (loop_device_fd == -1 && errno == ENOENT) {
+    // Workaround the case when the loop device doesn't exist.
+    TEST_AND_RETURN_FALSE_ERRNO(mknod(out_lo_dev_name->c_str(),
+                                      S_IFBLK | 0660,
+                                      makedev(LOOP_MAJOR, loop_number)) == 0);
+    loop_device_fd =
+        HANDLE_EINTR(open(out_lo_dev_name->c_str(), O_RDWR | O_LARGEFILE));
+  }
+  TEST_AND_RETURN_FALSE_ERRNO(loop_device_fd != -1);
+  ScopedFdCloser loop_device_fd_closer(&loop_device_fd);
+
+  struct loop_info64 device_info;
+  if (ioctl(loop_device_fd, LOOP_GET_STATUS64, &device_info) != -1 ||
+      errno != ENXIO) {
+    PLOG(ERROR) << "Loop device " << out_lo_dev_name->c_str()
+                << " already in use";
+    return false;
+  }
+
+  // Open our data file and assign it to the loop device.
+  int data_fd = open(filename.c_str(),
+                     (writable ? O_RDWR : O_RDONLY) | O_LARGEFILE | O_CLOEXEC);
+  TEST_AND_RETURN_FALSE_ERRNO(data_fd >= 0);
+  ScopedFdCloser data_fd_closer(&data_fd);
+  TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_SET_FD, data_fd) == 0);
+
+  memset(&device_info, 0, sizeof(device_info));
+  device_info.lo_offset = 0;
+  device_info.lo_sizelimit = 0;  // 0 means whole file.
+  device_info.lo_flags = (writable ? 0 : LO_FLAGS_READ_ONLY);
+  device_info.lo_number = loop_number;
+  strncpy(reinterpret_cast<char*>(device_info.lo_file_name),
+          base::FilePath(filename).BaseName().value().c_str(),
+          LO_NAME_SIZE - 1);
+  device_info.lo_file_name[LO_NAME_SIZE - 1] = '\0';
+  TEST_AND_RETURN_FALSE_ERRNO(
+      ioctl(loop_device_fd, LOOP_SET_STATUS64, &device_info) == 0);
+  return true;
+}
+
+bool UnbindLoopDevice(const string& lo_dev_name) {
+  int loop_device_fd =
+      HANDLE_EINTR(open(lo_dev_name.c_str(), O_RDWR | O_LARGEFILE));
+  if (loop_device_fd == -1 && errno == ENOENT)
+    return true;
+  TEST_AND_RETURN_FALSE_ERRNO(loop_device_fd != -1);
+  ScopedFdCloser loop_device_fd_closer(&loop_device_fd);
+
+  struct loop_info64 device_info;
+  // Check if the device is bound before trying to unbind it.
+  int get_stat_err = ioctl(loop_device_fd, LOOP_GET_STATUS64, &device_info);
+  if (get_stat_err == -1 && errno == ENXIO)
+    return true;
+
+  TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_CLR_FD) == 0);
+  return true;
+}
+
+bool ExpectVectorsEq(const brillo::Blob& expected,
+                     const brillo::Blob& actual) {
+  EXPECT_EQ(expected.size(), actual.size());
+  if (expected.size() != actual.size())
+    return false;
+  bool is_all_eq = true;
+  for (unsigned int i = 0; i < expected.size(); i++) {
+    EXPECT_EQ(expected[i], actual[i]) << "offset: " << i;
+    is_all_eq = is_all_eq && (expected[i] == actual[i]);
+  }
+  return is_all_eq;
+}
+
+void FillWithData(brillo::Blob* buffer) {
+  size_t input_counter = 0;
+  for (uint8_t& b : *buffer) {
+    b = kRandomString[input_counter];
+    input_counter++;
+    input_counter %= sizeof(kRandomString);
+  }
+}
+
+ScopedLoopMounter::ScopedLoopMounter(const string& file_path,
+                                     string* mnt_path,
+                                     unsigned long flags) {  // NOLINT - long
+  EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+  *mnt_path = temp_dir_.path().value();
+
+  string loop_dev;
+  loop_binder_.reset(
+      new ScopedLoopbackDeviceBinder(file_path, true, &loop_dev));
+
+  EXPECT_TRUE(utils::MountFilesystem(loop_dev, *mnt_path, flags, "", ""));
+  unmounter_.reset(new ScopedFilesystemUnmounter(*mnt_path));
+}
+
+base::FilePath GetBuildArtifactsPath() {
+  base::FilePath exe_path;
+  base::ReadSymbolicLink(base::FilePath("/proc/self/exe"), &exe_path);
+  return exe_path.DirName();
+}
+
+string GetBuildArtifactsPath(const string& relative_path) {
+  return GetBuildArtifactsPath().Append(relative_path).value();
+}
+
+}  // namespace test_utils
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/test_utils.h b/update_engine/common/test_utils.h
new file mode 100644
index 0000000..ba9f5f2
--- /dev/null
+++ b/update_engine/common/test_utils.h
@@ -0,0 +1,272 @@
+//
+// 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_COMMON_TEST_UTILS_H_
+#define UPDATE_ENGINE_COMMON_TEST_UTILS_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// Streams used for gtest's PrintTo() functions.
+#include <iostream>  // NOLINT(readability/streams)
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+// These are some handy functions for unittests.
+
+namespace chromeos_update_engine {
+
+// PrintTo() functions are used by gtest to log these objects. These PrintTo()
+// functions must be defined in the same namespace as the first argument.
+void PrintTo(const Extent& extent, ::std::ostream* os);
+void PrintTo(const ErrorCode& error_code, ::std::ostream* os);
+
+namespace test_utils {
+
+// 300 byte pseudo-random string. Not null terminated.
+// This does not gzip compress well.
+extern const uint8_t kRandomString[300];
+
+// Writes the data passed to path. The file at path will be overwritten if it
+// exists. Returns true on success, false otherwise.
+bool WriteFileVector(const std::string& path, const brillo::Blob& data);
+bool WriteFileString(const std::string& path, const std::string& data);
+
+// Binds provided |filename| to an unused loopback device, whose name is written
+// to the string pointed to by |out_lo_dev_name|. The new loop device will be
+// read-only unless |writable| is set to true. Returns true on success, false
+// otherwise (along with corresponding test failures), in which case the content
+// of |out_lo_dev_name| is unknown.
+bool BindToUnusedLoopDevice(const std::string& filename,
+                            bool writable,
+                            std::string* out_lo_dev_name);
+bool UnbindLoopDevice(const std::string& lo_dev_name);
+
+// Returns true iff a == b
+bool ExpectVectorsEq(const brillo::Blob& a, const brillo::Blob& b);
+
+inline int System(const std::string& cmd) {
+  return system(cmd.c_str());
+}
+
+inline int Symlink(const std::string& oldpath, const std::string& newpath) {
+  return symlink(oldpath.c_str(), newpath.c_str());
+}
+
+inline int Chmod(const std::string& path, mode_t mode) {
+  return chmod(path.c_str(), mode);
+}
+
+inline int Mkdir(const std::string& path, mode_t mode) {
+  return mkdir(path.c_str(), mode);
+}
+
+inline int Chdir(const std::string& path) {
+  return chdir(path.c_str());
+}
+
+// Reads a symlink from disk. Returns empty string on failure.
+std::string Readlink(const std::string& path);
+
+// Checks if xattr is supported in the directory specified by
+// |dir_path| which must be writable. Returns true if the feature is
+// supported, false if not or if an error occurred.
+bool IsXAttrSupported(const base::FilePath& dir_path);
+
+void FillWithData(brillo::Blob* buffer);
+
+// Class to unmount FS when object goes out of scope
+class ScopedFilesystemUnmounter {
+ public:
+  explicit ScopedFilesystemUnmounter(const std::string& mountpoint)
+      : mountpoint_(mountpoint),
+        should_unmount_(true) {}
+  ~ScopedFilesystemUnmounter() {
+    if (should_unmount_) {
+      utils::UnmountFilesystem(mountpoint_);
+    }
+  }
+  void set_should_unmount(bool unmount) { should_unmount_ = unmount; }
+ private:
+  const std::string mountpoint_;
+  bool should_unmount_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedFilesystemUnmounter);
+};
+
+class ScopedLoopbackDeviceBinder {
+ public:
+  ScopedLoopbackDeviceBinder(const std::string& file,
+                             bool writable,
+                             std::string* dev) {
+    is_bound_ = BindToUnusedLoopDevice(file, writable, &dev_);
+    EXPECT_TRUE(is_bound_);
+
+    if (is_bound_ && dev)
+      *dev = dev_;
+  }
+
+  ~ScopedLoopbackDeviceBinder() {
+    if (!is_bound_)
+      return;
+
+    for (int retry = 0; retry < 5; retry++) {
+      if (UnbindLoopDevice(dev_))
+        return;
+      sleep(1);
+    }
+    ADD_FAILURE();
+  }
+
+  const std::string &dev() {
+    EXPECT_TRUE(is_bound_);
+    return dev_;
+  }
+
+  bool is_bound() const { return is_bound_; }
+
+ private:
+  std::string dev_;
+  bool is_bound_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedLoopbackDeviceBinder);
+};
+
+class ScopedTempFile {
+ public:
+  ScopedTempFile() : ScopedTempFile("update_engine_test_temp_file.XXXXXX") {}
+
+  explicit ScopedTempFile(const std::string& pattern) {
+    EXPECT_TRUE(utils::MakeTempFile(pattern, &path_, nullptr));
+    unlinker_.reset(new ScopedPathUnlinker(path_));
+  }
+
+  const std::string& path() const { return path_; }
+
+ private:
+  std::string path_;
+  std::unique_ptr<ScopedPathUnlinker> unlinker_;
+};
+
+class ScopedLoopMounter {
+ public:
+  explicit ScopedLoopMounter(const std::string& file_path,
+                             std::string* mnt_path,
+                             unsigned long flags);  // NOLINT(runtime/int)
+
+ private:
+  // These objects must be destructed in the following order:
+  //   ScopedFilesystemUnmounter (the file system must be unmounted first)
+  //   ScopedLoopbackDeviceBinder (then the loop device can be deleted)
+  //   ScopedDirRemover (then the mount point can be deleted)
+  base::ScopedTempDir temp_dir_;
+  std::unique_ptr<ScopedLoopbackDeviceBinder> loop_binder_;
+  std::unique_ptr<ScopedFilesystemUnmounter> unmounter_;
+};
+
+// Returns the path where the build artifacts are stored. This is the directory
+// where the unittest executable is being run from.
+base::FilePath GetBuildArtifactsPath();
+// Returns the path of the build artifact specified in |relative_path|.
+std::string GetBuildArtifactsPath(const std::string& relative_path);
+
+}  // namespace test_utils
+
+// Useful actions for test. These need to be defined in the
+// chromeos_update_engine namespace.
+
+class NoneType;
+
+template<typename T>
+class ObjectFeederAction;
+
+template<typename T>
+class ActionTraits<ObjectFeederAction<T>> {
+ public:
+  typedef T OutputObjectType;
+  typedef NoneType InputObjectType;
+};
+
+// This is a simple Action class for testing. It feeds an object into
+// another action.
+template<typename T>
+class ObjectFeederAction : public Action<ObjectFeederAction<T>> {
+ public:
+  typedef NoneType InputObjectType;
+  typedef T OutputObjectType;
+  void PerformAction() {
+    LOG(INFO) << "feeder running!";
+    CHECK(this->processor_);
+    if (this->HasOutputPipe()) {
+      this->SetOutputObject(out_obj_);
+    }
+    this->processor_->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  static std::string StaticType() { return "ObjectFeederAction"; }
+  std::string Type() const { return StaticType(); }
+  void set_obj(const T& out_obj) {
+    out_obj_ = out_obj;
+  }
+ private:
+  T out_obj_;
+};
+
+template<typename T>
+class ObjectCollectorAction;
+
+template<typename T>
+class ActionTraits<ObjectCollectorAction<T>> {
+ public:
+  typedef NoneType OutputObjectType;
+  typedef T InputObjectType;
+};
+
+// This is a simple Action class for testing. It receives an object from
+// another action.
+template<typename T>
+class ObjectCollectorAction : public Action<ObjectCollectorAction<T>> {
+ public:
+  typedef T InputObjectType;
+  typedef NoneType OutputObjectType;
+  void PerformAction() {
+    LOG(INFO) << "collector running!";
+    ASSERT_TRUE(this->processor_);
+    if (this->HasInputObject()) {
+      object_ = this->GetInputObject();
+    }
+    this->processor_->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  static std::string StaticType() { return "ObjectCollectorAction"; }
+  std::string Type() const { return StaticType(); }
+  const T& object() const { return object_; }
+ private:
+  T object_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_TEST_UTILS_H_
diff --git a/update_engine/common/utils.cc b/update_engine/common/utils.cc
new file mode 100644
index 0000000..8e2e6ae
--- /dev/null
+++ b/update_engine/common/utils.cc
@@ -0,0 +1,1060 @@
+//
+// 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/common/utils.h"
+
+#include <stdint.h>
+
+#include <dirent.h>
+#include <elf.h>
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_file.h>
+#include <base/format_macros.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.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 <brillo/data_encoding.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_consumer/file_writer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::min;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The following constants control how UnmountFilesystem should retry if
+// umount() fails with an errno EBUSY, i.e. retry 5 times over the course of
+// one second.
+const int kUnmountMaxNumOfRetries = 5;
+const int kUnmountRetryIntervalInMicroseconds = 200 * 1000;  // 200 ms
+
+// Number of bytes to read from a file to attempt to detect its contents. Used
+// in GetFileFormat.
+const int kGetFileFormatMaxHeaderSize = 32;
+
+// The path to the kernel's boot_id.
+const char kBootIdPath[] = "/proc/sys/kernel/random/boot_id";
+
+// A pointer to a null-terminated string containing the root directory where all
+// the temporary files should be created. If null, the system default is used
+// instead.
+const char* root_temp_dir = nullptr;
+
+// Return true if |disk_name| is an MTD or a UBI device. Note that this test is
+// simply based on the name of the device.
+bool IsMtdDeviceName(const string& disk_name) {
+  return base::StartsWith(disk_name, "/dev/ubi",
+                          base::CompareCase::SENSITIVE) ||
+         base::StartsWith(disk_name, "/dev/mtd", base::CompareCase::SENSITIVE);
+}
+
+// Return the device name for the corresponding partition on a NAND device.
+// WARNING: This function returns device names that are not mountable.
+string MakeNandPartitionName(int partition_num) {
+  switch (partition_num) {
+    case 2:
+    case 4:
+    case 6: {
+      return base::StringPrintf("/dev/mtd%d", partition_num);
+    }
+    default: {
+      return base::StringPrintf("/dev/ubi%d_0", partition_num);
+    }
+  }
+}
+
+// Return the device name for the corresponding partition on a NAND device that
+// may be mountable (but may not be writable).
+string MakeNandPartitionNameForMount(int partition_num) {
+  switch (partition_num) {
+    case 2:
+    case 4:
+    case 6: {
+      return base::StringPrintf("/dev/mtd%d", partition_num);
+    }
+    case 3:
+    case 5:
+    case 7: {
+      return base::StringPrintf("/dev/ubiblock%d_0", partition_num);
+    }
+    default: {
+      return base::StringPrintf("/dev/ubi%d_0", partition_num);
+    }
+  }
+}
+
+// If |path| is absolute, or explicit relative to the current working directory,
+// leaves it as is. Otherwise, uses the system's temp directory, as defined by
+// base::GetTempDir() and prepends it to |path|. On success stores the full
+// temporary path in |template_path| and returns true.
+bool GetTempName(const string& path, base::FilePath* template_path) {
+  if (path[0] == '/' ||
+      base::StartsWith(path, "./", base::CompareCase::SENSITIVE) ||
+      base::StartsWith(path, "../", base::CompareCase::SENSITIVE)) {
+    *template_path = base::FilePath(path);
+    return true;
+  }
+
+  base::FilePath temp_dir;
+  if (root_temp_dir) {
+    temp_dir = base::FilePath(root_temp_dir);
+  } else {
+#ifdef __ANDROID__
+    temp_dir = base::FilePath(constants::kNonVolatileDirectory).Append("tmp");
+#else
+    TEST_AND_RETURN_FALSE(base::GetTempDir(&temp_dir));
+#endif  // __ANDROID__
+  }
+  if (!base::PathExists(temp_dir))
+    TEST_AND_RETURN_FALSE(base::CreateDirectory(temp_dir));
+  *template_path = temp_dir.Append(path);
+  return true;
+}
+
+}  // namespace
+
+namespace utils {
+
+void SetRootTempDir(const char* new_root_temp_dir) {
+  root_temp_dir = new_root_temp_dir;
+}
+
+string ParseECVersion(string input_line) {
+  base::TrimWhitespaceASCII(input_line, base::TRIM_ALL, &input_line);
+
+  // At this point we want to convert the format key=value pair from mosys to
+  // a vector of key value pairs.
+  vector<pair<string, string>> kv_pairs;
+  if (base::SplitStringIntoKeyValuePairs(input_line, '=', ' ', &kv_pairs)) {
+    for (const pair<string, string>& kv_pair : kv_pairs) {
+      // Finally match against the fw_verion which may have quotes.
+      if (kv_pair.first == "fw_version") {
+        string output;
+        // Trim any quotes.
+        base::TrimString(kv_pair.second, "\"", &output);
+        return output;
+      }
+    }
+  }
+  LOG(ERROR) << "Unable to parse fwid from ec info.";
+  return "";
+}
+
+bool WriteFile(const char* path, const void* data, int data_len) {
+  DirectFileWriter writer;
+  TEST_AND_RETURN_FALSE_ERRNO(0 == writer.Open(path,
+                                               O_WRONLY | O_CREAT | O_TRUNC,
+                                               0600));
+  ScopedFileWriterCloser closer(&writer);
+  TEST_AND_RETURN_FALSE_ERRNO(writer.Write(data, data_len));
+  return true;
+}
+
+bool ReadAll(
+    int fd, void* buf, size_t count, size_t* out_bytes_read, bool* eof) {
+  char* c_buf = static_cast<char*>(buf);
+  size_t bytes_read = 0;
+  *eof = false;
+  while (bytes_read < count) {
+    ssize_t rc = HANDLE_EINTR(read(fd, c_buf + bytes_read, count - bytes_read));
+    if (rc < 0) {
+      // EAGAIN and EWOULDBLOCK are normal return values when there's no more
+      // input and we are in non-blocking mode.
+      if (errno != EWOULDBLOCK && errno != EAGAIN) {
+        PLOG(ERROR) << "Error reading fd " << fd;
+        *out_bytes_read = bytes_read;
+        return false;
+      }
+      break;
+    } else if (rc == 0) {
+      // A value of 0 means that we reached EOF and there is nothing else to
+      // read from this fd.
+      *eof = true;
+      break;
+    } else {
+      bytes_read += rc;
+    }
+  }
+  *out_bytes_read = bytes_read;
+  return true;
+}
+
+bool WriteAll(int fd, const void* buf, size_t count) {
+  const char* c_buf = static_cast<const char*>(buf);
+  ssize_t bytes_written = 0;
+  while (bytes_written < static_cast<ssize_t>(count)) {
+    ssize_t rc = write(fd, c_buf + bytes_written, count - bytes_written);
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    bytes_written += rc;
+  }
+  return true;
+}
+
+bool PWriteAll(int fd, const void* buf, size_t count, off_t offset) {
+  const char* c_buf = static_cast<const char*>(buf);
+  size_t bytes_written = 0;
+  int num_attempts = 0;
+  while (bytes_written < count) {
+    num_attempts++;
+    ssize_t rc = pwrite(fd, c_buf + bytes_written, count - bytes_written,
+                        offset + bytes_written);
+    // TODO(garnold) for debugging failure in chromium-os:31077; to be removed.
+    if (rc < 0) {
+      PLOG(ERROR) << "pwrite error; num_attempts=" << num_attempts
+                  << " bytes_written=" << bytes_written
+                  << " count=" << count << " offset=" << offset;
+    }
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    bytes_written += rc;
+  }
+  return true;
+}
+
+bool WriteAll(const FileDescriptorPtr& fd, const void* buf, size_t count) {
+  const char* c_buf = static_cast<const char*>(buf);
+  ssize_t bytes_written = 0;
+  while (bytes_written < static_cast<ssize_t>(count)) {
+    ssize_t rc = fd->Write(c_buf + bytes_written, count - bytes_written);
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    bytes_written += rc;
+  }
+  return true;
+}
+
+bool PWriteAll(const FileDescriptorPtr& fd,
+               const void* buf,
+               size_t count,
+               off_t offset) {
+  TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) !=
+                              static_cast<off_t>(-1));
+  return WriteAll(fd, buf, count);
+}
+
+bool PReadAll(int fd, void* buf, size_t count, off_t offset,
+              ssize_t* out_bytes_read) {
+  char* c_buf = static_cast<char*>(buf);
+  ssize_t bytes_read = 0;
+  while (bytes_read < static_cast<ssize_t>(count)) {
+    ssize_t rc = pread(fd, c_buf + bytes_read, count - bytes_read,
+                       offset + bytes_read);
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    if (rc == 0) {
+      break;
+    }
+    bytes_read += rc;
+  }
+  *out_bytes_read = bytes_read;
+  return true;
+}
+
+bool PReadAll(const FileDescriptorPtr& fd, void* buf, size_t count, off_t offset,
+              ssize_t* out_bytes_read) {
+  TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) !=
+                              static_cast<off_t>(-1));
+  char* c_buf = static_cast<char*>(buf);
+  ssize_t bytes_read = 0;
+  while (bytes_read < static_cast<ssize_t>(count)) {
+    ssize_t rc = fd->Read(c_buf + bytes_read, count - bytes_read);
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    if (rc == 0) {
+      break;
+    }
+    bytes_read += rc;
+  }
+  *out_bytes_read = bytes_read;
+  return true;
+}
+
+// Append |nbytes| of content from |buf| to the vector pointed to by either
+// |vec_p| or |str_p|.
+static void AppendBytes(const uint8_t* buf, size_t nbytes,
+                        brillo::Blob* vec_p) {
+  CHECK(buf);
+  CHECK(vec_p);
+  vec_p->insert(vec_p->end(), buf, buf + nbytes);
+}
+static void AppendBytes(const uint8_t* buf, size_t nbytes,
+                        string* str_p) {
+  CHECK(buf);
+  CHECK(str_p);
+  str_p->append(buf, buf + nbytes);
+}
+
+// Reads from an open file |fp|, appending the read content to the container
+// pointer to by |out_p|.  Returns true upon successful reading all of the
+// file's content, false otherwise. If |size| is not -1, reads up to |size|
+// bytes.
+template <class T>
+static bool Read(FILE* fp, off_t size, T* out_p) {
+  CHECK(fp);
+  CHECK(size == -1 || size >= 0);
+  uint8_t buf[1024];
+  while (size == -1 || size > 0) {
+    off_t bytes_to_read = sizeof(buf);
+    if (size > 0 && bytes_to_read > size) {
+      bytes_to_read = size;
+    }
+    size_t nbytes = fread(buf, 1, bytes_to_read, fp);
+    if (!nbytes) {
+      break;
+    }
+    AppendBytes(buf, nbytes, out_p);
+    if (size != -1) {
+      CHECK(size >= static_cast<off_t>(nbytes));
+      size -= nbytes;
+    }
+  }
+  if (ferror(fp)) {
+    return false;
+  }
+  return size == 0 || feof(fp);
+}
+
+// Opens a file |path| for reading and appends its the contents to a container
+// |out_p|. Starts reading the file from |offset|. If |offset| is beyond the end
+// of the file, returns success. If |size| is not -1, reads up to |size| bytes.
+template <class T>
+static bool ReadFileChunkAndAppend(const string& path,
+                                   off_t offset,
+                                   off_t size,
+                                   T* out_p) {
+  CHECK_GE(offset, 0);
+  CHECK(size == -1 || size >= 0);
+  base::ScopedFILE fp(fopen(path.c_str(), "r"));
+  if (!fp.get())
+    return false;
+  if (offset) {
+    // Return success without appending any data if a chunk beyond the end of
+    // the file is requested.
+    if (offset >= FileSize(path)) {
+      return true;
+    }
+    TEST_AND_RETURN_FALSE_ERRNO(fseek(fp.get(), offset, SEEK_SET) == 0);
+  }
+  return Read(fp.get(), size, out_p);
+}
+
+// TODO(deymo): This is only used in unittest, but requires the private
+// Read<string>() defined here. Expose Read<string>() or move to base/ version.
+bool ReadPipe(const string& cmd, string* out_p) {
+  FILE* fp = popen(cmd.c_str(), "r");
+  if (!fp)
+    return false;
+  bool success = Read(fp, -1, out_p);
+  return (success && pclose(fp) >= 0);
+}
+
+bool ReadFile(const string& path, brillo::Blob* out_p) {
+  return ReadFileChunkAndAppend(path, 0, -1, out_p);
+}
+
+bool ReadFile(const string& path, string* out_p) {
+  return ReadFileChunkAndAppend(path, 0, -1, out_p);
+}
+
+bool ReadFileChunk(const string& path, off_t offset, off_t size,
+                   brillo::Blob* out_p) {
+  return ReadFileChunkAndAppend(path, offset, size, out_p);
+}
+
+off_t BlockDevSize(int fd) {
+  uint64_t dev_size;
+  int rc = ioctl(fd, BLKGETSIZE64, &dev_size);
+  if (rc == -1) {
+    dev_size = -1;
+    PLOG(ERROR) << "Error running ioctl(BLKGETSIZE64) on " << fd;
+  }
+  return dev_size;
+}
+
+off_t FileSize(int fd) {
+  struct stat stbuf;
+  int rc = fstat(fd, &stbuf);
+  CHECK_EQ(rc, 0);
+  if (rc < 0) {
+    PLOG(ERROR) << "Error stat-ing " << fd;
+    return rc;
+  }
+  if (S_ISREG(stbuf.st_mode))
+    return stbuf.st_size;
+  if (S_ISBLK(stbuf.st_mode))
+    return BlockDevSize(fd);
+  LOG(ERROR) << "Couldn't determine the type of " << fd;
+  return -1;
+}
+
+off_t FileSize(const string& path) {
+  int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
+  if (fd == -1) {
+    PLOG(ERROR) << "Error opening " << path;
+    return fd;
+  }
+  off_t size = FileSize(fd);
+  if (size == -1)
+    PLOG(ERROR) << "Error getting file size of " << path;
+  close(fd);
+  return size;
+}
+
+void HexDumpArray(const uint8_t* const arr, const size_t length) {
+  LOG(INFO) << "Logging array of length: " << length;
+  const unsigned int bytes_per_line = 16;
+  for (uint32_t i = 0; i < length; i += bytes_per_line) {
+    const unsigned int bytes_remaining = length - i;
+    const unsigned int bytes_per_this_line = min(bytes_per_line,
+                                                 bytes_remaining);
+    char header[100];
+    int r = snprintf(header, sizeof(header), "0x%08x : ", i);
+    TEST_AND_RETURN(r == 13);
+    string line = header;
+    for (unsigned int j = 0; j < bytes_per_this_line; j++) {
+      char buf[20];
+      uint8_t c = arr[i + j];
+      r = snprintf(buf, sizeof(buf), "%02x ", static_cast<unsigned int>(c));
+      TEST_AND_RETURN(r == 3);
+      line += buf;
+    }
+    LOG(INFO) << line;
+  }
+}
+
+bool SplitPartitionName(const string& partition_name,
+                        string* out_disk_name,
+                        int* out_partition_num) {
+  if (!base::StartsWith(partition_name, "/dev/",
+                        base::CompareCase::SENSITIVE)) {
+    LOG(ERROR) << "Invalid partition device name: " << partition_name;
+    return false;
+  }
+
+  size_t last_nondigit_pos = partition_name.find_last_not_of("0123456789");
+  if (last_nondigit_pos == string::npos ||
+      (last_nondigit_pos + 1) == partition_name.size()) {
+    LOG(ERROR) << "Unable to parse partition device name: " << partition_name;
+    return false;
+  }
+
+  size_t partition_name_len = string::npos;
+  if (partition_name[last_nondigit_pos] == '_') {
+    // NAND block devices have weird naming which could be something
+    // like "/dev/ubiblock2_0". We discard "_0" in such a case.
+    size_t prev_nondigit_pos =
+        partition_name.find_last_not_of("0123456789", last_nondigit_pos - 1);
+    if (prev_nondigit_pos == string::npos ||
+        (prev_nondigit_pos + 1) == last_nondigit_pos) {
+      LOG(ERROR) << "Unable to parse partition device name: " << partition_name;
+      return false;
+    }
+
+    partition_name_len = last_nondigit_pos - prev_nondigit_pos;
+    last_nondigit_pos = prev_nondigit_pos;
+  }
+
+  if (out_disk_name) {
+    // Special case for MMC devices which have the following naming scheme:
+    // mmcblk0p2
+    size_t disk_name_len = last_nondigit_pos;
+    if (partition_name[last_nondigit_pos] != 'p' ||
+        last_nondigit_pos == 0 ||
+        !isdigit(partition_name[last_nondigit_pos - 1])) {
+      disk_name_len++;
+    }
+    *out_disk_name = partition_name.substr(0, disk_name_len);
+  }
+
+  if (out_partition_num) {
+    string partition_str = partition_name.substr(last_nondigit_pos + 1,
+                                                 partition_name_len);
+    *out_partition_num = atoi(partition_str.c_str());
+  }
+  return true;
+}
+
+string MakePartitionName(const string& disk_name, int partition_num) {
+  if (partition_num < 1) {
+    LOG(ERROR) << "Invalid partition number: " << partition_num;
+    return string();
+  }
+
+  if (!base::StartsWith(disk_name, "/dev/", base::CompareCase::SENSITIVE)) {
+    LOG(ERROR) << "Invalid disk name: " << disk_name;
+    return string();
+  }
+
+  if (IsMtdDeviceName(disk_name)) {
+    // Special case for UBI block devices.
+    //   1. ubiblock is not writable, we need to use plain "ubi".
+    //   2. There is a "_0" suffix.
+    return MakeNandPartitionName(partition_num);
+  }
+
+  string partition_name = disk_name;
+  if (isdigit(partition_name.back())) {
+    // Special case for devices with names ending with a digit.
+    // Add "p" to separate the disk name from partition number,
+    // e.g. "/dev/loop0p2"
+    partition_name += 'p';
+  }
+
+  partition_name += std::to_string(partition_num);
+
+  return partition_name;
+}
+
+string MakePartitionNameForMount(const string& part_name) {
+  if (IsMtdDeviceName(part_name)) {
+    int partition_num;
+    if (!SplitPartitionName(part_name, nullptr, &partition_num)) {
+      return "";
+    }
+    return MakeNandPartitionNameForMount(partition_num);
+  }
+  return part_name;
+}
+
+string ErrnoNumberAsString(int err) {
+  char buf[100];
+  buf[0] = '\0';
+  return strerror_r(err, buf, sizeof(buf));
+}
+
+bool FileExists(const char* path) {
+  struct stat stbuf;
+  return 0 == lstat(path, &stbuf);
+}
+
+bool IsSymlink(const char* path) {
+  struct stat stbuf;
+  return lstat(path, &stbuf) == 0 && S_ISLNK(stbuf.st_mode) != 0;
+}
+
+bool TryAttachingUbiVolume(int volume_num, int timeout) {
+  const string volume_path = base::StringPrintf("/dev/ubi%d_0", volume_num);
+  if (FileExists(volume_path.c_str())) {
+    return true;
+  }
+
+  int exit_code;
+  vector<string> cmd = {
+      "ubiattach",
+      "-m",
+      base::StringPrintf("%d", volume_num),
+      "-d",
+      base::StringPrintf("%d", volume_num)
+  };
+  TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr));
+  TEST_AND_RETURN_FALSE(exit_code == 0);
+
+  cmd = {
+      "ubiblock",
+      "--create",
+      volume_path
+  };
+  TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr));
+  TEST_AND_RETURN_FALSE(exit_code == 0);
+
+  while (timeout > 0 && !FileExists(volume_path.c_str())) {
+    sleep(1);
+    timeout--;
+  }
+
+  return FileExists(volume_path.c_str());
+}
+
+bool MakeTempFile(const string& base_filename_template,
+                  string* filename,
+                  int* fd) {
+  base::FilePath filename_template;
+  TEST_AND_RETURN_FALSE(
+      GetTempName(base_filename_template, &filename_template));
+  DCHECK(filename || fd);
+  vector<char> buf(filename_template.value().size() + 1);
+  memcpy(buf.data(), filename_template.value().data(),
+         filename_template.value().size());
+  buf[filename_template.value().size()] = '\0';
+
+  int mkstemp_fd = mkstemp(buf.data());
+  TEST_AND_RETURN_FALSE_ERRNO(mkstemp_fd >= 0);
+  if (filename) {
+    *filename = buf.data();
+  }
+  if (fd) {
+    *fd = mkstemp_fd;
+  } else {
+    close(mkstemp_fd);
+  }
+  return true;
+}
+
+bool SetBlockDeviceReadOnly(const string& device, bool read_only) {
+  int fd = HANDLE_EINTR(open(device.c_str(), O_RDONLY | O_CLOEXEC));
+  if (fd < 0) {
+    PLOG(ERROR) << "Opening block device " << device;
+    return false;
+  }
+  ScopedFdCloser fd_closer(&fd);
+  // We take no action if not needed.
+  int read_only_flag;
+  int expected_flag = read_only ? 1 : 0;
+  int rc = ioctl(fd, BLKROGET, &read_only_flag);
+  // In case of failure reading the setting we will try to set it anyway.
+  if (rc == 0 && read_only_flag == expected_flag)
+    return true;
+
+  rc = ioctl(fd, BLKROSET, &expected_flag);
+  if (rc != 0) {
+    PLOG(ERROR) << "Marking block device " << device << " as read_only="
+                << expected_flag;
+    return false;
+  }
+  return true;
+}
+
+bool MountFilesystem(const string& device,
+                     const string& mountpoint,
+                     unsigned long mountflags,  // NOLINT(runtime/int)
+                     const string& type,
+                     const string& fs_mount_options) {
+  vector<const char*> fstypes;
+  if (type.empty()) {
+    fstypes = {"ext2", "ext3", "ext4", "squashfs"};
+  } else {
+    fstypes = {type.c_str()};
+  }
+  for (const char* fstype : fstypes) {
+    int rc = mount(device.c_str(), mountpoint.c_str(), fstype, mountflags,
+                   fs_mount_options.c_str());
+    if (rc == 0)
+      return true;
+
+    PLOG(WARNING) << "Unable to mount destination device " << device
+                  << " on " << mountpoint << " as " << fstype;
+  }
+  if (!type.empty()) {
+    LOG(ERROR) << "Unable to mount " << device << " with any supported type";
+  }
+  return false;
+}
+
+bool UnmountFilesystem(const string& mountpoint) {
+  int num_retries = 1;
+  for (;; ++num_retries) {
+    if (umount(mountpoint.c_str()) == 0)
+      return true;
+    if (errno != EBUSY || num_retries >= kUnmountMaxNumOfRetries)
+      break;
+    usleep(kUnmountRetryIntervalInMicroseconds);
+  }
+  if (errno == EINVAL) {
+    LOG(INFO) << "Not a mountpoint: " << mountpoint;
+    return false;
+  }
+  PLOG(WARNING) << "Error unmounting " << mountpoint << " after " << num_retries
+                << " attempts. Lazy unmounting instead, error was";
+  if (umount2(mountpoint.c_str(), MNT_DETACH) != 0) {
+    PLOG(ERROR) << "Lazy unmount failed";
+    return false;
+  }
+  return true;
+}
+
+// Tries to parse the header of an ELF file to obtain a human-readable
+// description of it on the |output| string.
+static bool GetFileFormatELF(const uint8_t* buffer, size_t size,
+                             string* output) {
+  // 0x00: EI_MAG - ELF magic header, 4 bytes.
+  if (size < SELFMAG || memcmp(buffer, ELFMAG, SELFMAG) != 0)
+    return false;
+  *output = "ELF";
+
+  // 0x04: EI_CLASS, 1 byte.
+  if (size < EI_CLASS + 1)
+    return true;
+  switch (buffer[EI_CLASS]) {
+    case ELFCLASS32:
+      *output += " 32-bit";
+      break;
+    case ELFCLASS64:
+      *output += " 64-bit";
+      break;
+    default:
+      *output += " ?-bit";
+  }
+
+  // 0x05: EI_DATA, endianness, 1 byte.
+  if (size < EI_DATA + 1)
+    return true;
+  uint8_t ei_data = buffer[EI_DATA];
+  switch (ei_data) {
+    case ELFDATA2LSB:
+      *output += " little-endian";
+      break;
+    case ELFDATA2MSB:
+      *output += " big-endian";
+      break;
+    default:
+      *output += " ?-endian";
+      // Don't parse anything after the 0x10 offset if endianness is unknown.
+      return true;
+  }
+
+  const Elf32_Ehdr* hdr = reinterpret_cast<const Elf32_Ehdr*>(buffer);
+  // 0x12: e_machine, 2 byte endianness based on ei_data. The position (0x12)
+  // and size is the same for both 32 and 64 bits.
+  if (size < offsetof(Elf32_Ehdr, e_machine) + sizeof(hdr->e_machine))
+    return true;
+  uint16_t e_machine;
+  // Fix endianess regardless of the host endianess.
+  if (ei_data == ELFDATA2LSB)
+    e_machine = le16toh(hdr->e_machine);
+  else
+    e_machine = be16toh(hdr->e_machine);
+
+  switch (e_machine) {
+    case EM_386:
+      *output += " x86";
+      break;
+    case EM_MIPS:
+      *output += " mips";
+      break;
+    case EM_ARM:
+      *output += " arm";
+      break;
+    case EM_X86_64:
+      *output += " x86-64";
+      break;
+    default:
+      *output += " unknown-arch";
+  }
+  return true;
+}
+
+string GetFileFormat(const string& path) {
+  brillo::Blob buffer;
+  if (!ReadFileChunkAndAppend(path, 0, kGetFileFormatMaxHeaderSize, &buffer))
+    return "File not found.";
+
+  string result;
+  if (GetFileFormatELF(buffer.data(), buffer.size(), &result))
+    return result;
+
+  return "data";
+}
+
+int FuzzInt(int value, unsigned int range) {
+  int min = value - range / 2;
+  int max = value + range - range / 2;
+  return base::RandInt(min, max);
+}
+
+string FormatSecs(unsigned secs) {
+  return FormatTimeDelta(TimeDelta::FromSeconds(secs));
+}
+
+string FormatTimeDelta(TimeDelta delta) {
+  string str;
+
+  // Handle negative durations by prefixing with a minus.
+  if (delta.ToInternalValue() < 0) {
+    delta *= -1;
+    str = "-";
+  }
+
+  // Canonicalize into days, hours, minutes, seconds and microseconds.
+  unsigned days = delta.InDays();
+  delta -= TimeDelta::FromDays(days);
+  unsigned hours = delta.InHours();
+  delta -= TimeDelta::FromHours(hours);
+  unsigned mins = delta.InMinutes();
+  delta -= TimeDelta::FromMinutes(mins);
+  unsigned secs = delta.InSeconds();
+  delta -= TimeDelta::FromSeconds(secs);
+  unsigned usecs = delta.InMicroseconds();
+
+  if (days)
+    base::StringAppendF(&str, "%ud", days);
+  if (days || hours)
+    base::StringAppendF(&str, "%uh", hours);
+  if (days || hours || mins)
+    base::StringAppendF(&str, "%um", mins);
+  base::StringAppendF(&str, "%u", secs);
+  if (usecs) {
+    int width = 6;
+    while ((usecs / 10) * 10 == usecs) {
+      usecs /= 10;
+      width--;
+    }
+    base::StringAppendF(&str, ".%0*u", width, usecs);
+  }
+  base::StringAppendF(&str, "s");
+  return str;
+}
+
+string ToString(const Time utc_time) {
+  Time::Exploded exp_time;
+  utc_time.UTCExplode(&exp_time);
+  return base::StringPrintf("%d/%d/%d %d:%02d:%02d GMT",
+                      exp_time.month,
+                      exp_time.day_of_month,
+                      exp_time.year,
+                      exp_time.hour,
+                      exp_time.minute,
+                      exp_time.second);
+}
+
+string ToString(bool b) {
+  return (b ? "true" : "false");
+}
+
+string ToString(DownloadSource source) {
+  switch (source) {
+    case kDownloadSourceHttpsServer: return "HttpsServer";
+    case kDownloadSourceHttpServer:  return "HttpServer";
+    case kDownloadSourceHttpPeer:    return "HttpPeer";
+    case kNumDownloadSources:        return "Unknown";
+    // Don't add a default case to let the compiler warn about newly added
+    // download sources which should be added here.
+  }
+
+  return "Unknown";
+}
+
+string ToString(PayloadType payload_type) {
+  switch (payload_type) {
+    case kPayloadTypeDelta:      return "Delta";
+    case kPayloadTypeFull:       return "Full";
+    case kPayloadTypeForcedFull: return "ForcedFull";
+    case kNumPayloadTypes:       return "Unknown";
+    // Don't add a default case to let the compiler warn about newly added
+    // payload types which should be added here.
+  }
+
+  return "Unknown";
+}
+
+ErrorCode GetBaseErrorCode(ErrorCode code) {
+  // Ignore the higher order bits in the code by applying the mask as
+  // we want the enumerations to be in the small contiguous range
+  // with values less than ErrorCode::kUmaReportedMax.
+  ErrorCode base_code = static_cast<ErrorCode>(
+      static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+  // Make additional adjustments required for UMA and error classification.
+  // TODO(jaysri): Move this logic to UeErrorCode.cc when we fix
+  // chromium-os:34369.
+  if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) {
+    // Since we want to keep the enums to a small value, aggregate all HTTP
+    // errors into this one bucket for UMA and error classification purposes.
+    LOG(INFO) << "Converting error code " << base_code
+#ifdef USE_NESTLABS
+              << " to ErrorCode::kErrorInHTTPResponse";
+#else
+              << " to ErrorCode::kOmahaErrorInHTTPResponse";
+#endif
+    base_code = ErrorCode::kOmahaErrorInHTTPResponse;
+  }
+
+  return base_code;
+}
+
+Time TimeFromStructTimespec(struct timespec *ts) {
+  int64_t us = static_cast<int64_t>(ts->tv_sec) * Time::kMicrosecondsPerSecond +
+      static_cast<int64_t>(ts->tv_nsec) / Time::kNanosecondsPerMicrosecond;
+  return Time::UnixEpoch() + TimeDelta::FromMicroseconds(us);
+}
+
+string StringVectorToString(const vector<string> &vec_str) {
+  string str = "[";
+  for (vector<string>::const_iterator i = vec_str.begin();
+       i != vec_str.end(); ++i) {
+    if (i != vec_str.begin())
+      str += ", ";
+    str += '"';
+    str += *i;
+    str += '"';
+  }
+  str += "]";
+  return str;
+}
+
+string CalculateP2PFileId(const string& payload_hash, size_t payload_size) {
+  string encoded_hash = brillo::data_encoding::Base64Encode(payload_hash);
+  return base::StringPrintf("cros_update_size_%" PRIuS "_hash_%s",
+                            payload_size,
+                            encoded_hash.c_str());
+}
+
+bool DecodeAndStoreBase64String(const string& base64_encoded,
+                                base::FilePath *out_path) {
+  brillo::Blob contents;
+
+  out_path->clear();
+
+  if (base64_encoded.size() == 0) {
+    LOG(ERROR) << "Can't decode empty string.";
+    return false;
+  }
+
+  if (!brillo::data_encoding::Base64Decode(base64_encoded, &contents) ||
+      contents.size() == 0) {
+    LOG(ERROR) << "Error decoding base64.";
+    return false;
+  }
+
+  FILE *file = base::CreateAndOpenTemporaryFile(out_path);
+  if (file == nullptr) {
+    LOG(ERROR) << "Error creating temporary file.";
+    return false;
+  }
+
+  if (fwrite(contents.data(), 1, contents.size(), file) != contents.size()) {
+    PLOG(ERROR) << "Error writing to temporary file.";
+    if (fclose(file) != 0)
+      PLOG(ERROR) << "Error closing temporary file.";
+    if (unlink(out_path->value().c_str()) != 0)
+      PLOG(ERROR) << "Error unlinking temporary file.";
+    out_path->clear();
+    return false;
+  }
+
+  if (fclose(file) != 0) {
+    PLOG(ERROR) << "Error closing temporary file.";
+    out_path->clear();
+    return false;
+  }
+
+  return true;
+}
+
+bool ConvertToOmahaInstallDate(Time time, int *out_num_days) {
+  time_t unix_time = time.ToTimeT();
+  // Output of: date +"%s" --date="Jan 1, 2007 0:00 PST".
+  const time_t kOmahaEpoch = 1167638400;
+  const int64_t kNumSecondsPerWeek = 7*24*3600;
+  const int64_t kNumDaysPerWeek = 7;
+
+  time_t omaha_time = unix_time - kOmahaEpoch;
+
+  if (omaha_time < 0)
+    return false;
+
+  // Note, as per the comment in utils.h we are deliberately not
+  // handling DST correctly.
+
+  int64_t num_weeks_since_omaha_epoch = omaha_time / kNumSecondsPerWeek;
+  *out_num_days = num_weeks_since_omaha_epoch * kNumDaysPerWeek;
+
+  return true;
+}
+
+bool GetMinorVersion(const brillo::KeyValueStore& store,
+                     uint32_t* minor_version) {
+  string result;
+  if (store.GetString("PAYLOAD_MINOR_VERSION", &result)) {
+    if (!base::StringToUint(result, minor_version)) {
+      LOG(ERROR) << "StringToUint failed when parsing delta minor version.";
+      return false;
+    }
+    return true;
+  }
+  return false;
+}
+
+bool IsZlibCompatible(const string& fingerprint) {
+  if (fingerprint.size() != sizeof(kCompatibleZlibFingerprint[0]) - 1) {
+    LOG(ERROR) << "Invalid fingerprint: " << fingerprint;
+    return false;
+  }
+  for (auto& f : kCompatibleZlibFingerprint) {
+    if (base::CompareCaseInsensitiveASCII(fingerprint, f) == 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool ReadExtents(const string& path, const vector<Extent>& extents,
+                 brillo::Blob* out_data, ssize_t out_data_size,
+                 size_t block_size) {
+  brillo::Blob data(out_data_size);
+  ssize_t bytes_read = 0;
+  int fd = open(path.c_str(), O_RDONLY);
+  TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+  ScopedFdCloser fd_closer(&fd);
+
+  for (const Extent& extent : extents) {
+    ssize_t bytes_read_this_iteration = 0;
+    ssize_t bytes = extent.num_blocks() * block_size;
+    TEST_AND_RETURN_FALSE(bytes_read + bytes <= out_data_size);
+    TEST_AND_RETURN_FALSE(utils::PReadAll(fd,
+                                          &data[bytes_read],
+                                          bytes,
+                                          extent.start_block() * block_size,
+                                          &bytes_read_this_iteration));
+    TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes);
+    bytes_read += bytes_read_this_iteration;
+  }
+  TEST_AND_RETURN_FALSE(out_data_size == bytes_read);
+  *out_data = data;
+  return true;
+}
+
+bool GetBootId(string* boot_id) {
+  TEST_AND_RETURN_FALSE(
+      base::ReadFileToString(base::FilePath(kBootIdPath), boot_id));
+  base::TrimWhitespaceASCII(*boot_id, base::TRIM_TRAILING, boot_id);
+  return true;
+}
+
+}  // namespace utils
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common/utils.h b/update_engine/common/utils.h
new file mode 100644
index 0000000..3cffcdd
--- /dev/null
+++ b/update_engine/common/utils.h
@@ -0,0 +1,438 @@
+//
+// 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_COMMON_UTILS_H_
+#define UPDATE_ENGINE_COMMON_UTILS_H_
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/time/time.h>
+#include <brillo/key_value_store.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+namespace utils {
+
+// Converts a struct timespec representing a number of seconds since
+// the Unix epoch to a base::Time. Sub-microsecond time is rounded
+// down.
+base::Time TimeFromStructTimespec(struct timespec *ts);
+
+// Formats |vec_str| as a string of the form ["<elem1>", "<elem2>"].
+// Does no escaping, only use this for presentation in error messages.
+std::string StringVectorToString(const std::vector<std::string> &vec_str);
+
+// Calculates the p2p file id from payload hash and size
+std::string CalculateP2PFileId(const std::string& payload_hash,
+                               size_t payload_size);
+
+// Parse the firmware version from one line of output from the
+// "mosys" command.
+std::string ParseECVersion(std::string input_line);
+
+// Writes the data passed to path. The file at path will be overwritten if it
+// exists. Returns true on success, false otherwise.
+bool WriteFile(const char* path, const void* data, int data_len);
+
+// Calls write() or pwrite() repeatedly until all count bytes at buf are
+// written to fd or an error occurs. Returns true on success.
+bool WriteAll(int fd, const void* buf, size_t count);
+bool PWriteAll(int fd, const void* buf, size_t count, off_t offset);
+
+bool WriteAll(const FileDescriptorPtr& fd, const void* buf, size_t count);
+bool PWriteAll(const FileDescriptorPtr& fd,
+               const void* buf,
+               size_t count,
+               off_t offset);
+
+// Calls read() repeatedly until |count| bytes are read or EOF or EWOULDBLOCK
+// is reached. Returns whether all read() calls succeeded (including EWOULDBLOCK
+// as a success case), sets |eof| to whether the eof was reached and sets
+// |out_bytes_read| to the actual number of bytes read regardless of the return
+// value.
+bool ReadAll(
+    int fd, void* buf, size_t count, size_t* out_bytes_read, bool* eof);
+
+// Calls pread() repeatedly until count bytes are read, or EOF is reached.
+// Returns number of bytes read in *bytes_read. Returns true on success.
+bool PReadAll(int fd, void* buf, size_t count, off_t offset,
+              ssize_t* out_bytes_read);
+
+bool PReadAll(const FileDescriptorPtr& fd, void* buf, size_t count, off_t offset,
+              ssize_t* out_bytes_read);
+
+// Opens |path| for reading and appends its entire content to the container
+// pointed to by |out_p|. Returns true upon successfully reading all of the
+// file's content, false otherwise, in which case the state of the output
+// container is unknown. ReadFileChunk starts reading the file from |offset|; if
+// |size| is not -1, only up to |size| bytes are read in.
+bool ReadFile(const std::string& path, brillo::Blob* out_p);
+bool ReadFile(const std::string& path, std::string* out_p);
+bool ReadFileChunk(const std::string& path, off_t offset, off_t size,
+                   brillo::Blob* out_p);
+
+// Invokes |cmd| in a pipe and appends its stdout to the container pointed to by
+// |out_p|. Returns true upon successfully reading all of the output, false
+// otherwise, in which case the state of the output container is unknown.
+bool ReadPipe(const std::string& cmd, std::string* out_p);
+
+// Returns the size of the block device at the file descriptor fd. If an error
+// occurs, -1 is returned.
+off_t BlockDevSize(int fd);
+
+// Returns the size of the file at path, or the file desciptor fd. If the file
+// is actually a block device, this function will automatically call
+// BlockDevSize. If the file doesn't exist or some error occurrs, -1 is
+// returned.
+off_t FileSize(const std::string& path);
+off_t FileSize(int fd);
+
+std::string ErrnoNumberAsString(int err);
+
+// Returns true if the file exists for sure. Returns false if it doesn't exist,
+// or an error occurs.
+bool FileExists(const char* path);
+
+// Returns true if |path| exists and is a symbolic link.
+bool IsSymlink(const char* path);
+
+// Try attaching UBI |volume_num|. If there is any error executing required
+// commands to attach the volume, this function returns false. This function
+// only returns true if "/dev/ubi%d_0" becomes available in |timeout| seconds.
+bool TryAttachingUbiVolume(int volume_num, int timeout);
+
+// Setup the directory |new_root_temp_dir| to be used as the root directory for
+// temporary files instead of the system's default. If the directory doesn't
+// exists, it will be created when first used.
+// NOTE: The memory pointed by |new_root_temp_dir| must be available until this
+// function is called again with a different value.
+void SetRootTempDir(const char* new_root_temp_dir);
+
+// If |base_filename_template| is neither absolute (starts with "/") nor
+// explicitly relative to the current working directory (starts with "./" or
+// "../"), then it is prepended the system's temporary directory. On success,
+// stores the name of the new temporary file in |filename|. If |fd| is
+// non-null, the file descriptor returned by mkstemp is written to it and
+// kept open; otherwise, it is closed. The template must end with "XXXXXX".
+// Returns true on success.
+bool MakeTempFile(const std::string& base_filename_template,
+                  std::string* filename,
+                  int* fd);
+
+// Splits the partition device name into the block device name and partition
+// number. For example, "/dev/sda3" will be split into {"/dev/sda", 3} and
+// "/dev/mmcblk0p2" into {"/dev/mmcblk0", 2}
+// Returns false when malformed device name is passed in.
+// If both output parameters are omitted (null), can be used
+// just to test the validity of the device name. Note that the function
+// simply checks if the device name looks like a valid device, no other
+// checks are performed (i.e. it doesn't check if the device actually exists).
+bool SplitPartitionName(const std::string& partition_name,
+                        std::string* out_disk_name,
+                        int* out_partition_num);
+
+// Builds a partition device name from the block device name and partition
+// number. For example:
+// {"/dev/sda", 1} => "/dev/sda1"
+// {"/dev/mmcblk2", 12} => "/dev/mmcblk2p12"
+// Returns empty string when invalid parameters are passed in
+std::string MakePartitionName(const std::string& disk_name,
+                              int partition_num);
+
+// Similar to "MakePartitionName" but returns a name that is suitable for
+// mounting. On NAND system we can write to "/dev/ubiX_0", which is what
+// MakePartitionName returns, but we cannot mount that device. To mount, we
+// have to use "/dev/ubiblockX_0" for rootfs. Stateful and OEM partitions are
+// mountable with "/dev/ubiX_0". The input is a partition device such as
+// /dev/sda3. Return empty string on error.
+std::string MakePartitionNameForMount(const std::string& part_name);
+
+// Set the read-only attribute on the block device |device| to the value passed
+// in |read_only|. Return whether the operation succeeded.
+bool SetBlockDeviceReadOnly(const std::string& device, bool read_only);
+
+// Synchronously mount or unmount a filesystem. Return true on success.
+// When mounting, it will attempt to mount the device as the passed filesystem
+// type |type|, with the passed |flags| options. If |type| is empty, "ext2",
+// "ext3", "ext4" and "squashfs" will be tried.
+bool MountFilesystem(const std::string& device,
+                     const std::string& mountpoint,
+                     unsigned long flags,  // NOLINT(runtime/int)
+                     const std::string& type,
+                     const std::string& fs_mount_options);
+bool UnmountFilesystem(const std::string& mountpoint);
+
+// Returns a human-readable string with the file format based on magic constants
+// on the header of the file.
+std::string GetFileFormat(const std::string& path);
+
+// Returns the string representation of the given UTC time.
+// such as "11/14/2011 14:05:30 GMT".
+std::string ToString(const base::Time utc_time);
+
+// Returns true or false depending on the value of b.
+std::string ToString(bool b);
+
+// Returns a string representation of the given enum.
+std::string ToString(DownloadSource source);
+
+// Returns a string representation of the given enum.
+std::string ToString(PayloadType payload_type);
+
+// Fuzzes an integer |value| randomly in the range:
+// [value - range / 2, value + range - range / 2]
+int FuzzInt(int value, unsigned int range);
+
+// Log a string in hex to LOG(INFO). Useful for debugging.
+void HexDumpArray(const uint8_t* const arr, const size_t length);
+inline void HexDumpString(const std::string& str) {
+  HexDumpArray(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+inline void HexDumpVector(const brillo::Blob& vect) {
+  HexDumpArray(vect.data(), vect.size());
+}
+
+template<typename KeyType, typename ValueType>
+bool MapContainsKey(const std::map<KeyType, ValueType>& m, const KeyType& k) {
+  return m.find(k) != m.end();
+}
+template<typename KeyType>
+bool SetContainsKey(const std::set<KeyType>& s, const KeyType& k) {
+  return s.find(k) != s.end();
+}
+
+template<typename T>
+bool VectorContainsValue(const std::vector<T>& vect, const T& value) {
+  return std::find(vect.begin(), vect.end(), value) != vect.end();
+}
+
+template<typename T>
+bool VectorIndexOf(const std::vector<T>& vect, const T& value,
+                   typename std::vector<T>::size_type* out_index) {
+  typename std::vector<T>::const_iterator it = std::find(vect.begin(),
+                                                         vect.end(),
+                                                         value);
+  if (it == vect.end()) {
+    return false;
+  } else {
+    *out_index = it - vect.begin();
+    return true;
+  }
+}
+
+// Converts seconds into human readable notation including days, hours, minutes
+// and seconds. For example, 185 will yield 3m5s, 4300 will yield 1h11m40s, and
+// 360000 will yield 4d4h0m0s.  Zero padding not applied. Seconds are always
+// shown in the result.
+std::string FormatSecs(unsigned secs);
+
+// Converts a TimeDelta into human readable notation including days, hours,
+// minutes, seconds and fractions of a second down to microsecond granularity,
+// as necessary; for example, an output of 5d2h0m15.053s means that the input
+// time was precise to the milliseconds only. Zero padding not applied, except
+// for fractions. Seconds are always shown, but fractions thereof are only shown
+// when applicable. If |delta| is negative, the output will have a leading '-'
+// followed by the absolute duration.
+std::string FormatTimeDelta(base::TimeDelta delta);
+
+// This method transforms the given error code to be suitable for UMA and
+// for error classification purposes by removing the higher order bits and
+// aggregating error codes beyond the enum range, etc. This method is
+// idempotent, i.e. if called with a value previously returned by this method,
+// it'll return the same value again.
+ErrorCode GetBaseErrorCode(ErrorCode code);
+
+// Decodes the data in |base64_encoded| and stores it in a temporary
+// file. Returns false if the given data is empty, not well-formed
+// base64 or if an error occurred. If true is returned, the decoded
+// data is stored in the file returned in |out_path|. The file should
+// be deleted when no longer needed.
+bool DecodeAndStoreBase64String(const std::string& base64_encoded,
+                                base::FilePath *out_path);
+
+// Converts |time| to an Omaha InstallDate which is defined as "the
+// number of PST8PDT calendar weeks since Jan 1st 2007 0:00 PST, times
+// seven" with PST8PDT defined as "Pacific Time" (e.g. UTC-07:00 if
+// daylight savings is observed and UTC-08:00 otherwise.)
+//
+// If the passed in |time| variable is before Monday January 1st 2007
+// 0:00 PST, False is returned and the value returned in
+// |out_num_days| is undefined. Otherwise the number of PST8PDT
+// calendar weeks since that date times seven is returned in
+// |out_num_days| and the function returns True.
+//
+// (NOTE: This function does not currently take daylight savings time
+// into account so the result may up to one hour off. This is because
+// the glibc date and timezone routines depend on the TZ environment
+// variable and changing environment variables is not thread-safe.
+bool ConvertToOmahaInstallDate(base::Time time, int *out_num_days);
+
+// Look for the minor version value in the passed |store| and set
+// |minor_version| to that value. Return whether the value was found and valid.
+bool GetMinorVersion(const brillo::KeyValueStore& store,
+                     uint32_t* minor_version);
+
+// Returns whether zlib |fingerprint| is compatible with zlib we are using.
+bool IsZlibCompatible(const std::string& fingerprint);
+
+// This function reads the specified data in |extents| into |out_data|. The
+// extents are read from the file at |path|. |out_data_size| is the size of
+// |out_data|. Returns false if the number of bytes to read given in
+// |extents| does not equal |out_data_size|.
+bool ReadExtents(const std::string& path, const std::vector<Extent>& extents,
+                 brillo::Blob* out_data, ssize_t out_data_size,
+                 size_t block_size);
+
+// Read the current boot identifier and store it in |boot_id|. This identifier
+// is constants during the same boot of the kernel and is regenerated after
+// reboot. Returns whether it succeeded getting the boot_id.
+bool GetBootId(std::string* boot_id);
+
+}  // namespace utils
+
+
+// Utility class to close a file descriptor
+class ScopedFdCloser {
+ public:
+  explicit ScopedFdCloser(int* fd) : fd_(fd) {}
+  ~ScopedFdCloser() {
+    if (should_close_ && fd_ && (*fd_ >= 0) && !IGNORE_EINTR(close(*fd_)))
+      *fd_ = -1;
+  }
+  void set_should_close(bool should_close) { should_close_ = should_close; }
+ private:
+  int* fd_;
+  bool should_close_ = true;
+  DISALLOW_COPY_AND_ASSIGN(ScopedFdCloser);
+};
+
+// Utility class to delete a file when it goes out of scope.
+class ScopedPathUnlinker {
+ public:
+  explicit ScopedPathUnlinker(const std::string& path)
+      : path_(path),
+        should_remove_(true) {}
+  ~ScopedPathUnlinker() {
+    if (should_remove_ && unlink(path_.c_str()) < 0) {
+      PLOG(ERROR) << "Unable to unlink path " << path_;
+    }
+  }
+  void set_should_remove(bool should_remove) { should_remove_ = should_remove; }
+
+ private:
+  const std::string path_;
+  bool should_remove_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedPathUnlinker);
+};
+
+// A little object to call ActionComplete on the ActionProcessor when
+// it's destructed.
+class ScopedActionCompleter {
+ public:
+  explicit ScopedActionCompleter(ActionProcessor* processor,
+                                 AbstractAction* action)
+      : processor_(processor),
+        action_(action),
+        code_(ErrorCode::kError),
+        should_complete_(true) {}
+  ~ScopedActionCompleter() {
+    if (should_complete_)
+      processor_->ActionComplete(action_, code_);
+  }
+  void set_code(ErrorCode code) { code_ = code; }
+  void set_should_complete(bool should_complete) {
+    should_complete_ = should_complete;
+  }
+  ErrorCode get_code() const { return code_; }
+
+ private:
+  ActionProcessor* processor_;
+  AbstractAction* action_;
+  ErrorCode code_;
+  bool should_complete_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedActionCompleter);
+};
+
+}  // namespace chromeos_update_engine
+
+#define TEST_AND_RETURN_FALSE_ERRNO(_x)                                        \
+  do {                                                                         \
+    bool _success = static_cast<bool>(_x);                                     \
+    if (!_success) {                                                           \
+      std::string _msg =                                                       \
+          chromeos_update_engine::utils::ErrnoNumberAsString(errno);           \
+      LOG(ERROR) << #_x " failed: " << _msg;                                   \
+      return false;                                                            \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN_FALSE(_x)                                              \
+  do {                                                                         \
+    bool _success = static_cast<bool>(_x);                                     \
+    if (!_success) {                                                           \
+      LOG(ERROR) << #_x " failed.";                                            \
+      return false;                                                            \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN_ERRNO(_x)                                              \
+  do {                                                                         \
+    bool _success = static_cast<bool>(_x);                                     \
+    if (!_success) {                                                           \
+      std::string _msg =                                                       \
+          chromeos_update_engine::utils::ErrnoNumberAsString(errno);           \
+      LOG(ERROR) << #_x " failed: " << _msg;                                   \
+      return;                                                                  \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN(_x)                                                    \
+  do {                                                                         \
+    bool _success = static_cast<bool>(_x);                                     \
+    if (!_success) {                                                           \
+      LOG(ERROR) << #_x " failed.";                                            \
+      return;                                                                  \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN_FALSE_ERRCODE(_x)                                      \
+  do {                                                                         \
+    errcode_t _error = (_x);                                                   \
+    if (_error) {                                                              \
+      errno = _error;                                                          \
+      LOG(ERROR) << #_x " failed: " << _error;                                 \
+      return false;                                                            \
+    }                                                                          \
+  } while (0)
+
+#endif  // UPDATE_ENGINE_COMMON_UTILS_H_
diff --git a/update_engine/common/utils_unittest.cc b/update_engine/common/utils_unittest.cc
new file mode 100644
index 0000000..634de01
--- /dev/null
+++ b/update_engine/common/utils_unittest.cc
@@ -0,0 +1,479 @@
+//
+// 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/common/utils.h"
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#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 std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class UtilsTest : public ::testing::Test { };
+
+TEST(UtilsTest, CanParseECVersion) {
+  // Should be able to parse and valid key value line.
+  EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345"));
+  EXPECT_EQ("123456", utils::ParseECVersion(
+      "b=1231a fw_version=123456 a=fasd2"));
+  EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345"));
+  EXPECT_EQ("00VFA616", utils::ParseECVersion(
+      "vendor=\"sam\" fw_version=\"00VFA616\""));
+
+  // For invalid entries, should return the empty string.
+  EXPECT_EQ("", utils::ParseECVersion("b=1231a fw_version a=fasd2"));
+}
+
+TEST(UtilsTest, ReadFileFailure) {
+  brillo::Blob empty;
+  EXPECT_FALSE(utils::ReadFile("/this/doesn't/exist", &empty));
+}
+
+TEST(UtilsTest, ReadFileChunk) {
+  base::FilePath file;
+  EXPECT_TRUE(base::CreateTemporaryFile(&file));
+  ScopedPathUnlinker unlinker(file.value());
+  brillo::Blob data;
+  const size_t kSize = 1024 * 1024;
+  for (size_t i = 0; i < kSize; i++) {
+    data.push_back(i % 255);
+  }
+  EXPECT_TRUE(utils::WriteFile(file.value().c_str(), data.data(), data.size()));
+  brillo::Blob in_data;
+  EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), kSize, 10, &in_data));
+  EXPECT_TRUE(in_data.empty());
+  EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), 0, -1, &in_data));
+  EXPECT_TRUE(data == in_data);
+  in_data.clear();
+  EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), 10, 20, &in_data));
+  EXPECT_TRUE(brillo::Blob(data.begin() + 10, data.begin() + 10 + 20) ==
+              in_data);
+}
+
+TEST(UtilsTest, ErrnoNumberAsStringTest) {
+  EXPECT_EQ("No such file or directory", utils::ErrnoNumberAsString(ENOENT));
+}
+
+TEST(UtilsTest, IsSymlinkTest) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  string temp_file = temp_dir.path().Append("temp-file").value();
+  EXPECT_TRUE(utils::WriteFile(temp_file.c_str(), "", 0));
+  string temp_symlink = temp_dir.path().Append("temp-symlink").value();
+  EXPECT_EQ(0, symlink(temp_file.c_str(), temp_symlink.c_str()));
+  EXPECT_FALSE(utils::IsSymlink(temp_dir.path().value().c_str()));
+  EXPECT_FALSE(utils::IsSymlink(temp_file.c_str()));
+  EXPECT_TRUE(utils::IsSymlink(temp_symlink.c_str()));
+  EXPECT_FALSE(utils::IsSymlink("/non/existent/path"));
+}
+
+TEST(UtilsTest, SplitPartitionNameTest) {
+  string disk;
+  int part_num;
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/sda3", &disk, &part_num));
+  EXPECT_EQ("/dev/sda", disk);
+  EXPECT_EQ(3, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/sdp1234", &disk, &part_num));
+  EXPECT_EQ("/dev/sdp", disk);
+  EXPECT_EQ(1234, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/mmcblk0p3", &disk, &part_num));
+  EXPECT_EQ("/dev/mmcblk0", disk);
+  EXPECT_EQ(3, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/ubiblock3_2", &disk, &part_num));
+  EXPECT_EQ("/dev/ubiblock", disk);
+  EXPECT_EQ(3, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/loop10", &disk, &part_num));
+  EXPECT_EQ("/dev/loop", disk);
+  EXPECT_EQ(10, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/loop28p11", &disk, &part_num));
+  EXPECT_EQ("/dev/loop28", disk);
+  EXPECT_EQ(11, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/loop10_0", &disk, &part_num));
+  EXPECT_EQ("/dev/loop", disk);
+  EXPECT_EQ(10, part_num);
+
+  EXPECT_TRUE(utils::SplitPartitionName("/dev/loop28p11_0", &disk, &part_num));
+  EXPECT_EQ("/dev/loop28", disk);
+  EXPECT_EQ(11, part_num);
+
+  EXPECT_FALSE(utils::SplitPartitionName("/dev/mmcblk0p", &disk, &part_num));
+  EXPECT_FALSE(utils::SplitPartitionName("/dev/sda", &disk, &part_num));
+  EXPECT_FALSE(utils::SplitPartitionName("/dev/foo/bar", &disk, &part_num));
+  EXPECT_FALSE(utils::SplitPartitionName("/", &disk, &part_num));
+  EXPECT_FALSE(utils::SplitPartitionName("", &disk, &part_num));
+}
+
+TEST(UtilsTest, MakePartitionNameTest) {
+  EXPECT_EQ("/dev/sda4", utils::MakePartitionName("/dev/sda", 4));
+  EXPECT_EQ("/dev/sda123", utils::MakePartitionName("/dev/sda", 123));
+  EXPECT_EQ("/dev/mmcblk2", utils::MakePartitionName("/dev/mmcblk", 2));
+  EXPECT_EQ("/dev/mmcblk0p2", utils::MakePartitionName("/dev/mmcblk0", 2));
+  EXPECT_EQ("/dev/loop8", utils::MakePartitionName("/dev/loop", 8));
+  EXPECT_EQ("/dev/loop12p2", utils::MakePartitionName("/dev/loop12", 2));
+  EXPECT_EQ("/dev/ubi5_0", utils::MakePartitionName("/dev/ubiblock", 5));
+  EXPECT_EQ("/dev/mtd4", utils::MakePartitionName("/dev/ubiblock", 4));
+  EXPECT_EQ("/dev/ubi3_0", utils::MakePartitionName("/dev/ubiblock", 3));
+  EXPECT_EQ("/dev/mtd2", utils::MakePartitionName("/dev/ubiblock", 2));
+  EXPECT_EQ("/dev/ubi1_0", utils::MakePartitionName("/dev/ubiblock", 1));
+}
+
+TEST(UtilsTest, MakePartitionNameForMountTest) {
+  EXPECT_EQ("/dev/sda4", utils::MakePartitionNameForMount("/dev/sda4"));
+  EXPECT_EQ("/dev/sda123", utils::MakePartitionNameForMount("/dev/sda123"));
+  EXPECT_EQ("/dev/mmcblk2", utils::MakePartitionNameForMount("/dev/mmcblk2"));
+  EXPECT_EQ("/dev/mmcblk0p2",
+            utils::MakePartitionNameForMount("/dev/mmcblk0p2"));
+  EXPECT_EQ("/dev/loop0", utils::MakePartitionNameForMount("/dev/loop0"));
+  EXPECT_EQ("/dev/loop8", utils::MakePartitionNameForMount("/dev/loop8"));
+  EXPECT_EQ("/dev/loop12p2",
+            utils::MakePartitionNameForMount("/dev/loop12p2"));
+  EXPECT_EQ("/dev/ubiblock5_0",
+            utils::MakePartitionNameForMount("/dev/ubiblock5_0"));
+  EXPECT_EQ("/dev/mtd4",
+            utils::MakePartitionNameForMount("/dev/ubi4_0"));
+  EXPECT_EQ("/dev/ubiblock3_0",
+            utils::MakePartitionNameForMount("/dev/ubiblock3"));
+  EXPECT_EQ("/dev/mtd2", utils::MakePartitionNameForMount("/dev/ubi2"));
+  EXPECT_EQ("/dev/ubi1_0",
+            utils::MakePartitionNameForMount("/dev/ubiblock1"));
+}
+
+TEST(UtilsTest, FuzzIntTest) {
+  static const uint32_t kRanges[] = { 0, 1, 2, 20 };
+  for (uint32_t range : kRanges) {
+    const int kValue = 50;
+    for (int tries = 0; tries < 100; ++tries) {
+      uint32_t value = utils::FuzzInt(kValue, range);
+      EXPECT_GE(value, kValue - range / 2);
+      EXPECT_LE(value, kValue + range - range / 2);
+    }
+  }
+}
+
+namespace {
+void GetFileFormatTester(const string& expected,
+                         const vector<uint8_t>& contents) {
+  test_utils::ScopedTempFile file;
+  ASSERT_TRUE(utils::WriteFile(file.path().c_str(),
+                               reinterpret_cast<const char*>(contents.data()),
+                               contents.size()));
+  EXPECT_EQ(expected, utils::GetFileFormat(file.path()));
+}
+}  // namespace
+
+TEST(UtilsTest, GetFileFormatTest) {
+  EXPECT_EQ("File not found.", utils::GetFileFormat("/path/to/nowhere"));
+  GetFileFormatTester("data", vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8});
+  GetFileFormatTester("ELF", vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46});
+
+  // Real tests from cros_installer on different boards.
+  // ELF 32-bit LSB executable, Intel 80386
+  GetFileFormatTester(
+      "ELF 32-bit little-endian x86",
+      vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                      0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
+                      0x90, 0x83, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00});
+
+  // ELF 32-bit LSB executable, MIPS
+  GetFileFormatTester(
+      "ELF 32-bit little-endian mips",
+      vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                      0x03, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00,
+                      0xc0, 0x12, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00});
+
+  // ELF 32-bit LSB executable, ARM
+  GetFileFormatTester(
+      "ELF 32-bit little-endian arm",
+      vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                      0x02, 0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00,
+                      0x85, 0x8b, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00});
+
+  // ELF 64-bit LSB executable, x86-64
+  GetFileFormatTester(
+      "ELF 64-bit little-endian x86-64",
+      vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
+                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                      0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
+                      0xb0, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00});
+}
+
+TEST(UtilsTest, FormatTimeDeltaTest) {
+  // utils::FormatTimeDelta() is not locale-aware (it's only used for logging
+  // which is not localized) so we only need to test the C locale
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromMilliseconds(100)),
+            "0.1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(0)),
+            "0s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(1)),
+            "1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(59)),
+            "59s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(60)),
+            "1m0s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(61)),
+            "1m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(90)),
+            "1m30s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(1205)),
+            "20m5s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3600)),
+            "1h0m0s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3601)),
+            "1h0m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3661)),
+            "1h1m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(7261)),
+            "2h1m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(86400)),
+            "1d0h0m0s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(86401)),
+            "1d0h0m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(200000)),
+            "2d7h33m20s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(200000) +
+                                   base::TimeDelta::FromMilliseconds(1)),
+            "2d7h33m20.001s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(-1)),
+            "-1s");
+}
+
+TEST(UtilsTest, TimeFromStructTimespecTest) {
+  struct timespec ts;
+
+  // Unix epoch (Thursday 00:00:00 UTC on Jan 1, 1970)
+  ts = (struct timespec) {.tv_sec = 0, .tv_nsec = 0};
+  EXPECT_EQ(base::Time::UnixEpoch(), utils::TimeFromStructTimespec(&ts));
+
+  // 42 ms after the Unix billennium (Sunday 01:46:40 UTC on September 9, 2001)
+  ts = (struct timespec) {.tv_sec = 1000 * 1000 * 1000,
+                          .tv_nsec = 42 * 1000 * 1000};
+  base::Time::Exploded exploded = (base::Time::Exploded) {
+    .year = 2001, .month = 9, .day_of_week = 0, .day_of_month = 9,
+    .hour = 1, .minute = 46, .second = 40, .millisecond = 42};
+  EXPECT_EQ(base::Time::FromUTCExploded(exploded),
+            utils::TimeFromStructTimespec(&ts));
+}
+
+TEST(UtilsTest, DecodeAndStoreBase64String) {
+  base::FilePath path;
+
+  // Ensure we return false on empty strings or invalid base64.
+  EXPECT_FALSE(utils::DecodeAndStoreBase64String("", &path));
+  EXPECT_FALSE(utils::DecodeAndStoreBase64String("not valid base64", &path));
+
+  // Pass known base64 and check that it matches. This string was generated
+  // the following way:
+  //
+  //   $ echo "Update Engine" | base64
+  //   VXBkYXRlIEVuZ2luZQo=
+  EXPECT_TRUE(utils::DecodeAndStoreBase64String("VXBkYXRlIEVuZ2luZQo=",
+                                                &path));
+  ScopedPathUnlinker unlinker(path.value());
+  string expected_contents = "Update Engine\n";
+  string contents;
+  EXPECT_TRUE(utils::ReadFile(path.value(), &contents));
+  EXPECT_EQ(contents, expected_contents);
+  EXPECT_EQ(static_cast<off_t>(expected_contents.size()),
+            utils::FileSize(path.value()));
+}
+
+TEST(UtilsTest, ConvertToOmahaInstallDate) {
+  // The Omaha Epoch starts at Jan 1, 2007 0:00 PST which is a
+  // Monday. In Unix time, this point in time is easily obtained via
+  // the date(1) command like this:
+  //
+  //  $ date +"%s" --date="Jan 1, 2007 0:00 PST"
+  const time_t omaha_epoch = 1167638400;
+  int value;
+
+  // Points in time *on and after* the Omaha epoch should not fail.
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch), &value));
+  EXPECT_GE(value, 0);
+
+  // Anything before the Omaha epoch should fail. We test it for two points.
+  EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch - 1), &value));
+  EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch - 100*24*3600), &value));
+
+  // Check that we jump from 0 to 7 exactly on the one-week mark, e.g.
+  // on Jan 8, 2007 0:00 PST.
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 7*24*3600 - 1), &value));
+  EXPECT_EQ(value, 0);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 7*24*3600), &value));
+  EXPECT_EQ(value, 7);
+
+  // Check a couple of more values.
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 10*24*3600), &value));
+  EXPECT_EQ(value, 7);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 20*24*3600), &value));
+  EXPECT_EQ(value, 14);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 26*24*3600), &value));
+  EXPECT_EQ(value, 21);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 29*24*3600), &value));
+  EXPECT_EQ(value, 28);
+
+  // The date Jun 4, 2007 0:00 PDT is a Monday and is hence a point
+  // where the Omaha InstallDate jumps 7 days. Its unix time is
+  // 1180940400. Notably, this is a point in time where Daylight
+  // Savings Time (DST) was is in effect (e.g. it's PDT, not PST).
+  //
+  // Note that as utils::ConvertToOmahaInstallDate() _deliberately_
+  // ignores DST (as it's hard to implement in a thread-safe way using
+  // glibc, see comments in utils.h) we have to fudge by the DST
+  // offset which is one hour. Conveniently, if the function were
+  // someday modified to be DST aware, this test would have to be
+  // modified as well.
+  const time_t dst_time = 1180940400;  // Jun 4, 2007 0:00 PDT.
+  const time_t fudge = 3600;
+  int value1, value2;
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(dst_time + fudge - 1), &value1));
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(dst_time + fudge), &value2));
+  EXPECT_EQ(value1, value2 - 7);
+}
+
+TEST(UtilsTest, GetMinorVersion) {
+  // Test GetMinorVersion by verifying that it parses the conf file and returns
+  // the correct value.
+  uint32_t minor_version;
+
+  brillo::KeyValueStore store;
+  EXPECT_FALSE(utils::GetMinorVersion(store, &minor_version));
+
+  EXPECT_TRUE(store.LoadFromString("PAYLOAD_MINOR_VERSION=one-two-three\n"));
+  EXPECT_FALSE(utils::GetMinorVersion(store, &minor_version));
+
+  EXPECT_TRUE(store.LoadFromString("PAYLOAD_MINOR_VERSION=123\n"));
+  EXPECT_TRUE(utils::GetMinorVersion(store, &minor_version));
+  EXPECT_EQ(123U, minor_version);
+}
+
+static bool BoolMacroTestHelper() {
+  int i = 1;
+  unsigned int ui = 1;
+  bool b = 1;
+  std::unique_ptr<char> cptr(new char);
+
+  TEST_AND_RETURN_FALSE(i);
+  TEST_AND_RETURN_FALSE(ui);
+  TEST_AND_RETURN_FALSE(b);
+  TEST_AND_RETURN_FALSE(cptr);
+
+  TEST_AND_RETURN_FALSE_ERRNO(i);
+  TEST_AND_RETURN_FALSE_ERRNO(ui);
+  TEST_AND_RETURN_FALSE_ERRNO(b);
+  TEST_AND_RETURN_FALSE_ERRNO(cptr);
+
+  return true;
+}
+
+static void VoidMacroTestHelper(bool* ret) {
+  int i = 1;
+  unsigned int ui = 1;
+  bool b = 1;
+  std::unique_ptr<char> cptr(new char);
+
+  *ret = false;
+
+  TEST_AND_RETURN(i);
+  TEST_AND_RETURN(ui);
+  TEST_AND_RETURN(b);
+  TEST_AND_RETURN(cptr);
+
+  TEST_AND_RETURN_ERRNO(i);
+  TEST_AND_RETURN_ERRNO(ui);
+  TEST_AND_RETURN_ERRNO(b);
+  TEST_AND_RETURN_ERRNO(cptr);
+
+  *ret = true;
+}
+
+TEST(UtilsTest, TestMacros) {
+  bool void_test = false;
+  VoidMacroTestHelper(&void_test);
+  EXPECT_TRUE(void_test);
+
+  EXPECT_TRUE(BoolMacroTestHelper());
+}
+
+TEST(UtilsTest, RunAsRootUnmountFilesystemFailureTest) {
+  EXPECT_FALSE(utils::UnmountFilesystem("/path/to/non-existing-dir"));
+}
+
+TEST(UtilsTest, RunAsRootUnmountFilesystemBusyFailureTest) {
+  string tmp_image;
+  EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &tmp_image, nullptr));
+  ScopedPathUnlinker tmp_image_unlinker(tmp_image);
+
+  EXPECT_TRUE(base::CopyFile(
+      test_utils::GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"),
+      base::FilePath(tmp_image)));
+
+  base::ScopedTempDir mnt_dir;
+  EXPECT_TRUE(mnt_dir.CreateUniqueTempDir());
+
+  string loop_dev;
+  test_utils::ScopedLoopbackDeviceBinder loop_binder(
+      tmp_image, true, &loop_dev);
+
+  // This is the actual test part. While we hold a file descriptor open for the
+  // mounted filesystem, umount should still succeed.
+  EXPECT_TRUE(utils::MountFilesystem(
+      loop_dev, mnt_dir.path().value(), MS_RDONLY, "ext4", ""));
+  string target_file = mnt_dir.path().Append("empty-file").value();
+  int fd = HANDLE_EINTR(open(target_file.c_str(), O_RDONLY));
+  EXPECT_GE(fd, 0);
+  EXPECT_TRUE(utils::UnmountFilesystem(mnt_dir.path().value()));
+  IGNORE_EINTR(close(fd));
+  // The filesystem was already unmounted so this call should fail.
+  EXPECT_FALSE(utils::UnmountFilesystem(mnt_dir.path().value()));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common_service.cc b/update_engine/common_service.cc
new file mode 100644
index 0000000..33cfabc
--- /dev/null
+++ b/update_engine/common_service.cc
@@ -0,0 +1,392 @@
+//
+// 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/common_service.h"
+
+#include <set>
+#include <string>
+
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.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/connection_manager_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/omaha_utils.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+#if USE_NESTLABS
+#include "update_engine/update_attempter_nestlabs.h"
+#else
+#include "update_engine/update_attempter.h"
+#endif
+
+using base::StringPrintf;
+using brillo::ErrorPtr;
+using brillo::string_utils::ToString;
+using std::set;
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Log and set the error on the passed ErrorPtr.
+void LogAndSetError(ErrorPtr* error,
+                    const tracked_objects::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";
+#ifdef USE_NESTLABS
+const char* const UpdateEngineService::kErrorFailed =
+    "com.nestlabs.UpdateEngine.Error.Failed";
+#else
+const char* const UpdateEngineService::kErrorFailed =
+    "org.chromium.UpdateEngine.Error.Failed";
+#endif
+
+UpdateEngineService::UpdateEngineService(SystemState* system_state)
+    : system_state_(system_state) {
+}
+
+// UpdateEngineInterfaceInterface methods implementation.
+
+bool UpdateEngineService::AttemptUpdate(ErrorPtr* /* error */,
+                                        const string& in_app_version,
+                                        const string& in_omaha_url,
+                                        int32_t in_flags_as_int) {
+  AttemptUpdateFlags flags = static_cast<AttemptUpdateFlags>(in_flags_as_int);
+  bool interactive = !(flags & kAttemptUpdateFlagNonInteractive);
+
+  LOG(INFO) << "Attempt update: app_version=\"" << in_app_version << "\" "
+            << "omaha_url=\"" << in_omaha_url << "\" "
+            << "flags=0x" << std::hex << flags << " "
+            << "interactive=" << (interactive ? "yes" : "no");
+  system_state_->update_attempter()->CheckForUpdate(
+      in_app_version, in_omaha_url, interactive);
+  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::GetStatus(ErrorPtr* error,
+                                    int64_t* out_last_checked_time,
+                                    double* out_progress,
+                                    string* out_current_operation,
+                                    string* out_new_version,
+                                    int64_t* out_new_size) {
+  if (!system_state_->update_attempter()->GetStatus(out_last_checked_time,
+                                                    out_progress,
+                                                    out_current_operation,
+                                                    out_new_version,
+                                                    out_new_size)) {
+    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;
+  }
+  // Update the weave state because updated the target channel.
+  system_state_->update_attempter()->BroadcastChannel();
+  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,
+                                        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) {
+  set<string> allowed_types;
+  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();
+    }
+  }
+
+  // Check if this setting is allowed by the device policy.
+  if (device_policy &&
+      device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+    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->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::GetUpdateOverCellularPermission(ErrorPtr* /* error */,
+                                                          bool* out_allowed) {
+  ConnectionManagerInterface* cm = system_state_->connection_manager();
+
+  // The device_policy is loaded in a lazy way before an update check and is
+  // used to determine if an update is allowed over cellular. Load the device
+  // policy now from the libbrillo cache if it wasn't already loaded.
+  if (!system_state_->device_policy()) {
+    UpdateAttempter* update_attempter = system_state_->update_attempter();
+    if (update_attempter)
+      update_attempter->RefreshDevicePolicy();
+  }
+
+  // Return the current setting based on the same logic used while checking for
+  // updates. A log message could be printed as the result of this test.
+  LOG(INFO) << "Checking if updates over cellular networks are allowed:";
+  *out_allowed = cm->IsUpdateAllowedOver(ConnectionType::kCellular,
+                                         ConnectionTethering::kUnknown);
+  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_->payload_state()->GetAttemptErrorCode();
+  *out_last_attempt_error = static_cast<int>(error_code);
+  return true;
+}
+
+bool UpdateEngineService::GetEolStatus(ErrorPtr* error,
+                                       int32_t* out_eol_status) {
+  PrefsInterface* prefs = system_state_->prefs();
+
+  string str_eol_status;
+  if (prefs->Exists(kPrefsOmahaEolStatus) &&
+      !prefs->GetString(kPrefsOmahaEolStatus, &str_eol_status)) {
+    LogAndSetError(error, FROM_HERE, "Error getting the end-of-life status.");
+    return false;
+  }
+
+  // StringToEolStatus will return kSupported for invalid values.
+  *out_eol_status = static_cast<int32_t>(StringToEolStatus(str_eol_status));
+  return true;
+}
+
+#ifdef USE_NESTLABS
+bool UpdateEngineService::MarkBootSuccessful(brillo::ErrorPtr* /* error */) {
+  system_state_->update_attempter()->UpdateBootFlags();
+  return true;
+}
+#endif // USE_NESTLABS
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/common_service.h b/update_engine/common_service.h
new file mode 100644
index 0000000..d83a29e
--- /dev/null
+++ b/update_engine/common_service.h
@@ -0,0 +1,157 @@
+//
+// 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_COMMON_SERVICE_H_
+#define UPDATE_ENGINE_COMMON_SERVICE_H_
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/memory/ref_counted.h>
+#include <brillo/errors/error.h>
+
+#include "update_engine/system_state.h"
+
+namespace chromeos_update_engine {
+
+class UpdateEngineService {
+ public:
+  // Flags used in the AttemptUpdateWithFlags() D-Bus method.
+  typedef enum {
+    kAttemptUpdateFlagNonInteractive = (1<<0)
+  } AttemptUpdateFlags;
+
+  // 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;
+
+  bool AttemptUpdate(brillo::ErrorPtr* error,
+                     const std::string& in_app_version,
+                     const std::string& in_omaha_url,
+                     int32_t in_flags_as_int);
+
+  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);
+
+  // 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,
+                 int64_t* out_last_checked_time,
+                 double* out_progress,
+                 std::string* out_current_operation,
+                 std::string* out_new_version,
+                 int64_t* out_new_size);
+
+  // 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, 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);
+
+  // 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);
+
+  // Returns the current end-of-life status of the device. This value is updated
+  // on every update check and persisted on disk across reboots.
+  bool GetEolStatus(brillo::ErrorPtr* error, int32_t* out_eol_status);
+
+#ifdef USE_NESTLABS
+  bool MarkBootSuccessful(brillo::ErrorPtr* error);
+#endif // USE_NESTLABS
+
+ private:
+  SystemState* system_state_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_SERVICE_H_
diff --git a/update_engine/common_service_unittest.cc b/update_engine/common_service_unittest.cc
new file mode 100644
index 0000000..0a7bfc3
--- /dev/null
+++ b/update_engine/common_service_unittest.cc
@@ -0,0 +1,151 @@
+//
+// 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/common_service.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+#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/fake_system_state.h"
+#include "update_engine/omaha_utils.h"
+
+using std::string;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::_;
+
+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", false /* interactive */));
+  // The update is non-interactive when we pass the non-interactive flag.
+  EXPECT_TRUE(common_service_.AttemptUpdate(
+      &error_, "app_ver", "url",
+      UpdateEngineService::kAttemptUpdateFlagNonInteractive));
+  EXPECT_EQ(nullptr, error_);
+}
+
+// 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(SetArgumentPointee<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));
+}
+
+TEST_F(UpdateEngineServiceTest, GetEolStatusTest) {
+  FakePrefs fake_prefs;
+  fake_system_state_.set_prefs(&fake_prefs);
+  // The default value should be "supported".
+  int32_t eol_status = static_cast<int32_t>(EolStatus::kEol);
+  EXPECT_TRUE(common_service_.GetEolStatus(&error_, &eol_status));
+  EXPECT_EQ(nullptr, error_);
+  EXPECT_EQ(EolStatus::kSupported, static_cast<EolStatus>(eol_status));
+
+  fake_prefs.SetString(kPrefsOmahaEolStatus, "security-only");
+  EXPECT_TRUE(common_service_.GetEolStatus(&error_, &eol_status));
+  EXPECT_EQ(nullptr, error_);
+  EXPECT_EQ(EolStatus::kSecurityOnly, static_cast<EolStatus>(eol_status));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/connection_manager.cc b/update_engine/connection_manager.cc
new file mode 100644
index 0000000..f72d9e8
--- /dev/null
+++ b/update_engine/connection_manager.cc
@@ -0,0 +1,205 @@
+//
+// 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/connection_manager.h"
+
+#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/prefs.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/connection_utils.h"
+#include "update_engine/shill_proxy.h"
+#include "update_engine/system_state.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));
+}
+}
+
+ConnectionManager::ConnectionManager(ShillProxyInterface* shill_proxy,
+                                     SystemState* system_state)
+    : shill_proxy_(shill_proxy), system_state_(system_state) {}
+
+bool ConnectionManager::IsUpdateAllowedOver(
+    ConnectionType type, ConnectionTethering tethering) const {
+  switch (type) {
+    case ConnectionType::kBluetooth:
+      return false;
+
+    case ConnectionType::kCellular: {
+      set<string> allowed_types;
+      const policy::DevicePolicy* device_policy =
+          system_state_->device_policy();
+
+      // A device_policy is loaded in a lazy way right before an update check,
+      // so the device_policy should be already loaded at this point. If it's
+      // not, return a safe value for this setting.
+      if (!device_policy) {
+        LOG(INFO) << "Disabling updates over cellular networks as there's no "
+                     "device policy loaded yet.";
+        return false;
+      }
+
+      if (device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+        // The update setting is enforced by the device policy.
+
+        if (!ContainsKey(allowed_types, shill::kTypeCellular)) {
+          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;
+      } else {
+        // There's no update setting in the device policy, using the local user
+        // setting.
+        PrefsInterface* prefs = system_state_->prefs();
+
+        if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) {
+          LOG(INFO) << "Disabling updates over cellular connection as there's "
+                       "no device policy setting nor user preference present.";
+          return false;
+        }
+
+        bool stored_value;
+        if (!prefs->GetBoolean(kPrefsUpdateOverCellularPermission,
+                               &stored_value)) {
+          return false;
+        }
+
+        if (!stored_value) {
+          LOG(INFO) << "Disabling updates over cellular connection per user "
+                       "setting.";
+          return false;
+        }
+        LOG(INFO) << "Allowing updates over cellular per user setting.";
+        return true;
+      }
+    }
+
+    default:
+      if (tethering == ConnectionTethering::kConfirmed) {
+        // Treat this connection as if it is a cellular connection.
+        LOG(INFO) << "Current connection is confirmed tethered, using Cellular "
+                     "setting.";
+        return IsUpdateAllowedOver(ConnectionType::kCellular,
+                                   ConnectionTethering::kUnknown);
+      }
+      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() == "/")
+    return false;
+  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/update_engine/connection_manager.h b/update_engine/connection_manager.h
new file mode 100644
index 0000000..e5a9d49
--- /dev/null
+++ b/update_engine/connection_manager.h
@@ -0,0 +1,67 @@
+//
+// 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_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_CONNECTION_MANAGER_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <dbus/object_path.h>
+
+#include "update_engine/connection_manager_interface.h"
+#include "update_engine/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;
+
+ private:
+  // Returns (via out_path) the default network path, or empty string 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_CONNECTION_MANAGER_H_
diff --git a/update_engine/connection_manager_android.cc b/update_engine/connection_manager_android.cc
new file mode 100644
index 0000000..2dd824a
--- /dev/null
+++ b/update_engine/connection_manager_android.cc
@@ -0,0 +1,38 @@
+//
+// 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/connection_manager_android.h"
+
+namespace chromeos_update_engine {
+
+namespace connection_manager {
+std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager(
+    SystemState* system_state) {
+  return std::unique_ptr<ConnectionManagerInterface>(
+      new ConnectionManagerAndroid());
+}
+}
+
+bool ConnectionManagerAndroid::GetConnectionProperties(
+    ConnectionType* out_type, ConnectionTethering* out_tethering) {
+  return false;
+}
+bool ConnectionManagerAndroid::IsUpdateAllowedOver(
+    ConnectionType type, ConnectionTethering tethering) const {
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/connection_manager_android.h b/update_engine/connection_manager_android.h
new file mode 100644
index 0000000..0cd5e73
--- /dev/null
+++ b/update_engine/connection_manager_android.h
@@ -0,0 +1,43 @@
+//
+// 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_CONNECTION_MANAGER_ANDROID_H_
+#define UPDATE_ENGINE_CONNECTION_MANAGER_ANDROID_H_
+
+#include <base/macros.h>
+
+#include "update_engine/connection_manager_interface.h"
+
+namespace chromeos_update_engine {
+
+// TODO(senj): Remove this class and use ShillProvider from the UpdateManager.
+class ConnectionManagerAndroid : public ConnectionManagerInterface {
+ public:
+  ConnectionManagerAndroid() = default;
+  ~ConnectionManagerAndroid() override = default;
+
+  // ConnectionManagerInterface overrides.
+  bool GetConnectionProperties(ConnectionType* out_type,
+                               ConnectionTethering* out_tethering) override;
+  bool IsUpdateAllowedOver(ConnectionType type,
+                           ConnectionTethering tethering) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(ConnectionManagerAndroid);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CONNECTION_MANAGER_ANDROID_H_
diff --git a/update_engine/connection_manager_interface.h b/update_engine/connection_manager_interface.h
new file mode 100644
index 0000000..df8eb4b
--- /dev/null
+++ b/update_engine/connection_manager_interface.h
@@ -0,0 +1,64 @@
+//
+// 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_CONNECTION_MANAGER_INTERFACE_H_
+#define UPDATE_ENGINE_CONNECTION_MANAGER_INTERFACE_H_
+
+#include <memory>
+
+#include <base/macros.h>
+
+#include "update_engine/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;
+
+ 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 chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CONNECTION_MANAGER_INTERFACE_H_
diff --git a/update_engine/connection_manager_unittest.cc b/update_engine/connection_manager_unittest.cc
new file mode 100644
index 0000000..0bb5547
--- /dev/null
+++ b/update_engine/connection_manager_unittest.cc
@@ -0,0 +1,396 @@
+//
+// 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/connection_manager.h"
+
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <brillo/any.h>
+#include <brillo/make_unique_ptr.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/fake_shill_proxy.h"
+#include "update_engine/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::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+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 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());
+}
+
+TEST_F(ConnectionManagerTest, SimpleTest) {
+  TestWithServiceType(shill::kTypeEthernet, nullptr, ConnectionType::kEthernet);
+  TestWithServiceType(shill::kTypeWifi, nullptr, ConnectionType::kWifi);
+  TestWithServiceType(shill::kTypeWimax, nullptr, ConnectionType::kWimax);
+  TestWithServiceType(
+      shill::kTypeBluetooth, nullptr, ConnectionType::kBluetooth);
+  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);
+  TestWithServiceType(
+      shill::kTypeVPN, shill::kTypeWimax, ConnectionType::kWimax);
+}
+
+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, 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, AllowUpdatesOverWimaxTest) {
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWimax,
+                                        ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverBluetoothTest) {
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kBluetooth,
+                                         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, the others are ignored (see Bluetooth for example).
+  set<string> allowed_set;
+  allowed_set.insert(StringForConnectionType(ConnectionType::kCellular));
+  allowed_set.insert(StringForConnectionType(ConnectionType::kBluetooth));
+
+  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));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWimax,
+                                        ConnectionTethering::kUnknown));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kBluetooth,
+                                         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, BlockUpdatesOverCellularByDefaultTest) {
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                         ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverTetheredNetworkByDefaultTest) {
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
+                                         ConnectionTethering::kConfirmed));
+  EXPECT_FALSE(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));
+  allowed_set.insert(StringForConnectionType(ConnectionType::kWimax));
+
+  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, BlockUpdatesOver3GIfErrorInPolicyFetchTest) {
+  policy::MockDevicePolicy allow_3g_policy;
+
+  fake_system_state_.set_device_policy(&allow_3g_policy);
+
+  set<string> allowed_set;
+  allowed_set.insert(StringForConnectionType(ConnectionType::kCellular));
+
+  // Return false for GetAllowedConnectionTypesForUpdate and see
+  // that updates are still blocked for 3G despite the value being in
+  // the string set above.
+  EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+      .Times(1)
+      .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(false)));
+
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                         ConnectionTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, UseUserPrefForUpdatesOverCellularIfNoPolicyTest) {
+  policy::MockDevicePolicy no_policy;
+  testing::NiceMock<MockPrefs>* prefs = fake_system_state_.mock_prefs();
+
+  fake_system_state_.set_device_policy(&no_policy);
+
+  // No setting enforced by the device policy, user prefs should be used.
+  EXPECT_CALL(no_policy, GetAllowedConnectionTypesForUpdate(_))
+      .Times(3)
+      .WillRepeatedly(Return(false));
+
+  // No user pref: block.
+  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+      .Times(1)
+      .WillOnce(Return(false));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                         ConnectionTethering::kUnknown));
+
+  // Allow per user pref.
+  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+      .Times(1)
+      .WillOnce(DoAll(SetArgPointee<1>(true), Return(true)));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                        ConnectionTethering::kUnknown));
+
+  // Block per user pref.
+  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+      .Times(1)
+      .WillOnce(DoAll(SetArgPointee<1>(false), Return(true)));
+  EXPECT_FALSE(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::kTypeWimax,
+               StringForConnectionType(ConnectionType::kWimax));
+  EXPECT_STREQ(shill::kTypeBluetooth,
+               StringForConnectionType(ConnectionType::kBluetooth));
+  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/update_engine/connection_utils.cc b/update_engine/connection_utils.cc
new file mode 100644
index 0000000..9b6b526
--- /dev/null
+++ b/update_engine/connection_utils.cc
@@ -0,0 +1,70 @@
+//
+// 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/connection_utils.h"
+
+#include <shill/dbus-constants.h>
+
+namespace chromeos_update_engine {
+namespace connection_utils {
+
+ConnectionType ParseConnectionType(const std::string& type_str) {
+  if (type_str == shill::kTypeEthernet) {
+    return ConnectionType::kEthernet;
+  } else if (type_str == shill::kTypeWifi) {
+    return ConnectionType::kWifi;
+  } else if (type_str == shill::kTypeWimax) {
+    return ConnectionType::kWimax;
+  } else if (type_str == shill::kTypeBluetooth) {
+    return ConnectionType::kBluetooth;
+  } else if (type_str == shill::kTypeCellular) {
+    return ConnectionType::kCellular;
+  }
+  return ConnectionType::kUnknown;
+}
+
+ConnectionTethering ParseConnectionTethering(const std::string& tethering_str) {
+  if (tethering_str == shill::kTetheringNotDetectedState) {
+    return ConnectionTethering::kNotDetected;
+  } else if (tethering_str == shill::kTetheringSuspectedState) {
+    return ConnectionTethering::kSuspected;
+  } else if (tethering_str == shill::kTetheringConfirmedState) {
+    return ConnectionTethering::kConfirmed;
+  }
+  return ConnectionTethering::kUnknown;
+}
+
+const char* StringForConnectionType(ConnectionType type) {
+  switch (type) {
+    case ConnectionType::kEthernet:
+      return shill::kTypeEthernet;
+    case ConnectionType::kWifi:
+      return shill::kTypeWifi;
+    case ConnectionType::kWimax:
+      return shill::kTypeWimax;
+    case ConnectionType::kBluetooth:
+      return shill::kTypeBluetooth;
+    case ConnectionType::kCellular:
+      return shill::kTypeCellular;
+    case ConnectionType::kUnknown:
+      return "Unknown";
+  }
+  return "Unknown";
+}
+
+}  // namespace connection_utils
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/connection_utils.h b/update_engine/connection_utils.h
new file mode 100644
index 0000000..e385517
--- /dev/null
+++ b/update_engine/connection_utils.h
@@ -0,0 +1,51 @@
+//
+// 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_CONNECTION_UTILS_H_
+#define UPDATE_ENGINE_CONNECTION_UTILS_H_
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+enum class ConnectionType {
+  kEthernet,
+  kWifi,
+  kWimax,
+  kBluetooth,
+  kCellular,
+  kUnknown
+};
+
+enum class ConnectionTethering {
+  kNotDetected,
+  kSuspected,
+  kConfirmed,
+  kUnknown,
+};
+
+namespace connection_utils {
+// Helper methods for converting shill strings into symbolic values.
+ConnectionType ParseConnectionType(const std::string& type_str);
+ConnectionTethering ParseConnectionTethering(const std::string& tethering_str);
+
+// Returns the string representation corresponding to the given connection type.
+const char* StringForConnectionType(ConnectionType type);
+}  // namespace connection_utils
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CONNECTION_UTILS_H_
diff --git a/update_engine/daemon.cc b/update_engine/daemon.cc
new file mode 100644
index 0000000..4155243
--- /dev/null
+++ b/update_engine/daemon.cc
@@ -0,0 +1,117 @@
+//
+// 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/daemon.h"
+
+#include <sysexits.h>
+
+#include <base/bind.h>
+#include <base/location.h>
+#if USE_WEAVE || USE_BINDER
+#include <binderwrapper/binder_wrapper.h>
+#endif  // USE_WEAVE || USE_BINDER
+
+#if USE_OMAHA
+#include "update_engine/real_system_state.h"
+#else  // !USE_OMAHA
+#include "update_engine/daemon_state_android.h"
+#endif  // USE_OMAHA
+
+namespace chromeos_update_engine {
+
+int UpdateEngineDaemon::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;
+
+#if USE_WEAVE || USE_BINDER
+  android::BinderWrapper::Create();
+  binder_watcher_.Init();
+#endif  // USE_WEAVE || USE_BINDER
+
+#if USE_OMAHA
+  // 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.";
+#else  // !USE_OMAHA
+  DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid();
+  daemon_state_.reset(daemon_state_android);
+  LOG_IF(ERROR, !daemon_state_android->Initialize())
+      << "Failed to initialize system state.";
+#endif  // USE_OMAHA
+
+#if USE_BINDER
+  // Create the Binder Service.
+#if USE_OMAHA
+  binder_service_ = new BinderUpdateEngineBrilloService{real_system_state};
+#else  // !USE_OMAHA
+  binder_service_ = new BinderUpdateEngineAndroidService{
+      daemon_state_android->service_delegate()};
+#endif  // USE_OMAHA
+  auto binder_wrapper = android::BinderWrapper::Get();
+  if (!binder_wrapper->RegisterService(binder_service_->ServiceName(),
+                                       binder_service_)) {
+    LOG(ERROR) << "Failed to register binder service.";
+  }
+
+  daemon_state_->AddObserver(binder_service_.get());
+#endif  // USE_BINDER
+
+#if USE_DBUS
+  // Create the DBus service.
+  dbus_adaptor_.reset(new UpdateEngineAdaptor(real_system_state));
+  daemon_state_->AddObserver(dbus_adaptor_.get());
+
+  dbus_adaptor_->RegisterAsync(base::Bind(&UpdateEngineDaemon::OnDBusRegistered,
+                                          base::Unretained(this)));
+  LOG(INFO) << "Waiting for DBus object to be registered.";
+#else  // !USE_DBUS
+  daemon_state_->StartUpdater();
+#endif  // USE_DBUS
+  return EX_OK;
+}
+
+#if USE_DBUS
+void UpdateEngineDaemon::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();
+}
+#endif  // USE_DBUS
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/daemon.h b/update_engine/daemon.h
new file mode 100644
index 0000000..15351b4
--- /dev/null
+++ b/update_engine/daemon.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_DAEMON_H_
+#define UPDATE_ENGINE_DAEMON_H_
+
+#include <memory>
+#include <string>
+
+#if USE_WEAVE || USE_BINDER
+#include <brillo/binder_watcher.h>
+#endif  // USE_WEAVE || USE_BINDER
+#include <brillo/daemons/daemon.h>
+
+#if USE_BINDER
+#if USE_OMAHA
+#include "update_engine/binder_service_brillo.h"
+#else  // !USE_OMAHA
+#include "update_engine/binder_service_android.h"
+#endif  // USE_OMAHA
+#endif  // USE_BINDER
+#include "update_engine/common/subprocess.h"
+#include "update_engine/daemon_state_interface.h"
+#if USE_DBUS
+#ifdef USE_NESTLABS
+#include "update_engine/dbus_service_nestlabs.h"
+#else
+#include "update_engine/dbus_service.h"
+#endif
+#endif  // USE_DBUS
+
+namespace chromeos_update_engine {
+
+class UpdateEngineDaemon : public brillo::Daemon {
+ public:
+  UpdateEngineDaemon() = default;
+
+ protected:
+  int OnInit() override;
+
+ private:
+#if USE_DBUS
+  // 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_;
+#endif  // USE_DBUS
+
+  // 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_;
+
+#if USE_WEAVE || USE_BINDER
+  brillo::BinderWatcher binder_watcher_;
+#endif  // USE_WEAVE || USE_BINDER
+
+#if USE_BINDER
+#if USE_OMAHA
+  android::sp<BinderUpdateEngineBrilloService> binder_service_;
+#else  // !USE_OMAHA
+  android::sp<BinderUpdateEngineAndroidService> binder_service_;
+#endif  // USE_OMAHA
+#endif  // USE_BINDER
+
+  // The daemon state with all the required daemon classes for the configured
+  // platform.
+  std::unique_ptr<DaemonStateInterface> daemon_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateEngineDaemon);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DAEMON_H_
diff --git a/update_engine/daemon_state_android.cc b/update_engine/daemon_state_android.cc
new file mode 100644
index 0000000..0960b1a
--- /dev/null
+++ b/update_engine/daemon_state_android.cc
@@ -0,0 +1,92 @@
+//
+// 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/daemon_state_android.h"
+
+#include <base/logging.h>
+
+#include "update_engine/common/boot_control.h"
+#include "update_engine/common/boot_control_stub.h"
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/update_attempter_android.h"
+
+namespace chromeos_update_engine {
+
+bool DaemonStateAndroid::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_.reset(new BootControlStub());
+  }
+
+  hardware_ = hardware::CreateHardware();
+  if (!hardware_) {
+    LOG(ERROR) << "Error intializing the HardwareInterface.";
+    return false;
+  }
+
+  LOG_IF(INFO, !hardware_->IsNormalBootMode()) << "Booted in dev mode.";
+  LOG_IF(INFO, !hardware_->IsOfficialBuild()) << "Booted non-official build.";
+
+  // Initialize 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 = new Prefs();
+  prefs_.reset(prefs);
+  if (!prefs->Init(non_volatile_path.Append(kPrefsSubDirectory))) {
+    LOG(ERROR) << "Failed to initialize preferences.";
+    return false;
+  }
+
+  // The CertificateChecker singleton is used by the update attempter.
+  certificate_checker_.reset(
+      new CertificateChecker(prefs_.get(), &openssl_wrapper_));
+  certificate_checker_->Init();
+
+  // Initialize the UpdateAttempter before the UpdateManager.
+  update_attempter_.reset(new UpdateAttempterAndroid(
+      this, prefs_.get(), boot_control_.get(), hardware_.get()));
+
+  return true;
+}
+
+bool DaemonStateAndroid::StartUpdater() {
+  // The DaemonState in Android is a passive daemon. It will only start applying
+  // an update when instructed to do so from the exposed binder API.
+  update_attempter_->Init();
+  return true;
+}
+
+void DaemonStateAndroid::AddObserver(ServiceObserverInterface* observer) {
+  service_observers_.insert(observer);
+}
+
+void DaemonStateAndroid::RemoveObserver(ServiceObserverInterface* observer) {
+  service_observers_.erase(observer);
+}
+
+ServiceDelegateAndroidInterface* DaemonStateAndroid::service_delegate() {
+  return update_attempter_.get();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/daemon_state_android.h b/update_engine/daemon_state_android.h
new file mode 100644
index 0000000..928a14e
--- /dev/null
+++ b/update_engine/daemon_state_android.h
@@ -0,0 +1,76 @@
+//
+// 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_DAEMON_STATE_ANDROID_H_
+#define UPDATE_ENGINE_DAEMON_STATE_ANDROID_H_
+
+#include <memory>
+#include <set>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/daemon_state_interface.h"
+#include "update_engine/service_delegate_android_interface.h"
+#include "update_engine/service_observer_interface.h"
+#include "update_engine/update_attempter_android.h"
+
+namespace chromeos_update_engine {
+
+class DaemonStateAndroid : public DaemonStateInterface {
+ public:
+  DaemonStateAndroid() = default;
+  ~DaemonStateAndroid() override = default;
+
+  bool Initialize();
+
+  // DaemonStateInterface overrides.
+  bool StartUpdater() override;
+  void AddObserver(ServiceObserverInterface* observer) override;
+  void RemoveObserver(ServiceObserverInterface* observer) override;
+
+  const std::set<ServiceObserverInterface*>& service_observers() override {
+    return service_observers_;
+  }
+
+  // Return a pointer to the service delegate.
+  ServiceDelegateAndroidInterface* service_delegate();
+
+ protected:
+  std::set<ServiceObserverInterface*> service_observers_;
+
+  // Interface for the boot control functions.
+  std::unique_ptr<BootControlInterface> boot_control_;
+
+  // Interface for the hardware functions.
+  std::unique_ptr<HardwareInterface> hardware_;
+
+  // Interface for persisted store.
+  std::unique_ptr<PrefsInterface> prefs_;
+
+  // The main class handling the updates.
+  std::unique_ptr<UpdateAttempterAndroid> update_attempter_;
+
+  // OpenSSLWrapper and CertificateChecker used for checking changes in SSL
+  // certificates.
+  OpenSSLWrapper openssl_wrapper_;
+  std::unique_ptr<CertificateChecker> certificate_checker_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DAEMON_STATE_ANDROID_H_
diff --git a/update_engine/daemon_state_interface.h b/update_engine/daemon_state_interface.h
new file mode 100644
index 0000000..2356816
--- /dev/null
+++ b/update_engine/daemon_state_interface.h
@@ -0,0 +1,49 @@
+//
+// 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_DAEMON_STATE_INTERFACE_H_
+#define UPDATE_ENGINE_DAEMON_STATE_INTERFACE_H_
+
+#include "update_engine/service_observer_interface.h"
+
+#include <memory>
+#include <set>
+
+namespace chromeos_update_engine {
+
+class DaemonStateInterface {
+ public:
+  virtual ~DaemonStateInterface() = default;
+
+  // Start the daemon loop. Should be called only once to start the daemon's
+  // main functionality.
+  virtual bool StartUpdater() = 0;
+
+  // Add and remove an observer. All the registered observers will be called
+  // whenever there's a new status to update.
+  virtual void AddObserver(ServiceObserverInterface* observer) = 0;
+  virtual void RemoveObserver(ServiceObserverInterface* observer) = 0;
+
+  // Return the set of current observers.
+  virtual const std::set<ServiceObserverInterface*>& service_observers() = 0;
+
+ protected:
+  DaemonStateInterface() = default;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DAEMON_STATE_INTERFACE_H_
diff --git a/update_engine/dbus-constants-nestlabs.h b/update_engine/dbus-constants-nestlabs.h
new file mode 100644
index 0000000..04bc658
--- /dev/null
+++ b/update_engine/dbus-constants-nestlabs.h
@@ -0,0 +1,56 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYSTEM_API_DBUS_UPDATE_ENGINE_DBUS_CONSTANTS_NESTLABS_H_
+#define SYSTEM_API_DBUS_UPDATE_ENGINE_DBUS_CONSTANTS_NESTLABS_H_
+
+// Original location for a upstream version of this file is
+// external/cros/system_api/dbus/update_engine/dbus-constants.h
+// This is a customized copy for Nestlabs implementation of UpdateAttempter
+
+namespace update_engine {
+const char kUpdateEngineInterface[] = "com.nestlabs.UpdateEngineInterface";
+const char kUpdateEngineServicePath[] = "/com/nestlabs/UpdateEngine";
+const char kUpdateEngineServiceName[] = "com.nestlabs.UpdateEngine";
+
+// Generic UpdateEngine D-Bus error.
+const char kUpdateEngineServiceErrorFailed[] =
+    "com.nestlabs.UpdateEngine.Error.Failed";
+
+// Methods.
+const char kAttemptUpdate[] = "AttemptUpdate";
+const char kGetLastAttemptError[] = "GetLastAttemptError";
+const char kGetStatus[] = "GetStatus";
+const char kRebootIfNeeded[] = "RebootIfNeeded";
+const char kSetChannel[] = "SetChannel";
+const char kGetChannel[] = "GetChannel";
+const char kAttemptRollback[] = "AttemptRollback";
+const char kCanRollback[] = "CanRollback";
+
+// Signals.
+const char kStatusUpdate[] = "StatusUpdate";
+
+// Flags used in the AttemptUpdateWithFlags() D-Bus method.
+typedef enum {
+  kAttemptUpdateFlagNonInteractive = (1 << 0)
+} AttemptUpdateFlags;
+
+// Operations contained in StatusUpdate signals.
+const char kUpdateStatusIdle[] = "UPDATE_STATUS_IDLE";
+const char kUpdateStatusCheckingForUpdate[] =
+    "UPDATE_STATUS_CHECKING_FOR_UPDATE";
+const char kUpdateStatusUpdateAvailable[] = "UPDATE_STATUS_UPDATE_AVAILABLE";
+const char kUpdateStatusDownloading[] = "UPDATE_STATUS_DOWNLOADING";
+const char kUpdateStatusVerifying[] = "UPDATE_STATUS_VERIFYING";
+const char kUpdateStatusFinalizing[] = "UPDATE_STATUS_FINALIZING";
+const char kUpdateStatusUpdatedNeedReboot[] =
+    "UPDATE_STATUS_UPDATED_NEED_REBOOT";
+const char kUpdateStatusReportingErrorEvent[] =
+    "UPDATE_STATUS_REPORTING_ERROR_EVENT";
+const char kUpdateStatusAttemptingRollback[] =
+    "UPDATE_STATUS_ATTEMPTING_ROLLBACK";
+const char kUpdateStatusDisabled[] = "UPDATE_STATUS_DISABLED";
+}  // namespace update_engine
+
+#endif  // SYSTEM_API_DBUS_UPDATE_ENGINE_DBUS_CONSTANTS_NESTLABS_H_
diff --git a/update_engine/dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml b/update_engine/dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml
new file mode 100644
index 0000000..190e3d4
--- /dev/null
+++ b/update_engine/dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<node name="/com/nestlabs/UpdateEngine">
+  <interface name="com.nestlabs.UpdateEngineInterface">
+    <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+                value="update_engine_service" />
+    <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+                value="update_engine_client" />
+    <method name="AttemptUpdate">
+      <arg type="s" name="app_version" direction="in" />
+      <arg type="s" name="url" direction="in" />
+    </method>
+    <method name="AttemptRollback">
+      <arg type="b" name="powerwash" direction="in" />
+    </method>
+    <method name="CanRollback">
+      <arg type="b" name="can_rollback" direction="out" />
+    </method>
+    <method name="ResetStatus">
+    </method>
+    <method name="GetStatus">
+      <arg type="x" name="last_checked_time" direction="out" />
+      <arg type="d" name="progress" direction="out" />
+      <arg type="s" name="current_operation" direction="out" />
+      <arg type="s" name="new_version" direction="out" />
+      <arg type="x" name="new_size" direction="out" />
+    </method>
+    <method name="RebootIfNeeded">
+    </method>
+    <method name="GetDurationSinceUpdate">
+      <arg type="x" name="usec_wallclock" direction="out" />
+    </method>
+    <signal name="StatusUpdate">
+      <arg type="x" name="last_checked_time" />
+      <arg type="d" name="progress" />
+      <arg type="s" name="current_operation" />
+      <arg type="s" name="new_version" />
+      <arg type="x" name="new_size" />
+    </signal>
+    <method name="GetPrevVersion">
+      <arg type="s" name="prev_version" direction="out" />
+    </method>
+    <method name="GetRollbackPartition">
+      <arg type="s" name="rollback_partition_name" direction="out" />
+    </method>
+    <method name="GetLastAttemptError">
+      <arg type="i" name="last_attempt_error" direction="out" />
+    </method>
+    <method name="MarkBootSuccessful" />
+  </interface>
+</node>
diff --git a/update_engine/dbus_bindings/dbus-service-config-nestlabs.json b/update_engine/dbus_bindings/dbus-service-config-nestlabs.json
new file mode 100644
index 0000000..f69a372
--- /dev/null
+++ b/update_engine/dbus_bindings/dbus-service-config-nestlabs.json
@@ -0,0 +1,3 @@
+{
+  "service_name": "com.nestlabs.UpdateEngine"
+}
diff --git a/update_engine/dbus_bindings/dbus-service-config.json b/update_engine/dbus_bindings/dbus-service-config.json
new file mode 100644
index 0000000..fdae3ba
--- /dev/null
+++ b/update_engine/dbus_bindings/dbus-service-config.json
@@ -0,0 +1,3 @@
+{
+  "service_name": "org.chromium.UpdateEngine"
+}
diff --git a/update_engine/dbus_bindings/org.chromium.LibCrosService.dbus-xml b/update_engine/dbus_bindings/org.chromium.LibCrosService.dbus-xml
new file mode 100644
index 0000000..2da1929
--- /dev/null
+++ b/update_engine/dbus_bindings/org.chromium.LibCrosService.dbus-xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/org/chromium/LibCrosService"
+      xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+  <interface name="org.chromium.LibCrosServiceInterface">
+    <method name="ResolveNetworkProxy">
+      <arg name="source_url" type="s" direction="in" />
+      <arg name="signal_interface" type="s" direction="in" />
+      <arg name="signal_name" type="s" direction="in" />
+      <annotation name="org.chromium.DBus.Method.Kind" value="simple" />
+    </method>
+    <method name="GetKioskAppRequiredPlatformVersion">
+      <arg name="required_platform_version" type="s" direction="out" />
+    </method>
+  </interface>
+  <interface name="org.chromium.UpdateEngineLibcrosProxyResolvedInterface">
+    <signal name="ProxyResolved">
+      <arg name="source_url" type="s" direction="out" />
+      <arg name="proxy_info" type="s" direction="out" />
+      <arg name="error_message" type="s" direction="out" />
+    </signal>
+  </interface>
+</node>
diff --git a/update_engine/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml b/update_engine/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
new file mode 100644
index 0000000..848f775
--- /dev/null
+++ b/update_engine/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<node name="/org/chromium/UpdateEngine">
+  <interface name="org.chromium.UpdateEngineInterface">
+    <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+                value="update_engine_service" />
+    <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+                value="update_engine_client" />
+    <method name="AttemptUpdate">
+      <arg type="s" name="app_version" direction="in" />
+      <arg type="s" name="omaha_url" direction="in" />
+    </method>
+    <!-- TODO(zeuthen,chromium:286399): Rename to AttemptUpdate and
+         update Chrome and other users of the AttemptUpdate() method
+         in lockstep.
+    -->
+    <method name="AttemptUpdateWithFlags">
+      <arg type="s" name="app_version" direction="in" />
+      <arg type="s" name="omaha_url" direction="in" />
+      <!-- See AttemptUpdateFlags enum in update_engine/dbus-constants.h. -->
+      <arg type="i" name="flags" direction="in" />
+    </method>
+    <method name="AttemptRollback">
+      <arg type="b" name="powerwash" direction="in" />
+    </method>
+    <method name="CanRollback">
+      <arg type="b" name="can_rollback" direction="out" />
+    </method>
+    <method name="ResetStatus">
+    </method>
+    <method name="GetStatus">
+      <arg type="x" name="last_checked_time" direction="out" />
+      <arg type="d" name="progress" direction="out" />
+      <arg type="s" name="current_operation" direction="out" />
+      <arg type="s" name="new_version" direction="out" />
+      <arg type="x" name="new_size" direction="out" />
+    </method>
+    <method name="RebootIfNeeded">
+    </method>
+    <method name="SetChannel">
+      <arg type="s" name="target_channel" direction="in" />
+      <arg type="b" name="is_powerwash_allowed" direction="in" />
+    </method>
+    <method name="GetChannel">
+      <arg type="b" name="get_current_channel" direction="in" />
+      <arg type="s" name="channel" direction="out" />
+    </method>
+    <method name="SetCohortHint">
+      <arg type="s" name="cohort_hint" direction="in" />
+    </method>
+    <method name="GetCohortHint">
+      <arg type="s" name="cohort_hint" direction="out" />
+    </method>
+    <method name="SetP2PUpdatePermission">
+      <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+        value="update_engine_service_set_p2p_update_permission" />
+      <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+        value="update_engine_client_set_p2p_update_permission" />
+      <arg type="b" name="enabled" direction="in" />
+    </method>
+    <method name="GetP2PUpdatePermission">
+      <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+        value="update_engine_service_get_p2p_update_permission" />
+      <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+        value="update_engine_client_get_p2p_update_permission" />
+      <arg type="b" name="enabled" direction="out" />
+    </method>
+    <method name="SetUpdateOverCellularPermission">
+      <arg type="b" name="allowed" direction="in" />
+    </method>
+    <method name="GetUpdateOverCellularPermission">
+      <arg type="b" name="allowed" direction="out" />
+    </method>
+    <method name="GetDurationSinceUpdate">
+      <arg type="x" name="usec_wallclock" direction="out" />
+    </method>
+    <signal name="StatusUpdate">
+      <arg type="x" name="last_checked_time" />
+      <arg type="d" name="progress" />
+      <arg type="s" name="current_operation" />
+      <arg type="s" name="new_version" />
+      <arg type="x" name="new_size" />
+    </signal>
+    <method name="GetPrevVersion">
+      <arg type="s" name="prev_version" direction="out" />
+    </method>
+    <method name="GetRollbackPartition">
+      <arg type="s" name="rollback_partition_name" direction="out" />
+    </method>
+    <method name="GetLastAttemptError">
+      <arg type="i" name="last_attempt_error" direction="out" />
+    </method>
+    <method name="GetEolStatus">
+      <arg type="i" name="eol_status" direction="out" />
+    </method>
+  </interface>
+</node>
diff --git a/update_engine/dbus_connection.cc b/update_engine/dbus_connection.cc
new file mode 100644
index 0000000..cf17ec9
--- /dev/null
+++ b/update_engine/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/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/update_engine/dbus_connection.h b/update_engine/dbus_connection.h
new file mode 100644
index 0000000..c3205ba
--- /dev/null
+++ b/update_engine/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_DBUS_CONNECTION_H_
+#define UPDATE_ENGINE_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_DBUS_CONNECTION_H_
diff --git a/update_engine/dbus_service.cc b/update_engine/dbus_service.cc
new file mode 100644
index 0000000..0a7ad5b
--- /dev/null
+++ b/update_engine/dbus_service.cc
@@ -0,0 +1,185 @@
+//
+// 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/dbus_service.h"
+
+#include "update_engine/dbus-constants.h"
+#include "update_engine/dbus_connection.h"
+#include "update_engine/update_status_utils.h"
+
+namespace chromeos_update_engine {
+
+using brillo::ErrorPtr;
+using chromeos_update_engine::UpdateEngineService;
+using std::string;
+
+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);
+
+  return common_->AttemptUpdate(
+      error, in_app_version, in_omaha_url,
+      interactive ? 0 : UpdateEngineService::kAttemptUpdateFlagNonInteractive);
+}
+
+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::GetStatus(ErrorPtr* error,
+                                        int64_t* out_last_checked_time,
+                                        double* out_progress,
+                                        string* out_current_operation,
+                                        string* out_new_version,
+                                        int64_t* out_new_size) {
+  return common_->GetStatus(error,
+                            out_last_checked_time,
+                            out_progress,
+                            out_current_operation,
+                            out_new_version,
+                            out_new_size);
+}
+
+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::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);
+}
+
+bool DBusUpdateEngineService::GetEolStatus(ErrorPtr* error,
+                                           int32_t* out_eol_status) {
+  return common_->GetEolStatus(error, out_eol_status);
+}
+
+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(int64_t last_checked_time,
+                                           double progress,
+                                           update_engine::UpdateStatus status,
+                                           const string& new_version,
+                                           int64_t new_size) {
+  const string str_status = UpdateStatusToString(status);
+  SendStatusUpdateSignal(
+      last_checked_time, progress, str_status, new_version, new_size);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/dbus_service.h b/update_engine/dbus_service.h
new file mode 100644
index 0000000..d18aa16
--- /dev/null
+++ b/update_engine/dbus_service.h
@@ -0,0 +1,193 @@
+//
+// 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_DBUS_SERVICE_H_
+#define UPDATE_ENGINE_DBUS_SERVICE_H_
+
+#include <inttypes.h>
+
+#include <memory>
+#include <string>
+
+#include <base/memory/ref_counted.h>
+#include <brillo/errors/error.h>
+
+#include "update_engine/common_service.h"
+#include "update_engine/service_observer_interface.h"
+#if USE_NESTLABS
+#include "update_engine/update_attempter_nestlabs.h"
+#else
+#include "update_engine/update_attempter.h"
+#endif
+
+#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 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;
+
+  // 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,
+                 int64_t* out_last_checked_time,
+                 double* out_progress,
+                 std::string* out_current_operation,
+                 std::string* out_new_version,
+                 int64_t* out_new_size) 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;
+
+  // 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;
+
+  // Returns the current end-of-life status of the device in |out_eol_status|.
+  bool GetEolStatus(brillo::ErrorPtr* error, int32_t* out_eol_status) 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:
+  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(int64_t last_checked_time,
+                        double progress,
+                        update_engine::UpdateStatus status,
+                        const std::string& new_version,
+                        int64_t new_size) override;
+
+  void SendPayloadApplicationComplete(ErrorCode error_code) override {}
+
+  // Channel tracking changes are ignored.
+  void SendChannelChangeUpdate(const std::string& tracking_channel) override {}
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  DBusUpdateEngineService dbus_service_;
+  brillo::dbus_utils::DBusObject dbus_object_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DBUS_SERVICE_H_
diff --git a/update_engine/dbus_service_nestlabs.cc b/update_engine/dbus_service_nestlabs.cc
new file mode 100644
index 0000000..6833322
--- /dev/null
+++ b/update_engine/dbus_service_nestlabs.cc
@@ -0,0 +1,127 @@
+//
+// 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/dbus_service_nestlabs.h"
+
+#include "update_engine/dbus-constants-nestlabs.h"
+#include "update_engine/dbus_connection.h"
+#include "update_engine/update_status_utils.h"
+
+namespace chromeos_update_engine {
+
+using brillo::ErrorPtr;
+using chromeos_update_engine::UpdateEngineService;
+using std::string;
+
+DBusUpdateEngineService::DBusUpdateEngineService(SystemState* system_state)
+    : common_(new UpdateEngineService{system_state}) {
+}
+
+// com::nestlabs::UpdateEngineInterfaceInterface methods implementation.
+
+bool DBusUpdateEngineService::AttemptUpdate(ErrorPtr* error,
+                                            const string& in_app_version,
+                                            const string& in_url) {
+  return common_->AttemptUpdate(
+      error, in_app_version, in_url, false);
+}
+
+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::GetStatus(ErrorPtr* error,
+                                        int64_t* out_last_checked_time,
+                                        double* out_progress,
+                                        string* out_current_operation,
+                                        string* out_new_version,
+                                        int64_t* out_new_size) {
+  return common_->GetStatus(error,
+                            out_last_checked_time,
+                            out_progress,
+                            out_current_operation,
+                            out_new_version,
+                            out_new_size);
+}
+
+bool DBusUpdateEngineService::RebootIfNeeded(ErrorPtr* error) {
+  return common_->RebootIfNeeded(error);
+}
+
+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);
+}
+
+bool DBusUpdateEngineService::MarkBootSuccessful(brillo::ErrorPtr* error) {
+  return common_->MarkBootSuccessful(error);
+}
+
+UpdateEngineAdaptor::UpdateEngineAdaptor(SystemState* system_state)
+    : com::nestlabs::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(int64_t last_checked_time,
+                                           double progress,
+                                           update_engine::UpdateStatus status,
+                                           const string& new_version,
+                                           int64_t new_size) {
+  const string str_status = UpdateStatusToString(status);
+  SendStatusUpdateSignal(
+      last_checked_time, progress, str_status, new_version, new_size);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/dbus_service_nestlabs.h b/update_engine/dbus_service_nestlabs.h
new file mode 100644
index 0000000..0a6e6cb
--- /dev/null
+++ b/update_engine/dbus_service_nestlabs.h
@@ -0,0 +1,138 @@
+//
+// 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_DBUS_SERVICE_H_
+#define UPDATE_ENGINE_DBUS_SERVICE_H_
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/memory/ref_counted.h>
+#include <brillo/errors/error.h>
+
+#include "update_engine/common_service.h"
+#include "update_engine/service_observer_interface.h"
+#if USE_NESTLABS
+#include "update_engine/update_attempter_nestlabs.h"
+#else
+#include "update_engine/update_attempter.h"
+#endif
+
+#include "dbus_bindings/com.nestlabs.UpdateEngineInterface.h"
+
+namespace chromeos_update_engine {
+
+class DBusUpdateEngineService
+    : public com::nestlabs::UpdateEngineInterfaceInterface {
+ public:
+  explicit DBusUpdateEngineService(SystemState* system_state);
+  virtual ~DBusUpdateEngineService() = default;
+
+  // Implementation of com::nestlabs::UpdateEngineInterfaceInterface.
+  bool AttemptUpdate(brillo::ErrorPtr* error,
+                     const std::string& in_app_version,
+                     const std::string& in_url) 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;
+
+  // 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,
+                 int64_t* out_last_checked_time,
+                 double* out_progress,
+                 std::string* out_current_operation,
+                 std::string* out_new_version,
+                 int64_t* out_new_size) override;
+
+  // Reboots the device if an update is applied and a reboot is required.
+  bool RebootIfNeeded(brillo::ErrorPtr* error) 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;
+
+  bool MarkBootSuccessful(brillo::ErrorPtr* 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 com::nestlabs::UpdateEngineInterfaceAdaptor,
+                            public ServiceObserverInterface {
+ public:
+  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(int64_t last_checked_time,
+                        double progress,
+                        update_engine::UpdateStatus status,
+                        const std::string& new_version,
+                        int64_t new_size) override;
+
+  void SendPayloadApplicationComplete(ErrorCode error_code) override {}
+
+  // Channel tracking changes are ignored.
+  void SendChannelChangeUpdate(const std::string& tracking_channel) override {}
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  DBusUpdateEngineService dbus_service_;
+  brillo::dbus_utils::DBusObject dbus_object_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DBUS_SERVICE_H_
diff --git a/update_engine/dbus_test_utils.h b/update_engine/dbus_test_utils.h
new file mode 100644
index 0000000..b3748ce
--- /dev/null
+++ b/update_engine/dbus_test_utils.h
@@ -0,0 +1,89 @@
+//
+// 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_DBUS_TEST_UTILS_H_
+#define UPDATE_ENGINE_DBUS_TEST_UTILS_H_
+
+#include <set>
+#include <string>
+
+#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),                                                \
+                Register##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(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;
+    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_DBUS_TEST_UTILS_H_
diff --git a/update_engine/fake_file_writer.h b/update_engine/fake_file_writer.h
new file mode 100644
index 0000000..43b71c7
--- /dev/null
+++ b/update_engine/fake_file_writer.h
@@ -0,0 +1,76 @@
+//
+// Copyright (C) 2009 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_FAKE_FILE_WRITER_H_
+#define UPDATE_ENGINE_FAKE_FILE_WRITER_H_
+
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/file_writer.h"
+
+// FakeFileWriter is an implementation of FileWriter. It will succeed
+// calls to Open(), Close(), but not do any work. All calls to Write()
+// will append the passed data to an internal vector.
+
+namespace chromeos_update_engine {
+
+class FakeFileWriter : public FileWriter {
+ public:
+  FakeFileWriter() : was_opened_(false), was_closed_(false) {}
+
+  virtual int Open(const char* path, int flags, mode_t mode) {
+    CHECK(!was_opened_);
+    CHECK(!was_closed_);
+    was_opened_ = true;
+    return 0;
+  }
+
+  virtual ssize_t Write(const void* bytes, size_t count) {
+    CHECK(was_opened_);
+    CHECK(!was_closed_);
+    const char* char_bytes = reinterpret_cast<const char*>(bytes);
+    bytes_.insert(bytes_.end(), char_bytes, char_bytes + count);
+    return count;
+  }
+
+  virtual int Close() {
+    CHECK(was_opened_);
+    CHECK(!was_closed_);
+    was_closed_ = true;
+    return 0;
+  }
+
+  const brillo::Blob& bytes() {
+    return bytes_;
+  }
+
+ private:
+  // The internal store of all bytes that have been written
+  brillo::Blob bytes_;
+
+  // These are just to ensure FileWriter methods are called properly.
+  bool was_opened_;
+  bool was_closed_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeFileWriter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_FILE_WRITER_H_
diff --git a/update_engine/fake_p2p_manager.h b/update_engine/fake_p2p_manager.h
new file mode 100644
index 0000000..a8cf4ea
--- /dev/null
+++ b/update_engine/fake_p2p_manager.h
@@ -0,0 +1,130 @@
+//
+// 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_FAKE_P2P_MANAGER_H_
+#define UPDATE_ENGINE_FAKE_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/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_FAKE_P2P_MANAGER_H_
diff --git a/update_engine/fake_p2p_manager_configuration.h b/update_engine/fake_p2p_manager_configuration.h
new file mode 100644
index 0000000..1bc1dc8
--- /dev/null
+++ b/update_engine/fake_p2p_manager_configuration.h
@@ -0,0 +1,106 @@
+//
+// 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_FAKE_P2P_MANAGER_CONFIGURATION_H_
+#define UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_
+
+#include "update_engine/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_.path();
+  }
+
+  // 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_FAKE_P2P_MANAGER_CONFIGURATION_H_
diff --git a/update_engine/fake_shill_proxy.cc b/update_engine/fake_shill_proxy.cc
new file mode 100644
index 0000000..17698cd
--- /dev/null
+++ b/update_engine/fake_shill_proxy.cc
@@ -0,0 +1,47 @@
+//
+// 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/fake_shill_proxy.h"
+
+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/update_engine/fake_shill_proxy.h b/update_engine/fake_shill_proxy.h
new file mode 100644
index 0000000..ae17eaa
--- /dev/null
+++ b/update_engine/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_FAKE_SHILL_PROXY_H_
+#define UPDATE_ENGINE_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/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_FAKE_SHILL_PROXY_H_
diff --git a/update_engine/fake_system_state.cc b/update_engine/fake_system_state.cc
new file mode 100644
index 0000000..d51f775
--- /dev/null
+++ b/update_engine/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/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, nullptr),
+      mock_request_params_(this),
+      fake_update_manager_(&fake_clock_),
+      clock_(&fake_clock_),
+      connection_manager_(&mock_connection_manager_),
+      hardware_(&fake_hardware_),
+      metrics_lib_(&mock_metrics_lib_),
+      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/update_engine/fake_system_state.h b/update_engine/fake_system_state.h
new file mode 100644
index 0000000..030cb07
--- /dev/null
+++ b/update_engine/fake_system_state.h
@@ -0,0 +1,273 @@
+//
+// 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_FAKE_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_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_prefs.h"
+#include "update_engine/mock_connection_manager.h"
+#include "update_engine/mock_omaha_request_params.h"
+#include "update_engine/mock_p2p_manager.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/mock_power_manager.h"
+#include "update_engine/mock_update_attempter.h"
+#include "update_engine/system_state.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 MetricsLibraryInterface* metrics_lib() override {
+    return metrics_lib_;
+  }
+
+  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 WeaveServiceInterface* weave_service() override { return nullptr; }
+
+  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 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_lib(MetricsLibraryInterface* metrics_lib) {
+    metrics_lib_ = metrics_lib ? metrics_lib : &mock_metrics_lib_;
+  }
+
+  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;
+  }
+
+  // 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<MetricsLibraryMock>* mock_metrics_lib() {
+    CHECK(metrics_lib_ == &mock_metrics_lib_);
+    return &mock_metrics_lib_;
+  }
+
+  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<MetricsLibraryMock> mock_metrics_lib_;
+  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_;
+  MetricsLibraryInterface* metrics_lib_;
+  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_};
+
+  // Other object pointers (not preinitialized).
+  const policy::DevicePolicy* device_policy_;
+
+  // Other data members.
+  bool fake_system_rebooted_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_
diff --git a/update_engine/generate_pc_file.sh b/update_engine/generate_pc_file.sh
new file mode 100755
index 0000000..ab101f4
--- /dev/null
+++ b/update_engine/generate_pc_file.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+#
+# 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.
+#
+
+set -e
+
+OUT=$1
+shift
+PC_IN=$1
+shift
+INCLUDE_DIR=$1
+shift
+
+sed \
+  -e "s|@INCLUDE_DIR@|${INCLUDE_DIR}|g" \
+  "${PC_IN}.pc.in" > "${OUT}/${PC_IN}.pc"
diff --git a/update_engine/hardware_android.cc b/update_engine/hardware_android.cc
new file mode 100644
index 0000000..653ccf9
--- /dev/null
+++ b/update_engine/hardware_android.cc
@@ -0,0 +1,185 @@
+//
+// 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/hardware_android.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+
+#include <bootloader.h>
+
+#include <base/files/file_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <cutils/properties.h>
+
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/utils_android.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The powerwash arguments passed to recovery. Arguments are separated by \n.
+const char kAndroidRecoveryPowerwashCommand[] =
+    "recovery\n"
+    "--wipe_data\n"
+    "--reason=wipe_data_from_ota\n";
+
+// Write a recovery command line |message| to the BCB. The arguments to recovery
+// must be separated by '\n'. An empty string will erase the BCB.
+bool WriteBootloaderRecoveryMessage(const string& message) {
+  base::FilePath misc_device;
+  if (!utils::DeviceForMountPoint("/misc", &misc_device))
+    return false;
+
+  // Setup a bootloader_message with just the command and recovery fields set.
+  bootloader_message boot = {};
+  if (!message.empty()) {
+    strncpy(boot.command, "boot-recovery", sizeof(boot.command) - 1);
+    memcpy(boot.recovery,
+           message.data(),
+           std::min(message.size(), sizeof(boot.recovery) - 1));
+  }
+
+  int fd =
+      HANDLE_EINTR(open(misc_device.value().c_str(), O_WRONLY | O_SYNC, 0600));
+  if (fd < 0) {
+    PLOG(ERROR) << "Opening misc";
+    return false;
+  }
+  ScopedFdCloser fd_closer(&fd);
+  // We only re-write the first part of the bootloader_message, up to and
+  // including the recovery message.
+  size_t boot_size =
+      offsetof(bootloader_message, recovery) + sizeof(boot.recovery);
+  if (!utils::WriteAll(fd, &boot, boot_size)) {
+    PLOG(ERROR) << "Writing recovery command to misc";
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+namespace hardware {
+
+// Factory defined in hardware.h.
+std::unique_ptr<HardwareInterface> CreateHardware() {
+  return brillo::make_unique_ptr(new HardwareAndroid());
+}
+
+}  // namespace hardware
+
+// In Android there are normally three kinds of builds: eng, userdebug and user.
+// These builds target respectively a developer build, a debuggable version of
+// the final product and the pristine final product the end user will run.
+// Apart from the ro.build.type property name, they differ in the following
+// properties that characterize the builds:
+// * eng builds: ro.secure=0 and ro.debuggable=1
+// * userdebug builds: ro.secure=1 and ro.debuggable=1
+// * user builds: ro.secure=1 and ro.debuggable=0
+//
+// See IsOfficialBuild() and IsNormalMode() for the meaning of these options in
+// Android.
+
+bool HardwareAndroid::IsOfficialBuild() const {
+  // We run an official build iff ro.secure == 1, because we expect the build to
+  // behave like the end user product and check for updates. Note that while
+  // developers are able to build "official builds" by just running "make user",
+  // that will only result in a more restrictive environment. The important part
+  // is that we don't produce and push "non-official" builds to the end user.
+  //
+  // In case of a non-bool value, we take the most restrictive option and
+  // assume we are in an official-build.
+  return property_get_bool("ro.secure", 1) != 0;
+}
+
+bool HardwareAndroid::IsNormalBootMode() const {
+  // We are running in "dev-mode" iff ro.debuggable == 1. In dev-mode the
+  // update_engine will allow extra developers options, such as providing a
+  // different update URL. In case of error, we assume the build is in
+  // normal-mode.
+  return property_get_bool("ro.debuggable", 0) != 1;
+}
+
+bool HardwareAndroid::AreDevFeaturesEnabled() const {
+  return !IsNormalBootMode();
+}
+
+bool HardwareAndroid::IsOOBEEnabled() const {
+  // No OOBE flow blocking updates for Android-based boards.
+  return false;
+}
+
+bool HardwareAndroid::IsOOBEComplete(base::Time* out_time_of_oobe) const {
+  LOG(WARNING) << "OOBE is not enabled but IsOOBEComplete() called.";
+  if (out_time_of_oobe)
+    *out_time_of_oobe = base::Time();
+  return true;
+}
+
+string HardwareAndroid::GetHardwareClass() const {
+  LOG(WARNING) << "STUB: GetHardwareClass().";
+  return "ANDROID";
+}
+
+string HardwareAndroid::GetFirmwareVersion() const {
+  LOG(WARNING) << "STUB: GetFirmwareVersion().";
+  return "0";
+}
+
+string HardwareAndroid::GetECVersion() const {
+  LOG(WARNING) << "STUB: GetECVersion().";
+  return "0";
+}
+
+int HardwareAndroid::GetPowerwashCount() const {
+  LOG(WARNING) << "STUB: Assuming no factory reset was performed.";
+  return 0;
+}
+
+bool HardwareAndroid::SchedulePowerwash() {
+  LOG(INFO) << "Scheduling a powerwash to BCB.";
+  return WriteBootloaderRecoveryMessage(kAndroidRecoveryPowerwashCommand);
+}
+
+bool HardwareAndroid::CancelPowerwash() {
+  return WriteBootloaderRecoveryMessage("");
+}
+
+bool HardwareAndroid::GetNonVolatileDirectory(base::FilePath* path) const {
+  base::FilePath local_path(constants::kNonVolatileDirectory);
+  if (!base::PathExists(local_path)) {
+    LOG(ERROR) << "Non-volatile directory not found: " << local_path.value();
+    return false;
+  }
+  *path = local_path;
+  return true;
+}
+
+bool HardwareAndroid::GetPowerwashSafeDirectory(base::FilePath* path) const {
+  // On Android, we don't have a directory persisted across powerwash.
+  return false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/hardware_android.h b/update_engine/hardware_android.h
new file mode 100644
index 0000000..78af871
--- /dev/null
+++ b/update_engine/hardware_android.h
@@ -0,0 +1,57 @@
+//
+// 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_HARDWARE_ANDROID_H_
+#define UPDATE_ENGINE_HARDWARE_ANDROID_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements the real interface with the hardware in the Android platform.
+class HardwareAndroid final : public HardwareInterface {
+ public:
+  HardwareAndroid() = default;
+  ~HardwareAndroid() override = default;
+
+  // 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 GetFirmwareVersion() const override;
+  std::string GetECVersion() const override;
+  int GetPowerwashCount() const override;
+  bool SchedulePowerwash() override;
+  bool CancelPowerwash() override;
+  bool GetNonVolatileDirectory(base::FilePath* path) const override;
+  bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HardwareAndroid);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_HARDWARE_ANDROID_H_
diff --git a/update_engine/hardware_chromeos.cc b/update_engine/hardware_chromeos.cc
new file mode 100644
index 0000000..4b0b82f
--- /dev/null
+++ b/update_engine/hardware_chromeos.cc
@@ -0,0 +1,251 @@
+//
+// 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/hardware_chromeos.h"
+
+#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 <brillo/make_unique_ptr.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/dbus_connection.h"
+
+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 whitelisted in the powerwash scripts.
+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 contents of the powerwash marker file.
+const char kPowerwashCommand[] = "safe fast keepimg reason=update_engine\n";
+
+// UpdateManager config path.
+const char* kConfigFilePath = "/etc/update_manager.conf";
+
+// UpdateManager config options:
+const char* kConfigOptsIsOOBEEnabled = "is_oobe_enabled";
+
+}  // 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::GetFirmwareVersion() const {
+  return ReadValueFromCrosSystem("fwid");
+}
+
+string HardwareChromeOS::GetECVersion() const {
+  string input_line;
+  int exit_code = 0;
+  vector<string> cmd = {"/usr/sbin/mosys", "-k", "ec", "info"};
+
+  bool success = Subprocess::SynchronousExec(cmd, &exit_code, &input_line);
+  if (!success || exit_code) {
+    LOG(ERROR) << "Unable to read ec info from mosys (" << exit_code << ")";
+    return "";
+  }
+
+  return utils::ParseECVersion(input_line);
+}
+
+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 result = utils::WriteFile(
+      kPowerwashMarkerFile, kPowerwashCommand, strlen(kPowerwashCommand));
+  if (result) {
+    LOG(INFO) << "Created " << kPowerwashMarkerFile
+              << " to powerwash on next reboot";
+  } 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;
+  }
+
+  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;
+}
+
+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.
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/hardware_chromeos.h b/update_engine/hardware_chromeos.h
new file mode 100644
index 0000000..03ad750
--- /dev/null
+++ b/update_engine/hardware_chromeos.h
@@ -0,0 +1,72 @@
+//
+// 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_HARDWARE_CHROMEOS_H_
+#define UPDATE_ENGINE_HARDWARE_CHROMEOS_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <debugd/dbus-proxies.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 GetFirmwareVersion() const override;
+  std::string GetECVersion() const override;
+  int GetPowerwashCount() const override;
+  bool SchedulePowerwash() override;
+  bool CancelPowerwash() override;
+  bool GetNonVolatileDirectory(base::FilePath* path) const override;
+  bool GetPowerwashSafeDirectory(base::FilePath* path) 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_HARDWARE_CHROMEOS_H_
diff --git a/update_engine/hardware_chromeos_unittest.cc b/update_engine/hardware_chromeos_unittest.cc
new file mode 100644
index 0000000..a6bad54
--- /dev/null
+++ b/update_engine/hardware_chromeos_unittest.cc
@@ -0,0 +1,89 @@
+//
+// 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/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_.path().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_.path().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_.path().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/update_engine/image_properties.h b/update_engine/image_properties.h
new file mode 100644
index 0000000..6026c2e
--- /dev/null
+++ b/update_engine/image_properties.h
@@ -0,0 +1,84 @@
+//
+// 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_IMAGE_PROPERTIES_H_
+#define UPDATE_ENGINE_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 board name this image was built for.
+  std::string board;
+
+  // The release channel this image was obtained from.
+  std::string current_channel;
+
+  // 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);
+
+// 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_IMAGE_PROPERTIES_H_
diff --git a/update_engine/image_properties_android.cc b/update_engine/image_properties_android.cc
new file mode 100644
index 0000000..00822da
--- /dev/null
+++ b/update_engine/image_properties_android.cc
@@ -0,0 +1,111 @@
+//
+// 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/image_properties.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <brillo/osrelease_reader.h>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/system_state.h"
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Build time properties name used in Brillo.
+const char kProductId[] = "product_id";
+const char kProductVersion[] = "product_version";
+
+// Prefs used to store the target channel and powerwash settings.
+const char kPrefsImgPropChannelName[] = "img-prop-channel-name";
+const char kPrefsImgPropPowerwashAllowed[] = "img-prop-powerwash-allowed";
+
+std::string GetStringWithDefault(const brillo::OsReleaseReader& osrelease,
+                                 const std::string& key,
+                                 const std::string& default_value) {
+  std::string result;
+  if (osrelease.GetString(key, &result))
+    return result;
+  LOG(INFO) << "Cannot load ImageProperty " << key << ", using default value "
+            << default_value;
+  return default_value;
+}
+
+}  // namespace
+
+namespace test {
+void SetImagePropertiesRootPrefix(const char* /* test_root_prefix */) {}
+}  // namespace test
+
+ImageProperties LoadImageProperties(SystemState* system_state) {
+  ImageProperties result;
+
+  brillo::OsReleaseReader osrelease;
+  osrelease.Load();
+  result.product_id = GetStringWithDefault(
+      osrelease, kProductId, "developer-boards:brillo-starter-board");
+  result.canary_product_id = result.product_id;
+  result.version = GetStringWithDefault(osrelease, kProductVersion, "0.0.0.0");
+
+  result.board = "brillo";
+
+  // Brillo images don't have a channel assigned. We stored the name of the
+  // channel where we got the image from in prefs at the time of the update, so
+  // we use that as the current channel if available. During provisioning, there
+  // is no value assigned, so we default to the "stable-channel".
+  std::string current_channel_key =
+      kPrefsChannelOnSlotPrefix +
+      std::to_string(system_state->boot_control()->GetCurrentSlot());
+  std::string current_channel;
+  if (!system_state->prefs()->Exists(current_channel_key) ||
+      !system_state->prefs()->GetString(current_channel_key, &current_channel))
+    current_channel = "stable-channel";
+  result.current_channel = current_channel;
+
+  // Brillo only supports the official omaha URL.
+  result.omaha_url = constants::kOmahaDefaultProductionURL;
+
+  return result;
+}
+
+MutableImageProperties LoadMutableImageProperties(SystemState* system_state) {
+  MutableImageProperties result;
+  PrefsInterface* const prefs = system_state->prefs();
+  if (!prefs->GetString(kPrefsImgPropChannelName, &result.target_channel))
+    result.target_channel.clear();
+  if (!prefs->GetBoolean(kPrefsImgPropPowerwashAllowed,
+                         &result.is_powerwash_allowed)) {
+    result.is_powerwash_allowed = false;
+  }
+  return result;
+}
+
+bool StoreMutableImageProperties(SystemState* system_state,
+                                 const MutableImageProperties& properties) {
+  PrefsInterface* const prefs = system_state->prefs();
+  return (
+      prefs->SetString(kPrefsImgPropChannelName, properties.target_channel) &&
+      prefs->SetBoolean(kPrefsImgPropPowerwashAllowed,
+                        properties.is_powerwash_allowed));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/image_properties_chromeos.cc b/update_engine/image_properties_chromeos.cc
new file mode 100644
index 0000000..501e662
--- /dev/null
+++ b/update_engine/image_properties_chromeos.cc
@@ -0,0 +1,150 @@
+//
+// 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/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/system_state.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);
+
+  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);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/image_properties_chromeos_unittest.cc b/update_engine/image_properties_chromeos_unittest.cc
new file mode 100644
index 0000000..12c2039
--- /dev/null
+++ b/update_engine/image_properties_chromeos_unittest.cc
@@ -0,0 +1,166 @@
+//
+// 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/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/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_.path().Append("etc")));
+    EXPECT_TRUE(base::CreateDirectory(
+        base::FilePath(tempdir_.path().value() + kStatefulPartition + "/etc")));
+    test::SetImagePropertiesRootPrefix(tempdir_.path().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_.path().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_.path().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_.path().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_.path().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_.path().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_.path().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_.path().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_.path().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_.path().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_.path().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/update_engine/include/debugd/dbus-proxies.h b/update_engine/include/debugd/dbus-proxies.h
new file mode 100644
index 0000000..a528480
--- /dev/null
+++ b/update_engine/include/debugd/dbus-proxies.h
@@ -0,0 +1,2334 @@
+// Automatic generation of D-Bus interfaces:
+//  - org.chromium.debugd
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXIES_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXIES_H
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <brillo/any.h>
+#include <brillo/dbus/dbus_method_invoker.h>
+#include <brillo/dbus/dbus_property.h>
+#include <brillo/dbus/dbus_signal_handler.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+
+// Abstract interface proxy for org::chromium::debugd.
+class debugdProxyInterface {
+ public:
+  virtual ~debugdProxyInterface() = default;
+
+  // Starts pinging the specified hostname with the specified options, with
+  // output directed to the given output file descriptor. The returned opaque
+  // string functions as a handle for this particular ping. Multiple pings
+  // can be running at once.
+  virtual bool PingStart(
+      const dbus::FileDescriptor& in_outfd,
+      const std::string& in_destination,
+      const brillo::VariantDictionary& in_options,
+      std::string* out_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Starts pinging the specified hostname with the specified options, with
+  // output directed to the given output file descriptor. The returned opaque
+  // string functions as a handle for this particular ping. Multiple pings
+  // can be running at once.
+  virtual void PingStartAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const std::string& in_destination,
+      const brillo::VariantDictionary& in_options,
+      const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops a running ping.
+  virtual bool PingStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops a running ping.
+  virtual void PingStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Start system/kernel tracing.  If tracing is already enabled it is
+  // stopped first and any collected events are discarded.  The kernel
+  // must have been configured to support tracing.
+  virtual bool SystraceStart(
+      const std::string& in_categories,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Start system/kernel tracing.  If tracing is already enabled it is
+  // stopped first and any collected events are discarded.  The kernel
+  // must have been configured to support tracing.
+  virtual void SystraceStartAsync(
+      const std::string& in_categories,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stop system/kernel tracing and write the collected event data.
+  virtual bool SystraceStop(
+      const dbus::FileDescriptor& in_outfd,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stop system/kernel tracing and write the collected event data.
+  virtual void SystraceStopAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Return current status for system/kernel tracing including whether it
+  // is enabled, the tracing clock, and the set of events enabled.
+  virtual bool SystraceStatus(
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Return current status for system/kernel tracing including whether it
+  // is enabled, the tracing clock, and the set of events enabled.
+  virtual void SystraceStatusAsync(
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool TracePathStart(
+      const dbus::FileDescriptor& in_outfd,
+      const std::string& in_destination,
+      const brillo::VariantDictionary& in_options,
+      std::string* out_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void TracePathStartAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const std::string& in_destination,
+      const brillo::VariantDictionary& in_options,
+      const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops a running tracepath.
+  virtual bool TracePathStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops a running tracepath.
+  virtual void TracePathStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns the routing table.
+  virtual bool GetRoutes(
+      const brillo::VariantDictionary& in_options,
+      std::vector<std::string>* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns the routing table.
+  virtual void GetRoutesAsync(
+      const brillo::VariantDictionary& in_options,
+      const base::Callback<void(const std::vector<std::string>& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns modem information as a JSON string. See the design document for
+  // a rationale.
+  virtual bool GetModemStatus(
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns modem information as a JSON string. See the design document for
+  // a rationale.
+  virtual void GetModemStatusAsync(
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs the specified command through the modem serial interface and
+  // returns the output.
+  virtual bool RunModemCommand(
+      const std::string& in_command,
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs the specified command through the modem serial interface and
+  // returns the output.
+  virtual void RunModemCommandAsync(
+      const std::string& in_command,
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns network information as a JSON string. See the design document
+  // for a rationale.
+  virtual bool GetNetworkStatus(
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns network information as a JSON string. See the design document
+  // for a rationale.
+  virtual void GetNetworkStatusAsync(
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns WiMAX information as a JSON string. See the design document for
+  // a rationale.
+  virtual bool GetWiMaxStatus(
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns WiMAX information as a JSON string. See the design document for
+  // a rationale.
+  virtual void GetWiMaxStatusAsync(
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs system-wide perf profiling. The profile parameters are selected by
+  // perf_args.
+  virtual bool GetPerfOutput(
+      uint32_t in_duration_sec,
+      const std::vector<std::string>& in_perf_args,
+      int32_t* out_status,
+      std::vector<uint8_t>* out_perf_data,
+      std::vector<uint8_t>* out_perf_stat,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs system-wide perf profiling. The profile parameters are selected by
+  // perf_args.
+  virtual void GetPerfOutputAsync(
+      uint32_t in_duration_sec,
+      const std::vector<std::string>& in_perf_args,
+      const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs system-wide perf profiling. It can can profile events other than
+  // cycles (example: iTLB-misses), and can collect branch profiles. It can
+  // also return raw counter values. The exact profile or counters to be
+  // collected is chosen at random and depends on what CPU is used by the
+  // system (certain CPUs do not support certain profiling modes).
+  virtual bool GetRandomPerfOutput(
+      uint32_t in_duration_sec,
+      int32_t* out_status,
+      std::vector<uint8_t>* out_perf_data,
+      std::vector<uint8_t>* out_perf_stat,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs system-wide perf profiling. It can can profile events other than
+  // cycles (example: iTLB-misses), and can collect branch profiles. It can
+  // also return raw counter values. The exact profile or counters to be
+  // collected is chosen at random and depends on what CPU is used by the
+  // system (certain CPUs do not support certain profiling modes).
+  virtual void GetRandomPerfOutputAsync(
+      uint32_t in_duration_sec,
+      const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns perf event data. Does systemwide profiling. It can profile
+  // events other than cycles (example: iTLB-misses), and can collect branch
+  // profiles. The exact profile to be collected is chosen at random
+  // and depends on what CPU is used by the system (certain CPUs do not
+  // support certain profiling modes).
+  virtual bool GetRichPerfData(
+      uint32_t in_duration_sec,
+      std::vector<uint8_t>* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns perf event data. Does systemwide profiling. It can profile
+  // events other than cycles (example: iTLB-misses), and can collect branch
+  // profiles. The exact profile to be collected is chosen at random
+  // and depends on what CPU is used by the system (certain CPUs do not
+  // support certain profiling modes).
+  virtual void GetRichPerfDataAsync(
+      uint32_t in_duration_sec,
+      const base::Callback<void(const std::vector<uint8_t>& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // DEPRECATED: Use DumpDebugLogs instead.
+  // Packages up system logs into a .tar.gz and returns it over the supplied
+  // file descriptor.
+  virtual bool GetDebugLogs(
+      const dbus::FileDescriptor& in_outfd,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // DEPRECATED: Use DumpDebugLogs instead.
+  // Packages up system logs into a .tar.gz and returns it over the supplied
+  // file descriptor.
+  virtual void GetDebugLogsAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Packages up system logs into a .tar(.gz) and returns it over the
+  // supplied file descriptor.
+  virtual bool DumpDebugLogs(
+      bool in_is_compressed,
+      const dbus::FileDescriptor& in_outfd,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Packages up system logs into a .tar(.gz) and returns it over the
+  // supplied file descriptor.
+  virtual void DumpDebugLogsAsync(
+      bool in_is_compressed,
+      const dbus::FileDescriptor& in_outfd,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Enables or disables debug mode for a specified subsystem.
+  virtual bool SetDebugMode(
+      const std::string& in_subsystem,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Enables or disables debug mode for a specified subsystem.
+  virtual void SetDebugModeAsync(
+      const std::string& in_subsystem,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Fetches the contents of a single system log, identified by name. See
+  // /src/log_tool.cc for a list of valid names.
+  virtual bool GetLog(
+      const std::string& in_log,
+      std::string* out_contents,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Fetches the contents of a single system log, identified by name. See
+  // /src/log_tool.cc for a list of valid names.
+  virtual void GetLogAsync(
+      const std::string& in_log,
+      const base::Callback<void(const std::string& /*contents*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns all the system logs.
+  virtual bool GetAllLogs(
+      std::map<std::string, std::string>* out_logs,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns all the system logs.
+  virtual void GetAllLogsAsync(
+      const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns system logs for feedback reports.
+  virtual bool GetFeedbackLogs(
+      std::map<std::string, std::string>* out_logs,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns system logs for feedback reports.
+  virtual void GetFeedbackLogsAsync(
+      const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns list of User log file names that Chrome itself must collect.
+  // These logfiles are relative to the user's profile path and must be
+  // collected separately for each user.
+  virtual bool GetUserLogFiles(
+      std::map<std::string, std::string>* out_user_log_files,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns list of User log file names that Chrome itself must collect.
+  // These logfiles are relative to the user's profile path and must be
+  // collected separately for each user.
+  virtual void GetUserLogFilesAsync(
+      const base::Callback<void(const std::map<std::string, std::string>& /*user_log_files*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Example method. See /doc/hacking.md.
+  virtual bool GetExample(
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Example method. See /doc/hacking.md.
+  virtual void GetExampleAsync(
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns information about network interfaces as a JSON string.
+  virtual bool GetInterfaces(
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Returns information about network interfaces as a JSON string.
+  virtual void GetInterfacesAsync(
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Tests ICMP connectivity to a specified host.
+  virtual bool TestICMP(
+      const std::string& in_host,
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Tests ICMP connectivity to a specified host.
+  virtual void TestICMPAsync(
+      const std::string& in_host,
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Tests ICMP connectivity to a specified host (with options).
+  virtual bool TestICMPWithOptions(
+      const std::string& in_host,
+      const std::map<std::string, std::string>& in_options,
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Tests ICMP connectivity to a specified host (with options).
+  virtual void TestICMPWithOptionsAsync(
+      const std::string& in_host,
+      const std::map<std::string, std::string>& in_options,
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs BatteryFirmware utility.
+  virtual bool BatteryFirmware(
+      const std::string& in_option,
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs BatteryFirmware utility.
+  virtual void BatteryFirmwareAsync(
+      const std::string& in_option,
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs Smartctl utility.
+  virtual bool Smartctl(
+      const std::string& in_option,
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Runs Smartctl utility.
+  virtual void SmartctlAsync(
+      const std::string& in_option,
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Starts running memtester.
+  virtual bool MemtesterStart(
+      const dbus::FileDescriptor& in_outfd,
+      uint32_t in_memory,
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Starts running memtester.
+  virtual void MemtesterStartAsync(
+      const dbus::FileDescriptor& in_outfd,
+      uint32_t in_memory,
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops running memtester.
+  virtual bool MemtesterStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops running memtester.
+  virtual void MemtesterStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Starts running badblocks test.
+  virtual bool BadblocksStart(
+      const dbus::FileDescriptor& in_outfd,
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Starts running badblocks test.
+  virtual void BadblocksStartAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops running badblocks.
+  virtual bool BadblocksStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops running badblocks.
+  virtual void BadblocksStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Starts a packet capture with the specified options, with diagnostic
+  // status directed to the "statfd" file descriptor and packet capture
+  // data sent to the "outfd" file descriptor.  The returned opaque string
+  // functions as a handle for this particular packet capture.  Multiple
+  // captures can be running at once.  Captures can be initiated on
+  // Ethernet-like devices or WiFi devices in "client mode" (showing only
+  // Ethernet frames) by specifying the "device" parameter (see below).
+  // By specifying a channel, the script will find or create a "monitor
+  // mode" interface if one is available and produce an "over the air"
+  // packet capture.  The name of the output packet capture file is sent
+  // to the output file descriptor.
+  virtual bool PacketCaptureStart(
+      const dbus::FileDescriptor& in_statfd,
+      const dbus::FileDescriptor& in_outfd,
+      const brillo::VariantDictionary& in_options,
+      std::string* out_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Starts a packet capture with the specified options, with diagnostic
+  // status directed to the "statfd" file descriptor and packet capture
+  // data sent to the "outfd" file descriptor.  The returned opaque string
+  // functions as a handle for this particular packet capture.  Multiple
+  // captures can be running at once.  Captures can be initiated on
+  // Ethernet-like devices or WiFi devices in "client mode" (showing only
+  // Ethernet frames) by specifying the "device" parameter (see below).
+  // By specifying a channel, the script will find or create a "monitor
+  // mode" interface if one is available and produce an "over the air"
+  // packet capture.  The name of the output packet capture file is sent
+  // to the output file descriptor.
+  virtual void PacketCaptureStartAsync(
+      const dbus::FileDescriptor& in_statfd,
+      const dbus::FileDescriptor& in_outfd,
+      const brillo::VariantDictionary& in_options,
+      const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops a running packet capture.
+  virtual bool PacketCaptureStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Stops a running packet capture.
+  virtual void PacketCaptureStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Triggers show-task-states(T) SysRq.
+  // See https://www.kernel.org/doc/Documentation/sysrq.txt.
+  virtual bool LogKernelTaskStates(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Triggers show-task-states(T) SysRq.
+  // See https://www.kernel.org/doc/Documentation/sysrq.txt.
+  virtual void LogKernelTaskStatesAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Triggers uploading of system crashes (the crash_sender program).
+  virtual bool UploadCrashes(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Triggers uploading of system crashes (the crash_sender program).
+  virtual void UploadCrashesAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Removes rootfs verification. Requires a system reboot before it will
+  // take effect. Restricted to pre-owner dev mode.
+  virtual bool RemoveRootfsVerification(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Removes rootfs verification. Requires a system reboot before it will
+  // take effect. Restricted to pre-owner dev mode.
+  virtual void RemoveRootfsVerificationAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Enables OS booting from a USB image. Restricted to pre-owner dev mode.
+  virtual bool EnableBootFromUsb(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Enables OS booting from a USB image. Restricted to pre-owner dev mode.
+  virtual void EnableBootFromUsbAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Sets up sshd to provide an SSH server immediately and on future reboots.
+  // Also installs the test SSH keys to allow access by cros tools. Requires
+  // that rootfs verification has been removed. Restricted to pre-owner dev
+  // mode.
+  virtual bool ConfigureSshServer(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Sets up sshd to provide an SSH server immediately and on future reboots.
+  // Also installs the test SSH keys to allow access by cros tools. Requires
+  // that rootfs verification has been removed. Restricted to pre-owner dev
+  // mode.
+  virtual void ConfigureSshServerAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Sets both the system and dev mode password for the indicated account.
+  // Restricted to pre-owner dev mode.
+  virtual bool SetUserPassword(
+      const std::string& in_username,
+      const std::string& in_password,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Sets both the system and dev mode password for the indicated account.
+  // Restricted to pre-owner dev mode.
+  virtual void SetUserPasswordAsync(
+      const std::string& in_username,
+      const std::string& in_password,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Sets up Chrome for remote debugging. It will take effect after a reboot
+  // and using port 9222.
+  // Requires that rootfs verification has been removed. Restricted to
+  // pre-owner dev mode.
+  virtual bool EnableChromeRemoteDebugging(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Sets up Chrome for remote debugging. It will take effect after a reboot
+  // and using port 9222.
+  // Requires that rootfs verification has been removed. Restricted to
+  // pre-owner dev mode.
+  virtual void EnableChromeRemoteDebuggingAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Convenience function to enable a predefined set of tools from the Chrome
+  // UI. Equivalent to calling these functions in order:
+  //   1. EnableBootFromUsb()
+  //   2. ConfigureSshServer()
+  //   3. SetUserPassword("root", root_password)
+  // Requires that rootfs verification has been removed. If any sub-function
+  // fails, this function will exit with an error without attempting any
+  // further configuration or rollback. Restricted to pre-owner dev mode.
+  virtual bool EnableChromeDevFeatures(
+      const std::string& in_root_password,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Convenience function to enable a predefined set of tools from the Chrome
+  // UI. Equivalent to calling these functions in order:
+  //   1. EnableBootFromUsb()
+  //   2. ConfigureSshServer()
+  //   3. SetUserPassword("root", root_password)
+  // Requires that rootfs verification has been removed. If any sub-function
+  // fails, this function will exit with an error without attempting any
+  // further configuration or rollback. Restricted to pre-owner dev mode.
+  virtual void EnableChromeDevFeaturesAsync(
+      const std::string& in_root_password,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Queries which dev features have been enabled. Each dev feature will be
+  // indicated by a bit flag in the return value. Flags are defined in the
+  // DevFeatureFlag enumeration. If the dev tools are unavailable (system is
+  // not in dev mode/pre-login state), the DEV_FEATURES_DISABLED flag will be
+  // set and the rest of the bits will always be set to 0.
+  virtual bool QueryDevFeatures(
+      int32_t* out_features,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Queries which dev features have been enabled. Each dev feature will be
+  // indicated by a bit flag in the return value. Flags are defined in the
+  // DevFeatureFlag enumeration. If the dev tools are unavailable (system is
+  // not in dev mode/pre-login state), the DEV_FEATURES_DISABLED flag will be
+  // set and the rest of the bits will always be set to 0.
+  virtual void QueryDevFeaturesAsync(
+      const base::Callback<void(int32_t /*features*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Allow uploading of device coredump files.
+  virtual bool EnableDevCoredumpUpload(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Allow uploading of device coredump files.
+  virtual void EnableDevCoredumpUploadAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Disallow uploading of device coredump files.
+  virtual bool DisableDevCoredumpUpload(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // Disallow uploading of device coredump files.
+  virtual void DisableDevCoredumpUploadAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+};
+
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::debugd.
+class debugdProxy final : public debugdProxyInterface {
+ public:
+  debugdProxy(const scoped_refptr<dbus::Bus>& bus) :
+      bus_{bus},
+      dbus_object_proxy_{
+          bus_->GetObjectProxy(service_name_, object_path_)} {
+  }
+
+  ~debugdProxy() override {
+    bus_->RemoveObjectProxy(
+        service_name_, object_path_, base::Bind(&base::DoNothing));
+  }
+
+  void ReleaseObjectProxy(const base::Closure& callback) {
+    bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+  }
+
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+  // Starts pinging the specified hostname with the specified options, with
+  // output directed to the given output file descriptor. The returned opaque
+  // string functions as a handle for this particular ping. Multiple pings
+  // can be running at once.
+  bool PingStart(
+      const dbus::FileDescriptor& in_outfd,
+      const std::string& in_destination,
+      const brillo::VariantDictionary& in_options,
+      std::string* out_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "PingStart",
+        error,
+        in_outfd,
+        in_destination,
+        in_options);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_handle);
+  }
+
+  // Starts pinging the specified hostname with the specified options, with
+  // output directed to the given output file descriptor. The returned opaque
+  // string functions as a handle for this particular ping. Multiple pings
+  // can be running at once.
+  void PingStartAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const std::string& in_destination,
+      const brillo::VariantDictionary& in_options,
+      const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "PingStart",
+        success_callback,
+        error_callback,
+        in_outfd,
+        in_destination,
+        in_options);
+  }
+
+  // Stops a running ping.
+  bool PingStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "PingStop",
+        error,
+        in_handle);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Stops a running ping.
+  void PingStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "PingStop",
+        success_callback,
+        error_callback,
+        in_handle);
+  }
+
+  // Start system/kernel tracing.  If tracing is already enabled it is
+  // stopped first and any collected events are discarded.  The kernel
+  // must have been configured to support tracing.
+  bool SystraceStart(
+      const std::string& in_categories,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SystraceStart",
+        error,
+        in_categories);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Start system/kernel tracing.  If tracing is already enabled it is
+  // stopped first and any collected events are discarded.  The kernel
+  // must have been configured to support tracing.
+  void SystraceStartAsync(
+      const std::string& in_categories,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SystraceStart",
+        success_callback,
+        error_callback,
+        in_categories);
+  }
+
+  // Stop system/kernel tracing and write the collected event data.
+  bool SystraceStop(
+      const dbus::FileDescriptor& in_outfd,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SystraceStop",
+        error,
+        in_outfd);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Stop system/kernel tracing and write the collected event data.
+  void SystraceStopAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SystraceStop",
+        success_callback,
+        error_callback,
+        in_outfd);
+  }
+
+  // Return current status for system/kernel tracing including whether it
+  // is enabled, the tracing clock, and the set of events enabled.
+  bool SystraceStatus(
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SystraceStatus",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status);
+  }
+
+  // Return current status for system/kernel tracing including whether it
+  // is enabled, the tracing clock, and the set of events enabled.
+  void SystraceStatusAsync(
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SystraceStatus",
+        success_callback,
+        error_callback);
+  }
+
+  bool TracePathStart(
+      const dbus::FileDescriptor& in_outfd,
+      const std::string& in_destination,
+      const brillo::VariantDictionary& in_options,
+      std::string* out_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "TracePathStart",
+        error,
+        in_outfd,
+        in_destination,
+        in_options);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_handle);
+  }
+
+  void TracePathStartAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const std::string& in_destination,
+      const brillo::VariantDictionary& in_options,
+      const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "TracePathStart",
+        success_callback,
+        error_callback,
+        in_outfd,
+        in_destination,
+        in_options);
+  }
+
+  // Stops a running tracepath.
+  bool TracePathStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "TracePathStop",
+        error,
+        in_handle);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Stops a running tracepath.
+  void TracePathStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "TracePathStop",
+        success_callback,
+        error_callback,
+        in_handle);
+  }
+
+  // Returns the routing table.
+  bool GetRoutes(
+      const brillo::VariantDictionary& in_options,
+      std::vector<std::string>* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetRoutes",
+        error,
+        in_options);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_result);
+  }
+
+  // Returns the routing table.
+  void GetRoutesAsync(
+      const brillo::VariantDictionary& in_options,
+      const base::Callback<void(const std::vector<std::string>& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetRoutes",
+        success_callback,
+        error_callback,
+        in_options);
+  }
+
+  // Returns modem information as a JSON string. See the design document for
+  // a rationale.
+  bool GetModemStatus(
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetModemStatus",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status);
+  }
+
+  // Returns modem information as a JSON string. See the design document for
+  // a rationale.
+  void GetModemStatusAsync(
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetModemStatus",
+        success_callback,
+        error_callback);
+  }
+
+  // Runs the specified command through the modem serial interface and
+  // returns the output.
+  bool RunModemCommand(
+      const std::string& in_command,
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "RunModemCommand",
+        error,
+        in_command);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status);
+  }
+
+  // Runs the specified command through the modem serial interface and
+  // returns the output.
+  void RunModemCommandAsync(
+      const std::string& in_command,
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "RunModemCommand",
+        success_callback,
+        error_callback,
+        in_command);
+  }
+
+  // Returns network information as a JSON string. See the design document
+  // for a rationale.
+  bool GetNetworkStatus(
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetNetworkStatus",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status);
+  }
+
+  // Returns network information as a JSON string. See the design document
+  // for a rationale.
+  void GetNetworkStatusAsync(
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetNetworkStatus",
+        success_callback,
+        error_callback);
+  }
+
+  // Returns WiMAX information as a JSON string. See the design document for
+  // a rationale.
+  bool GetWiMaxStatus(
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetWiMaxStatus",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status);
+  }
+
+  // Returns WiMAX information as a JSON string. See the design document for
+  // a rationale.
+  void GetWiMaxStatusAsync(
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetWiMaxStatus",
+        success_callback,
+        error_callback);
+  }
+
+  // Runs system-wide perf profiling. The profile parameters are selected by
+  // perf_args.
+  bool GetPerfOutput(
+      uint32_t in_duration_sec,
+      const std::vector<std::string>& in_perf_args,
+      int32_t* out_status,
+      std::vector<uint8_t>* out_perf_data,
+      std::vector<uint8_t>* out_perf_stat,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetPerfOutput",
+        error,
+        in_duration_sec,
+        in_perf_args);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status, out_perf_data, out_perf_stat);
+  }
+
+  // Runs system-wide perf profiling. The profile parameters are selected by
+  // perf_args.
+  void GetPerfOutputAsync(
+      uint32_t in_duration_sec,
+      const std::vector<std::string>& in_perf_args,
+      const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetPerfOutput",
+        success_callback,
+        error_callback,
+        in_duration_sec,
+        in_perf_args);
+  }
+
+  // Runs system-wide perf profiling. It can can profile events other than
+  // cycles (example: iTLB-misses), and can collect branch profiles. It can
+  // also return raw counter values. The exact profile or counters to be
+  // collected is chosen at random and depends on what CPU is used by the
+  // system (certain CPUs do not support certain profiling modes).
+  bool GetRandomPerfOutput(
+      uint32_t in_duration_sec,
+      int32_t* out_status,
+      std::vector<uint8_t>* out_perf_data,
+      std::vector<uint8_t>* out_perf_stat,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetRandomPerfOutput",
+        error,
+        in_duration_sec);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status, out_perf_data, out_perf_stat);
+  }
+
+  // Runs system-wide perf profiling. It can can profile events other than
+  // cycles (example: iTLB-misses), and can collect branch profiles. It can
+  // also return raw counter values. The exact profile or counters to be
+  // collected is chosen at random and depends on what CPU is used by the
+  // system (certain CPUs do not support certain profiling modes).
+  void GetRandomPerfOutputAsync(
+      uint32_t in_duration_sec,
+      const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetRandomPerfOutput",
+        success_callback,
+        error_callback,
+        in_duration_sec);
+  }
+
+  // Returns perf event data. Does systemwide profiling. It can profile
+  // events other than cycles (example: iTLB-misses), and can collect branch
+  // profiles. The exact profile to be collected is chosen at random
+  // and depends on what CPU is used by the system (certain CPUs do not
+  // support certain profiling modes).
+  bool GetRichPerfData(
+      uint32_t in_duration_sec,
+      std::vector<uint8_t>* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetRichPerfData",
+        error,
+        in_duration_sec);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status);
+  }
+
+  // Returns perf event data. Does systemwide profiling. It can profile
+  // events other than cycles (example: iTLB-misses), and can collect branch
+  // profiles. The exact profile to be collected is chosen at random
+  // and depends on what CPU is used by the system (certain CPUs do not
+  // support certain profiling modes).
+  void GetRichPerfDataAsync(
+      uint32_t in_duration_sec,
+      const base::Callback<void(const std::vector<uint8_t>& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetRichPerfData",
+        success_callback,
+        error_callback,
+        in_duration_sec);
+  }
+
+  // DEPRECATED: Use DumpDebugLogs instead.
+  // Packages up system logs into a .tar.gz and returns it over the supplied
+  // file descriptor.
+  bool GetDebugLogs(
+      const dbus::FileDescriptor& in_outfd,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetDebugLogs",
+        error,
+        in_outfd);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // DEPRECATED: Use DumpDebugLogs instead.
+  // Packages up system logs into a .tar.gz and returns it over the supplied
+  // file descriptor.
+  void GetDebugLogsAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetDebugLogs",
+        success_callback,
+        error_callback,
+        in_outfd);
+  }
+
+  // Packages up system logs into a .tar(.gz) and returns it over the
+  // supplied file descriptor.
+  bool DumpDebugLogs(
+      bool in_is_compressed,
+      const dbus::FileDescriptor& in_outfd,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "DumpDebugLogs",
+        error,
+        in_is_compressed,
+        in_outfd);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Packages up system logs into a .tar(.gz) and returns it over the
+  // supplied file descriptor.
+  void DumpDebugLogsAsync(
+      bool in_is_compressed,
+      const dbus::FileDescriptor& in_outfd,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "DumpDebugLogs",
+        success_callback,
+        error_callback,
+        in_is_compressed,
+        in_outfd);
+  }
+
+  // Enables or disables debug mode for a specified subsystem.
+  bool SetDebugMode(
+      const std::string& in_subsystem,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SetDebugMode",
+        error,
+        in_subsystem);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Enables or disables debug mode for a specified subsystem.
+  void SetDebugModeAsync(
+      const std::string& in_subsystem,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SetDebugMode",
+        success_callback,
+        error_callback,
+        in_subsystem);
+  }
+
+  // Fetches the contents of a single system log, identified by name. See
+  // /src/log_tool.cc for a list of valid names.
+  bool GetLog(
+      const std::string& in_log,
+      std::string* out_contents,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetLog",
+        error,
+        in_log);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_contents);
+  }
+
+  // Fetches the contents of a single system log, identified by name. See
+  // /src/log_tool.cc for a list of valid names.
+  void GetLogAsync(
+      const std::string& in_log,
+      const base::Callback<void(const std::string& /*contents*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetLog",
+        success_callback,
+        error_callback,
+        in_log);
+  }
+
+  // Returns all the system logs.
+  bool GetAllLogs(
+      std::map<std::string, std::string>* out_logs,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetAllLogs",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_logs);
+  }
+
+  // Returns all the system logs.
+  void GetAllLogsAsync(
+      const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetAllLogs",
+        success_callback,
+        error_callback);
+  }
+
+  // Returns system logs for feedback reports.
+  bool GetFeedbackLogs(
+      std::map<std::string, std::string>* out_logs,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetFeedbackLogs",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_logs);
+  }
+
+  // Returns system logs for feedback reports.
+  void GetFeedbackLogsAsync(
+      const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetFeedbackLogs",
+        success_callback,
+        error_callback);
+  }
+
+  // Returns list of User log file names that Chrome itself must collect.
+  // These logfiles are relative to the user's profile path and must be
+  // collected separately for each user.
+  bool GetUserLogFiles(
+      std::map<std::string, std::string>* out_user_log_files,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetUserLogFiles",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_user_log_files);
+  }
+
+  // Returns list of User log file names that Chrome itself must collect.
+  // These logfiles are relative to the user's profile path and must be
+  // collected separately for each user.
+  void GetUserLogFilesAsync(
+      const base::Callback<void(const std::map<std::string, std::string>& /*user_log_files*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetUserLogFiles",
+        success_callback,
+        error_callback);
+  }
+
+  // Example method. See /doc/hacking.md.
+  bool GetExample(
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetExample",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_result);
+  }
+
+  // Example method. See /doc/hacking.md.
+  void GetExampleAsync(
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetExample",
+        success_callback,
+        error_callback);
+  }
+
+  // Returns information about network interfaces as a JSON string.
+  bool GetInterfaces(
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetInterfaces",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_result);
+  }
+
+  // Returns information about network interfaces as a JSON string.
+  void GetInterfacesAsync(
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "GetInterfaces",
+        success_callback,
+        error_callback);
+  }
+
+  // Tests ICMP connectivity to a specified host.
+  bool TestICMP(
+      const std::string& in_host,
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "TestICMP",
+        error,
+        in_host);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_result);
+  }
+
+  // Tests ICMP connectivity to a specified host.
+  void TestICMPAsync(
+      const std::string& in_host,
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "TestICMP",
+        success_callback,
+        error_callback,
+        in_host);
+  }
+
+  // Tests ICMP connectivity to a specified host (with options).
+  bool TestICMPWithOptions(
+      const std::string& in_host,
+      const std::map<std::string, std::string>& in_options,
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "TestICMPWithOptions",
+        error,
+        in_host,
+        in_options);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_result);
+  }
+
+  // Tests ICMP connectivity to a specified host (with options).
+  void TestICMPWithOptionsAsync(
+      const std::string& in_host,
+      const std::map<std::string, std::string>& in_options,
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "TestICMPWithOptions",
+        success_callback,
+        error_callback,
+        in_host,
+        in_options);
+  }
+
+  // Runs BatteryFirmware utility.
+  bool BatteryFirmware(
+      const std::string& in_option,
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "BatteryFirmware",
+        error,
+        in_option);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_result);
+  }
+
+  // Runs BatteryFirmware utility.
+  void BatteryFirmwareAsync(
+      const std::string& in_option,
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "BatteryFirmware",
+        success_callback,
+        error_callback,
+        in_option);
+  }
+
+  // Runs Smartctl utility.
+  bool Smartctl(
+      const std::string& in_option,
+      std::string* out_result,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "Smartctl",
+        error,
+        in_option);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_result);
+  }
+
+  // Runs Smartctl utility.
+  void SmartctlAsync(
+      const std::string& in_option,
+      const base::Callback<void(const std::string& /*result*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "Smartctl",
+        success_callback,
+        error_callback,
+        in_option);
+  }
+
+  // Starts running memtester.
+  bool MemtesterStart(
+      const dbus::FileDescriptor& in_outfd,
+      uint32_t in_memory,
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "MemtesterStart",
+        error,
+        in_outfd,
+        in_memory);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status);
+  }
+
+  // Starts running memtester.
+  void MemtesterStartAsync(
+      const dbus::FileDescriptor& in_outfd,
+      uint32_t in_memory,
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "MemtesterStart",
+        success_callback,
+        error_callback,
+        in_outfd,
+        in_memory);
+  }
+
+  // Stops running memtester.
+  bool MemtesterStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "MemtesterStop",
+        error,
+        in_handle);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Stops running memtester.
+  void MemtesterStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "MemtesterStop",
+        success_callback,
+        error_callback,
+        in_handle);
+  }
+
+  // Starts running badblocks test.
+  bool BadblocksStart(
+      const dbus::FileDescriptor& in_outfd,
+      std::string* out_status,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "BadblocksStart",
+        error,
+        in_outfd);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_status);
+  }
+
+  // Starts running badblocks test.
+  void BadblocksStartAsync(
+      const dbus::FileDescriptor& in_outfd,
+      const base::Callback<void(const std::string& /*status*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "BadblocksStart",
+        success_callback,
+        error_callback,
+        in_outfd);
+  }
+
+  // Stops running badblocks.
+  bool BadblocksStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "BadblocksStop",
+        error,
+        in_handle);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Stops running badblocks.
+  void BadblocksStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "BadblocksStop",
+        success_callback,
+        error_callback,
+        in_handle);
+  }
+
+  // Starts a packet capture with the specified options, with diagnostic
+  // status directed to the "statfd" file descriptor and packet capture
+  // data sent to the "outfd" file descriptor.  The returned opaque string
+  // functions as a handle for this particular packet capture.  Multiple
+  // captures can be running at once.  Captures can be initiated on
+  // Ethernet-like devices or WiFi devices in "client mode" (showing only
+  // Ethernet frames) by specifying the "device" parameter (see below).
+  // By specifying a channel, the script will find or create a "monitor
+  // mode" interface if one is available and produce an "over the air"
+  // packet capture.  The name of the output packet capture file is sent
+  // to the output file descriptor.
+  bool PacketCaptureStart(
+      const dbus::FileDescriptor& in_statfd,
+      const dbus::FileDescriptor& in_outfd,
+      const brillo::VariantDictionary& in_options,
+      std::string* out_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "PacketCaptureStart",
+        error,
+        in_statfd,
+        in_outfd,
+        in_options);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_handle);
+  }
+
+  // Starts a packet capture with the specified options, with diagnostic
+  // status directed to the "statfd" file descriptor and packet capture
+  // data sent to the "outfd" file descriptor.  The returned opaque string
+  // functions as a handle for this particular packet capture.  Multiple
+  // captures can be running at once.  Captures can be initiated on
+  // Ethernet-like devices or WiFi devices in "client mode" (showing only
+  // Ethernet frames) by specifying the "device" parameter (see below).
+  // By specifying a channel, the script will find or create a "monitor
+  // mode" interface if one is available and produce an "over the air"
+  // packet capture.  The name of the output packet capture file is sent
+  // to the output file descriptor.
+  void PacketCaptureStartAsync(
+      const dbus::FileDescriptor& in_statfd,
+      const dbus::FileDescriptor& in_outfd,
+      const brillo::VariantDictionary& in_options,
+      const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "PacketCaptureStart",
+        success_callback,
+        error_callback,
+        in_statfd,
+        in_outfd,
+        in_options);
+  }
+
+  // Stops a running packet capture.
+  bool PacketCaptureStop(
+      const std::string& in_handle,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "PacketCaptureStop",
+        error,
+        in_handle);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Stops a running packet capture.
+  void PacketCaptureStopAsync(
+      const std::string& in_handle,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "PacketCaptureStop",
+        success_callback,
+        error_callback,
+        in_handle);
+  }
+
+  // Triggers show-task-states(T) SysRq.
+  // See https://www.kernel.org/doc/Documentation/sysrq.txt.
+  bool LogKernelTaskStates(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "LogKernelTaskStates",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Triggers show-task-states(T) SysRq.
+  // See https://www.kernel.org/doc/Documentation/sysrq.txt.
+  void LogKernelTaskStatesAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "LogKernelTaskStates",
+        success_callback,
+        error_callback);
+  }
+
+  // Triggers uploading of system crashes (the crash_sender program).
+  bool UploadCrashes(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "UploadCrashes",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Triggers uploading of system crashes (the crash_sender program).
+  void UploadCrashesAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "UploadCrashes",
+        success_callback,
+        error_callback);
+  }
+
+  // Removes rootfs verification. Requires a system reboot before it will
+  // take effect. Restricted to pre-owner dev mode.
+  bool RemoveRootfsVerification(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "RemoveRootfsVerification",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Removes rootfs verification. Requires a system reboot before it will
+  // take effect. Restricted to pre-owner dev mode.
+  void RemoveRootfsVerificationAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "RemoveRootfsVerification",
+        success_callback,
+        error_callback);
+  }
+
+  // Enables OS booting from a USB image. Restricted to pre-owner dev mode.
+  bool EnableBootFromUsb(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "EnableBootFromUsb",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Enables OS booting from a USB image. Restricted to pre-owner dev mode.
+  void EnableBootFromUsbAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "EnableBootFromUsb",
+        success_callback,
+        error_callback);
+  }
+
+  // Sets up sshd to provide an SSH server immediately and on future reboots.
+  // Also installs the test SSH keys to allow access by cros tools. Requires
+  // that rootfs verification has been removed. Restricted to pre-owner dev
+  // mode.
+  bool ConfigureSshServer(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "ConfigureSshServer",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Sets up sshd to provide an SSH server immediately and on future reboots.
+  // Also installs the test SSH keys to allow access by cros tools. Requires
+  // that rootfs verification has been removed. Restricted to pre-owner dev
+  // mode.
+  void ConfigureSshServerAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "ConfigureSshServer",
+        success_callback,
+        error_callback);
+  }
+
+  // Sets both the system and dev mode password for the indicated account.
+  // Restricted to pre-owner dev mode.
+  bool SetUserPassword(
+      const std::string& in_username,
+      const std::string& in_password,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SetUserPassword",
+        error,
+        in_username,
+        in_password);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Sets both the system and dev mode password for the indicated account.
+  // Restricted to pre-owner dev mode.
+  void SetUserPasswordAsync(
+      const std::string& in_username,
+      const std::string& in_password,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "SetUserPassword",
+        success_callback,
+        error_callback,
+        in_username,
+        in_password);
+  }
+
+  // Sets up Chrome for remote debugging. It will take effect after a reboot
+  // and using port 9222.
+  // Requires that rootfs verification has been removed. Restricted to
+  // pre-owner dev mode.
+  bool EnableChromeRemoteDebugging(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "EnableChromeRemoteDebugging",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Sets up Chrome for remote debugging. It will take effect after a reboot
+  // and using port 9222.
+  // Requires that rootfs verification has been removed. Restricted to
+  // pre-owner dev mode.
+  void EnableChromeRemoteDebuggingAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "EnableChromeRemoteDebugging",
+        success_callback,
+        error_callback);
+  }
+
+  // Convenience function to enable a predefined set of tools from the Chrome
+  // UI. Equivalent to calling these functions in order:
+  //   1. EnableBootFromUsb()
+  //   2. ConfigureSshServer()
+  //   3. SetUserPassword("root", root_password)
+  // Requires that rootfs verification has been removed. If any sub-function
+  // fails, this function will exit with an error without attempting any
+  // further configuration or rollback. Restricted to pre-owner dev mode.
+  bool EnableChromeDevFeatures(
+      const std::string& in_root_password,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "EnableChromeDevFeatures",
+        error,
+        in_root_password);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Convenience function to enable a predefined set of tools from the Chrome
+  // UI. Equivalent to calling these functions in order:
+  //   1. EnableBootFromUsb()
+  //   2. ConfigureSshServer()
+  //   3. SetUserPassword("root", root_password)
+  // Requires that rootfs verification has been removed. If any sub-function
+  // fails, this function will exit with an error without attempting any
+  // further configuration or rollback. Restricted to pre-owner dev mode.
+  void EnableChromeDevFeaturesAsync(
+      const std::string& in_root_password,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "EnableChromeDevFeatures",
+        success_callback,
+        error_callback,
+        in_root_password);
+  }
+
+  // Queries which dev features have been enabled. Each dev feature will be
+  // indicated by a bit flag in the return value. Flags are defined in the
+  // DevFeatureFlag enumeration. If the dev tools are unavailable (system is
+  // not in dev mode/pre-login state), the DEV_FEATURES_DISABLED flag will be
+  // set and the rest of the bits will always be set to 0.
+  bool QueryDevFeatures(
+      int32_t* out_features,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "QueryDevFeatures",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_features);
+  }
+
+  // Queries which dev features have been enabled. Each dev feature will be
+  // indicated by a bit flag in the return value. Flags are defined in the
+  // DevFeatureFlag enumeration. If the dev tools are unavailable (system is
+  // not in dev mode/pre-login state), the DEV_FEATURES_DISABLED flag will be
+  // set and the rest of the bits will always be set to 0.
+  void QueryDevFeaturesAsync(
+      const base::Callback<void(int32_t /*features*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "QueryDevFeatures",
+        success_callback,
+        error_callback);
+  }
+
+  // Allow uploading of device coredump files.
+  bool EnableDevCoredumpUpload(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "EnableDevCoredumpUpload",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Allow uploading of device coredump files.
+  void EnableDevCoredumpUploadAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "EnableDevCoredumpUpload",
+        success_callback,
+        error_callback);
+  }
+
+  // Disallow uploading of device coredump files.
+  bool DisableDevCoredumpUpload(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "DisableDevCoredumpUpload",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // Disallow uploading of device coredump files.
+  void DisableDevCoredumpUploadAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.debugd",
+        "DisableDevCoredumpUpload",
+        success_callback,
+        error_callback);
+  }
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  const std::string service_name_{"org.chromium.debugd"};
+  const dbus::ObjectPath object_path_{"/org/chromium/debugd"};
+  dbus::ObjectProxy* dbus_object_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(debugdProxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+
+#endif  // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXIES_H
diff --git a/update_engine/include/debugd/dbus-proxy-mocks.h b/update_engine/include/debugd/dbus-proxy-mocks.h
new file mode 100644
index 0000000..042a9fd
--- /dev/null
+++ b/update_engine/include/debugd/dbus-proxy-mocks.h
@@ -0,0 +1,453 @@
+// Automatic generation of D-Bus interface mock proxies for:
+//  - org.chromium.debugd
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXY_MOCKS_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXY_MOCKS_H
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/any.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+
+#include "debugd/dbus-proxies.h"
+
+namespace org {
+namespace chromium {
+
+// Mock object for debugdProxyInterface.
+class debugdProxyMock : public debugdProxyInterface {
+ public:
+  debugdProxyMock() = default;
+
+  MOCK_METHOD6(PingStart,
+               bool(const dbus::FileDescriptor& /*in_outfd*/,
+                    const std::string& /*in_destination*/,
+                    const brillo::VariantDictionary& /*in_options*/,
+                    std::string* /*out_handle*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD6(PingStartAsync,
+               void(const dbus::FileDescriptor& /*in_outfd*/,
+                    const std::string& /*in_destination*/,
+                    const brillo::VariantDictionary& /*in_options*/,
+                    const base::Callback<void(const std::string& /*handle*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(PingStop,
+               bool(const std::string& /*in_handle*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(PingStopAsync,
+               void(const std::string& /*in_handle*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SystraceStart,
+               bool(const std::string& /*in_categories*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SystraceStartAsync,
+               void(const std::string& /*in_categories*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SystraceStop,
+               bool(const dbus::FileDescriptor& /*in_outfd*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SystraceStopAsync,
+               void(const dbus::FileDescriptor& /*in_outfd*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SystraceStatus,
+               bool(std::string* /*out_status*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SystraceStatusAsync,
+               void(const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD6(TracePathStart,
+               bool(const dbus::FileDescriptor& /*in_outfd*/,
+                    const std::string& /*in_destination*/,
+                    const brillo::VariantDictionary& /*in_options*/,
+                    std::string* /*out_handle*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD6(TracePathStartAsync,
+               void(const dbus::FileDescriptor& /*in_outfd*/,
+                    const std::string& /*in_destination*/,
+                    const brillo::VariantDictionary& /*in_options*/,
+                    const base::Callback<void(const std::string& /*handle*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(TracePathStop,
+               bool(const std::string& /*in_handle*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(TracePathStopAsync,
+               void(const std::string& /*in_handle*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetRoutes,
+               bool(const brillo::VariantDictionary& /*in_options*/,
+                    std::vector<std::string>* /*out_result*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetRoutesAsync,
+               void(const brillo::VariantDictionary& /*in_options*/,
+                    const base::Callback<void(const std::vector<std::string>& /*result*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetModemStatus,
+               bool(std::string* /*out_status*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetModemStatusAsync,
+               void(const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RunModemCommand,
+               bool(const std::string& /*in_command*/,
+                    std::string* /*out_status*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RunModemCommandAsync,
+               void(const std::string& /*in_command*/,
+                    const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetNetworkStatus,
+               bool(std::string* /*out_status*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetNetworkStatusAsync,
+               void(const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetWiMaxStatus,
+               bool(std::string* /*out_status*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetWiMaxStatusAsync,
+               void(const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD7(GetPerfOutput,
+               bool(uint32_t /*in_duration_sec*/,
+                    const std::vector<std::string>& /*in_perf_args*/,
+                    int32_t* /*out_status*/,
+                    std::vector<uint8_t>* /*out_perf_data*/,
+                    std::vector<uint8_t>* /*out_perf_stat*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(GetPerfOutputAsync,
+               void(uint32_t /*in_duration_sec*/,
+                    const std::vector<std::string>& /*in_perf_args*/,
+                    const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD6(GetRandomPerfOutput,
+               bool(uint32_t /*in_duration_sec*/,
+                    int32_t* /*out_status*/,
+                    std::vector<uint8_t>* /*out_perf_data*/,
+                    std::vector<uint8_t>* /*out_perf_stat*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetRandomPerfOutputAsync,
+               void(uint32_t /*in_duration_sec*/,
+                    const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetRichPerfData,
+               bool(uint32_t /*in_duration_sec*/,
+                    std::vector<uint8_t>* /*out_status*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetRichPerfDataAsync,
+               void(uint32_t /*in_duration_sec*/,
+                    const base::Callback<void(const std::vector<uint8_t>& /*status*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetDebugLogs,
+               bool(const dbus::FileDescriptor& /*in_outfd*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetDebugLogsAsync,
+               void(const dbus::FileDescriptor& /*in_outfd*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(DumpDebugLogs,
+               bool(bool /*in_is_compressed*/,
+                    const dbus::FileDescriptor& /*in_outfd*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(DumpDebugLogsAsync,
+               void(bool /*in_is_compressed*/,
+                    const dbus::FileDescriptor& /*in_outfd*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetDebugMode,
+               bool(const std::string& /*in_subsystem*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetDebugModeAsync,
+               void(const std::string& /*in_subsystem*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetLog,
+               bool(const std::string& /*in_log*/,
+                    std::string* /*out_contents*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetLogAsync,
+               void(const std::string& /*in_log*/,
+                    const base::Callback<void(const std::string& /*contents*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetAllLogs,
+               bool(std::map<std::string, std::string>* /*out_logs*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetAllLogsAsync,
+               void(const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetFeedbackLogs,
+               bool(std::map<std::string, std::string>* /*out_logs*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetFeedbackLogsAsync,
+               void(const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetUserLogFiles,
+               bool(std::map<std::string, std::string>* /*out_user_log_files*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetUserLogFilesAsync,
+               void(const base::Callback<void(const std::map<std::string, std::string>& /*user_log_files*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetExample,
+               bool(std::string* /*out_result*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetExampleAsync,
+               void(const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetInterfaces,
+               bool(std::string* /*out_result*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetInterfacesAsync,
+               void(const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(TestICMP,
+               bool(const std::string& /*in_host*/,
+                    std::string* /*out_result*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(TestICMPAsync,
+               void(const std::string& /*in_host*/,
+                    const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(TestICMPWithOptions,
+               bool(const std::string& /*in_host*/,
+                    const std::map<std::string, std::string>& /*in_options*/,
+                    std::string* /*out_result*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(TestICMPWithOptionsAsync,
+               void(const std::string& /*in_host*/,
+                    const std::map<std::string, std::string>& /*in_options*/,
+                    const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(BatteryFirmware,
+               bool(const std::string& /*in_option*/,
+                    std::string* /*out_result*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(BatteryFirmwareAsync,
+               void(const std::string& /*in_option*/,
+                    const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(Smartctl,
+               bool(const std::string& /*in_option*/,
+                    std::string* /*out_result*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SmartctlAsync,
+               void(const std::string& /*in_option*/,
+                    const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(MemtesterStart,
+               bool(const dbus::FileDescriptor& /*in_outfd*/,
+                    uint32_t /*in_memory*/,
+                    std::string* /*out_status*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(MemtesterStartAsync,
+               void(const dbus::FileDescriptor& /*in_outfd*/,
+                    uint32_t /*in_memory*/,
+                    const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(MemtesterStop,
+               bool(const std::string& /*in_handle*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(MemtesterStopAsync,
+               void(const std::string& /*in_handle*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(BadblocksStart,
+               bool(const dbus::FileDescriptor& /*in_outfd*/,
+                    std::string* /*out_status*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(BadblocksStartAsync,
+               void(const dbus::FileDescriptor& /*in_outfd*/,
+                    const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(BadblocksStop,
+               bool(const std::string& /*in_handle*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(BadblocksStopAsync,
+               void(const std::string& /*in_handle*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD6(PacketCaptureStart,
+               bool(const dbus::FileDescriptor& /*in_statfd*/,
+                    const dbus::FileDescriptor& /*in_outfd*/,
+                    const brillo::VariantDictionary& /*in_options*/,
+                    std::string* /*out_handle*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD6(PacketCaptureStartAsync,
+               void(const dbus::FileDescriptor& /*in_statfd*/,
+                    const dbus::FileDescriptor& /*in_outfd*/,
+                    const brillo::VariantDictionary& /*in_options*/,
+                    const base::Callback<void(const std::string& /*handle*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(PacketCaptureStop,
+               bool(const std::string& /*in_handle*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(PacketCaptureStopAsync,
+               void(const std::string& /*in_handle*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(LogKernelTaskStates,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(LogKernelTaskStatesAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(UploadCrashes,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(UploadCrashesAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(RemoveRootfsVerification,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RemoveRootfsVerificationAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(EnableBootFromUsb,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(EnableBootFromUsbAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(ConfigureSshServer,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(ConfigureSshServerAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetUserPassword,
+               bool(const std::string& /*in_username*/,
+                    const std::string& /*in_password*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(SetUserPasswordAsync,
+               void(const std::string& /*in_username*/,
+                    const std::string& /*in_password*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(EnableChromeRemoteDebugging,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(EnableChromeRemoteDebuggingAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(EnableChromeDevFeatures,
+               bool(const std::string& /*in_root_password*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(EnableChromeDevFeaturesAsync,
+               void(const std::string& /*in_root_password*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(QueryDevFeatures,
+               bool(int32_t* /*out_features*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(QueryDevFeaturesAsync,
+               void(const base::Callback<void(int32_t /*features*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(EnableDevCoredumpUpload,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(EnableDevCoredumpUploadAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(DisableDevCoredumpUpload,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(DisableDevCoredumpUploadAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(debugdProxyMock);
+};
+}  // namespace chromium
+}  // namespace org
+
+#endif  // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXY_MOCKS_H
diff --git a/update_engine/include/libcros/dbus-proxy-mocks.h b/update_engine/include/libcros/dbus-proxy-mocks.h
new file mode 100644
index 0000000..16790bd
--- /dev/null
+++ b/update_engine/include/libcros/dbus-proxy-mocks.h
@@ -0,0 +1,77 @@
+// Automatic generation of D-Bus interface mock proxies for:
+//  - org.chromium.LibCrosServiceInterface
+//  - org.chromium.UpdateEngineLibcrosProxyResolvedInterface
+#ifndef ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_LIBCROS_DBUS_PROXY_MOCKS_H
+#define ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_LIBCROS_DBUS_PROXY_MOCKS_H
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/any.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+
+#include "libcros/dbus-proxies.h"
+
+namespace org {
+namespace chromium {
+
+// Mock object for LibCrosServiceInterfaceProxyInterface.
+class LibCrosServiceInterfaceProxyMock : public LibCrosServiceInterfaceProxyInterface {
+ public:
+  LibCrosServiceInterfaceProxyMock() = default;
+
+  MOCK_METHOD5(ResolveNetworkProxy,
+               bool(const std::string& /*in_source_url*/,
+                    const std::string& /*in_signal_interface*/,
+                    const std::string& /*in_signal_name*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD6(ResolveNetworkProxyAsync,
+               void(const std::string& /*in_source_url*/,
+                    const std::string& /*in_signal_interface*/,
+                    const std::string& /*in_signal_name*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetKioskAppRequiredPlatformVersion,
+               bool(std::string* /*out_required_platform_version*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetKioskAppRequiredPlatformVersionAsync,
+               void(const base::Callback<void(const std::string& /*required_platform_version*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_CONST_METHOD0(GetObjectPath, const dbus::ObjectPath&());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(LibCrosServiceInterfaceProxyMock);
+};
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+// Mock object for UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface.
+class UpdateEngineLibcrosProxyResolvedInterfaceProxyMock : public UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface {
+ public:
+  UpdateEngineLibcrosProxyResolvedInterfaceProxyMock() = default;
+
+  MOCK_METHOD2(RegisterProxyResolvedSignalHandler,
+               void(const base::Callback<void(const std::string&,
+                                              const std::string&,
+                                              const std::string&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_CONST_METHOD0(GetObjectPath, const dbus::ObjectPath&());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UpdateEngineLibcrosProxyResolvedInterfaceProxyMock);
+};
+}  // namespace chromium
+}  // namespace org
+
+#endif  // ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_LIBCROS_DBUS_PROXY_MOCKS_H
diff --git a/update_engine/include/power_manager/dbus-proxies.h b/update_engine/include/power_manager/dbus-proxies.h
new file mode 100644
index 0000000..e66848d
--- /dev/null
+++ b/update_engine/include/power_manager/dbus-proxies.h
@@ -0,0 +1,1280 @@
+// Automatic generation of D-Bus interfaces:
+//  - org.chromium.PowerManager
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXIES_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXIES_H
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <brillo/any.h>
+#include <brillo/dbus/dbus_method_invoker.h>
+#include <brillo/dbus/dbus_property.h>
+#include <brillo/dbus/dbus_signal_handler.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+
+// Abstract interface proxy for org::chromium::PowerManager.
+class PowerManagerProxyInterface {
+ public:
+  virtual ~PowerManagerProxyInterface() = default;
+
+  virtual bool RequestShutdown(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void RequestShutdownAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |reason| arg is a power_manager::RequestRestartReason value.
+  virtual bool RequestRestart(
+      int32_t in_reason,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |reason| arg is a power_manager::RequestRestartReason value.
+  virtual void RequestRestartAsync(
+      int32_t in_reason,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |external_wakeup_count| arg is optional, and it will call two
+  // different methods in the backend. This can't be expressed in the DBus
+  // Introspection XML file.
+  virtual bool RequestSuspend(
+      uint64_t in_external_wakeup_count,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |external_wakeup_count| arg is optional, and it will call two
+  // different methods in the backend. This can't be expressed in the DBus
+  // Introspection XML file.
+  virtual void RequestSuspendAsync(
+      uint64_t in_external_wakeup_count,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool DecreaseScreenBrightness(
+      bool in_allow_off,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void DecreaseScreenBrightnessAsync(
+      bool in_allow_off,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool IncreaseScreenBrightness(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void IncreaseScreenBrightnessAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool GetScreenBrightnessPercent(
+      double* out_percent,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void GetScreenBrightnessPercentAsync(
+      const base::Callback<void(double /*percent*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |style| arg must be one of the values:
+  //   power_manager::kBrightnessTransitionGradual or
+  //   power_manager::kBrightnessTransitionInstant.
+  virtual bool SetScreenBrightnessPercent(
+      double in_percent,
+      int32_t in_style,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |style| arg must be one of the values:
+  //   power_manager::kBrightnessTransitionGradual or
+  //   power_manager::kBrightnessTransitionInstant.
+  virtual void SetScreenBrightnessPercentAsync(
+      double in_percent,
+      int32_t in_style,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool DecreaseKeyboardBrightness(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void DecreaseKeyboardBrightnessAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool IncreaseKeyboardBrightness(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void IncreaseKeyboardBrightnessAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::PowerSupplyProperties protobuf.
+  virtual bool GetPowerSupplyProperties(
+      std::vector<uint8_t>* out_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::PowerSupplyProperties protobuf.
+  virtual void GetPowerSupplyPropertiesAsync(
+      const base::Callback<void(const std::vector<uint8_t>& /*serialized_proto*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool HandleVideoActivity(
+      bool in_fullscreen,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void HandleVideoActivityAsync(
+      bool in_fullscreen,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |type| arg is a power_manager::UserActivityType.
+  virtual bool HandleUserActivity(
+      int32_t in_type,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |type| arg is a power_manager::UserActivityType.
+  virtual void HandleUserActivityAsync(
+      int32_t in_type,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool SetIsProjecting(
+      bool in_is_projecting,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void SetIsProjectingAsync(
+      bool in_is_projecting,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::PowerManagementPolicy protobuf.
+  virtual bool SetPolicy(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::PowerManagementPolicy protobuf.
+  virtual void SetPolicyAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool SetPowerSource(
+      const std::string& in_id,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void SetPowerSourceAsync(
+      const std::string& in_id,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |timestamp_internal| arg is represented as the return value of
+  // base::TimeTicks::ToInternalValue().
+  virtual bool HandlePowerButtonAcknowledgment(
+      int64_t in_timestamp_internal,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |timestamp_internal| arg is represented as the return value of
+  // base::TimeTicks::ToInternalValue().
+  virtual void HandlePowerButtonAcknowledgmentAsync(
+      int64_t in_timestamp_internal,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_request_proto| arg is a serialized
+  // power_manager::RegisterSuspendDelayRequest protobuf.
+  // The |serialized_reply_proto| arg is a serialized
+  // RegisterSuspendDelayReply protobuf.
+  virtual bool RegisterSuspendDelay(
+      const std::vector<uint8_t>& in_serialized_request_proto,
+      std::vector<uint8_t>* out_serialized_reply_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_request_proto| arg is a serialized
+  // power_manager::RegisterSuspendDelayRequest protobuf.
+  // The |serialized_reply_proto| arg is a serialized
+  // RegisterSuspendDelayReply protobuf.
+  virtual void RegisterSuspendDelayAsync(
+      const std::vector<uint8_t>& in_serialized_request_proto,
+      const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::UnregisterSuspendDelayRequest protobuf.
+  virtual bool UnregisterSuspendDelay(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::UnregisterSuspendDelayRequest protobuf.
+  virtual void UnregisterSuspendDelayAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::SuspendReadinessInfo protobuf.
+  virtual bool HandleSuspendReadiness(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::SuspendReadinessInfo protobuf.
+  virtual void HandleSuspendReadinessAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_request_proto| arg is a serialized
+  // power_manager::RegisterSuspendDelayRequest protobuf.
+  // The |serialized_reply_proto| arg is a serialized
+  // RegisterSuspendDelayReply protobuf.
+  virtual bool RegisterDarkSuspendDelay(
+      const std::vector<uint8_t>& in_serialized_request_proto,
+      std::vector<uint8_t>* out_serialized_reply_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_request_proto| arg is a serialized
+  // power_manager::RegisterSuspendDelayRequest protobuf.
+  // The |serialized_reply_proto| arg is a serialized
+  // RegisterSuspendDelayReply protobuf.
+  virtual void RegisterDarkSuspendDelayAsync(
+      const std::vector<uint8_t>& in_serialized_request_proto,
+      const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::UnregisterSuspendDelayRequest protobuf.
+  virtual bool UnregisterDarkSuspendDelay(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::UnregisterSuspendDelayRequest protobuf.
+  virtual void UnregisterDarkSuspendDelayAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::SuspendReadinessInfo protobuf.
+  virtual bool HandleDarkSuspendReadiness(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::SuspendReadinessInfo protobuf.
+  virtual void HandleDarkSuspendReadinessAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::DarkResumeWakeReason protobuf.
+  virtual bool RecordDarkResumeWakeReason(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::DarkResumeWakeReason protobuf.
+  virtual void RecordDarkResumeWakeReasonAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void RegisterBrightnessChangedSignalHandler(
+      const base::Callback<void(int32_t,
+                                bool)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterKeyboardBrightnessChangedSignalHandler(
+      const base::Callback<void(int32_t,
+                                bool)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterPeripheralBatteryStatusSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterPowerSupplyPollSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterSuspendImminentSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterSuspendDoneSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterDarkSuspendImminentSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterInputEventSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterIdleActionImminentSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterIdleActionDeferredSignalHandler(
+      const base::Closure& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+};
+
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::PowerManager.
+class PowerManagerProxy final : public PowerManagerProxyInterface {
+ public:
+  PowerManagerProxy(const scoped_refptr<dbus::Bus>& bus) :
+      bus_{bus},
+      dbus_object_proxy_{
+          bus_->GetObjectProxy(service_name_, object_path_)} {
+  }
+
+  ~PowerManagerProxy() override {
+    bus_->RemoveObjectProxy(
+        service_name_, object_path_, base::Bind(&base::DoNothing));
+  }
+
+  void RegisterBrightnessChangedSignalHandler(
+      const base::Callback<void(int32_t,
+                                bool)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "BrightnessChanged",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterKeyboardBrightnessChangedSignalHandler(
+      const base::Callback<void(int32_t,
+                                bool)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "KeyboardBrightnessChanged",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterPeripheralBatteryStatusSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "PeripheralBatteryStatus",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterPowerSupplyPollSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "PowerSupplyPoll",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterSuspendImminentSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SuspendImminent",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterSuspendDoneSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SuspendDone",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterDarkSuspendImminentSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "DarkSuspendImminent",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterInputEventSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "InputEvent",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterIdleActionImminentSignalHandler(
+      const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "IdleActionImminent",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterIdleActionDeferredSignalHandler(
+      const base::Closure& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "IdleActionDeferred",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void ReleaseObjectProxy(const base::Closure& callback) {
+    bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+  }
+
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+  bool RequestShutdown(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RequestShutdown",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void RequestShutdownAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RequestShutdown",
+        success_callback,
+        error_callback);
+  }
+
+  // The |reason| arg is a power_manager::RequestRestartReason value.
+  bool RequestRestart(
+      int32_t in_reason,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RequestRestart",
+        error,
+        in_reason);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |reason| arg is a power_manager::RequestRestartReason value.
+  void RequestRestartAsync(
+      int32_t in_reason,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RequestRestart",
+        success_callback,
+        error_callback,
+        in_reason);
+  }
+
+  // The |external_wakeup_count| arg is optional, and it will call two
+  // different methods in the backend. This can't be expressed in the DBus
+  // Introspection XML file.
+  bool RequestSuspend(
+      uint64_t in_external_wakeup_count,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RequestSuspend",
+        error,
+        in_external_wakeup_count);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |external_wakeup_count| arg is optional, and it will call two
+  // different methods in the backend. This can't be expressed in the DBus
+  // Introspection XML file.
+  void RequestSuspendAsync(
+      uint64_t in_external_wakeup_count,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RequestSuspend",
+        success_callback,
+        error_callback,
+        in_external_wakeup_count);
+  }
+
+  bool DecreaseScreenBrightness(
+      bool in_allow_off,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "DecreaseScreenBrightness",
+        error,
+        in_allow_off);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void DecreaseScreenBrightnessAsync(
+      bool in_allow_off,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "DecreaseScreenBrightness",
+        success_callback,
+        error_callback,
+        in_allow_off);
+  }
+
+  bool IncreaseScreenBrightness(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "IncreaseScreenBrightness",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void IncreaseScreenBrightnessAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "IncreaseScreenBrightness",
+        success_callback,
+        error_callback);
+  }
+
+  bool GetScreenBrightnessPercent(
+      double* out_percent,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "GetScreenBrightnessPercent",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_percent);
+  }
+
+  void GetScreenBrightnessPercentAsync(
+      const base::Callback<void(double /*percent*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "GetScreenBrightnessPercent",
+        success_callback,
+        error_callback);
+  }
+
+  // The |style| arg must be one of the values:
+  //   power_manager::kBrightnessTransitionGradual or
+  //   power_manager::kBrightnessTransitionInstant.
+  bool SetScreenBrightnessPercent(
+      double in_percent,
+      int32_t in_style,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SetScreenBrightnessPercent",
+        error,
+        in_percent,
+        in_style);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |style| arg must be one of the values:
+  //   power_manager::kBrightnessTransitionGradual or
+  //   power_manager::kBrightnessTransitionInstant.
+  void SetScreenBrightnessPercentAsync(
+      double in_percent,
+      int32_t in_style,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SetScreenBrightnessPercent",
+        success_callback,
+        error_callback,
+        in_percent,
+        in_style);
+  }
+
+  bool DecreaseKeyboardBrightness(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "DecreaseKeyboardBrightness",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void DecreaseKeyboardBrightnessAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "DecreaseKeyboardBrightness",
+        success_callback,
+        error_callback);
+  }
+
+  bool IncreaseKeyboardBrightness(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "IncreaseKeyboardBrightness",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void IncreaseKeyboardBrightnessAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "IncreaseKeyboardBrightness",
+        success_callback,
+        error_callback);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::PowerSupplyProperties protobuf.
+  bool GetPowerSupplyProperties(
+      std::vector<uint8_t>* out_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "GetPowerSupplyProperties",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_serialized_proto);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::PowerSupplyProperties protobuf.
+  void GetPowerSupplyPropertiesAsync(
+      const base::Callback<void(const std::vector<uint8_t>& /*serialized_proto*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "GetPowerSupplyProperties",
+        success_callback,
+        error_callback);
+  }
+
+  bool HandleVideoActivity(
+      bool in_fullscreen,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandleVideoActivity",
+        error,
+        in_fullscreen);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void HandleVideoActivityAsync(
+      bool in_fullscreen,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandleVideoActivity",
+        success_callback,
+        error_callback,
+        in_fullscreen);
+  }
+
+  // The |type| arg is a power_manager::UserActivityType.
+  bool HandleUserActivity(
+      int32_t in_type,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandleUserActivity",
+        error,
+        in_type);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |type| arg is a power_manager::UserActivityType.
+  void HandleUserActivityAsync(
+      int32_t in_type,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandleUserActivity",
+        success_callback,
+        error_callback,
+        in_type);
+  }
+
+  bool SetIsProjecting(
+      bool in_is_projecting,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SetIsProjecting",
+        error,
+        in_is_projecting);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void SetIsProjectingAsync(
+      bool in_is_projecting,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SetIsProjecting",
+        success_callback,
+        error_callback,
+        in_is_projecting);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::PowerManagementPolicy protobuf.
+  bool SetPolicy(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SetPolicy",
+        error,
+        in_serialized_proto);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::PowerManagementPolicy protobuf.
+  void SetPolicyAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SetPolicy",
+        success_callback,
+        error_callback,
+        in_serialized_proto);
+  }
+
+  bool SetPowerSource(
+      const std::string& in_id,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SetPowerSource",
+        error,
+        in_id);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void SetPowerSourceAsync(
+      const std::string& in_id,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "SetPowerSource",
+        success_callback,
+        error_callback,
+        in_id);
+  }
+
+  // The |timestamp_internal| arg is represented as the return value of
+  // base::TimeTicks::ToInternalValue().
+  bool HandlePowerButtonAcknowledgment(
+      int64_t in_timestamp_internal,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandlePowerButtonAcknowledgment",
+        error,
+        in_timestamp_internal);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |timestamp_internal| arg is represented as the return value of
+  // base::TimeTicks::ToInternalValue().
+  void HandlePowerButtonAcknowledgmentAsync(
+      int64_t in_timestamp_internal,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandlePowerButtonAcknowledgment",
+        success_callback,
+        error_callback,
+        in_timestamp_internal);
+  }
+
+  // The |serialized_request_proto| arg is a serialized
+  // power_manager::RegisterSuspendDelayRequest protobuf.
+  // The |serialized_reply_proto| arg is a serialized
+  // RegisterSuspendDelayReply protobuf.
+  bool RegisterSuspendDelay(
+      const std::vector<uint8_t>& in_serialized_request_proto,
+      std::vector<uint8_t>* out_serialized_reply_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RegisterSuspendDelay",
+        error,
+        in_serialized_request_proto);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_serialized_reply_proto);
+  }
+
+  // The |serialized_request_proto| arg is a serialized
+  // power_manager::RegisterSuspendDelayRequest protobuf.
+  // The |serialized_reply_proto| arg is a serialized
+  // RegisterSuspendDelayReply protobuf.
+  void RegisterSuspendDelayAsync(
+      const std::vector<uint8_t>& in_serialized_request_proto,
+      const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RegisterSuspendDelay",
+        success_callback,
+        error_callback,
+        in_serialized_request_proto);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::UnregisterSuspendDelayRequest protobuf.
+  bool UnregisterSuspendDelay(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "UnregisterSuspendDelay",
+        error,
+        in_serialized_proto);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::UnregisterSuspendDelayRequest protobuf.
+  void UnregisterSuspendDelayAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "UnregisterSuspendDelay",
+        success_callback,
+        error_callback,
+        in_serialized_proto);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::SuspendReadinessInfo protobuf.
+  bool HandleSuspendReadiness(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandleSuspendReadiness",
+        error,
+        in_serialized_proto);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::SuspendReadinessInfo protobuf.
+  void HandleSuspendReadinessAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandleSuspendReadiness",
+        success_callback,
+        error_callback,
+        in_serialized_proto);
+  }
+
+  // The |serialized_request_proto| arg is a serialized
+  // power_manager::RegisterSuspendDelayRequest protobuf.
+  // The |serialized_reply_proto| arg is a serialized
+  // RegisterSuspendDelayReply protobuf.
+  bool RegisterDarkSuspendDelay(
+      const std::vector<uint8_t>& in_serialized_request_proto,
+      std::vector<uint8_t>* out_serialized_reply_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RegisterDarkSuspendDelay",
+        error,
+        in_serialized_request_proto);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_serialized_reply_proto);
+  }
+
+  // The |serialized_request_proto| arg is a serialized
+  // power_manager::RegisterSuspendDelayRequest protobuf.
+  // The |serialized_reply_proto| arg is a serialized
+  // RegisterSuspendDelayReply protobuf.
+  void RegisterDarkSuspendDelayAsync(
+      const std::vector<uint8_t>& in_serialized_request_proto,
+      const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RegisterDarkSuspendDelay",
+        success_callback,
+        error_callback,
+        in_serialized_request_proto);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::UnregisterSuspendDelayRequest protobuf.
+  bool UnregisterDarkSuspendDelay(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "UnregisterDarkSuspendDelay",
+        error,
+        in_serialized_proto);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::UnregisterSuspendDelayRequest protobuf.
+  void UnregisterDarkSuspendDelayAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "UnregisterDarkSuspendDelay",
+        success_callback,
+        error_callback,
+        in_serialized_proto);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::SuspendReadinessInfo protobuf.
+  bool HandleDarkSuspendReadiness(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandleDarkSuspendReadiness",
+        error,
+        in_serialized_proto);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::SuspendReadinessInfo protobuf.
+  void HandleDarkSuspendReadinessAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "HandleDarkSuspendReadiness",
+        success_callback,
+        error_callback,
+        in_serialized_proto);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::DarkResumeWakeReason protobuf.
+  bool RecordDarkResumeWakeReason(
+      const std::vector<uint8_t>& in_serialized_proto,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RecordDarkResumeWakeReason",
+        error,
+        in_serialized_proto);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  // The |serialized_proto| arg is a serialized
+  // power_manager::DarkResumeWakeReason protobuf.
+  void RecordDarkResumeWakeReasonAsync(
+      const std::vector<uint8_t>& in_serialized_proto,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.PowerManager",
+        "RecordDarkResumeWakeReason",
+        success_callback,
+        error_callback,
+        in_serialized_proto);
+  }
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  const std::string service_name_{"org.chromium.PowerManager"};
+  const dbus::ObjectPath object_path_{"/org/chromium/PowerManager"};
+  dbus::ObjectProxy* dbus_object_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(PowerManagerProxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+
+#endif  // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXIES_H
diff --git a/update_engine/include/power_manager/dbus-proxy-mocks.h b/update_engine/include/power_manager/dbus-proxy-mocks.h
new file mode 100644
index 0000000..d4e3dd0
--- /dev/null
+++ b/update_engine/include/power_manager/dbus-proxy-mocks.h
@@ -0,0 +1,266 @@
+// Automatic generation of D-Bus interface mock proxies for:
+//  - org.chromium.PowerManager
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXY_MOCKS_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXY_MOCKS_H
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/any.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+
+#include "power_manager/dbus-proxies.h"
+
+namespace org {
+namespace chromium {
+
+// Mock object for PowerManagerProxyInterface.
+class PowerManagerProxyMock : public PowerManagerProxyInterface {
+ public:
+  PowerManagerProxyMock() = default;
+
+  MOCK_METHOD2(RequestShutdown,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RequestShutdownAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RequestRestart,
+               bool(int32_t /*in_reason*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RequestRestartAsync,
+               void(int32_t /*in_reason*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RequestSuspend,
+               bool(uint64_t /*in_external_wakeup_count*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RequestSuspendAsync,
+               void(uint64_t /*in_external_wakeup_count*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(DecreaseScreenBrightness,
+               bool(bool /*in_allow_off*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(DecreaseScreenBrightnessAsync,
+               void(bool /*in_allow_off*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(IncreaseScreenBrightness,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(IncreaseScreenBrightnessAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetScreenBrightnessPercent,
+               bool(double* /*out_percent*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetScreenBrightnessPercentAsync,
+               void(const base::Callback<void(double /*percent*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetScreenBrightnessPercent,
+               bool(double /*in_percent*/,
+                    int32_t /*in_style*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(SetScreenBrightnessPercentAsync,
+               void(double /*in_percent*/,
+                    int32_t /*in_style*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(DecreaseKeyboardBrightness,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(DecreaseKeyboardBrightnessAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(IncreaseKeyboardBrightness,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(IncreaseKeyboardBrightnessAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetPowerSupplyProperties,
+               bool(std::vector<uint8_t>* /*out_serialized_proto*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetPowerSupplyPropertiesAsync,
+               void(const base::Callback<void(const std::vector<uint8_t>& /*serialized_proto*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(HandleVideoActivity,
+               bool(bool /*in_fullscreen*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(HandleVideoActivityAsync,
+               void(bool /*in_fullscreen*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(HandleUserActivity,
+               bool(int32_t /*in_type*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(HandleUserActivityAsync,
+               void(int32_t /*in_type*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetIsProjecting,
+               bool(bool /*in_is_projecting*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetIsProjectingAsync,
+               void(bool /*in_is_projecting*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetPolicy,
+               bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetPolicyAsync,
+               void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetPowerSource,
+               bool(const std::string& /*in_id*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetPowerSourceAsync,
+               void(const std::string& /*in_id*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(HandlePowerButtonAcknowledgment,
+               bool(int64_t /*in_timestamp_internal*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(HandlePowerButtonAcknowledgmentAsync,
+               void(int64_t /*in_timestamp_internal*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RegisterSuspendDelay,
+               bool(const std::vector<uint8_t>& /*in_serialized_request_proto*/,
+                    std::vector<uint8_t>* /*out_serialized_reply_proto*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RegisterSuspendDelayAsync,
+               void(const std::vector<uint8_t>& /*in_serialized_request_proto*/,
+                    const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(UnregisterSuspendDelay,
+               bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(UnregisterSuspendDelayAsync,
+               void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(HandleSuspendReadiness,
+               bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(HandleSuspendReadinessAsync,
+               void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RegisterDarkSuspendDelay,
+               bool(const std::vector<uint8_t>& /*in_serialized_request_proto*/,
+                    std::vector<uint8_t>* /*out_serialized_reply_proto*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RegisterDarkSuspendDelayAsync,
+               void(const std::vector<uint8_t>& /*in_serialized_request_proto*/,
+                    const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(UnregisterDarkSuspendDelay,
+               bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(UnregisterDarkSuspendDelayAsync,
+               void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(HandleDarkSuspendReadiness,
+               bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(HandleDarkSuspendReadinessAsync,
+               void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RecordDarkResumeWakeReason,
+               bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RecordDarkResumeWakeReasonAsync,
+               void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(RegisterBrightnessChangedSignalHandler,
+               void(const base::Callback<void(int32_t,
+                                              bool)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterKeyboardBrightnessChangedSignalHandler,
+               void(const base::Callback<void(int32_t,
+                                              bool)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterPeripheralBatteryStatusSignalHandler,
+               void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterPowerSupplyPollSignalHandler,
+               void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterSuspendImminentSignalHandler,
+               void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterSuspendDoneSignalHandler,
+               void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterDarkSuspendImminentSignalHandler,
+               void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterInputEventSignalHandler,
+               void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterIdleActionImminentSignalHandler,
+               void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterIdleActionDeferredSignalHandler,
+               void(const base::Closure& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PowerManagerProxyMock);
+};
+}  // namespace chromium
+}  // namespace org
+
+#endif  // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXY_MOCKS_H
diff --git a/update_engine/include/session_manager/dbus-proxies.h b/update_engine/include/session_manager/dbus-proxies.h
new file mode 100644
index 0000000..2ca0128
--- /dev/null
+++ b/update_engine/include/session_manager/dbus-proxies.h
@@ -0,0 +1,1065 @@
+// Automatic generation of D-Bus interfaces:
+//  - org.chromium.SessionManagerInterface
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXIES_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXIES_H
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <brillo/any.h>
+#include <brillo/dbus/dbus_method_invoker.h>
+#include <brillo/dbus/dbus_property.h>
+#include <brillo/dbus/dbus_signal_handler.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+
+// Abstract interface proxy for org::chromium::SessionManagerInterface.
+class SessionManagerInterfaceProxyInterface {
+ public:
+  virtual ~SessionManagerInterfaceProxyInterface() = default;
+
+  virtual bool EmitLoginPromptVisible(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void EmitLoginPromptVisibleAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool EnableChromeTesting(
+      bool in_force_relaunch,
+      const std::vector<std::string>& in_extra_arguments,
+      std::string* out_filepath,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void EnableChromeTestingAsync(
+      bool in_force_relaunch,
+      const std::vector<std::string>& in_extra_arguments,
+      const base::Callback<void(const std::string& /*filepath*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool StartSession(
+      const std::string& in_email_address,
+      const std::string& in_unique_identifier,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void StartSessionAsync(
+      const std::string& in_email_address,
+      const std::string& in_unique_identifier,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool StopSession(
+      const std::string& in_unique_identifier,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void StopSessionAsync(
+      const std::string& in_unique_identifier,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool StorePolicy(
+      const std::vector<uint8_t>& in_policy_blob,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void StorePolicyAsync(
+      const std::vector<uint8_t>& in_policy_blob,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool RetrievePolicy(
+      std::vector<uint8_t>* out_policy_blob,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void RetrievePolicyAsync(
+      const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool StorePolicyForUser(
+      const std::string& in_user_email,
+      const std::vector<uint8_t>& in_policy_blob,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void StorePolicyForUserAsync(
+      const std::string& in_user_email,
+      const std::vector<uint8_t>& in_policy_blob,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool RetrievePolicyForUser(
+      const std::string& in_user_email,
+      std::vector<uint8_t>* out_policy_blob,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void RetrievePolicyForUserAsync(
+      const std::string& in_user_email,
+      const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool StoreDeviceLocalAccountPolicy(
+      const std::string& in_account_id,
+      const std::vector<uint8_t>& in_policy_blob,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void StoreDeviceLocalAccountPolicyAsync(
+      const std::string& in_account_id,
+      const std::vector<uint8_t>& in_policy_blob,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool RetrieveDeviceLocalAccountPolicy(
+      const std::string& in_account_id,
+      std::vector<uint8_t>* out_policy_blob,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void RetrieveDeviceLocalAccountPolicyAsync(
+      const std::string& in_account_id,
+      const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool RetrieveSessionState(
+      std::string* out_state,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void RetrieveSessionStateAsync(
+      const base::Callback<void(const std::string& /*state*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool RetrieveActiveSessions(
+      std::map<std::string, std::string>* out_sessions,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void RetrieveActiveSessionsAsync(
+      const base::Callback<void(const std::map<std::string, std::string>& /*sessions*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool HandleSupervisedUserCreationStarting(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void HandleSupervisedUserCreationStartingAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool HandleSupervisedUserCreationFinished(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void HandleSupervisedUserCreationFinishedAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool LockScreen(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void LockScreenAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool HandleLockScreenShown(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void HandleLockScreenShownAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool HandleLockScreenDismissed(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void HandleLockScreenDismissedAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool RestartJob(
+      const dbus::FileDescriptor& in_cred_fd,
+      const std::vector<std::string>& in_argv,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void RestartJobAsync(
+      const dbus::FileDescriptor& in_cred_fd,
+      const std::vector<std::string>& in_argv,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool StartDeviceWipe(
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void StartDeviceWipeAsync(
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool SetFlagsForUser(
+      const std::string& in_user_email,
+      const std::vector<std::string>& in_flags,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void SetFlagsForUserAsync(
+      const std::string& in_user_email,
+      const std::vector<std::string>& in_flags,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool GetServerBackedStateKeys(
+      std::vector<std::vector<uint8_t>>* out_state_keys,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void GetServerBackedStateKeysAsync(
+      const base::Callback<void(const std::vector<std::vector<uint8_t>>& /*state_keys*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual bool InitMachineInfo(
+      const std::string& in_data,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void InitMachineInfoAsync(
+      const std::string& in_data,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+  virtual void RegisterLoginPromptVisibleSignalHandler(
+      const base::Closure& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterSessionStateChangedSignalHandler(
+      const base::Callback<void(const std::string&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterSetOwnerKeyCompleteSignalHandler(
+      const base::Callback<void(const std::string&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterPropertyChangeCompleteSignalHandler(
+      const base::Callback<void(const std::string&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterScreenIsLockedSignalHandler(
+      const base::Closure& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+  virtual void RegisterScreenIsUnlockedSignalHandler(
+      const base::Closure& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+};
+
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::SessionManagerInterface.
+class SessionManagerInterfaceProxy final : public SessionManagerInterfaceProxyInterface {
+ public:
+  SessionManagerInterfaceProxy(const scoped_refptr<dbus::Bus>& bus) :
+      bus_{bus},
+      dbus_object_proxy_{
+          bus_->GetObjectProxy(service_name_, object_path_)} {
+  }
+
+  ~SessionManagerInterfaceProxy() override {
+    bus_->RemoveObjectProxy(
+        service_name_, object_path_, base::Bind(&base::DoNothing));
+  }
+
+  void RegisterLoginPromptVisibleSignalHandler(
+      const base::Closure& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "LoginPromptVisible",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterSessionStateChangedSignalHandler(
+      const base::Callback<void(const std::string&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "SessionStateChanged",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterSetOwnerKeyCompleteSignalHandler(
+      const base::Callback<void(const std::string&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "SetOwnerKeyComplete",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterPropertyChangeCompleteSignalHandler(
+      const base::Callback<void(const std::string&)>& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "PropertyChangeComplete",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterScreenIsLockedSignalHandler(
+      const base::Closure& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "ScreenIsLocked",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void RegisterScreenIsUnlockedSignalHandler(
+      const base::Closure& signal_callback,
+      dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+    brillo::dbus_utils::ConnectToSignal(
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "ScreenIsUnlocked",
+        signal_callback,
+        on_connected_callback);
+  }
+
+  void ReleaseObjectProxy(const base::Closure& callback) {
+    bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+  }
+
+  const dbus::ObjectPath& GetObjectPath() const {
+    return object_path_;
+  }
+
+  dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+  bool EmitLoginPromptVisible(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "EmitLoginPromptVisible",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void EmitLoginPromptVisibleAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "EmitLoginPromptVisible",
+        success_callback,
+        error_callback);
+  }
+
+  bool EnableChromeTesting(
+      bool in_force_relaunch,
+      const std::vector<std::string>& in_extra_arguments,
+      std::string* out_filepath,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "EnableChromeTesting",
+        error,
+        in_force_relaunch,
+        in_extra_arguments);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_filepath);
+  }
+
+  void EnableChromeTestingAsync(
+      bool in_force_relaunch,
+      const std::vector<std::string>& in_extra_arguments,
+      const base::Callback<void(const std::string& /*filepath*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "EnableChromeTesting",
+        success_callback,
+        error_callback,
+        in_force_relaunch,
+        in_extra_arguments);
+  }
+
+  bool StartSession(
+      const std::string& in_email_address,
+      const std::string& in_unique_identifier,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StartSession",
+        error,
+        in_email_address,
+        in_unique_identifier);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_done);
+  }
+
+  void StartSessionAsync(
+      const std::string& in_email_address,
+      const std::string& in_unique_identifier,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StartSession",
+        success_callback,
+        error_callback,
+        in_email_address,
+        in_unique_identifier);
+  }
+
+  bool StopSession(
+      const std::string& in_unique_identifier,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StopSession",
+        error,
+        in_unique_identifier);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_done);
+  }
+
+  void StopSessionAsync(
+      const std::string& in_unique_identifier,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StopSession",
+        success_callback,
+        error_callback,
+        in_unique_identifier);
+  }
+
+  bool StorePolicy(
+      const std::vector<uint8_t>& in_policy_blob,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StorePolicy",
+        error,
+        in_policy_blob);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_done);
+  }
+
+  void StorePolicyAsync(
+      const std::vector<uint8_t>& in_policy_blob,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StorePolicy",
+        success_callback,
+        error_callback,
+        in_policy_blob);
+  }
+
+  bool RetrievePolicy(
+      std::vector<uint8_t>* out_policy_blob,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrievePolicy",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_policy_blob);
+  }
+
+  void RetrievePolicyAsync(
+      const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrievePolicy",
+        success_callback,
+        error_callback);
+  }
+
+  bool StorePolicyForUser(
+      const std::string& in_user_email,
+      const std::vector<uint8_t>& in_policy_blob,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StorePolicyForUser",
+        error,
+        in_user_email,
+        in_policy_blob);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_done);
+  }
+
+  void StorePolicyForUserAsync(
+      const std::string& in_user_email,
+      const std::vector<uint8_t>& in_policy_blob,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StorePolicyForUser",
+        success_callback,
+        error_callback,
+        in_user_email,
+        in_policy_blob);
+  }
+
+  bool RetrievePolicyForUser(
+      const std::string& in_user_email,
+      std::vector<uint8_t>* out_policy_blob,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrievePolicyForUser",
+        error,
+        in_user_email);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_policy_blob);
+  }
+
+  void RetrievePolicyForUserAsync(
+      const std::string& in_user_email,
+      const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrievePolicyForUser",
+        success_callback,
+        error_callback,
+        in_user_email);
+  }
+
+  bool StoreDeviceLocalAccountPolicy(
+      const std::string& in_account_id,
+      const std::vector<uint8_t>& in_policy_blob,
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StoreDeviceLocalAccountPolicy",
+        error,
+        in_account_id,
+        in_policy_blob);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_done);
+  }
+
+  void StoreDeviceLocalAccountPolicyAsync(
+      const std::string& in_account_id,
+      const std::vector<uint8_t>& in_policy_blob,
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StoreDeviceLocalAccountPolicy",
+        success_callback,
+        error_callback,
+        in_account_id,
+        in_policy_blob);
+  }
+
+  bool RetrieveDeviceLocalAccountPolicy(
+      const std::string& in_account_id,
+      std::vector<uint8_t>* out_policy_blob,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrieveDeviceLocalAccountPolicy",
+        error,
+        in_account_id);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_policy_blob);
+  }
+
+  void RetrieveDeviceLocalAccountPolicyAsync(
+      const std::string& in_account_id,
+      const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrieveDeviceLocalAccountPolicy",
+        success_callback,
+        error_callback,
+        in_account_id);
+  }
+
+  bool RetrieveSessionState(
+      std::string* out_state,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrieveSessionState",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_state);
+  }
+
+  void RetrieveSessionStateAsync(
+      const base::Callback<void(const std::string& /*state*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrieveSessionState",
+        success_callback,
+        error_callback);
+  }
+
+  bool RetrieveActiveSessions(
+      std::map<std::string, std::string>* out_sessions,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrieveActiveSessions",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_sessions);
+  }
+
+  void RetrieveActiveSessionsAsync(
+      const base::Callback<void(const std::map<std::string, std::string>& /*sessions*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RetrieveActiveSessions",
+        success_callback,
+        error_callback);
+  }
+
+  bool HandleSupervisedUserCreationStarting(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "HandleSupervisedUserCreationStarting",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void HandleSupervisedUserCreationStartingAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "HandleSupervisedUserCreationStarting",
+        success_callback,
+        error_callback);
+  }
+
+  bool HandleSupervisedUserCreationFinished(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "HandleSupervisedUserCreationFinished",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void HandleSupervisedUserCreationFinishedAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "HandleSupervisedUserCreationFinished",
+        success_callback,
+        error_callback);
+  }
+
+  bool LockScreen(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "LockScreen",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void LockScreenAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "LockScreen",
+        success_callback,
+        error_callback);
+  }
+
+  bool HandleLockScreenShown(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "HandleLockScreenShown",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void HandleLockScreenShownAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "HandleLockScreenShown",
+        success_callback,
+        error_callback);
+  }
+
+  bool HandleLockScreenDismissed(
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "HandleLockScreenDismissed",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void HandleLockScreenDismissedAsync(
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "HandleLockScreenDismissed",
+        success_callback,
+        error_callback);
+  }
+
+  bool RestartJob(
+      const dbus::FileDescriptor& in_cred_fd,
+      const std::vector<std::string>& in_argv,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RestartJob",
+        error,
+        in_cred_fd,
+        in_argv);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void RestartJobAsync(
+      const dbus::FileDescriptor& in_cred_fd,
+      const std::vector<std::string>& in_argv,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "RestartJob",
+        success_callback,
+        error_callback,
+        in_cred_fd,
+        in_argv);
+  }
+
+  bool StartDeviceWipe(
+      bool* out_done,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StartDeviceWipe",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_done);
+  }
+
+  void StartDeviceWipeAsync(
+      const base::Callback<void(bool /*done*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "StartDeviceWipe",
+        success_callback,
+        error_callback);
+  }
+
+  bool SetFlagsForUser(
+      const std::string& in_user_email,
+      const std::vector<std::string>& in_flags,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "SetFlagsForUser",
+        error,
+        in_user_email,
+        in_flags);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void SetFlagsForUserAsync(
+      const std::string& in_user_email,
+      const std::vector<std::string>& in_flags,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "SetFlagsForUser",
+        success_callback,
+        error_callback,
+        in_user_email,
+        in_flags);
+  }
+
+  bool GetServerBackedStateKeys(
+      std::vector<std::vector<uint8_t>>* out_state_keys,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "GetServerBackedStateKeys",
+        error);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error, out_state_keys);
+  }
+
+  void GetServerBackedStateKeysAsync(
+      const base::Callback<void(const std::vector<std::vector<uint8_t>>& /*state_keys*/)>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "GetServerBackedStateKeys",
+        success_callback,
+        error_callback);
+  }
+
+  bool InitMachineInfo(
+      const std::string& in_data,
+      brillo::ErrorPtr* error,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "InitMachineInfo",
+        error,
+        in_data);
+    return response && brillo::dbus_utils::ExtractMethodCallResults(
+        response.get(), error);
+  }
+
+  void InitMachineInfoAsync(
+      const std::string& in_data,
+      const base::Callback<void()>& success_callback,
+      const base::Callback<void(brillo::Error*)>& error_callback,
+      int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+    brillo::dbus_utils::CallMethodWithTimeout(
+        timeout_ms,
+        dbus_object_proxy_,
+        "org.chromium.SessionManagerInterface",
+        "InitMachineInfo",
+        success_callback,
+        error_callback,
+        in_data);
+  }
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  const std::string service_name_{"org.chromium.SessionManager"};
+  const dbus::ObjectPath object_path_{"/org/chromium/SessionManager"};
+  dbus::ObjectProxy* dbus_object_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(SessionManagerInterfaceProxy);
+};
+
+}  // namespace chromium
+}  // namespace org
+
+#endif  // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXIES_H
diff --git a/update_engine/include/session_manager/dbus-proxy-mocks.h b/update_engine/include/session_manager/dbus-proxy-mocks.h
new file mode 100644
index 0000000..2b6ce4d
--- /dev/null
+++ b/update_engine/include/session_manager/dbus-proxy-mocks.h
@@ -0,0 +1,252 @@
+// Automatic generation of D-Bus interface mock proxies for:
+//  - org.chromium.SessionManagerInterface
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXY_MOCKS_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXY_MOCKS_H
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/any.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+
+#include "session_manager/dbus-proxies.h"
+
+namespace org {
+namespace chromium {
+
+// Mock object for SessionManagerInterfaceProxyInterface.
+class SessionManagerInterfaceProxyMock : public SessionManagerInterfaceProxyInterface {
+ public:
+  SessionManagerInterfaceProxyMock() = default;
+
+  MOCK_METHOD2(EmitLoginPromptVisible,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(EmitLoginPromptVisibleAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(EnableChromeTesting,
+               bool(bool /*in_force_relaunch*/,
+                    const std::vector<std::string>& /*in_extra_arguments*/,
+                    std::string* /*out_filepath*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(EnableChromeTestingAsync,
+               void(bool /*in_force_relaunch*/,
+                    const std::vector<std::string>& /*in_extra_arguments*/,
+                    const base::Callback<void(const std::string& /*filepath*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(StartSession,
+               bool(const std::string& /*in_email_address*/,
+                    const std::string& /*in_unique_identifier*/,
+                    bool* /*out_done*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(StartSessionAsync,
+               void(const std::string& /*in_email_address*/,
+                    const std::string& /*in_unique_identifier*/,
+                    const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(StopSession,
+               bool(const std::string& /*in_unique_identifier*/,
+                    bool* /*out_done*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(StopSessionAsync,
+               void(const std::string& /*in_unique_identifier*/,
+                    const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(StorePolicy,
+               bool(const std::vector<uint8_t>& /*in_policy_blob*/,
+                    bool* /*out_done*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(StorePolicyAsync,
+               void(const std::vector<uint8_t>& /*in_policy_blob*/,
+                    const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RetrievePolicy,
+               bool(std::vector<uint8_t>* /*out_policy_blob*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RetrievePolicyAsync,
+               void(const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(StorePolicyForUser,
+               bool(const std::string& /*in_user_email*/,
+                    const std::vector<uint8_t>& /*in_policy_blob*/,
+                    bool* /*out_done*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(StorePolicyForUserAsync,
+               void(const std::string& /*in_user_email*/,
+                    const std::vector<uint8_t>& /*in_policy_blob*/,
+                    const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RetrievePolicyForUser,
+               bool(const std::string& /*in_user_email*/,
+                    std::vector<uint8_t>* /*out_policy_blob*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RetrievePolicyForUserAsync,
+               void(const std::string& /*in_user_email*/,
+                    const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(StoreDeviceLocalAccountPolicy,
+               bool(const std::string& /*in_account_id*/,
+                    const std::vector<uint8_t>& /*in_policy_blob*/,
+                    bool* /*out_done*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(StoreDeviceLocalAccountPolicyAsync,
+               void(const std::string& /*in_account_id*/,
+                    const std::vector<uint8_t>& /*in_policy_blob*/,
+                    const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RetrieveDeviceLocalAccountPolicy,
+               bool(const std::string& /*in_account_id*/,
+                    std::vector<uint8_t>* /*out_policy_blob*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RetrieveDeviceLocalAccountPolicyAsync,
+               void(const std::string& /*in_account_id*/,
+                    const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RetrieveSessionState,
+               bool(std::string* /*out_state*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RetrieveSessionStateAsync,
+               void(const base::Callback<void(const std::string& /*state*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RetrieveActiveSessions,
+               bool(std::map<std::string, std::string>* /*out_sessions*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RetrieveActiveSessionsAsync,
+               void(const base::Callback<void(const std::map<std::string, std::string>& /*sessions*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(HandleSupervisedUserCreationStarting,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(HandleSupervisedUserCreationStartingAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(HandleSupervisedUserCreationFinished,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(HandleSupervisedUserCreationFinishedAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(LockScreen,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(LockScreenAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(HandleLockScreenShown,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(HandleLockScreenShownAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(HandleLockScreenDismissed,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(HandleLockScreenDismissedAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RestartJob,
+               bool(const dbus::FileDescriptor& /*in_cred_fd*/,
+                    const std::vector<std::string>& /*in_argv*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(RestartJobAsync,
+               void(const dbus::FileDescriptor& /*in_cred_fd*/,
+                    const std::vector<std::string>& /*in_argv*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(StartDeviceWipe,
+               bool(bool* /*out_done*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(StartDeviceWipeAsync,
+               void(const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetFlagsForUser,
+               bool(const std::string& /*in_user_email*/,
+                    const std::vector<std::string>& /*in_flags*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(SetFlagsForUserAsync,
+               void(const std::string& /*in_user_email*/,
+                    const std::vector<std::string>& /*in_flags*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetServerBackedStateKeys,
+               bool(std::vector<std::vector<uint8_t>>* /*out_state_keys*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetServerBackedStateKeysAsync,
+               void(const base::Callback<void(const std::vector<std::vector<uint8_t>>& /*state_keys*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(InitMachineInfo,
+               bool(const std::string& /*in_data*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(InitMachineInfoAsync,
+               void(const std::string& /*in_data*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(RegisterLoginPromptVisibleSignalHandler,
+               void(const base::Closure& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterSessionStateChangedSignalHandler,
+               void(const base::Callback<void(const std::string&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterSetOwnerKeyCompleteSignalHandler,
+               void(const base::Callback<void(const std::string&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterPropertyChangeCompleteSignalHandler,
+               void(const base::Callback<void(const std::string&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterScreenIsLockedSignalHandler,
+               void(const base::Closure& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterScreenIsUnlockedSignalHandler,
+               void(const base::Closure& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SessionManagerInterfaceProxyMock);
+};
+}  // namespace chromium
+}  // namespace org
+
+#endif  // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXY_MOCKS_H
diff --git a/update_engine/include/shill/dbus-proxy-mocks.h b/update_engine/include/shill/dbus-proxy-mocks.h
new file mode 100644
index 0000000..e5d52f7
--- /dev/null
+++ b/update_engine/include/shill/dbus-proxy-mocks.h
@@ -0,0 +1,549 @@
+// Automatic generation of D-Bus interface mock proxies for:
+//  - org.chromium.flimflam.Manager
+//  - org.chromium.flimflam.Service
+#ifndef ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_SHILL_DBUS_PROXY_MOCKS_H
+#define ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_SHILL_DBUS_PROXY_MOCKS_H
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/any.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+
+#include "shill/dbus-proxies.h"
+
+namespace org {
+namespace chromium {
+namespace flimflam {
+
+// Mock object for ManagerProxyInterface.
+class ManagerProxyMock : public ManagerProxyInterface {
+ public:
+  ManagerProxyMock() = default;
+
+  MOCK_METHOD3(GetProperties,
+               bool(brillo::VariantDictionary*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetPropertiesAsync,
+               void(const base::Callback<void(const brillo::VariantDictionary&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetProperty,
+               bool(const std::string&,
+                    const brillo::Any&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(SetPropertyAsync,
+               void(const std::string&,
+                    const brillo::Any&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetState,
+               bool(std::string*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetStateAsync,
+               void(const base::Callback<void(const std::string&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(CreateProfile,
+               bool(const std::string&,
+                    dbus::ObjectPath*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(CreateProfileAsync,
+               void(const std::string&,
+                    const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RemoveProfile,
+               bool(const std::string&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RemoveProfileAsync,
+               void(const std::string&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(PushProfile,
+               bool(const std::string&,
+                    dbus::ObjectPath*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(PushProfileAsync,
+               void(const std::string&,
+                    const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(InsertUserProfile,
+               bool(const std::string&,
+                    const std::string&,
+                    dbus::ObjectPath*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(InsertUserProfileAsync,
+               void(const std::string&,
+                    const std::string&,
+                    const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(PopProfile,
+               bool(const std::string&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(PopProfileAsync,
+               void(const std::string&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(PopAnyProfile,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(PopAnyProfileAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(PopAllUserProfiles,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(PopAllUserProfilesAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(RecheckPortal,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RecheckPortalAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RequestScan,
+               bool(const std::string&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(RequestScanAsync,
+               void(const std::string&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(EnableTechnology,
+               bool(const std::string&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(EnableTechnologyAsync,
+               void(const std::string&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(DisableTechnology,
+               bool(const std::string&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(DisableTechnologyAsync,
+               void(const std::string&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetService,
+               bool(const brillo::VariantDictionary&,
+                    dbus::ObjectPath*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetServiceAsync,
+               void(const brillo::VariantDictionary&,
+                    const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetWifiService,
+               bool(const brillo::VariantDictionary&,
+                    dbus::ObjectPath*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetWifiServiceAsync,
+               void(const brillo::VariantDictionary&,
+                    const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(ConfigureService,
+               bool(const brillo::VariantDictionary&,
+                    dbus::ObjectPath*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(ConfigureServiceAsync,
+               void(const brillo::VariantDictionary&,
+                    const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(ConfigureServiceForProfile,
+               bool(const dbus::ObjectPath&,
+                    const brillo::VariantDictionary&,
+                    dbus::ObjectPath*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(ConfigureServiceForProfileAsync,
+               void(const dbus::ObjectPath&,
+                    const brillo::VariantDictionary&,
+                    const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(FindMatchingService,
+               bool(const brillo::VariantDictionary&,
+                    dbus::ObjectPath*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(FindMatchingServiceAsync,
+               void(const brillo::VariantDictionary&,
+                    const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetVPNService,
+               bool(const brillo::VariantDictionary&,
+                    dbus::ObjectPath*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(GetVPNServiceAsync,
+               void(const brillo::VariantDictionary&,
+                    const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetDebugLevel,
+               bool(int32_t*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetDebugLevelAsync,
+               void(const base::Callback<void(int32_t)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetDebugLevel,
+               bool(int32_t,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetDebugLevelAsync,
+               void(int32_t,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetServiceOrder,
+               bool(std::string*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetServiceOrderAsync,
+               void(const base::Callback<void(const std::string&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetServiceOrder,
+               bool(const std::string&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetServiceOrderAsync,
+               void(const std::string&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetDebugTags,
+               bool(std::string*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetDebugTagsAsync,
+               void(const base::Callback<void(const std::string&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetDebugTags,
+               bool(const std::string&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetDebugTagsAsync,
+               void(const std::string&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(ListDebugTags,
+               bool(std::string*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(ListDebugTagsAsync,
+               void(const base::Callback<void(const std::string&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetNetworksForGeolocation,
+               bool(brillo::VariantDictionary*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetNetworksForGeolocationAsync,
+               void(const base::Callback<void(const brillo::VariantDictionary&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD10(VerifyDestination,
+                bool(const std::string& /*in_certificate*/,
+                     const std::string& /*in_public_key*/,
+                     const std::string& /*in_nonce*/,
+                     const std::string& /*in_signed_data*/,
+                     const std::string& /*in_destination_udn*/,
+                     const std::string& /*in_hotspot_ssid*/,
+                     const std::string& /*in_hotspot_bssid*/,
+                     bool*,
+                     brillo::ErrorPtr* /*error*/,
+                     int /*timeout_ms*/));
+  MOCK_METHOD10(VerifyDestinationAsync,
+                void(const std::string& /*in_certificate*/,
+                     const std::string& /*in_public_key*/,
+                     const std::string& /*in_nonce*/,
+                     const std::string& /*in_signed_data*/,
+                     const std::string& /*in_destination_udn*/,
+                     const std::string& /*in_hotspot_ssid*/,
+                     const std::string& /*in_hotspot_bssid*/,
+                     const base::Callback<void(bool)>& /*success_callback*/,
+                     const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                     int /*timeout_ms*/));
+  bool VerifyAndEncryptCredentials(const std::string& /*in_certificate*/,
+                                   const std::string& /*in_public_key*/,
+                                   const std::string& /*in_nonce*/,
+                                   const std::string& /*in_signed_data*/,
+                                   const std::string& /*in_destination_udn*/,
+                                   const std::string& /*in_hotspot_ssid*/,
+                                   const std::string& /*in_hotspot_bssid*/,
+                                   const dbus::ObjectPath& /*in_network*/,
+                                   std::string*,
+                                   brillo::ErrorPtr* /*error*/,
+                                   int /*timeout_ms*/) override {
+    LOG(WARNING) << "VerifyAndEncryptCredentials(): gmock can't handle methods with 11 arguments. You can override this method in a subclass if you need to.";
+    return false;
+  }
+  void VerifyAndEncryptCredentialsAsync(const std::string& /*in_certificate*/,
+                                        const std::string& /*in_public_key*/,
+                                        const std::string& /*in_nonce*/,
+                                        const std::string& /*in_signed_data*/,
+                                        const std::string& /*in_destination_udn*/,
+                                        const std::string& /*in_hotspot_ssid*/,
+                                        const std::string& /*in_hotspot_bssid*/,
+                                        const dbus::ObjectPath& /*in_network*/,
+                                        const base::Callback<void(const std::string&)>& /*success_callback*/,
+                                        const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                                        int /*timeout_ms*/) override {
+    LOG(WARNING) << "VerifyAndEncryptCredentialsAsync(): gmock can't handle methods with 11 arguments. You can override this method in a subclass if you need to.";
+  }
+  bool VerifyAndEncryptData(const std::string& /*in_certificate*/,
+                            const std::string& /*in_public_key*/,
+                            const std::string& /*in_nonce*/,
+                            const std::string& /*in_signed_data*/,
+                            const std::string& /*in_destination_udn*/,
+                            const std::string& /*in_hotspot_ssid*/,
+                            const std::string& /*in_hotspot_bssid*/,
+                            const std::string& /*in_data*/,
+                            std::string*,
+                            brillo::ErrorPtr* /*error*/,
+                            int /*timeout_ms*/) override {
+    LOG(WARNING) << "VerifyAndEncryptData(): gmock can't handle methods with 11 arguments. You can override this method in a subclass if you need to.";
+    return false;
+  }
+  void VerifyAndEncryptDataAsync(const std::string& /*in_certificate*/,
+                                 const std::string& /*in_public_key*/,
+                                 const std::string& /*in_nonce*/,
+                                 const std::string& /*in_signed_data*/,
+                                 const std::string& /*in_destination_udn*/,
+                                 const std::string& /*in_hotspot_ssid*/,
+                                 const std::string& /*in_hotspot_bssid*/,
+                                 const std::string& /*in_data*/,
+                                 const base::Callback<void(const std::string&)>& /*success_callback*/,
+                                 const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                                 int /*timeout_ms*/) override {
+    LOG(WARNING) << "VerifyAndEncryptDataAsync(): gmock can't handle methods with 11 arguments. You can override this method in a subclass if you need to.";
+  }
+  MOCK_METHOD2(ConnectToBestServices,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(ConnectToBestServicesAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(CreateConnectivityReport,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(CreateConnectivityReportAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(ClaimInterface,
+               bool(const std::string& /*in_claimer_name*/,
+                    const std::string& /*in_interface_name*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(ClaimInterfaceAsync,
+               void(const std::string& /*in_claimer_name*/,
+                    const std::string& /*in_interface_name*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(ReleaseInterface,
+               bool(const std::string& /*in_claimer_name*/,
+                    const std::string& /*in_interface_name*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(ReleaseInterfaceAsync,
+               void(const std::string& /*in_claimer_name*/,
+                    const std::string& /*in_interface_name*/,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetSchedScan,
+               bool(bool,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetSchedScanAsync,
+               void(bool,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetupApModeInterface,
+               bool(std::string* /*out_interface_name*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetupApModeInterfaceAsync,
+               void(const base::Callback<void(const std::string& /*interface_name*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetupStationModeInterface,
+               bool(std::string* /*out_interface_name*/,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetupStationModeInterfaceAsync,
+               void(const base::Callback<void(const std::string& /*interface_name*/)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(RegisterPropertyChangedSignalHandler,
+               void(const base::Callback<void(const std::string&,
+                                              const brillo::Any&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_METHOD2(RegisterStateChangedSignalHandler,
+               void(const base::Callback<void(const std::string&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_CONST_METHOD0(GetObjectPath, const dbus::ObjectPath&());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ManagerProxyMock);
+};
+}  // namespace flimflam
+}  // namespace chromium
+}  // namespace org
+
+namespace org {
+namespace chromium {
+namespace flimflam {
+
+// Mock object for ServiceProxyInterface.
+class ServiceProxyMock : public ServiceProxyInterface {
+ public:
+  ServiceProxyMock() = default;
+
+  MOCK_METHOD3(GetProperties,
+               bool(brillo::VariantDictionary*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetPropertiesAsync,
+               void(const base::Callback<void(const brillo::VariantDictionary&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetProperty,
+               bool(const std::string&,
+                    const brillo::Any&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD5(SetPropertyAsync,
+               void(const std::string&,
+                    const brillo::Any&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(SetProperties,
+               bool(const brillo::VariantDictionary&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(SetPropertiesAsync,
+               void(const brillo::VariantDictionary&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(ClearProperty,
+               bool(const std::string&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(ClearPropertyAsync,
+               void(const std::string&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(ClearProperties,
+               bool(const std::vector<std::string>&,
+                    std::vector<bool>*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(ClearPropertiesAsync,
+               void(const std::vector<std::string>&,
+                    const base::Callback<void(const std::vector<bool>&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(Connect,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(ConnectAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(Disconnect,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(DisconnectAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(Remove,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(RemoveAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(ActivateCellularModem,
+               bool(const std::string&,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD4(ActivateCellularModemAsync,
+               void(const std::string&,
+                    const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(CompleteCellularActivation,
+               bool(brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(CompleteCellularActivationAsync,
+               void(const base::Callback<void()>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetLoadableProfileEntries,
+               bool(std::map<dbus::ObjectPath, std::string>*,
+                    brillo::ErrorPtr* /*error*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD3(GetLoadableProfileEntriesAsync,
+               void(const base::Callback<void(const std::map<dbus::ObjectPath, std::string>&)>& /*success_callback*/,
+                    const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+                    int /*timeout_ms*/));
+  MOCK_METHOD2(RegisterPropertyChangedSignalHandler,
+               void(const base::Callback<void(const std::string&,
+                                              const brillo::Any&)>& /*signal_callback*/,
+                    dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+  MOCK_CONST_METHOD0(GetObjectPath, const dbus::ObjectPath&());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ServiceProxyMock);
+};
+}  // namespace flimflam
+}  // namespace chromium
+}  // namespace org
+
+#endif  // ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_SHILL_DBUS_PROXY_MOCKS_H
diff --git a/update_engine/include/update_includes.sh b/update_engine/include/update_includes.sh
new file mode 100755
index 0000000..6008d59
--- /dev/null
+++ b/update_engine/include/update_includes.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+#
+# 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.
+#
+
+DBUS_GENERATOR=$(which dbus-binding-generator)
+MY_DIR=$(dirname "$0")
+
+if [[ -z "${ANDROID_HOST_OUT}" ]]; then
+  echo "You must run envsetup.sh and lunch first." >&2
+  exit 1
+fi
+
+if [[ -z "${DBUS_GENERATOR}" ]]; then
+  echo "DBus bindings generator not found." >&2
+  exit 1
+fi
+
+set -e
+
+# generate <kind> <dir> <xml> [xml ...]
+# Generate a DBus proxy and/or proxy mock in the passed |dir| for the provided
+# |xml| service files.
+# The parameter |kind| determines whether it should generate the mock only
+# (mock), the proxy only (proxy) or both (both).
+generate() {
+  local kind="$1"
+  local dir="$2"
+  local xmls=("${@:3}")
+
+  mkdir -p "${MY_DIR}/${dir}"
+  local outdir=$(realpath "${MY_DIR}/${dir}")
+  local proxyh="${outdir}/dbus-proxies.h"
+  local mockh="${outdir}/dbus-proxy-mocks.h"
+
+  ${DBUS_GENERATOR} "${xmls[@]}" --mock="${mockh}" --proxy="${proxyh}"
+
+  # Fix the include path to the dbus-proxies.h to include ${dir}.
+  sed "s,include \"dbus-proxies.h\",include \"${dir}/dbus-proxies.h\"," \
+    -i "${mockh}"
+
+  # Fix the header guards to be independent from the checkout location.
+  local guard=$(realpath "${MY_DIR}/../.." | tr '[:lower:]/ ' '[:upper:]__')
+  for header in "${mockh}" "${proxyh}"; do
+    sed "s,___CHROMEOS_DBUS_BINDING__${guard},___CHROMEOS_DBUS_BINDING__," \
+      -i "${header}"
+  done
+
+  # Remove the files not requested.
+  if [[ "${kind}" ==  "mock" ]]; then
+    rm -f "${proxyh}"
+  elif [[ "${kind}" == "proxy" ]]; then
+    rm -f "${mockh}"
+  fi
+}
+
+UE_DIR=$(realpath "${MY_DIR}/..")
+SHILL_DIR=$(realpath "${UE_DIR}/../connectivity/shill")
+
+generate mock "libcros" \
+  "${UE_DIR}/dbus_bindings/org.chromium.LibCrosService.dbus-xml"
+
+generate mock "shill" \
+  "${SHILL_DIR}"/dbus_bindings/org.chromium.flimflam.{Manager,Service}.dbus-xml
+
+echo "Done."
diff --git a/update_engine/init/update-engine.conf b/update_engine/init/update-engine.conf
new file mode 100644
index 0000000..4c05cf4
--- /dev/null
+++ b/update_engine/init/update-engine.conf
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+description     "System software update service"
+author          "chromium-os-dev@chromium.org"
+
+# N.B. The chromeos-factoryinstall ebuild edits the 'start on' line so as
+# to disable update_engine in factory images.  Do not change this without
+# also updating that reference.
+start on starting system-services
+stop on stopping system-services
+respawn
+
+expect fork
+
+# Runs the daemon at low/idle IO priority so that updates don't
+# impact system responsiveness.
+exec ionice -c3 update_engine
+
+# Put update_engine process in its own cgroup.
+# Default cpu.shares is 1024.
+post-start script
+  cgroup_dir="/sys/fs/cgroup/cpu/${UPSTART_JOB}"
+  mkdir -p "${cgroup_dir}"
+  echo $(status | cut -f 4 -d ' ') > "${cgroup_dir}/tasks"
+end script
diff --git a/update_engine/libcros_proxy.cc b/update_engine/libcros_proxy.cc
new file mode 100644
index 0000000..3aa87cb
--- /dev/null
+++ b/update_engine/libcros_proxy.cc
@@ -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.
+//
+
+#include "update_engine/libcros_proxy.h"
+
+#include "update_engine/dbus_connection.h"
+
+using org::chromium::LibCrosServiceInterfaceProxy;
+using org::chromium::LibCrosServiceInterfaceProxyInterface;
+using org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxy;
+using org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface;
+
+namespace {
+const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
+}  // namespace
+
+namespace chromeos_update_engine {
+
+LibCrosProxy::LibCrosProxy(
+    std::unique_ptr<LibCrosServiceInterfaceProxyInterface>
+        service_interface_proxy,
+    std::unique_ptr<UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface>
+        ue_proxy_resolved_interface)
+    : service_interface_proxy_(std::move(service_interface_proxy)),
+      ue_proxy_resolved_interface_(std::move(ue_proxy_resolved_interface)) {
+}
+
+LibCrosProxy::LibCrosProxy() {
+  const scoped_refptr<dbus::Bus>& bus = DBusConnection::Get()->GetDBus();
+  service_interface_proxy_.reset(
+      new LibCrosServiceInterfaceProxy(bus, kLibCrosServiceName));
+  ue_proxy_resolved_interface_.reset(
+      new UpdateEngineLibcrosProxyResolvedInterfaceProxy(bus,
+                                                         kLibCrosServiceName));
+}
+
+LibCrosServiceInterfaceProxyInterface* LibCrosProxy::service_interface_proxy() {
+  return service_interface_proxy_.get();
+}
+
+UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface*
+LibCrosProxy::ue_proxy_resolved_interface() {
+  return ue_proxy_resolved_interface_.get();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/libcros_proxy.h b/update_engine/libcros_proxy.h
new file mode 100644
index 0000000..03bf312
--- /dev/null
+++ b/update_engine/libcros_proxy.h
@@ -0,0 +1,62 @@
+//
+// 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_LIBCROS_PROXY_H_
+#define UPDATE_ENGINE_LIBCROS_PROXY_H_
+
+#include <memory>
+
+#include <base/macros.h>
+#include <dbus/bus.h>
+
+#include "libcros/dbus-proxies.h"
+
+namespace chromeos_update_engine {
+
+// This class handles the DBus connection with chrome to resolve proxies. This
+// is a thin class to just hold the generated proxies (real or mocked ones).
+class LibCrosProxy final {
+ public:
+  LibCrosProxy();
+  LibCrosProxy(
+      std::unique_ptr<org::chromium::LibCrosServiceInterfaceProxyInterface>
+          service_interface_proxy,
+      std::unique_ptr<
+          org::chromium::
+              UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface>
+          ue_proxy_resolved_interface);
+
+  ~LibCrosProxy() = default;
+
+  // Getters for the two proxies.
+  org::chromium::LibCrosServiceInterfaceProxyInterface*
+  service_interface_proxy();
+  org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface*
+  ue_proxy_resolved_interface();
+
+ private:
+  std::unique_ptr<org::chromium::LibCrosServiceInterfaceProxyInterface>
+      service_interface_proxy_;
+  std::unique_ptr<
+      org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface>
+      ue_proxy_resolved_interface_;
+
+  DISALLOW_COPY_AND_ASSIGN(LibCrosProxy);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_LIBCROS_PROXY_H_
diff --git a/update_engine/libcurl_http_fetcher.cc b/update_engine/libcurl_http_fetcher.cc
new file mode 100644
index 0000000..7cada57
--- /dev/null
+++ b/update_engine/libcurl_http_fetcher.cc
@@ -0,0 +1,718 @@
+//
+// Copyright (C) 2009 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/libcurl_http_fetcher.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/bind.h>
+#include <base/format_macros.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/platform_constants.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::max;
+using std::string;
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+namespace {
+const int kNoNetworkRetrySeconds = 10;
+}  // namespace
+
+LibcurlHttpFetcher::LibcurlHttpFetcher(ProxyResolver* proxy_resolver,
+                                       HardwareInterface* hardware)
+    : HttpFetcher(proxy_resolver), hardware_(hardware) {
+  // Dev users want a longer timeout (180 seconds) because they may
+  // be waiting on the dev server to build an image.
+  if (!hardware_->IsOfficialBuild())
+    low_speed_time_seconds_ = kDownloadDevModeLowSpeedTimeSeconds;
+  if (hardware_->IsOOBEEnabled() && !hardware_->IsOOBEComplete(nullptr))
+    max_retry_count_ = kDownloadMaxRetryCountOobeNotComplete;
+}
+
+LibcurlHttpFetcher::~LibcurlHttpFetcher() {
+  LOG_IF(ERROR, transfer_in_progress_)
+      << "Destroying the fetcher while a transfer is in progress.";
+  CleanUp();
+}
+
+bool LibcurlHttpFetcher::GetProxyType(const string& proxy,
+                                      curl_proxytype* out_type) {
+  if (base::StartsWith(
+          proxy, "socks5://", base::CompareCase::INSENSITIVE_ASCII) ||
+      base::StartsWith(
+          proxy, "socks://", base::CompareCase::INSENSITIVE_ASCII)) {
+    *out_type = CURLPROXY_SOCKS5_HOSTNAME;
+    return true;
+  }
+  if (base::StartsWith(
+          proxy, "socks4://", base::CompareCase::INSENSITIVE_ASCII)) {
+    *out_type = CURLPROXY_SOCKS4A;
+    return true;
+  }
+  if (base::StartsWith(
+          proxy, "http://", base::CompareCase::INSENSITIVE_ASCII) ||
+      base::StartsWith(
+          proxy, "https://", base::CompareCase::INSENSITIVE_ASCII)) {
+    *out_type = CURLPROXY_HTTP;
+    return true;
+  }
+  if (base::StartsWith(proxy, kNoProxy, base::CompareCase::INSENSITIVE_ASCII)) {
+    // known failure case. don't log.
+    return false;
+  }
+  LOG(INFO) << "Unknown proxy type: " << proxy;
+  return false;
+}
+
+void LibcurlHttpFetcher::ResumeTransfer(const string& url) {
+  LOG(INFO) << "Starting/Resuming transfer";
+  CHECK(!transfer_in_progress_);
+  url_ = url;
+  curl_multi_handle_ = curl_multi_init();
+  CHECK(curl_multi_handle_);
+
+  curl_handle_ = curl_easy_init();
+  CHECK(curl_handle_);
+  ignore_failure_ = false;
+
+  CHECK(HasProxy());
+  bool is_direct = (GetCurrentProxy() == kNoProxy);
+  LOG(INFO) << "Using proxy: " << (is_direct ? "no" : "yes");
+  if (is_direct) {
+    CHECK_EQ(curl_easy_setopt(curl_handle_,
+                              CURLOPT_PROXY,
+                              ""), CURLE_OK);
+  } else {
+    CHECK_EQ(curl_easy_setopt(curl_handle_,
+                              CURLOPT_PROXY,
+                              GetCurrentProxy().c_str()), CURLE_OK);
+    // Curl seems to require us to set the protocol
+    curl_proxytype type;
+    if (GetProxyType(GetCurrentProxy(), &type)) {
+      CHECK_EQ(curl_easy_setopt(curl_handle_,
+                                CURLOPT_PROXYTYPE,
+                                type), CURLE_OK);
+    }
+  }
+
+  if (post_data_set_) {
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK);
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
+                              post_data_.data()),
+             CURLE_OK);
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
+                              post_data_.size()),
+             CURLE_OK);
+  }
+
+  // Setup extra HTTP headers.
+  if (curl_http_headers_) {
+    curl_slist_free_all(curl_http_headers_);
+    curl_http_headers_ = nullptr;
+  }
+  for (const auto& header : extra_headers_) {
+    // curl_slist_append() copies the string.
+    curl_http_headers_ =
+        curl_slist_append(curl_http_headers_, header.second.c_str());
+  }
+  if (post_data_set_) {
+    // Set the Content-Type HTTP header, if one was specifically set.
+    if (post_content_type_ != kHttpContentTypeUnspecified) {
+      const string content_type_attr = base::StringPrintf(
+          "Content-Type: %s", GetHttpContentTypeString(post_content_type_));
+      curl_http_headers_ =
+          curl_slist_append(curl_http_headers_, content_type_attr.c_str());
+    } else {
+      LOG(WARNING) << "no content type set, using libcurl default";
+    }
+  }
+  CHECK_EQ(
+      curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, curl_http_headers_),
+      CURLE_OK);
+
+  if (bytes_downloaded_ > 0 || download_length_) {
+    // Resume from where we left off.
+    resume_offset_ = bytes_downloaded_;
+    CHECK_GE(resume_offset_, 0);
+
+    // Compute end offset, if one is specified. As per HTTP specification, this
+    // is an inclusive boundary. Make sure it doesn't overflow.
+    size_t end_offset = 0;
+    if (download_length_) {
+      end_offset = static_cast<size_t>(resume_offset_) + download_length_ - 1;
+      CHECK_LE((size_t) resume_offset_, end_offset);
+    }
+
+    // Create a string representation of the desired range.
+    string range_str = base::StringPrintf(
+        "%" PRIu64 "-", static_cast<uint64_t>(resume_offset_));
+    if (end_offset)
+      range_str += std::to_string(end_offset);
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_RANGE, range_str.c_str()),
+             CURLE_OK);
+  }
+
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
+                            StaticLibcurlWrite), CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()),
+           CURLE_OK);
+
+  // If the connection drops under |low_speed_limit_bps_| (10
+  // bytes/sec by default) for |low_speed_time_seconds_| (90 seconds,
+  // 180 on non-official builds), reconnect.
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_LIMIT,
+                            low_speed_limit_bps_),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_TIME,
+                            low_speed_time_seconds_),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CONNECTTIMEOUT,
+                            connect_timeout_seconds_),
+           CURLE_OK);
+
+  // By default, libcurl doesn't follow redirections. Allow up to
+  // |kDownloadMaxRedirects| redirections.
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_FOLLOWLOCATION, 1), CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_MAXREDIRS,
+                            kDownloadMaxRedirects),
+           CURLE_OK);
+
+  // Lock down the appropriate curl options for HTTP or HTTPS depending on
+  // the url.
+  if (hardware_->IsOfficialBuild()) {
+    if (base::StartsWith(
+            url_, "http://", base::CompareCase::INSENSITIVE_ASCII)) {
+      SetCurlOptionsForHttp();
+    } else if (base::StartsWith(
+                   url_, "https://", base::CompareCase::INSENSITIVE_ASCII)) {
+      SetCurlOptionsForHttps();
+#if !defined(__CHROMEOS__) && !defined(__BRILLO__)
+    } else if (base::StartsWith(
+                   url_, "file://", base::CompareCase::INSENSITIVE_ASCII)) {
+      SetCurlOptionsForFile();
+#endif
+    } else {
+      LOG(ERROR) << "Received invalid URI: " << url_;
+      // Lock down to no protocol supported for the transfer.
+      CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, 0), CURLE_OK);
+    }
+  } else {
+    LOG(INFO) << "Not setting http(s) curl options because we are "
+              << "running a dev/test image";
+  }
+
+  CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
+  transfer_in_progress_ = true;
+}
+
+// Lock down only the protocol in case of HTTP.
+void LibcurlHttpFetcher::SetCurlOptionsForHttp() {
+  LOG(INFO) << "Setting up curl options for HTTP";
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTP),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
+                            CURLPROTO_HTTP),
+           CURLE_OK);
+}
+
+// Security lock-down in official builds: makes sure that peer certificate
+// verification is enabled, restricts the set of trusted certificates,
+// restricts protocols to HTTPS, restricts ciphers to HIGH.
+void LibcurlHttpFetcher::SetCurlOptionsForHttps() {
+  LOG(INFO) << "Setting up curl options for HTTPS";
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_VERIFYPEER, 1),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_VERIFYHOST, 2),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CAPATH,
+                            constants::kCACertificatesPath),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
+                            CURLPROTO_HTTPS),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CIPHER_LIST, "HIGH:!ADH"),
+           CURLE_OK);
+
+#ifdef USE_NESTLABS
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_VERBOSE, 1L),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CAINFO,
+                            constants::kCACertificatesFilePath),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSLCERTTYPE,
+                            "PEM"),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSLCERT,
+                            constants::kSSLCertPath),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSLKEYTYPE,
+                            "PEM"),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSLKEY,
+                            constants::kSSLKeyPath),
+           CURLE_OK);
+#endif
+
+  if (server_to_check_ != ServerToCheck::kNone) {
+    CHECK_EQ(
+        curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_DATA, &server_to_check_),
+        CURLE_OK);
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_FUNCTION,
+                              CertificateChecker::ProcessSSLContext),
+             CURLE_OK);
+  }
+}
+
+// Lock down only the protocol in case of a local file.
+void LibcurlHttpFetcher::SetCurlOptionsForFile() {
+  LOG(INFO) << "Setting up curl options for FILE";
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_FILE),
+           CURLE_OK);
+  CHECK_EQ(
+      curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_FILE),
+      CURLE_OK);
+}
+
+// Begins the transfer, which must not have already been started.
+void LibcurlHttpFetcher::BeginTransfer(const string& url) {
+  CHECK(!transfer_in_progress_);
+  url_ = url;
+  auto closure = base::Bind(&LibcurlHttpFetcher::ProxiesResolved,
+                            base::Unretained(this));
+  if (!ResolveProxiesForUrl(url_, closure)) {
+    LOG(ERROR) << "Couldn't resolve proxies";
+    if (delegate_)
+      delegate_->TransferComplete(this, false);
+  }
+}
+
+void LibcurlHttpFetcher::ProxiesResolved() {
+  transfer_size_ = -1;
+  resume_offset_ = 0;
+  retry_count_ = 0;
+  no_network_retry_count_ = 0;
+  http_response_code_ = 0;
+  terminate_requested_ = false;
+  sent_byte_ = false;
+
+  // If we are paused, we delay these two operations until Unpause is called.
+  if (transfer_paused_) {
+    restart_transfer_on_unpause_ = true;
+    return;
+  }
+  ResumeTransfer(url_);
+  CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::ForceTransferTermination() {
+  CleanUp();
+  if (delegate_) {
+    // Note that after the callback returns this object may be destroyed.
+    delegate_->TransferTerminated(this);
+  }
+}
+
+void LibcurlHttpFetcher::TerminateTransfer() {
+  if (in_write_callback_) {
+    terminate_requested_ = true;
+  } else {
+    ForceTransferTermination();
+  }
+}
+
+void LibcurlHttpFetcher::SetHeader(const string& header_name,
+                                   const string& header_value) {
+  string header_line = header_name + ": " + header_value;
+  // Avoid the space if no data on the right side of the semicolon.
+  if (header_value.empty())
+    header_line = header_name + ":";
+  TEST_AND_RETURN(header_line.find('\n') == string::npos);
+  TEST_AND_RETURN(header_name.find(':') == string::npos);
+  extra_headers_[base::ToLowerASCII(header_name)] = header_line;
+}
+
+void LibcurlHttpFetcher::CurlPerformOnce() {
+  CHECK(transfer_in_progress_);
+  int running_handles = 0;
+  CURLMcode retcode = CURLM_CALL_MULTI_PERFORM;
+
+  // libcurl may request that we immediately call curl_multi_perform after it
+  // returns, so we do. libcurl promises that curl_multi_perform will not block.
+  while (CURLM_CALL_MULTI_PERFORM == retcode) {
+    retcode = curl_multi_perform(curl_multi_handle_, &running_handles);
+    if (terminate_requested_) {
+      ForceTransferTermination();
+      return;
+    }
+  }
+
+  // If the transfer completes while paused, we should ignore the failure once
+  // the fetcher is unpaused.
+  if (running_handles == 0 && transfer_paused_ && !ignore_failure_) {
+    LOG(INFO) << "Connection closed while paused, ignoring failure.";
+    ignore_failure_ = true;
+  }
+
+  if (running_handles != 0 || transfer_paused_) {
+    // There's either more work to do or we are paused, so we just keep the
+    // file descriptors to watch up to date and exit, until we are done with the
+    // work and we are not paused.
+    SetupMessageLoopSources();
+    return;
+  }
+
+  // At this point, the transfer was completed in some way (error, connection
+  // closed or download finished).
+
+  GetHttpResponseCode();
+  if (http_response_code_) {
+    LOG(INFO) << "HTTP response code: " << http_response_code_;
+    no_network_retry_count_ = 0;
+  } else {
+    LOG(ERROR) << "Unable to get http response code.";
+  }
+
+  // we're done!
+  CleanUp();
+
+  // TODO(petkov): This temporary code tries to deal with the case where the
+  // update engine performs an update check while the network is not ready
+  // (e.g., right after resume). Longer term, we should check if the network
+  // is online/offline and return an appropriate error code.
+  if (!sent_byte_ &&
+      http_response_code_ == 0 &&
+      no_network_retry_count_ < no_network_max_retries_) {
+    no_network_retry_count_++;
+    MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+                   base::Unretained(this)),
+        TimeDelta::FromSeconds(kNoNetworkRetrySeconds));
+    LOG(INFO) << "No HTTP response, retry " << no_network_retry_count_;
+  } else if ((!sent_byte_ && !IsHttpResponseSuccess()) ||
+             IsHttpResponseError()) {
+    // The transfer completed w/ error and we didn't get any bytes.
+    // If we have another proxy to try, try that.
+    //
+    // TODO(garnold) in fact there are two separate cases here: one case is an
+    // other-than-success return code (including no return code) and no
+    // received bytes, which is necessary due to the way callbacks are
+    // currently processing error conditions;  the second is an explicit HTTP
+    // error code, where some data may have been received (as in the case of a
+    // semi-successful multi-chunk fetch).  This is a confusing behavior and
+    // should be unified into a complete, coherent interface.
+    LOG(INFO) << "Transfer resulted in an error (" << http_response_code_
+              << "), " << bytes_downloaded_ << " bytes downloaded";
+
+    PopProxy();  // Delete the proxy we just gave up on.
+
+    if (HasProxy()) {
+      // We have another proxy. Retry immediately.
+      LOG(INFO) << "Retrying with next proxy setting";
+      MessageLoop::current()->PostTask(
+          FROM_HERE,
+          base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+                     base::Unretained(this)));
+    } else {
+      // Out of proxies. Give up.
+      LOG(INFO) << "No further proxies, indicating transfer complete";
+      if (delegate_)
+        delegate_->TransferComplete(this, false);  // signal fail
+      return;
+    }
+  } else if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) {
+    if (!ignore_failure_)
+      retry_count_++;
+    LOG(INFO) << "Transfer interrupted after downloading "
+              << bytes_downloaded_ << " of " << transfer_size_ << " bytes. "
+              << transfer_size_ - bytes_downloaded_ << " bytes remaining "
+              << "after " << retry_count_ << " attempt(s)";
+
+    if (retry_count_ > max_retry_count_) {
+      LOG(INFO) << "Reached max attempts (" << retry_count_ << ")";
+      if (delegate_)
+        delegate_->TransferComplete(this, false);  // signal fail
+      return;
+    }
+    // Need to restart transfer
+    LOG(INFO) << "Restarting transfer to download the remaining bytes";
+    MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+                   base::Unretained(this)),
+        TimeDelta::FromSeconds(retry_seconds_));
+  } else {
+    LOG(INFO) << "Transfer completed (" << http_response_code_
+              << "), " << bytes_downloaded_ << " bytes downloaded";
+    if (delegate_) {
+      bool success = IsHttpResponseSuccess();
+      delegate_->TransferComplete(this, success);
+    }
+    return;
+  }
+  // If we reach this point is because TransferComplete() was not called in any
+  // of the previous branches. The delegate is allowed to destroy the object
+  // once TransferComplete is called so this would be illegal.
+  ignore_failure_ = false;
+}
+
+size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
+  // Update HTTP response first.
+  GetHttpResponseCode();
+  const size_t payload_size = size * nmemb;
+
+  // Do nothing if no payload or HTTP response is an error.
+  if (payload_size == 0 || !IsHttpResponseSuccess()) {
+    LOG(INFO) << "HTTP response unsuccessful (" << http_response_code_
+              << ") or no payload (" << payload_size << "), nothing to do";
+    return 0;
+  }
+
+  sent_byte_ = true;
+  {
+    double transfer_size_double;
+    CHECK_EQ(curl_easy_getinfo(curl_handle_,
+                               CURLINFO_CONTENT_LENGTH_DOWNLOAD,
+                               &transfer_size_double), CURLE_OK);
+    off_t new_transfer_size = static_cast<off_t>(transfer_size_double);
+    if (new_transfer_size > 0) {
+      transfer_size_ = resume_offset_ + new_transfer_size;
+    }
+  }
+  bytes_downloaded_ += payload_size;
+  in_write_callback_ = true;
+  if (delegate_)
+    delegate_->ReceivedBytes(this, ptr, payload_size);
+  in_write_callback_ = false;
+  return payload_size;
+}
+
+void LibcurlHttpFetcher::Pause() {
+  if (transfer_paused_) {
+    LOG(ERROR) << "Fetcher already paused.";
+    return;
+  }
+  transfer_paused_ = true;
+  if (!transfer_in_progress_) {
+    // If pause before we started a connection, we don't need to notify curl
+    // about that, we will simply not start the connection later.
+    return;
+  }
+  CHECK(curl_handle_);
+  CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_ALL), CURLE_OK);
+}
+
+void LibcurlHttpFetcher::Unpause() {
+  if (!transfer_paused_) {
+    LOG(ERROR) << "Resume attempted when fetcher not paused.";
+    return;
+  }
+  transfer_paused_ = false;
+  if (restart_transfer_on_unpause_) {
+    restart_transfer_on_unpause_ = false;
+    ResumeTransfer(url_);
+    CurlPerformOnce();
+    return;
+  }
+  if (!transfer_in_progress_) {
+    // If resumed before starting the connection, there's no need to notify
+    // anybody. We will simply start the connection once it is time.
+    return;
+  }
+  CHECK(curl_handle_);
+  CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_CONT), CURLE_OK);
+  // Since the transfer is in progress, we need to dispatch a CurlPerformOnce()
+  // now to let the connection continue, otherwise it would be called by the
+  // TimeoutCallback but with a delay.
+  CurlPerformOnce();
+}
+
+// This method sets up callbacks with the MessageLoop.
+void LibcurlHttpFetcher::SetupMessageLoopSources() {
+  fd_set fd_read;
+  fd_set fd_write;
+  fd_set fd_exc;
+
+  FD_ZERO(&fd_read);
+  FD_ZERO(&fd_write);
+  FD_ZERO(&fd_exc);
+
+  int fd_max = 0;
+
+  // Ask libcurl for the set of file descriptors we should track on its
+  // behalf.
+  CHECK_EQ(curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
+                            &fd_exc, &fd_max), CURLM_OK);
+
+  // We should iterate through all file descriptors up to libcurl's fd_max or
+  // the highest one we're tracking, whichever is larger.
+  for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
+    if (!fd_task_maps_[t].empty())
+      fd_max = max(fd_max, fd_task_maps_[t].rbegin()->first);
+  }
+
+  // For each fd, if we're not tracking it, track it. If we are tracking it, but
+  // libcurl doesn't care about it anymore, stop tracking it. After this loop,
+  // there should be exactly as many tasks scheduled in fd_task_maps_[0|1] as
+  // there are read/write fds that we're tracking.
+  for (int fd = 0; fd <= fd_max; ++fd) {
+    // Note that fd_exc is unused in the current version of libcurl so is_exc
+    // should always be false.
+    bool is_exc = FD_ISSET(fd, &fd_exc) != 0;
+    bool must_track[2] = {
+      is_exc || (FD_ISSET(fd, &fd_read) != 0),  // track 0 -- read
+      is_exc || (FD_ISSET(fd, &fd_write) != 0)  // track 1 -- write
+    };
+    MessageLoop::WatchMode watch_modes[2] = {
+      MessageLoop::WatchMode::kWatchRead,
+      MessageLoop::WatchMode::kWatchWrite,
+    };
+
+    for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
+      auto fd_task_it = fd_task_maps_[t].find(fd);
+      bool tracked = fd_task_it != fd_task_maps_[t].end();
+
+      if (!must_track[t]) {
+        // If we have an outstanding io_channel, remove it.
+        if (tracked) {
+          MessageLoop::current()->CancelTask(fd_task_it->second);
+          fd_task_maps_[t].erase(fd_task_it);
+        }
+        continue;
+      }
+
+      // If we are already tracking this fd, continue -- nothing to do.
+      if (tracked)
+        continue;
+
+      // Track a new fd.
+      fd_task_maps_[t][fd] = MessageLoop::current()->WatchFileDescriptor(
+          FROM_HERE,
+          fd,
+          watch_modes[t],
+          true,  // persistent
+          base::Bind(&LibcurlHttpFetcher::CurlPerformOnce,
+                     base::Unretained(this)));
+
+      static int io_counter = 0;
+      io_counter++;
+      if (io_counter % 50 == 0) {
+        LOG(INFO) << "io_counter = " << io_counter;
+      }
+    }
+  }
+
+  // Set up a timeout callback for libcurl.
+  if (timeout_id_ == MessageLoop::kTaskIdNull) {
+    VLOG(1) << "Setting up timeout source: " << idle_seconds_ << " seconds.";
+    timeout_id_ = MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&LibcurlHttpFetcher::TimeoutCallback,
+                   base::Unretained(this)),
+        TimeDelta::FromSeconds(idle_seconds_));
+  }
+}
+
+void LibcurlHttpFetcher::RetryTimeoutCallback() {
+  if (transfer_paused_) {
+    restart_transfer_on_unpause_ = true;
+    return;
+  }
+  ResumeTransfer(url_);
+  CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::TimeoutCallback() {
+  // We always re-schedule the callback, even if we don't want to be called
+  // anymore. We will remove the event source separately if we don't want to
+  // be called back.
+  timeout_id_ = MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&LibcurlHttpFetcher::TimeoutCallback, base::Unretained(this)),
+      TimeDelta::FromSeconds(idle_seconds_));
+
+  // CurlPerformOnce() may call CleanUp(), so we need to schedule our callback
+  // first, since it could be canceled by this call.
+  if (transfer_in_progress_)
+    CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::CleanUp() {
+  MessageLoop::current()->CancelTask(timeout_id_);
+  timeout_id_ = MessageLoop::kTaskIdNull;
+
+  for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
+    for (const auto& fd_taks_pair : fd_task_maps_[t]) {
+      if (!MessageLoop::current()->CancelTask(fd_taks_pair.second)) {
+        LOG(WARNING) << "Error canceling the watch task "
+                     << fd_taks_pair.second << " for "
+                     << (t ? "writing" : "reading") << " the fd "
+                     << fd_taks_pair.first;
+      }
+    }
+    fd_task_maps_[t].clear();
+  }
+
+  if (curl_http_headers_) {
+    curl_slist_free_all(curl_http_headers_);
+    curl_http_headers_ = nullptr;
+  }
+  if (curl_handle_) {
+    if (curl_multi_handle_) {
+      CHECK_EQ(curl_multi_remove_handle(curl_multi_handle_, curl_handle_),
+               CURLM_OK);
+    }
+    curl_easy_cleanup(curl_handle_);
+    curl_handle_ = nullptr;
+  }
+  if (curl_multi_handle_) {
+    CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK);
+    curl_multi_handle_ = nullptr;
+  }
+  transfer_in_progress_ = false;
+  transfer_paused_ = false;
+  restart_transfer_on_unpause_ = false;
+}
+
+void LibcurlHttpFetcher::GetHttpResponseCode() {
+  long http_response_code = 0;  // NOLINT(runtime/int) - curl needs long.
+  if (base::StartsWith(url_, "file://", base::CompareCase::INSENSITIVE_ASCII)) {
+    // Fake out a valid response code for file:// URLs.
+    http_response_code_ = 299;
+  } else if (curl_easy_getinfo(curl_handle_,
+                               CURLINFO_RESPONSE_CODE,
+                               &http_response_code) == CURLE_OK) {
+    http_response_code_ = static_cast<int>(http_response_code);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/libcurl_http_fetcher.h b/update_engine/libcurl_http_fetcher.h
new file mode 100644
index 0000000..1541ea4
--- /dev/null
+++ b/update_engine/libcurl_http_fetcher.h
@@ -0,0 +1,268 @@
+//
+// Copyright (C) 2009 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_LIBCURL_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <curl/curl.h>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/http_fetcher.h"
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+class LibcurlHttpFetcher : public HttpFetcher {
+ public:
+  LibcurlHttpFetcher(ProxyResolver* proxy_resolver,
+                     HardwareInterface* hardware);
+
+  // Cleans up all internal state. Does not notify delegate
+  ~LibcurlHttpFetcher() override;
+
+  void SetOffset(off_t offset) override { bytes_downloaded_ = offset; }
+
+  void SetLength(size_t length) override { download_length_ = length; }
+  void UnsetLength() override { SetLength(0); }
+
+  // Begins the transfer if it hasn't already begun.
+  void BeginTransfer(const std::string& url) override;
+
+  // If the transfer is in progress, aborts the transfer early. The transfer
+  // cannot be resumed.
+  void TerminateTransfer() override;
+
+  // Pass the headers to libcurl.
+  void SetHeader(const std::string& header_name,
+                 const std::string& header_value) override;
+
+  // Suspend the transfer by calling curl_easy_pause(CURLPAUSE_ALL).
+  void Pause() override;
+
+  // Resume the transfer by calling curl_easy_pause(CURLPAUSE_CONT).
+  void Unpause() override;
+
+  // Libcurl sometimes asks to be called back after some time while
+  // leaving that time unspecified. In that case, we pick a reasonable
+  // default of one second, but it can be overridden here. This is
+  // primarily useful for testing.
+  // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
+  //     if libcurl returns a -1 timeout here, it just means that libcurl
+  //     currently has no stored timeout value. You must not wait too long
+  //     (more than a few seconds perhaps) before you call
+  //     curl_multi_perform() again.
+  void set_idle_seconds(int seconds) override { idle_seconds_ = seconds; }
+
+  // Sets the retry timeout. Useful for testing.
+  void set_retry_seconds(int seconds) override { retry_seconds_ = seconds; }
+
+  void set_no_network_max_retries(int retries) {
+    no_network_max_retries_ = retries;
+  }
+
+  void set_server_to_check(ServerToCheck server_to_check) {
+    server_to_check_ = server_to_check;
+  }
+
+  size_t GetBytesDownloaded() override {
+    return static_cast<size_t>(bytes_downloaded_);
+  }
+
+  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
+    low_speed_limit_bps_ = low_speed_bps;
+    low_speed_time_seconds_ = low_speed_sec;
+  }
+
+  void set_connect_timeout(int connect_timeout_seconds) override {
+    connect_timeout_seconds_ = connect_timeout_seconds;
+  }
+
+  void set_max_retry_count(int max_retry_count) override {
+    max_retry_count_ = max_retry_count;
+  }
+
+ private:
+  // Callback for when proxy resolution has completed. This begins the
+  // transfer.
+  void ProxiesResolved();
+
+  // Asks libcurl for the http response code and stores it in the object.
+  void GetHttpResponseCode();
+
+  // Checks whether stored HTTP response is within the success range.
+  inline bool IsHttpResponseSuccess() {
+    return (http_response_code_ >= 200 && http_response_code_ < 300);
+  }
+
+  // Checks whether stored HTTP response is within the error range. This
+  // includes both errors with the request (4xx) and server errors (5xx).
+  inline bool IsHttpResponseError() {
+    return (http_response_code_ >= 400 && http_response_code_ < 600);
+  }
+
+  // Resumes a transfer where it left off. This will use the
+  // HTTP Range: header to make a new connection from where the last
+  // left off.
+  virtual void ResumeTransfer(const std::string& url);
+
+  void TimeoutCallback();
+  void RetryTimeoutCallback();
+
+  // Calls into curl_multi_perform to let libcurl do its work. Returns after
+  // curl_multi_perform is finished, which may actually be after more than
+  // one call to curl_multi_perform. This method will set up the message
+  // loop with sources for future work that libcurl will do, if any, or complete
+  // the transfer and finish the action if no work left to do.
+  // This method will not block.
+  void CurlPerformOnce();
+
+  // Sets up message loop sources as needed by libcurl. This is generally
+  // the file descriptor of the socket and a timer in case nothing happens
+  // on the fds.
+  void SetupMessageLoopSources();
+
+  // Callback called by libcurl when new data has arrived on the transfer
+  size_t LibcurlWrite(void *ptr, size_t size, size_t nmemb);
+  static size_t StaticLibcurlWrite(void *ptr, size_t size,
+                                   size_t nmemb, void *stream) {
+    return reinterpret_cast<LibcurlHttpFetcher*>(stream)->
+        LibcurlWrite(ptr, size, nmemb);
+  }
+
+  // Cleans up the following if they are non-null:
+  // curl(m) handles, fd_task_maps_, timeout_id_.
+  void CleanUp();
+
+  // Force terminate the transfer. This will invoke the delegate's (if any)
+  // TransferTerminated callback so, after returning, this fetcher instance may
+  // be destroyed.
+  void ForceTransferTermination();
+
+  // Sets the curl options for HTTP URL.
+  void SetCurlOptionsForHttp();
+
+  // Sets the curl options for HTTPS URL.
+  void SetCurlOptionsForHttps();
+
+  // Sets the curl options for file URI.
+  void SetCurlOptionsForFile();
+
+  // Convert a proxy URL into a curl proxy type, if applicable. Returns true iff
+  // conversion was successful, false otherwise (in which case nothing is
+  // written to |out_type|).
+  bool GetProxyType(const std::string& proxy, curl_proxytype* out_type);
+
+  // Hardware interface used to query dev-mode and official build settings.
+  HardwareInterface* hardware_;
+
+  // Handles for the libcurl library
+  CURLM* curl_multi_handle_{nullptr};
+  CURL* curl_handle_{nullptr};
+  struct curl_slist* curl_http_headers_{nullptr};
+
+  // The extra headers that will be sent on each request.
+  std::map<std::string, std::string> extra_headers_;
+
+  // Lists of all read(0)/write(1) file descriptors that we're waiting on from
+  // the message loop. libcurl may open/close descriptors and switch their
+  // directions so maintain two separate lists so that watch conditions can be
+  // set appropriately.
+  std::map<int, brillo::MessageLoop::TaskId> fd_task_maps_[2];
+
+  // The TaskId of the timer we're waiting on. kTaskIdNull if we are not waiting
+  // on it.
+  brillo::MessageLoop::TaskId timeout_id_{brillo::MessageLoop::kTaskIdNull};
+
+  bool transfer_in_progress_{false};
+  bool transfer_paused_{false};
+
+  // Whether it should ignore transfer failures for the purpose of retrying the
+  // connection.
+  bool ignore_failure_{false};
+
+  // Whether we should restart the transfer once Unpause() is called. This can
+  // be caused because either the connection dropped while pause or the proxy
+  // was resolved and we never started the transfer in the first place.
+  bool restart_transfer_on_unpause_{false};
+
+  // The transfer size. -1 if not known.
+  off_t transfer_size_{0};
+
+  // How many bytes have been downloaded and sent to the delegate.
+  off_t bytes_downloaded_{0};
+
+  // The remaining maximum number of bytes to download. Zero represents an
+  // unspecified length.
+  size_t download_length_{0};
+
+  // If we resumed an earlier transfer, data offset that we used for the
+  // new connection.  0 otherwise.
+  // In this class, resume refers to resuming a dropped HTTP connection,
+  // not to resuming an interrupted download.
+  off_t resume_offset_{0};
+
+  // Number of resumes performed so far and the max allowed.
+  int retry_count_{0};
+  int max_retry_count_{kDownloadMaxRetryCount};
+
+  // Seconds to wait before retrying a resume.
+  int retry_seconds_{20};
+
+  // Number of resumes due to no network (e.g., HTTP response code 0).
+  int no_network_retry_count_{0};
+  int no_network_max_retries_{0};
+
+  // Seconds to wait before asking libcurl to "perform".
+  int idle_seconds_{1};
+
+  // If true, we are currently performing a write callback on the delegate.
+  bool in_write_callback_{false};
+
+  // If true, we have returned at least one byte in the write callback
+  // to the delegate.
+  bool sent_byte_{false};
+
+  // We can't clean everything up while we're in a write callback, so
+  // if we get a terminate request, queue it until we can handle it.
+  bool terminate_requested_{false};
+
+  // The ServerToCheck used when checking this connection's certificate. If no
+  // certificate check needs to be performed, this should be set to
+  // ServerToCheck::kNone.
+  ServerToCheck server_to_check_{ServerToCheck::kNone};
+
+  int low_speed_limit_bps_{kDownloadLowSpeedLimitBps};
+  int low_speed_time_seconds_{kDownloadLowSpeedTimeSeconds};
+  int connect_timeout_seconds_{kDownloadConnectTimeoutSeconds};
+
+  DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
diff --git a/update_engine/libupdate_engine-client-test.pc.in b/update_engine/libupdate_engine-client-test.pc.in
new file mode 100644
index 0000000..92a4af3
--- /dev/null
+++ b/update_engine/libupdate_engine-client-test.pc.in
@@ -0,0 +1,6 @@
+include_dir=@INCLUDE_DIR@
+
+Name: libupdate_engine-client-test
+Version: 1.0
+Description: update_engine client interface mock library
+Cflags: -I${include_dir}
diff --git a/update_engine/libupdate_engine-client.pc.in b/update_engine/libupdate_engine-client.pc.in
new file mode 100644
index 0000000..4c87e1d
--- /dev/null
+++ b/update_engine/libupdate_engine-client.pc.in
@@ -0,0 +1,6 @@
+include_dir=@INCLUDE_DIR@
+
+Name: libupdate_engine-client
+Version: 1.0
+Description: update_engine client interface library
+Cflags: -I${include_dir}
diff --git a/update_engine/local_coverage_rate b/update_engine/local_coverage_rate
new file mode 100755
index 0000000..b189806
--- /dev/null
+++ b/update_engine/local_coverage_rate
@@ -0,0 +1,101 @@
+#!/bin/bash
+
+#
+# Copyright (C) 2009 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.
+#
+
+# Calculates the test-coverage percentage for non-test files in the
+# update_engine directory. Requires a file 'app.info' to contain the
+# results of running the unittests while collecting coverage data.
+
+cat app.info | awk -F '[,:]' '
+
+BEGIN { OFS = ":"; }
+
+/^SF:/{ FILEN = $2; }
+
+/^end_of_record$/{ FILEN = ""; }
+
+/^DA:/{ print FILEN, $2, $3; }
+
+' | sort | awk -F : '
+BEGIN {
+  OFS = ":";
+  FILEN = "";
+  LINE = "";
+  HITS = 0;
+}
+{
+  NEWFILEN = $1;
+  NEWLINE = $2;
+  if ((NEWFILEN == FILEN) && (NEWLINE == LINE)) {
+    HITS += $3
+  } else {
+    if (FILEN != "") {
+      print FILEN, LINE, HITS;
+    }
+    FILEN = NEWFILEN;
+    LINE = NEWLINE;
+    HITS = $3;
+  }
+}
+' | grep '^.*\/trunk\/src\/platform\/update_engine\/' | \
+fgrep -v '_unittest.cc:' | \
+fgrep -v '/test_utils.' | \
+fgrep -v '/test_http_server.cc' | \
+fgrep -v '/testrunner.cc' | \
+fgrep -v '/mock' | \
+fgrep -v '.pb.cc' | \
+awk -F : '
+
+function printfile() {
+  if (FNAME != "")
+    printf "%-40s %4d / %4d: %5.1f%%\n", FNAME, FILE_GOOD_LINES,
+        (FILE_BAD_LINES + FILE_GOOD_LINES),
+        (FILE_GOOD_LINES * 100) / (FILE_BAD_LINES + FILE_GOOD_LINES);
+}
+
+BEGIN {
+  FNAME = "";
+  FILE_BAD_LINES = 0;
+  FILE_GOOD_LINES = 0;
+}
+{
+  // calc filename
+  ARR_SIZE = split($1, PARTS, "/");
+  NEWFNAME = PARTS[ARR_SIZE];
+  if (NEWFNAME != FNAME) {
+    printfile();
+    FILE_BAD_LINES = 0;
+    FILE_GOOD_LINES = 0;
+    FNAME = NEWFNAME;
+  }
+  if ($3 == "0") {
+    BAD_LINES += 1;
+    FILE_BAD_LINES += 1;
+  } else {
+    GOOD_LINES += 1;
+    FILE_GOOD_LINES += 1;
+  }
+}
+
+END {
+  printfile();
+  print "---\nSummary: tested " GOOD_LINES " / " (BAD_LINES + GOOD_LINES);
+  printf(
+    "Test coverage: %.1f%%\n",
+    ((GOOD_LINES * 100) / (BAD_LINES + GOOD_LINES)));
+}
+'
diff --git a/update_engine/main.cc b/update_engine/main.cc
new file mode 100644
index 0000000..4275bc1
--- /dev/null
+++ b/update_engine/main.cc
@@ -0,0 +1,131 @@
+//
+// 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 <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xz.h>
+
+#include <string>
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/flag_helper.h>
+
+#include "update_engine/common/terminator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/daemon.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+namespace {
+
+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 GetTimeAsString(time_t utime) {
+  struct tm tm;
+  CHECK_EQ(localtime_r(&utime, &tm), &tm);
+  char str[16];
+  CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u);
+  return str;
+}
+
+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(),
+                         GetTimeAsString(::time(nullptr)).c_str());
+  mkdir(kLogsDir.c_str(), 0755);
+  SetupLogSymlink(kLogSymlink, kLogPath);
+  return kLogSymlink;
+}
+
+void SetupLogging(bool log_to_std_err) {
+  string log_file;
+  logging::LoggingSettings log_settings;
+  log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+  log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+
+  if (log_to_std_err) {
+    // Log to stderr initially.
+    log_settings.log_file = nullptr;
+    log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+  } else {
+    log_file = SetupLogFile("/var/log");
+    log_settings.log_file = log_file.c_str();
+    log_settings.logging_dest = logging::LOG_TO_FILE;
+  }
+
+  logging::InitLogging(log_settings);
+}
+
+}  // namespace
+}  // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+  DEFINE_bool(logtostderr, false,
+              "Write logs to stderr instead of to a file in log_dir.");
+  DEFINE_bool(foreground, false,
+              "Don't daemon()ize; run in foreground.");
+
+  chromeos_update_engine::Terminator::Init();
+  brillo::FlagHelper::Init(argc, argv, "Chromium OS Update Engine");
+  chromeos_update_engine::SetupLogging(FLAGS_logtostderr);
+  if (!FLAGS_foreground)
+    PLOG_IF(FATAL, daemon(0, 0) == 1) << "daemon() failed";
+
+  LOG(INFO) << "Chrome OS Update Engine starting";
+
+  // xz-embedded requires to initialize its CRC-32 table once on startup.
+  xz_crc32_init();
+
+  // Ensure that all written files have safe permissions.
+  // This is a mask, so we _block_ all permissions for the group owner and other
+  // users but allow all permissions for the user owner. We allow execution
+  // for the owner so we can create directories.
+  // Done _after_ log file creation.
+  umask(S_IRWXG | S_IRWXO);
+
+  chromeos_update_engine::UpdateEngineDaemon update_engine_daemon;
+  int exit_code = update_engine_daemon.Run();
+
+  LOG(INFO) << "Chrome OS Update Engine terminating with exit code "
+            << exit_code;
+  return exit_code;
+}
diff --git a/update_engine/metrics.cc b/update_engine/metrics.cc
new file mode 100644
index 0000000..742ba7e
--- /dev/null
+++ b/update_engine/metrics.cc
@@ -0,0 +1,526 @@
+//
+// 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/metrics.h"
+
+#include <string>
+
+#include <base/logging.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/utils.h"
+#include "update_engine/metrics_utils.h"
+#include "update_engine/system_state.h"
+
+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 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 kMetricSuccessfulUpdatePayloadType[] =
+    "UpdateEngine.SuccessfulUpdate.PayloadType";
+const char kMetricSuccessfulUpdatePayloadSizeMiB[] =
+    "UpdateEngine.SuccessfulUpdate.PayloadSizeMiB";
+const char kMetricSuccessfulUpdateRebootCount[] =
+    "UpdateEngine.SuccessfulUpdate.RebootCount";
+const char kMetricSuccessfulUpdateTotalDurationMinutes[] =
+    "UpdateEngine.SuccessfulUpdate.TotalDurationMinutes";
+const char kMetricSuccessfulUpdateUpdatesAbandonedCount[] =
+    "UpdateEngine.SuccessfulUpdate.UpdatesAbandonedCount";
+const char kMetricSuccessfulUpdateUrlSwitchCount[] =
+    "UpdateEngine.SuccessfulUpdate.UrlSwitchCount";
+
+// UpdateEngine.Rollback.* metric.
+const char kMetricRollbackResult[] = "UpdateEngine.Rollback.Result";
+
+// UpdateEngine.CertificateCheck.* metrics.
+const char kMetricCertificateCheckUpdateCheck[] =
+    "UpdateEngine.CertificateCheck.UpdateCheck";
+const char kMetricCertificateCheckDownload[] =
+    "UpdateEngine.CertificateCheck.Download";
+
+// UpdateEngine.* metrics.
+const char kMetricFailedUpdateCount[] = "UpdateEngine.FailedUpdateCount";
+const char kMetricInstallDateProvisioningSource[] =
+    "UpdateEngine.InstallDateProvisioningSource";
+const char kMetricTimeToRebootMinutes[] =
+    "UpdateEngine.TimeToRebootMinutes";
+
+void ReportDailyMetrics(SystemState *system_state,
+                        base::TimeDelta os_age) {
+  string metric = metrics::kMetricDailyOSAgeDays;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(os_age)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(
+      metric,
+      static_cast<int>(os_age.InDays()),
+      0,     // min: 0 days
+      6*30,  // max: 6 months (approx)
+      50);   // num_buckets
+}
+
+void ReportUpdateCheckMetrics(SystemState *system_state,
+                              CheckResult result,
+                              CheckReaction reaction,
+                              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;
+    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+    system_state->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;
+    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+    system_state->metrics_lib()->SendEnumToUMA(metric, value, max_value);
+  }
+  if (download_error_code != metrics::DownloadErrorCode::kUnset) {
+    metric = metrics::kMetricCheckDownloadErrorCode;
+    value = static_cast<int>(download_error_code);
+    LOG(INFO) << "Sending " << value << " for metric " << metric << " (sparse)";
+    system_state->metrics_lib()->SendSparseToUMA(metric, value);
+  }
+
+  base::TimeDelta time_since_last;
+  if (metrics_utils::WallclockDurationHelper(
+          system_state,
+          kPrefsMetricsCheckLastReportingTime,
+          &time_since_last)) {
+    metric = kMetricCheckTimeSinceLastCheckMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+              << " for metric " << metric;
+    system_state->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 (metrics_utils::MonotonicDurationHelper(system_state,
+                                             &uptime_since_last_storage,
+                                             &uptime_since_last)) {
+    metric = kMetricCheckTimeSinceLastCheckUptimeMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+              << " for metric " << metric;
+    system_state->metrics_lib()->SendToUMA(
+        metric,
+        uptime_since_last.InMinutes(),
+        0,         // min: 0 min
+        30*24*60,  // max: 30 days
+        50);       // num_buckets
+  }
+}
+
+void ReportAbnormallyTerminatedUpdateAttemptMetrics(
+    SystemState *system_state) {
+
+  string metric = metrics::kMetricAttemptResult;
+  AttemptResult attempt_result = AttemptResult::kAbnormalTermination;
+
+  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric,
+      static_cast<int>(attempt_result),
+      static_cast<int>(AttemptResult::kNumConstants));
+}
+
+void ReportUpdateAttemptMetrics(
+    SystemState *system_state,
+    int attempt_number,
+    PayloadType payload_type,
+    base::TimeDelta duration,
+    base::TimeDelta duration_uptime,
+    int64_t payload_size,
+    int64_t payload_bytes_downloaded,
+    int64_t payload_download_speed_bps,
+    DownloadSource download_source,
+    AttemptResult attempt_result,
+    ErrorCode internal_error_code,
+    DownloadErrorCode payload_download_error_code,
+    ConnectionType connection_type) {
+  string metric;
+
+  metric = metrics::kMetricAttemptNumber;
+  LOG(INFO) << "Uploading " << attempt_number << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         attempt_number,
+                                         0,    // min: 0 attempts
+                                         49,   // max: 49 attempts
+                                         50);  // num_buckets
+
+  metric = metrics::kMetricAttemptPayloadType;
+  LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(metric,
+                                             payload_type,
+                                             kNumPayloadTypes);
+
+  metric = metrics::kMetricAttemptDurationMinutes;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         duration.InMinutes(),
+                                         0,         // min: 0 min
+                                         10*24*60,  // max: 10 days
+                                         50);       // num_buckets
+
+  metric = metrics::kMetricAttemptDurationUptimeMinutes;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime)
+            << " for metric " <<  metric;
+  system_state->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;
+  LOG(INFO) << "Uploading " << payload_size_mib << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         payload_size_mib,
+                                         0,     // min: 0 MiB
+                                         1024,  // max: 1024 MiB = 1 GiB
+                                         50);   // num_buckets
+
+  metric = metrics::kMetricAttemptPayloadBytesDownloadedMiB;
+  int64_t payload_bytes_downloaded_mib =
+       payload_bytes_downloaded / kNumBytesInOneMiB;
+  LOG(INFO) << "Uploading " << payload_bytes_downloaded_mib
+            << " for metric " <<  metric;
+  system_state->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;
+  LOG(INFO) << "Uploading " << payload_download_speed_kbps
+            << " for metric " <<  metric;
+  system_state->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;
+  LOG(INFO) << "Uploading " << download_source
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(metric,
+                                             download_source,
+                                             kNumDownloadSources);
+
+  metric = metrics::kMetricAttemptResult;
+  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric,
+      static_cast<int>(attempt_result),
+      static_cast<int>(AttemptResult::kNumConstants));
+
+  if (internal_error_code != ErrorCode::kSuccess) {
+    metric = metrics::kMetricAttemptInternalErrorCode;
+    LOG(INFO) << "Uploading " << internal_error_code
+              << " for metric " <<  metric;
+    system_state->metrics_lib()->SendEnumToUMA(
+        metric,
+        static_cast<int>(internal_error_code),
+        static_cast<int>(ErrorCode::kUmaReportedMax));
+  }
+
+  if (payload_download_error_code != DownloadErrorCode::kUnset) {
+    metric = metrics::kMetricAttemptDownloadErrorCode;
+    LOG(INFO) << "Uploading " << static_cast<int>(payload_download_error_code)
+              << " for metric " <<  metric << " (sparse)";
+    system_state->metrics_lib()->SendSparseToUMA(
+        metric,
+        static_cast<int>(payload_download_error_code));
+  }
+
+  base::TimeDelta time_since_last;
+  if (metrics_utils::WallclockDurationHelper(
+          system_state,
+          kPrefsMetricsAttemptLastReportingTime,
+          &time_since_last)) {
+    metric = kMetricAttemptTimeSinceLastAttemptMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+              << " for metric " << metric;
+    system_state->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 (metrics_utils::MonotonicDurationHelper(system_state,
+                                             &uptime_since_last_storage,
+                                             &uptime_since_last)) {
+    metric = kMetricAttemptTimeSinceLastAttemptUptimeMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+              << " for metric " << metric;
+    system_state->metrics_lib()->SendToUMA(
+        metric,
+        uptime_since_last.InMinutes(),
+        0,         // min: 0 min
+        30*24*60,  // max: 30 days
+        50);       // num_buckets
+  }
+
+  metric = metrics::kMetricAttemptConnectionType;
+  LOG(INFO) << "Uploading " << static_cast<int>(connection_type)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric,
+      static_cast<int>(connection_type),
+      static_cast<int>(ConnectionType::kNumConstants));
+}
+
+
+void ReportSuccessfulUpdateMetrics(
+         SystemState *system_state,
+         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,
+         int reboot_count,
+         int url_switch_count) {
+  string metric;
+  int64_t mbs;
+
+  metric = kMetricSuccessfulUpdatePayloadSizeMiB;
+  mbs = payload_size / kNumBytesInOneMiB;
+  LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+  system_state->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) {
+      LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+      system_state->metrics_lib()->SendToUMA(metric,
+                                             mbs,
+                                             0,     // min: 0 MiB
+                                             1024,  // max: 1024 MiB = 1 GiB
+                                             50);   // num_buckets
+    }
+  }
+
+  metric = metrics::kMetricSuccessfulUpdateDownloadSourcesUsed;
+  LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used
+            << " (bit flags) for metric " << metric;
+  system_state->metrics_lib()->SendToUMA(
+      metric,
+      download_sources_used,
+      0,                               // min
+      (1 << kNumDownloadSources) - 1,  // max
+      1 << kNumDownloadSources);       // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage;
+  LOG(INFO) << "Uploading " << download_overhead_percentage
+            << "% for metric " << metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         download_overhead_percentage,
+                                         0,     // min: 0% overhead
+                                         1000,  // max: 1000% overhead
+                                         50);   // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateUrlSwitchCount;
+  LOG(INFO) << "Uploading " << url_switch_count
+            << " (count) for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         url_switch_count,
+                                         0,    // min: 0 URL switches
+                                         49,   // max: 49 URL switches
+                                         50);  // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateTotalDurationMinutes;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration)
+            << " for metric " <<  metric;
+  system_state->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::kMetricSuccessfulUpdateRebootCount;
+  LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric "
+            <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         reboot_count,
+                                         0,    // min: 0 reboots
+                                         49,   // max: 49 reboots
+                                         50);  // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdatePayloadType;
+  system_state->metrics_lib()->SendEnumToUMA(metric,
+                                             payload_type,
+                                             kNumPayloadTypes);
+  LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+            << " for metric " <<  metric;
+
+  metric = metrics::kMetricSuccessfulUpdateAttemptCount;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         attempt_count,
+                                         1,    // min: 1 attempt
+                                         50,   // max: 50 attempts
+                                         50);  // num_buckets
+  LOG(INFO) << "Uploading " << attempt_count
+            << " for metric " <<  metric;
+
+  metric = metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount;
+  LOG(INFO) << "Uploading " << updates_abandoned_count
+            << " (count) for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         updates_abandoned_count,
+                                         0,    // min: 0 counts
+                                         49,   // max: 49 counts
+                                         50);  // num_buckets
+}
+
+void ReportRollbackMetrics(SystemState *system_state,
+                           RollbackResult result) {
+  string metric;
+  int value;
+
+  metric = metrics::kMetricRollbackResult;
+  value = static_cast<int>(result);
+  LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric,
+      value,
+      static_cast<int>(metrics::RollbackResult::kNumConstants));
+}
+
+void ReportCertificateCheckMetrics(SystemState* system_state,
+                                   ServerToCheck server_to_check,
+                                   CertificateCheckResult result) {
+  string metric;
+  switch (server_to_check) {
+    case ServerToCheck::kUpdate:
+      metric = kMetricCertificateCheckUpdateCheck;
+      break;
+    case ServerToCheck::kDownload:
+      metric = kMetricCertificateCheckDownload;
+      break;
+    case ServerToCheck::kNone:
+      return;
+  }
+  LOG(INFO) << "Uploading " << static_cast<int>(result) << " for metric "
+            << metric;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric, static_cast<int>(result),
+      static_cast<int>(CertificateCheckResult::kNumConstants));
+}
+
+}  // namespace metrics
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/metrics.h b/update_engine/metrics.h
new file mode 100644
index 0000000..7c369ee
--- /dev/null
+++ b/update_engine/metrics.h
@@ -0,0 +1,321 @@
+//
+// 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_METRICS_H_
+#define UPDATE_ENGINE_METRICS_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/error_code.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 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 kMetricAttemptTimeSinceLastAttemptSeconds[];
+extern const char kMetricAttemptTimeSinceLastAttemptUptimeSeconds[];
+extern const char kMetricAttemptPayloadBytesDownloaded[];
+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 kMetricSuccessfulUpdatePayloadType[];
+extern const char kMetricSuccessfulUpdatePayloadSizeMiB[];
+extern const char kMetricSuccessfulUpdateTotalDurationMinutes[];
+extern const char kMetricSuccessfulUpdateRebootCount[];
+extern const char kMetricSuccessfulUpdateUpdatesAbandonedCount[];
+extern const char kMetricSuccessfulUpdateUrlSwitchCount[];
+
+// UpdateEngine.Rollback.* metric.
+extern const char kMetricRollbackResult[];
+
+// UpdateEngine.* metrics.
+extern const char kMetricFailedUpdateCount[];
+extern const char kMetricInstallDateProvisioningSource[];
+extern const char kMetricTimeToRebootMinutes[];
+
+// The possible outcomes when checking for updates.
+//
+// This is used in the UpdateEngine.Check.Result histogram.
+enum class CheckResult {
+  kUpdateAvailable,    // Response indicates an update is available.
+  kNoUpdateAvailable,  // Response indicates no updates are available.
+  kDownloadError,      // Error downloading response from Omaha.
+  kParsingError,       // Error parsing response.
+  kRebootPending,      // No update check was performed a reboot is pending.
+
+  kNumConstants,
+  kUnset = -1
+};
+
+// Possible ways a device can react to a new update being available.
+//
+// This is used in the UpdateEngine.Check.Reaction histogram.
+enum class CheckReaction {
+  kUpdating,    // Device proceeds to download and apply update.
+  kIgnored  ,   // Device-policy dictates ignoring the update.
+  kDeferring,   // Device-policy dictates waiting.
+  kBackingOff,  // Previous errors dictates waiting.
+
+  kNumConstants,
+  kUnset = -1
+};
+
+// The possible ways that downloading from a HTTP or HTTPS server can fail.
+//
+// This is used in the UpdateEngine.Check.DownloadErrorCode and
+// UpdateEngine.Attempt.DownloadErrorCode histograms.
+enum class DownloadErrorCode {
+  // Errors that can happen in the field. See http://crbug.com/355745
+  // for how we plan to add more detail in the future.
+  kDownloadError = 0,  // Error downloading data from server.
+
+  // IMPORTANT: When adding a new error code, add at the bottom of the
+  // above block and before the kInputMalformed field. This
+  // is to ensure that error codes are not reordered.
+
+  // This error code is used to convey that malformed input was given
+  // to the utils::GetDownloadErrorCode() function. This should never
+  // happen but if it does it's because of an internal update_engine
+  // error and we're interested in knowing this.
+  kInputMalformed = 100,
+
+  // Bucket for capturing HTTP status codes not in the 200-599
+  // range. This should never happen in practice but if it does we
+  // want to know.
+  kHttpStatusOther = 101,
+
+  // Above 200 and below 600, the value is the HTTP status code.
+  kHttpStatus200 = 200,
+
+  kNumConstants = 600,
+
+  kUnset = -1
+};
+
+// Possible ways an update attempt can end.
+//
+// This is used in the UpdateEngine.Attempt.Result histogram.
+enum class AttemptResult {
+  kUpdateSucceeded,             // The update succeeded.
+  kInternalError,               // An internal error occurred.
+  kPayloadDownloadError,        // Failure while downloading payload.
+  kMetadataMalformed,           // Metadata was malformed.
+  kOperationMalformed,          // An operation was malformed.
+  kOperationExecutionError,     // An operation failed to execute.
+  kMetadataVerificationFailed,  // Metadata verification failed.
+  kPayloadVerificationFailed,   // Payload verification failed.
+  kVerificationFailed,          // Root or Kernel partition verification failed.
+  kPostInstallFailed,           // The postinstall step failed.
+  kAbnormalTermination,         // The attempt ended abnormally.
+  kUpdateCanceled,              // Update canceled by the user.
+
+  kNumConstants,
+
+  kUnset = -1
+};
+
+// Possible ways the device is connected to the Internet.
+//
+// This is used in the UpdateEngine.Attempt.ConnectionType histogram.
+enum class ConnectionType {
+  kUnknown,           // Unknown.
+  kEthernet,          // Ethernet.
+  kWifi,              // Wireless.
+  kWimax,             // WiMax.
+  kBluetooth,         // Bluetooth.
+  kCellular,          // Cellular.
+  kTetheredEthernet,  // Tethered (Ethernet).
+  kTetheredWifi,      // Tethered (Wifi).
+
+  kNumConstants,
+  kUnset = -1
+};
+
+// Possible ways a rollback can end.
+//
+// This is used in the UpdateEngine.Rollback histogram.
+enum class RollbackResult {
+  kFailed,
+  kSuccess,
+
+  kNumConstants
+};
+
+// Helper function to report metrics related to rollback. The
+// following metrics are reported:
+//
+//  |kMetricRollbackResult|
+void ReportRollbackMetrics(SystemState *system_state,
+                           RollbackResult result);
+
+// Helper function to report metrics reported once a day. The
+// following metrics are reported:
+//
+//  |kMetricDailyOSAgeDays|
+void ReportDailyMetrics(SystemState *system_state,
+                        base::TimeDelta os_age);
+
+// Helper function to report metrics after completing an update check
+// with the ChromeOS update server ("Omaha"). The following metrics
+// are reported:
+//
+//  |kMetricCheckResult|
+//  |kMetricCheckReaction|
+//  |kMetricCheckDownloadErrorCode|
+//  |kMetricCheckTimeSinceLastCheckMinutes|
+//  |kMetricCheckTimeSinceLastCheckUptimeMinutes|
+//
+// The |kMetricCheckResult| metric will only be reported if |result|
+// is not |kUnset|.
+//
+// The |kMetricCheckReaction| metric will only be reported if
+// |reaction| is not |kUnset|.
+//
+// The |kMetricCheckDownloadErrorCode| will only be reported if
+// |download_error_code| is not |kUnset|.
+//
+// The values for the |kMetricCheckTimeSinceLastCheckMinutes| and
+// |kMetricCheckTimeSinceLastCheckUptimeMinutes| metrics are
+// automatically reported and calculated by maintaining persistent
+// and process-local state variables.
+void ReportUpdateCheckMetrics(SystemState *system_state,
+                              CheckResult result,
+                              CheckReaction reaction,
+                              DownloadErrorCode download_error_code);
+
+
+// Helper function to report metrics after the completion of each
+// update attempt. The following metrics are reported:
+//
+//  |kMetricAttemptNumber|
+//  |kMetricAttemptPayloadType|
+//  |kMetricAttemptPayloadSizeMiB|
+//  |kMetricAttemptDurationSeconds|
+//  |kMetricAttemptDurationUptimeSeconds|
+//  |kMetricAttemptTimeSinceLastAttemptMinutes|
+//  |kMetricAttemptTimeSinceLastAttemptUptimeMinutes|
+//  |kMetricAttemptPayloadBytesDownloadedMiB|
+//  |kMetricAttemptPayloadDownloadSpeedKBps|
+//  |kMetricAttemptDownloadSource|
+//  |kMetricAttemptResult|
+//  |kMetricAttemptInternalErrorCode|
+//  |kMetricAttemptDownloadErrorCode|
+//
+// The |kMetricAttemptInternalErrorCode| metric will only be reported
+// if |internal_error_code| is not |kErrorSuccess|.
+//
+// The |kMetricAttemptDownloadErrorCode| metric will only be
+// reported if |payload_download_error_code| is not |kUnset|.
+//
+// The values for the |kMetricAttemptTimeSinceLastAttemptMinutes| and
+// |kMetricAttemptTimeSinceLastAttemptUptimeMinutes| metrics are
+// automatically calculated and reported by maintaining persistent and
+// process-local state variables.
+void ReportUpdateAttemptMetrics(
+    SystemState *system_state,
+    int attempt_number,
+    PayloadType payload_type,
+    base::TimeDelta duration,
+    base::TimeDelta duration_uptime,
+    int64_t payload_size,
+    int64_t payload_bytes_downloaded,
+    int64_t payload_download_speed_bps,
+    DownloadSource download_source,
+    AttemptResult attempt_result,
+    ErrorCode internal_error_code,
+    DownloadErrorCode payload_download_error_code,
+    ConnectionType connection_type);
+
+// Reports the |kAbnormalTermination| for the |kMetricAttemptResult|
+// metric. No other metrics in the UpdateEngine.Attempt.* namespace
+// will be reported.
+void ReportAbnormallyTerminatedUpdateAttemptMetrics(SystemState *system_state);
+
+// Helper function to report the after the completion of a successful
+// update attempt. The following metrics are reported:
+//
+//  |kMetricSuccessfulUpdateAttemptCount|
+//  |kMetricSuccessfulUpdateUpdatesAbandonedCount|
+//  |kMetricSuccessfulUpdatePayloadType|
+//  |kMetricSuccessfulUpdatePayloadSizeMiB|
+//  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpsServer|
+//  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpServer|
+//  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpPeer|
+//  |kMetricSuccessfulUpdateBytesDownloadedMiB|
+//  |kMetricSuccessfulUpdateDownloadSourcesUsed|
+//  |kMetricSuccessfulUpdateDownloadOverheadPercentage|
+//  |kMetricSuccessfulUpdateTotalDurationMinutes|
+//  |kMetricSuccessfulUpdateRebootCount|
+//  |kMetricSuccessfulUpdateUrlSwitchCount|
+//
+// The values for the |kMetricSuccessfulUpdateDownloadSourcesUsed| are
+// |kMetricSuccessfulUpdateBytesDownloadedMiB| metrics automatically
+// calculated from examining the |num_bytes_downloaded| array.
+void ReportSuccessfulUpdateMetrics(
+    SystemState *system_state,
+    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,
+    int reboot_count,
+    int url_switch_count);
+
+// Helper function to report the after the completion of a SSL certificate
+// check. One of the following metrics is reported:
+//
+//  |kMetricCertificateCheckUpdateCheck|
+//  |kMetricCertificateCheckDownload|
+void ReportCertificateCheckMetrics(SystemState* system_state,
+                                   ServerToCheck server_to_check,
+                                   CertificateCheckResult result);
+
+}  // namespace metrics
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_METRICS_H_
diff --git a/update_engine/metrics_utils.cc b/update_engine/metrics_utils.cc
new file mode 100644
index 0000000..263bacd
--- /dev/null
+++ b/update_engine/metrics_utils.cc
@@ -0,0 +1,309 @@
+//
+// 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/metrics_utils.h"
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/system_state.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace chromeos_update_engine {
+namespace metrics_utils {
+
+metrics::AttemptResult GetAttemptResult(ErrorCode code) {
+  ErrorCode base_code = static_cast<ErrorCode>(
+      static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+  switch (base_code) {
+    case ErrorCode::kSuccess:
+      return metrics::AttemptResult::kUpdateSucceeded;
+
+    case ErrorCode::kDownloadTransferError:
+      return metrics::AttemptResult::kPayloadDownloadError;
+
+    case ErrorCode::kDownloadInvalidMetadataSize:
+    case ErrorCode::kDownloadInvalidMetadataMagicString:
+    case ErrorCode::kDownloadMetadataSignatureError:
+    case ErrorCode::kDownloadMetadataSignatureVerificationError:
+    case ErrorCode::kPayloadMismatchedType:
+    case ErrorCode::kUnsupportedMajorPayloadVersion:
+    case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kDownloadNewPartitionInfoError:
+    case ErrorCode::kDownloadSignatureMissingInManifest:
+    case ErrorCode::kDownloadManifestParseError:
+    case ErrorCode::kDownloadOperationHashMissingError:
+      return metrics::AttemptResult::kMetadataMalformed;
+
+    case ErrorCode::kDownloadOperationHashMismatch:
+    case ErrorCode::kDownloadOperationHashVerificationError:
+      return metrics::AttemptResult::kOperationMalformed;
+
+    case ErrorCode::kDownloadOperationExecutionError:
+    case ErrorCode::kInstallDeviceOpenError:
+    case ErrorCode::kKernelDeviceOpenError:
+    case ErrorCode::kDownloadWriteError:
+    case ErrorCode::kFilesystemCopierError:
+    case ErrorCode::kFilesystemVerifierError:
+      return metrics::AttemptResult::kOperationExecutionError;
+
+    case ErrorCode::kDownloadMetadataSignatureMismatch:
+      return metrics::AttemptResult::kMetadataVerificationFailed;
+
+    case ErrorCode::kPayloadSizeMismatchError:
+    case ErrorCode::kPayloadHashMismatchError:
+    case ErrorCode::kDownloadPayloadVerificationError:
+    case ErrorCode::kSignedDeltaPayloadExpectedError:
+    case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+      return metrics::AttemptResult::kPayloadVerificationFailed;
+
+    case ErrorCode::kNewRootfsVerificationError:
+    case ErrorCode::kNewKernelVerificationError:
+      return metrics::AttemptResult::kVerificationFailed;
+
+    case ErrorCode::kPostinstallRunnerError:
+    case ErrorCode::kPostinstallBootedFromFirmwareB:
+    case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+      return metrics::AttemptResult::kPostInstallFailed;
+
+    case ErrorCode::kUserCanceled:
+      return metrics::AttemptResult::kUpdateCanceled;
+
+    // We should never get these errors in the update-attempt stage so
+    // return internal error if this happens.
+    case ErrorCode::kError:
+    case ErrorCode::kOmahaRequestXMLParseError:
+    case ErrorCode::kOmahaRequestError:
+    case ErrorCode::kOmahaResponseHandlerError:
+    case ErrorCode::kDownloadStateInitializationError:
+    case ErrorCode::kOmahaRequestEmptyResponseError:
+    case ErrorCode::kDownloadInvalidMetadataSignature:
+    case ErrorCode::kOmahaResponseInvalid:
+    case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+    // TODO(deymo): The next two items belong in their own category; they
+    // should not be counted as internal errors. b/27112092
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+    case ErrorCode::kNonCriticalUpdateInOOBE:
+    case ErrorCode::kOmahaErrorInHTTPResponse:
+    case ErrorCode::kDownloadMetadataSignatureMissingError:
+    case ErrorCode::kOmahaUpdateDeferredForBackoff:
+    case ErrorCode::kPostinstallPowerwashError:
+    case ErrorCode::kUpdateCanceledByChannelChange:
+    case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+      return metrics::AttemptResult::kInternalError;
+
+    // Special flags. These can't happen (we mask them out above) but
+    // the compiler doesn't know that. Just break out so we can warn and
+    // return |kInternalError|.
+    case ErrorCode::kUmaReportedMax:
+    case ErrorCode::kOmahaRequestHTTPResponseBase:
+    case ErrorCode::kDevModeFlag:
+    case ErrorCode::kResumedFlag:
+    case ErrorCode::kTestImageFlag:
+    case ErrorCode::kTestOmahaUrlFlag:
+    case ErrorCode::kSpecialFlags:
+      break;
+  }
+
+  LOG(ERROR) << "Unexpected error code " << base_code;
+  return metrics::AttemptResult::kInternalError;
+}
+
+metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code) {
+  ErrorCode base_code = static_cast<ErrorCode>(
+      static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+  if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) {
+    int http_status =
+        static_cast<int>(base_code) -
+        static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase);
+    if (http_status >= 200 && http_status <= 599) {
+      return static_cast<metrics::DownloadErrorCode>(
+          static_cast<int>(metrics::DownloadErrorCode::kHttpStatus200) +
+          http_status - 200);
+    } else if (http_status == 0) {
+      // The code is using HTTP Status 0 for "Unable to get http
+      // response code."
+      return metrics::DownloadErrorCode::kDownloadError;
+    }
+    LOG(WARNING) << "Unexpected HTTP status code " << http_status;
+    return metrics::DownloadErrorCode::kHttpStatusOther;
+  }
+
+  switch (base_code) {
+    // Unfortunately, ErrorCode::kDownloadTransferError is returned for a wide
+    // variety of errors (proxy errors, host not reachable, timeouts etc.).
+    //
+    // For now just map that to kDownloading. See http://crbug.com/355745
+    // for how we plan to add more detail in the future.
+    case ErrorCode::kDownloadTransferError:
+      return metrics::DownloadErrorCode::kDownloadError;
+
+    // All of these error codes are not related to downloading so break
+    // out so we can warn and return InputMalformed.
+    case ErrorCode::kSuccess:
+    case ErrorCode::kError:
+    case ErrorCode::kOmahaRequestError:
+    case ErrorCode::kOmahaResponseHandlerError:
+    case ErrorCode::kFilesystemCopierError:
+    case ErrorCode::kPostinstallRunnerError:
+    case ErrorCode::kPayloadMismatchedType:
+    case ErrorCode::kInstallDeviceOpenError:
+    case ErrorCode::kKernelDeviceOpenError:
+    case ErrorCode::kPayloadHashMismatchError:
+    case ErrorCode::kPayloadSizeMismatchError:
+    case ErrorCode::kDownloadPayloadVerificationError:
+    case ErrorCode::kDownloadNewPartitionInfoError:
+    case ErrorCode::kDownloadWriteError:
+    case ErrorCode::kNewRootfsVerificationError:
+    case ErrorCode::kNewKernelVerificationError:
+    case ErrorCode::kSignedDeltaPayloadExpectedError:
+    case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+    case ErrorCode::kPostinstallBootedFromFirmwareB:
+    case ErrorCode::kDownloadStateInitializationError:
+    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::kOmahaRequestEmptyResponseError:
+    case ErrorCode::kOmahaRequestXMLParseError:
+    case ErrorCode::kDownloadInvalidMetadataSize:
+    case ErrorCode::kDownloadInvalidMetadataSignature:
+    case ErrorCode::kOmahaResponseInvalid:
+    case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+    case ErrorCode::kNonCriticalUpdateInOOBE:
+    case ErrorCode::kOmahaErrorInHTTPResponse:
+    case ErrorCode::kDownloadOperationHashMissingError:
+    case ErrorCode::kDownloadMetadataSignatureMissingError:
+    case ErrorCode::kOmahaUpdateDeferredForBackoff:
+    case ErrorCode::kPostinstallPowerwashError:
+    case ErrorCode::kUpdateCanceledByChannelChange:
+    case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+    case ErrorCode::kUnsupportedMajorPayloadVersion:
+    case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+    case ErrorCode::kFilesystemVerifierError:
+    case ErrorCode::kUserCanceled:
+      break;
+
+    // Special flags. These can't happen (we mask them out above) but
+    // the compiler doesn't know that. Just break out so we can warn and
+    // return |kInputMalformed|.
+    case ErrorCode::kUmaReportedMax:
+    case ErrorCode::kOmahaRequestHTTPResponseBase:
+    case ErrorCode::kDevModeFlag:
+    case ErrorCode::kResumedFlag:
+    case ErrorCode::kTestImageFlag:
+    case ErrorCode::kTestOmahaUrlFlag:
+    case ErrorCode::kSpecialFlags:
+      LOG(ERROR) << "Unexpected error code " << base_code;
+      break;
+  }
+
+  return metrics::DownloadErrorCode::kInputMalformed;
+}
+
+metrics::ConnectionType GetConnectionType(ConnectionType type,
+                                          ConnectionTethering tethering) {
+  switch (type) {
+    case ConnectionType::kUnknown:
+      return metrics::ConnectionType::kUnknown;
+
+    case ConnectionType::kEthernet:
+      if (tethering == ConnectionTethering::kConfirmed)
+        return metrics::ConnectionType::kTetheredEthernet;
+      else
+        return metrics::ConnectionType::kEthernet;
+
+    case ConnectionType::kWifi:
+      if (tethering == ConnectionTethering::kConfirmed)
+        return metrics::ConnectionType::kTetheredWifi;
+      else
+        return metrics::ConnectionType::kWifi;
+
+    case ConnectionType::kWimax:
+      return metrics::ConnectionType::kWimax;
+
+    case ConnectionType::kBluetooth:
+      return metrics::ConnectionType::kBluetooth;
+
+    case ConnectionType::kCellular:
+      return metrics::ConnectionType::kCellular;
+  }
+
+  LOG(ERROR) << "Unexpected network connection type: type="
+             << static_cast<int>(type)
+             << ", tethering=" << static_cast<int>(tethering);
+
+  return metrics::ConnectionType::kUnknown;
+}
+
+bool 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 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 metrics_utils
+}  // namespace chromeos_update_engine
diff --git a/update_engine/metrics_utils.h b/update_engine/metrics_utils.h
new file mode 100644
index 0000000..d9826c1
--- /dev/null
+++ b/update_engine/metrics_utils.h
@@ -0,0 +1,71 @@
+//
+// 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_METRICS_UTILS_H_
+#define UPDATE_ENGINE_METRICS_UTILS_H_
+
+#include "update_engine/connection_utils.h"
+#include "update_engine/metrics.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+namespace metrics_utils {
+
+// Transforms a ErrorCode value into a metrics::DownloadErrorCode.
+// This obviously only works for errors related to downloading so if |code|
+// is e.g. |ErrorCode::kFilesystemCopierError| then
+// |kDownloadErrorCodeInputMalformed| is returned.
+metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code);
+
+// Transforms a ErrorCode value into a metrics::AttemptResult.
+//
+// If metrics::AttemptResult::kPayloadDownloadError is returned, you
+// can use utils::GetDownloadError() to get more detail.
+metrics::AttemptResult GetAttemptResult(ErrorCode code);
+
+// Calculates the internet connection type given |type| and |tethering|.
+metrics::ConnectionType GetConnectionType(ConnectionType type,
+                                          ConnectionTethering tethering);
+
+// 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);
+
+}  // namespace metrics_utils
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_METRICS_UTILS_H_
diff --git a/update_engine/metrics_utils_unittest.cc b/update_engine/metrics_utils_unittest.cc
new file mode 100644
index 0000000..edf6bc3
--- /dev/null
+++ b/update_engine/metrics_utils_unittest.cc
@@ -0,0 +1,210 @@
+//
+// 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/metrics_utils.h"
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/fake_system_state.h"
+
+namespace chromeos_update_engine {
+namespace metrics_utils {
+
+class MetricsUtilsTest : public ::testing::Test {};
+
+TEST(MetricsUtilsTest, GetConnectionType) {
+  // Check that expected combinations map to the right value.
+  EXPECT_EQ(metrics::ConnectionType::kUnknown,
+            GetConnectionType(ConnectionType::kUnknown,
+                              ConnectionTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            GetConnectionType(ConnectionType::kEthernet,
+                              ConnectionTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            GetConnectionType(ConnectionType::kWifi,
+                              ConnectionTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kWimax,
+            GetConnectionType(ConnectionType::kWimax,
+                              ConnectionTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kBluetooth,
+            GetConnectionType(ConnectionType::kBluetooth,
+                              ConnectionTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kCellular,
+            GetConnectionType(ConnectionType::kCellular,
+                              ConnectionTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kTetheredEthernet,
+            GetConnectionType(ConnectionType::kEthernet,
+                              ConnectionTethering::kConfirmed));
+  EXPECT_EQ(metrics::ConnectionType::kTetheredWifi,
+            GetConnectionType(ConnectionType::kWifi,
+                              ConnectionTethering::kConfirmed));
+
+  // Ensure that we don't report tethered ethernet unless it's confirmed.
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            GetConnectionType(ConnectionType::kEthernet,
+                              ConnectionTethering::kNotDetected));
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            GetConnectionType(ConnectionType::kEthernet,
+                              ConnectionTethering::kSuspected));
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            GetConnectionType(ConnectionType::kEthernet,
+                              ConnectionTethering::kUnknown));
+
+  // Ditto for tethered wifi.
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            GetConnectionType(ConnectionType::kWifi,
+                              ConnectionTethering::kNotDetected));
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            GetConnectionType(ConnectionType::kWifi,
+                              ConnectionTethering::kSuspected));
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            GetConnectionType(ConnectionType::kWifi,
+                              ConnectionTethering::kUnknown));
+}
+
+TEST(MetricsUtilsTest, 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(metrics_utils::WallclockDurationHelper(&fake_system_state,
+                                                      state_variable_key,
+                                                      &duration));
+
+  // Next time, we should get zero since the clock didn't advance.
+  EXPECT_TRUE(metrics_utils::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(metrics_utils::WallclockDurationHelper(&fake_system_state,
+                                                     state_variable_key,
+                                                     &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+  EXPECT_TRUE(metrics_utils::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(metrics_utils::WallclockDurationHelper(&fake_system_state,
+                                                     state_variable_key,
+                                                     &duration));
+  EXPECT_EQ(duration.InSeconds(), 1);
+  EXPECT_TRUE(metrics_utils::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(metrics_utils::WallclockDurationHelper(&fake_system_state,
+                                                     state_variable_key,
+                                                     &duration));
+  EXPECT_EQ(duration.InSeconds(), 2);
+  EXPECT_TRUE(metrics_utils::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(metrics_utils::WallclockDurationHelper(&fake_system_state,
+                                                      state_variable_key,
+                                                      &duration));
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000));
+  EXPECT_TRUE(metrics_utils::WallclockDurationHelper(&fake_system_state,
+                                                     state_variable_key,
+                                                     &duration));
+  EXPECT_EQ(duration.InSeconds(), 1);
+}
+
+TEST(MetricsUtilsTest, 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(metrics_utils::MonotonicDurationHelper(&fake_system_state,
+                                                      &storage,
+                                                      &duration));
+
+  // Next time, we should get zero since the clock didn't advance.
+  EXPECT_TRUE(metrics_utils::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(metrics_utils::MonotonicDurationHelper(&fake_system_state,
+                                                     &storage,
+                                                     &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+  EXPECT_TRUE(metrics_utils::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(metrics_utils::MonotonicDurationHelper(&fake_system_state,
+                                                     &storage,
+                                                     &duration));
+  EXPECT_EQ(duration.InSeconds(), 1);
+  EXPECT_TRUE(metrics_utils::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(metrics_utils::MonotonicDurationHelper(&fake_system_state,
+                                                     &storage,
+                                                     &duration));
+  EXPECT_EQ(duration.InSeconds(), 2);
+  EXPECT_TRUE(metrics_utils::MonotonicDurationHelper(&fake_system_state,
+                                                     &storage,
+                                                     &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+}
+
+}  // namespace metrics_utils
+}  // namespace chromeos_update_engine
diff --git a/update_engine/mock_certificate_checker.h b/update_engine/mock_certificate_checker.h
new file mode 100644
index 0000000..c86f502
--- /dev/null
+++ b/update_engine/mock_certificate_checker.h
@@ -0,0 +1,38 @@
+//
+// 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_MOCK_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_MOCK_CERTIFICATE_CHECKER_H_
+
+#include <gmock/gmock.h>
+#include <openssl/ssl.h>
+
+#include "update_engine/certificate_checker.h"
+
+namespace chromeos_update_engine {
+
+class MockOpenSSLWrapper : public OpenSSLWrapper {
+ public:
+  MOCK_CONST_METHOD4(GetCertificateDigest,
+                     bool(X509_STORE_CTX* x509_ctx,
+                          int* out_depth,
+                          unsigned int* out_digest_length,
+                          uint8_t* out_digest));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_CERTIFICATE_CHECKER_H_
diff --git a/update_engine/mock_connection_manager.h b/update_engine/mock_connection_manager.h
new file mode 100644
index 0000000..e37460b
--- /dev/null
+++ b/update_engine/mock_connection_manager.h
@@ -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.
+//
+
+#ifndef UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/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));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
diff --git a/update_engine/mock_file_writer.h b/update_engine/mock_file_writer.h
new file mode 100644
index 0000000..72d6a86
--- /dev/null
+++ b/update_engine/mock_file_writer.h
@@ -0,0 +1,33 @@
+//
+// 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_MOCK_FILE_WRITER_H_
+#define UPDATE_ENGINE_MOCK_FILE_WRITER_H_
+
+#include <gmock/gmock.h>
+#include "update_engine/payload_consumer/file_writer.h"
+
+namespace chromeos_update_engine {
+
+class MockFileWriter : public FileWriter {
+ public:
+  MOCK_METHOD2(Write, ssize_t(const void* bytes, size_t count));
+  MOCK_METHOD0(Close, int());
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_FILE_WRITER_H_
diff --git a/update_engine/mock_omaha_request_params.h b/update_engine/mock_omaha_request_params.h
new file mode 100644
index 0000000..5d5d47b
--- /dev/null
+++ b/update_engine/mock_omaha_request_params.h
@@ -0,0 +1,91 @@
+//
+// 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_MOCK_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/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, to_more_stable_channel())
+        .WillByDefault(testing::Invoke(
+            this, &MockOmahaRequestParams::fake_to_more_stable_channel));
+    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, is_powerwash_allowed())
+        .WillByDefault(testing::Invoke(
+            this, &MockOmahaRequestParams::fake_is_powerwash_allowed));
+  }
+
+  MOCK_CONST_METHOD0(to_more_stable_channel, bool(void));
+  MOCK_CONST_METHOD0(GetAppId, std::string(void));
+  MOCK_METHOD3(SetTargetChannel, bool(const std::string& channel,
+                                      bool is_powerwash_allowed,
+                                      std::string* error));
+  MOCK_METHOD0(UpdateDownloadChannel, void(void));
+  MOCK_CONST_METHOD0(is_powerwash_allowed, bool(void));
+  MOCK_CONST_METHOD0(IsUpdateUrlOfficial, 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.
+  bool fake_to_more_stable_channel() const {
+    return OmahaRequestParams::to_more_stable_channel();
+  }
+
+  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 fake_is_powerwash_allowed() const {
+    return OmahaRequestParams::is_powerwash_allowed();
+  }
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_
diff --git a/update_engine/mock_p2p_manager.h b/update_engine/mock_p2p_manager.h
new file mode 100644
index 0000000..5f4418e
--- /dev/null
+++ b/update_engine/mock_p2p_manager.h
@@ -0,0 +1,109 @@
+//
+// 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_MOCK_P2P_MANAGER_H_
+#define UPDATE_ENGINE_MOCK_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/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_MOCK_P2P_MANAGER_H_
diff --git a/update_engine/mock_payload_state.h b/update_engine/mock_payload_state.h
new file mode 100644
index 0000000..2f654c7
--- /dev/null
+++ b/update_engine/mock_payload_state.h
@@ -0,0 +1,82 @@
+//
+// 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_MOCK_PAYLOAD_STATE_H_
+#define UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/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&));
+
+  // 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(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_CONST_METHOD0(GetAttemptErrorCode, ErrorCode());
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
diff --git a/update_engine/mock_power_manager.h b/update_engine/mock_power_manager.h
new file mode 100644
index 0000000..8363171
--- /dev/null
+++ b/update_engine/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_MOCK_POWER_MANAGER_H_
+#define UPDATE_ENGINE_MOCK_POWER_MANAGER_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/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_MOCK_POWER_MANAGER_H_
diff --git a/update_engine/mock_proxy_resolver.h b/update_engine/mock_proxy_resolver.h
new file mode 100644
index 0000000..0595f5a
--- /dev/null
+++ b/update_engine/mock_proxy_resolver.h
@@ -0,0 +1,38 @@
+//
+// 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_MOCK_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_MOCK_PROXY_RESOLVER_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/proxy_resolver.h"
+
+namespace chromeos_update_engine {
+
+class MockProxyResolver : public ProxyResolver {
+ public:
+  MOCK_METHOD3(GetProxiesForUrl,
+               bool(const std::string& url,
+                    ProxiesResolvedFn callback,
+                    void* data));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_PROXY_RESOLVER_H_
diff --git a/update_engine/mock_update_attempter.h b/update_engine/mock_update_attempter.h
new file mode 100644
index 0000000..89f163e
--- /dev/null
+++ b/update_engine/mock_update_attempter.h
@@ -0,0 +1,64 @@
+//
+// 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_MOCK_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
+
+#include <string>
+
+#include "update_engine/update_attempter.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+class MockUpdateAttempter : public UpdateAttempter {
+ public:
+  using UpdateAttempter::UpdateAttempter;
+
+  MOCK_METHOD6(Update, void(const std::string& app_version,
+                            const std::string& omaha_url,
+                            const std::string& target_channel,
+                            const std::string& target_version_prefix,
+                            bool obey_proxies,
+                            bool interactive));
+
+  MOCK_METHOD5(GetStatus, bool(int64_t* last_checked_time,
+                               double* progress,
+                               std::string* current_operation,
+                               std::string* new_version,
+                               int64_t* new_size));
+
+  MOCK_METHOD1(GetBootTimeAtUpdate, bool(base::Time* out_boot_time));
+
+  MOCK_METHOD0(ResetStatus, bool(void));
+
+  MOCK_METHOD3(CheckForUpdate, void(const std::string& app_version,
+                                    const std::string& omaha_url,
+                                    bool is_interactive));
+
+  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));
+
+  MOCK_METHOD0(IsAnyUpdateSourceAllowed, bool(void));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
diff --git a/update_engine/network_selector.h b/update_engine/network_selector.h
new file mode 100644
index 0000000..22aed8e
--- /dev/null
+++ b/update_engine/network_selector.h
@@ -0,0 +1,33 @@
+//
+// 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_NETWORK_SELECTOR_H_
+#define UPDATE_ENGINE_NETWORK_SELECTOR_H_
+
+#include <memory>
+
+#include "update_engine/network_selector_interface.h"
+
+namespace chromeos_update_engine {
+namespace network {
+
+// Creates the NetworkSelectorInterface instance for the given platform.
+std::unique_ptr<NetworkSelectorInterface> CreateNetworkSelector();
+
+}  // namespace network
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_NETWORK_SELECTOR_H_
diff --git a/update_engine/network_selector_android.cc b/update_engine/network_selector_android.cc
new file mode 100644
index 0000000..6879b69
--- /dev/null
+++ b/update_engine/network_selector_android.cc
@@ -0,0 +1,45 @@
+//
+// 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/network_selector_android.h"
+
+#include <android/multinetwork.h>
+#include <base/logging.h>
+#include <brillo/make_unique_ptr.h>
+
+namespace chromeos_update_engine {
+
+namespace network {
+
+// Factory defined in network_selector.h.
+std::unique_ptr<NetworkSelectorInterface> CreateNetworkSelector() {
+  return brillo::make_unique_ptr(new NetworkSelectorAndroid());
+}
+
+}  // namespace network
+
+// Defined in network_selector_interface.h.
+const NetworkId kDefaultNetworkId = NETWORK_UNSPECIFIED;
+
+bool NetworkSelectorAndroid::SetProcessNetwork(NetworkId network_id) {
+  if (android_setprocnetwork(network_id) < 0) {
+    PLOG(ERROR) << "Binding the network to " << network_id;
+    return false;
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/network_selector_android.h b/update_engine/network_selector_android.h
new file mode 100644
index 0000000..135536c
--- /dev/null
+++ b/update_engine/network_selector_android.h
@@ -0,0 +1,40 @@
+//
+// 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_NETWORK_SELECTOR_ANDROID_H_
+#define UPDATE_ENGINE_NETWORK_SELECTOR_ANDROID_H_
+
+#include <base/macros.h>
+
+#include "update_engine/network_selector_interface.h"
+
+namespace chromeos_update_engine {
+
+class NetworkSelectorAndroid final : public NetworkSelectorInterface {
+ public:
+  NetworkSelectorAndroid() = default;
+  ~NetworkSelectorAndroid() override = default;
+
+  // NetworkSelectorInterface overrides.
+  bool SetProcessNetwork(NetworkId network_id) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NetworkSelectorAndroid);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_NETWORK_SELECTOR_ANDROID_H_
diff --git a/update_engine/network_selector_interface.h b/update_engine/network_selector_interface.h
new file mode 100644
index 0000000..6c17b2c
--- /dev/null
+++ b/update_engine/network_selector_interface.h
@@ -0,0 +1,49 @@
+//
+// 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_NETWORK_SELECTOR_INTERFACE_H_
+#define UPDATE_ENGINE_NETWORK_SELECTOR_INTERFACE_H_
+
+#include <cstdint>
+
+namespace chromeos_update_engine {
+
+typedef uint64_t NetworkId;
+
+// A constant value used to indicate the default network id. Defined in the
+// network_selector_*.cc file.
+extern const NetworkId kDefaultNetworkId;
+
+// A class that handles the network used for the connections performed from this
+// process in a platform-specific way.
+
+class NetworkSelectorInterface {
+ public:
+
+  virtual ~NetworkSelectorInterface() = default;
+
+  // Set the current process network. All sockets created in the future will be
+  // bound to this particular network. Call this with the special value
+  // kNetworkId to use the default network.
+  virtual bool SetProcessNetwork(NetworkId network_id) = 0;
+
+ protected:
+  NetworkSelectorInterface() = default;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_NETWORK_SELECTOR_INTERFACE_H_
diff --git a/update_engine/network_selector_stub.cc b/update_engine/network_selector_stub.cc
new file mode 100644
index 0000000..218d454
--- /dev/null
+++ b/update_engine/network_selector_stub.cc
@@ -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.
+//
+
+#include "update_engine/network_selector_stub.h"
+
+#include <base/logging.h>
+#include <brillo/make_unique_ptr.h>
+
+namespace chromeos_update_engine {
+
+namespace network {
+
+// Factory defined in network_selector.h.
+std::unique_ptr<NetworkSelectorInterface> CreateNetworkSelector() {
+  return brillo::make_unique_ptr(new NetworkSelectorStub());
+}
+
+}  // namespace network
+
+// Defined in network_selector_interface.h.
+const NetworkId kDefaultNetworkId = 0;
+
+bool NetworkSelectorStub::SetProcessNetwork(NetworkId network_id) {
+  if (network_id != kDefaultNetworkId) {
+    LOG(ERROR) << "SetProcessNetwork not implemented.";
+    return false;
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/network_selector_stub.h b/update_engine/network_selector_stub.h
new file mode 100644
index 0000000..b3f7b48
--- /dev/null
+++ b/update_engine/network_selector_stub.h
@@ -0,0 +1,40 @@
+//
+// 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_NETWORK_SELECTOR_STUB_H_
+#define UPDATE_ENGINE_NETWORK_SELECTOR_STUB_H_
+
+#include <base/macros.h>
+
+#include "update_engine/network_selector_interface.h"
+
+namespace chromeos_update_engine {
+
+class NetworkSelectorStub final : public NetworkSelectorInterface {
+ public:
+  NetworkSelectorStub() = default;
+  ~NetworkSelectorStub() override = default;
+
+  // NetworkSelectorInterface overrides.
+  bool SetProcessNetwork(NetworkId network_id) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NetworkSelectorStub);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_NETWORK_SELECTOR_STUB_H_
diff --git a/update_engine/omaha_request_action.cc b/update_engine/omaha_request_action.cc
new file mode 100644
index 0000000..3d2dac1
--- /dev/null
+++ b/update_engine/omaha_request_action.cc
@@ -0,0 +1,1535 @@
+//
+// 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/omaha_request_action.h"
+
+#include <inttypes.h>
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <base/bind.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 <expat.h>
+#include <metrics/metrics_library.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/platform_constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/connection_manager_interface.h"
+#include "update_engine/metrics.h"
+#include "update_engine/metrics_utils.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// List of custom pair tags that we interpret in the Omaha Response:
+static const char* kTagDeadline = "deadline";
+static const char* kTagDisablePayloadBackoff = "DisablePayloadBackoff";
+static const char* kTagVersion = "version";
+// Deprecated: "IsDelta"
+static const char* kTagIsDeltaPayload = "IsDeltaPayload";
+static const char* kTagMaxFailureCountPerUrl = "MaxFailureCountPerUrl";
+static const char* kTagMaxDaysToScatter = "MaxDaysToScatter";
+// Deprecated: "ManifestSignatureRsa"
+// Deprecated: "ManifestSize"
+static const char* kTagMetadataSignatureRsa = "MetadataSignatureRsa";
+static const char* kTagMetadataSize = "MetadataSize";
+static const char* kTagMoreInfo = "MoreInfo";
+// Deprecated: "NeedsAdmin"
+static const char* kTagPrompt = "Prompt";
+static const char* kTagSha256 = "sha256";
+static const char* kTagDisableP2PForDownloading = "DisableP2PForDownloading";
+static const char* kTagDisableP2PForSharing = "DisableP2PForSharing";
+static const char* kTagPublicKeyRsa = "PublicKeyRsa";
+
+static const char* kOmahaUpdaterVersion = "0.1.0.0";
+
+// X-GoogleUpdate headers.
+static const char* kXGoogleUpdateInteractivity = "X-GoogleUpdate-Interactivity";
+static const char* kXGoogleUpdateAppId = "X-GoogleUpdate-AppId";
+static const char* kXGoogleUpdateUpdater = "X-GoogleUpdate-Updater";
+
+// updatecheck attributes (without the underscore prefix).
+static const char* kEolAttr = "eol";
+
+namespace {
+
+// 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.
+string GetPingAttribute(const string& name, int ping_days) {
+  if (ping_days > 0 || ping_days == OmahaRequestAction::kNeverPinged)
+    return base::StringPrintf(" %s=\"%d\"", name.c_str(), ping_days);
+  return "";
+}
+
+// Returns an XML ping element if any of the elapsed days need to be
+// sent, or an empty string otherwise.
+string GetPingXml(int ping_active_days, int ping_roll_call_days) {
+  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 "";
+}
+
+// Returns an XML that goes into the body of the <app> element of the Omaha
+// request based on the given parameters.
+string GetAppBody(const OmahaEvent* event,
+                  OmahaRequestParams* params,
+                  bool ping_only,
+                  bool include_ping,
+                  int ping_active_days,
+                  int ping_roll_call_days,
+                  PrefsInterface* prefs) {
+  string app_body;
+  if (event == nullptr) {
+    if (include_ping)
+        app_body = GetPingXml(ping_active_days, ping_roll_call_days);
+    if (!ping_only) {
+      app_body += base::StringPrintf(
+          "        <updatecheck targetversionprefix=\"%s\""
+          "></updatecheck>\n",
+          XmlEncodeWithDefault(params->target_version_prefix(), "").c_str());
+
+      // 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 = "0.0.0.0";
+      }
+      // 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, "0.0.0.0").c_str());
+        LOG_IF(WARNING, !prefs->SetString(kPrefsPreviousVersion, ""))
+            << "Unable to reset the previous version.";
+      }
+    }
+  } else {
+    // 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));
+    }
+    app_body = base::StringPrintf(
+        "        <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
+        event->type, event->result, error_code.c_str());
+  }
+
+  return app_body;
+}
+
+// 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.
+string GetCohortArgXml(PrefsInterface* prefs,
+                       const string arg_name,
+                       const string prefs_key) {
+  // There's nothing wrong with not having a given cohort setting, so we check
+  // existance first to avoid the warning log message.
+  if (!prefs->Exists(prefs_key))
+    return "";
+  string cohort_value;
+  if (!prefs->GetString(prefs_key, &cohort_value) || cohort_value.empty())
+    return "";
+  // This is a sanity 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());
+}
+
+// Returns an XML that corresponds to the entire <app> node of the Omaha
+// request based on the given parameters.
+string GetAppXml(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,
+                 SystemState* system_state) {
+  string app_body = GetAppBody(event, params, ping_only, include_ping,
+                               ping_active_days, ping_roll_call_days,
+                               system_state->prefs());
+  string app_versions;
+
+  // If we are upgrading 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->to_more_stable_channel() && params->is_powerwash_allowed()) {
+    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=\"0.0.0.0\" from_version=\"" +
+        XmlEncodeWithDefault(params->app_version(), "0.0.0.0") + "\" ";
+  } else {
+    app_versions = "version=\"" +
+        XmlEncodeWithDefault(params->app_version(), "0.0.0.0") + "\" ";
+  }
+
+  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() ? "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 += GetCohortArgXml(system_state->prefs(),
+                                     "cohort", kPrefsOmahaCohort);
+  app_cohort_args += GetCohortArgXml(system_state->prefs(),
+                                     "cohorthint", kPrefsOmahaCohortHint);
+  app_cohort_args += GetCohortArgXml(system_state->prefs(),
+                                     "cohortname", kPrefsOmahaCohortName);
+
+  string app_xml = "    <app "
+      "appid=\"" + XmlEncodeWithDefault(params->GetAppId(), "") + "\" " +
+      app_cohort_args +
+      app_versions +
+      app_channels +
+      "lang=\"" + XmlEncodeWithDefault(params->app_lang(), "en-US") + "\" " +
+      "board=\"" + XmlEncodeWithDefault(params->os_board(), "") + "\" " +
+      "hardware_class=\"" + XmlEncodeWithDefault(params->hwid(), "") + "\" " +
+      "delta_okay=\"" + delta_okay_str + "\" "
+      "fw_version=\"" + XmlEncodeWithDefault(params->fw_version(), "") + "\" " +
+      "ec_version=\"" + XmlEncodeWithDefault(params->ec_version(), "") + "\" " +
+      install_date_in_days_str +
+      ">\n" +
+         app_body +
+      "    </app>\n";
+
+  return app_xml;
+}
+
+// Returns an XML that corresponds to the entire <os> node of the Omaha
+// request based on the given parameters.
+string GetOsXml(OmahaRequestParams* params) {
+  string os_xml ="    <os "
+      "version=\"" + XmlEncodeWithDefault(params->os_version(), "") + "\" " +
+      "platform=\"" + XmlEncodeWithDefault(params->os_platform(), "") + "\" " +
+      "sp=\"" + XmlEncodeWithDefault(params->os_sp(), "") + "\">"
+      "</os>\n";
+  return os_xml;
+}
+
+// Returns an XML that corresponds to the entire Omaha request based on the
+// given parameters.
+string GetRequestXml(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,
+                     SystemState* system_state) {
+  string os_xml = GetOsXml(params);
+  string app_xml = GetAppXml(event, params, ping_only, include_ping,
+                             ping_active_days, ping_roll_call_days,
+                             install_date_in_days, system_state);
+
+  string install_source = base::StringPrintf("installsource=\"%s\" ",
+      (params->interactive() ? "ondemandupdate" : "scheduler"));
+
+  string updater_version = XmlEncodeWithDefault(
+      base::StringPrintf("%s-%s",
+                         constants::kOmahaUpdaterID,
+                         kOmahaUpdaterVersion), "");
+  string request_xml =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      "<request protocol=\"3.0\" " + (
+          "version=\"" + updater_version + "\" "
+          "updaterversion=\"" + updater_version + "\" " +
+          install_source +
+          "ismachine=\"1\">\n") +
+      os_xml +
+      app_xml +
+      "</request>\n";
+
+  return request_xml;
+}
+
+}  // namespace
+
+// 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 app_cohort;
+  string app_cohorthint;
+  string app_cohortname;
+  bool app_cohort_set = false;
+  bool app_cohorthint_set = false;
+  bool app_cohortname_set = false;
+  string updatecheck_status;
+  string updatecheck_poll_interval;
+  map<string, string> updatecheck_attrs;
+  string daystart_elapsed_days;
+  string daystart_elapsed_seconds;
+  vector<string> url_codebase;
+  string package_name;
+  string package_size;
+  string manifest_version;
+  map<string, string> action_postinstall_attrs;
+};
+
+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") {
+    if (attrs.find("cohort") != attrs.end()) {
+      data->app_cohort_set = true;
+      data->app_cohort = attrs["cohort"];
+    }
+    if (attrs.find("cohorthint") != attrs.end()) {
+      data->app_cohorthint_set = true;
+      data->app_cohorthint = attrs["cohorthint"];
+    }
+    if (attrs.find("cohortname") != attrs.end()) {
+      data->app_cohortname_set = true;
+      data->app_cohortname = attrs["cohortname"];
+    }
+  } else if (data->current_path == "/response/app/updatecheck") {
+    // There is only supposed to be a single <updatecheck> element.
+    data->updatecheck_status = attrs["status"];
+    data->updatecheck_poll_interval = attrs["PollInterval"];
+    // 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["elapsed_days"];
+    data->daystart_elapsed_seconds = attrs["elapsed_seconds"];
+  } else if (data->current_path == "/response/app/updatecheck/urls/url") {
+    // Look at all <url> elements.
+    data->url_codebase.push_back(attrs["codebase"]);
+  } else if (data->package_name.empty() && data->current_path ==
+             "/response/app/updatecheck/manifest/packages/package") {
+    // Only look at the first <package>.
+    data->package_name = attrs["name"];
+    data->package_size = attrs["size"];
+  } else if (data->current_path == "/response/app/updatecheck/manifest") {
+    // Get the version.
+    data->manifest_version = attrs[kTagVersion];
+  } else if (data->current_path ==
+             "/response/app/updatecheck/manifest/actions/action") {
+    // We only care about the postinstall action.
+    if (attrs["event"] == "postinstall") {
+      data->action_postinstall_attrs = 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
+
+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("&quot;");
+        break;
+      case '\'':
+        output->append("&apos;");
+        break;
+      case '&':
+        output->append("&amp;");
+        break;
+      case '<':
+        output->append("&lt;");
+        break;
+      case '>':
+        output->append("&gt;");
+        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;
+}
+
+OmahaRequestAction::OmahaRequestAction(
+    SystemState* system_state,
+    OmahaEvent* event,
+    std::unique_ptr<HttpFetcher> http_fetcher,
+    bool ping_only)
+    : system_state_(system_state),
+      event_(event),
+      http_fetcher_(std::move(http_fetcher)),
+      ping_only_(ping_only),
+      ping_active_days_(0),
+      ping_roll_call_days_(0) {
+  params_ = system_state->request_params();
+}
+
+OmahaRequestAction::~OmahaRequestAction() {}
+
+// Calculates the value to use for the ping days parameter.
+int OmahaRequestAction::CalculatePingDays(const string& key) {
+  int days = kNeverPinged;
+  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_ == OmahaRequestAction::kNeverPinged &&
+      ping_roll_call_days_ == OmahaRequestAction::kNeverPinged) {
+    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;
+    }
+    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 sanity-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::PerformAction() {
+  http_fetcher_->set_delegate(this);
+  InitPingDays();
+  if (ping_only_ && !ShouldPing()) {
+    processor_->ActionComplete(this, ErrorCode::kSuccess);
+    return;
+  }
+
+  string request_post(GetRequestXml(event_.get(),
+                                    params_,
+                                    ping_only_,
+                                    ShouldPing(),  // include_ping
+                                    ping_active_days_,
+                                    ping_roll_call_days_,
+                                    GetInstallDate(system_state_),
+                                    system_state_));
+
+  // Set X-GoogleUpdate 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.
+void 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);
+}
+
+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;
+}
+}  // namespace
+
+bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data,
+                                       OmahaResponse* output_object,
+                                       ScopedActionCompleter* completer) {
+  if (parser_data->updatecheck_status.empty()) {
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  // 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".
+  if (parser_data->app_cohort_set)
+    PersistCohortData(kPrefsOmahaCohort, parser_data->app_cohort);
+  if (parser_data->app_cohorthint_set)
+    PersistCohortData(kPrefsOmahaCohortHint, parser_data->app_cohorthint);
+  if (parser_data->app_cohortname_set)
+    PersistCohortData(kPrefsOmahaCohortName, parser_data->app_cohortname);
+
+  // Parse the updatecheck attributes.
+  PersistEolStatus(parser_data->updatecheck_attrs);
+
+  if (!ParseStatus(parser_data, output_object, completer))
+    return false;
+
+  // Note: ParseUrls MUST be called before ParsePackage as ParsePackage
+  // appends the package name to the URLs populated in this method.
+  if (!ParseUrls(parser_data, output_object, completer))
+    return false;
+
+  if (!ParsePackage(parser_data, output_object, completer))
+    return false;
+
+  if (!ParseParams(parser_data, output_object, completer))
+    return false;
+
+  return true;
+}
+
+bool OmahaRequestAction::ParseStatus(OmahaParserData* parser_data,
+                                     OmahaResponse* output_object,
+                                     ScopedActionCompleter* completer) {
+  const string& status = parser_data->updatecheck_status;
+  if (status == "noupdate") {
+    LOG(INFO) << "No update.";
+    output_object->update_exists = false;
+    SetOutputObject(*output_object);
+    completer->set_code(ErrorCode::kSuccess);
+    return false;
+  }
+
+  if (status != "ok") {
+    LOG(ERROR) << "Unknown Omaha response status: " << status;
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  return true;
+}
+
+bool OmahaRequestAction::ParseUrls(OmahaParserData* parser_data,
+                                   OmahaResponse* output_object,
+                                   ScopedActionCompleter* completer) {
+  if (parser_data->url_codebase.empty()) {
+    LOG(ERROR) << "No Omaha Response URLs";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  LOG(INFO) << "Found " << parser_data->url_codebase.size() << " url(s)";
+  output_object->payload_urls.clear();
+  for (const auto& codebase : parser_data->url_codebase) {
+    if (codebase.empty()) {
+      LOG(ERROR) << "Omaha Response URL has empty codebase";
+      completer->set_code(ErrorCode::kOmahaResponseInvalid);
+      return false;
+    }
+    output_object->payload_urls.push_back(codebase);
+  }
+
+  return true;
+}
+
+bool OmahaRequestAction::ParsePackage(OmahaParserData* parser_data,
+                                      OmahaResponse* output_object,
+                                      ScopedActionCompleter* completer) {
+  if (parser_data->package_name.empty()) {
+    LOG(ERROR) << "Omaha Response has empty package name";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  // Append the package name to each URL in our list so that we don't
+  // propagate the urlBase vs packageName distinctions beyond this point.
+  // From now on, we only need to use payload_urls.
+  for (auto& payload_url : output_object->payload_urls)
+    payload_url += parser_data->package_name;
+
+  // Parse the payload size.
+  off_t size = ParseInt(parser_data->package_size);
+  if (size <= 0) {
+    LOG(ERROR) << "Omaha Response has invalid payload size: " << size;
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+  output_object->size = size;
+
+  LOG(INFO) << "Payload size = " << output_object->size << " bytes";
+
+  return true;
+}
+
+bool OmahaRequestAction::ParseParams(OmahaParserData* parser_data,
+                                     OmahaResponse* output_object,
+                                     ScopedActionCompleter* completer) {
+  output_object->version = parser_data->manifest_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;
+
+  map<string, string> attrs = parser_data->action_postinstall_attrs;
+  if (attrs.empty()) {
+    LOG(ERROR) << "Omaha Response has no postinstall event action";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  output_object->hash = attrs[kTagSha256];
+  if (output_object->hash.empty()) {
+    LOG(ERROR) << "Omaha Response has empty sha256 value";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  // Get the optional properties one by one.
+  output_object->more_info_url = attrs[kTagMoreInfo];
+  output_object->metadata_size = ParseInt(attrs[kTagMetadataSize]);
+  output_object->metadata_signature = attrs[kTagMetadataSignatureRsa];
+  output_object->prompt = ParseBool(attrs[kTagPrompt]);
+  output_object->deadline = attrs[kTagDeadline];
+  output_object->max_days_to_scatter = ParseInt(attrs[kTagMaxDaysToScatter]);
+  output_object->disable_p2p_for_downloading =
+      ParseBool(attrs[kTagDisableP2PForDownloading]);
+  output_object->disable_p2p_for_sharing =
+      ParseBool(attrs[kTagDisableP2PForSharing]);
+  output_object->public_key_rsa = attrs[kTagPublicKeyRsa];
+
+  string max = attrs[kTagMaxFailureCountPerUrl];
+  if (!base::StringToUint(max, &output_object->max_failure_count_per_url))
+    output_object->max_failure_count_per_url = kDefaultMaxFailureCountPerUrl;
+
+  output_object->is_delta_payload = ParseBool(attrs[kTagIsDeltaPayload]);
+
+  output_object->disable_payload_backoff =
+      ParseBool(attrs[kTagDisablePayloadBackoff]);
+
+  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();
+
+  // 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;
+  }
+
+  if (!successful) {
+    LOG(ERROR) << "Omaha request network transfer failed.";
+    int code = GetHTTPResponseCode();
+    // Makes sure we send sane error values.
+    if (code < 0 || code >= 1000) {
+      code = 999;
+    }
+    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);
+  XML_ParserFree(parser);
+
+  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);
+    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;
+  }
+
+  // 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!";
+
+  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;
+  output_object.update_exists = true;
+  SetOutputObject(output_object);
+
+  if (ShouldIgnoreUpdate(output_object)) {
+    output_object.update_exists = false;
+    completer.set_code(ErrorCode::kOmahaUpdateIgnoredPerPolicy);
+    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 (system_state_->hardware()->IsOOBEEnabled() &&
+      !system_state_->hardware()->IsOOBEComplete(nullptr) &&
+      output_object.deadline.empty() &&
+      params_->app_version() != "ForcedUpdate") {
+    output_object.update_exists = false;
+    LOG(INFO) << "Ignoring non-critical Omaha updates until OOBE is done.";
+    completer.set_code(ErrorCode::kNonCriticalUpdateInOOBE);
+    return;
+  }
+
+  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;
+  }
+
+  string file_id = utils::CalculateP2PFileId(response.hash, response.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;
+  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. Just skip scattering in this
+      // case to be safe.
+     LOG(INFO) << "Not scattering as UpdateFirstSeenAt value cannot be read";
+     return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+    }
+  } 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. Just skip scattering in this
+      // case to be safe.
+      LOG(INFO) << "Not scattering as UpdateFirstSeenAt value "
+                << utils::ToString(update_first_seen_at)
+                << " cannot be 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);
+
+  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;
+
+  string metric_name = metrics::kMetricInstallDateProvisioningSource;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric_name,
+      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::PersistEolStatus(const map<string, string>& attrs) {
+  auto eol_attr = attrs.find(kEolAttr);
+  if (eol_attr != attrs.end()) {
+    return system_state_->prefs()->SetString(kPrefsOmahaEolStatus,
+                                             eol_attr->second);
+  } else if (system_state_->prefs()->Exists(kPrefsOmahaEolStatus)) {
+    return system_state_->prefs()->Delete(kPrefsOmahaEolStatus);
+  }
+  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:
+    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;
+  }
+
+  metrics::ReportUpdateCheckMetrics(system_state_,
+                                    result, reaction, download_error_code);
+}
+
+bool OmahaRequestAction::ShouldIgnoreUpdate(
+    const OmahaResponse& response) 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.";
+      return true;
+    }
+  }
+
+  if (!IsUpdateAllowedOverCurrentConnection()) {
+    LOG(INFO) << "Update is not allowed over current connection.";
+    return true;
+  }
+
+  // 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::IsUpdateAllowedOverCurrentConnection() 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);
+  LOG(INFO) << "We are connected via "
+            << connection_utils::StringForConnectionType(type)
+            << ", Updates allowed: " << (is_allowed ? "Yes" : "No");
+  return is_allowed;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/omaha_request_action.h b/update_engine/omaha_request_action.h
new file mode 100644
index 0000000..2915a6a
--- /dev/null
+++ b/update_engine/omaha_request_action.h
@@ -0,0 +1,337 @@
+//
+// 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_OMAHA_REQUEST_ACTION_H_
+#define UPDATE_ENGINE_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/omaha_response.h"
+#include "update_engine/system_state.h"
+
+// The Omaha Request action makes a request to Omaha and can output
+// the response on the output ActionPipe.
+
+namespace chromeos_update_engine {
+
+// 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);
+
+// 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;
+};
+
+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 kNeverPinged = -1;
+  static const int kPingTimeJump = -2;
+  // We choose this value of 10 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 - neither too long nor too little - so we don't give up the preferred
+  // URLs that appear earlier in list too quickly before moving on to the
+  // fallback ones.
+  static const int kDefaultMaxFailureCountPerUrl = 10;
+
+  // 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);
+  ~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)
+  void 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_TEST(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE);
+  FRIEND_TEST(OmahaRequestActionTest,
+              GetInstallDateWhenOOBECompletedWithInvalidDate);
+  FRIEND_TEST(OmahaRequestActionTest,
+              GetInstallDateWhenOOBECompletedWithValidDate);
+  FRIEND_TEST(OmahaRequestActionTest,
+              GetInstallDateWhenOOBECompletedDateChanges);
+
+  // 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);
+
+  // Parse and persist the end-of-life status flag sent back in the updatecheck
+  // tag attributes. The flag will be validated and stored in the Prefs.
+  bool PersistEolStatus(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;
+
+  // 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 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* 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) const;
+
+  // Returns true if updates are allowed over the current type of connection.
+  // False otherwise.
+  bool IsUpdateAllowedOverCurrentConnection() 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_;
+
+  // 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_;
+
+  DISALLOW_COPY_AND_ASSIGN(OmahaRequestAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_
diff --git a/update_engine/omaha_request_action_unittest.cc b/update_engine/omaha_request_action_unittest.cc
new file mode 100644
index 0000000..1c1d25c
--- /dev/null
+++ b/update_engine/omaha_request_action_unittest.cc
@@ -0,0 +1,2263 @@
+//
+// 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/omaha_request_action.h"
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.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/bind_lambda.h>
+#include <brillo/make_unique_ptr.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 <gtest/gtest.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/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/fake_system_state.h"
+#include "update_engine/metrics.h"
+#include "update_engine/mock_connection_manager.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/omaha_request_params.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+using std::vector;
+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::SaveArg;
+using testing::SetArgumentPointee;
+using testing::_;
+
+namespace {
+
+const char kTestAppId[] = "test-app-id";
+
+// This is a helper struct to allow unit tests build an update response with the
+// values they care about.
+struct FakeUpdateResponse {
+  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></response>";
+  }
+
+  string GetUpdateResponse() const {
+    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\">"
+        "<urls><url codebase=\"" + codebase + "\"/></urls>"
+        "<manifest version=\"" + version + "\">"
+        "<packages><package hash=\"not-used\" name=\"" + filename +  "\" "
+        "size=\"" + base::Int64ToString(size) + "\"/></packages>"
+        "<actions><action event=\"postinstall\" "
+        "ChromeOSVersion=\"" + version + "\" "
+        "MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt + "\" "
+        "IsDelta=\"true\" "
+        "IsDeltaPayload=\"true\" "
+        "MaxDaysToScatter=\"" + max_days_to_scatter + "\" "
+        "sha256=\"" + hash + "\" "
+        "needsadmin=\"" + needsadmin + "\" " +
+        (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) +
+        (disable_p2p_for_downloading ?
+            "DisableP2PForDownloading=\"true\" " : "") +
+        (disable_p2p_for_sharing ? "DisableP2PForSharing=\"true\" " : "") +
+        "/></actions></manifest></updatecheck></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 version = "1.2.3.4";
+  string more_info_url = "http://more/info";
+  string prompt = "true";
+  string codebase = "http://code/base/";
+  string filename = "file.signed";
+  string hash = "HASH1234=";
+  string needsadmin = "false";
+  int64_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;
+
+  // 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;
+};
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+class OmahaRequestActionTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    fake_system_state_.set_request_params(&request_params_);
+    fake_system_state_.set_prefs(&fake_prefs_);
+  }
+
+  // Returns true iff an output response was obtained from the
+  // OmahaRequestAction. |prefs| may be null, in which case a local MockPrefs
+  // is used. |payload_state| may be null, in which case a local mock is used.
+  // |p2p_manager| may be null, in which case a local mock is used.
+  // |connection_manager| may be null, in which case a local mock is used.
+  // out_response may be null. If |fail_http_response_code| is non-negative,
+  // the transfer will fail with that code. |ping_only| is passed through to the
+  // OmahaRequestAction constructor. out_post_data may be null; if non-null, the
+  // post-data received by the mock HttpFetcher is returned.
+  //
+  // 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(OmahaRequestParams* request_params,
+                       const string& http_response,
+                       int fail_http_response_code,
+                       bool ping_only,
+                       ErrorCode expected_code,
+                       metrics::CheckResult expected_check_result,
+                       metrics::CheckReaction expected_check_reaction,
+                       metrics::DownloadErrorCode expected_download_error_code,
+                       OmahaResponse* out_response,
+                       brillo::Blob* out_post_data);
+
+  // 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);
+
+  FakeSystemState fake_system_state_;
+  FakeUpdateResponse fake_update_response_;
+
+  // By default, all tests use these objects unless they replace them in the
+  // fake_system_state_.
+  OmahaRequestParams request_params_ = OmahaRequestParams{
+      &fake_system_state_,
+      constants::kOmahaPlatformName,
+      OmahaRequestParams::kOsVersion,
+      "service_pack",
+      "x86-generic",
+      kTestAppId,
+      "0.1.0.0",
+      "en-US",
+      "unittest",
+      "OEM MODEL 09235 7471",
+      "ChromeOSFirmware.1.0",
+      "0X0A1",
+      false,   // delta okay
+      false,   // interactive
+      "http://url",
+      ""};     // target_version_prefix
+
+  FakePrefs fake_prefs_;
+};
+
+namespace {
+class OmahaRequestActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  OmahaRequestActionTestProcessorDelegate()
+      : expected_code_(ErrorCode::kSuccess) {}
+  ~OmahaRequestActionTestProcessorDelegate() override {
+  }
+  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);
+    else
+      EXPECT_EQ(ErrorCode::kSuccess, code);
+  }
+  ErrorCode expected_code_;
+};
+}  // namespace
+
+class OutputObjectCollectorAction;
+
+template<>
+class ActionTraits<OutputObjectCollectorAction> {
+ public:
+  // Does not take an object for input
+  typedef OmahaResponse InputObjectType;
+  // On success, puts the output path on output
+  typedef NoneType OutputObjectType;
+};
+
+class OutputObjectCollectorAction : public Action<OutputObjectCollectorAction> {
+ public:
+  OutputObjectCollectorAction() : has_input_object_(false) {}
+  void PerformAction() {
+    // copy input object
+    has_input_object_ = HasInputObject();
+    if (has_input_object_)
+      omaha_response_ = GetInputObject();
+    processor_->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  // Should never be called
+  void TerminateProcessing() {
+    CHECK(false);
+  }
+  // Debugging/logging
+  static string StaticType() {
+    return "OutputObjectCollectorAction";
+  }
+  string Type() const { return StaticType(); }
+  using InputObjectType =
+      ActionTraits<OutputObjectCollectorAction>::InputObjectType;
+  using OutputObjectType =
+      ActionTraits<OutputObjectCollectorAction>::OutputObjectType;
+  bool has_input_object_;
+  OmahaResponse omaha_response_;
+};
+
+bool OmahaRequestActionTest::TestUpdateCheck(
+    OmahaRequestParams* request_params,
+    const string& http_response,
+    int fail_http_response_code,
+    bool ping_only,
+    ErrorCode expected_code,
+    metrics::CheckResult expected_check_result,
+    metrics::CheckReaction expected_check_reaction,
+    metrics::DownloadErrorCode expected_download_error_code,
+    OmahaResponse* out_response,
+    brillo::Blob* out_post_data) {
+  brillo::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+  MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
+                                                 http_response.size(),
+                                                 nullptr);
+  if (fail_http_response_code >= 0) {
+    fetcher->FailTransfer(fail_http_response_code);
+  }
+  if (request_params)
+    fake_system_state_.set_request_params(request_params);
+  OmahaRequestAction action(&fake_system_state_,
+                            nullptr,
+                            brillo::make_unique_ptr(fetcher),
+                            ping_only);
+  OmahaRequestActionTestProcessorDelegate delegate;
+  delegate.expected_code_ = expected_code;
+
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  OutputObjectCollectorAction collector_action;
+  BondActions(&action, &collector_action);
+  processor.EnqueueAction(&collector_action);
+
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+      .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+      SendEnumToUMA(metrics::kMetricCheckResult,
+          static_cast<int>(expected_check_result),
+          static_cast<int>(metrics::CheckResult::kNumConstants) - 1))
+      .Times(expected_check_result == metrics::CheckResult::kUnset ? 0 : 1);
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+      SendEnumToUMA(metrics::kMetricCheckReaction,
+          static_cast<int>(expected_check_reaction),
+          static_cast<int>(metrics::CheckReaction::kNumConstants) - 1))
+      .Times(expected_check_reaction == metrics::CheckReaction::kUnset ? 0 : 1);
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+      SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode,
+          static_cast<int>(expected_download_error_code)))
+      .Times(expected_download_error_code == metrics::DownloadErrorCode::kUnset
+             ? 0 : 1);
+
+  loop.PostTask(base::Bind(
+      [](ActionProcessor* processor) { processor->StartProcessing(); },
+      base::Unretained(&processor)));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+  if (collector_action.has_input_object_ && out_response)
+    *out_response = collector_action.omaha_response_;
+  if (out_post_data)
+    *out_post_data = fetcher->post_data();
+  return collector_action.has_input_object_;
+}
+
+// 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 TestEvent(OmahaRequestParams params,
+               OmahaEvent* event,
+               const string& http_response,
+               brillo::Blob* out_post_data) {
+  brillo::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+  MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
+                                                 http_response.size(),
+                                                 nullptr);
+  FakeSystemState fake_system_state;
+  fake_system_state.set_request_params(&params);
+  OmahaRequestAction action(&fake_system_state,
+                            event,
+                            brillo::make_unique_ptr(fetcher),
+                            false);
+  OmahaRequestActionTestProcessorDelegate delegate;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  loop.PostTask(base::Bind(
+      [](ActionProcessor* processor) { processor->StartProcessing(); },
+      base::Unretained(&processor)));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+
+  if (out_post_data)
+    *out_post_data = fetcher->post_data();
+}
+
+TEST_F(OmahaRequestActionTest, RejectEntities) {
+  OmahaResponse response;
+  fake_update_response_.include_entity = true;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaRequestXMLHasEntityDecl,
+                      metrics::CheckResult::kParsingError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoUpdateTest) {
+  OmahaResponse response;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+// Test that all the values in the response are parsed in a normal update
+// response.
+TEST_F(OmahaRequestActionTest, ValidUpdateTest) {
+  OmahaResponse response;
+  fake_update_response_.deadline = "20101020";
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(fake_update_response_.version, response.version);
+  EXPECT_EQ(fake_update_response_.GetPayloadUrl(), response.payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.more_info_url, response.more_info_url);
+  EXPECT_EQ(fake_update_response_.hash, response.hash);
+  EXPECT_EQ(fake_update_response_.size, response.size);
+  EXPECT_EQ(fake_update_response_.prompt == "true", response.prompt);
+  EXPECT_EQ(fake_update_response_.deadline, response.deadline);
+  // Omaha cohort attribets 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, ExtraHeadersSentTest) {
+  const string http_response = "<?xml invalid response";
+  request_params_.set_interactive(true);
+
+  brillo::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  MockHttpFetcher* fetcher =
+      new MockHttpFetcher(http_response.data(), http_response.size(), nullptr);
+  OmahaRequestAction action(
+      &fake_system_state_, nullptr, brillo::make_unique_ptr(fetcher), false);
+  ActionProcessor processor;
+  processor.EnqueueAction(&action);
+
+  loop.PostTask(base::Bind(
+      [](ActionProcessor* processor) { processor->StartProcessing(); },
+      base::Unretained(&processor)));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+
+  // Check that the headers were set in the fetcher during the action. Note that
+  // we set this request as "interactive".
+  EXPECT_EQ("fg", fetcher->GetHeader("X-GoogleUpdate-Interactivity"));
+  EXPECT_EQ(kTestAppId, fetcher->GetHeader("X-GoogleUpdate-AppId"));
+  EXPECT_NE("", fetcher->GetHeader("X-GoogleUpdate-Updater"));
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByConnection) {
+  OmahaResponse response;
+  // 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(SetArgumentPointee<0>(ConnectionType::kEthernet),
+                SetArgumentPointee<1>(ConnectionTethering::kUnknown),
+                Return(true)));
+  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kEthernet, _))
+      .WillRepeatedly(Return(false));
+
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateIgnoredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kIgnored,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByRollback) {
+  string rollback_version = "1234.0.0";
+  OmahaResponse response;
+
+  MockPayloadState mock_payload_state;
+  fake_system_state_.set_payload_state(&mock_payload_state);
+
+  EXPECT_CALL(mock_payload_state, GetRollbackVersion())
+    .WillRepeatedly(Return(rollback_version));
+
+  fake_update_response_.version = rollback_version;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateIgnoredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kIgnored,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+// Verify that update checks called during OOBE will only try to download
+// an update if the response includes a non-empty deadline field.
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBE) {
+  OmahaResponse response;
+
+  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kNonCriticalUpdateInOOBE,
+                      metrics::CheckResult::kUnset,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+
+  // The IsOOBEComplete() value is ignored when the OOBE flow is not enabled.
+  fake_system_state_.fake_hardware()->SetIsOOBEEnabled(false);
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  fake_system_state_.fake_hardware()->SetIsOOBEEnabled(true);
+
+  // The payload is applied when a deadline was set in the response.
+  fake_update_response_.deadline = "20101020";
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, WallClockBasedWaitAloneCausesScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_update_check_count_wait_enabled(false);
+  params.set_waiting_period(TimeDelta::FromDays(2));
+
+  ASSERT_FALSE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kDeferring,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+
+  // Verify if we are interactive check we don't defer.
+  params.set_interactive(true);
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoWallClockBasedWaitCausesNoScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(false);
+  params.set_waiting_period(TimeDelta::FromDays(2));
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(1);
+  params.set_max_update_checks_allowed(8);
+
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ZeroMaxDaysToScatterCausesNoScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta::FromDays(2));
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(1);
+  params.set_max_update_checks_allowed(8);
+
+  fake_update_response_.max_days_to_scatter = "0";
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+
+TEST_F(OmahaRequestActionTest, ZeroUpdateCheckCountCausesNoScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta());
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(0);
+  params.set_max_update_checks_allowed(0);
+
+  ASSERT_TRUE(TestUpdateCheck(
+                      &params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  int64_t count;
+  ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+  ASSERT_EQ(count, 0);
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NonZeroUpdateCheckCountCausesScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta());
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(1);
+  params.set_max_update_checks_allowed(8);
+
+  ASSERT_FALSE(TestUpdateCheck(
+                      &params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kDeferring,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  int64_t count;
+  ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+  ASSERT_GT(count, 0);
+  EXPECT_FALSE(response.update_exists);
+
+  // Verify if we are interactive check we don't defer.
+  params.set_interactive(true);
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ExistingUpdateCheckCountCausesScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta());
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(1);
+  params.set_max_update_checks_allowed(8);
+
+  ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsUpdateCheckCount, 5));
+
+  ASSERT_FALSE(TestUpdateCheck(
+                      &params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kDeferring,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  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);
+
+  // Verify if we are interactive check we don't defer.
+  params.set_interactive(true);
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, CohortsArePersisted) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  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";
+
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  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) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  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 = "";
+
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  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) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
+
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  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) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  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";
+
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  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();
+
+  OmahaRequestParams params = request_params_;
+  fake_system_state_.set_request_params(&params);
+  OmahaRequestAction action(&fake_system_state_, nullptr,
+                            brillo::make_unique_ptr(
+                                new MockHttpFetcher(http_response.data(),
+                                                    http_response.size(),
+                                                    nullptr)),
+                            false);
+  OmahaRequestActionTestProcessorDelegate delegate;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&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) {
+  OmahaResponse response;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "invalid xml>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaRequestXMLParseError,
+                      metrics::CheckResult::kParsingError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, EmptyResponseTest) {
+  OmahaResponse response;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaRequestEmptyResponseError,
+                      metrics::CheckResult::kParsingError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingStatusTest) {
+  OmahaResponse response;
+  ASSERT_FALSE(TestUpdateCheck(
+      nullptr,  // request_params
+      "<?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>",
+      -1,
+      false,  // ping_only
+      ErrorCode::kOmahaResponseInvalid,
+      metrics::CheckResult::kParsingError,
+      metrics::CheckReaction::kUnset,
+      metrics::DownloadErrorCode::kUnset,
+      &response,
+      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, InvalidStatusTest) {
+  OmahaResponse response;
+  ASSERT_FALSE(TestUpdateCheck(
+      nullptr,  // request_params
+      "<?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>",
+      -1,
+      false,  // ping_only
+      ErrorCode::kOmahaResponseInvalid,
+      metrics::CheckResult::kParsingError,
+      metrics::CheckReaction::kUnset,
+      metrics::DownloadErrorCode::kUnset,
+      &response,
+      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingNodesetTest) {
+  OmahaResponse response;
+  ASSERT_FALSE(TestUpdateCheck(
+      nullptr,  // request_params
+      "<?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>",
+      -1,
+      false,  // ping_only
+      ErrorCode::kOmahaResponseInvalid,
+      metrics::CheckResult::kParsingError,
+      metrics::CheckReaction::kUnset,
+      metrics::DownloadErrorCode::kUnset,
+      &response,
+      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingFieldTest) {
+  string input_response =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+      "<daystart elapsed_seconds=\"100\"/>"
+      "<app appid=\"xyz\" 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\"/></packages>"
+      "<actions><action event=\"postinstall\" "
+      "ChromeOSVersion=\"10.2.3.4\" "
+      "Prompt=\"false\" "
+      "IsDelta=\"true\" "
+      "IsDeltaPayload=\"false\" "
+      "sha256=\"lkq34j5345\" "
+      "needsadmin=\"true\" "
+      "/></actions></manifest></updatecheck></app></response>";
+  LOG(INFO) << "Input Response = " << input_response;
+
+  OmahaResponse response;
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              input_response,
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ("10.2.3.4", response.version);
+  EXPECT_EQ("http://missing/field/test/f", response.payload_urls[0]);
+  EXPECT_EQ("", response.more_info_url);
+  EXPECT_EQ("lkq34j5345", response.hash);
+  EXPECT_EQ(587, response.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");
+  OmahaRequestAction action(&fake_system_state_, nullptr,
+                            brillo::make_unique_ptr(
+                                new MockHttpFetcher(http_response.data(),
+                                                    http_response.size(),
+                                                    nullptr)),
+                            false);
+  TerminateEarlyTestProcessorDelegate delegate;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  loop.PostTask(base::Bind(&TerminateTransferTestStarter, &processor));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+}
+
+TEST_F(OmahaRequestActionTest, XmlEncodeTest) {
+  string output;
+  EXPECT_TRUE(XmlEncode("ab", &output));
+  EXPECT_EQ("ab", output);
+  EXPECT_TRUE(XmlEncode("a<b", &output));
+  EXPECT_EQ("a&lt;b", output);
+  EXPECT_TRUE(XmlEncode("<&>\"\'\\", &output));
+  EXPECT_EQ("&lt;&amp;&gt;&quot;&apos;\\", output);
+  EXPECT_TRUE(XmlEncode("&lt;&amp;&gt;", &output));
+  EXPECT_EQ("&amp;lt;&amp;amp;&amp;gt;", 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(OmahaRequestActionTest, XmlEncodeWithDefaultTest) {
+  EXPECT_EQ("&lt;&amp;&gt;", XmlEncodeWithDefault("<&>", "something else"));
+  EXPECT_EQ("<not escaped>", XmlEncodeWithDefault("\xc2", "<not escaped>"));
+}
+
+TEST_F(OmahaRequestActionTest, XmlEncodeIsUsedForParams) {
+  brillo::Blob post_data;
+
+  // Make sure XML Encode is being called on the params
+  OmahaRequestParams params(&fake_system_state_,
+                            constants::kOmahaPlatformName,
+                            OmahaRequestParams::kOsVersion,
+                            "testtheservice_pack>",
+                            "x86 generic<id",
+                            kTestAppId,
+                            "0.1.0.0",
+                            "en-US",
+                            "unittest_track&lt;",
+                            "<OEM MODEL>",
+                            "ChromeOSFirmware.1.0",
+                            "EC100",
+                            false,   // delta okay
+                            false,   // interactive
+                            "http://url",
+                            "");     // target_version_prefix
+  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."), " "));
+  OmahaResponse response;
+  ASSERT_FALSE(
+      TestUpdateCheck(&params,
+                      "invalid xml>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaRequestXMLParseError,
+                      metrics::CheckResult::kParsingError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_NE(string::npos, post_str.find("testtheservice_pack&gt;"));
+  EXPECT_EQ(string::npos, post_str.find("testtheservice_pack>"));
+  EXPECT_NE(string::npos, post_str.find("x86 generic&lt;id"));
+  EXPECT_EQ(string::npos, post_str.find("x86 generic<id"));
+  EXPECT_NE(string::npos, post_str.find("unittest_track&amp;lt;"));
+  EXPECT_EQ(string::npos, post_str.find("unittest_track&lt;"));
+  EXPECT_NE(string::npos, post_str.find("&lt;OEM MODEL&gt;"));
+  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&amp;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) {
+  OmahaResponse response;
+  fake_update_response_.deadline = "&lt;20110101";
+  fake_update_response_.more_info_url = "testthe&lt;url";
+  fake_update_response_.codebase = "testthe&amp;codebase/";
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  EXPECT_EQ(response.more_info_url, "testthe<url");
+  EXPECT_EQ(response.payload_urls[0], "testthe&codebase/file.signed");
+  EXPECT_EQ(response.deadline, "<20110101");
+}
+
+TEST_F(OmahaRequestActionTest, ParseIntTest) {
+  OmahaResponse response;
+  // overflows int32_t:
+  fake_update_response_.size = 123123123123123ll;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  EXPECT_EQ(response.size, 123123123123123ll);
+}
+
+TEST_F(OmahaRequestActionTest, FormatUpdateCheckOutputTest) {
+  brillo::Blob post_data;
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+
+  EXPECT_CALL(prefs, GetString(kPrefsPreviousVersion, _))
+      .WillOnce(DoAll(SetArgumentPointee<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(nullptr,  // request_params
+                               "invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               nullptr,  // response
+                               &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_NE(post_str.find(
+      "        <ping active=\"1\" a=\"-1\" r=\"-1\"></ping>\n"
+      "        <updatecheck targetversionprefix=\"\"></updatecheck>\n"),
+      string::npos);
+  EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
+            string::npos);
+  EXPECT_NE(post_str.find("fw_version=\"ChromeOSFirmware.1.0\""),
+            string::npos);
+  EXPECT_NE(post_str.find("ec_version=\"0X0A1\""),
+            string::npos);
+  // 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) {
+  brillo::Blob post_data;
+  TestEvent(request_params_,
+            new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
+            "invalid xml>",
+            &post_data);
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  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) {
+  brillo::Blob post_data;
+  TestEvent(request_params_,
+            new OmahaEvent(OmahaEvent::kTypeDownloadComplete,
+                           OmahaEvent::kResultError,
+                           ErrorCode::kError),
+            "invalid xml>",
+            &post_data);
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  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");
+  // Create a copy of the OmahaRequestParams to reuse it later.
+  OmahaRequestParams params = request_params_;
+  fake_system_state_.set_request_params(&params);
+  OmahaRequestAction update_check_action(
+      &fake_system_state_,
+      nullptr,
+      brillo::make_unique_ptr(
+          new MockHttpFetcher(http_response.data(),
+                              http_response.size(),
+                              nullptr)),
+      false);
+  EXPECT_FALSE(update_check_action.IsEvent());
+
+  params = request_params_;
+  fake_system_state_.set_request_params(&params);
+  OmahaRequestAction event_action(
+      &fake_system_state_,
+      new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+      brillo::make_unique_ptr(
+          new MockHttpFetcher(http_response.data(),
+                              http_response.size(),
+                              nullptr)),
+      false);
+  EXPECT_TRUE(event_action.IsEvent());
+}
+
+TEST_F(OmahaRequestActionTest, FormatDeltaOkayOutputTest) {
+  for (int i = 0; i < 2; i++) {
+    bool delta_okay = i == 1;
+    const char* delta_okay_str = delta_okay ? "true" : "false";
+    brillo::Blob post_data;
+    OmahaRequestParams params(&fake_system_state_,
+                              constants::kOmahaPlatformName,
+                              OmahaRequestParams::kOsVersion,
+                              "service_pack",
+                              "x86-generic",
+                              kTestAppId,
+                              "0.1.0.0",
+                              "en-US",
+                              "unittest_track",
+                              "OEM MODEL REV 1234",
+                              "ChromeOSFirmware.1.0",
+                              "EC100",
+                              delta_okay,
+                              false,  // interactive
+                              "http://url",
+                              "");    // target_version_prefix
+    ASSERT_FALSE(TestUpdateCheck(&params,
+                                 "invalid xml>",
+                                 -1,
+                                 false,  // ping_only
+                                 ErrorCode::kOmahaRequestXMLParseError,
+                                 metrics::CheckResult::kParsingError,
+                                 metrics::CheckReaction::kUnset,
+                                 metrics::DownloadErrorCode::kUnset,
+                                 nullptr,
+                                 &post_data));
+    // convert post_data to string
+    string post_str(post_data.begin(), post_data.end());
+    EXPECT_NE(post_str.find(base::StringPrintf(" delta_okay=\"%s\"",
+                                               delta_okay_str)),
+              string::npos)
+        << "i = " << i;
+  }
+}
+
+TEST_F(OmahaRequestActionTest, FormatInteractiveOutputTest) {
+  for (int i = 0; i < 2; i++) {
+    bool interactive = i == 1;
+    const char* interactive_str = interactive ? "ondemandupdate" : "scheduler";
+    brillo::Blob post_data;
+    FakeSystemState fake_system_state;
+    OmahaRequestParams params(&fake_system_state_,
+                              constants::kOmahaPlatformName,
+                              OmahaRequestParams::kOsVersion,
+                              "service_pack",
+                              "x86-generic",
+                              kTestAppId,
+                              "0.1.0.0",
+                              "en-US",
+                              "unittest_track",
+                              "OEM MODEL REV 1234",
+                              "ChromeOSFirmware.1.0",
+                              "EC100",
+                              true,   // delta_okay
+                              interactive,
+                              "http://url",
+                              "");    // target_version_prefix
+    ASSERT_FALSE(TestUpdateCheck(&params,
+                                 "invalid xml>",
+                                 -1,
+                                 false,  // ping_only
+                                 ErrorCode::kOmahaRequestXMLParseError,
+                                 metrics::CheckResult::kParsingError,
+                                 metrics::CheckReaction::kUnset,
+                                 metrics::DownloadErrorCode::kUnset,
+                                 nullptr,
+                                 &post_data));
+    // convert post_data to string
+    string post_str(post_data.begin(), post_data.end());
+    EXPECT_NE(post_str.find(base::StringPrintf("installsource=\"%s\"",
+                                               interactive_str)),
+              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);
+}
+
+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(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(six_days_ago), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(five_days_ago), Return(true)));
+  brillo::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      ping_only,
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUnset,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  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(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(three_days_ago), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+  brillo::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  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(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(four_days_ago), Return(true)));
+  brillo::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  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(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(one_hour_ago), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<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));
+  brillo::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  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(SetArgumentPointee<1>(now), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+  brillo::Blob post_data;
+  EXPECT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      true,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUnset,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  EXPECT_EQ(0U, post_data.size());
+}
+
+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(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(future), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(future), Return(true)));
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(Return(true));
+  brillo::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?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>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  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));
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?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>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      nullptr));
+}
+
+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);
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?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>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      nullptr));
+}
+
+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);
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?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>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      nullptr));
+}
+
+TEST_F(OmahaRequestActionTest, ParseUpdateCheckAttributesTest) {
+  // Test that the "eol" flags is only parsed from the "_eol" attribute and not
+  // the "eol" attribute.
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+                      "protocol=\"3.0\"><app appid=\"foo\" status=\"ok\">"
+                      "<ping status=\"ok\"/><updatecheck status=\"noupdate\" "
+                      "_eol=\"security-only\" eol=\"eol\" _foo=\"bar\"/>"
+                      "</app></response>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      nullptr));
+  string eol_pref;
+  EXPECT_TRUE(
+      fake_system_state_.prefs()->GetString(kPrefsOmahaEolStatus, &eol_pref));
+  // Note that the eol="eol" attribute should be ignored and the _eol should be
+  // used instead.
+  EXPECT_EQ("security-only", eol_pref);
+}
+
+TEST_F(OmahaRequestActionTest, NoUniqueIDTest) {
+  brillo::Blob post_data;
+  ASSERT_FALSE(TestUpdateCheck(nullptr,  // request_params
+                               "invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               nullptr,  // response
+                               &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_EQ(post_str.find("machineid="), string::npos);
+  EXPECT_EQ(post_str.find("userid="), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, NetworkFailureTest) {
+  OmahaResponse response;
+  const int http_error_code =
+      static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 501;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "",
+                      501,
+                      false,  // ping_only
+                      static_cast<ErrorCode>(http_error_code),
+                      metrics::CheckResult::kDownloadError,
+                      metrics::CheckReaction::kUnset,
+                      static_cast<metrics::DownloadErrorCode>(501),
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NetworkFailureBadHTTPCodeTest) {
+  OmahaResponse response;
+  const int http_error_code =
+      static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 999;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "",
+                      1500,
+                      false,  // ping_only
+                      static_cast<ErrorCode>(http_error_code),
+                      metrics::CheckResult::kDownloadError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kHttpStatusOther,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsPersistedFirstTime) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta().FromDays(1));
+  params.set_update_check_count_wait_enabled(false);
+
+  Time arbitrary_date;
+  Time::FromString("6/4/1989", &arbitrary_date);
+  fake_system_state_.fake_clock()->SetWallclockTime(arbitrary_date);
+  ASSERT_FALSE(TestUpdateCheck(&params,
+                               fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                               metrics::CheckResult::kUpdateAvailable,
+                               metrics::CheckReaction::kDeferring,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
+
+  int64_t timestamp = 0;
+  ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, &timestamp));
+  EXPECT_EQ(arbitrary_date.ToInternalValue(), timestamp);
+  EXPECT_FALSE(response.update_exists);
+
+  // Verify if we are interactive check we don't defer.
+  params.set_interactive(true);
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsUsedIfAlreadyPresent) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta().FromDays(1));
+  params.set_update_check_count_wait_enabled(false);
+
+  Time t1, t2;
+  Time::FromString("1/1/2012", &t1);
+  Time::FromString("1/3/2012", &t2);
+  ASSERT_TRUE(
+      fake_prefs_.SetInt64(kPrefsUpdateFirstSeenAt, t1.ToInternalValue()));
+  fake_system_state_.fake_clock()->SetWallclockTime(t2);
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  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, &timestamp));
+  ASSERT_TRUE(timestamp == t1.ToInternalValue());
+}
+
+TEST_F(OmahaRequestActionTest, TestChangingToMoreStableChannel) {
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+  brillo::Blob post_data;
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_root(tempdir.path().value());
+  params.set_app_id("{22222222-2222-2222-2222-222222222222}");
+  params.set_app_version("1.2.3.4");
+  params.set_current_channel("canary-channel");
+  EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+  params.UpdateDownloadChannel();
+  EXPECT_TRUE(params.to_more_stable_channel());
+  EXPECT_TRUE(params.is_powerwash_allowed());
+  ASSERT_FALSE(TestUpdateCheck(&params,
+                               "invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               nullptr,  // response
+                               &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  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\" "));
+}
+
+TEST_F(OmahaRequestActionTest, TestChangingToLessStableChannel) {
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+  brillo::Blob post_data;
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_root(tempdir.path().value());
+  params.set_app_id("{11111111-1111-1111-1111-111111111111}");
+  params.set_app_version("5.6.7.8");
+  params.set_current_channel("stable-channel");
+  EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
+  params.UpdateDownloadChannel();
+  EXPECT_FALSE(params.to_more_stable_channel());
+  EXPECT_FALSE(params.is_powerwash_allowed());
+  ASSERT_FALSE(TestUpdateCheck(&params,
+                               "invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               nullptr,  // response
+                               &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  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"));
+}
+
+// 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);
+
+  brillo::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  // We shouldn't send a ping in this case since powerwash > 0.
+  string post_str(post_data.begin(), post_data.end());
+  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");
+
+  brillo::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+
+  // 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) {
+  OmahaResponse response;
+  OmahaRequestParams request_params = request_params_;
+  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;
+  ASSERT_TRUE(
+      TestUpdateCheck(&request_params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  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;
+  return
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      response,
+                      nullptr);
+}
+
+TEST_F(OmahaRequestActionTest, ParseInstallDateFromResponse) {
+  OmahaResponse response;
+
+  // 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);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/omaha_request_params.cc b/update_engine/omaha_request_params.cc
new file mode 100644
index 0000000..3402451
--- /dev/null
+++ b/update_engine/omaha_request_params.cc
@@ -0,0 +1,220 @@
+//
+// 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/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/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/utils.h"
+#include "update_engine/system_state.h"
+
+#define CALL_MEMBER_FN(object, member) ((object).*(member))
+
+using std::map;
+using std::string;
+using std::vector;
+
+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& in_app_version,
+                              const string& in_update_url,
+                              bool in_interactive) {
+  LOG(INFO) << "Initializing parameters for this update attempt";
+  image_props_ = LoadImageProperties(system_state_);
+  mutable_image_props_ = LoadMutableImageProperties(system_state_);
+
+  // Sanity 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 (!in_app_version.empty())
+    image_props_.version = in_app_version;
+
+  os_sp_ = image_props_.version + "_" + GetMachineType();
+  app_lang_ = "en-US";
+  hwid_ = system_state_->hardware()->GetHardwareClass();
+  if (CollectECFWVersions()) {
+    fw_version_ = system_state_->hardware()->GetFirmwareVersion();
+    ec_version_ = system_state_->hardware()->GetECVersion();
+  }
+
+  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 (in_update_url.empty())
+    update_url_ = image_props_.omaha_url;
+  else
+    update_url_ = in_update_url;
+
+  // Set the interactive flag accordingly.
+  interactive_ = in_interactive;
+  return true;
+}
+
+bool OmahaRequestParams::IsUpdateUrlOfficial() const {
+  return (update_url_ == constants::kOmahaDefaultAUTestURL ||
+          update_url_ == image_props_.omaha_url);
+}
+
+bool OmahaRequestParams::CollectECFWVersions() const {
+  return base::StartsWith(hwid_, string("SAMS ALEX"),
+                          base::CompareCase::SENSITIVE) ||
+         base::StartsWith(hwid_, string("BUTTERFLY"),
+                          base::CompareCase::SENSITIVE) ||
+         base::StartsWith(hwid_, string("LUMPY"),
+                          base::CompareCase::SENSITIVE) ||
+         base::StartsWith(hwid_, string("PARROT"),
+                          base::CompareCase::SENSITIVE) ||
+         base::StartsWith(hwid_, string("SPRING"),
+                          base::CompareCase::SENSITIVE) ||
+         base::StartsWith(hwid_, string("SNOW"), base::CompareCase::SENSITIVE);
+}
+
+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)) {
+    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",
+          new_target_channel.c_str(), valid_channels.c_str());
+    }
+    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) const {
+  return GetChannelIndex(channel) >= 0;
+}
+
+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 < arraysize(kChannelsByStability); ++t)
+    if (channel == kChannelsByStability[t])
+      return t;
+
+  return -1;
+}
+
+bool OmahaRequestParams::to_more_stable_channel() const {
+  int current_channel_index = GetChannelIndex(image_props_.current_channel);
+  int download_channel_index = GetChannelIndex(download_channel_);
+
+  return download_channel_index > current_channel_index;
+}
+
+string OmahaRequestParams::GetAppId() const {
+  return download_channel_ == "canary-channel" ? image_props_.canary_product_id
+                                               : image_props_.product_id;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/omaha_request_params.h b/update_engine/omaha_request_params.h
new file mode 100644
index 0000000..379563a
--- /dev/null
+++ b/update_engine/omaha_request_params.h
@@ -0,0 +1,339 @@
+//
+// 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_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/image_properties.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),
+        wall_clock_based_wait_enabled_(false),
+        update_check_count_wait_enabled_(false),
+        min_update_checks_needed_(kDefaultMinUpdateChecks),
+        max_update_checks_allowed_(kDefaultMaxUpdateChecks) {}
+
+  OmahaRequestParams(SystemState* system_state,
+                     const std::string& in_os_platform,
+                     const std::string& in_os_version,
+                     const std::string& in_os_sp,
+                     const std::string& in_os_board,
+                     const std::string& in_app_id,
+                     const std::string& in_app_version,
+                     const std::string& in_app_lang,
+                     const std::string& in_target_channel,
+                     const std::string& in_hwid,
+                     const std::string& in_fw_version,
+                     const std::string& in_ec_version,
+                     bool in_delta_okay,
+                     bool in_interactive,
+                     const std::string& in_update_url,
+                     const std::string& in_target_version_prefix)
+      : system_state_(system_state),
+        os_platform_(in_os_platform),
+        os_version_(in_os_version),
+        os_sp_(in_os_sp),
+        app_lang_(in_app_lang),
+        hwid_(in_hwid),
+        fw_version_(in_fw_version),
+        ec_version_(in_ec_version),
+        delta_okay_(in_delta_okay),
+        interactive_(in_interactive),
+        update_url_(in_update_url),
+        target_version_prefix_(in_target_version_prefix),
+        wall_clock_based_wait_enabled_(false),
+        update_check_count_wait_enabled_(false),
+        min_update_checks_needed_(kDefaultMinUpdateChecks),
+        max_update_checks_allowed_(kDefaultMaxUpdateChecks) {
+    image_props_.board = in_os_board;
+    image_props_.product_id = in_app_id;
+    image_props_.canary_product_id = in_app_id;
+    image_props_.version = in_app_version;
+    image_props_.current_channel = in_target_channel;
+    mutable_image_props_.target_channel = in_target_channel;
+    mutable_image_props_.is_powerwash_allowed = false;
+  }
+
+  virtual ~OmahaRequestParams();
+
+  // 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 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 fw_version() const { return fw_version_; }
+  inline std::string ec_version() const { return ec_version_; }
+
+  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 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 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_;
+  }
+
+  // True if we're trying to update to a more stable channel.
+  // i.e. index(target_channel) > index(current_channel).
+  virtual bool to_more_stable_channel() const;
+
+  // Returns the app id corresponding to the current value of the
+  // download channel.
+  virtual std::string GetAppId() const;
+
+  // Suggested defaults
+  static const char kOsVersion[];
+  static const char kIsPowerwashAllowedKey[];
+  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,
+            bool in_interactive);
+
+  // 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();
+
+  virtual bool is_powerwash_allowed() const {
+    return mutable_image_props_.is_powerwash_allowed;
+  }
+
+  // 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;
+  }
+
+ private:
+  FRIEND_TEST(OmahaRequestParamsTest, IsValidChannelTest);
+  FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest);
+  FRIEND_TEST(OmahaRequestParamsTest, ToMoreStableChannelFlagTest);
+  FRIEND_TEST(OmahaRequestParamsTest, CollectECFWVersionsTest);
+
+  // Returns true if |channel| is a valid channel, false otherwise.
+  bool IsValidChannel(const std::string& channel) const;
+
+  // Returns the index of the given channel.
+  int GetChannelIndex(const std::string& channel) const;
+
+  // Returns True if we should store the fw/ec versions based on our hwid_.
+  // Compares hwid to a set of whitelisted prefixes.
+  bool CollectECFWVersions() const;
+
+  // These are individual helper methods to initialize the said properties from
+  // the LSB value.
+  void SetTargetChannelFromLsbValue();
+  void SetCurrentChannelFromLsbValue();
+  void SetIsPowerwashAllowedFromLsbValue();
+
+  // Initializes the required properties from the LSB value.
+  void InitFromLsbValue();
+
+  // 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_;
+
+  std::string hwid_;  // Hardware Qualification ID of the client
+  std::string fw_version_;  // Chrome OS Firmware Version.
+  std::string ec_version_;  // Chrome OS EC Version.
+  bool delta_okay_;  // If this client can accept a delta
+  bool interactive_;   // Whether this is a user-initiated update check
+
+  // 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_;
+
+  // True if scattering is 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 is 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_;
+
+  // TODO(jaysri): Uncomment this after fixing unit tests, as part of
+  // chromium-os:39752
+  // DISALLOW_COPY_AND_ASSIGN(OmahaRequestParams);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
diff --git a/update_engine/omaha_request_params_unittest.cc b/update_engine/omaha_request_params_unittest.cc
new file mode 100644
index 0000000..7d4dc2d
--- /dev/null
+++ b/update_engine/omaha_request_params_unittest.cc
@@ -0,0 +1,243 @@
+//
+// 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/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/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());
+    // Create a fresh copy of the params for each test, so there's no
+    // unintended reuse of state across tests.
+    params_ = OmahaRequestParams(&fake_system_state_);
+    params_.set_root(tempdir_.path().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);
+  }
+
+  OmahaRequestParams params_;
+  FakeSystemState 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("", "", false));
+  // 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", "", false));
+  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", false));
+  EXPECT_EQ("http://forced.google.com", params_.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingURLTest) {
+  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_EQ(constants::kOmahaDefaultProductionURL, params_.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, DeltaOKTest) {
+  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_TRUE(params_.delta_okay());
+}
+
+TEST_F(OmahaRequestParamsTest, NoDeltasTest) {
+  ASSERT_TRUE(WriteFileString(tempdir_.path().Append(".nodelta").value(), ""));
+  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_FALSE(params_.delta_okay());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelTest) {
+  {
+    OmahaRequestParams params(&fake_system_state_);
+    params.set_root(tempdir_.path().value());
+    EXPECT_TRUE(params.Init("", "", false));
+    EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
+    EXPECT_FALSE(params.is_powerwash_allowed());
+  }
+  params_.set_root(tempdir_.path().value());
+  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_EQ("canary-channel", params_.target_channel());
+  EXPECT_FALSE(params_.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, SetIsPowerwashAllowedTest) {
+  {
+    OmahaRequestParams params(&fake_system_state_);
+    params.set_root(tempdir_.path().value());
+    EXPECT_TRUE(params.Init("", "", false));
+    EXPECT_TRUE(params.SetTargetChannel("canary-channel", true, nullptr));
+    EXPECT_TRUE(params.is_powerwash_allowed());
+  }
+  params_.set_root(tempdir_.path().value());
+  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_EQ("canary-channel", params_.target_channel());
+  EXPECT_TRUE(params_.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) {
+  {
+    OmahaRequestParams params(&fake_system_state_);
+    params.set_root(tempdir_.path().value());
+    SetLockDown(true);
+    EXPECT_TRUE(params.Init("", "", 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.is_powerwash_allowed());
+  }
+  params_.set_root(tempdir_.path().value());
+  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_EQ("stable-channel", params_.target_channel());
+  EXPECT_FALSE(params_.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(""));
+}
+
+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("", "", false);
+  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_.to_more_stable_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, CollectECFWVersionsTest) {
+  params_.hwid_ = string("STUMPY ALEX 12345");
+  EXPECT_FALSE(params_.CollectECFWVersions());
+
+  params_.hwid_ = string("SNOW 12345");
+  EXPECT_TRUE(params_.CollectECFWVersions());
+
+  params_.hwid_ = string("SAMS ALEX 12345");
+  EXPECT_TRUE(params_.CollectECFWVersions());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/omaha_response.h b/update_engine/omaha_response.h
new file mode 100644
index 0000000..60ec4ac
--- /dev/null
+++ b/update_engine/omaha_response.h
@@ -0,0 +1,85 @@
+//
+// 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_OMAHA_RESPONSE_H_
+#define UPDATE_ENGINE_OMAHA_RESPONSE_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#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;
+
+  // 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;
+
+  std::string more_info_url;
+  std::string hash;
+  std::string metadata_signature;
+  std::string deadline;
+  off_t size = 0;
+  off_t metadata_size = 0;
+  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 payload described in this response is a delta payload.
+  // False if it's a full payload.
+  bool is_delta_payload = 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;
+
+  // 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;
+};
+static_assert(sizeof(off_t) == 8, "off_t not 64 bit");
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_RESPONSE_H_
diff --git a/update_engine/omaha_response_handler_action.cc b/update_engine/omaha_response_handler_action.cc
new file mode 100644
index 0000000..33380d7
--- /dev/null
+++ b/update_engine/omaha_response_handler_action.cc
@@ -0,0 +1,210 @@
+//
+// 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/omaha_response_handler_action.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.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/connection_manager_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_state_interface.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+OmahaResponseHandlerAction::OmahaResponseHandlerAction(
+    SystemState* system_state)
+    : OmahaResponseHandlerAction(system_state,
+                                 constants::kOmahaResponseDeadlineFile) {}
+
+OmahaResponseHandlerAction::OmahaResponseHandlerAction(
+    SystemState* system_state, const string& deadline_file)
+    : system_state_(system_state),
+      got_no_update_response_(false),
+      key_path_(constants::kUpdatePayloadPublicKeyPath),
+      deadline_file_(deadline_file) {}
+
+void OmahaResponseHandlerAction::PerformAction() {
+  CHECK(HasInputObject());
+  ScopedActionCompleter completer(processor_, this);
+  const OmahaResponse& response = GetInputObject();
+  if (!response.update_exists) {
+    got_no_update_response_ = true;
+    LOG(INFO) << "There are no updates. Aborting.";
+    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;
+  }
+
+  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.
+  install_plan_.payload_size = response.size;
+  install_plan_.payload_hash = response.hash;
+  install_plan_.metadata_size = response.metadata_size;
+  install_plan_.metadata_signature = response.metadata_signature;
+  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(), 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, response.hash))
+        << "Unable to save the update check response hash.";
+  }
+  install_plan_.payload_type = response.is_delta_payload
+                                   ? InstallPayloadType::kDelta
+                                   : InstallPayloadType::kFull;
+
+  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());
+
+  if (params->to_more_stable_channel() && params->is_powerwash_allowed())
+    install_plan_.powerwash_required = true;
+
+  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()) {
+    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);
+  }
+
+  completer.set_code(ErrorCode::kSuccess);
+}
+
+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;
+    }
+  }
+
+  // If we're using p2p, |install_plan_.download_url| may contain a
+  // HTTP URL even if |response.payload_urls| contain only HTTPS URLs.
+  if (!base::StartsWith(install_plan_.download_url, "https://",
+                        base::CompareCase::INSENSITIVE_ASCII)) {
+    LOG(INFO) << "Mandating hash checks since download_url is not HTTPS.";
+    return true;
+  }
+
+  // TODO(jaysri): VALIDATION: For official builds, we currently waive hash
+  // checks for HTTPS until we have rolled out at least once and are confident
+  // nothing breaks. chromium-os:37082 tracks turning this on for HTTPS
+  // eventually.
+
+  // Even if there's a single non-HTTPS URL, make the hash checks as
+  // mandatory because we could be downloading the payload from any URL later
+  // on. It's really hard to do book-keeping based on each byte being
+  // downloaded to see whether we only used HTTPS throughout.
+  for (size_t i = 0; i < response.payload_urls.size(); i++) {
+    if (!base::StartsWith(response.payload_urls[i], "https://",
+                          base::CompareCase::INSENSITIVE_ASCII)) {
+      LOG(INFO) << "Mandating payload hash checks since Omaha response "
+                << "contains non-HTTPS URL(s)";
+      return true;
+    }
+  }
+
+  LOG(INFO) << "Waiving payload hash checks since Omaha response "
+            << "only has HTTPS URL(s)";
+  return false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/omaha_response_handler_action.h b/update_engine/omaha_response_handler_action.h
new file mode 100644
index 0000000..51dfa7a
--- /dev/null
+++ b/update_engine/omaha_response_handler_action.h
@@ -0,0 +1,98 @@
+//
+// 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_OMAHA_RESPONSE_HANDLER_ACTION_H_
+#define UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_
+
+#include <string>
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/common/action.h"
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/system_state.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); }
+
+  bool GotNoUpdateResponse() const { return got_no_update_response_; }
+  const InstallPlan& install_plan() const { return install_plan_; }
+
+  // Debugging/logging
+  static std::string StaticType() { return "OmahaResponseHandlerAction"; }
+  std::string Type() const override { return StaticType(); }
+  void set_key_path(const std::string& path) { key_path_ = path; }
+
+ 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_;
+
+  // True only if we got a response and the response said no updates
+  bool got_no_update_response_;
+
+  // Public key path to use for payload verification.
+  std::string key_path_;
+
+  // File used for communication deadline to Chrome.
+  const std::string deadline_file_;
+
+  // Special ctor + friend declarations for testing purposes.
+  OmahaResponseHandlerAction(SystemState* system_state,
+                             const std::string& deadline_file);
+
+  friend class OmahaResponseHandlerActionTest;
+
+  FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+
+  DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_
diff --git a/update_engine/omaha_response_handler_action_unittest.cc b/update_engine/omaha_response_handler_action_unittest.cc
new file mode 100644
index 0000000..60b139b
--- /dev/null
+++ b/update_engine/omaha_response_handler_action_unittest.cc
@@ -0,0 +1,424 @@
+//
+// 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/omaha_response_handler_action.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/platform_constants.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using testing::Return;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerActionTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    FakeBootControl* fake_boot_control = fake_system_state_.fake_boot_control();
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameKernel, 0, "/dev/sdz2");
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameRoot, 0, "/dev/sdz3");
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameKernel, 1, "/dev/sdz4");
+    fake_boot_control->SetPartitionDevice(
+        kLegacyPartitionNameRoot, 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);
+
+  FakeSystemState fake_system_state_;
+};
+
+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()) {
+      code_ = code;
+      code_set_ = true;
+    }
+  }
+  ErrorCode code_;
+  bool code_set_;
+};
+
+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";
+}  // namespace
+
+bool OmahaResponseHandlerActionTest::DoTest(
+    const OmahaResponse& in,
+    const string& test_deadline_file,
+    InstallPlan* out) {
+  ActionProcessor processor;
+  OmahaResponseHandlerActionProcessorDelegate delegate;
+  processor.set_delegate(&delegate);
+
+  ObjectFeederAction<OmahaResponse> feeder_action;
+  feeder_action.set_obj(in);
+  if (in.update_exists && in.version != kBadVersion) {
+    EXPECT_CALL(*(fake_system_state_.mock_prefs()),
+                SetString(kPrefsUpdateCheckResponseHash, in.hash))
+        .WillOnce(Return(true));
+
+    int slot = 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.payload_urls.size() ? in.payload_urls[0] : "";
+  EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
+      .WillRepeatedly(Return(current_url));
+
+  OmahaResponseHandlerAction response_handler_action(
+      &fake_system_state_,
+      (test_deadline_file.empty() ?
+       constants::kOmahaResponseDeadlineFile : test_deadline_file));
+  BondActions(&feeder_action, &response_handler_action);
+  ObjectCollectorAction<InstallPlan> collector_action;
+  BondActions(&response_handler_action, &collector_action);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&response_handler_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_TRUE(!processor.IsRunning())
+      << "Update test to handle non-async actions";
+  if (out)
+    *out = collector_action.object();
+  EXPECT_TRUE(delegate.code_set_);
+  return delegate.code_ == ErrorCode::kSuccess;
+}
+
+TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
+  string test_deadline_file;
+  CHECK(utils::MakeTempFile(
+          "omaha_response_handler_action_unittest-XXXXXX",
+          &test_deadline_file, nullptr));
+  ScopedPathUnlinker deadline_unlinker(test_deadline_file);
+  {
+    OmahaResponse in;
+    in.update_exists = true;
+    in.version = "a.b.c.d";
+    in.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+    in.more_info_url = "http://more/info";
+    in.hash = "HASH+";
+    in.size = 12;
+    in.prompt = false;
+    in.deadline = "20101020";
+    InstallPlan install_plan;
+    EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
+    EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(in.hash, install_plan.payload_hash);
+    EXPECT_EQ(1U, install_plan.target_slot);
+    string deadline;
+    EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
+    EXPECT_EQ("20101020", deadline);
+    struct stat deadline_stat;
+    EXPECT_EQ(0, stat(test_deadline_file.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.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+    in.more_info_url = "http://more/info";
+    in.hash = "HASHj+";
+    in.size = 12;
+    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, &install_plan));
+    EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(in.hash, install_plan.payload_hash);
+    EXPECT_EQ(0U, install_plan.target_slot);
+    string deadline;
+    EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline) &&
+                deadline.empty());
+    EXPECT_EQ(in.version, install_plan.version);
+  }
+  {
+    OmahaResponse in;
+    in.update_exists = true;
+    in.version = "a.b.c.d";
+    in.payload_urls.push_back(kLongName);
+    in.more_info_url = "http://more/info";
+    in.hash = "HASHj+";
+    in.size = 12;
+    in.prompt = true;
+    in.deadline = "some-deadline";
+    InstallPlan install_plan;
+    fake_system_state_.fake_boot_control()->SetCurrentSlot(0);
+    EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
+    EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(in.hash, install_plan.payload_hash);
+    EXPECT_EQ(1U, install_plan.target_slot);
+    string deadline;
+    EXPECT_TRUE(utils::ReadFile(test_deadline_file, &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, HashChecksForHttpTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("http://test.should/need/hash.checks.signed");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  // 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.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_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.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+              IsUpdateUrlOfficial())
+      .WillRepeatedly(Return(false));
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_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.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  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.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_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.payload_urls.push_back("https://test.should.not/need/hash.checks.signed");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+              IsUpdateUrlOfficial())
+      .WillRepeatedly(Return(true));
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_FALSE(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.payload_urls.push_back("http://test.should.still/need/hash.checks");
+  in.payload_urls.push_back("https://test.should.still/need/hash.checks");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+              IsUpdateUrlOfficial())
+      .WillRepeatedly(Return(true));
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_TRUE(install_plan.hash_checks_mandatory);
+  EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, ChangeToMoreStableChannelTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("https://MoreStableChannelTest");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHjk";
+  in.size = 15;
+
+  // 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.path().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(), SetString(_, "stable-channel"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, true))
+      .WillOnce(Return(true));
+#endif  // __ANDROID__
+  EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+  params.UpdateDownloadChannel();
+  EXPECT_TRUE(params.to_more_stable_channel());
+  EXPECT_TRUE(params.is_powerwash_allowed());
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_TRUE(install_plan.powerwash_required);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, ChangeToLessStableChannelTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("https://LessStableChannelTest");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHjk";
+  in.size = 15;
+
+  // 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.path().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(), SetString(_, "canary-channel"))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, false))
+      .WillOnce(Return(true));
+#endif  // __ANDROID__
+  EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
+  params.UpdateDownloadChannel();
+  EXPECT_FALSE(params.to_more_stable_channel());
+  EXPECT_FALSE(params.is_powerwash_allowed());
+
+  fake_system_state_.set_request_params(&params);
+  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.payload_urls.push_back("https://would.not/cause/hash/checks");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+
+  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(&params);
+
+  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(in.hash, install_plan.payload_hash);
+  EXPECT_EQ(install_plan.download_url, p2p_url);
+  EXPECT_TRUE(install_plan.hash_checks_mandatory);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/omaha_utils.cc b/update_engine/omaha_utils.cc
new file mode 100644
index 0000000..6bd7525
--- /dev/null
+++ b/update_engine/omaha_utils.cc
@@ -0,0 +1,57 @@
+//
+// 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/omaha_utils.h"
+
+#include <base/logging.h>
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The possible string values for the end-of-life status.
+const char kEolStatusSupported[] = "supported";
+const char kEolStatusSecurityOnly[] = "security-only";
+const char kEolStatusEol[] = "eol";
+
+}  // namespace
+
+const char* EolStatusToString(EolStatus eol_status) {
+  switch (eol_status) {
+    case EolStatus::kSupported:
+      return kEolStatusSupported;
+    case EolStatus::kSecurityOnly:
+      return kEolStatusSecurityOnly;
+    case EolStatus::kEol:
+      return kEolStatusEol;
+  }
+  // Only reached if an invalid number is casted to |EolStatus|.
+  LOG(WARNING) << "Invalid EolStatus value: " << static_cast<int>(eol_status);
+  return kEolStatusSupported;
+}
+
+EolStatus StringToEolStatus(const std::string& eol_status) {
+  if (eol_status == kEolStatusSupported || eol_status.empty())
+    return EolStatus::kSupported;
+  if (eol_status == kEolStatusSecurityOnly)
+    return EolStatus::kSecurityOnly;
+  if (eol_status == kEolStatusEol)
+    return EolStatus::kEol;
+  LOG(WARNING) << "Invalid end-of-life attribute: " << eol_status;
+  return EolStatus::kSupported;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/omaha_utils.h b/update_engine/omaha_utils.h
new file mode 100644
index 0000000..8614540
--- /dev/null
+++ b/update_engine/omaha_utils.h
@@ -0,0 +1,40 @@
+//
+// 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_OMAHA_UTILS_H_
+#define UPDATE_ENGINE_OMAHA_UTILS_H_
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+// The end-of-life status of the device.
+enum class EolStatus {
+  kSupported = 0,
+  kSecurityOnly,
+  kEol,
+};
+
+// Returns the string representation of the |eol_status|.
+const char* EolStatusToString(EolStatus eol_status);
+
+// Converts the end-of-life status string to an EolStatus numeric value. In case
+// of an invalid string, the default "supported" value will be used instead.
+EolStatus StringToEolStatus(const std::string& eol_status);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_UTILS_H_
diff --git a/update_engine/omaha_utils_unittest.cc b/update_engine/omaha_utils_unittest.cc
new file mode 100644
index 0000000..8ceb76b
--- /dev/null
+++ b/update_engine/omaha_utils_unittest.cc
@@ -0,0 +1,42 @@
+//
+// 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/omaha_utils.h"
+
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+class OmahaUtilsTest : public ::testing::Test {};
+
+TEST(OmahaUtilsTest, EolStatusTest) {
+  EXPECT_EQ(EolStatus::kEol, StringToEolStatus("eol"));
+
+  // Supported values are converted back and forth properly.
+  const std::vector<EolStatus> tests = {
+      EolStatus::kSupported, EolStatus::kSecurityOnly, EolStatus::kEol};
+  for (EolStatus eol_status : tests) {
+    EXPECT_EQ(eol_status, StringToEolStatus(EolStatusToString(eol_status)))
+        << "The StringToEolStatus() was " << EolStatusToString(eol_status);
+  }
+
+  // Invalid values are assumed as "supported".
+  EXPECT_EQ(EolStatus::kSupported, StringToEolStatus(""));
+  EXPECT_EQ(EolStatus::kSupported, StringToEolStatus("hello, world!"));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/p2p_manager.cc b/update_engine/p2p_manager.cc
new file mode 100644
index 0000000..127e5ff
--- /dev/null
+++ b/update_engine/p2p_manager.cc
@@ -0,0 +1,742 @@
+//
+// 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 _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include "update_engine/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::map;
+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 output;
+
+  may_be_running_ = true;  // Unless successful, we must be conservative.
+
+  vector<string> args = configuration_->GetInitctlArgs(should_be_running);
+  if (!Subprocess::SynchronousExec(args, &return_code, &output)) {
+    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 (output != 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/update_engine/p2p_manager.h b/update_engine/p2p_manager.h
new file mode 100644
index 0000000..4ffab9a
--- /dev/null
+++ b/update_engine/p2p_manager.h
@@ -0,0 +1,188 @@
+//
+// 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_P2P_MANAGER_H_
+#define UPDATE_ENGINE_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_P2P_MANAGER_H_
diff --git a/update_engine/p2p_manager_unittest.cc b/update_engine/p2p_manager_unittest.cc
new file mode 100644
index 0000000..463c0e2
--- /dev/null
+++ b/update_engine/p2p_manager_unittest.cc
@@ -0,0 +1,515 @@
+//
+// 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/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>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/stringprintf.h>
+#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/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/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::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+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)));
+  }
+
+  base::MessageLoopForIO base_loop_;
+  brillo::BaseMessageLoop loop_{&base_loop_};
+  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 specificed 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) {
+  if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+  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) {
+  if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+  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) {
+  if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  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", "echo http://1.2.3.4/; sleep 2"});
+  manager_->LookupUrlForFile("foobar", 42, TimeDelta::FromMilliseconds(500),
+                             base::Bind(ExpectUrl, ""));
+  loop_.Run();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/parcelable_update_engine_status.cc b/update_engine/parcelable_update_engine_status.cc
new file mode 100644
index 0000000..d8eb6db
--- /dev/null
+++ b/update_engine/parcelable_update_engine_status.cc
@@ -0,0 +1,77 @@
+//
+// 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/parcelable_update_engine_status.h"
+
+#include <binder/Parcel.h>
+
+namespace android {
+namespace brillo {
+
+status_t ParcelableUpdateEngineStatus::writeToParcel(Parcel* parcel) const {
+  status_t status;
+
+  status = parcel->writeInt64(last_checked_time_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->writeDouble(progress_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->writeString16(current_operation_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->writeString16(new_version_);
+  if (status != OK) {
+    return status;
+  }
+
+  return parcel->writeInt64(new_size_);
+}
+
+status_t ParcelableUpdateEngineStatus::readFromParcel(const Parcel* parcel) {
+  status_t status;
+
+  status = parcel->readInt64(&last_checked_time_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->readDouble(&progress_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->readString16(&current_operation_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->readString16(&new_version_);
+  if (status != OK) {
+    return status;
+  }
+
+  return parcel->readInt64(&new_size_);
+}
+
+}  // namespace brillo
+}  // namespace android
diff --git a/update_engine/parcelable_update_engine_status.h b/update_engine/parcelable_update_engine_status.h
new file mode 100644
index 0000000..2cfedd9
--- /dev/null
+++ b/update_engine/parcelable_update_engine_status.h
@@ -0,0 +1,46 @@
+//
+// 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_UPDATE_ENGINE_STATUS_H_
+#define UPDATE_ENGINE_UPDATE_ENGINE_STATUS_H_
+
+#include <binder/Parcelable.h>
+#include <utils/String16.h>
+
+namespace android {
+namespace brillo {
+
+// Parcelable object containing the current status of update engine, to be sent
+// over binder to clients from the server.
+class ParcelableUpdateEngineStatus : public Parcelable {
+ public:
+  ParcelableUpdateEngineStatus() = default;
+  virtual ~ParcelableUpdateEngineStatus() = default;
+
+  status_t writeToParcel(Parcel* parcel) const override;
+  status_t readFromParcel(const Parcel* parcel) override;
+
+  int64_t last_checked_time_;
+  double progress_;
+  android::String16 current_operation_;
+  android::String16 new_version_;
+  int64_t new_size_;
+};
+
+}  // namespace brillo
+}  // namespace android
+
+#endif  // UPDATE_ENGINE_UPDATE_ENGINE_STATUS_H_
diff --git a/update_engine/payload_consumer/bzip_extent_writer.cc b/update_engine/payload_consumer/bzip_extent_writer.cc
new file mode 100644
index 0000000..0fcc8ba
--- /dev/null
+++ b/update_engine/payload_consumer/bzip_extent_writer.cc
@@ -0,0 +1,91 @@
+//
+// Copyright (C) 2009 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/payload_consumer/bzip_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const brillo::Blob::size_type kOutputBufferLength = 16 * 1024;
+}
+
+bool BzipExtentWriter::Init(FileDescriptorPtr fd,
+                            const vector<Extent>& extents,
+                            uint32_t block_size) {
+  // Init bzip2 stream
+  int rc = BZ2_bzDecompressInit(&stream_,
+                                0,   // verbosity. (0 == silent)
+                                0);  // 0 = faster algo, more memory
+
+  TEST_AND_RETURN_FALSE(rc == BZ_OK);
+
+  return next_->Init(fd, extents, block_size);
+}
+
+bool BzipExtentWriter::Write(const void* bytes, size_t count) {
+  brillo::Blob output_buffer(kOutputBufferLength);
+
+  // Copy the input data into |input_buffer_| only if |input_buffer_| already
+  // contains unconsumed data. Otherwise, process the data directly from the
+  // source.
+  const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
+  const uint8_t* input_end = input + count;
+  if (!input_buffer_.empty()) {
+    input_buffer_.insert(input_buffer_.end(), input, input_end);
+    input = input_buffer_.data();
+    input_end = input + input_buffer_.size();
+  }
+  stream_.next_in = reinterpret_cast<char*>(const_cast<uint8_t*>(input));
+  stream_.avail_in = input_end - input;
+
+  for (;;) {
+    stream_.next_out = reinterpret_cast<char*>(output_buffer.data());
+    stream_.avail_out = output_buffer.size();
+
+    int rc = BZ2_bzDecompress(&stream_);
+    TEST_AND_RETURN_FALSE(rc == BZ_OK || rc == BZ_STREAM_END);
+
+    if (stream_.avail_out == output_buffer.size())
+      break;  // got no new bytes
+
+    TEST_AND_RETURN_FALSE(
+        next_->Write(output_buffer.data(),
+                     output_buffer.size() - stream_.avail_out));
+
+    if (rc == BZ_STREAM_END)
+      CHECK_EQ(stream_.avail_in, 0u);
+    if (stream_.avail_in == 0)
+      break;  // no more input to process
+  }
+
+  // Store unconsumed data (if any) in |input_buffer_|.
+  if (stream_.avail_in || !input_buffer_.empty()) {
+    brillo::Blob new_input_buffer(input_end - stream_.avail_in, input_end);
+    new_input_buffer.swap(input_buffer_);
+  }
+
+  return true;
+}
+
+bool BzipExtentWriter::EndImpl() {
+  TEST_AND_RETURN_FALSE(input_buffer_.empty());
+  TEST_AND_RETURN_FALSE(BZ2_bzDecompressEnd(&stream_) == BZ_OK);
+  return next_->End();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/bzip_extent_writer.h b/update_engine/payload_consumer/bzip_extent_writer.h
new file mode 100644
index 0000000..0ad542e
--- /dev/null
+++ b/update_engine/payload_consumer/bzip_extent_writer.h
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2009 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_PAYLOAD_CONSUMER_BZIP_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_BZIP_EXTENT_WRITER_H_
+
+#include <bzlib.h>
+#include <memory>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+
+// BzipExtentWriter is a concrete ExtentWriter subclass that bzip-decompresses
+// what it's given in Write. It passes the decompressed data to an underlying
+// ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class BzipExtentWriter : public ExtentWriter {
+ public:
+  explicit BzipExtentWriter(std::unique_ptr<ExtentWriter> next)
+      : next_(std::move(next)) {
+    memset(&stream_, 0, sizeof(stream_));
+  }
+  ~BzipExtentWriter() override = default;
+
+  bool Init(FileDescriptorPtr fd,
+            const std::vector<Extent>& extents,
+            uint32_t block_size) override;
+  bool Write(const void* bytes, size_t count) override;
+  bool EndImpl() override;
+
+ private:
+  std::unique_ptr<ExtentWriter> next_;  // The underlying ExtentWriter.
+  bz_stream stream_;  // the libbz2 stream
+  brillo::Blob input_buffer_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_BZIP_EXTENT_WRITER_H_
diff --git a/update_engine/payload_consumer/bzip_extent_writer_unittest.cc b/update_engine/payload_consumer/bzip_extent_writer_unittest.cc
new file mode 100644
index 0000000..8ac3e59
--- /dev/null
+++ b/update_engine/payload_consumer/bzip_extent_writer_unittest.cc
@@ -0,0 +1,132 @@
+//
+// Copyright (C) 2009 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/payload_consumer/bzip_extent_writer.h"
+
+#include <fcntl.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const uint32_t kBlockSize = 4096;
+}
+
+class BzipExtentWriterTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    fd_.reset(new EintrSafeFileDescriptor);
+    ASSERT_TRUE(fd_->Open(temp_file_.path().c_str(), O_RDWR, 0600));
+  }
+  void TearDown() override {
+    fd_->Close();
+  }
+  void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+  void TestZeroPad(bool aligned_size);
+
+  FileDescriptorPtr fd_;
+  test_utils::ScopedTempFile temp_file_{"BzipExtentWriterTest-file.XXXXXX"};
+};
+
+TEST_F(BzipExtentWriterTest, SimpleTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  // 'echo test | bzip2 | hexdump' yields:
+  static const char test_uncompressed[] = "test\n";
+  static const uint8_t test[] = {
+    0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xcc, 0xc3,
+    0x71, 0xd4, 0x00, 0x00, 0x02, 0x41, 0x80, 0x00, 0x10, 0x02, 0x00, 0x0c,
+    0x00, 0x20, 0x00, 0x21, 0x9a, 0x68, 0x33, 0x4d, 0x19, 0x97, 0x8b, 0xb9,
+    0x22, 0x9c, 0x28, 0x48, 0x66, 0x61, 0xb8, 0xea, 0x00,
+  };
+
+  BzipExtentWriter bzip_writer(
+      brillo::make_unique_ptr(new DirectExtentWriter()));
+  EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+  EXPECT_TRUE(bzip_writer.Write(test, sizeof(test)));
+  EXPECT_TRUE(bzip_writer.End());
+
+  brillo::Blob buf;
+  EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &buf));
+  EXPECT_EQ(strlen(test_uncompressed), buf.size());
+  EXPECT_EQ(string(buf.begin(), buf.end()), string(test_uncompressed));
+}
+
+TEST_F(BzipExtentWriterTest, ChunkedTest) {
+  // Generated with:
+  //   yes "ABC" | head -c 819200 | bzip2 -9 |
+  //     hexdump -v -e '"      " 11/1 "0x%02x, " "\n"'
+  static const uint8_t kCompressedData[] = {
+      0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xbe,
+      0x1c, 0xda, 0xee, 0x03, 0x1f, 0xff, 0xc4, 0x00, 0x00, 0x10, 0x38,
+      0x00, 0x20, 0x00, 0x50, 0x66, 0x9a, 0x05, 0x28, 0x38, 0x00, 0x11,
+      0x60, 0x00, 0x22, 0xd0, 0x00, 0x45, 0xc0, 0x00, 0x8b, 0xc5, 0xdc,
+      0x91, 0x4e, 0x14, 0x24, 0x2f, 0x87, 0x36, 0xbb, 0x80};
+  brillo::Blob compressed_data(std::begin(kCompressedData),
+                               std::end(kCompressedData));
+
+  const brillo::Blob::size_type kDecompressedLength = 800 * 1024;  // 800 KiB
+  const size_t kChunkSize = 3;
+
+  brillo::Blob decompressed_data(kDecompressedLength);
+  for (size_t i = 0; i < decompressed_data.size(); ++i)
+    decompressed_data[i] = static_cast<uint8_t>("ABC\n"[i % 4]);
+
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(0);
+  extent.set_num_blocks((kDecompressedLength + kBlockSize - 1) / kBlockSize);
+  extents.push_back(extent);
+
+  BzipExtentWriter bzip_writer(
+      brillo::make_unique_ptr(new DirectExtentWriter()));
+  EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+
+  brillo::Blob original_compressed_data = compressed_data;
+  for (brillo::Blob::size_type i = 0; i < compressed_data.size();
+       i += kChunkSize) {
+    size_t this_chunk_size = min(kChunkSize, compressed_data.size() - i);
+    EXPECT_TRUE(bzip_writer.Write(&compressed_data[i], this_chunk_size));
+  }
+  EXPECT_TRUE(bzip_writer.End());
+
+  // Check that the const input has not been clobbered.
+  test_utils::ExpectVectorsEq(original_compressed_data, compressed_data);
+
+  brillo::Blob output;
+  EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &output));
+  EXPECT_EQ(kDecompressedLength, output.size());
+  test_utils::ExpectVectorsEq(decompressed_data, output);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/delta_performer.cc b/update_engine/payload_consumer/delta_performer.cc
new file mode 100644
index 0000000..507ad8c
--- /dev/null
+++ b/update_engine/payload_consumer/delta_performer.cc
@@ -0,0 +1,1875 @@
+//
+// 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/payload_consumer/delta_performer.h"
+
+#include <endian.h>
+#include <errno.h>
+#include <linux/fs.h>
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <applypatch/imgpatch.h>
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/data_encoding.h>
+#include <brillo/make_unique_ptr.h>
+#include <google/protobuf/repeated_field.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/subprocess.h"
+#include "update_engine/common/terminator.h"
+#include "update_engine/payload_consumer/bzip_extent_writer.h"
+#include "update_engine/payload_consumer/download_action.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+#if USE_MTD
+#include "update_engine/payload_consumer/mtd_file_descriptor.h"
+#endif
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_consumer/xz_extent_writer.h"
+
+using google::protobuf::RepeatedPtrField;
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const uint64_t DeltaPerformer::kDeltaVersionOffset = sizeof(kDeltaMagic);
+const uint64_t DeltaPerformer::kDeltaVersionSize = 8;
+const uint64_t DeltaPerformer::kDeltaManifestSizeOffset =
+    kDeltaVersionOffset + kDeltaVersionSize;
+const uint64_t DeltaPerformer::kDeltaManifestSizeSize = 8;
+const uint64_t DeltaPerformer::kDeltaMetadataSignatureSizeSize = 4;
+const uint64_t DeltaPerformer::kMaxPayloadHeaderSize = 24;
+const uint64_t DeltaPerformer::kSupportedMajorPayloadVersion = 2;
+const uint32_t DeltaPerformer::kSupportedMinorPayloadVersion = 3;
+
+const unsigned DeltaPerformer::kProgressLogMaxChunks = 10;
+const unsigned DeltaPerformer::kProgressLogTimeoutSeconds = 30;
+const unsigned DeltaPerformer::kProgressDownloadWeight = 50;
+const unsigned DeltaPerformer::kProgressOperationsWeight = 50;
+
+namespace {
+const int kUpdateStateOperationInvalid = -1;
+const int kMaxResumedUpdateFailures = 10;
+#if USE_MTD
+const int kUbiVolumeAttachTimeout = 5 * 60;
+#endif
+
+FileDescriptorPtr CreateFileDescriptor(const char* path) {
+  FileDescriptorPtr ret;
+#if USE_MTD
+  if (strstr(path, "/dev/ubi") == path) {
+    if (!UbiFileDescriptor::IsUbi(path)) {
+      // The volume might not have been attached at boot time.
+      int volume_no;
+      if (utils::SplitPartitionName(path, nullptr, &volume_no)) {
+        utils::TryAttachingUbiVolume(volume_no, kUbiVolumeAttachTimeout);
+      }
+    }
+    if (UbiFileDescriptor::IsUbi(path)) {
+      LOG(INFO) << path << " is a UBI device.";
+      ret.reset(new UbiFileDescriptor);
+    }
+  } else if (MtdFileDescriptor::IsMtd(path)) {
+    LOG(INFO) << path << " is an MTD device.";
+    ret.reset(new MtdFileDescriptor);
+  } else {
+    LOG(INFO) << path << " is not an MTD nor a UBI device.";
+#endif
+    ret.reset(new EintrSafeFileDescriptor);
+#if USE_MTD
+  }
+#endif
+  return ret;
+}
+
+// Opens path for read/write. On success returns an open FileDescriptor
+// and sets *err to 0. On failure, sets *err to errno and returns nullptr.
+FileDescriptorPtr OpenFile(const char* path, int mode, int* err) {
+  // Try to mark the block device read-only based on the mode. Ignore any
+  // failure since this won't work when passing regular files.
+  utils::SetBlockDeviceReadOnly(path, (mode & O_ACCMODE) == O_RDONLY);
+
+  FileDescriptorPtr fd = CreateFileDescriptor(path);
+#if USE_MTD
+  // On NAND devices, we can either read, or write, but not both. So here we
+  // use O_WRONLY.
+  if (UbiFileDescriptor::IsUbi(path) || MtdFileDescriptor::IsMtd(path)) {
+    mode = O_WRONLY;
+  }
+#endif
+  if (!fd->Open(path, mode, 000)) {
+    *err = errno;
+    PLOG(ERROR) << "Unable to open file " << path;
+    return nullptr;
+  }
+  *err = 0;
+  return fd;
+}
+
+// Discard the tail of the block device referenced by |fd|, from the offset
+// |data_size| until the end of the block device. Returns whether the data was
+// discarded.
+bool DiscardPartitionTail(const FileDescriptorPtr& fd, uint64_t data_size) {
+  uint64_t part_size = fd->BlockDevSize();
+  if (!part_size || part_size <= data_size)
+    return false;
+
+  struct blkioctl_request {
+    int number;
+    const char* name;
+  };
+  const vector<blkioctl_request> blkioctl_requests = {
+      {BLKSECDISCARD, "BLKSECDISCARD"},
+      {BLKDISCARD, "BLKDISCARD"},
+#ifdef BLKZEROOUT
+      {BLKZEROOUT, "BLKZEROOUT"},
+#endif
+  };
+  for (const auto& req : blkioctl_requests) {
+    int error = 0;
+    if (fd->BlkIoctl(req.number, data_size, part_size - data_size, &error) &&
+        error == 0) {
+      return true;
+    }
+    LOG(WARNING) << "Error discarding the last "
+                 << (part_size - data_size) / 1024 << " KiB using ioctl("
+                 << req.name << ")";
+  }
+  return false;
+}
+
+}  // namespace
+
+
+// Computes the ratio of |part| and |total|, scaled to |norm|, using integer
+// arithmetic.
+static uint64_t IntRatio(uint64_t part, uint64_t total, uint64_t norm) {
+  return part * norm / total;
+}
+
+void DeltaPerformer::LogProgress(const char* message_prefix) {
+  // Format operations total count and percentage.
+  string total_operations_str("?");
+  string completed_percentage_str("");
+  if (num_total_operations_) {
+    total_operations_str = std::to_string(num_total_operations_);
+    // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+    completed_percentage_str =
+        base::StringPrintf(" (%" PRIu64 "%%)",
+                           IntRatio(next_operation_num_, num_total_operations_,
+                                    100));
+  }
+
+  // Format download total count and percentage.
+  size_t payload_size = install_plan_->payload_size;
+  string payload_size_str("?");
+  string downloaded_percentage_str("");
+  if (payload_size) {
+    payload_size_str = std::to_string(payload_size);
+    // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+    downloaded_percentage_str =
+        base::StringPrintf(" (%" PRIu64 "%%)",
+                           IntRatio(total_bytes_received_, payload_size, 100));
+  }
+
+  LOG(INFO) << (message_prefix ? message_prefix : "") << next_operation_num_
+            << "/" << total_operations_str << " operations"
+            << completed_percentage_str << ", " << total_bytes_received_
+            << "/" << payload_size_str << " bytes downloaded"
+            << downloaded_percentage_str << ", overall progress "
+            << overall_progress_ << "%";
+}
+
+void DeltaPerformer::UpdateOverallProgress(bool force_log,
+                                           const char* message_prefix) {
+  // Compute our download and overall progress.
+  unsigned new_overall_progress = 0;
+  static_assert(kProgressDownloadWeight + kProgressOperationsWeight == 100,
+                "Progress weights don't add up");
+  // Only consider download progress if its total size is known; otherwise
+  // adjust the operations weight to compensate for the absence of download
+  // progress. Also, make sure to cap the download portion at
+  // kProgressDownloadWeight, in case we end up downloading more than we
+  // initially expected (this indicates a problem, but could generally happen).
+  // TODO(garnold) the correction of operations weight when we do not have the
+  // total payload size, as well as the conditional guard below, should both be
+  // eliminated once we ensure that the payload_size in the install plan is
+  // always given and is non-zero. This currently isn't the case during unit
+  // tests (see chromium-os:37969).
+  size_t payload_size = install_plan_->payload_size;
+  unsigned actual_operations_weight = kProgressOperationsWeight;
+  if (payload_size)
+    new_overall_progress += min(
+        static_cast<unsigned>(IntRatio(total_bytes_received_, payload_size,
+                                       kProgressDownloadWeight)),
+        kProgressDownloadWeight);
+  else
+    actual_operations_weight += kProgressDownloadWeight;
+
+  // Only add completed operations if their total number is known; we definitely
+  // expect an update to have at least one operation, so the expectation is that
+  // this will eventually reach |actual_operations_weight|.
+  if (num_total_operations_)
+    new_overall_progress += IntRatio(next_operation_num_, num_total_operations_,
+                                     actual_operations_weight);
+
+  // Progress ratio cannot recede, unless our assumptions about the total
+  // payload size, total number of operations, or the monotonicity of progress
+  // is breached.
+  if (new_overall_progress < overall_progress_) {
+    LOG(WARNING) << "progress counter receded from " << overall_progress_
+                 << "% down to " << new_overall_progress << "%; this is a bug";
+    force_log = true;
+  }
+  overall_progress_ = new_overall_progress;
+
+  // Update chunk index, log as needed: if forced by called, or we completed a
+  // progress chunk, or a timeout has expired.
+  base::Time curr_time = base::Time::Now();
+  unsigned curr_progress_chunk =
+      overall_progress_ * kProgressLogMaxChunks / 100;
+  if (force_log || curr_progress_chunk > last_progress_chunk_ ||
+      curr_time > forced_progress_log_time_) {
+    forced_progress_log_time_ = curr_time + forced_progress_log_wait_;
+    LogProgress(message_prefix);
+  }
+  last_progress_chunk_ = curr_progress_chunk;
+}
+
+
+size_t DeltaPerformer::CopyDataToBuffer(const char** bytes_p, size_t* count_p,
+                                        size_t max) {
+  const size_t count = *count_p;
+  if (!count)
+    return 0;  // Special case shortcut.
+  size_t read_len = min(count, max - buffer_.size());
+  const char* bytes_start = *bytes_p;
+  const char* bytes_end = bytes_start + read_len;
+  buffer_.insert(buffer_.end(), bytes_start, bytes_end);
+  *bytes_p = bytes_end;
+  *count_p = count - read_len;
+  return read_len;
+}
+
+
+bool DeltaPerformer::HandleOpResult(bool op_result, const char* op_type_name,
+                                    ErrorCode* error) {
+  if (op_result)
+    return true;
+
+  size_t partition_first_op_num =
+      current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0;
+  LOG(ERROR) << "Failed to perform " << op_type_name << " operation "
+             << next_operation_num_ << ", which is the operation "
+             << next_operation_num_ - partition_first_op_num
+             << " in partition \""
+             << partitions_[current_partition_].partition_name() << "\"";
+  if (*error == ErrorCode::kSuccess)
+    *error = ErrorCode::kDownloadOperationExecutionError;
+  return false;
+}
+
+int DeltaPerformer::Close() {
+  int err = -CloseCurrentPartition();
+  LOG_IF(ERROR, !payload_hash_calculator_.Finalize() ||
+                !signed_hash_calculator_.Finalize())
+      << "Unable to finalize the hash.";
+  if (!buffer_.empty()) {
+    LOG(INFO) << "Discarding " << buffer_.size() << " unused downloaded bytes";
+    if (err >= 0)
+      err = 1;
+  }
+  return -err;
+}
+
+int DeltaPerformer::CloseCurrentPartition() {
+  int err = 0;
+  if (source_fd_ && !source_fd_->Close()) {
+    err = errno;
+    PLOG(ERROR) << "Error closing source partition";
+    if (!err)
+      err = 1;
+  }
+  source_fd_.reset();
+  source_path_.clear();
+
+  if (target_fd_ && !target_fd_->Close()) {
+    err = errno;
+    PLOG(ERROR) << "Error closing target partition";
+    if (!err)
+      err = 1;
+  }
+  target_fd_.reset();
+  target_path_.clear();
+  return -err;
+}
+
+bool DeltaPerformer::OpenCurrentPartition() {
+  if (current_partition_ >= partitions_.size())
+    return false;
+
+  const PartitionUpdate& partition = partitions_[current_partition_];
+  // Open source fds if we have a delta payload with minor version >= 2.
+  if (install_plan_->payload_type == InstallPayloadType::kDelta &&
+      GetMinorVersion() != kInPlaceMinorPayloadVersion) {
+    source_path_ = install_plan_->partitions[current_partition_].source_path;
+    int err;
+    source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, &err);
+    if (!source_fd_) {
+      LOG(ERROR) << "Unable to open source partition "
+                 << partition.partition_name() << " on slot "
+                 << BootControlInterface::SlotName(install_plan_->source_slot)
+                 << ", file " << source_path_;
+      return false;
+    }
+  }
+
+  target_path_ = install_plan_->partitions[current_partition_].target_path;
+  int err;
+  target_fd_ = OpenFile(target_path_.c_str(), O_RDWR, &err);
+  if (!target_fd_) {
+    LOG(ERROR) << "Unable to open target partition "
+               << partition.partition_name() << " on slot "
+               << BootControlInterface::SlotName(install_plan_->target_slot)
+               << ", file " << target_path_;
+    return false;
+  }
+
+  LOG(INFO) << "Applying " << partition.operations().size()
+            << " operations to partition \"" << partition.partition_name()
+            << "\"";
+
+  // Discard the end of the partition, but ignore failures.
+  DiscardPartitionTail(
+      target_fd_, install_plan_->partitions[current_partition_].target_size);
+
+  return true;
+}
+
+namespace {
+
+void LogPartitionInfoHash(const PartitionInfo& info, const string& tag) {
+  string sha256 = brillo::data_encoding::Base64Encode(info.hash());
+  LOG(INFO) << "PartitionInfo " << tag << " sha256: " << sha256
+            << " size: " << info.size();
+}
+
+void LogPartitionInfo(const vector<PartitionUpdate>& partitions) {
+  for (const PartitionUpdate& partition : partitions) {
+    LogPartitionInfoHash(partition.old_partition_info(),
+                         "old " + partition.partition_name());
+    LogPartitionInfoHash(partition.new_partition_info(),
+                         "new " + partition.partition_name());
+  }
+}
+
+}  // namespace
+
+bool DeltaPerformer::GetMetadataSignatureSizeOffset(
+    uint64_t* out_offset) const {
+  if (GetMajorVersion() == kBrilloMajorPayloadVersion) {
+    *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize;
+    return true;
+  }
+  return false;
+}
+
+bool DeltaPerformer::GetManifestOffset(uint64_t* out_offset) const {
+  // Actual manifest begins right after the manifest size field or
+  // metadata signature size field if major version >= 2.
+  if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
+    *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize;
+    return true;
+  }
+  if (major_payload_version_ == kBrilloMajorPayloadVersion) {
+    *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize +
+                  kDeltaMetadataSignatureSizeSize;
+    return true;
+  }
+  LOG(ERROR) << "Unknown major payload version: " << major_payload_version_;
+  return false;
+}
+
+uint64_t DeltaPerformer::GetMetadataSize() const {
+  return metadata_size_;
+}
+
+uint64_t DeltaPerformer::GetMajorVersion() const {
+  return major_payload_version_;
+}
+
+uint32_t DeltaPerformer::GetMinorVersion() const {
+  if (manifest_.has_minor_version()) {
+    return manifest_.minor_version();
+  } else {
+    return install_plan_->payload_type == InstallPayloadType::kDelta
+               ? kSupportedMinorPayloadVersion
+               : kFullPayloadMinorVersion;
+  }
+}
+
+bool DeltaPerformer::GetManifest(DeltaArchiveManifest* out_manifest_p) const {
+  if (!manifest_parsed_)
+    return false;
+  *out_manifest_p = manifest_;
+  return true;
+}
+
+bool DeltaPerformer::IsHeaderParsed() const {
+  return metadata_size_ != 0;
+}
+
+DeltaPerformer::MetadataParseResult DeltaPerformer::ParsePayloadMetadata(
+    const brillo::Blob& payload, ErrorCode* error) {
+  *error = ErrorCode::kSuccess;
+  uint64_t manifest_offset;
+
+  if (!IsHeaderParsed()) {
+    // Ensure we have data to cover the major payload version.
+    if (payload.size() < kDeltaManifestSizeOffset)
+      return kMetadataParseInsufficientData;
+
+    // Validate the magic string.
+    if (memcmp(payload.data(), kDeltaMagic, sizeof(kDeltaMagic)) != 0) {
+      LOG(ERROR) << "Bad payload format -- invalid delta magic.";
+      *error = ErrorCode::kDownloadInvalidMetadataMagicString;
+      return kMetadataParseError;
+    }
+
+    // Extract the payload version from the metadata.
+    static_assert(sizeof(major_payload_version_) == kDeltaVersionSize,
+                  "Major payload version size mismatch");
+    memcpy(&major_payload_version_,
+           &payload[kDeltaVersionOffset],
+           kDeltaVersionSize);
+    // switch big endian to host
+    major_payload_version_ = be64toh(major_payload_version_);
+
+    if (major_payload_version_ != supported_major_version_ &&
+        major_payload_version_ != kChromeOSMajorPayloadVersion) {
+      LOG(ERROR) << "Bad payload format -- unsupported payload version: "
+          << major_payload_version_;
+      *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+      return kMetadataParseError;
+    }
+
+    // Get the manifest offset now that we have payload version.
+    if (!GetManifestOffset(&manifest_offset)) {
+      *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+      return kMetadataParseError;
+    }
+    // Check again with the manifest offset.
+    if (payload.size() < manifest_offset)
+      return kMetadataParseInsufficientData;
+
+    // Next, parse the manifest size.
+    static_assert(sizeof(manifest_size_) == kDeltaManifestSizeSize,
+                  "manifest_size size mismatch");
+    memcpy(&manifest_size_,
+           &payload[kDeltaManifestSizeOffset],
+           kDeltaManifestSizeSize);
+    manifest_size_ = be64toh(manifest_size_);  // switch big endian to host
+
+    if (GetMajorVersion() == kBrilloMajorPayloadVersion) {
+      // Parse the metadata signature size.
+      static_assert(sizeof(metadata_signature_size_) ==
+                    kDeltaMetadataSignatureSizeSize,
+                    "metadata_signature_size size mismatch");
+      uint64_t metadata_signature_size_offset;
+      if (!GetMetadataSignatureSizeOffset(&metadata_signature_size_offset)) {
+        *error = ErrorCode::kError;
+        return kMetadataParseError;
+      }
+      memcpy(&metadata_signature_size_,
+             &payload[metadata_signature_size_offset],
+             kDeltaMetadataSignatureSizeSize);
+      metadata_signature_size_ = be32toh(metadata_signature_size_);
+    }
+
+    // If the metadata size is present in install plan, check for it immediately
+    // even before waiting for that many number of bytes to be downloaded in the
+    // payload. This will prevent any attack which relies on us downloading data
+    // beyond the expected metadata size.
+    metadata_size_ = manifest_offset + manifest_size_;
+    if (install_plan_->hash_checks_mandatory) {
+      if (install_plan_->metadata_size != metadata_size_) {
+        LOG(ERROR) << "Mandatory metadata size in Omaha response ("
+                   << install_plan_->metadata_size
+                   << ") is missing/incorrect, actual = " << metadata_size_;
+        *error = ErrorCode::kDownloadInvalidMetadataSize;
+        return kMetadataParseError;
+      }
+    }
+  }
+
+  // Now that we have validated the metadata size, we should wait for the full
+  // metadata and its signature (if exist) to be read in before we can parse it.
+  if (payload.size() < metadata_size_ + metadata_signature_size_)
+    return kMetadataParseInsufficientData;
+
+  // Log whether we validated the size or simply trusting what's in the payload
+  // here. This is logged here (after we received the full metadata data) so
+  // that we just log once (instead of logging n times) if it takes n
+  // DeltaPerformer::Write calls to download the full manifest.
+  if (install_plan_->metadata_size == metadata_size_) {
+    LOG(INFO) << "Manifest size in payload matches expected value from Omaha";
+  } else {
+    // For mandatory-cases, we'd have already returned a kMetadataParseError
+    // above. We'll be here only for non-mandatory cases. Just send a UMA stat.
+    LOG(WARNING) << "Ignoring missing/incorrect metadata size ("
+                 << install_plan_->metadata_size
+                 << ") in Omaha response as validation is not mandatory. "
+                 << "Trusting metadata size in payload = " << metadata_size_;
+  }
+
+  // We have the full metadata in |payload|. Verify its integrity
+  // and authenticity based on the information we have in Omaha response.
+  *error = ValidateMetadataSignature(payload);
+  if (*error != ErrorCode::kSuccess) {
+    if (install_plan_->hash_checks_mandatory) {
+      // The autoupdate_CatchBadSignatures test checks for this string
+      // in log-files. Keep in sync.
+      LOG(ERROR) << "Mandatory metadata signature validation failed";
+      return kMetadataParseError;
+    }
+
+    // For non-mandatory cases, just send a UMA stat.
+    LOG(WARNING) << "Ignoring metadata signature validation failures";
+    *error = ErrorCode::kSuccess;
+  }
+
+  if (!GetManifestOffset(&manifest_offset)) {
+    *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+    return kMetadataParseError;
+  }
+  // The payload metadata is deemed valid, it's safe to parse the protobuf.
+  if (!manifest_.ParseFromArray(&payload[manifest_offset], manifest_size_)) {
+    LOG(ERROR) << "Unable to parse manifest in update file.";
+    *error = ErrorCode::kDownloadManifestParseError;
+    return kMetadataParseError;
+  }
+
+  manifest_parsed_ = true;
+  return kMetadataParseSuccess;
+}
+
+// Wrapper around write. Returns true if all requested bytes
+// were written, or false on any error, regardless of progress
+// and stores an action exit code in |error|.
+bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode *error) {
+  *error = ErrorCode::kSuccess;
+
+  const char* c_bytes = reinterpret_cast<const char*>(bytes);
+
+  // Update the total byte downloaded count and the progress logs.
+  total_bytes_received_ += count;
+  UpdateOverallProgress(false, "Completed ");
+
+  while (!manifest_valid_) {
+    // Read data up to the needed limit; this is either maximium payload header
+    // size, or the full metadata size (once it becomes known).
+    const bool do_read_header = !IsHeaderParsed();
+    CopyDataToBuffer(&c_bytes, &count,
+                     (do_read_header ? kMaxPayloadHeaderSize :
+                      metadata_size_ + metadata_signature_size_));
+
+    MetadataParseResult result = ParsePayloadMetadata(buffer_, error);
+    if (result == kMetadataParseError)
+      return false;
+    if (result == kMetadataParseInsufficientData) {
+      // If we just processed the header, make an attempt on the manifest.
+      if (do_read_header && IsHeaderParsed())
+        continue;
+
+      return true;
+    }
+
+    // Checks the integrity of the payload manifest.
+    if ((*error = ValidateManifest()) != ErrorCode::kSuccess)
+      return false;
+    manifest_valid_ = true;
+
+    // Clear the download buffer.
+    DiscardBuffer(false, metadata_size_);
+
+    // This populates |partitions_| and the |install_plan.partitions| with the
+    // list of partitions from the manifest.
+    if (!ParseManifestPartitions(error))
+      return false;
+
+    num_total_operations_ = 0;
+    for (const auto& partition : partitions_) {
+      num_total_operations_ += partition.operations_size();
+      acc_num_operations_.push_back(num_total_operations_);
+    }
+
+    LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestMetadataSize,
+                                      metadata_size_))
+        << "Unable to save the manifest metadata size.";
+    LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestSignatureSize,
+                                      metadata_signature_size_))
+        << "Unable to save the manifest signature size.";
+
+    if (!PrimeUpdateState()) {
+      *error = ErrorCode::kDownloadStateInitializationError;
+      LOG(ERROR) << "Unable to prime the update state.";
+      return false;
+    }
+
+    if (!OpenCurrentPartition()) {
+      *error = ErrorCode::kInstallDeviceOpenError;
+      return false;
+    }
+
+    if (next_operation_num_ > 0)
+      UpdateOverallProgress(true, "Resuming after ");
+    LOG(INFO) << "Starting to apply update payload operations";
+  }
+
+  while (next_operation_num_ < num_total_operations_) {
+    // Check if we should cancel the current attempt for any reason.
+    // In this case, *error will have already been populated with the reason
+    // why we're canceling.
+    if (download_delegate_ && download_delegate_->ShouldCancel(error))
+      return false;
+
+    // We know there are more operations to perform because we didn't reach the
+    // |num_total_operations_| limit yet.
+    while (next_operation_num_ >= acc_num_operations_[current_partition_]) {
+      CloseCurrentPartition();
+      current_partition_++;
+      if (!OpenCurrentPartition()) {
+        *error = ErrorCode::kInstallDeviceOpenError;
+        return false;
+      }
+    }
+    const size_t partition_operation_num = next_operation_num_ - (
+        current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0);
+
+    const InstallOperation& op =
+        partitions_[current_partition_].operations(partition_operation_num);
+
+    CopyDataToBuffer(&c_bytes, &count, op.data_length());
+
+    // Check whether we received all of the next operation's data payload.
+    if (!CanPerformInstallOperation(op))
+      return true;
+
+    // Validate the operation only if the metadata signature is present.
+    // Otherwise, keep the old behavior. This serves as a knob to disable
+    // the validation logic in case we find some regression after rollout.
+    // NOTE: If hash checks are mandatory and if metadata_signature is empty,
+    // we would have already failed in ParsePayloadMetadata method and thus not
+    // even be here. So no need to handle that case again here.
+    if (!install_plan_->metadata_signature.empty()) {
+      // Note: Validate must be called only if CanPerformInstallOperation is
+      // called. Otherwise, we might be failing operations before even if there
+      // isn't sufficient data to compute the proper hash.
+      *error = ValidateOperationHash(op);
+      if (*error != ErrorCode::kSuccess) {
+        if (install_plan_->hash_checks_mandatory) {
+          LOG(ERROR) << "Mandatory operation hash check failed";
+          return false;
+        }
+
+        // For non-mandatory cases, just send a UMA stat.
+        LOG(WARNING) << "Ignoring operation validation errors";
+        *error = ErrorCode::kSuccess;
+      }
+    }
+
+    // Makes sure we unblock exit when this operation completes.
+    ScopedTerminatorExitUnblocker exit_unblocker =
+        ScopedTerminatorExitUnblocker();  // Avoids a compiler unused var bug.
+
+    bool op_result;
+    switch (op.type()) {
+      case InstallOperation::REPLACE:
+      case InstallOperation::REPLACE_BZ:
+      case InstallOperation::REPLACE_XZ:
+        op_result = PerformReplaceOperation(op);
+        break;
+      case InstallOperation::ZERO:
+      case InstallOperation::DISCARD:
+        op_result = PerformZeroOrDiscardOperation(op);
+        break;
+      case InstallOperation::MOVE:
+        op_result = PerformMoveOperation(op);
+        break;
+      case InstallOperation::BSDIFF:
+        op_result = PerformBsdiffOperation(op);
+        break;
+      case InstallOperation::SOURCE_COPY:
+        op_result = PerformSourceCopyOperation(op, error);
+        break;
+      case InstallOperation::SOURCE_BSDIFF:
+        op_result = PerformSourceBsdiffOperation(op, error);
+        break;
+      case InstallOperation::IMGDIFF:
+        op_result = PerformImgdiffOperation(op, error);
+        break;
+      default:
+       op_result = false;
+    }
+    if (!HandleOpResult(op_result, InstallOperationTypeName(op.type()), error))
+      return false;
+
+    next_operation_num_++;
+    UpdateOverallProgress(false, "Completed ");
+    CheckpointUpdateProgress();
+  }
+
+  // In major version 2, we don't add dummy operation to the payload.
+  // If we already extracted the signature we should skip this step.
+  if (major_payload_version_ == kBrilloMajorPayloadVersion &&
+      manifest_.has_signatures_offset() && manifest_.has_signatures_size() &&
+      signatures_message_data_.empty()) {
+    if (manifest_.signatures_offset() != buffer_offset_) {
+      LOG(ERROR) << "Payload signatures offset points to blob offset "
+                 << manifest_.signatures_offset()
+                 << " but signatures are expected at offset "
+                 << buffer_offset_;
+      *error = ErrorCode::kDownloadPayloadVerificationError;
+      return false;
+    }
+    CopyDataToBuffer(&c_bytes, &count, manifest_.signatures_size());
+    // Needs more data to cover entire signature.
+    if (buffer_.size() < manifest_.signatures_size())
+      return true;
+    if (!ExtractSignatureMessage()) {
+      LOG(ERROR) << "Extract payload signature failed.";
+      *error = ErrorCode::kDownloadPayloadVerificationError;
+      return false;
+    }
+    DiscardBuffer(true, 0);
+    // Since we extracted the SignatureMessage we need to advance the
+    // checkpoint, otherwise we would reload the signature and try to extract
+    // it again.
+    CheckpointUpdateProgress();
+  }
+
+  return true;
+}
+
+bool DeltaPerformer::IsManifestValid() {
+  return manifest_valid_;
+}
+
+bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) {
+  if (major_payload_version_ == kBrilloMajorPayloadVersion) {
+    partitions_.clear();
+    for (const PartitionUpdate& partition : manifest_.partitions()) {
+      partitions_.push_back(partition);
+    }
+    manifest_.clear_partitions();
+  } else if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
+    LOG(INFO) << "Converting update information from old format.";
+    PartitionUpdate root_part;
+    root_part.set_partition_name(kLegacyPartitionNameRoot);
+#ifdef __ANDROID__
+    LOG(WARNING) << "Legacy payload major version provided to an Android "
+                    "build. Assuming no post-install. Please use major version "
+                    "2 or newer.";
+    root_part.set_run_postinstall(false);
+#else
+    root_part.set_run_postinstall(true);
+#endif  // __ANDROID__
+    if (manifest_.has_old_rootfs_info()) {
+      *root_part.mutable_old_partition_info() = manifest_.old_rootfs_info();
+      manifest_.clear_old_rootfs_info();
+    }
+    if (manifest_.has_new_rootfs_info()) {
+      *root_part.mutable_new_partition_info() = manifest_.new_rootfs_info();
+      manifest_.clear_new_rootfs_info();
+    }
+    *root_part.mutable_operations() = manifest_.install_operations();
+    manifest_.clear_install_operations();
+    partitions_.push_back(std::move(root_part));
+
+    PartitionUpdate kern_part;
+    kern_part.set_partition_name(kLegacyPartitionNameKernel);
+    kern_part.set_run_postinstall(false);
+    if (manifest_.has_old_kernel_info()) {
+      *kern_part.mutable_old_partition_info() = manifest_.old_kernel_info();
+      manifest_.clear_old_kernel_info();
+    }
+    if (manifest_.has_new_kernel_info()) {
+      *kern_part.mutable_new_partition_info() = manifest_.new_kernel_info();
+      manifest_.clear_new_kernel_info();
+    }
+    *kern_part.mutable_operations() = manifest_.kernel_install_operations();
+    manifest_.clear_kernel_install_operations();
+    partitions_.push_back(std::move(kern_part));
+  }
+
+  // Fill in the InstallPlan::partitions based on the partitions from the
+  // payload.
+  install_plan_->partitions.clear();
+  for (const auto& partition : partitions_) {
+    InstallPlan::Partition install_part;
+    install_part.name = partition.partition_name();
+    install_part.run_postinstall =
+        partition.has_run_postinstall() && partition.run_postinstall();
+    if (install_part.run_postinstall) {
+      install_part.postinstall_path =
+          (partition.has_postinstall_path() ? partition.postinstall_path()
+                                            : kPostinstallDefaultScript);
+      install_part.filesystem_type = partition.filesystem_type();
+      install_part.postinstall_optional = partition.postinstall_optional();
+    }
+
+    if (partition.has_old_partition_info()) {
+      const PartitionInfo& info = partition.old_partition_info();
+      install_part.source_size = info.size();
+      install_part.source_hash.assign(info.hash().begin(), info.hash().end());
+    }
+
+    if (!partition.has_new_partition_info()) {
+      LOG(ERROR) << "Unable to get new partition hash info on partition "
+                 << install_part.name << ".";
+      *error = ErrorCode::kDownloadNewPartitionInfoError;
+      return false;
+    }
+    const PartitionInfo& info = partition.new_partition_info();
+    install_part.target_size = info.size();
+    install_part.target_hash.assign(info.hash().begin(), info.hash().end());
+
+    install_plan_->partitions.push_back(install_part);
+  }
+
+  if (!install_plan_->LoadPartitionsFromSlots(boot_control_)) {
+    LOG(ERROR) << "Unable to determine all the partition devices.";
+    *error = ErrorCode::kInstallDeviceOpenError;
+    return false;
+  }
+  LogPartitionInfo(partitions_);
+  return true;
+}
+
+bool DeltaPerformer::CanPerformInstallOperation(
+    const chromeos_update_engine::InstallOperation& operation) {
+  // If we don't have a data blob we can apply it right away.
+  if (!operation.has_data_offset() && !operation.has_data_length())
+    return true;
+
+  // See if we have the entire data blob in the buffer
+  if (operation.data_offset() < buffer_offset_) {
+    LOG(ERROR) << "we threw away data it seems?";
+    return false;
+  }
+
+  return (operation.data_offset() + operation.data_length() <=
+          buffer_offset_ + buffer_.size());
+}
+
+bool DeltaPerformer::PerformReplaceOperation(
+    const InstallOperation& operation) {
+  CHECK(operation.type() == InstallOperation::REPLACE ||
+        operation.type() == InstallOperation::REPLACE_BZ ||
+        operation.type() == InstallOperation::REPLACE_XZ);
+
+  // Since we delete data off the beginning of the buffer as we use it,
+  // the data we need should be exactly at the beginning of the buffer.
+  TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+  TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+
+  // Extract the signature message if it's in this operation.
+  if (ExtractSignatureMessageFromOperation(operation)) {
+    // If this is dummy replace operation, we ignore it after extracting the
+    // signature.
+    DiscardBuffer(true, 0);
+    return true;
+  }
+
+  // Setup the ExtentWriter stack based on the operation type.
+  std::unique_ptr<ExtentWriter> writer =
+    brillo::make_unique_ptr(new ZeroPadExtentWriter(
+      brillo::make_unique_ptr(new DirectExtentWriter())));
+
+  if (operation.type() == InstallOperation::REPLACE_BZ) {
+    writer.reset(new BzipExtentWriter(std::move(writer)));
+  } else if (operation.type() == InstallOperation::REPLACE_XZ) {
+    writer.reset(new XzExtentWriter(std::move(writer)));
+  }
+
+  // Create a vector of extents to pass to the ExtentWriter.
+  vector<Extent> extents;
+  for (int i = 0; i < operation.dst_extents_size(); i++) {
+    extents.push_back(operation.dst_extents(i));
+  }
+
+  TEST_AND_RETURN_FALSE(writer->Init(target_fd_, extents, block_size_));
+  TEST_AND_RETURN_FALSE(writer->Write(buffer_.data(), operation.data_length()));
+  TEST_AND_RETURN_FALSE(writer->End());
+
+  // Update buffer
+  DiscardBuffer(true, buffer_.size());
+  return true;
+}
+
+bool DeltaPerformer::PerformZeroOrDiscardOperation(
+    const InstallOperation& operation) {
+  CHECK(operation.type() == InstallOperation::DISCARD ||
+        operation.type() == InstallOperation::ZERO);
+
+  // These operations have no blob.
+  TEST_AND_RETURN_FALSE(!operation.has_data_offset());
+  TEST_AND_RETURN_FALSE(!operation.has_data_length());
+
+#ifdef BLKZEROOUT
+  bool attempt_ioctl = true;
+  int request =
+      (operation.type() == InstallOperation::ZERO ? BLKZEROOUT : BLKDISCARD);
+#else  // !defined(BLKZEROOUT)
+  bool attempt_ioctl = false;
+  int request = 0;
+#endif  // !defined(BLKZEROOUT)
+
+  brillo::Blob zeros;
+  for (const Extent& extent : operation.dst_extents()) {
+    const uint64_t start = extent.start_block() * block_size_;
+    const uint64_t length = extent.num_blocks() * block_size_;
+    if (attempt_ioctl) {
+      int result = 0;
+      if (target_fd_->BlkIoctl(request, start, length, &result) && result == 0)
+        continue;
+      attempt_ioctl = false;
+      zeros.resize(16 * block_size_);
+    }
+    // In case of failure, we fall back to writing 0 to the selected region.
+    for (uint64_t offset = 0; offset < length; offset += zeros.size()) {
+      uint64_t chunk_length = min(length - offset,
+                                  static_cast<uint64_t>(zeros.size()));
+      TEST_AND_RETURN_FALSE(
+          utils::PWriteAll(target_fd_, zeros.data(), chunk_length, start + offset));
+    }
+  }
+  return true;
+}
+
+bool DeltaPerformer::PerformMoveOperation(const InstallOperation& operation) {
+  // Calculate buffer size. Note, this function doesn't do a sliding
+  // window to copy in case the source and destination blocks overlap.
+  // If we wanted to do a sliding window, we could program the server
+  // to generate deltas that effectively did a sliding window.
+
+  uint64_t blocks_to_read = 0;
+  for (int i = 0; i < operation.src_extents_size(); i++)
+    blocks_to_read += operation.src_extents(i).num_blocks();
+
+  uint64_t blocks_to_write = 0;
+  for (int i = 0; i < operation.dst_extents_size(); i++)
+    blocks_to_write += operation.dst_extents(i).num_blocks();
+
+  DCHECK_EQ(blocks_to_write, blocks_to_read);
+  brillo::Blob buf(blocks_to_write * block_size_);
+
+  // Read in bytes.
+  ssize_t bytes_read = 0;
+  for (int i = 0; i < operation.src_extents_size(); i++) {
+    ssize_t bytes_read_this_iteration = 0;
+    const Extent& extent = operation.src_extents(i);
+    const size_t bytes = extent.num_blocks() * block_size_;
+    TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
+    TEST_AND_RETURN_FALSE(utils::PReadAll(target_fd_,
+                                          &buf[bytes_read],
+                                          bytes,
+                                          extent.start_block() * block_size_,
+                                          &bytes_read_this_iteration));
+    TEST_AND_RETURN_FALSE(
+        bytes_read_this_iteration == static_cast<ssize_t>(bytes));
+    bytes_read += bytes_read_this_iteration;
+  }
+
+  // Write bytes out.
+  ssize_t bytes_written = 0;
+  for (int i = 0; i < operation.dst_extents_size(); i++) {
+    const Extent& extent = operation.dst_extents(i);
+    const size_t bytes = extent.num_blocks() * block_size_;
+    TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
+    TEST_AND_RETURN_FALSE(utils::PWriteAll(target_fd_,
+                                           &buf[bytes_written],
+                                           bytes,
+                                           extent.start_block() * block_size_));
+    bytes_written += bytes;
+  }
+  DCHECK_EQ(bytes_written, bytes_read);
+  DCHECK_EQ(bytes_written, static_cast<ssize_t>(buf.size()));
+  return true;
+}
+
+namespace {
+
+// Takes |extents| and fills an empty vector |blocks| with a block index for
+// each block in |extents|. For example, [(3, 2), (8, 1)] would give [3, 4, 8].
+void ExtentsToBlocks(const RepeatedPtrField<Extent>& extents,
+                     vector<uint64_t>* blocks) {
+  for (const Extent& ext : extents) {
+    for (uint64_t j = 0; j < ext.num_blocks(); j++)
+      blocks->push_back(ext.start_block() + j);
+  }
+}
+
+// Takes |extents| and returns the number of blocks in those extents.
+uint64_t GetBlockCount(const RepeatedPtrField<Extent>& extents) {
+  uint64_t sum = 0;
+  for (const Extent& ext : extents) {
+    sum += ext.num_blocks();
+  }
+  return sum;
+}
+
+// Compare |calculated_hash| with source hash in |operation|, return false and
+// dump hash and set |error| if don't match.
+bool ValidateSourceHash(const brillo::Blob& calculated_hash,
+                        const InstallOperation& operation,
+                        ErrorCode* error) {
+  brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
+                                    operation.src_sha256_hash().end());
+  if (calculated_hash != expected_source_hash) {
+    LOG(ERROR) << "The hash of the source data on disk for this operation "
+               << "doesn't match the expected value. This could mean that the "
+               << "delta update payload was targeted for another version, or "
+               << "that the source partition was modified after it was "
+               << "installed, for example, by mounting a filesystem.";
+    LOG(ERROR) << "Expected:   sha256|hex = "
+               << base::HexEncode(expected_source_hash.data(),
+                                  expected_source_hash.size());
+    LOG(ERROR) << "Calculated: sha256|hex = "
+               << base::HexEncode(calculated_hash.data(),
+                                  calculated_hash.size());
+
+    vector<string> source_extents;
+    for (const Extent& ext : operation.src_extents()) {
+      source_extents.push_back(
+          base::StringPrintf("%" PRIu64 ":%" PRIu64,
+                             static_cast<uint64_t>(ext.start_block()),
+                             static_cast<uint64_t>(ext.num_blocks())));
+    }
+    LOG(ERROR) << "Operation source (offset:size) in blocks: "
+               << base::JoinString(source_extents, ",");
+
+    *error = ErrorCode::kDownloadStateInitializationError;
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+bool DeltaPerformer::PerformSourceCopyOperation(
+    const InstallOperation& operation, ErrorCode* error) {
+  if (operation.has_src_length())
+    TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0);
+  if (operation.has_dst_length())
+    TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
+
+  uint64_t blocks_to_read = GetBlockCount(operation.src_extents());
+  uint64_t blocks_to_write = GetBlockCount(operation.dst_extents());
+  TEST_AND_RETURN_FALSE(blocks_to_write ==  blocks_to_read);
+
+  // Create vectors of all the individual src/dst blocks.
+  vector<uint64_t> src_blocks;
+  vector<uint64_t> dst_blocks;
+  ExtentsToBlocks(operation.src_extents(), &src_blocks);
+  ExtentsToBlocks(operation.dst_extents(), &dst_blocks);
+  DCHECK_EQ(src_blocks.size(), blocks_to_read);
+  DCHECK_EQ(src_blocks.size(), dst_blocks.size());
+
+  brillo::Blob buf(block_size_);
+  ssize_t bytes_read = 0;
+  HashCalculator source_hasher;
+  // Read/write one block at a time.
+  for (uint64_t i = 0; i < blocks_to_read; i++) {
+    ssize_t bytes_read_this_iteration = 0;
+    uint64_t src_block = src_blocks[i];
+    uint64_t dst_block = dst_blocks[i];
+
+    // Read in bytes.
+    TEST_AND_RETURN_FALSE(
+        utils::PReadAll(source_fd_,
+                        buf.data(),
+                        block_size_,
+                        src_block * block_size_,
+                        &bytes_read_this_iteration));
+
+    // Write bytes out.
+    TEST_AND_RETURN_FALSE(
+        utils::PWriteAll(target_fd_,
+                         buf.data(),
+                         block_size_,
+                         dst_block * block_size_));
+
+    bytes_read += bytes_read_this_iteration;
+    TEST_AND_RETURN_FALSE(bytes_read_this_iteration ==
+                          static_cast<ssize_t>(block_size_));
+
+    if (operation.has_src_sha256_hash())
+      TEST_AND_RETURN_FALSE(source_hasher.Update(buf.data(), buf.size()));
+  }
+
+  if (operation.has_src_sha256_hash()) {
+    TEST_AND_RETURN_FALSE(source_hasher.Finalize());
+    TEST_AND_RETURN_FALSE(
+        ValidateSourceHash(source_hasher.raw_hash(), operation, error));
+  }
+
+  DCHECK_EQ(bytes_read, static_cast<ssize_t>(blocks_to_read * block_size_));
+  return true;
+}
+
+bool DeltaPerformer::ExtentsToBsdiffPositionsString(
+    const RepeatedPtrField<Extent>& extents,
+    uint64_t block_size,
+    uint64_t full_length,
+    string* positions_string) {
+  string ret;
+  uint64_t length = 0;
+  for (const Extent& extent : extents) {
+    int64_t start = extent.start_block() * block_size;
+    uint64_t this_length =
+        min(full_length - length,
+            static_cast<uint64_t>(extent.num_blocks()) * block_size);
+    ret += base::StringPrintf("%" PRIi64 ":%" PRIu64 ",", start, this_length);
+    length += this_length;
+  }
+  TEST_AND_RETURN_FALSE(length == full_length);
+  if (!ret.empty())
+    ret.resize(ret.size() - 1);  // Strip trailing comma off
+  *positions_string = ret;
+  return true;
+}
+
+bool DeltaPerformer::PerformBsdiffOperation(const InstallOperation& operation) {
+  // Since we delete data off the beginning of the buffer as we use it,
+  // the data we need should be exactly at the beginning of the buffer.
+  TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+  TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+
+  string input_positions;
+  TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
+                                                       block_size_,
+                                                       operation.src_length(),
+                                                       &input_positions));
+  string output_positions;
+  TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(),
+                                                       block_size_,
+                                                       operation.dst_length(),
+                                                       &output_positions));
+
+  string temp_filename;
+  TEST_AND_RETURN_FALSE(utils::MakeTempFile("au_patch.XXXXXX",
+                                            &temp_filename,
+                                            nullptr));
+  ScopedPathUnlinker path_unlinker(temp_filename);
+  {
+    int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+    ScopedFdCloser fd_closer(&fd);
+    TEST_AND_RETURN_FALSE(
+        utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+  }
+
+  // Update the buffer to release the patch data memory as soon as the patch
+  // file is written out.
+  DiscardBuffer(true, buffer_.size());
+
+  vector<string> cmd{kBspatchPath, target_path_, target_path_, temp_filename,
+                     input_positions, output_positions};
+
+  int return_code = 0;
+  TEST_AND_RETURN_FALSE(
+      Subprocess::SynchronousExecFlags(cmd, Subprocess::kSearchPath,
+                                       &return_code, nullptr));
+  TEST_AND_RETURN_FALSE(return_code == 0);
+
+  if (operation.dst_length() % block_size_) {
+    // Zero out rest of final block.
+    // TODO(adlr): build this into bspatch; it's more efficient that way.
+    const Extent& last_extent =
+        operation.dst_extents(operation.dst_extents_size() - 1);
+    const uint64_t end_byte =
+        (last_extent.start_block() + last_extent.num_blocks()) * block_size_;
+    const uint64_t begin_byte =
+        end_byte - (block_size_ - operation.dst_length() % block_size_);
+    brillo::Blob zeros(end_byte - begin_byte);
+    TEST_AND_RETURN_FALSE(
+        utils::PWriteAll(target_fd_, zeros.data(), end_byte - begin_byte, begin_byte));
+  }
+  return true;
+}
+
+bool DeltaPerformer::PerformSourceBsdiffOperation(
+    const InstallOperation& operation, ErrorCode* error) {
+  // Since we delete data off the beginning of the buffer as we use it,
+  // the data we need should be exactly at the beginning of the buffer.
+  TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+  TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+  if (operation.has_src_length())
+    TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0);
+  if (operation.has_dst_length())
+    TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
+
+  if (operation.has_src_sha256_hash()) {
+    HashCalculator source_hasher;
+    const uint64_t kMaxBlocksToRead = 512;  // 2MB if block size is 4KB
+    brillo::Blob buf(kMaxBlocksToRead * block_size_);
+    for (const Extent& extent : operation.src_extents()) {
+      for (uint64_t i = 0; i < extent.num_blocks(); i += kMaxBlocksToRead) {
+        uint64_t blocks_to_read = min(
+            kMaxBlocksToRead, static_cast<uint64_t>(extent.num_blocks()) - i);
+        ssize_t bytes_to_read = blocks_to_read * block_size_;
+        ssize_t bytes_read_this_iteration = 0;
+        TEST_AND_RETURN_FALSE(
+            utils::PReadAll(source_fd_, buf.data(), bytes_to_read,
+                            (extent.start_block() + i) * block_size_,
+                            &bytes_read_this_iteration));
+        TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes_to_read);
+        TEST_AND_RETURN_FALSE(source_hasher.Update(buf.data(), bytes_to_read));
+      }
+    }
+    TEST_AND_RETURN_FALSE(source_hasher.Finalize());
+    TEST_AND_RETURN_FALSE(
+        ValidateSourceHash(source_hasher.raw_hash(), operation, error));
+  }
+
+  string input_positions;
+  TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
+                                                       block_size_,
+                                                       operation.src_length(),
+                                                       &input_positions));
+  string output_positions;
+  TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(),
+                                                       block_size_,
+                                                       operation.dst_length(),
+                                                       &output_positions));
+
+  string temp_filename;
+  TEST_AND_RETURN_FALSE(utils::MakeTempFile("au_patch.XXXXXX",
+                                            &temp_filename,
+                                            nullptr));
+  ScopedPathUnlinker path_unlinker(temp_filename);
+  {
+    int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+    ScopedFdCloser fd_closer(&fd);
+    TEST_AND_RETURN_FALSE(
+        utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+  }
+
+  // Update the buffer to release the patch data memory as soon as the patch
+  // file is written out.
+  DiscardBuffer(true, buffer_.size());
+
+  vector<string> cmd{kBspatchPath, source_path_, target_path_, temp_filename,
+                     input_positions, output_positions};
+
+  int return_code = 0;
+  TEST_AND_RETURN_FALSE(
+      Subprocess::SynchronousExecFlags(cmd, Subprocess::kSearchPath,
+                                       &return_code, nullptr));
+  TEST_AND_RETURN_FALSE(return_code == 0);
+  return true;
+}
+
+bool DeltaPerformer::PerformImgdiffOperation(const InstallOperation& operation,
+                                             ErrorCode* error) {
+  // Since we delete data off the beginning of the buffer as we use it,
+  // the data we need should be exactly at the beginning of the buffer.
+  TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+  TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+
+  uint64_t src_blocks = GetBlockCount(operation.src_extents());
+  brillo::Blob src_data(src_blocks * block_size_);
+
+  ssize_t bytes_read = 0;
+  for (const Extent& extent : operation.src_extents()) {
+    ssize_t bytes_read_this_iteration = 0;
+    ssize_t bytes_to_read = extent.num_blocks() * block_size_;
+    TEST_AND_RETURN_FALSE(utils::PReadAll(source_fd_,
+                                          &src_data[bytes_read],
+                                          bytes_to_read,
+                                          extent.start_block() * block_size_,
+                                          &bytes_read_this_iteration));
+    TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes_to_read);
+    bytes_read += bytes_read_this_iteration;
+  }
+
+  if (operation.has_src_sha256_hash()) {
+    brillo::Blob src_hash;
+    TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfData(src_data, &src_hash));
+    TEST_AND_RETURN_FALSE(ValidateSourceHash(src_hash, operation, error));
+  }
+
+  vector<Extent> target_extents(operation.dst_extents().begin(),
+                                operation.dst_extents().end());
+  DirectExtentWriter writer;
+  TEST_AND_RETURN_FALSE(writer.Init(target_fd_, target_extents, block_size_));
+  TEST_AND_RETURN_FALSE(
+      ApplyImagePatch(src_data.data(),
+                      src_data.size(),
+                      buffer_.data(),
+                      operation.data_length(),
+                      [](const unsigned char* data, ssize_t len, void* token) {
+                        return reinterpret_cast<ExtentWriter*>(token)
+                                       ->Write(data, len)
+                                   ? len
+                                   : 0;
+                      },
+                      &writer) == 0);
+  TEST_AND_RETURN_FALSE(writer.End());
+
+  DiscardBuffer(true, buffer_.size());
+  return true;
+}
+
+bool DeltaPerformer::ExtractSignatureMessageFromOperation(
+    const InstallOperation& operation) {
+  if (operation.type() != InstallOperation::REPLACE ||
+      !manifest_.has_signatures_offset() ||
+      manifest_.signatures_offset() != operation.data_offset()) {
+    return false;
+  }
+  TEST_AND_RETURN_FALSE(manifest_.has_signatures_size() &&
+                        manifest_.signatures_size() == operation.data_length());
+  TEST_AND_RETURN_FALSE(ExtractSignatureMessage());
+  return true;
+}
+
+bool DeltaPerformer::ExtractSignatureMessage() {
+  TEST_AND_RETURN_FALSE(signatures_message_data_.empty());
+  TEST_AND_RETURN_FALSE(buffer_offset_ == manifest_.signatures_offset());
+  TEST_AND_RETURN_FALSE(buffer_.size() >= manifest_.signatures_size());
+  signatures_message_data_.assign(
+      buffer_.begin(),
+      buffer_.begin() + manifest_.signatures_size());
+
+  // Save the signature blob because if the update is interrupted after the
+  // download phase we don't go through this path anymore. Some alternatives to
+  // consider:
+  //
+  // 1. On resume, re-download the signature blob from the server and re-verify
+  // it.
+  //
+  // 2. Verify the signature as soon as it's received and don't checkpoint the
+  // blob and the signed sha-256 context.
+  LOG_IF(WARNING, !prefs_->SetString(kPrefsUpdateStateSignatureBlob,
+                                     string(signatures_message_data_.begin(),
+                                            signatures_message_data_.end())))
+      << "Unable to store the signature blob.";
+
+  LOG(INFO) << "Extracted signature data of size "
+            << manifest_.signatures_size() << " at "
+            << manifest_.signatures_offset();
+  return true;
+}
+
+bool DeltaPerformer::GetPublicKeyFromResponse(base::FilePath *out_tmp_key) {
+  if (hardware_->IsOfficialBuild() ||
+      utils::FileExists(public_key_path_.c_str()) ||
+      install_plan_->public_key_rsa.empty())
+    return false;
+
+  if (!utils::DecodeAndStoreBase64String(install_plan_->public_key_rsa,
+                                         out_tmp_key))
+    return false;
+
+  return true;
+}
+
+ErrorCode DeltaPerformer::ValidateMetadataSignature(
+    const brillo::Blob& payload) {
+  if (payload.size() < metadata_size_ + metadata_signature_size_)
+    return ErrorCode::kDownloadMetadataSignatureError;
+
+  brillo::Blob metadata_signature_blob, metadata_signature_protobuf_blob;
+  if (!install_plan_->metadata_signature.empty()) {
+    // Convert base64-encoded signature to raw bytes.
+    if (!brillo::data_encoding::Base64Decode(
+        install_plan_->metadata_signature, &metadata_signature_blob)) {
+      LOG(ERROR) << "Unable to decode base64 metadata signature: "
+                 << install_plan_->metadata_signature;
+      return ErrorCode::kDownloadMetadataSignatureError;
+    }
+  } else if (major_payload_version_ == kBrilloMajorPayloadVersion) {
+    metadata_signature_protobuf_blob.assign(payload.begin() + metadata_size_,
+                                            payload.begin() + metadata_size_ +
+                                            metadata_signature_size_);
+  }
+
+  if (metadata_signature_blob.empty() &&
+      metadata_signature_protobuf_blob.empty()) {
+    if (install_plan_->hash_checks_mandatory) {
+      LOG(ERROR) << "Missing mandatory metadata signature in both Omaha "
+                 << "response and payload.";
+      return ErrorCode::kDownloadMetadataSignatureMissingError;
+    }
+
+    LOG(WARNING) << "Cannot validate metadata as the signature is empty";
+    return ErrorCode::kSuccess;
+  }
+
+  // See if we should use the public RSA key in the Omaha response.
+  base::FilePath path_to_public_key(public_key_path_);
+  base::FilePath tmp_key;
+  if (GetPublicKeyFromResponse(&tmp_key))
+    path_to_public_key = tmp_key;
+  ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+  if (tmp_key.empty())
+    tmp_key_remover.set_should_remove(false);
+
+  LOG(INFO) << "Verifying metadata hash signature using public key: "
+            << path_to_public_key.value();
+
+  HashCalculator metadata_hasher;
+  metadata_hasher.Update(payload.data(), metadata_size_);
+  if (!metadata_hasher.Finalize()) {
+    LOG(ERROR) << "Unable to compute actual hash of manifest";
+    return ErrorCode::kDownloadMetadataSignatureVerificationError;
+  }
+
+  brillo::Blob calculated_metadata_hash = metadata_hasher.raw_hash();
+  PayloadVerifier::PadRSA2048SHA256Hash(&calculated_metadata_hash);
+  if (calculated_metadata_hash.empty()) {
+    LOG(ERROR) << "Computed actual hash of metadata is empty.";
+    return ErrorCode::kDownloadMetadataSignatureVerificationError;
+  }
+
+  if (!metadata_signature_blob.empty()) {
+    brillo::Blob expected_metadata_hash;
+    if (!PayloadVerifier::GetRawHashFromSignature(metadata_signature_blob,
+                                                  path_to_public_key.value(),
+                                                  &expected_metadata_hash)) {
+      LOG(ERROR) << "Unable to compute expected hash from metadata signature";
+      return ErrorCode::kDownloadMetadataSignatureError;
+    }
+    if (calculated_metadata_hash != expected_metadata_hash) {
+      LOG(ERROR) << "Manifest hash verification failed. Expected hash = ";
+      utils::HexDumpVector(expected_metadata_hash);
+      LOG(ERROR) << "Calculated hash = ";
+      utils::HexDumpVector(calculated_metadata_hash);
+      return ErrorCode::kDownloadMetadataSignatureMismatch;
+    }
+  } else {
+    if (!PayloadVerifier::VerifySignature(metadata_signature_protobuf_blob,
+                                          path_to_public_key.value(),
+                                          calculated_metadata_hash)) {
+      LOG(ERROR) << "Manifest hash verification failed.";
+      return ErrorCode::kDownloadMetadataSignatureMismatch;
+    }
+  }
+
+  // The autoupdate_CatchBadSignatures test checks for this string in
+  // log-files. Keep in sync.
+  LOG(INFO) << "Metadata hash signature matches value in Omaha response.";
+  return ErrorCode::kSuccess;
+}
+
+ErrorCode DeltaPerformer::ValidateManifest() {
+  // Perform assorted checks to sanity check the manifest, make sure it
+  // matches data from other sources, and that it is a supported version.
+
+  bool has_old_fields =
+      (manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info());
+  for (const PartitionUpdate& partition : manifest_.partitions()) {
+    has_old_fields = has_old_fields || partition.has_old_partition_info();
+  }
+
+  // The presence of an old partition hash is the sole indicator for a delta
+  // update.
+  InstallPayloadType actual_payload_type =
+      has_old_fields ? InstallPayloadType::kDelta : InstallPayloadType::kFull;
+
+  if (install_plan_->payload_type == InstallPayloadType::kUnknown) {
+    LOG(INFO) << "Detected a '"
+              << InstallPayloadTypeToString(actual_payload_type)
+              << "' payload.";
+    install_plan_->payload_type = actual_payload_type;
+  } else if (install_plan_->payload_type != actual_payload_type) {
+    LOG(ERROR) << "InstallPlan expected a '"
+               << InstallPayloadTypeToString(install_plan_->payload_type)
+               << "' payload but the downloaded manifest contains a '"
+               << InstallPayloadTypeToString(actual_payload_type)
+               << "' payload.";
+    return ErrorCode::kPayloadMismatchedType;
+  }
+
+  // Check that the minor version is compatible.
+  if (actual_payload_type == InstallPayloadType::kFull) {
+    if (manifest_.minor_version() != kFullPayloadMinorVersion) {
+      LOG(ERROR) << "Manifest contains minor version "
+                 << manifest_.minor_version()
+                 << ", but all full payloads should have version "
+                 << kFullPayloadMinorVersion << ".";
+      return ErrorCode::kUnsupportedMinorPayloadVersion;
+    }
+  } else {
+    if (manifest_.minor_version() != supported_minor_version_) {
+      LOG(ERROR) << "Manifest contains minor version "
+                 << manifest_.minor_version()
+                 << " not the supported "
+                 << supported_minor_version_;
+      return ErrorCode::kUnsupportedMinorPayloadVersion;
+    }
+  }
+
+  if (major_payload_version_ != kChromeOSMajorPayloadVersion) {
+    if (manifest_.has_old_rootfs_info() ||
+        manifest_.has_new_rootfs_info() ||
+        manifest_.has_old_kernel_info() ||
+        manifest_.has_new_kernel_info() ||
+        manifest_.install_operations_size() != 0 ||
+        manifest_.kernel_install_operations_size() != 0) {
+      LOG(ERROR) << "Manifest contains deprecated field only supported in "
+                 << "major payload version 1, but the payload major version is "
+                 << major_payload_version_;
+      return ErrorCode::kPayloadMismatchedType;
+    }
+  }
+
+  // TODO(garnold) we should be adding more and more manifest checks, such as
+  // partition boundaries etc (see chromium-os:37661).
+
+  return ErrorCode::kSuccess;
+}
+
+ErrorCode DeltaPerformer::ValidateOperationHash(
+    const InstallOperation& operation) {
+  if (!operation.data_sha256_hash().size()) {
+    if (!operation.data_length()) {
+      // Operations that do not have any data blob won't have any operation hash
+      // either. So, these operations are always considered validated since the
+      // metadata that contains all the non-data-blob portions of the operation
+      // has already been validated. This is true for both HTTP and HTTPS cases.
+      return ErrorCode::kSuccess;
+    }
+
+    // No hash is present for an operation that has data blobs. This shouldn't
+    // happen normally for any client that has this code, because the
+    // corresponding update should have been produced with the operation
+    // hashes. So if it happens it means either we've turned operation hash
+    // generation off in DeltaDiffGenerator or it's a regression of some sort.
+    // One caveat though: The last operation is a dummy signature operation
+    // that doesn't have a hash at the time the manifest is created. So we
+    // should not complaint about that operation. This operation can be
+    // recognized by the fact that it's offset is mentioned in the manifest.
+    if (manifest_.signatures_offset() &&
+        manifest_.signatures_offset() == operation.data_offset()) {
+      LOG(INFO) << "Skipping hash verification for signature operation "
+                << next_operation_num_ + 1;
+    } else {
+      if (install_plan_->hash_checks_mandatory) {
+        LOG(ERROR) << "Missing mandatory operation hash for operation "
+                   << next_operation_num_ + 1;
+        return ErrorCode::kDownloadOperationHashMissingError;
+      }
+
+      LOG(WARNING) << "Cannot validate operation " << next_operation_num_ + 1
+                   << " as there's no operation hash in manifest";
+    }
+    return ErrorCode::kSuccess;
+  }
+
+  brillo::Blob expected_op_hash;
+  expected_op_hash.assign(operation.data_sha256_hash().data(),
+                          (operation.data_sha256_hash().data() +
+                           operation.data_sha256_hash().size()));
+
+  HashCalculator operation_hasher;
+  operation_hasher.Update(buffer_.data(), operation.data_length());
+  if (!operation_hasher.Finalize()) {
+    LOG(ERROR) << "Unable to compute actual hash of operation "
+               << next_operation_num_;
+    return ErrorCode::kDownloadOperationHashVerificationError;
+  }
+
+  brillo::Blob calculated_op_hash = operation_hasher.raw_hash();
+  if (calculated_op_hash != expected_op_hash) {
+    LOG(ERROR) << "Hash verification failed for operation "
+               << next_operation_num_ << ". Expected hash = ";
+    utils::HexDumpVector(expected_op_hash);
+    LOG(ERROR) << "Calculated hash over " << operation.data_length()
+               << " bytes at offset: " << operation.data_offset() << " = ";
+    utils::HexDumpVector(calculated_op_hash);
+    return ErrorCode::kDownloadOperationHashMismatch;
+  }
+
+  return ErrorCode::kSuccess;
+}
+
+#define TEST_AND_RETURN_VAL(_retval, _condition)                \
+  do {                                                          \
+    if (!(_condition)) {                                        \
+      LOG(ERROR) << "VerifyPayload failure: " << #_condition;   \
+      return _retval;                                           \
+    }                                                           \
+  } while (0);
+
+ErrorCode DeltaPerformer::VerifyPayload(
+    const string& update_check_response_hash,
+    const uint64_t update_check_response_size) {
+
+  // See if we should use the public RSA key in the Omaha response.
+  base::FilePath path_to_public_key(public_key_path_);
+  base::FilePath tmp_key;
+  if (GetPublicKeyFromResponse(&tmp_key))
+    path_to_public_key = tmp_key;
+  ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+  if (tmp_key.empty())
+    tmp_key_remover.set_should_remove(false);
+
+  LOG(INFO) << "Verifying payload using public key: "
+            << path_to_public_key.value();
+
+  // Verifies the download size.
+  TEST_AND_RETURN_VAL(ErrorCode::kPayloadSizeMismatchError,
+                      update_check_response_size ==
+                      metadata_size_ + metadata_signature_size_ +
+                      buffer_offset_);
+
+  // Verifies the payload hash.
+  const string& payload_hash_data = payload_hash_calculator_.hash();
+  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadVerificationError,
+                      !payload_hash_data.empty());
+  TEST_AND_RETURN_VAL(ErrorCode::kPayloadHashMismatchError,
+                      payload_hash_data == update_check_response_hash);
+
+  // Verifies the signed payload hash.
+  if (!utils::FileExists(path_to_public_key.value().c_str())) {
+    LOG(WARNING) << "Not verifying signed delta payload -- missing public key.";
+    return ErrorCode::kSuccess;
+  }
+  TEST_AND_RETURN_VAL(ErrorCode::kSignedDeltaPayloadExpectedError,
+                      !signatures_message_data_.empty());
+  brillo::Blob hash_data = signed_hash_calculator_.raw_hash();
+  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+                      PayloadVerifier::PadRSA2048SHA256Hash(&hash_data));
+  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+                      !hash_data.empty());
+
+  if (!PayloadVerifier::VerifySignature(
+      signatures_message_data_, path_to_public_key.value(), hash_data)) {
+    // The autoupdate_CatchBadSignatures test checks for this string
+    // in log-files. Keep in sync.
+    LOG(ERROR) << "Public key verification failed, thus update failed.";
+    return ErrorCode::kDownloadPayloadPubKeyVerificationError;
+  }
+
+  LOG(INFO) << "Payload hash matches value in payload.";
+
+  // At this point, we are guaranteed to have downloaded a full payload, i.e
+  // the one whose size matches the size mentioned in Omaha response. If any
+  // errors happen after this, it's likely a problem with the payload itself or
+  // the state of the system and not a problem with the URL or network.  So,
+  // indicate that to the download delegate so that AU can backoff
+  // appropriately.
+  if (download_delegate_)
+    download_delegate_->DownloadComplete();
+
+  return ErrorCode::kSuccess;
+}
+
+void DeltaPerformer::DiscardBuffer(bool do_advance_offset,
+                                   size_t signed_hash_buffer_size) {
+  // Update the buffer offset.
+  if (do_advance_offset)
+    buffer_offset_ += buffer_.size();
+
+  // Hash the content.
+  payload_hash_calculator_.Update(buffer_.data(), buffer_.size());
+  signed_hash_calculator_.Update(buffer_.data(), signed_hash_buffer_size);
+
+  // Swap content with an empty vector to ensure that all memory is released.
+  brillo::Blob().swap(buffer_);
+}
+
+bool DeltaPerformer::CanResumeUpdate(PrefsInterface* prefs,
+                                     const string& update_check_response_hash) {
+  int64_t next_operation = kUpdateStateOperationInvalid;
+  if (!(prefs->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) &&
+        next_operation != kUpdateStateOperationInvalid &&
+        next_operation > 0))
+    return false;
+
+  string interrupted_hash;
+  if (!(prefs->GetString(kPrefsUpdateCheckResponseHash, &interrupted_hash) &&
+        !interrupted_hash.empty() &&
+        interrupted_hash == update_check_response_hash))
+    return false;
+
+  int64_t resumed_update_failures;
+  // Note that storing this value is optional, but if it is there it should not
+  // be more than the limit.
+  if (prefs->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures) &&
+      resumed_update_failures > kMaxResumedUpdateFailures)
+    return false;
+
+  // Sanity check the rest.
+  int64_t next_data_offset = -1;
+  if (!(prefs->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset) &&
+        next_data_offset >= 0))
+    return false;
+
+  string sha256_context;
+  if (!(prefs->GetString(kPrefsUpdateStateSHA256Context, &sha256_context) &&
+        !sha256_context.empty()))
+    return false;
+
+  int64_t manifest_metadata_size = 0;
+  if (!(prefs->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size) &&
+        manifest_metadata_size > 0))
+    return false;
+
+  int64_t manifest_signature_size = 0;
+  if (!(prefs->GetInt64(kPrefsManifestSignatureSize,
+                        &manifest_signature_size) &&
+        manifest_signature_size >= 0))
+    return false;
+
+  return true;
+}
+
+bool DeltaPerformer::ResetUpdateProgress(PrefsInterface* prefs, bool quick) {
+  TEST_AND_RETURN_FALSE(prefs->SetInt64(kPrefsUpdateStateNextOperation,
+                                        kUpdateStateOperationInvalid));
+  if (!quick) {
+    prefs->SetString(kPrefsUpdateCheckResponseHash, "");
+    prefs->SetInt64(kPrefsUpdateStateNextDataOffset, -1);
+    prefs->SetInt64(kPrefsUpdateStateNextDataLength, 0);
+    prefs->SetString(kPrefsUpdateStateSHA256Context, "");
+    prefs->SetString(kPrefsUpdateStateSignedSHA256Context, "");
+    prefs->SetString(kPrefsUpdateStateSignatureBlob, "");
+    prefs->SetInt64(kPrefsManifestMetadataSize, -1);
+    prefs->SetInt64(kPrefsManifestSignatureSize, -1);
+    prefs->SetInt64(kPrefsResumedUpdateFailures, 0);
+  }
+  return true;
+}
+
+bool DeltaPerformer::CheckpointUpdateProgress() {
+  Terminator::set_exit_blocked(true);
+  if (last_updated_buffer_offset_ != buffer_offset_) {
+    // Resets the progress in case we die in the middle of the state update.
+    ResetUpdateProgress(prefs_, true);
+    TEST_AND_RETURN_FALSE(
+        prefs_->SetString(kPrefsUpdateStateSHA256Context,
+                          payload_hash_calculator_.GetContext()));
+    TEST_AND_RETURN_FALSE(
+        prefs_->SetString(kPrefsUpdateStateSignedSHA256Context,
+                          signed_hash_calculator_.GetContext()));
+    TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataOffset,
+                                           buffer_offset_));
+    last_updated_buffer_offset_ = buffer_offset_;
+
+    if (next_operation_num_ < num_total_operations_) {
+      size_t partition_index = current_partition_;
+      while (next_operation_num_ >= acc_num_operations_[partition_index])
+        partition_index++;
+      const size_t partition_operation_num = next_operation_num_ - (
+          partition_index ? acc_num_operations_[partition_index - 1] : 0);
+      const InstallOperation& op =
+          partitions_[partition_index].operations(partition_operation_num);
+      TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+                                             op.data_length()));
+    } else {
+      TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+                                             0));
+    }
+  }
+  TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextOperation,
+                                         next_operation_num_));
+  return true;
+}
+
+bool DeltaPerformer::PrimeUpdateState() {
+  CHECK(manifest_valid_);
+  block_size_ = manifest_.block_size();
+
+  int64_t next_operation = kUpdateStateOperationInvalid;
+  if (!prefs_->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) ||
+      next_operation == kUpdateStateOperationInvalid ||
+      next_operation <= 0) {
+    // Initiating a new update, no more state needs to be initialized.
+    return true;
+  }
+  next_operation_num_ = next_operation;
+
+  // Resuming an update -- load the rest of the update state.
+  int64_t next_data_offset = -1;
+  TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsUpdateStateNextDataOffset,
+                                         &next_data_offset) &&
+                        next_data_offset >= 0);
+  buffer_offset_ = next_data_offset;
+
+  // The signed hash context and the signature blob may be empty if the
+  // interrupted update didn't reach the signature.
+  string signed_hash_context;
+  if (prefs_->GetString(kPrefsUpdateStateSignedSHA256Context,
+                        &signed_hash_context)) {
+    TEST_AND_RETURN_FALSE(
+        signed_hash_calculator_.SetContext(signed_hash_context));
+  }
+
+  string signature_blob;
+  if (prefs_->GetString(kPrefsUpdateStateSignatureBlob, &signature_blob)) {
+    signatures_message_data_.assign(signature_blob.begin(),
+                                    signature_blob.end());
+  }
+
+  string hash_context;
+  TEST_AND_RETURN_FALSE(prefs_->GetString(kPrefsUpdateStateSHA256Context,
+                                          &hash_context) &&
+                        payload_hash_calculator_.SetContext(hash_context));
+
+  int64_t manifest_metadata_size = 0;
+  TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsManifestMetadataSize,
+                                         &manifest_metadata_size) &&
+                        manifest_metadata_size > 0);
+  metadata_size_ = manifest_metadata_size;
+
+  int64_t manifest_signature_size = 0;
+  TEST_AND_RETURN_FALSE(
+      prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size) &&
+      manifest_signature_size >= 0);
+  metadata_signature_size_ = manifest_signature_size;
+
+  // Advance the download progress to reflect what doesn't need to be
+  // re-downloaded.
+  total_bytes_received_ += buffer_offset_;
+
+  // Speculatively count the resume as a failure.
+  int64_t resumed_update_failures;
+  if (prefs_->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures)) {
+    resumed_update_failures++;
+  } else {
+    resumed_update_failures = 1;
+  }
+  prefs_->SetInt64(kPrefsResumedUpdateFailures, resumed_update_failures);
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/delta_performer.h b/update_engine/payload_consumer/delta_performer.h
new file mode 100644
index 0000000..74143e0
--- /dev/null
+++ b/update_engine/payload_consumer/delta_performer.h
@@ -0,0 +1,407 @@
+//
+// 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_PAYLOAD_CONSUMER_DELTA_PERFORMER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_DELTA_PERFORMER_H_
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <brillo/secure_blob.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_consumer/file_writer.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class DownloadActionDelegate;
+class BootControlInterface;
+class HardwareInterface;
+class PrefsInterface;
+
+// This class performs the actions in a delta update synchronously. The delta
+// update itself should be passed in in chunks as it is received.
+
+class DeltaPerformer : public FileWriter {
+ public:
+  enum MetadataParseResult {
+    kMetadataParseSuccess,
+    kMetadataParseError,
+    kMetadataParseInsufficientData,
+  };
+
+  static const uint64_t kDeltaVersionOffset;
+  static const uint64_t kDeltaVersionSize;
+  static const uint64_t kDeltaManifestSizeOffset;
+  static const uint64_t kDeltaManifestSizeSize;
+  static const uint64_t kDeltaMetadataSignatureSizeSize;
+  static const uint64_t kMaxPayloadHeaderSize;
+  static const uint64_t kSupportedMajorPayloadVersion;
+  static const uint32_t kSupportedMinorPayloadVersion;
+
+  // Defines the granularity of progress logging in terms of how many "completed
+  // chunks" we want to report at the most.
+  static const unsigned kProgressLogMaxChunks;
+  // Defines a timeout since the last progress was logged after which we want to
+  // force another log message (even if the current chunk was not completed).
+  static const unsigned kProgressLogTimeoutSeconds;
+  // These define the relative weights (0-100) we give to the different work
+  // components associated with an update when computing an overall progress.
+  // Currently they include the download progress and the number of completed
+  // operations. They must add up to one hundred (100).
+  static const unsigned kProgressDownloadWeight;
+  static const unsigned kProgressOperationsWeight;
+
+  DeltaPerformer(PrefsInterface* prefs,
+                 BootControlInterface* boot_control,
+                 HardwareInterface* hardware,
+                 DownloadActionDelegate* download_delegate,
+                 InstallPlan* install_plan)
+      : prefs_(prefs),
+        boot_control_(boot_control),
+        hardware_(hardware),
+        download_delegate_(download_delegate),
+        install_plan_(install_plan) {}
+
+  // FileWriter's Write implementation where caller doesn't care about
+  // error codes.
+  bool Write(const void* bytes, size_t count) override {
+    ErrorCode error;
+    return Write(bytes, count, &error);
+  }
+
+  // FileWriter's Write implementation that returns a more specific |error| code
+  // in case of failures in Write operation.
+  bool Write(const void* bytes, size_t count, ErrorCode *error) override;
+
+  // Wrapper around close. Returns 0 on success or -errno on error.
+  // Closes both 'path' given to Open() and the kernel path.
+  int Close() override;
+
+  // Open the target and source (if delta payload) file descriptors for the
+  // |current_partition_|. The manifest needs to be already parsed for this to
+  // work. Returns whether the required file descriptors were successfully open.
+  bool OpenCurrentPartition();
+
+  // Closes the current partition file descriptors if open. Returns 0 on success
+  // or -errno on error.
+  int CloseCurrentPartition();
+
+  // Returns |true| only if the manifest has been processed and it's valid.
+  bool IsManifestValid();
+
+  // Verifies the downloaded payload against the signed hash included in the
+  // payload, against the update check hash (which is in base64 format)  and
+  // size using the public key and returns ErrorCode::kSuccess on success, an
+  // error code on failure.  This method should be called after closing the
+  // stream. Note this method skips the signed hash check if the public key is
+  // unavailable; it returns ErrorCode::kSignedDeltaPayloadExpectedError if the
+  // public key is available but the delta payload doesn't include a signature.
+  ErrorCode VerifyPayload(const std::string& update_check_response_hash,
+                          const uint64_t update_check_response_size);
+
+  // Converts an ordered collection of Extent objects which contain data of
+  // length full_length to a comma-separated string. For each Extent, the
+  // string will have the start offset and then the length in bytes.
+  // The length value of the last extent in the string may be short, since
+  // the full length of all extents in the string is capped to full_length.
+  // Also, an extent starting at kSparseHole, appears as -1 in the string.
+  // For example, if the Extents are {1, 1}, {4, 2}, {kSparseHole, 1},
+  // {0, 1}, block_size is 4096, and full_length is 5 * block_size - 13,
+  // the resulting string will be: "4096:4096,16384:8192,-1:4096,0:4083"
+  static bool ExtentsToBsdiffPositionsString(
+      const google::protobuf::RepeatedPtrField<Extent>& extents,
+      uint64_t block_size,
+      uint64_t full_length,
+      std::string* positions_string);
+
+  // Returns true if a previous update attempt can be continued based on the
+  // persistent preferences and the new update check response hash.
+  static bool CanResumeUpdate(PrefsInterface* prefs,
+                              const std::string& update_check_response_hash);
+
+  // Resets the persistent update progress state to indicate that an update
+  // can't be resumed. Performs a quick update-in-progress reset if |quick| is
+  // true, otherwise resets all progress-related update state. Returns true on
+  // success, false otherwise.
+  static bool ResetUpdateProgress(PrefsInterface* prefs, bool quick);
+
+  // Attempts to parse the update metadata starting from the beginning of
+  // |payload|. On success, returns kMetadataParseSuccess. Returns
+  // kMetadataParseInsufficientData if more data is needed to parse the complete
+  // metadata. Returns kMetadataParseError if the metadata can't be parsed given
+  // the payload.
+  MetadataParseResult ParsePayloadMetadata(const brillo::Blob& payload,
+                                           ErrorCode* error);
+
+  void set_public_key_path(const std::string& public_key_path) {
+    public_key_path_ = public_key_path;
+  }
+
+  // Set |*out_offset| to the byte offset where the size of the metadata signature
+  // is stored in a payload. Return true on success, if this field is not
+  // present in the payload, return false.
+  bool GetMetadataSignatureSizeOffset(uint64_t* out_offset) const;
+
+  // Set |*out_offset| to the byte offset at which the manifest protobuf begins
+  // in a payload. Return true on success, false if the offset is unknown.
+  bool GetManifestOffset(uint64_t* out_offset) const;
+
+  // Returns the size of the payload metadata, which includes the payload header
+  // and the manifest. If the header was not yet parsed, returns zero.
+  uint64_t GetMetadataSize() const;
+
+  // If the manifest was successfully parsed, copies it to |*out_manifest_p|.
+  // Returns true on success.
+  bool GetManifest(DeltaArchiveManifest* out_manifest_p) const;
+
+  // Return true if header parsing is finished and no errors occurred.
+  bool IsHeaderParsed() const;
+
+  // Returns the major payload version. If the version was not yet parsed,
+  // returns zero.
+  uint64_t GetMajorVersion() const;
+
+  // Returns the delta minor version. If this value is defined in the manifest,
+  // it returns that value, otherwise it returns the default value.
+  uint32_t GetMinorVersion() const;
+
+ private:
+  friend class DeltaPerformerTest;
+  friend class DeltaPerformerIntegrationTest;
+  FRIEND_TEST(DeltaPerformerTest, BrilloMetadataSignatureSizeTest);
+  FRIEND_TEST(DeltaPerformerTest, BrilloVerifyMetadataSignatureTest);
+  FRIEND_TEST(DeltaPerformerTest, UsePublicKeyFromResponse);
+
+  // Parse and move the update instructions of all partitions into our local
+  // |partitions_| variable based on the version of the payload. Requires the
+  // manifest to be parsed and valid.
+  bool ParseManifestPartitions(ErrorCode* error);
+
+  // Appends up to |*count_p| bytes from |*bytes_p| to |buffer_|, but only to
+  // the extent that the size of |buffer_| does not exceed |max|. Advances
+  // |*cbytes_p| and decreases |*count_p| by the actual number of bytes copied,
+  // and returns this number.
+  size_t CopyDataToBuffer(const char** bytes_p, size_t* count_p, size_t max);
+
+  // If |op_result| is false, emits an error message using |op_type_name| and
+  // sets |*error| accordingly. Otherwise does nothing. Returns |op_result|.
+  bool HandleOpResult(bool op_result, const char* op_type_name,
+                      ErrorCode* error);
+
+  // Logs the progress of downloading/applying an update.
+  void LogProgress(const char* message_prefix);
+
+  // Update overall progress metrics, log as necessary.
+  void UpdateOverallProgress(bool force_log, const char* message_prefix);
+
+  // Returns true if enough of the delta file has been passed via Write()
+  // to be able to perform a given install operation.
+  bool CanPerformInstallOperation(const InstallOperation& operation);
+
+  // Checks the integrity of the payload manifest. Returns true upon success,
+  // false otherwise.
+  ErrorCode ValidateManifest();
+
+  // Validates that the hash of the blobs corresponding to the given |operation|
+  // matches what's specified in the manifest in the payload.
+  // Returns ErrorCode::kSuccess on match or a suitable error code otherwise.
+  ErrorCode ValidateOperationHash(const InstallOperation& operation);
+
+  // Given the |payload|, verifies that the signed hash of its metadata matches
+  // what's specified in the install plan from Omaha (if present) or the
+  // metadata signature in payload itself (if present). Returns
+  // ErrorCode::kSuccess on match or a suitable error code otherwise. This
+  // method must be called before any part of the metadata is parsed so that a
+  // man-in-the-middle attack on the SSL connection to the payload server
+  // doesn't exploit any vulnerability in the code that parses the protocol
+  // buffer.
+  ErrorCode ValidateMetadataSignature(const brillo::Blob& payload);
+
+  // Returns true on success.
+  bool PerformInstallOperation(const InstallOperation& operation);
+
+  // These perform a specific type of operation and return true on success.
+  // |error| will be set if source hash mismatch, otherwise |error| might not be
+  // set even if it fails.
+  bool PerformReplaceOperation(const InstallOperation& operation);
+  bool PerformZeroOrDiscardOperation(const InstallOperation& operation);
+  bool PerformMoveOperation(const InstallOperation& operation);
+  bool PerformBsdiffOperation(const InstallOperation& operation);
+  bool PerformSourceCopyOperation(const InstallOperation& operation,
+                                  ErrorCode* error);
+  bool PerformSourceBsdiffOperation(const InstallOperation& operation,
+                                    ErrorCode* error);
+  bool PerformImgdiffOperation(const InstallOperation& operation,
+                               ErrorCode* error);
+
+  // Extracts the payload signature message from the blob on the |operation| if
+  // the offset matches the one specified by the manifest. Returns whether the
+  // signature was extracted.
+  bool ExtractSignatureMessageFromOperation(const InstallOperation& operation);
+
+  // Extracts the payload signature message from the current |buffer_| if the
+  // offset matches the one specified by the manifest. Returns whether the
+  // signature was extracted.
+  bool ExtractSignatureMessage();
+
+  // Updates the payload hash calculator with the bytes in |buffer_|, also
+  // updates the signed hash calculator with the first |signed_hash_buffer_size|
+  // bytes in |buffer_|. Then discard the content, ensuring that memory is being
+  // deallocated. If |do_advance_offset|, advances the internal offset counter
+  // accordingly.
+  void DiscardBuffer(bool do_advance_offset, size_t signed_hash_buffer_size);
+
+  // Checkpoints the update progress into persistent storage to allow this
+  // update attempt to be resumed after reboot.
+  bool CheckpointUpdateProgress();
+
+  // Primes the required update state. Returns true if the update state was
+  // successfully initialized to a saved resume state or if the update is a new
+  // update. Returns false otherwise.
+  bool PrimeUpdateState();
+
+  // If the Omaha response contains a public RSA key and we're allowed
+  // to use it (e.g. if we're in developer mode), extract the key from
+  // the response and store it in a temporary file and return true. In
+  // the affirmative the path to the temporary file is stored in
+  // |out_tmp_key| and it is the responsibility of the caller to clean
+  // it up.
+  bool GetPublicKeyFromResponse(base::FilePath *out_tmp_key);
+
+  // Update Engine preference store.
+  PrefsInterface* prefs_;
+
+  // BootControl and Hardware interface references.
+  BootControlInterface* boot_control_;
+  HardwareInterface* hardware_;
+
+  // The DownloadActionDelegate instance monitoring the DownloadAction, or a
+  // nullptr if not used.
+  DownloadActionDelegate* download_delegate_;
+
+  // Install Plan based on Omaha Response.
+  InstallPlan* install_plan_;
+
+  // File descriptor of the source partition. Only set while updating a
+  // partition when using a delta payload.
+  FileDescriptorPtr source_fd_{nullptr};
+
+  // File descriptor of the target partition. Only set while performing the
+  // operations of a given partition.
+  FileDescriptorPtr target_fd_{nullptr};
+
+  // Paths the |source_fd_| and |target_fd_| refer to.
+  std::string source_path_;
+  std::string target_path_;
+
+  // Parsed manifest. Set after enough bytes to parse the manifest were
+  // downloaded.
+  DeltaArchiveManifest manifest_;
+  bool manifest_parsed_{false};
+  bool manifest_valid_{false};
+  uint64_t metadata_size_{0};
+  uint64_t manifest_size_{0};
+  uint32_t metadata_signature_size_{0};
+  uint64_t major_payload_version_{0};
+
+  // Accumulated number of operations per partition. The i-th element is the
+  // sum of the number of operations for all the partitions from 0 to i
+  // inclusive. Valid when |manifest_valid_| is true.
+  std::vector<size_t> acc_num_operations_;
+
+  // The total operations in a payload. Valid when |manifest_valid_| is true,
+  // otherwise 0.
+  size_t num_total_operations_{0};
+
+  // The list of partitions to update as found in the manifest major version 2.
+  // When parsing an older manifest format, the information is converted over to
+  // this format instead.
+  std::vector<PartitionUpdate> partitions_;
+
+  // Index in the list of partitions (|partitions_| member) of the current
+  // partition being processed.
+  size_t current_partition_{0};
+
+  // Index of the next operation to perform in the manifest. The index is linear
+  // on the total number of operation on the manifest.
+  size_t next_operation_num_{0};
+
+  // A buffer used for accumulating downloaded data. Initially, it stores the
+  // payload metadata; once that's downloaded and parsed, it stores data for the
+  // next update operation.
+  brillo::Blob buffer_;
+  // Offset of buffer_ in the binary blobs section of the update.
+  uint64_t buffer_offset_{0};
+
+  // Last |buffer_offset_| value updated as part of the progress update.
+  uint64_t last_updated_buffer_offset_{std::numeric_limits<uint64_t>::max()};
+
+  // The block size (parsed from the manifest).
+  uint32_t block_size_{0};
+
+  // Calculates the whole payload file hash, including headers and signatures.
+  HashCalculator payload_hash_calculator_;
+
+  // Calculates the hash of the portion of the payload signed by the payload
+  // signature. This hash skips the metadata signature portion, located after
+  // the metadata and doesn't include the payload signature itself.
+  HashCalculator signed_hash_calculator_;
+
+  // Signatures message blob extracted directly from the payload.
+  brillo::Blob signatures_message_data_;
+
+  // The public key to be used. Provided as a member so that tests can
+  // override with test keys.
+  std::string public_key_path_{constants::kUpdatePayloadPublicKeyPath};
+
+  // The number of bytes received so far, used for progress tracking.
+  size_t total_bytes_received_{0};
+
+  // An overall progress counter, which should reflect both download progress
+  // and the ratio of applied operations. Range is 0-100.
+  unsigned overall_progress_{0};
+
+  // The last progress chunk recorded.
+  unsigned last_progress_chunk_{0};
+
+  // The timeout after which we should force emitting a progress log (constant),
+  // and the actual point in time for the next forced log to be emitted.
+  const base::TimeDelta forced_progress_log_wait_{
+      base::TimeDelta::FromSeconds(kProgressLogTimeoutSeconds)};
+  base::Time forced_progress_log_time_;
+
+  // The payload major payload version supported by DeltaPerformer.
+  uint64_t supported_major_version_{kSupportedMajorPayloadVersion};
+
+  // The delta minor payload version supported by DeltaPerformer.
+  uint32_t supported_minor_version_{kSupportedMinorPayloadVersion};
+
+  DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_DELTA_PERFORMER_H_
diff --git a/update_engine/payload_consumer/delta_performer_integration_test.cc b/update_engine/payload_consumer/delta_performer_integration_test.cc
new file mode 100644
index 0000000..afbb8dc
--- /dev/null
+++ b/update_engine/payload_consumer/delta_performer_integration_test.cc
@@ -0,0 +1,1045 @@
+//
+// 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/payload_consumer/delta_performer.h"
+
+#include <inttypes.h>
+#include <sys/mount.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest.h>
+#include <openssl/pem.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/mock_prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/mock_download_action.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+using test_utils::GetBuildArtifactsPath;
+using test_utils::ScopedLoopMounter;
+using test_utils::System;
+using test_utils::kRandomString;
+using testing::Return;
+using testing::_;
+
+extern const char* kUnittestPrivateKeyPath;
+extern const char* kUnittestPublicKeyPath;
+extern const char* kUnittestPrivateKey2Path;
+extern const char* kUnittestPublicKey2Path;
+
+static const uint32_t kDefaultKernelSize = 4096;  // Something small for a test
+static const uint8_t kNewData[] = {'T', 'h', 'i', 's', ' ', 'i', 's', ' ',
+                                   'n', 'e', 'w', ' ', 'd', 'a', 't', 'a', '.'};
+
+namespace {
+struct DeltaState {
+  string a_img;
+  string b_img;
+  string result_img;
+  size_t image_size;
+
+  string delta_path;
+  uint64_t metadata_size;
+
+  string old_kernel;
+  brillo::Blob old_kernel_data;
+
+  string new_kernel;
+  brillo::Blob new_kernel_data;
+
+  string result_kernel;
+  brillo::Blob result_kernel_data;
+  size_t kernel_size;
+
+  // The InstallPlan referenced by the DeltaPerformer. This needs to outlive
+  // the DeltaPerformer.
+  InstallPlan install_plan;
+
+  // The in-memory copy of delta file.
+  brillo::Blob delta;
+
+  // Mock and fake instances used by the delta performer.
+  FakeBootControl fake_boot_control_;
+  FakeHardware fake_hardware_;
+  MockDownloadActionDelegate mock_delegate_;
+};
+
+enum SignatureTest {
+  kSignatureNone,  // No payload signing.
+  kSignatureGenerator,  // Sign the payload at generation time.
+  kSignatureGenerated,  // Sign the payload after it's generated.
+  kSignatureGeneratedPlaceholder,  // Insert placeholder signatures, then real.
+  kSignatureGeneratedPlaceholderMismatch,  // Insert a wrong sized placeholder.
+  kSignatureGeneratedShell,  // Sign the generated payload through shell cmds.
+  kSignatureGeneratedShellBadKey,  // Sign with a bad key through shell cmds.
+  kSignatureGeneratedShellRotateCl1,  // Rotate key, test client v1
+  kSignatureGeneratedShellRotateCl2,  // Rotate key, test client v2
+};
+
+enum OperationHashTest {
+  kInvalidOperationData,
+  kValidOperationData,
+};
+
+}  // namespace
+
+class DeltaPerformerIntegrationTest : public ::testing::Test {
+ public:
+  static void SetSupportedVersion(DeltaPerformer* performer,
+                                  uint64_t minor_version) {
+    performer->supported_minor_version_ = minor_version;
+  }
+};
+
+static void CompareFilesByBlock(const string& a_file, const string& b_file,
+                                size_t image_size) {
+  EXPECT_EQ(0U, image_size % kBlockSize);
+
+  brillo::Blob a_data, b_data;
+  EXPECT_TRUE(utils::ReadFile(a_file, &a_data)) << "file failed: " << a_file;
+  EXPECT_TRUE(utils::ReadFile(b_file, &b_data)) << "file failed: " << b_file;
+
+  EXPECT_GE(a_data.size(), image_size);
+  EXPECT_GE(b_data.size(), image_size);
+  for (size_t i = 0; i < image_size; i += kBlockSize) {
+    EXPECT_EQ(0U, i % kBlockSize);
+    brillo::Blob a_sub(&a_data[i], &a_data[i + kBlockSize]);
+    brillo::Blob b_sub(&b_data[i], &b_data[i + kBlockSize]);
+    EXPECT_TRUE(a_sub == b_sub) << "Block " << (i/kBlockSize) << " differs";
+  }
+  if (::testing::Test::HasNonfatalFailure()) {
+    LOG(INFO) << "Compared filesystems with size " << image_size
+              << ", partition A " << a_file << " size: " << a_data.size()
+              << ", partition B " << b_file << " size: " << b_data.size();
+  }
+}
+
+static bool WriteSparseFile(const string& path, off_t size) {
+  int fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
+  TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+  ScopedFdCloser fd_closer(&fd);
+  off_t rc = lseek(fd, size + 1, SEEK_SET);
+  TEST_AND_RETURN_FALSE_ERRNO(rc != static_cast<off_t>(-1));
+  int return_code = ftruncate(fd, size);
+  TEST_AND_RETURN_FALSE_ERRNO(return_code == 0);
+  return true;
+}
+
+static bool WriteByteAtOffset(const string& path, off_t offset) {
+  int fd = open(path.c_str(), O_CREAT | O_WRONLY, 0644);
+  TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+  ScopedFdCloser fd_closer(&fd);
+  EXPECT_TRUE(utils::PWriteAll(fd, "\0", 1, offset));
+  return true;
+}
+
+static size_t GetSignatureSize(const string& private_key_path) {
+  const brillo::Blob data(1, 'x');
+  brillo::Blob hash;
+  EXPECT_TRUE(HashCalculator::RawHashOfData(data, &hash));
+  brillo::Blob signature;
+  EXPECT_TRUE(PayloadSigner::SignHash(hash,
+                                      private_key_path,
+                                      &signature));
+  return signature.size();
+}
+
+static bool InsertSignaturePlaceholder(int signature_size,
+                                       const string& payload_path,
+                                       uint64_t* out_metadata_size) {
+  vector<brillo::Blob> signatures;
+  signatures.push_back(brillo::Blob(signature_size, 0));
+
+  return PayloadSigner::AddSignatureToPayload(
+      payload_path,
+      signatures,
+      {},
+      payload_path,
+      out_metadata_size);
+}
+
+static void SignGeneratedPayload(const string& payload_path,
+                                 uint64_t* out_metadata_size) {
+  string private_key_path = GetBuildArtifactsPath(kUnittestPrivateKeyPath);
+  int signature_size = GetSignatureSize(private_key_path);
+  brillo::Blob hash;
+  ASSERT_TRUE(PayloadSigner::HashPayloadForSigning(
+      payload_path, {signature_size}, &hash, nullptr));
+  brillo::Blob signature;
+  ASSERT_TRUE(PayloadSigner::SignHash(hash, private_key_path, &signature));
+  ASSERT_TRUE(PayloadSigner::AddSignatureToPayload(
+      payload_path, {signature}, {}, payload_path, out_metadata_size));
+  EXPECT_TRUE(PayloadSigner::VerifySignedPayload(
+      payload_path, GetBuildArtifactsPath(kUnittestPublicKeyPath)));
+}
+
+static void SignGeneratedShellPayload(SignatureTest signature_test,
+                                      const string& payload_path) {
+  string private_key_path = GetBuildArtifactsPath(kUnittestPrivateKeyPath);
+  if (signature_test == kSignatureGeneratedShellBadKey) {
+    ASSERT_TRUE(utils::MakeTempFile("key.XXXXXX",
+                                    &private_key_path,
+                                    nullptr));
+  } else {
+    ASSERT_TRUE(signature_test == kSignatureGeneratedShell ||
+                signature_test == kSignatureGeneratedShellRotateCl1 ||
+                signature_test == kSignatureGeneratedShellRotateCl2);
+  }
+  ScopedPathUnlinker key_unlinker(private_key_path);
+  key_unlinker.set_should_remove(signature_test ==
+                                 kSignatureGeneratedShellBadKey);
+  // Generates a new private key that will not match the public key.
+  if (signature_test == kSignatureGeneratedShellBadKey) {
+    LOG(INFO) << "Generating a mismatched private key.";
+    // The code below executes the equivalent of:
+    // openssl genrsa -out <private_key_path> 2048
+    RSA* rsa = RSA_new();
+    BIGNUM* e = BN_new();
+    EXPECT_EQ(1, BN_set_word(e, RSA_F4));
+    EXPECT_EQ(1, RSA_generate_key_ex(rsa, 2048, e, nullptr));
+    BN_free(e);
+    FILE* fprikey = fopen(private_key_path.c_str(), "w");
+    EXPECT_NE(nullptr, fprikey);
+    EXPECT_EQ(1,
+              PEM_write_RSAPrivateKey(
+                  fprikey, rsa, nullptr, nullptr, 0, nullptr, nullptr));
+    fclose(fprikey);
+    RSA_free(rsa);
+  }
+  int signature_size = GetSignatureSize(private_key_path);
+  string hash_file;
+  ASSERT_TRUE(utils::MakeTempFile("hash.XXXXXX", &hash_file, nullptr));
+  ScopedPathUnlinker hash_unlinker(hash_file);
+  string signature_size_string;
+  if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+      signature_test == kSignatureGeneratedShellRotateCl2)
+    signature_size_string = base::StringPrintf("%d:%d",
+                                               signature_size, signature_size);
+  else
+    signature_size_string = base::StringPrintf("%d", signature_size);
+  string delta_generator_path = GetBuildArtifactsPath("delta_generator");
+  ASSERT_EQ(0,
+            System(base::StringPrintf(
+                "%s -in_file=%s -signature_size=%s -out_hash_file=%s",
+                delta_generator_path.c_str(),
+                payload_path.c_str(),
+                signature_size_string.c_str(),
+                hash_file.c_str())));
+
+  // Sign the hash
+  brillo::Blob hash, signature;
+  ASSERT_TRUE(utils::ReadFile(hash_file, &hash));
+  ASSERT_TRUE(PayloadSigner::SignHash(hash, private_key_path, &signature));
+
+  string sig_file;
+  ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file, nullptr));
+  ScopedPathUnlinker sig_unlinker(sig_file);
+  ASSERT_TRUE(test_utils::WriteFileVector(sig_file, signature));
+
+  string sig_file2;
+  ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file2, nullptr));
+  ScopedPathUnlinker sig2_unlinker(sig_file2);
+  if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+      signature_test == kSignatureGeneratedShellRotateCl2) {
+    ASSERT_TRUE(PayloadSigner::SignHash(
+        hash, GetBuildArtifactsPath(kUnittestPrivateKey2Path), &signature));
+    ASSERT_TRUE(test_utils::WriteFileVector(sig_file2, signature));
+    // Append second sig file to first path
+    sig_file += ":" + sig_file2;
+  }
+
+  ASSERT_EQ(0,
+            System(base::StringPrintf(
+                "%s -in_file=%s -signature_file=%s -out_file=%s",
+                delta_generator_path.c_str(),
+                payload_path.c_str(),
+                sig_file.c_str(),
+                payload_path.c_str())));
+  int verify_result = System(base::StringPrintf(
+      "%s -in_file=%s -public_key=%s -public_key_version=%d",
+      delta_generator_path.c_str(),
+      payload_path.c_str(),
+      (signature_test == kSignatureGeneratedShellRotateCl2
+           ? GetBuildArtifactsPath(kUnittestPublicKey2Path)
+           : GetBuildArtifactsPath(kUnittestPublicKeyPath))
+          .c_str(),
+      signature_test == kSignatureGeneratedShellRotateCl2 ? 2 : 1));
+  if (signature_test == kSignatureGeneratedShellBadKey) {
+    ASSERT_NE(0, verify_result);
+  } else {
+    ASSERT_EQ(0, verify_result);
+  }
+}
+
+static void GenerateDeltaFile(bool full_kernel,
+                              bool full_rootfs,
+                              bool noop,
+                              ssize_t chunk_size,
+                              SignatureTest signature_test,
+                              DeltaState *state,
+                              uint32_t minor_version) {
+  EXPECT_TRUE(utils::MakeTempFile("a_img.XXXXXX", &state->a_img, nullptr));
+  EXPECT_TRUE(utils::MakeTempFile("b_img.XXXXXX", &state->b_img, nullptr));
+
+  // result_img is used in minor version 2. Instead of applying the update
+  // in-place on A, we apply it to a new image, result_img.
+  EXPECT_TRUE(
+      utils::MakeTempFile("result_img.XXXXXX", &state->result_img, nullptr));
+
+  EXPECT_TRUE(
+      base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"),
+                     base::FilePath(state->a_img)));
+
+  state->image_size = utils::FileSize(state->a_img);
+
+  // Create ImageInfo A & B
+  ImageInfo old_image_info;
+  ImageInfo new_image_info;
+
+  if (!full_rootfs) {
+    old_image_info.set_channel("src-channel");
+    old_image_info.set_board("src-board");
+    old_image_info.set_version("src-version");
+    old_image_info.set_key("src-key");
+    old_image_info.set_build_channel("src-build-channel");
+    old_image_info.set_build_version("src-build-version");
+  }
+
+  new_image_info.set_channel("test-channel");
+  new_image_info.set_board("test-board");
+  new_image_info.set_version("test-version");
+  new_image_info.set_key("test-key");
+  new_image_info.set_build_channel("test-build-channel");
+  new_image_info.set_build_version("test-build-version");
+
+  // Make some changes to the A image.
+  {
+    string a_mnt;
+    ScopedLoopMounter b_mounter(state->a_img, &a_mnt, 0);
+
+    brillo::Blob hardtocompress;
+    while (hardtocompress.size() < 3 * kBlockSize) {
+      hardtocompress.insert(hardtocompress.end(),
+                            std::begin(kRandomString), std::end(kRandomString));
+    }
+    EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+                                                    a_mnt.c_str()).c_str(),
+                                 hardtocompress.data(),
+                                 hardtocompress.size()));
+
+    brillo::Blob zeros(16 * 1024, 0);
+    EXPECT_EQ(static_cast<int>(zeros.size()),
+              base::WriteFile(base::FilePath(base::StringPrintf(
+                                  "%s/move-to-sparse", a_mnt.c_str())),
+                              reinterpret_cast<const char*>(zeros.data()),
+                              zeros.size()));
+
+    EXPECT_TRUE(
+        WriteSparseFile(base::StringPrintf("%s/move-from-sparse",
+                                           a_mnt.c_str()), 16 * 1024));
+
+    EXPECT_TRUE(WriteByteAtOffset(
+        base::StringPrintf("%s/move-semi-sparse", a_mnt.c_str()), 4096));
+
+    // Write 1 MiB of 0xff to try to catch the case where writing a bsdiff
+    // patch fails to zero out the final block.
+    brillo::Blob ones(1024 * 1024, 0xff);
+    EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/ones",
+                                                    a_mnt.c_str()).c_str(),
+                                 ones.data(),
+                                 ones.size()));
+  }
+
+  if (noop) {
+    EXPECT_TRUE(base::CopyFile(base::FilePath(state->a_img),
+                               base::FilePath(state->b_img)));
+    old_image_info = new_image_info;
+  } else {
+    if (minor_version == kSourceMinorPayloadVersion) {
+      // Create a result image with image_size bytes of garbage.
+      brillo::Blob ones(state->image_size, 0xff);
+      EXPECT_TRUE(utils::WriteFile(state->result_img.c_str(),
+                                   ones.data(),
+                                   ones.size()));
+      EXPECT_EQ(utils::FileSize(state->a_img),
+                utils::FileSize(state->result_img));
+    }
+
+    EXPECT_TRUE(
+        base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"),
+                       base::FilePath(state->b_img)));
+
+    // Make some changes to the B image.
+    string b_mnt;
+    ScopedLoopMounter b_mounter(state->b_img, &b_mnt, 0);
+    base::FilePath mnt_path(b_mnt);
+
+    EXPECT_TRUE(base::CopyFile(mnt_path.Append("regular-small"),
+                               mnt_path.Append("regular-small2")));
+    EXPECT_TRUE(base::DeleteFile(mnt_path.Append("regular-small"), false));
+    EXPECT_TRUE(base::Move(mnt_path.Append("regular-small2"),
+                           mnt_path.Append("regular-small")));
+    EXPECT_TRUE(
+        test_utils::WriteFileString(mnt_path.Append("foo").value(), "foo"));
+    EXPECT_EQ(0, base::WriteFile(mnt_path.Append("emptyfile"), "", 0));
+
+    EXPECT_TRUE(
+        WriteSparseFile(mnt_path.Append("fullsparse").value(), 1024 * 1024));
+    EXPECT_TRUE(
+        WriteSparseFile(mnt_path.Append("move-to-sparse").value(), 16 * 1024));
+
+    brillo::Blob zeros(16 * 1024, 0);
+    EXPECT_EQ(static_cast<int>(zeros.size()),
+              base::WriteFile(mnt_path.Append("move-from-sparse"),
+                              reinterpret_cast<const char*>(zeros.data()),
+                              zeros.size()));
+
+    EXPECT_TRUE(
+        WriteByteAtOffset(mnt_path.Append("move-semi-sparse").value(), 4096));
+    EXPECT_TRUE(WriteByteAtOffset(mnt_path.Append("partsparse").value(), 4096));
+
+    EXPECT_TRUE(
+        base::CopyFile(mnt_path.Append("regular-16k"), mnt_path.Append("tmp")));
+    EXPECT_TRUE(base::Move(mnt_path.Append("tmp"),
+                           mnt_path.Append("link-hard-regular-16k")));
+
+    EXPECT_TRUE(base::DeleteFile(mnt_path.Append("link-short_symlink"), false));
+    EXPECT_TRUE(test_utils::WriteFileString(
+        mnt_path.Append("link-short_symlink").value(), "foobar"));
+
+    brillo::Blob hardtocompress;
+    while (hardtocompress.size() < 3 * kBlockSize) {
+      hardtocompress.insert(hardtocompress.end(),
+                            std::begin(kRandomString), std::end(kRandomString));
+    }
+    EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+                                              b_mnt.c_str()).c_str(),
+                                 hardtocompress.data(),
+                                 hardtocompress.size()));
+  }
+
+  string old_kernel;
+  EXPECT_TRUE(utils::MakeTempFile("old_kernel.XXXXXX",
+                                  &state->old_kernel,
+                                  nullptr));
+
+  string new_kernel;
+  EXPECT_TRUE(utils::MakeTempFile("new_kernel.XXXXXX",
+                                  &state->new_kernel,
+                                  nullptr));
+
+  string result_kernel;
+  EXPECT_TRUE(utils::MakeTempFile("result_kernel.XXXXXX",
+                                  &state->result_kernel,
+                                  nullptr));
+
+  state->kernel_size = kDefaultKernelSize;
+  state->old_kernel_data.resize(kDefaultKernelSize);
+  state->new_kernel_data.resize(state->old_kernel_data.size());
+  state->result_kernel_data.resize(state->old_kernel_data.size());
+  test_utils::FillWithData(&state->old_kernel_data);
+  test_utils::FillWithData(&state->new_kernel_data);
+  test_utils::FillWithData(&state->result_kernel_data);
+
+  // change the new kernel data
+  std::copy(std::begin(kNewData), std::end(kNewData),
+            state->new_kernel_data.begin());
+
+  if (noop) {
+    state->old_kernel_data = state->new_kernel_data;
+  }
+
+  // Write kernels to disk
+  EXPECT_TRUE(utils::WriteFile(state->old_kernel.c_str(),
+                               state->old_kernel_data.data(),
+                               state->old_kernel_data.size()));
+  EXPECT_TRUE(utils::WriteFile(state->new_kernel.c_str(),
+                               state->new_kernel_data.data(),
+                               state->new_kernel_data.size()));
+  EXPECT_TRUE(utils::WriteFile(state->result_kernel.c_str(),
+                               state->result_kernel_data.data(),
+                               state->result_kernel_data.size()));
+
+  EXPECT_TRUE(utils::MakeTempFile("delta.XXXXXX",
+                                  &state->delta_path,
+                                  nullptr));
+  LOG(INFO) << "delta path: " << state->delta_path;
+  {
+    const string private_key =
+        signature_test == kSignatureGenerator
+            ? GetBuildArtifactsPath(kUnittestPrivateKeyPath)
+            : "";
+
+    PayloadGenerationConfig payload_config;
+    payload_config.is_delta = !full_rootfs;
+    payload_config.hard_chunk_size = chunk_size;
+    payload_config.rootfs_partition_size = kRootFSPartitionSize;
+    payload_config.version.major = kChromeOSMajorPayloadVersion;
+    payload_config.version.minor = minor_version;
+    if (!full_rootfs) {
+      payload_config.source.partitions.emplace_back(kLegacyPartitionNameRoot);
+      payload_config.source.partitions.emplace_back(kLegacyPartitionNameKernel);
+      payload_config.source.partitions.front().path = state->a_img;
+      if (!full_kernel)
+        payload_config.source.partitions.back().path = state->old_kernel;
+      payload_config.source.image_info = old_image_info;
+      EXPECT_TRUE(payload_config.source.LoadImageSize());
+      for (PartitionConfig& part : payload_config.source.partitions)
+        EXPECT_TRUE(part.OpenFilesystem());
+    } else {
+      if (payload_config.hard_chunk_size == -1)
+        // Use 1 MiB chunk size for the full unittests.
+        payload_config.hard_chunk_size = 1024 * 1024;
+    }
+    payload_config.target.partitions.emplace_back(kLegacyPartitionNameRoot);
+    payload_config.target.partitions.back().path = state->b_img;
+    payload_config.target.partitions.emplace_back(kLegacyPartitionNameKernel);
+    payload_config.target.partitions.back().path = state->new_kernel;
+    payload_config.target.image_info = new_image_info;
+    EXPECT_TRUE(payload_config.target.LoadImageSize());
+    for (PartitionConfig& part : payload_config.target.partitions)
+      EXPECT_TRUE(part.OpenFilesystem());
+
+    EXPECT_TRUE(payload_config.Validate());
+    EXPECT_TRUE(
+        GenerateUpdatePayloadFile(
+            payload_config,
+            state->delta_path,
+            private_key,
+            &state->metadata_size));
+  }
+  // Extend the "partitions" holding the file system a bit.
+  EXPECT_EQ(0, HANDLE_EINTR(truncate(state->a_img.c_str(),
+                                     state->image_size + 1024 * 1024)));
+  EXPECT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024),
+            utils::FileSize(state->a_img));
+  EXPECT_EQ(0, HANDLE_EINTR(truncate(state->b_img.c_str(),
+                                     state->image_size + 1024 * 1024)));
+  EXPECT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024),
+            utils::FileSize(state->b_img));
+
+  if (signature_test == kSignatureGeneratedPlaceholder ||
+      signature_test == kSignatureGeneratedPlaceholderMismatch) {
+    int signature_size =
+        GetSignatureSize(GetBuildArtifactsPath(kUnittestPrivateKeyPath));
+    LOG(INFO) << "Inserting placeholder signature.";
+    ASSERT_TRUE(InsertSignaturePlaceholder(signature_size, state->delta_path,
+                                           &state->metadata_size));
+
+    if (signature_test == kSignatureGeneratedPlaceholderMismatch) {
+      signature_size -= 1;
+      LOG(INFO) << "Inserting mismatched placeholder signature.";
+      ASSERT_FALSE(InsertSignaturePlaceholder(signature_size, state->delta_path,
+                                              &state->metadata_size));
+      return;
+    }
+  }
+
+  if (signature_test == kSignatureGenerated ||
+      signature_test == kSignatureGeneratedPlaceholder ||
+      signature_test == kSignatureGeneratedPlaceholderMismatch) {
+    // Generate the signed payload and update the metadata size in state to
+    // reflect the new size after adding the signature operation to the
+    // manifest.
+    LOG(INFO) << "Signing payload.";
+    SignGeneratedPayload(state->delta_path, &state->metadata_size);
+  } else if (signature_test == kSignatureGeneratedShell ||
+             signature_test == kSignatureGeneratedShellBadKey ||
+             signature_test == kSignatureGeneratedShellRotateCl1 ||
+             signature_test == kSignatureGeneratedShellRotateCl2) {
+    SignGeneratedShellPayload(signature_test, state->delta_path);
+  }
+}
+
+static void ApplyDeltaFile(bool full_kernel, bool full_rootfs, bool noop,
+                           SignatureTest signature_test, DeltaState* state,
+                           bool hash_checks_mandatory,
+                           OperationHashTest op_hash_test,
+                           DeltaPerformer** performer,
+                           uint32_t minor_version) {
+  // Check the metadata.
+  {
+    DeltaArchiveManifest manifest;
+    EXPECT_TRUE(PayloadSigner::LoadPayloadMetadata(state->delta_path,
+                                                   nullptr,
+                                                   &manifest,
+                                                   nullptr,
+                                                   &state->metadata_size,
+                                                   nullptr));
+    LOG(INFO) << "Metadata size: " << state->metadata_size;
+    EXPECT_TRUE(utils::ReadFile(state->delta_path, &state->delta));
+
+    if (signature_test == kSignatureNone) {
+      EXPECT_FALSE(manifest.has_signatures_offset());
+      EXPECT_FALSE(manifest.has_signatures_size());
+    } else {
+      EXPECT_TRUE(manifest.has_signatures_offset());
+      EXPECT_TRUE(manifest.has_signatures_size());
+      Signatures sigs_message;
+      EXPECT_TRUE(sigs_message.ParseFromArray(
+          &state->delta[state->metadata_size + manifest.signatures_offset()],
+          manifest.signatures_size()));
+      if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+          signature_test == kSignatureGeneratedShellRotateCl2)
+        EXPECT_EQ(2, sigs_message.signatures_size());
+      else
+        EXPECT_EQ(1, sigs_message.signatures_size());
+      const Signatures_Signature& signature = sigs_message.signatures(0);
+      EXPECT_EQ(1U, signature.version());
+
+      uint64_t expected_sig_data_length = 0;
+      vector<string> key_paths{GetBuildArtifactsPath(kUnittestPrivateKeyPath)};
+      if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+          signature_test == kSignatureGeneratedShellRotateCl2) {
+        key_paths.push_back(GetBuildArtifactsPath(kUnittestPrivateKey2Path));
+      }
+      EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+          key_paths,
+          &expected_sig_data_length));
+      EXPECT_EQ(expected_sig_data_length, manifest.signatures_size());
+      EXPECT_FALSE(signature.data().empty());
+    }
+
+    if (noop) {
+      EXPECT_EQ(0, manifest.install_operations_size());
+      EXPECT_EQ(1, manifest.kernel_install_operations_size());
+    }
+
+    if (full_kernel) {
+      EXPECT_FALSE(manifest.has_old_kernel_info());
+    } else {
+      EXPECT_EQ(state->old_kernel_data.size(),
+                manifest.old_kernel_info().size());
+      EXPECT_FALSE(manifest.old_kernel_info().hash().empty());
+    }
+
+    EXPECT_EQ(manifest.new_image_info().channel(), "test-channel");
+    EXPECT_EQ(manifest.new_image_info().board(), "test-board");
+    EXPECT_EQ(manifest.new_image_info().version(), "test-version");
+    EXPECT_EQ(manifest.new_image_info().key(), "test-key");
+    EXPECT_EQ(manifest.new_image_info().build_channel(), "test-build-channel");
+    EXPECT_EQ(manifest.new_image_info().build_version(), "test-build-version");
+
+    if (!full_rootfs) {
+      if (noop) {
+        EXPECT_EQ(manifest.old_image_info().channel(), "test-channel");
+        EXPECT_EQ(manifest.old_image_info().board(), "test-board");
+        EXPECT_EQ(manifest.old_image_info().version(), "test-version");
+        EXPECT_EQ(manifest.old_image_info().key(), "test-key");
+        EXPECT_EQ(manifest.old_image_info().build_channel(),
+                  "test-build-channel");
+        EXPECT_EQ(manifest.old_image_info().build_version(),
+                  "test-build-version");
+      } else {
+        EXPECT_EQ(manifest.old_image_info().channel(), "src-channel");
+        EXPECT_EQ(manifest.old_image_info().board(), "src-board");
+        EXPECT_EQ(manifest.old_image_info().version(), "src-version");
+        EXPECT_EQ(manifest.old_image_info().key(), "src-key");
+        EXPECT_EQ(manifest.old_image_info().build_channel(),
+                  "src-build-channel");
+        EXPECT_EQ(manifest.old_image_info().build_version(),
+                  "src-build-version");
+      }
+    }
+
+
+    if (full_rootfs) {
+      EXPECT_FALSE(manifest.has_old_rootfs_info());
+      EXPECT_FALSE(manifest.has_old_image_info());
+      EXPECT_TRUE(manifest.has_new_image_info());
+    } else {
+      EXPECT_EQ(state->image_size, manifest.old_rootfs_info().size());
+      EXPECT_FALSE(manifest.old_rootfs_info().hash().empty());
+    }
+
+    EXPECT_EQ(state->new_kernel_data.size(), manifest.new_kernel_info().size());
+    EXPECT_EQ(state->image_size, manifest.new_rootfs_info().size());
+
+    EXPECT_FALSE(manifest.new_kernel_info().hash().empty());
+    EXPECT_FALSE(manifest.new_rootfs_info().hash().empty());
+  }
+
+  MockPrefs prefs;
+  EXPECT_CALL(prefs, SetInt64(kPrefsManifestMetadataSize,
+                              state->metadata_size)).WillOnce(Return(true));
+  EXPECT_CALL(prefs, SetInt64(kPrefsManifestSignatureSize, 0))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextOperation, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStateNextOperation, _))
+      .WillOnce(Return(false));
+  EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataOffset, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataLength, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSHA256Context, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignedSHA256Context, _))
+      .WillRepeatedly(Return(true));
+  if (op_hash_test == kValidOperationData && signature_test != kSignatureNone) {
+    EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignatureBlob, _))
+        .WillOnce(Return(true));
+  }
+
+  EXPECT_CALL(state->mock_delegate_, ShouldCancel(_))
+      .WillRepeatedly(Return(false));
+
+  // Update the A image in place.
+  InstallPlan* install_plan = &state->install_plan;
+  install_plan->hash_checks_mandatory = hash_checks_mandatory;
+  install_plan->metadata_size = state->metadata_size;
+  install_plan->payload_type = (full_kernel && full_rootfs)
+                                   ? InstallPayloadType::kFull
+                                   : InstallPayloadType::kDelta;
+  install_plan->source_slot = 0;
+  install_plan->target_slot = 1;
+
+  InstallPlan::Partition root_part;
+  root_part.name = kLegacyPartitionNameRoot;
+
+  InstallPlan::Partition kernel_part;
+  kernel_part.name = kLegacyPartitionNameKernel;
+
+  LOG(INFO) << "Setting payload metadata size in Omaha  = "
+            << state->metadata_size;
+  ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+      state->delta.data(),
+      state->metadata_size,
+      GetBuildArtifactsPath(kUnittestPrivateKeyPath),
+      &install_plan->metadata_signature));
+  EXPECT_FALSE(install_plan->metadata_signature.empty());
+
+  *performer = new DeltaPerformer(&prefs,
+                                  &state->fake_boot_control_,
+                                  &state->fake_hardware_,
+                                  &state->mock_delegate_,
+                                  install_plan);
+  string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath);
+  EXPECT_TRUE(utils::FileExists(public_key_path.c_str()));
+  (*performer)->set_public_key_path(public_key_path);
+  DeltaPerformerIntegrationTest::SetSupportedVersion(*performer, minor_version);
+
+  EXPECT_EQ(static_cast<off_t>(state->image_size),
+            HashCalculator::RawHashOfFile(
+                state->a_img,
+                state->image_size,
+                &root_part.source_hash));
+  EXPECT_TRUE(HashCalculator::RawHashOfData(
+                  state->old_kernel_data,
+                  &kernel_part.source_hash));
+
+  // This partitions are normally filed by the FilesystemVerifierAction with
+  // the source hashes used for deltas.
+  install_plan->partitions = {root_part, kernel_part};
+
+  // With minor version 2, we want the target to be the new image, result_img,
+  // but with version 1, we want to update A in place.
+  string target_root, target_kernel;
+  if (minor_version == kSourceMinorPayloadVersion) {
+    target_root = state->result_img;
+    target_kernel = state->result_kernel;
+  } else {
+    target_root = state->a_img;
+    target_kernel = state->old_kernel;
+  }
+
+  state->fake_boot_control_.SetPartitionDevice(
+      kLegacyPartitionNameRoot, install_plan->source_slot, state->a_img);
+  state->fake_boot_control_.SetPartitionDevice(
+      kLegacyPartitionNameKernel, install_plan->source_slot, state->old_kernel);
+  state->fake_boot_control_.SetPartitionDevice(
+      kLegacyPartitionNameRoot, install_plan->target_slot, target_root);
+  state->fake_boot_control_.SetPartitionDevice(
+      kLegacyPartitionNameKernel, install_plan->target_slot, target_kernel);
+
+  ErrorCode expected_error, actual_error;
+  bool continue_writing;
+  switch (op_hash_test) {
+    case kInvalidOperationData: {
+      // Muck with some random offset post the metadata size so that
+      // some operation hash will result in a mismatch.
+      int some_offset = state->metadata_size + 300;
+      LOG(INFO) << "Tampered value at offset: " << some_offset;
+      state->delta[some_offset]++;
+      expected_error = ErrorCode::kDownloadOperationHashMismatch;
+      continue_writing = false;
+      break;
+    }
+
+    case kValidOperationData:
+    default:
+      // no change.
+      expected_error = ErrorCode::kSuccess;
+      continue_writing = true;
+      break;
+  }
+
+  // Write at some number of bytes per operation. Arbitrarily chose 5.
+  const size_t kBytesPerWrite = 5;
+  for (size_t i = 0; i < state->delta.size(); i += kBytesPerWrite) {
+    size_t count = std::min(state->delta.size() - i, kBytesPerWrite);
+    bool write_succeeded = ((*performer)->Write(&state->delta[i],
+                                                count,
+                                                &actual_error));
+    // Normally write_succeeded should be true every time and
+    // actual_error should be ErrorCode::kSuccess. If so, continue the loop.
+    // But if we seeded an operation hash error above, then write_succeeded
+    // will be false. The failure may happen at any operation n. So, all
+    // Writes until n-1 should succeed and the nth operation will fail with
+    // actual_error. In this case, we should bail out of the loop because
+    // we cannot proceed applying the delta.
+    if (!write_succeeded) {
+      LOG(INFO) << "Write failed. Checking if it failed with expected error";
+      EXPECT_EQ(expected_error, actual_error);
+      if (!continue_writing) {
+        LOG(INFO) << "Cannot continue writing. Bailing out.";
+        break;
+      }
+    }
+
+    EXPECT_EQ(ErrorCode::kSuccess, actual_error);
+  }
+
+  // If we had continued all the way through, Close should succeed.
+  // Otherwise, it should fail. Check appropriately.
+  bool close_result = (*performer)->Close();
+  if (continue_writing)
+    EXPECT_EQ(0, close_result);
+  else
+    EXPECT_LE(0, close_result);
+}
+
+void VerifyPayloadResult(DeltaPerformer* performer,
+                         DeltaState* state,
+                         ErrorCode expected_result,
+                         uint32_t minor_version) {
+  if (!performer) {
+    EXPECT_TRUE(!"Skipping payload verification since performer is null.");
+    return;
+  }
+
+  int expected_times = (expected_result == ErrorCode::kSuccess) ? 1 : 0;
+  EXPECT_CALL(state->mock_delegate_, DownloadComplete()).Times(expected_times);
+
+  LOG(INFO) << "Verifying payload for expected result "
+            << expected_result;
+  EXPECT_EQ(expected_result, performer->VerifyPayload(
+      HashCalculator::HashOfData(state->delta),
+      state->delta.size()));
+  LOG(INFO) << "Verified payload.";
+
+  if (expected_result != ErrorCode::kSuccess) {
+    // no need to verify new partition if VerifyPayload failed.
+    return;
+  }
+
+  brillo::Blob updated_kernel_partition;
+  if (minor_version == kSourceMinorPayloadVersion) {
+    CompareFilesByBlock(state->result_kernel, state->new_kernel,
+                        state->kernel_size);
+    CompareFilesByBlock(state->result_img, state->b_img,
+                        state->image_size);
+    EXPECT_TRUE(utils::ReadFile(state->result_kernel,
+                                &updated_kernel_partition));
+  } else {
+    CompareFilesByBlock(state->old_kernel, state->new_kernel,
+                        state->kernel_size);
+    CompareFilesByBlock(state->a_img, state->b_img,
+                        state->image_size);
+    EXPECT_TRUE(utils::ReadFile(state->old_kernel, &updated_kernel_partition));
+  }
+
+  ASSERT_GE(updated_kernel_partition.size(), arraysize(kNewData));
+  EXPECT_TRUE(std::equal(std::begin(kNewData), std::end(kNewData),
+                         updated_kernel_partition.begin()));
+
+  const auto& partitions = state->install_plan.partitions;
+  EXPECT_EQ(2U, partitions.size());
+  EXPECT_EQ(kLegacyPartitionNameRoot, partitions[0].name);
+  EXPECT_EQ(kLegacyPartitionNameKernel, partitions[1].name);
+
+  EXPECT_EQ(kDefaultKernelSize, partitions[1].target_size);
+  brillo::Blob expected_new_kernel_hash;
+  EXPECT_TRUE(HashCalculator::RawHashOfData(state->new_kernel_data,
+                                            &expected_new_kernel_hash));
+  EXPECT_EQ(expected_new_kernel_hash, partitions[1].target_hash);
+
+  EXPECT_EQ(state->image_size, partitions[0].target_size);
+  brillo::Blob expected_new_rootfs_hash;
+  EXPECT_EQ(static_cast<off_t>(state->image_size),
+            HashCalculator::RawHashOfFile(state->b_img,
+                                          state->image_size,
+                                          &expected_new_rootfs_hash));
+  EXPECT_EQ(expected_new_rootfs_hash, partitions[0].target_hash);
+}
+
+void VerifyPayload(DeltaPerformer* performer,
+                   DeltaState* state,
+                   SignatureTest signature_test,
+                   uint32_t minor_version) {
+  ErrorCode expected_result = ErrorCode::kSuccess;
+  switch (signature_test) {
+    case kSignatureNone:
+      expected_result = ErrorCode::kSignedDeltaPayloadExpectedError;
+      break;
+    case kSignatureGeneratedShellBadKey:
+      expected_result = ErrorCode::kDownloadPayloadPubKeyVerificationError;
+      break;
+    default: break;  // appease gcc
+  }
+
+  VerifyPayloadResult(performer, state, expected_result, minor_version);
+}
+
+void DoSmallImageTest(bool full_kernel, bool full_rootfs, bool noop,
+                      ssize_t chunk_size,
+                      SignatureTest signature_test,
+                      bool hash_checks_mandatory, uint32_t minor_version) {
+  DeltaState state;
+  DeltaPerformer *performer = nullptr;
+  GenerateDeltaFile(full_kernel, full_rootfs, noop, chunk_size,
+                    signature_test, &state, minor_version);
+
+  ScopedPathUnlinker a_img_unlinker(state.a_img);
+  ScopedPathUnlinker b_img_unlinker(state.b_img);
+  ScopedPathUnlinker new_img_unlinker(state.result_img);
+  ScopedPathUnlinker delta_unlinker(state.delta_path);
+  ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+  ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+  ScopedPathUnlinker result_kernel_unlinker(state.result_kernel);
+  ApplyDeltaFile(full_kernel, full_rootfs, noop, signature_test,
+                 &state, hash_checks_mandatory, kValidOperationData,
+                 &performer, minor_version);
+  VerifyPayload(performer, &state, signature_test, minor_version);
+  delete performer;
+}
+
+void DoOperationHashMismatchTest(OperationHashTest op_hash_test,
+                                 bool hash_checks_mandatory) {
+  DeltaState state;
+  uint64_t minor_version = kFullPayloadMinorVersion;
+  GenerateDeltaFile(true, true, false, -1, kSignatureGenerated, &state,
+                    minor_version);
+  ScopedPathUnlinker a_img_unlinker(state.a_img);
+  ScopedPathUnlinker b_img_unlinker(state.b_img);
+  ScopedPathUnlinker delta_unlinker(state.delta_path);
+  ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+  ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+  DeltaPerformer *performer = nullptr;
+  ApplyDeltaFile(true, true, false, kSignatureGenerated, &state,
+                 hash_checks_mandatory, op_hash_test, &performer,
+                 minor_version);
+  delete performer;
+}
+
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignaturePlaceholderTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedPlaceholder,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignaturePlaceholderMismatchTest) {
+  DeltaState state;
+  GenerateDeltaFile(false, false, false, -1,
+                    kSignatureGeneratedPlaceholderMismatch, &state,
+                    kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageChunksTest) {
+  DoSmallImageTest(false, false, false, kBlockSize, kSignatureGenerator,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootFullKernelSmallImageTest) {
+  DoSmallImageTest(true, false, false, -1, kSignatureGenerator,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootFullSmallImageTest) {
+  DoSmallImageTest(true, true, false, -1, kSignatureGenerator,
+                   true, kFullPayloadMinorVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootNoopSmallImageTest) {
+  DoSmallImageTest(false, false, true, -1, kSignatureGenerator,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignNoneTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureNone,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGenerated,
+                   true, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShell,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellBadKeyTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellBadKey,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellRotateCl1Test) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl1,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellRotateCl2Test) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl2,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSourceOpsTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+                   false, kSourceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootMandatoryOperationHashMismatchTest) {
+  DoOperationHashMismatchTest(kInvalidOperationData, true);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/delta_performer_unittest.cc b/update_engine/payload_consumer/delta_performer_unittest.cc
new file mode 100644
index 0000000..09bbb7c
--- /dev/null
+++ b/update_engine/payload_consumer/delta_performer_unittest.cc
@@ -0,0 +1,914 @@
+//
+// 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/payload_consumer/delta_performer.h"
+
+#include <endian.h>
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/mock_download_action.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/bzip.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+using test_utils::GetBuildArtifactsPath;
+using test_utils::System;
+using test_utils::kRandomString;
+using testing::_;
+
+extern const char* kUnittestPrivateKeyPath;
+extern const char* kUnittestPublicKeyPath;
+
+namespace {
+
+const char kBogusMetadataSignature1[] =
+    "awSFIUdUZz2VWFiR+ku0Pj00V7bPQPQFYQSXjEXr3vaw3TE4xHV5CraY3/YrZpBv"
+    "J5z4dSBskoeuaO1TNC/S6E05t+yt36tE4Fh79tMnJ/z9fogBDXWgXLEUyG78IEQr"
+    "YH6/eBsQGT2RJtBgXIXbZ9W+5G9KmGDoPOoiaeNsDuqHiBc/58OFsrxskH8E6vMS"
+    "BmMGGk82mvgzic7ApcoURbCGey1b3Mwne/hPZ/bb9CIyky8Og9IfFMdL2uAweOIR"
+    "fjoTeLYZpt+WN65Vu7jJ0cQN8e1y+2yka5112wpRf/LLtPgiAjEZnsoYpLUd7CoV"
+    "pLRtClp97kN2+tXGNBQqkA==";
+
+#ifdef __ANDROID__
+const char kZlibFingerprintPath[] =
+    "/data/nativetest/update_engine_unittests/zlib_fingerprint";
+#else
+const char kZlibFingerprintPath[] = "/etc/zlib_fingerprint";
+#endif  // __ANDROID__
+
+// Different options that determine what we should fill into the
+// install_plan.metadata_signature to simulate the contents received in the
+// Omaha response.
+enum MetadataSignatureTest {
+  kEmptyMetadataSignature,
+  kInvalidMetadataSignature,
+  kValidMetadataSignature,
+};
+
+// Compressed data without checksum, generated with:
+// echo -n a | xz -9 --check=none | hexdump -v -e '"    " 12/1 "0x%02x, " "\n"'
+const uint8_t kXzCompressedData[] = {
+    0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41,
+    0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+    0x01, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x01,
+    0xad, 0xa6, 0x58, 0x04, 0x06, 0x72, 0x9e, 0x7a, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x59, 0x5a,
+};
+
+// Gzipped 'abc', generated with:
+// echo -n abc | minigzip | hexdump -v -e '"    " 12/1 "0x%02x, " "\n"'
+const uint8_t kSourceGzippedData[] = {
+    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4b, 0x4c,
+    0x4a, 0x06, 0x00, 0xc2, 0x41, 0x24, 0x35, 0x03, 0x00, 0x00, 0x00,
+};
+
+// Gzipped 'def', generated with:
+// echo -n def | minigzip | hexdump -v -e '"    " 12/1 "0x%02x, " "\n"'
+const uint8_t kTargetGzippedData[] = {
+    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4b, 0x49,
+    0x4d, 0x03, 0x00, 0x61, 0xe1, 0xc4, 0x0c, 0x03, 0x00, 0x00, 0x00,
+};
+
+// Imgdiff data, generated with:
+// echo -n abc | minigzip > abc && truncate -s 4096 abc
+// echo -n def | minigzip > def && truncate -s 4096 def
+// imgdiff abc def patch && hexdump -v -e '"    " 12/1 "0x%02x, " "\n"' patch
+const uint8_t kImgdiffData[] = {
+    0x49, 0x4d, 0x47, 0x44, 0x49, 0x46, 0x46, 0x32, 0x03, 0x00, 0x00, 0x00,
+    0x03, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xf1, 0xff,
+    0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x0f,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30, 0x2a, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x5a,
+    0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xc3, 0xc8, 0xfb, 0x1f,
+    0x00, 0x00, 0x01, 0x40, 0x00, 0x5c, 0x00, 0x20, 0x00, 0x30, 0xcd, 0x34,
+    0x12, 0x34, 0x54, 0x60, 0x5c, 0xce, 0x2e, 0xe4, 0x8a, 0x70, 0xa1, 0x21,
+    0x87, 0x91, 0xf6, 0x3e, 0x42, 0x5a, 0x68, 0x39, 0x17, 0x72, 0x45, 0x38,
+    0x50, 0x90, 0x00, 0x00, 0x00, 0x00, 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41,
+    0x59, 0x26, 0x53, 0x59, 0x42, 0x3c, 0xb0, 0xf9, 0x00, 0x00, 0x00, 0x01,
+    0x00, 0x07, 0x00, 0x20, 0x00, 0x21, 0x98, 0x19, 0x84, 0x61, 0x77, 0x24,
+    0x53, 0x85, 0x09, 0x04, 0x23, 0xcb, 0x0f, 0x90, 0x42, 0x53, 0x44, 0x49,
+    0x46, 0x46, 0x34, 0x30, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x0f, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26,
+    0x53, 0x59, 0x6f, 0x02, 0x77, 0xf3, 0x00, 0x00, 0x07, 0x40, 0x41, 0xe0,
+    0x10, 0xc0, 0x00, 0x00, 0x02, 0x20, 0x00, 0x20, 0x00, 0x21, 0x29, 0xa3,
+    0x10, 0x86, 0x03, 0x84, 0x04, 0xae, 0x5f, 0x17, 0x72, 0x45, 0x38, 0x50,
+    0x90, 0x6f, 0x02, 0x77, 0xf3, 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59,
+    0x26, 0x53, 0x59, 0x71, 0x62, 0xbd, 0xa7, 0x00, 0x00, 0x20, 0x40, 0x32,
+    0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x00, 0x48, 0x20, 0x00,
+    0x30, 0xc0, 0x02, 0xa5, 0x19, 0xa5, 0x92, 0x6f, 0xc2, 0x5d, 0xac, 0x0e,
+    0x17, 0x72, 0x45, 0x38, 0x50, 0x90, 0x71, 0x62, 0xbd, 0xa7, 0x42, 0x5a,
+    0x68, 0x39, 0x17, 0x72, 0x45, 0x38, 0x50, 0x90, 0x00, 0x00, 0x00, 0x00,
+};
+
+}  // namespace
+
+class DeltaPerformerTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    install_plan_.source_slot = 0;
+    install_plan_.target_slot = 1;
+    EXPECT_CALL(mock_delegate_, ShouldCancel(_))
+        .WillRepeatedly(testing::Return(false));
+  }
+
+  // Test helper placed where it can easily be friended from DeltaPerformer.
+  void RunManifestValidation(const DeltaArchiveManifest& manifest,
+                             uint64_t major_version,
+                             InstallPayloadType payload_type,
+                             ErrorCode expected) {
+    install_plan_.payload_type = payload_type;
+
+    // The Manifest we are validating.
+    performer_.manifest_.CopyFrom(manifest);
+    performer_.major_payload_version_ = major_version;
+
+    EXPECT_EQ(expected, performer_.ValidateManifest());
+  }
+
+  brillo::Blob GeneratePayload(const brillo::Blob& blob_data,
+                               const vector<AnnotatedOperation>& aops,
+                               bool sign_payload) {
+    return GeneratePayload(blob_data, aops, sign_payload,
+                           DeltaPerformer::kSupportedMajorPayloadVersion,
+                           DeltaPerformer::kSupportedMinorPayloadVersion);
+  }
+
+  brillo::Blob GeneratePayload(const brillo::Blob& blob_data,
+                               const vector<AnnotatedOperation>& aops,
+                               bool sign_payload,
+                               uint64_t major_version,
+                               uint32_t minor_version) {
+    string blob_path;
+    EXPECT_TRUE(utils::MakeTempFile("Blob-XXXXXX", &blob_path, nullptr));
+    ScopedPathUnlinker blob_unlinker(blob_path);
+    EXPECT_TRUE(utils::WriteFile(blob_path.c_str(),
+                                 blob_data.data(),
+                                 blob_data.size()));
+
+    PayloadGenerationConfig config;
+    config.version.major = major_version;
+    config.version.minor = minor_version;
+
+    PayloadFile payload;
+    EXPECT_TRUE(payload.Init(config));
+
+    PartitionConfig old_part(kLegacyPartitionNameRoot);
+    if (minor_version != kFullPayloadMinorVersion) {
+      // When generating a delta payload we need to include the old partition
+      // information to mark it as a delta payload.
+      old_part.path = "/dev/null";
+      old_part.size = 0;
+    }
+    PartitionConfig new_part(kLegacyPartitionNameRoot);
+    new_part.path = "/dev/zero";
+    new_part.size = 1234;
+
+    payload.AddPartition(old_part, new_part, aops);
+
+    // We include a kernel partition without operations.
+    old_part.name = kLegacyPartitionNameKernel;
+    new_part.name = kLegacyPartitionNameKernel;
+    new_part.size = 0;
+    payload.AddPartition(old_part, new_part, {});
+
+    string payload_path;
+    EXPECT_TRUE(utils::MakeTempFile("Payload-XXXXXX", &payload_path, nullptr));
+    ScopedPathUnlinker payload_unlinker(payload_path);
+    string private_key =
+        sign_payload ? GetBuildArtifactsPath(kUnittestPrivateKeyPath) : "";
+    EXPECT_TRUE(payload.WritePayload(
+        payload_path, blob_path, private_key, &install_plan_.metadata_size));
+
+    brillo::Blob payload_data;
+    EXPECT_TRUE(utils::ReadFile(payload_path, &payload_data));
+    return payload_data;
+  }
+
+  // Apply |payload_data| on partition specified in |source_path|.
+  // Expect result of performer_.Write() to be |expect_success|.
+  // Returns the result of the payload application.
+  brillo::Blob ApplyPayload(const brillo::Blob& payload_data,
+                            const string& source_path,
+                            bool expect_success) {
+    return ApplyPayloadToData(payload_data, source_path, brillo::Blob(),
+                              expect_success);
+  }
+
+  // Apply the payload provided in |payload_data| reading from the |source_path|
+  // file and writing the contents to a new partition. The existing data in the
+  // new target file are set to |target_data| before applying the payload.
+  // Expect result of performer_.Write() to be |expect_success|.
+  // Returns the result of the payload application.
+  brillo::Blob ApplyPayloadToData(const brillo::Blob& payload_data,
+                                  const string& source_path,
+                                  const brillo::Blob& target_data,
+                                  bool expect_success) {
+    string new_part;
+    EXPECT_TRUE(utils::MakeTempFile("Partition-XXXXXX", &new_part, nullptr));
+    ScopedPathUnlinker partition_unlinker(new_part);
+    EXPECT_TRUE(utils::WriteFile(new_part.c_str(), target_data.data(),
+                                 target_data.size()));
+
+    // We installed the operations only in the rootfs partition, but the
+    // delta performer needs to access all the partitions.
+    fake_boot_control_.SetPartitionDevice(
+        kLegacyPartitionNameRoot, install_plan_.target_slot, new_part);
+    fake_boot_control_.SetPartitionDevice(
+        kLegacyPartitionNameRoot, install_plan_.source_slot, source_path);
+    fake_boot_control_.SetPartitionDevice(
+        kLegacyPartitionNameKernel, install_plan_.target_slot, "/dev/null");
+    fake_boot_control_.SetPartitionDevice(
+        kLegacyPartitionNameKernel, install_plan_.source_slot, "/dev/null");
+
+    EXPECT_EQ(expect_success,
+              performer_.Write(payload_data.data(), payload_data.size()));
+    EXPECT_EQ(0, performer_.Close());
+
+    brillo::Blob partition_data;
+    EXPECT_TRUE(utils::ReadFile(new_part, &partition_data));
+    return partition_data;
+  }
+
+  // Calls delta performer's Write method by pretending to pass in bytes from a
+  // delta file whose metadata size is actual_metadata_size and tests if all
+  // checks are correctly performed if the install plan contains
+  // expected_metadata_size and that the result of the parsing are as per
+  // hash_checks_mandatory flag.
+  void DoMetadataSizeTest(uint64_t expected_metadata_size,
+                          uint64_t actual_metadata_size,
+                          bool hash_checks_mandatory) {
+    install_plan_.hash_checks_mandatory = hash_checks_mandatory;
+
+    // Set a valid magic string and version number 1.
+    EXPECT_TRUE(performer_.Write("CrAU", 4));
+    uint64_t version = htobe64(kChromeOSMajorPayloadVersion);
+    EXPECT_TRUE(performer_.Write(&version, 8));
+
+    install_plan_.metadata_size = expected_metadata_size;
+    ErrorCode error_code;
+    // When filling in size in manifest, exclude the size of the 20-byte header.
+    uint64_t size_in_manifest = htobe64(actual_metadata_size - 20);
+    bool result = performer_.Write(&size_in_manifest, 8, &error_code);
+    if (expected_metadata_size == actual_metadata_size ||
+        !hash_checks_mandatory) {
+      EXPECT_TRUE(result);
+    } else {
+      EXPECT_FALSE(result);
+      EXPECT_EQ(ErrorCode::kDownloadInvalidMetadataSize, error_code);
+    }
+
+    EXPECT_LT(performer_.Close(), 0);
+  }
+
+  // Generates a valid delta file but tests the delta performer by suppling
+  // different metadata signatures as per metadata_signature_test flag and
+  // sees if the result of the parsing are as per hash_checks_mandatory flag.
+  void DoMetadataSignatureTest(MetadataSignatureTest metadata_signature_test,
+                               bool sign_payload,
+                               bool hash_checks_mandatory) {
+    // Loads the payload and parses the manifest.
+    brillo::Blob payload = GeneratePayload(brillo::Blob(),
+        vector<AnnotatedOperation>(), sign_payload,
+        kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion);
+
+    LOG(INFO) << "Payload size: " << payload.size();
+
+    install_plan_.hash_checks_mandatory = hash_checks_mandatory;
+
+    DeltaPerformer::MetadataParseResult expected_result, actual_result;
+    ErrorCode expected_error, actual_error;
+
+    // Fill up the metadata signature in install plan according to the test.
+    switch (metadata_signature_test) {
+      case kEmptyMetadataSignature:
+        install_plan_.metadata_signature.clear();
+        expected_result = DeltaPerformer::kMetadataParseError;
+        expected_error = ErrorCode::kDownloadMetadataSignatureMissingError;
+        break;
+
+      case kInvalidMetadataSignature:
+        install_plan_.metadata_signature = kBogusMetadataSignature1;
+        expected_result = DeltaPerformer::kMetadataParseError;
+        expected_error = ErrorCode::kDownloadMetadataSignatureMismatch;
+        break;
+
+      case kValidMetadataSignature:
+      default:
+        // Set the install plan's metadata size to be the same as the one
+        // in the manifest so that we pass the metadata size checks. Only
+        // then we can get to manifest signature checks.
+        ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+            payload.data(),
+            install_plan_.metadata_size,
+            GetBuildArtifactsPath(kUnittestPrivateKeyPath),
+            &install_plan_.metadata_signature));
+        EXPECT_FALSE(install_plan_.metadata_signature.empty());
+        expected_result = DeltaPerformer::kMetadataParseSuccess;
+        expected_error = ErrorCode::kSuccess;
+        break;
+    }
+
+    // Ignore the expected result/error if hash checks are not mandatory.
+    if (!hash_checks_mandatory) {
+      expected_result = DeltaPerformer::kMetadataParseSuccess;
+      expected_error = ErrorCode::kSuccess;
+    }
+
+    // Use the public key corresponding to the private key used above to
+    // sign the metadata.
+    string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath);
+    EXPECT_TRUE(utils::FileExists(public_key_path.c_str()));
+    performer_.set_public_key_path(public_key_path);
+
+    // Init actual_error with an invalid value so that we make sure
+    // ParsePayloadMetadata properly populates it in all cases.
+    actual_error = ErrorCode::kUmaReportedMax;
+    actual_result = performer_.ParsePayloadMetadata(payload, &actual_error);
+
+    EXPECT_EQ(expected_result, actual_result);
+    EXPECT_EQ(expected_error, actual_error);
+
+    // Check that the parsed metadata size is what's expected. This test
+    // implicitly confirms that the metadata signature is valid, if required.
+    EXPECT_EQ(install_plan_.metadata_size, performer_.GetMetadataSize());
+  }
+
+  void SetSupportedMajorVersion(uint64_t major_version) {
+    performer_.supported_major_version_ = major_version;
+  }
+  FakePrefs prefs_;
+  InstallPlan install_plan_;
+  FakeBootControl fake_boot_control_;
+  FakeHardware fake_hardware_;
+  MockDownloadActionDelegate mock_delegate_;
+  DeltaPerformer performer_{
+      &prefs_, &fake_boot_control_, &fake_hardware_, &mock_delegate_, &install_plan_};
+};
+
+TEST_F(DeltaPerformerTest, FullPayloadWriteTest) {
+  install_plan_.payload_type = InstallPayloadType::kFull;
+  brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
+                                            std::end(kRandomString));
+  expected_data.resize(4096);  // block size
+  vector<AnnotatedOperation> aops;
+  AnnotatedOperation aop;
+  *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+  aop.op.set_data_offset(0);
+  aop.op.set_data_length(expected_data.size());
+  aop.op.set_type(InstallOperation::REPLACE);
+  aops.push_back(aop);
+
+  brillo::Blob payload_data = GeneratePayload(expected_data, aops, false,
+      kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion);
+
+  EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
+}
+
+TEST_F(DeltaPerformerTest, ShouldCancelTest) {
+  install_plan_.payload_type = InstallPayloadType::kFull;
+  brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
+                                            std::end(kRandomString));
+  expected_data.resize(4096);  // block size
+  vector<AnnotatedOperation> aops;
+  AnnotatedOperation aop;
+  *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+  aop.op.set_data_offset(0);
+  aop.op.set_data_length(expected_data.size());
+  aop.op.set_type(InstallOperation::REPLACE);
+  aops.push_back(aop);
+
+  brillo::Blob payload_data = GeneratePayload(expected_data, aops, false,
+      kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion);
+
+  testing::Mock::VerifyAndClearExpectations(&mock_delegate_);
+  EXPECT_CALL(mock_delegate_, ShouldCancel(_))
+      .WillOnce(
+          testing::DoAll(testing::SetArgumentPointee<0>(ErrorCode::kError),
+                         testing::Return(true)));
+
+  ApplyPayload(payload_data, "/dev/null", false);
+}
+
+TEST_F(DeltaPerformerTest, ReplaceOperationTest) {
+  brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
+                                            std::end(kRandomString));
+  expected_data.resize(4096);  // block size
+  vector<AnnotatedOperation> aops;
+  AnnotatedOperation aop;
+  *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+  aop.op.set_data_offset(0);
+  aop.op.set_data_length(expected_data.size());
+  aop.op.set_type(InstallOperation::REPLACE);
+  aops.push_back(aop);
+
+  brillo::Blob payload_data = GeneratePayload(expected_data, aops, false);
+
+  EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
+}
+
+TEST_F(DeltaPerformerTest, ReplaceBzOperationTest) {
+  brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
+                                            std::end(kRandomString));
+  expected_data.resize(4096);  // block size
+  brillo::Blob bz_data;
+  EXPECT_TRUE(BzipCompress(expected_data, &bz_data));
+
+  vector<AnnotatedOperation> aops;
+  AnnotatedOperation aop;
+  *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+  aop.op.set_data_offset(0);
+  aop.op.set_data_length(bz_data.size());
+  aop.op.set_type(InstallOperation::REPLACE_BZ);
+  aops.push_back(aop);
+
+  brillo::Blob payload_data = GeneratePayload(bz_data, aops, false);
+
+  EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
+}
+
+TEST_F(DeltaPerformerTest, ReplaceXzOperationTest) {
+  brillo::Blob xz_data(std::begin(kXzCompressedData),
+                         std::end(kXzCompressedData));
+  // The compressed xz data contains only a single "a", but the operation should
+  // pad the rest of the two blocks with zeros.
+  brillo::Blob expected_data = brillo::Blob(4096, 0);
+  expected_data[0] = 'a';
+
+  AnnotatedOperation aop;
+  *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+  aop.op.set_data_offset(0);
+  aop.op.set_data_length(xz_data.size());
+  aop.op.set_type(InstallOperation::REPLACE_XZ);
+  vector<AnnotatedOperation> aops = {aop};
+
+  brillo::Blob payload_data = GeneratePayload(xz_data, aops, false);
+
+  EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
+}
+
+TEST_F(DeltaPerformerTest, ZeroOperationTest) {
+  brillo::Blob existing_data = brillo::Blob(4096 * 10, 'a');
+  brillo::Blob expected_data = existing_data;
+  // Blocks 4, 5 and 7 should have zeros instead of 'a' after the operation is
+  // applied.
+  std::fill(expected_data.data() + 4096 * 4, expected_data.data() + 4096 * 6,
+            0);
+  std::fill(expected_data.data() + 4096 * 7, expected_data.data() + 4096 * 8,
+            0);
+
+  AnnotatedOperation aop;
+  *(aop.op.add_dst_extents()) = ExtentForRange(4, 2);
+  *(aop.op.add_dst_extents()) = ExtentForRange(7, 1);
+  aop.op.set_type(InstallOperation::ZERO);
+  vector<AnnotatedOperation> aops = {aop};
+
+  brillo::Blob payload_data = GeneratePayload(brillo::Blob(), aops, false);
+
+  EXPECT_EQ(expected_data,
+            ApplyPayloadToData(payload_data, "/dev/null", existing_data, true));
+}
+
+TEST_F(DeltaPerformerTest, SourceCopyOperationTest) {
+  brillo::Blob expected_data(std::begin(kRandomString),
+                             std::end(kRandomString));
+  expected_data.resize(4096);  // block size
+  AnnotatedOperation aop;
+  *(aop.op.add_src_extents()) = ExtentForRange(0, 1);
+  *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+  aop.op.set_type(InstallOperation::SOURCE_COPY);
+  brillo::Blob src_hash;
+  EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
+  aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
+
+  brillo::Blob payload_data = GeneratePayload(brillo::Blob(), {aop}, false);
+
+  string source_path;
+  EXPECT_TRUE(utils::MakeTempFile("Source-XXXXXX",
+                                  &source_path, nullptr));
+  ScopedPathUnlinker path_unlinker(source_path);
+  EXPECT_TRUE(utils::WriteFile(source_path.c_str(),
+                               expected_data.data(),
+                               expected_data.size()));
+
+  EXPECT_EQ(expected_data, ApplyPayload(payload_data, source_path, true));
+}
+
+TEST_F(DeltaPerformerTest, ImgdiffOperationTest) {
+  brillo::Blob imgdiff_data(std::begin(kImgdiffData), std::end(kImgdiffData));
+
+  AnnotatedOperation aop;
+  *(aop.op.add_src_extents()) = ExtentForRange(0, 1);
+  *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+  aop.op.set_data_offset(0);
+  aop.op.set_data_length(imgdiff_data.size());
+  aop.op.set_type(InstallOperation::IMGDIFF);
+
+  brillo::Blob payload_data = GeneratePayload(imgdiff_data, {aop}, false);
+
+  string source_path;
+  EXPECT_TRUE(utils::MakeTempFile("Source-XXXXXX", &source_path, nullptr));
+  ScopedPathUnlinker path_unlinker(source_path);
+  brillo::Blob source_data(std::begin(kSourceGzippedData),
+                           std::end(kSourceGzippedData));
+  source_data.resize(4096);  // block size
+  EXPECT_TRUE(utils::WriteFile(
+      source_path.c_str(), source_data.data(), source_data.size()));
+
+  brillo::Blob target_data(std::begin(kTargetGzippedData),
+                           std::end(kTargetGzippedData));
+  target_data.resize(4096);  // block size
+  EXPECT_EQ(target_data, ApplyPayload(payload_data, source_path, true));
+}
+
+TEST_F(DeltaPerformerTest, SourceHashMismatchTest) {
+  brillo::Blob expected_data = {'f', 'o', 'o'};
+  brillo::Blob actual_data = {'b', 'a', 'r'};
+  expected_data.resize(4096);  // block size
+  actual_data.resize(4096);    // block size
+
+  AnnotatedOperation aop;
+  *(aop.op.add_src_extents()) = ExtentForRange(0, 1);
+  *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+  aop.op.set_type(InstallOperation::SOURCE_COPY);
+  brillo::Blob src_hash;
+  EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
+  aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
+
+  brillo::Blob payload_data = GeneratePayload(brillo::Blob(), {aop}, false);
+
+  string source_path;
+  EXPECT_TRUE(utils::MakeTempFile("Source-XXXXXX", &source_path, nullptr));
+  ScopedPathUnlinker path_unlinker(source_path);
+  EXPECT_TRUE(utils::WriteFile(source_path.c_str(), actual_data.data(),
+                               actual_data.size()));
+
+  EXPECT_EQ(actual_data, ApplyPayload(payload_data, source_path, false));
+}
+
+TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) {
+  uint64_t test[] = {1, 1, 4, 2, 0, 1};
+  static_assert(arraysize(test) % 2 == 0, "Array size uneven");
+  const uint64_t block_size = 4096;
+  const uint64_t file_length = 4 * block_size - 13;
+
+  google::protobuf::RepeatedPtrField<Extent> extents;
+  for (size_t i = 0; i < arraysize(test); i += 2) {
+    *(extents.Add()) = ExtentForRange(test[i], test[i + 1]);
+  }
+
+  string expected_output = "4096:4096,16384:8192,0:4083";
+  string actual_output;
+  EXPECT_TRUE(DeltaPerformer::ExtentsToBsdiffPositionsString(extents,
+                                                             block_size,
+                                                             file_length,
+                                                             &actual_output));
+  EXPECT_EQ(expected_output, actual_output);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullGoodTest) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  manifest.mutable_new_kernel_info();
+  manifest.mutable_new_rootfs_info();
+  manifest.set_minor_version(kFullPayloadMinorVersion);
+
+  RunManifestValidation(manifest,
+                        kChromeOSMajorPayloadVersion,
+                        InstallPayloadType::kFull,
+                        ErrorCode::kSuccess);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestDeltaGoodTest) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  manifest.mutable_old_kernel_info();
+  manifest.mutable_old_rootfs_info();
+  manifest.mutable_new_kernel_info();
+  manifest.mutable_new_rootfs_info();
+  manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+  RunManifestValidation(manifest,
+                        kChromeOSMajorPayloadVersion,
+                        InstallPayloadType::kDelta,
+                        ErrorCode::kSuccess);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullUnsetMinorVersion) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+
+  RunManifestValidation(manifest,
+                        DeltaPerformer::kSupportedMajorPayloadVersion,
+                        InstallPayloadType::kFull,
+                        ErrorCode::kSuccess);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestDeltaUnsetMinorVersion) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  // Add an empty old_rootfs_info() to trick the DeltaPerformer into think that
+  // this is a delta payload manifest with a missing minor version.
+  manifest.mutable_old_rootfs_info();
+
+  RunManifestValidation(manifest,
+                        DeltaPerformer::kSupportedMajorPayloadVersion,
+                        InstallPayloadType::kDelta,
+                        ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullOldKernelTest) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  manifest.mutable_old_kernel_info();
+  manifest.mutable_new_kernel_info();
+  manifest.mutable_new_rootfs_info();
+  manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+  RunManifestValidation(manifest,
+                        kChromeOSMajorPayloadVersion,
+                        InstallPayloadType::kFull,
+                        ErrorCode::kPayloadMismatchedType);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullOldRootfsTest) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  manifest.mutable_old_rootfs_info();
+  manifest.mutable_new_kernel_info();
+  manifest.mutable_new_rootfs_info();
+  manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+  RunManifestValidation(manifest,
+                        kChromeOSMajorPayloadVersion,
+                        InstallPayloadType::kFull,
+                        ErrorCode::kPayloadMismatchedType);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullPartitionUpdateTest) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  PartitionUpdate* partition = manifest.add_partitions();
+  partition->mutable_old_partition_info();
+  partition->mutable_new_partition_info();
+  manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+  RunManifestValidation(manifest,
+                        kBrilloMajorPayloadVersion,
+                        InstallPayloadType::kFull,
+                        ErrorCode::kPayloadMismatchedType);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestBadMinorVersion) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+
+  // Generate a bad version number.
+  manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion +
+                             10000);
+  // Mark the manifest as a delta payload by setting old_rootfs_info.
+  manifest.mutable_old_rootfs_info();
+
+  RunManifestValidation(manifest,
+                        DeltaPerformer::kSupportedMajorPayloadVersion,
+                        InstallPayloadType::kDelta,
+                        ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeTest) {
+  EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic)));
+
+  uint64_t major_version = htobe64(kBrilloMajorPayloadVersion);
+  EXPECT_TRUE(performer_.Write(&major_version, 8));
+
+  uint64_t manifest_size = rand() % 256;
+  uint64_t manifest_size_be = htobe64(manifest_size);
+  EXPECT_TRUE(performer_.Write(&manifest_size_be, 8));
+
+  uint32_t metadata_signature_size = rand() % 256;
+  uint32_t metadata_signature_size_be = htobe32(metadata_signature_size);
+  EXPECT_TRUE(performer_.Write(&metadata_signature_size_be, 4));
+
+  EXPECT_LT(performer_.Close(), 0);
+
+  EXPECT_TRUE(performer_.IsHeaderParsed());
+  EXPECT_EQ(kBrilloMajorPayloadVersion, performer_.GetMajorVersion());
+  uint64_t manifest_offset;
+  EXPECT_TRUE(performer_.GetManifestOffset(&manifest_offset));
+  EXPECT_EQ(24U, manifest_offset);  // 4 + 8 + 8 + 4
+  EXPECT_EQ(manifest_offset + manifest_size, performer_.GetMetadataSize());
+  EXPECT_EQ(metadata_signature_size, performer_.metadata_signature_size_);
+}
+
+TEST_F(DeltaPerformerTest, BrilloVerifyMetadataSignatureTest) {
+  brillo::Blob payload_data = GeneratePayload({}, {}, true,
+                                              kBrilloMajorPayloadVersion,
+                                              kSourceMinorPayloadVersion);
+  install_plan_.hash_checks_mandatory = true;
+  // Just set these value so that we can use ValidateMetadataSignature directly.
+  performer_.major_payload_version_ = kBrilloMajorPayloadVersion;
+  performer_.metadata_size_ = install_plan_.metadata_size;
+  uint64_t signature_length;
+  EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+      {GetBuildArtifactsPath(kUnittestPrivateKeyPath)}, &signature_length));
+  performer_.metadata_signature_size_ = signature_length;
+  performer_.set_public_key_path(GetBuildArtifactsPath(kUnittestPublicKeyPath));
+  EXPECT_EQ(ErrorCode::kSuccess,
+            performer_.ValidateMetadataSignature(payload_data));
+}
+
+TEST_F(DeltaPerformerTest, BadDeltaMagicTest) {
+  EXPECT_TRUE(performer_.Write("junk", 4));
+  EXPECT_FALSE(performer_.Write("morejunk", 8));
+  EXPECT_LT(performer_.Close(), 0);
+}
+
+TEST_F(DeltaPerformerTest, MissingMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(0, 75456, true);
+}
+
+TEST_F(DeltaPerformerTest, MissingNonMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(0, 123456, false);
+}
+
+TEST_F(DeltaPerformerTest, InvalidMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(13000, 140000, true);
+}
+
+TEST_F(DeltaPerformerTest, InvalidNonMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(40000, 50000, false);
+}
+
+TEST_F(DeltaPerformerTest, ValidMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(85376, 85376, true);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryEmptyMetadataSignatureTest) {
+  DoMetadataSignatureTest(kEmptyMetadataSignature, true, true);
+}
+
+TEST_F(DeltaPerformerTest, NonMandatoryEmptyMetadataSignatureTest) {
+  DoMetadataSignatureTest(kEmptyMetadataSignature, true, false);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryInvalidMetadataSignatureTest) {
+  DoMetadataSignatureTest(kInvalidMetadataSignature, true, true);
+}
+
+TEST_F(DeltaPerformerTest, NonMandatoryInvalidMetadataSignatureTest) {
+  DoMetadataSignatureTest(kInvalidMetadataSignature, true, false);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryValidMetadataSignature1Test) {
+  DoMetadataSignatureTest(kValidMetadataSignature, false, true);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryValidMetadataSignature2Test) {
+  DoMetadataSignatureTest(kValidMetadataSignature, true, true);
+}
+
+TEST_F(DeltaPerformerTest, NonMandatoryValidMetadataSignatureTest) {
+  DoMetadataSignatureTest(kValidMetadataSignature, true, false);
+}
+
+TEST_F(DeltaPerformerTest, UsePublicKeyFromResponse) {
+  base::FilePath key_path;
+
+  // The result of the GetPublicKeyResponse() method is based on three things
+  //
+  //  1. Whether it's an official build; and
+  //  2. Whether the Public RSA key to be used is in the root filesystem; and
+  //  3. Whether the response has a public key
+  //
+  // We test all eight combinations to ensure that we only use the
+  // public key in the response if
+  //
+  //  a. it's not an official build; and
+  //  b. there is no key in the root filesystem.
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  string non_existing_file = temp_dir.path().Append("non-existing").value();
+  string existing_file = temp_dir.path().Append("existing").value();
+  EXPECT_EQ(0, System(base::StringPrintf("touch %s", existing_file.c_str())));
+
+  // Non-official build, non-existing public-key, key in response -> true
+  fake_hardware_.SetIsOfficialBuild(false);
+  performer_.public_key_path_ = non_existing_file;
+  install_plan_.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
+  EXPECT_TRUE(performer_.GetPublicKeyFromResponse(&key_path));
+  EXPECT_FALSE(key_path.empty());
+  EXPECT_EQ(unlink(key_path.value().c_str()), 0);
+  // Same with official build -> false
+  fake_hardware_.SetIsOfficialBuild(true);
+  EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+  // Non-official build, existing public-key, key in response -> false
+  fake_hardware_.SetIsOfficialBuild(false);
+  performer_.public_key_path_ = existing_file;
+  install_plan_.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
+  EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+  // Same with official build -> false
+  fake_hardware_.SetIsOfficialBuild(true);
+  EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+  // Non-official build, non-existing public-key, no key in response -> false
+  fake_hardware_.SetIsOfficialBuild(false);
+  performer_.public_key_path_ = non_existing_file;
+  install_plan_.public_key_rsa = "";
+  EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+  // Same with official build -> false
+  fake_hardware_.SetIsOfficialBuild(true);
+  EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+  // Non-official build, existing public-key, no key in response -> false
+  fake_hardware_.SetIsOfficialBuild(false);
+  performer_.public_key_path_ = existing_file;
+  install_plan_.public_key_rsa = "";
+  EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+  // Same with official build -> false
+  fake_hardware_.SetIsOfficialBuild(true);
+  EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+  // Non-official build, non-existing public-key, key in response
+  // but invalid base64 -> false
+  fake_hardware_.SetIsOfficialBuild(false);
+  performer_.public_key_path_ = non_existing_file;
+  install_plan_.public_key_rsa = "not-valid-base64";
+  EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+}
+
+TEST_F(DeltaPerformerTest, ConfVersionsMatch) {
+  // Test that the versions in update_engine.conf that is installed to the
+  // image match the supported delta versions in the update engine.
+  uint32_t minor_version;
+  brillo::KeyValueStore store;
+  EXPECT_TRUE(store.Load(GetBuildArtifactsPath().Append("update_engine.conf")));
+  EXPECT_TRUE(utils::GetMinorVersion(store, &minor_version));
+  EXPECT_EQ(DeltaPerformer::kSupportedMinorPayloadVersion, minor_version);
+
+  string major_version_str;
+  uint64_t major_version;
+  EXPECT_TRUE(store.GetString("PAYLOAD_MAJOR_VERSION", &major_version_str));
+  EXPECT_TRUE(base::StringToUint64(major_version_str, &major_version));
+  EXPECT_EQ(DeltaPerformer::kSupportedMajorPayloadVersion, major_version);
+}
+
+// Test that we recognize our own zlib compressor implementation as supported.
+// All other equivalent implementations should be added to
+// kCompatibleZlibFingerprint.
+TEST_F(DeltaPerformerTest, ZlibFingerprintMatch) {
+  string fingerprint;
+  EXPECT_TRUE(base::ReadFileToString(base::FilePath(kZlibFingerprintPath),
+                                     &fingerprint));
+  EXPECT_TRUE(utils::IsZlibCompatible(fingerprint));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/download_action.cc b/update_engine/payload_consumer/download_action.cc
new file mode 100644
index 0000000..084848e
--- /dev/null
+++ b/update_engine/payload_consumer/download_action.cc
@@ -0,0 +1,329 @@
+//
+// 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/payload_consumer/download_action.h"
+
+#include <errno.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/error_code_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+
+using base::FilePath;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+DownloadAction::DownloadAction(PrefsInterface* prefs,
+                               BootControlInterface* boot_control,
+                               HardwareInterface* hardware,
+                               SystemState* system_state,
+                               HttpFetcher* http_fetcher)
+    : prefs_(prefs),
+      boot_control_(boot_control),
+      hardware_(hardware),
+      system_state_(system_state),
+      http_fetcher_(http_fetcher),
+      writer_(nullptr),
+      code_(ErrorCode::kSuccess),
+      delegate_(nullptr),
+      bytes_received_(0),
+      p2p_sharing_fd_(-1),
+      p2p_visible_(true) {
+}
+
+DownloadAction::~DownloadAction() {}
+
+void DownloadAction::CloseP2PSharingFd(bool delete_p2p_file) {
+  if (p2p_sharing_fd_ != -1) {
+    if (close(p2p_sharing_fd_) != 0) {
+      PLOG(ERROR) << "Error closing p2p sharing fd";
+    }
+    p2p_sharing_fd_ = -1;
+  }
+
+  if (delete_p2p_file) {
+    FilePath path =
+      system_state_->p2p_manager()->FileGetPath(p2p_file_id_);
+    if (unlink(path.value().c_str()) != 0) {
+      PLOG(ERROR) << "Error deleting p2p file " << path.value();
+    } else {
+      LOG(INFO) << "Deleted p2p file " << path.value();
+    }
+  }
+
+  // Don't use p2p from this point onwards.
+  p2p_file_id_.clear();
+}
+
+bool DownloadAction::SetupP2PSharingFd() {
+  P2PManager *p2p_manager = system_state_->p2p_manager();
+
+  if (!p2p_manager->FileShare(p2p_file_id_, install_plan_.payload_size)) {
+    LOG(ERROR) << "Unable to share file via p2p";
+    CloseP2PSharingFd(true);  // delete p2p file
+    return false;
+  }
+
+  // File has already been created (and allocated, xattrs been
+  // populated etc.) by FileShare() so just open it for writing.
+  FilePath path = p2p_manager->FileGetPath(p2p_file_id_);
+  p2p_sharing_fd_ = open(path.value().c_str(), O_WRONLY);
+  if (p2p_sharing_fd_ == -1) {
+    PLOG(ERROR) << "Error opening file " << path.value();
+    CloseP2PSharingFd(true);  // Delete p2p file.
+    return false;
+  }
+
+  // Ensure file to share is world-readable, otherwise
+  // p2p-server and p2p-http-server can't access it.
+  //
+  // (Q: Why doesn't the file have mode 0644 already? A: Because
+  // the process-wide umask is set to 0700 in main.cc.)
+  if (fchmod(p2p_sharing_fd_, 0644) != 0) {
+    PLOG(ERROR) << "Error setting mode 0644 on " << path.value();
+    CloseP2PSharingFd(true);  // Delete p2p file.
+    return false;
+  }
+
+  // All good.
+  LOG(INFO) << "Writing payload contents to " << path.value();
+  p2p_manager->FileGetVisible(p2p_file_id_, &p2p_visible_);
+  return true;
+}
+
+void DownloadAction::WriteToP2PFile(const void* data,
+                                    size_t length,
+                                    off_t file_offset) {
+  if (p2p_sharing_fd_ == -1) {
+    if (!SetupP2PSharingFd())
+      return;
+  }
+
+  // Check that the file is at least |file_offset| bytes long - if
+  // it's not something is wrong and we must immediately delete the
+  // file to avoid propagating this problem to other peers.
+  //
+  // How can this happen? It could be that we're resuming an update
+  // after a system crash... in this case, it could be that
+  //
+  //  1. the p2p file didn't get properly synced to stable storage; or
+  //  2. the file was deleted at bootup (it's in /var/cache after all); or
+  //  3. other reasons
+  off_t p2p_size = utils::FileSize(p2p_sharing_fd_);
+  if (p2p_size < 0) {
+    PLOG(ERROR) << "Error getting file status for p2p file";
+    CloseP2PSharingFd(true);  // Delete p2p file.
+    return;
+  }
+  if (p2p_size < file_offset) {
+    LOG(ERROR) << "Wanting to write to file offset " << file_offset
+               << " but existing p2p file is only " << p2p_size
+               << " bytes.";
+    CloseP2PSharingFd(true);  // Delete p2p file.
+    return;
+  }
+
+  off_t cur_file_offset = lseek(p2p_sharing_fd_, file_offset, SEEK_SET);
+  if (cur_file_offset != static_cast<off_t>(file_offset)) {
+    PLOG(ERROR) << "Error seeking to position "
+                << file_offset << " in p2p file";
+    CloseP2PSharingFd(true);  // Delete p2p file.
+  } else {
+    // OK, seeking worked, now write the data
+    ssize_t bytes_written = write(p2p_sharing_fd_, data, length);
+    if (bytes_written != static_cast<ssize_t>(length)) {
+      PLOG(ERROR) << "Error writing "
+                  << length << " bytes at file offset "
+                  << file_offset << " in p2p file";
+      CloseP2PSharingFd(true);  // Delete p2p file.
+    }
+  }
+}
+
+void DownloadAction::PerformAction() {
+  http_fetcher_->set_delegate(this);
+
+  // Get the InstallPlan and read it
+  CHECK(HasInputObject());
+  install_plan_ = GetInputObject();
+  bytes_received_ = 0;
+
+  install_plan_.Dump();
+
+  LOG(INFO) << "Marking new slot as unbootable";
+  if (!boot_control_->MarkSlotUnbootable(install_plan_.target_slot)) {
+    LOG(WARNING) << "Unable to mark new slot "
+                 << BootControlInterface::SlotName(install_plan_.target_slot)
+                 << ". Proceeding with the update anyway.";
+  }
+
+  if (writer_) {
+    LOG(INFO) << "Using writer for test.";
+  } else {
+    delta_performer_.reset(new DeltaPerformer(
+        prefs_, boot_control_, hardware_, delegate_, &install_plan_));
+    writer_ = delta_performer_.get();
+  }
+  download_active_ = true;
+
+  if (system_state_ != nullptr) {
+    const PayloadStateInterface* payload_state = system_state_->payload_state();
+    string file_id = utils::CalculateP2PFileId(install_plan_.payload_hash,
+                                               install_plan_.payload_size);
+    if (payload_state->GetUsingP2PForSharing()) {
+      // If we're sharing the update, store the file_id to convey
+      // that we should write to the file.
+      p2p_file_id_ = file_id;
+      LOG(INFO) << "p2p file id: " << p2p_file_id_;
+    } else {
+      // Even if we're not sharing the update, it could be that
+      // there's a partial file from a previous attempt with the same
+      // hash. If this is the case, we NEED to clean it up otherwise
+      // we're essentially timing out other peers downloading from us
+      // (since we're never going to complete the file).
+      FilePath path = system_state_->p2p_manager()->FileGetPath(file_id);
+      if (!path.empty()) {
+        if (unlink(path.value().c_str()) != 0) {
+          PLOG(ERROR) << "Error deleting p2p file " << path.value();
+        } else {
+          LOG(INFO) << "Deleting partial p2p file " << path.value()
+                    << " since we're not using p2p to share.";
+        }
+      }
+    }
+
+    // Tweak timeouts on the HTTP fetcher if we're downloading from a
+    // local peer.
+    if (payload_state->GetUsingP2PForDownloading() &&
+        payload_state->GetP2PUrl() == install_plan_.download_url) {
+      LOG(INFO) << "Tweaking HTTP fetcher since we're downloading via p2p";
+      http_fetcher_->set_low_speed_limit(kDownloadP2PLowSpeedLimitBps,
+                                         kDownloadP2PLowSpeedTimeSeconds);
+      http_fetcher_->set_max_retry_count(kDownloadP2PMaxRetryCount);
+      http_fetcher_->set_connect_timeout(kDownloadP2PConnectTimeoutSeconds);
+    }
+  }
+
+  http_fetcher_->BeginTransfer(install_plan_.download_url);
+}
+
+void DownloadAction::SuspendAction() {
+  http_fetcher_->Pause();
+}
+
+void DownloadAction::ResumeAction() {
+  http_fetcher_->Unpause();
+}
+
+void DownloadAction::TerminateProcessing() {
+  if (writer_) {
+    writer_->Close();
+    writer_ = nullptr;
+  }
+  download_active_ = false;
+  CloseP2PSharingFd(false);  // Keep p2p file.
+  // Terminates the transfer. The action is terminated, if necessary, when the
+  // TransferTerminated callback is received.
+  http_fetcher_->TerminateTransfer();
+}
+
+void DownloadAction::SeekToOffset(off_t offset) {
+  bytes_received_ = offset;
+}
+
+void DownloadAction::ReceivedBytes(HttpFetcher* fetcher,
+                                   const void* bytes,
+                                   size_t length) {
+  // Note that bytes_received_ is the current offset.
+  if (!p2p_file_id_.empty()) {
+    WriteToP2PFile(bytes, length, bytes_received_);
+  }
+
+  bytes_received_ += length;
+  if (delegate_ && download_active_) {
+    delegate_->BytesReceived(
+        length, bytes_received_, install_plan_.payload_size);
+  }
+  if (writer_ && !writer_->Write(bytes, length, &code_)) {
+    LOG(ERROR) << "Error " << utils::ErrorCodeToString(code_) << " (" << code_
+               << ") in DeltaPerformer's Write method when "
+               << "processing the received payload -- Terminating processing";
+    // Delete p2p file, if applicable.
+    if (!p2p_file_id_.empty())
+      CloseP2PSharingFd(true);
+    // Don't tell the action processor that the action is complete until we get
+    // the TransferTerminated callback. Otherwise, this and the HTTP fetcher
+    // objects may get destroyed before all callbacks are complete.
+    TerminateProcessing();
+    return;
+  }
+
+  // Call p2p_manager_->FileMakeVisible() when we've successfully
+  // verified the manifest!
+  if (!p2p_visible_ && system_state_ && delta_performer_.get() &&
+      delta_performer_->IsManifestValid()) {
+    LOG(INFO) << "Manifest has been validated. Making p2p file visible.";
+    system_state_->p2p_manager()->FileMakeVisible(p2p_file_id_);
+    p2p_visible_ = true;
+  }
+}
+
+void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
+  if (writer_) {
+    LOG_IF(WARNING, writer_->Close() != 0) << "Error closing the writer.";
+    writer_ = nullptr;
+  }
+  download_active_ = false;
+  ErrorCode code =
+      successful ? ErrorCode::kSuccess : ErrorCode::kDownloadTransferError;
+  if (code == ErrorCode::kSuccess && delta_performer_.get()) {
+    code = delta_performer_->VerifyPayload(install_plan_.payload_hash,
+                                           install_plan_.payload_size);
+    if (code != ErrorCode::kSuccess) {
+      LOG(ERROR) << "Download of " << install_plan_.download_url
+                 << " failed due to payload verification error.";
+      // Delete p2p file, if applicable.
+      if (!p2p_file_id_.empty())
+        CloseP2PSharingFd(true);
+    }
+  }
+
+  // Write the path to the output pipe if we're successful.
+  if (code == ErrorCode::kSuccess && HasOutputPipe())
+    SetOutputObject(install_plan_);
+  processor_->ActionComplete(this, code);
+}
+
+void DownloadAction::TransferTerminated(HttpFetcher *fetcher) {
+  if (code_ != ErrorCode::kSuccess) {
+    processor_->ActionComplete(this, code_);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/download_action.h b/update_engine/payload_consumer/download_action.h
new file mode 100644
index 0000000..285930a
--- /dev/null
+++ b/update_engine/payload_consumer/download_action.h
@@ -0,0 +1,183 @@
+//
+// 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_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/http_fetcher.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/system_state.h"
+
+// The Download Action downloads a specified url to disk. The url should point
+// to an update in a delta payload format. The payload will be piped into a
+// DeltaPerformer that will apply the delta to the disk.
+
+namespace chromeos_update_engine {
+
+class DownloadActionDelegate {
+ public:
+  virtual ~DownloadActionDelegate() = default;
+
+  // Called periodically after bytes are received. This method will be invoked
+  // only if the DownloadAction is running. |bytes_progressed| is the number of
+  // bytes downloaded since the last call of this method, |bytes_received|
+  // the number of bytes downloaded thus far and |total| is the number of bytes
+  // expected.
+  virtual void BytesReceived(uint64_t bytes_progressed,
+                             uint64_t bytes_received,
+                             uint64_t total) = 0;
+
+  // Returns whether the download should be canceled, in which case the
+  // |cancel_reason| error should be set to the reason why the download was
+  // canceled.
+  virtual bool ShouldCancel(ErrorCode* cancel_reason) = 0;
+
+  // Called once the complete payload has been downloaded. Note that any errors
+  // while applying or downloading the partial payload will result in this
+  // method not being called.
+  virtual void DownloadComplete() = 0;
+};
+
+class PrefsInterface;
+
+class DownloadAction : public InstallPlanAction,
+                       public HttpFetcherDelegate {
+ public:
+  // Debugging/logging
+  static std::string StaticType() { return "DownloadAction"; }
+
+  // Takes ownership of the passed in HttpFetcher. Useful for testing.
+  // A good calling pattern is:
+  // DownloadAction(prefs, boot_contol, hardware, system_state,
+  //                new WhateverHttpFetcher);
+  DownloadAction(PrefsInterface* prefs,
+                 BootControlInterface* boot_control,
+                 HardwareInterface* hardware,
+                 SystemState* system_state,
+                 HttpFetcher* http_fetcher);
+  ~DownloadAction() override;
+
+  // InstallPlanAction overrides.
+  void PerformAction() override;
+  void SuspendAction() override;
+  void ResumeAction() override;
+  void TerminateProcessing() override;
+  std::string Type() const override { return StaticType(); }
+
+  // Testing
+  void SetTestFileWriter(FileWriter* writer) {
+    writer_ = writer;
+  }
+
+  int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
+  // HttpFetcherDelegate methods (see http_fetcher.h)
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override;
+  void SeekToOffset(off_t offset) override;
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override;
+  void TransferTerminated(HttpFetcher* fetcher) override;
+
+  DownloadActionDelegate* delegate() const { return delegate_; }
+  void set_delegate(DownloadActionDelegate* delegate) {
+    delegate_ = delegate;
+  }
+
+  HttpFetcher* http_fetcher() { return http_fetcher_.get(); }
+
+  // Returns the p2p file id for the file being written or the empty
+  // string if we're not writing to a p2p file.
+  std::string p2p_file_id() { return p2p_file_id_; }
+
+ private:
+  // Closes the file descriptor for the p2p file being written and
+  // clears |p2p_file_id_| to indicate that we're no longer sharing
+  // the file. If |delete_p2p_file| is True, also deletes the file.
+  // If there is no p2p file descriptor, this method does nothing.
+  void CloseP2PSharingFd(bool delete_p2p_file);
+
+  // Starts sharing the p2p file. Must be called before
+  // WriteToP2PFile(). Returns True if this worked.
+  bool SetupP2PSharingFd();
+
+  // Writes |length| bytes of payload from |data| into |file_offset|
+  // of the p2p file. Also does sanity checks; for example ensures we
+  // don't end up with a file with holes in it.
+  //
+  // This method does nothing if SetupP2PSharingFd() hasn't been
+  // called or if CloseP2PSharingFd() has been called.
+  void WriteToP2PFile(const void* data, size_t length, off_t file_offset);
+
+  // The InstallPlan passed in
+  InstallPlan install_plan_;
+
+  // SystemState required pointers.
+  PrefsInterface* prefs_;
+  BootControlInterface* boot_control_;
+  HardwareInterface* hardware_;
+
+  // Global context for the system.
+  SystemState* system_state_;
+
+  // Pointer to the HttpFetcher that does the http work.
+  std::unique_ptr<HttpFetcher> http_fetcher_;
+
+  // The FileWriter that downloaded data should be written to. It will
+  // either point to *decompressing_file_writer_ or *delta_performer_.
+  FileWriter* writer_;
+
+  std::unique_ptr<DeltaPerformer> delta_performer_;
+
+  // Used by TransferTerminated to figure if this action terminated itself or
+  // was terminated by the action processor.
+  ErrorCode code_;
+
+  // For reporting status to outsiders
+  DownloadActionDelegate* delegate_;
+  uint64_t bytes_received_;
+  bool download_active_{false};
+
+  // The file-id for the file we're sharing or the empty string
+  // if we're not using p2p to share.
+  std::string p2p_file_id_;
+
+  // The file descriptor for the p2p file used for caching the payload or -1
+  // if we're not using p2p to share.
+  int p2p_sharing_fd_;
+
+  // Set to |false| if p2p file is not visible.
+  bool p2p_visible_;
+
+  DISALLOW_COPY_AND_ASSIGN(DownloadAction);
+};
+
+// We want to be sure that we're compiled with large file support on linux,
+// just in case we find ourselves downloading large images.
+static_assert(8 == sizeof(off_t), "off_t not 64 bit");
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_
diff --git a/update_engine/payload_consumer/download_action_unittest.cc b/update_engine/payload_consumer/download_action_unittest.cc
new file mode 100644
index 0000000..5e9ef5c
--- /dev/null
+++ b/update_engine/payload_consumer/download_action_unittest.cc
@@ -0,0 +1,628 @@
+//
+// 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/payload_consumer/download_action.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.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/location.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/mock_prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/fake_p2p_manager_configuration.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/payload_consumer/mock_download_action.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+
+namespace chromeos_update_engine {
+
+using base::FilePath;
+using base::ReadFileToString;
+using base::WriteFile;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using test_utils::ScopedTempFile;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Return;
+using testing::_;
+
+class DownloadActionTest : public ::testing::Test { };
+
+namespace {
+
+class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  explicit DownloadActionTestProcessorDelegate(ErrorCode expected_code)
+      : processing_done_called_(false),
+        expected_code_(expected_code) {}
+  ~DownloadActionTestProcessorDelegate() override {
+    EXPECT_TRUE(processing_done_called_);
+  }
+  void ProcessingDone(const ActionProcessor* processor,
+                      ErrorCode code) override {
+    brillo::MessageLoop::current()->BreakLoop();
+    brillo::Blob found_data;
+    ASSERT_TRUE(utils::ReadFile(path_, &found_data));
+    if (expected_code_ != ErrorCode::kDownloadWriteError) {
+      ASSERT_EQ(expected_data_.size(), found_data.size());
+      for (unsigned i = 0; i < expected_data_.size(); i++) {
+        EXPECT_EQ(expected_data_[i], found_data[i]);
+      }
+    }
+    processing_done_called_ = true;
+  }
+
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) override {
+    const string type = action->Type();
+    if (type == DownloadAction::StaticType()) {
+      EXPECT_EQ(expected_code_, code);
+    } else {
+      EXPECT_EQ(ErrorCode::kSuccess, code);
+    }
+  }
+
+  string path_;
+  brillo::Blob expected_data_;
+  bool processing_done_called_;
+  ErrorCode expected_code_;
+};
+
+class TestDirectFileWriter : public DirectFileWriter {
+ public:
+  TestDirectFileWriter() : fail_write_(0), current_write_(0) {}
+  void set_fail_write(int fail_write) { fail_write_ = fail_write; }
+
+  virtual bool Write(const void* bytes, size_t count) {
+    if (++current_write_ == fail_write_) {
+      return false;
+    }
+    return DirectFileWriter::Write(bytes, count);
+  }
+
+ private:
+  // If positive, fail on the |fail_write_| call to Write.
+  int fail_write_;
+  int current_write_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+                             MockHttpFetcher* http_fetcher) {
+  processor->StartProcessing();
+  http_fetcher->SetOffset(1);
+}
+
+void TestWithData(const brillo::Blob& data,
+                  int fail_write,
+                  bool use_download_delegate) {
+  brillo::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+  FakeSystemState fake_system_state;
+
+  // TODO(adlr): see if we need a different file for build bots
+  ScopedTempFile output_temp_file;
+  TestDirectFileWriter writer;
+  EXPECT_EQ(
+      0, writer.Open(output_temp_file.path().c_str(), O_WRONLY | O_CREAT, 0));
+  writer.set_fail_write(fail_write);
+
+  // We pull off the first byte from data and seek past it.
+  string hash = HashCalculator::HashOfBytes(&data[1], data.size() - 1);
+  uint64_t size = data.size();
+  InstallPlan install_plan;
+  install_plan.payload_type = InstallPayloadType::kDelta;
+  install_plan.payload_size = size;
+  install_plan.payload_hash = hash;
+  install_plan.source_slot = 0;
+  install_plan.target_slot = 1;
+  // We mark both slots as bootable. Only the target slot should be unbootable
+  // after the download starts.
+  fake_system_state.fake_boot_control()->SetSlotBootable(
+      install_plan.source_slot, true);
+  fake_system_state.fake_boot_control()->SetSlotBootable(
+      install_plan.target_slot, true);
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+  MockPrefs prefs;
+  MockHttpFetcher* http_fetcher = new MockHttpFetcher(data.data(),
+                                                      data.size(),
+                                                      nullptr);
+  // takes ownership of passed in HttpFetcher
+  DownloadAction download_action(&prefs,
+                                 fake_system_state.boot_control(),
+                                 fake_system_state.hardware(),
+                                 &fake_system_state,
+                                 http_fetcher);
+  download_action.SetTestFileWriter(&writer);
+  BondActions(&feeder_action, &download_action);
+  MockDownloadActionDelegate download_delegate;
+  if (use_download_delegate) {
+    InSequence s;
+    download_action.set_delegate(&download_delegate);
+    if (data.size() > kMockHttpFetcherChunkSize)
+      EXPECT_CALL(download_delegate,
+                  BytesReceived(_, 1 + kMockHttpFetcherChunkSize, _));
+    EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(AtLeast(1));
+  }
+  ErrorCode expected_code = ErrorCode::kSuccess;
+  if (fail_write > 0)
+    expected_code = ErrorCode::kDownloadWriteError;
+  DownloadActionTestProcessorDelegate delegate(expected_code);
+  delegate.expected_data_ = brillo::Blob(data.begin() + 1, data.end());
+  delegate.path_ = output_temp_file.path();
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&download_action);
+
+  loop.PostTask(FROM_HERE,
+                base::Bind(&StartProcessorInRunLoop, &processor, http_fetcher));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+
+  EXPECT_TRUE(fake_system_state.fake_boot_control()->IsSlotBootable(
+      install_plan.source_slot));
+  EXPECT_FALSE(fake_system_state.fake_boot_control()->IsSlotBootable(
+      install_plan.target_slot));
+}
+}  // namespace
+
+TEST(DownloadActionTest, SimpleTest) {
+  brillo::Blob small;
+  const char* foo = "foo";
+  small.insert(small.end(), foo, foo + strlen(foo));
+  TestWithData(small,
+               0,  // fail_write
+               true);  // use_download_delegate
+}
+
+TEST(DownloadActionTest, LargeTest) {
+  brillo::Blob big(5 * kMockHttpFetcherChunkSize);
+  char c = '0';
+  for (unsigned int i = 0; i < big.size(); i++) {
+    big[i] = c;
+    c = ('9' == c) ? '0' : c + 1;
+  }
+  TestWithData(big,
+               0,  // fail_write
+               true);  // use_download_delegate
+}
+
+TEST(DownloadActionTest, FailWriteTest) {
+  brillo::Blob big(5 * kMockHttpFetcherChunkSize);
+  char c = '0';
+  for (unsigned int i = 0; i < big.size(); i++) {
+    big[i] = c;
+    c = ('9' == c) ? '0' : c + 1;
+  }
+  TestWithData(big,
+               2,  // fail_write
+               true);  // use_download_delegate
+}
+
+TEST(DownloadActionTest, NoDownloadDelegateTest) {
+  brillo::Blob small;
+  const char* foo = "foofoo";
+  small.insert(small.end(), foo, foo + strlen(foo));
+  TestWithData(small,
+               0,  // fail_write
+               false);  // use_download_delegate
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  void ProcessingStopped(const ActionProcessor* processor) {
+    brillo::MessageLoop::current()->BreakLoop();
+  }
+};
+
+void TerminateEarlyTestStarter(ActionProcessor* processor) {
+  processor->StartProcessing();
+  CHECK(processor->IsRunning());
+  processor->StopProcessing();
+}
+
+void TestTerminateEarly(bool use_download_delegate) {
+  brillo::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  brillo::Blob data(kMockHttpFetcherChunkSize +
+                      kMockHttpFetcherChunkSize / 2);
+  memset(data.data(), 0, data.size());
+
+  ScopedTempFile temp_file;
+  {
+    DirectFileWriter writer;
+    EXPECT_EQ(0, writer.Open(temp_file.path().c_str(), O_WRONLY | O_CREAT, 0));
+
+    // takes ownership of passed in HttpFetcher
+    ObjectFeederAction<InstallPlan> feeder_action;
+    InstallPlan install_plan;
+    feeder_action.set_obj(install_plan);
+    FakeSystemState fake_system_state_;
+    MockPrefs prefs;
+    DownloadAction download_action(
+        &prefs,
+        fake_system_state_.boot_control(),
+        fake_system_state_.hardware(),
+        &fake_system_state_,
+        new MockHttpFetcher(data.data(), data.size(), nullptr));
+    download_action.SetTestFileWriter(&writer);
+    MockDownloadActionDelegate download_delegate;
+    if (use_download_delegate) {
+      download_action.set_delegate(&download_delegate);
+      EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(0);
+    }
+    TerminateEarlyTestProcessorDelegate delegate;
+    ActionProcessor processor;
+    processor.set_delegate(&delegate);
+    processor.EnqueueAction(&feeder_action);
+    processor.EnqueueAction(&download_action);
+    BondActions(&feeder_action, &download_action);
+
+    loop.PostTask(FROM_HERE,
+                  base::Bind(&TerminateEarlyTestStarter, &processor));
+    loop.Run();
+    EXPECT_FALSE(loop.PendingTasks());
+  }
+
+  // 1 or 0 chunks should have come through
+  const off_t resulting_file_size(utils::FileSize(temp_file.path()));
+  EXPECT_GE(resulting_file_size, 0);
+  if (resulting_file_size != 0)
+    EXPECT_EQ(kMockHttpFetcherChunkSize,
+              static_cast<size_t>(resulting_file_size));
+}
+
+}  // namespace
+
+TEST(DownloadActionTest, TerminateEarlyTest) {
+  TestTerminateEarly(true);
+}
+
+TEST(DownloadActionTest, TerminateEarlyNoDownloadDelegateTest) {
+  TestTerminateEarly(false);
+}
+
+class DownloadActionTestAction;
+
+template<>
+class ActionTraits<DownloadActionTestAction> {
+ public:
+  typedef InstallPlan OutputObjectType;
+  typedef InstallPlan InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class DownloadActionTestAction : public Action<DownloadActionTestAction> {
+ public:
+  DownloadActionTestAction() : did_run_(false) {}
+  typedef InstallPlan InputObjectType;
+  typedef InstallPlan OutputObjectType;
+  ActionPipe<InstallPlan>* in_pipe() { return in_pipe_.get(); }
+  ActionPipe<InstallPlan>* out_pipe() { return out_pipe_.get(); }
+  ActionProcessor* processor() { return processor_; }
+  void PerformAction() {
+    did_run_ = true;
+    ASSERT_TRUE(HasInputObject());
+    EXPECT_TRUE(expected_input_object_ == GetInputObject());
+    ASSERT_TRUE(processor());
+    processor()->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  string Type() const { return "DownloadActionTestAction"; }
+  InstallPlan expected_input_object_;
+  bool did_run_;
+};
+
+namespace {
+// This class is an ActionProcessorDelegate that simply terminates the
+// run loop when the ActionProcessor has completed processing. It's used
+// only by the test PassObjectOutTest.
+class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+    brillo::MessageLoop::current()->BreakLoop();
+  }
+};
+
+}  // namespace
+
+TEST(DownloadActionTest, PassObjectOutTest) {
+  brillo::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  DirectFileWriter writer;
+  EXPECT_EQ(0, writer.Open("/dev/null", O_WRONLY | O_CREAT, 0));
+
+  // takes ownership of passed in HttpFetcher
+  InstallPlan install_plan;
+  install_plan.payload_size = 1;
+  install_plan.payload_hash = HashCalculator::HashOfString("x");
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+  MockPrefs prefs;
+  FakeSystemState fake_system_state_;
+  DownloadAction download_action(&prefs,
+                                 fake_system_state_.boot_control(),
+                                 fake_system_state_.hardware(),
+                                 &fake_system_state_,
+                                 new MockHttpFetcher("x", 1, nullptr));
+  download_action.SetTestFileWriter(&writer);
+
+  DownloadActionTestAction test_action;
+  test_action.expected_input_object_ = install_plan;
+  BondActions(&feeder_action, &download_action);
+  BondActions(&download_action, &test_action);
+
+  ActionProcessor processor;
+  PassObjectOutTestProcessorDelegate delegate;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&download_action);
+  processor.EnqueueAction(&test_action);
+
+  loop.PostTask(
+      FROM_HERE,
+      base::Bind(
+          [](ActionProcessor* processor) { processor->StartProcessing(); },
+          base::Unretained(&processor)));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+
+  EXPECT_EQ(true, test_action.did_run_);
+}
+
+// Test fixture for P2P tests.
+class P2PDownloadActionTest : public testing::Test {
+ protected:
+  P2PDownloadActionTest()
+    : start_at_offset_(0),
+      fake_um_(fake_system_state_.fake_clock()) {}
+
+  ~P2PDownloadActionTest() override {}
+
+  // Derived from testing::Test.
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  // Derived from testing::Test.
+  void TearDown() override {
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  // To be called by tests to setup the download. The
+  // |starting_offset| parameter is for where to resume.
+  void SetupDownload(off_t starting_offset) {
+    start_at_offset_ = starting_offset;
+    // Prepare data 10 kB of data.
+    data_.clear();
+    for (unsigned int i = 0; i < 10 * 1000; i++)
+      data_ += 'a' + (i % 25);
+
+    // Setup p2p.
+    FakeP2PManagerConfiguration *test_conf = new FakeP2PManagerConfiguration();
+    p2p_manager_.reset(P2PManager::Construct(
+        test_conf, nullptr, &fake_um_, "cros_au", 3,
+        base::TimeDelta::FromDays(5)));
+    fake_system_state_.set_p2p_manager(p2p_manager_.get());
+  }
+
+  // To be called by tests to perform the download. The
+  // |use_p2p_to_share| parameter is used to indicate whether the
+  // payload should be shared via p2p.
+  void StartDownload(bool use_p2p_to_share) {
+    EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+                GetUsingP2PForSharing())
+        .WillRepeatedly(Return(use_p2p_to_share));
+
+    ScopedTempFile output_temp_file;
+    TestDirectFileWriter writer;
+    EXPECT_EQ(
+        0, writer.Open(output_temp_file.path().c_str(), O_WRONLY | O_CREAT, 0));
+    InstallPlan install_plan;
+    install_plan.payload_size = data_.length();
+    install_plan.payload_hash = "1234hash";
+    ObjectFeederAction<InstallPlan> feeder_action;
+    feeder_action.set_obj(install_plan);
+    MockPrefs prefs;
+    http_fetcher_ = new MockHttpFetcher(data_.c_str(),
+                                        data_.length(),
+                                        nullptr);
+    // Note that DownloadAction takes ownership of the passed in HttpFetcher.
+    download_action_.reset(new DownloadAction(&prefs,
+                                              fake_system_state_.boot_control(),
+                                              fake_system_state_.hardware(),
+                                              &fake_system_state_,
+                                              http_fetcher_));
+    download_action_->SetTestFileWriter(&writer);
+    BondActions(&feeder_action, download_action_.get());
+    DownloadActionTestProcessorDelegate delegate(ErrorCode::kSuccess);
+    delegate.expected_data_ = brillo::Blob(data_.begin() + start_at_offset_,
+                                           data_.end());
+    delegate.path_ = output_temp_file.path();
+    processor_.set_delegate(&delegate);
+    processor_.EnqueueAction(&feeder_action);
+    processor_.EnqueueAction(download_action_.get());
+
+    loop_.PostTask(FROM_HERE, base::Bind(
+        &P2PDownloadActionTest::StartProcessorInRunLoopForP2P,
+        base::Unretained(this)));
+    loop_.Run();
+  }
+
+  // Mainloop used to make StartDownload() synchronous.
+  brillo::FakeMessageLoop loop_{nullptr};
+
+  // The DownloadAction instance under test.
+  unique_ptr<DownloadAction> download_action_;
+
+  // The HttpFetcher used in the test.
+  MockHttpFetcher* http_fetcher_;
+
+  // The P2PManager used in the test.
+  unique_ptr<P2PManager> p2p_manager_;
+
+  // The ActionProcessor used for running the actions.
+  ActionProcessor processor_;
+
+  // A fake system state.
+  FakeSystemState fake_system_state_;
+
+  // The data being downloaded.
+  string data_;
+
+ private:
+  // Callback used in StartDownload() method.
+  void StartProcessorInRunLoopForP2P() {
+    processor_.StartProcessing();
+    http_fetcher_->SetOffset(start_at_offset_);
+  }
+
+  // The requested starting offset passed to SetupDownload().
+  off_t start_at_offset_;
+
+  chromeos_update_manager::FakeUpdateManager fake_um_;
+};
+
+TEST_F(P2PDownloadActionTest, IsWrittenTo) {
+  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  SetupDownload(0);     // starting_offset
+  StartDownload(true);  // use_p2p_to_share
+
+  // Check the p2p file and its content matches what was sent.
+  string file_id = download_action_->p2p_file_id();
+  EXPECT_NE("", file_id);
+  EXPECT_EQ(static_cast<int>(data_.length()),
+            p2p_manager_->FileGetSize(file_id));
+  EXPECT_EQ(static_cast<int>(data_.length()),
+            p2p_manager_->FileGetExpectedSize(file_id));
+  string p2p_file_contents;
+  EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+                               &p2p_file_contents));
+  EXPECT_EQ(data_, p2p_file_contents);
+}
+
+TEST_F(P2PDownloadActionTest, DeleteIfHoleExists) {
+  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  SetupDownload(1000);  // starting_offset
+  StartDownload(true);  // use_p2p_to_share
+
+  // DownloadAction should convey that the file is not being shared.
+  // and that we don't have any p2p files.
+  EXPECT_EQ(download_action_->p2p_file_id(), "");
+  EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+TEST_F(P2PDownloadActionTest, CanAppend) {
+  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  SetupDownload(1000);  // starting_offset
+
+  // Prepare the file with existing data before starting to write to
+  // it via DownloadAction.
+  string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+  ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+  string existing_data;
+  for (unsigned int i = 0; i < 1000; i++)
+    existing_data += '0' + (i % 10);
+  ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+                      1000), 1000);
+
+  StartDownload(true);  // use_p2p_to_share
+
+  // DownloadAction should convey the same file_id and the file should
+  // have the expected size.
+  EXPECT_EQ(download_action_->p2p_file_id(), file_id);
+  EXPECT_EQ(static_cast<ssize_t>(data_.length()),
+            p2p_manager_->FileGetSize(file_id));
+  EXPECT_EQ(static_cast<ssize_t>(data_.length()),
+            p2p_manager_->FileGetExpectedSize(file_id));
+  string p2p_file_contents;
+  // Check that the first 1000 bytes wasn't touched and that we
+  // appended the remaining as appropriate.
+  EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+                               &p2p_file_contents));
+  EXPECT_EQ(existing_data, p2p_file_contents.substr(0, 1000));
+  EXPECT_EQ(data_.substr(1000), p2p_file_contents.substr(1000));
+}
+
+TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) {
+  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  SetupDownload(1000);  // starting_offset
+
+  // Prepare the file with all existing data before starting to write
+  // to it via DownloadAction.
+  string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+  ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+  string existing_data;
+  for (unsigned int i = 0; i < 1000; i++)
+    existing_data += '0' + (i % 10);
+  ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+                      1000), 1000);
+
+  // Check that the file is there.
+  EXPECT_EQ(1000, p2p_manager_->FileGetSize(file_id));
+  EXPECT_EQ(1, p2p_manager_->CountSharedFiles());
+
+  StartDownload(false);  // use_p2p_to_share
+
+  // DownloadAction should have deleted the p2p file. Check that it's gone.
+  EXPECT_EQ(-1, p2p_manager_->FileGetSize(file_id));
+  EXPECT_EQ(0, p2p_manager_->CountSharedFiles());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/extent_writer.cc b/update_engine/payload_consumer/extent_writer.cc
new file mode 100644
index 0000000..5501e22
--- /dev/null
+++ b/update_engine/payload_consumer/extent_writer.cc
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2009 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/payload_consumer/extent_writer.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::min;
+
+namespace chromeos_update_engine {
+
+bool DirectExtentWriter::Write(const void* bytes, size_t count) {
+  if (count == 0)
+    return true;
+  const char* c_bytes = reinterpret_cast<const char*>(bytes);
+  size_t bytes_written = 0;
+  while (count - bytes_written > 0) {
+    TEST_AND_RETURN_FALSE(next_extent_index_ < extents_.size());
+    uint64_t bytes_remaining_next_extent =
+        extents_[next_extent_index_].num_blocks() * block_size_ -
+        extent_bytes_written_;
+    CHECK_NE(bytes_remaining_next_extent, static_cast<uint64_t>(0));
+    size_t bytes_to_write =
+        static_cast<size_t>(min(static_cast<uint64_t>(count - bytes_written),
+                                bytes_remaining_next_extent));
+    TEST_AND_RETURN_FALSE(bytes_to_write > 0);
+
+    if (extents_[next_extent_index_].start_block() != kSparseHole) {
+      const off64_t offset =
+          extents_[next_extent_index_].start_block() * block_size_ +
+          extent_bytes_written_;
+      TEST_AND_RETURN_FALSE_ERRNO(fd_->Seek(offset, SEEK_SET) !=
+                                  static_cast<off64_t>(-1));
+      TEST_AND_RETURN_FALSE(
+          utils::WriteAll(fd_, c_bytes + bytes_written, bytes_to_write));
+    }
+    bytes_written += bytes_to_write;
+    extent_bytes_written_ += bytes_to_write;
+    if (bytes_remaining_next_extent == bytes_to_write) {
+      // We filled this extent
+      CHECK_EQ(extent_bytes_written_,
+               extents_[next_extent_index_].num_blocks() * block_size_);
+      // move to next extent
+      extent_bytes_written_ = 0;
+      next_extent_index_++;
+    }
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/extent_writer.h b/update_engine/payload_consumer/extent_writer.h
new file mode 100644
index 0000000..6484ebf
--- /dev/null
+++ b/update_engine/payload_consumer/extent_writer.h
@@ -0,0 +1,134 @@
+//
+// Copyright (C) 2009 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_PAYLOAD_CONSUMER_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_EXTENT_WRITER_H_
+
+#include <vector>
+
+#include <base/logging.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/update_metadata.pb.h"
+
+// ExtentWriter is an abstract class which synchronously writes to a given
+// file descriptor at the extents given.
+
+namespace chromeos_update_engine {
+
+class ExtentWriter {
+ public:
+  ExtentWriter() = default;
+  virtual ~ExtentWriter() {
+    LOG_IF(ERROR, !end_called_) << "End() not called on ExtentWriter.";
+  }
+
+  // Returns true on success.
+  virtual bool Init(FileDescriptorPtr fd,
+                    const std::vector<Extent>& extents,
+                    uint32_t block_size) = 0;
+
+  // Returns true on success.
+  virtual bool Write(const void* bytes, size_t count) = 0;
+
+  // Should be called when all writing is complete. Returns true on success.
+  // The fd is not closed. Caller is responsible for closing it.
+  bool End() {
+    end_called_ = true;
+    return EndImpl();
+  }
+  virtual bool EndImpl() = 0;
+ private:
+  bool end_called_{false};
+};
+
+// DirectExtentWriter is probably the simplest ExtentWriter implementation.
+// It writes the data directly into the extents.
+
+class DirectExtentWriter : public ExtentWriter {
+ public:
+  DirectExtentWriter() = default;
+  ~DirectExtentWriter() override = default;
+
+  bool Init(FileDescriptorPtr fd,
+            const std::vector<Extent>& extents,
+            uint32_t block_size) override {
+    fd_ = fd;
+    block_size_ = block_size;
+    extents_ = extents;
+    return true;
+  }
+  bool Write(const void* bytes, size_t count) override;
+  bool EndImpl() override { return true; }
+
+ private:
+  FileDescriptorPtr fd_{nullptr};
+
+  size_t block_size_{0};
+  // Bytes written into next_extent_index_ thus far
+  uint64_t extent_bytes_written_{0};
+  std::vector<Extent> extents_;
+  // The next call to write should correspond to extents_[next_extent_index_]
+  std::vector<Extent>::size_type next_extent_index_{0};
+};
+
+// Takes an underlying ExtentWriter to which all operations are delegated.
+// When End() is called, ZeroPadExtentWriter ensures that the total number
+// of bytes written is a multiple of block_size_. If not, it writes zeros
+// to pad as needed.
+
+class ZeroPadExtentWriter : public ExtentWriter {
+ public:
+  explicit ZeroPadExtentWriter(
+      std::unique_ptr<ExtentWriter> underlying_extent_writer)
+      : underlying_extent_writer_(std::move(underlying_extent_writer)) {}
+  ~ZeroPadExtentWriter() override = default;
+
+  bool Init(FileDescriptorPtr fd,
+            const std::vector<Extent>& extents,
+            uint32_t block_size) override {
+    block_size_ = block_size;
+    return underlying_extent_writer_->Init(fd, extents, block_size);
+  }
+  bool Write(const void* bytes, size_t count) override {
+    if (underlying_extent_writer_->Write(bytes, count)) {
+      bytes_written_mod_block_size_ += count;
+      bytes_written_mod_block_size_ %= block_size_;
+      return true;
+    }
+    return false;
+  }
+  bool EndImpl() override {
+    if (bytes_written_mod_block_size_) {
+      const size_t write_size = block_size_ - bytes_written_mod_block_size_;
+      brillo::Blob zeros(write_size, 0);
+      TEST_AND_RETURN_FALSE(underlying_extent_writer_->Write(zeros.data(),
+                                                             write_size));
+    }
+    return underlying_extent_writer_->End();
+  }
+
+ private:
+  std::unique_ptr<ExtentWriter> underlying_extent_writer_;
+  size_t block_size_{0};
+  size_t bytes_written_mod_block_size_{0};
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_EXTENT_WRITER_H_
diff --git a/update_engine/payload_consumer/extent_writer_unittest.cc b/update_engine/payload_consumer/extent_writer_unittest.cc
new file mode 100644
index 0000000..24d238e
--- /dev/null
+++ b/update_engine/payload_consumer/extent_writer_unittest.cc
@@ -0,0 +1,266 @@
+//
+// Copyright (C) 2009 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/payload_consumer/extent_writer.h"
+
+#include <fcntl.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.h>
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using chromeos_update_engine::test_utils::ExpectVectorsEq;
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+static_assert(sizeof(off_t) == 8, "off_t not 64 bit");
+
+namespace {
+const size_t kBlockSize = 4096;
+}
+
+class ExtentWriterTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    fd_.reset(new EintrSafeFileDescriptor);
+    ASSERT_TRUE(fd_->Open(temp_file_.path().c_str(), O_RDWR, 0600));
+  }
+  void TearDown() override {
+    fd_->Close();
+  }
+
+  // Writes data to an extent writer in 'chunk_size' chunks with
+  // the first chunk of size first_chunk_size. It calculates what the
+  // resultant file should look like and ensure that the extent writer
+  // wrote the file correctly.
+  void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+  void TestZeroPad(bool aligned_size);
+
+  FileDescriptorPtr fd_;
+  test_utils::ScopedTempFile temp_file_{"ExtentWriterTest-file.XXXXXX"};
+};
+
+TEST_F(ExtentWriterTest, SimpleTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  const string bytes = "1234";
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+  EXPECT_TRUE(direct_writer.Write(bytes.data(), bytes.size()));
+  EXPECT_TRUE(direct_writer.End());
+
+  EXPECT_EQ(static_cast<off_t>(kBlockSize + bytes.size()),
+            utils::FileSize(temp_file_.path()));
+
+  brillo::Blob result_file;
+  EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &result_file));
+
+  brillo::Blob expected_file(kBlockSize);
+  expected_file.insert(expected_file.end(),
+                       bytes.data(), bytes.data() + bytes.size());
+  ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroLengthTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+  EXPECT_TRUE(direct_writer.Write(nullptr, 0));
+  EXPECT_TRUE(direct_writer.End());
+}
+
+TEST_F(ExtentWriterTest, OverflowExtentTest) {
+  WriteAlignedExtents(kBlockSize * 3, kBlockSize * 3);
+}
+
+TEST_F(ExtentWriterTest, UnalignedWriteTest) {
+  WriteAlignedExtents(7, 7);
+}
+
+TEST_F(ExtentWriterTest, LargeUnalignedWriteTest) {
+  WriteAlignedExtents(kBlockSize * 2, kBlockSize / 2);
+}
+
+void ExtentWriterTest::WriteAlignedExtents(size_t chunk_size,
+                                           size_t first_chunk_size) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(2);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  brillo::Blob data(kBlockSize * 3);
+  test_utils::FillWithData(&data);
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+  size_t bytes_written = 0;
+  while (bytes_written < data.size()) {
+    size_t bytes_to_write = min(data.size() - bytes_written, chunk_size);
+    if (bytes_written == 0) {
+      bytes_to_write = min(data.size() - bytes_written, first_chunk_size);
+    }
+    EXPECT_TRUE(direct_writer.Write(&data[bytes_written], bytes_to_write));
+    bytes_written += bytes_to_write;
+  }
+  EXPECT_TRUE(direct_writer.End());
+
+  EXPECT_EQ(static_cast<off_t>(data.size()),
+            utils::FileSize(temp_file_.path()));
+
+  brillo::Blob result_file;
+  EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &result_file));
+
+  brillo::Blob expected_file;
+  expected_file.insert(expected_file.end(),
+                       data.begin() + kBlockSize,
+                       data.begin() + kBlockSize * 2);
+  expected_file.insert(expected_file.end(),
+                       data.begin(), data.begin() + kBlockSize);
+  expected_file.insert(expected_file.end(),
+                       data.begin() + kBlockSize * 2, data.end());
+  ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadNullTest) {
+  TestZeroPad(true);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadFillTest) {
+  TestZeroPad(false);
+}
+
+void ExtentWriterTest::TestZeroPad(bool aligned_size) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  brillo::Blob data(kBlockSize * 2);
+  test_utils::FillWithData(&data);
+
+  ZeroPadExtentWriter zero_pad_writer(
+      brillo::make_unique_ptr(new DirectExtentWriter()));
+
+  EXPECT_TRUE(zero_pad_writer.Init(fd_, extents, kBlockSize));
+  size_t bytes_to_write = data.size();
+  const size_t missing_bytes = (aligned_size ? 0 : 9);
+  bytes_to_write -= missing_bytes;
+  fd_->Seek(kBlockSize - missing_bytes, SEEK_SET);
+  EXPECT_EQ(3, fd_->Write("xxx", 3));
+  ASSERT_TRUE(zero_pad_writer.Write(data.data(), bytes_to_write));
+  EXPECT_TRUE(zero_pad_writer.End());
+
+  EXPECT_EQ(static_cast<off_t>(data.size()),
+            utils::FileSize(temp_file_.path()));
+
+  brillo::Blob result_file;
+  EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &result_file));
+
+  brillo::Blob expected_file;
+  expected_file.insert(expected_file.end(),
+                       data.begin() + kBlockSize,
+                       data.begin() + kBlockSize * 2);
+  expected_file.insert(expected_file.end(),
+                       data.begin(), data.begin() + kBlockSize);
+  if (missing_bytes) {
+    memset(&expected_file[kBlockSize - missing_bytes], 0, missing_bytes);
+  }
+
+  ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, SparseFileTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(kSparseHole);
+  extent.set_num_blocks(2);
+  extents.push_back(extent);
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  const int block_count = 4;
+  const int on_disk_count = 2;
+
+  brillo::Blob data(17);
+  test_utils::FillWithData(&data);
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+  size_t bytes_written = 0;
+  while (bytes_written < (block_count * kBlockSize)) {
+    size_t bytes_to_write = min(block_count * kBlockSize - bytes_written,
+                                data.size());
+    EXPECT_TRUE(direct_writer.Write(data.data(), bytes_to_write));
+    bytes_written += bytes_to_write;
+  }
+  EXPECT_TRUE(direct_writer.End());
+
+  // check file size, then data inside
+  ASSERT_EQ(static_cast<off_t>(2 * kBlockSize),
+            utils::FileSize(temp_file_.path()));
+
+  brillo::Blob resultant_data;
+  EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &resultant_data));
+
+  // Create expected data
+  brillo::Blob expected_data(on_disk_count * kBlockSize);
+  brillo::Blob big(block_count * kBlockSize);
+  for (brillo::Blob::size_type i = 0; i < big.size(); i++) {
+    big[i] = data[i % data.size()];
+  }
+  memcpy(&expected_data[kBlockSize], &big[0], kBlockSize);
+  memcpy(&expected_data[0], &big[3 * kBlockSize], kBlockSize);
+  ExpectVectorsEq(expected_data, resultant_data);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/fake_extent_writer.h b/update_engine/payload_consumer/fake_extent_writer.h
new file mode 100644
index 0000000..762c6d5
--- /dev/null
+++ b/update_engine/payload_consumer/fake_extent_writer.h
@@ -0,0 +1,71 @@
+//
+// 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_PAYLOAD_CONSUMER_FAKE_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_EXTENT_WRITER_H_
+
+#include <memory>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/extent_writer.h"
+
+namespace chromeos_update_engine {
+
+// FakeExtentWriter is a concrete ExtentWriter subclass that keeps track of all
+// the written data, useful for testing.
+class FakeExtentWriter : public ExtentWriter {
+ public:
+  FakeExtentWriter() = default;
+  ~FakeExtentWriter() override = default;
+
+  // ExtentWriter overrides.
+  bool Init(FileDescriptorPtr /* fd */,
+            const std::vector<Extent>& /* extents */,
+            uint32_t /* block_size */) override {
+    init_called_ = true;
+    return true;
+  };
+  bool Write(const void* bytes, size_t count) override {
+    if (!init_called_ || end_called_)
+      return false;
+    written_data_.insert(written_data_.end(),
+                         reinterpret_cast<const uint8_t*>(bytes),
+                         reinterpret_cast<const uint8_t*>(bytes) + count);
+    return true;
+  }
+  bool EndImpl() override {
+    end_called_ = true;
+    return true;
+  }
+
+  // Fake methods.
+  bool InitCalled() { return init_called_; }
+  bool EndCalled() { return end_called_; }
+  brillo::Blob WrittenData() { return written_data_; }
+
+ private:
+  bool init_called_{false};
+  bool end_called_{false};
+  brillo::Blob written_data_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeExtentWriter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_EXTENT_WRITER_H_
diff --git a/update_engine/payload_consumer/file_descriptor.cc b/update_engine/payload_consumer/file_descriptor.cc
new file mode 100644
index 0000000..8a23dea
--- /dev/null
+++ b/update_engine/payload_consumer/file_descriptor.cc
@@ -0,0 +1,138 @@
+//
+// 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/payload_consumer/file_descriptor.h"
+
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <base/posix/eintr_wrapper.h>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+  CHECK_EQ(fd_, -1);
+  return ((fd_ = HANDLE_EINTR(open(path, flags, mode))) >= 0);
+}
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags) {
+  CHECK_EQ(fd_, -1);
+  return ((fd_ = HANDLE_EINTR(open(path, flags))) >= 0);
+}
+
+ssize_t EintrSafeFileDescriptor::Read(void* buf, size_t count) {
+  CHECK_GE(fd_, 0);
+  return HANDLE_EINTR(read(fd_, buf, count));
+}
+
+ssize_t EintrSafeFileDescriptor::Write(const void* buf, size_t count) {
+  CHECK_GE(fd_, 0);
+
+  // Attempt repeated writes, as long as some progress is being made.
+  char* char_buf = const_cast<char*>(reinterpret_cast<const char*>(buf));
+  ssize_t written = 0;
+  while (count > 0) {
+    ssize_t ret = HANDLE_EINTR(write(fd_, char_buf, count));
+
+    // Fail on either an error or no progress.
+    if (ret <= 0)
+      return (written ? written : ret);
+    written += ret;
+    count -= ret;
+    char_buf += ret;
+  }
+  return written;
+}
+
+off64_t EintrSafeFileDescriptor::Seek(off64_t offset, int whence) {
+  CHECK_GE(fd_, 0);
+  return lseek64(fd_, offset, whence);
+}
+
+uint64_t EintrSafeFileDescriptor::BlockDevSize() {
+  if (fd_ < 0)
+    return 0;
+  struct stat stbuf;
+  if (fstat(fd_, &stbuf) < 0) {
+    PLOG(ERROR) << "Error stat-ing fd " << fd_;
+    return 0;
+  }
+  if (!S_ISBLK(stbuf.st_mode))
+    return 0;
+  off_t block_size = utils::BlockDevSize(fd_);
+  return block_size < 0 ? 0 : block_size;
+}
+
+bool EintrSafeFileDescriptor::BlkIoctl(int request,
+                                       uint64_t start,
+                                       uint64_t length,
+                                       int* result) {
+  // If the ioctl BLKZEROOUT is not defined, just fail to perform any of these
+  // operations.
+#ifndef BLKZEROOUT
+  return false;
+#else  // defined(BLKZEROOUT)
+  DCHECK(request == BLKDISCARD || request == BLKZEROOUT ||
+         request == BLKSECDISCARD);
+  // On some devices, the BLKDISCARD will actually read back as zeros, instead
+  // of "undefined" data. The BLKDISCARDZEROES ioctl tells whether that's the
+  // case, so we issue a BLKDISCARD in those cases to speed up the writes.
+  unsigned int arg;
+  if (request == BLKZEROOUT && ioctl(fd_, BLKDISCARDZEROES, &arg) == 0 && arg)
+    request = BLKDISCARD;
+
+  // Ensure the |fd_| is in O_DIRECT mode during this operation, so the write
+  // cache for this region is invalidated. This is required since otherwise
+  // reading back this region could consume stale data from the cache.
+  int flags = fcntl(fd_, F_GETFL, 0);
+  if (flags == -1) {
+    PLOG(WARNING) << "Couldn't get flags on fd " << fd_;
+    return false;
+  }
+  if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags | O_DIRECT) == -1) {
+    PLOG(WARNING) << "Couldn't set O_DIRECT on fd " << fd_;
+    return false;
+  }
+
+  uint64_t range[2] = {start, length};
+  *result = ioctl(fd_, request, range);
+
+  if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags) == -1) {
+    PLOG(WARNING) << "Couldn't remove O_DIRECT on fd " << fd_;
+    return false;
+  }
+  return true;
+#endif  // defined(BLKZEROOUT)
+}
+
+bool EintrSafeFileDescriptor::Close() {
+  CHECK_GE(fd_, 0);
+  if (IGNORE_EINTR(close(fd_)))
+    return false;
+  Reset();
+  return true;
+}
+
+void EintrSafeFileDescriptor::Reset() {
+  fd_ = -1;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/file_descriptor.h b/update_engine/payload_consumer/file_descriptor.h
new file mode 100644
index 0000000..7bb2974
--- /dev/null
+++ b/update_engine/payload_consumer/file_descriptor.h
@@ -0,0 +1,146 @@
+//
+// 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_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_H_
+
+#include <errno.h>
+#include <memory>
+#include <sys/types.h>
+
+#include <base/logging.h>
+
+// Abstraction for managing opening, reading, writing and closing of file
+// descriptors. This includes an abstract class and one standard implementation
+// based on POSIX system calls.
+//
+// TODO(garnold) this class is modeled after (and augments the functionality of)
+// the FileWriter class; ultimately, the latter should be replaced by the former
+// throughout the codebase.  A few deviations from the original FileWriter:
+//
+// * Providing two flavors of Open()
+//
+// * A FileDescriptor is reusable and can be used to read/write multiple files
+//   as long as open/close preconditions are respected.
+//
+// * Write() returns the number of bytes written: this appears to be more useful
+//   for clients, who may wish to retry or otherwise do something useful with
+//   the remaining data that was not written.
+//
+// * Provides a Reset() method, which will force to abandon a currently open
+//   file descriptor and allow opening another file, without necessarily
+//   properly closing the old one. This may be useful in cases where a "closer"
+//   class does not care whether Close() was successful, but may need to reuse
+//   the same file descriptor again.
+
+namespace chromeos_update_engine {
+
+class FileDescriptor;
+using FileDescriptorPtr = std::shared_ptr<FileDescriptor>;
+
+// An abstract class defining the file descriptor API.
+class FileDescriptor {
+ public:
+  FileDescriptor() {}
+  virtual ~FileDescriptor() {}
+
+  // Opens a file descriptor. The descriptor must be in the closed state prior
+  // to this call. Returns true on success, false otherwise. Specific
+  // implementations may set errno accordingly.
+  virtual bool Open(const char* path, int flags, mode_t mode) = 0;
+  virtual bool Open(const char* path, int flags) = 0;
+
+  // Reads from a file descriptor up to a given count. The descriptor must be
+  // open prior to this call. Returns the number of bytes read, or -1 on error.
+  // Specific implementations may set errno accordingly.
+  virtual ssize_t Read(void* buf, size_t count) = 0;
+
+  // Writes to a file descriptor. The descriptor must be open prior to this
+  // call. Returns the number of bytes written, or -1 if an error occurred and
+  // no bytes were written. Specific implementations may set errno accordingly.
+  virtual ssize_t Write(const void* buf, size_t count) = 0;
+
+  // Seeks to an offset. Returns the resulting offset location as measured in
+  // bytes from the beginning. On error, return -1. Specific implementations
+  // may set errno accordingly.
+  virtual off64_t Seek(off64_t offset, int whence) = 0;
+
+  // Return the size of the block device in bytes, or 0 if the device is not a
+  // block device or an error occurred.
+  virtual uint64_t BlockDevSize() = 0;
+
+  // Runs a ioctl() on the file descriptor if supported. Returns whether
+  // the operation is supported. The |request| can be one of BLKDISCARD,
+  // BLKZEROOUT and BLKSECDISCARD to discard, write zeros or securely discard
+  // the blocks. These ioctls accept a range of bytes (|start| and |length|)
+  // over which they perform the operation. The return value from the ioctl is
+  // stored in |result|.
+  virtual bool BlkIoctl(int request,
+                        uint64_t start,
+                        uint64_t length,
+                        int* result) = 0;
+
+  // Closes a file descriptor. The descriptor must be open prior to this call.
+  // Returns true on success, false otherwise. Specific implementations may set
+  // errno accordingly.
+  virtual bool Close() = 0;
+
+  // Resets the file descriptor, abandoning a currently open file and returning
+  // the descriptor to the closed state.
+  virtual void Reset() = 0;
+
+  // Indicates whether or not an implementation sets meaningful errno.
+  virtual bool IsSettingErrno() = 0;
+
+  // Indicates whether the descriptor is currently open.
+  virtual bool IsOpen() = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileDescriptor);
+};
+
+// A simple EINTR-immune wrapper implementation around standard system calls.
+class EintrSafeFileDescriptor : public FileDescriptor {
+ public:
+  EintrSafeFileDescriptor() : fd_(-1) {}
+
+  // Interface methods.
+  bool Open(const char* path, int flags, mode_t mode) override;
+  bool Open(const char* path, int flags) override;
+  ssize_t Read(void* buf, size_t count) override;
+  ssize_t Write(const void* buf, size_t count) override;
+  off64_t Seek(off64_t offset, int whence) override;
+  uint64_t BlockDevSize() override;
+  bool BlkIoctl(int request,
+                uint64_t start,
+                uint64_t length,
+                int* result) override;
+  bool Close() override;
+  void Reset() override;
+  bool IsSettingErrno() override {
+    return true;
+  }
+  bool IsOpen() override {
+    return (fd_ >= 0);
+  }
+
+ protected:
+  int fd_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_H_
diff --git a/update_engine/payload_consumer/file_writer.cc b/update_engine/payload_consumer/file_writer.cc
new file mode 100644
index 0000000..d280ddb
--- /dev/null
+++ b/update_engine/payload_consumer/file_writer.cc
@@ -0,0 +1,60 @@
+//
+// Copyright (C) 2009 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/payload_consumer/file_writer.h"
+
+#include <errno.h>
+
+namespace chromeos_update_engine {
+
+int DirectFileWriter::Open(const char* path, int flags, mode_t mode) {
+  CHECK_EQ(fd_, -1);
+  fd_ = open(path, flags, mode);
+  if (fd_ < 0)
+    return -errno;
+  return 0;
+}
+
+bool DirectFileWriter::Write(const void* bytes, size_t count) {
+  CHECK_GE(fd_, 0);
+  const char* char_bytes = reinterpret_cast<const char*>(bytes);
+
+  size_t bytes_written = 0;
+  while (bytes_written < count) {
+    ssize_t rc = write(fd_, char_bytes + bytes_written,
+                       count - bytes_written);
+    if (rc < 0)
+      return false;
+    bytes_written += rc;
+  }
+  CHECK_EQ(bytes_written, count);
+  return bytes_written == count;
+}
+
+int DirectFileWriter::Close() {
+  CHECK_GE(fd_, 0);
+  int rc = close(fd_);
+
+  // This can be any negative number that's not -1. This way, this FileWriter
+  // won't be used again for another file.
+  fd_ = -2;
+
+  if (rc < 0)
+    return -errno;
+  return rc;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/file_writer.h b/update_engine/payload_consumer/file_writer.h
new file mode 100644
index 0000000..96ebde6
--- /dev/null
+++ b/update_engine/payload_consumer/file_writer.h
@@ -0,0 +1,103 @@
+//
+// Copyright (C) 2009 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_PAYLOAD_CONSUMER_FILE_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_WRITER_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/utils.h"
+
+// FileWriter is a class that is used to (synchronously, for now) write to
+// a file. This file is a thin wrapper around open/write/close system calls,
+// but provides and interface that can be customized by subclasses that wish
+// to filter the data.
+
+namespace chromeos_update_engine {
+
+class FileWriter {
+ public:
+  FileWriter() {}
+  virtual ~FileWriter() {}
+
+  // Wrapper around write. Returns true if all requested bytes
+  // were written, or false on any error, regardless of progress.
+  virtual bool Write(const void* bytes, size_t count) = 0;
+
+  // Same as the Write method above but returns a detailed |error| code
+  // in addition if the returned value is false. By default this method
+  // returns kActionExitDownloadWriteError as the error code, but subclasses
+  // can override if they wish to return more specific error codes.
+  virtual bool Write(const void* bytes,
+                     size_t count,
+                     ErrorCode* error) {
+     *error = ErrorCode::kDownloadWriteError;
+     return Write(bytes, count);
+  }
+
+  // Wrapper around close. Returns 0 on success or -errno on error.
+  virtual int Close() = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileWriter);
+};
+
+// Direct file writer is probably the simplest FileWriter implementation.
+// It calls the system calls directly.
+
+class DirectFileWriter : public FileWriter {
+ public:
+  DirectFileWriter() = default;
+
+  // FileWriter overrides.
+  bool Write(const void* bytes, size_t count) override;
+  int Close() override;
+
+  // Wrapper around open. Returns 0 on success or -errno on error.
+  int Open(const char* path, int flags, mode_t mode);
+
+  int fd() const { return fd_; }
+
+ private:
+  int fd_{-1};
+
+  DISALLOW_COPY_AND_ASSIGN(DirectFileWriter);
+};
+
+class ScopedFileWriterCloser {
+ public:
+  explicit ScopedFileWriterCloser(FileWriter* writer) : writer_(writer) {}
+  ~ScopedFileWriterCloser() {
+    int err = writer_->Close();
+    if (err)
+      LOG(ERROR) << "FileWriter::Close failed: "
+                 << utils::ErrnoNumberAsString(-err);
+  }
+ private:
+  FileWriter* writer_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedFileWriterCloser);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_WRITER_H_
diff --git a/update_engine/payload_consumer/file_writer_unittest.cc b/update_engine/payload_consumer/file_writer_unittest.cc
new file mode 100644
index 0000000..debb4c3
--- /dev/null
+++ b/update_engine/payload_consumer/file_writer_unittest.cc
@@ -0,0 +1,79 @@
+//
+// Copyright (C) 2009 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/payload_consumer/file_writer.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FileWriterTest : public ::testing::Test { };
+
+TEST(FileWriterTest, SimpleTest) {
+  // Create a uniquely named file for testing.
+  string path;
+  ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+  ScopedPathUnlinker path_unlinker(path);
+
+  DirectFileWriter file_writer;
+  EXPECT_EQ(0, file_writer.Open(path.c_str(),
+                                O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY,
+                                0644));
+  EXPECT_TRUE(file_writer.Write("test", 4));
+  brillo::Blob actual_data;
+  EXPECT_TRUE(utils::ReadFile(path, &actual_data));
+
+  EXPECT_FALSE(memcmp("test", actual_data.data(), actual_data.size()));
+  EXPECT_EQ(0, file_writer.Close());
+}
+
+TEST(FileWriterTest, ErrorTest) {
+  DirectFileWriter file_writer;
+  const string path("/tmp/ENOENT/FileWriterTest");
+  EXPECT_EQ(-ENOENT, file_writer.Open(path.c_str(),
+                                      O_CREAT | O_LARGEFILE | O_TRUNC, 0644));
+}
+
+TEST(FileWriterTest, WriteErrorTest) {
+  // Create a uniquely named file for testing.
+  string path;
+  ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+  ScopedPathUnlinker path_unlinker(path);
+
+  DirectFileWriter file_writer;
+  EXPECT_EQ(0, file_writer.Open(path.c_str(),
+                                O_CREAT | O_LARGEFILE | O_TRUNC | O_RDONLY,
+                                0644));
+  EXPECT_FALSE(file_writer.Write("x", 1));
+  EXPECT_EQ(0, file_writer.Close());
+}
+
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/filesystem_verifier_action.cc b/update_engine/payload_consumer/filesystem_verifier_action.cc
new file mode 100644
index 0000000..5156f96
--- /dev/null
+++ b/update_engine/payload_consumer/filesystem_verifier_action.cc
@@ -0,0 +1,262 @@
+//
+// 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/payload_consumer/filesystem_verifier_action.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <string>
+
+#include <base/bind.h>
+#include <brillo/data_encoding.h>
+#include <brillo/streams/file_stream.h>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+const off_t kReadFileBufferSize = 128 * 1024;
+
+string StringForHashBytes(const brillo::Blob& hash) {
+  return brillo::data_encoding::Base64Encode(hash.data(), hash.size());
+}
+}  // namespace
+
+void FilesystemVerifierAction::PerformAction() {
+  // Will tell the ActionProcessor we've failed if we return.
+  ScopedActionCompleter abort_action_completer(processor_, this);
+
+  if (!HasInputObject()) {
+    LOG(ERROR) << "FilesystemVerifierAction missing input object.";
+    return;
+  }
+  install_plan_ = GetInputObject();
+
+  if (install_plan_.partitions.empty()) {
+    LOG(INFO) << "No partitions to verify.";
+    if (HasOutputPipe())
+      SetOutputObject(install_plan_);
+    abort_action_completer.set_code(ErrorCode::kSuccess);
+    return;
+  }
+
+  StartPartitionHashing();
+  abort_action_completer.set_should_complete(false);
+}
+
+void FilesystemVerifierAction::TerminateProcessing() {
+  cancelled_ = true;
+  Cleanup(ErrorCode::kSuccess);  // error code is ignored if canceled_ is true.
+}
+
+bool FilesystemVerifierAction::IsCleanupPending() const {
+  return src_stream_ != nullptr;
+}
+
+void FilesystemVerifierAction::Cleanup(ErrorCode code) {
+  src_stream_.reset();
+  // This memory is not used anymore.
+  buffer_.clear();
+
+  if (cancelled_)
+    return;
+  if (code == ErrorCode::kSuccess && HasOutputPipe())
+    SetOutputObject(install_plan_);
+  processor_->ActionComplete(this, code);
+}
+
+void FilesystemVerifierAction::StartPartitionHashing() {
+  if (partition_index_ == install_plan_.partitions.size()) {
+    Cleanup(ErrorCode::kSuccess);
+    return;
+  }
+  InstallPlan::Partition& partition =
+      install_plan_.partitions[partition_index_];
+
+  string part_path;
+  switch (verifier_step_) {
+    case VerifierStep::kVerifySourceHash:
+      part_path = partition.source_path;
+      remaining_size_ = partition.source_size;
+      break;
+    case VerifierStep::kVerifyTargetHash:
+      part_path = partition.target_path;
+      remaining_size_ = partition.target_size;
+      break;
+  }
+  LOG(INFO) << "Hashing partition " << partition_index_ << " ("
+            << partition.name << ") on device " << part_path;
+  if (part_path.empty())
+    return Cleanup(ErrorCode::kFilesystemVerifierError);
+
+  brillo::ErrorPtr error;
+  src_stream_ = brillo::FileStream::Open(
+      base::FilePath(part_path),
+      brillo::Stream::AccessMode::READ,
+      brillo::FileStream::Disposition::OPEN_EXISTING,
+      &error);
+
+  if (!src_stream_) {
+    LOG(ERROR) << "Unable to open " << part_path << " for reading";
+    return Cleanup(ErrorCode::kFilesystemVerifierError);
+  }
+
+  buffer_.resize(kReadFileBufferSize);
+  read_done_ = false;
+  hasher_.reset(new HashCalculator());
+
+  // Start the first read.
+  ScheduleRead();
+}
+
+void FilesystemVerifierAction::ScheduleRead() {
+  size_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()),
+                                  remaining_size_);
+  if (!bytes_to_read) {
+    OnReadDoneCallback(0);
+    return;
+  }
+
+  bool read_async_ok = src_stream_->ReadAsync(
+    buffer_.data(),
+    bytes_to_read,
+    base::Bind(&FilesystemVerifierAction::OnReadDoneCallback,
+               base::Unretained(this)),
+    base::Bind(&FilesystemVerifierAction::OnReadErrorCallback,
+               base::Unretained(this)),
+    nullptr);
+
+  if (!read_async_ok) {
+    LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
+    Cleanup(ErrorCode::kError);
+  }
+}
+
+void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) {
+  if (bytes_read == 0) {
+    read_done_ = true;
+  } else {
+    remaining_size_ -= bytes_read;
+    CHECK(!read_done_);
+    if (!hasher_->Update(buffer_.data(), bytes_read)) {
+      LOG(ERROR) << "Unable to update the hash.";
+      Cleanup(ErrorCode::kError);
+      return;
+    }
+  }
+
+  // We either terminate the current partition or have more data to read.
+  if (cancelled_)
+    return Cleanup(ErrorCode::kError);
+
+  if (read_done_ || remaining_size_ == 0) {
+    if (remaining_size_ != 0) {
+      LOG(ERROR) << "Failed to read the remaining " << remaining_size_
+                 << " bytes from partition "
+                 << install_plan_.partitions[partition_index_].name;
+      return Cleanup(ErrorCode::kFilesystemVerifierError);
+    }
+    return FinishPartitionHashing();
+  }
+  ScheduleRead();
+}
+
+void FilesystemVerifierAction::OnReadErrorCallback(
+      const brillo::Error* error) {
+  // TODO(deymo): Transform the read-error into an specific ErrorCode.
+  LOG(ERROR) << "Asynchronous read failed.";
+  Cleanup(ErrorCode::kError);
+}
+
+void FilesystemVerifierAction::FinishPartitionHashing() {
+  if (!hasher_->Finalize()) {
+    LOG(ERROR) << "Unable to finalize the hash.";
+    return Cleanup(ErrorCode::kError);
+  }
+  InstallPlan::Partition& partition =
+      install_plan_.partitions[partition_index_];
+  LOG(INFO) << "Hash of " << partition.name << ": " << hasher_->hash();
+
+  switch (verifier_step_) {
+    case VerifierStep::kVerifyTargetHash:
+      if (partition.target_hash != hasher_->raw_hash()) {
+        LOG(ERROR) << "New '" << partition.name
+                   << "' partition verification failed.";
+        if (install_plan_.payload_type == InstallPayloadType::kFull)
+          return Cleanup(ErrorCode::kNewRootfsVerificationError);
+        // If we have not verified source partition yet, now that the target
+        // partition does not match, and it's not a full payload, we need to
+        // switch to kVerifySourceHash step to check if it's because the source
+        // partition does not match either.
+        verifier_step_ = VerifierStep::kVerifySourceHash;
+      } else {
+        partition_index_++;
+      }
+      break;
+    case VerifierStep::kVerifySourceHash:
+      if (partition.source_hash != hasher_->raw_hash()) {
+        LOG(ERROR) << "Old '" << partition.name
+                   << "' partition verification failed.";
+        LOG(ERROR) << "This is a server-side error due to mismatched delta"
+                   << " update image!";
+        LOG(ERROR) << "The delta I've been given contains a " << partition.name
+                   << " delta update that must be applied over a "
+                   << partition.name << " with a specific checksum, but the "
+                   << partition.name
+                   << " we're starting with doesn't have that checksum! This"
+                      " means that the delta I've been given doesn't match my"
+                      " existing system. The "
+                   << partition.name << " partition I have has hash: "
+                   << StringForHashBytes(hasher_->raw_hash())
+                   << " but the update expected me to have "
+                   << StringForHashBytes(partition.source_hash) << " .";
+        LOG(INFO) << "To get the checksum of the " << partition.name
+                  << " partition run this command: dd if="
+                  << partition.source_path
+                  << " bs=1M count=" << partition.source_size
+                  << " iflag=count_bytes 2>/dev/null | openssl dgst -sha256 "
+                     "-binary | openssl base64";
+        LOG(INFO) << "To get the checksum of partitions in a bin file, "
+                  << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
+        return Cleanup(ErrorCode::kDownloadStateInitializationError);
+      }
+      // The action will skip kVerifySourceHash step if target partition hash
+      // matches, if we are in this step, it means target hash does not match,
+      // and now that the source partition hash matches, we should set the error
+      // code to reflect the error in target partition.
+      // We only need to verify the source partition which the target hash does
+      // not match, the rest of the partitions don't matter.
+      return Cleanup(ErrorCode::kNewRootfsVerificationError);
+  }
+  // Start hashing the next partition, if any.
+  hasher_.reset();
+  buffer_.clear();
+  src_stream_->CloseBlocking(nullptr);
+  StartPartitionHashing();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/filesystem_verifier_action.h b/update_engine/payload_consumer/filesystem_verifier_action.h
new file mode 100644
index 0000000..616f7b7
--- /dev/null
+++ b/update_engine/payload_consumer/filesystem_verifier_action.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_PAYLOAD_CONSUMER_FILESYSTEM_VERIFIER_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILESYSTEM_VERIFIER_ACTION_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/streams/stream.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/payload_consumer/install_plan.h"
+
+// This action will hash all the partitions of the target slot involved in the
+// update. The hashes are then verified against the ones in the InstallPlan.
+// If the target hash does not match, the action will fail. In case of failure,
+// the error code will depend on whether the source slot hashes are provided and
+// match.
+
+namespace chromeos_update_engine {
+
+// The step FilesystemVerifier is on. On kVerifyTargetHash it computes the hash
+// on the target partitions based on the already populated size and verifies it
+// matches the one in the target_hash in the InstallPlan.
+// If the hash matches, then we skip the kVerifySourceHash step, otherwise we
+// need to check if the source is the root cause of the mismatch.
+enum class VerifierStep {
+  kVerifyTargetHash,
+  kVerifySourceHash,
+};
+
+class FilesystemVerifierAction : public InstallPlanAction {
+ public:
+  FilesystemVerifierAction() = default;
+
+  void PerformAction() override;
+  void TerminateProcessing() override;
+
+  // Used for testing. Return true if Cleanup() has not yet been called due
+  // to a callback upon the completion or cancellation of the verifier action.
+  // A test should wait until IsCleanupPending() returns false before
+  // terminating the main loop.
+  bool IsCleanupPending() const;
+
+  // Debugging/logging
+  static std::string StaticType() { return "FilesystemVerifierAction"; }
+  std::string Type() const override { return StaticType(); }
+
+ private:
+  // Starts the hashing of the current partition. If there aren't any partitions
+  // remaining to be hashed, it finishes the action.
+  void StartPartitionHashing();
+
+  // Schedules the asynchronous read of the filesystem.
+  void ScheduleRead();
+
+  // Called from the main loop when a single read from |src_stream_| succeeds or
+  // fails, calling OnReadDoneCallback() and OnReadErrorCallback() respectively.
+  void OnReadDoneCallback(size_t bytes_read);
+  void OnReadErrorCallback(const brillo::Error* error);
+
+  // When the read is done, finalize the hash checking of the current partition
+  // and continue checking the next one.
+  void FinishPartitionHashing();
+
+  // Cleans up all the variables we use for async operations and tells the
+  // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
+  // true if TerminateProcessing() was called.
+  void Cleanup(ErrorCode code);
+
+  // The type of the partition that we are verifying.
+  VerifierStep verifier_step_ = VerifierStep::kVerifyTargetHash;
+
+  // The index in the install_plan_.partitions vector of the partition currently
+  // being hashed.
+  size_t partition_index_{0};
+
+  // If not null, the FileStream used to read from the device.
+  brillo::StreamPtr src_stream_;
+
+  // Buffer for storing data we read.
+  brillo::Blob buffer_;
+
+  bool read_done_{false};  // true if reached EOF on the input stream.
+  bool cancelled_{false};  // true if the action has been cancelled.
+
+  // The install plan we're passed in via the input pipe.
+  InstallPlan install_plan_;
+
+  // Calculates the hash of the data.
+  std::unique_ptr<HashCalculator> hasher_;
+
+  // Reads and hashes this many bytes from the head of the input stream. This
+  // field is initialized from the corresponding InstallPlan::Partition size,
+  // when the partition starts to be hashed.
+  int64_t remaining_size_{0};
+
+  DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILESYSTEM_VERIFIER_ACTION_H_
diff --git a/update_engine/payload_consumer/filesystem_verifier_action_unittest.cc b/update_engine/payload_consumer/filesystem_verifier_action_unittest.cc
new file mode 100644
index 0000000..2e1d95d
--- /dev/null
+++ b/update_engine/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -0,0 +1,302 @@
+//
+// 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/payload_consumer/filesystem_verifier_action.h"
+
+#include <fcntl.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using brillo::MessageLoop;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FilesystemVerifierActionTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  void TearDown() override {
+    EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
+  }
+
+  // Returns true iff test has completed successfully.
+  bool DoTest(bool terminate_early, bool hash_fail);
+
+  brillo::FakeMessageLoop loop_{nullptr};
+};
+
+class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
+ public:
+  explicit FilesystemVerifierActionTestDelegate(
+      FilesystemVerifierAction* action)
+      : action_(action), ran_(false), code_(ErrorCode::kError) {}
+  void ExitMainLoop() {
+    // We need to wait for the Action to call Cleanup.
+    if (action_->IsCleanupPending()) {
+      LOG(INFO) << "Waiting for Cleanup() to be called.";
+      MessageLoop::current()->PostDelayedTask(
+          FROM_HERE,
+          base::Bind(&FilesystemVerifierActionTestDelegate::ExitMainLoop,
+                     base::Unretained(this)),
+          base::TimeDelta::FromMilliseconds(100));
+    } else {
+      MessageLoop::current()->BreakLoop();
+    }
+  }
+  void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+    ExitMainLoop();
+  }
+  void ProcessingStopped(const ActionProcessor* processor) {
+    ExitMainLoop();
+  }
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) {
+    if (action->Type() == FilesystemVerifierAction::StaticType()) {
+      ran_ = true;
+      code_ = code;
+    }
+  }
+  bool ran() const { return ran_; }
+  ErrorCode code() const { return code_; }
+
+ private:
+  FilesystemVerifierAction* action_;
+  bool ran_;
+  ErrorCode code_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+                             FilesystemVerifierAction* filesystem_copier_action,
+                             bool terminate_early) {
+  processor->StartProcessing();
+  if (terminate_early) {
+    EXPECT_NE(nullptr, filesystem_copier_action);
+    processor->StopProcessing();
+  }
+}
+
+bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
+                                          bool hash_fail) {
+  string a_loop_file;
+
+  if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr))) {
+    ADD_FAILURE();
+    return false;
+  }
+  ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
+
+  // Make random data for a.
+  const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
+  brillo::Blob a_loop_data(kLoopFileSize);
+  test_utils::FillWithData(&a_loop_data);
+
+  // Write data to disk
+  if (!(test_utils::WriteFileVector(a_loop_file, a_loop_data))) {
+    ADD_FAILURE();
+    return false;
+  }
+
+  // Attach loop devices to the files
+  string a_dev;
+  test_utils::ScopedLoopbackDeviceBinder a_dev_releaser(
+      a_loop_file, false, &a_dev);
+  if (!(a_dev_releaser.is_bound())) {
+    ADD_FAILURE();
+    return false;
+  }
+
+  LOG(INFO) << "verifying: "  << a_loop_file << " (" << a_dev << ")";
+
+  bool success = true;
+
+  // Set up the action objects
+  InstallPlan install_plan;
+  install_plan.source_slot = 0;
+  install_plan.target_slot = 1;
+  InstallPlan::Partition part;
+  part.name = "part";
+  part.target_size = kLoopFileSize - (hash_fail ? 1 : 0);
+  part.target_path = a_dev;
+  if (!HashCalculator::RawHashOfData(a_loop_data, &part.target_hash)) {
+    ADD_FAILURE();
+    success = false;
+  }
+  part.source_size = kLoopFileSize;
+  part.source_path = a_dev;
+  if (!HashCalculator::RawHashOfData(a_loop_data, &part.source_hash)) {
+    ADD_FAILURE();
+    success = false;
+  }
+  install_plan.partitions = {part};
+
+  ActionProcessor processor;
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  FilesystemVerifierAction copier_action;
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&feeder_action, &copier_action);
+  BondActions(&copier_action, &collector_action);
+
+  FilesystemVerifierActionTestDelegate delegate(&copier_action);
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&copier_action);
+  processor.EnqueueAction(&collector_action);
+
+  feeder_action.set_obj(install_plan);
+
+  loop_.PostTask(FROM_HERE, base::Bind(&StartProcessorInRunLoop,
+                                       &processor,
+                                       &copier_action,
+                                       terminate_early));
+  loop_.Run();
+
+  if (!terminate_early) {
+    bool is_delegate_ran = delegate.ran();
+    EXPECT_TRUE(is_delegate_ran);
+    success = success && is_delegate_ran;
+  } else {
+    EXPECT_EQ(ErrorCode::kError, delegate.code());
+    return (ErrorCode::kError == delegate.code());
+  }
+  if (hash_fail) {
+    ErrorCode expected_exit_code = ErrorCode::kNewRootfsVerificationError;
+    EXPECT_EQ(expected_exit_code, delegate.code());
+    return (expected_exit_code == delegate.code());
+  }
+  EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
+
+  // Make sure everything in the out_image is there
+  brillo::Blob a_out;
+  if (!utils::ReadFile(a_dev, &a_out)) {
+    ADD_FAILURE();
+    return false;
+  }
+  const bool is_a_file_reading_eq =
+      test_utils::ExpectVectorsEq(a_loop_data, a_out);
+  EXPECT_TRUE(is_a_file_reading_eq);
+  success = success && is_a_file_reading_eq;
+
+  bool is_install_plan_eq = (collector_action.object() == install_plan);
+  EXPECT_TRUE(is_install_plan_eq);
+  success = success && is_install_plan_eq;
+  return success;
+}
+
+class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate {
+ public:
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) {
+    if (action->Type() == FilesystemVerifierAction::StaticType()) {
+      ran_ = true;
+      code_ = code;
+    }
+  }
+  bool ran_;
+  ErrorCode code_;
+};
+
+TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) {
+  ActionProcessor processor;
+  FilesystemVerifierActionTest2Delegate delegate;
+
+  processor.set_delegate(&delegate);
+
+  FilesystemVerifierAction copier_action;
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&copier_action, &collector_action);
+
+  processor.EnqueueAction(&copier_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning());
+  EXPECT_TRUE(delegate.ran_);
+  EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
+  ActionProcessor processor;
+  FilesystemVerifierActionTest2Delegate delegate;
+
+  processor.set_delegate(&delegate);
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  InstallPlan install_plan;
+  InstallPlan::Partition part;
+  part.name = "nope";
+  part.source_path = "/no/such/file";
+  part.target_path = "/no/such/file";
+  install_plan.partitions = {part};
+
+  feeder_action.set_obj(install_plan);
+  FilesystemVerifierAction verifier_action;
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&verifier_action, &collector_action);
+
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&verifier_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning());
+  EXPECT_TRUE(delegate.ran_);
+  EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) {
+  ASSERT_EQ(0U, getuid());
+  EXPECT_TRUE(DoTest(false, false));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) {
+  ASSERT_EQ(0U, getuid());
+  EXPECT_TRUE(DoTest(false, true));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) {
+  ASSERT_EQ(0U, getuid());
+  EXPECT_TRUE(DoTest(true, false));
+  // TerminateEarlyTest may leak some null callbacks from the Stream class.
+  while (loop_.RunOnce(false)) {}
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/install_plan.cc b/update_engine/payload_consumer/install_plan.cc
new file mode 100644
index 0000000..b04da74
--- /dev/null
+++ b/update_engine/payload_consumer/install_plan.cc
@@ -0,0 +1,122 @@
+//
+// 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/payload_consumer/install_plan.h"
+
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+string InstallPayloadTypeToString(InstallPayloadType type) {
+  switch (type) {
+    case InstallPayloadType::kUnknown:
+      return "unknown";
+    case InstallPayloadType::kFull:
+      return "full";
+    case InstallPayloadType::kDelta:
+      return "delta";
+  }
+  return "invalid type";
+}
+
+bool InstallPlan::operator==(const InstallPlan& that) const {
+  return ((is_resume == that.is_resume) &&
+          (payload_type == that.payload_type) &&
+          (download_url == that.download_url) &&
+          (payload_size == that.payload_size) &&
+          (payload_hash == that.payload_hash) &&
+          (metadata_size == that.metadata_size) &&
+          (metadata_signature == that.metadata_signature) &&
+          (source_slot == that.source_slot) &&
+          (target_slot == that.target_slot) &&
+          (partitions == that.partitions));
+}
+
+bool InstallPlan::operator!=(const InstallPlan& that) const {
+  return !((*this) == that);
+}
+
+void InstallPlan::Dump() const {
+  string partitions_str;
+  for (const auto& partition : partitions) {
+    partitions_str +=
+        base::StringPrintf(", part: %s (source_size: %" PRIu64
+                           ", target_size %" PRIu64 ", postinst:%s)",
+                           partition.name.c_str(),
+                           partition.source_size,
+                           partition.target_size,
+                           utils::ToString(partition.run_postinstall).c_str());
+  }
+
+  LOG(INFO) << "InstallPlan: "
+            << (is_resume ? "resume" : "new_update")
+            << ", payload type: " << InstallPayloadTypeToString(payload_type)
+            << ", source_slot: " << BootControlInterface::SlotName(source_slot)
+            << ", target_slot: " << BootControlInterface::SlotName(target_slot)
+            << ", url: " << download_url
+            << ", payload size: " << payload_size
+            << ", payload hash: " << payload_hash
+            << ", metadata size: " << metadata_size
+            << ", metadata signature: " << metadata_signature
+            << partitions_str
+            << ", hash_checks_mandatory: " << utils::ToString(
+                hash_checks_mandatory)
+            << ", powerwash_required: " << utils::ToString(powerwash_required);
+}
+
+bool InstallPlan::LoadPartitionsFromSlots(BootControlInterface* boot_control) {
+  bool result = true;
+  for (Partition& partition : partitions) {
+    if (source_slot != BootControlInterface::kInvalidSlot) {
+      result = boot_control->GetPartitionDevice(
+          partition.name, source_slot, &partition.source_path) && result;
+    } else {
+      partition.source_path.clear();
+    }
+
+    if (target_slot != BootControlInterface::kInvalidSlot) {
+      result = boot_control->GetPartitionDevice(
+          partition.name, target_slot, &partition.target_path) && result;
+    } else {
+      partition.target_path.clear();
+    }
+  }
+  return result;
+}
+
+bool InstallPlan::Partition::operator==(
+    const InstallPlan::Partition& that) const {
+  return (name == that.name &&
+          source_path == that.source_path &&
+          source_size == that.source_size &&
+          source_hash == that.source_hash &&
+          target_path == that.target_path &&
+          target_size == that.target_size &&
+          target_hash == that.target_hash &&
+          run_postinstall == that.run_postinstall &&
+          postinstall_path == that.postinstall_path &&
+          filesystem_type == that.filesystem_type &&
+          postinstall_optional == that.postinstall_optional);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/install_plan.h b/update_engine/payload_consumer/install_plan.h
new file mode 100644
index 0000000..3f0005c
--- /dev/null
+++ b/update_engine/payload_consumer/install_plan.h
@@ -0,0 +1,154 @@
+//
+// 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_PAYLOAD_CONSUMER_INSTALL_PLAN_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_INSTALL_PLAN_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/boot_control_interface.h"
+
+// InstallPlan is a simple struct that contains relevant info for many
+// parts of the update system about the install that should happen.
+namespace chromeos_update_engine {
+
+enum class InstallPayloadType {
+  kUnknown,
+  kFull,
+  kDelta,
+};
+
+std::string InstallPayloadTypeToString(InstallPayloadType type);
+
+struct InstallPlan {
+  InstallPlan() = default;
+
+  bool operator==(const InstallPlan& that) const;
+  bool operator!=(const InstallPlan& that) const;
+
+  void Dump() const;
+
+  // Load the |source_path| and |target_path| of all |partitions| based on the
+  // |source_slot| and |target_slot| if available. Returns whether it succeeded
+  // to load all the partitions for the valid slots.
+  bool LoadPartitionsFromSlots(BootControlInterface* boot_control);
+
+  bool is_resume{false};
+  InstallPayloadType payload_type{InstallPayloadType::kUnknown};
+  std::string download_url;  // url to download from
+  std::string version;       // version we are installing.
+
+  uint64_t payload_size{0};              // size of the payload
+  std::string payload_hash;              // SHA256 hash of the payload
+  uint64_t metadata_size{0};             // size of the metadata
+  std::string metadata_signature;        // signature of the  metadata
+
+  // The partition slots used for the update.
+  BootControlInterface::Slot source_slot{BootControlInterface::kInvalidSlot};
+  BootControlInterface::Slot target_slot{BootControlInterface::kInvalidSlot};
+
+  // The vector below is used for partition verification. The flow is:
+  //
+  // 1. DownloadAction fills in the expected source and target partition sizes
+  // and hashes based on the manifest.
+  //
+  // 2. FilesystemVerifierAction computes and verifies the partition sizes and
+  // hashes against the expected values.
+  struct Partition {
+    bool operator==(const Partition& that) const;
+
+    // The name of the partition.
+    std::string name;
+
+    std::string source_path;
+    uint64_t source_size{0};
+    brillo::Blob source_hash;
+
+    std::string target_path;
+    uint64_t target_size{0};
+    brillo::Blob target_hash;
+
+    // Whether we should run the postinstall script from this partition and the
+    // postinstall parameters.
+    bool run_postinstall{false};
+    std::string postinstall_path;
+    std::string filesystem_type;
+    bool postinstall_optional{false};
+  };
+  std::vector<Partition> partitions;
+
+  // True if payload hash checks are mandatory based on the system state and
+  // the Omaha response.
+  bool hash_checks_mandatory{false};
+
+  // True if Powerwash is required on reboot after applying the payload.
+  // False otherwise.
+  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;
+};
+
+class InstallPlanAction;
+
+template<>
+class ActionTraits<InstallPlanAction> {
+ public:
+  // Takes the install plan as input
+  typedef InstallPlan InputObjectType;
+  // Passes the install plan as output
+  typedef InstallPlan OutputObjectType;
+};
+
+// Basic action that only receives and sends Install Plans.
+// Can be used to construct an Install Plan to send to any other Action that
+// accept an InstallPlan.
+class InstallPlanAction : public Action<InstallPlanAction> {
+ public:
+  InstallPlanAction() {}
+  explicit InstallPlanAction(const InstallPlan& install_plan):
+    install_plan_(install_plan) {}
+
+  void PerformAction() override {
+    if (HasOutputPipe()) {
+      SetOutputObject(install_plan_);
+    }
+    processor_->ActionComplete(this, ErrorCode::kSuccess);
+  }
+
+  InstallPlan* install_plan() { return &install_plan_; }
+
+  static std::string StaticType() { return "InstallPlanAction"; }
+  std::string Type() const override { return StaticType(); }
+
+  typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
+  typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType;
+
+ private:
+  InstallPlan install_plan_;
+
+  DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_INSTALL_PLAN_H_
diff --git a/update_engine/payload_consumer/mock_download_action.h b/update_engine/payload_consumer/mock_download_action.h
new file mode 100644
index 0000000..3abb809
--- /dev/null
+++ b/update_engine/payload_consumer/mock_download_action.h
@@ -0,0 +1,41 @@
+//
+// 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_PAYLOAD_CONSUMER_MOCK_DOWNLOAD_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_MOCK_DOWNLOAD_ACTION_H_
+
+#include <stdint.h>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/payload_consumer/download_action.h"
+
+namespace chromeos_update_engine {
+
+class MockDownloadActionDelegate : public DownloadActionDelegate {
+ public:
+  MOCK_METHOD3(BytesReceived,
+               void(uint64_t bytes_progressed,
+                    uint64_t bytes_received,
+                    uint64_t total));
+  MOCK_METHOD1(ShouldCancel, bool(ErrorCode* cancel_reason));
+  MOCK_METHOD0(DownloadComplete, void());
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_MOCK_DOWNLOAD_ACTION_H_
diff --git a/update_engine/payload_consumer/mtd_file_descriptor.cc b/update_engine/payload_consumer/mtd_file_descriptor.cc
new file mode 100644
index 0000000..3f0a33f
--- /dev/null
+++ b/update_engine/payload_consumer/mtd_file_descriptor.cc
@@ -0,0 +1,265 @@
+//
+// 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/payload_consumer/mtd_file_descriptor.h"
+
+#include <fcntl.h>
+#include <mtd/ubi-user.h>
+#include <string>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+static const char kSysfsClassUbi[] = "/sys/class/ubi/";
+static const char kUsableEbSize[] = "/usable_eb_size";
+static const char kReservedEbs[] = "/reserved_ebs";
+
+using chromeos_update_engine::UbiVolumeInfo;
+using chromeos_update_engine::utils::ReadFile;
+
+// Return a UbiVolumeInfo pointer if |path| is a UBI volume. Otherwise, return
+// a null unique pointer.
+std::unique_ptr<UbiVolumeInfo> GetUbiVolumeInfo(const string& path) {
+  base::FilePath device_node(path);
+  base::FilePath ubi_name(device_node.BaseName());
+
+  string sysfs_node(kSysfsClassUbi);
+  sysfs_node.append(ubi_name.MaybeAsASCII());
+
+  std::unique_ptr<UbiVolumeInfo> ret;
+
+  // Obtain volume info from sysfs.
+  string s_reserved_ebs;
+  if (!ReadFile(sysfs_node + kReservedEbs, &s_reserved_ebs)) {
+    LOG(ERROR) << "Cannot read " << sysfs_node + kReservedEbs;
+    return ret;
+  }
+  string s_eb_size;
+  if (!ReadFile(sysfs_node + kUsableEbSize, &s_eb_size)) {
+    LOG(ERROR) << "Cannot read " << sysfs_node + kUsableEbSize;
+    return ret;
+  }
+
+  base::TrimWhitespaceASCII(s_reserved_ebs,
+                            base::TRIM_TRAILING,
+                            &s_reserved_ebs);
+  base::TrimWhitespaceASCII(s_eb_size, base::TRIM_TRAILING, &s_eb_size);
+
+  uint64_t reserved_ebs, eb_size;
+  if (!base::StringToUint64(s_reserved_ebs, &reserved_ebs)) {
+    LOG(ERROR) << "Cannot parse reserved_ebs: " << s_reserved_ebs;
+    return ret;
+  }
+  if (!base::StringToUint64(s_eb_size, &eb_size)) {
+    LOG(ERROR) << "Cannot parse usable_eb_size: " << s_eb_size;
+    return ret;
+  }
+
+  ret.reset(new UbiVolumeInfo);
+  ret->reserved_ebs = reserved_ebs;
+  ret->eraseblock_size = eb_size;
+  return ret;
+}
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+MtdFileDescriptor::MtdFileDescriptor()
+    : read_ctx_(nullptr, &mtd_read_close),
+      write_ctx_(nullptr, &mtd_write_close) {}
+
+bool MtdFileDescriptor::IsMtd(const char* path) {
+  uint64_t size;
+  return mtd_node_info(path, &size, nullptr, nullptr) == 0;
+}
+
+bool MtdFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+  // This File Descriptor does not support read and write.
+  TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
+  // But we need to open the underlying file descriptor in O_RDWR mode because
+  // during write, we need to read back to verify the write actually sticks or
+  // we have to skip the block. That job is done by mtdutils library.
+  if ((flags & O_ACCMODE) == O_WRONLY) {
+    flags &= ~O_ACCMODE;
+    flags |= O_RDWR;
+  }
+  TEST_AND_RETURN_FALSE(
+      EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
+
+  if ((flags & O_ACCMODE) == O_RDWR) {
+    write_ctx_.reset(mtd_write_descriptor(fd_, path));
+    nr_written_ = 0;
+  } else {
+    read_ctx_.reset(mtd_read_descriptor(fd_, path));
+  }
+
+  if (!read_ctx_ && !write_ctx_) {
+    Close();
+    return false;
+  }
+
+  return true;
+}
+
+bool MtdFileDescriptor::Open(const char* path, int flags) {
+  mode_t cur = umask(022);
+  umask(cur);
+  return Open(path, flags, 0777 & ~cur);
+}
+
+ssize_t MtdFileDescriptor::Read(void* buf, size_t count) {
+  CHECK(read_ctx_);
+  return mtd_read_data(read_ctx_.get(), static_cast<char*>(buf), count);
+}
+
+ssize_t MtdFileDescriptor::Write(const void* buf, size_t count) {
+  CHECK(write_ctx_);
+  ssize_t result = mtd_write_data(write_ctx_.get(),
+                                  static_cast<const char*>(buf),
+                                  count);
+  if (result > 0) {
+    nr_written_ += result;
+  }
+  return result;
+}
+
+off64_t MtdFileDescriptor::Seek(off64_t offset, int whence) {
+  if (write_ctx_) {
+    // Ignore seek in write mode.
+    return nr_written_;
+  }
+  return EintrSafeFileDescriptor::Seek(offset, whence);
+}
+
+bool MtdFileDescriptor::Close() {
+  read_ctx_.reset();
+  write_ctx_.reset();
+  return EintrSafeFileDescriptor::Close();
+}
+
+bool UbiFileDescriptor::IsUbi(const char* path) {
+  base::FilePath device_node(path);
+  base::FilePath ubi_name(device_node.BaseName());
+  TEST_AND_RETURN_FALSE(base::StartsWith(ubi_name.MaybeAsASCII(), "ubi",
+                                         base::CompareCase::SENSITIVE));
+
+  return static_cast<bool>(GetUbiVolumeInfo(path));
+}
+
+bool UbiFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+  std::unique_ptr<UbiVolumeInfo> info = GetUbiVolumeInfo(path);
+  if (!info) {
+    return false;
+  }
+
+  // This File Descriptor does not support read and write.
+  TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
+  TEST_AND_RETURN_FALSE(
+      EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
+
+  usable_eb_blocks_ = info->reserved_ebs;
+  eraseblock_size_ = info->eraseblock_size;
+  volume_size_ = usable_eb_blocks_ * eraseblock_size_;
+
+  if ((flags & O_ACCMODE) == O_WRONLY) {
+    // It's best to use volume update ioctl so that UBI layer will mark the
+    // volume as being updated, and only clear that mark if the update is
+    // successful. We will need to pad to the whole volume size at close.
+    uint64_t vsize = volume_size_;
+    if (ioctl(fd_, UBI_IOCVOLUP, &vsize) != 0) {
+      PLOG(ERROR) << "Cannot issue volume update ioctl";
+      EintrSafeFileDescriptor::Close();
+      return false;
+    }
+    mode_ = kWriteOnly;
+    nr_written_ = 0;
+  } else {
+    mode_ = kReadOnly;
+  }
+
+  return true;
+}
+
+bool UbiFileDescriptor::Open(const char* path, int flags) {
+  mode_t cur = umask(022);
+  umask(cur);
+  return Open(path, flags, 0777 & ~cur);
+}
+
+ssize_t UbiFileDescriptor::Read(void* buf, size_t count) {
+  CHECK(mode_ == kReadOnly);
+  return EintrSafeFileDescriptor::Read(buf, count);
+}
+
+ssize_t UbiFileDescriptor::Write(const void* buf, size_t count) {
+  CHECK(mode_ == kWriteOnly);
+  ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, count);
+  if (nr_chunk >= 0) {
+    nr_written_ += nr_chunk;
+  }
+  return nr_chunk;
+}
+
+off64_t UbiFileDescriptor::Seek(off64_t offset, int whence) {
+  if (mode_ == kWriteOnly) {
+    // Ignore seek in write mode.
+    return nr_written_;
+  }
+  return EintrSafeFileDescriptor::Seek(offset, whence);
+}
+
+bool UbiFileDescriptor::Close() {
+  bool pad_ok = true;
+  if (IsOpen() && mode_ == kWriteOnly) {
+    char buf[1024];
+    memset(buf, 0xFF, sizeof(buf));
+    while (nr_written_ < volume_size_) {
+      // We have written less than the whole volume. In order for us to clear
+      // the update marker, we need to fill the rest. It is recommended to fill
+      // UBI writes with 0xFF.
+      uint64_t to_write = volume_size_ - nr_written_;
+      if (to_write > sizeof(buf)) {
+        to_write = sizeof(buf);
+      }
+      ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, to_write);
+      if (nr_chunk < 0) {
+        LOG(ERROR) << "Cannot 0xFF-pad before closing.";
+        // There is an error, but we can't really do any meaningful thing here.
+        pad_ok = false;
+        break;
+      }
+      nr_written_ += nr_chunk;
+    }
+  }
+  return EintrSafeFileDescriptor::Close() && pad_ok;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/mtd_file_descriptor.h b/update_engine/payload_consumer/mtd_file_descriptor.h
new file mode 100644
index 0000000..6c945b2
--- /dev/null
+++ b/update_engine/payload_consumer/mtd_file_descriptor.h
@@ -0,0 +1,104 @@
+//
+// 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_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_
+
+// This module defines file descriptors that deal with NAND media. We are
+// concerned with raw NAND access (as MTD device), and through UBI layer.
+
+#include <mtdutils.h>
+
+#include "update_engine/payload_consumer/file_descriptor.h"
+
+namespace chromeos_update_engine {
+
+// A class defining the file descriptor API for raw MTD device. This file
+// descriptor supports either random read, or sequential write but not both at
+// once.
+class MtdFileDescriptor : public EintrSafeFileDescriptor {
+ public:
+  MtdFileDescriptor();
+
+  static bool IsMtd(const char* path);
+
+  bool Open(const char* path, int flags, mode_t mode) override;
+  bool Open(const char* path, int flags) override;
+  ssize_t Read(void* buf, size_t count) override;
+  ssize_t Write(const void* buf, size_t count) override;
+  off64_t Seek(off64_t offset, int whence) override;
+  uint64_t BlockDevSize() override { return 0; }
+  bool BlkIoctl(int request,
+                uint64_t start,
+                uint64_t length,
+                int* result) override {
+    return false;
+  }
+  bool Close() override;
+
+ private:
+  std::unique_ptr<MtdReadContext, decltype(&mtd_read_close)> read_ctx_;
+  std::unique_ptr<MtdWriteContext, decltype(&mtd_write_close)> write_ctx_;
+  uint64_t nr_written_;
+};
+
+struct UbiVolumeInfo {
+  // Number of eraseblocks.
+  uint64_t reserved_ebs;
+  // Size of each eraseblock.
+  uint64_t eraseblock_size;
+};
+
+// A file descriptor to update a UBI volume, similar to MtdFileDescriptor.
+// Once the file descriptor is opened for write, the volume is marked as being
+// updated. The volume will not be usable until an update is completed. See
+// UBI_IOCVOLUP ioctl operation.
+class UbiFileDescriptor : public EintrSafeFileDescriptor {
+ public:
+  // Perform some queries about |path| to see if it is a UBI volume.
+  static bool IsUbi(const char* path);
+
+  bool Open(const char* path, int flags, mode_t mode) override;
+  bool Open(const char* path, int flags) override;
+  ssize_t Read(void* buf, size_t count) override;
+  ssize_t Write(const void* buf, size_t count) override;
+  off64_t Seek(off64_t offset, int whence) override;
+  uint64_t BlockDevSize() override { return 0; }
+  bool BlkIoctl(int request,
+                uint64_t start,
+                uint64_t length,
+                int* result) override {
+    return false;
+  }
+  bool Close() override;
+
+ private:
+  enum Mode {
+    kReadOnly,
+    kWriteOnly
+  };
+
+  uint64_t usable_eb_blocks_;
+  uint64_t eraseblock_size_;
+  uint64_t volume_size_;
+  uint64_t nr_written_;
+
+  Mode mode_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_
diff --git a/update_engine/payload_consumer/payload_constants.cc b/update_engine/payload_consumer/payload_constants.cc
new file mode 100644
index 0000000..6078a74
--- /dev/null
+++ b/update_engine/payload_consumer/payload_constants.cc
@@ -0,0 +1,71 @@
+//
+// 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/payload_consumer/payload_constants.h"
+
+namespace chromeos_update_engine {
+
+const uint64_t kChromeOSMajorPayloadVersion = 1;
+const uint64_t kBrilloMajorPayloadVersion = 2;
+
+const uint32_t kFullPayloadMinorVersion = 0;
+const uint32_t kInPlaceMinorPayloadVersion = 1;
+const uint32_t kSourceMinorPayloadVersion = 2;
+const uint32_t kOpSrcHashMinorPayloadVersion = 3;
+const uint32_t kImgdiffMinorPayloadVersion = 4;
+
+const char kLegacyPartitionNameKernel[] = "boot";
+const char kLegacyPartitionNameRoot[] = "system";
+
+const char kDeltaMagic[4] = {'C', 'r', 'A', 'U'};
+const char kBspatchPath[] = "bspatch";
+
+// The zlib in Android and Chrome OS are currently compatible with each other,
+// so they are sharing the same array, but if in the future they are no longer
+// compatible with each other, we coule make the same change on the other one to
+// make them compatible again or use ifdef here.
+const char kCompatibleZlibFingerprint[][65] = {
+    "ea973605ccbbdb24f59f449c5f65861a1a9bc7a4353377aaaa06cb3e0f1cfbd7",
+    "3747fa404cceb00a5ec3606fc779510aaa784d5864ab1d5c28b9e267c40aad5c",
+};
+
+const char* InstallOperationTypeName(InstallOperation_Type op_type) {
+  switch (op_type) {
+    case InstallOperation::BSDIFF:
+      return "BSDIFF";
+    case InstallOperation::MOVE:
+      return "MOVE";
+    case InstallOperation::REPLACE:
+      return "REPLACE";
+    case InstallOperation::REPLACE_BZ:
+      return "REPLACE_BZ";
+    case InstallOperation::SOURCE_COPY:
+      return "SOURCE_COPY";
+    case InstallOperation::SOURCE_BSDIFF:
+      return "SOURCE_BSDIFF";
+    case InstallOperation::ZERO:
+      return "ZERO";
+    case InstallOperation::DISCARD:
+      return "DISCARD";
+    case InstallOperation::REPLACE_XZ:
+      return "REPLACE_XZ";
+    case InstallOperation::IMGDIFF:
+      return "IMGDIFF";
+  }
+  return "<unknown_op>";
+}
+
+};  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/payload_constants.h b/update_engine/payload_consumer/payload_constants.h
new file mode 100644
index 0000000..2dbc5fa
--- /dev/null
+++ b/update_engine/payload_consumer/payload_constants.h
@@ -0,0 +1,77 @@
+//
+// 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_PAYLOAD_CONSUMER_PAYLOAD_CONSTANTS_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_CONSTANTS_H_
+
+#include <stdint.h>
+
+#include <limits>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The major version used by Chrome OS.
+extern const uint64_t kChromeOSMajorPayloadVersion;
+
+// The major version used by Brillo.
+extern const uint64_t kBrilloMajorPayloadVersion;
+
+// The minor version used for all full payloads.
+extern const uint32_t kFullPayloadMinorVersion;
+
+// The minor version used by the in-place delta generator algorithm.
+extern const uint32_t kInPlaceMinorPayloadVersion;
+
+// The minor version used by the A to B delta generator algorithm.
+extern const uint32_t kSourceMinorPayloadVersion;
+
+// The minor version that allows per-operation source hash.
+extern const uint32_t kOpSrcHashMinorPayloadVersion;
+
+// The minor version that allows IMGDIFF operation.
+extern const uint32_t kImgdiffMinorPayloadVersion;
+
+
+// The kernel and rootfs partition names used by the BootControlInterface when
+// handling update payloads with a major version 1. The names of the updated
+// partitions are include in the payload itself for major version 2.
+extern const char kLegacyPartitionNameKernel[];
+extern const char kLegacyPartitionNameRoot[];
+
+extern const char kBspatchPath[];
+extern const char kDeltaMagic[4];
+
+// The list of compatible SHA256 hashes of zlib source code.
+// This is used to check if the source image have a compatible zlib (produce
+// same compressed result given the same input).
+// When a new fingerprint is found, please examine the changes in zlib source
+// carefully and determine if it's still compatible with previous version, if
+// yes then add the new fingerprint to this array, otherwise remove all previous
+// fingerprints in the array first, and only include the new fingerprint.
+extern const char kCompatibleZlibFingerprint[2][65];
+
+// A block number denoting a hole on a sparse file. Used on Extents to refer to
+// section of blocks not present on disk on a sparse file.
+const uint64_t kSparseHole = std::numeric_limits<uint64_t>::max();
+
+// Return the name of the operation type.
+const char* InstallOperationTypeName(InstallOperation_Type op_type);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_CONSTANTS_H_
diff --git a/update_engine/payload_consumer/payload_verifier.cc b/update_engine/payload_consumer/payload_verifier.cc
new file mode 100644
index 0000000..ab5238c
--- /dev/null
+++ b/update_engine/payload_consumer/payload_verifier.cc
@@ -0,0 +1,183 @@
+//
+// 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/payload_consumer/payload_verifier.h"
+
+#include <base/logging.h>
+#include <openssl/pem.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The following is a standard PKCS1-v1_5 padding for SHA256 signatures, as
+// defined in RFC3447. It is prepended to the actual signature (32 bytes) to
+// form a sequence of 256 bytes (2048 bits) that is amenable to RSA signing. The
+// padded hash will look as follows:
+//
+//    0x00 0x01 0xff ... 0xff 0x00  ASN1HEADER  SHA256HASH
+//   |--------------205-----------||----19----||----32----|
+//
+// where ASN1HEADER is the ASN.1 description of the signed data. The complete 51
+// bytes of actual data (i.e. the ASN.1 header complete with the hash) are
+// packed as follows:
+//
+//  SEQUENCE(2+49) {
+//   SEQUENCE(2+13) {
+//    OBJECT(2+9) id-sha256
+//    NULL(2+0)
+//   }
+//   OCTET STRING(2+32) <actual signature bytes...>
+//  }
+const uint8_t kRSA2048SHA256Padding[] = {
+  // PKCS1-v1_5 padding
+  0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0x00,
+  // ASN.1 header
+  0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+  0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+  0x00, 0x04, 0x20,
+};
+
+}  // namespace
+
+bool PayloadVerifier::VerifySignature(const brillo::Blob& signature_blob,
+                                      const string& public_key_path,
+                                      const brillo::Blob& hash_data) {
+  TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+  Signatures signatures;
+  LOG(INFO) << "signature blob size = " <<  signature_blob.size();
+  TEST_AND_RETURN_FALSE(signatures.ParseFromArray(signature_blob.data(),
+                                                  signature_blob.size()));
+
+  if (!signatures.signatures_size()) {
+    LOG(ERROR) << "No signatures stored in the blob.";
+    return false;
+  }
+
+  std::vector<brillo::Blob> tested_hashes;
+  // Tries every signature in the signature blob.
+  for (int i = 0; i < signatures.signatures_size(); i++) {
+    const Signatures_Signature& signature = signatures.signatures(i);
+    brillo::Blob sig_data(signature.data().begin(), signature.data().end());
+    brillo::Blob sig_hash_data;
+    if (!GetRawHashFromSignature(sig_data, public_key_path, &sig_hash_data))
+      continue;
+
+    if (hash_data == sig_hash_data) {
+      LOG(INFO) << "Verified correct signature " << i + 1 << " out of "
+                << signatures.signatures_size() << " signatures.";
+      return true;
+    }
+    tested_hashes.push_back(sig_hash_data);
+  }
+  LOG(ERROR) << "None of the " << signatures.signatures_size()
+             << " signatures is correct. Expected:";
+  utils::HexDumpVector(hash_data);
+  LOG(ERROR) << "But found decrypted hashes:";
+  for (const auto& sig_hash_data : tested_hashes) {
+    utils::HexDumpVector(sig_hash_data);
+  }
+  return false;
+}
+
+
+bool PayloadVerifier::GetRawHashFromSignature(
+    const brillo::Blob& sig_data,
+    const string& public_key_path,
+    brillo::Blob* out_hash_data) {
+  TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+  // The code below executes the equivalent of:
+  //
+  // openssl rsautl -verify -pubin -inkey |public_key_path|
+  //   -in |sig_data| -out |out_hash_data|
+
+  // Loads the public key.
+  FILE* fpubkey = fopen(public_key_path.c_str(), "rb");
+  if (!fpubkey) {
+    LOG(ERROR) << "Unable to open public key file: " << public_key_path;
+    return false;
+  }
+
+  char dummy_password[] = { ' ', 0 };  // Ensure no password is read from stdin.
+  RSA* rsa = PEM_read_RSA_PUBKEY(fpubkey, nullptr, nullptr, dummy_password);
+  fclose(fpubkey);
+  TEST_AND_RETURN_FALSE(rsa != nullptr);
+  unsigned int keysize = RSA_size(rsa);
+  if (sig_data.size() > 2 * keysize) {
+    LOG(ERROR) << "Signature size is too big for public key size.";
+    RSA_free(rsa);
+    return false;
+  }
+
+  // Decrypts the signature.
+  brillo::Blob hash_data(keysize);
+  int decrypt_size = RSA_public_decrypt(sig_data.size(),
+                                        sig_data.data(),
+                                        hash_data.data(),
+                                        rsa,
+                                        RSA_NO_PADDING);
+  RSA_free(rsa);
+  TEST_AND_RETURN_FALSE(decrypt_size > 0 &&
+                        decrypt_size <= static_cast<int>(hash_data.size()));
+  hash_data.resize(decrypt_size);
+  out_hash_data->swap(hash_data);
+  return true;
+}
+
+bool PayloadVerifier::PadRSA2048SHA256Hash(brillo::Blob* hash) {
+  TEST_AND_RETURN_FALSE(hash->size() == 32);
+  hash->insert(hash->begin(),
+               reinterpret_cast<const char*>(kRSA2048SHA256Padding),
+               reinterpret_cast<const char*>(kRSA2048SHA256Padding +
+                                             sizeof(kRSA2048SHA256Padding)));
+  TEST_AND_RETURN_FALSE(hash->size() == 256);
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/payload_verifier.h b/update_engine/payload_consumer/payload_verifier.h
new file mode 100644
index 0000000..22ced40
--- /dev/null
+++ b/update_engine/payload_consumer/payload_verifier.h
@@ -0,0 +1,65 @@
+//
+// 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_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signature verification.
+// See payload_generator/payload_signer.h for payload signing.
+
+namespace chromeos_update_engine {
+
+class PayloadVerifier {
+ public:
+  // Interprets |signature_blob| as a protocol buffer containing the Signatures
+  // message and decrypts each signature data using the |public_key_path|.
+  // Returns whether *any* of the decrypted hashes matches the |hash_data|.
+  // In case of any error parsing the signatures or the public key, returns
+  // false.
+  static bool VerifySignature(const brillo::Blob& signature_blob,
+                              const std::string& public_key_path,
+                              const brillo::Blob& hash_data);
+
+  // Decrypts sig_data with the given public_key_path and populates
+  // out_hash_data with the decoded raw hash. Returns true if successful,
+  // false otherwise.
+  static bool GetRawHashFromSignature(const brillo::Blob& sig_data,
+                                      const std::string& public_key_path,
+                                      brillo::Blob* out_hash_data);
+
+  // Pads a SHA256 hash so that it may be encrypted/signed with RSA2048
+  // using the PKCS#1 v1.5 scheme.
+  // hash should be a pointer to vector of exactly 256 bits. The vector
+  // will be modified in place and will result in having a length of
+  // 2048 bits. Returns true on success, false otherwise.
+  static bool PadRSA2048SHA256Hash(brillo::Blob* hash);
+
+ private:
+  // This should never be constructed
+  DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadVerifier);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
diff --git a/update_engine/payload_consumer/postinstall_runner_action.cc b/update_engine/payload_consumer/postinstall_runner_action.cc
new file mode 100644
index 0000000..a1b6f25
--- /dev/null
+++ b/update_engine/payload_consumer/postinstall_runner_action.cc
@@ -0,0 +1,377 @@
+//
+// 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/payload_consumer/postinstall_runner_action.h"
+
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cmath>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+namespace {
+
+// The file descriptor number from the postinstall program's perspective where
+// it can report status updates. This can be any number greater than 2 (stderr),
+// but must be kept in sync with the "bin/postinst_progress" defined in the
+// sample_images.sh file.
+const int kPostinstallStatusFd = 3;
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+using brillo::MessageLoop;
+using std::string;
+using std::vector;
+
+void PostinstallRunnerAction::PerformAction() {
+  CHECK(HasInputObject());
+  install_plan_ = GetInputObject();
+
+  if (install_plan_.powerwash_required) {
+    if (hardware_->SchedulePowerwash()) {
+      powerwash_scheduled_ = true;
+    } else {
+      return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
+    }
+  }
+
+  // Initialize all the partition weights.
+  partition_weight_.resize(install_plan_.partitions.size());
+  total_weight_ = 0;
+  for (size_t i = 0; i < install_plan_.partitions.size(); ++i) {
+    // TODO(deymo): This code sets the weight to all the postinstall commands,
+    // but we could remember how long they took in the past and use those
+    // values.
+    partition_weight_[i] = install_plan_.partitions[i].run_postinstall;
+    total_weight_ += partition_weight_[i];
+  }
+  accumulated_weight_ = 0;
+  ReportProgress(0);
+
+  PerformPartitionPostinstall();
+}
+
+void PostinstallRunnerAction::PerformPartitionPostinstall() {
+  if (install_plan_.download_url.empty()) {
+    LOG(INFO) << "Skipping post-install during rollback";
+    return CompletePostinstall(ErrorCode::kSuccess);
+  }
+
+  // Skip all the partitions that don't have a post-install step.
+  while (current_partition_ < install_plan_.partitions.size() &&
+         !install_plan_.partitions[current_partition_].run_postinstall) {
+    VLOG(1) << "Skipping post-install on partition "
+            << install_plan_.partitions[current_partition_].name;
+    current_partition_++;
+  }
+  if (current_partition_ == install_plan_.partitions.size())
+    return CompletePostinstall(ErrorCode::kSuccess);
+
+  const InstallPlan::Partition& partition =
+      install_plan_.partitions[current_partition_];
+
+  const string mountable_device =
+      utils::MakePartitionNameForMount(partition.target_path);
+  if (mountable_device.empty()) {
+    LOG(ERROR) << "Cannot make mountable device from " << partition.target_path;
+    return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
+  }
+
+  // Perform post-install for the current_partition_ partition. At this point we
+  // need to call CompletePartitionPostinstall to complete the operation and
+  // cleanup.
+#ifdef __ANDROID__
+  fs_mount_dir_ = "/postinstall";
+#else   // __ANDROID__
+  base::FilePath temp_dir;
+  TEST_AND_RETURN(base::CreateNewTempDirectory("au_postint_mount", &temp_dir));
+  fs_mount_dir_ = temp_dir.value();
+#endif  // __ANDROID__
+
+  base::FilePath postinstall_path(partition.postinstall_path);
+  if (postinstall_path.IsAbsolute()) {
+    LOG(ERROR) << "Invalid absolute path passed to postinstall, use a relative"
+                  "path instead: "
+               << partition.postinstall_path;
+    return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
+  }
+
+  string abs_path =
+      base::FilePath(fs_mount_dir_).Append(postinstall_path).value();
+  if (!base::StartsWith(
+          abs_path, fs_mount_dir_, base::CompareCase::SENSITIVE)) {
+    LOG(ERROR) << "Invalid relative postinstall path: "
+               << partition.postinstall_path;
+    return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
+  }
+
+#ifdef __ANDROID__
+  // In Chromium OS, the postinstall step is allowed to write to the block
+  // device on the target image, so we don't mark it as read-only and should
+  // be read-write since we just wrote to it during the update.
+
+  // Mark the block device as read-only before mounting for post-install.
+  if (!utils::SetBlockDeviceReadOnly(mountable_device, true)) {
+    return CompletePartitionPostinstall(
+        1, "Error marking the device " + mountable_device + " read only.");
+  }
+#endif  // __ANDROID__
+
+  if (!utils::MountFilesystem(mountable_device,
+                              fs_mount_dir_,
+                              MS_RDONLY,
+                              partition.filesystem_type,
+                              constants::kPostinstallMountOptions)) {
+    return CompletePartitionPostinstall(
+        1, "Error mounting the device " + mountable_device);
+  }
+
+  LOG(INFO) << "Performing postinst (" << partition.postinstall_path << " at "
+            << abs_path << ") installed on device " << partition.target_path
+            << " and mountable device " << mountable_device;
+
+  // Logs the file format of the postinstall script we are about to run. This
+  // will help debug when the postinstall script doesn't match the architecture
+  // of our build.
+  LOG(INFO) << "Format file for new " << partition.postinstall_path
+            << " is: " << utils::GetFileFormat(abs_path);
+
+  // Runs the postinstall script asynchronously to free up the main loop while
+  // it's running.
+  vector<string> command = {abs_path};
+#ifdef __ANDROID__
+  // In Brillo and Android, we pass the slot number and status fd.
+  command.push_back(std::to_string(install_plan_.target_slot));
+  command.push_back(std::to_string(kPostinstallStatusFd));
+#else
+  // Chrome OS postinstall expects the target rootfs as the first parameter.
+  command.push_back(partition.target_path);
+#endif  // __ANDROID__
+
+  current_command_ = Subprocess::Get().ExecFlags(
+      command,
+      Subprocess::kRedirectStderrToStdout,
+      {kPostinstallStatusFd},
+      base::Bind(&PostinstallRunnerAction::CompletePartitionPostinstall,
+                 base::Unretained(this)));
+  // Subprocess::Exec should never return a negative process id.
+  CHECK_GE(current_command_, 0);
+
+  if (!current_command_) {
+    CompletePartitionPostinstall(1, "Postinstall didn't launch");
+    return;
+  }
+
+  // Monitor the status file descriptor.
+  progress_fd_ =
+      Subprocess::Get().GetPipeFd(current_command_, kPostinstallStatusFd);
+  int fd_flags = fcntl(progress_fd_, F_GETFL, 0) | O_NONBLOCK;
+  if (HANDLE_EINTR(fcntl(progress_fd_, F_SETFL, fd_flags)) < 0) {
+    PLOG(ERROR) << "Unable to set non-blocking I/O mode on fd " << progress_fd_;
+  }
+
+  progress_task_ = MessageLoop::current()->WatchFileDescriptor(
+      FROM_HERE,
+      progress_fd_,
+      MessageLoop::WatchMode::kWatchRead,
+      true,
+      base::Bind(&PostinstallRunnerAction::OnProgressFdReady,
+                 base::Unretained(this)));
+}
+
+void PostinstallRunnerAction::OnProgressFdReady() {
+  char buf[1024];
+  size_t bytes_read;
+  do {
+    bytes_read = 0;
+    bool eof;
+    bool ok =
+        utils::ReadAll(progress_fd_, buf, arraysize(buf), &bytes_read, &eof);
+    progress_buffer_.append(buf, bytes_read);
+    // Process every line.
+    vector<string> lines = base::SplitString(
+        progress_buffer_, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+    if (!lines.empty()) {
+      progress_buffer_ = lines.back();
+      lines.pop_back();
+      for (const auto& line : lines) {
+        ProcessProgressLine(line);
+      }
+    }
+    if (!ok || eof) {
+      // There was either an error or an EOF condition, so we are done watching
+      // the file descriptor.
+      MessageLoop::current()->CancelTask(progress_task_);
+      progress_task_ = MessageLoop::kTaskIdNull;
+      return;
+    }
+  } while (bytes_read);
+}
+
+bool PostinstallRunnerAction::ProcessProgressLine(const string& line) {
+  double frac = 0;
+  if (sscanf(line.c_str(), "global_progress %lf", &frac) == 1 &&
+      !std::isnan(frac)) {
+    ReportProgress(frac);
+    return true;
+  }
+
+  return false;
+}
+
+void PostinstallRunnerAction::ReportProgress(double frac) {
+  if (!delegate_)
+    return;
+  if (current_partition_ >= partition_weight_.size()) {
+    delegate_->ProgressUpdate(1.);
+    return;
+  }
+  if (!std::isfinite(frac) || frac < 0)
+    frac = 0;
+  if (frac > 1)
+    frac = 1;
+  double postinst_action_progress =
+      (accumulated_weight_ + partition_weight_[current_partition_] * frac) /
+      total_weight_;
+  delegate_->ProgressUpdate(postinst_action_progress);
+}
+
+void PostinstallRunnerAction::Cleanup() {
+  utils::UnmountFilesystem(fs_mount_dir_);
+#ifndef __ANDROID__
+  if (!base::DeleteFile(base::FilePath(fs_mount_dir_), false)) {
+    PLOG(WARNING) << "Not removing temporary mountpoint " << fs_mount_dir_;
+  }
+#endif  // !__ANDROID__
+  fs_mount_dir_.clear();
+
+  progress_fd_ = -1;
+  if (progress_task_ != MessageLoop::kTaskIdNull) {
+    MessageLoop::current()->CancelTask(progress_task_);
+    progress_task_ = MessageLoop::kTaskIdNull;
+  }
+  progress_buffer_.clear();
+}
+
+void PostinstallRunnerAction::CompletePartitionPostinstall(
+    int return_code, const string& output) {
+  current_command_ = 0;
+  Cleanup();
+
+  if (return_code != 0) {
+    LOG(ERROR) << "Postinst command failed with code: " << return_code;
+    ErrorCode error_code = ErrorCode::kPostinstallRunnerError;
+
+    if (return_code == 3) {
+      // This special return code means that we tried to update firmware,
+      // but couldn't because we booted from FW B, and we need to reboot
+      // to get back to FW A.
+      error_code = ErrorCode::kPostinstallBootedFromFirmwareB;
+    }
+
+    if (return_code == 4) {
+      // This special return code means that we tried to update firmware,
+      // but couldn't because we booted from FW B, and we need to reboot
+      // to get back to FW A.
+      error_code = ErrorCode::kPostinstallFirmwareRONotUpdatable;
+    }
+
+    // If postinstall script for this partition is optional we can ignore the
+    // result.
+    if (install_plan_.partitions[current_partition_].postinstall_optional) {
+      LOG(INFO) << "Ignoring postinstall failure since it is optional";
+    } else {
+      return CompletePostinstall(error_code);
+    }
+  }
+  accumulated_weight_ += partition_weight_[current_partition_];
+  current_partition_++;
+  ReportProgress(0);
+
+  PerformPartitionPostinstall();
+}
+
+void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {
+  // We only attempt to mark the new slot as active if all the postinstall
+  // steps succeeded.
+  if (error_code == ErrorCode::kSuccess &&
+      !boot_control_->SetActiveBootSlot(install_plan_.target_slot)) {
+    error_code = ErrorCode::kPostinstallRunnerError;
+  }
+
+  ScopedActionCompleter completer(processor_, this);
+  completer.set_code(error_code);
+
+  if (error_code != ErrorCode::kSuccess) {
+    LOG(ERROR) << "Postinstall action failed.";
+
+    // Undo any changes done to trigger Powerwash.
+    if (powerwash_scheduled_)
+      hardware_->CancelPowerwash();
+
+    return;
+  }
+
+  LOG(INFO) << "All post-install commands succeeded";
+  if (HasOutputPipe()) {
+    SetOutputObject(install_plan_);
+  }
+}
+
+void PostinstallRunnerAction::SuspendAction() {
+  if (!current_command_)
+    return;
+  if (kill(current_command_, SIGSTOP) != 0) {
+    PLOG(ERROR) << "Couldn't pause child process " << current_command_;
+  }
+}
+
+void PostinstallRunnerAction::ResumeAction() {
+  if (!current_command_)
+    return;
+  if (kill(current_command_, SIGCONT) != 0) {
+    PLOG(ERROR) << "Couldn't resume child process " << current_command_;
+  }
+}
+
+void PostinstallRunnerAction::TerminateProcessing() {
+  if (!current_command_)
+    return;
+  // Calling KillExec() will discard the callback we registered and therefore
+  // the unretained reference to this object.
+  Subprocess::Get().KillExec(current_command_);
+  current_command_ = 0;
+  Cleanup();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/postinstall_runner_action.h b/update_engine/payload_consumer/postinstall_runner_action.h
new file mode 100644
index 0000000..2bde3ca
--- /dev/null
+++ b/update_engine/payload_consumer/postinstall_runner_action.h
@@ -0,0 +1,150 @@
+//
+// 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_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
+
+#include <string>
+#include <vector>
+
+#include <brillo/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/payload_consumer/install_plan.h"
+
+// The Postinstall Runner Action is responsible for running the postinstall
+// script of a successfully downloaded update.
+
+namespace chromeos_update_engine {
+
+class BootControlInterface;
+
+class PostinstallRunnerAction : public InstallPlanAction {
+ public:
+  PostinstallRunnerAction(BootControlInterface* boot_control,
+                          HardwareInterface* hardware)
+      : boot_control_(boot_control), hardware_(hardware) {}
+
+  // InstallPlanAction overrides.
+  void PerformAction() override;
+  void SuspendAction() override;
+  void ResumeAction() override;
+  void TerminateProcessing() override;
+
+  class DelegateInterface {
+   public:
+    virtual ~DelegateInterface() = default;
+
+    // Called whenever there is an overall progress update from the postinstall
+    // programs.
+    virtual void ProgressUpdate(double progress) = 0;
+  };
+
+  void set_delegate(DelegateInterface* delegate) { delegate_ = delegate; }
+
+  // Debugging/logging
+  static std::string StaticType() { return "PostinstallRunnerAction"; }
+  std::string Type() const override { return StaticType(); }
+
+ private:
+  friend class PostinstallRunnerActionTest;
+  FRIEND_TEST(PostinstallRunnerActionTest, ProcessProgressLineTest);
+
+  void PerformPartitionPostinstall();
+
+  // Called whenever the |progress_fd_| has data available to read.
+  void OnProgressFdReady();
+
+  // Updates the action progress according to the |line| passed from the
+  // postinstall program. Valid lines are:
+  //     global_progress <frac>
+  //         <frac> should be between 0.0 and 1.0; sets the progress to the
+  //         <frac> value.
+  bool ProcessProgressLine(const std::string& line);
+
+  // Report the progress to the delegate given that the postinstall operation
+  // for |current_partition_| has a current progress of |frac|, a value between
+  // 0 and 1 for that step.
+  void ReportProgress(double frac);
+
+  // Cleanup the setup made when running postinstall for a given partition.
+  // Unmount and remove the mountpoint directory if needed and cleanup the
+  // status file descriptor and message loop task watching for it.
+  void Cleanup();
+
+  // Subprocess::Exec callback.
+  void CompletePartitionPostinstall(int return_code,
+                                    const std::string& output);
+
+  // Complete the Action with the passed |error_code| and mark the new slot as
+  // ready. Called when the post-install script was run for all the partitions.
+  void CompletePostinstall(ErrorCode error_code);
+
+  InstallPlan install_plan_;
+
+  // The path where the filesystem will be mounted during post-install.
+  std::string fs_mount_dir_;
+
+  // The partition being processed on the list of partitions specified in the
+  // InstallPlan.
+  size_t current_partition_{0};
+
+  // A non-negative value representing the estimated weight of each partition
+  // passed in the install plan. The weight is used to predict the overall
+  // progress from the individual progress of each partition and should
+  // correspond to the time it takes to run it.
+  std::vector<double> partition_weight_;
+
+  // The sum of all the weights in |partition_weight_|.
+  double total_weight_{0};
+
+  // The sum of all the weights in |partition_weight_| up to but not including
+  // the |current_partition_|.
+  double accumulated_weight_{0};
+
+  // The delegate used to notify of progress updates, if any.
+  DelegateInterface* delegate_{nullptr};
+
+  // The BootControlInerface used to mark the new slot as ready.
+  BootControlInterface* boot_control_;
+
+  // HardwareInterface used to signal powerwash.
+  HardwareInterface* hardware_;
+
+  // Whether the Powerwash was scheduled before invoking post-install script.
+  // Used for cleaning up if post-install fails.
+  bool powerwash_scheduled_{false};
+
+  // Postinstall command currently running, or 0 if no program running.
+  pid_t current_command_{0};
+
+  // The parent progress file descriptor used to watch for progress reports from
+  // the postinstall program and the task watching for them.
+  int progress_fd_{-1};
+  brillo::MessageLoop::TaskId progress_task_{brillo::MessageLoop::kTaskIdNull};
+
+  // A buffer of a partial read line from the progress file descriptor.
+  std::string progress_buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(PostinstallRunnerAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
diff --git a/update_engine/payload_consumer/postinstall_runner_action_unittest.cc b/update_engine/payload_consumer/postinstall_runner_action_unittest.cc
new file mode 100644
index 0000000..e82a866
--- /dev/null
+++ b/update_engine/payload_consumer/postinstall_runner_action_unittest.cc
@@ -0,0 +1,362 @@
+//
+// 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/payload_consumer/postinstall_runner_action.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using brillo::MessageLoop;
+using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PostinstActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  PostinstActionProcessorDelegate() = default;
+  void ProcessingDone(const ActionProcessor* processor,
+                      ErrorCode code) override {
+    MessageLoop::current()->BreakLoop();
+    processing_done_called_ = true;
+  }
+  void ProcessingStopped(const ActionProcessor* processor) override {
+    MessageLoop::current()->BreakLoop();
+    processing_stopped_called_ = true;
+  }
+
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) override {
+    if (action->Type() == PostinstallRunnerAction::StaticType()) {
+      code_ = code;
+      code_set_ = true;
+    }
+  }
+
+  ErrorCode code_{ErrorCode::kError};
+  bool code_set_{false};
+  bool processing_done_called_{false};
+  bool processing_stopped_called_{false};
+};
+
+class MockPostinstallRunnerActionDelegate
+    : public PostinstallRunnerAction::DelegateInterface {
+ public:
+  MOCK_METHOD1(ProgressUpdate, void(double progress));
+};
+
+class PostinstallRunnerActionTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    async_signal_handler_.Init();
+    subprocess_.Init(&async_signal_handler_);
+    // These tests use the postinstall files generated by "generate_images.sh"
+    // stored in the "disk_ext2_unittest.img" image.
+    postinstall_image_ =
+        test_utils::GetBuildArtifactsPath("gen/disk_ext2_unittest.img");
+  }
+
+  // Setup an action processor and run the PostinstallRunnerAction with a single
+  // partition |device_path|, running the |postinstall_program| command from
+  // there.
+  void RunPosinstallAction(const string& device_path,
+                           const string& postinstall_program,
+                           bool powerwash_required);
+
+ public:
+  void ResumeRunningAction() {
+    ASSERT_NE(nullptr, postinstall_action_);
+    postinstall_action_->ResumeAction();
+  }
+
+  void SuspendRunningAction() {
+    if (!postinstall_action_ || !postinstall_action_->current_command_ ||
+        test_utils::Readlink(base::StringPrintf(
+            "/proc/%d/fd/0", postinstall_action_->current_command_)) !=
+            "/dev/zero") {
+      // We need to wait for the postinstall command to start and flag that it
+      // is ready by redirecting its input to /dev/zero.
+      loop_.PostDelayedTask(
+          FROM_HERE,
+          base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction,
+                     base::Unretained(this)),
+          base::TimeDelta::FromMilliseconds(100));
+    } else {
+      postinstall_action_->SuspendAction();
+      // Schedule to be resumed in a little bit.
+      loop_.PostDelayedTask(
+          FROM_HERE,
+          base::Bind(&PostinstallRunnerActionTest::ResumeRunningAction,
+                     base::Unretained(this)),
+          base::TimeDelta::FromMilliseconds(100));
+    }
+  }
+
+  void CancelWhenStarted() {
+    if (!postinstall_action_ || !postinstall_action_->current_command_) {
+      // Wait for the postinstall command to run.
+      loop_.PostDelayedTask(
+          FROM_HERE,
+          base::Bind(&PostinstallRunnerActionTest::CancelWhenStarted,
+                     base::Unretained(this)),
+          base::TimeDelta::FromMilliseconds(10));
+    } else {
+      CHECK(processor_);
+      processor_->StopProcessing();
+    }
+  }
+
+ protected:
+  base::MessageLoopForIO base_loop_;
+  brillo::BaseMessageLoop loop_{&base_loop_};
+  brillo::AsynchronousSignalHandler async_signal_handler_;
+  Subprocess subprocess_;
+
+  // The path to the postinstall sample image.
+  string postinstall_image_;
+
+  FakeBootControl fake_boot_control_;
+  FakeHardware fake_hardware_;
+  PostinstActionProcessorDelegate processor_delegate_;
+
+  // The PostinstallRunnerAction delegate receiving the progress updates.
+  PostinstallRunnerAction::DelegateInterface* setup_action_delegate_{nullptr};
+
+  // A pointer to the posinstall_runner action and the processor.
+  PostinstallRunnerAction* postinstall_action_{nullptr};
+  ActionProcessor* processor_{nullptr};
+};
+
+void PostinstallRunnerActionTest::RunPosinstallAction(
+    const string& device_path,
+    const string& postinstall_program,
+    bool powerwash_required) {
+  ActionProcessor processor;
+  processor_ = &processor;
+  ObjectFeederAction<InstallPlan> feeder_action;
+  InstallPlan::Partition part;
+  part.name = "part";
+  part.target_path = device_path;
+  part.run_postinstall = true;
+  part.postinstall_path = postinstall_program;
+  InstallPlan install_plan;
+  install_plan.partitions = {part};
+  install_plan.download_url = "http://127.0.0.1:8080/update";
+  install_plan.powerwash_required = powerwash_required;
+  feeder_action.set_obj(install_plan);
+  PostinstallRunnerAction runner_action(&fake_boot_control_, &fake_hardware_);
+  postinstall_action_ = &runner_action;
+  runner_action.set_delegate(setup_action_delegate_);
+  BondActions(&feeder_action, &runner_action);
+  ObjectCollectorAction<InstallPlan> collector_action;
+  BondActions(&runner_action, &collector_action);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&runner_action);
+  processor.EnqueueAction(&collector_action);
+  processor.set_delegate(&processor_delegate_);
+
+  loop_.PostTask(
+      FROM_HERE,
+      base::Bind(
+          [](ActionProcessor* processor) { processor->StartProcessing(); },
+          base::Unretained(&processor)));
+  loop_.Run();
+  ASSERT_FALSE(processor.IsRunning());
+  postinstall_action_ = nullptr;
+  processor_ = nullptr;
+  EXPECT_TRUE(processor_delegate_.processing_stopped_called_ ||
+              processor_delegate_.processing_done_called_);
+  if (processor_delegate_.processing_done_called_) {
+    // Sanity check that the code was set when the processor finishes.
+    EXPECT_TRUE(processor_delegate_.code_set_);
+  }
+}
+
+TEST_F(PostinstallRunnerActionTest, ProcessProgressLineTest) {
+  PostinstallRunnerAction action(&fake_boot_control_, &fake_hardware_);
+  testing::StrictMock<MockPostinstallRunnerActionDelegate> mock_delegate_;
+  action.set_delegate(&mock_delegate_);
+
+  action.current_partition_ = 1;
+  action.partition_weight_ = {1, 2, 5};
+  action.accumulated_weight_ = 1;
+  action.total_weight_ = 8;
+
+  // 50% of the second action is 2/8 = 0.25 of the total.
+  EXPECT_CALL(mock_delegate_, ProgressUpdate(0.25));
+  action.ProcessProgressLine("global_progress 0.5");
+  testing::Mock::VerifyAndClearExpectations(&mock_delegate_);
+
+  // 1.5 should be read as 100%, to catch rounding error cases like 1.000001.
+  // 100% of the second is 3/8 of the total.
+  EXPECT_CALL(mock_delegate_, ProgressUpdate(0.375));
+  action.ProcessProgressLine("global_progress 1.5");
+  testing::Mock::VerifyAndClearExpectations(&mock_delegate_);
+
+  // None of these should trigger a progress update.
+  action.ProcessProgressLine("foo_bar");
+  action.ProcessProgressLine("global_progress");
+  action.ProcessProgressLine("global_progress ");
+  action.ProcessProgressLine("global_progress NaN");
+  action.ProcessProgressLine("global_progress Exception in ... :)");
+}
+
+// Test that postinstall succeeds in the simple case of running the default
+// /postinst command which only exits 0.
+TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+  RunPosinstallAction(loop.dev(), kPostinstallDefaultScript, false);
+  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
+  EXPECT_TRUE(processor_delegate_.processing_done_called_);
+
+  // Since powerwash_required was false, this should not trigger a powerwash.
+  EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled());
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootRunSymlinkFileTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+  RunPosinstallAction(loop.dev(), "bin/postinst_link", false);
+  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+  // Run a simple postinstall program but requiring a powerwash.
+  RunPosinstallAction(loop.dev(), "bin/postinst_example", true);
+  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
+
+  // Check that powerwash was scheduled.
+  EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled());
+}
+
+// Runs postinstall from a partition file that doesn't mount, so it should
+// fail.
+TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
+  RunPosinstallAction("/dev/null", kPostinstallDefaultScript, false);
+  EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
+
+  // In case of failure, Postinstall should not signal a powerwash even if it
+  // was requested.
+  EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled());
+}
+
+// Check that the failures from the postinstall script cause the action to
+// fail.
+TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+  RunPosinstallAction(loop.dev(), "bin/postinst_fail1", false);
+  EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
+}
+
+// The exit code 3 and 4 are a specials cases that would be reported back to
+// UMA with a different error code. Test those cases are properly detected.
+TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+  RunPosinstallAction(loop.dev(), "bin/postinst_fail3", false);
+  EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB,
+            processor_delegate_.code_);
+}
+
+// Check that you can't specify an absolute path.
+TEST_F(PostinstallRunnerActionTest, RunAsRootAbsolutePathNotAllowedTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+  RunPosinstallAction(loop.dev(), "/etc/../bin/sh", false);
+  EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
+}
+
+#ifdef __ANDROID__
+// Check that the postinstall file is relabeled to the postinstall label.
+// SElinux labels are only set on Android.
+TEST_F(PostinstallRunnerActionTest, RunAsRootCheckFileContextsTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+  RunPosinstallAction(loop.dev(), "bin/self_check_context", false);
+  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
+}
+#endif  // __ANDROID__
+
+// Check that you can suspend/resume postinstall actions.
+TEST_F(PostinstallRunnerActionTest, RunAsRootSuspendResumeActionTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+
+  // We need to wait for the child to run and setup its signal handler.
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction,
+                            base::Unretained(this)));
+  RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
+  // postinst_suspend returns 0 only if it was suspended at some point.
+  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
+  EXPECT_TRUE(processor_delegate_.processing_done_called_);
+}
+
+// Test that we can cancel a postinstall action while it is running.
+TEST_F(PostinstallRunnerActionTest, RunAsRootCancelPostinstallActionTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+
+  // Wait for the action to start and then cancel it.
+  CancelWhenStarted();
+  RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
+  // When canceling the action, the action never finished and therefore we had
+  // a ProcessingStopped call instead.
+  EXPECT_FALSE(processor_delegate_.code_set_);
+  EXPECT_TRUE(processor_delegate_.processing_stopped_called_);
+}
+
+// Test that we parse and process the progress reports from the progress
+// file descriptor.
+TEST_F(PostinstallRunnerActionTest, RunAsRootProgressUpdatesTest) {
+  testing::StrictMock<MockPostinstallRunnerActionDelegate> mock_delegate_;
+  testing::InSequence s;
+  EXPECT_CALL(mock_delegate_, ProgressUpdate(0));
+
+  // The postinst_progress program will call with 0.25, 0.5 and 1.
+  EXPECT_CALL(mock_delegate_, ProgressUpdate(0.25));
+  EXPECT_CALL(mock_delegate_, ProgressUpdate(0.5));
+  EXPECT_CALL(mock_delegate_, ProgressUpdate(1.));
+
+  EXPECT_CALL(mock_delegate_, ProgressUpdate(1.));
+
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+  setup_action_delegate_ = &mock_delegate_;
+  RunPosinstallAction(loop.dev(), "bin/postinst_progress", false);
+  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/xz_extent_writer.cc b/update_engine/payload_consumer/xz_extent_writer.cc
new file mode 100644
index 0000000..370a00e
--- /dev/null
+++ b/update_engine/payload_consumer/xz_extent_writer.cc
@@ -0,0 +1,123 @@
+//
+// 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/payload_consumer/xz_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const brillo::Blob::size_type kOutputBufferLength = 16 * 1024;
+
+// xz uses a variable dictionary size which impacts on the compression ratio
+// and is required to be reconstructed in RAM during decompression. While we
+// control the required memory from the compressor side, the decompressor allows
+// to set a limit on this dictionary size, rejecting compressed streams that
+// require more than that. "xz -9" requires up to 64 MiB, so a 64 MiB limit
+// will allow compressed streams up to -9, the maximum compression setting.
+const uint32_t kXzMaxDictSize = 64 * 1024 * 1024;
+
+const char* XzErrorString(enum xz_ret error) {
+  #define __XZ_ERROR_STRING_CASE(code) case code: return #code;
+  switch (error) {
+    __XZ_ERROR_STRING_CASE(XZ_OK)
+    __XZ_ERROR_STRING_CASE(XZ_STREAM_END)
+    __XZ_ERROR_STRING_CASE(XZ_UNSUPPORTED_CHECK)
+    __XZ_ERROR_STRING_CASE(XZ_MEM_ERROR)
+    __XZ_ERROR_STRING_CASE(XZ_MEMLIMIT_ERROR)
+    __XZ_ERROR_STRING_CASE(XZ_FORMAT_ERROR)
+    __XZ_ERROR_STRING_CASE(XZ_OPTIONS_ERROR)
+    __XZ_ERROR_STRING_CASE(XZ_DATA_ERROR)
+    __XZ_ERROR_STRING_CASE(XZ_BUF_ERROR)
+    default:
+      return "<unknown xz error>";
+  }
+  #undef __XZ_ERROR_STRING_CASE
+};
+}  // namespace
+
+XzExtentWriter::~XzExtentWriter() {
+  xz_dec_end(stream_);
+}
+
+bool XzExtentWriter::Init(FileDescriptorPtr fd,
+                          const vector<Extent>& extents,
+                          uint32_t block_size) {
+  stream_ = xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize);
+  TEST_AND_RETURN_FALSE(stream_ != nullptr);
+  return underlying_writer_->Init(fd, extents, block_size);
+}
+
+bool XzExtentWriter::Write(const void* bytes, size_t count) {
+  // Copy the input data into |input_buffer_| only if |input_buffer_| already
+  // contains unconsumed data. Otherwise, process the data directly from the
+  // source.
+  const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
+  if (!input_buffer_.empty()) {
+    input_buffer_.insert(input_buffer_.end(), input, input + count);
+    input = input_buffer_.data();
+    count = input_buffer_.size();
+  }
+
+  xz_buf request;
+  request.in = input;
+  request.in_pos = 0;
+  request.in_size = count;
+
+  brillo::Blob output_buffer(kOutputBufferLength);
+  request.out = output_buffer.data();
+  request.out_size = output_buffer.size();
+  for (;;) {
+    request.out_pos = 0;
+
+    xz_ret ret = xz_dec_run(stream_, &request);
+    if (ret != XZ_OK && ret != XZ_STREAM_END) {
+      LOG(ERROR) << "xz_dec_run returned " << XzErrorString(ret);
+      return false;
+    }
+
+    if (request.out_pos == 0)
+      break;
+
+    TEST_AND_RETURN_FALSE(
+        underlying_writer_->Write(output_buffer.data(), request.out_pos));
+    if (ret == XZ_STREAM_END) {
+      if (request.in_size != request.in_pos) {
+          LOG(ERROR) << "request.in_size != request.in_pos ( "
+                     << request.in_size << " vs " << request.in_pos << " )";
+          return false;
+      }
+    }
+    if (request.in_size == request.in_pos)
+      break;  // No more input to process.
+  }
+  output_buffer.clear();
+
+  // Store unconsumed data (if any) in |input_buffer_|. Since |input| can point
+  // to the existing |input_buffer_| we create a new one before assigning it.
+  brillo::Blob new_input_buffer(request.in + request.in_pos,
+                                request.in + request.in_size);
+  input_buffer_ = std::move(new_input_buffer);
+  return true;
+}
+
+bool XzExtentWriter::EndImpl() {
+  TEST_AND_RETURN_FALSE(input_buffer_.empty());
+  return underlying_writer_->End();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/xz_extent_writer.h b/update_engine/payload_consumer/xz_extent_writer.h
new file mode 100644
index 0000000..a6b3257
--- /dev/null
+++ b/update_engine/payload_consumer/xz_extent_writer.h
@@ -0,0 +1,60 @@
+//
+// 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_PAYLOAD_CONSUMER_XZ_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_XZ_EXTENT_WRITER_H_
+
+#include <xz.h>
+
+#include <memory>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/extent_writer.h"
+
+// XzExtentWriter is a concrete ExtentWriter subclass that xz-decompresses
+// what it's given in Write using xz-embedded. Note that xz-embedded only
+// supports files with either no CRC or CRC-32. It passes the decompressed data
+// to an underlying ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class XzExtentWriter : public ExtentWriter {
+ public:
+  explicit XzExtentWriter(std::unique_ptr<ExtentWriter> underlying_writer)
+      : underlying_writer_(std::move(underlying_writer)) {}
+  ~XzExtentWriter() override;
+
+  bool Init(FileDescriptorPtr fd,
+            const std::vector<Extent>& extents,
+            uint32_t block_size) override;
+  bool Write(const void* bytes, size_t count) override;
+  bool EndImpl() override;
+
+ private:
+  // The underlying ExtentWriter.
+  std::unique_ptr<ExtentWriter> underlying_writer_;
+  // The opaque xz decompressor struct.
+  xz_dec* stream_{nullptr};
+  brillo::Blob input_buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(XzExtentWriter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_XZ_EXTENT_WRITER_H_
diff --git a/update_engine/payload_consumer/xz_extent_writer_unittest.cc b/update_engine/payload_consumer/xz_extent_writer_unittest.cc
new file mode 100644
index 0000000..fb8bb40
--- /dev/null
+++ b/update_engine/payload_consumer/xz_extent_writer_unittest.cc
@@ -0,0 +1,165 @@
+//
+// 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/payload_consumer/xz_extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/fake_extent_writer.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const char kSampleData[] = "Redundaaaaaaaaaaaaaant\n";
+
+// Compressed data with CRC-32 check, generated with:
+// echo "Redundaaaaaaaaaaaaaant" | xz -9 --check=crc32 |
+// hexdump -v -e '"    " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressedDataCRC32[] = {
+    0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36,
+    0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+    0xe0, 0x00, 0x16, 0x00, 0x10, 0x5d, 0x00, 0x29, 0x19, 0x48, 0x87, 0x88,
+    0xec, 0x49, 0x88, 0x73, 0x8b, 0x5d, 0xa6, 0x46, 0xb4, 0x00, 0x00, 0x00,
+    0x68, 0xfc, 0x7b, 0x25, 0x00, 0x01, 0x28, 0x17, 0x46, 0x9e, 0x08, 0xfe,
+    0x90, 0x42, 0x99, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a,
+};
+
+// Compressed data without checksum, generated with:
+// echo "Redundaaaaaaaaaaaaaant" | xz -9 --check=none |
+// hexdump -v -e '"    " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressedDataNoCheck[] = {
+    0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41,
+    0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+    0xe0, 0x00, 0x16, 0x00, 0x10, 0x5d, 0x00, 0x29, 0x19, 0x48, 0x87, 0x88,
+    0xec, 0x49, 0x88, 0x73, 0x8b, 0x5d, 0xa6, 0x46, 0xb4, 0x00, 0x00, 0x00,
+    0x00, 0x01, 0x24, 0x17, 0x4a, 0xd1, 0xbd, 0x52, 0x06, 0x72, 0x9e, 0x7a,
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x5a,
+};
+
+// Highly redundant data bigger than the internal buffer, generated with:
+// dd if=/dev/zero bs=30K count=1 | tr '\0' 'a' | xz -9 --check=crc32 |
+// hexdump -v -e '"    " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressed30KiBofA[] = {
+    0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36,
+    0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+    0xe0, 0x77, 0xff, 0x00, 0x41, 0x5d, 0x00, 0x30, 0xef, 0xfb, 0xbf, 0xfe,
+    0xa3, 0xb1, 0x5e, 0xe5, 0xf8, 0x3f, 0xb2, 0xaa, 0x26, 0x55, 0xf8, 0x68,
+    0x70, 0x41, 0x70, 0x15, 0x0f, 0x8d, 0xfd, 0x1e, 0x4c, 0x1b, 0x8a, 0x42,
+    0xb7, 0x19, 0xf4, 0x69, 0x18, 0x71, 0xae, 0x66, 0x23, 0x8a, 0x8a, 0x4d,
+    0x2f, 0xa3, 0x0d, 0xd9, 0x7f, 0xa6, 0xe3, 0x8c, 0x23, 0x11, 0x53, 0xe0,
+    0x59, 0x18, 0xc5, 0x75, 0x8a, 0xe2, 0x76, 0x4c, 0xee, 0x30, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0xf9, 0x47, 0xb5, 0xee, 0x00, 0x01, 0x59, 0x80,
+    0xf0, 0x01, 0x00, 0x00, 0xe0, 0x41, 0x96, 0xde, 0x3e, 0x30, 0x0d, 0x8b,
+    0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a,
+};
+
+}  // namespace
+
+class XzExtentWriterTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    fake_extent_writer_ = new FakeExtentWriter();
+    xz_writer_.reset(
+        new XzExtentWriter(brillo::make_unique_ptr(fake_extent_writer_)));
+  }
+
+  void WriteAll(const brillo::Blob& compressed) {
+    EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+    EXPECT_TRUE(xz_writer_->Write(compressed.data(), compressed.size()));
+    EXPECT_TRUE(xz_writer_->End());
+
+    EXPECT_TRUE(fake_extent_writer_->InitCalled());
+    EXPECT_TRUE(fake_extent_writer_->EndCalled());
+  }
+
+  // Owned by |xz_writer_|. This object is invalidated after |xz_writer_| is
+  // deleted.
+  FakeExtentWriter* fake_extent_writer_{nullptr};
+  std::unique_ptr<XzExtentWriter> xz_writer_;
+
+  const brillo::Blob sample_data_{
+      std::begin(kSampleData),
+      std::begin(kSampleData) + strlen(kSampleData)};
+  FileDescriptorPtr fd_;
+};
+
+TEST_F(XzExtentWriterTest, CreateAndDestroy) {
+  // Test that no Init() or End() called doesn't crash the program.
+  EXPECT_FALSE(fake_extent_writer_->InitCalled());
+  EXPECT_FALSE(fake_extent_writer_->EndCalled());
+}
+
+TEST_F(XzExtentWriterTest, CompressedSampleData) {
+  WriteAll(brillo::Blob(std::begin(kCompressedDataNoCheck),
+                          std::end(kCompressedDataNoCheck)));
+  EXPECT_EQ(sample_data_, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, CompressedSampleDataWithCrc) {
+  WriteAll(brillo::Blob(std::begin(kCompressedDataCRC32),
+                          std::end(kCompressedDataCRC32)));
+  EXPECT_EQ(sample_data_, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, CompressedDataBiggerThanTheBuffer) {
+  // Test that even if the output data is bigger than the internal buffer, all
+  // the data is written.
+  WriteAll(brillo::Blob(std::begin(kCompressed30KiBofA),
+                          std::end(kCompressed30KiBofA)));
+  brillo::Blob expected_data(30 * 1024, 'a');
+  EXPECT_EQ(expected_data, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, GarbageDataRejected) {
+  EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+  // The sample_data_ is an uncompressed string.
+  EXPECT_FALSE(xz_writer_->Write(sample_data_.data(), sample_data_.size()));
+  EXPECT_TRUE(xz_writer_->End());
+
+  EXPECT_TRUE(fake_extent_writer_->EndCalled());
+}
+
+TEST_F(XzExtentWriterTest, PartialDataIsKept) {
+  brillo::Blob compressed(std::begin(kCompressed30KiBofA),
+                            std::end(kCompressed30KiBofA));
+  EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+  for (uint8_t byte : compressed) {
+    EXPECT_TRUE(xz_writer_->Write(&byte, 1));
+  }
+  EXPECT_TRUE(xz_writer_->End());
+
+  // The sample_data_ is an uncompressed string.
+  brillo::Blob expected_data(30 * 1024, 'a');
+  EXPECT_EQ(expected_data, fake_extent_writer_->WrittenData());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/ab_generator.cc b/update_engine/payload_generator/ab_generator.cc
new file mode 100644
index 0000000..efb8ccf
--- /dev/null
+++ b/update_engine/payload_generator/ab_generator.cc
@@ -0,0 +1,321 @@
+//
+// 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/payload_generator/ab_generator.h"
+
+#include <algorithm>
+
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/bzip.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+
+using chromeos_update_engine::diff_utils::IsAReplaceOperation;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+bool ABGenerator::GenerateOperations(
+    const PayloadGenerationConfig& config,
+    const PartitionConfig& old_part,
+    const PartitionConfig& new_part,
+    BlobFileWriter* blob_file,
+    vector<AnnotatedOperation>* aops) {
+  TEST_AND_RETURN_FALSE(old_part.name == new_part.name);
+
+  ssize_t hard_chunk_blocks = (config.hard_chunk_size == -1 ? -1 :
+                               config.hard_chunk_size / config.block_size);
+  size_t soft_chunk_blocks = config.soft_chunk_size / config.block_size;
+
+  aops->clear();
+  TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(aops,
+                                                       old_part,
+                                                       new_part,
+                                                       hard_chunk_blocks,
+                                                       soft_chunk_blocks,
+                                                       config.version,
+                                                       blob_file));
+  LOG(INFO) << "done reading " << new_part.name;
+
+  TEST_AND_RETURN_FALSE(
+      FragmentOperations(config.version, aops, new_part.path, blob_file));
+  SortOperationsByDestination(aops);
+
+  // Use the soft_chunk_size when merging operations to prevent merging all
+  // the operations into a huge one if there's no hard limit.
+  size_t merge_chunk_blocks = soft_chunk_blocks;
+  if (hard_chunk_blocks != -1 &&
+      static_cast<size_t>(hard_chunk_blocks) < soft_chunk_blocks) {
+    merge_chunk_blocks = hard_chunk_blocks;
+  }
+
+  TEST_AND_RETURN_FALSE(MergeOperations(
+      aops, config.version, merge_chunk_blocks, new_part.path, blob_file));
+
+  if (config.version.minor >= kOpSrcHashMinorPayloadVersion)
+    TEST_AND_RETURN_FALSE(AddSourceHash(aops, old_part.path));
+
+  return true;
+}
+
+void ABGenerator::SortOperationsByDestination(
+    vector<AnnotatedOperation>* aops) {
+  sort(aops->begin(), aops->end(), diff_utils::CompareAopsByDestination);
+}
+
+bool ABGenerator::FragmentOperations(const PayloadVersion& version,
+                                     vector<AnnotatedOperation>* aops,
+                                     const string& target_part_path,
+                                     BlobFileWriter* blob_file) {
+  vector<AnnotatedOperation> fragmented_aops;
+  for (const AnnotatedOperation& aop : *aops) {
+    if (aop.op.type() == InstallOperation::SOURCE_COPY) {
+      TEST_AND_RETURN_FALSE(SplitSourceCopy(aop, &fragmented_aops));
+    } else if (IsAReplaceOperation(aop.op.type())) {
+      TEST_AND_RETURN_FALSE(SplitAReplaceOp(
+          version, aop, target_part_path, &fragmented_aops, blob_file));
+    } else {
+      fragmented_aops.push_back(aop);
+    }
+  }
+  *aops = std::move(fragmented_aops);
+  return true;
+}
+
+bool ABGenerator::SplitSourceCopy(
+    const AnnotatedOperation& original_aop,
+    vector<AnnotatedOperation>* result_aops) {
+  InstallOperation original_op = original_aop.op;
+  TEST_AND_RETURN_FALSE(original_op.type() == InstallOperation::SOURCE_COPY);
+  // Keeps track of the index of curr_src_ext.
+  int curr_src_ext_index = 0;
+  Extent curr_src_ext = original_op.src_extents(curr_src_ext_index);
+  for (int i = 0; i < original_op.dst_extents_size(); i++) {
+    const Extent& dst_ext = original_op.dst_extents(i);
+    // The new operation which will have only one dst extent.
+    InstallOperation new_op;
+    uint64_t blocks_left = dst_ext.num_blocks();
+    while (blocks_left > 0) {
+      if (curr_src_ext.num_blocks() <= blocks_left) {
+        // If the curr_src_ext is smaller than dst_ext, add it.
+        blocks_left -= curr_src_ext.num_blocks();
+        *(new_op.add_src_extents()) = curr_src_ext;
+        if (curr_src_ext_index + 1 < original_op.src_extents().size()) {
+          curr_src_ext = original_op.src_extents(++curr_src_ext_index);
+        } else {
+          break;
+        }
+      } else {
+        // Split src_exts that are bigger than the dst_ext we're dealing with.
+        Extent first_ext;
+        first_ext.set_num_blocks(blocks_left);
+        first_ext.set_start_block(curr_src_ext.start_block());
+        *(new_op.add_src_extents()) = first_ext;
+        // Keep the second half of the split op.
+        curr_src_ext.set_num_blocks(curr_src_ext.num_blocks() - blocks_left);
+        curr_src_ext.set_start_block(curr_src_ext.start_block() + blocks_left);
+        blocks_left -= first_ext.num_blocks();
+      }
+    }
+    // Fix up our new operation and add it to the results.
+    new_op.set_type(InstallOperation::SOURCE_COPY);
+    *(new_op.add_dst_extents()) = dst_ext;
+    new_op.set_src_length(dst_ext.num_blocks() * kBlockSize);
+    new_op.set_dst_length(dst_ext.num_blocks() * kBlockSize);
+
+    AnnotatedOperation new_aop;
+    new_aop.op = new_op;
+    new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
+    result_aops->push_back(new_aop);
+  }
+  if (curr_src_ext_index != original_op.src_extents().size() - 1) {
+    LOG(FATAL) << "Incorrectly split SOURCE_COPY operation. Did not use all "
+               << "source extents.";
+  }
+  return true;
+}
+
+bool ABGenerator::SplitAReplaceOp(const PayloadVersion& version,
+                                  const AnnotatedOperation& original_aop,
+                                  const string& target_part_path,
+                                  vector<AnnotatedOperation>* result_aops,
+                                  BlobFileWriter* blob_file) {
+  InstallOperation original_op = original_aop.op;
+  TEST_AND_RETURN_FALSE(IsAReplaceOperation(original_op.type()));
+  const bool is_replace = original_op.type() == InstallOperation::REPLACE;
+
+  uint32_t data_offset = original_op.data_offset();
+  for (int i = 0; i < original_op.dst_extents_size(); i++) {
+    const Extent& dst_ext = original_op.dst_extents(i);
+    // Make a new operation with only one dst extent.
+    InstallOperation new_op;
+    *(new_op.add_dst_extents()) = dst_ext;
+    uint32_t data_size = dst_ext.num_blocks() * kBlockSize;
+    new_op.set_dst_length(data_size);
+    // If this is a REPLACE, attempt to reuse portions of the existing blob.
+    if (is_replace) {
+      new_op.set_type(InstallOperation::REPLACE);
+      new_op.set_data_length(data_size);
+      new_op.set_data_offset(data_offset);
+      data_offset += data_size;
+    }
+
+    AnnotatedOperation new_aop;
+    new_aop.op = new_op;
+    new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
+    TEST_AND_RETURN_FALSE(
+        AddDataAndSetType(&new_aop, version, target_part_path, blob_file));
+
+    result_aops->push_back(new_aop);
+  }
+  return true;
+}
+
+bool ABGenerator::MergeOperations(vector<AnnotatedOperation>* aops,
+                                  const PayloadVersion& version,
+                                  size_t chunk_blocks,
+                                  const string& target_part_path,
+                                  BlobFileWriter* blob_file) {
+  vector<AnnotatedOperation> new_aops;
+  for (const AnnotatedOperation& curr_aop : *aops) {
+    if (new_aops.empty()) {
+      new_aops.push_back(curr_aop);
+      continue;
+    }
+    AnnotatedOperation& last_aop = new_aops.back();
+    bool last_is_a_replace = IsAReplaceOperation(last_aop.op.type());
+
+    if (last_aop.op.dst_extents_size() <= 0 ||
+        curr_aop.op.dst_extents_size() <= 0) {
+      new_aops.push_back(curr_aop);
+      continue;
+    }
+    uint32_t last_dst_idx = last_aop.op.dst_extents_size() - 1;
+    uint32_t last_end_block =
+        last_aop.op.dst_extents(last_dst_idx).start_block() +
+        last_aop.op.dst_extents(last_dst_idx).num_blocks();
+    uint32_t curr_start_block = curr_aop.op.dst_extents(0).start_block();
+    uint32_t combined_block_count =
+        last_aop.op.dst_extents(last_dst_idx).num_blocks() +
+        curr_aop.op.dst_extents(0).num_blocks();
+    bool is_a_replace = IsAReplaceOperation(curr_aop.op.type());
+
+    bool is_delta_op = curr_aop.op.type() == InstallOperation::SOURCE_COPY;
+    if (((is_delta_op && (last_aop.op.type() == curr_aop.op.type())) ||
+         (is_a_replace && last_is_a_replace)) &&
+        last_end_block == curr_start_block &&
+        combined_block_count <= chunk_blocks) {
+      // If the operations have the same type (which is a type that we can
+      // merge), are contiguous, are fragmented to have one destination extent,
+      // and their combined block count would be less than chunk size, merge
+      // them.
+      last_aop.name = base::StringPrintf("%s,%s",
+                                         last_aop.name.c_str(),
+                                         curr_aop.name.c_str());
+
+      if (is_delta_op) {
+        ExtendExtents(last_aop.op.mutable_src_extents(),
+                      curr_aop.op.src_extents());
+        if (curr_aop.op.src_length() > 0)
+          last_aop.op.set_src_length(last_aop.op.src_length() +
+                                     curr_aop.op.src_length());
+      }
+      ExtendExtents(last_aop.op.mutable_dst_extents(),
+                    curr_aop.op.dst_extents());
+      if (curr_aop.op.dst_length() > 0)
+        last_aop.op.set_dst_length(last_aop.op.dst_length() +
+                                   curr_aop.op.dst_length());
+      // Set the data length to zero so we know to add the blob later.
+      if (is_a_replace)
+        last_aop.op.set_data_length(0);
+    } else {
+      // Otherwise just include the extent as is.
+      new_aops.push_back(curr_aop);
+    }
+  }
+
+  // Set the blobs for REPLACE/REPLACE_BZ/REPLACE_XZ operations that have been
+  // merged.
+  for (AnnotatedOperation& curr_aop : new_aops) {
+    if (curr_aop.op.data_length() == 0 &&
+        IsAReplaceOperation(curr_aop.op.type())) {
+      TEST_AND_RETURN_FALSE(
+          AddDataAndSetType(&curr_aop, version, target_part_path, blob_file));
+    }
+  }
+
+  *aops = new_aops;
+  return true;
+}
+
+bool ABGenerator::AddDataAndSetType(AnnotatedOperation* aop,
+                                    const PayloadVersion& version,
+                                    const string& target_part_path,
+                                    BlobFileWriter* blob_file) {
+  TEST_AND_RETURN_FALSE(IsAReplaceOperation(aop->op.type()));
+
+  brillo::Blob data(aop->op.dst_length());
+  vector<Extent> dst_extents;
+  ExtentsToVector(aop->op.dst_extents(), &dst_extents);
+  TEST_AND_RETURN_FALSE(utils::ReadExtents(target_part_path,
+                                           dst_extents,
+                                           &data,
+                                           data.size(),
+                                           kBlockSize));
+
+  brillo::Blob blob;
+  InstallOperation_Type op_type;
+  TEST_AND_RETURN_FALSE(
+      diff_utils::GenerateBestFullOperation(data, version, &blob, &op_type));
+
+  // If the operation doesn't point to a data blob or points to a data blob of
+  // a different type then we add it.
+  if (aop->op.type() != op_type || aop->op.data_length() != blob.size()) {
+    aop->op.set_type(op_type);
+    aop->SetOperationBlob(blob, blob_file);
+  }
+
+  return true;
+}
+
+bool ABGenerator::AddSourceHash(vector<AnnotatedOperation>* aops,
+                                const string& source_part_path) {
+  for (AnnotatedOperation& aop : *aops) {
+    if (aop.op.src_extents_size() == 0)
+      continue;
+
+    vector<Extent> src_extents;
+    ExtentsToVector(aop.op.src_extents(), &src_extents);
+    brillo::Blob src_data, src_hash;
+    uint64_t src_length =
+        aop.op.has_src_length()
+            ? aop.op.src_length()
+            : BlocksInExtents(aop.op.src_extents()) * kBlockSize;
+    TEST_AND_RETURN_FALSE(utils::ReadExtents(
+        source_part_path, src_extents, &src_data, src_length, kBlockSize));
+    TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfData(src_data, &src_hash));
+    aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/ab_generator.h b/update_engine/payload_generator/ab_generator.h
new file mode 100644
index 0000000..77afb87
--- /dev/null
+++ b/update_engine/payload_generator/ab_generator.h
@@ -0,0 +1,136 @@
+//
+// 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_PAYLOAD_GENERATOR_AB_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_AB_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/payload_generator/filesystem_interface.h"
+#include "update_engine/payload_generator/operations_generator.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The ABGenerator is an operations generator that generates payloads using the
+// A-to-B operations SOURCE_COPY and SOURCE_BSDIFF introduced in the payload
+// minor version 2 format.
+class ABGenerator : public OperationsGenerator {
+ public:
+  ABGenerator() = default;
+
+  // Generate the update payload operations for the given partition using
+  // SOURCE_* operations, used for generating deltas for the minor version
+  // kSourceMinorPayloadVersion. This function will generate operations in the
+  // partition that will read blocks from the source partition in random order
+  // and write the new image on the target partition, also possibly in random
+  // order. The operations are stored in |aops| and should be executed in that
+  // order. All the offsets in the operations reference the data written to
+  // |blob_file|.
+  bool GenerateOperations(
+      const PayloadGenerationConfig& config,
+      const PartitionConfig& old_part,
+      const PartitionConfig& new_part,
+      BlobFileWriter* blob_file,
+      std::vector<AnnotatedOperation>* aops) override;
+
+  // Split the operations in the vector of AnnotatedOperations |aops|
+  // such that for every operation there is only one dst extent and updates
+  // |aops| with the new list of operations. All kinds of operations are
+  // fragmented except BSDIFF and SOURCE_BSDIFF operations.
+  // The |target_part_path| is the filename of the new image, where the
+  // destination extents refer to. The blobs of the operations in |aops| should
+  // reference |blob_file|. |blob_file| are updated if needed.
+  static bool FragmentOperations(const PayloadVersion& version,
+                                 std::vector<AnnotatedOperation>* aops,
+                                 const std::string& target_part_path,
+                                 BlobFileWriter* blob_file);
+
+  // Takes a vector of AnnotatedOperations |aops| and sorts them by the first
+  // start block in their destination extents. Sets |aops| to a vector of the
+  // sorted operations.
+  static void SortOperationsByDestination(
+      std::vector<AnnotatedOperation>* aops);
+
+  // Takes an SOURCE_COPY install operation, |aop|, and adds one operation for
+  // each dst extent in |aop| to |ops|. The new operations added to |ops| will
+  // have only one dst extent. The src extents are split so the number of blocks
+  // in the src and dst extents are equal.
+  // E.g. we have a SOURCE_COPY operation:
+  //   src extents: [(1, 3), (5, 1), (7, 1)], dst extents: [(2, 2), (6, 3)]
+  // Then we will get 2 new operations:
+  //   1. src extents: [(1, 2)], dst extents: [(2, 2)]
+  //   2. src extents: [(3, 1),(5, 1),(7, 1)], dst extents: [(6, 3)]
+  static bool SplitSourceCopy(const AnnotatedOperation& original_aop,
+                              std::vector<AnnotatedOperation>* result_aops);
+
+  // Takes a REPLACE, REPLACE_BZ or REPLACE_XZ operation |aop|, and adds one
+  // operation for each dst extent in |aop| to |ops|. The new operations added
+  // to |ops| will have only one dst extent each, and may be of a different
+  // type depending on whether compression is advantageous.
+  static bool SplitAReplaceOp(const PayloadVersion& version,
+                              const AnnotatedOperation& original_aop,
+                              const std::string& target_part,
+                              std::vector<AnnotatedOperation>* result_aops,
+                              BlobFileWriter* blob_file);
+
+  // Takes a sorted (by first destination extent) vector of operations |aops|
+  // and merges SOURCE_COPY, REPLACE, REPLACE_BZ and REPLACE_XZ, operations in
+  // that vector.
+  // It will merge two operations if:
+  //   - They are both REPLACE_*, or they are both SOURCE_COPY,
+  //   - Their destination blocks are contiguous.
+  //   - Their combined blocks do not exceed |chunk_blocks| blocks.
+  // Note that unlike other methods, you can't pass a negative number in
+  // |chunk_blocks|.
+  static bool MergeOperations(std::vector<AnnotatedOperation>* aops,
+                              const PayloadVersion& version,
+                              size_t chunk_blocks,
+                              const std::string& target_part,
+                              BlobFileWriter* blob_file);
+
+  // Takes a vector of AnnotatedOperations |aops|, adds source hash to all
+  // operations that have src_extents.
+  static bool AddSourceHash(std::vector<AnnotatedOperation>* aops,
+                            const std::string& source_part_path);
+
+ private:
+  // Adds the data payload for a REPLACE/REPLACE_BZ/REPLACE_XZ operation |aop|
+  // by reading its output extents from |target_part_path| and appending a
+  // corresponding data blob to |blob_file|. The blob will be compressed if this
+  // is smaller than the uncompressed form, and the operation type will be set
+  // accordingly. |*blob_file| will be updated as well. If the operation happens
+  // to have the right type and already points to a data blob, nothing is
+  // written. Caller should only set type and data blob if it's valid.
+  static bool AddDataAndSetType(AnnotatedOperation* aop,
+                                const PayloadVersion& version,
+                                const std::string& target_part_path,
+                                BlobFileWriter* blob_file);
+
+  DISALLOW_COPY_AND_ASSIGN(ABGenerator);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_AB_GENERATOR_H_
diff --git a/update_engine/payload_generator/ab_generator_unittest.cc b/update_engine/payload_generator/ab_generator_unittest.cc
new file mode 100644
index 0000000..3fd2323
--- /dev/null
+++ b/update_engine/payload_generator/ab_generator_unittest.cc
@@ -0,0 +1,599 @@
+//
+// 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/payload_generator/ab_generator.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <random>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/bzip.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+bool ExtentEquals(const Extent& ext, uint64_t start_block, uint64_t num_blocks) {
+  return ext.start_block() == start_block && ext.num_blocks() == num_blocks;
+}
+
+// Tests splitting of a REPLACE/REPLACE_BZ operation.
+void TestSplitReplaceOrReplaceBzOperation(InstallOperation_Type orig_type,
+                                          bool compressible) {
+  const size_t op_ex1_start_block = 2;
+  const size_t op_ex1_num_blocks = 2;
+  const size_t op_ex2_start_block = 6;
+  const size_t op_ex2_num_blocks = 1;
+  const size_t part_num_blocks = 7;
+
+  // Create the target partition data.
+  string part_path;
+  EXPECT_TRUE(utils::MakeTempFile(
+      "SplitReplaceOrReplaceBzTest_part.XXXXXX", &part_path, nullptr));
+  ScopedPathUnlinker part_path_unlinker(part_path);
+  const size_t part_size = part_num_blocks * kBlockSize;
+  brillo::Blob part_data;
+  if (compressible) {
+    part_data.resize(part_size);
+    test_utils::FillWithData(&part_data);
+  } else {
+    std::mt19937 gen(12345);
+    std::uniform_int_distribution<uint8_t> dis(0, 255);
+    for (uint32_t i = 0; i < part_size; i++)
+      part_data.push_back(dis(gen));
+  }
+  ASSERT_EQ(part_size, part_data.size());
+  ASSERT_TRUE(utils::WriteFile(part_path.c_str(), part_data.data(), part_size));
+
+  // Create original operation and blob data.
+  const size_t op_ex1_offset = op_ex1_start_block * kBlockSize;
+  const size_t op_ex1_size = op_ex1_num_blocks * kBlockSize;
+  const size_t op_ex2_offset = op_ex2_start_block * kBlockSize;
+  const size_t op_ex2_size = op_ex2_num_blocks * kBlockSize;
+  InstallOperation op;
+  op.set_type(orig_type);
+  *(op.add_dst_extents()) = ExtentForRange(op_ex1_start_block,
+                                           op_ex1_num_blocks);
+  *(op.add_dst_extents()) = ExtentForRange(op_ex2_start_block,
+                                           op_ex2_num_blocks);
+  op.set_dst_length(op_ex1_num_blocks + op_ex2_num_blocks);
+
+  brillo::Blob op_data;
+  op_data.insert(op_data.end(),
+                 part_data.begin() + op_ex1_offset,
+                 part_data.begin() + op_ex1_offset + op_ex1_size);
+  op_data.insert(op_data.end(),
+                 part_data.begin() + op_ex2_offset,
+                 part_data.begin() + op_ex2_offset + op_ex2_size);
+  brillo::Blob op_blob;
+  if (orig_type == InstallOperation::REPLACE) {
+    op_blob = op_data;
+  } else {
+    ASSERT_TRUE(BzipCompress(op_data, &op_blob));
+  }
+  op.set_data_offset(0);
+  op.set_data_length(op_blob.size());
+
+  AnnotatedOperation aop;
+  aop.op = op;
+  aop.name = "SplitTestOp";
+
+  // Create the data file.
+  string data_path;
+  EXPECT_TRUE(utils::MakeTempFile(
+      "SplitReplaceOrReplaceBzTest_data.XXXXXX", &data_path, nullptr));
+  ScopedPathUnlinker data_path_unlinker(data_path);
+  int data_fd = open(data_path.c_str(), O_RDWR, 000);
+  EXPECT_GE(data_fd, 0);
+  ScopedFdCloser data_fd_closer(&data_fd);
+  EXPECT_TRUE(utils::WriteFile(data_path.c_str(), op_blob.data(),
+                               op_blob.size()));
+  off_t data_file_size = op_blob.size();
+  BlobFileWriter blob_file(data_fd, &data_file_size);
+
+  // Split the operation.
+  vector<AnnotatedOperation> result_ops;
+  PayloadVersion version(kChromeOSMajorPayloadVersion,
+                         kSourceMinorPayloadVersion);
+  ASSERT_TRUE(ABGenerator::SplitAReplaceOp(
+      version, aop, part_path, &result_ops, &blob_file));
+
+  // Check the result.
+  InstallOperation_Type expected_type =
+      compressible ? InstallOperation::REPLACE_BZ : InstallOperation::REPLACE;
+
+  ASSERT_EQ(2U, result_ops.size());
+
+  EXPECT_EQ("SplitTestOp:0", result_ops[0].name);
+  InstallOperation first_op = result_ops[0].op;
+  EXPECT_EQ(expected_type, first_op.type());
+  EXPECT_EQ(op_ex1_size, first_op.dst_length());
+  EXPECT_EQ(1, first_op.dst_extents().size());
+  EXPECT_TRUE(ExtentEquals(first_op.dst_extents(0), op_ex1_start_block,
+                           op_ex1_num_blocks));
+  // Obtain the expected blob.
+  brillo::Blob first_expected_data(
+      part_data.begin() + op_ex1_offset,
+      part_data.begin() + op_ex1_offset + op_ex1_size);
+  brillo::Blob first_expected_blob;
+  if (compressible) {
+    ASSERT_TRUE(BzipCompress(first_expected_data, &first_expected_blob));
+  } else {
+    first_expected_blob = first_expected_data;
+  }
+  EXPECT_EQ(first_expected_blob.size(), first_op.data_length());
+  // Check that the actual blob matches what's expected.
+  brillo::Blob first_data_blob(first_op.data_length());
+  ssize_t bytes_read;
+  ASSERT_TRUE(utils::PReadAll(data_fd,
+                              first_data_blob.data(),
+                              first_op.data_length(),
+                              first_op.data_offset(),
+                              &bytes_read));
+  ASSERT_EQ(bytes_read, static_cast<ssize_t>(first_op.data_length()));
+  EXPECT_EQ(first_expected_blob, first_data_blob);
+
+  EXPECT_EQ("SplitTestOp:1", result_ops[1].name);
+  InstallOperation second_op = result_ops[1].op;
+  EXPECT_EQ(expected_type, second_op.type());
+  EXPECT_EQ(op_ex2_size, second_op.dst_length());
+  EXPECT_EQ(1, second_op.dst_extents().size());
+  EXPECT_TRUE(ExtentEquals(second_op.dst_extents(0), op_ex2_start_block,
+                           op_ex2_num_blocks));
+  // Obtain the expected blob.
+  brillo::Blob second_expected_data(
+      part_data.begin() + op_ex2_offset,
+      part_data.begin() + op_ex2_offset + op_ex2_size);
+  brillo::Blob second_expected_blob;
+  if (compressible) {
+    ASSERT_TRUE(BzipCompress(second_expected_data, &second_expected_blob));
+  } else {
+    second_expected_blob = second_expected_data;
+  }
+  EXPECT_EQ(second_expected_blob.size(), second_op.data_length());
+  // Check that the actual blob matches what's expected.
+  brillo::Blob second_data_blob(second_op.data_length());
+  ASSERT_TRUE(utils::PReadAll(data_fd,
+                              second_data_blob.data(),
+                              second_op.data_length(),
+                              second_op.data_offset(),
+                              &bytes_read));
+  ASSERT_EQ(bytes_read, static_cast<ssize_t>(second_op.data_length()));
+  EXPECT_EQ(second_expected_blob, second_data_blob);
+
+  // Check relative layout of data blobs.
+  EXPECT_EQ(first_op.data_offset() + first_op.data_length(),
+            second_op.data_offset());
+  EXPECT_EQ(second_op.data_offset() + second_op.data_length(), data_file_size);
+  // If we split a REPLACE into multiple ones, ensure reuse of preexisting blob.
+  if (!compressible && orig_type == InstallOperation::REPLACE) {
+    EXPECT_EQ(0U, first_op.data_offset());
+  }
+}
+
+// Tests merging of REPLACE/REPLACE_BZ operations.
+void TestMergeReplaceOrReplaceBzOperations(InstallOperation_Type orig_type,
+                                           bool compressible) {
+  const size_t first_op_num_blocks = 1;
+  const size_t second_op_num_blocks = 2;
+  const size_t total_op_num_blocks = first_op_num_blocks + second_op_num_blocks;
+  const size_t part_num_blocks = total_op_num_blocks + 2;
+
+  // Create the target partition data.
+  string part_path;
+  EXPECT_TRUE(utils::MakeTempFile(
+      "MergeReplaceOrReplaceBzTest_part.XXXXXX", &part_path, nullptr));
+  ScopedPathUnlinker part_path_unlinker(part_path);
+  const size_t part_size = part_num_blocks * kBlockSize;
+  brillo::Blob part_data;
+  if (compressible) {
+    part_data.resize(part_size);
+    test_utils::FillWithData(&part_data);
+  } else {
+    std::mt19937 gen(12345);
+    std::uniform_int_distribution<uint8_t> dis(0, 255);
+    for (uint32_t i = 0; i < part_size; i++)
+      part_data.push_back(dis(gen));
+  }
+  ASSERT_EQ(part_size, part_data.size());
+  ASSERT_TRUE(utils::WriteFile(part_path.c_str(), part_data.data(), part_size));
+
+  // Create original operations and blob data.
+  vector<AnnotatedOperation> aops;
+  brillo::Blob blob_data;
+  const size_t total_op_size = total_op_num_blocks * kBlockSize;
+
+  InstallOperation first_op;
+  first_op.set_type(orig_type);
+  const size_t first_op_size = first_op_num_blocks * kBlockSize;
+  first_op.set_dst_length(first_op_size);
+  *(first_op.add_dst_extents()) = ExtentForRange(0, first_op_num_blocks);
+  brillo::Blob first_op_data(part_data.begin(),
+                               part_data.begin() + first_op_size);
+  brillo::Blob first_op_blob;
+  if (orig_type == InstallOperation::REPLACE) {
+    first_op_blob = first_op_data;
+  } else {
+    ASSERT_TRUE(BzipCompress(first_op_data, &first_op_blob));
+  }
+  first_op.set_data_offset(0);
+  first_op.set_data_length(first_op_blob.size());
+  blob_data.insert(blob_data.end(), first_op_blob.begin(), first_op_blob.end());
+  AnnotatedOperation first_aop;
+  first_aop.op = first_op;
+  first_aop.name = "first";
+  aops.push_back(first_aop);
+
+  InstallOperation second_op;
+  second_op.set_type(orig_type);
+  const size_t second_op_size = second_op_num_blocks * kBlockSize;
+  second_op.set_dst_length(second_op_size);
+  *(second_op.add_dst_extents()) = ExtentForRange(first_op_num_blocks,
+                                                  second_op_num_blocks);
+  brillo::Blob second_op_data(part_data.begin() + first_op_size,
+                                part_data.begin() + total_op_size);
+  brillo::Blob second_op_blob;
+  if (orig_type == InstallOperation::REPLACE) {
+    second_op_blob = second_op_data;
+  } else {
+    ASSERT_TRUE(BzipCompress(second_op_data, &second_op_blob));
+  }
+  second_op.set_data_offset(first_op_blob.size());
+  second_op.set_data_length(second_op_blob.size());
+  blob_data.insert(blob_data.end(), second_op_blob.begin(),
+                   second_op_blob.end());
+  AnnotatedOperation second_aop;
+  second_aop.op = second_op;
+  second_aop.name = "second";
+  aops.push_back(second_aop);
+
+  // Create the data file.
+  string data_path;
+  EXPECT_TRUE(utils::MakeTempFile(
+      "MergeReplaceOrReplaceBzTest_data.XXXXXX", &data_path, nullptr));
+  ScopedPathUnlinker data_path_unlinker(data_path);
+  int data_fd = open(data_path.c_str(), O_RDWR, 000);
+  EXPECT_GE(data_fd, 0);
+  ScopedFdCloser data_fd_closer(&data_fd);
+  EXPECT_TRUE(utils::WriteFile(data_path.c_str(), blob_data.data(),
+                               blob_data.size()));
+  off_t data_file_size = blob_data.size();
+  BlobFileWriter blob_file(data_fd, &data_file_size);
+
+  // Merge the operations.
+  PayloadVersion version(kChromeOSMajorPayloadVersion,
+                         kSourceMinorPayloadVersion);
+  EXPECT_TRUE(
+      ABGenerator::MergeOperations(&aops, version, 5, part_path, &blob_file));
+
+  // Check the result.
+  InstallOperation_Type expected_op_type =
+      compressible ? InstallOperation::REPLACE_BZ : InstallOperation::REPLACE;
+  EXPECT_EQ(1U, aops.size());
+  InstallOperation new_op = aops[0].op;
+  EXPECT_EQ(expected_op_type, new_op.type());
+  EXPECT_FALSE(new_op.has_src_length());
+  EXPECT_EQ(total_op_num_blocks * kBlockSize, new_op.dst_length());
+  EXPECT_EQ(1, new_op.dst_extents().size());
+  EXPECT_TRUE(ExtentEquals(new_op.dst_extents(0), 0, total_op_num_blocks));
+  EXPECT_EQ("first,second", aops[0].name);
+
+  // Check to see if the blob pointed to in the new extent has what we expect.
+  brillo::Blob expected_data(part_data.begin(),
+                               part_data.begin() + total_op_size);
+  brillo::Blob expected_blob;
+  if (compressible) {
+    ASSERT_TRUE(BzipCompress(expected_data, &expected_blob));
+  } else {
+    expected_blob = expected_data;
+  }
+  ASSERT_EQ(expected_blob.size(), new_op.data_length());
+  ASSERT_EQ(blob_data.size() + expected_blob.size(),
+            static_cast<size_t>(data_file_size));
+  brillo::Blob new_op_blob(new_op.data_length());
+  ssize_t bytes_read;
+  ASSERT_TRUE(utils::PReadAll(data_fd,
+                              new_op_blob.data(),
+                              new_op.data_length(),
+                              new_op.data_offset(),
+                              &bytes_read));
+  ASSERT_EQ(static_cast<ssize_t>(new_op.data_length()), bytes_read);
+  EXPECT_EQ(expected_blob, new_op_blob);
+}
+
+}  // namespace
+
+class ABGeneratorTest : public ::testing::Test {};
+
+TEST_F(ABGeneratorTest, SplitSourceCopyTest) {
+  InstallOperation op;
+  op.set_type(InstallOperation::SOURCE_COPY);
+  *(op.add_src_extents()) = ExtentForRange(2, 3);
+  *(op.add_src_extents()) = ExtentForRange(6, 1);
+  *(op.add_src_extents()) = ExtentForRange(8, 4);
+  *(op.add_dst_extents()) = ExtentForRange(10, 2);
+  *(op.add_dst_extents()) = ExtentForRange(14, 3);
+  *(op.add_dst_extents()) = ExtentForRange(18, 3);
+
+  AnnotatedOperation aop;
+  aop.op = op;
+  aop.name = "SplitSourceCopyTestOp";
+  vector<AnnotatedOperation> result_ops;
+  EXPECT_TRUE(ABGenerator::SplitSourceCopy(aop, &result_ops));
+  EXPECT_EQ(3U, result_ops.size());
+
+  EXPECT_EQ("SplitSourceCopyTestOp:0", result_ops[0].name);
+  InstallOperation first_op = result_ops[0].op;
+  EXPECT_EQ(InstallOperation::SOURCE_COPY, first_op.type());
+  EXPECT_EQ(kBlockSize * 2, first_op.src_length());
+  EXPECT_EQ(1, first_op.src_extents().size());
+  EXPECT_EQ(2U, first_op.src_extents(0).start_block());
+  EXPECT_EQ(2U, first_op.src_extents(0).num_blocks());
+  EXPECT_EQ(kBlockSize * 2, first_op.dst_length());
+  EXPECT_EQ(1, first_op.dst_extents().size());
+  EXPECT_EQ(10U, first_op.dst_extents(0).start_block());
+  EXPECT_EQ(2U, first_op.dst_extents(0).num_blocks());
+
+  EXPECT_EQ("SplitSourceCopyTestOp:1", result_ops[1].name);
+  InstallOperation second_op = result_ops[1].op;
+  EXPECT_EQ(InstallOperation::SOURCE_COPY, second_op.type());
+  EXPECT_EQ(kBlockSize * 3, second_op.src_length());
+  EXPECT_EQ(3, second_op.src_extents().size());
+  EXPECT_EQ(4U, second_op.src_extents(0).start_block());
+  EXPECT_EQ(1U, second_op.src_extents(0).num_blocks());
+  EXPECT_EQ(6U, second_op.src_extents(1).start_block());
+  EXPECT_EQ(1U, second_op.src_extents(1).num_blocks());
+  EXPECT_EQ(8U, second_op.src_extents(2).start_block());
+  EXPECT_EQ(1U, second_op.src_extents(2).num_blocks());
+  EXPECT_EQ(kBlockSize * 3, second_op.dst_length());
+  EXPECT_EQ(1, second_op.dst_extents().size());
+  EXPECT_EQ(14U, second_op.dst_extents(0).start_block());
+  EXPECT_EQ(3U, second_op.dst_extents(0).num_blocks());
+
+  EXPECT_EQ("SplitSourceCopyTestOp:2", result_ops[2].name);
+  InstallOperation third_op = result_ops[2].op;
+  EXPECT_EQ(InstallOperation::SOURCE_COPY, third_op.type());
+  EXPECT_EQ(kBlockSize * 3, third_op.src_length());
+  EXPECT_EQ(1, third_op.src_extents().size());
+  EXPECT_EQ(9U, third_op.src_extents(0).start_block());
+  EXPECT_EQ(3U, third_op.src_extents(0).num_blocks());
+  EXPECT_EQ(kBlockSize * 3, third_op.dst_length());
+  EXPECT_EQ(1, third_op.dst_extents().size());
+  EXPECT_EQ(18U, third_op.dst_extents(0).start_block());
+  EXPECT_EQ(3U, third_op.dst_extents(0).num_blocks());
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceTest) {
+  TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE, false);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceIntoReplaceBzTest) {
+  TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE, true);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceBzTest) {
+  TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE_BZ, true);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceBzIntoReplaceTest) {
+  TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE_BZ, false);
+}
+
+TEST_F(ABGeneratorTest, SortOperationsByDestinationTest) {
+  vector<AnnotatedOperation> aops;
+  // One operation with multiple destination extents.
+  InstallOperation first_op;
+  *(first_op.add_dst_extents()) = ExtentForRange(6, 1);
+  *(first_op.add_dst_extents()) = ExtentForRange(10, 2);
+  AnnotatedOperation first_aop;
+  first_aop.op = first_op;
+  first_aop.name = "first";
+  aops.push_back(first_aop);
+
+  // One with no destination extent. Should end up at the end of the vector.
+  InstallOperation second_op;
+  AnnotatedOperation second_aop;
+  second_aop.op = second_op;
+  second_aop.name = "second";
+  aops.push_back(second_aop);
+
+  // One with one destination extent.
+  InstallOperation third_op;
+  *(third_op.add_dst_extents()) = ExtentForRange(3, 2);
+  AnnotatedOperation third_aop;
+  third_aop.op = third_op;
+  third_aop.name = "third";
+  aops.push_back(third_aop);
+
+  ABGenerator::SortOperationsByDestination(&aops);
+  EXPECT_EQ(3U, aops.size());
+  EXPECT_EQ(third_aop.name, aops[0].name);
+  EXPECT_EQ(first_aop.name, aops[1].name);
+  EXPECT_EQ(second_aop.name, aops[2].name);
+}
+
+TEST_F(ABGeneratorTest, MergeSourceCopyOperationsTest) {
+  vector<AnnotatedOperation> aops;
+  InstallOperation first_op;
+  first_op.set_type(InstallOperation::SOURCE_COPY);
+  first_op.set_src_length(kBlockSize);
+  first_op.set_dst_length(kBlockSize);
+  *(first_op.add_src_extents()) = ExtentForRange(1, 1);
+  *(first_op.add_dst_extents()) = ExtentForRange(6, 1);
+  AnnotatedOperation first_aop;
+  first_aop.op = first_op;
+  first_aop.name = "1";
+  aops.push_back(first_aop);
+
+  InstallOperation second_op;
+  second_op.set_type(InstallOperation::SOURCE_COPY);
+  second_op.set_src_length(3 * kBlockSize);
+  second_op.set_dst_length(3 * kBlockSize);
+  *(second_op.add_src_extents()) = ExtentForRange(2, 2);
+  *(second_op.add_src_extents()) = ExtentForRange(8, 2);
+  *(second_op.add_dst_extents()) = ExtentForRange(7, 3);
+  *(second_op.add_dst_extents()) = ExtentForRange(11, 1);
+  AnnotatedOperation second_aop;
+  second_aop.op = second_op;
+  second_aop.name = "2";
+  aops.push_back(second_aop);
+
+  InstallOperation third_op;
+  third_op.set_type(InstallOperation::SOURCE_COPY);
+  third_op.set_src_length(kBlockSize);
+  third_op.set_dst_length(kBlockSize);
+  *(third_op.add_src_extents()) = ExtentForRange(11, 1);
+  *(third_op.add_dst_extents()) = ExtentForRange(12, 1);
+  AnnotatedOperation third_aop;
+  third_aop.op = third_op;
+  third_aop.name = "3";
+  aops.push_back(third_aop);
+
+  BlobFileWriter blob_file(0, nullptr);
+  PayloadVersion version(kChromeOSMajorPayloadVersion,
+                         kSourceMinorPayloadVersion);
+  EXPECT_TRUE(ABGenerator::MergeOperations(&aops, version, 5, "", &blob_file));
+
+  EXPECT_EQ(1U, aops.size());
+  InstallOperation first_result_op = aops[0].op;
+  EXPECT_EQ(InstallOperation::SOURCE_COPY, first_result_op.type());
+  EXPECT_EQ(kBlockSize * 5, first_result_op.src_length());
+  EXPECT_EQ(3, first_result_op.src_extents().size());
+  EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(0), 1, 3));
+  EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(1), 8, 2));
+  EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(2), 11, 1));
+  EXPECT_EQ(kBlockSize * 5, first_result_op.dst_length());
+  EXPECT_EQ(2, first_result_op.dst_extents().size());
+  EXPECT_TRUE(ExtentEquals(first_result_op.dst_extents(0), 6, 4));
+  EXPECT_TRUE(ExtentEquals(first_result_op.dst_extents(1), 11, 2));
+  EXPECT_EQ(aops[0].name, "1,2,3");
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceOperationsTest) {
+  TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE, false);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceOperationsToReplaceBzTest) {
+  TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE, true);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceBzOperationsTest) {
+  TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE_BZ, true);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceBzOperationsToReplaceTest) {
+  TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE_BZ, false);
+}
+
+TEST_F(ABGeneratorTest, NoMergeOperationsTest) {
+  // Test to make sure we don't merge operations that shouldn't be merged.
+  vector<AnnotatedOperation> aops;
+  InstallOperation first_op;
+  first_op.set_type(InstallOperation::ZERO);
+  *(first_op.add_dst_extents()) = ExtentForRange(0, 1);
+  AnnotatedOperation first_aop;
+  first_aop.op = first_op;
+  aops.push_back(first_aop);
+
+  // Should merge with first, except op types don't match...
+  InstallOperation second_op;
+  second_op.set_type(InstallOperation::REPLACE);
+  *(second_op.add_dst_extents()) = ExtentForRange(1, 2);
+  second_op.set_data_length(2 * kBlockSize);
+  AnnotatedOperation second_aop;
+  second_aop.op = second_op;
+  aops.push_back(second_aop);
+
+  // Should merge with second, except it would exceed chunk size...
+  InstallOperation third_op;
+  third_op.set_type(InstallOperation::REPLACE);
+  *(third_op.add_dst_extents()) = ExtentForRange(3, 3);
+  third_op.set_data_length(3 * kBlockSize);
+  AnnotatedOperation third_aop;
+  third_aop.op = third_op;
+  aops.push_back(third_aop);
+
+  // Should merge with third, except they aren't contiguous...
+  InstallOperation fourth_op;
+  fourth_op.set_type(InstallOperation::REPLACE);
+  *(fourth_op.add_dst_extents()) = ExtentForRange(7, 2);
+  fourth_op.set_data_length(2 * kBlockSize);
+  AnnotatedOperation fourth_aop;
+  fourth_aop.op = fourth_op;
+  aops.push_back(fourth_aop);
+
+  BlobFileWriter blob_file(0, nullptr);
+  PayloadVersion version(kChromeOSMajorPayloadVersion,
+                         kSourceMinorPayloadVersion);
+  EXPECT_TRUE(ABGenerator::MergeOperations(&aops, version, 4, "", &blob_file));
+
+  // No operations were merged, the number of ops is the same.
+  EXPECT_EQ(4U, aops.size());
+}
+
+TEST_F(ABGeneratorTest, AddSourceHashTest) {
+  vector<AnnotatedOperation> aops;
+  InstallOperation first_op;
+  first_op.set_type(InstallOperation::SOURCE_COPY);
+  first_op.set_src_length(kBlockSize);
+  *(first_op.add_src_extents()) = ExtentForRange(0, 1);
+  AnnotatedOperation first_aop;
+  first_aop.op = first_op;
+  aops.push_back(first_aop);
+
+  InstallOperation second_op;
+  second_op.set_type(InstallOperation::REPLACE);
+  AnnotatedOperation second_aop;
+  second_aop.op = second_op;
+  aops.push_back(second_aop);
+
+  string src_part_path;
+  EXPECT_TRUE(utils::MakeTempFile("AddSourceHashTest_src_part.XXXXXX",
+                                  &src_part_path, nullptr));
+  ScopedPathUnlinker src_part_path_unlinker(src_part_path);
+  brillo::Blob src_data(kBlockSize);
+  test_utils::FillWithData(&src_data);
+  ASSERT_TRUE(utils::WriteFile(src_part_path.c_str(), src_data.data(),
+                               src_data.size()));
+
+  EXPECT_TRUE(ABGenerator::AddSourceHash(&aops, src_part_path));
+
+  EXPECT_TRUE(aops[0].op.has_src_sha256_hash());
+  EXPECT_FALSE(aops[1].op.has_src_sha256_hash());
+  brillo::Blob expected_hash;
+  EXPECT_TRUE(HashCalculator::RawHashOfData(src_data, &expected_hash));
+  brillo::Blob result_hash(aops[0].op.src_sha256_hash().begin(),
+                           aops[0].op.src_sha256_hash().end());
+  EXPECT_EQ(expected_hash, result_hash);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/annotated_operation.cc b/update_engine/payload_generator/annotated_operation.cc
new file mode 100644
index 0000000..e28fe85
--- /dev/null
+++ b/update_engine/payload_generator/annotated_operation.cc
@@ -0,0 +1,75 @@
+//
+// 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/payload_generator/annotated_operation.h"
+
+#include <base/format_macros.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+namespace chromeos_update_engine {
+
+namespace {
+// Output the list of extents as (start_block, num_blocks) in the passed output
+// stream.
+void OutputExtents(std::ostream* os,
+                   const google::protobuf::RepeatedPtrField<Extent>& extents) {
+  for (const auto& extent : extents) {
+    *os << " (" << extent.start_block() << ", " << extent.num_blocks() << ")";
+  }
+}
+}  // namespace
+
+bool AnnotatedOperation::SetOperationBlob(const brillo::Blob& blob,
+                                          BlobFileWriter* blob_file) {
+  if (blob.empty()) {
+    op.clear_data_offset();
+    op.clear_data_length();
+    return true;
+  }
+  off_t data_offset = blob_file->StoreBlob(blob);
+  TEST_AND_RETURN_FALSE(data_offset != -1);
+  op.set_data_offset(data_offset);
+  op.set_data_length(blob.size());
+  return true;
+}
+
+std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop) {
+  // For example, this prints:
+  // REPLACE_BZ 500 @3000
+  //   name: /foo/bar
+  //    dst: (123, 3) (127, 2)
+  os << InstallOperationTypeName(aop.op.type()) << " "  << aop.op.data_length();
+  if (aop.op.data_length() > 0)
+    os << " @" << aop.op.data_offset();
+  if (!aop.name.empty()) {
+    os << std::endl << "  name: " << aop.name;
+  }
+  if (aop.op.src_extents_size() != 0) {
+    os << std::endl << "   src:";
+    OutputExtents(&os, aop.op.src_extents());
+  }
+  if (aop.op.dst_extents_size() != 0) {
+    os << std::endl << "   dst:";
+    OutputExtents(&os, aop.op.dst_extents());
+  }
+  return os;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/annotated_operation.h b/update_engine/payload_generator/annotated_operation.h
new file mode 100644
index 0000000..653bab0
--- /dev/null
+++ b/update_engine/payload_generator/annotated_operation.h
@@ -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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
+
+#include <ostream>  // NOLINT(readability/streams)
+#include <string>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+struct AnnotatedOperation {
+  // The name given to the operation, for logging and debugging purposes only.
+  // This normally includes the path to the file and the chunk used, if any.
+  std::string name;
+
+  // The InstallOperation, as defined by the protobuf.
+  InstallOperation op;
+
+  // Writes |blob| to the end of |blob_file|. It sets the data_offset and
+  // data_length in AnnotatedOperation to match the offset and size of |blob|
+  // in |blob_file|.
+  bool SetOperationBlob(const brillo::Blob& blob, BlobFileWriter* blob_file);
+};
+
+// For logging purposes.
+std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
diff --git a/update_engine/payload_generator/blob_file_writer.cc b/update_engine/payload_generator/blob_file_writer.cc
new file mode 100644
index 0000000..8225df4
--- /dev/null
+++ b/update_engine/payload_generator/blob_file_writer.cc
@@ -0,0 +1,47 @@
+//
+// 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/payload_generator/blob_file_writer.h"
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+off_t BlobFileWriter::StoreBlob(const brillo::Blob& blob) {
+  base::AutoLock auto_lock(blob_mutex_);
+  if (!utils::PWriteAll(blob_fd_, blob.data(), blob.size(), *blob_file_size_))
+    return -1;
+
+  off_t result = *blob_file_size_;
+  *blob_file_size_ += blob.size();
+
+  stored_blobs_++;
+  if (total_blobs_ > 0 &&
+      (10 * (stored_blobs_ - 1) / total_blobs_) !=
+      (10 * stored_blobs_ / total_blobs_)) {
+    LOG(INFO) << (100 * stored_blobs_ / total_blobs_)
+              << "% complete " << stored_blobs_ << "/" << total_blobs_
+              << " ops (output size: " << *blob_file_size_ << ")";
+  }
+  return result;
+}
+
+void BlobFileWriter::SetTotalBlobs(size_t total_blobs) {
+  total_blobs_ = total_blobs;
+  stored_blobs_ = 0;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/blob_file_writer.h b/update_engine/payload_generator/blob_file_writer.h
new file mode 100644
index 0000000..cbc13ae
--- /dev/null
+++ b/update_engine/payload_generator/blob_file_writer.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_PAYLOAD_GENERATOR_BLOB_FILE_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_BLOB_FILE_WRITER_H_
+
+#include <base/macros.h>
+
+#include <base/synchronization/lock.h>
+#include <brillo/secure_blob.h>
+
+namespace chromeos_update_engine {
+
+class BlobFileWriter {
+ public:
+  // Create the BlobFileWriter object that will manage the blobs stored to
+  // |blob_fd| in a thread safe way.
+  BlobFileWriter(int blob_fd, off_t* blob_file_size)
+    : blob_fd_(blob_fd),
+      blob_file_size_(blob_file_size) {}
+
+  // Store the passed |blob| in the blob file. Returns the offset at which it
+  // was stored, or -1 in case of failure.
+  off_t StoreBlob(const brillo::Blob& blob);
+
+  // The number of |total_blobs| is the number of blobs that will be stored but
+  // is only used for logging purposes. If not set or set to 0, logging will be
+  // skipped. This function will also reset the number of stored blobs to 0.
+  void SetTotalBlobs(size_t total_blobs);
+
+ private:
+  size_t total_blobs_{0};
+  size_t stored_blobs_{0};
+
+  // The file and its size are protected with the |blob_mutex_|.
+  int blob_fd_;
+  off_t* blob_file_size_;
+
+  base::Lock blob_mutex_;
+
+  DISALLOW_COPY_AND_ASSIGN(BlobFileWriter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_BLOB_FILE_WRITER_H_
diff --git a/update_engine/payload_generator/blob_file_writer_unittest.cc b/update_engine/payload_generator/blob_file_writer_unittest.cc
new file mode 100644
index 0000000..5f94ef3
--- /dev/null
+++ b/update_engine/payload_generator/blob_file_writer_unittest.cc
@@ -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.
+//
+
+#include "update_engine/payload_generator/blob_file_writer.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using chromeos_update_engine::test_utils::FillWithData;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class BlobFileWriterTest : public ::testing::Test {};
+
+TEST(BlobFileWriterTest, SimpleTest) {
+  string blob_path;
+  int blob_fd;
+  EXPECT_TRUE(utils::MakeTempFile("BlobFileWriterTest.XXXXXX",
+                                  &blob_path,
+                                  &blob_fd));
+  off_t blob_file_size = 0;
+  BlobFileWriter blob_file(blob_fd, &blob_file_size);
+
+  off_t blob_size = 1024;
+  brillo::Blob blob(blob_size);
+  FillWithData(&blob);
+  EXPECT_EQ(0, blob_file.StoreBlob(blob));
+  EXPECT_EQ(blob_size, blob_file.StoreBlob(blob));
+
+  brillo::Blob stored_blob(blob_size);
+  ssize_t bytes_read;
+  ASSERT_TRUE(utils::PReadAll(blob_fd,
+                              stored_blob.data(),
+                              blob_size,
+                              0,
+                              &bytes_read));
+  EXPECT_EQ(bytes_read, blob_size);
+  EXPECT_EQ(blob, stored_blob);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/block_mapping.cc b/update_engine/payload_generator/block_mapping.cc
new file mode 100644
index 0000000..ff10f0b
--- /dev/null
+++ b/update_engine/payload_generator/block_mapping.cc
@@ -0,0 +1,161 @@
+//
+// 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/payload_generator/block_mapping.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+size_t HashValue(const brillo::Blob& blob) {
+  std::hash<string> hash_fn;
+  return hash_fn(string(blob.begin(), blob.end()));
+}
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+BlockMapping::BlockId BlockMapping::AddBlock(const brillo::Blob& block_data) {
+  return AddBlock(-1, 0, block_data);
+}
+
+BlockMapping::BlockId BlockMapping::AddDiskBlock(int fd, off_t byte_offset) {
+  brillo::Blob blob(block_size_);
+  ssize_t bytes_read = 0;
+  if (!utils::PReadAll(fd, blob.data(), block_size_, byte_offset, &bytes_read))
+    return -1;
+  if (static_cast<size_t>(bytes_read) != block_size_)
+    return -1;
+  return AddBlock(fd, byte_offset, blob);
+}
+
+bool BlockMapping::AddManyDiskBlocks(int fd,
+                                     off_t initial_byte_offset,
+                                     size_t num_blocks,
+                                     vector<BlockId>* block_ids) {
+  bool ret = true;
+  block_ids->resize(num_blocks);
+  for (size_t block = 0; block < num_blocks; block++) {
+    (*block_ids)[block] = AddDiskBlock(
+        fd, initial_byte_offset + block * block_size_);
+    ret = ret && (*block_ids)[block] != -1;
+  }
+  return ret;
+}
+
+BlockMapping::BlockId BlockMapping::AddBlock(int fd,
+                                             off_t byte_offset,
+                                             const brillo::Blob& block_data) {
+  if (block_data.size() != block_size_)
+    return -1;
+  size_t h = HashValue(block_data);
+
+  // We either reuse a UniqueBlock or create a new one. If we need a new
+  // UniqueBlock it could also be part of a new or existing bucket (if there is
+  // a hash collision).
+  vector<UniqueBlock> *bucket = nullptr;
+
+  auto mapping_it = mapping_.find(h);
+  if (mapping_it == mapping_.end()) {
+    bucket = &mapping_[h];
+  } else {
+    for (UniqueBlock& existing_block : mapping_it->second) {
+      bool equals = false;
+      if (!existing_block.CompareData(block_data, &equals))
+        return -1;
+      if (equals)
+        return existing_block.block_id;
+    }
+    bucket = &mapping_it->second;
+  }
+
+  // No existing block was found at this point, so we create and fill in a new
+  // one.
+  bucket->emplace_back();
+  UniqueBlock *new_ublock = &bucket->back();
+
+  new_ublock->times_read = 1;
+  new_ublock->fd = fd;
+  new_ublock->byte_offset = byte_offset;
+  new_ublock->block_id = used_block_ids++;
+  // We need to cache blocks that are not referencing any disk location.
+  if (fd == -1)
+    new_ublock->block_data = block_data;
+
+  return new_ublock->block_id;
+}
+
+bool BlockMapping::UniqueBlock::CompareData(const brillo::Blob& other_block,
+                                            bool* equals) {
+  if (!block_data.empty()) {
+    *equals = block_data == other_block;
+    return true;
+  }
+  const size_t block_size = other_block.size();
+  brillo::Blob blob(block_size);
+  ssize_t bytes_read = 0;
+  if (!utils::PReadAll(fd, blob.data(), block_size, byte_offset, &bytes_read))
+    return false;
+  if (static_cast<size_t>(bytes_read) != block_size)
+    return false;
+  *equals = blob == other_block;
+
+  // We increase the number of times we had to read this block from disk and
+  // we cache this block based on that. This caching method is optimized for
+  // the common use case of having two partitions that share blocks between them
+  // but have few repeated blocks inside each partition, such as the block
+  // with all zeros or duplicated files.
+  times_read++;
+  if (times_read > 3)
+    block_data = std::move(blob);
+  return true;
+}
+
+bool MapPartitionBlocks(const string& old_part,
+                        const string& new_part,
+                        size_t old_size,
+                        size_t new_size,
+                        size_t block_size,
+                        vector<BlockMapping::BlockId>* old_block_ids,
+                        vector<BlockMapping::BlockId>* new_block_ids) {
+  BlockMapping mapping(block_size);
+  if (mapping.AddBlock(brillo::Blob(block_size, '\0')) != 0)
+    return false;
+  int old_fd = HANDLE_EINTR(open(old_part.c_str(), O_RDONLY));
+  int new_fd = HANDLE_EINTR(open(new_part.c_str(), O_RDONLY));
+  ScopedFdCloser old_fd_closer(&old_fd);
+  ScopedFdCloser new_fd_closer(&new_fd);
+
+  TEST_AND_RETURN_FALSE(mapping.AddManyDiskBlocks(
+      old_fd, 0, old_size / block_size, old_block_ids));
+  TEST_AND_RETURN_FALSE(mapping.AddManyDiskBlocks(
+      new_fd, 0, new_size / block_size, new_block_ids));
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/block_mapping.h b/update_engine/payload_generator/block_mapping.h
new file mode 100644
index 0000000..3fe94ab
--- /dev/null
+++ b/update_engine/payload_generator/block_mapping.h
@@ -0,0 +1,112 @@
+//
+// 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_PAYLOAD_GENERATOR_BLOCK_MAPPING_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_BLOCK_MAPPING_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+// BlockMapping allows to map data blocks (brillo::Blobs of block_size size)
+// into unique integer values called "block ids". This mapping differs from a
+// hash function in that two blocks with the same data will have the same id but
+// also two blocks with the same id will have the same data. This is only valid
+// in the context of the same BlockMapping instance.
+class BlockMapping {
+ public:
+  using BlockId = int64_t;
+
+  explicit BlockMapping(size_t block_size) : block_size_(block_size) {}
+
+  // Add a single data block to the mapping. Returns its unique block id.
+  // In case of error returns -1.
+  BlockId AddBlock(const brillo::Blob& block_data);
+
+  // Add a block from disk reading it from the file descriptor |fd| from the
+  // offset in bytes |byte_offset|. The data block may or may not be cached, so
+  // the file descriptor must be available until the BlockMapping is destroyed.
+  // Returns the unique block id of the added block or -1 in case of error.
+  BlockId AddDiskBlock(int fd, off_t byte_offset);
+
+  // This is a helper method to add |num_blocks| contiguous blocks reading them
+  // from the file descriptor |fd| starting at offset |initial_byte_offset|.
+  // Returns whether it succeeded to add all the disk blocks and stores in
+  // |block_ids| the block id for each one of the added blocks.
+  bool AddManyDiskBlocks(int fd, off_t initial_byte_offset, size_t num_blocks,
+                         std::vector<BlockId>* block_ids);
+
+ private:
+  FRIEND_TEST(BlockMappingTest, BlocksAreNotKeptInMemory);
+
+  // Add a single block passed in |block_data|. If |fd| is not -1, the block
+  // can be discarded to save RAM and retrieved later from |fd| at the position
+  // |byte_offset|.
+  BlockId AddBlock(int fd, off_t byte_offset, const brillo::Blob& block_data);
+
+  size_t block_size_;
+
+  BlockId used_block_ids{0};
+
+  // The UniqueBlock represents the data of a block associated to a unique
+  // block id.
+  struct UniqueBlock {
+    brillo::Blob block_data;
+
+    // The block id assigned to this unique block.
+    BlockId block_id;
+
+    // The location on this unique block on disk (if not cached in block_data).
+    int fd{-1};
+    off_t byte_offset{0};
+
+    // Number of times we have seen this data block. Used for caching.
+    uint32_t times_read{0};
+
+    // Compares the UniqueBlock data with the other_block data and stores if
+    // they are equal in |equals|. Returns whether there was an error reading
+    // the block from disk while comparing it.
+    bool CompareData(const brillo::Blob& other_block, bool* equals);
+  };
+
+  // A mapping from hash values to possible block ids.
+  std::map<size_t, std::vector<UniqueBlock>> mapping_;
+};
+
+// Maps the blocks of the old and new partitions |old_part| and |new_part| whose
+// size in bytes are |old_size| and |new_size| into block ids where two blocks
+// with the same data will have the same block id and vice versa, regardless of
+// the partition they are on.
+// The block ids number 0 corresponds to the block with all zeros, but any
+// other block id number is assigned randomly.
+bool MapPartitionBlocks(const std::string& old_part,
+                        const std::string& new_part,
+                        size_t old_size,
+                        size_t new_size,
+                        size_t block_size,
+                        std::vector<BlockMapping::BlockId>* old_block_ids,
+                        std::vector<BlockMapping::BlockId>* new_block_ids);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_BLOCK_MAPPING_H_
diff --git a/update_engine/payload_generator/block_mapping_unittest.cc b/update_engine/payload_generator/block_mapping_unittest.cc
new file mode 100644
index 0000000..4d09710
--- /dev/null
+++ b/update_engine/payload_generator/block_mapping_unittest.cc
@@ -0,0 +1,134 @@
+//
+// 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/payload_generator/block_mapping.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+}  // namespace
+
+class BlockMappingTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    EXPECT_TRUE(utils::MakeTempFile("BlockMappingTest_old.XXXXXX",
+                                    &old_part_path_,
+                                    nullptr));
+    EXPECT_TRUE(utils::MakeTempFile("BlockMappingTest_new.XXXXXX",
+                                    &new_part_path_,
+                                    nullptr));
+
+    old_part_unlinker_.reset(new ScopedPathUnlinker(old_part_path_));
+    new_part_unlinker_.reset(new ScopedPathUnlinker(new_part_path_));
+  }
+
+  // Old new partition files used in testing.
+  string old_part_path_;
+  string new_part_path_;
+  std::unique_ptr<ScopedPathUnlinker> old_part_unlinker_;
+  std::unique_ptr<ScopedPathUnlinker> new_part_unlinker_;
+
+  size_t block_size_{1024};
+  BlockMapping bm_{block_size_};  // BlockMapping under test.
+};
+
+TEST_F(BlockMappingTest, FirstAddedBlockIsZero) {
+  brillo::Blob blob(block_size_);
+  // The BlockMapping just assigns the block ids in order, so it doesn't matter
+  // what are the contents of the first block.
+  blob[0] = 42;
+  EXPECT_EQ(0, bm_.AddBlock(blob));
+  blob[0] = 5;
+  EXPECT_EQ(1, bm_.AddBlock(blob));
+}
+
+TEST_F(BlockMappingTest, BlocksAreNotKeptInMemory) {
+  test_utils::WriteFileString(old_part_path_, string(block_size_, 'a'));
+  int old_fd = HANDLE_EINTR(open(old_part_path_.c_str(), O_RDONLY));
+  ScopedFdCloser old_fd_closer(&old_fd);
+
+  EXPECT_EQ(0, bm_.AddDiskBlock(old_fd, 0));
+
+  // Check that the block_data is not stored on memory if we just used the block
+  // once.
+  for (const auto& it : bm_.mapping_) {
+    for (const BlockMapping::UniqueBlock& ublock : it.second) {
+      EXPECT_TRUE(ublock.block_data.empty());
+    }
+  }
+
+  brillo::Blob block(block_size_, 'a');
+  for (int i = 0; i < 5; ++i) {
+    // Re-add the same block 5 times.
+    EXPECT_EQ(0, bm_.AddBlock(block));
+  }
+
+  for (const auto& it : bm_.mapping_) {
+    for (const BlockMapping::UniqueBlock& ublock : it.second) {
+      EXPECT_FALSE(ublock.block_data.empty());
+      // The block was loaded from disk only 4 times, and after that the counter
+      // is not updated anymore.
+      EXPECT_EQ(4U, ublock.times_read);
+    }
+  }
+}
+
+TEST_F(BlockMappingTest, MapPartitionBlocks) {
+  // A string with 10 blocks where all the blocks are different.
+  string old_contents(10 * block_size_, '\0');
+  for (size_t i = 0; i < old_contents.size(); ++i)
+    old_contents[i] = 4 + i / block_size_;
+  test_utils::WriteFileString(old_part_path_, old_contents);
+
+  // A string including the block with all zeros and overlapping some of the
+  // other blocks in old_contents.
+  string new_contents(6 * block_size_, '\0');
+  for (size_t i = 0; i < new_contents.size(); ++i)
+    new_contents[i] = i / block_size_;
+  test_utils::WriteFileString(new_part_path_, new_contents);
+
+  vector<BlockMapping::BlockId> old_ids, new_ids;
+  EXPECT_TRUE(MapPartitionBlocks(old_part_path_,
+                                 new_part_path_,
+                                 old_contents.size(),
+                                 new_contents.size(),
+                                 block_size_,
+                                 &old_ids,
+                                 &new_ids));
+
+  EXPECT_EQ((vector<BlockMapping::BlockId>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}),
+            old_ids);
+  EXPECT_EQ((vector<BlockMapping::BlockId>{0, 11, 12, 13, 1, 2}),
+            new_ids);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/bzip.cc b/update_engine/payload_generator/bzip.cc
new file mode 100644
index 0000000..c2388ca
--- /dev/null
+++ b/update_engine/payload_generator/bzip.cc
@@ -0,0 +1,66 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_generator/bzip.h"
+
+#include <bzlib.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <limits>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+bool BzipCompress(const brillo::Blob& in, brillo::Blob* out) {
+  TEST_AND_RETURN_FALSE(out);
+  out->clear();
+  if (in.size() == 0)
+    return true;
+
+  // We expect a compression ratio of about 35% with bzip2, so we start with
+  // that much output space, which will then be doubled if needed.
+  size_t buf_size = 40 + in.size() * 35 / 100;
+  out->resize(buf_size);
+
+  // Try increasing buffer size until it works
+  for (;;) {
+    if (buf_size > std::numeric_limits<uint32_t>::max())
+      return false;
+    uint32_t data_size = buf_size;
+    int rc = BZ2_bzBuffToBuffCompress(
+        reinterpret_cast<char*>(out->data()),
+        &data_size,
+        reinterpret_cast<char*>(const_cast<uint8_t*>(in.data())),
+        in.size(),
+        9,   // Best compression
+        0,   // Silent verbosity
+        0);  // Default work factor
+    TEST_AND_RETURN_FALSE(rc == BZ_OUTBUFF_FULL || rc == BZ_OK);
+    if (rc == BZ_OK) {
+      // we're done!
+      out->resize(data_size);
+      return true;
+    }
+
+    // Data didn't fit; double the buffer size.
+    buf_size *= 2;
+    out->resize(buf_size);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/bzip.h b/update_engine/payload_generator/bzip.h
new file mode 100644
index 0000000..198768f
--- /dev/null
+++ b/update_engine/payload_generator/bzip.h
@@ -0,0 +1,29 @@
+//
+// 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_PAYLOAD_GENERATOR_BZIP_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_BZIP_H_
+
+#include <brillo/secure_blob.h>
+
+namespace chromeos_update_engine {
+
+// Compresses the input buffer |in| into |out| with bzip2.
+bool BzipCompress(const brillo::Blob& in, brillo::Blob* out);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_BZIP_H_
diff --git a/update_engine/payload_generator/cycle_breaker.cc b/update_engine/payload_generator/cycle_breaker.cc
new file mode 100644
index 0000000..52a6f60
--- /dev/null
+++ b/update_engine/payload_generator/cycle_breaker.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/payload_generator/cycle_breaker.h"
+
+#include <inttypes.h>
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/graph_utils.h"
+#include "update_engine/payload_generator/tarjan.h"
+
+using std::make_pair;
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// This is the outer function from the original paper.
+void CycleBreaker::BreakCycles(const Graph& graph, set<Edge>* out_cut_edges) {
+  cut_edges_.clear();
+
+  // Make a copy, which we will modify by removing edges. Thus, in each
+  // iteration subgraph_ is the current subgraph or the original with
+  // vertices we desire. This variable was "A_K" in the original paper.
+  subgraph_ = graph;
+
+  // The paper calls for the "adjacency structure (i.e., graph) of
+  // strong (-ly connected) component K with least vertex in subgraph
+  // induced by {s, s + 1, ..., n}".
+  // We arbitrarily order each vertex by its index in the graph. Thus,
+  // each iteration, we are looking at the subgraph {s, s + 1, ..., n}
+  // and looking for the strongly connected component with vertex s.
+
+  TarjanAlgorithm tarjan;
+  skipped_ops_ = 0;
+
+  for (Graph::size_type i = 0; i < subgraph_.size(); i++) {
+    InstallOperation_Type op_type = graph[i].aop.op.type();
+    if (op_type == InstallOperation::REPLACE ||
+        op_type == InstallOperation::REPLACE_BZ) {
+      skipped_ops_++;
+      continue;
+    }
+
+    if (i > 0) {
+      // Erase node (i - 1) from subgraph_. First, erase what it points to
+      subgraph_[i - 1].out_edges.clear();
+      // Now, erase any pointers to node (i - 1)
+      for (Graph::size_type j = i; j < subgraph_.size(); j++) {
+        subgraph_[j].out_edges.erase(i - 1);
+      }
+    }
+
+    // Calculate SCC (strongly connected component) with vertex i.
+    vector<Vertex::Index> component_indexes;
+    tarjan.Execute(i, &subgraph_, &component_indexes);
+
+    // Set subgraph edges for the components in the SCC.
+    for (vector<Vertex::Index>::iterator it = component_indexes.begin();
+         it != component_indexes.end(); ++it) {
+      subgraph_[*it].subgraph_edges.clear();
+      for (vector<Vertex::Index>::iterator jt = component_indexes.begin();
+           jt != component_indexes.end(); ++jt) {
+        // If there's a link from *it -> *jt in the graph,
+        // add a subgraph_ edge
+        if (utils::MapContainsKey(subgraph_[*it].out_edges, *jt))
+          subgraph_[*it].subgraph_edges.insert(*jt);
+      }
+    }
+
+    current_vertex_ = i;
+    blocked_.clear();
+    blocked_.resize(subgraph_.size());
+    blocked_graph_.clear();
+    blocked_graph_.resize(subgraph_.size());
+    Circuit(current_vertex_, 0);
+  }
+
+  out_cut_edges->swap(cut_edges_);
+  LOG(INFO) << "Cycle breaker skipped " << skipped_ops_ << " ops.";
+  DCHECK(stack_.empty());
+}
+
+static const size_t kMaxEdgesToConsider = 2;
+
+void CycleBreaker::HandleCircuit() {
+  stack_.push_back(current_vertex_);
+  CHECK_GE(stack_.size(),
+           static_cast<vector<Vertex::Index>::size_type>(2));
+  Edge min_edge = make_pair(stack_[0], stack_[1]);
+  uint64_t min_edge_weight = std::numeric_limits<uint64_t>::max();
+  size_t edges_considered = 0;
+  for (vector<Vertex::Index>::const_iterator it = stack_.begin();
+       it != (stack_.end() - 1); ++it) {
+    Edge edge = make_pair(*it, *(it + 1));
+    if (cut_edges_.find(edge) != cut_edges_.end()) {
+      stack_.pop_back();
+      return;
+    }
+    uint64_t edge_weight = graph_utils::EdgeWeight(subgraph_, edge);
+    if (edge_weight < min_edge_weight) {
+      min_edge_weight = edge_weight;
+      min_edge = edge;
+    }
+    edges_considered++;
+    if (edges_considered == kMaxEdgesToConsider)
+      break;
+  }
+  cut_edges_.insert(min_edge);
+  stack_.pop_back();
+}
+
+void CycleBreaker::Unblock(Vertex::Index u) {
+  blocked_[u] = false;
+
+  for (Vertex::EdgeMap::iterator it = blocked_graph_[u].out_edges.begin();
+       it != blocked_graph_[u].out_edges.end(); ) {
+    Vertex::Index w = it->first;
+    blocked_graph_[u].out_edges.erase(it++);
+    if (blocked_[w])
+      Unblock(w);
+  }
+}
+
+bool CycleBreaker::StackContainsCutEdge() const {
+  for (vector<Vertex::Index>::const_iterator it = ++stack_.begin(),
+           e = stack_.end(); it != e; ++it) {
+    Edge edge = make_pair(*(it - 1), *it);
+    if (utils::SetContainsKey(cut_edges_, edge)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool CycleBreaker::Circuit(Vertex::Index vertex, Vertex::Index depth) {
+  // "vertex" was "v" in the original paper.
+  bool found = false;  // Was "f" in the original paper.
+  stack_.push_back(vertex);
+  blocked_[vertex] = true;
+  {
+    static int counter = 0;
+    counter++;
+    if (counter == 10000) {
+      counter = 0;
+      std::string stack_str;
+      for (Vertex::Index index : stack_) {
+        stack_str += std::to_string(index);
+        stack_str += " -> ";
+      }
+      LOG(INFO) << "stack: " << stack_str;
+    }
+  }
+
+  for (Vertex::SubgraphEdgeMap::iterator w =
+           subgraph_[vertex].subgraph_edges.begin();
+       w != subgraph_[vertex].subgraph_edges.end(); ++w) {
+    if (*w == current_vertex_) {
+      // The original paper called for printing stack_ followed by
+      // current_vertex_ here, which is a cycle. Instead, we call
+      // HandleCircuit() to break it.
+      HandleCircuit();
+      found = true;
+    } else if (!blocked_[*w]) {
+      if (Circuit(*w, depth + 1)) {
+        found = true;
+        if ((depth > kMaxEdgesToConsider) || StackContainsCutEdge())
+          break;
+      }
+    }
+  }
+
+  if (found) {
+    Unblock(vertex);
+  } else {
+    for (Vertex::SubgraphEdgeMap::iterator w =
+             subgraph_[vertex].subgraph_edges.begin();
+         w != subgraph_[vertex].subgraph_edges.end(); ++w) {
+      if (blocked_graph_[*w].out_edges.find(vertex) ==
+          blocked_graph_[*w].out_edges.end()) {
+        blocked_graph_[*w].out_edges.insert(make_pair(vertex,
+                                                      EdgeProperties()));
+      }
+    }
+  }
+  CHECK_EQ(vertex, stack_.back());
+  stack_.pop_back();
+  return found;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/cycle_breaker.h b/update_engine/payload_generator/cycle_breaker.h
new file mode 100644
index 0000000..231d63a
--- /dev/null
+++ b/update_engine/payload_generator/cycle_breaker.h
@@ -0,0 +1,71 @@
+//
+// 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_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_
+
+// This is a modified implementation of Donald B. Johnson's algorithm for
+// finding all elementary cycles (a.k.a. circuits) in a directed graph.
+// See the paper "Finding All the Elementary Circuits of a Directed Graph"
+// at http://dutta.csc.ncsu.edu/csc791_spring07/wrap/circuits_johnson.pdf
+// for reference.
+
+// Note: this version of the algorithm not only finds cycles, but breaks them.
+// It uses a simple greedy algorithm for cutting: when a cycle is discovered,
+// the edge with the least weight is cut. Longer term we may wish to do
+// something more intelligent, since the goal is (ideally) to minimize the
+// sum of the weights of all cut cycles. In practice, it's intractable
+// to consider all cycles before cutting any; there are simply too many.
+// In a sample graph representative of a typical workload, I found over
+// 5 * 10^15 cycles.
+
+#include <set>
+#include <vector>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+class CycleBreaker {
+ public:
+  CycleBreaker() : skipped_ops_(0) {}
+  // out_cut_edges is replaced with the cut edges.
+  void BreakCycles(const Graph& graph, std::set<Edge>* out_cut_edges);
+
+  size_t skipped_ops() const { return skipped_ops_; }
+
+ private:
+  void HandleCircuit();
+  void Unblock(Vertex::Index u);
+  bool Circuit(Vertex::Index vertex, Vertex::Index depth);
+  bool StackContainsCutEdge() const;
+
+  std::vector<bool> blocked_;  // "blocked" in the paper
+  Vertex::Index current_vertex_;  // "s" in the paper
+  std::vector<Vertex::Index> stack_;  // the stack variable in the paper
+  Graph subgraph_;  // "A_K" in the paper
+  Graph blocked_graph_;  // "B" in the paper
+
+  std::set<Edge> cut_edges_;
+
+  // Number of operations skipped b/c we know they don't have any
+  // incoming edges.
+  size_t skipped_ops_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_
diff --git a/update_engine/payload_generator/cycle_breaker_unittest.cc b/update_engine/payload_generator/cycle_breaker_unittest.cc
new file mode 100644
index 0000000..e92bc30
--- /dev/null
+++ b/update_engine/payload_generator/cycle_breaker_unittest.cc
@@ -0,0 +1,278 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_generator/cycle_breaker.h"
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/graph_types.h"
+
+using std::make_pair;
+using std::pair;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+void SetOpForNodes(Graph* graph) {
+  for (Vertex& vertex : *graph) {
+    vertex.aop.op.set_type(InstallOperation::MOVE);
+  }
+}
+}  // namespace
+
+class CycleBreakerTest : public ::testing::Test {};
+
+TEST(CycleBreakerTest, SimpleTest) {
+  int counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Vertex::Index n_d = counter++;
+  const Vertex::Index n_e = counter++;
+  const Vertex::Index n_f = counter++;
+  const Vertex::Index n_g = counter++;
+  const Vertex::Index n_h = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+  SetOpForNodes(&graph);
+
+  graph[n_a].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_a].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties()));
+  graph[n_c].out_edges.insert(make_pair(n_d, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_b, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_c, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_f].out_edges.insert(make_pair(n_g, EdgeProperties()));
+  graph[n_g].out_edges.insert(make_pair(n_h, EdgeProperties()));
+  graph[n_h].out_edges.insert(make_pair(n_g, EdgeProperties()));
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  breaker.BreakCycles(graph, &broken_edges);
+
+  // The following cycles must be cut:
+  // A->E->B
+  // C->D->E
+  // G->H
+
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_e)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_e, n_b)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_b, n_a)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_c, n_d)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_d, n_e)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_e, n_c)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_g, n_h)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_h, n_g)));
+  EXPECT_EQ(3U, broken_edges.size());
+}
+
+namespace {
+pair<Vertex::Index, EdgeProperties> EdgeWithWeight(Vertex::Index dest,
+uint64_t weight) {
+  EdgeProperties props;
+  props.extents.resize(1);
+  props.extents[0].set_num_blocks(weight);
+  return make_pair(dest, props);
+}
+}  // namespace
+
+
+// This creates a bunch of cycles like this:
+//
+//               root <------.
+//    (t)->     / | \        |
+//             V  V  V       |
+//             N  N  N       |
+//              \ | /        |
+//               VVV         |
+//                N          |
+//              / | \        |
+//             V  V  V       |
+//             N  N  N       |
+//               ...         |
+//     (s)->    \ | /        |
+//               VVV         |
+//                N          |
+//                 \_________/
+//
+// such that the original cutting algo would cut edges (s). We changed
+// the algorithm to cut cycles (t) instead, since they are closer to the
+// root, and that can massively speed up cycle cutting.
+TEST(CycleBreakerTest, AggressiveCutTest) {
+  size_t counter = 0;
+
+  const int kNodesPerGroup = 4;
+  const int kGroups = 33;
+
+  Graph graph(kGroups * kNodesPerGroup + 1);  // + 1 for the root node
+  SetOpForNodes(&graph);
+
+  const Vertex::Index n_root = counter++;
+
+  Vertex::Index last_hub = n_root;
+  for (int i = 0; i < kGroups; i++) {
+    uint64_t weight = 5;
+    if (i == 0)
+      weight = 2;
+    else if (i == (kGroups - 1))
+      weight = 1;
+
+    const Vertex::Index next_hub = counter++;
+
+    for (int j = 0; j < (kNodesPerGroup - 1); j++) {
+      const Vertex::Index node = counter++;
+      graph[last_hub].out_edges.insert(EdgeWithWeight(node, weight));
+      graph[node].out_edges.insert(EdgeWithWeight(next_hub, weight));
+    }
+    last_hub = next_hub;
+  }
+
+  graph[last_hub].out_edges.insert(EdgeWithWeight(n_root, 5));
+
+  EXPECT_EQ(counter, graph.size());
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  LOG(INFO) << "If this hangs for more than 1 second, the test has failed.";
+  breaker.BreakCycles(graph, &broken_edges);
+
+  set<Edge> expected_cuts;
+
+  for (Vertex::EdgeMap::const_iterator it = graph[n_root].out_edges.begin(),
+       e = graph[n_root].out_edges.end(); it != e; ++it) {
+    expected_cuts.insert(make_pair(n_root, it->first));
+  }
+
+  EXPECT_TRUE(broken_edges == expected_cuts);
+}
+
+TEST(CycleBreakerTest, WeightTest) {
+  size_t counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Vertex::Index n_d = counter++;
+  const Vertex::Index n_e = counter++;
+  const Vertex::Index n_f = counter++;
+  const Vertex::Index n_g = counter++;
+  const Vertex::Index n_h = counter++;
+  const Vertex::Index n_i = counter++;
+  const Vertex::Index n_j = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+  SetOpForNodes(&graph);
+
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 4));
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_f, 3));
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_h, 2));
+  graph[n_b].out_edges.insert(EdgeWithWeight(n_a, 3));
+  graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 4));
+  graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 5));
+  graph[n_c].out_edges.insert(EdgeWithWeight(n_d, 3));
+  graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 6));
+  graph[n_d].out_edges.insert(EdgeWithWeight(n_e, 3));
+  graph[n_e].out_edges.insert(EdgeWithWeight(n_d, 4));
+  graph[n_e].out_edges.insert(EdgeWithWeight(n_g, 5));
+  graph[n_f].out_edges.insert(EdgeWithWeight(n_g, 2));
+  graph[n_g].out_edges.insert(EdgeWithWeight(n_f, 3));
+  graph[n_g].out_edges.insert(EdgeWithWeight(n_d, 5));
+  graph[n_h].out_edges.insert(EdgeWithWeight(n_i, 8));
+  graph[n_i].out_edges.insert(EdgeWithWeight(n_e, 4));
+  graph[n_i].out_edges.insert(EdgeWithWeight(n_h, 9));
+  graph[n_i].out_edges.insert(EdgeWithWeight(n_j, 6));
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  breaker.BreakCycles(graph, &broken_edges);
+
+  // These are required to be broken:
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_b, n_a)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_b, n_c)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_d, n_e)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_f, n_g)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_h, n_i)));
+}
+
+TEST(CycleBreakerTest, UnblockGraphTest) {
+  size_t counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Vertex::Index n_d = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+  SetOpForNodes(&graph);
+
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1));
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_c, 1));
+  graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 2));
+  graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 2));
+  graph[n_b].out_edges.insert(EdgeWithWeight(n_d, 2));
+  graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 2));
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  breaker.BreakCycles(graph, &broken_edges);
+
+  // These are required to be broken:
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_b)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_c)));
+}
+
+TEST(CycleBreakerTest, SkipOpsTest) {
+  size_t counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+  SetOpForNodes(&graph);
+  graph[n_a].aop.op.set_type(InstallOperation::REPLACE_BZ);
+  graph[n_c].aop.op.set_type(InstallOperation::REPLACE);
+
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1));
+  graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 1));
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  breaker.BreakCycles(graph, &broken_edges);
+
+  EXPECT_EQ(2U, breaker.skipped_ops());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/delta_diff_generator.cc b/update_engine/payload_generator/delta_diff_generator.cc
new file mode 100644
index 0000000..a140d21
--- /dev/null
+++ b/update_engine/payload_generator/delta_diff_generator.cc
@@ -0,0 +1,139 @@
+//
+// 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/payload_generator/delta_diff_generator.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/ab_generator.h"
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/full_update_generator.h"
+#include "update_engine/payload_generator/inplace_generator.h"
+#include "update_engine/payload_generator/payload_file.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// bytes
+const size_t kRootFSPartitionSize = static_cast<size_t>(2) * 1024 * 1024 * 1024;
+const size_t kBlockSize = 4096;  // bytes
+
+bool GenerateUpdatePayloadFile(
+    const PayloadGenerationConfig& config,
+    const string& output_path,
+    const string& private_key_path,
+    uint64_t* metadata_size) {
+  if (!config.version.Validate()) {
+    LOG(ERROR) << "Unsupported major.minor version: " << config.version.major
+               << "." << config.version.minor;
+    return false;
+  }
+
+  // Create empty payload file object.
+  PayloadFile payload;
+  TEST_AND_RETURN_FALSE(payload.Init(config));
+
+  const string kTempFileTemplate("CrAU_temp_data.XXXXXX");
+  string temp_file_path;
+  int data_file_fd;
+  TEST_AND_RETURN_FALSE(
+      utils::MakeTempFile(kTempFileTemplate, &temp_file_path, &data_file_fd));
+  ScopedPathUnlinker temp_file_unlinker(temp_file_path);
+  TEST_AND_RETURN_FALSE(data_file_fd >= 0);
+
+  {
+    off_t data_file_size = 0;
+    ScopedFdCloser data_file_fd_closer(&data_file_fd);
+    BlobFileWriter blob_file(data_file_fd, &data_file_size);
+    if (config.is_delta) {
+      TEST_AND_RETURN_FALSE(config.source.partitions.size() ==
+                            config.target.partitions.size());
+    }
+    PartitionConfig empty_part("");
+    for (size_t i = 0; i < config.target.partitions.size(); i++) {
+      const PartitionConfig& old_part =
+          config.is_delta ? config.source.partitions[i] : empty_part;
+      const PartitionConfig& new_part = config.target.partitions[i];
+      LOG(INFO) << "Partition name: " << new_part.name;
+      LOG(INFO) << "Partition size: " << new_part.size;
+      LOG(INFO) << "Block count: " << new_part.size / config.block_size;
+
+      // Select payload generation strategy based on the config.
+      unique_ptr<OperationsGenerator> strategy;
+      // We don't efficiently support deltas on squashfs. For now, we will
+      // produce full operations in that case.
+      if (!old_part.path.empty() &&
+          !diff_utils::IsSquashfs4Filesystem(new_part.path)) {
+        // Delta update.
+        if (config.version.minor == kInPlaceMinorPayloadVersion) {
+          LOG(INFO) << "Using generator InplaceGenerator().";
+          strategy.reset(new InplaceGenerator());
+        } else {
+          LOG(INFO) << "Using generator ABGenerator().";
+          strategy.reset(new ABGenerator());
+        }
+      } else {
+        LOG(INFO) << "Using generator FullUpdateGenerator().";
+        strategy.reset(new FullUpdateGenerator());
+      }
+
+      vector<AnnotatedOperation> aops;
+      // Generate the operations using the strategy we selected above.
+      TEST_AND_RETURN_FALSE(strategy->GenerateOperations(config,
+                                                         old_part,
+                                                         new_part,
+                                                         &blob_file,
+                                                         &aops));
+
+      // Filter the no-operations. OperationsGenerators should not output this
+      // kind of operations normally, but this is an extra step to fix that if
+      // happened.
+      diff_utils::FilterNoopOperations(&aops);
+
+      TEST_AND_RETURN_FALSE(payload.AddPartition(old_part, new_part, aops));
+    }
+  }
+
+  LOG(INFO) << "Writing payload file...";
+  // Write payload file to disk.
+  TEST_AND_RETURN_FALSE(payload.WritePayload(output_path, temp_file_path,
+                                             private_key_path, metadata_size));
+
+  LOG(INFO) << "All done. Successfully created delta file with "
+            << "metadata size = " << *metadata_size;
+  return true;
+}
+
+};  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/delta_diff_generator.h b/update_engine/payload_generator/delta_diff_generator.h
new file mode 100644
index 0000000..d8bdae2
--- /dev/null
+++ b/update_engine/payload_generator/delta_diff_generator.h
@@ -0,0 +1,47 @@
+//
+// 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_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+
+#include <string>
+
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+extern const size_t kBlockSize;
+extern const size_t kRootFSPartitionSize;
+
+// The |config| describes the payload generation request, describing both
+// old and new images for delta payloads and only the new image for full
+// payloads.
+// For delta payloads, the images should be already mounted read-only at
+// the respective rootfs_mountpt.
+// |private_key_path| points to a private key used to sign the update.
+// Pass empty string to not sign the update.
+// |output_path| is the filename where the delta update should be written.
+// Returns true on success. Also writes the size of the metadata into
+// |metadata_size|.
+bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config,
+                               const std::string& output_path,
+                               const std::string& private_key_path,
+                               uint64_t* metadata_size);
+
+
+};  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
diff --git a/update_engine/payload_generator/delta_diff_utils.cc b/update_engine/payload_generator/delta_diff_utils.cc
new file mode 100644
index 0000000..50fbdf2
--- /dev/null
+++ b/update_engine/payload_generator/delta_diff_utils.cc
@@ -0,0 +1,836 @@
+//
+// 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/payload_generator/delta_diff_utils.h"
+
+#include <endian.h>
+#include <ext2fs/ext2fs.h>
+
+#include <algorithm>
+#include <map>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/block_mapping.h"
+#include "update_engine/payload_generator/bzip.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/payload_generator/xz.h"
+
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+namespace {
+
+const char* const kBsdiffPath = "bsdiff";
+const char* const kImgdiffPath = "imgdiff";
+
+// The maximum destination size allowed for bsdiff. In general, bsdiff should
+// work for arbitrary big files, but the payload generation and payload
+// application requires a significant amount of RAM. We put a hard-limit of
+// 200 MiB that should not affect any released board, but will limit the
+// Chrome binary in ASan builders.
+const uint64_t kMaxBsdiffDestinationSize = 200 * 1024 * 1024;  // bytes
+
+// The maximum destination size allowed for imgdiff. In general, imgdiff should
+// work for arbitrary big files, but the payload application is quite memory
+// intensive, so we limit these operations to 50 MiB.
+const uint64_t kMaxImgdiffDestinationSize = 50 * 1024 * 1024;  // bytes
+
+// Process a range of blocks from |range_start| to |range_end| in the extent at
+// position |*idx_p| of |extents|. If |do_remove| is true, this range will be
+// removed, which may cause the extent to be trimmed, split or removed entirely.
+// The value of |*idx_p| is updated to point to the next extent to be processed.
+// Returns true iff the next extent to process is a new or updated one.
+bool ProcessExtentBlockRange(vector<Extent>* extents, size_t* idx_p,
+                             const bool do_remove, uint64_t range_start,
+                             uint64_t range_end) {
+  size_t idx = *idx_p;
+  uint64_t start_block = (*extents)[idx].start_block();
+  uint64_t num_blocks = (*extents)[idx].num_blocks();
+  uint64_t range_size = range_end - range_start;
+
+  if (do_remove) {
+    if (range_size == num_blocks) {
+      // Remove the entire extent.
+      extents->erase(extents->begin() + idx);
+    } else if (range_end == num_blocks) {
+      // Trim the end of the extent.
+      (*extents)[idx].set_num_blocks(num_blocks - range_size);
+      idx++;
+    } else if (range_start == 0) {
+      // Trim the head of the extent.
+      (*extents)[idx].set_start_block(start_block + range_size);
+      (*extents)[idx].set_num_blocks(num_blocks - range_size);
+    } else {
+      // Trim the middle, splitting the remainder into two parts.
+      (*extents)[idx].set_num_blocks(range_start);
+      Extent e;
+      e.set_start_block(start_block + range_end);
+      e.set_num_blocks(num_blocks - range_end);
+      idx++;
+      extents->insert(extents->begin() + idx, e);
+    }
+  } else if (range_end == num_blocks) {
+    // Done with this extent.
+    idx++;
+  } else {
+    return false;
+  }
+
+  *idx_p = idx;
+  return true;
+}
+
+// Remove identical corresponding block ranges in |src_extents| and
+// |dst_extents|. Used for preventing moving of blocks onto themselves during
+// MOVE operations. The value of |total_bytes| indicates the actual length of
+// content; this may be slightly less than the total size of blocks, in which
+// case the last block is only partly occupied with data. Returns the total
+// number of bytes removed.
+size_t RemoveIdenticalBlockRanges(vector<Extent>* src_extents,
+                                  vector<Extent>* dst_extents,
+                                  const size_t total_bytes) {
+  size_t src_idx = 0;
+  size_t dst_idx = 0;
+  uint64_t src_offset = 0, dst_offset = 0;
+  bool new_src = true, new_dst = true;
+  size_t removed_bytes = 0, nonfull_block_bytes;
+  bool do_remove = false;
+  while (src_idx < src_extents->size() && dst_idx < dst_extents->size()) {
+    if (new_src) {
+      src_offset = 0;
+      new_src = false;
+    }
+    if (new_dst) {
+      dst_offset = 0;
+      new_dst = false;
+    }
+
+    do_remove = ((*src_extents)[src_idx].start_block() + src_offset ==
+                 (*dst_extents)[dst_idx].start_block() + dst_offset);
+
+    uint64_t src_num_blocks = (*src_extents)[src_idx].num_blocks();
+    uint64_t dst_num_blocks = (*dst_extents)[dst_idx].num_blocks();
+    uint64_t min_num_blocks = std::min(src_num_blocks - src_offset,
+                                       dst_num_blocks - dst_offset);
+    uint64_t prev_src_offset = src_offset;
+    uint64_t prev_dst_offset = dst_offset;
+    src_offset += min_num_blocks;
+    dst_offset += min_num_blocks;
+
+    new_src = ProcessExtentBlockRange(src_extents, &src_idx, do_remove,
+                                      prev_src_offset, src_offset);
+    new_dst = ProcessExtentBlockRange(dst_extents, &dst_idx, do_remove,
+                                      prev_dst_offset, dst_offset);
+    if (do_remove)
+      removed_bytes += min_num_blocks * kBlockSize;
+  }
+
+  // If we removed the last block and this block is only partly used by file
+  // content, deduct the unused portion from the total removed byte count.
+  if (do_remove && (nonfull_block_bytes = total_bytes % kBlockSize))
+    removed_bytes -= kBlockSize - nonfull_block_bytes;
+
+  return removed_bytes;
+}
+
+// Returns true if the given blob |data| contains gzip header magic.
+bool ContainsGZip(const brillo::Blob& data) {
+  const uint8_t kGZipMagic[] = {0x1f, 0x8b, 0x08, 0x00};
+  return std::search(data.begin(),
+                     data.end(),
+                     std::begin(kGZipMagic),
+                     std::end(kGZipMagic)) != data.end();
+}
+
+}  // namespace
+
+namespace diff_utils {
+
+bool DeltaReadPartition(vector<AnnotatedOperation>* aops,
+                        const PartitionConfig& old_part,
+                        const PartitionConfig& new_part,
+                        ssize_t hard_chunk_blocks,
+                        size_t soft_chunk_blocks,
+                        const PayloadVersion& version,
+                        BlobFileWriter* blob_file) {
+  ExtentRanges old_visited_blocks;
+  ExtentRanges new_visited_blocks;
+
+  TEST_AND_RETURN_FALSE(DeltaMovedAndZeroBlocks(
+      aops,
+      old_part.path,
+      new_part.path,
+      old_part.size / kBlockSize,
+      new_part.size / kBlockSize,
+      soft_chunk_blocks,
+      version,
+      blob_file,
+      &old_visited_blocks,
+      &new_visited_blocks));
+
+  map<string, vector<Extent>> old_files_map;
+  if (old_part.fs_interface) {
+    vector<FilesystemInterface::File> old_files;
+    old_part.fs_interface->GetFiles(&old_files);
+    for (const FilesystemInterface::File& file : old_files)
+      old_files_map[file.name] = file.extents;
+  }
+
+  TEST_AND_RETURN_FALSE(new_part.fs_interface);
+  vector<FilesystemInterface::File> new_files;
+  new_part.fs_interface->GetFiles(&new_files);
+
+  // The processing is very straightforward here, we generate operations for
+  // every file (and pseudo-file such as the metadata) in the new filesystem
+  // based on the file with the same name in the old filesystem, if any.
+  // Files with overlapping data blocks (like hardlinks or filesystems with tail
+  // packing or compression where the blocks store more than one file) are only
+  // generated once in the new image, but are also used only once from the old
+  // image due to some simplifications (see below).
+  for (const FilesystemInterface::File& new_file : new_files) {
+    // Ignore the files in the new filesystem without blocks. Symlinks with
+    // data blocks (for example, symlinks bigger than 60 bytes in ext2) are
+    // handled as normal files. We also ignore blocks that were already
+    // processed by a previous file.
+    vector<Extent> new_file_extents = FilterExtentRanges(
+        new_file.extents, new_visited_blocks);
+    new_visited_blocks.AddExtents(new_file_extents);
+
+    if (new_file_extents.empty())
+      continue;
+
+    LOG(INFO) << "Encoding file " << new_file.name << " ("
+              << BlocksInExtents(new_file_extents) << " blocks)";
+
+    // We can't visit each dst image inode more than once, as that would
+    // duplicate work. Here, we avoid visiting each source image inode
+    // more than once. Technically, we could have multiple operations
+    // that read the same blocks from the source image for diffing, but
+    // we choose not to avoid complexity. Eventually we will move away
+    // from using a graph/cycle detection/etc to generate diffs, and at that
+    // time, it will be easy (non-complex) to have many operations read
+    // from the same source blocks. At that time, this code can die. -adlr
+    vector<Extent> old_file_extents = FilterExtentRanges(
+        old_files_map[new_file.name], old_visited_blocks);
+    old_visited_blocks.AddExtents(old_file_extents);
+
+    TEST_AND_RETURN_FALSE(DeltaReadFile(aops,
+                                        old_part.path,
+                                        new_part.path,
+                                        old_file_extents,
+                                        new_file_extents,
+                                        new_file.name,  // operation name
+                                        hard_chunk_blocks,
+                                        version,
+                                        blob_file));
+  }
+  // Process all the blocks not included in any file. We provided all the unused
+  // blocks in the old partition as available data.
+  vector<Extent> new_unvisited = {
+      ExtentForRange(0, new_part.size / kBlockSize)};
+  new_unvisited = FilterExtentRanges(new_unvisited, new_visited_blocks);
+  if (new_unvisited.empty())
+    return true;
+
+  vector<Extent> old_unvisited;
+  if (old_part.fs_interface) {
+    old_unvisited.push_back(ExtentForRange(0, old_part.size / kBlockSize));
+    old_unvisited = FilterExtentRanges(old_unvisited, old_visited_blocks);
+  }
+
+  LOG(INFO) << "Scanning " << BlocksInExtents(new_unvisited)
+            << " unwritten blocks using chunk size of "
+            << soft_chunk_blocks << " blocks.";
+  // We use the soft_chunk_blocks limit for the <non-file-data> as we don't
+  // really know the structure of this data and we should not expect it to have
+  // redundancy between partitions.
+  TEST_AND_RETURN_FALSE(DeltaReadFile(aops,
+                                      old_part.path,
+                                      new_part.path,
+                                      old_unvisited,
+                                      new_unvisited,
+                                      "<non-file-data>",  // operation name
+                                      soft_chunk_blocks,
+                                      version,
+                                      blob_file));
+
+  return true;
+}
+
+bool DeltaMovedAndZeroBlocks(vector<AnnotatedOperation>* aops,
+                             const string& old_part,
+                             const string& new_part,
+                             size_t old_num_blocks,
+                             size_t new_num_blocks,
+                             ssize_t chunk_blocks,
+                             const PayloadVersion& version,
+                             BlobFileWriter* blob_file,
+                             ExtentRanges* old_visited_blocks,
+                             ExtentRanges* new_visited_blocks) {
+  vector<BlockMapping::BlockId> old_block_ids;
+  vector<BlockMapping::BlockId> new_block_ids;
+  TEST_AND_RETURN_FALSE(MapPartitionBlocks(old_part,
+                                           new_part,
+                                           old_num_blocks * kBlockSize,
+                                           new_num_blocks * kBlockSize,
+                                           kBlockSize,
+                                           &old_block_ids,
+                                           &new_block_ids));
+
+  // If the update is inplace, we map all the blocks that didn't move,
+  // regardless of the contents since they are already copied and no operation
+  // is required.
+  if (version.InplaceUpdate()) {
+    uint64_t num_blocks = std::min(old_num_blocks, new_num_blocks);
+    for (uint64_t block = 0; block < num_blocks; block++) {
+      if (old_block_ids[block] == new_block_ids[block] &&
+          !old_visited_blocks->ContainsBlock(block) &&
+          !new_visited_blocks->ContainsBlock(block)) {
+        old_visited_blocks->AddBlock(block);
+        new_visited_blocks->AddBlock(block);
+      }
+    }
+  }
+
+  // A mapping from the block_id to the list of block numbers with that block id
+  // in the old partition. This is used to lookup where in the old partition
+  // is a block from the new partition.
+  map<BlockMapping::BlockId, vector<uint64_t>> old_blocks_map;
+
+  for (uint64_t block = old_num_blocks; block-- > 0; ) {
+    if (old_block_ids[block] != 0 && !old_visited_blocks->ContainsBlock(block))
+      old_blocks_map[old_block_ids[block]].push_back(block);
+
+    // Mark all zeroed blocks in the old image as "used" since it doesn't make
+    // any sense to spend I/O to read zeros from the source partition and more
+    // importantly, these could sometimes be blocks discarded in the SSD which
+    // would read non-zero values.
+    if (old_block_ids[block] == 0)
+      old_visited_blocks->AddBlock(block);
+  }
+
+  // The collection of blocks in the new partition with just zeros. This is a
+  // common case for free-space that's also problematic for bsdiff, so we want
+  // to optimize it using REPLACE_BZ operations. The blob for a REPLACE_BZ of
+  // just zeros is so small that it doesn't make sense to spend the I/O reading
+  // zeros from the old partition.
+  vector<Extent> new_zeros;
+
+  vector<Extent> old_identical_blocks;
+  vector<Extent> new_identical_blocks;
+
+  for (uint64_t block = 0; block < new_num_blocks; block++) {
+    // Only produce operations for blocks that were not yet visited.
+    if (new_visited_blocks->ContainsBlock(block))
+      continue;
+    if (new_block_ids[block] == 0) {
+      AppendBlockToExtents(&new_zeros, block);
+      continue;
+    }
+
+    auto old_blocks_map_it = old_blocks_map.find(new_block_ids[block]);
+    // Check if the block exists in the old partition at all.
+    if (old_blocks_map_it == old_blocks_map.end() ||
+        old_blocks_map_it->second.empty())
+      continue;
+
+    AppendBlockToExtents(&old_identical_blocks,
+                         old_blocks_map_it->second.back());
+    AppendBlockToExtents(&new_identical_blocks, block);
+    // We can't reuse source blocks in minor version 1 because the cycle
+    // breaking algorithm used in the in-place update doesn't support that.
+    if (version.InplaceUpdate())
+      old_blocks_map_it->second.pop_back();
+  }
+
+  // Produce operations for the zero blocks split per output extent.
+  // TODO(deymo): Produce ZERO operations instead of calling DeltaReadFile().
+  size_t num_ops = aops->size();
+  new_visited_blocks->AddExtents(new_zeros);
+  for (const Extent& extent : new_zeros) {
+    TEST_AND_RETURN_FALSE(DeltaReadFile(aops,
+                                        "",
+                                        new_part,
+                                        vector<Extent>(),        // old_extents
+                                        vector<Extent>{extent},  // new_extents
+                                        "<zeros>",
+                                        chunk_blocks,
+                                        version,
+                                        blob_file));
+  }
+  LOG(INFO) << "Produced " << (aops->size() - num_ops) << " operations for "
+            << BlocksInExtents(new_zeros) << " zeroed blocks";
+
+  // Produce MOVE/SOURCE_COPY operations for the moved blocks.
+  num_ops = aops->size();
+  if (chunk_blocks == -1)
+    chunk_blocks = new_num_blocks;
+  uint64_t used_blocks = 0;
+  old_visited_blocks->AddExtents(old_identical_blocks);
+  new_visited_blocks->AddExtents(new_identical_blocks);
+  for (const Extent& extent : new_identical_blocks) {
+    // We split the operation at the extent boundary or when bigger than
+    // chunk_blocks.
+    for (uint64_t op_block_offset = 0; op_block_offset < extent.num_blocks();
+         op_block_offset += chunk_blocks) {
+      aops->emplace_back();
+      AnnotatedOperation* aop = &aops->back();
+      aop->name = "<identical-blocks>";
+      aop->op.set_type(version.OperationAllowed(InstallOperation::SOURCE_COPY)
+                           ? InstallOperation::SOURCE_COPY
+                           : InstallOperation::MOVE);
+
+      uint64_t chunk_num_blocks =
+          std::min(static_cast<uint64_t>(extent.num_blocks()) - op_block_offset,
+                   static_cast<uint64_t>(chunk_blocks));
+
+      // The current operation represents the move/copy operation for the
+      // sublist starting at |used_blocks| of length |chunk_num_blocks| where
+      // the src and dst are from |old_identical_blocks| and
+      // |new_identical_blocks| respectively.
+      StoreExtents(
+          ExtentsSublist(old_identical_blocks, used_blocks, chunk_num_blocks),
+          aop->op.mutable_src_extents());
+
+      Extent* op_dst_extent = aop->op.add_dst_extents();
+      op_dst_extent->set_start_block(extent.start_block() + op_block_offset);
+      op_dst_extent->set_num_blocks(chunk_num_blocks);
+      CHECK(
+          vector<Extent>{*op_dst_extent} ==  // NOLINT(whitespace/braces)
+          ExtentsSublist(new_identical_blocks, used_blocks, chunk_num_blocks));
+
+      used_blocks += chunk_num_blocks;
+    }
+  }
+  LOG(INFO) << "Produced " << (aops->size() - num_ops) << " operations for "
+            << used_blocks << " identical blocks moved";
+
+  return true;
+}
+
+bool DeltaReadFile(vector<AnnotatedOperation>* aops,
+                   const string& old_part,
+                   const string& new_part,
+                   const vector<Extent>& old_extents,
+                   const vector<Extent>& new_extents,
+                   const string& name,
+                   ssize_t chunk_blocks,
+                   const PayloadVersion& version,
+                   BlobFileWriter* blob_file) {
+  brillo::Blob data;
+  InstallOperation operation;
+
+  uint64_t total_blocks = BlocksInExtents(new_extents);
+  if (chunk_blocks == -1)
+    chunk_blocks = total_blocks;
+
+  for (uint64_t block_offset = 0; block_offset < total_blocks;
+      block_offset += chunk_blocks) {
+    // Split the old/new file in the same chunks. Note that this could drop
+    // some information from the old file used for the new chunk. If the old
+    // file is smaller (or even empty when there's no old file) the chunk will
+    // also be empty.
+    vector<Extent> old_extents_chunk = ExtentsSublist(
+        old_extents, block_offset, chunk_blocks);
+    vector<Extent> new_extents_chunk = ExtentsSublist(
+        new_extents, block_offset, chunk_blocks);
+    NormalizeExtents(&old_extents_chunk);
+    NormalizeExtents(&new_extents_chunk);
+
+    TEST_AND_RETURN_FALSE(ReadExtentsToDiff(old_part,
+                                            new_part,
+                                            old_extents_chunk,
+                                            new_extents_chunk,
+                                            version,
+                                            &data,
+                                            &operation));
+
+    // Check if the operation writes nothing.
+    if (operation.dst_extents_size() == 0) {
+      if (operation.type() == InstallOperation::MOVE) {
+        LOG(INFO) << "Empty MOVE operation ("
+                  << name << "), skipping";
+        continue;
+      } else {
+        LOG(ERROR) << "Empty non-MOVE operation";
+        return false;
+      }
+    }
+
+    // Now, insert into the list of operations.
+    AnnotatedOperation aop;
+    aop.name = name;
+    if (static_cast<uint64_t>(chunk_blocks) < total_blocks) {
+      aop.name = base::StringPrintf("%s:%" PRIu64,
+                                    name.c_str(), block_offset / chunk_blocks);
+    }
+    aop.op = operation;
+
+    // Write the data
+    TEST_AND_RETURN_FALSE(aop.SetOperationBlob(data, blob_file));
+    aops->emplace_back(aop);
+  }
+  return true;
+}
+
+bool GenerateBestFullOperation(const brillo::Blob& new_data,
+                               const PayloadVersion& version,
+                               brillo::Blob* out_blob,
+                               InstallOperation_Type* out_type) {
+  if (new_data.empty())
+    return false;
+
+  if (version.OperationAllowed(InstallOperation::ZERO) &&
+      std::all_of(
+          new_data.begin(), new_data.end(), [](uint8_t x) { return x == 0; })) {
+    // The read buffer is all zeros, so produce a ZERO operation. No need to
+    // check other types of operations in this case.
+    *out_blob = brillo::Blob();
+    *out_type = InstallOperation::ZERO;
+    return true;
+  }
+
+  bool out_blob_set = false;
+
+  // Try compressing |new_data| with xz first.
+  if (version.OperationAllowed(InstallOperation::REPLACE_XZ)) {
+    brillo::Blob new_data_xz;
+    if (XzCompress(new_data, &new_data_xz) && !new_data_xz.empty()) {
+      *out_type = InstallOperation::REPLACE_XZ;
+      *out_blob = std::move(new_data_xz);
+      out_blob_set = true;
+    }
+  }
+
+  // Try compressing it with bzip2.
+  if (version.OperationAllowed(InstallOperation::REPLACE_BZ)) {
+    brillo::Blob new_data_bz;
+    // TODO(deymo): Implement some heuristic to determine if it is worth trying
+    // to compress the blob with bzip2 if we already have a good REPLACE_XZ.
+    if (BzipCompress(new_data, &new_data_bz) && !new_data_bz.empty() &&
+        (!out_blob_set || out_blob->size() > new_data_bz.size())) {
+      // A REPLACE_BZ is better or nothing else was set.
+      *out_type = InstallOperation::REPLACE_BZ;
+      *out_blob = std::move(new_data_bz);
+      out_blob_set = true;
+    }
+  }
+
+  // If nothing else worked or it was badly compressed we try a REPLACE.
+  if (!out_blob_set || out_blob->size() >= new_data.size()) {
+    *out_type = InstallOperation::REPLACE;
+    // This needs to make a copy of the data in the case bzip or xz didn't
+    // compress well, which is not the common case so the performance hit is
+    // low.
+    *out_blob = new_data;
+  }
+  return true;
+}
+
+bool ReadExtentsToDiff(const string& old_part,
+                       const string& new_part,
+                       const vector<Extent>& old_extents,
+                       const vector<Extent>& new_extents,
+                       const PayloadVersion& version,
+                       brillo::Blob* out_data,
+                       InstallOperation* out_op) {
+  InstallOperation operation;
+
+  // We read blocks from old_extents and write blocks to new_extents.
+  uint64_t blocks_to_read = BlocksInExtents(old_extents);
+  uint64_t blocks_to_write = BlocksInExtents(new_extents);
+
+  // Disable bsdiff and imgdiff when the data is too big.
+  bool bsdiff_allowed =
+      version.OperationAllowed(InstallOperation::SOURCE_BSDIFF) ||
+      version.OperationAllowed(InstallOperation::BSDIFF);
+  if (bsdiff_allowed &&
+      blocks_to_read * kBlockSize > kMaxBsdiffDestinationSize) {
+    LOG(INFO) << "bsdiff blacklisted, data too big: "
+              << blocks_to_read * kBlockSize << " bytes";
+    bsdiff_allowed = false;
+  }
+
+  bool imgdiff_allowed = version.OperationAllowed(InstallOperation::IMGDIFF);
+  if (imgdiff_allowed &&
+      blocks_to_read * kBlockSize > kMaxImgdiffDestinationSize) {
+    LOG(INFO) << "imgdiff blacklisted, data too big: "
+              << blocks_to_read * kBlockSize << " bytes";
+    imgdiff_allowed = false;
+  }
+
+  // Make copies of the extents so we can modify them.
+  vector<Extent> src_extents = old_extents;
+  vector<Extent> dst_extents = new_extents;
+
+  // Read in bytes from new data.
+  brillo::Blob new_data;
+  TEST_AND_RETURN_FALSE(utils::ReadExtents(new_part,
+                                           new_extents,
+                                           &new_data,
+                                           kBlockSize * blocks_to_write,
+                                           kBlockSize));
+  TEST_AND_RETURN_FALSE(!new_data.empty());
+
+  // Data blob that will be written to delta file.
+  brillo::Blob data_blob;
+
+  // Try generating a full operation for the given new data, regardless of the
+  // old_data.
+  InstallOperation_Type op_type;
+  TEST_AND_RETURN_FALSE(
+      GenerateBestFullOperation(new_data, version, &data_blob, &op_type));
+  operation.set_type(op_type);
+
+  brillo::Blob old_data;
+  if (blocks_to_read > 0) {
+    // Read old data.
+    TEST_AND_RETURN_FALSE(
+        utils::ReadExtents(old_part, src_extents, &old_data,
+                           kBlockSize * blocks_to_read, kBlockSize));
+    if (old_data == new_data) {
+      // No change in data.
+      operation.set_type(version.OperationAllowed(InstallOperation::SOURCE_COPY)
+                             ? InstallOperation::SOURCE_COPY
+                             : InstallOperation::MOVE);
+      data_blob = brillo::Blob();
+    } else if (bsdiff_allowed || imgdiff_allowed) {
+      // If the source file is considered bsdiff safe (no bsdiff bugs
+      // triggered), see if BSDIFF encoding is smaller.
+      base::FilePath old_chunk;
+      TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&old_chunk));
+      ScopedPathUnlinker old_unlinker(old_chunk.value());
+      TEST_AND_RETURN_FALSE(utils::WriteFile(
+          old_chunk.value().c_str(), old_data.data(), old_data.size()));
+      base::FilePath new_chunk;
+      TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&new_chunk));
+      ScopedPathUnlinker new_unlinker(new_chunk.value());
+      TEST_AND_RETURN_FALSE(utils::WriteFile(
+          new_chunk.value().c_str(), new_data.data(), new_data.size()));
+
+      if (bsdiff_allowed) {
+        brillo::Blob bsdiff_delta;
+        TEST_AND_RETURN_FALSE(DiffFiles(
+            kBsdiffPath, old_chunk.value(), new_chunk.value(), &bsdiff_delta));
+        CHECK_GT(bsdiff_delta.size(), static_cast<brillo::Blob::size_type>(0));
+        if (bsdiff_delta.size() < data_blob.size()) {
+          operation.set_type(
+              version.OperationAllowed(InstallOperation::SOURCE_BSDIFF)
+                  ? InstallOperation::SOURCE_BSDIFF
+                  : InstallOperation::BSDIFF);
+          data_blob = std::move(bsdiff_delta);
+        }
+      }
+      if (imgdiff_allowed && ContainsGZip(old_data) && ContainsGZip(new_data)) {
+        brillo::Blob imgdiff_delta;
+        // Imgdiff might fail in some cases, only use the result if it succeed,
+        // otherwise print the extents to analyze.
+        if (DiffFiles(kImgdiffPath,
+                      old_chunk.value(),
+                      new_chunk.value(),
+                      &imgdiff_delta) &&
+            imgdiff_delta.size() > 0) {
+          if (imgdiff_delta.size() < data_blob.size()) {
+            operation.set_type(InstallOperation::IMGDIFF);
+            data_blob = std::move(imgdiff_delta);
+          }
+        } else {
+          LOG(ERROR) << "Imgdiff failed with source extents: "
+                     << ExtentsToString(src_extents)
+                     << ", destination extents: "
+                     << ExtentsToString(dst_extents);
+        }
+      }
+    }
+  }
+
+  size_t removed_bytes = 0;
+  // Remove identical src/dst block ranges in MOVE operations.
+  if (operation.type() == InstallOperation::MOVE) {
+    removed_bytes = RemoveIdenticalBlockRanges(
+        &src_extents, &dst_extents, new_data.size());
+  }
+  // Set legacy src_length and dst_length fields.
+  operation.set_src_length(old_data.size() - removed_bytes);
+  operation.set_dst_length(new_data.size() - removed_bytes);
+
+  // Embed extents in the operation.
+  StoreExtents(src_extents, operation.mutable_src_extents());
+  StoreExtents(dst_extents, operation.mutable_dst_extents());
+
+  // Replace operations should not have source extents.
+  if (IsAReplaceOperation(operation.type())) {
+    operation.clear_src_extents();
+    operation.clear_src_length();
+  }
+
+  *out_data = std::move(data_blob);
+  *out_op = operation;
+
+  return true;
+}
+
+// Runs the bsdiff or imgdiff tool in |diff_path| on two files and returns the
+// resulting delta in |out|. Returns true on success.
+bool DiffFiles(const string& diff_path,
+               const string& old_file,
+               const string& new_file,
+               brillo::Blob* out) {
+  const string kPatchFile = "delta.patchXXXXXX";
+  string patch_file_path;
+
+  TEST_AND_RETURN_FALSE(
+      utils::MakeTempFile(kPatchFile, &patch_file_path, nullptr));
+
+  vector<string> cmd;
+  cmd.push_back(diff_path);
+  cmd.push_back(old_file);
+  cmd.push_back(new_file);
+  cmd.push_back(patch_file_path);
+
+  int rc = 1;
+  string stdout;
+  TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &rc, &stdout));
+  if (rc != 0) {
+    LOG(ERROR) << diff_path << " returned " << rc << std::endl << stdout;
+    return false;
+  }
+  TEST_AND_RETURN_FALSE(utils::ReadFile(patch_file_path, out));
+  unlink(patch_file_path.c_str());
+  return true;
+}
+
+bool IsAReplaceOperation(InstallOperation_Type op_type) {
+  return (op_type == InstallOperation::REPLACE ||
+          op_type == InstallOperation::REPLACE_BZ ||
+          op_type == InstallOperation::REPLACE_XZ);
+}
+
+// Returns true if |op| is a no-op operation that doesn't do any useful work
+// (e.g., a move operation that copies blocks onto themselves).
+bool IsNoopOperation(const InstallOperation& op) {
+  return (op.type() == InstallOperation::MOVE &&
+          ExpandExtents(op.src_extents()) == ExpandExtents(op.dst_extents()));
+}
+
+void FilterNoopOperations(vector<AnnotatedOperation>* ops) {
+  ops->erase(
+      std::remove_if(
+          ops->begin(), ops->end(),
+          [](const AnnotatedOperation& aop){return IsNoopOperation(aop.op);}),
+      ops->end());
+}
+
+bool InitializePartitionInfo(const PartitionConfig& part, PartitionInfo* info) {
+  info->set_size(part.size);
+  HashCalculator hasher;
+  TEST_AND_RETURN_FALSE(hasher.UpdateFile(part.path, part.size) ==
+                        static_cast<off_t>(part.size));
+  TEST_AND_RETURN_FALSE(hasher.Finalize());
+  const brillo::Blob& hash = hasher.raw_hash();
+  info->set_hash(hash.data(), hash.size());
+  LOG(INFO) << part.path << ": size=" << part.size << " hash=" << hasher.hash();
+  return true;
+}
+
+bool CompareAopsByDestination(AnnotatedOperation first_aop,
+                              AnnotatedOperation second_aop) {
+  // We want empty operations to be at the end of the payload.
+  if (!first_aop.op.dst_extents().size() || !second_aop.op.dst_extents().size())
+    return ((!first_aop.op.dst_extents().size()) <
+            (!second_aop.op.dst_extents().size()));
+  uint32_t first_dst_start = first_aop.op.dst_extents(0).start_block();
+  uint32_t second_dst_start = second_aop.op.dst_extents(0).start_block();
+  return first_dst_start < second_dst_start;
+}
+
+bool IsExtFilesystem(const string& device) {
+  brillo::Blob header;
+  // See include/linux/ext2_fs.h for more details on the structure. We obtain
+  // ext2 constants from ext2fs/ext2fs.h header but we don't link with the
+  // library.
+  if (!utils::ReadFileChunk(
+          device, 0, SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE, &header) ||
+      header.size() < SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE)
+    return false;
+
+  const uint8_t* superblock = header.data() + SUPERBLOCK_OFFSET;
+
+  // ext3_fs.h: ext3_super_block.s_blocks_count
+  uint32_t block_count =
+      *reinterpret_cast<const uint32_t*>(superblock + 1 * sizeof(int32_t));
+
+  // ext3_fs.h: ext3_super_block.s_log_block_size
+  uint32_t log_block_size =
+      *reinterpret_cast<const uint32_t*>(superblock + 6 * sizeof(int32_t));
+
+  // ext3_fs.h: ext3_super_block.s_magic
+  uint16_t magic =
+      *reinterpret_cast<const uint16_t*>(superblock + 14 * sizeof(int32_t));
+
+  block_count = le32toh(block_count);
+  log_block_size = le32toh(log_block_size) + EXT2_MIN_BLOCK_LOG_SIZE;
+  magic = le16toh(magic);
+
+  if (magic != EXT2_SUPER_MAGIC)
+    return false;
+
+  // Sanity check the parameters.
+  TEST_AND_RETURN_FALSE(log_block_size >= EXT2_MIN_BLOCK_LOG_SIZE &&
+                        log_block_size <= EXT2_MAX_BLOCK_LOG_SIZE);
+  TEST_AND_RETURN_FALSE(block_count > 0);
+  return true;
+}
+
+bool IsSquashfs4Filesystem(const string& device) {
+  brillo::Blob header;
+  // See fs/squashfs/squashfs_fs.h for format details. We only support
+  // Squashfs 4.x little endian.
+
+  // The first 96 is enough to read the squashfs superblock.
+  const ssize_t kSquashfsSuperBlockSize = 96;
+  if (!utils::ReadFileChunk(device, 0, kSquashfsSuperBlockSize, &header) ||
+      header.size() < kSquashfsSuperBlockSize)
+    return false;
+
+  // Check magic, squashfs_fs.h: SQUASHFS_MAGIC
+  if (memcmp(header.data(), "hsqs", 4) != 0)
+    return false;  // Only little endian is supported.
+
+  // squashfs_fs.h: struct squashfs_super_block.s_major
+  uint16_t s_major = *reinterpret_cast<const uint16_t*>(
+      header.data() + 5 * sizeof(uint32_t) + 4 * sizeof(uint16_t));
+
+  if (s_major != 4) {
+    LOG(ERROR) << "Found unsupported squashfs major version " << s_major;
+    return false;
+  }
+  return true;
+}
+
+}  // namespace diff_utils
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/delta_diff_utils.h b/update_engine/payload_generator/delta_diff_utils.h
new file mode 100644
index 0000000..4cc85fc
--- /dev/null
+++ b/update_engine/payload_generator/delta_diff_utils.h
@@ -0,0 +1,155 @@
+//
+// 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_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+namespace diff_utils {
+
+// Create operations in |aops| to produce all the blocks in the |new_part|
+// partition using the filesystem opened in that PartitionConfig.
+// It uses the files reported by the filesystem in |old_part| and the data
+// blocks in that partition (if available) to determine the best way to compress
+// the new files (REPLACE, REPLACE_BZ, COPY, BSDIFF) and writes any necessary
+// data to |blob_file|. |hard_chunk_blocks| and |soft_chunk_blocks| are the hard
+// and soft chunk limits in number of blocks respectively. The soft chunk limit
+// is used to split MOVE and SOURCE_COPY operations and REPLACE_BZ of zeroed
+// blocks, while the hard limit is used to split a file when generating other
+// operations. A value of -1 in |hard_chunk_blocks| means whole files.
+bool DeltaReadPartition(std::vector<AnnotatedOperation>* aops,
+                        const PartitionConfig& old_part,
+                        const PartitionConfig& new_part,
+                        ssize_t hard_chunk_blocks,
+                        size_t soft_chunk_blocks,
+                        const PayloadVersion& version,
+                        BlobFileWriter* blob_file);
+
+// Create operations in |aops| for identical blocks that moved around in the old
+// and new partition and also handle zeroed blocks. The old and new partition
+// are stored in the |old_part| and |new_part| files and have |old_num_blocks|
+// and |new_num_blocks| respectively. The maximum operation size is
+// |chunk_blocks| blocks, or unlimited if |chunk_blocks| is -1. The blobs of the
+// produced operations are stored in the |blob_file|.
+// The collections |old_visited_blocks| and |new_visited_blocks| state what
+// blocks already have operations reading or writing them and only operations
+// for unvisited blocks are produced by this function updating both collections
+// with the used blocks.
+bool DeltaMovedAndZeroBlocks(std::vector<AnnotatedOperation>* aops,
+                             const std::string& old_part,
+                             const std::string& new_part,
+                             size_t old_num_blocks,
+                             size_t new_num_blocks,
+                             ssize_t chunk_blocks,
+                             const PayloadVersion& version,
+                             BlobFileWriter* blob_file,
+                             ExtentRanges* old_visited_blocks,
+                             ExtentRanges* new_visited_blocks);
+
+// For a given file |name| append operations to |aops| to produce it in the
+// |new_part|. The file will be split in chunks of |chunk_blocks| blocks each
+// or treated as a single chunk if |chunk_blocks| is -1. The file data is
+// stored in |new_part| in the blocks described by |new_extents| and, if it
+// exists, the old version exists in |old_part| in the blocks described by
+// |old_extents|. The operations added to |aops| reference the data blob
+// in the |blob_file|. Returns true on success.
+bool DeltaReadFile(std::vector<AnnotatedOperation>* aops,
+                   const std::string& old_part,
+                   const std::string& new_part,
+                   const std::vector<Extent>& old_extents,
+                   const std::vector<Extent>& new_extents,
+                   const std::string& name,
+                   ssize_t chunk_blocks,
+                   const PayloadVersion& version,
+                   BlobFileWriter* blob_file);
+
+// Reads the blocks |old_extents| from |old_part| (if it exists) and the
+// |new_extents| from |new_part| and determines the smallest way to encode
+// this |new_extents| for the diff. It stores necessary data in |out_data| and
+// fills in |out_op|. If there's no change in old and new files, it creates a
+// MOVE or SOURCE_COPY operation. If there is a change, the smallest of the
+// operations allowed in the given |version| (REPLACE, REPLACE_BZ, BSDIFF,
+// SOURCE_BSDIFF or IMGDIFF) wins.
+// |new_extents| must not be empty. Returns true on success.
+bool ReadExtentsToDiff(const std::string& old_part,
+                       const std::string& new_part,
+                       const std::vector<Extent>& old_extents,
+                       const std::vector<Extent>& new_extents,
+                       const PayloadVersion& version,
+                       brillo::Blob* out_data,
+                       InstallOperation* out_op);
+
+// Runs the bsdiff or imgdiff tool in |diff_path| on two files and returns the
+// resulting delta in |out|. Returns true on success.
+bool DiffFiles(const std::string& diff_path,
+               const std::string& old_file,
+               const std::string& new_file,
+               brillo::Blob* out);
+
+// Generates the best allowed full operation to produce |new_data|. The allowed
+// operations are based on |payload_version|. The operation blob will be stored
+// in |out_blob| and the resulting operation type in |out_type|. Returns whether
+// a valid full operation was generated.
+bool GenerateBestFullOperation(const brillo::Blob& new_data,
+                               const PayloadVersion& version,
+                               brillo::Blob* out_blob,
+                               InstallOperation_Type* out_type);
+
+// Returns whether op_type is one of the REPLACE full operations.
+bool IsAReplaceOperation(InstallOperation_Type op_type);
+
+// Returns true if |op| is a no-op operation that doesn't do any useful work
+// (e.g., a move operation that copies blocks onto themselves).
+bool IsNoopOperation(const InstallOperation& op);
+
+// Filters all the operations that are no-op, maintaining the relative order
+// of the rest of the operations.
+void FilterNoopOperations(std::vector<AnnotatedOperation>* ops);
+
+bool InitializePartitionInfo(const PartitionConfig& partition,
+                             PartitionInfo* info);
+
+// Compare two AnnotatedOperations by the start block of the first Extent in
+// their destination extents.
+bool CompareAopsByDestination(AnnotatedOperation first_aop,
+                              AnnotatedOperation second_aop);
+
+// Returns whether the filesystem is an ext[234] filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsExtFilesystem(const std::string& device);
+
+// Returns whether the filesystem is a squashfs4 filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsSquashfs4Filesystem(const std::string& device);
+
+}  // namespace diff_utils
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
diff --git a/update_engine/payload_generator/delta_diff_utils_unittest.cc b/update_engine/payload_generator/delta_diff_utils_unittest.cc
new file mode 100644
index 0000000..7044b95
--- /dev/null
+++ b/update_engine/payload_generator/delta_diff_utils_unittest.cc
@@ -0,0 +1,773 @@
+//
+// 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/payload_generator/delta_diff_utils.h"
+
+#include <algorithm>
+#include <random>
+#include <string>
+#include <vector>
+
+#include <base/files/scoped_file.h>
+#include <base/format_macros.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/payload_generator/fake_filesystem.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Squashfs example filesystem, generated with:
+//   echo hola>hola
+//   mksquashfs hola hola.sqfs -noappend -nopad
+//   hexdump hola.sqfs -e '16/1 "%02x, " "\n"'
+const uint8_t kSquashfsFile[] = {
+  0x68, 0x73, 0x71, 0x73, 0x02, 0x00, 0x00, 0x00,  // magic, inodes
+  0x3e, 0x49, 0x61, 0x54, 0x00, 0x00, 0x02, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
+  0xc0, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00,  // flags, noids, major, minor
+  0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // root_inode
+  0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // bytes_used
+  0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x68, 0x6f, 0x6c, 0x61, 0x0a, 0x2c, 0x00, 0x78,
+  0xda, 0x63, 0x62, 0x58, 0xc2, 0xc8, 0xc0, 0xc0,
+  0xc8, 0xd0, 0x6b, 0x91, 0x18, 0x02, 0x64, 0xa0,
+  0x00, 0x56, 0x06, 0x90, 0xcc, 0x7f, 0xb0, 0xbc,
+  0x9d, 0x67, 0x62, 0x08, 0x13, 0x54, 0x1c, 0x44,
+  0x4b, 0x03, 0x31, 0x33, 0x10, 0x03, 0x00, 0xb5,
+  0x87, 0x04, 0x89, 0x16, 0x00, 0x78, 0xda, 0x63,
+  0x60, 0x80, 0x00, 0x46, 0x28, 0xcd, 0xc4, 0xc0,
+  0xcc, 0x90, 0x91, 0x9f, 0x93, 0x08, 0x00, 0x04,
+  0x70, 0x01, 0xab, 0x10, 0x80, 0x60, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x78,
+  0xda, 0x63, 0x60, 0x80, 0x00, 0x05, 0x28, 0x0d,
+  0x00, 0x01, 0x10, 0x00, 0x21, 0xc5, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x99,
+  0xcd, 0x02, 0x00, 0x88, 0x13, 0x00, 0x00, 0xdd,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+// Writes the |data| in the blocks specified by |extents| on the partition
+// |part_path|. The |data| size could be smaller than the size of the blocks
+// passed.
+bool WriteExtents(const string& part_path,
+                  const vector<Extent>& extents,
+                  off_t block_size,
+                  const brillo::Blob& data) {
+  uint64_t offset = 0;
+  base::ScopedFILE fp(fopen(part_path.c_str(), "r+"));
+  TEST_AND_RETURN_FALSE(fp.get());
+
+  for (const Extent& extent : extents) {
+    if (offset >= data.size())
+      break;
+    TEST_AND_RETURN_FALSE(
+        fseek(fp.get(), extent.start_block() * block_size, SEEK_SET) == 0);
+    uint64_t to_write =
+        std::min(static_cast<uint64_t>(extent.num_blocks()) * block_size,
+                 static_cast<uint64_t>(data.size()) - offset);
+    TEST_AND_RETURN_FALSE(
+        fwrite(data.data() + offset, 1, to_write, fp.get()) == to_write);
+    offset += extent.num_blocks() * block_size;
+  }
+  return true;
+}
+
+// Create a fake filesystem of the given |size| and initialize the partition
+// holding it in the PartitionConfig |part|.
+void CreatePartition(PartitionConfig* part, const string& pattern,
+                     uint64_t block_size, off_t size) {
+  int fd = -1;
+  ASSERT_TRUE(utils::MakeTempFile(pattern.c_str(), &part->path, &fd));
+  ASSERT_EQ(0, ftruncate(fd, size));
+  ASSERT_EQ(0, close(fd));
+  part->fs_interface.reset(new FakeFilesystem(block_size, size / block_size));
+  part->size = size;
+}
+
+// Writes to the |partition| path blocks such they are all different and they
+// include the tag passed in |tag|, making them also different to any other
+// block on a partition initialized with this function with a different tag.
+// The |block_size| should be a divisor of the partition size.
+// Returns whether the function succeeded writing the partition.
+bool InitializePartitionWithUniqueBlocks(const PartitionConfig& part,
+                                         uint64_t block_size,
+                                         uint64_t tag) {
+  TEST_AND_RETURN_FALSE(part.size % block_size == 0);
+  size_t num_blocks = part.size / block_size;
+  brillo::Blob file_data(part.size);
+  for (size_t i = 0; i < num_blocks; ++i) {
+    string prefix = base::StringPrintf(
+        "block tag 0x%.16" PRIx64 ", block number %16" PRIuS " ", tag, i);
+    brillo::Blob block_data(prefix.begin(), prefix.end());
+    TEST_AND_RETURN_FALSE(prefix.size() <= block_size);
+    block_data.resize(block_size, 'X');
+    std::copy(block_data.begin(), block_data.end(),
+              file_data.begin() + i * block_size);
+  }
+  return test_utils::WriteFileVector(part.path, file_data);
+}
+
+}  // namespace
+
+class DeltaDiffUtilsTest : public ::testing::Test {
+ protected:
+  const uint64_t kDefaultBlockCount = 128;
+
+  void SetUp() override {
+    CreatePartition(&old_part_, "DeltaDiffUtilsTest-old_part-XXXXXX",
+                    block_size_, block_size_ * kDefaultBlockCount);
+    CreatePartition(&new_part_, "DeltaDiffUtilsTest-old_part-XXXXXX",
+                    block_size_, block_size_ * kDefaultBlockCount);
+    ASSERT_TRUE(utils::MakeTempFile("DeltaDiffUtilsTest-blob-XXXXXX",
+                                    &blob_path_,
+                                    &blob_fd_));
+  }
+
+  void TearDown() override {
+    unlink(old_part_.path.c_str());
+    unlink(new_part_.path.c_str());
+    if (blob_fd_ != -1)
+      close(blob_fd_);
+    unlink(blob_path_.c_str());
+  }
+
+  // Helper function to call DeltaMovedAndZeroBlocks() using this class' data
+  // members. This simply avoids repeating all the arguments that never change.
+  bool RunDeltaMovedAndZeroBlocks(ssize_t chunk_blocks,
+                                  uint32_t minor_version) {
+    BlobFileWriter blob_file(blob_fd_, &blob_size_);
+    PayloadVersion version(kChromeOSMajorPayloadVersion, minor_version);
+    version.imgdiff_allowed = true;  // Assume no fingerprint mismatch.
+    return diff_utils::DeltaMovedAndZeroBlocks(&aops_,
+                                               old_part_.path,
+                                               new_part_.path,
+                                               old_part_.size / block_size_,
+                                               new_part_.size / block_size_,
+                                               chunk_blocks,
+                                               version,
+                                               &blob_file,
+                                               &old_visited_blocks_,
+                                               &new_visited_blocks_);
+  }
+
+  // Old and new temporary partitions used in the tests. These are initialized
+  // with
+  PartitionConfig old_part_{"part"};
+  PartitionConfig new_part_{"part"};
+
+  // The file holding the output blob from the various diff utils functions.
+  string blob_path_;
+  int blob_fd_{-1};
+  off_t blob_size_{0};
+
+  size_t block_size_{kBlockSize};
+
+  // Default input/output arguments used when calling DeltaMovedAndZeroBlocks().
+  vector<AnnotatedOperation> aops_;
+  ExtentRanges old_visited_blocks_;
+  ExtentRanges new_visited_blocks_;
+};
+
+TEST_F(DeltaDiffUtilsTest, MoveSmallTest) {
+  brillo::Blob data_blob(block_size_);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(11, 1) };
+  vector<Extent> new_extents = { ExtentForRange(1, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob));
+  EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob));
+
+  brillo::Blob data;
+  InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_.path,
+      new_part_.path,
+      old_extents,
+      new_extents,
+      PayloadVersion(kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion),
+      &data,
+      &op));
+  EXPECT_TRUE(data.empty());
+
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(InstallOperation::MOVE, op.type());
+  EXPECT_FALSE(op.has_data_offset());
+  EXPECT_FALSE(op.has_data_length());
+  EXPECT_EQ(1, op.src_extents_size());
+  EXPECT_EQ(kBlockSize, op.src_length());
+  EXPECT_EQ(1, op.dst_extents_size());
+  EXPECT_EQ(kBlockSize, op.dst_length());
+  EXPECT_EQ(BlocksInExtents(op.src_extents()),
+            BlocksInExtents(op.dst_extents()));
+  EXPECT_EQ(1U, BlocksInExtents(op.dst_extents()));
+}
+
+TEST_F(DeltaDiffUtilsTest, MoveWithSameBlock) {
+  // Setup the old/new files so that it has immobile chunks; we make sure to
+  // utilize all sub-cases of such chunks: blocks 21--22 induce a split (src)
+  // and complete removal (dst), whereas blocks 24--25 induce trimming of the
+  // tail (src) and head (dst) of extents. The final block (29) is used for
+  // ensuring we properly account for the number of bytes removed in cases where
+  // the last block is partly filled. The detailed configuration:
+  //
+  // Old:  [ 20     21 22     23     24 25 ] [ 28     29 ]
+  // New:  [ 18 ] [ 21 22 ] [ 20 ] [ 24 25     26 ] [ 29 ]
+  // Same:          ^^ ^^            ^^ ^^            ^^
+  vector<Extent> old_extents = {
+      ExtentForRange(20, 6),
+      ExtentForRange(28, 2) };
+  vector<Extent> new_extents = {
+      ExtentForRange(18, 1),
+      ExtentForRange(21, 2),
+      ExtentForRange(20, 1),
+      ExtentForRange(24, 3),
+      ExtentForRange(29, 1) };
+
+  uint64_t num_blocks = BlocksInExtents(old_extents);
+  EXPECT_EQ(num_blocks, BlocksInExtents(new_extents));
+
+  // The size of the data should match the total number of blocks. Each block
+  // has a different content.
+  brillo::Blob file_data;
+  for (uint64_t i = 0; i < num_blocks; ++i) {
+    file_data.resize(file_data.size() + kBlockSize, 'a' + i);
+  }
+
+  EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, file_data));
+  EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, file_data));
+
+  brillo::Blob data;
+  InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_.path,
+      new_part_.path,
+      old_extents,
+      new_extents,
+      PayloadVersion(kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion),
+      &data,
+      &op));
+
+  EXPECT_TRUE(data.empty());
+
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(InstallOperation::MOVE, op.type());
+  EXPECT_FALSE(op.has_data_offset());
+  EXPECT_FALSE(op.has_data_length());
+
+  // The expected old and new extents that actually moved. See comment above.
+  old_extents = {
+      ExtentForRange(20, 1),
+      ExtentForRange(23, 1),
+      ExtentForRange(28, 1) };
+  new_extents = {
+      ExtentForRange(18, 1),
+      ExtentForRange(20, 1),
+      ExtentForRange(26, 1) };
+  num_blocks = BlocksInExtents(old_extents);
+
+  EXPECT_EQ(num_blocks * kBlockSize, op.src_length());
+  EXPECT_EQ(num_blocks * kBlockSize, op.dst_length());
+
+  EXPECT_EQ(old_extents.size(), static_cast<size_t>(op.src_extents_size()));
+  for (int i = 0; i < op.src_extents_size(); i++) {
+    EXPECT_EQ(old_extents[i].start_block(), op.src_extents(i).start_block())
+        << "i == " << i;
+    EXPECT_EQ(old_extents[i].num_blocks(), op.src_extents(i).num_blocks())
+        << "i == " << i;
+  }
+
+  EXPECT_EQ(new_extents.size(), static_cast<size_t>(op.dst_extents_size()));
+  for (int i = 0; i < op.dst_extents_size(); i++) {
+    EXPECT_EQ(new_extents[i].start_block(), op.dst_extents(i).start_block())
+        << "i == " << i;
+    EXPECT_EQ(new_extents[i].num_blocks(), op.dst_extents(i).num_blocks())
+        << "i == " << i;
+  }
+}
+
+TEST_F(DeltaDiffUtilsTest, BsdiffSmallTest) {
+  // Test a BSDIFF operation from block 1 to block 2.
+  brillo::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(1, 1) };
+  vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob));
+  // Modify one byte in the new file.
+  data_blob[0]++;
+  EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob));
+
+  brillo::Blob data;
+  InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_.path,
+      new_part_.path,
+      old_extents,
+      new_extents,
+      PayloadVersion(kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion),
+      &data,
+      &op));
+
+  EXPECT_FALSE(data.empty());
+
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(InstallOperation::BSDIFF, op.type());
+  EXPECT_FALSE(op.has_data_offset());
+  EXPECT_FALSE(op.has_data_length());
+  EXPECT_EQ(1, op.src_extents_size());
+  EXPECT_EQ(kBlockSize, op.src_length());
+  EXPECT_EQ(1, op.dst_extents_size());
+  EXPECT_EQ(kBlockSize, op.dst_length());
+  EXPECT_EQ(BlocksInExtents(op.src_extents()),
+            BlocksInExtents(op.dst_extents()));
+  EXPECT_EQ(1U, BlocksInExtents(op.dst_extents()));
+}
+
+TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) {
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(1, 1) };
+  vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+  // Make a blob that's just 1's that will compress well.
+  brillo::Blob ones(kBlockSize, 1);
+
+  // Make a blob with random data that won't compress well.
+  brillo::Blob random_data;
+  std::mt19937 gen(12345);
+  std::uniform_int_distribution<uint8_t> dis(0, 255);
+  for (uint32_t i = 0; i < kBlockSize; i++) {
+    random_data.push_back(dis(gen));
+  }
+
+  for (int i = 0; i < 2; i++) {
+    brillo::Blob data_to_test = i == 0 ? random_data : ones;
+    // The old_extents will be initialized with 0.
+    EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize,
+                             data_to_test));
+
+    brillo::Blob data;
+    InstallOperation op;
+    EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+        old_part_.path,
+        new_part_.path,
+        old_extents,
+        new_extents,
+        PayloadVersion(kChromeOSMajorPayloadVersion,
+                       kInPlaceMinorPayloadVersion),
+        &data,
+        &op));
+    EXPECT_FALSE(data.empty());
+
+    EXPECT_TRUE(op.has_type());
+    const InstallOperation_Type expected_type =
+        (i == 0 ? InstallOperation::REPLACE : InstallOperation::REPLACE_BZ);
+    EXPECT_EQ(expected_type, op.type());
+    EXPECT_FALSE(op.has_data_offset());
+    EXPECT_FALSE(op.has_data_length());
+    EXPECT_EQ(0, op.src_extents_size());
+    EXPECT_FALSE(op.has_src_length());
+    EXPECT_EQ(1, op.dst_extents_size());
+    EXPECT_EQ(data_to_test.size(), op.dst_length());
+    EXPECT_EQ(1U, BlocksInExtents(op.dst_extents()));
+  }
+}
+
+TEST_F(DeltaDiffUtilsTest, SourceCopyTest) {
+  // Makes sure SOURCE_COPY operations are emitted whenever src_ops_allowed
+  // is true. It is the same setup as MoveSmallTest, which checks that
+  // the operation is well-formed.
+  brillo::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(11, 1) };
+  vector<Extent> new_extents = { ExtentForRange(1, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob));
+  EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob));
+
+  brillo::Blob data;
+  InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_.path,
+      new_part_.path,
+      old_extents,
+      new_extents,
+      PayloadVersion(kChromeOSMajorPayloadVersion, kSourceMinorPayloadVersion),
+      &data,
+      &op));
+  EXPECT_TRUE(data.empty());
+
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(InstallOperation::SOURCE_COPY, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, SourceBsdiffTest) {
+  // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed
+  // is true. It is the same setup as BsdiffSmallTest, which checks
+  // that the operation is well-formed.
+  brillo::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(1, 1) };
+  vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob));
+  // Modify one byte in the new file.
+  data_blob[0]++;
+  EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob));
+
+  brillo::Blob data;
+  InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_.path,
+      new_part_.path,
+      old_extents,
+      new_extents,
+      PayloadVersion(kChromeOSMajorPayloadVersion, kSourceMinorPayloadVersion),
+      &data,
+      &op));
+
+  EXPECT_FALSE(data.empty());
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(InstallOperation::SOURCE_BSDIFF, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, IsNoopOperationTest) {
+  InstallOperation op;
+  op.set_type(InstallOperation::REPLACE_BZ);
+  EXPECT_FALSE(diff_utils::IsNoopOperation(op));
+  op.set_type(InstallOperation::MOVE);
+  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+  *(op.add_src_extents()) = ExtentForRange(3, 2);
+  *(op.add_dst_extents()) = ExtentForRange(3, 2);
+  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+  *(op.add_src_extents()) = ExtentForRange(7, 5);
+  *(op.add_dst_extents()) = ExtentForRange(7, 5);
+  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+  *(op.add_src_extents()) = ExtentForRange(20, 2);
+  *(op.add_dst_extents()) = ExtentForRange(20, 1);
+  *(op.add_dst_extents()) = ExtentForRange(21, 1);
+  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+  *(op.add_src_extents()) = ExtentForRange(24, 1);
+  *(op.add_dst_extents()) = ExtentForRange(25, 1);
+  EXPECT_FALSE(diff_utils::IsNoopOperation(op));
+}
+
+TEST_F(DeltaDiffUtilsTest, FilterNoopOperations) {
+  AnnotatedOperation aop1;
+  aop1.op.set_type(InstallOperation::REPLACE_BZ);
+  *(aop1.op.add_dst_extents()) = ExtentForRange(3, 2);
+  aop1.name = "aop1";
+
+  AnnotatedOperation aop2 = aop1;
+  aop2.name = "aop2";
+
+  AnnotatedOperation noop;
+  noop.op.set_type(InstallOperation::MOVE);
+  *(noop.op.add_src_extents()) = ExtentForRange(3, 2);
+  *(noop.op.add_dst_extents()) = ExtentForRange(3, 2);
+  noop.name = "noop";
+
+  vector<AnnotatedOperation> ops = {noop, aop1, noop, noop, aop2, noop};
+  diff_utils::FilterNoopOperations(&ops);
+  EXPECT_EQ(2u, ops.size());
+  EXPECT_EQ("aop1", ops[0].name);
+  EXPECT_EQ("aop2", ops[1].name);
+}
+
+// Test the simple case where all the blocks are different and no new blocks are
+// zeroed.
+TEST_F(DeltaDiffUtilsTest, NoZeroedOrUniqueBlocksDetected) {
+  InitializePartitionWithUniqueBlocks(old_part_, block_size_, 5);
+  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42);
+
+  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
+                                         kInPlaceMinorPayloadVersion));
+
+  EXPECT_EQ(0U, old_visited_blocks_.blocks());
+  EXPECT_EQ(0U, new_visited_blocks_.blocks());
+  EXPECT_EQ(0, blob_size_);
+  EXPECT_TRUE(aops_.empty());
+}
+
+// Test that when the partitions have identical blocks in the same positions no
+// MOVE operation is performed and all the blocks are handled.
+TEST_F(DeltaDiffUtilsTest, IdenticalPartitionsDontMove) {
+  InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42);
+  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42);
+
+  // Mark some of the blocks as already visited.
+  vector<Extent> already_visited = {ExtentForRange(5, 10),
+                                    ExtentForRange(25, 10)};
+  old_visited_blocks_.AddExtents(already_visited);
+  new_visited_blocks_.AddExtents(already_visited);
+
+  // Most of the blocks rest in the same place, but there's no need for MOVE
+  // operations on those blocks.
+  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
+                                         kInPlaceMinorPayloadVersion));
+
+  EXPECT_EQ(kDefaultBlockCount, old_visited_blocks_.blocks());
+  EXPECT_EQ(kDefaultBlockCount, new_visited_blocks_.blocks());
+  EXPECT_EQ(0, blob_size_);
+  EXPECT_TRUE(aops_.empty());
+}
+
+// Test that when the partitions have identical blocks in the same positions
+// MOVE operation is performed and all the blocks are handled.
+TEST_F(DeltaDiffUtilsTest, IdenticalBlocksAreCopiedFromSource) {
+  // We use a smaller partition for this test.
+  old_part_.size = kBlockSize * 50;
+  new_part_.size = kBlockSize * 50;
+
+  InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42);
+  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42);
+
+  // Mark some of the blocks as already visited.
+  vector<Extent> already_visited = {ExtentForRange(5, 5),
+                                    ExtentForRange(25, 7)};
+  old_visited_blocks_.AddExtents(already_visited);
+  new_visited_blocks_.AddExtents(already_visited);
+
+  // Override some of the old blocks with different data.
+  vector<Extent> different_blocks = {ExtentForRange(40, 5)};
+  EXPECT_TRUE(WriteExtents(old_part_.path, different_blocks, kBlockSize,
+                           brillo::Blob(5 * kBlockSize, 'a')));
+
+  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(10,  // chunk_blocks
+                                         kSourceMinorPayloadVersion));
+
+  ExtentRanges expected_ranges;
+  expected_ranges.AddExtent(ExtentForRange(0, 50));
+  expected_ranges.SubtractExtents(different_blocks);
+
+  EXPECT_EQ(expected_ranges.extent_set(), old_visited_blocks_.extent_set());
+  EXPECT_EQ(expected_ranges.extent_set(), new_visited_blocks_.extent_set());
+  EXPECT_EQ(0, blob_size_);
+
+  // We expect all the blocks that we didn't override with |different_blocks|
+  // and that we didn't mark as visited in |already_visited| to match and have a
+  // SOURCE_COPY operation chunked at 10 blocks.
+  vector<Extent> expected_op_extents = {
+      ExtentForRange(0, 5),
+      ExtentForRange(10, 10),
+      ExtentForRange(20, 5),
+      ExtentForRange(32, 8),
+      ExtentForRange(45, 5),
+  };
+
+  EXPECT_EQ(expected_op_extents.size(), aops_.size());
+  for (size_t i = 0; i < aops_.size() && i < expected_op_extents.size(); ++i) {
+    SCOPED_TRACE(base::StringPrintf("Failed on operation number %" PRIuS, i));
+    const AnnotatedOperation& aop = aops_[i];
+    EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
+    EXPECT_EQ(1, aop.op.src_extents_size());
+    EXPECT_EQ(expected_op_extents[i], aop.op.src_extents(0));
+    EXPECT_EQ(1, aop.op.dst_extents_size());
+    EXPECT_EQ(expected_op_extents[i], aop.op.dst_extents(0));
+  }
+}
+
+TEST_F(DeltaDiffUtilsTest, IdenticalBlocksAreCopiedInOder) {
+  // We use a smaller partition for this test.
+  old_part_.size = block_size_ * 50;
+  new_part_.size = block_size_ * 50;
+
+  // Create two identical partitions with 5 copies of the same unique "file".
+  brillo::Blob file_data(block_size_ * 10, 'a');
+  for (size_t offset = 0; offset < file_data.size(); offset += block_size_)
+    file_data[offset] = 'a' + offset / block_size_;
+
+  brillo::Blob partition_data(old_part_.size);
+  for (size_t offset = 0; offset < partition_data.size();
+       offset += file_data.size()) {
+    std::copy(file_data.begin(), file_data.end(),
+              partition_data.begin() + offset);
+  }
+  EXPECT_TRUE(test_utils::WriteFileVector(old_part_.path, partition_data));
+  EXPECT_TRUE(test_utils::WriteFileVector(new_part_.path, partition_data));
+
+  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
+                                         kSourceMinorPayloadVersion));
+
+  // There should be only one SOURCE_COPY, for the whole partition and the
+  // source extents should cover only the first copy of the source file since
+  // we prefer to re-read files (maybe cached) instead of continue reading the
+  // rest of the partition.
+  EXPECT_EQ(1U, aops_.size());
+  const AnnotatedOperation& aop = aops_[0];
+  EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
+  EXPECT_EQ(5, aop.op.src_extents_size());
+  for (int i = 0; i < aop.op.src_extents_size(); ++i) {
+    EXPECT_EQ(ExtentForRange(0, 10), aop.op.src_extents(i));
+  }
+
+  EXPECT_EQ(1, aop.op.dst_extents_size());
+  EXPECT_EQ(ExtentForRange(0, 50), aop.op.dst_extents(0));
+
+  EXPECT_EQ(0, blob_size_);
+}
+
+// Test that all blocks with zeros are handled separately using REPLACE_BZ
+// operations unless they are not moved.
+TEST_F(DeltaDiffUtilsTest, ZeroBlocksUseReplaceBz) {
+  InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42);
+  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 5);
+
+  // We set four ranges of blocks with zeros: a single block, a range that fits
+  // in the chunk size, a range that doesn't and finally a range of zeros that
+  // was also zeros in the old image.
+  vector<Extent> new_zeros = {
+      ExtentForRange(10, 1),
+      ExtentForRange(20, 4),
+      // The last range is split since the old image has zeros in part of it.
+      ExtentForRange(30, 20),
+  };
+  brillo::Blob zeros_data(BlocksInExtents(new_zeros) * block_size_, '\0');
+  EXPECT_TRUE(WriteExtents(new_part_.path, new_zeros, block_size_, zeros_data));
+
+  vector<Extent> old_zeros = vector<Extent>{ExtentForRange(43, 7)};
+  EXPECT_TRUE(WriteExtents(old_part_.path, old_zeros, block_size_, zeros_data));
+
+  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(5,  // chunk_blocks
+                                         kInPlaceMinorPayloadVersion));
+
+  // Zeroed blocks from old_visited_blocks_ were copied over, so me actually
+  // use them regardless of the trivial MOVE operation not being emitted.
+  EXPECT_EQ(old_zeros,
+            old_visited_blocks_.GetExtentsForBlockCount(
+                old_visited_blocks_.blocks()));
+
+  // All the new zeroed blocks should be used, part with REPLACE_BZ and part
+  // trivial MOVE operations (not included).
+  EXPECT_EQ(new_zeros,
+            new_visited_blocks_.GetExtentsForBlockCount(
+                new_visited_blocks_.blocks()));
+
+  vector<Extent> expected_op_extents = {
+      ExtentForRange(10, 1),
+      ExtentForRange(20, 4),
+      // This range should be split.
+      ExtentForRange(30, 5),
+      ExtentForRange(35, 5),
+      ExtentForRange(40, 3),
+  };
+
+  EXPECT_EQ(expected_op_extents.size(), aops_.size());
+  for (size_t i = 0; i < aops_.size() && i < expected_op_extents.size(); ++i) {
+    SCOPED_TRACE(base::StringPrintf("Failed on operation number %" PRIuS, i));
+    const AnnotatedOperation& aop = aops_[i];
+    EXPECT_EQ(InstallOperation::REPLACE_BZ, aop.op.type());
+    EXPECT_EQ(0, aop.op.src_extents_size());
+    EXPECT_EQ(1, aop.op.dst_extents_size());
+    EXPECT_EQ(expected_op_extents[i], aop.op.dst_extents(0));
+  }
+  EXPECT_NE(0, blob_size_);
+}
+
+TEST_F(DeltaDiffUtilsTest, ShuffledBlocksAreTracked) {
+  vector<uint64_t> permutation = {0, 1, 5, 6, 7, 2, 3, 4, 9, 10, 11, 12, 8};
+  vector<Extent> perm_extents;
+  for (uint64_t x : permutation)
+    AppendBlockToExtents(&perm_extents, x);
+
+  // We use a smaller partition for this test.
+  old_part_.size = block_size_ * permutation.size();
+  new_part_.size = block_size_ * permutation.size();
+  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 123);
+
+  // We initialize the old_part_ with the blocks from new_part but in the
+  // |permutation| order. Block i in the old_part_ will contain the same data
+  // as block permutation[i] in the new_part_.
+  brillo::Blob new_contents;
+  EXPECT_TRUE(utils::ReadFile(new_part_.path, &new_contents));
+  EXPECT_TRUE(WriteExtents(old_part_.path, perm_extents, block_size_,
+                           new_contents));
+
+  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
+                                         kSourceMinorPayloadVersion));
+
+  EXPECT_EQ(permutation.size(), old_visited_blocks_.blocks());
+  EXPECT_EQ(permutation.size(), new_visited_blocks_.blocks());
+
+  // There should be only one SOURCE_COPY, with a complicate list of extents.
+  EXPECT_EQ(1U, aops_.size());
+  const AnnotatedOperation& aop = aops_[0];
+  EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
+  vector<Extent> aop_src_extents;
+  ExtentsToVector(aop.op.src_extents(), &aop_src_extents);
+  EXPECT_EQ(perm_extents, aop_src_extents);
+
+  EXPECT_EQ(1, aop.op.dst_extents_size());
+  EXPECT_EQ(ExtentForRange(0, permutation.size()), aop.op.dst_extents(0));
+
+  EXPECT_EQ(0, blob_size_);
+}
+
+TEST_F(DeltaDiffUtilsTest, IsExtFilesystemTest) {
+  EXPECT_TRUE(diff_utils::IsExtFilesystem(
+      test_utils::GetBuildArtifactsPath("gen/disk_ext2_1k.img")));
+  EXPECT_TRUE(diff_utils::IsExtFilesystem(
+      test_utils::GetBuildArtifactsPath("gen/disk_ext2_4k.img")));
+}
+
+TEST_F(DeltaDiffUtilsTest, IsSquashfs4FilesystemTest) {
+  uint8_t buffer[sizeof(kSquashfsFile)];
+  memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile));
+  string img;
+  EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
+  ScopedPathUnlinker img_unlinker(img);
+
+  // Not enough bytes passed.
+  EXPECT_TRUE(utils::WriteFile(img.c_str(), buffer, 10));
+  EXPECT_FALSE(diff_utils::IsSquashfs4Filesystem(img));
+
+  // The whole file system is passed, which is enough for parsing.
+  EXPECT_TRUE(utils::WriteFile(img.c_str(), buffer, sizeof(kSquashfsFile)));
+  EXPECT_TRUE(diff_utils::IsSquashfs4Filesystem(img));
+
+  // Modify the major version to 5.
+  uint16_t* s_major = reinterpret_cast<uint16_t*>(buffer + 0x1c);
+  *s_major = 5;
+  EXPECT_TRUE(utils::WriteFile(img.c_str(), buffer, sizeof(kSquashfsFile)));
+  EXPECT_FALSE(diff_utils::IsSquashfs4Filesystem(img));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/ext2_filesystem.cc b/update_engine/payload_generator/ext2_filesystem.cc
new file mode 100644
index 0000000..b452b41
--- /dev/null
+++ b/update_engine/payload_generator/ext2_filesystem.cc
@@ -0,0 +1,355 @@
+//
+// 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/payload_generator/ext2_filesystem.h"
+
+#include <et/com_err.h>
+#include <ext2fs/ext2_io.h>
+#include <ext2fs/ext2fs.h>
+
+#include <map>
+#include <set>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Processes all blocks belonging to an inode and adds them to the extent list.
+// This function should match the prototype expected by ext2fs_block_iterate2().
+int ProcessInodeAllBlocks(ext2_filsys fs,
+                          blk_t* blocknr,
+                          e2_blkcnt_t blockcnt,
+                          blk_t ref_blk,
+                          int ref_offset,
+                          void* priv) {
+  vector<Extent>* extents = static_cast<vector<Extent>*>(priv);
+  AppendBlockToExtents(extents, *blocknr);
+  return 0;
+}
+
+// Processes only indirect, double indirect or triple indirect metadata
+// blocks belonging to an inode. This function should match the prototype of
+// ext2fs_block_iterate2().
+int AddMetadataBlocks(ext2_filsys fs,
+                      blk_t* blocknr,
+                      e2_blkcnt_t blockcnt,
+                      blk_t ref_blk,
+                      int ref_offset,
+                      void* priv) {
+  set<uint64_t>* blocks = static_cast<set<uint64_t>*>(priv);
+  // If |blockcnt| is non-negative, |blocknr| points to the physical block
+  // number.
+  // If |blockcnt| is negative, it is one of the values: BLOCK_COUNT_IND,
+  // BLOCK_COUNT_DIND, BLOCK_COUNT_TIND or BLOCK_COUNT_TRANSLATOR and
+  // |blocknr| points to a block in the first three cases. The last case is
+  // only used by GNU Hurd, so we shouldn't see those cases here.
+  if (blockcnt == BLOCK_COUNT_IND || blockcnt == BLOCK_COUNT_DIND ||
+      blockcnt == BLOCK_COUNT_TIND) {
+    blocks->insert(*blocknr);
+  }
+  return 0;
+}
+
+struct UpdateFileAndAppendState {
+  std::map<ext2_ino_t, FilesystemInterface::File>* inodes = nullptr;
+  set<ext2_ino_t>* used_inodes = nullptr;
+  vector<FilesystemInterface::File>* files = nullptr;
+  ext2_filsys filsys;
+};
+
+int UpdateFileAndAppend(ext2_ino_t dir,
+                        int entry,
+                        struct ext2_dir_entry *dirent,
+                        int offset,
+                        int blocksize,
+                        char *buf,
+                        void *priv_data) {
+  UpdateFileAndAppendState* state =
+      static_cast<UpdateFileAndAppendState*>(priv_data);
+  uint32_t file_type = dirent->name_len >> 8;
+  // Directories can't have hard links, and they are added from the outer loop.
+  if (file_type == EXT2_FT_DIR)
+    return 0;
+
+  auto ino_file = state->inodes->find(dirent->inode);
+  if (ino_file == state->inodes->end())
+    return 0;
+  auto dir_file = state->inodes->find(dir);
+  if (dir_file == state->inodes->end())
+    return 0;
+  string basename(dirent->name, dirent->name_len & 0xff);
+  ino_file->second.name = dir_file->second.name;
+  if (dir_file->second.name != "/")
+    ino_file->second.name += "/";
+  ino_file->second.name += basename;
+
+  // Append this file to the output. If the file has a hard link, it will be
+  // added twice to the output, but with different names, which is ok. That will
+  // help identify all the versions of the same file.
+  state->files->push_back(ino_file->second);
+  state->used_inodes->insert(dirent->inode);
+  return 0;
+}
+
+}  // namespace
+
+unique_ptr<Ext2Filesystem> Ext2Filesystem::CreateFromFile(
+    const string& filename) {
+  if (filename.empty())
+    return nullptr;
+  unique_ptr<Ext2Filesystem> result(new Ext2Filesystem());
+  result->filename_ = filename;
+
+  errcode_t err = ext2fs_open(filename.c_str(),
+                              0,  // flags (read only)
+                              0,  // superblock block number
+                              0,  // block_size (autodetect)
+                              unix_io_manager,
+                              &result->filsys_);
+  if (err) {
+    LOG(ERROR) << "Opening ext2fs " << filename;
+    return nullptr;
+  }
+  return result;
+}
+
+Ext2Filesystem::~Ext2Filesystem() {
+  ext2fs_free(filsys_);
+}
+
+size_t Ext2Filesystem::GetBlockSize() const {
+  return filsys_->blocksize;
+}
+
+size_t Ext2Filesystem::GetBlockCount() const {
+  return ext2fs_blocks_count(filsys_->super);
+}
+
+bool Ext2Filesystem::GetFiles(vector<File>* files) const {
+  TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_read_inode_bitmap(filsys_));
+
+  ext2_inode_scan iscan;
+  TEST_AND_RETURN_FALSE_ERRCODE(
+      ext2fs_open_inode_scan(filsys_, 0 /* buffer_blocks */, &iscan));
+
+  std::map<ext2_ino_t, File> inodes;
+
+  // List of directories. We need to first parse all the files in a directory
+  // to later fix the absolute paths.
+  vector<ext2_ino_t> directories;
+
+  set<uint64_t> inode_blocks;
+
+  // Iterator
+  ext2_ino_t it_ino;
+  ext2_inode it_inode;
+
+  bool ok = true;
+  while (true) {
+    errcode_t error = ext2fs_get_next_inode(iscan, &it_ino, &it_inode);
+    if (error) {
+      LOG(ERROR) << "Failed to retrieve next inode (" << error << ")";
+      ok = false;
+      break;
+    }
+    if (it_ino == 0)
+      break;
+
+    // Skip inodes that are not in use.
+    if (!ext2fs_test_inode_bitmap(filsys_->inode_map, it_ino))
+      continue;
+
+    File& file = inodes[it_ino];
+    if (it_ino == EXT2_RESIZE_INO) {
+      file.name = "<group-descriptors>";
+    } else {
+      file.name = base::StringPrintf("<inode-%u>", it_ino);
+    }
+
+    memset(&file.file_stat, 0, sizeof(file.file_stat));
+    file.file_stat.st_ino = it_ino;
+    file.file_stat.st_mode = it_inode.i_mode;
+    file.file_stat.st_nlink = it_inode.i_links_count;
+    file.file_stat.st_uid = it_inode.i_uid;
+    file.file_stat.st_gid = it_inode.i_gid;
+    file.file_stat.st_size = it_inode.i_size;
+    file.file_stat.st_blksize = filsys_->blocksize;
+    file.file_stat.st_blocks = it_inode.i_blocks;
+    file.file_stat.st_atime = it_inode.i_atime;
+    file.file_stat.st_mtime = it_inode.i_mtime;
+    file.file_stat.st_ctime = it_inode.i_ctime;
+
+    bool is_dir = (ext2fs_check_directory(filsys_, it_ino) == 0);
+    if (is_dir)
+      directories.push_back(it_ino);
+
+    if (!ext2fs_inode_has_valid_blocks(&it_inode))
+      continue;
+
+    // Process the inode data and metadata blocks.
+    // For normal files, inode blocks are indirect, double indirect
+    // and triple indirect blocks (no data blocks). For directories and
+    // the journal, all blocks are considered metadata blocks.
+    int flags = it_ino < EXT2_GOOD_OLD_FIRST_INO ? 0 : BLOCK_FLAG_DATA_ONLY;
+    error = ext2fs_block_iterate2(filsys_, it_ino, flags,
+                                  nullptr,  // block_buf
+                                  ProcessInodeAllBlocks,
+                                  &file.extents);
+
+    if (error) {
+      LOG(ERROR) << "Failed to enumerate inode " << it_ino
+                << " blocks (" << error << ")";
+      continue;
+    }
+    if (it_ino >= EXT2_GOOD_OLD_FIRST_INO) {
+      ext2fs_block_iterate2(filsys_, it_ino, 0, nullptr,
+                            AddMetadataBlocks,
+                            &inode_blocks);
+    }
+  }
+  ext2fs_close_inode_scan(iscan);
+  if (!ok)
+    return false;
+
+  // The set of inodes already added to the output. There can be less elements
+  // here than in files since the later can contain repeated inodes due to
+  // hardlink files.
+  set<ext2_ino_t> used_inodes;
+
+  UpdateFileAndAppendState priv_data;
+  priv_data.inodes = &inodes;
+  priv_data.used_inodes = &used_inodes;
+  priv_data.files = files;
+  priv_data.filsys = filsys_;
+
+  files->clear();
+  // Iterate over all the files of each directory to update the name and add it.
+  for (ext2_ino_t dir_ino : directories) {
+    char* dir_name = nullptr;
+    errcode_t error = ext2fs_get_pathname(filsys_, dir_ino, 0, &dir_name);
+    if (error) {
+      // Not being able to read a directory name is not a fatal error, it is
+      // just skiped.
+      LOG(WARNING) << "Reading directory name on inode " << dir_ino
+                   << " (error " << error << ")";
+      inodes[dir_ino].name = base::StringPrintf("<dir-%u>", dir_ino);
+    } else {
+      inodes[dir_ino].name = dir_name;
+      files->push_back(inodes[dir_ino]);
+      used_inodes.insert(dir_ino);
+    }
+    ext2fs_free_mem(&dir_name);
+
+    error = ext2fs_dir_iterate2(
+        filsys_, dir_ino, 0, nullptr /* block_buf */,
+        UpdateFileAndAppend, &priv_data);
+    if (error) {
+      LOG(WARNING) << "Failed to enumerate files in directory "
+                   << inodes[dir_ino].name << " (error " << error << ")";
+    }
+  }
+
+  // Add <inode-blocks> file with the blocks that hold inodes.
+  File inode_file;
+  inode_file.name = "<inode-blocks>";
+  for (uint64_t block : inode_blocks) {
+    AppendBlockToExtents(&inode_file.extents, block);
+  }
+  files->push_back(inode_file);
+
+  // Add <free-spacce> blocs.
+  errcode_t error = ext2fs_read_block_bitmap(filsys_);
+  if (error) {
+    LOG(ERROR) << "Reading the blocks bitmap (error " << error << ")";
+  } else {
+    File free_space;
+    free_space.name = "<free-space>";
+    blk64_t blk_start = ext2fs_get_block_bitmap_start2(filsys_->block_map);
+    blk64_t blk_end = ext2fs_get_block_bitmap_end2(filsys_->block_map);
+    for (blk64_t block = blk_start; block < blk_end; block++) {
+      if (!ext2fs_test_block_bitmap2(filsys_->block_map, block))
+        AppendBlockToExtents(&free_space.extents, block);
+    }
+    files->push_back(free_space);
+  }
+
+  // Add all the unreachable files plus the pseudo-files with an inode. Since
+  // these inodes aren't files in the filesystem, ignore the empty ones.
+  for (const auto& ino_file : inodes) {
+    if (used_inodes.find(ino_file.first) != used_inodes.end())
+      continue;
+    if (ino_file.second.extents.empty())
+      continue;
+
+    File file = ino_file.second;
+    ExtentRanges ranges;
+    ranges.AddExtents(file.extents);
+    file.extents = ranges.GetExtentsForBlockCount(ranges.blocks());
+
+    files->push_back(file);
+  }
+
+  return true;
+}
+
+bool Ext2Filesystem::LoadSettings(brillo::KeyValueStore* store) const {
+  // First search for the settings inode following symlinks if we find some.
+  ext2_ino_t ino_num = 0;
+  errcode_t err = ext2fs_namei_follow(
+      filsys_, EXT2_ROOT_INO /* root */, EXT2_ROOT_INO /* cwd */,
+      "/etc/update_engine.conf", &ino_num);
+  if (err != 0)
+    return false;
+
+  ext2_inode ino_data;
+  if (ext2fs_read_inode(filsys_, ino_num, &ino_data) != 0)
+    return false;
+
+  // Load the list of blocks and then the contents of the inodes.
+  vector<Extent> extents;
+  err = ext2fs_block_iterate2(filsys_, ino_num, BLOCK_FLAG_DATA_ONLY,
+                              nullptr,  // block_buf
+                              ProcessInodeAllBlocks,
+                              &extents);
+  if (err != 0)
+    return false;
+
+  brillo::Blob blob;
+  uint64_t physical_size = BlocksInExtents(extents) * filsys_->blocksize;
+  // Sparse holes in the settings file are not supported.
+  if (EXT2_I_SIZE(&ino_data) > physical_size)
+    return false;
+  if (!utils::ReadExtents(filename_, extents, &blob, physical_size,
+                          filsys_->blocksize))
+    return false;
+
+  string text(blob.begin(), blob.begin() + EXT2_I_SIZE(&ino_data));
+  return store->LoadFromString(text);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/ext2_filesystem.h b/update_engine/payload_generator/ext2_filesystem.h
new file mode 100644
index 0000000..248e208
--- /dev/null
+++ b/update_engine/payload_generator/ext2_filesystem.h
@@ -0,0 +1,71 @@
+//
+// 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_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <ext2fs/ext2fs.h>
+
+namespace chromeos_update_engine {
+
+class Ext2Filesystem : public FilesystemInterface {
+ public:
+  // Creates an Ext2Filesystem from a ext2 formatted filesystem stored in a
+  // file. The file doesn't need to be loop-back mounted.
+  static std::unique_ptr<Ext2Filesystem> CreateFromFile(
+      const std::string& filename);
+  virtual ~Ext2Filesystem();
+
+  // FilesystemInterface overrides.
+  size_t GetBlockSize() const override;
+  size_t GetBlockCount() const override;
+
+  // GetFiles will return one FilesystemInterface::File for every file and every
+  // directory in the filesystem. Hard-linked files will appear in the list
+  // several times with the same list of blocks.
+  // On addition to actual files, it also returns these pseudo-files:
+  //  <free-space>: With all the unallocated data-blocks.
+  //  <inode-blocks>: Will all the data-blocks for second and third level inodes
+  //    of all the files.
+  //  <group-descriptors>: With the block group descriptor and their reserved
+  //    space.
+  //  <metadata>: With the rest of ext2 metadata blocks, such as superblocks
+  //    and bitmap tables.
+  bool GetFiles(std::vector<File>* files) const override;
+
+  bool LoadSettings(brillo::KeyValueStore* store) const override;
+
+ private:
+  Ext2Filesystem() = default;
+
+  // The ext2 main data structure holding the filesystem.
+  ext2_filsys filsys_ = nullptr;
+
+  // The file where the filesystem is stored.
+  std::string filename_;
+
+  DISALLOW_COPY_AND_ASSIGN(Ext2Filesystem);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
diff --git a/update_engine/payload_generator/ext2_filesystem_unittest.cc b/update_engine/payload_generator/ext2_filesystem_unittest.cc
new file mode 100644
index 0000000..a3c7731
--- /dev/null
+++ b/update_engine/payload_generator/ext2_filesystem_unittest.cc
@@ -0,0 +1,204 @@
+//
+// 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/payload_generator/ext2_filesystem.h"
+
+#include <unistd.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using chromeos_update_engine::test_utils::GetBuildArtifactsPath;
+using std::map;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+uint64_t kDefaultFilesystemSize = 4 * 1024 * 1024;
+size_t kDefaultFilesystemBlockCount = 1024;
+size_t kDefaultFilesystemBlockSize = 4096;
+
+// Checks that all the blocks in |extents| are in the range [0, total_blocks).
+void ExpectBlocksInRange(const vector<Extent>& extents, uint64_t total_blocks) {
+  for (const Extent& extent : extents) {
+    EXPECT_LE(0U, extent.start_block());
+    EXPECT_LE(extent.start_block() + extent.num_blocks(), total_blocks);
+  }
+}
+
+}  // namespace
+
+class Ext2FilesystemTest : public ::testing::Test {};
+
+TEST_F(Ext2FilesystemTest, InvalidFilesystem) {
+  test_utils::ScopedTempFile fs_filename_{"Ext2FilesystemTest-XXXXXX"};
+  ASSERT_EQ(0, truncate(fs_filename_.path().c_str(), kDefaultFilesystemSize));
+  unique_ptr<Ext2Filesystem> fs =
+      Ext2Filesystem::CreateFromFile(fs_filename_.path());
+  ASSERT_EQ(nullptr, fs.get());
+
+  fs = Ext2Filesystem::CreateFromFile("/path/to/invalid/file");
+  ASSERT_EQ(nullptr, fs.get());
+}
+
+TEST_F(Ext2FilesystemTest, EmptyFilesystem) {
+  unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(
+      GetBuildArtifactsPath("gen/disk_ext2_4k_empty.img"));
+
+  ASSERT_NE(nullptr, fs.get());
+  EXPECT_EQ(kDefaultFilesystemBlockCount, fs->GetBlockCount());
+  EXPECT_EQ(kDefaultFilesystemBlockSize, fs->GetBlockSize());
+
+  vector<FilesystemInterface::File> files;
+  EXPECT_TRUE(fs->GetFiles(&files));
+
+  map<string, FilesystemInterface::File> map_files;
+  for (const auto& file : files) {
+    EXPECT_EQ(map_files.end(), map_files.find(file.name))
+        << "File " << file.name << " repeated in the list.";
+    map_files[file.name] = file;
+    ExpectBlocksInRange(file.extents, fs->GetBlockCount());
+  }
+  EXPECT_EQ(2U, map_files["/"].file_stat.st_ino);
+  EXPECT_FALSE(map_files["<free-space>"].extents.empty());
+}
+
+// This test parses the sample images generated during build time with the
+// "generate_image.sh" script. The expected conditions of each file in these
+// images is encoded in the file name, as defined in the mentioned script.
+TEST_F(Ext2FilesystemTest, ParseGeneratedImages) {
+  const vector<string> kGeneratedImages = {
+      "disk_ext2_1k.img",
+      "disk_ext2_4k.img" };
+  base::FilePath build_path = GetBuildArtifactsPath().Append("gen");
+  for (const string& fs_name : kGeneratedImages) {
+    LOG(INFO) << "Testing " << fs_name;
+    unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(
+        build_path.Append(fs_name).value());
+    ASSERT_NE(nullptr, fs.get());
+
+    vector<FilesystemInterface::File> files;
+    map<string, FilesystemInterface::File> map_files;
+    set<string> filenames;
+    EXPECT_TRUE(fs->GetFiles(&files));
+    for (const auto& file : files) {
+      // Check no repeated files. We should parse hard-links with two different
+      // names.
+      EXPECT_EQ(map_files.end(), map_files.find(file.name))
+          << "File " << file.name << " repeated in the list.";
+      map_files[file.name] = file;
+      filenames.insert(file.name);
+      ExpectBlocksInRange(file.extents, fs->GetBlockCount());
+    }
+
+    // Check that all the files are parsed, and the /removed file should not
+    // be included in the list.
+    set<string> kExpectedFiles = {
+        "/",
+        "/cdev",
+        "/dir1",
+        "/dir1/file",
+        "/dir1/dir2",
+        "/dir1/dir2/file",
+        "/dir1/dir2/dir1",
+        "/empty-file",
+        "/fifo",
+        "/link-hard-regular-16k",
+        "/link-long_symlink",
+        "/link-short_symlink",
+        "/lost+found",
+        "/regular-small",
+        "/regular-16k",
+        "/regular-32k-zeros",
+        "/regular-with_net_cap",
+        "/sparse_empty-10k",
+        "/sparse_empty-2blocks",
+        "/sparse-10000blocks",
+        "/sparse-16k-last_block",
+        "/sparse-16k-first_block",
+        "/sparse-16k-holes",
+        "<inode-blocks>",
+        "<free-space>",
+        "<group-descriptors>",
+    };
+    EXPECT_EQ(kExpectedFiles, filenames);
+
+    FilesystemInterface::File file;
+
+    // Small symlinks don't actually have data blocks.
+    EXPECT_TRUE(map_files["/link-short_symlink"].extents.empty());
+    EXPECT_EQ(1U, BlocksInExtents(map_files["/link-long_symlink"].extents));
+
+    // Hard-links report the same list of blocks.
+    EXPECT_EQ(map_files["/link-hard-regular-16k"].extents,
+              map_files["/regular-16k"].extents);
+    EXPECT_FALSE(map_files["/regular-16k"].extents.empty());
+
+    // The number of blocks in these files doesn't depend on the
+    // block size.
+    EXPECT_TRUE(map_files["/empty-file"].extents.empty());
+    EXPECT_EQ(1U, BlocksInExtents(map_files["/regular-small"].extents));
+    EXPECT_EQ(1U, BlocksInExtents(map_files["/regular-with_net_cap"].extents));
+    EXPECT_TRUE(map_files["/sparse_empty-10k"].extents.empty());
+    EXPECT_TRUE(map_files["/sparse_empty-2blocks"].extents.empty());
+    EXPECT_EQ(1U, BlocksInExtents(map_files["/sparse-16k-last_block"].extents));
+    EXPECT_EQ(1U,
+              BlocksInExtents(map_files["/sparse-16k-first_block"].extents));
+    EXPECT_EQ(2U, BlocksInExtents(map_files["/sparse-16k-holes"].extents));
+  }
+}
+
+TEST_F(Ext2FilesystemTest, LoadSettingsFailsTest) {
+  unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(
+      GetBuildArtifactsPath("gen/disk_ext2_1k.img"));
+  ASSERT_NE(nullptr, fs.get());
+
+  brillo::KeyValueStore store;
+  // disk_ext2_1k.img doesn't have the /etc/update_engine.conf file.
+  EXPECT_FALSE(fs->LoadSettings(&store));
+}
+
+TEST_F(Ext2FilesystemTest, LoadSettingsWorksTest) {
+  unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(
+      GetBuildArtifactsPath("gen/disk_ext2_unittest.img"));
+  ASSERT_NE(nullptr, fs.get());
+
+  brillo::KeyValueStore store;
+  EXPECT_TRUE(fs->LoadSettings(&store));
+  string minor_version;
+  EXPECT_TRUE(store.GetString("PAYLOAD_MINOR_VERSION", &minor_version));
+  EXPECT_EQ("1234", minor_version);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/extent_ranges.cc b/update_engine/payload_generator/extent_ranges.cc
new file mode 100644
index 0000000..848fdc7
--- /dev/null
+++ b/update_engine/payload_generator/extent_ranges.cc
@@ -0,0 +1,306 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_generator/extent_ranges.h"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+bool ExtentRanges::ExtentsOverlapOrTouch(const Extent& a, const Extent& b) {
+  if (a.start_block() == b.start_block())
+    return true;
+  if (a.start_block() == kSparseHole || b.start_block() == kSparseHole)
+    return false;
+  if (a.start_block() < b.start_block()) {
+    return a.start_block() + a.num_blocks() >= b.start_block();
+  } else {
+    return b.start_block() + b.num_blocks() >= a.start_block();
+  }
+}
+
+bool ExtentRanges::ExtentsOverlap(const Extent& a, const Extent& b) {
+  if (a.start_block() == b.start_block())
+    return true;
+  if (a.start_block() == kSparseHole || b.start_block() == kSparseHole)
+    return false;
+  if (a.start_block() < b.start_block()) {
+    return a.start_block() + a.num_blocks() > b.start_block();
+  } else {
+    return b.start_block() + b.num_blocks() > a.start_block();
+  }
+}
+
+void ExtentRanges::AddBlock(uint64_t block) {
+  AddExtent(ExtentForRange(block, 1));
+}
+
+void ExtentRanges::SubtractBlock(uint64_t block) {
+  SubtractExtent(ExtentForRange(block, 1));
+}
+
+namespace {
+
+Extent UnionOverlappingExtents(const Extent& first, const Extent& second) {
+  CHECK_NE(kSparseHole, first.start_block());
+  CHECK_NE(kSparseHole, second.start_block());
+  uint64_t start = std::min(first.start_block(), second.start_block());
+  uint64_t end = std::max(first.start_block() + first.num_blocks(),
+                          second.start_block() + second.num_blocks());
+  return ExtentForRange(start, end - start);
+}
+
+}  // namespace
+
+void ExtentRanges::AddExtent(Extent extent) {
+  if (extent.start_block() == kSparseHole || extent.num_blocks() == 0)
+    return;
+
+  ExtentSet::iterator begin_del = extent_set_.end();
+  ExtentSet::iterator end_del = extent_set_.end();
+  uint64_t del_blocks = 0;
+  for (ExtentSet::iterator it = extent_set_.begin(), e = extent_set_.end();
+       it != e; ++it) {
+    if (ExtentsOverlapOrTouch(*it, extent)) {
+      end_del = it;
+      ++end_del;
+      del_blocks += it->num_blocks();
+      if (begin_del == extent_set_.end())
+        begin_del = it;
+
+      extent = UnionOverlappingExtents(extent, *it);
+    }
+  }
+  extent_set_.erase(begin_del, end_del);
+  extent_set_.insert(extent);
+  blocks_ -= del_blocks;
+  blocks_ += extent.num_blocks();
+}
+
+namespace {
+// Returns base - subtractee (set subtraction).
+ExtentRanges::ExtentSet SubtractOverlappingExtents(const Extent& base,
+                                                   const Extent& subtractee) {
+  ExtentRanges::ExtentSet ret;
+  if (subtractee.start_block() > base.start_block()) {
+    ret.insert(ExtentForRange(base.start_block(),
+                              subtractee.start_block() - base.start_block()));
+  }
+  uint64_t base_end = base.start_block() + base.num_blocks();
+  uint64_t subtractee_end = subtractee.start_block() + subtractee.num_blocks();
+  if (base_end > subtractee_end) {
+    ret.insert(ExtentForRange(subtractee_end, base_end - subtractee_end));
+  }
+  return ret;
+}
+}  // namespace
+
+void ExtentRanges::SubtractExtent(const Extent& extent) {
+  if (extent.start_block() == kSparseHole || extent.num_blocks() == 0)
+    return;
+
+  ExtentSet::iterator begin_del = extent_set_.end();
+  ExtentSet::iterator end_del = extent_set_.end();
+  uint64_t del_blocks = 0;
+  ExtentSet new_extents;
+  for (ExtentSet::iterator it = extent_set_.begin(), e = extent_set_.end();
+       it != e; ++it) {
+    if (!ExtentsOverlap(*it, extent))
+      continue;
+
+    if (begin_del == extent_set_.end())
+      begin_del = it;
+    end_del = it;
+    ++end_del;
+
+    del_blocks += it->num_blocks();
+
+    ExtentSet subtraction = SubtractOverlappingExtents(*it, extent);
+    for (ExtentSet::iterator jt = subtraction.begin(), je = subtraction.end();
+         jt != je; ++jt) {
+      new_extents.insert(*jt);
+      del_blocks -= jt->num_blocks();
+    }
+  }
+  extent_set_.erase(begin_del, end_del);
+  extent_set_.insert(new_extents.begin(), new_extents.end());
+  blocks_ -= del_blocks;
+}
+
+void ExtentRanges::AddRanges(const ExtentRanges& ranges) {
+  for (ExtentSet::const_iterator it = ranges.extent_set_.begin(),
+           e = ranges.extent_set_.end(); it != e; ++it) {
+    AddExtent(*it);
+  }
+}
+
+void ExtentRanges::SubtractRanges(const ExtentRanges& ranges) {
+  for (ExtentSet::const_iterator it = ranges.extent_set_.begin(),
+           e = ranges.extent_set_.end(); it != e; ++it) {
+    SubtractExtent(*it);
+  }
+}
+
+void ExtentRanges::AddExtents(const vector<Extent>& extents) {
+  for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+       it != e; ++it) {
+    AddExtent(*it);
+  }
+}
+
+void ExtentRanges::SubtractExtents(const vector<Extent>& extents) {
+  for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+       it != e; ++it) {
+    SubtractExtent(*it);
+  }
+}
+
+void ExtentRanges::AddRepeatedExtents(
+    const ::google::protobuf::RepeatedPtrField<Extent> &exts) {
+  for (int i = 0, e = exts.size(); i != e; ++i) {
+    AddExtent(exts.Get(i));
+  }
+}
+
+void ExtentRanges::SubtractRepeatedExtents(
+    const ::google::protobuf::RepeatedPtrField<Extent> &exts) {
+  for (int i = 0, e = exts.size(); i != e; ++i) {
+    SubtractExtent(exts.Get(i));
+  }
+}
+
+bool ExtentRanges::ContainsBlock(uint64_t block) const {
+  auto lower = extent_set_.lower_bound(ExtentForRange(block, 1));
+  // The block could be on the extent before the one in |lower|.
+  if (lower != extent_set_.begin())
+    lower--;
+  // Any extent starting at block+1 or later is not interesting, so this is the
+  // upper limit.
+  auto upper = extent_set_.lower_bound(ExtentForRange(block + 1, 0));
+  for (auto iter = lower; iter != upper; ++iter) {
+    if (iter->start_block() <= block &&
+        block < iter->start_block() + iter->num_blocks()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void ExtentRanges::Dump() const {
+  LOG(INFO) << "ExtentRanges Dump. blocks: " << blocks_;
+  for (ExtentSet::const_iterator it = extent_set_.begin(),
+           e = extent_set_.end();
+       it != e; ++it) {
+    LOG(INFO) << "{" << it->start_block() << ", " << it->num_blocks() << "}";
+  }
+}
+
+Extent ExtentForRange(uint64_t start_block, uint64_t num_blocks) {
+  Extent ret;
+  ret.set_start_block(start_block);
+  ret.set_num_blocks(num_blocks);
+  return ret;
+}
+
+vector<Extent> ExtentRanges::GetExtentsForBlockCount(
+    uint64_t count) const {
+  vector<Extent> out;
+  if (count == 0)
+    return out;
+  uint64_t out_blocks = 0;
+  CHECK(count <= blocks_);
+  for (ExtentSet::const_iterator it = extent_set_.begin(),
+           e = extent_set_.end();
+       it != e; ++it) {
+    const uint64_t blocks_needed = count - out_blocks;
+    const Extent& extent = *it;
+    out.push_back(extent);
+    out_blocks += extent.num_blocks();
+    if (extent.num_blocks() < blocks_needed)
+      continue;
+    if (extent.num_blocks() == blocks_needed)
+      break;
+    // If we get here, we just added the last extent needed, but it's too big
+    out_blocks -= extent.num_blocks();
+    out_blocks += blocks_needed;
+    out.back().set_num_blocks(blocks_needed);
+    break;
+  }
+  return out;
+}
+
+vector<Extent> FilterExtentRanges(const vector<Extent>& extents,
+                                  const ExtentRanges& ranges) {
+  vector<Extent> result;
+  const ExtentRanges::ExtentSet& extent_set = ranges.extent_set();
+  for (Extent extent : extents) {
+    // The extents are sorted by the start_block. We want to iterate all the
+    // Extents in the ExtentSet possibly overlapping the current |extent|. This
+    // is achieved by looking from the extent whose start_block is *lower* than
+    // the extent.start_block() up to the greatest extent whose start_block is
+    // lower than extent.start_block() + extent.num_blocks().
+    auto lower = extent_set.lower_bound(extent);
+    // We need to decrement the lower_bound to look at the extent that could
+    // overlap the beginning of the current |extent|.
+    if (lower != extent_set.begin())
+      lower--;
+    auto upper = extent_set.lower_bound(
+        ExtentForRange(extent.start_block() + extent.num_blocks(), 0));
+    for (auto iter = lower; iter != upper; ++iter) {
+      if (!ExtentRanges::ExtentsOverlap(extent, *iter))
+        continue;
+      if (iter->start_block() <= extent.start_block()) {
+        // We need to cut blocks from the beginning of the |extent|.
+        uint64_t cut_blocks = iter->start_block() + iter->num_blocks() -
+            extent.start_block();
+        if (cut_blocks >= extent.num_blocks()) {
+          extent.set_num_blocks(0);
+          break;
+        }
+        extent = ExtentForRange(extent.start_block() + cut_blocks,
+                                extent.num_blocks() - cut_blocks);
+      } else {
+        // We need to cut blocks on the middle of the extent, possible up to the
+        // end of it.
+        result.push_back(
+            ExtentForRange(extent.start_block(),
+                           iter->start_block() - extent.start_block()));
+        uint64_t new_start = iter->start_block() + iter->num_blocks();
+        uint64_t old_end = extent.start_block() + extent.num_blocks();
+        if (new_start >= old_end) {
+          extent.set_num_blocks(0);
+          break;
+        }
+        extent = ExtentForRange(new_start, old_end - new_start);
+      }
+    }
+    if (extent.num_blocks() > 0)
+      result.push_back(extent);
+  }
+  return result;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/extent_ranges.h b/update_engine/payload_generator/extent_ranges.h
new file mode 100644
index 0000000..198c834
--- /dev/null
+++ b/update_engine/payload_generator/extent_ranges.h
@@ -0,0 +1,94 @@
+//
+// 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_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// An ExtentRanges object represents an unordered collection of extents (and
+// therefore blocks). Such an object may be modified by adding or subtracting
+// blocks (think: set addition or set subtraction). Note that ExtentRanges
+// ignores sparse hole extents mostly to avoid confusion between extending a
+// sparse hole range vs. set addition but also to ensure that the delta
+// generator doesn't use sparse holes as scratch space.
+
+namespace chromeos_update_engine {
+
+struct ExtentLess {
+  bool operator()(const Extent& x, const Extent& y) const {
+    return x.start_block() < y.start_block();
+  }
+};
+
+Extent ExtentForRange(uint64_t start_block, uint64_t num_blocks);
+
+class ExtentRanges {
+ public:
+  typedef std::set<Extent, ExtentLess> ExtentSet;
+
+  ExtentRanges() : blocks_(0) {}
+  void AddBlock(uint64_t block);
+  void SubtractBlock(uint64_t block);
+  void AddExtent(Extent extent);
+  void SubtractExtent(const Extent& extent);
+  void AddExtents(const std::vector<Extent>& extents);
+  void SubtractExtents(const std::vector<Extent>& extents);
+  void AddRepeatedExtents(
+      const ::google::protobuf::RepeatedPtrField<Extent> &exts);
+  void SubtractRepeatedExtents(
+      const ::google::protobuf::RepeatedPtrField<Extent> &exts);
+  void AddRanges(const ExtentRanges& ranges);
+  void SubtractRanges(const ExtentRanges& ranges);
+
+  // Returns whether the block |block| is in this ExtentRange.
+  bool ContainsBlock(uint64_t block) const;
+
+  static bool ExtentsOverlapOrTouch(const Extent& a, const Extent& b);
+  static bool ExtentsOverlap(const Extent& a, const Extent& b);
+
+  // Dumps contents to the log file. Useful for debugging.
+  void Dump() const;
+
+  uint64_t blocks() const { return blocks_; }
+  const ExtentSet& extent_set() const { return extent_set_; }
+
+  // Returns an ordered vector of extents for |count| blocks,
+  // using extents in extent_set_. The returned extents are not
+  // removed from extent_set_. |count| must be less than or equal to
+  // the number of blocks in this extent set.
+  std::vector<Extent> GetExtentsForBlockCount(uint64_t count) const;
+
+ private:
+  ExtentSet extent_set_;
+  uint64_t blocks_;
+};
+
+// Filters out from the passed list of extents |extents| all the blocks in the
+// ExtentRanges set. Note that the order of the blocks in |extents| is preserved
+// omitting blocks present in the ExtentRanges |ranges|.
+std::vector<Extent> FilterExtentRanges(const std::vector<Extent>& extents,
+                                       const ExtentRanges& ranges);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
diff --git a/update_engine/payload_generator/extent_ranges_unittest.cc b/update_engine/payload_generator/extent_ranges_unittest.cc
new file mode 100644
index 0000000..3705bac
--- /dev/null
+++ b/update_engine/payload_generator/extent_ranges_unittest.cc
@@ -0,0 +1,345 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_generator/extent_ranges.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ExtentRangesTest : public ::testing::Test {};
+
+namespace {
+void ExpectRangeEq(const ExtentRanges& ranges,
+                   const uint64_t* expected,
+                   size_t sz,
+                   int line) {
+  uint64_t blocks = 0;
+  for (size_t i = 1; i < sz; i += 2) {
+    blocks += expected[i];
+  }
+  EXPECT_EQ(blocks, ranges.blocks()) << "line: " << line;
+
+  const ExtentRanges::ExtentSet& result = ranges.extent_set();
+  ExtentRanges::ExtentSet::const_iterator it = result.begin();
+  for (size_t i = 0; i < sz; i += 2) {
+    EXPECT_FALSE(it == result.end()) << "line: " << line;
+    EXPECT_EQ(expected[i], it->start_block()) << "line: " << line;
+    EXPECT_EQ(expected[i + 1], it->num_blocks()) << "line: " << line;
+    ++it;
+  }
+}
+
+#define EXPECT_RANGE_EQ(ranges, var)                            \
+  do {                                                          \
+    ExpectRangeEq(ranges, var, arraysize(var), __LINE__);       \
+  } while (0)
+
+void ExpectRangesOverlapOrTouch(uint64_t a_start, uint64_t a_num,
+                                uint64_t b_start, uint64_t b_num) {
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+                                                                 a_num),
+                                                  ExtentForRange(b_start,
+                                                                 b_num)));
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+                                                                 b_num),
+                                                  ExtentForRange(a_start,
+                                                                 a_num)));
+}
+
+void ExpectFalseRangesOverlapOrTouch(uint64_t a_start, uint64_t a_num,
+                                     uint64_t b_start, uint64_t b_num) {
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+                                                                  a_num),
+                                                   ExtentForRange(b_start,
+                                                                  b_num)));
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+                                                                  b_num),
+                                                   ExtentForRange(a_start,
+                                                                  a_num)));
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+                                                           a_num),
+                                            ExtentForRange(b_start,
+                                                           b_num)));
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+                                                           b_num),
+                                            ExtentForRange(a_start,
+                                                           a_num)));
+}
+
+void ExpectRangesOverlap(uint64_t a_start, uint64_t a_num,
+                         uint64_t b_start, uint64_t b_num) {
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+                                                          a_num),
+                                           ExtentForRange(b_start,
+                                                          b_num)));
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+                                                          b_num),
+                                           ExtentForRange(a_start,
+                                                          a_num)));
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+                                                                 a_num),
+                                                  ExtentForRange(b_start,
+                                                                 b_num)));
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+                                                                 b_num),
+                                                  ExtentForRange(a_start,
+                                                                 a_num)));
+}
+
+void ExpectFalseRangesOverlap(uint64_t a_start, uint64_t a_num,
+                              uint64_t b_start, uint64_t b_num) {
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+                                                           a_num),
+                                            ExtentForRange(b_start,
+                                                           b_num)));
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+                                                           b_num),
+                                            ExtentForRange(a_start,
+                                                           a_num)));
+}
+
+}  // namespace
+
+TEST(ExtentRangesTest, ExtentsOverlapTest) {
+  ExpectRangesOverlapOrTouch(10, 20, 30, 10);
+  ExpectRangesOverlap(10, 20, 25, 10);
+  ExpectFalseRangesOverlapOrTouch(10, 20, 35, 10);
+  ExpectFalseRangesOverlap(10, 20, 30, 10);
+  ExpectRangesOverlap(12, 4, 12, 3);
+
+  ExpectRangesOverlapOrTouch(kSparseHole, 2, kSparseHole, 3);
+  ExpectRangesOverlap(kSparseHole, 2, kSparseHole, 3);
+  ExpectFalseRangesOverlapOrTouch(kSparseHole, 2, 10, 3);
+  ExpectFalseRangesOverlapOrTouch(10, 2, kSparseHole, 3);
+  ExpectFalseRangesOverlap(kSparseHole, 2, 10, 3);
+  ExpectFalseRangesOverlap(10, 2, kSparseHole, 3);
+}
+
+TEST(ExtentRangesTest, SimpleTest) {
+  ExtentRanges ranges;
+  {
+    static const uint64_t expected[] = {};
+    // Can't use arraysize() on 0-length arrays:
+    ExpectRangeEq(ranges, expected, 0, __LINE__);
+  }
+  ranges.SubtractBlock(2);
+  {
+    static const uint64_t expected[] = {};
+    // Can't use arraysize() on 0-length arrays:
+    ExpectRangeEq(ranges, expected, 0, __LINE__);
+  }
+
+  ranges.AddBlock(0);
+  ranges.Dump();
+  ranges.AddBlock(1);
+  ranges.AddBlock(3);
+
+  {
+    static const uint64_t expected[] = {0, 2, 3, 1};
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+  ranges.AddBlock(2);
+  {
+    static const uint64_t expected[] = {0, 4};
+    EXPECT_RANGE_EQ(ranges, expected);
+    ranges.AddBlock(kSparseHole);
+    EXPECT_RANGE_EQ(ranges, expected);
+    ranges.SubtractBlock(kSparseHole);
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+  ranges.SubtractBlock(2);
+  {
+    static const uint64_t expected[] = {0, 2, 3, 1};
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+
+  for (uint64_t i = 100; i < 1000; i += 100) {
+    ranges.AddExtent(ExtentForRange(i, 50));
+  }
+  {
+    static const uint64_t expected[] = {
+      0, 2, 3, 1, 100, 50, 200, 50, 300, 50, 400, 50,
+      500, 50, 600, 50, 700, 50, 800, 50, 900, 50
+    };
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+
+  ranges.SubtractExtent(ExtentForRange(210, 410 - 210));
+  {
+    static const uint64_t expected[] = {
+      0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+      600, 50, 700, 50, 800, 50, 900, 50
+    };
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+  ranges.AddExtent(ExtentForRange(100000, 0));
+  {
+    static const uint64_t expected[] = {
+      0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+      600, 50, 700, 50, 800, 50, 900, 50
+    };
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+  ranges.SubtractExtent(ExtentForRange(3, 0));
+  {
+    static const uint64_t expected[] = {
+      0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+      600, 50, 700, 50, 800, 50, 900, 50
+    };
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+}
+
+TEST(ExtentRangesTest, MultipleRanges) {
+  ExtentRanges ranges_a, ranges_b;
+  ranges_a.AddBlock(0);
+  ranges_b.AddBlock(4);
+  ranges_b.AddBlock(3);
+  {
+    uint64_t expected[] = {3, 2};
+    EXPECT_RANGE_EQ(ranges_b, expected);
+  }
+  ranges_a.AddRanges(ranges_b);
+  {
+    uint64_t expected[] = {0, 1, 3, 2};
+    EXPECT_RANGE_EQ(ranges_a, expected);
+  }
+  ranges_a.SubtractRanges(ranges_b);
+  {
+    uint64_t expected[] = {0, 1};
+    EXPECT_RANGE_EQ(ranges_a, expected);
+  }
+  {
+    uint64_t expected[] = {3, 2};
+    EXPECT_RANGE_EQ(ranges_b, expected);
+  }
+}
+
+TEST(ExtentRangesTest, GetExtentsForBlockCountTest) {
+  ExtentRanges ranges;
+  ranges.AddExtents(vector<Extent>(1, ExtentForRange(10, 30)));
+  {
+    vector<Extent> zero_extents = ranges.GetExtentsForBlockCount(0);
+    EXPECT_TRUE(zero_extents.empty());
+  }
+  ::google::protobuf::RepeatedPtrField<Extent> rep_field;
+  *rep_field.Add() = ExtentForRange(30, 40);
+  ranges.AddRepeatedExtents(rep_field);
+  ranges.SubtractExtents(vector<Extent>(1, ExtentForRange(20, 10)));
+  *rep_field.Mutable(0) = ExtentForRange(50, 10);
+  ranges.SubtractRepeatedExtents(rep_field);
+  EXPECT_EQ(40U, ranges.blocks());
+
+  for (int i = 0; i < 2; i++) {
+    vector<Extent> expected(2);
+    expected[0] = ExtentForRange(10, 10);
+    expected[1] = ExtentForRange(30, i == 0 ? 10 : 20);
+    vector<Extent> actual =
+        ranges.GetExtentsForBlockCount(10 + expected[1].num_blocks());
+    EXPECT_EQ(expected.size(), actual.size());
+    for (vector<Extent>::size_type j = 0, e = expected.size(); j != e; ++j) {
+      EXPECT_EQ(expected[j].start_block(), actual[j].start_block())
+          << "j = " << j;
+      EXPECT_EQ(expected[j].num_blocks(), actual[j].num_blocks())
+          << "j = " << j;
+    }
+  }
+}
+
+TEST(ExtentRangesTest, ContainsBlockTest) {
+  ExtentRanges ranges;
+  EXPECT_FALSE(ranges.ContainsBlock(123));
+
+  ranges.AddExtent(ExtentForRange(10, 10));
+  ranges.AddExtent(ExtentForRange(100, 1));
+
+  EXPECT_FALSE(ranges.ContainsBlock(9));
+  EXPECT_TRUE(ranges.ContainsBlock(10));
+  EXPECT_TRUE(ranges.ContainsBlock(15));
+  EXPECT_TRUE(ranges.ContainsBlock(19));
+  EXPECT_FALSE(ranges.ContainsBlock(20));
+
+  // Test for an extent with just the block we are requesting.
+  EXPECT_FALSE(ranges.ContainsBlock(99));
+  EXPECT_TRUE(ranges.ContainsBlock(100));
+  EXPECT_FALSE(ranges.ContainsBlock(101));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesEmptyRanges) {
+  ExtentRanges ranges;
+  EXPECT_EQ(vector<Extent>(),
+            FilterExtentRanges(vector<Extent>(), ranges));
+  EXPECT_EQ(
+      vector<Extent>{ ExtentForRange(50, 10) },
+      FilterExtentRanges(vector<Extent>{ ExtentForRange(50, 10) }, ranges));
+  // Check that the empty Extents are ignored.
+  EXPECT_EQ(
+      (vector<Extent>{ ExtentForRange(10, 10), ExtentForRange(20, 10) }),
+      FilterExtentRanges(vector<Extent>{
+           ExtentForRange(10, 10),
+           ExtentForRange(3, 0),
+           ExtentForRange(20, 10) }, ranges));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesMultipleRanges) {
+  // Two overlaping extents, with three ranges to remove.
+  vector<Extent> extents {
+      ExtentForRange(10, 100),
+      ExtentForRange(30, 100) };
+  ExtentRanges ranges;
+  // This overlaps the beginning of the second extent.
+  ranges.AddExtent(ExtentForRange(28, 3));
+  ranges.AddExtent(ExtentForRange(50, 10));
+  ranges.AddExtent(ExtentForRange(70, 10));
+  // This overlaps the end of the second extent.
+  ranges.AddExtent(ExtentForRange(108, 6));
+  EXPECT_EQ(
+      (vector<Extent>{
+           // For the first extent:
+           ExtentForRange(10, 18),
+           ExtentForRange(31, 19),
+           ExtentForRange(60, 10),
+           ExtentForRange(80, 28),
+           // For the second extent:
+           ExtentForRange(31, 19),
+           ExtentForRange(60, 10),
+           ExtentForRange(80, 28),
+           ExtentForRange(114, 16)}),
+      FilterExtentRanges(extents, ranges));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesOvelapping) {
+  ExtentRanges ranges;
+  ranges.AddExtent(ExtentForRange(10, 3));
+  ranges.AddExtent(ExtentForRange(20, 5));
+  // Requested extent overlaps with one of the ranges.
+  EXPECT_EQ(vector<Extent>(),
+            FilterExtentRanges(vector<Extent>{
+                                   ExtentForRange(10, 1),
+                                   ExtentForRange(22, 1) },
+                               ranges));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/extent_utils.cc b/update_engine/payload_generator/extent_utils.cc
new file mode 100644
index 0000000..89ccca2
--- /dev/null
+++ b/update_engine/payload_generator/extent_utils.cc
@@ -0,0 +1,166 @@
+//
+// Copyright (C) 2009 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/payload_generator/extent_utils.h"
+
+#include <inttypes.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void AppendBlockToExtents(vector<Extent>* extents, uint64_t block) {
+  // First try to extend the last extent in |extents|, if any.
+  if (!extents->empty()) {
+    Extent& extent = extents->back();
+    uint64_t next_block = extent.start_block() == kSparseHole ?
+        kSparseHole : extent.start_block() + extent.num_blocks();
+    if (next_block == block) {
+      extent.set_num_blocks(extent.num_blocks() + 1);
+      return;
+    }
+  }
+  // If unable to extend the last extent, append a new single-block extent.
+  Extent new_extent;
+  new_extent.set_start_block(block);
+  new_extent.set_num_blocks(1);
+  extents->push_back(new_extent);
+}
+
+Extent GetElement(const vector<Extent>& collection, size_t index) {
+  return collection[index];
+}
+
+Extent GetElement(
+    const google::protobuf::RepeatedPtrField<Extent>& collection,
+    size_t index) {
+  return collection.Get(index);
+}
+
+void ExtendExtents(
+    google::protobuf::RepeatedPtrField<Extent>* extents,
+    const google::protobuf::RepeatedPtrField<Extent>& extents_to_add) {
+  vector<Extent> extents_vector;
+  vector<Extent> extents_to_add_vector;
+  ExtentsToVector(*extents, &extents_vector);
+  ExtentsToVector(extents_to_add, &extents_to_add_vector);
+  extents_vector.insert(extents_vector.end(),
+                        extents_to_add_vector.begin(),
+                        extents_to_add_vector.end());
+  NormalizeExtents(&extents_vector);
+  extents->Clear();
+  StoreExtents(extents_vector, extents);
+}
+
+// Stores all Extents in 'extents' into 'out'.
+void StoreExtents(const vector<Extent>& extents,
+                  google::protobuf::RepeatedPtrField<Extent>* out) {
+  for (const Extent& extent : extents) {
+    Extent* new_extent = out->Add();
+    *new_extent = extent;
+  }
+}
+
+// Stores all extents in |extents| into |out_vector|.
+void ExtentsToVector(const google::protobuf::RepeatedPtrField<Extent>& extents,
+                     vector<Extent>* out_vector) {
+  out_vector->clear();
+  for (int i = 0; i < extents.size(); i++) {
+    out_vector->push_back(extents.Get(i));
+  }
+}
+
+string ExtentsToString(const vector<Extent>& extents) {
+  string ext_str;
+  for (const Extent& e : extents)
+    ext_str += base::StringPrintf("[%" PRIu64 ", %" PRIu64 "] ",
+                                  static_cast<uint64_t>(e.start_block()),
+                                  static_cast<uint64_t>(e.num_blocks()));
+  return ext_str;
+}
+
+void NormalizeExtents(vector<Extent>* extents) {
+  vector<Extent> new_extents;
+  for (const Extent& curr_ext : *extents) {
+    if (new_extents.empty()) {
+      new_extents.push_back(curr_ext);
+      continue;
+    }
+    Extent& last_ext = new_extents.back();
+    if (last_ext.start_block() + last_ext.num_blocks() ==
+        curr_ext.start_block()) {
+      // If the extents are touching, we want to combine them.
+      last_ext.set_num_blocks(last_ext.num_blocks() + curr_ext.num_blocks());
+    } else {
+      // Otherwise just include the extent as is.
+      new_extents.push_back(curr_ext);
+    }
+  }
+  *extents = new_extents;
+}
+
+vector<Extent> ExtentsSublist(const vector<Extent>& extents,
+                              uint64_t block_offset, uint64_t block_count) {
+  vector<Extent> result;
+  uint64_t scanned_blocks = 0;
+  if (block_count == 0)
+    return result;
+  uint64_t end_block_offset = block_offset + block_count;
+  for (const Extent& extent : extents) {
+    // The loop invariant is that if |extents| has enough blocks, there's
+    // still some extent to add to |result|. This implies that at the beginning
+    // of the loop scanned_blocks < block_offset + block_count.
+    if (scanned_blocks + extent.num_blocks() > block_offset) {
+      // This case implies that |extent| has some overlapping with the requested
+      // subsequence.
+      uint64_t new_start = extent.start_block();
+      uint64_t new_num_blocks = extent.num_blocks();
+      if (scanned_blocks + new_num_blocks > end_block_offset) {
+        // Cut the end part of the extent.
+        new_num_blocks = end_block_offset - scanned_blocks;
+      }
+      if (block_offset > scanned_blocks) {
+        // Cut the begin part of the extent.
+        new_num_blocks -= block_offset - scanned_blocks;
+        new_start += block_offset - scanned_blocks;
+      }
+      result.push_back(ExtentForRange(new_start, new_num_blocks));
+    }
+    scanned_blocks += extent.num_blocks();
+    if (scanned_blocks >= end_block_offset)
+      break;
+  }
+  return result;
+}
+
+bool operator==(const Extent& a, const Extent& b) {
+  return a.start_block() == b.start_block() && a.num_blocks() == b.num_blocks();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/extent_utils.h b/update_engine/payload_generator/extent_utils.h
new file mode 100644
index 0000000..3e45264
--- /dev/null
+++ b/update_engine/payload_generator/extent_utils.h
@@ -0,0 +1,105 @@
+//
+// 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_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/update_metadata.pb.h"
+
+// Utility functions for manipulating Extents and lists of blocks.
+
+namespace chromeos_update_engine {
+
+// |block| must either be the next block in the last extent or a block
+// in the next extent. This function will not handle inserting block
+// into an arbitrary place in the extents.
+void AppendBlockToExtents(std::vector<Extent>* extents, uint64_t block);
+
+// Get/SetElement are intentionally overloaded so that templated functions
+// can accept either type of collection of Extents.
+Extent GetElement(const std::vector<Extent>& collection, size_t index);
+Extent GetElement(
+    const google::protobuf::RepeatedPtrField<Extent>& collection,
+    size_t index);
+
+// Return the total number of blocks in a collection (vector or
+// RepeatedPtrField) of Extents.
+template<typename T>
+uint64_t BlocksInExtents(const T& collection) {
+  uint64_t ret = 0;
+  for (size_t i = 0; i < static_cast<size_t>(collection.size()); ++i) {
+    ret += GetElement(collection, i).num_blocks();
+  }
+  return ret;
+}
+
+// Takes a collection (vector or RepeatedPtrField) of Extent and
+// returns a vector of the blocks referenced, in order.
+template<typename T>
+std::vector<uint64_t> ExpandExtents(const T& extents) {
+  std::vector<uint64_t> ret;
+  for (size_t i = 0, e = static_cast<size_t>(extents.size()); i != e; ++i) {
+    const Extent extent = GetElement(extents, i);
+    if (extent.start_block() == kSparseHole) {
+      ret.resize(ret.size() + extent.num_blocks(), kSparseHole);
+    } else {
+      for (uint64_t block = extent.start_block();
+           block < (extent.start_block() + extent.num_blocks()); block++) {
+        ret.push_back(block);
+      }
+    }
+  }
+  return ret;
+}
+
+// Stores all Extents in 'extents' into 'out'.
+void StoreExtents(const std::vector<Extent>& extents,
+                  google::protobuf::RepeatedPtrField<Extent>* out);
+
+// Stores all extents in |extents| into |out_vector|.
+void ExtentsToVector(const google::protobuf::RepeatedPtrField<Extent>& extents,
+                     std::vector<Extent>* out_vector);
+
+// Returns a string representing all extents in |extents|.
+std::string ExtentsToString(const std::vector<Extent>& extents);
+
+// Takes a pointer to extents |extents| and extents |extents_to_add|, and
+// merges them by adding |extents_to_add| to |extents| and normalizing.
+void ExtendExtents(
+  google::protobuf::RepeatedPtrField<Extent>* extents,
+  const google::protobuf::RepeatedPtrField<Extent>& extents_to_add);
+
+// Takes a vector of extents and normalizes those extents. Expects the extents
+// to be sorted by start block. E.g. if |extents| is [(1, 2), (3, 5), (10, 2)]
+// then |extents| will be changed to [(1, 7), (10, 2)].
+void NormalizeExtents(std::vector<Extent>* extents);
+
+// Return a subsequence of the list of blocks passed. Both the passed list of
+// blocks |extents| and the return value are expressed as a list of Extent, not
+// blocks. The returned list skips the first |block_offset| blocks from the
+// |extents| and cotains |block_count| blocks (or less if |extents| is shorter).
+std::vector<Extent> ExtentsSublist(const std::vector<Extent>& extents,
+                                   uint64_t block_offset, uint64_t block_count);
+
+bool operator==(const Extent& a, const Extent& b);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
diff --git a/update_engine/payload_generator/extent_utils_unittest.cc b/update_engine/payload_generator/extent_utils_unittest.cc
new file mode 100644
index 0000000..d470e7b
--- /dev/null
+++ b/update_engine/payload_generator/extent_utils_unittest.cc
@@ -0,0 +1,164 @@
+//
+// 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/payload_generator/extent_utils.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ExtentUtilsTest : public ::testing::Test {};
+
+TEST(ExtentUtilsTest, AppendSparseToExtentsTest) {
+  vector<Extent> extents;
+
+  EXPECT_EQ(0U, extents.size());
+  AppendBlockToExtents(&extents, kSparseHole);
+  EXPECT_EQ(1U, extents.size());
+  AppendBlockToExtents(&extents, 0);
+  EXPECT_EQ(2U, extents.size());
+  AppendBlockToExtents(&extents, kSparseHole);
+  AppendBlockToExtents(&extents, kSparseHole);
+
+  ASSERT_EQ(3U, extents.size());
+  EXPECT_EQ(kSparseHole, extents[0].start_block());
+  EXPECT_EQ(1U, extents[0].num_blocks());
+  EXPECT_EQ(0U, extents[1].start_block());
+  EXPECT_EQ(1U, extents[1].num_blocks());
+  EXPECT_EQ(kSparseHole, extents[2].start_block());
+  EXPECT_EQ(2U, extents[2].num_blocks());
+}
+
+TEST(ExtentUtilsTest, BlocksInExtentsTest) {
+  {
+    vector<Extent> extents;
+    EXPECT_EQ(0U, BlocksInExtents(extents));
+    extents.push_back(ExtentForRange(0, 1));
+    EXPECT_EQ(1U, BlocksInExtents(extents));
+    extents.push_back(ExtentForRange(23, 55));
+    EXPECT_EQ(56U, BlocksInExtents(extents));
+    extents.push_back(ExtentForRange(1, 2));
+    EXPECT_EQ(58U, BlocksInExtents(extents));
+  }
+  {
+    google::protobuf::RepeatedPtrField<Extent> extents;
+    EXPECT_EQ(0U, BlocksInExtents(extents));
+    *extents.Add() = ExtentForRange(0, 1);
+    EXPECT_EQ(1U, BlocksInExtents(extents));
+    *extents.Add() = ExtentForRange(23, 55);
+    EXPECT_EQ(56U, BlocksInExtents(extents));
+    *extents.Add() = ExtentForRange(1, 2);
+    EXPECT_EQ(58U, BlocksInExtents(extents));
+  }
+}
+
+TEST(ExtentUtilsTest, ExtendExtentsTest) {
+  InstallOperation first_op;
+  *(first_op.add_src_extents()) = ExtentForRange(1, 1);
+  *(first_op.add_src_extents()) = ExtentForRange(3, 1);
+
+  InstallOperation second_op;
+  *(second_op.add_src_extents()) = ExtentForRange(4, 2);
+  *(second_op.add_src_extents()) = ExtentForRange(8, 2);
+
+  ExtendExtents(first_op.mutable_src_extents(), second_op.src_extents());
+  vector<Extent> first_op_vec;
+  ExtentsToVector(first_op.src_extents(), &first_op_vec);
+  EXPECT_EQ((vector<Extent>{
+      ExtentForRange(1, 1),
+      ExtentForRange(3, 3),
+      ExtentForRange(8, 2)}), first_op_vec);
+}
+
+TEST(ExtentUtilsTest, NormalizeExtentsSimpleList) {
+  // Make sure it works when there's just one extent.
+  vector<Extent> extents;
+  NormalizeExtents(&extents);
+  EXPECT_EQ(0U, extents.size());
+
+  extents = { ExtentForRange(0, 3) };
+  NormalizeExtents(&extents);
+  EXPECT_EQ(1U, extents.size());
+  EXPECT_EQ(ExtentForRange(0, 3), extents[0]);
+}
+
+TEST(ExtentUtilsTest, NormalizeExtentsTest) {
+  vector<Extent> extents = {
+      ExtentForRange(0, 3),
+      ExtentForRange(3, 2),
+      ExtentForRange(5, 1),
+      ExtentForRange(8, 4),
+      ExtentForRange(13, 1),
+      ExtentForRange(14, 2)
+  };
+  NormalizeExtents(&extents);
+  EXPECT_EQ(3U, extents.size());
+  EXPECT_EQ(ExtentForRange(0, 6), extents[0]);
+  EXPECT_EQ(ExtentForRange(8, 4), extents[1]);
+  EXPECT_EQ(ExtentForRange(13, 3), extents[2]);
+}
+
+TEST(ExtentUtilsTest, ExtentsSublistTest) {
+  vector<Extent> extents = {
+      ExtentForRange(10, 10),
+      ExtentForRange(30, 10),
+      ExtentForRange(50, 10)
+  };
+
+  // Simple empty result cases.
+  EXPECT_EQ(vector<Extent>(),
+            ExtentsSublist(extents, 1000, 20));
+  EXPECT_EQ(vector<Extent>(),
+            ExtentsSublist(extents, 5, 0));
+  EXPECT_EQ(vector<Extent>(),
+            ExtentsSublist(extents, 30, 1));
+
+  // Normal test cases.
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(13, 2) },
+            ExtentsSublist(extents, 3, 2));
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(15, 5) },
+            ExtentsSublist(extents, 5, 5));
+  EXPECT_EQ((vector<Extent>{ ExtentForRange(15, 5), ExtentForRange(30, 5) }),
+            ExtentsSublist(extents, 5, 10));
+  EXPECT_EQ((vector<Extent>{
+                 ExtentForRange(13, 7),
+                 ExtentForRange(30, 10),
+                 ExtentForRange(50, 3), }),
+            ExtentsSublist(extents, 3, 20));
+
+  // Extact match case.
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(30, 10) },
+            ExtentsSublist(extents, 10, 10));
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(50, 10) },
+            ExtentsSublist(extents, 20, 10));
+
+  // Cases where the requested num_blocks is too big.
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(53, 7) },
+            ExtentsSublist(extents, 23, 100));
+  EXPECT_EQ((vector<Extent>{ ExtentForRange(34, 6), ExtentForRange(50, 10) }),
+            ExtentsSublist(extents, 14, 100));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/fake_filesystem.cc b/update_engine/payload_generator/fake_filesystem.cc
new file mode 100644
index 0000000..234e2f6
--- /dev/null
+++ b/update_engine/payload_generator/fake_filesystem.cc
@@ -0,0 +1,60 @@
+//
+// 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/payload_generator/fake_filesystem.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+FakeFilesystem::FakeFilesystem(uint64_t block_size, uint64_t block_count) :
+    block_size_(block_size),
+    block_count_(block_count) {
+}
+
+size_t FakeFilesystem::GetBlockSize() const {
+  return block_size_;
+}
+
+size_t FakeFilesystem::GetBlockCount() const {
+  return block_count_;
+}
+
+bool FakeFilesystem::GetFiles(std::vector<File>* files) const {
+  *files = files_;
+  return true;
+}
+
+void FakeFilesystem::AddFile(const std::string& filename,
+                             const std::vector<Extent>& extents) {
+  File file;
+  file.name = filename;
+  file.extents = extents;
+  for (const Extent& extent : extents) {
+    EXPECT_LE(0U, extent.start_block());
+    EXPECT_LE(extent.start_block() + extent.num_blocks(), block_count_);
+  }
+  files_.push_back(file);
+}
+
+bool FakeFilesystem::LoadSettings(brillo::KeyValueStore* store) const {
+  if (minor_version_ < 0)
+    return false;
+  store->SetString("PAYLOAD_MINOR_VERSION", std::to_string(minor_version_));
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/fake_filesystem.h b/update_engine/payload_generator/fake_filesystem.h
new file mode 100644
index 0000000..1b13920
--- /dev/null
+++ b/update_engine/payload_generator/fake_filesystem.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_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
+
+// A fake filesystem interface implementation allowing the user to add arbitrary
+// files/metadata.
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <string>
+#include <vector>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class FakeFilesystem : public FilesystemInterface {
+ public:
+  FakeFilesystem(uint64_t block_size, uint64_t block_count);
+  virtual ~FakeFilesystem() = default;
+
+  // FilesystemInterface overrides.
+  size_t GetBlockSize() const override;
+  size_t GetBlockCount() const override;
+  bool GetFiles(std::vector<File>* files) const override;
+  bool LoadSettings(brillo::KeyValueStore* store) const override;
+
+  // Fake methods.
+
+  // Add a file to the list of fake files.
+  void AddFile(const std::string& filename, const std::vector<Extent>& extents);
+
+  // Sets the PAYLOAD_MINOR_VERSION key stored by LoadSettings(). Use a negative
+  // value to produce an error in LoadSettings().
+  void SetMinorVersion(int minor_version) {
+    minor_version_ = minor_version;
+  }
+
+ private:
+  FakeFilesystem() = default;
+
+  uint64_t block_size_;
+  uint64_t block_count_;
+  int minor_version_{-1};
+
+  std::vector<File> files_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeFilesystem);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
diff --git a/update_engine/payload_generator/filesystem_interface.h b/update_engine/payload_generator/filesystem_interface.h
new file mode 100644
index 0000000..866c46b
--- /dev/null
+++ b/update_engine/payload_generator/filesystem_interface.h
@@ -0,0 +1,95 @@
+//
+// 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_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
+
+// This class is used to abstract a filesystem and iterate the blocks
+// associated with the files and filesystem structures.
+// For the purposes of the update payload generation, a filesystem is a formated
+// partition composed by fixed-size blocks, since that's the interface used in
+// the update payload.
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/key_value_store.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class FilesystemInterface {
+ public:
+  // This represents a file or pseudo-file in the filesystem. It can include
+  // all sort of files, like symlinks, hardlinks, directories and even a file
+  // entry representing the metadata, free space, journaling data, etc.
+  struct File {
+    File() {
+      memset(&file_stat, 0, sizeof(file_stat));
+    }
+
+    // The stat struct for the file. This is invalid (inode 0) for some
+    // pseudo-files.
+    struct stat file_stat;
+
+    // The absolute path to the file inside the filesystem, for example,
+    // "/usr/bin/bash". For pseudo-files, like blocks associated to internal
+    // filesystem tables or free space, the path doesn't start with a /.
+    std::string name;
+
+    // The list of all physical blocks holding the data of this file in
+    // the same order as the logical data. All the block numbers shall be
+    // between 0 and GetBlockCount() - 1. The blocks are encoded in extents,
+    // indicating the starting block, and the number of consecutive blocks.
+    std::vector<Extent> extents;
+  };
+
+  virtual ~FilesystemInterface() = default;
+
+  // Returns the size of a block in the filesystem.
+  virtual size_t GetBlockSize() const = 0;
+
+  // Returns the number of blocks in the filesystem.
+  virtual size_t GetBlockCount() const = 0;
+
+  // Stores in |files| the list of files and pseudo-files in the filesystem. See
+  // FileInterface for details. The paths returned by this method shall not
+  // be repeated; but the same block could be present in more than one file as
+  // happens for example with hard-linked files, but not limited to those cases.
+  // Returns whether the function succeeded.
+  virtual bool GetFiles(std::vector<File>* files) const = 0;
+
+  // Load the image settings stored in the filesystem in the
+  // /etc/update_engine.conf file. Returns whether the settings were found.
+  virtual bool LoadSettings(brillo::KeyValueStore* store) const = 0;
+
+ protected:
+  FilesystemInterface() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FilesystemInterface);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
diff --git a/update_engine/payload_generator/full_update_generator.cc b/update_engine/payload_generator/full_update_generator.cc
new file mode 100644
index 0000000..8fdb6ec
--- /dev/null
+++ b/update_engine/payload_generator/full_update_generator.cc
@@ -0,0 +1,205 @@
+//
+// 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/payload_generator/full_update_generator.h"
+
+#include <fcntl.h>
+#include <inttypes.h>
+
+#include <algorithm>
+#include <deque>
+#include <memory>
+
+#include <base/format_macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/synchronization/lock.h>
+#include <base/threading/simple_thread.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const size_t kDefaultFullChunkSize = 1024 * 1024;  // 1 MiB
+
+// This class encapsulates a full update chunk processing thread work. The
+// processor reads a chunk of data from the input file descriptor and compresses
+// it. The processor will destroy itself when the work is done.
+class ChunkProcessor : public base::DelegateSimpleThread::Delegate {
+ public:
+  // Read a chunk of |size| bytes from |fd| starting at offset |offset|.
+  ChunkProcessor(const PayloadVersion& version,
+                 int fd,
+                 off_t offset,
+                 size_t size,
+                 BlobFileWriter* blob_file,
+                 AnnotatedOperation* aop)
+      : version_(version),
+        fd_(fd),
+        offset_(offset),
+        size_(size),
+        blob_file_(blob_file),
+        aop_(aop) {}
+  // We use a default move constructor since all the data members are POD types.
+  ChunkProcessor(ChunkProcessor&&) = default;
+  ~ChunkProcessor() override = default;
+
+  // Overrides DelegateSimpleThread::Delegate.
+  // Run() handles the read from |fd| in a thread-safe way, and stores the
+  // new operation to generate the region starting at |offset| of size |size|
+  // in the output operation |aop|. The associated blob data is stored in
+  // |blob_fd| and |blob_file_size| is updated.
+  void Run() override;
+
+ private:
+  bool ProcessChunk();
+
+  // Work parameters.
+  const PayloadVersion& version_;
+  int fd_;
+  off_t offset_;
+  size_t size_;
+  BlobFileWriter* blob_file_;
+  AnnotatedOperation* aop_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChunkProcessor);
+};
+
+void ChunkProcessor::Run() {
+  if (!ProcessChunk()) {
+    LOG(ERROR) << "Error processing region at " << offset_ << " of size "
+               << size_;
+  }
+}
+
+bool ChunkProcessor::ProcessChunk() {
+  brillo::Blob buffer_in_(size_);
+  brillo::Blob op_blob;
+  ssize_t bytes_read = -1;
+  TEST_AND_RETURN_FALSE(utils::PReadAll(fd_,
+                                        buffer_in_.data(),
+                                        buffer_in_.size(),
+                                        offset_,
+                                        &bytes_read));
+  TEST_AND_RETURN_FALSE(bytes_read == static_cast<ssize_t>(size_));
+
+  InstallOperation_Type op_type;
+  TEST_AND_RETURN_FALSE(diff_utils::GenerateBestFullOperation(
+      buffer_in_, version_, &op_blob, &op_type));
+
+  aop_->op.set_type(op_type);
+  TEST_AND_RETURN_FALSE(aop_->SetOperationBlob(op_blob, blob_file_));
+  return true;
+}
+
+}  // namespace
+
+bool FullUpdateGenerator::GenerateOperations(
+    const PayloadGenerationConfig& config,
+    const PartitionConfig& old_part,
+    const PartitionConfig& new_part,
+    BlobFileWriter* blob_file,
+    vector<AnnotatedOperation>* aops) {
+  TEST_AND_RETURN_FALSE(new_part.ValidateExists());
+
+  // FullUpdateGenerator requires a positive chunk_size, otherwise there will
+  // be only one operation with the whole partition which should not be allowed.
+  // For performance reasons, we force a small default hard limit of 1 MiB. This
+  // limit can be changed in the config, and we will use the smaller of the two
+  // soft/hard limits.
+  size_t full_chunk_size;
+  if (config.hard_chunk_size >= 0) {
+    full_chunk_size = std::min(static_cast<size_t>(config.hard_chunk_size),
+                               config.soft_chunk_size);
+  } else {
+    full_chunk_size = std::min(kDefaultFullChunkSize, config.soft_chunk_size);
+    LOG(INFO) << "No chunk_size provided, using the default chunk_size for the "
+              << "full operations: " << full_chunk_size << " bytes.";
+  }
+  TEST_AND_RETURN_FALSE(full_chunk_size > 0);
+  TEST_AND_RETURN_FALSE(full_chunk_size % config.block_size == 0);
+
+  size_t chunk_blocks = full_chunk_size / config.block_size;
+  size_t max_threads = std::max(sysconf(_SC_NPROCESSORS_ONLN), 4L);
+  LOG(INFO) << "Compressing partition " << new_part.name
+            << " from " << new_part.path << " splitting in chunks of "
+            << chunk_blocks << " blocks (" << config.block_size
+            << " bytes each) using " << max_threads << " threads";
+
+  int in_fd = open(new_part.path.c_str(), O_RDONLY, 0);
+  TEST_AND_RETURN_FALSE(in_fd >= 0);
+  ScopedFdCloser in_fd_closer(&in_fd);
+
+  // We potentially have all the ChunkProcessors in memory but only
+  // |max_threads| will actually hold a block in memory while we process.
+  size_t partition_blocks = new_part.size / config.block_size;
+  size_t num_chunks = (partition_blocks + chunk_blocks - 1) / chunk_blocks;
+  aops->resize(num_chunks);
+  vector<ChunkProcessor> chunk_processors;
+  chunk_processors.reserve(num_chunks);
+  blob_file->SetTotalBlobs(num_chunks);
+
+  for (size_t i = 0; i < num_chunks; ++i) {
+    size_t start_block = i * chunk_blocks;
+    // The last chunk could be smaller.
+    size_t num_blocks = std::min(chunk_blocks,
+                                 partition_blocks - i * chunk_blocks);
+
+    // Preset all the static information about the operations. The
+    // ChunkProcessor will set the rest.
+    AnnotatedOperation* aop = aops->data() + i;
+    aop->name = base::StringPrintf("<%s-operation-%" PRIuS ">",
+                                   new_part.name.c_str(), i);
+    Extent* dst_extent = aop->op.add_dst_extents();
+    dst_extent->set_start_block(start_block);
+    dst_extent->set_num_blocks(num_blocks);
+
+    chunk_processors.emplace_back(
+        config.version,
+        in_fd,
+        static_cast<off_t>(start_block) * config.block_size,
+        num_blocks * config.block_size,
+        blob_file,
+        aop);
+  }
+
+  // Thread pool used for worker threads.
+  base::DelegateSimpleThreadPool thread_pool("full-update-generator",
+                                             max_threads);
+  thread_pool.Start();
+  for (ChunkProcessor& processor : chunk_processors)
+    thread_pool.AddWork(&processor);
+  thread_pool.JoinAll();
+
+  // All the work done, disable logging.
+  blob_file->SetTotalBlobs(0);
+
+  // All the operations must have a type set at this point. Otherwise, a
+  // ChunkProcessor failed to complete.
+  for (const AnnotatedOperation& aop : *aops) {
+    if (!aop.op.has_type())
+      return false;
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/full_update_generator.h b/update_engine/payload_generator/full_update_generator.h
new file mode 100644
index 0000000..d722028
--- /dev/null
+++ b/update_engine/payload_generator/full_update_generator.h
@@ -0,0 +1,53 @@
+//
+// 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_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/payload_generator/operations_generator.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+class FullUpdateGenerator : public OperationsGenerator {
+ public:
+  FullUpdateGenerator() = default;
+
+  // OperationsGenerator override.
+  // Creates a full update for the target image defined in |config|. |config|
+  // must be a valid payload generation configuration for a full payload.
+  // Populates |aops|, with data about the update operations, and writes
+  // relevant data to |blob_file|.
+  bool GenerateOperations(
+      const PayloadGenerationConfig& config,
+      const PartitionConfig& old_part,
+      const PartitionConfig& new_part,
+      BlobFileWriter* blob_file,
+      std::vector<AnnotatedOperation>* aops) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FullUpdateGenerator);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
diff --git a/update_engine/payload_generator/full_update_generator_unittest.cc b/update_engine/payload_generator/full_update_generator_unittest.cc
new file mode 100644
index 0000000..9e62de2
--- /dev/null
+++ b/update_engine/payload_generator/full_update_generator_unittest.cc
@@ -0,0 +1,144 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_generator/full_update_generator.h"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using chromeos_update_engine::test_utils::FillWithData;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FullUpdateGeneratorTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    config_.is_delta = false;
+    config_.version.minor = kFullPayloadMinorVersion;
+    config_.hard_chunk_size = 128 * 1024;
+    config_.block_size = 4096;
+
+    EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_partition.XXXXXX",
+                                    &new_part_conf.path,
+                                    nullptr));
+    EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_blobs.XXXXXX",
+                                    &out_blobs_path_,
+                                    &out_blobs_fd_));
+
+    blob_file_.reset(new BlobFileWriter(out_blobs_fd_, &out_blobs_length_));
+    part_path_unlinker_.reset(new ScopedPathUnlinker(new_part_conf.path));
+    out_blobs_unlinker_.reset(new ScopedPathUnlinker(out_blobs_path_));
+  }
+
+  PayloadGenerationConfig config_;
+  PartitionConfig new_part_conf{"part"};
+
+  vector<AnnotatedOperation> aops;
+
+  // Output file holding the payload blobs.
+  string out_blobs_path_;
+  int out_blobs_fd_{-1};
+  off_t out_blobs_length_{0};
+  ScopedFdCloser out_blobs_fd_closer_{&out_blobs_fd_};
+
+  std::unique_ptr<BlobFileWriter> blob_file_;
+  std::unique_ptr<ScopedPathUnlinker> part_path_unlinker_;
+  std::unique_ptr<ScopedPathUnlinker> out_blobs_unlinker_;
+
+  // FullUpdateGenerator under test.
+  FullUpdateGenerator generator_;
+};
+
+TEST_F(FullUpdateGeneratorTest, RunTest) {
+  brillo::Blob new_part(9 * 1024 * 1024);
+  FillWithData(&new_part);
+  new_part_conf.size = new_part.size();
+
+  EXPECT_TRUE(test_utils::WriteFileVector(new_part_conf.path, new_part));
+
+  EXPECT_TRUE(generator_.GenerateOperations(config_,
+                                            new_part_conf,  // this is ignored
+                                            new_part_conf,
+                                            blob_file_.get(),
+                                            &aops));
+  int64_t new_part_chunks = new_part_conf.size / config_.hard_chunk_size;
+  EXPECT_EQ(new_part_chunks, static_cast<int64_t>(aops.size()));
+  for (off_t i = 0; i < new_part_chunks; ++i) {
+    EXPECT_EQ(1, aops[i].op.dst_extents_size());
+    EXPECT_EQ(
+        static_cast<uint64_t>(i * config_.hard_chunk_size / config_.block_size),
+        aops[i].op.dst_extents(0).start_block())
+        << "i = " << i;
+    EXPECT_EQ(config_.hard_chunk_size / config_.block_size,
+              aops[i].op.dst_extents(0).num_blocks());
+    if (aops[i].op.type() != InstallOperation::REPLACE) {
+      EXPECT_EQ(InstallOperation::REPLACE_BZ, aops[i].op.type());
+    }
+  }
+}
+
+// Test that if the chunk size is not a divisor of the image size, it handles
+// correctly the last chunk of the partition.
+TEST_F(FullUpdateGeneratorTest, ChunkSizeTooBig) {
+  config_.hard_chunk_size = 1024 * 1024;
+  config_.soft_chunk_size = config_.hard_chunk_size;
+  brillo::Blob new_part(1536 * 1024);  // 1.5 MiB
+  new_part_conf.size = new_part.size();
+
+  EXPECT_TRUE(test_utils::WriteFileVector(new_part_conf.path, new_part));
+
+  EXPECT_TRUE(generator_.GenerateOperations(config_,
+                                            new_part_conf,  // this is ignored
+                                            new_part_conf,
+                                            blob_file_.get(),
+                                            &aops));
+  // new_part has one chunk and a half.
+  EXPECT_EQ(2U, aops.size());
+  EXPECT_EQ(config_.hard_chunk_size / config_.block_size,
+            BlocksInExtents(aops[0].op.dst_extents()));
+  EXPECT_EQ((new_part.size() - config_.hard_chunk_size) / config_.block_size,
+            BlocksInExtents(aops[1].op.dst_extents()));
+}
+
+// Test that if the image size is much smaller than the chunk size, it handles
+// correctly the only chunk of the partition.
+TEST_F(FullUpdateGeneratorTest, ImageSizeTooSmall) {
+  brillo::Blob new_part(16 * 1024);
+  new_part_conf.size = new_part.size();
+
+  EXPECT_TRUE(test_utils::WriteFileVector(new_part_conf.path, new_part));
+
+  EXPECT_TRUE(generator_.GenerateOperations(config_,
+                                            new_part_conf,  // this is ignored
+                                            new_part_conf,
+                                            blob_file_.get(),
+                                            &aops));
+
+  // new_part has less than one chunk.
+  EXPECT_EQ(1U, aops.size());
+  EXPECT_EQ(new_part.size() / config_.block_size,
+            BlocksInExtents(aops[0].op.dst_extents()));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/generate_delta_main.cc b/update_engine/payload_generator/generate_delta_main.cc
new file mode 100644
index 0000000..99af679
--- /dev/null
+++ b/update_engine/payload_generator/generate_delta_main.cc
@@ -0,0 +1,587 @@
+//
+// 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.
+//
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <brillo/flag_helper.h>
+#include <brillo/key_value_store.h>
+
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/terminator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/payload_generator/xz.h"
+#include "update_engine/update_metadata.pb.h"
+
+// This file contains a simple program that takes an old path, a new path,
+// and an output file as arguments and the path to an output file and
+// generates a delta that can be sent to Chrome OS clients.
+
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+void ParseSignatureSizes(const string& signature_sizes_flag,
+                         vector<int>* signature_sizes) {
+  signature_sizes->clear();
+  vector<string> split_strings =
+      base::SplitString(signature_sizes_flag, ":", base::TRIM_WHITESPACE,
+                        base::SPLIT_WANT_ALL);
+  for (const string& str : split_strings) {
+    int size = 0;
+    bool parsing_successful = base::StringToInt(str, &size);
+    LOG_IF(FATAL, !parsing_successful)
+        << "Invalid signature size: " << str;
+
+    LOG_IF(FATAL, size != (2048 / 8)) <<
+        "Only signature sizes of 256 bytes are supported.";
+
+    signature_sizes->push_back(size);
+  }
+}
+
+bool ParseImageInfo(const string& channel,
+                    const string& board,
+                    const string& version,
+                    const string& key,
+                    const string& build_channel,
+                    const string& build_version,
+                    ImageInfo* image_info) {
+  // All of these arguments should be present or missing.
+  bool empty = channel.empty();
+
+  CHECK_EQ(channel.empty(), empty);
+  CHECK_EQ(board.empty(), empty);
+  CHECK_EQ(version.empty(), empty);
+  CHECK_EQ(key.empty(), empty);
+
+  if (empty)
+    return false;
+
+  image_info->set_channel(channel);
+  image_info->set_board(board);
+  image_info->set_version(version);
+  image_info->set_key(key);
+
+  image_info->set_build_channel(
+      build_channel.empty() ? channel : build_channel);
+
+  image_info->set_build_version(
+      build_version.empty() ? version : build_version);
+
+  return true;
+}
+
+void CalculateHashForSigning(const vector<int> &sizes,
+                             const string& out_hash_file,
+                             const string& out_metadata_hash_file,
+                             const string& in_file) {
+  LOG(INFO) << "Calculating hash for signing.";
+  LOG_IF(FATAL, in_file.empty())
+      << "Must pass --in_file to calculate hash for signing.";
+  LOG_IF(FATAL, out_hash_file.empty())
+      << "Must pass --out_hash_file to calculate hash for signing.";
+
+  brillo::Blob payload_hash, metadata_hash;
+  CHECK(PayloadSigner::HashPayloadForSigning(in_file, sizes, &payload_hash,
+                                             &metadata_hash));
+  CHECK(utils::WriteFile(out_hash_file.c_str(), payload_hash.data(),
+                         payload_hash.size()));
+  if (!out_metadata_hash_file.empty())
+    CHECK(utils::WriteFile(out_metadata_hash_file.c_str(), metadata_hash.data(),
+                           metadata_hash.size()));
+
+  LOG(INFO) << "Done calculating hash for signing.";
+}
+
+void SignatureFileFlagToBlobs(const string& signature_file_flag,
+                              vector<brillo::Blob>* signatures) {
+  vector<string> signature_files =
+      base::SplitString(signature_file_flag, ":", base::TRIM_WHITESPACE,
+                        base::SPLIT_WANT_ALL);
+  for (const string& signature_file : signature_files) {
+    brillo::Blob signature;
+    CHECK(utils::ReadFile(signature_file, &signature));
+    signatures->push_back(signature);
+  }
+}
+
+void SignPayload(const string& in_file,
+                 const string& out_file,
+                 const string& payload_signature_file,
+                 const string& metadata_signature_file,
+                 const string& out_metadata_size_file) {
+  LOG(INFO) << "Signing payload.";
+  LOG_IF(FATAL, in_file.empty())
+      << "Must pass --in_file to sign payload.";
+  LOG_IF(FATAL, out_file.empty())
+      << "Must pass --out_file to sign payload.";
+  LOG_IF(FATAL, payload_signature_file.empty())
+      << "Must pass --signature_file to sign payload.";
+  vector<brillo::Blob> signatures, metadata_signatures;
+  SignatureFileFlagToBlobs(payload_signature_file, &signatures);
+  SignatureFileFlagToBlobs(metadata_signature_file, &metadata_signatures);
+  uint64_t final_metadata_size;
+  CHECK(PayloadSigner::AddSignatureToPayload(in_file, signatures,
+      metadata_signatures, out_file, &final_metadata_size));
+  LOG(INFO) << "Done signing payload. Final metadata size = "
+            << final_metadata_size;
+  if (!out_metadata_size_file.empty()) {
+    string metadata_size_string = std::to_string(final_metadata_size);
+    CHECK(utils::WriteFile(out_metadata_size_file.c_str(),
+                           metadata_size_string.data(),
+                           metadata_size_string.size()));
+  }
+}
+
+void VerifySignedPayload(const string& in_file,
+                         const string& public_key) {
+  LOG(INFO) << "Verifying signed payload.";
+  LOG_IF(FATAL, in_file.empty())
+      << "Must pass --in_file to verify signed payload.";
+  LOG_IF(FATAL, public_key.empty())
+      << "Must pass --public_key to verify signed payload.";
+  CHECK(PayloadSigner::VerifySignedPayload(in_file, public_key));
+  LOG(INFO) << "Done verifying signed payload.";
+}
+
+// TODO(deymo): This function is likely broken for deltas minor version 2 or
+// newer. Move this function to a new file and make the delta_performer
+// integration tests use this instead.
+void ApplyDelta(const string& in_file,
+                const string& old_kernel,
+                const string& old_rootfs,
+                const string& prefs_dir) {
+  LOG(INFO) << "Applying delta.";
+  LOG_IF(FATAL, old_rootfs.empty())
+      << "Must pass --old_image to apply delta.";
+  Prefs prefs;
+  InstallPlan install_plan;
+  LOG(INFO) << "Setting up preferences under: " << prefs_dir;
+  LOG_IF(ERROR, !prefs.Init(base::FilePath(prefs_dir)))
+      << "Failed to initialize preferences.";
+  // Get original checksums
+  LOG(INFO) << "Calculating original checksums";
+  ImageConfig old_image;
+  old_image.partitions.emplace_back(kLegacyPartitionNameRoot);
+  old_image.partitions.back().path = old_rootfs;
+  old_image.partitions.emplace_back(kLegacyPartitionNameKernel);
+  old_image.partitions.back().path = old_kernel;
+  CHECK(old_image.LoadImageSize());
+  for (const auto& old_part : old_image.partitions) {
+    PartitionInfo part_info;
+    CHECK(diff_utils::InitializePartitionInfo(old_part, &part_info));
+    InstallPlan::Partition part;
+    part.name = old_part.name;
+    part.source_hash.assign(part_info.hash().begin(),
+                            part_info.hash().end());
+    part.source_path = old_part.path;
+    // Apply the delta in-place to the old_part.
+    part.target_path = old_part.path;
+    install_plan.partitions.push_back(part);
+  }
+
+  DeltaPerformer performer(&prefs, nullptr, nullptr, nullptr, &install_plan);
+  brillo::Blob buf(1024 * 1024);
+  int fd = open(in_file.c_str(), O_RDONLY, 0);
+  CHECK_GE(fd, 0);
+  ScopedFdCloser fd_closer(&fd);
+  for (off_t offset = 0;; offset += buf.size()) {
+    ssize_t bytes_read;
+    CHECK(utils::PReadAll(fd, buf.data(), buf.size(), offset, &bytes_read));
+    if (bytes_read == 0)
+      break;
+    CHECK_EQ(performer.Write(buf.data(), bytes_read), bytes_read);
+  }
+  CHECK_EQ(performer.Close(), 0);
+  DeltaPerformer::ResetUpdateProgress(&prefs, false);
+  LOG(INFO) << "Done applying delta.";
+}
+
+int ExtractProperties(const string& payload_path, const string& props_file) {
+  brillo::KeyValueStore properties;
+  TEST_AND_RETURN_FALSE(
+      PayloadSigner::ExtractPayloadProperties(payload_path, &properties));
+  if (props_file == "-") {
+    printf("%s", properties.SaveToString().c_str());
+  } else {
+    properties.Save(base::FilePath(props_file));
+    LOG(INFO) << "Generated properties file at " << props_file;
+  }
+  return true;
+}
+
+int Main(int argc, char** argv) {
+  DEFINE_string(old_image, "", "Path to the old rootfs");
+  DEFINE_string(new_image, "", "Path to the new rootfs");
+  DEFINE_string(old_kernel, "", "Path to the old kernel partition image");
+  DEFINE_string(new_kernel, "", "Path to the new kernel partition image");
+  DEFINE_string(old_partitions, "",
+                "Path to the old partitions. To pass multiple partitions, use "
+                "a single argument with a colon between paths, e.g. "
+                "/path/to/part:/path/to/part2::/path/to/last_part . Path can "
+                "be empty, but it has to match the order of partition_names.");
+  DEFINE_string(new_partitions, "",
+                "Path to the new partitions. To pass multiple partitions, use "
+                "a single argument with a colon between paths, e.g. "
+                "/path/to/part:/path/to/part2:/path/to/last_part . Path has "
+                "to match the order of partition_names.");
+  DEFINE_string(partition_names,
+                string(kLegacyPartitionNameRoot) + ":" +
+                kLegacyPartitionNameKernel,
+                "Names of the partitions. To pass multiple names, use a single "
+                "argument with a colon between names, e.g. "
+                "name:name2:name3:last_name . Name can not be empty, and it "
+                "has to match the order of partitions.");
+  DEFINE_string(in_file, "",
+                "Path to input delta payload file used to hash/sign payloads "
+                "and apply delta over old_image (for debugging)");
+  DEFINE_string(out_file, "", "Path to output delta payload file");
+  DEFINE_string(out_hash_file, "", "Path to output hash file");
+  DEFINE_string(out_metadata_hash_file, "",
+                "Path to output metadata hash file");
+  DEFINE_string(out_metadata_size_file, "",
+                "Path to output metadata size file");
+  DEFINE_string(private_key, "", "Path to private key in .pem format");
+  DEFINE_string(public_key, "", "Path to public key in .pem format");
+  DEFINE_int32(public_key_version, -1,
+               "DEPRECATED. Key-check version # of client");
+  DEFINE_string(prefs_dir, "/tmp/update_engine_prefs",
+                "Preferences directory, used with apply_delta");
+  DEFINE_string(signature_size, "",
+                "Raw signature size used for hash calculation. "
+                "You may pass in multiple sizes by colon separating them. E.g. "
+                "2048:2048:4096 will assume 3 signatures, the first two with "
+                "2048 size and the last 4096.");
+  DEFINE_string(signature_file, "",
+                "Raw signature file to sign payload with. To pass multiple "
+                "signatures, use a single argument with a colon between paths, "
+                "e.g. /path/to/sig:/path/to/next:/path/to/last_sig . Each "
+                "signature will be assigned a client version, starting from "
+                "kSignatureOriginalVersion.");
+  DEFINE_string(metadata_signature_file, "",
+                "Raw signature file with the signature of the metadata hash. "
+                "To pass multiple signatures, use a single argument with a "
+                "colon between paths, "
+                "e.g. /path/to/sig:/path/to/next:/path/to/last_sig .");
+  DEFINE_int32(chunk_size, 200 * 1024 * 1024,
+               "Payload chunk size (-1 for whole files)");
+  DEFINE_uint64(rootfs_partition_size,
+               chromeos_update_engine::kRootFSPartitionSize,
+               "RootFS partition size for the image once installed");
+  DEFINE_uint64(major_version, 1,
+               "The major version of the payload being generated.");
+  DEFINE_int32(minor_version, -1,
+               "The minor version of the payload being generated "
+               "(-1 means autodetect).");
+  DEFINE_string(properties_file, "",
+                "If passed, dumps the payload properties of the payload passed "
+                "in --in_file and exits.");
+  DEFINE_string(zlib_fingerprint, "",
+                "The fingerprint of zlib in the source image in hash string "
+                "format, used to check imgdiff compatibility.");
+
+  DEFINE_string(old_channel, "",
+                "The channel for the old image. 'dev-channel', 'npo-channel', "
+                "etc. Ignored, except during delta generation.");
+  DEFINE_string(old_board, "",
+                "The board for the old image. 'x86-mario', 'lumpy', "
+                "etc. Ignored, except during delta generation.");
+  DEFINE_string(old_version, "",
+                "The build version of the old image. 1.2.3, etc.");
+  DEFINE_string(old_key, "",
+                "The key used to sign the old image. 'premp', 'mp', 'mp-v3',"
+                " etc");
+  DEFINE_string(old_build_channel, "",
+                "The channel for the build of the old image. 'dev-channel', "
+                "etc, but will never contain special channels such as "
+                "'npo-channel'. Ignored, except during delta generation.");
+  DEFINE_string(old_build_version, "",
+                "The version of the build containing the old image.");
+
+  DEFINE_string(new_channel, "",
+                "The channel for the new image. 'dev-channel', 'npo-channel', "
+                "etc. Ignored, except during delta generation.");
+  DEFINE_string(new_board, "",
+                "The board for the new image. 'x86-mario', 'lumpy', "
+                "etc. Ignored, except during delta generation.");
+  DEFINE_string(new_version, "",
+                "The build version of the new image. 1.2.3, etc.");
+  DEFINE_string(new_key, "",
+                "The key used to sign the new image. 'premp', 'mp', 'mp-v3',"
+                " etc");
+  DEFINE_string(new_build_channel, "",
+                "The channel for the build of the new image. 'dev-channel', "
+                "etc, but will never contain special channels such as "
+                "'npo-channel'. Ignored, except during delta generation.");
+  DEFINE_string(new_build_version, "",
+                "The version of the build containing the new image.");
+  DEFINE_string(new_postinstall_config_file, "",
+                "A config file specifying postinstall related metadata. "
+                "Only allowed in major version 2 or newer.");
+
+  brillo::FlagHelper::Init(argc, argv,
+      "Generates a payload to provide to ChromeOS' update_engine.\n\n"
+      "This tool can create full payloads and also delta payloads if the src\n"
+      "image is provided. It also provides debugging options to apply, sign\n"
+      "and verify payloads.");
+  Terminator::Init();
+
+  logging::LoggingSettings log_settings;
+  log_settings.log_file     = "delta_generator.log";
+  log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+  log_settings.lock_log     = logging::LOCK_LOG_FILE;
+  log_settings.delete_old   = logging::APPEND_TO_OLD_LOG_FILE;
+
+  logging::InitLogging(log_settings);
+
+  // Initialize the Xz compressor.
+  XzCompressInit();
+
+  vector<int> signature_sizes;
+  ParseSignatureSizes(FLAGS_signature_size, &signature_sizes);
+
+  if (!FLAGS_out_hash_file.empty() || !FLAGS_out_metadata_hash_file.empty()) {
+    CHECK(FLAGS_out_metadata_size_file.empty());
+    CalculateHashForSigning(signature_sizes, FLAGS_out_hash_file,
+                            FLAGS_out_metadata_hash_file, FLAGS_in_file);
+    return 0;
+  }
+  if (!FLAGS_signature_file.empty()) {
+    SignPayload(FLAGS_in_file, FLAGS_out_file, FLAGS_signature_file,
+                FLAGS_metadata_signature_file, FLAGS_out_metadata_size_file);
+    return 0;
+  }
+  if (!FLAGS_public_key.empty()) {
+    LOG_IF(WARNING, FLAGS_public_key_version != -1)
+        << "--public_key_version is deprecated and ignored.";
+    VerifySignedPayload(FLAGS_in_file, FLAGS_public_key);
+    return 0;
+  }
+  if (!FLAGS_properties_file.empty()) {
+    return ExtractProperties(FLAGS_in_file, FLAGS_properties_file) ? 0 : 1;
+  }
+  if (!FLAGS_in_file.empty()) {
+    ApplyDelta(FLAGS_in_file, FLAGS_old_kernel, FLAGS_old_image,
+               FLAGS_prefs_dir);
+    return 0;
+  }
+
+  // A payload generation was requested. Convert the flags to a
+  // PayloadGenerationConfig.
+  PayloadGenerationConfig payload_config;
+  vector<string> partition_names, old_partitions, new_partitions;
+
+  partition_names =
+      base::SplitString(FLAGS_partition_names, ":", base::TRIM_WHITESPACE,
+                        base::SPLIT_WANT_ALL);
+  CHECK(!partition_names.empty());
+  if (FLAGS_major_version == kChromeOSMajorPayloadVersion ||
+      FLAGS_new_partitions.empty()) {
+    LOG_IF(FATAL, partition_names.size() != 2)
+        << "To support more than 2 partitions, please use the "
+        << "--new_partitions flag and major version 2.";
+    LOG_IF(FATAL, partition_names[0] != kLegacyPartitionNameRoot ||
+                  partition_names[1] != kLegacyPartitionNameKernel)
+        << "To support non-default partition name, please use the "
+        << "--new_partitions flag and major version 2.";
+  }
+
+  if (!FLAGS_new_partitions.empty()) {
+    LOG_IF(FATAL, !FLAGS_new_image.empty() || !FLAGS_new_kernel.empty())
+        << "--new_image and --new_kernel are deprecated, please use "
+        << "--new_partitions for all partitions.";
+    new_partitions =
+        base::SplitString(FLAGS_new_partitions, ":", base::TRIM_WHITESPACE,
+                          base::SPLIT_WANT_ALL);
+    CHECK(partition_names.size() == new_partitions.size());
+
+    payload_config.is_delta = !FLAGS_old_partitions.empty();
+    LOG_IF(FATAL, !FLAGS_old_image.empty() || !FLAGS_old_kernel.empty())
+        << "--old_image and --old_kernel are deprecated, please use "
+        << "--old_partitions if you are using --new_partitions.";
+  } else {
+    new_partitions = {FLAGS_new_image, FLAGS_new_kernel};
+    LOG(WARNING) << "--new_partitions is empty, using deprecated --new_image "
+                 << "and --new_kernel flags.";
+
+    payload_config.is_delta = !FLAGS_old_image.empty() ||
+                              !FLAGS_old_kernel.empty();
+    LOG_IF(FATAL, !FLAGS_old_partitions.empty())
+        << "Please use --new_partitions if you are using --old_partitions.";
+  }
+  for (size_t i = 0; i < partition_names.size(); i++) {
+    LOG_IF(FATAL, partition_names[i].empty())
+        << "Partition name can't be empty, see --partition_names.";
+    payload_config.target.partitions.emplace_back(partition_names[i]);
+    payload_config.target.partitions.back().path = new_partitions[i];
+  }
+
+  if (payload_config.is_delta) {
+    if (!FLAGS_old_partitions.empty()) {
+      old_partitions =
+          base::SplitString(FLAGS_old_partitions, ":", base::TRIM_WHITESPACE,
+                            base::SPLIT_WANT_ALL);
+      CHECK(old_partitions.size() == new_partitions.size());
+    } else {
+      old_partitions = {FLAGS_old_image, FLAGS_old_kernel};
+      LOG(WARNING) << "--old_partitions is empty, using deprecated --old_image "
+                   << "and --old_kernel flags.";
+    }
+    for (size_t i = 0; i < partition_names.size(); i++) {
+      payload_config.source.partitions.emplace_back(partition_names[i]);
+      payload_config.source.partitions.back().path = old_partitions[i];
+    }
+  }
+
+  if (!FLAGS_new_postinstall_config_file.empty()) {
+    LOG_IF(FATAL, FLAGS_major_version == kChromeOSMajorPayloadVersion)
+        << "Postinstall config is only allowed in major version 2 or newer.";
+    brillo::KeyValueStore store;
+    CHECK(store.Load(base::FilePath(FLAGS_new_postinstall_config_file)));
+    CHECK(payload_config.target.LoadPostInstallConfig(store));
+  }
+
+  // Use the default soft_chunk_size defined in the config.
+  payload_config.hard_chunk_size = FLAGS_chunk_size;
+  payload_config.block_size = kBlockSize;
+
+  // The partition size is never passed to the delta_generator, so we
+  // need to detect those from the provided files.
+  if (payload_config.is_delta) {
+    CHECK(payload_config.source.LoadImageSize());
+  }
+  CHECK(payload_config.target.LoadImageSize());
+
+  CHECK(!FLAGS_out_file.empty());
+
+  // Ignore failures. These are optional arguments.
+  ParseImageInfo(FLAGS_new_channel,
+                 FLAGS_new_board,
+                 FLAGS_new_version,
+                 FLAGS_new_key,
+                 FLAGS_new_build_channel,
+                 FLAGS_new_build_version,
+                 &payload_config.target.image_info);
+
+  // Ignore failures. These are optional arguments.
+  ParseImageInfo(FLAGS_old_channel,
+                 FLAGS_old_board,
+                 FLAGS_old_version,
+                 FLAGS_old_key,
+                 FLAGS_old_build_channel,
+                 FLAGS_old_build_version,
+                 &payload_config.source.image_info);
+
+  payload_config.rootfs_partition_size = FLAGS_rootfs_partition_size;
+
+  if (payload_config.is_delta) {
+    // Avoid opening the filesystem interface for full payloads.
+    for (PartitionConfig& part : payload_config.target.partitions)
+      CHECK(part.OpenFilesystem());
+    for (PartitionConfig& part : payload_config.source.partitions)
+      CHECK(part.OpenFilesystem());
+  }
+
+  payload_config.version.major = FLAGS_major_version;
+  LOG(INFO) << "Using provided major_version=" << FLAGS_major_version;
+
+  if (FLAGS_minor_version == -1) {
+    // Autodetect minor_version by looking at the update_engine.conf in the old
+    // image.
+    if (payload_config.is_delta) {
+      payload_config.version.minor = kInPlaceMinorPayloadVersion;
+      brillo::KeyValueStore store;
+      uint32_t minor_version;
+      for (const PartitionConfig& part : payload_config.source.partitions) {
+        if (part.fs_interface && part.fs_interface->LoadSettings(&store) &&
+            utils::GetMinorVersion(store, &minor_version)) {
+          payload_config.version.minor = minor_version;
+          break;
+        }
+      }
+    } else {
+      payload_config.version.minor = kFullPayloadMinorVersion;
+    }
+    LOG(INFO) << "Auto-detected minor_version=" << payload_config.version.minor;
+  } else {
+    payload_config.version.minor = FLAGS_minor_version;
+    LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version;
+  }
+
+  if (!FLAGS_zlib_fingerprint.empty()) {
+    if (utils::IsZlibCompatible(FLAGS_zlib_fingerprint)) {
+      payload_config.version.imgdiff_allowed = true;
+    } else {
+      LOG(INFO) << "IMGDIFF operation disabled due to fingerprint mismatch.";
+    }
+  }
+
+  if (payload_config.is_delta) {
+    LOG(INFO) << "Generating delta update";
+  } else {
+    LOG(INFO) << "Generating full update";
+  }
+
+  // From this point, all the options have been parsed.
+  if (!payload_config.Validate()) {
+    LOG(ERROR) << "Invalid options passed. See errors above.";
+    return 1;
+  }
+
+  uint64_t metadata_size;
+  if (!GenerateUpdatePayloadFile(payload_config,
+                                 FLAGS_out_file,
+                                 FLAGS_private_key,
+                                 &metadata_size)) {
+    return 1;
+  }
+  if (!FLAGS_out_metadata_size_file.empty()) {
+    string metadata_size_string = std::to_string(metadata_size);
+    CHECK(utils::WriteFile(FLAGS_out_metadata_size_file.c_str(),
+                           metadata_size_string.data(),
+                           metadata_size_string.size()));
+  }
+  return 0;
+}
+
+}  // namespace
+
+}  // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+  return chromeos_update_engine::Main(argc, argv);
+}
diff --git a/update_engine/payload_generator/graph_types.cc b/update_engine/payload_generator/graph_types.cc
new file mode 100644
index 0000000..7da76f7
--- /dev/null
+++ b/update_engine/payload_generator/graph_types.cc
@@ -0,0 +1,23 @@
+//
+// 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/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+const Vertex::Index Vertex::kInvalidIndex = static_cast<Vertex::Index>(-1);
+
+}  // chromeos_update_engine
diff --git a/update_engine/payload_generator/graph_types.h b/update_engine/payload_generator/graph_types.h
new file mode 100644
index 0000000..fee8575
--- /dev/null
+++ b/update_engine/payload_generator/graph_types.h
@@ -0,0 +1,90 @@
+//
+// Copyright (C) 2009 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_PAYLOAD_GENERATOR_GRAPH_TYPES_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+// A few classes that help in generating delta images use these types
+// for the graph work.
+
+namespace chromeos_update_engine {
+
+struct EdgeProperties {
+  // Read-before extents. I.e., blocks in |extents| must be read by the
+  // node pointed to before the pointing node runs (presumably b/c it
+  // overwrites these blocks).
+  std::vector<Extent> extents;
+
+  // Write before extents. I.e., blocks in |write_extents| must be written
+  // by the node pointed to before the pointing node runs (presumably
+  // b/c it reads the data written by the other node).
+  std::vector<Extent> write_extents;
+
+  bool operator==(const EdgeProperties& that) const {
+    return extents == that.extents && write_extents == that.write_extents;
+  }
+};
+
+struct Vertex {
+  Vertex() :
+      valid(true),
+      index(-1),
+      lowlink(-1) {}
+  bool valid;
+
+  typedef std::map<std::vector<Vertex>::size_type, EdgeProperties> EdgeMap;
+  EdgeMap out_edges;
+
+  // We sometimes wish to consider a subgraph of a graph. A subgraph would have
+  // a subset of the vertices from the graph and a subset of the edges.
+  // When considering this vertex within a subgraph, subgraph_edges stores
+  // the out-edges.
+  typedef std::set<std::vector<Vertex>::size_type> SubgraphEdgeMap;
+  SubgraphEdgeMap subgraph_edges;
+
+  // For Tarjan's algorithm:
+  std::vector<Vertex>::size_type index;
+  std::vector<Vertex>::size_type lowlink;
+
+  // Other Vertex properties:
+  AnnotatedOperation aop;
+
+  typedef std::vector<Vertex>::size_type Index;
+  static const Vertex::Index kInvalidIndex;
+};
+
+typedef std::vector<Vertex> Graph;
+
+typedef std::pair<Vertex::Index, Vertex::Index> Edge;
+
+const uint64_t kTempBlockStart = 1ULL << 60;
+static_assert(kTempBlockStart != 0, "kTempBlockStart invalid");
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_
diff --git a/update_engine/payload_generator/graph_utils.cc b/update_engine/payload_generator/graph_utils.cc
new file mode 100644
index 0000000..2d5fb63
--- /dev/null
+++ b/update_engine/payload_generator/graph_utils.cc
@@ -0,0 +1,141 @@
+//
+// Copyright (C) 2009 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/payload_generator/graph_utils.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+namespace graph_utils {
+
+uint64_t EdgeWeight(const Graph& graph, const Edge& edge) {
+  uint64_t weight = 0;
+  const vector<Extent>& extents =
+      graph[edge.first].out_edges.find(edge.second)->second.extents;
+  for (vector<Extent>::const_iterator it = extents.begin();
+       it != extents.end(); ++it) {
+    if (it->start_block() != kSparseHole)
+      weight += it->num_blocks();
+  }
+  return weight;
+}
+
+void AddReadBeforeDep(Vertex* src,
+                      Vertex::Index dst,
+                      uint64_t block) {
+  Vertex::EdgeMap::iterator edge_it = src->out_edges.find(dst);
+  if (edge_it == src->out_edges.end()) {
+    // Must create new edge
+    pair<Vertex::EdgeMap::iterator, bool> result =
+        src->out_edges.insert(make_pair(dst, EdgeProperties()));
+    CHECK(result.second);
+    edge_it = result.first;
+  }
+  AppendBlockToExtents(&edge_it->second.extents, block);
+}
+
+void AddReadBeforeDepExtents(Vertex* src,
+                             Vertex::Index dst,
+                             const vector<Extent>& extents) {
+  // TODO(adlr): Be more efficient than adding each block individually.
+  for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+       it != e; ++it) {
+    const Extent& extent = *it;
+    for (uint64_t block = extent.start_block(),
+             block_end = extent.start_block() + extent.num_blocks();
+         block != block_end; ++block) {
+      AddReadBeforeDep(src, dst, block);
+    }
+  }
+}
+
+void DropWriteBeforeDeps(Vertex::EdgeMap* edge_map) {
+  // Specially crafted for-loop for the map-iterate-delete dance.
+  for (Vertex::EdgeMap::iterator it = edge_map->begin();
+       it != edge_map->end(); ) {
+    if (!it->second.write_extents.empty())
+      it->second.write_extents.clear();
+    if (it->second.extents.empty()) {
+      // Erase *it, as it contains no blocks
+      edge_map->erase(it++);
+    } else {
+      ++it;
+    }
+  }
+}
+
+// For each node N in graph, drop all edges N->|index|.
+void DropIncomingEdgesTo(Graph* graph, Vertex::Index index) {
+  // This would be much more efficient if we had doubly-linked
+  // edges in the graph.
+  for (Graph::iterator it = graph->begin(), e = graph->end(); it != e; ++it) {
+    it->out_edges.erase(index);
+  }
+}
+
+namespace {
+template<typename T>
+void DumpExtents(const T& field, int prepend_space_count) {
+  string header(prepend_space_count, ' ');
+  for (int i = 0, e = field.size(); i != e; ++i) {
+    LOG(INFO) << header << "(" << GetElement(field, i).start_block() << ", "
+              << GetElement(field, i).num_blocks() << ")";
+  }
+}
+
+void DumpOutEdges(const Vertex::EdgeMap& out_edges) {
+  for (Vertex::EdgeMap::const_iterator it = out_edges.begin(),
+           e = out_edges.end(); it != e; ++it) {
+    LOG(INFO) << "    " << it->first << " read-before:";
+    DumpExtents(it->second.extents, 6);
+    LOG(INFO) << "      write-before:";
+    DumpExtents(it->second.write_extents, 6);
+  }
+}
+}  // namespace
+
+void DumpGraph(const Graph& graph) {
+  LOG(INFO) << "Graph length: " << graph.size();
+  for (Graph::size_type i = 0, e = graph.size(); i != e; ++i) {
+    LOG(INFO) << i
+              << (graph[i].valid ? "" : "-INV")
+              << ": " << graph[i].aop.name
+              << ": " << InstallOperationTypeName(graph[i].aop.op.type());
+    LOG(INFO) << "  src_extents:";
+    DumpExtents(graph[i].aop.op.src_extents(), 4);
+    LOG(INFO) << "  dst_extents:";
+    DumpExtents(graph[i].aop.op.dst_extents(), 4);
+    LOG(INFO) << "  out edges:";
+    DumpOutEdges(graph[i].out_edges);
+  }
+}
+
+}  // namespace graph_utils
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/graph_utils.h b/update_engine/payload_generator/graph_utils.h
new file mode 100644
index 0000000..b32e666
--- /dev/null
+++ b/update_engine/payload_generator/graph_utils.h
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2009 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_PAYLOAD_GENERATOR_GRAPH_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_
+
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/update_metadata.pb.h"
+
+// A few utility functions for graphs
+
+namespace chromeos_update_engine {
+
+namespace graph_utils {
+
+// Returns the number of blocks represented by all extents in the edge.
+uint64_t EdgeWeight(const Graph& graph, const Edge& edge);
+
+// These add a read-before dependency from graph[src] -> graph[dst]. If the dep
+// already exists, the block/s is/are added to the existing edge.
+void AddReadBeforeDep(Vertex* src,
+                      Vertex::Index dst,
+                      uint64_t block);
+void AddReadBeforeDepExtents(Vertex* src,
+                             Vertex::Index dst,
+                             const std::vector<Extent>& extents);
+
+void DropWriteBeforeDeps(Vertex::EdgeMap* edge_map);
+
+// For each node N in graph, drop all edges N->|index|.
+void DropIncomingEdgesTo(Graph* graph, Vertex::Index index);
+
+void DumpGraph(const Graph& graph);
+
+}  // namespace graph_utils
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_
diff --git a/update_engine/payload_generator/graph_utils_unittest.cc b/update_engine/payload_generator/graph_utils_unittest.cc
new file mode 100644
index 0000000..dddf815
--- /dev/null
+++ b/update_engine/payload_generator/graph_utils_unittest.cc
@@ -0,0 +1,95 @@
+//
+// Copyright (C) 2009 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/payload_generator/graph_utils.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::make_pair;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class GraphUtilsTest : public ::testing::Test {};
+
+TEST(GraphUtilsTest, SimpleTest) {
+  Graph graph(2);
+
+  graph[0].out_edges.insert(make_pair(1, EdgeProperties()));
+
+  vector<Extent>& extents = graph[0].out_edges[1].extents;
+
+  EXPECT_EQ(0U, extents.size());
+  AppendBlockToExtents(&extents, 0);
+  EXPECT_EQ(1U, extents.size());
+  AppendBlockToExtents(&extents, 1);
+  AppendBlockToExtents(&extents, 2);
+  EXPECT_EQ(1U, extents.size());
+  AppendBlockToExtents(&extents, 4);
+
+  EXPECT_EQ(2U, extents.size());
+  EXPECT_EQ(0U, extents[0].start_block());
+  EXPECT_EQ(3U, extents[0].num_blocks());
+  EXPECT_EQ(4U, extents[1].start_block());
+  EXPECT_EQ(1U, extents[1].num_blocks());
+
+  EXPECT_EQ(4U, graph_utils::EdgeWeight(graph, make_pair(0, 1)));
+}
+
+
+TEST(GraphUtilsTest, DepsTest) {
+  Graph graph(3);
+
+  graph_utils::AddReadBeforeDep(&graph[0], 1, 3);
+  EXPECT_EQ(1U, graph[0].out_edges.size());
+  {
+    Extent& extent = graph[0].out_edges[1].extents[0];
+    EXPECT_EQ(3U, extent.start_block());
+    EXPECT_EQ(1U, extent.num_blocks());
+  }
+  graph_utils::AddReadBeforeDep(&graph[0], 1, 4);
+  EXPECT_EQ(1U, graph[0].out_edges.size());
+  {
+    Extent& extent = graph[0].out_edges[1].extents[0];
+    EXPECT_EQ(3U, extent.start_block());
+    EXPECT_EQ(2U, extent.num_blocks());
+  }
+  graph_utils::AddReadBeforeDepExtents(&graph[2], 1,
+    vector<Extent>(1, ExtentForRange(5, 2)));
+  EXPECT_EQ(1U, graph[2].out_edges.size());
+  {
+    Extent& extent = graph[2].out_edges[1].extents[0];
+    EXPECT_EQ(5U, extent.start_block());
+    EXPECT_EQ(2U, extent.num_blocks());
+  }
+  // Change most recent edge from read-before to write-before
+  graph[2].out_edges[1].write_extents.swap(graph[2].out_edges[1].extents);
+  graph_utils::DropWriteBeforeDeps(&graph[2].out_edges);
+  EXPECT_EQ(0U, graph[2].out_edges.size());
+
+  EXPECT_EQ(1U, graph[0].out_edges.size());
+  graph_utils::DropIncomingEdgesTo(&graph, 1);
+  EXPECT_EQ(0U, graph[0].out_edges.size());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/inplace_generator.cc b/update_engine/payload_generator/inplace_generator.cc
new file mode 100644
index 0000000..bc140e8
--- /dev/null
+++ b/update_engine/payload_generator/inplace_generator.cc
@@ -0,0 +1,821 @@
+//
+// 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/payload_generator/inplace_generator.h"
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/cycle_breaker.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/payload_generator/graph_utils.h"
+#include "update_engine/payload_generator/topological_sort.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::make_pair;
+using std::map;
+using std::pair;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+using Block = InplaceGenerator::Block;
+
+namespace {
+
+// The only PayloadVersion supported by this implementation.
+const PayloadVersion kInPlacePayloadVersion{kChromeOSMajorPayloadVersion,
+                                            kInPlaceMinorPayloadVersion};
+
+// This class allocates non-existent temp blocks, starting from
+// kTempBlockStart. Other code is responsible for converting these
+// temp blocks into real blocks, as the client can't read or write to
+// these blocks.
+class DummyExtentAllocator {
+ public:
+  vector<Extent> Allocate(const uint64_t block_count) {
+    vector<Extent> ret(1);
+    ret[0].set_start_block(next_block_);
+    ret[0].set_num_blocks(block_count);
+    next_block_ += block_count;
+    return ret;
+  }
+
+ private:
+  uint64_t next_block_{kTempBlockStart};
+};
+
+// Takes a vector of blocks and returns an equivalent vector of Extent
+// objects.
+vector<Extent> CompressExtents(const vector<uint64_t>& blocks) {
+  vector<Extent> new_extents;
+  for (uint64_t block : blocks) {
+    AppendBlockToExtents(&new_extents, block);
+  }
+  return new_extents;
+}
+
+// Helper class to compare two operations by start block of the first Extent in
+// their destination extents given the index of the operations in the graph.
+class IndexedInstallOperationsDstComparator {
+ public:
+  explicit IndexedInstallOperationsDstComparator(Graph* graph)
+    : graph_(graph) {}
+
+  // Compares the operations in the vertex a and b of graph_.
+  bool operator()(size_t a, size_t b) const {
+    return diff_utils::CompareAopsByDestination((*graph_)[a].aop,
+                                                (*graph_)[b].aop);
+  }
+
+ private:
+  const Graph* const graph_;
+};
+
+}  // namespace
+
+void InplaceGenerator::CheckGraph(const Graph& graph) {
+  for (const Vertex& v : graph) {
+    CHECK(v.aop.op.has_type());
+  }
+}
+
+void InplaceGenerator::SubstituteBlocks(
+    Vertex* vertex,
+    const vector<Extent>& remove_extents,
+    const vector<Extent>& replace_extents) {
+  // First, expand out the blocks that op reads from
+  vector<uint64_t> read_blocks =
+      ExpandExtents(vertex->aop.op.src_extents());
+  {
+    // Expand remove_extents and replace_extents
+    vector<uint64_t> remove_extents_expanded = ExpandExtents(remove_extents);
+    vector<uint64_t> replace_extents_expanded = ExpandExtents(replace_extents);
+    CHECK_EQ(remove_extents_expanded.size(), replace_extents_expanded.size());
+    map<uint64_t, uint64_t> conversion;
+    for (vector<uint64_t>::size_type i = 0;
+         i < replace_extents_expanded.size(); i++) {
+      conversion[remove_extents_expanded[i]] = replace_extents_expanded[i];
+    }
+    ApplyMap(&read_blocks, conversion);
+    for (auto& edge_prop_pair : vertex->out_edges) {
+      vector<uint64_t> write_before_deps_expanded = ExpandExtents(
+          edge_prop_pair.second.write_extents);
+      ApplyMap(&write_before_deps_expanded, conversion);
+      edge_prop_pair.second.write_extents =
+          CompressExtents(write_before_deps_expanded);
+    }
+  }
+  // Convert read_blocks back to extents
+  vertex->aop.op.clear_src_extents();
+  vector<Extent> new_extents = CompressExtents(read_blocks);
+  StoreExtents(new_extents, vertex->aop.op.mutable_src_extents());
+}
+
+bool InplaceGenerator::CutEdges(Graph* graph,
+                                const set<Edge>& edges,
+                                vector<CutEdgeVertexes>* out_cuts) {
+  DummyExtentAllocator scratch_allocator;
+  vector<CutEdgeVertexes> cuts;
+  cuts.reserve(edges.size());
+
+  uint64_t scratch_blocks_used = 0;
+  for (const Edge& edge : edges) {
+    cuts.resize(cuts.size() + 1);
+    vector<Extent> old_extents =
+        (*graph)[edge.first].out_edges[edge.second].extents;
+    // Choose some scratch space
+    scratch_blocks_used += graph_utils::EdgeWeight(*graph, edge);
+    cuts.back().tmp_extents =
+        scratch_allocator.Allocate(graph_utils::EdgeWeight(*graph, edge));
+    // create vertex to copy original->scratch
+    cuts.back().new_vertex = graph->size();
+    graph->emplace_back();
+    cuts.back().old_src = edge.first;
+    cuts.back().old_dst = edge.second;
+
+    EdgeProperties& cut_edge_properties =
+        (*graph)[edge.first].out_edges.find(edge.second)->second;
+
+    // This should never happen, as we should only be cutting edges between
+    // real file nodes, and write-before relationships are created from
+    // a real file node to a temp copy node:
+    CHECK(cut_edge_properties.write_extents.empty())
+        << "Can't cut edge that has write-before relationship.";
+
+    // make node depend on the copy operation
+    (*graph)[edge.first].out_edges.insert(make_pair(graph->size() - 1,
+                                                    cut_edge_properties));
+
+    // Set src/dst extents and other proto variables for copy operation
+    graph->back().aop.op.set_type(InstallOperation::MOVE);
+    StoreExtents(cut_edge_properties.extents,
+                 graph->back().aop.op.mutable_src_extents());
+    StoreExtents(cuts.back().tmp_extents,
+                 graph->back().aop.op.mutable_dst_extents());
+    graph->back().aop.op.set_src_length(
+        graph_utils::EdgeWeight(*graph, edge) * kBlockSize);
+    graph->back().aop.op.set_dst_length(graph->back().aop.op.src_length());
+
+    // make the dest node read from the scratch space
+    SubstituteBlocks(
+        &((*graph)[edge.second]),
+        (*graph)[edge.first].out_edges[edge.second].extents,
+        cuts.back().tmp_extents);
+
+    // delete the old edge
+    CHECK_EQ(static_cast<Graph::size_type>(1),
+             (*graph)[edge.first].out_edges.erase(edge.second));
+
+    // Add an edge from dst to copy operation
+    EdgeProperties write_before_edge_properties;
+    write_before_edge_properties.write_extents = cuts.back().tmp_extents;
+    (*graph)[edge.second].out_edges.insert(
+        make_pair(graph->size() - 1, write_before_edge_properties));
+  }
+  out_cuts->swap(cuts);
+  return true;
+}
+
+// Creates all the edges for the graph. Writers of a block point to
+// readers of the same block. This is because for an edge A->B, B
+// must complete before A executes.
+void InplaceGenerator::CreateEdges(
+    Graph* graph,
+    const vector<Block>& blocks) {
+  for (vector<Block>::size_type i = 0;
+       i < blocks.size(); i++) {
+    // Blocks with both a reader and writer get an edge
+    if (blocks[i].reader == Vertex::kInvalidIndex ||
+        blocks[i].writer == Vertex::kInvalidIndex)
+      continue;
+    // Don't have a node depend on itself
+    if (blocks[i].reader == blocks[i].writer)
+      continue;
+    // See if there's already an edge we can add onto
+    Vertex::EdgeMap::iterator edge_it =
+        (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader);
+    if (edge_it == (*graph)[blocks[i].writer].out_edges.end()) {
+      // No existing edge. Create one
+      (*graph)[blocks[i].writer].out_edges.insert(
+          make_pair(blocks[i].reader, EdgeProperties()));
+      edge_it = (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader);
+      CHECK(edge_it != (*graph)[blocks[i].writer].out_edges.end());
+    }
+    AppendBlockToExtents(&edge_it->second.extents, i);
+  }
+}
+
+namespace {
+
+class SortCutsByTopoOrderLess {
+ public:
+  explicit SortCutsByTopoOrderLess(
+      const vector<vector<Vertex::Index>::size_type>& table)
+      : table_(table) {}
+  bool operator()(const CutEdgeVertexes& a, const CutEdgeVertexes& b) {
+    return table_[a.old_dst] < table_[b.old_dst];
+  }
+ private:
+  const vector<vector<Vertex::Index>::size_type>& table_;
+};
+
+}  // namespace
+
+void InplaceGenerator::GenerateReverseTopoOrderMap(
+    const vector<Vertex::Index>& op_indexes,
+    vector<vector<Vertex::Index>::size_type>* reverse_op_indexes) {
+  vector<vector<Vertex::Index>::size_type> table(op_indexes.size());
+  for (vector<Vertex::Index>::size_type i = 0, e = op_indexes.size();
+       i != e; ++i) {
+    Vertex::Index node = op_indexes[i];
+    if (table.size() < (node + 1)) {
+      table.resize(node + 1);
+    }
+    table[node] = i;
+  }
+  reverse_op_indexes->swap(table);
+}
+
+void InplaceGenerator::SortCutsByTopoOrder(
+    const vector<Vertex::Index>& op_indexes,
+    vector<CutEdgeVertexes>* cuts) {
+  // first, make a reverse lookup table.
+  vector<vector<Vertex::Index>::size_type> table;
+  GenerateReverseTopoOrderMap(op_indexes, &table);
+  SortCutsByTopoOrderLess less(table);
+  sort(cuts->begin(), cuts->end(), less);
+}
+
+void InplaceGenerator::MoveAndSortFullOpsToBack(
+    Graph* graph,
+    vector<Vertex::Index>* op_indexes) {
+  vector<Vertex::Index> ret;
+  vector<Vertex::Index> full_ops;
+  ret.reserve(op_indexes->size());
+  for (auto op_index : *op_indexes) {
+    InstallOperation_Type type = (*graph)[op_index].aop.op.type();
+    if (type == InstallOperation::REPLACE ||
+        type == InstallOperation::REPLACE_BZ) {
+      full_ops.push_back(op_index);
+    } else {
+      ret.push_back(op_index);
+    }
+  }
+  LOG(INFO) << "Stats: " << full_ops.size() << " full ops out of "
+            << (full_ops.size() + ret.size()) << " total ops.";
+  // Sort full ops according to their dst_extents.
+  sort(full_ops.begin(), full_ops.end(),
+       IndexedInstallOperationsDstComparator(graph));
+  ret.insert(ret.end(), full_ops.begin(), full_ops.end());
+  op_indexes->swap(ret);
+}
+
+namespace {
+
+template<typename T>
+bool TempBlocksExistInExtents(const T& extents) {
+  for (int i = 0, e = extents.size(); i < e; ++i) {
+    Extent extent = GetElement(extents, i);
+    uint64_t start = extent.start_block();
+    uint64_t num = extent.num_blocks();
+    if (start >= kTempBlockStart || (start + num) >= kTempBlockStart) {
+      LOG(ERROR) << "temp block!";
+      LOG(ERROR) << "start: " << start << ", num: " << num;
+      LOG(ERROR) << "kTempBlockStart: " << kTempBlockStart;
+      LOG(ERROR) << "returning true";
+      return true;
+    }
+    // check for wrap-around, which would be a bug:
+    CHECK(start <= (start + num));
+  }
+  return false;
+}
+
+// Converts the cuts, which must all have the same |old_dst| member,
+// to full. It does this by converting the |old_dst| to REPLACE or
+// REPLACE_BZ, dropping all incoming edges to |old_dst|, and marking
+// all temp nodes invalid.
+bool ConvertCutsToFull(
+    Graph* graph,
+    const string& new_part,
+    BlobFileWriter* blob_file,
+    vector<Vertex::Index>* op_indexes,
+    vector<vector<Vertex::Index>::size_type>* reverse_op_indexes,
+    const vector<CutEdgeVertexes>& cuts) {
+  CHECK(!cuts.empty());
+  set<Vertex::Index> deleted_nodes;
+  for (const CutEdgeVertexes& cut : cuts) {
+    TEST_AND_RETURN_FALSE(InplaceGenerator::ConvertCutToFullOp(
+        graph,
+        cut,
+        new_part,
+        blob_file));
+    deleted_nodes.insert(cut.new_vertex);
+  }
+  deleted_nodes.insert(cuts[0].old_dst);
+
+  vector<Vertex::Index> new_op_indexes;
+  new_op_indexes.reserve(op_indexes->size());
+  for (Vertex::Index vertex_index : *op_indexes) {
+    if (utils::SetContainsKey(deleted_nodes, vertex_index))
+      continue;
+    new_op_indexes.push_back(vertex_index);
+  }
+  new_op_indexes.push_back(cuts[0].old_dst);
+  op_indexes->swap(new_op_indexes);
+  InplaceGenerator::GenerateReverseTopoOrderMap(*op_indexes,
+                                                reverse_op_indexes);
+  return true;
+}
+
+// Tries to assign temp blocks for a collection of cuts, all of which share
+// the same old_dst member. If temp blocks can't be found, old_dst will be
+// converted to a REPLACE or REPLACE_BZ operation. Returns true on success,
+// which can happen even if blocks are converted to full. Returns false
+// on exceptional error cases.
+bool AssignBlockForAdjoiningCuts(
+    Graph* graph,
+    const string& new_part,
+    BlobFileWriter* blob_file,
+    vector<Vertex::Index>* op_indexes,
+    vector<vector<Vertex::Index>::size_type>* reverse_op_indexes,
+    const vector<CutEdgeVertexes>& cuts) {
+  CHECK(!cuts.empty());
+  const Vertex::Index old_dst = cuts[0].old_dst;
+  // Calculate # of blocks needed
+  uint64_t blocks_needed = 0;
+  vector<uint64_t> cuts_blocks_needed(cuts.size());
+  for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) {
+    uint64_t cut_blocks_needed = 0;
+    for (const Extent& extent : cuts[i].tmp_extents) {
+      cut_blocks_needed += extent.num_blocks();
+    }
+    blocks_needed += cut_blocks_needed;
+    cuts_blocks_needed[i] = cut_blocks_needed;
+  }
+
+  // Find enough blocks
+  ExtentRanges scratch_ranges;
+  // Each block that's supplying temp blocks and the corresponding blocks:
+  typedef vector<pair<Vertex::Index, ExtentRanges>> SupplierVector;
+  SupplierVector block_suppliers;
+  uint64_t scratch_blocks_found = 0;
+  for (vector<Vertex::Index>::size_type i = (*reverse_op_indexes)[old_dst] + 1,
+           e = op_indexes->size(); i < e; ++i) {
+    Vertex::Index test_node = (*op_indexes)[i];
+    if (!(*graph)[test_node].valid)
+      continue;
+    // See if this node has sufficient blocks
+    ExtentRanges ranges;
+    ranges.AddRepeatedExtents((*graph)[test_node].aop.op.dst_extents());
+    ranges.SubtractExtent(ExtentForRange(
+        kTempBlockStart, kSparseHole - kTempBlockStart));
+    ranges.SubtractRepeatedExtents((*graph)[test_node].aop.op.src_extents());
+    // For now, for simplicity, subtract out all blocks in read-before
+    // dependencies.
+    for (Vertex::EdgeMap::const_iterator edge_i =
+             (*graph)[test_node].out_edges.begin(),
+             edge_e = (*graph)[test_node].out_edges.end();
+         edge_i != edge_e; ++edge_i) {
+      ranges.SubtractExtents(edge_i->second.extents);
+    }
+
+    // Prevent using the block 0 as scratch space due to crbug.com/480751.
+    if (ranges.ContainsBlock(0)) {
+      LOG(INFO) << "Removing block 0 from the selected scratch range in vertex "
+                << i;
+      ranges.SubtractBlock(0);
+    }
+
+    if (ranges.blocks() == 0)
+      continue;
+
+    if (ranges.blocks() + scratch_blocks_found > blocks_needed) {
+      // trim down ranges
+      vector<Extent> new_ranges = ranges.GetExtentsForBlockCount(
+          blocks_needed - scratch_blocks_found);
+      ranges = ExtentRanges();
+      ranges.AddExtents(new_ranges);
+    }
+    scratch_ranges.AddRanges(ranges);
+    block_suppliers.push_back(make_pair(test_node, ranges));
+    scratch_blocks_found += ranges.blocks();
+    if (scratch_ranges.blocks() >= blocks_needed)
+      break;
+  }
+  if (scratch_ranges.blocks() < blocks_needed) {
+    LOG(INFO) << "Unable to find sufficient scratch";
+    TEST_AND_RETURN_FALSE(ConvertCutsToFull(graph,
+                                            new_part,
+                                            blob_file,
+                                            op_indexes,
+                                            reverse_op_indexes,
+                                            cuts));
+    return true;
+  }
+  // Use the scratch we found
+  TEST_AND_RETURN_FALSE(scratch_ranges.blocks() == scratch_blocks_found);
+
+  // Make all the suppliers depend on this node
+  for (const auto& index_range_pair : block_suppliers) {
+    graph_utils::AddReadBeforeDepExtents(
+        &(*graph)[index_range_pair.first],
+        old_dst,
+        index_range_pair.second.GetExtentsForBlockCount(
+            index_range_pair.second.blocks()));
+  }
+
+  // Replace temp blocks in each cut
+  for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) {
+    const CutEdgeVertexes& cut = cuts[i];
+    vector<Extent> real_extents =
+        scratch_ranges.GetExtentsForBlockCount(cuts_blocks_needed[i]);
+    scratch_ranges.SubtractExtents(real_extents);
+
+    // Fix the old dest node w/ the real blocks
+    InplaceGenerator::SubstituteBlocks(&(*graph)[old_dst],
+                                         cut.tmp_extents,
+                                         real_extents);
+
+    // Fix the new node w/ the real blocks. Since the new node is just a
+    // copy operation, we can replace all the dest extents w/ the real
+    // blocks.
+    InstallOperation* op = &(*graph)[cut.new_vertex].aop.op;
+    op->clear_dst_extents();
+    StoreExtents(real_extents, op->mutable_dst_extents());
+  }
+  return true;
+}
+
+}  // namespace
+
+bool InplaceGenerator::AssignTempBlocks(
+    Graph* graph,
+    const string& new_part,
+    BlobFileWriter* blob_file,
+    vector<Vertex::Index>* op_indexes,
+    vector<vector<Vertex::Index>::size_type>* reverse_op_indexes,
+    const vector<CutEdgeVertexes>& cuts) {
+  CHECK(!cuts.empty());
+
+  // group of cuts w/ the same old_dst:
+  vector<CutEdgeVertexes> cuts_group;
+
+  for (vector<CutEdgeVertexes>::size_type i = cuts.size() - 1, e = 0;
+       true ; --i) {
+    LOG(INFO) << "Fixing temp blocks in cut " << i
+              << ": old dst: " << cuts[i].old_dst << " new vertex: "
+              << cuts[i].new_vertex << " path: "
+              << (*graph)[cuts[i].old_dst].aop.name;
+
+    if (cuts_group.empty() || (cuts_group[0].old_dst == cuts[i].old_dst)) {
+      cuts_group.push_back(cuts[i]);
+    } else {
+      CHECK(!cuts_group.empty());
+      TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts(graph,
+                                                        new_part,
+                                                        blob_file,
+                                                        op_indexes,
+                                                        reverse_op_indexes,
+                                                        cuts_group));
+      cuts_group.clear();
+      cuts_group.push_back(cuts[i]);
+    }
+
+    if (i == e) {
+      // break out of for() loop
+      break;
+    }
+  }
+  CHECK(!cuts_group.empty());
+  TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts(graph,
+                                                    new_part,
+                                                    blob_file,
+                                                    op_indexes,
+                                                    reverse_op_indexes,
+                                                    cuts_group));
+  return true;
+}
+
+bool InplaceGenerator::NoTempBlocksRemain(const Graph& graph) {
+  size_t idx = 0;
+  for (Graph::const_iterator it = graph.begin(), e = graph.end(); it != e;
+       ++it, ++idx) {
+    if (!it->valid)
+      continue;
+    const InstallOperation& op = it->aop.op;
+    if (TempBlocksExistInExtents(op.dst_extents()) ||
+        TempBlocksExistInExtents(op.src_extents())) {
+      LOG(INFO) << "bad extents in node " << idx;
+      LOG(INFO) << "so yeah";
+      return false;
+    }
+
+    // Check out-edges:
+    for (const auto& edge_prop_pair : it->out_edges) {
+      if (TempBlocksExistInExtents(edge_prop_pair.second.extents) ||
+          TempBlocksExistInExtents(edge_prop_pair.second.write_extents)) {
+        LOG(INFO) << "bad out edge in node " << idx;
+        LOG(INFO) << "so yeah";
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool InplaceGenerator::ConvertCutToFullOp(Graph* graph,
+                                          const CutEdgeVertexes& cut,
+                                          const string& new_part,
+                                          BlobFileWriter* blob_file) {
+  // Drop all incoming edges, keep all outgoing edges
+
+  // Keep all outgoing edges
+  if ((*graph)[cut.old_dst].aop.op.type() != InstallOperation::REPLACE_BZ &&
+      (*graph)[cut.old_dst].aop.op.type() != InstallOperation::REPLACE) {
+    Vertex::EdgeMap out_edges = (*graph)[cut.old_dst].out_edges;
+    graph_utils::DropWriteBeforeDeps(&out_edges);
+
+    // Replace the operation with a REPLACE or REPLACE_BZ to generate the same
+    // |new_extents| list of blocks and update the graph.
+    vector<AnnotatedOperation> new_aop;
+    vector<Extent> new_extents;
+    ExtentsToVector((*graph)[cut.old_dst].aop.op.dst_extents(),
+                    &new_extents);
+    TEST_AND_RETURN_FALSE(diff_utils::DeltaReadFile(
+        &new_aop,
+        "",  // old_part
+        new_part,
+        vector<Extent>(),  // old_extents
+        new_extents,
+        (*graph)[cut.old_dst].aop.name,
+        -1,  // chunk_blocks, forces to have a single operation.
+        kInPlacePayloadVersion,
+        blob_file));
+    TEST_AND_RETURN_FALSE(new_aop.size() == 1);
+    TEST_AND_RETURN_FALSE(AddInstallOpToGraph(
+      graph, cut.old_dst, nullptr, new_aop.front().op, new_aop.front().name));
+
+    (*graph)[cut.old_dst].out_edges = out_edges;
+
+    // Right now we don't have doubly-linked edges, so we have to scan
+    // the whole graph.
+    graph_utils::DropIncomingEdgesTo(graph, cut.old_dst);
+  }
+
+  // Delete temp node
+  (*graph)[cut.old_src].out_edges.erase(cut.new_vertex);
+  CHECK((*graph)[cut.old_dst].out_edges.find(cut.new_vertex) ==
+        (*graph)[cut.old_dst].out_edges.end());
+  (*graph)[cut.new_vertex].valid = false;
+  LOG(INFO) << "marked node invalid: " << cut.new_vertex;
+  return true;
+}
+
+bool InplaceGenerator::ConvertGraphToDag(Graph* graph,
+                                         const string& new_part,
+                                         BlobFileWriter* blob_file,
+                                         vector<Vertex::Index>* final_order,
+                                         Vertex::Index scratch_vertex) {
+  CycleBreaker cycle_breaker;
+  LOG(INFO) << "Finding cycles...";
+  set<Edge> cut_edges;
+  cycle_breaker.BreakCycles(*graph, &cut_edges);
+  LOG(INFO) << "done finding cycles";
+  CheckGraph(*graph);
+
+  // Calculate number of scratch blocks needed
+
+  LOG(INFO) << "Cutting cycles...";
+  vector<CutEdgeVertexes> cuts;
+  TEST_AND_RETURN_FALSE(CutEdges(graph, cut_edges, &cuts));
+  LOG(INFO) << "done cutting cycles";
+  LOG(INFO) << "There are " << cuts.size() << " cuts.";
+  CheckGraph(*graph);
+
+  LOG(INFO) << "Creating initial topological order...";
+  TopologicalSort(*graph, final_order);
+  LOG(INFO) << "done with initial topo order";
+  CheckGraph(*graph);
+
+  LOG(INFO) << "Moving full ops to the back";
+  MoveAndSortFullOpsToBack(graph, final_order);
+  LOG(INFO) << "done moving full ops to back";
+
+  vector<vector<Vertex::Index>::size_type> inverse_final_order;
+  GenerateReverseTopoOrderMap(*final_order, &inverse_final_order);
+
+  SortCutsByTopoOrder(*final_order, &cuts);
+
+  if (!cuts.empty())
+    TEST_AND_RETURN_FALSE(AssignTempBlocks(graph,
+                                           new_part,
+                                           blob_file,
+                                           final_order,
+                                           &inverse_final_order,
+                                           cuts));
+  LOG(INFO) << "Making sure all temp blocks have been allocated";
+
+  // Remove the scratch node, if any
+  if (scratch_vertex != Vertex::kInvalidIndex) {
+    final_order->erase(final_order->begin() +
+                       inverse_final_order[scratch_vertex]);
+    (*graph)[scratch_vertex].valid = false;
+    GenerateReverseTopoOrderMap(*final_order, &inverse_final_order);
+  }
+
+  graph_utils::DumpGraph(*graph);
+  CHECK(NoTempBlocksRemain(*graph));
+  LOG(INFO) << "done making sure all temp blocks are allocated";
+  return true;
+}
+
+void InplaceGenerator::CreateScratchNode(uint64_t start_block,
+                                         uint64_t num_blocks,
+                                         Vertex* vertex) {
+  vertex->aop.name = "<scratch>";
+  vertex->aop.op.set_type(InstallOperation::REPLACE_BZ);
+  vertex->aop.op.set_data_offset(0);
+  vertex->aop.op.set_data_length(0);
+  Extent* extent = vertex->aop.op.add_dst_extents();
+  extent->set_start_block(start_block);
+  extent->set_num_blocks(num_blocks);
+}
+
+bool InplaceGenerator::AddInstallOpToBlocksVector(
+    const InstallOperation& operation,
+    const Graph& graph,
+    Vertex::Index vertex,
+    vector<Block>* blocks) {
+  // See if this is already present.
+  TEST_AND_RETURN_FALSE(operation.dst_extents_size() > 0);
+
+  enum BlockField { READER = 0, WRITER, BLOCK_FIELD_COUNT };
+  for (int field = READER; field < BLOCK_FIELD_COUNT; field++) {
+    const char* past_participle = (field == READER) ? "read" : "written";
+    const google::protobuf::RepeatedPtrField<Extent>& extents =
+        (field == READER) ? operation.src_extents() : operation.dst_extents();
+    Vertex::Index Block::*access_type = (field == READER) ?
+        &Block::reader : &Block::writer;
+
+    for (const Extent& extent : extents) {
+      for (uint64_t block = extent.start_block();
+           block < (extent.start_block() + extent.num_blocks()); block++) {
+        if ((*blocks)[block].*access_type != Vertex::kInvalidIndex) {
+          LOG(FATAL) << "Block " << block << " is already "
+                     << past_participle << " by "
+                     << (*blocks)[block].*access_type << "("
+                     << graph[(*blocks)[block].*access_type].aop.name
+                     << ") and also " << vertex << "("
+                     << graph[vertex].aop.name << ")";
+        }
+        (*blocks)[block].*access_type = vertex;
+      }
+    }
+  }
+  return true;
+}
+
+bool InplaceGenerator::AddInstallOpToGraph(Graph* graph,
+                                           Vertex::Index existing_vertex,
+                                           vector<Block>* blocks,
+                                           const InstallOperation& operation,
+                                           const string& op_name) {
+  Vertex::Index vertex = existing_vertex;
+  if (vertex == Vertex::kInvalidIndex) {
+    graph->emplace_back();
+    vertex = graph->size() - 1;
+  }
+  (*graph)[vertex].aop.op = operation;
+  CHECK((*graph)[vertex].aop.op.has_type());
+  (*graph)[vertex].aop.name = op_name;
+
+  if (blocks)
+    TEST_AND_RETURN_FALSE(InplaceGenerator::AddInstallOpToBlocksVector(
+        (*graph)[vertex].aop.op,
+        *graph,
+        vertex,
+        blocks));
+  return true;
+}
+
+void InplaceGenerator::ApplyMap(vector<uint64_t>* collection,
+                                const map<uint64_t, uint64_t>& the_map) {
+  for (uint64_t& elem : *collection) {
+    const auto& map_it = the_map.find(elem);
+    if (map_it != the_map.end())
+      elem = map_it->second;
+  }
+}
+
+bool InplaceGenerator::ResolveReadAfterWriteDependencies(
+    const PartitionConfig& old_part,
+    const PartitionConfig& new_part,
+    uint64_t partition_size,
+    size_t block_size,
+    BlobFileWriter* blob_file,
+    vector<AnnotatedOperation>* aops) {
+  // Convert the operations to the graph.
+  Graph graph;
+  CheckGraph(graph);
+  vector<Block> blocks(std::max(old_part.size, new_part.size) / block_size);
+  for (const auto& aop : *aops) {
+    AddInstallOpToGraph(
+        &graph, Vertex::kInvalidIndex, &blocks, aop.op, aop.name);
+  }
+  CheckGraph(graph);
+
+  // Final scratch block (if there's space)
+  Vertex::Index scratch_vertex = Vertex::kInvalidIndex;
+  if (blocks.size() < (partition_size / block_size)) {
+    scratch_vertex = graph.size();
+    graph.emplace_back();
+    size_t scratch_blocks = (partition_size / block_size) - blocks.size();
+    LOG(INFO) << "Added " << scratch_blocks << " scratch space blocks.";
+    CreateScratchNode(blocks.size(), scratch_blocks, &graph.back());
+  }
+  CheckGraph(graph);
+
+  LOG(INFO) << "Creating edges...";
+  CreateEdges(&graph, blocks);
+  LOG(INFO) << "Done creating edges";
+  CheckGraph(graph);
+
+  vector<Vertex::Index> final_order;
+  TEST_AND_RETURN_FALSE(ConvertGraphToDag(
+      &graph,
+      new_part.path,
+      blob_file,
+      &final_order,
+      scratch_vertex));
+
+  // Copy operations over to the |aops| vector in the final_order generated by
+  // the topological sort.
+  aops->clear();
+  aops->reserve(final_order.size());
+  for (const Vertex::Index vertex_index : final_order) {
+    const Vertex& vertex = graph[vertex_index];
+    aops->push_back(vertex.aop);
+  }
+  return true;
+}
+
+bool InplaceGenerator::GenerateOperations(
+    const PayloadGenerationConfig& config,
+    const PartitionConfig& old_part,
+    const PartitionConfig& new_part,
+    BlobFileWriter* blob_file,
+    vector<AnnotatedOperation>* aops) {
+  TEST_AND_RETURN_FALSE(old_part.name == new_part.name);
+  TEST_AND_RETURN_FALSE(config.version.major == kInPlacePayloadVersion.major);
+  TEST_AND_RETURN_FALSE(config.version.minor == kInPlacePayloadVersion.minor);
+
+  ssize_t hard_chunk_blocks = (config.hard_chunk_size == -1 ? -1 :
+                               config.hard_chunk_size / config.block_size);
+  size_t soft_chunk_blocks = config.soft_chunk_size / config.block_size;
+  uint64_t partition_size = new_part.size;
+  if (new_part.name == kLegacyPartitionNameRoot)
+    partition_size = config.rootfs_partition_size;
+
+  LOG(INFO) << "Delta compressing " << new_part.name << " partition...";
+  TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(aops,
+                                                       old_part,
+                                                       new_part,
+                                                       hard_chunk_blocks,
+                                                       soft_chunk_blocks,
+                                                       config.version,
+                                                       blob_file));
+  LOG(INFO) << "Done reading " << new_part.name;
+
+  TEST_AND_RETURN_FALSE(ResolveReadAfterWriteDependencies(
+      old_part, new_part, partition_size, config.block_size, blob_file, aops));
+  LOG(INFO) << "Done reordering " << new_part.name;
+  return true;
+}
+
+};  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/inplace_generator.h b/update_engine/payload_generator/inplace_generator.h
new file mode 100644
index 0000000..f108639
--- /dev/null
+++ b/update_engine/payload_generator/inplace_generator.h
@@ -0,0 +1,243 @@
+//
+// 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_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/payload_generator/operations_generator.h"
+
+// InplaceGenerator contains all functionality related to the inplace algorithm
+// for generating update payloads. These are the functions used when delta minor
+// version is 1.
+
+namespace chromeos_update_engine {
+
+// This struct stores all relevant info for an edge that is cut between
+// nodes old_src -> old_dst by creating new vertex new_vertex. The new
+// relationship is:
+// old_src -(read before)-> new_vertex <-(write before)- old_dst
+// new_vertex is a MOVE operation that moves some existing blocks into
+// temp space. The temp extents are, by necessity, stored in new_vertex
+// (as dst extents) and old_dst (as src extents), but they are also broken
+// out into tmp_extents, as the nodes themselves may contain many more
+// extents.
+struct CutEdgeVertexes {
+  Vertex::Index new_vertex;
+  Vertex::Index old_src;
+  Vertex::Index old_dst;
+  std::vector<Extent> tmp_extents;
+};
+
+class InplaceGenerator : public OperationsGenerator {
+ public:
+  // Represents a disk block on the install partition.
+  struct Block {
+    // During install, each block on the install partition will be written
+    // and some may be read (in all likelihood, many will be read).
+    // The reading and writing will be performed by InstallOperations,
+    // each of which has a corresponding vertex in a graph.
+    // A Block object tells which vertex will read or write this block
+    // at install time.
+    // Generally, there will be a vector of Block objects whose length
+    // is the number of blocks on the install partition.
+    Block() : reader(Vertex::kInvalidIndex), writer(Vertex::kInvalidIndex) {}
+    Vertex::Index reader;
+    Vertex::Index writer;
+  };
+
+  InplaceGenerator() = default;
+
+  // Checks all the operations in the graph have a type assigned.
+  static void CheckGraph(const Graph& graph);
+
+  // Modifies blocks read by 'op' so that any blocks referred to by
+  // 'remove_extents' are replaced with blocks from 'replace_extents'.
+  // 'remove_extents' and 'replace_extents' must be the same number of blocks.
+  // Blocks will be substituted in the order listed in the vectors.
+  // E.g. if 'op' reads blocks 1, 2, 3, 4, 5, 6, 7, 8, remove_extents
+  // contains blocks 6, 2, 3, 5, and replace blocks contains
+  // 12, 13, 14, 15, then op will be changed to read from:
+  // 1, 13, 14, 4, 15, 12, 7, 8
+  static void SubstituteBlocks(Vertex* vertex,
+                               const std::vector<Extent>& remove_extents,
+                               const std::vector<Extent>& replace_extents);
+
+  // Cuts 'edges' from 'graph' according to the AU algorithm. This means
+  // for each edge A->B, remove the dependency that B occur before A.
+  // Do this by creating a new operation X that copies from the blocks
+  // specified by the edge's properties to temp space T. Modify B to read
+  // from T rather than the blocks in the edge. Modify A to depend on X,
+  // but not on B. Free space is found by looking in 'blocks'.
+  // Returns true on success.
+  static bool CutEdges(Graph* graph,
+                       const std::set<Edge>& edges,
+                       std::vector<CutEdgeVertexes>* out_cuts);
+
+  // Creates all the edges for the graph. Writers of a block point to
+  // readers of the same block. This is because for an edge A->B, B
+  // must complete before A executes.
+  static void CreateEdges(Graph* graph,
+                          const std::vector<Block>& blocks);
+
+  // Takes |op_indexes|, which is effectively a mapping from order in
+  // which the op is performed -> graph vertex index, and produces the
+  // reverse: a mapping from graph vertex index -> op_indexes index.
+  static void GenerateReverseTopoOrderMap(
+      const std::vector<Vertex::Index>& op_indexes,
+      std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes);
+
+  // Sorts the vector |cuts| by its |cuts[].old_dest| member. Order is
+  // determined by the order of elements in op_indexes.
+  static void SortCutsByTopoOrder(
+      const std::vector<Vertex::Index>& op_indexes,
+      std::vector<CutEdgeVertexes>* cuts);
+
+  // Given a topologically sorted graph |op_indexes| and |graph|, alters
+  // |op_indexes| to move all the full operations to the end of the vector.
+  // Full operations should not be depended on, so this is safe.
+  static void MoveAndSortFullOpsToBack(Graph* graph,
+                                std::vector<Vertex::Index>* op_indexes);
+
+  // Returns true iff there are no extents in the graph that refer to temp
+  // blocks. Temp blocks are in the range [kTempBlockStart, kSparseHole).
+  static bool NoTempBlocksRemain(const Graph& graph);
+
+  // Takes a |graph|, which has edges that must be cut, as listed in
+  // |cuts|.  Cuts the edges. Maintains a list in which the operations
+  // will be performed (in |op_indexes|) and the reverse (in
+  // |reverse_op_indexes|).  Cutting edges requires scratch space, and
+  // if insufficient scratch is found, the file is reread and will be
+  // send down (either as REPLACE or REPLACE_BZ).  Returns true on
+  // success.
+  static bool AssignTempBlocks(
+      Graph* graph,
+      const std::string& new_part,
+      BlobFileWriter* blob_file,
+      std::vector<Vertex::Index>* op_indexes,
+      std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes,
+      const std::vector<CutEdgeVertexes>& cuts);
+
+  // Handles allocation of temp blocks to a cut edge by converting the
+  // dest node to a full op. This removes the need for temp blocks, but
+  // comes at the cost of a worse compression ratio.
+  // For example, say we have A->B->A. It would first be cut to form:
+  // A->B->N<-A, where N copies blocks to temp space. If there are no
+  // temp blocks, this function can be called to convert it to the form:
+  // A->B. Now, A is a full operation.
+  static bool ConvertCutToFullOp(Graph* graph,
+                                 const CutEdgeVertexes& cut,
+                                 const std::string& new_part,
+                                 BlobFileWriter* blob_file);
+
+  // Takes a graph, which is not a DAG, which represents the files just
+  // read from disk, and converts it into a DAG by breaking all cycles
+  // and finding temp space to resolve broken edges.
+  // The final order of the nodes is given in |final_order|
+  // Some files may need to be reread from disk, thus |fd| and
+  // |data_file_size| are be passed.
+  // If |scratch_vertex| is not kInvalidIndex, removes it from
+  // |final_order| before returning.
+  // Returns true on success.
+  static bool ConvertGraphToDag(Graph* graph,
+                                const std::string& new_part,
+                                BlobFileWriter* blob_file,
+                                std::vector<Vertex::Index>* final_order,
+                                Vertex::Index scratch_vertex);
+
+  // Creates a dummy REPLACE_BZ node in the given |vertex|. This can be used
+  // to provide scratch space. The node writes |num_blocks| blocks starting at
+  // |start_block|The node should be marked invalid before writing all nodes to
+  // the output file.
+  static void CreateScratchNode(uint64_t start_block,
+                                uint64_t num_blocks,
+                                Vertex* vertex);
+
+  // The |blocks| vector contains a reader and writer for each block on the
+  // filesystem that's being in-place updated. We populate the reader/writer
+  // fields of |blocks| by calling this function.
+  // For each block in |operation| that is read or written, find that block
+  // in |blocks| and set the reader/writer field to the vertex passed.
+  // |graph| is not strictly necessary, but useful for printing out
+  // error messages.
+  static bool AddInstallOpToBlocksVector(const InstallOperation& operation,
+                                         const Graph& graph,
+                                         Vertex::Index vertex,
+                                         std::vector<Block>* blocks);
+
+  // Add a vertex (if |existing_vertex| is kInvalidVertex) or update an
+  // |existing_vertex| with the passed |operation|.
+  // This method will also register the vertex as the reader or writer of the
+  // blocks involved in the operation updating the |blocks| vector. The
+  // |op_name| associated with the Vertex is used for logging purposes.
+  static bool AddInstallOpToGraph(Graph* graph,
+                                  Vertex::Index existing_vertex,
+                                  std::vector<Block>* blocks,
+                                  const InstallOperation& operation,
+                                  const std::string& op_name);
+
+  // Apply the transformation stored in |the_map| to the |collection| vector
+  // replacing the map keys found in |collection| with its associated value in
+  // |the_map|.
+  static void ApplyMap(std::vector<uint64_t>* collection,
+                       const std::map<uint64_t, uint64_t>& the_map);
+
+  // Resolve all read-after-write dependencies in the operation list |aops|. The
+  // operations in |aops| are such that they generate the desired |new_part| if
+  // applied reading always from the original image. This function reorders the
+  // operations and generates new operations when needed to make these
+  // operations produce the same |new_part| result when applied in-place.
+  // The new operations will create blobs in |data_file_fd| and update
+  // the file size pointed by |data_file_size| if needed.
+  // On success, stores the new operations in |aops| in the right order and
+  // returns true.
+  static bool ResolveReadAfterWriteDependencies(
+      const PartitionConfig& old_part,
+      const PartitionConfig& new_part,
+      uint64_t partition_size,
+      size_t block_size,
+      BlobFileWriter* blob_file,
+      std::vector<AnnotatedOperation>* aops);
+
+  // Generate the update payload operations for the given partition using
+  // only operations that read from the target and/or write to the target,
+  // hence, applying the payload "in-place" in the target partition. This method
+  // assumes that the contents of the source image are pre-copied to the target
+  // partition, up to the size of the source image. Use this method to generate
+  // a delta update with the minor version kInPlaceMinorPayloadVersion.
+  // The operations are stored in |aops|. All the offsets in the operations
+  // reference the data written to |blob_file|.
+  bool GenerateOperations(
+      const PayloadGenerationConfig& config,
+      const PartitionConfig& old_part,
+      const PartitionConfig& new_part,
+      BlobFileWriter* blob_file,
+      std::vector<AnnotatedOperation>* aops) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InplaceGenerator);
+};
+
+};  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_
diff --git a/update_engine/payload_generator/inplace_generator_unittest.cc b/update_engine/payload_generator/inplace_generator_unittest.cc
new file mode 100644
index 0000000..20ac50b
--- /dev/null
+++ b/update_engine/payload_generator/inplace_generator_unittest.cc
@@ -0,0 +1,750 @@
+//
+// 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/payload_generator/inplace_generator.h"
+
+#include <map>
+#include <set>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/cycle_breaker.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/payload_generator/graph_utils.h"
+
+using std::map;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+using Block = InplaceGenerator::Block;
+
+namespace {
+
+void GenVertex(Vertex* out,
+               const vector<Extent>& src_extents,
+               const vector<Extent>& dst_extents,
+               const string& path,
+               InstallOperation_Type type) {
+  out->aop.op.set_type(type);
+  out->aop.name = path;
+  StoreExtents(src_extents, out->aop.op.mutable_src_extents());
+  StoreExtents(dst_extents, out->aop.op.mutable_dst_extents());
+}
+
+vector<Extent> VectOfExt(uint64_t start_block, uint64_t num_blocks) {
+  return vector<Extent>(1, ExtentForRange(start_block, num_blocks));
+}
+
+EdgeProperties EdgeWithReadDep(const vector<Extent>& extents) {
+  EdgeProperties ret;
+  ret.extents = extents;
+  return ret;
+}
+
+EdgeProperties EdgeWithWriteDep(const vector<Extent>& extents) {
+  EdgeProperties ret;
+  ret.write_extents = extents;
+  return ret;
+}
+
+template<typename T>
+void DumpVect(const vector<T>& vect) {
+  stringstream ss(stringstream::out);
+  for (typename vector<T>::const_iterator it = vect.begin(), e = vect.end();
+       it != e; ++it) {
+    ss << *it << ", ";
+  }
+  LOG(INFO) << "{" << ss.str() << "}";
+}
+
+void AppendExtent(vector<Extent>* vect, uint64_t start, uint64_t length) {
+  vect->resize(vect->size() + 1);
+  vect->back().set_start_block(start);
+  vect->back().set_num_blocks(length);
+}
+
+void OpAppendExtent(InstallOperation* op, uint64_t start, uint64_t length) {
+  Extent* extent = op->add_src_extents();
+  extent->set_start_block(start);
+  extent->set_num_blocks(length);
+}
+
+}  // namespace
+
+class InplaceGeneratorTest : public ::testing::Test {
+ protected:
+  // Initialize |blob_path_|, |blob_file_size_| and |blob_file_fd_| variables
+  // with a new blob file. The file is closed and removed automatically when
+  // the test finishes.
+  void CreateBlobFile() {
+    // blob_fd_closer_ takes a pointer to blob_fd_. Make sure we destroy a
+    // previous instance before overriding blob_fd_.
+    blob_fd_closer_.reset();
+    EXPECT_TRUE(utils::MakeTempFile(
+        "InplaceGenerator_blob_file.XXXXXX", &blob_path_, &blob_fd_));
+    blob_path_unlinker_.reset(new ScopedPathUnlinker(blob_path_));
+    blob_fd_closer_.reset(new ScopedFdCloser(&blob_fd_));
+    blob_file_size_ = 0;
+    EXPECT_GE(blob_fd_, 0);
+    blob_file_.reset(new BlobFileWriter(blob_fd_, &blob_file_size_));
+  }
+
+  // Dump the list of operations |aops| in case of test failure.
+  void DumpAopsOnFailure(const vector<AnnotatedOperation>& aops) {
+    if (HasNonfatalFailure()) {
+      LOG(INFO) << "Result operation list:";
+      for (const auto& aop : aops) {
+        LOG(INFO) << aop;
+      }
+    }
+  }
+
+  // Blob file name, file descriptor and file size used to store operation
+  // blobs.
+  string blob_path_;
+  int blob_fd_{-1};
+  off_t blob_file_size_{0};
+  std::unique_ptr<BlobFileWriter> blob_file_;
+  std::unique_ptr<ScopedPathUnlinker> blob_path_unlinker_;
+  std::unique_ptr<ScopedFdCloser> blob_fd_closer_;
+};
+
+TEST_F(InplaceGeneratorTest, BlockDefaultValues) {
+  // Tests that a Block is initialized with the default values as a
+  // Vertex::kInvalidIndex. This is required by the delta generators.
+  Block block;
+  EXPECT_EQ(Vertex::kInvalidIndex, block.reader);
+  EXPECT_EQ(Vertex::kInvalidIndex, block.writer);
+}
+
+TEST_F(InplaceGeneratorTest, SubstituteBlocksTest) {
+  vector<Extent> remove_blocks;
+  AppendExtent(&remove_blocks, 3, 3);
+  AppendExtent(&remove_blocks, 7, 1);
+  vector<Extent> replace_blocks;
+  AppendExtent(&replace_blocks, 10, 2);
+  AppendExtent(&replace_blocks, 13, 2);
+  Vertex vertex;
+  InstallOperation& op = vertex.aop.op;
+  OpAppendExtent(&op, 4, 3);
+  OpAppendExtent(&op, kSparseHole, 4);  // Sparse hole in file
+  OpAppendExtent(&op, 3, 1);
+  OpAppendExtent(&op, 7, 3);
+
+  InplaceGenerator::SubstituteBlocks(&vertex, remove_blocks, replace_blocks);
+
+  EXPECT_EQ(7, op.src_extents_size());
+  EXPECT_EQ(11U, op.src_extents(0).start_block());
+  EXPECT_EQ(1U, op.src_extents(0).num_blocks());
+  EXPECT_EQ(13U, op.src_extents(1).start_block());
+  EXPECT_EQ(1U, op.src_extents(1).num_blocks());
+  EXPECT_EQ(6U, op.src_extents(2).start_block());
+  EXPECT_EQ(1U, op.src_extents(2).num_blocks());
+  EXPECT_EQ(kSparseHole, op.src_extents(3).start_block());
+  EXPECT_EQ(4U, op.src_extents(3).num_blocks());
+  EXPECT_EQ(10U, op.src_extents(4).start_block());
+  EXPECT_EQ(1U, op.src_extents(4).num_blocks());
+  EXPECT_EQ(14U, op.src_extents(5).start_block());
+  EXPECT_EQ(1U, op.src_extents(5).num_blocks());
+  EXPECT_EQ(8U, op.src_extents(6).start_block());
+  EXPECT_EQ(2U, op.src_extents(6).num_blocks());
+}
+
+TEST_F(InplaceGeneratorTest, CutEdgesTest) {
+  Graph graph;
+  vector<Block> blocks(9);
+
+  // Create nodes in graph
+  {
+    graph.resize(graph.size() + 1);
+    graph.back().aop.op.set_type(InstallOperation::MOVE);
+    // Reads from blocks 3, 5, 7
+    vector<Extent> extents;
+    AppendBlockToExtents(&extents, 3);
+    AppendBlockToExtents(&extents, 5);
+    AppendBlockToExtents(&extents, 7);
+    StoreExtents(extents, graph.back().aop.op.mutable_src_extents());
+    blocks[3].reader = graph.size() - 1;
+    blocks[5].reader = graph.size() - 1;
+    blocks[7].reader = graph.size() - 1;
+
+    // Writes to blocks 1, 2, 4
+    extents.clear();
+    AppendBlockToExtents(&extents, 1);
+    AppendBlockToExtents(&extents, 2);
+    AppendBlockToExtents(&extents, 4);
+    StoreExtents(extents, graph.back().aop.op.mutable_dst_extents());
+    blocks[1].writer = graph.size() - 1;
+    blocks[2].writer = graph.size() - 1;
+    blocks[4].writer = graph.size() - 1;
+  }
+  {
+    graph.resize(graph.size() + 1);
+    graph.back().aop.op.set_type(InstallOperation::MOVE);
+    // Reads from blocks 1, 2, 4
+    vector<Extent> extents;
+    AppendBlockToExtents(&extents, 1);
+    AppendBlockToExtents(&extents, 2);
+    AppendBlockToExtents(&extents, 4);
+    StoreExtents(extents, graph.back().aop.op.mutable_src_extents());
+    blocks[1].reader = graph.size() - 1;
+    blocks[2].reader = graph.size() - 1;
+    blocks[4].reader = graph.size() - 1;
+
+    // Writes to blocks 3, 5, 6
+    extents.clear();
+    AppendBlockToExtents(&extents, 3);
+    AppendBlockToExtents(&extents, 5);
+    AppendBlockToExtents(&extents, 6);
+    StoreExtents(extents, graph.back().aop.op.mutable_dst_extents());
+    blocks[3].writer = graph.size() - 1;
+    blocks[5].writer = graph.size() - 1;
+    blocks[6].writer = graph.size() - 1;
+  }
+
+  // Create edges
+  InplaceGenerator::CreateEdges(&graph, blocks);
+
+  // Find cycles
+  CycleBreaker cycle_breaker;
+  set<Edge> cut_edges;
+  cycle_breaker.BreakCycles(graph, &cut_edges);
+
+  EXPECT_EQ(1U, cut_edges.size());
+  EXPECT_TRUE(cut_edges.end() != cut_edges.find(
+      std::pair<Vertex::Index, Vertex::Index>(1, 0)));
+
+  vector<CutEdgeVertexes> cuts;
+  EXPECT_TRUE(InplaceGenerator::CutEdges(&graph, cut_edges, &cuts));
+
+  EXPECT_EQ(3U, graph.size());
+
+  // Check new node in graph:
+  EXPECT_EQ(InstallOperation::MOVE, graph.back().aop.op.type());
+  EXPECT_EQ(2, graph.back().aop.op.src_extents_size());
+  EXPECT_EQ(1, graph.back().aop.op.dst_extents_size());
+  EXPECT_EQ(kTempBlockStart, graph.back().aop.op.dst_extents(0).start_block());
+  EXPECT_EQ(2U, graph.back().aop.op.dst_extents(0).num_blocks());
+  EXPECT_TRUE(graph.back().out_edges.empty());
+
+  // Check that old node reads from new blocks
+  EXPECT_EQ(2, graph[0].aop.op.src_extents_size());
+  EXPECT_EQ(kTempBlockStart, graph[0].aop.op.src_extents(0).start_block());
+  EXPECT_EQ(2U, graph[0].aop.op.src_extents(0).num_blocks());
+  EXPECT_EQ(7U, graph[0].aop.op.src_extents(1).start_block());
+  EXPECT_EQ(1U, graph[0].aop.op.src_extents(1).num_blocks());
+
+  // And that the old dst extents haven't changed
+  EXPECT_EQ(2, graph[0].aop.op.dst_extents_size());
+  EXPECT_EQ(1U, graph[0].aop.op.dst_extents(0).start_block());
+  EXPECT_EQ(2U, graph[0].aop.op.dst_extents(0).num_blocks());
+  EXPECT_EQ(4U, graph[0].aop.op.dst_extents(1).start_block());
+  EXPECT_EQ(1U, graph[0].aop.op.dst_extents(1).num_blocks());
+
+  // Ensure it only depends on the next node and the new temp node
+  EXPECT_EQ(2U, graph[0].out_edges.size());
+  EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(1));
+  EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(graph.size() -
+                                                                  1));
+
+  // Check second node has unchanged extents
+  EXPECT_EQ(2, graph[1].aop.op.src_extents_size());
+  EXPECT_EQ(1U, graph[1].aop.op.src_extents(0).start_block());
+  EXPECT_EQ(2U, graph[1].aop.op.src_extents(0).num_blocks());
+  EXPECT_EQ(4U, graph[1].aop.op.src_extents(1).start_block());
+  EXPECT_EQ(1U, graph[1].aop.op.src_extents(1).num_blocks());
+
+  EXPECT_EQ(2, graph[1].aop.op.dst_extents_size());
+  EXPECT_EQ(3U, graph[1].aop.op.dst_extents(0).start_block());
+  EXPECT_EQ(1U, graph[1].aop.op.dst_extents(0).num_blocks());
+  EXPECT_EQ(5U, graph[1].aop.op.dst_extents(1).start_block());
+  EXPECT_EQ(2U, graph[1].aop.op.dst_extents(1).num_blocks());
+
+  // Ensure it only depends on the next node
+  EXPECT_EQ(1U, graph[1].out_edges.size());
+  EXPECT_TRUE(graph[1].out_edges.end() != graph[1].out_edges.find(2));
+}
+
+TEST_F(InplaceGeneratorTest, AssignTempBlocksReuseTest) {
+  Graph graph(9);
+
+  const vector<Extent> empt;
+  uint64_t tmp = kTempBlockStart;
+  const string kFilename = "/foo";
+
+  vector<CutEdgeVertexes> cuts;
+  cuts.resize(3);
+
+  // Simple broken loop:
+  GenVertex(
+      &graph[0], VectOfExt(0, 1), VectOfExt(1, 1), "", InstallOperation::MOVE);
+  GenVertex(&graph[1],
+            VectOfExt(tmp, 1),
+            VectOfExt(0, 1),
+            "",
+            InstallOperation::MOVE);
+  GenVertex(&graph[2],
+            VectOfExt(1, 1),
+            VectOfExt(tmp, 1),
+            "",
+            InstallOperation::MOVE);
+  // Corresponding edges:
+  graph[0].out_edges[2] = EdgeWithReadDep(VectOfExt(1, 1));
+  graph[1].out_edges[2] = EdgeWithWriteDep(VectOfExt(tmp, 1));
+  graph[1].out_edges[0] = EdgeWithReadDep(VectOfExt(0, 1));
+  // Store the cut:
+  cuts[0].old_dst = 1;
+  cuts[0].old_src = 0;
+  cuts[0].new_vertex = 2;
+  cuts[0].tmp_extents = VectOfExt(tmp, 1);
+  tmp++;
+
+  // Slightly more complex pair of loops:
+  GenVertex(
+      &graph[3], VectOfExt(4, 2), VectOfExt(2, 2), "", InstallOperation::MOVE);
+  GenVertex(
+      &graph[4], VectOfExt(6, 1), VectOfExt(7, 1), "", InstallOperation::MOVE);
+  GenVertex(&graph[5],
+            VectOfExt(tmp, 3),
+            VectOfExt(4, 3),
+            kFilename,
+            InstallOperation::MOVE);
+  GenVertex(&graph[6],
+            VectOfExt(2, 2),
+            VectOfExt(tmp, 2),
+            "",
+            InstallOperation::MOVE);
+  GenVertex(&graph[7],
+            VectOfExt(7, 1),
+            VectOfExt(tmp + 2, 1),
+            "",
+            InstallOperation::MOVE);
+  // Corresponding edges:
+  graph[3].out_edges[6] = EdgeWithReadDep(VectOfExt(2, 2));
+  graph[4].out_edges[7] = EdgeWithReadDep(VectOfExt(7, 1));
+  graph[5].out_edges[6] = EdgeWithWriteDep(VectOfExt(tmp, 2));
+  graph[5].out_edges[7] = EdgeWithWriteDep(VectOfExt(tmp + 2, 1));
+  graph[5].out_edges[3] = EdgeWithReadDep(VectOfExt(4, 2));
+  graph[5].out_edges[4] = EdgeWithReadDep(VectOfExt(6, 1));
+  // Store the cuts:
+  cuts[1].old_dst = 5;
+  cuts[1].old_src = 3;
+  cuts[1].new_vertex = 6;
+  cuts[1].tmp_extents = VectOfExt(tmp, 2);
+  cuts[2].old_dst = 5;
+  cuts[2].old_src = 4;
+  cuts[2].new_vertex = 7;
+  cuts[2].tmp_extents = VectOfExt(tmp + 2, 1);
+
+  // Supplier of temp block:
+  GenVertex(&graph[8], empt, VectOfExt(8, 1), "", InstallOperation::REPLACE);
+
+  // Specify the final order:
+  vector<Vertex::Index> op_indexes;
+  op_indexes.push_back(2);
+  op_indexes.push_back(0);
+  op_indexes.push_back(1);
+  op_indexes.push_back(6);
+  op_indexes.push_back(3);
+  op_indexes.push_back(7);
+  op_indexes.push_back(4);
+  op_indexes.push_back(5);
+  op_indexes.push_back(8);
+
+  vector<vector<Vertex::Index>::size_type> reverse_op_indexes;
+  InplaceGenerator::GenerateReverseTopoOrderMap(op_indexes,
+                                                &reverse_op_indexes);
+
+  CreateBlobFile();
+  EXPECT_TRUE(InplaceGenerator::AssignTempBlocks(&graph,
+                                                 "/dev/zero",
+                                                 blob_file_.get(),
+                                                 &op_indexes,
+                                                 &reverse_op_indexes,
+                                                 cuts));
+  EXPECT_FALSE(graph[6].valid);
+  EXPECT_FALSE(graph[7].valid);
+  EXPECT_EQ(1, graph[1].aop.op.src_extents_size());
+  EXPECT_EQ(2U, graph[1].aop.op.src_extents(0).start_block());
+  EXPECT_EQ(1U, graph[1].aop.op.src_extents(0).num_blocks());
+  EXPECT_EQ(InstallOperation::REPLACE_BZ, graph[5].aop.op.type());
+}
+
+TEST_F(InplaceGeneratorTest, MoveAndSortFullOpsToBackTest) {
+  Graph graph(4);
+  graph[0].aop.name = "A";
+  graph[0].aop.op.set_type(InstallOperation::REPLACE);
+  graph[1].aop.name = "B";
+  graph[1].aop.op.set_type(InstallOperation::BSDIFF);
+  graph[2].aop.name = "C";
+  graph[2].aop.op.set_type(InstallOperation::REPLACE_BZ);
+  graph[3].aop.name = "D";
+  graph[3].aop.op.set_type(InstallOperation::MOVE);
+
+  vector<Vertex::Index> vect(graph.size());
+
+  for (vector<Vertex::Index>::size_type i = 0; i < vect.size(); ++i) {
+    vect[i] = i;
+  }
+  InplaceGenerator::MoveAndSortFullOpsToBack(&graph, &vect);
+  EXPECT_EQ(vect.size(), graph.size());
+  EXPECT_EQ(graph[vect[0]].aop.name, "B");
+  EXPECT_EQ(graph[vect[1]].aop.name, "D");
+  EXPECT_EQ(graph[vect[2]].aop.name, "A");
+  EXPECT_EQ(graph[vect[3]].aop.name, "C");
+}
+
+TEST_F(InplaceGeneratorTest, AssignTempBlocksTest) {
+  Graph graph(9);
+  const vector<Extent> empt;  // empty
+  const string kFilename = "/foo";
+
+  // Some scratch space:
+  GenVertex(&graph[0], empt, VectOfExt(200, 1), "", InstallOperation::REPLACE);
+  GenVertex(&graph[1], empt, VectOfExt(210, 10), "", InstallOperation::REPLACE);
+  GenVertex(&graph[2], empt, VectOfExt(220, 1), "", InstallOperation::REPLACE);
+
+  // A cycle that requires 10 blocks to break:
+  GenVertex(&graph[3],
+            VectOfExt(10, 11),
+            VectOfExt(0, 9),
+            "",
+            InstallOperation::BSDIFF);
+  graph[3].out_edges[4] = EdgeWithReadDep(VectOfExt(0, 9));
+  GenVertex(&graph[4],
+            VectOfExt(0, 9),
+            VectOfExt(10, 11),
+            "",
+            InstallOperation::BSDIFF);
+  graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11));
+
+  // A cycle that requires 9 blocks to break:
+  GenVertex(&graph[5],
+            VectOfExt(40, 11),
+            VectOfExt(30, 10),
+            "",
+            InstallOperation::BSDIFF);
+  graph[5].out_edges[6] = EdgeWithReadDep(VectOfExt(30, 10));
+  GenVertex(&graph[6],
+            VectOfExt(30, 10),
+            VectOfExt(40, 11),
+            "",
+            InstallOperation::BSDIFF);
+  graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11));
+
+  // A cycle that requires 40 blocks to break (which is too many):
+  GenVertex(&graph[7],
+            VectOfExt(120, 50),
+            VectOfExt(60, 40),
+            "",
+            InstallOperation::BSDIFF);
+  graph[7].out_edges[8] = EdgeWithReadDep(VectOfExt(60, 40));
+  GenVertex(&graph[8],
+            VectOfExt(60, 40),
+            VectOfExt(120, 50),
+            kFilename,
+            InstallOperation::BSDIFF);
+  graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50));
+
+  graph_utils::DumpGraph(graph);
+
+  vector<Vertex::Index> final_order;
+
+  CreateBlobFile();
+  EXPECT_TRUE(InplaceGenerator::ConvertGraphToDag(&graph,
+                                                  "/dev/zero",
+                                                  blob_file_.get(),
+                                                  &final_order,
+                                                  Vertex::kInvalidIndex));
+
+  Graph expected_graph(12);
+  GenVertex(&expected_graph[0],
+            empt,
+            VectOfExt(200, 1),
+            "",
+            InstallOperation::REPLACE);
+  GenVertex(&expected_graph[1],
+            empt,
+            VectOfExt(210, 10),
+            "",
+            InstallOperation::REPLACE);
+  GenVertex(&expected_graph[2],
+            empt,
+            VectOfExt(220, 1),
+            "",
+            InstallOperation::REPLACE);
+  GenVertex(&expected_graph[3],
+            VectOfExt(10, 11),
+            VectOfExt(0, 9),
+            "",
+            InstallOperation::BSDIFF);
+  expected_graph[3].out_edges[9] = EdgeWithReadDep(VectOfExt(0, 9));
+  GenVertex(&expected_graph[4],
+            VectOfExt(60, 9),
+            VectOfExt(10, 11),
+            "",
+            InstallOperation::BSDIFF);
+  expected_graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11));
+  expected_graph[4].out_edges[9] = EdgeWithWriteDep(VectOfExt(60, 9));
+  GenVertex(&expected_graph[5],
+            VectOfExt(40, 11),
+            VectOfExt(30, 10),
+            "",
+            InstallOperation::BSDIFF);
+  expected_graph[5].out_edges[10] = EdgeWithReadDep(VectOfExt(30, 10));
+
+  GenVertex(&expected_graph[6],
+            VectOfExt(60, 10),
+            VectOfExt(40, 11),
+            "",
+            InstallOperation::BSDIFF);
+  expected_graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11));
+  expected_graph[6].out_edges[10] = EdgeWithWriteDep(VectOfExt(60, 10));
+
+  GenVertex(&expected_graph[7],
+            VectOfExt(120, 50),
+            VectOfExt(60, 40),
+            "",
+            InstallOperation::BSDIFF);
+  expected_graph[7].out_edges[6] = EdgeWithReadDep(VectOfExt(60, 10));
+
+  GenVertex(&expected_graph[8],
+            empt,
+            VectOfExt(0, 50),
+            "/foo",
+            InstallOperation::REPLACE_BZ);
+  expected_graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50));
+
+  GenVertex(&expected_graph[9],
+            VectOfExt(0, 9),
+            VectOfExt(60, 9),
+            "",
+            InstallOperation::MOVE);
+
+  GenVertex(&expected_graph[10],
+            VectOfExt(30, 10),
+            VectOfExt(60, 10),
+            "",
+            InstallOperation::MOVE);
+  expected_graph[10].out_edges[4] = EdgeWithReadDep(VectOfExt(60, 9));
+
+  EXPECT_EQ(12U, graph.size());
+  EXPECT_FALSE(graph.back().valid);
+  for (Graph::size_type i = 0; i < graph.size() - 1; i++) {
+    EXPECT_TRUE(graph[i].out_edges == expected_graph[i].out_edges);
+    if (i == 8) {
+      // special case
+    } else {
+      // EXPECT_TRUE(graph[i] == expected_graph[i]) << "i = " << i;
+    }
+  }
+}
+
+TEST_F(InplaceGeneratorTest, CreateScratchNodeTest) {
+  Vertex vertex;
+  InplaceGenerator::CreateScratchNode(12, 34, &vertex);
+  EXPECT_EQ(InstallOperation::REPLACE_BZ, vertex.aop.op.type());
+  EXPECT_EQ(0U, vertex.aop.op.data_offset());
+  EXPECT_EQ(0U, vertex.aop.op.data_length());
+  EXPECT_EQ(1, vertex.aop.op.dst_extents_size());
+  EXPECT_EQ(12U, vertex.aop.op.dst_extents(0).start_block());
+  EXPECT_EQ(34U, vertex.aop.op.dst_extents(0).num_blocks());
+}
+
+TEST_F(InplaceGeneratorTest, ApplyMapTest) {
+  vector<uint64_t> collection = {1, 2, 3, 4, 6};
+  vector<uint64_t> expected_values = {1, 2, 5, 4, 8};
+  map<uint64_t, uint64_t> value_map;
+  value_map[3] = 5;
+  value_map[6] = 8;
+  value_map[5] = 10;
+
+  InplaceGenerator::ApplyMap(&collection, value_map);
+  EXPECT_EQ(expected_values, collection);
+}
+
+// We can't produce MOVE operations with a source or destination in the block 0.
+// This test checks that the cycle breaker procedure doesn't produce such
+// operations.
+TEST_F(InplaceGeneratorTest, ResolveReadAfterWriteDependenciesAvoidMoveToZero) {
+  size_t block_size = 4096;
+  size_t num_blocks = 4;
+  vector<AnnotatedOperation> aops;
+
+  // Create a REPLACE_BZ for block 0, and a circular dependency among all other
+  // blocks. This situation would prefer to issue a MOVE to scratch space and
+  // the only available block is 0.
+  aops.emplace_back();
+  aops.back().name = base::StringPrintf("<bz-block-0>");
+  aops.back().op.set_type(InstallOperation::REPLACE_BZ);
+  StoreExtents({ExtentForRange(0, 1)}, aops.back().op.mutable_dst_extents());
+
+  for (size_t i = 1; i < num_blocks; i++) {
+    AnnotatedOperation aop;
+    aop.name = base::StringPrintf("<op-%" PRIuS ">", i);
+    aop.op.set_type(InstallOperation::BSDIFF);
+    StoreExtents({ExtentForRange(1 + i % (num_blocks - 1), 1)},
+                 aop.op.mutable_src_extents());
+    StoreExtents({ExtentForRange(i, 1)}, aop.op.mutable_dst_extents());
+    aops.push_back(aop);
+  }
+
+  PartitionConfig part("part");
+  part.path = "/dev/zero";
+  part.size = num_blocks * block_size;
+
+  CreateBlobFile();
+
+  // We ran two tests here. The first one without enough blocks for the scratch
+  // space, forcing it to create a new full operation and the second case with
+  // one extra block in the partition that can be used for the move operation.
+  for (const auto part_blocks : vector<uint64_t>{num_blocks, num_blocks + 1}) {
+    SCOPED_TRACE(
+        base::StringPrintf("Using partition_blocks=%" PRIu64, part_blocks));
+    vector<AnnotatedOperation> result_aops = aops;
+    EXPECT_TRUE(InplaceGenerator::ResolveReadAfterWriteDependencies(
+        part,
+        part,
+        part_blocks * block_size,
+        block_size,
+        blob_file_.get(),
+        &result_aops));
+
+    size_t full_ops = 0;
+    for (const auto& aop : result_aops) {
+      if (diff_utils::IsAReplaceOperation(aop.op.type()))
+        full_ops++;
+
+      if (aop.op.type() != InstallOperation::MOVE)
+        continue;
+      for (const Extent& extent : aop.op.src_extents()) {
+        EXPECT_NE(0U, extent.start_block())
+            << "On src extents for aop: " << aop;
+      }
+      for (const Extent& extent : aop.op.dst_extents()) {
+        EXPECT_NE(0U, extent.start_block())
+            << "On dst extents for aop: " << aop;
+      }
+    }
+
+    // If there's extra space in the partition, it should not use a new full
+    // operation for it.
+    EXPECT_EQ(part_blocks == num_blocks ? 2U : 1U, full_ops);
+
+    DumpAopsOnFailure(result_aops);
+  }
+}
+
+// Test that we can shrink a filesystem and break cycles.
+TEST_F(InplaceGeneratorTest, ResolveReadAfterWriteDependenciesShrinkData) {
+  size_t block_size = 4096;
+  size_t old_blocks = 10;
+  size_t new_blocks = 8;
+  vector<AnnotatedOperation> aops;
+
+  // Create a loop using the blocks 1-6 and one other operation writing to the
+  // block 7 from outside the new partition. The loop in the blocks 1-6 uses
+  // two-block operations, so it needs two blocks of scratch space. It can't use
+  // the block 0 as scratch space (see previous test) and it can't use the
+  // blocks 7 or 8 due the last move operation.
+
+  aops.emplace_back();
+  aops.back().name = base::StringPrintf("<bz-block-0>");
+  aops.back().op.set_type(InstallOperation::REPLACE_BZ);
+  StoreExtents({ExtentForRange(0, 1)}, aops.back().op.mutable_dst_extents());
+
+  const size_t num_ops = 3;
+  for (size_t i = 0; i < num_ops; i++) {
+    AnnotatedOperation aop;
+    aop.name = base::StringPrintf("<op-%" PRIuS ">", i);
+    aop.op.set_type(InstallOperation::BSDIFF);
+    StoreExtents({ExtentForRange(1 + 2 * i, 2)}, aop.op.mutable_src_extents());
+    StoreExtents({ExtentForRange(1 + 2 * ((i + 1) % num_ops), 2)},
+                 aop.op.mutable_dst_extents());
+    aops.push_back(aop);
+  }
+
+  {
+    AnnotatedOperation aop;
+    aop.name = "<op-shrink>";
+    aop.op.set_type(InstallOperation::BSDIFF);
+    StoreExtents({ExtentForRange(8, 1)}, aop.op.mutable_src_extents());
+    StoreExtents({ExtentForRange(7, 1)}, aop.op.mutable_dst_extents());
+    aops.push_back(aop);
+  }
+
+  PartitionConfig old_part("part");
+  old_part.path = "/dev/zero";
+  old_part.size = old_blocks * block_size;
+
+  PartitionConfig new_part("part");
+  new_part.path = "/dev/zero";
+  new_part.size = new_blocks * block_size;
+
+  CreateBlobFile();
+
+  EXPECT_TRUE(InplaceGenerator::ResolveReadAfterWriteDependencies(
+      old_part,
+      new_part,
+      (old_blocks + 2) * block_size,  // enough scratch space.
+      block_size,
+      blob_file_.get(),
+      &aops));
+
+  size_t full_ops = 0;
+  for (const auto& aop : aops) {
+    if (diff_utils::IsAReplaceOperation(aop.op.type()))
+      full_ops++;
+  }
+  // There should be only one REPLACE* operation, the one we added for block 0.
+  EXPECT_EQ(1U, full_ops);
+
+  // There should be only one MOVE operation, the one used to break the loop
+  // which should write to scratch space past the block 7 (the last block of the
+  // new partition) which is being written later.
+  size_t move_ops = 0;
+  for (const auto& aop : aops) {
+    if (aop.op.type() == InstallOperation::MOVE) {
+      move_ops++;
+      for (const Extent& extent : aop.op.dst_extents()) {
+        EXPECT_LE(7U, extent.start_block()) << "On dst extents for aop: "
+                                            << aop;
+      }
+    }
+  }
+  EXPECT_EQ(1U, move_ops);
+
+  DumpAopsOnFailure(aops);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/operations_generator.h b/update_engine/payload_generator/operations_generator.h
new file mode 100644
index 0000000..9127d7b
--- /dev/null
+++ b/update_engine/payload_generator/operations_generator.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_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
+
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+class OperationsGenerator {
+ public:
+  virtual ~OperationsGenerator() = default;
+
+  // This method generates a list of operations to update from the partition
+  // |old_part| to |new_part| and stores the generated operations in |aops|.
+  // These operations are generated based on the given |config|.
+  // The operations should be applied in the order specified in the list, and
+  // they respect the payload version and type (delta or full) specified in
+  // |config|.
+  // The operations generated will refer to offsets in the file |blob_file|,
+  // where this function stores the output, but not necessarily in the same
+  // order as they appear in the |aops|.
+  virtual bool GenerateOperations(
+      const PayloadGenerationConfig& config,
+      const PartitionConfig& old_part,
+      const PartitionConfig& new_part,
+      BlobFileWriter* blob_file,
+      std::vector<AnnotatedOperation>* aops) = 0;
+
+ protected:
+  OperationsGenerator() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OperationsGenerator);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
diff --git a/update_engine/payload_generator/payload_file.cc b/update_engine/payload_generator/payload_file.cc
new file mode 100644
index 0000000..2f95b21
--- /dev/null
+++ b/update_engine/payload_generator/payload_file.cc
@@ -0,0 +1,356 @@
+//
+// 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/payload_generator/payload_file.h"
+
+#include <endian.h>
+
+#include <algorithm>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/file_writer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/payload_signer.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+struct DeltaObject {
+  DeltaObject(const string& in_name, const int in_type, const off_t in_size)
+      : name(in_name),
+        type(in_type),
+        size(in_size) {}
+  bool operator <(const DeltaObject& object) const {
+    return (size != object.size) ? (size < object.size) : (name < object.name);
+  }
+  string name;
+  int type;
+  off_t size;
+};
+
+// Writes the uint64_t passed in in host-endian to the file as big-endian.
+// Returns true on success.
+bool WriteUint64AsBigEndian(FileWriter* writer, const uint64_t value) {
+  uint64_t value_be = htobe64(value);
+  TEST_AND_RETURN_FALSE(writer->Write(&value_be, sizeof(value_be)));
+  return true;
+}
+
+}  // namespace
+
+bool PayloadFile::Init(const PayloadGenerationConfig& config) {
+  TEST_AND_RETURN_FALSE(config.version.Validate());
+  major_version_ = config.version.major;
+  manifest_.set_minor_version(config.version.minor);
+
+  if (!config.source.ImageInfoIsEmpty())
+    *(manifest_.mutable_old_image_info()) = config.source.image_info;
+
+  if (!config.target.ImageInfoIsEmpty())
+    *(manifest_.mutable_new_image_info()) = config.target.image_info;
+
+  manifest_.set_block_size(config.block_size);
+  return true;
+}
+
+bool PayloadFile::AddPartition(const PartitionConfig& old_conf,
+                               const PartitionConfig& new_conf,
+                               const vector<AnnotatedOperation>& aops) {
+  // Check partitions order for Chrome OS
+  if (major_version_ == kChromeOSMajorPayloadVersion) {
+    const vector<const char*> part_order = { kLegacyPartitionNameRoot,
+                                             kLegacyPartitionNameKernel };
+    TEST_AND_RETURN_FALSE(part_vec_.size() < part_order.size());
+    TEST_AND_RETURN_FALSE(new_conf.name == part_order[part_vec_.size()]);
+  }
+  Partition part;
+  part.name = new_conf.name;
+  part.aops = aops;
+  part.postinstall = new_conf.postinstall;
+  // Initialize the PartitionInfo objects if present.
+  if (!old_conf.path.empty())
+    TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(old_conf,
+                                                              &part.old_info));
+  TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(new_conf,
+                                                            &part.new_info));
+  part_vec_.push_back(std::move(part));
+  return true;
+}
+
+bool PayloadFile::WritePayload(const string& payload_file,
+                               const string& data_blobs_path,
+                               const string& private_key_path,
+                               uint64_t* metadata_size_out) {
+  // Reorder the data blobs with the manifest_.
+  string ordered_blobs_path;
+  TEST_AND_RETURN_FALSE(utils::MakeTempFile(
+      "CrAU_temp_data.ordered.XXXXXX",
+      &ordered_blobs_path,
+      nullptr));
+  ScopedPathUnlinker ordered_blobs_unlinker(ordered_blobs_path);
+  TEST_AND_RETURN_FALSE(ReorderDataBlobs(data_blobs_path, ordered_blobs_path));
+
+  // Check that install op blobs are in order.
+  uint64_t next_blob_offset = 0;
+  for (const auto& part : part_vec_) {
+    for (const auto& aop : part.aops) {
+      if (!aop.op.has_data_offset())
+        continue;
+      if (aop.op.data_offset() != next_blob_offset) {
+        LOG(FATAL) << "bad blob offset! " << aop.op.data_offset() << " != "
+                   << next_blob_offset;
+      }
+      next_blob_offset += aop.op.data_length();
+    }
+  }
+
+  // Copy the operations and partition info from the part_vec_ to the manifest.
+  manifest_.clear_install_operations();
+  manifest_.clear_kernel_install_operations();
+  manifest_.clear_partitions();
+  for (const auto& part : part_vec_) {
+    if (major_version_ == kBrilloMajorPayloadVersion) {
+      PartitionUpdate* partition = manifest_.add_partitions();
+      partition->set_partition_name(part.name);
+      if (part.postinstall.run) {
+        partition->set_run_postinstall(true);
+        if (!part.postinstall.path.empty())
+          partition->set_postinstall_path(part.postinstall.path);
+        if (!part.postinstall.filesystem_type.empty())
+          partition->set_filesystem_type(part.postinstall.filesystem_type);
+        partition->set_postinstall_optional(part.postinstall.optional);
+      }
+      for (const AnnotatedOperation& aop : part.aops) {
+        *partition->add_operations() = aop.op;
+      }
+      if (part.old_info.has_size() || part.old_info.has_hash())
+        *(partition->mutable_old_partition_info()) = part.old_info;
+      if (part.new_info.has_size() || part.new_info.has_hash())
+        *(partition->mutable_new_partition_info()) = part.new_info;
+    } else {
+      // major_version_ == kChromeOSMajorPayloadVersion
+      if (part.name == kLegacyPartitionNameKernel) {
+        for (const AnnotatedOperation& aop : part.aops)
+          *manifest_.add_kernel_install_operations() = aop.op;
+        if (part.old_info.has_size() || part.old_info.has_hash())
+          *manifest_.mutable_old_kernel_info() = part.old_info;
+        if (part.new_info.has_size() || part.new_info.has_hash())
+          *manifest_.mutable_new_kernel_info() = part.new_info;
+      } else {
+        for (const AnnotatedOperation& aop : part.aops)
+          *manifest_.add_install_operations() = aop.op;
+        if (part.old_info.has_size() || part.old_info.has_hash())
+          *manifest_.mutable_old_rootfs_info() = part.old_info;
+        if (part.new_info.has_size() || part.new_info.has_hash())
+          *manifest_.mutable_new_rootfs_info() = part.new_info;
+      }
+    }
+  }
+
+  // Signatures appear at the end of the blobs. Note the offset in the
+  // manifest_.
+  uint64_t signature_blob_length = 0;
+  if (!private_key_path.empty()) {
+    TEST_AND_RETURN_FALSE(
+        PayloadSigner::SignatureBlobLength(vector<string>(1, private_key_path),
+                                           &signature_blob_length));
+    PayloadSigner::AddSignatureToManifest(
+        next_blob_offset, signature_blob_length,
+        major_version_ == kChromeOSMajorPayloadVersion, &manifest_);
+  }
+
+  // Serialize protobuf
+  string serialized_manifest;
+  TEST_AND_RETURN_FALSE(manifest_.AppendToString(&serialized_manifest));
+
+  uint64_t metadata_size =
+      sizeof(kDeltaMagic) + 2 * sizeof(uint64_t) + serialized_manifest.size();
+
+  LOG(INFO) << "Writing final delta file header...";
+  DirectFileWriter writer;
+  TEST_AND_RETURN_FALSE_ERRNO(writer.Open(payload_file.c_str(),
+                                          O_WRONLY | O_CREAT | O_TRUNC,
+                                          0644) == 0);
+  ScopedFileWriterCloser writer_closer(&writer);
+
+  // Write header
+  TEST_AND_RETURN_FALSE(writer.Write(kDeltaMagic, sizeof(kDeltaMagic)));
+
+  // Write major version number
+  TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer, major_version_));
+
+  // Write protobuf length
+  TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer,
+                                               serialized_manifest.size()));
+
+  // Write metadata signature size.
+  uint32_t metadata_signature_size = 0;
+  if (major_version_ == kBrilloMajorPayloadVersion) {
+    // Metadata signature has the same size as payload signature, because they
+    // are both the same kind of signature for the same kind of hash.
+    uint32_t metadata_signature_size = htobe32(signature_blob_length);
+    TEST_AND_RETURN_FALSE(writer.Write(&metadata_signature_size,
+                                       sizeof(metadata_signature_size)));
+    metadata_size += sizeof(metadata_signature_size);
+    // Set correct size instead of big endian size.
+    metadata_signature_size = signature_blob_length;
+  }
+
+  // Write protobuf
+  LOG(INFO) << "Writing final delta file protobuf... "
+            << serialized_manifest.size();
+  TEST_AND_RETURN_FALSE(writer.Write(serialized_manifest.data(),
+                                     serialized_manifest.size()));
+
+  // Write metadata signature blob.
+  if (major_version_ == kBrilloMajorPayloadVersion &&
+      !private_key_path.empty()) {
+    brillo::Blob metadata_hash, metadata_signature;
+    TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfFile(payload_file,
+                                                             metadata_size,
+                                                             &metadata_hash));
+    TEST_AND_RETURN_FALSE(
+        PayloadSigner::SignHashWithKeys(metadata_hash,
+                                        vector<string>(1, private_key_path),
+                                        &metadata_signature));
+    TEST_AND_RETURN_FALSE(writer.Write(metadata_signature.data(),
+                                       metadata_signature.size()));
+  }
+
+  // Append the data blobs
+  LOG(INFO) << "Writing final delta file data blobs...";
+  int blobs_fd = open(ordered_blobs_path.c_str(), O_RDONLY, 0);
+  ScopedFdCloser blobs_fd_closer(&blobs_fd);
+  TEST_AND_RETURN_FALSE(blobs_fd >= 0);
+  for (;;) {
+    vector<char> buf(1024 * 1024);
+    ssize_t rc = read(blobs_fd, buf.data(), buf.size());
+    if (0 == rc) {
+      // EOF
+      break;
+    }
+    TEST_AND_RETURN_FALSE_ERRNO(rc > 0);
+    TEST_AND_RETURN_FALSE(writer.Write(buf.data(), rc));
+  }
+
+  // Write payload signature blob.
+  if (!private_key_path.empty()) {
+    LOG(INFO) << "Signing the update...";
+    brillo::Blob signature_blob;
+    TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(
+        payload_file,
+        vector<string>(1, private_key_path),
+        metadata_size,
+        metadata_signature_size,
+        metadata_size + metadata_signature_size + manifest_.signatures_offset(),
+        &signature_blob));
+    TEST_AND_RETURN_FALSE(writer.Write(signature_blob.data(),
+                                       signature_blob.size()));
+  }
+
+  ReportPayloadUsage(metadata_size);
+  *metadata_size_out = metadata_size;
+  return true;
+}
+
+bool PayloadFile::ReorderDataBlobs(
+    const string& data_blobs_path,
+    const string& new_data_blobs_path) {
+  int in_fd = open(data_blobs_path.c_str(), O_RDONLY, 0);
+  TEST_AND_RETURN_FALSE_ERRNO(in_fd >= 0);
+  ScopedFdCloser in_fd_closer(&in_fd);
+
+  DirectFileWriter writer;
+  TEST_AND_RETURN_FALSE(
+      writer.Open(new_data_blobs_path.c_str(),
+                  O_WRONLY | O_TRUNC | O_CREAT,
+                  0644) == 0);
+  ScopedFileWriterCloser writer_closer(&writer);
+  uint64_t out_file_size = 0;
+
+  for (auto& part : part_vec_) {
+    for (AnnotatedOperation& aop : part.aops) {
+      if (!aop.op.has_data_offset())
+        continue;
+      CHECK(aop.op.has_data_length());
+      brillo::Blob buf(aop.op.data_length());
+      ssize_t rc = pread(in_fd, buf.data(), buf.size(), aop.op.data_offset());
+      TEST_AND_RETURN_FALSE(rc == static_cast<ssize_t>(buf.size()));
+
+      // Add the hash of the data blobs for this operation
+      TEST_AND_RETURN_FALSE(AddOperationHash(&aop.op, buf));
+
+      aop.op.set_data_offset(out_file_size);
+      TEST_AND_RETURN_FALSE(writer.Write(buf.data(), buf.size()));
+      out_file_size += buf.size();
+    }
+  }
+  return true;
+}
+
+bool PayloadFile::AddOperationHash(InstallOperation* op,
+                                   const brillo::Blob& buf) {
+  HashCalculator hasher;
+  TEST_AND_RETURN_FALSE(hasher.Update(buf.data(), buf.size()));
+  TEST_AND_RETURN_FALSE(hasher.Finalize());
+  const brillo::Blob& hash = hasher.raw_hash();
+  op->set_data_sha256_hash(hash.data(), hash.size());
+  return true;
+}
+
+void PayloadFile::ReportPayloadUsage(uint64_t metadata_size) const {
+  vector<DeltaObject> objects;
+  off_t total_size = 0;
+
+  for (const auto& part : part_vec_) {
+    for (const AnnotatedOperation& aop : part.aops) {
+      objects.push_back(DeltaObject(aop.name,
+                                    aop.op.type(),
+                                    aop.op.data_length()));
+      total_size += aop.op.data_length();
+    }
+  }
+
+  objects.push_back(DeltaObject("<manifest-metadata>",
+                                -1,
+                                metadata_size));
+  total_size += metadata_size;
+
+  std::sort(objects.begin(), objects.end());
+
+  static const char kFormatString[] = "%6.2f%% %10jd %-10s %s\n";
+  for (const DeltaObject& object : objects) {
+    fprintf(
+        stderr, kFormatString,
+        object.size * 100.0 / total_size,
+        static_cast<intmax_t>(object.size),
+        (object.type >= 0 ? InstallOperationTypeName(
+                                static_cast<InstallOperation_Type>(object.type))
+                          : "-"),
+        object.name.c_str());
+  }
+  fprintf(stderr, kFormatString,
+          100.0, static_cast<intmax_t>(total_size), "", "<total>");
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/payload_file.h b/update_engine/payload_generator/payload_file.h
new file mode 100644
index 0000000..7cc792a
--- /dev/null
+++ b/update_engine/payload_generator/payload_file.h
@@ -0,0 +1,105 @@
+//
+// 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_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// Class to handle the creation of a payload file. This class is the only one
+// dealing with writing the payload and its format, but has no logic about what
+// should be on it.
+class PayloadFile {
+ public:
+  // Initialize the payload file with the payload generation config. It computes
+  // required hashes of the requested partitions.
+  bool Init(const PayloadGenerationConfig& config);
+
+  // Add a partition to the payload manifest. Including partition name, list of
+  // operations and partition info. The operations in |aops|
+  // reference a blob stored in the file provided to WritePayload().
+  bool AddPartition(const PartitionConfig& old_conf,
+                    const PartitionConfig& new_conf,
+                    const std::vector<AnnotatedOperation>& aops);
+
+  // Write the payload to the |payload_file| file. The operations reference
+  // blobs in the |data_blobs_path| file and the blobs will be reordered in the
+  // payload file to match the order of the operations. The size of the metadata
+  // section of the payload is stored in |metadata_size_out|.
+  bool WritePayload(const std::string& payload_file,
+                    const std::string& data_blobs_path,
+                    const std::string& private_key_path,
+                    uint64_t* metadata_size_out);
+
+ private:
+  FRIEND_TEST(PayloadFileTest, ReorderBlobsTest);
+
+  // Computes a SHA256 hash of the given buf and sets the hash value in the
+  // operation so that update_engine could verify. This hash should be set
+  // for all operations that have a non-zero data blob. One exception is the
+  // dummy operation for signature blob because the contents of the signature
+  // blob will not be available at payload creation time. So, update_engine will
+  // gracefully ignore the dummy signature operation.
+  static bool AddOperationHash(InstallOperation* op, const brillo::Blob& buf);
+
+  // Install operations in the manifest may reference data blobs, which
+  // are in data_blobs_path. This function creates a new data blobs file
+  // with the data blobs in the same order as the referencing install
+  // operations in the manifest. E.g. if manifest[0] has a data blob
+  // "X" at offset 1, manifest[1] has a data blob "Y" at offset 0,
+  // and data_blobs_path's file contains "YX", new_data_blobs_path
+  // will set to be a file that contains "XY".
+  bool ReorderDataBlobs(const std::string& data_blobs_path,
+                        const std::string& new_data_blobs_path);
+
+  // Print in stderr the Payload usage report.
+  void ReportPayloadUsage(uint64_t metadata_size) const;
+
+  // The major_version of the requested payload.
+  uint64_t major_version_;
+
+  DeltaArchiveManifest manifest_;
+
+  // Struct has necessary information to write PartitionUpdate in protobuf.
+  struct Partition {
+    // The name of the partition.
+    std::string name;
+
+    // The operations to be performed to this partition.
+    std::vector<AnnotatedOperation> aops;
+
+    PartitionInfo old_info;
+    PartitionInfo new_info;
+
+    PostInstallConfig postinstall;
+  };
+
+  std::vector<Partition> part_vec_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
diff --git a/update_engine/payload_generator/payload_file_unittest.cc b/update_engine/payload_generator/payload_file_unittest.cc
new file mode 100644
index 0000000..e8e7e14
--- /dev/null
+++ b/update_engine/payload_generator/payload_file_unittest.cc
@@ -0,0 +1,94 @@
+//
+// 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/payload_generator/payload_file.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PayloadFileTest : public ::testing::Test {
+ protected:
+  PayloadFile payload_;
+};
+
+TEST_F(PayloadFileTest, ReorderBlobsTest) {
+  string orig_blobs;
+  EXPECT_TRUE(utils::MakeTempFile("ReorderBlobsTest.orig.XXXXXX", &orig_blobs,
+                                  nullptr));
+  ScopedPathUnlinker orig_blobs_unlinker(orig_blobs);
+
+  // The operations have three blob and one gap (the whitespace):
+  // Rootfs operation 1: [8, 3] bcd
+  // Rootfs operation 2: [7, 1] a
+  // Kernel operation 1: [0, 6] kernel
+  string orig_data = "kernel abcd";
+  EXPECT_TRUE(
+      utils::WriteFile(orig_blobs.c_str(), orig_data.data(), orig_data.size()));
+
+  string new_blobs;
+  EXPECT_TRUE(
+      utils::MakeTempFile("ReorderBlobsTest.new.XXXXXX", &new_blobs, nullptr));
+  ScopedPathUnlinker new_blobs_unlinker(new_blobs);
+
+  payload_.part_vec_.resize(2);
+
+  vector<AnnotatedOperation> aops;
+  AnnotatedOperation aop;
+  aop.op.set_data_offset(8);
+  aop.op.set_data_length(3);
+  aops.push_back(aop);
+
+  aop.op.set_data_offset(7);
+  aop.op.set_data_length(1);
+  aops.push_back(aop);
+  payload_.part_vec_[0].aops = aops;
+
+  aop.op.set_data_offset(0);
+  aop.op.set_data_length(6);
+  payload_.part_vec_[1].aops = {aop};
+
+  EXPECT_TRUE(payload_.ReorderDataBlobs(orig_blobs, new_blobs));
+
+  const vector<AnnotatedOperation>& part0_aops = payload_.part_vec_[0].aops;
+  const vector<AnnotatedOperation>& part1_aops = payload_.part_vec_[1].aops;
+  string new_data;
+  EXPECT_TRUE(utils::ReadFile(new_blobs, &new_data));
+  // Kernel blobs should appear at the end.
+  EXPECT_EQ("bcdakernel", new_data);
+
+  EXPECT_EQ(2U, part0_aops.size());
+  EXPECT_EQ(0U, part0_aops[0].op.data_offset());
+  EXPECT_EQ(3U, part0_aops[0].op.data_length());
+  EXPECT_EQ(3U, part0_aops[1].op.data_offset());
+  EXPECT_EQ(1U, part0_aops[1].op.data_length());
+
+  EXPECT_EQ(1U, part1_aops.size());
+  EXPECT_EQ(4U, part1_aops[0].op.data_offset());
+  EXPECT_EQ(6U, part1_aops[0].op.data_length());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/payload_generation_config.cc b/update_engine/payload_generator/payload_generation_config.cc
new file mode 100644
index 0000000..38a72a9
--- /dev/null
+++ b/update_engine/payload_generator/payload_generation_config.cc
@@ -0,0 +1,216 @@
+//
+// 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/payload_generator/payload_generation_config.h"
+
+#include <base/logging.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/ext2_filesystem.h"
+#include "update_engine/payload_generator/raw_filesystem.h"
+
+namespace chromeos_update_engine {
+
+bool PostInstallConfig::IsEmpty() const {
+  return !run && path.empty() && filesystem_type.empty() && !optional;
+}
+
+bool PartitionConfig::ValidateExists() const {
+  TEST_AND_RETURN_FALSE(!path.empty());
+  TEST_AND_RETURN_FALSE(utils::FileExists(path.c_str()));
+  TEST_AND_RETURN_FALSE(size > 0);
+  // The requested size is within the limits of the file.
+  TEST_AND_RETURN_FALSE(static_cast<off_t>(size) <=
+                        utils::FileSize(path.c_str()));
+  return true;
+}
+
+bool PartitionConfig::OpenFilesystem() {
+  if (path.empty())
+    return true;
+  fs_interface.reset();
+  if (diff_utils::IsExtFilesystem(path)) {
+    fs_interface = Ext2Filesystem::CreateFromFile(path);
+    // TODO(deymo): The delta generator algorithm doesn't support a block size
+    // different than 4 KiB. Remove this check once that's fixed. b/26972455
+    if (fs_interface)
+      TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize);
+  }
+
+  if (!fs_interface) {
+    // Fall back to a RAW filesystem.
+    TEST_AND_RETURN_FALSE(size % kBlockSize == 0);
+    fs_interface = RawFilesystem::Create(
+      "<" + name + "-partition>",
+      kBlockSize,
+      size / kBlockSize);
+  }
+  return true;
+}
+
+bool ImageConfig::ValidateIsEmpty() const {
+  TEST_AND_RETURN_FALSE(ImageInfoIsEmpty());
+  return partitions.empty();
+}
+
+bool ImageConfig::LoadImageSize() {
+  for (PartitionConfig& part : partitions) {
+    if (part.path.empty())
+      continue;
+    part.size = utils::FileSize(part.path);
+  }
+  return true;
+}
+
+bool ImageConfig::LoadPostInstallConfig(const brillo::KeyValueStore& store) {
+  bool found_postinstall = false;
+  for (PartitionConfig& part : partitions) {
+    bool run_postinstall;
+    if (!store.GetBoolean("RUN_POSTINSTALL_" + part.name, &run_postinstall) ||
+        !run_postinstall)
+      continue;
+    found_postinstall = true;
+    part.postinstall.run = true;
+    store.GetString("POSTINSTALL_PATH_" + part.name, &part.postinstall.path);
+    store.GetString("FILESYSTEM_TYPE_" + part.name,
+                    &part.postinstall.filesystem_type);
+    store.GetBoolean("POSTINSTALL_OPTIONAL_" + part.name,
+                     &part.postinstall.optional);
+  }
+  if (!found_postinstall) {
+    LOG(ERROR) << "No valid postinstall config found.";
+    return false;
+  }
+  return true;
+}
+
+bool ImageConfig::ImageInfoIsEmpty() const {
+  return image_info.board().empty()
+    && image_info.key().empty()
+    && image_info.channel().empty()
+    && image_info.version().empty()
+    && image_info.build_channel().empty()
+    && image_info.build_version().empty();
+}
+
+PayloadVersion::PayloadVersion(uint64_t major_version, uint32_t minor_version) {
+  major = major_version;
+  minor = minor_version;
+}
+
+bool PayloadVersion::Validate() const {
+  TEST_AND_RETURN_FALSE(major == kChromeOSMajorPayloadVersion ||
+                        major == kBrilloMajorPayloadVersion);
+  TEST_AND_RETURN_FALSE(minor == kFullPayloadMinorVersion ||
+                        minor == kInPlaceMinorPayloadVersion ||
+                        minor == kSourceMinorPayloadVersion ||
+                        minor == kOpSrcHashMinorPayloadVersion ||
+                        minor == kImgdiffMinorPayloadVersion);
+  return true;
+}
+
+bool PayloadVersion::OperationAllowed(InstallOperation_Type operation) const {
+  switch (operation) {
+    // Full operations:
+    case InstallOperation::REPLACE:
+    case InstallOperation::REPLACE_BZ:
+      // These operations were included in the original payload format.
+      return true;
+
+    case InstallOperation::REPLACE_XZ:
+      // These operations are included in the major version used in Brillo, but
+      // can also be used with minor version 3 or newer.
+      return major == kBrilloMajorPayloadVersion ||
+             minor >= kOpSrcHashMinorPayloadVersion;
+
+    case InstallOperation::ZERO:
+    case InstallOperation::DISCARD:
+      // The implementation of these operations had a bug in earlier versions
+      // that prevents them from being used in any payload. We will enable
+      // them for delta payloads for now.
+      return minor >= kImgdiffMinorPayloadVersion;
+
+    // Delta operations:
+    case InstallOperation::MOVE:
+    case InstallOperation::BSDIFF:
+      // MOVE and BSDIFF were replaced by SOURCE_COPY and SOURCE_BSDIFF and
+      // should not be used in newer delta versions, since the idempotent checks
+      // were removed.
+      return minor == kInPlaceMinorPayloadVersion;
+
+    case InstallOperation::SOURCE_COPY:
+    case InstallOperation::SOURCE_BSDIFF:
+      return minor >= kSourceMinorPayloadVersion;
+
+    case InstallOperation::IMGDIFF:
+      return minor >= kImgdiffMinorPayloadVersion && imgdiff_allowed;
+  }
+  return false;
+}
+
+bool PayloadVersion::IsDelta() const {
+  return minor != kFullPayloadMinorVersion;
+}
+
+bool PayloadVersion::InplaceUpdate() const {
+  return minor == kInPlaceMinorPayloadVersion;
+}
+
+bool PayloadGenerationConfig::Validate() const {
+  TEST_AND_RETURN_FALSE(version.Validate());
+  TEST_AND_RETURN_FALSE(version.IsDelta() == is_delta);
+  if (is_delta) {
+    for (const PartitionConfig& part : source.partitions) {
+      if (!part.path.empty()) {
+        TEST_AND_RETURN_FALSE(part.ValidateExists());
+        TEST_AND_RETURN_FALSE(part.size % block_size == 0);
+      }
+      // Source partition should not have postinstall.
+      TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty());
+    }
+
+    // If new_image_info is present, old_image_info must be present.
+    TEST_AND_RETURN_FALSE(source.ImageInfoIsEmpty() ==
+                          target.ImageInfoIsEmpty());
+  } else {
+    // All the "source" image fields must be empty for full payloads.
+    TEST_AND_RETURN_FALSE(source.ValidateIsEmpty());
+  }
+
+  // In all cases, the target image must exists.
+  for (const PartitionConfig& part : target.partitions) {
+    TEST_AND_RETURN_FALSE(part.ValidateExists());
+    TEST_AND_RETURN_FALSE(part.size % block_size == 0);
+    if (version.minor == kInPlaceMinorPayloadVersion &&
+        part.name == kLegacyPartitionNameRoot)
+      TEST_AND_RETURN_FALSE(rootfs_partition_size >= part.size);
+    if (version.major == kChromeOSMajorPayloadVersion)
+      TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty());
+  }
+
+  TEST_AND_RETURN_FALSE(hard_chunk_size == -1 ||
+                        hard_chunk_size % block_size == 0);
+  TEST_AND_RETURN_FALSE(soft_chunk_size % block_size == 0);
+
+  TEST_AND_RETURN_FALSE(rootfs_partition_size % block_size == 0);
+
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/payload_generation_config.h b/update_engine/payload_generator/payload_generation_config.h
new file mode 100644
index 0000000..373c7cd
--- /dev/null
+++ b/update_engine/payload_generator/payload_generation_config.h
@@ -0,0 +1,192 @@
+//
+// 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_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
+
+#include <cstddef>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <brillo/key_value_store.h>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/filesystem_interface.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+struct PostInstallConfig {
+  // Whether the postinstall config is empty.
+  bool IsEmpty() const;
+
+  // Whether this partition carries a filesystem with post-install program that
+  // must be run to finalize the update process.
+  bool run = false;
+
+  // The path to the post-install program relative to the root of this
+  // filesystem.
+  std::string path;
+
+  // The filesystem type used to mount the partition in order to run the
+  // post-install program.
+  std::string filesystem_type;
+
+  // Whether this postinstall script should be ignored if it fails.
+  bool optional = false;
+};
+
+struct PartitionConfig {
+  explicit PartitionConfig(std::string name) : name(name) {}
+
+  // Returns whether the PartitionConfig is not an empty image and all the
+  // fields are set correctly to a valid image file.
+  bool ValidateExists() const;
+
+  // Open then filesystem stored in this partition and stores it in
+  // |fs_interface|. Returns whether opening the filesystem worked.
+  bool OpenFilesystem();
+
+  // The path to the partition file. This can be a regular file or a block
+  // device such as a loop device.
+  std::string path;
+
+  // The size of the data in |path|. If rootfs verification is used (verity)
+  // this value should match the size of the verity device for the rootfs, and
+  // the size of the whole kernel. This value could be smaller than the
+  // partition and is the size of the data update_engine assumes verified for
+  // the source image, and the size of that data it should generate for the
+  // target image.
+  uint64_t size = 0;
+
+  // The FilesystemInterface implementation used to access this partition's
+  // files.
+  std::unique_ptr<FilesystemInterface> fs_interface;
+
+  std::string name;
+
+  PostInstallConfig postinstall;
+};
+
+// The ImageConfig struct describes a pair of binaries kernel and rootfs and the
+// metadata associated with the image they are part of, like build number, size,
+// etc.
+struct ImageConfig {
+  // Returns whether the ImageConfig is an empty image.
+  bool ValidateIsEmpty() const;
+
+  // Load |rootfs_size| and |kernel.size| from the respective image files. For
+  // the kernel, the whole |kernel.path| file is assumed. For the rootfs, the
+  // size is detected from the filesystem.
+  // Returns whether the image size was properly detected.
+  bool LoadImageSize();
+
+  // Load postinstall config from a key value store.
+  bool LoadPostInstallConfig(const brillo::KeyValueStore& store);
+
+  // Returns whether the |image_info| field is empty.
+  bool ImageInfoIsEmpty() const;
+
+  // The ImageInfo message defined in the update_metadata.proto file describes
+  // the metadata of the image.
+  ImageInfo image_info;
+
+  // The updated partitions.
+  std::vector<PartitionConfig> partitions;
+};
+
+struct PayloadVersion {
+  PayloadVersion() : PayloadVersion(0, 0) {}
+  PayloadVersion(uint64_t major_version, uint32_t minor_version);
+
+  // Returns whether the PayloadVersion is valid.
+  bool Validate() const;
+
+  // Return whether the passed |operation| is allowed by this payload.
+  bool OperationAllowed(InstallOperation_Type operation) const;
+
+  // Whether this payload version is a delta payload.
+  bool IsDelta() const;
+
+  // Tells whether the update is done in-place, that is, whether the operations
+  // read and write from the same partition.
+  bool InplaceUpdate() const;
+
+  // The major version of the payload.
+  uint64_t major;
+
+  // The minor version of the payload.
+  uint32_t minor;
+
+  // Wheter the IMGDIFF operation is allowed based on the available compressor
+  // in the delta_generator and the one supported by the target.
+  bool imgdiff_allowed = false;
+};
+
+// The PayloadGenerationConfig struct encapsulates all the configuration to
+// build the requested payload. This includes information about the old and new
+// image as well as the restrictions applied to the payload (like minor-version
+// and full/delta payload).
+struct PayloadGenerationConfig {
+  // Returns whether the PayloadGenerationConfig is valid.
+  bool Validate() const;
+
+  // Image information about the new image that's the target of this payload.
+  ImageConfig target;
+
+  // Image information pertaining the old image, if any. This is only valid
+  // if is_full is false, so we are requested a delta payload.
+  ImageConfig source;
+
+  // Wheter the requested payload is a delta payload.
+  bool is_delta = false;
+
+  // The major/minor version of the payload.
+  PayloadVersion version;
+
+  // The size of the rootfs partition, that not necessarily is the same as the
+  // filesystem in either source or target version, since there is some space
+  // after the partition used to store the verity hashes and or the bootcache.
+  uint64_t rootfs_partition_size = 0;
+
+  // The |hard_chunk_size| is the maximum size that a single operation should
+  // write in the destination. Operations bigger than chunk_size should be
+  // split. A value of -1 means no hard chunk size limit. A very low limit
+  // means more operations, and less of a chance to reuse the data.
+  ssize_t hard_chunk_size = -1;
+
+  // The |soft_chunk_size| is the preferred chunk size to use when there's no
+  // significant impact to the operations. For example, REPLACE, MOVE and
+  // SOURCE_COPY operations are not significantly impacted by the chunk size,
+  // except for a few bytes overhead in the manifest to describe extra
+  // operations. On the other hand, splitting BSDIFF operations impacts the
+  // payload size since it is not possible to use the redundancy *between*
+  // chunks.
+  size_t soft_chunk_size = 2 * 1024 * 1024;
+
+  // TODO(deymo): Remove the block_size member and maybe replace it with a
+  // minimum alignment size for blocks (if needed). Algorithms should be able to
+  // pick the block_size they want, but for now only 4 KiB is supported.
+
+  // The block size used for all the operations in the manifest.
+  size_t block_size = 4096;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
diff --git a/update_engine/payload_generator/payload_generation_config_unittest.cc b/update_engine/payload_generator/payload_generation_config_unittest.cc
new file mode 100644
index 0000000..3545056
--- /dev/null
+++ b/update_engine/payload_generator/payload_generation_config_unittest.cc
@@ -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.
+//
+
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+class PayloadGenerationConfigTest : public ::testing::Test {};
+
+TEST_F(PayloadGenerationConfigTest, SimpleLoadPostInstallConfigTest) {
+  ImageConfig image_config;
+  image_config.partitions.emplace_back("root");
+  brillo::KeyValueStore store;
+  EXPECT_TRUE(
+      store.LoadFromString("RUN_POSTINSTALL_root=true\n"
+                           "POSTINSTALL_PATH_root=postinstall\n"
+                           "FILESYSTEM_TYPE_root=ext4\n"
+                           "POSTINSTALL_OPTIONAL_root=true"));
+  EXPECT_TRUE(image_config.LoadPostInstallConfig(store));
+  EXPECT_FALSE(image_config.partitions[0].postinstall.IsEmpty());
+  EXPECT_EQ(true, image_config.partitions[0].postinstall.run);
+  EXPECT_EQ("postinstall", image_config.partitions[0].postinstall.path);
+  EXPECT_EQ("ext4", image_config.partitions[0].postinstall.filesystem_type);
+  EXPECT_TRUE(image_config.partitions[0].postinstall.optional);
+}
+
+TEST_F(PayloadGenerationConfigTest, LoadPostInstallConfigNameMismatchTest) {
+  ImageConfig image_config;
+  image_config.partitions.emplace_back("system");
+  brillo::KeyValueStore store;
+  EXPECT_TRUE(
+      store.LoadFromString("RUN_POSTINSTALL_root=true\n"
+                           "POSTINSTALL_PATH_root=postinstall\n"
+                           "FILESYSTEM_TYPE_root=ext4"));
+  EXPECT_FALSE(image_config.LoadPostInstallConfig(store));
+  EXPECT_TRUE(image_config.partitions[0].postinstall.IsEmpty());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/payload_signer.cc b/update_engine/payload_generator/payload_signer.cc
new file mode 100644
index 0000000..824195d
--- /dev/null
+++ b/update_engine/payload_generator/payload_signer.cc
@@ -0,0 +1,554 @@
+//
+// 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/payload_generator/payload_signer.h"
+
+#include <endian.h>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <brillo/data_encoding.h>
+#include <brillo/streams/file_stream.h>
+#include <brillo/streams/stream.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The payload verifier will check all the signatures included in the payload
+// regardless of the version field. Old version of the verifier require the
+// version field to be included and be 1.
+const uint32_t kSignatureMessageLegacyVersion = 1;
+
+// Given raw |signatures|, packs them into a protobuf and serializes it into a
+// binary blob. Returns true on success, false otherwise.
+bool ConvertSignatureToProtobufBlob(const vector<brillo::Blob>& signatures,
+                                    brillo::Blob* out_signature_blob) {
+  // Pack it into a protobuf
+  Signatures out_message;
+  for (const brillo::Blob& signature : signatures) {
+    Signatures_Signature* sig_message = out_message.add_signatures();
+    // Set all the signatures with the same version number.
+    sig_message->set_version(kSignatureMessageLegacyVersion);
+    sig_message->set_data(signature.data(), signature.size());
+  }
+
+  // Serialize protobuf
+  string serialized;
+  TEST_AND_RETURN_FALSE(out_message.AppendToString(&serialized));
+  out_signature_blob->insert(out_signature_blob->end(),
+                             serialized.begin(),
+                             serialized.end());
+  LOG(INFO) << "Signature blob size: " << out_signature_blob->size();
+  return true;
+}
+
+// Given an unsigned payload under |payload_path| and the |signature_blob| and
+// |metadata_signature_blob| generates an updated payload that includes the
+// signatures. It populates |out_metadata_size| with the size of the final
+// manifest after adding the dummy signature operation, and
+// |out_signatures_offset| with the expected offset for the new blob, and
+// |out_metadata_signature_size| which will be size of |metadata_signature_blob|
+// if the payload major version supports metadata signature, 0 otherwise.
+// Returns true on success, false otherwise.
+bool AddSignatureBlobToPayload(const string& payload_path,
+                               const brillo::Blob& signature_blob,
+                               const brillo::Blob& metadata_signature_blob,
+                               brillo::Blob* out_payload,
+                               uint64_t* out_metadata_size,
+                               uint32_t* out_metadata_signature_size,
+                               uint64_t* out_signatures_offset) {
+  uint64_t manifest_offset = 20;
+  const int kProtobufSizeOffset = 12;
+
+  DeltaArchiveManifest manifest;
+  uint64_t metadata_size, major_version;
+  uint32_t metadata_signature_size;
+  TEST_AND_RETURN_FALSE(
+      PayloadSigner::LoadPayloadMetadata(payload_path,
+                                         nullptr,
+                                         &manifest,
+                                         &major_version,
+                                         &metadata_size,
+                                         &metadata_signature_size));
+
+  brillo::Blob payload;
+  TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload));
+
+  if (major_version == kBrilloMajorPayloadVersion) {
+    // Write metadata signature size in header.
+    uint32_t metadata_signature_size_be =
+        htobe32(metadata_signature_blob.size());
+    memcpy(payload.data() + manifest_offset, &metadata_signature_size_be,
+           sizeof(metadata_signature_size_be));
+    manifest_offset += sizeof(metadata_signature_size_be);
+    // Replace metadata signature.
+    payload.erase(payload.begin() + metadata_size,
+                  payload.begin() + metadata_size + metadata_signature_size);
+    payload.insert(payload.begin() + metadata_size,
+                   metadata_signature_blob.begin(),
+                   metadata_signature_blob.end());
+    metadata_signature_size = metadata_signature_blob.size();
+    LOG(INFO) << "Metadata signature size: " << metadata_signature_size;
+  }
+
+  // Is there already a signature op in place?
+  if (manifest.has_signatures_size()) {
+    // The signature op is tied to the size of the signature blob, but not it's
+    // contents. We don't allow the manifest to change if there is already an op
+    // present, because that might invalidate previously generated
+    // hashes/signatures.
+    if (manifest.signatures_size() != signature_blob.size()) {
+      LOG(ERROR) << "Attempt to insert different signature sized blob. "
+                 << "(current:" << manifest.signatures_size()
+                 << "new:" << signature_blob.size() << ")";
+      return false;
+    }
+
+    LOG(INFO) << "Matching signature sizes already present.";
+  } else {
+    // Updates the manifest to include the signature operation.
+    PayloadSigner::AddSignatureToManifest(
+        payload.size() - metadata_size - metadata_signature_size,
+        signature_blob.size(),
+        major_version == kChromeOSMajorPayloadVersion,
+        &manifest);
+
+    // Updates the payload to include the new manifest.
+    string serialized_manifest;
+    TEST_AND_RETURN_FALSE(manifest.AppendToString(&serialized_manifest));
+    LOG(INFO) << "Updated protobuf size: " << serialized_manifest.size();
+    payload.erase(payload.begin() + manifest_offset,
+                  payload.begin() + metadata_size);
+    payload.insert(payload.begin() + manifest_offset,
+                   serialized_manifest.begin(),
+                   serialized_manifest.end());
+
+    // Updates the protobuf size.
+    uint64_t size_be = htobe64(serialized_manifest.size());
+    memcpy(&payload[kProtobufSizeOffset], &size_be, sizeof(size_be));
+    metadata_size = serialized_manifest.size() + manifest_offset;
+
+    LOG(INFO) << "Updated payload size: " << payload.size();
+    LOG(INFO) << "Updated metadata size: " << metadata_size;
+  }
+  uint64_t signatures_offset = metadata_size + metadata_signature_size +
+                               manifest.signatures_offset();
+  LOG(INFO) << "Signature Blob Offset: " << signatures_offset;
+  payload.resize(signatures_offset);
+  payload.insert(payload.begin() + signatures_offset,
+                 signature_blob.begin(),
+                 signature_blob.end());
+
+  *out_payload = std::move(payload);
+  *out_metadata_size = metadata_size;
+  *out_metadata_signature_size = metadata_signature_size;
+  *out_signatures_offset = signatures_offset;
+  return true;
+}
+
+// Given a |payload| with correct signature op and metadata signature size in
+// header and |metadata_size|, |metadata_signature_size|, |signatures_offset|,
+// calculate hash for payload and metadata, save it to |out_hash_data| and
+// |out_metadata_hash|.
+bool CalculateHashFromPayload(const brillo::Blob& payload,
+                              const uint64_t metadata_size,
+                              const uint32_t metadata_signature_size,
+                              const uint64_t signatures_offset,
+                              brillo::Blob* out_hash_data,
+                              brillo::Blob* out_metadata_hash) {
+  if (out_metadata_hash) {
+    // Calculates the hash on the manifest.
+    TEST_AND_RETURN_FALSE(
+        HashCalculator::RawHashOfBytes(payload.data(), metadata_size,
+                                       out_metadata_hash));
+  }
+  if (out_hash_data) {
+    // Calculates the hash on the updated payload. Note that we skip metadata
+    // signature and payload signature.
+    HashCalculator calc;
+    TEST_AND_RETURN_FALSE(calc.Update(payload.data(), metadata_size));
+    TEST_AND_RETURN_FALSE(signatures_offset >=
+                          metadata_size + metadata_signature_size);
+    TEST_AND_RETURN_FALSE(calc.Update(
+        payload.data() + metadata_size + metadata_signature_size,
+        signatures_offset - metadata_size - metadata_signature_size));
+    TEST_AND_RETURN_FALSE(calc.Finalize());
+    *out_hash_data = calc.raw_hash();
+  }
+  return true;
+}
+
+}  // namespace
+
+void PayloadSigner::AddSignatureToManifest(uint64_t signature_blob_offset,
+                                           uint64_t signature_blob_length,
+                                           bool add_dummy_op,
+                                           DeltaArchiveManifest* manifest) {
+  LOG(INFO) << "Making room for signature in file";
+  manifest->set_signatures_offset(signature_blob_offset);
+  LOG(INFO) << "set? " << manifest->has_signatures_offset();
+  manifest->set_signatures_offset(signature_blob_offset);
+  manifest->set_signatures_size(signature_blob_length);
+  // Add a dummy op at the end to appease older clients
+  if (add_dummy_op) {
+    InstallOperation* dummy_op = manifest->add_kernel_install_operations();
+    dummy_op->set_type(InstallOperation::REPLACE);
+    dummy_op->set_data_offset(signature_blob_offset);
+    dummy_op->set_data_length(signature_blob_length);
+    Extent* dummy_extent = dummy_op->add_dst_extents();
+    // Tell the dummy op to write this data to a big sparse hole
+    dummy_extent->set_start_block(kSparseHole);
+    dummy_extent->set_num_blocks((signature_blob_length + kBlockSize - 1) /
+                                 kBlockSize);
+  }
+}
+
+bool PayloadSigner::LoadPayloadMetadata(const string& payload_path,
+                                        brillo::Blob* out_payload_metadata,
+                                        DeltaArchiveManifest* out_manifest,
+                                        uint64_t* out_major_version,
+                                        uint64_t* out_metadata_size,
+                                        uint32_t* out_metadata_signature_size) {
+  brillo::StreamPtr payload_file =
+      brillo::FileStream::Open(base::FilePath(payload_path),
+                               brillo::Stream::AccessMode::READ,
+                               brillo::FileStream::Disposition::OPEN_EXISTING,
+                               nullptr);
+  TEST_AND_RETURN_FALSE(payload_file);
+  brillo::Blob payload_metadata;
+
+  payload_metadata.resize(DeltaPerformer::kMaxPayloadHeaderSize);
+  TEST_AND_RETURN_FALSE(payload_file->ReadAllBlocking(
+      payload_metadata.data(), payload_metadata.size(), nullptr));
+
+  const uint8_t* read_pointer = payload_metadata.data();
+  TEST_AND_RETURN_FALSE(
+      memcmp(read_pointer, kDeltaMagic, sizeof(kDeltaMagic)) == 0);
+  read_pointer += sizeof(kDeltaMagic);
+
+  uint64_t major_version;
+  memcpy(&major_version, read_pointer, sizeof(major_version));
+  read_pointer += sizeof(major_version);
+  major_version = be64toh(major_version);
+  TEST_AND_RETURN_FALSE(major_version == kChromeOSMajorPayloadVersion ||
+                        major_version == kBrilloMajorPayloadVersion);
+  if (out_major_version)
+    *out_major_version = major_version;
+
+  uint64_t manifest_size = 0;
+  memcpy(&manifest_size, read_pointer, sizeof(manifest_size));
+  read_pointer += sizeof(manifest_size);
+  manifest_size = be64toh(manifest_size);
+
+  uint32_t metadata_signature_size = 0;
+  if (major_version == kBrilloMajorPayloadVersion) {
+    memcpy(&metadata_signature_size, read_pointer,
+           sizeof(metadata_signature_size));
+    read_pointer += sizeof(metadata_signature_size);
+    metadata_signature_size = be32toh(metadata_signature_size);
+  }
+  if (out_metadata_signature_size)
+    *out_metadata_signature_size = metadata_signature_size;
+
+  uint64_t header_size = read_pointer - payload_metadata.data();
+  uint64_t metadata_size = header_size + manifest_size;
+  if (out_metadata_size)
+    *out_metadata_size = metadata_size;
+
+  size_t bytes_read = payload_metadata.size();
+  payload_metadata.resize(metadata_size);
+  TEST_AND_RETURN_FALSE(
+      payload_file->ReadAllBlocking(payload_metadata.data() + bytes_read,
+                                    payload_metadata.size() - bytes_read,
+                                    nullptr));
+  if (out_manifest) {
+    TEST_AND_RETURN_FALSE(out_manifest->ParseFromArray(
+        payload_metadata.data() + header_size, manifest_size));
+  }
+  if (out_payload_metadata)
+    *out_payload_metadata = std::move(payload_metadata);
+  return true;
+}
+
+bool PayloadSigner::VerifySignedPayload(const string& payload_path,
+                                        const string& public_key_path) {
+  DeltaArchiveManifest manifest;
+  uint64_t metadata_size;
+  uint32_t metadata_signature_size;
+  TEST_AND_RETURN_FALSE(LoadPayloadMetadata(payload_path,
+                                            nullptr,
+                                            &manifest,
+                                            nullptr,
+                                            &metadata_size,
+                                            &metadata_signature_size));
+  brillo::Blob payload;
+  TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload));
+  TEST_AND_RETURN_FALSE(manifest.has_signatures_offset() &&
+                        manifest.has_signatures_size());
+  uint64_t signatures_offset = metadata_size + metadata_signature_size +
+                               manifest.signatures_offset();
+  CHECK_EQ(payload.size(), signatures_offset + manifest.signatures_size());
+  brillo::Blob payload_hash, metadata_hash;
+  TEST_AND_RETURN_FALSE(CalculateHashFromPayload(payload,
+                                                 metadata_size,
+                                                 metadata_signature_size,
+                                                 signatures_offset,
+                                                 &payload_hash,
+                                                 &metadata_hash));
+  brillo::Blob signature_blob(payload.begin() + signatures_offset,
+                              payload.end());
+  TEST_AND_RETURN_FALSE(PayloadVerifier::PadRSA2048SHA256Hash(&payload_hash));
+  TEST_AND_RETURN_FALSE(PayloadVerifier::VerifySignature(
+      signature_blob, public_key_path, payload_hash));
+  if (metadata_signature_size) {
+    signature_blob.assign(payload.begin() + metadata_size,
+                          payload.begin() + metadata_size +
+                          metadata_signature_size);
+    TEST_AND_RETURN_FALSE(
+        PayloadVerifier::PadRSA2048SHA256Hash(&metadata_hash));
+    TEST_AND_RETURN_FALSE(PayloadVerifier::VerifySignature(
+        signature_blob, public_key_path, metadata_hash));
+  }
+  return true;
+}
+
+bool PayloadSigner::SignHash(const brillo::Blob& hash,
+                             const string& private_key_path,
+                             brillo::Blob* out_signature) {
+  LOG(INFO) << "Signing hash with private key: " << private_key_path;
+  // We expect unpadded SHA256 hash coming in
+  TEST_AND_RETURN_FALSE(hash.size() == 32);
+  brillo::Blob padded_hash(hash);
+  PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash);
+
+  // The code below executes the equivalent of:
+  //
+  // openssl rsautl -raw -sign -inkey |private_key_path|
+  //   -in |padded_hash| -out |out_signature|
+
+  FILE* fprikey = fopen(private_key_path.c_str(), "rb");
+  TEST_AND_RETURN_FALSE(fprikey != nullptr);
+  RSA* rsa = PEM_read_RSAPrivateKey(fprikey, nullptr, nullptr, nullptr);
+  fclose(fprikey);
+  TEST_AND_RETURN_FALSE(rsa != nullptr);
+  brillo::Blob signature(RSA_size(rsa));
+  ssize_t signature_size = RSA_private_encrypt(padded_hash.size(),
+                                               padded_hash.data(),
+                                               signature.data(),
+                                               rsa,
+                                               RSA_NO_PADDING);
+  RSA_free(rsa);
+  if (signature_size < 0) {
+    LOG(ERROR) << "Signing hash failed: "
+               << ERR_error_string(ERR_get_error(), nullptr);
+    return false;
+  }
+  TEST_AND_RETURN_FALSE(static_cast<size_t>(signature_size) ==
+                        signature.size());
+  out_signature->swap(signature);
+  return true;
+}
+
+bool PayloadSigner::SignHashWithKeys(const brillo::Blob& hash_data,
+                                     const vector<string>& private_key_paths,
+                                     brillo::Blob* out_signature_blob) {
+  vector<brillo::Blob> signatures;
+  for (const string& path : private_key_paths) {
+    brillo::Blob signature;
+    TEST_AND_RETURN_FALSE(SignHash(hash_data, path, &signature));
+    signatures.push_back(signature);
+  }
+  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+                                                       out_signature_blob));
+  return true;
+}
+
+bool PayloadSigner::SignPayload(const string& unsigned_payload_path,
+                                const vector<string>& private_key_paths,
+                                const uint64_t metadata_size,
+                                const uint32_t metadata_signature_size,
+                                const uint64_t signatures_offset,
+                                brillo::Blob* out_signature_blob) {
+  brillo::Blob payload;
+  TEST_AND_RETURN_FALSE(utils::ReadFile(unsigned_payload_path, &payload));
+  brillo::Blob hash_data;
+  TEST_AND_RETURN_FALSE(CalculateHashFromPayload(payload,
+                                                 metadata_size,
+                                                 metadata_signature_size,
+                                                 signatures_offset,
+                                                 &hash_data,
+                                                 nullptr));
+  TEST_AND_RETURN_FALSE(SignHashWithKeys(hash_data,
+                                         private_key_paths,
+                                         out_signature_blob));
+  return true;
+}
+
+bool PayloadSigner::SignatureBlobLength(const vector<string>& private_key_paths,
+                                        uint64_t* out_length) {
+  DCHECK(out_length);
+  brillo::Blob x_blob(1, 'x'), hash_blob, sig_blob;
+  TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfBytes(x_blob.data(),
+                                                       x_blob.size(),
+                                                       &hash_blob));
+  TEST_AND_RETURN_FALSE(
+      SignHashWithKeys(hash_blob, private_key_paths, &sig_blob));
+  *out_length = sig_blob.size();
+  return true;
+}
+
+bool PayloadSigner::HashPayloadForSigning(const string& payload_path,
+                                          const vector<int>& signature_sizes,
+                                          brillo::Blob* out_payload_hash_data,
+                                          brillo::Blob* out_metadata_hash) {
+  // Create a signature blob with signatures filled with 0.
+  // Will be used for both payload signature and metadata signature.
+  vector<brillo::Blob> signatures;
+  for (int signature_size : signature_sizes) {
+    signatures.emplace_back(signature_size, 0);
+  }
+  brillo::Blob signature_blob;
+  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+                                                       &signature_blob));
+
+  brillo::Blob payload;
+  uint64_t metadata_size, signatures_offset;
+  uint32_t metadata_signature_size;
+  // Prepare payload for hashing.
+  TEST_AND_RETURN_FALSE(AddSignatureBlobToPayload(payload_path,
+                                                  signature_blob,
+                                                  signature_blob,
+                                                  &payload,
+                                                  &metadata_size,
+                                                  &metadata_signature_size,
+                                                  &signatures_offset));
+  TEST_AND_RETURN_FALSE(CalculateHashFromPayload(payload,
+                                                 metadata_size,
+                                                 metadata_signature_size,
+                                                 signatures_offset,
+                                                 out_payload_hash_data,
+                                                 out_metadata_hash));
+  return true;
+}
+
+bool PayloadSigner::AddSignatureToPayload(
+    const string& payload_path,
+    const vector<brillo::Blob>& payload_signatures,
+    const vector<brillo::Blob>& metadata_signatures,
+    const string& signed_payload_path,
+    uint64_t *out_metadata_size) {
+  // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
+
+  // Loads the payload and adds the signature op to it.
+  brillo::Blob signature_blob, metadata_signature_blob;
+  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(payload_signatures,
+                                                       &signature_blob));
+  if (!metadata_signatures.empty()) {
+    TEST_AND_RETURN_FALSE(
+        ConvertSignatureToProtobufBlob(metadata_signatures,
+                                       &metadata_signature_blob));
+  }
+  brillo::Blob payload;
+  uint64_t signatures_offset;
+  uint32_t metadata_signature_size;
+  TEST_AND_RETURN_FALSE(AddSignatureBlobToPayload(payload_path,
+                                                  signature_blob,
+                                                  metadata_signature_blob,
+                                                  &payload,
+                                                  out_metadata_size,
+                                                  &metadata_signature_size,
+                                                  &signatures_offset));
+
+  LOG(INFO) << "Signed payload size: " << payload.size();
+  TEST_AND_RETURN_FALSE(utils::WriteFile(signed_payload_path.c_str(),
+                                         payload.data(),
+                                         payload.size()));
+  return true;
+}
+
+bool PayloadSigner::GetMetadataSignature(const void* const metadata,
+                                         size_t metadata_size,
+                                         const string& private_key_path,
+                                         string* out_signature) {
+  // Calculates the hash on the updated payload. Note that the payload includes
+  // the signature op but doesn't include the signature blob at the end.
+  brillo::Blob metadata_hash;
+  TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfBytes(metadata,
+                                                       metadata_size,
+                                                       &metadata_hash));
+
+  brillo::Blob signature;
+  TEST_AND_RETURN_FALSE(SignHash(metadata_hash,
+                                 private_key_path,
+                                 &signature));
+
+  *out_signature = brillo::data_encoding::Base64Encode(signature);
+  return true;
+}
+
+bool PayloadSigner::ExtractPayloadProperties(
+    const string& payload_path, brillo::KeyValueStore* properties) {
+  DeltaArchiveManifest manifest;
+  brillo::Blob payload_metadata;
+  uint64_t major_version, metadata_size;
+  uint32_t metadata_signature_size;
+  uint64_t file_size = utils::FileSize(payload_path);
+
+  TEST_AND_RETURN_FALSE(
+      PayloadSigner::LoadPayloadMetadata(payload_path,
+                                         &payload_metadata,
+                                         &manifest,
+                                         &major_version,
+                                         &metadata_size,
+                                         &metadata_signature_size));
+
+  properties->SetString(kPayloadPropertyFileSize, std::to_string(file_size));
+  properties->SetString(kPayloadPropertyMetadataSize,
+                        std::to_string(metadata_size));
+
+  brillo::Blob file_hash, metadata_hash;
+  TEST_AND_RETURN_FALSE(
+      HashCalculator::RawHashOfFile(payload_path, file_size, &file_hash) ==
+      static_cast<off_t>(file_size));
+  TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfBytes(
+      payload_metadata.data(), payload_metadata.size(), &metadata_hash));
+
+  properties->SetString(kPayloadPropertyFileHash,
+                        brillo::data_encoding::Base64Encode(file_hash));
+  properties->SetString(kPayloadPropertyMetadataHash,
+                        brillo::data_encoding::Base64Encode(metadata_hash));
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/payload_signer.h b/update_engine/payload_generator/payload_signer.h
new file mode 100644
index 0000000..00e32fa
--- /dev/null
+++ b/update_engine/payload_generator/payload_signer.h
@@ -0,0 +1,147 @@
+//
+// 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_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/key_value_store.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signing.
+// See update_metadata.proto for more info.
+
+namespace chromeos_update_engine {
+
+class PayloadSigner {
+ public:
+  // Reads the payload metadata from the given |payload_path| into the
+  // |out_payload_metadata| vector if not null. It also parses the manifest
+  // protobuf in the payload and returns it in |out_manifest| if not null, along
+  // with the major version of the payload in |out_major_version| if not null,
+  // the size of the entire metadata in |out_metadata_size| and the size of
+  // metadata signature in |out_metadata_signature_size| if not null. Returns
+  // whether a valid payload metadata was found and parsed.
+  static bool LoadPayloadMetadata(const std::string& payload_path,
+                                  brillo::Blob* out_payload_metadata,
+                                  DeltaArchiveManifest* out_manifest,
+                                  uint64_t* out_major_version,
+                                  uint64_t* out_metadata_size,
+                                  uint32_t* out_metadata_signature_size);
+
+  // Returns true if the payload in |payload_path| is signed and its hash can be
+  // verified using the public key in |public_key_path| with the signature
+  // of a given version in the signature blob. Returns false otherwise.
+  static bool VerifySignedPayload(const std::string& payload_path,
+                                  const std::string& public_key_path);
+
+  // Adds specified signature offset/length to given |manifest|, also adds a
+  // dummy operation that points to a signature blob located at the specified
+  // offset/length if |add_dummy_op| is true.
+  static void AddSignatureToManifest(uint64_t signature_blob_offset,
+                                     uint64_t signature_blob_length,
+                                     bool add_dummy_op,
+                                     DeltaArchiveManifest* manifest);
+
+  // Given a raw |hash| and a private key in |private_key_path| calculates the
+  // raw signature in |out_signature|. Returns true on success, false otherwise.
+  static bool SignHash(const brillo::Blob& hash,
+                       const std::string& private_key_path,
+                       brillo::Blob* out_signature);
+
+  // Sign |hash_data| blob with all private keys in |private_key_paths|, then
+  // convert the signatures to protobuf blob.
+  static bool SignHashWithKeys(
+      const brillo::Blob& hash_data,
+      const std::vector<std::string>& private_key_paths,
+      brillo::Blob* out_signature_blob);
+
+  // Given an unsigned payload in |unsigned_payload_path|, private keys in
+  // |private_key_path|, metadata size in |metadata_size|, metadata signature
+  // size in |metadata_signature_size| and signatures offset in
+  // |signatures_offset|, calculates the payload signature blob into
+  // |out_signature_blob|. Note that the payload must already have an
+  // updated manifest that includes the dummy signature op and correct metadata
+  // signature size in header. Returns true on success, false otherwise.
+  static bool SignPayload(const std::string& unsigned_payload_path,
+                          const std::vector<std::string>& private_key_paths,
+                          const uint64_t metadata_size,
+                          const uint32_t metadata_signature_size,
+                          const uint64_t signatures_offset,
+                          brillo::Blob* out_signature_blob);
+
+  // Returns the length of out_signature_blob that will result in a call
+  // to SignPayload with the given private keys. Returns true on success.
+  static bool SignatureBlobLength(
+      const std::vector<std::string>& private_key_paths,
+      uint64_t* out_length);
+
+  // Given an unsigned payload in |payload_path|,
+  // this method does two things:
+  // 1. It loads the payload into memory, and inserts placeholder signature
+  //    operations and placeholder metadata signature to make the header and
+  //    the manifest match what the final signed payload will look like based
+  //    on |signatures_sizes|, if needed.
+  // 2. It calculates the raw SHA256 hash of the payload and the metadata in
+  //    |payload_path| (except signatures) and returns the result in
+  //    |out_hash_data| and |out_metadata_hash| respectively.
+  //
+  // The changes to payload are not preserved or written to disk.
+  static bool HashPayloadForSigning(const std::string& payload_path,
+                                    const std::vector<int>& signature_sizes,
+                                    brillo::Blob* out_payload_hash_data,
+                                    brillo::Blob* out_metadata_hash);
+
+  // Given an unsigned payload in |payload_path| (with no dummy signature op)
+  // and the raw |payload_signatures| and |metadata_signatures| updates the
+  // payload to include the signature thus turning it into a signed payload. The
+  // new payload is stored in |signed_payload_path|. |payload_path| and
+  // |signed_payload_path| can point to the same file. Populates
+  // |out_metadata_size| with the size of the metadata after adding the
+  // signature operation in the manifest. Returns true on success, false
+  // otherwise.
+  static bool AddSignatureToPayload(
+      const std::string& payload_path,
+      const std::vector<brillo::Blob>& payload_signatures,
+      const std::vector<brillo::Blob>& metadata_signatures,
+      const std::string& signed_payload_path,
+      uint64_t* out_metadata_size);
+
+  // Computes the SHA256 hash of the first metadata_size bytes of |metadata|
+  // and signs the hash with the given private_key_path and writes the signed
+  // hash in |out_signature|. Returns true if successful or false if there was
+  // any error in the computations.
+  static bool GetMetadataSignature(const void* const metadata,
+                                   size_t metadata_size,
+                                   const std::string& private_key_path,
+                                   std::string* out_signature);
+
+  static bool ExtractPayloadProperties(const std::string& payload_path,
+                                       brillo::KeyValueStore* properties);
+
+ private:
+  // This should never be constructed
+  DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadSigner);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
diff --git a/update_engine/payload_generator/payload_signer_unittest.cc b/update_engine/payload_generator/payload_signer_unittest.cc
new file mode 100644
index 0000000..62b6e7a
--- /dev/null
+++ b/update_engine/payload_generator/payload_signer_unittest.cc
@@ -0,0 +1,264 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_generator/payload_signer.h"
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/update_metadata.pb.h"
+
+using chromeos_update_engine::test_utils::GetBuildArtifactsPath;
+using std::string;
+using std::vector;
+
+// Note: the test key was generated with the following command:
+// openssl genrsa -out unittest_key.pem 2048
+// The public-key version is created by the build system.
+
+namespace chromeos_update_engine {
+
+const char* kUnittestPrivateKeyPath = "unittest_key.pem";
+const char* kUnittestPublicKeyPath = "unittest_key.pub.pem";
+const char* kUnittestPrivateKey2Path = "unittest_key2.pem";
+const char* kUnittestPublicKey2Path = "unittest_key2.pub.pem";
+
+// Some data and its corresponding hash and signature:
+const char kDataToSign[] = "This is some data to sign.";
+
+// Generated by:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+//   hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const uint8_t kDataHash[] = {
+  0x7a, 0x07, 0xa6, 0x44, 0x08, 0x86, 0x20, 0xa6,
+  0xc1, 0xf8, 0xd9, 0x02, 0x05, 0x63, 0x0d, 0xb7,
+  0xfc, 0x2b, 0xa0, 0xa9, 0x7c, 0x9d, 0x1d, 0x8c,
+  0x01, 0xf5, 0x78, 0x6d, 0xc5, 0x11, 0xb4, 0x06
+};
+
+// Generated with openssl 1.0, which at the time of this writing, you need
+// to download and install yourself. Here's my command:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+//    ~/local/bin/openssl pkeyutl -sign -inkey unittest_key.pem -pkeyopt
+//    digest:sha256 | hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const uint8_t kDataSignature[] = {
+  0x9f, 0x86, 0x25, 0x8b, 0xf3, 0xcc, 0xe3, 0x95,
+  0x5f, 0x45, 0x83, 0xb2, 0x66, 0xf0, 0x2a, 0xcf,
+  0xb7, 0xaa, 0x52, 0x25, 0x7a, 0xdd, 0x9d, 0x65,
+  0xe5, 0xd6, 0x02, 0x4b, 0x37, 0x99, 0x53, 0x06,
+  0xc2, 0xc9, 0x37, 0x36, 0x25, 0x62, 0x09, 0x4f,
+  0x6b, 0x22, 0xf8, 0xb3, 0x89, 0x14, 0x98, 0x1a,
+  0xbc, 0x30, 0x90, 0x4a, 0x43, 0xf5, 0xea, 0x2e,
+  0xf0, 0xa4, 0xba, 0xc3, 0xa7, 0xa3, 0x44, 0x70,
+  0xd6, 0xc4, 0x89, 0xd8, 0x45, 0x71, 0xbb, 0xee,
+  0x59, 0x87, 0x3d, 0xd5, 0xe5, 0x40, 0x22, 0x3d,
+  0x73, 0x7e, 0x2a, 0x58, 0x93, 0x8e, 0xcb, 0x9c,
+  0xf2, 0xbb, 0x4a, 0xc9, 0xd2, 0x2c, 0x52, 0x42,
+  0xb0, 0xd1, 0x13, 0x22, 0xa4, 0x78, 0xc7, 0xc6,
+  0x3e, 0xf1, 0xdc, 0x4c, 0x7b, 0x2d, 0x40, 0xda,
+  0x58, 0xac, 0x4a, 0x11, 0x96, 0x3d, 0xa0, 0x01,
+  0xf6, 0x96, 0x74, 0xf6, 0x6c, 0x0c, 0x49, 0x69,
+  0x4e, 0xc1, 0x7e, 0x9f, 0x2a, 0x42, 0xdd, 0x15,
+  0x6b, 0x37, 0x2e, 0x3a, 0xa7, 0xa7, 0x6d, 0x91,
+  0x13, 0xe8, 0x59, 0xde, 0xfe, 0x99, 0x07, 0xd9,
+  0x34, 0x0f, 0x17, 0xb3, 0x05, 0x4c, 0xd2, 0xc6,
+  0x82, 0xb7, 0x38, 0x36, 0x63, 0x1d, 0x9e, 0x21,
+  0xa6, 0x32, 0xef, 0xf1, 0x65, 0xe6, 0xed, 0x95,
+  0x25, 0x9b, 0x61, 0xe0, 0xba, 0x86, 0xa1, 0x7f,
+  0xf8, 0xa5, 0x4a, 0x32, 0x1f, 0x15, 0x20, 0x8a,
+  0x41, 0xc5, 0xb0, 0xd9, 0x4a, 0xda, 0x85, 0xf3,
+  0xdc, 0xa0, 0x98, 0x5d, 0x1d, 0x18, 0x9d, 0x2e,
+  0x42, 0xea, 0x69, 0x13, 0x74, 0x3c, 0x74, 0xf7,
+  0x6d, 0x43, 0xb0, 0x63, 0x90, 0xdb, 0x04, 0xd5,
+  0x05, 0xc9, 0x73, 0x1f, 0x6c, 0xd6, 0xfa, 0x46,
+  0x4e, 0x0f, 0x33, 0x58, 0x5b, 0x0d, 0x1b, 0x55,
+  0x39, 0xb9, 0x0f, 0x43, 0x37, 0xc0, 0x06, 0x0c,
+  0x29, 0x93, 0x43, 0xc7, 0x43, 0xb9, 0xab, 0x7d
+};
+
+namespace {
+void SignSampleData(brillo::Blob* out_signature_blob,
+                    const vector<string>& private_keys) {
+  brillo::Blob data_blob(std::begin(kDataToSign),
+                         std::begin(kDataToSign) + strlen(kDataToSign));
+  uint64_t length = 0;
+  EXPECT_TRUE(PayloadSigner::SignatureBlobLength(private_keys, &length));
+  EXPECT_GT(length, 0U);
+  brillo::Blob hash_blob;
+  EXPECT_TRUE(HashCalculator::RawHashOfBytes(data_blob.data(),
+                                             data_blob.size(),
+                                             &hash_blob));
+  EXPECT_TRUE(PayloadSigner::SignHashWithKeys(
+      hash_blob,
+      private_keys,
+      out_signature_blob));
+  EXPECT_EQ(length, out_signature_blob->size());
+}
+}  // namespace
+
+class PayloadSignerTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash_data_);
+  }
+
+  void DoWriteAndLoadPayloadTest(const PayloadGenerationConfig& config) {
+    PayloadFile payload;
+    payload.Init(config);
+    string payload_path;
+    EXPECT_TRUE(utils::MakeTempFile("payload.XXXXXX", &payload_path, nullptr));
+    ScopedPathUnlinker payload_path_unlinker(payload_path);
+    uint64_t metadata_size;
+    EXPECT_TRUE(
+        payload.WritePayload(payload_path, "/dev/null", "", &metadata_size));
+    brillo::Blob payload_metadata_blob;
+    DeltaArchiveManifest manifest;
+    uint64_t load_metadata_size, load_major_version;
+    EXPECT_TRUE(PayloadSigner::LoadPayloadMetadata(payload_path,
+                                                   &payload_metadata_blob,
+                                                   &manifest,
+                                                   &load_major_version,
+                                                   &load_metadata_size,
+                                                   nullptr));
+    EXPECT_EQ(metadata_size, payload_metadata_blob.size());
+    EXPECT_EQ(config.version.major, load_major_version);
+    EXPECT_EQ(metadata_size, load_metadata_size);
+  }
+
+  brillo::Blob padded_hash_data_{std::begin(kDataHash), std::end(kDataHash)};
+};
+
+TEST_F(PayloadSignerTest, LoadPayloadV1Test) {
+  PayloadGenerationConfig config;
+  config.version.major = kChromeOSMajorPayloadVersion;
+  DoWriteAndLoadPayloadTest(config);
+}
+
+TEST_F(PayloadSignerTest, LoadPayloadV2Test) {
+  PayloadGenerationConfig config;
+  config.version.major = kBrilloMajorPayloadVersion;
+  DoWriteAndLoadPayloadTest(config);
+}
+
+TEST_F(PayloadSignerTest, SignSimpleTextTest) {
+  brillo::Blob signature_blob;
+  SignSampleData(&signature_blob,
+                 {GetBuildArtifactsPath(kUnittestPrivateKeyPath)});
+
+  // Check the signature itself
+  Signatures signatures;
+  EXPECT_TRUE(signatures.ParseFromArray(signature_blob.data(),
+                                        signature_blob.size()));
+  EXPECT_EQ(1, signatures.signatures_size());
+  const Signatures_Signature& signature = signatures.signatures(0);
+  EXPECT_EQ(1U, signature.version());
+  const string& sig_data = signature.data();
+  ASSERT_EQ(arraysize(kDataSignature), sig_data.size());
+  for (size_t i = 0; i < arraysize(kDataSignature); i++) {
+    EXPECT_EQ(kDataSignature[i], static_cast<uint8_t>(sig_data[i]));
+  }
+}
+
+TEST_F(PayloadSignerTest, VerifyAllSignatureTest) {
+  brillo::Blob signature_blob;
+  SignSampleData(&signature_blob,
+                 {GetBuildArtifactsPath(kUnittestPrivateKeyPath),
+                  GetBuildArtifactsPath(kUnittestPrivateKey2Path)});
+
+  // Either public key should pass the verification.
+  EXPECT_TRUE(PayloadVerifier::VerifySignature(
+      signature_blob,
+      GetBuildArtifactsPath(kUnittestPublicKeyPath),
+      padded_hash_data_));
+  EXPECT_TRUE(PayloadVerifier::VerifySignature(
+      signature_blob,
+      GetBuildArtifactsPath(kUnittestPublicKey2Path),
+      padded_hash_data_));
+}
+
+TEST_F(PayloadSignerTest, VerifySignatureTest) {
+  brillo::Blob signature_blob;
+  SignSampleData(&signature_blob,
+                 {GetBuildArtifactsPath(kUnittestPrivateKeyPath)});
+
+  EXPECT_TRUE(PayloadVerifier::VerifySignature(
+      signature_blob,
+      GetBuildArtifactsPath(kUnittestPublicKeyPath),
+      padded_hash_data_));
+  // Passing the invalid key should fail the verification.
+  EXPECT_FALSE(PayloadVerifier::VerifySignature(
+      signature_blob,
+      GetBuildArtifactsPath(kUnittestPublicKey2Path),
+      padded_hash_data_));
+}
+
+TEST_F(PayloadSignerTest, SkipMetadataSignatureTest) {
+  string payload_path;
+  EXPECT_TRUE(utils::MakeTempFile("payload.XXXXXX", &payload_path, nullptr));
+  ScopedPathUnlinker payload_path_unlinker(payload_path);
+
+  PayloadGenerationConfig config;
+  config.version.major = kBrilloMajorPayloadVersion;
+  PayloadFile payload;
+  EXPECT_TRUE(payload.Init(config));
+  uint64_t metadata_size;
+  EXPECT_TRUE(
+      payload.WritePayload(payload_path, "/dev/null", "", &metadata_size));
+  const vector<int> sizes = {256};
+  brillo::Blob unsigned_payload_hash, unsigned_metadata_hash;
+  EXPECT_TRUE(PayloadSigner::HashPayloadForSigning(
+      payload_path, sizes, &unsigned_payload_hash, &unsigned_metadata_hash));
+  EXPECT_TRUE(
+      payload.WritePayload(payload_path,
+                           "/dev/null",
+                           GetBuildArtifactsPath(kUnittestPrivateKeyPath),
+                           &metadata_size));
+  brillo::Blob signed_payload_hash, signed_metadata_hash;
+  EXPECT_TRUE(PayloadSigner::HashPayloadForSigning(
+      payload_path, sizes, &signed_payload_hash, &signed_metadata_hash));
+  EXPECT_EQ(unsigned_payload_hash, signed_payload_hash);
+  EXPECT_EQ(unsigned_metadata_hash, signed_metadata_hash);
+}
+
+TEST_F(PayloadSignerTest, VerifySignedPayloadTest) {
+  string payload_path;
+  EXPECT_TRUE(utils::MakeTempFile("payload.XXXXXX", &payload_path, nullptr));
+  ScopedPathUnlinker payload_path_unlinker(payload_path);
+
+  PayloadGenerationConfig config;
+  config.version.major = kBrilloMajorPayloadVersion;
+  PayloadFile payload;
+  EXPECT_TRUE(payload.Init(config));
+  uint64_t metadata_size;
+  EXPECT_TRUE(
+      payload.WritePayload(payload_path,
+                           "/dev/null",
+                           GetBuildArtifactsPath(kUnittestPrivateKeyPath),
+                           &metadata_size));
+  EXPECT_TRUE(PayloadSigner::VerifySignedPayload(
+      payload_path, GetBuildArtifactsPath(kUnittestPublicKeyPath)));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/raw_filesystem.cc b/update_engine/payload_generator/raw_filesystem.cc
new file mode 100644
index 0000000..2fb1400
--- /dev/null
+++ b/update_engine/payload_generator/raw_filesystem.cc
@@ -0,0 +1,53 @@
+//
+// 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/payload_generator/raw_filesystem.h"
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_engine {
+
+unique_ptr<RawFilesystem> RawFilesystem::Create(
+      const std::string& filename, uint64_t block_size, uint64_t block_count) {
+  unique_ptr<RawFilesystem> result(new RawFilesystem());
+  result->filename_ = filename;
+  result->block_size_ = block_size;
+  result->block_count_ = block_count;
+  return result;
+}
+
+size_t RawFilesystem::GetBlockSize() const {
+  return block_size_;
+}
+
+size_t RawFilesystem::GetBlockCount() const {
+  return block_count_;
+}
+
+bool RawFilesystem::GetFiles(std::vector<File>* files) const {
+  files->clear();
+  File file;
+  file.name = filename_;
+  file.extents = { ExtentForRange(0, block_count_) };
+  files->push_back(file);
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/raw_filesystem.h b/update_engine/payload_generator/raw_filesystem.h
new file mode 100644
index 0000000..0aecd81
--- /dev/null
+++ b/update_engine/payload_generator/raw_filesystem.h
@@ -0,0 +1,60 @@
+//
+// 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_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
+
+// A simple filesystem interface implementation used for unknown filesystem
+// format such as the kernel.
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <string>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+class RawFilesystem : public FilesystemInterface {
+ public:
+  static std::unique_ptr<RawFilesystem> Create(
+      const std::string& filename, uint64_t block_size, uint64_t block_count);
+  virtual ~RawFilesystem() = default;
+
+  // FilesystemInterface overrides.
+  size_t GetBlockSize() const override;
+  size_t GetBlockCount() const override;
+
+  // GetFiles will return only one file with all the blocks of the filesystem
+  // with the name passed during construction.
+  bool GetFiles(std::vector<File>* files) const override;
+
+  bool LoadSettings(brillo::KeyValueStore* store) const override {
+    return false;
+  }
+
+ private:
+  RawFilesystem() = default;
+
+  std::string filename_;
+  uint64_t block_count_;
+  uint64_t block_size_;
+
+  DISALLOW_COPY_AND_ASSIGN(RawFilesystem);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
diff --git a/update_engine/payload_generator/tarjan.cc b/update_engine/payload_generator/tarjan.cc
new file mode 100644
index 0000000..98e29f9
--- /dev/null
+++ b/update_engine/payload_generator/tarjan.cc
@@ -0,0 +1,83 @@
+//
+// 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.
+//
+#include "update_engine/payload_generator/tarjan.h"
+
+#include <algorithm>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/common/utils.h"
+
+using std::min;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const vector<Vertex>::size_type kInvalidIndex = -1;
+}
+
+void TarjanAlgorithm::Execute(Vertex::Index vertex,
+                              Graph* graph,
+                              vector<Vertex::Index>* out) {
+  stack_.clear();
+  components_.clear();
+  index_ = 0;
+  for (Graph::iterator it = graph->begin(); it != graph->end(); ++it)
+    it->index = it->lowlink = kInvalidIndex;
+  required_vertex_ = vertex;
+
+  Tarjan(vertex, graph);
+  if (!components_.empty())
+    out->swap(components_[0]);
+}
+
+void TarjanAlgorithm::Tarjan(Vertex::Index vertex, Graph* graph) {
+  CHECK_EQ((*graph)[vertex].index, kInvalidIndex);
+  (*graph)[vertex].index = index_;
+  (*graph)[vertex].lowlink = index_;
+  index_++;
+  stack_.push_back(vertex);
+  for (Vertex::EdgeMap::iterator it = (*graph)[vertex].out_edges.begin();
+       it != (*graph)[vertex].out_edges.end(); ++it) {
+    Vertex::Index vertex_next = it->first;
+    if ((*graph)[vertex_next].index == kInvalidIndex) {
+      Tarjan(vertex_next, graph);
+      (*graph)[vertex].lowlink = min((*graph)[vertex].lowlink,
+                                     (*graph)[vertex_next].lowlink);
+    } else if (utils::VectorContainsValue(stack_, vertex_next)) {
+      (*graph)[vertex].lowlink = min((*graph)[vertex].lowlink,
+                                     (*graph)[vertex_next].index);
+    }
+  }
+  if ((*graph)[vertex].lowlink == (*graph)[vertex].index) {
+    vector<Vertex::Index> component;
+    Vertex::Index other_vertex;
+    do {
+      other_vertex = stack_.back();
+      stack_.pop_back();
+      component.push_back(other_vertex);
+    } while (other_vertex != vertex && !stack_.empty());
+
+    if (utils::VectorContainsValue(component, required_vertex_)) {
+      components_.resize(components_.size() + 1);
+      component.swap(components_.back());
+    }
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/tarjan.h b/update_engine/payload_generator/tarjan.h
new file mode 100644
index 0000000..50cf563
--- /dev/null
+++ b/update_engine/payload_generator/tarjan.h
@@ -0,0 +1,52 @@
+//
+// 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_PAYLOAD_GENERATOR_TARJAN_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_TARJAN_H_
+
+// This is an implementation of Tarjan's algorithm which finds all
+// Strongly Connected Components in a graph.
+
+// Note: a true Tarjan algorithm would find all strongly connected components
+// in the graph. This implementation will only find the strongly connected
+// component containing the vertex passed in.
+
+#include <vector>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+class TarjanAlgorithm {
+ public:
+  TarjanAlgorithm() : index_(0), required_vertex_(0) {}
+
+  // 'out' is set to the result if there is one, otherwise it's untouched.
+  void Execute(Vertex::Index vertex,
+               Graph* graph,
+               std::vector<Vertex::Index>* out);
+ private:
+  void Tarjan(Vertex::Index vertex, Graph* graph);
+
+  Vertex::Index index_;
+  Vertex::Index required_vertex_;
+  std::vector<Vertex::Index> stack_;
+  std::vector<std::vector<Vertex::Index>> components_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_TARJAN_H_
diff --git a/update_engine/payload_generator/tarjan_unittest.cc b/update_engine/payload_generator/tarjan_unittest.cc
new file mode 100644
index 0000000..c29cbdc
--- /dev/null
+++ b/update_engine/payload_generator/tarjan_unittest.cc
@@ -0,0 +1,94 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_generator/tarjan.h"
+
+#include <string>
+#include <utility>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/graph_types.h"
+
+using std::make_pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class TarjanAlgorithmTest : public ::testing::Test {};
+
+TEST(TarjanAlgorithmTest, SimpleTest) {
+  const Vertex::Index n_a = 0;
+  const Vertex::Index n_b = 1;
+  const Vertex::Index n_c = 2;
+  const Vertex::Index n_d = 3;
+  const Vertex::Index n_e = 4;
+  const Vertex::Index n_f = 5;
+  const Vertex::Index n_g = 6;
+  const Vertex::Index n_h = 7;
+  const Graph::size_type kNodeCount = 8;
+
+  Graph graph(kNodeCount);
+
+  graph[n_a].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_a].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties()));
+  graph[n_c].out_edges.insert(make_pair(n_d, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_b, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_c, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_f].out_edges.insert(make_pair(n_g, EdgeProperties()));
+  graph[n_g].out_edges.insert(make_pair(n_h, EdgeProperties()));
+  graph[n_h].out_edges.insert(make_pair(n_g, EdgeProperties()));
+
+  TarjanAlgorithm tarjan;
+
+  for (Vertex::Index i = n_a; i <= n_e; i++) {
+    vector<Vertex::Index> vertex_indexes;
+    tarjan.Execute(i, &graph, &vertex_indexes);
+
+    EXPECT_EQ(5U, vertex_indexes.size());
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_a));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_b));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_c));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_d));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_e));
+  }
+
+  {
+    vector<Vertex::Index> vertex_indexes;
+    tarjan.Execute(n_f, &graph, &vertex_indexes);
+
+    EXPECT_EQ(1U, vertex_indexes.size());
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_f));
+  }
+
+  for (Vertex::Index i = n_g; i <= n_h; i++) {
+    vector<Vertex::Index> vertex_indexes;
+    tarjan.Execute(i, &graph, &vertex_indexes);
+
+    EXPECT_EQ(2U, vertex_indexes.size());
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_g));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_h));
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/topological_sort.cc b/update_engine/payload_generator/topological_sort.cc
new file mode 100644
index 0000000..f164336
--- /dev/null
+++ b/update_engine/payload_generator/topological_sort.cc
@@ -0,0 +1,56 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_generator/topological_sort.h"
+
+#include <set>
+#include <vector>
+
+#include <base/logging.h>
+
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+void TopologicalSortVisit(const Graph& graph,
+                          set<Vertex::Index>* visited_nodes,
+                          vector<Vertex::Index>* nodes,
+                          Vertex::Index node) {
+  if (visited_nodes->find(node) != visited_nodes->end())
+    return;
+
+  visited_nodes->insert(node);
+  // Visit all children.
+  for (Vertex::EdgeMap::const_iterator it = graph[node].out_edges.begin();
+       it != graph[node].out_edges.end(); ++it) {
+    TopologicalSortVisit(graph, visited_nodes, nodes, it->first);
+  }
+  // Visit this node.
+  nodes->push_back(node);
+}
+}  // namespace
+
+void TopologicalSort(const Graph& graph, vector<Vertex::Index>* out) {
+  set<Vertex::Index> visited_nodes;
+
+  for (Vertex::Index i = 0; i < graph.size(); i++) {
+    TopologicalSortVisit(graph, &visited_nodes, out, i);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/topological_sort.h b/update_engine/payload_generator/topological_sort.h
new file mode 100644
index 0000000..461cbe1
--- /dev/null
+++ b/update_engine/payload_generator/topological_sort.h
@@ -0,0 +1,42 @@
+//
+// 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_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_
+
+#include <vector>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+// Performs a topological sort on the directed graph 'graph' and stores
+// the nodes, in order visited, in 'out'.
+// For example, this graph:
+// A ---> C ----.
+//  \           v
+//   `--> B --> D
+// Might result in this in 'out':
+// out[0] = D
+// out[1] = B
+// out[2] = C
+// out[3] = A
+// Note: results are undefined if there is a cycle in the graph.
+void TopologicalSort(const Graph& graph, std::vector<Vertex::Index>* out);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_
diff --git a/update_engine/payload_generator/topological_sort_unittest.cc b/update_engine/payload_generator/topological_sort_unittest.cc
new file mode 100644
index 0000000..1d866a7
--- /dev/null
+++ b/update_engine/payload_generator/topological_sort_unittest.cc
@@ -0,0 +1,95 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_generator/topological_sort.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+using std::make_pair;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class TopologicalSortTest : public ::testing::Test {};
+
+namespace {
+// Returns true if the value is found in vect. If found, the index is stored
+// in out_index if out_index is not null.
+template<typename T>
+bool IndexOf(const vector<T>& vect,
+             const T& value,
+             typename vector<T>::size_type* out_index) {
+  for (typename vector<T>::size_type i = 0; i < vect.size(); i++) {
+    if (vect[i] == value) {
+      if (out_index) {
+        *out_index = i;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+}  // namespace
+
+TEST(TopologicalSortTest, SimpleTest) {
+  int counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Vertex::Index n_d = counter++;
+  const Vertex::Index n_e = counter++;
+  const Vertex::Index n_f = counter++;
+  const Vertex::Index n_g = counter++;
+  const Vertex::Index n_h = counter++;
+  const Vertex::Index n_i = counter++;
+  const Vertex::Index n_j = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+
+  graph[n_i].out_edges.insert(make_pair(n_j, EdgeProperties()));
+  graph[n_i].out_edges.insert(make_pair(n_c, EdgeProperties()));
+  graph[n_i].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_i].out_edges.insert(make_pair(n_h, EdgeProperties()));
+  graph[n_c].out_edges.insert(make_pair(n_b, EdgeProperties()));
+  graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_d, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_g, EdgeProperties()));
+  graph[n_g].out_edges.insert(make_pair(n_d, EdgeProperties()));
+  graph[n_g].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_a, EdgeProperties()));
+
+  vector<Vertex::Index> sorted;
+  TopologicalSort(graph, &sorted);
+
+  for (Vertex::Index i = 0; i < graph.size(); i++) {
+    vector<Vertex::Index>::size_type src_index = 0;
+    EXPECT_TRUE(IndexOf(sorted, i, &src_index));
+    for (Vertex::EdgeMap::const_iterator it = graph[i].out_edges.begin();
+         it != graph[i].out_edges.end(); ++it) {
+      vector<Vertex::Index>::size_type dst_index = 0;
+      EXPECT_TRUE(IndexOf(sorted, it->first, &dst_index));
+      EXPECT_LT(dst_index, src_index);
+    }
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/xz.h b/update_engine/payload_generator/xz.h
new file mode 100644
index 0000000..6e0a26c
--- /dev/null
+++ b/update_engine/payload_generator/xz.h
@@ -0,0 +1,34 @@
+//
+// 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_PAYLOAD_GENERATOR_XZ_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_XZ_H_
+
+#include <brillo/secure_blob.h>
+
+namespace chromeos_update_engine {
+
+// Initialize the xz compression unit. Call once before any call to
+// XzCompress().
+void XzCompressInit();
+
+// Compresses the input buffer |in| into |out| with xz. The compressed stream
+// will be the equivalent of running xz -9 --check=none
+bool XzCompress(const brillo::Blob& in, brillo::Blob* out);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_XZ_H_
diff --git a/update_engine/payload_generator/xz_android.cc b/update_engine/payload_generator/xz_android.cc
new file mode 100644
index 0000000..f3b836d
--- /dev/null
+++ b/update_engine/payload_generator/xz_android.cc
@@ -0,0 +1,116 @@
+//
+// 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/payload_generator/xz.h"
+
+#include <7zCrc.h>
+#include <Xz.h>
+#include <XzEnc.h>
+
+#include <algorithm>
+
+#include <base/logging.h>
+
+namespace {
+
+bool xz_initialized = false;
+
+// An ISeqInStream implementation that reads all the data from the passed Blob.
+struct BlobReaderStream : public ISeqInStream {
+  explicit BlobReaderStream(const brillo::Blob& data) : data_(data) {
+    Read = &BlobReaderStream::ReadStatic;
+  }
+
+  static SRes ReadStatic(void* p, void* buf, size_t* size) {
+    auto* self =
+        static_cast<BlobReaderStream*>(reinterpret_cast<ISeqInStream*>(p));
+    *size = std::min(*size, self->data_.size() - self->pos_);
+    memcpy(buf, self->data_.data() + self->pos_, *size);
+    self->pos_ += *size;
+    return SZ_OK;
+  }
+
+  const brillo::Blob& data_;
+
+  // The current reader position.
+  size_t pos_ = 0;
+};
+
+// An ISeqOutStream implementation that writes all the data to the passed Blob.
+struct BlobWriterStream : public ISeqOutStream {
+  explicit BlobWriterStream(brillo::Blob* data) : data_(data) {
+    Write = &BlobWriterStream::WriteStatic;
+  }
+
+  static size_t WriteStatic(void* p, const void* buf, size_t size) {
+    auto* self =
+        static_cast<BlobWriterStream*>(reinterpret_cast<ISeqOutStream*>(p));
+    const uint8_t* buffer = reinterpret_cast<const uint8_t*>(buf);
+    self->data_->reserve(self->data_->size() + size);
+    self->data_->insert(self->data_->end(), buffer, buffer + size);
+    return size;
+  }
+
+  brillo::Blob* data_;
+};
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+void XzCompressInit() {
+  if (xz_initialized)
+    return;
+  xz_initialized = true;
+  // Although we don't include a CRC32 for the stream, the xz file header has
+  // a CRC32 of the header itself, which required the CRC table to be
+  // initialized.
+  CrcGenerateTable();
+}
+
+bool XzCompress(const brillo::Blob& in, brillo::Blob* out) {
+  CHECK(xz_initialized) << "Initialize XzCompress first";
+  out->clear();
+  if (in.empty())
+    return true;
+
+  // Xz compression properties.
+  CXzProps props;
+  XzProps_Init(&props);
+  // No checksum in the xz stream. xz-embedded (used by the decompressor) only
+  // supports CRC32, but we already check the sha-1 of the whole blob during
+  // payload application.
+  props.checkId = XZ_CHECK_NO;
+
+  // LZMA2 compression properties.
+  CLzma2EncProps lzma2Props;
+  props.lzma2Props = &lzma2Props;
+  Lzma2EncProps_Init(&lzma2Props);
+  // LZMA compression "level 6" requires 9 MB of RAM to decompress in the worst
+  // case.
+  lzma2Props.lzmaProps.level = 6;
+  lzma2Props.lzmaProps.numThreads = 1;
+  // The input size data is used to reduce the dictionary size if possible.
+  lzma2Props.lzmaProps.reduceSize = in.size();
+  Lzma2EncProps_Normalize(&lzma2Props);
+
+  BlobWriterStream out_writer(out);
+  BlobReaderStream in_reader(in);
+  SRes res = Xz_Encode(&out_writer, &in_reader, &props, nullptr /* progress */);
+  return res == SZ_OK;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/xz_chromeos.cc b/update_engine/payload_generator/xz_chromeos.cc
new file mode 100644
index 0000000..a8cda4e
--- /dev/null
+++ b/update_engine/payload_generator/xz_chromeos.cc
@@ -0,0 +1,28 @@
+//
+// 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/payload_generator/xz.h"
+
+namespace chromeos_update_engine {
+
+void XzCompressInit() {}
+
+bool XzCompress(const brillo::Blob& in, brillo::Blob* out) {
+  // No Xz compressor implementation in Chrome OS delta_generator builds.
+  return false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/zip_unittest.cc b/update_engine/payload_generator/zip_unittest.cc
new file mode 100644
index 0000000..54adfcb
--- /dev/null
+++ b/update_engine/payload_generator/zip_unittest.cc
@@ -0,0 +1,172 @@
+//
+// 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 <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_consumer/bzip_extent_writer.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+#include "update_engine/payload_consumer/xz_extent_writer.h"
+#include "update_engine/payload_generator/bzip.h"
+#include "update_engine/payload_generator/xz.h"
+
+using chromeos_update_engine::test_utils::kRandomString;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// ExtentWriter class that writes to memory, used to test the decompression
+// step with the corresponding extent writer.
+class MemoryExtentWriter : public ExtentWriter {
+ public:
+  // Creates the ExtentWriter that will write all the bytes to the passed |data|
+  // blob.
+  explicit MemoryExtentWriter(brillo::Blob* data) : data_(data) {
+    data_->clear();
+  }
+  ~MemoryExtentWriter() override = default;
+
+  bool Init(FileDescriptorPtr fd,
+            const vector<Extent>& extents,
+            uint32_t block_size) override {
+    return true;
+  }
+  bool Write(const void* bytes, size_t count) override {
+    data_->reserve(data_->size() + count);
+    data_->insert(data_->end(),
+                  static_cast<const uint8_t*>(bytes),
+                  static_cast<const uint8_t*>(bytes) + count);
+    return true;
+  }
+  bool EndImpl() override { return true; }
+
+ private:
+  brillo::Blob* data_;
+};
+
+template <typename W>
+bool DecompressWithWriter(const brillo::Blob& in, brillo::Blob* out) {
+  std::unique_ptr<ExtentWriter> writer(
+      new W(brillo::make_unique_ptr(new MemoryExtentWriter(out))));
+  // Init() parameters are ignored by the testing MemoryExtentWriter.
+  bool ok = writer->Init(nullptr, {}, 1);
+  ok = writer->Write(in.data(), in.size()) && ok;
+  // Call End() even if the Write failed.
+  ok = writer->End() && ok;
+  return ok;
+}
+
+}  // namespace
+
+template <typename T>
+class ZipTest : public ::testing::Test {
+ public:
+  bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const = 0;
+  bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const = 0;
+};
+
+class BzipTest {};
+
+template <>
+class ZipTest<BzipTest> : public ::testing::Test {
+ public:
+  bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const {
+    return BzipCompress(in, out);
+  }
+  bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const {
+    return DecompressWithWriter<BzipExtentWriter>(in, out);
+  }
+};
+
+class XzTest {};
+
+template <>
+class ZipTest<XzTest> : public ::testing::Test {
+ public:
+  bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const {
+    return XzCompress(in, out);
+  }
+  bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const {
+    return DecompressWithWriter<XzExtentWriter>(in, out);
+  }
+};
+
+#ifdef __ANDROID__
+typedef ::testing::Types<BzipTest, XzTest> ZipTestTypes;
+#else
+// Chrome OS implementation of Xz compressor just returns false.
+typedef ::testing::Types<BzipTest> ZipTestTypes;
+#endif  // __ANDROID__
+
+TYPED_TEST_CASE(ZipTest, ZipTestTypes);
+
+TYPED_TEST(ZipTest, SimpleTest) {
+  string in_str(
+      "this should compress well xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+  brillo::Blob in(in_str.begin(), in_str.end());
+  brillo::Blob out;
+  EXPECT_TRUE(this->ZipCompress(in, &out));
+  EXPECT_LT(out.size(), in.size());
+  EXPECT_GT(out.size(), 0U);
+  brillo::Blob decompressed;
+  EXPECT_TRUE(this->ZipDecompress(out, &decompressed));
+  EXPECT_EQ(in.size(), decompressed.size());
+  EXPECT_TRUE(!memcmp(in.data(), decompressed.data(), in.size()));
+}
+
+TYPED_TEST(ZipTest, PoorCompressionTest) {
+  brillo::Blob in(std::begin(kRandomString), std::end(kRandomString));
+  brillo::Blob out;
+  EXPECT_TRUE(this->ZipCompress(in, &out));
+  EXPECT_GT(out.size(), in.size());
+  brillo::Blob decompressed;
+  EXPECT_TRUE(this->ZipDecompress(out, &decompressed));
+  EXPECT_EQ(in.size(), decompressed.size());
+  EXPECT_EQ(in, decompressed);
+}
+
+TYPED_TEST(ZipTest, MalformedZipTest) {
+  brillo::Blob in(std::begin(kRandomString), std::end(kRandomString));
+  brillo::Blob out;
+  EXPECT_FALSE(this->ZipDecompress(in, &out));
+}
+
+TYPED_TEST(ZipTest, EmptyInputsTest) {
+  brillo::Blob in;
+  brillo::Blob out;
+  EXPECT_TRUE(this->ZipDecompress(in, &out));
+  EXPECT_EQ(0U, out.size());
+
+  EXPECT_TRUE(this->ZipCompress(in, &out));
+  EXPECT_EQ(0U, out.size());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_properties.h b/update_engine/payload_properties.h
new file mode 100644
index 0000000..05c60f0
--- /dev/null
+++ b/update_engine/payload_properties.h
@@ -0,0 +1,13 @@
+#ifndef PAYLOAD_PROPERTIES_H_
+#define PAYLOAD_PROPERTIES_H_
+
+#include "update_engine/omaha_response.h"
+
+namespace chromeos_update_engine {
+
+// reuse existing response structure from Omaha.
+using PayloadProperties = OmahaResponse;
+
+}; // namespace chromeos_update_engine
+
+#endif // PAYLOAD_PROPERTIES_H_
diff --git a/update_engine/payload_properties_handler_action.cc b/update_engine/payload_properties_handler_action.cc
new file mode 100644
index 0000000..74150df
--- /dev/null
+++ b/update_engine/payload_properties_handler_action.cc
@@ -0,0 +1,58 @@
+#include "update_engine/payload_properties_handler_action.h"
+#include "update_engine/payload_state.h"
+
+namespace chromeos_update_engine {
+
+PayloadPropertiesHandlerAction::PayloadPropertiesHandlerAction(SystemState* system_state)
+    : system_state_(system_state)
+{
+}
+
+PayloadPropertiesHandlerAction::~PayloadPropertiesHandlerAction()
+{
+}
+
+void PayloadPropertiesHandlerAction::PerformAction()
+{
+    // Get the InstallPlan and read it
+    CHECK(HasInputObject());
+    const PayloadProperties &payload_prop = GetInputObject();
+
+    LOG(INFO) << "Update URL " << system_state_->payload_state()->GetCurrentUrl();;
+
+    install_plan_.download_url = system_state_->payload_state()->GetCurrentUrl();;
+    install_plan_.payload_type = InstallPayloadType::kFull;
+
+    install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot();
+    if (install_plan_.source_slot == BootControlInterface::kInvalidSlot)
+    {
+        LOG(ERROR) << "Current slot is invalid! Exit with error.";
+        processor_->ActionComplete(this, ErrorCode::kError);
+        return;
+    }
+
+    if (!system_state_->boot_control()->IsSlotBootable(install_plan_.source_slot))
+    {
+        LOG(ERROR) << "Current slot " << system_state_->boot_control()
+                   << " is not bootable. Exit with error.";
+        processor_->ActionComplete(this, ErrorCode::kError);
+        return;
+    }
+
+    install_plan_.target_slot = (install_plan_.source_slot + 1) % system_state_->boot_control()->GetNumSlots();
+
+    install_plan_.hash_checks_mandatory = true;
+    install_plan_.powerwash_required = false;
+    install_plan_.version = payload_prop.version;
+    install_plan_.payload_size = payload_prop.size;
+    install_plan_.payload_hash = payload_prop.hash;
+    install_plan_.metadata_size = payload_prop.metadata_size;
+    install_plan_.is_resume = false;
+    if (HasOutputPipe())
+        SetOutputObject(install_plan_);
+
+    LOG(INFO) << "Using this install plan:";
+    install_plan_.Dump();
+    processor_->ActionComplete(this, ErrorCode::kSuccess);
+}
+}; // chromeos_update_engine
diff --git a/update_engine/payload_properties_handler_action.h b/update_engine/payload_properties_handler_action.h
new file mode 100644
index 0000000..c42f422
--- /dev/null
+++ b/update_engine/payload_properties_handler_action.h
@@ -0,0 +1,55 @@
+#ifndef PAYLOAD_PROPERTIES_HANDLER_ACTION_H_
+#define PAYLOAD_PROPERTIES_HANDLER_ACTION_H_
+
+#include "update_engine/common/action.h"
+#include "update_engine/system_state.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/payload_properties.h"
+
+// This class reads in Payload properties and converts what it sees into
+// an install plan which is passed out.
+
+namespace chromeos_update_engine {
+
+class PayloadPropertiesHandlerAction;
+
+template<>
+class ActionTraits<PayloadPropertiesHandlerAction> {
+ public:
+  // Takes parameters on the input pipe.
+  typedef PayloadProperties InputObjectType;
+  typedef InstallPlan OutputObjectType;
+};
+
+class PayloadPropertiesHandlerAction : public Action<PayloadPropertiesHandlerAction>
+{
+ public:
+
+  PayloadPropertiesHandlerAction(SystemState* system_state);
+  ~PayloadPropertiesHandlerAction() override;
+
+  typedef ActionTraits<PayloadPropertiesHandlerAction>::InputObjectType InputObjectType;
+  typedef ActionTraits<PayloadPropertiesHandlerAction>::OutputObjectType OutputObjectType;
+
+  void PerformAction() override;
+
+  const InstallPlan& install_plan() const { return install_plan_; }
+
+  // Debugging/logging
+  static std::string StaticType() { return "PayloadPropertiesHandlerAction"; }
+  std::string Type() const override { return StaticType(); }
+
+ private:
+
+  // Global system context.
+  SystemState* system_state_;
+
+  // The install plan, if we have an update.
+  InstallPlan install_plan_;
+
+  DISALLOW_COPY_AND_ASSIGN(PayloadPropertiesHandlerAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // PAYLOAD_PROPERTIES_HANDLER_ACTION_H_
diff --git a/update_engine/payload_properties_request_action.cc b/update_engine/payload_properties_request_action.cc
new file mode 100644
index 0000000..cf0aa6c
--- /dev/null
+++ b/update_engine/payload_properties_request_action.cc
@@ -0,0 +1,201 @@
+//
+// 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/omaha_request_action.h"
+
+#include <inttypes.h>
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <base/bind.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/string_split.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/time/time.h>
+#include <expat.h>
+#include <metrics/metrics_library.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/platform_constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/connection_manager_interface.h"
+#include "update_engine/metrics.h"
+#include "update_engine/metrics_utils.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/payload_properties_request_action.h"
+
+#define HAS_KEY(x, y) ((x).find((y)) != (x).end())
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::pair;
+using std::vector;
+
+PayloadPropertiesRequestAction::PayloadPropertiesRequestAction(
+    SystemState* system_state,
+    PayloadProperties *properties,
+    HttpFetcher *http_fetcher)
+    : system_state_(system_state),
+      properties_(properties),
+      http_fetcher_(http_fetcher)
+{
+}
+
+PayloadPropertiesRequestAction::~PayloadPropertiesRequestAction() {}
+
+void PayloadPropertiesRequestAction::PerformAction() {
+  http_fetcher_->set_delegate(this);
+
+  // we support only one URL for now
+  CHECK(properties_->payload_urls.size() == 1);
+
+  LOG(INFO) << "URL: " << properties_->payload_urls[0];
+  http_fetcher_->BeginTransfer(properties_->payload_urls[0]);
+}
+
+void PayloadPropertiesRequestAction::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.
+void PayloadPropertiesRequestAction::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);
+}
+
+// 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 PayloadPropertiesRequestAction::TransferComplete(HttpFetcher *fetcher,
+                                          bool successful) {
+  ScopedActionCompleter completer(processor_, this);
+
+  if (!successful) {
+    LOG(ERROR) << "Payload properties network transfer failed.";
+    int code = GetHTTPResponseCode();
+    // Makes sure we send sane error values.
+    if (code < 0 || code >= 1000) {
+      code = 999;
+    }
+    completer.set_code(static_cast<ErrorCode>(
+        static_cast<int>(ErrorCode::kHTTPResponseBase) + code));
+    return;
+  }
+
+  auto prop_end = std::find(response_buffer_.begin(), response_buffer_.end(), 0);
+  vector<pair<string, string>> kv_pairs;
+  std::map<string, string> kv_map;
+  if (!base::SplitStringIntoKeyValuePairs(string(response_buffer_.begin(), prop_end),
+                ' ', '\n', &kv_pairs))
+  {
+    LOG(ERROR) << "Can't parse Payload properties";
+    return;
+  }
+  std::copy(kv_pairs.begin(), kv_pairs.end(),
+                std::inserter(kv_map, kv_map.begin()));
+
+  if (!HAS_KEY(kv_map, "FILE_SIZE:") ||
+      !base::StringToInt64(kv_map.at("FILE_SIZE:"), &properties_->size))
+  {
+    LOG(ERROR) << "Can't get FILE_SIZE from Payload properties: ";
+    return;
+  }
+  if (!HAS_KEY(kv_map, "METADATA_SIZE:") ||
+      !base::StringToInt64(kv_map.at("METADATA_SIZE:"), &properties_->metadata_size))
+  {
+    LOG(ERROR) << "Can't get METADATA_SIZE from Payload properties: ";
+    return;
+  }
+  if (!HAS_KEY(kv_map, "FILE_HASH:"))
+  {
+    LOG(ERROR) << "Can't get FILE_HASH from Payload properties: ";
+    return;
+  }
+  properties_->hash = kv_map["FILE_HASH:"];
+  if (!HAS_KEY(kv_map, "Larry-app-version:"))
+  {
+    LOG(ERROR) << "Can't get Larry-app-version from Payload properties: ";
+    return;
+  }
+  properties_->version = kv_map["Larry-app-version:"];
+
+  properties_->update_exists = true;
+
+  system_state_->payload_state()->SetResponse(*properties_);
+
+  if (HasOutputPipe())
+      SetOutputObject(*properties_);
+  completer.set_code(ErrorCode::kSuccess);
+
+  // TODO add more properties to be able to recover update after crash or reboot
+}
+
+void PayloadPropertiesRequestAction::ActionCompleted(ErrorCode code)
+{
+  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()) {
+        result = metrics::CheckResult::kUpdateAvailable;
+        reaction = metrics::CheckReaction::kUpdating;
+    } else {
+      result = metrics::CheckResult::kNoUpdateAvailable;
+    }
+    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;
+  }
+
+  metrics::ReportUpdateCheckMetrics(system_state_,
+                                    result, reaction, download_error_code);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_properties_request_action.h b/update_engine/payload_properties_request_action.h
new file mode 100644
index 0000000..b986515
--- /dev/null
+++ b/update_engine/payload_properties_request_action.h
@@ -0,0 +1,97 @@
+//
+// 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_PAYLOAD_PROPERTIES_REQUEST_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_PROPERTIES_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/payload_properties.h"
+#include "update_engine/system_state.h"
+
+// The Payload Properties action downloads properties header and outputs
+// properties on the output ActionPipe.
+
+namespace chromeos_update_engine {
+
+class PayloadPropertiesRequestAction;
+
+template<>
+class ActionTraits<PayloadPropertiesRequestAction> {
+ public:
+  // Takes parameters on the input pipe.
+  typedef NoneType InputObjectType;
+  typedef PayloadProperties OutputObjectType;
+};
+
+
+class PayloadPropertiesRequestAction : public Action<PayloadPropertiesRequestAction>,
+                           public HttpFetcherDelegate {
+ public:
+  PayloadPropertiesRequestAction(SystemState* system_state,
+                     PayloadProperties *properties,
+                     HttpFetcher *http_fetcher);
+  ~PayloadPropertiesRequestAction() override;
+  typedef ActionTraits<PayloadPropertiesRequestAction>::InputObjectType InputObjectType;
+  typedef ActionTraits<PayloadPropertiesRequestAction>::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 "PayloadPropertiesRequestAction"; }
+  std::string Type() const override { return StaticType(); }
+
+  // Delegate methods (see http_fetcher.h)
+  void ReceivedBytes(HttpFetcher *fetcher,
+                     const void* bytes, size_t length) override;
+
+  void TransferComplete(HttpFetcher *fetcher, bool successful) override;
+
+private:
+  // Global system context.
+  SystemState* system_state_;
+
+  PayloadProperties * properties_;
+
+  // pointer to the HttpFetcher that does the http work
+  std::unique_ptr<HttpFetcher> http_fetcher_;
+
+  // Stores the response from the omaha server
+  brillo::Blob response_buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(PayloadPropertiesRequestAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_PROPERTIES_REQUEST_ACTION_H_
diff --git a/update_engine/payload_state.cc b/update_engine/payload_state.cc
new file mode 100644
index 0000000..1da472f
--- /dev/null
+++ b/update_engine/payload_state.cc
@@ -0,0 +1,1397 @@
+//
+// 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/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/prefs.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/connection_manager_interface.h"
+#include "update_engine/metrics_utils.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/system_state.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::min;
+using std::string;
+
+namespace chromeos_update_engine {
+
+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;
+
+PayloadState::PayloadState()
+    : prefs_(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),
+      attempt_num_bytes_downloaded_(0),
+      attempt_connection_type_(metrics::ConnectionType::kUnknown),
+      attempt_error_code_(ErrorCode::kSuccess),
+      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();
+  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();
+  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;
+  }
+
+  // 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 (static_cast<uint32_t>(url_index_) >= candidate_urls_.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:
+      metrics::ReportRollbackMetrics(system_state_,
+                                     metrics::RollbackResult::kSuccess);
+      break;
+  }
+  attempt_error_code_ = ErrorCode::kSuccess;
+
+  // Reset the number of responses seen since it counts from the last
+  // successful update, e.g. now.
+  SetNumResponsesSeen(0);
+
+  CreateSystemUpdatedMarkerFile();
+}
+
+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) << ")";
+  attempt_error_code_ = 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:
+      metrics::ReportRollbackMetrics(system_state_,
+                                     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:
+      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:
+      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;
+  }
+  if (response_.is_delta_payload) {
+    // 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()) {
+    // Backoffs are needed only for official builds. We do not want any delays
+    // or update failures due to backoffs during testing or development.
+    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() {
+  // Update the payload attempt number for full payloads and the backoff time.
+  if (response_.is_delta_payload) {
+    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() {
+  uint32_t next_url_index = GetUrlIndex() + 1;
+  if (next_url_index < candidate_urls_.size()) {
+    LOG(INFO) << "Incrementing the URL index for next attempt";
+    SetUrlIndex(next_url_index);
+  } else {
+    LOG(INFO) << "Resetting the current URL index (" << GetUrlIndex() << ") to "
+              << "0 as we only have " << candidate_urls_.size()
+              << " candidate URL(s)";
+    SetUrlIndex(0);
+    IncrementPayloadAttemptNumber();
+    IncrementFullPayloadAttemptNumber();
+  }
+
+  // If we have multiple URLs, record that we just switched to another one
+  if (candidate_urls_.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";
+    IncrementUrlIndex();
+  }
+}
+
+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 (GetUrlIndex() < candidate_urls_.size())  {
+    string current_url = candidate_urls_[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() {
+  PayloadType payload_type;
+  OmahaRequestParams* params = system_state_->request_params();
+  if (response_.is_delta_payload) {
+    payload_type = kPayloadTypeDelta;
+  } else if (params->delta_okay()) {
+    payload_type = kPayloadTypeFull;
+  } else {  // Full payload, delta was not allowed by request.
+    payload_type = kPayloadTypeForcedFull;
+  }
+  return payload_type;
+}
+
+// TODO(zeuthen): Currently we don't report the UpdateEngine.Attempt.*
+// metrics if the attempt ends abnormally, e.g. if the update_engine
+// process crashes or the device is rebooted. See
+// http://crbug.com/357676
+void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) {
+  int attempt_number = GetPayloadAttemptNumber();
+
+  PayloadType payload_type = CalculatePayloadType();
+
+  int64_t payload_size = response_.size;
+
+  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::kNumConstants:
+    case metrics::AttemptResult::kUnset:
+      break;
+  }
+
+  metrics::ReportUpdateAttemptMetrics(system_state_,
+                                      attempt_number,
+                                      payload_type,
+                                      duration,
+                                      duration_uptime,
+                                      payload_size,
+                                      payload_bytes_downloaded,
+                                      payload_download_speed_bps,
+                                      download_source,
+                                      attempt_result,
+                                      internal_error_code,
+                                      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;
+
+  metrics::ReportAbnormallyTerminatedUpdateAttemptMetrics(system_state_);
+
+  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();
+
+  prefs_->Delete(kPrefsUpdateTimestampStart);
+  prefs_->Delete(kPrefsUpdateDurationUptime);
+
+  PayloadType payload_type = CalculatePayloadType();
+
+  int64_t payload_size = response_.size;
+
+  int attempt_count = GetPayloadAttemptNumber();
+
+  int updates_abandoned_count = num_responses_seen_ - 1;
+
+  metrics::ReportSuccessfulUpdateMetrics(system_state_,
+                                         attempt_count,
+                                         updates_abandoned_count,
+                                         payload_type,
+                                         payload_size,
+                                         total_bytes_by_source,
+                                         download_overhead_percentage,
+                                         duration,
+                                         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) {
+  CHECK(prefs_);
+  num_reboots_ = num_reboots;
+  prefs_->SetInt64(kPrefsNumReboots, num_reboots);
+  LOG(INFO) << "Number of Reboots during current update attempt = "
+            << num_reboots_;
+}
+
+void PayloadState::ResetPersistedState() {
+  SetPayloadAttemptNumber(0);
+  SetFullPayloadAttemptNumber(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());
+}
+
+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.
+  }
+}
+
+int64_t PayloadState::GetPersistedValue(const string& key) {
+  CHECK(prefs_);
+  if (!prefs_->Exists(key))
+    return 0;
+
+  int64_t stored_value;
+  if (!prefs_->GetInt64(key, &stored_value))
+    return 0;
+
+  if (stored_value < 0) {
+    LOG(ERROR) << key << ": Invalid value (" << stored_value
+               << ") in persisted state. Defaulting to 0";
+    return 0;
+  }
+
+  return stored_value;
+}
+
+string PayloadState::CalculateResponseSignature() {
+  string response_sign = base::StringPrintf(
+      "NumURLs = %d\n", static_cast<int>(candidate_urls_.size()));
+
+  for (size_t i = 0; i < candidate_urls_.size(); i++)
+    response_sign += base::StringPrintf("Candidate Url%d = %s\n",
+                                        static_cast<int>(i),
+                                        candidate_urls_[i].c_str());
+
+  response_sign += base::StringPrintf(
+      "Payload Size = %ju\n"
+      "Payload Sha256 Hash = %s\n"
+      "Metadata Size = %ju\n"
+      "Metadata Signature = %s\n"
+      "Is Delta Payload = %d\n"
+      "Max Failure Count Per Url = %d\n"
+      "Disable Payload Backoff = %d\n",
+      static_cast<uintmax_t>(response_.size),
+      response_.hash.c_str(),
+      static_cast<uintmax_t>(response_.metadata_size),
+      response_.metadata_signature.c_str(),
+      response_.is_delta_payload,
+      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));
+}
+
+void PayloadState::LoadFullPayloadAttemptNumber() {
+  SetFullPayloadAttemptNumber(GetPersistedValue(
+      kPrefsFullPayloadAttemptNumber));
+}
+
+void PayloadState::SetPayloadAttemptNumber(int payload_attempt_number) {
+  CHECK(prefs_);
+  payload_attempt_number_ = payload_attempt_number;
+  LOG(INFO) << "Payload Attempt Number = " << payload_attempt_number_;
+  prefs_->SetInt64(kPrefsPayloadAttemptNumber, payload_attempt_number_);
+}
+
+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::LoadUrlIndex() {
+  SetUrlIndex(GetPersistedValue(kPrefsCurrentUrlIndex));
+}
+
+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(kPrefsWallClockWaitPeriod)));
+}
+
+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(kPrefsWallClockWaitPeriod,
+                     scattering_wait_period_.InSeconds());
+  } else {
+    prefs_->Delete(kPrefsWallClockWaitPeriod);
+  }
+}
+
+void PayloadState::LoadUrlSwitchCount() {
+  SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount));
+}
+
+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));
+}
+
+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);
+  }
+
+  // Sanity 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) {
+  CHECK(prefs_);
+  update_timestamp_start_ = value;
+  prefs_->SetInt64(kPrefsUpdateTimestampStart,
+                   update_timestamp_start_.ToInternalValue());
+  LOG(INFO) << "Update Timestamp Start = "
+            << utils::ToString(update_timestamp_start_);
+}
+
+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);
+  }
+
+  // Sanity-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));
+}
+
+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) << "Blacklisting 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_;
+  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), 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), 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));
+}
+
+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 (size_t i = 0; i < response_.payload_urls.size(); i++) {
+    string candidate_url = response_.payload_urls[i];
+    if (base::StartsWith(candidate_url, "http://",
+                         base::CompareCase::INSENSITIVE_ASCII) &&
+        !http_url_ok) {
+      continue;
+    }
+    candidate_urls_.push_back(candidate_url);
+    LOG(INFO) << "Candidate Url" << (candidate_urls_.size() - 1)
+              << ": " << candidate_url;
+  }
+
+  LOG(INFO) << "Found " << candidate_urls_.size() << " candidate URLs "
+            << "out of " << response_.payload_urls.size() << " URLs supplied";
+}
+
+void PayloadState::CreateSystemUpdatedMarkerFile() {
+  CHECK(prefs_);
+  int64_t value = system_state_->clock()->GetWallclockTime().ToInternalValue();
+  prefs_->SetInt64(kPrefsSystemUpdatedMarker, value);
+}
+
+void PayloadState::BootedIntoUpdate(TimeDelta time_to_reboot) {
+  // Send |time_to_reboot| as a UMA stat.
+  string metric = metrics::kMetricTimeToRebootMinutes;
+  system_state_->metrics_lib()->SendToUMA(metric,
+                                          time_to_reboot.InMinutes(),
+                                          0,         // min: 0 minute
+                                          30*24*60,  // max: 1 month (approx)
+                                          kNumDefaultUmaBuckets);
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(time_to_reboot)
+            << " for metric " <<  metric;
+}
+
+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;
+
+  // Figure out if we just booted into a new update
+  if (prefs_->Exists(kPrefsSystemUpdatedMarker)) {
+    int64_t stored_value;
+    if (prefs_->GetInt64(kPrefsSystemUpdatedMarker, &stored_value)) {
+      Time system_updated_at = Time::FromInternalValue(stored_value);
+      if (!system_updated_at.is_null()) {
+        TimeDelta time_to_reboot =
+            system_state_->clock()->GetWallclockTime() - system_updated_at;
+        if (time_to_reboot.ToInternalValue() < 0) {
+          LOG(ERROR) << "time_to_reboot is negative - system_updated_at: "
+                     << utils::ToString(system_updated_at);
+        } else {
+          BootedIntoUpdate(time_to_reboot);
+        }
+      }
+    }
+    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.
+      string metric = metrics::kMetricFailedUpdateCount;
+      LOG(INFO) << "Uploading " << target_attempt
+                << " (count) for metric " <<  metric;
+      system_state_->metrics_lib()->SendToUMA(
+           metric,
+           target_attempt,
+           1,    // min value
+           50,   // max value
+           kNumDefaultUmaBuckets);
+    } 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));
+}
+
+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);
+  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;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_state.h b/update_engine/payload_state.h
new file mode 100644
index 0000000..46711b6
--- /dev/null
+++ b/update_engine/payload_state.h
@@ -0,0 +1,580 @@
+//
+// 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_PAYLOAD_STATE_H_
+#define UPDATE_ENGINE_PAYLOAD_STATE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/metrics.h"
+#include "update_engine/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 candidate_urls_.size() ? candidate_urls_[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 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 SetP2PUrl(const std::string& url) override {
+    p2p_url_ = url;
+  }
+
+  std::string GetP2PUrl() const override {
+    return p2p_url_;
+  }
+
+  inline ErrorCode GetAttemptErrorCode() const override {
+    return attempt_error_code_;
+  }
+
+ private:
+  enum class AttemptType {
+    kUpdate,
+    kRollback,
+  };
+
+  friend class PayloadStateTest;
+  FRIEND_TEST(PayloadStateTest, RebootAfterUpdateFailedMetric);
+  FRIEND_TEST(PayloadStateTest, RebootAfterUpdateSucceed);
+  FRIEND_TEST(PayloadStateTest, RebootAfterCanceledUpdate);
+  FRIEND_TEST(PayloadStateTest, RollbackVersion);
+  FRIEND_TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs);
+
+  // 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();
+
+  // 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();
+
+  // Returns the persisted value from prefs_ for the given key. It also
+  // validates that the value returned is non-negative.
+  int64_t GetPersistedValue(const std::string& key);
+
+  // 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);
+
+  // 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 everytime 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 everytime 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 the blacklisted version from our prefs file.
+  void LoadRollbackVersion();
+
+  // Blacklists this version from getting AU'd to until we receive a new update
+  // response.
+  void SetRollbackVersion(const std::string& rollback_version);
+
+  // Clears any blacklisted version.
+  void ResetRollbackVersion();
+
+  inline uint32_t GetUrlIndex() {
+    return url_index_;
+  }
+
+  // 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();
+
+  // Writes the current wall-clock time to the kPrefsSystemUpdatedMarker
+  // state variable.
+  void CreateSystemUpdatedMarkerFile();
+
+  // Called at program startup if the device booted into a new update.
+  // The |time_to_reboot| parameter contains the (wall-clock) duration
+  // from when the update successfully completed (the value written
+  // into the kPrefsSystemUpdatedMarker state variable) until the device
+  // was booted into the update (current wall-clock time).
+  void BootedIntoUpdate(base::TimeDelta time_to_reboot);
+
+  // 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();
+
+  // 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_;
+
+  // 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 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.
+  int64_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 everytime 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::string> candidate_urls_;
+
+  // This stores a blacklisted 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_;
+
+  // The attempt error code when the attempt finished.
+  ErrorCode attempt_error_code_;
+
+  // Whether we're currently rolling back.
+  AttemptType attempt_type_;
+
+  // The current scattering wallclock-based wait period.
+  base::TimeDelta scattering_wait_period_;
+
+  DISALLOW_COPY_AND_ASSIGN(PayloadState);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_STATE_H_
diff --git a/update_engine/payload_state_interface.h b/update_engine/payload_state_interface.h
new file mode 100644
index 0000000..68798ee
--- /dev/null
+++ b/update_engine/payload_state_interface.h
@@ -0,0 +1,200 @@
+//
+// 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_PAYLOAD_STATE_INTERFACE_H_
+#define UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
+
+#include <string>
+
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/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 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;
+  virtual ErrorCode GetAttemptErrorCode() const = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
diff --git a/update_engine/payload_state_unittest.cc b/update_engine/payload_state_unittest.cc
new file mode 100644
index 0000000..b671722
--- /dev/null
+++ b/update_engine/payload_state_unittest.cc
@@ -0,0 +1,1652 @@
+//
+// 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/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/fake_clock.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/fake_prefs.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/fake_system_state.h"
+#include "update_engine/omaha_request_action.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::_;
+
+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,
+                                       PayloadState* payload_state,
+                                       OmahaResponse* response) {
+  response->payload_urls.clear();
+  response->payload_urls.push_back("http://test");
+  response->payload_urls.push_back("https://test");
+  response->size = 523456789;
+  response->hash = hash;
+  response->metadata_size = 558123;
+  response->metadata_signature = "metasign";
+  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 =
+      (http_enabled ? expected_urls_both : expected_url_https_only) +
+      base::StringPrintf("Payload Size = 523456789\n"
+                         "Payload Sha256 Hash = %s\n"
+                         "Metadata Size = 558123\n"
+                         "Metadata Signature = metasign\n"
+                         "Is Delta Payload = %d\n"
+                         "Max Failure Count Per Url = %d\n"
+                         "Disable Payload Backoff = %d\n",
+                         hash.c_str(),
+                         response->is_delta_payload,
+                         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 = "NumURLs = 0\n"
+                                  "Payload Size = 0\n"
+                                  "Payload Sha256 Hash = \n"
+                                  "Metadata Size = 0\n"
+                                  "Metadata Signature = \n"
+                                  "Is Delta Payload = 0\n"
+                                  "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.payload_urls.push_back("https://single.url.test");
+  response.size = 123456789;
+  response.hash = "hash";
+  response.metadata_size = 58123;
+  response.metadata_signature = "msign";
+  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 = "NumURLs = 1\n"
+                                  "Candidate Url0 = https://single.url.test\n"
+                                  "Payload Size = 123456789\n"
+                                  "Payload Sha256 Hash = hash\n"
+                                  "Metadata Size = 58123\n"
+                                  "Metadata Signature = msign\n"
+                                  "Is Delta Payload = 0\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.payload_urls.push_back("http://multiple.url.test");
+  response.payload_urls.push_back("https://multiple.url.test");
+  response.size = 523456789;
+  response.hash = "rhash";
+  response.metadata_size = 558123;
+  response.metadata_signature = "metasign";
+  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 = "NumURLs = 2\n"
+                                  "Candidate Url0 = http://multiple.url.test\n"
+                                  "Candidate Url1 = https://multiple.url.test\n"
+                                  "Payload Size = 523456789\n"
+                                  "Payload Sha256 Hash = rhash\n"
+                                  "Metadata Size = 558123\n"
+                                  "Metadata Signature = metasign\n"
+                                  "Is Delta Payload = 0\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, &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));
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+
+  // Set the first response.
+  SetupPayloadStateWith2Urls("Hash5823", true, &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, &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, &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, &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, &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;
+  response.is_delta_payload = false;
+  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, &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;
+  response.is_delta_payload = true;
+  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, &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, &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(SetArgumentPointee<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, &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;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  OmahaRequestParams params(&fake_system_state);
+  params.Init("", "", true);  // is_interactive = True.
+  fake_system_state.set_request_params(&params);
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &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;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  OmahaRequestParams params(&fake_system_state);
+  params.Init("", "", false);  // is_interactive = False.
+  fake_system_state.set_request_params(&params);
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &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;
+  response.is_delta_payload = true;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", 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;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash8939", true, &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, &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, &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, &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_lib(), SendToUMA(_, _, _, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateUrlSwitchCount,
+      3, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateTotalDurationMinutes,
+      _, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage,
+      314, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeFull, kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
+      kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateAttemptCount, 1, _, _, _));
+
+  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, &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.
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateDownloadSourcesUsed,
+      (1 << kDownloadSourceHttpServer),
+      _, _, _));
+
+  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, &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, RollbackVersion) {
+  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_TRUE(payload_state.GetRollbackVersion().empty());
+
+  // Mock out the os version and make sure it's blacklisted correctly.
+  string rollback_version = "2345.0.0";
+  OmahaRequestParams params(&fake_system_state);
+  params.Init(rollback_version, "", false);
+  fake_system_state.set_request_params(&params);
+
+  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(
+          SetArgumentPointee<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_lib(), SendToUMA(_, _, _, _, _))
+    .Times(0);
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(0);
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+              SendEnumToUMA(
+                  metrics::kMetricRollbackResult,
+                  static_cast<int>(metrics::RollbackResult::kSuccess),
+                  static_cast<int>(metrics::RollbackResult::kNumConstants)));
+  payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, DurationsAreCorrect) {
+  OmahaResponse response;
+  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, &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, &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));
+  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.SetWallclockTime(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, &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.SetWallclockTime(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_lib(), SendToUMA(
+      metrics::kMetricTimeToRebootMinutes,
+      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;
+  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, _));
+
+  // No metrics are reported after a crash.
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+              SendToUMA(_, _, _, _, _)).Times(0);
+
+  // 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_lib(),
+      SendEnumToUMA(
+          metrics::kMetricAttemptResult,
+          static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+          _)).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_lib(),
+      SendEnumToUMA(
+          metrics::kMetricAttemptResult,
+          static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+          _)).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));
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+      SendEnumToUMA(
+          metrics::kMetricAttemptResult,
+          static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+          _)).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, &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(SetArgumentPointee<0>(false), Return(true)));
+
+  // Reset state and set again.
+  SetupPayloadStateWith2Urls("Hash8433", 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, &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(SetArgumentPointee<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, &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;
+  response.is_delta_payload = true;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  // Simulate a successful download and update.
+  payload_state.DownloadComplete();
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeDelta, kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
+      kNumPayloadTypes));
+  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(&params);
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  payload_state.DownloadComplete();
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeDelta,
+      kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
+      kNumPayloadTypes));
+  payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsForcedFull) {
+  OmahaResponse response;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  // 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(&params);
+
+  // Simulate a successful download and update.
+  payload_state.DownloadComplete();
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeForcedFull,
+      kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeForcedFull,
+      kNumPayloadTypes));
+  payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsFull) {
+  OmahaResponse response;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &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(&params);
+
+  // Simulate a successful download and update.
+  payload_state.DownloadComplete();
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeFull,
+      kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
+      kNumPayloadTypes));
+  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, &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_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, 1, _, _, _));
+  payload_state.ReportFailedBootIfNeeded();
+  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+
+  // 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_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, 2, _, _, _));
+  payload_state.ReportFailedBootIfNeeded();
+  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+
+  // 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_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, 1, _, _, _));
+  payload_state.ReportFailedBootIfNeeded();
+  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+}
+
+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, &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_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, _, _, _, _))
+      .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, &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_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, _, _, _, _))
+      .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_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, _, _, _, _))
+      .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, &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, &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, &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, &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, &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, &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());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/power_manager_android.cc b/update_engine/power_manager_android.cc
new file mode 100644
index 0000000..6b7e880
--- /dev/null
+++ b/update_engine/power_manager_android.cc
@@ -0,0 +1,34 @@
+//
+// 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/power_manager_android.h"
+
+#include <base/logging.h>
+
+namespace chromeos_update_engine {
+
+namespace power_manager {
+std::unique_ptr<PowerManagerInterface> CreatePowerManager() {
+  return std::unique_ptr<PowerManagerInterface>(new PowerManagerAndroid());
+}
+}
+
+bool PowerManagerAndroid::RequestReboot() {
+  LOG(WARNING) << "PowerManager not implemented.";
+  return false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/power_manager_android.h b/update_engine/power_manager_android.h
new file mode 100644
index 0000000..86399ab
--- /dev/null
+++ b/update_engine/power_manager_android.h
@@ -0,0 +1,40 @@
+//
+// 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_POWER_MANAGER_ANDROID_H_
+#define UPDATE_ENGINE_POWER_MANAGER_ANDROID_H_
+
+#include <base/macros.h>
+
+#include "update_engine/power_manager_interface.h"
+
+namespace chromeos_update_engine {
+
+class PowerManagerAndroid : public PowerManagerInterface {
+ public:
+  PowerManagerAndroid() = default;
+  ~PowerManagerAndroid() override = default;
+
+  // PowerManagerInterface overrides.
+  bool RequestReboot() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PowerManagerAndroid);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_POWER_MANAGER_ANDROID_H_
diff --git a/update_engine/power_manager_chromeos.cc b/update_engine/power_manager_chromeos.cc
new file mode 100644
index 0000000..e175f95
--- /dev/null
+++ b/update_engine/power_manager_chromeos.cc
@@ -0,0 +1,43 @@
+//
+// 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/power_manager_chromeos.h"
+
+#include <power_manager/dbus-constants.h>
+#include <power_manager/dbus-proxies.h>
+
+#include "update_engine/dbus_connection.h"
+
+namespace chromeos_update_engine {
+
+namespace power_manager {
+std::unique_ptr<PowerManagerInterface> CreatePowerManager() {
+  return std::unique_ptr<PowerManagerInterface>(new PowerManagerChromeOS());
+}
+}
+
+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, &error);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/power_manager_chromeos.h b/update_engine/power_manager_chromeos.h
new file mode 100644
index 0000000..ad49889
--- /dev/null
+++ b/update_engine/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_POWER_MANAGER_H_
+#define UPDATE_ENGINE_POWER_MANAGER_H_
+
+#include <base/macros.h>
+#include <power_manager/dbus-proxies.h>
+
+#include "update_engine/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_POWER_MANAGER_H_
diff --git a/update_engine/power_manager_interface.h b/update_engine/power_manager_interface.h
new file mode 100644
index 0000000..be059ec
--- /dev/null
+++ b/update_engine/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_POWER_MANAGER_INTERFACE_H_
+#define UPDATE_ENGINE_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 chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_POWER_MANAGER_INTERFACE_H_
diff --git a/update_engine/proxy_resolver.cc b/update_engine/proxy_resolver.cc
new file mode 100644
index 0000000..abd6f76
--- /dev/null
+++ b/update_engine/proxy_resolver.cc
@@ -0,0 +1,66 @@
+//
+// 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.
+//
+
+#include "update_engine/proxy_resolver.h"
+
+#include <base/bind.h>
+#include <base/location.h>
+
+using brillo::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char kNoProxy[] = "direct://";
+
+DirectProxyResolver::~DirectProxyResolver() {
+  if (idle_callback_id_ != MessageLoop::kTaskIdNull) {
+    // The DirectProxyResolver is instantiated as part of the UpdateAttempter
+    // which is also instantiated by default by the FakeSystemState, even when
+    // it is not used. We check the manage_shares_id_ before calling the
+    // MessageLoop::current() since the unit test using a FakeSystemState may
+    // have not define a MessageLoop for the current thread.
+    MessageLoop::current()->CancelTask(idle_callback_id_);
+    idle_callback_id_ = MessageLoop::kTaskIdNull;
+  }
+}
+
+bool DirectProxyResolver::GetProxiesForUrl(const string& url,
+                                           ProxiesResolvedFn callback,
+                                           void* data) {
+  idle_callback_id_ = MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(
+            &DirectProxyResolver::ReturnCallback,
+            base::Unretained(this),
+            callback,
+            data));
+  return true;
+}
+
+void DirectProxyResolver::ReturnCallback(ProxiesResolvedFn callback,
+                                         void* data) {
+  idle_callback_id_ = MessageLoop::kTaskIdNull;
+
+  // Initialize proxy pool with as many proxies as indicated (all identical).
+  deque<string> proxies(num_proxies_, kNoProxy);
+
+  (*callback)(proxies, data);
+}
+
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/proxy_resolver.h b/update_engine/proxy_resolver.h
new file mode 100644
index 0000000..2c8824f
--- /dev/null
+++ b/update_engine/proxy_resolver.h
@@ -0,0 +1,88 @@
+//
+// 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_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <string>
+
+#include <base/logging.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+extern const char kNoProxy[];
+
+// Callback for a call to GetProxiesForUrl().
+// Resultant proxies are in |out_proxy|. Each will be in one of the
+// following forms:
+// http://<host>[:<port>] - HTTP proxy
+// socks{4,5}://<host>[:<port>] - SOCKS4/5 proxy
+// kNoProxy - no proxy
+typedef void (*ProxiesResolvedFn)(const std::deque<std::string>& proxies,
+                                  void* data);
+
+class ProxyResolver {
+ public:
+  ProxyResolver() {}
+  virtual ~ProxyResolver() {}
+
+  // Finds proxies for the given URL and returns them via the callback.
+  // |data| will be passed to the callback.
+  // Returns true on success.
+  virtual bool GetProxiesForUrl(const std::string& url,
+                                ProxiesResolvedFn callback,
+                                void* data) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
+};
+
+// Always says to not use a proxy
+class DirectProxyResolver : public ProxyResolver {
+ public:
+  DirectProxyResolver() = default;
+  ~DirectProxyResolver() override;
+  bool GetProxiesForUrl(const std::string& url,
+                        ProxiesResolvedFn callback,
+                        void* data) override;
+
+  // Set the number of direct (non-) proxies to be returned by resolver.
+  // The default value is 1; higher numbers are currently used in testing.
+  inline void set_num_proxies(size_t num_proxies) {
+    num_proxies_ = num_proxies;
+  }
+
+ private:
+  // The ID of the main loop callback.
+  brillo::MessageLoop::TaskId idle_callback_id_{
+      brillo::MessageLoop::kTaskIdNull};
+
+  // Number of direct proxies to return on resolved list; currently used for
+  // testing.
+  size_t num_proxies_{1};
+
+  // The MainLoop callback, from here we return to the client.
+  void ReturnCallback(ProxiesResolvedFn callback, void* data);
+  DISALLOW_COPY_AND_ASSIGN(DirectProxyResolver);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PROXY_RESOLVER_H_
diff --git a/update_engine/real_system_state.cc b/update_engine/real_system_state.cc
new file mode 100644
index 0000000..4ee7c69
--- /dev/null
+++ b/update_engine/real_system_state.cc
@@ -0,0 +1,210 @@
+//
+// 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/real_system_state.h"
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/location.h>
+#include <base/time/time.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/message_loop.h>
+
+#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/hardware.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/state_factory.h"
+#include "update_engine/weave_service_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() {
+  metrics_lib_.Init();
+
+  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_ = brillo::make_unique_ptr(new BootControlStub());
+  }
+
+  hardware_ = hardware::CreateHardware();
+  if (!hardware_) {
+    LOG(ERROR) << "Error intializing the HardwareInterface.";
+    return false;
+  }
+
+  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 intializing the ConnectionManagerInterface.";
+    return false;
+  }
+
+  power_manager_ = power_manager::CreatePowerManager();
+  if (!power_manager_) {
+    LOG(ERROR) << "Error intializing the PowerManagerInterface.";
+    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("", "", false)) {
+    LOG(WARNING) << "Ignoring OmahaRequestParams initialization error. Some "
+                    "features might not work properly.";
+  }
+
+  certificate_checker_.reset(
+      new CertificateChecker(prefs_.get(), &openssl_wrapper_));
+  certificate_checker_->Init();
+
+#if USE_LIBCROS
+  LibCrosProxy* libcros_proxy = &libcros_proxy_;
+#else
+  LibCrosProxy* libcros_proxy = nullptr;
+#endif  // USE_LIBCROS
+
+  // Initialize the UpdateAttempter before the UpdateManager.
+  update_attempter_.reset(
+      new UpdateAttempter(this, certificate_checker_.get(), libcros_proxy));
+  update_attempter_->Init();
+
+  weave_service_ = ConstructWeaveService(update_attempter_.get());
+  if (weave_service_)
+    update_attempter_->AddObserver(weave_service_.get());
+
+  // Initialize the Update Manager using the default state factory.
+  chromeos_update_manager::State* um_state =
+      chromeos_update_manager::DefaultStateFactory(
+          &policy_provider_, libcros_proxy, 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;
+  }
+
+  // All is well. Initialization successful.
+  return true;
+}
+
+bool RealSystemState::StartUpdater() {
+  // Initiate update checks.
+  update_attempter_->ScheduleUpdates();
+
+#ifndef USE_NESTLABS
+  // Update boot flags after 45 seconds.
+  MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&UpdateAttempter::UpdateBootFlags,
+                 base::Unretained(update_attempter_.get())),
+      base::TimeDelta::FromSeconds(45));
+#endif // USE_NESTLABS
+
+  // 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/update_engine/real_system_state.h b/update_engine/real_system_state.h
new file mode 100644
index 0000000..b280461
--- /dev/null
+++ b/update_engine/real_system_state.h
@@ -0,0 +1,198 @@
+//
+// 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_REAL_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
+
+#include "update_engine/system_state.h"
+
+#include <memory>
+#include <set>
+
+#include <metrics/metrics_library.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/clock.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/connection_manager_interface.h"
+#include "update_engine/daemon_state_interface.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state.h"
+#include "update_engine/power_manager_interface.h"
+#include "update_engine/omaha_request_params.h"
+#if USE_NESTLABS
+#include "update_engine/update_attempter_nestlabs.h"
+#else
+#include "update_engine/update_attempter.h"
+#endif
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/weave_service_interface.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 MetricsLibraryInterface* metrics_lib() override {
+    return &metrics_lib_;
+  }
+
+  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 WeaveServiceInterface* weave_service() override {
+    return weave_service_.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_; }
+
+ private:
+#if USE_LIBCROS
+  // LibCros proxy using the DBus connection.
+  LibCrosProxy libcros_proxy_;
+#endif  // USE_LIBCROS
+
+  // Interface for the power manager.
+  std::unique_ptr<PowerManagerInterface> power_manager_;
+
+  // Interface for the clock.
+  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 Library interface for reporting UMA stats.
+  MetricsLibrary metrics_lib_;
+
+  // 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<WeaveServiceInterface> weave_service_;
+
+  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};
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
diff --git a/update_engine/run_unittests b/update_engine/run_unittests
new file mode 100755
index 0000000..f07078d
--- /dev/null
+++ b/update_engine/run_unittests
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+#
+# 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.
+#
+
+# Runs the update engine unit tests, including both userland and run-as-root
+# tests.
+
+if [ ! -e ./update_engine_unittests ]; then
+  echo 'Error: unit test binary missing' >&2
+  exit 1
+fi
+
+user_pass=0
+./update_engine_unittests --gtest_filter='-*.RunAsRoot*' && user_pass=1
+root_pass=0
+sudo ./update_engine_unittests --gtest_filter='*.RunAsRoot*' && root_pass=1
+
+echo -n "User tests: " && [ $user_pass == 1 ] && echo "PASSED" || echo "FAILED"
+echo -n "Root tests: " && [ $root_pass == 1 ] && echo "PASSED" || echo "FAILED"
+
+exit $((2 - user_pass - root_pass))
diff --git a/update_engine/sample_images/generate_images.sh b/update_engine/sample_images/generate_images.sh
new file mode 100755
index 0000000..6a0d1ea
--- /dev/null
+++ b/update_engine/sample_images/generate_images.sh
@@ -0,0 +1,269 @@
+#!/bin/bash
+
+#
+# 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 script generates some sample images used in unittests and packages them
+# in the sample_images.tar.bz2 file. The list of generated images and their
+# options are described in the main() function. You need to manually run this
+# script to update the generated images whenever you modify this script.
+
+set -e
+
+# cleanup <path>
+# Unmount and remove the mountpoint <path>
+cleanup() {
+  if ! sudo umount "$1" 2>/dev/null; then
+    if mountpoint -q "$1"; then
+      sync && sudo umount "$1"
+    fi
+  fi
+  rmdir "$1"
+}
+
+# add_files_default <mntdir> <block_size>
+# Add several test files to the image mounted in <mntdir>.
+add_files_default() {
+  local mntdir="$1"
+  local block_size="$2"
+
+  ### Generate the files used in unittest with descriptive names.
+  sudo touch "${mntdir}"/empty-file
+
+  # regular: Regular files.
+  echo "small file" | sudo dd of="${mntdir}"/regular-small status=none
+  dd if=/dev/zero bs=1024 count=16 status=none | tr '\0' '\141' |
+    sudo dd of="${mntdir}"/regular-16k status=none
+  sudo dd if=/dev/zero of="${mntdir}"/regular-32k-zeros bs=1024 count=16 \
+    status=none
+
+  echo "with net_cap" | sudo dd of="${mntdir}"/regular-with_net_cap status=none
+  sudo setcap cap_net_raw=ep "${mntdir}"/regular-with_net_cap
+
+  # sparse_empty: Files with no data blocks at all (only sparse holes).
+  sudo truncate --size=10240 "${mntdir}"/sparse_empty-10k
+  sudo truncate --size=$(( block_size * 2 )) "${mntdir}"/sparse_empty-2blocks
+
+  # sparse: Files with some data blocks but also sparse holes.
+  echo -n "foo" |
+    sudo dd of="${mntdir}"/sparse-16k-last_block bs=1 \
+      seek=$(( 16 * 1024 - 3)) status=none
+
+  # ext2 inodes have 12 direct blocks, one indirect, one double indirect and
+  # one triple indirect. 10000 should be enough to have an indirect and double
+  # indirect block.
+  echo -n "foo" |
+    sudo dd of="${mntdir}"/sparse-10000blocks bs=1 \
+      seek=$(( block_size * 10000 )) status=none
+
+  sudo truncate --size=16384 "${mntdir}"/sparse-16k-first_block
+  echo "first block" | sudo dd of="${mntdir}"/sparse-16k-first_block status=none
+
+  sudo truncate --size=16384 "${mntdir}"/sparse-16k-holes
+  echo "a" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=100 status=none
+  echo "b" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=10000 status=none
+
+  # link: symlinks and hardlinks.
+  sudo ln -s "broken-link" "${mntdir}"/link-short_symlink
+  sudo ln -s $(dd if=/dev/zero bs=256 count=1 status=none | tr '\0' '\141') \
+    "${mntdir}"/link-long_symlink
+  sudo ln "${mntdir}"/regular-16k "${mntdir}"/link-hard-regular-16k
+
+  # Directories.
+  sudo mkdir -p "${mntdir}"/dir1/dir2/dir1
+  echo "foo" | sudo tee "${mntdir}"/dir1/dir2/file >/dev/null
+  echo "bar" | sudo tee "${mntdir}"/dir1/file >/dev/null
+
+  # FIFO
+  sudo mkfifo "${mntdir}"/fifo
+
+  # character special file
+  sudo mknod "${mntdir}"/cdev c 2 3
+
+  # removed: removed files that should not be listed.
+  echo "We will remove this file so it's contents will be somewhere in the " \
+    "empty space data but it won't be all zeros." |
+    sudo dd of="${mntdir}"/removed conv=fsync status=none
+  sudo rm "${mntdir}"/removed
+}
+
+# add_files_ue_settings <mntdir> <block_size>
+# Add the update_engine.conf settings file. This file contains the
+add_files_ue_settings() {
+  local mntdir="$1"
+
+  sudo mkdir -p "${mntdir}"/etc >/dev/null
+  sudo tee "${mntdir}"/etc/update_engine.conf >/dev/null <<EOF
+PAYLOAD_MINOR_VERSION=1234
+EOF
+  # Example of a real lsb-release file released on link stable.
+  sudo tee "${mntdir}"/etc/lsb-release >/dev/null <<EOF
+CHROMEOS_AUSERVER=https://tools.google.com/service/update2
+CHROMEOS_BOARD_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
+CHROMEOS_CANARY_APPID={90F229CE-83E2-4FAF-8479-E368A34938B1}
+CHROMEOS_DEVSERVER=
+CHROMEOS_RELEASE_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
+CHROMEOS_RELEASE_BOARD=link-signed-mp-v4keys
+CHROMEOS_RELEASE_BRANCH_NUMBER=63
+CHROMEOS_RELEASE_BUILD_NUMBER=6946
+CHROMEOS_RELEASE_BUILD_TYPE=Official Build
+CHROMEOS_RELEASE_CHROME_MILESTONE=43
+CHROMEOS_RELEASE_DESCRIPTION=6946.63.0 (Official Build) stable-channel link
+CHROMEOS_RELEASE_NAME=Chrome OS
+CHROMEOS_RELEASE_PATCH_NUMBER=0
+CHROMEOS_RELEASE_TRACK=stable-channel
+CHROMEOS_RELEASE_VERSION=6946.63.0
+GOOGLE_RELEASE=6946.63.0
+EOF
+}
+
+add_files_postinstall() {
+  local mntdir="$1"
+
+  sudo mkdir -p "${mntdir}"/bin >/dev/null
+
+  # A postinstall bash program.
+  sudo tee "${mntdir}"/bin/postinst_example >/dev/null <<EOF
+#!/etc/../bin/sh
+echo "I'm a postinstall program and I know how to write to stdout"
+echo "My call was $@"
+exit 0
+EOF
+
+  # A symlink to another program. This should also work.
+  sudo ln -s "postinst_example" "${mntdir}"/bin/postinst_link
+
+  sudo tee "${mntdir}"/bin/postinst_fail3 >/dev/null <<EOF
+#!/etc/../bin/sh
+exit 3
+EOF
+
+  sudo tee "${mntdir}"/bin/postinst_fail1 >/dev/null <<EOF
+#!/etc/../bin/sh
+exit 1
+EOF
+
+  # A program that succeeds if it is suspended during the first 5 minutes.
+  sudo tee "${mntdir}"/bin/postinst_suspend >/dev/null <<EOF
+#!/etc/../bin/sh
+trap "{ echo Got a SIGCONT; exit 0; }" CONT
+# Signal that we are ready to receive the signal by redirecting our stdin to
+# /dev/zero, the test can detect that.
+exec </dev/zero
+# Allow the signal handler to run every 100 ms.
+i=3000
+while [ \$i -ge 0 ]; do
+  sleep 0.1
+  i=\$((i-1))
+done
+exit 1
+EOF
+
+  # A program that reports back progress.
+  sudo tee "${mntdir}"/bin/postinst_progress >/dev/null <<EOF
+#!/etc/../bin/sh
+# These values have exact representation in IEEE 754 so we avoid rounding
+# errors.
+echo global_progress 0.25 >&3
+echo global_progress 0.5 >&3
+echo global_progress 1.0 >&3
+exit 0
+EOF
+
+  # A postinstall bash program.
+  sudo tee "${mntdir}"/bin/self_check_context >/dev/null <<EOF
+#!/etc/../bin/sh
+echo "This is my context:"
+ls -lZ "\$0" | grep -F ' u:object_r:postinstall_file:s0 ' || exit 5
+exit 0
+EOF
+
+  sudo tee "${mntdir}"/postinst >/dev/null <<EOF
+#!/etc/../bin/sh
+echo "postinst"
+exit 0
+EOF
+
+  sudo chmod +x "${mntdir}"/postinst "${mntdir}"/bin/*
+}
+
+# generate_fs <filename> <kind> <size> [block_size] [block_groups]
+generate_fs() {
+  local filename="$1"
+  local kind="$2"
+  local size="$3"
+  local block_size="${4:-4096}"
+  local block_groups="${5:-}"
+
+  local mkfs_opts=( -q -F -b "${block_size}" -L "ROOT-TEST" -t ext2 )
+  if [[ -n "${block_groups}" ]]; then
+    mkfs_opts+=( -G "${block_groups}" )
+  fi
+
+  local mntdir=$(mktemp --tmpdir -d generate_ext2.XXXXXX)
+  trap 'cleanup "${mntdir}"; rm -f "${filename}"' INT TERM EXIT
+
+  # Cleanup old image.
+  if [[ -e "${filename}" ]]; then
+    rm -f "${filename}"
+  fi
+  truncate --size="${size}" "${filename}"
+
+  mkfs.ext2 "${mkfs_opts[@]}" "${filename}"
+  sudo mount "${filename}" "${mntdir}" -o loop
+
+  case "${kind}" in
+    unittest)
+      add_files_ue_settings "${mntdir}" "${block_size}"
+      add_files_postinstall "${mntdir}" "${block_size}"
+      ;;
+    default)
+      add_files_default "${mntdir}" "${block_size}"
+      ;;
+    empty)
+      ;;
+  esac
+
+  cleanup "${mntdir}"
+  trap - INT TERM EXIT
+}
+
+OUTPUT_DIR=$(dirname "$0")
+IMAGES=()
+
+# generate_image <image_name> [<image args> ...]
+generate_image() {
+  echo "Generating image $1.img"
+  IMAGES+=( "$1.img" )
+  generate_fs "${OUTPUT_DIR}/$1.img" "${@:2}"
+}
+
+main() {
+  # Add more sample images here.
+  generate_image disk_ext2_1k default $((1024 * 1024)) 1024
+  generate_image disk_ext2_4k default $((1024 * 4096)) 4096
+  generate_image disk_ext2_4k_empty empty $((1024 * 4096)) 4096
+  generate_image disk_ext2_unittest unittest $((1024 * 4096)) 4096
+
+  # Generate the tarball and delete temporary images.
+  echo "Packing tar file sample_images.tar.bz2"
+  tar -jcf "${OUTPUT_DIR}/sample_images.tar.bz2" -C "${OUTPUT_DIR}" \
+    --sparse "${IMAGES[@]}"
+  cd "${OUTPUT_DIR}"
+  rm "${IMAGES[@]}"
+}
+
+main
diff --git a/update_engine/sample_images/sample_images.tar.bz2 b/update_engine/sample_images/sample_images.tar.bz2
new file mode 100644
index 0000000..72f4eb5
--- /dev/null
+++ b/update_engine/sample_images/sample_images.tar.bz2
Binary files differ
diff --git a/update_engine/sample_omaha_v3_response.xml b/update_engine/sample_omaha_v3_response.xml
new file mode 100644
index 0000000..abba523
--- /dev/null
+++ b/update_engine/sample_omaha_v3_response.xml
@@ -0,0 +1,19 @@
+<response protocol="3.0" server="prod">
+  <daystart elapsed_seconds="56652"/>
+  <app appid="{90f229ce-83e2-4faf-8479-e368a34938b1}" status="ok">
+    <updatecheck status="ok">
+      <urls>
+        <url codebase="https://storage.googleapis.com/chromeos-releases-public/canary-channel/canary-channel/3095.0.0/"/>
+      </urls>
+      <manifest version="3095.0.0">
+        <packages>
+          <package hash="HVOmp67vBjPdvpWmOC2Uw4UDwsc=" name="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed" required="true" size="400752559"/>
+        </packages>
+        <actions>
+          <action event="update" run="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed"/>
+          <action ChromeOSVersion="3095.0.0" ChromeVersion="24.0.1307.0" IsDelta="true" IsDeltaPayload="false" MaxDaysToScatter="14" MetadataSignatureRsa="xXrO/LahHlKk3YmqEf1qE0PN587Sc2IJV+FN7J7x1h49waNQIy/QwYO4LaOySgETe5JZXtkAEzzqakfJwxQ2pVfzj1GkExwjd5LTn1He2GvA73B8fKbS4bfP7dbUFwD5039xCwf1U2gezFViOiOPiVURx/pEsdhv+Cqx/3HbjIuj5au2dooSyDxLC5AnODzAKyYfAcjMuiLON+9SqmctJW+VjzdY9SbJAnkH2qqVjFyBKAXsYT+hOTIJ3MJpg8OSVxMMtGB99PxbOJ52F37d2Y5Fws/AUkNnNEsan/WRJA1kuWoS6rpeR8JQYuVhLiK2u/KpOcvMVRw3Q2VUxtcAGw==" MetadataSize="58315" event="postinstall" sha256="DIAVxoI+8NpsudUawOA5U92VHlaxQBS3ejN4EPM6T2A="/>
+        </actions>
+      </manifest>
+    </updatecheck>
+  </app>
+</response>
diff --git a/update_engine/scripts/brillo_update_payload b/update_engine/scripts/brillo_update_payload
new file mode 100755
index 0000000..648936b
--- /dev/null
+++ b/update_engine/scripts/brillo_update_payload
@@ -0,0 +1,627 @@
+#!/bin/bash
+
+# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Script to generate a Brillo update for use by the update engine.
+#
+# usage: brillo_update_payload COMMAND [ARGS]
+# The following commands are supported:
+#  generate    generate an unsigned payload
+#  hash        generate a payload or metadata hash
+#  sign        generate a signed payload
+#  properties  generate a properties file from a payload
+#
+#  Generate command arguments:
+#  --payload             generated unsigned payload output file
+#  --source_image        if defined, generate a delta payload from the specified
+#                        image to the target_image
+#  --target_image        the target image that should be sent to clients
+#  --metadata_size_file  if defined, generate a file containing the size of the payload
+#                        metadata in bytes to the specified file
+#
+#  Hash command arguments:
+#  --unsigned_payload    the input unsigned payload to generate the hash from
+#  --signature_size      signature sizes in bytes in the following format:
+#                        "size1:size2[:...]"
+#  --payload_hash_file   if defined, generate a payload hash and output to the
+#                        specified file
+#  --metadata_hash_file  if defined, generate a metadata hash and output to the
+#                        specified file
+#
+#  Sign command arguments:
+#  --unsigned_payload        the input unsigned payload to insert the signatures
+#  --payload                 the output signed payload
+#  --signature_size          signature sizes in bytes in the following format:
+#                            "size1:size2[:...]"
+#  --payload_signature_file  the payload signature files in the following
+#                            format:
+#                            "payload_signature1:payload_signature2[:...]"
+#  --metadata_signature_file the metadata signature files in the following
+#                            format:
+#                            "metadata_signature1:metadata_signature2[:...]"
+#  --metadata_size_file      if defined, generate a file containing the size of
+#                            the signed payload metadata in bytes to the
+#                            specified file
+#  Note that the number of signature sizes and payload signatures have to match.
+#
+#  Properties command arguments:
+#  --payload                 the input signed or unsigned payload
+#  --properties_file         the output path where to write the properties, or
+#                            '-' for stdout.
+
+
+# Exit codes:
+EX_UNSUPPORTED_DELTA=100
+
+warn() {
+  echo "brillo_update_payload: warning: $*" >&2
+}
+
+die() {
+  echo "brillo_update_payload: error: $*" >&2
+  exit 1
+}
+
+# Loads shflags. We first look at the default install location; then look for
+# crosutils (chroot); finally check our own directory (au-generator zipfile).
+load_shflags() {
+  local my_dir="$(dirname "$(readlink -f "$0")")"
+  local path
+  for path in /usr/share/misc {/usr/lib/crosutils,"${my_dir}"}/lib/shflags; do
+    if [[ -r "${path}/shflags" ]]; then
+      . "${path}/shflags" || die "Could not load ${path}/shflags."
+      return
+    fi
+  done
+  die "Could not find shflags."
+}
+
+load_shflags
+
+HELP_GENERATE="generate: Generate an unsigned update payload."
+HELP_HASH="hash: Generate the hashes of the unsigned payload and metadata used \
+for signing."
+HELP_SIGN="sign: Insert the signatures into the unsigned payload."
+HELP_PROPERTIES="properties: Extract payload properties to a file."
+
+usage() {
+  echo "Supported commands:"
+  echo
+  echo "${HELP_GENERATE}"
+  echo "${HELP_HASH}"
+  echo "${HELP_SIGN}"
+  echo "${HELP_PROPERTIES}"
+  echo
+  echo "Use: \"$0 <command> --help\" for more options."
+}
+
+# Check that a command is specified.
+if [[ $# -lt 1 ]]; then
+  echo "Please specify a command [generate|hash|sign|properties]"
+  exit 1
+fi
+
+# Parse command.
+COMMAND="${1:-}"
+shift
+
+case "${COMMAND}" in
+  generate)
+    FLAGS_HELP="${HELP_GENERATE}"
+    ;;
+
+  hash)
+    FLAGS_HELP="${HELP_HASH}"
+    ;;
+
+  sign)
+    FLAGS_HELP="${HELP_SIGN}"
+    ;;
+
+  properties)
+    FLAGS_HELP="${HELP_PROPERTIES}"
+    ;;
+  *)
+    echo "Unrecognized command: \"${COMMAND}\"" >&2
+    usage >&2
+    exit 1
+    ;;
+esac
+
+# Flags
+FLAGS_HELP="Usage: $0 ${COMMAND} [flags]
+${FLAGS_HELP}"
+
+if [[ "${COMMAND}" == "generate" ]]; then
+  DEFINE_string payload "" \
+    "Path to output the generated unsigned payload file."
+  DEFINE_string target_image "" \
+    "Path to the target image that should be sent to clients."
+  DEFINE_string source_image "" \
+    "Optional: Path to a source image. If specified, this makes a delta update."
+  DEFINE_string metadata_size_file "" \
+    "Optional: Path to output metadata size."
+fi
+if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then
+  DEFINE_string unsigned_payload "" "Path to the input unsigned payload."
+  DEFINE_string signature_size "" \
+    "Signature sizes in bytes in the following format: size1:size2[:...]"
+fi
+if [[ "${COMMAND}" == "hash" ]]; then
+  DEFINE_string metadata_hash_file "" \
+    "Optional: Path to output metadata hash file."
+  DEFINE_string payload_hash_file "" \
+    "Optional: Path to output payload hash file."
+fi
+if [[ "${COMMAND}" == "sign" ]]; then
+  DEFINE_string payload "" \
+    "Path to output the generated unsigned payload file."
+  DEFINE_string metadata_signature_file "" \
+    "The metatada signatures in the following format: \
+metadata_signature1:metadata_signature2[:...]"
+  DEFINE_string payload_signature_file "" \
+    "The payload signatures in the following format: \
+payload_signature1:payload_signature2[:...]"
+  DEFINE_string metadata_size_file "" \
+    "Optional: Path to output metadata size."
+fi
+if [[ "${COMMAND}" == "properties" ]]; then
+  DEFINE_string payload "" \
+    "Path to the input signed or unsigned payload file."
+  DEFINE_string properties_file "-" \
+    "Path to output the extracted property files. If '-' is passed stdout will \
+be used."
+fi
+
+DEFINE_string work_dir "/tmp" "Where to dump temporary files."
+
+# Parse command line flag arguments
+FLAGS "$@" || exit 1
+eval set -- "${FLAGS_ARGV}"
+set -e
+
+# Associative arrays from partition name to file in the source and target
+# images. The size of the updated area must be the size of the file.
+declare -A SRC_PARTITIONS
+declare -A DST_PARTITIONS
+
+# List of partition names in order.
+declare -a PARTITIONS_ORDER
+
+# A list of temporary files to remove during cleanup.
+CLEANUP_FILES=()
+
+# Global options to force the version of the payload.
+FORCE_MAJOR_VERSION=""
+FORCE_MINOR_VERSION=""
+
+# Path to the postinstall config file in target image if exists.
+POSTINSTALL_CONFIG_FILE=""
+
+# The fingerprint of zlib in the source image.
+ZLIB_FINGERPRINT=""
+
+# read_option_int <file.txt> <option_key> [default_value]
+#
+# Reads the unsigned integer value associated with |option_key| in a key=value
+# file |file.txt|. Prints the read value if found and valid, otherwise prints
+# the |default_value|.
+read_option_uint() {
+  local file_txt="$1"
+  local option_key="$2"
+  local default_value="${3:-}"
+  local value
+  if value=$(look "${option_key}=" "${file_txt}" | tail -n 1); then
+    if value=$(echo "${value}" | cut -f 2- -d "=" | grep -E "^[0-9]+$"); then
+      echo "${value}"
+      return
+    fi
+  fi
+  echo "${default_value}"
+}
+
+# truncate_file <file_path> <file_size>
+#
+# Truncate the given |file_path| to |file_size| using perl.
+# The truncate binary might not be available.
+truncate_file() {
+  local file_path="$1"
+  local file_size="$2"
+  perl -e "open(FILE, \"+<\", \$ARGV[0]); \
+           truncate(FILE, ${file_size}); \
+           close(FILE);" "${file_path}"
+}
+
+# Create a temporary file in the work_dir with an optional pattern name.
+# Prints the name of the newly created file.
+create_tempfile() {
+  local pattern="${1:-tempfile.XXXXXX}"
+  mktemp --tmpdir="${FLAGS_work_dir}" "${pattern}"
+}
+
+cleanup() {
+  local err=""
+  rm -f "${CLEANUP_FILES[@]}" || err=1
+
+  # If we are cleaning up after an error, or if we got an error during
+  # cleanup (even if we eventually succeeded) return a non-zero exit
+  # code. This triggers additional logging in most environments that call
+  # this script.
+  if [[ -n "${err}" ]]; then
+    die "Cleanup encountered an error."
+  fi
+}
+
+cleanup_on_error() {
+  trap - INT TERM ERR EXIT
+  cleanup
+  die "Cleanup success after an error."
+}
+
+cleanup_on_exit() {
+  trap - INT TERM ERR EXIT
+  cleanup
+}
+
+trap cleanup_on_error INT TERM ERR
+trap cleanup_on_exit EXIT
+
+
+# extract_image <image> <partitions_array> [partitions_order]
+#
+# Detect the format of the |image| file and extract its updatable partitions
+# into new temporary files. Add the list of partition names and its files to the
+# associative array passed in |partitions_array|. If |partitions_order| is
+# passed, set it to list of partition names in order.
+extract_image() {
+  local image="$1"
+
+  # Brillo images are zip files. We detect the 4-byte magic header of the zip
+  # file.
+  local magic=$(head --bytes=4 "${image}" | hexdump -e '1/1 "%.2x"')
+  if [[ "${magic}" == "504b0304" ]]; then
+    echo "Detected .zip file, extracting Brillo image."
+    extract_image_brillo "$@"
+    return
+  fi
+
+  # Chrome OS images are GPT partitioned disks. We should have the cgpt binary
+  # bundled here and we will use it to extract the partitions, so the GPT
+  # headers must be valid.
+  if cgpt show -q -n "${image}" >/dev/null; then
+    echo "Detected GPT image, extracting Chrome OS image."
+    extract_image_cros "$@"
+    return
+  fi
+
+  die "Couldn't detect the image format of ${image}"
+}
+
+# extract_image_cros <image.bin> <partitions_array> [partitions_order]
+#
+# Extract Chromium OS recovery images into new temporary files.
+extract_image_cros() {
+  local image="$1"
+  local partitions_array="$2"
+  local partitions_order="${3:-}"
+
+  local kernel root
+  kernel=$(create_tempfile "kernel.bin.XXXXXX")
+  CLEANUP_FILES+=("${kernel}")
+  root=$(create_tempfile "root.bin.XXXXXX")
+  CLEANUP_FILES+=("${root}")
+
+  cros_generate_update_payload --extract \
+    --image "${image}" \
+    --kern_path "${kernel}" --root_path "${root}" \
+    --work_dir "${FLAGS_work_dir}" --outside_chroot
+
+  # Chrome OS uses major_version 1 payloads for all versions, even if the
+  # updater supports a newer major version.
+  FORCE_MAJOR_VERSION="1"
+
+  if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then
+    # Copy from zlib_fingerprint in source image to stdout.
+    ZLIB_FINGERPRINT=$(e2cp "${root}":/etc/zlib_fingerprint -)
+  fi
+
+  # When generating legacy Chrome OS images, we need to use "boot" and "system"
+  # for the partition names to be compatible with updating Brillo devices with
+  # Chrome OS images.
+  eval ${partitions_array}[boot]=\""${kernel}"\"
+  eval ${partitions_array}[system]=\""${root}"\"
+
+  if [[ -n "${partitions_order}" ]]; then
+    eval "${partitions_order}=( \"system\" \"boot\" )"
+  fi
+
+  local part varname
+  for part in boot system; do
+    varname="${partitions_array}[${part}]"
+    printf "md5sum of %s: " "${varname}"
+    md5sum "${!varname}"
+  done
+}
+
+# extract_image_brillo <target_files.zip> <partitions_array> [partitions_order]
+#
+# Extract the A/B updated partitions from a Brillo target_files zip file into
+# new temporary files.
+extract_image_brillo() {
+  local image="$1"
+  local partitions_array="$2"
+  local partitions_order="${3:-}"
+
+  local partitions=( "boot" "system" )
+  local ab_partitions_list
+  ab_partitions_list=$(create_tempfile "ab_partitions_list.XXXXXX")
+  CLEANUP_FILES+=("${ab_partitions_list}")
+  if unzip -p "${image}" "META/ab_partitions.txt" >"${ab_partitions_list}"; then
+    if grep -v -E '^[a-zA-Z0-9_-]*$' "${ab_partitions_list}" >&2; then
+      die "Invalid partition names found in the partition list."
+    fi
+    partitions=($(cat "${ab_partitions_list}"))
+    if [[ ${#partitions[@]} -eq 0 ]]; then
+      die "The list of partitions is empty. Can't generate a payload."
+    fi
+  else
+    warn "No ab_partitions.txt found. Using default."
+  fi
+  echo "List of A/B partitions: ${partitions[@]}"
+
+  if [[ -n "${partitions_order}" ]]; then
+    eval "${partitions_order}=(${partitions[@]})"
+  fi
+
+  # All Brillo updaters support major version 2.
+  FORCE_MAJOR_VERSION="2"
+
+  if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then
+    # Source image
+    local ue_config=$(create_tempfile "ue_config.XXXXXX")
+    CLEANUP_FILES+=("${ue_config}")
+    if ! unzip -p "${image}" "META/update_engine_config.txt" \
+        >"${ue_config}"; then
+      warn "No update_engine_config.txt found. Assuming pre-release image, \
+using payload minor version 2"
+    fi
+    # For delta payloads, we use the major and minor version supported by the
+    # old updater.
+    FORCE_MINOR_VERSION=$(read_option_uint "${ue_config}" \
+      "PAYLOAD_MINOR_VERSION" 2)
+    FORCE_MAJOR_VERSION=$(read_option_uint "${ue_config}" \
+      "PAYLOAD_MAJOR_VERSION" 2)
+
+    # Brillo support for deltas started with minor version 3.
+    if [[ "${FORCE_MINOR_VERSION}" -le 2 ]]; then
+      warn "No delta support from minor version ${FORCE_MINOR_VERSION}. \
+Disabling deltas for this source version."
+      exit ${EX_UNSUPPORTED_DELTA}
+    fi
+
+    if [[ "${FORCE_MINOR_VERSION}" -ge 4 ]]; then
+      ZLIB_FINGERPRINT=$(unzip -p "${image}" "META/zlib_fingerprint.txt")
+    fi
+  else
+    # Target image
+    local postinstall_config=$(create_tempfile "postinstall_config.XXXXXX")
+    CLEANUP_FILES+=("${postinstall_config}")
+    if unzip -p "${image}" "META/postinstall_config.txt" \
+        >"${postinstall_config}"; then
+      POSTINSTALL_CONFIG_FILE="${postinstall_config}"
+    fi
+  fi
+
+  local part part_file temp_raw filesize
+  for part in "${partitions[@]}"; do
+    part_file=$(create_tempfile "${part}.img.XXXXXX")
+    CLEANUP_FILES+=("${part_file}")
+    unzip -p "${image}" "IMAGES/${part}.img" >"${part_file}"
+
+    # If the partition is stored as an Android sparse image file, we need to
+    # convert them to a raw image for the update.
+    local magic=$(head --bytes=4 "${part_file}" | hexdump -e '1/1 "%.2x"')
+    if [[ "${magic}" == "3aff26ed" ]]; then
+      temp_raw=$(create_tempfile "${part}.raw.XXXXXX")
+      CLEANUP_FILES+=("${temp_raw}")
+      echo "Converting Android sparse image ${part}.img to RAW."
+      simg2img "${part_file}" "${temp_raw}"
+      # At this point, we can drop the contents of the old part_file file, but
+      # we can't delete the file because it will be deleted in cleanup.
+      true >"${part_file}"
+      part_file="${temp_raw}"
+    fi
+
+    # delta_generator only supports images multiple of 4 KiB. For target images
+    # we pad the data with zeros if needed, but for source images we truncate
+    # down the data since the last block of the old image could be padded on
+    # disk with unknown data.
+    filesize=$(stat -c%s "${part_file}")
+    if [[ $(( filesize % 4096 )) -ne 0 ]]; then
+      if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then
+        echo "Rounding DOWN partition ${part}.img to a multiple of 4 KiB."
+        : $(( filesize = filesize & -4096 ))
+      else
+        echo "Rounding UP partition ${part}.img to a multiple of 4 KiB."
+        : $(( filesize = (filesize + 4095) & -4096 ))
+      fi
+      truncate_file "${part_file}" "${filesize}"
+    fi
+
+    eval "${partitions_array}[\"${part}\"]=\"${part_file}\""
+    echo "Extracted ${partitions_array}[${part}]: ${filesize} bytes"
+  done
+}
+
+validate_generate() {
+  [[ -n "${FLAGS_payload}" ]] ||
+    die "You must specify an output filename with --payload FILENAME"
+
+  [[ -n "${FLAGS_target_image}" ]] ||
+    die "You must specify a target image with --target_image FILENAME"
+}
+
+cmd_generate() {
+  local payload_type="delta"
+  if [[ -z "${FLAGS_source_image}" ]]; then
+    payload_type="full"
+  fi
+
+  echo "Extracting images for ${payload_type} update."
+
+  extract_image "${FLAGS_target_image}" DST_PARTITIONS PARTITIONS_ORDER
+  if [[ "${payload_type}" == "delta" ]]; then
+    extract_image "${FLAGS_source_image}" SRC_PARTITIONS
+  fi
+
+  echo "Generating ${payload_type} update."
+  # Common payload args:
+  GENERATOR_ARGS=( -out_file="${FLAGS_payload}" )
+
+  local part old_partitions="" new_partitions="" partition_names=""
+  for part in "${PARTITIONS_ORDER[@]}"; do
+    if [[ -n "${partition_names}" ]]; then
+      partition_names+=":"
+      new_partitions+=":"
+      old_partitions+=":"
+    fi
+    partition_names+="${part}"
+    new_partitions+="${DST_PARTITIONS[${part}]}"
+    old_partitions+="${SRC_PARTITIONS[${part}]:-}"
+  done
+
+  # Target image args:
+  GENERATOR_ARGS+=(
+    -partition_names="${partition_names}"
+    -new_partitions="${new_partitions}"
+  )
+
+  if [[ "${payload_type}" == "delta" ]]; then
+    # Source image args:
+    GENERATOR_ARGS+=(
+      -old_partitions="${old_partitions}"
+    )
+    if [[ -n "${FORCE_MINOR_VERSION}" ]]; then
+      GENERATOR_ARGS+=( --minor_version="${FORCE_MINOR_VERSION}" )
+    fi
+    if [[ -n "${ZLIB_FINGERPRINT}" ]]; then
+      GENERATOR_ARGS+=( --zlib_fingerprint="${ZLIB_FINGERPRINT}" )
+    fi
+  fi
+
+  if [[ -n "${FORCE_MAJOR_VERSION}" ]]; then
+    GENERATOR_ARGS+=( --major_version="${FORCE_MAJOR_VERSION}" )
+  fi
+
+  if [[ -n "${FLAGS_metadata_size_file}" ]]; then
+    GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" )
+  fi
+
+  if [[ -n "${POSTINSTALL_CONFIG_FILE}" ]]; then
+    GENERATOR_ARGS+=(
+      --new_postinstall_config_file="${POSTINSTALL_CONFIG_FILE}"
+    )
+  fi
+
+  echo "Running delta_generator with args: ${GENERATOR_ARGS[@]}"
+  "${GENERATOR}" "${GENERATOR_ARGS[@]}"
+
+  echo "Done generating ${payload_type} update."
+}
+
+validate_hash() {
+  [[ -n "${FLAGS_signature_size}" ]] ||
+    die "You must specify signature size with --signature_size SIZES"
+
+  [[ -n "${FLAGS_unsigned_payload}" ]] ||
+    die "You must specify the input unsigned payload with \
+--unsigned_payload FILENAME"
+
+  [[ -n "${FLAGS_payload_hash_file}" ]] ||
+    die "You must specify --payload_hash_file FILENAME"
+
+  [[ -n "${FLAGS_metadata_hash_file}" ]] ||
+    die "You must specify --metadata_hash_file FILENAME"
+}
+
+cmd_hash() {
+  "${GENERATOR}" \
+      -in_file="${FLAGS_unsigned_payload}" \
+      -signature_size="${FLAGS_signature_size}" \
+      -out_hash_file="${FLAGS_payload_hash_file}" \
+      -out_metadata_hash_file="${FLAGS_metadata_hash_file}"
+
+  echo "Done generating hash."
+}
+
+validate_sign() {
+  [[ -n "${FLAGS_signature_size}" ]] ||
+    die "You must specify signature size with --signature_size SIZES"
+
+  [[ -n "${FLAGS_unsigned_payload}" ]] ||
+    die "You must specify the input unsigned payload with \
+--unsigned_payload FILENAME"
+
+  [[ -n "${FLAGS_payload}" ]] ||
+    die "You must specify the output signed payload with --payload FILENAME"
+
+  [[ -n "${FLAGS_payload_signature_file}" ]] ||
+    die "You must specify the payload signature file with \
+--payload_signature_file SIGNATURES"
+
+  [[ -n "${FLAGS_metadata_signature_file}" ]] ||
+    die "You must specify the metadata signature file with \
+--metadata_signature_file SIGNATURES"
+}
+
+cmd_sign() {
+  GENERATOR_ARGS=(
+    -in_file="${FLAGS_unsigned_payload}"
+    -signature_size="${FLAGS_signature_size}"
+    -signature_file="${FLAGS_payload_signature_file}"
+    -metadata_signature_file="${FLAGS_metadata_signature_file}"
+    -out_file="${FLAGS_payload}"
+  )
+
+  if [[ -n "${FLAGS_metadata_size_file}" ]]; then
+    GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" )
+  fi
+
+  "${GENERATOR}" "${GENERATOR_ARGS[@]}"
+  echo "Done signing payload."
+}
+
+validate_properties() {
+  [[ -n "${FLAGS_payload}" ]] ||
+    die "You must specify the payload file with --payload FILENAME"
+
+  [[ -n "${FLAGS_properties_file}" ]] ||
+    die "You must specify a non empty --properties_file FILENAME"
+}
+
+cmd_properties() {
+  "${GENERATOR}" \
+      -in_file="${FLAGS_payload}" \
+      -properties_file="${FLAGS_properties_file}"
+}
+
+# Sanity check that the real generator exists:
+GENERATOR="$(which delta_generator || true)"
+[[ -x "${GENERATOR}" ]] || die "can't find delta_generator"
+
+case "$COMMAND" in
+  generate) validate_generate
+            cmd_generate
+            ;;
+  hash) validate_hash
+        cmd_hash
+        ;;
+  sign) validate_sign
+        cmd_sign
+        ;;
+  properties) validate_properties
+              cmd_properties
+              ;;
+esac
diff --git a/update_engine/scripts/paycheck.py b/update_engine/scripts/paycheck.py
new file mode 100755
index 0000000..0195f53
--- /dev/null
+++ b/update_engine/scripts/paycheck.py
@@ -0,0 +1,232 @@
+#!/usr/bin/python2
+#
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Command-line tool for checking and applying Chrome OS update payloads."""
+
+from __future__ import print_function
+
+import optparse
+import os
+import sys
+
+# pylint: disable=F0401
+lib_dir = os.path.join(os.path.dirname(__file__), 'lib')
+if os.path.exists(lib_dir) and os.path.isdir(lib_dir):
+  sys.path.insert(1, lib_dir)
+import update_payload
+
+
+_TYPE_FULL = 'full'
+_TYPE_DELTA = 'delta'
+
+
+def ParseArguments(argv):
+  """Parse and validate command-line arguments.
+
+  Args:
+    argv: command-line arguments to parse (excluding the program name)
+
+  Returns:
+    A tuple (opts, payload, extra_args), where `opts' are the options
+    returned by the parser, `payload' is the name of the payload file
+    (mandatory argument) and `extra_args' are any additional command-line
+    arguments.
+  """
+  parser = optparse.OptionParser(
+      usage=('Usage: %prog [OPTION...] PAYLOAD [DST_KERN DST_ROOT '
+             '[SRC_KERN SRC_ROOT]]'),
+      description=('Applies a Chrome OS update PAYLOAD to SRC_KERN and '
+                   'SRC_ROOT emitting DST_KERN and DST_ROOT, respectively. '
+                   'SRC_KERN and SRC_ROOT are only needed for delta payloads. '
+                   'When no partitions are provided, verifies the payload '
+                   'integrity.'),
+      epilog=('Note: a payload may verify correctly but fail to apply, and '
+              'vice versa; this is by design and can be thought of as static '
+              'vs dynamic correctness. A payload that both verifies and '
+              'applies correctly should be safe for use by the Chrome OS '
+              'Update Engine. Use --check to verify a payload prior to '
+              'applying it.'))
+
+  check_opts = optparse.OptionGroup(parser, 'Checking payload integrity')
+  check_opts.add_option('-c', '--check', action='store_true', default=False,
+                        help=('force payload integrity check (e.g. before '
+                              'applying)'))
+  check_opts.add_option('-D', '--describe', action='store_true', default=False,
+                        help='Print a friendly description of the payload.')
+  check_opts.add_option('-r', '--report', metavar='FILE',
+                        help="dump payload report (`-' for stdout)")
+  check_opts.add_option('-t', '--type', metavar='TYPE', dest='assert_type',
+                        help=("assert that payload is either `%s' or `%s'" %
+                              (_TYPE_FULL, _TYPE_DELTA)))
+  check_opts.add_option('-z', '--block-size', metavar='NUM', default=0,
+                        type='int',
+                        help='assert a non-default (4096) payload block size')
+  check_opts.add_option('-u', '--allow-unhashed', action='store_true',
+                        default=False, help='allow unhashed operations')
+  check_opts.add_option('-d', '--disabled_tests', metavar='TESTLIST',
+                        default=(),
+                        help=('comma-separated list of tests to disable; '
+                              'available values: ' +
+                              ', '.join(update_payload.CHECKS_TO_DISABLE)))
+  check_opts.add_option('-k', '--key', metavar='FILE',
+                        help=('Override standard key used for signature '
+                              'validation'))
+  check_opts.add_option('-m', '--meta-sig', metavar='FILE',
+                        help='verify metadata against its signature')
+  check_opts.add_option('-p', '--root-part-size', metavar='NUM',
+                        default=0, type='int',
+                        help=('override rootfs partition size auto-inference'))
+  check_opts.add_option('-P', '--kern-part-size', metavar='NUM',
+                        default=0, type='int',
+                        help=('override kernel partition size auto-inference'))
+  parser.add_option_group(check_opts)
+
+  trace_opts = optparse.OptionGroup(parser, 'Applying payload')
+  trace_opts.add_option('-x', '--extract-bsdiff', action='store_true',
+                        default=False,
+                        help=('use temp input/output files with BSDIFF '
+                              'operations (not in-place)'))
+  trace_opts.add_option('--bspatch-path', metavar='FILE',
+                        help=('use the specified bspatch binary'))
+  parser.add_option_group(trace_opts)
+
+  trace_opts = optparse.OptionGroup(parser, 'Block tracing')
+  trace_opts.add_option('-b', '--root-block', metavar='BLOCK', type='int',
+                        help='trace the origin for a rootfs block')
+  trace_opts.add_option('-B', '--kern-block', metavar='BLOCK', type='int',
+                        help='trace the origin for a kernel block')
+  trace_opts.add_option('-s', '--skip', metavar='NUM', default='0', type='int',
+                        help='skip first NUM occurrences of traced block')
+  parser.add_option_group(trace_opts)
+
+  # Parse command-line arguments.
+  opts, args = parser.parse_args(argv)
+
+  # Validate a value given to --type, if any.
+  if opts.assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
+    parser.error('invalid argument to --type: %s' % opts.assert_type)
+
+  # Convert and validate --disabled_tests value list, if provided.
+  if opts.disabled_tests:
+    opts.disabled_tests = opts.disabled_tests.split(',')
+    for test in opts.disabled_tests:
+      if test not in update_payload.CHECKS_TO_DISABLE:
+        parser.error('invalid argument to --disabled_tests: %s' % test)
+
+  # Ensure consistent use of block tracing options.
+  do_block_trace = not (opts.root_block is None and opts.kern_block is None)
+  if opts.skip and not do_block_trace:
+    parser.error('--skip must be used with either --root-block or --kern-block')
+
+  # There are several options that imply --check.
+  opts.check = (opts.check or opts.report or opts.assert_type or
+                opts.block_size or opts.allow_unhashed or
+                opts.disabled_tests or opts.meta_sig or opts.key or
+                opts.root_part_size or opts.kern_part_size)
+
+  # Check number of arguments, enforce payload type accordingly.
+  if len(args) == 3:
+    if opts.assert_type == _TYPE_DELTA:
+      parser.error('%s payload requires source partition arguments' %
+                   _TYPE_DELTA)
+    opts.assert_type = _TYPE_FULL
+  elif len(args) == 5:
+    if opts.assert_type == _TYPE_FULL:
+      parser.error('%s payload does not accept source partition arguments' %
+                   _TYPE_FULL)
+    opts.assert_type = _TYPE_DELTA
+  elif len(args) == 1:
+    # Not applying payload; if block tracing not requested either, do an
+    # integrity check.
+    if not do_block_trace:
+      opts.check = True
+    if opts.extract_bsdiff:
+      parser.error('--extract-bsdiff can only be used when applying payloads')
+    if opts.bspatch_path:
+      parser.error('--bspatch-path can only be used when applying payloads')
+  else:
+    parser.error('unexpected number of arguments')
+
+  # By default, look for a metadata-signature file with a name based on the name
+  # of the payload we are checking. We only do it if check was triggered.
+  if opts.check and not opts.meta_sig:
+    default_meta_sig = args[0] + '.metadata-signature'
+    if os.path.isfile(default_meta_sig):
+      opts.meta_sig = default_meta_sig
+      print('Using default metadata signature', opts.meta_sig, file=sys.stderr)
+
+  return opts, args[0], args[1:]
+
+
+def main(argv):
+  # Parse and validate arguments.
+  options, payload_file_name, extra_args = ParseArguments(argv[1:])
+
+  with open(payload_file_name) as payload_file:
+    payload = update_payload.Payload(payload_file)
+    try:
+      # Initialize payload.
+      payload.Init()
+
+      if options.describe:
+        payload.Describe()
+
+      # Perform payload integrity checks.
+      if options.check:
+        report_file = None
+        do_close_report_file = False
+        metadata_sig_file = None
+        try:
+          if options.report:
+            if options.report == '-':
+              report_file = sys.stdout
+            else:
+              report_file = open(options.report, 'w')
+              do_close_report_file = True
+
+          metadata_sig_file = options.meta_sig and open(options.meta_sig)
+          payload.Check(
+              pubkey_file_name=options.key,
+              metadata_sig_file=metadata_sig_file,
+              report_out_file=report_file,
+              assert_type=options.assert_type,
+              block_size=int(options.block_size),
+              rootfs_part_size=options.root_part_size,
+              kernel_part_size=options.kern_part_size,
+              allow_unhashed=options.allow_unhashed,
+              disabled_tests=options.disabled_tests)
+        finally:
+          if metadata_sig_file:
+            metadata_sig_file.close()
+          if do_close_report_file:
+            report_file.close()
+
+      # Trace blocks.
+      if options.root_block is not None:
+        payload.TraceBlock(options.root_block, options.skip, sys.stdout, False)
+      if options.kern_block is not None:
+        payload.TraceBlock(options.kern_block, options.skip, sys.stdout, True)
+
+      # Apply payload.
+      if extra_args:
+        dargs = {'bsdiff_in_place': not options.extract_bsdiff}
+        if options.bspatch_path:
+          dargs['bspatch_path'] = options.bspatch_path
+        if options.assert_type == _TYPE_DELTA:
+          dargs['old_kernel_part'] = extra_args[2]
+          dargs['old_rootfs_part'] = extra_args[3]
+
+        payload.Apply(extra_args[0], extra_args[1], **dargs)
+
+    except update_payload.PayloadError, e:
+      sys.stderr.write('Error: %s\n' % e)
+      return 1
+
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv))
diff --git a/update_engine/scripts/payload_apply.sh b/update_engine/scripts/payload_apply.sh
new file mode 100755
index 0000000..9158d65
--- /dev/null
+++ b/update_engine/scripts/payload_apply.sh
@@ -0,0 +1,34 @@
+#!/system/bin/sh
+
+PAYLOAD=$1
+HEADER_SIZE=512
+HEADER_LINES=6
+SIDELOAD_APP=`which update_engine_sideload`
+
+if [ "$#" -ne 1 ]
+then
+    echo "$0 [PAYLOAD]"
+    exit 1
+fi
+
+if [ -z $SIDELOAD_APP ]
+then
+    echo "Can't find update_engine_sideload app"
+    exit 1
+fi
+
+if [ ! -f $PAYLOAD ]
+then
+    echo "$PAYLOAD does not exist"
+    exit 1
+fi
+
+HEADER=`head -n $HEADER_LINES $PAYLOAD`
+FILE_HASH=`echo $HEADER | awk -F"FILE_HASH:" '{print $2}' | awk '{print $1}'`
+FILE_SIZE=`echo $HEADER | awk -F"FILE_SIZE:" '{print $2}' | awk '{print $1}'`
+METADATA_SIZE=`echo $HEADER | awk -F"METADATA_SIZE:" '{print $2}' | awk '{print $1}'`
+
+$SIDELOAD_APP --payload=file://$PAYLOAD --offset=$HEADER_SIZE --headers="FILE_HASH=$FILE_HASH
+FILE_SIZE=$FILE_SIZE
+METADATA_SIZE=$METADATA_SIZE
+" || exit 1
diff --git a/update_engine/scripts/test_paycheck.sh b/update_engine/scripts/test_paycheck.sh
new file mode 100755
index 0000000..c395db4
--- /dev/null
+++ b/update_engine/scripts/test_paycheck.sh
@@ -0,0 +1,175 @@
+#!/bin/bash
+#
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# A test script for paycheck.py and the update_payload.py library.
+#
+# This script requires three payload files, along with a metadata signature for
+# each, and a public key for verifying signatures. Payload include:
+#
+# - A full payload for release X (old_full_payload)
+#
+# - A full payload for release Y (new_full_payload), where Y > X
+#
+# - A delta payload from X to Y (delta_payload)
+#
+# The test performs the following:
+#
+# - It verifies each payload against its metadata signature, also asserting the
+#   payload type. Another artifact is a human-readable payload report, which
+#   is output to stdout to be inspected by the user.
+#
+# - It performs a random block trace on the delta payload (both kernel and
+#   rootfs blocks), dumping the traces to stdout for the user to inspect.
+#
+# - It applies old_full_payload to yield old kernel (old_kern.part) and rootfs
+#   (old_root.part) partitions.
+#
+# - It applies delta_payload to old_{kern,root}.part to yield new kernel
+#   (new_delta_kern.part) and rootfs (new_delta_root.part) partitions.
+#
+# - It applies new_full_payload to yield reference new kernel
+#   (new_full_kern.part) and rootfs (new_full_root.part) partitions.
+#
+# - It compares new_{delta,full}_kern.part and new_{delta,full}_root.part to
+#   ensure that they are binary identical.
+#
+# If all steps have completed successfully we know with high certainty that
+# paycheck.py (and hence update_payload.py) correctly parses both full and
+# delta payloads, and applies them to yield the expected result. We also know
+# that tracing works, to the extent it does not crash. Manual inspection of
+# payload reports and block traces will improve this our confidence and are
+# strongly encouraged. Finally, each paycheck.py execution is timed.
+
+
+# Stop on errors, unset variables.
+set -e
+set -u
+
+# Temporary image files.
+OLD_KERN_PART=old_kern.part
+OLD_ROOT_PART=old_root.part
+NEW_DELTA_KERN_PART=new_delta_kern.part
+NEW_DELTA_ROOT_PART=new_delta_root.part
+NEW_FULL_KERN_PART=new_full_kern.part
+NEW_FULL_ROOT_PART=new_full_root.part
+
+
+log() {
+  echo "$@" >&2
+}
+
+die() {
+  log "$@"
+  exit 1
+}
+
+usage_and_exit() {
+  cat >&2 <<EOF
+Usage: ${0##*/} old_full_payload delta_payload new_full_payload
+EOF
+  exit
+}
+
+check_payload() {
+  payload_file=$1
+  payload_type=$2
+
+  time ${paycheck} -t ${payload_type} ${payload_file}
+}
+
+trace_kern_block() {
+  payload_file=$1
+  block=$2
+  time ${paycheck} -B ${block} ${payload_file}
+}
+
+trace_root_block() {
+  payload_file=$1
+  block=$2
+  time ${paycheck} -b ${block} ${payload_file}
+}
+
+apply_full_payload() {
+  payload_file=$1
+  dst_kern_part="$2/$3"
+  dst_root_part="$2/$4"
+
+  time ${paycheck} ${payload_file} ${dst_kern_part} ${dst_root_part}
+}
+
+apply_delta_payload() {
+  payload_file=$1
+  dst_kern_part="$2/$3"
+  dst_root_part="$2/$4"
+  src_kern_part="$2/$5"
+  src_root_part="$2/$6"
+
+  time ${paycheck} ${payload_file} ${dst_kern_part} ${dst_root_part} \
+    ${src_kern_part} ${src_root_part}
+}
+
+main() {
+  # Read command-line arguments.
+  if [ $# == 1 ] && [ "$1" == "-h" ]; then
+    usage_and_exit
+  elif [ $# != 3 ]; then
+    die "Error: unexpected number of arguments"
+  fi
+  old_full_payload="$1"
+  delta_payload="$2"
+  new_full_payload="$3"
+
+  # Find paycheck.py
+  paycheck=${0%/*}/paycheck.py
+  if [ -z "${paycheck}" ] || [ ! -x ${paycheck} ]; then
+    die "cannot find ${paycheck} or file is not executable"
+  fi
+
+  # Check the payloads statically.
+  log "Checking payloads..."
+  check_payload "${old_full_payload}" full
+  check_payload "${new_full_payload}" full
+  check_payload "${delta_payload}" delta
+  log "Done"
+
+  # Trace a random block between 0-1024 on all payloads.
+  block=$((RANDOM * 1024 / 32767))
+  log "Tracing a random block (${block}) in full/delta payloads..."
+  trace_kern_block "${new_full_payload}" ${block}
+  trace_root_block "${new_full_payload}" ${block}
+  trace_kern_block "${delta_payload}" ${block}
+  trace_root_block "${delta_payload}" ${block}
+  log "Done"
+
+  # Apply full/delta payloads and verify results are identical.
+  tmpdir="$(mktemp -d --tmpdir test_paycheck.XXXXXXXX)"
+  log "Initiating application of payloads at $tmpdir"
+
+  log "Applying old full payload..."
+  apply_full_payload "${old_full_payload}" "${tmpdir}" "${OLD_KERN_PART}" \
+    "${OLD_ROOT_PART}"
+  log "Done"
+
+  log "Applying delta payload to old partitions..."
+  apply_delta_payload "${delta_payload}" "${tmpdir}" "${NEW_DELTA_KERN_PART}" \
+    "${NEW_DELTA_ROOT_PART}" "${OLD_KERN_PART}" "${OLD_ROOT_PART}"
+  log "Done"
+
+  log "Applying new full payload..."
+  apply_full_payload "${new_full_payload}" "${tmpdir}" "${NEW_FULL_KERN_PART}" \
+    "${NEW_FULL_ROOT_PART}"
+  log "Done"
+
+  log "Comparing results of delta and new full updates..."
+  diff "${tmpdir}/${NEW_FULL_KERN_PART}" "${tmpdir}/${NEW_DELTA_KERN_PART}"
+  diff "${tmpdir}/${NEW_FULL_ROOT_PART}" "${tmpdir}/${NEW_DELTA_ROOT_PART}"
+  log "Done"
+
+  log "Cleaning up"
+  rm -fr "${tmpdir}"
+}
+
+main "$@"
diff --git a/update_engine/scripts/update_payload/__init__.py b/update_engine/scripts/update_payload/__init__.py
new file mode 100644
index 0000000..1906a16
--- /dev/null
+++ b/update_engine/scripts/update_payload/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Library for processing, verifying and applying Chrome OS update payloads."""
+
+# Just raise the interface classes to the root namespace.
+# pylint: disable=W0401
+from checker import CHECKS_TO_DISABLE
+from error import PayloadError
+from payload import Payload
diff --git a/update_engine/scripts/update_payload/applier.py b/update_engine/scripts/update_payload/applier.py
new file mode 100644
index 0000000..e3708c7
--- /dev/null
+++ b/update_engine/scripts/update_payload/applier.py
@@ -0,0 +1,582 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Applying a Chrome OS update payload.
+
+This module is used internally by the main Payload class for applying an update
+payload. The interface for invoking the applier is as follows:
+
+  applier = PayloadApplier(payload)
+  applier.Run(...)
+
+"""
+
+from __future__ import print_function
+
+import array
+import bz2
+import hashlib
+import itertools
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+import common
+from error import PayloadError
+
+
+#
+# Helper functions.
+#
+def _VerifySha256(file_obj, expected_hash, name, length=-1):
+  """Verifies the SHA256 hash of a file.
+
+  Args:
+    file_obj: file object to read
+    expected_hash: the hash digest we expect to be getting
+    name: name string of this hash, for error reporting
+    length: precise length of data to verify (optional)
+
+  Raises:
+    PayloadError if computed hash doesn't match expected one, or if fails to
+    read the specified length of data.
+  """
+  # pylint: disable=E1101
+  hasher = hashlib.sha256()
+  block_length = 1024 * 1024
+  max_length = length if length >= 0 else sys.maxint
+
+  while max_length > 0:
+    read_length = min(max_length, block_length)
+    data = file_obj.read(read_length)
+    if not data:
+      break
+    max_length -= len(data)
+    hasher.update(data)
+
+  if length >= 0 and max_length > 0:
+    raise PayloadError(
+        'insufficient data (%d instead of %d) when verifying %s' %
+        (length - max_length, length, name))
+
+  actual_hash = hasher.digest()
+  if actual_hash != expected_hash:
+    raise PayloadError('%s hash (%s) not as expected (%s)' %
+                       (name, common.FormatSha256(actual_hash),
+                        common.FormatSha256(expected_hash)))
+
+
+def _ReadExtents(file_obj, extents, block_size, max_length=-1):
+  """Reads data from file as defined by extent sequence.
+
+  This tries to be efficient by not copying data as it is read in chunks.
+
+  Args:
+    file_obj: file object
+    extents: sequence of block extents (offset and length)
+    block_size: size of each block
+    max_length: maximum length to read (optional)
+
+  Returns:
+    A character array containing the concatenated read data.
+  """
+  data = array.array('c')
+  if max_length < 0:
+    max_length = sys.maxint
+  for ex in extents:
+    if max_length == 0:
+      break
+    read_length = min(max_length, ex.num_blocks * block_size)
+
+    # Fill with zeros or read from file, depending on the type of extent.
+    if ex.start_block == common.PSEUDO_EXTENT_MARKER:
+      data.extend(itertools.repeat('\0', read_length))
+    else:
+      file_obj.seek(ex.start_block * block_size)
+      data.fromfile(file_obj, read_length)
+
+    max_length -= read_length
+
+  return data
+
+
+def _WriteExtents(file_obj, data, extents, block_size, base_name):
+  """Writes data to file as defined by extent sequence.
+
+  This tries to be efficient by not copy data as it is written in chunks.
+
+  Args:
+    file_obj: file object
+    data: data to write
+    extents: sequence of block extents (offset and length)
+    block_size: size of each block
+    base_name: name string of extent sequence for error reporting
+
+  Raises:
+    PayloadError when things don't add up.
+  """
+  data_offset = 0
+  data_length = len(data)
+  for ex, ex_name in common.ExtentIter(extents, base_name):
+    if not data_length:
+      raise PayloadError('%s: more write extents than data' % ex_name)
+    write_length = min(data_length, ex.num_blocks * block_size)
+
+    # Only do actual writing if this is not a pseudo-extent.
+    if ex.start_block != common.PSEUDO_EXTENT_MARKER:
+      file_obj.seek(ex.start_block * block_size)
+      data_view = buffer(data, data_offset, write_length)
+      file_obj.write(data_view)
+
+    data_offset += write_length
+    data_length -= write_length
+
+  if data_length:
+    raise PayloadError('%s: more data than write extents' % base_name)
+
+
+def _ExtentsToBspatchArg(extents, block_size, base_name, data_length=-1):
+  """Translates an extent sequence into a bspatch-compatible string argument.
+
+  Args:
+    extents: sequence of block extents (offset and length)
+    block_size: size of each block
+    base_name: name string of extent sequence for error reporting
+    data_length: the actual total length of the data in bytes (optional)
+
+  Returns:
+    A tuple consisting of (i) a string of the form
+    "off_1:len_1,...,off_n:len_n", (ii) an offset where zero padding is needed
+    for filling the last extent, (iii) the length of the padding (zero means no
+    padding is needed and the extents cover the full length of data).
+
+  Raises:
+    PayloadError if data_length is too short or too long.
+  """
+  arg = ''
+  pad_off = pad_len = 0
+  if data_length < 0:
+    data_length = sys.maxint
+  for ex, ex_name in common.ExtentIter(extents, base_name):
+    if not data_length:
+      raise PayloadError('%s: more extents than total data length' % ex_name)
+
+    is_pseudo = ex.start_block == common.PSEUDO_EXTENT_MARKER
+    start_byte = -1 if is_pseudo else ex.start_block * block_size
+    num_bytes = ex.num_blocks * block_size
+    if data_length < num_bytes:
+      # We're only padding a real extent.
+      if not is_pseudo:
+        pad_off = start_byte + data_length
+        pad_len = num_bytes - data_length
+
+      num_bytes = data_length
+
+    arg += '%s%d:%d' % (arg and ',', start_byte, num_bytes)
+    data_length -= num_bytes
+
+  if data_length:
+    raise PayloadError('%s: extents not covering full data length' % base_name)
+
+  return arg, pad_off, pad_len
+
+
+#
+# Payload application.
+#
+class PayloadApplier(object):
+  """Applying an update payload.
+
+  This is a short-lived object whose purpose is to isolate the logic used for
+  applying an update payload.
+  """
+
+  def __init__(self, payload, bsdiff_in_place=True, bspatch_path=None,
+               imgpatch_path=None, truncate_to_expected_size=True):
+    """Initialize the applier.
+
+    Args:
+      payload: the payload object to check
+      bsdiff_in_place: whether to perform BSDIFF operation in-place (optional)
+      bspatch_path: path to the bspatch binary (optional)
+      imgpatch_path: path to the imgpatch binary (optional)
+      truncate_to_expected_size: whether to truncate the resulting partitions
+                                 to their expected sizes, as specified in the
+                                 payload (optional)
+    """
+    assert payload.is_init, 'uninitialized update payload'
+    self.payload = payload
+    self.block_size = payload.manifest.block_size
+    self.minor_version = payload.manifest.minor_version
+    self.bsdiff_in_place = bsdiff_in_place
+    self.bspatch_path = bspatch_path or 'bspatch'
+    self.imgpatch_path = imgpatch_path or 'imgpatch'
+    self.truncate_to_expected_size = truncate_to_expected_size
+
+  def _ApplyReplaceOperation(self, op, op_name, out_data, part_file, part_size):
+    """Applies a REPLACE{,_BZ} operation.
+
+    Args:
+      op: the operation object
+      op_name: name string for error reporting
+      out_data: the data to be written
+      part_file: the partition file object
+      part_size: the size of the partition
+
+    Raises:
+      PayloadError if something goes wrong.
+    """
+    block_size = self.block_size
+    data_length = len(out_data)
+
+    # Decompress data if needed.
+    if op.type == common.OpType.REPLACE_BZ:
+      out_data = bz2.decompress(out_data)
+      data_length = len(out_data)
+
+    # Write data to blocks specified in dst extents.
+    data_start = 0
+    for ex, ex_name in common.ExtentIter(op.dst_extents,
+                                         '%s.dst_extents' % op_name):
+      start_block = ex.start_block
+      num_blocks = ex.num_blocks
+      count = num_blocks * block_size
+
+      # Make sure it's not a fake (signature) operation.
+      if start_block != common.PSEUDO_EXTENT_MARKER:
+        data_end = data_start + count
+
+        # Make sure we're not running past partition boundary.
+        if (start_block + num_blocks) * block_size > part_size:
+          raise PayloadError(
+              '%s: extent (%s) exceeds partition size (%d)' %
+              (ex_name, common.FormatExtent(ex, block_size),
+               part_size))
+
+        # Make sure that we have enough data to write.
+        if data_end >= data_length + block_size:
+          raise PayloadError(
+              '%s: more dst blocks than data (even with padding)')
+
+        # Pad with zeros if necessary.
+        if data_end > data_length:
+          padding = data_end - data_length
+          out_data += '\0' * padding
+
+        self.payload.payload_file.seek(start_block * block_size)
+        part_file.seek(start_block * block_size)
+        part_file.write(out_data[data_start:data_end])
+
+      data_start += count
+
+    # Make sure we wrote all data.
+    if data_start < data_length:
+      raise PayloadError('%s: wrote fewer bytes (%d) than expected (%d)' %
+                         (op_name, data_start, data_length))
+
+  def _ApplyMoveOperation(self, op, op_name, part_file):
+    """Applies a MOVE operation.
+
+    Note that this operation must read the whole block data from the input and
+    only then dump it, due to our in-place update semantics; otherwise, it
+    might clobber data midway through.
+
+    Args:
+      op: the operation object
+      op_name: name string for error reporting
+      part_file: the partition file object
+
+    Raises:
+      PayloadError if something goes wrong.
+    """
+    block_size = self.block_size
+
+    # Gather input raw data from src extents.
+    in_data = _ReadExtents(part_file, op.src_extents, block_size)
+
+    # Dump extracted data to dst extents.
+    _WriteExtents(part_file, in_data, op.dst_extents, block_size,
+                  '%s.dst_extents' % op_name)
+
+  def _ApplyBsdiffOperation(self, op, op_name, patch_data, new_part_file):
+    """Applies a BSDIFF operation.
+
+    Args:
+      op: the operation object
+      op_name: name string for error reporting
+      patch_data: the binary patch content
+      new_part_file: the target partition file object
+
+    Raises:
+      PayloadError if something goes wrong.
+    """
+    # Implemented using a SOURCE_BSDIFF operation with the source and target
+    # partition set to the new partition.
+    self._ApplyDiffOperation(op, op_name, patch_data, new_part_file,
+                             new_part_file)
+
+  def _ApplySourceCopyOperation(self, op, op_name, old_part_file,
+                                new_part_file):
+    """Applies a SOURCE_COPY operation.
+
+    Args:
+      op: the operation object
+      op_name: name string for error reporting
+      old_part_file: the old partition file object
+      new_part_file: the new partition file object
+
+    Raises:
+      PayloadError if something goes wrong.
+    """
+    if not old_part_file:
+      raise PayloadError(
+          '%s: no source partition file provided for operation type (%d)' %
+          (op_name, op.type))
+
+    block_size = self.block_size
+
+    # Gather input raw data from src extents.
+    in_data = _ReadExtents(old_part_file, op.src_extents, block_size)
+
+    # Dump extracted data to dst extents.
+    _WriteExtents(new_part_file, in_data, op.dst_extents, block_size,
+                  '%s.dst_extents' % op_name)
+
+  def _ApplyDiffOperation(self, op, op_name, patch_data, old_part_file,
+                          new_part_file):
+    """Applies a SOURCE_BSDIFF or IMGDIFF operation.
+
+    Args:
+      op: the operation object
+      op_name: name string for error reporting
+      patch_data: the binary patch content
+      old_part_file: the source partition file object
+      new_part_file: the target partition file object
+
+    Raises:
+      PayloadError if something goes wrong.
+    """
+    if not old_part_file:
+      raise PayloadError(
+          '%s: no source partition file provided for operation type (%d)' %
+          (op_name, op.type))
+
+    block_size = self.block_size
+
+    # Dump patch data to file.
+    with tempfile.NamedTemporaryFile(delete=False) as patch_file:
+      patch_file_name = patch_file.name
+      patch_file.write(patch_data)
+
+    if (hasattr(new_part_file, 'fileno') and
+        ((not old_part_file) or hasattr(old_part_file, 'fileno')) and
+        op.type != common.OpType.IMGDIFF):
+      # Construct input and output extents argument for bspatch.
+      in_extents_arg, _, _ = _ExtentsToBspatchArg(
+          op.src_extents, block_size, '%s.src_extents' % op_name,
+          data_length=op.src_length)
+      out_extents_arg, pad_off, pad_len = _ExtentsToBspatchArg(
+          op.dst_extents, block_size, '%s.dst_extents' % op_name,
+          data_length=op.dst_length)
+
+      new_file_name = '/dev/fd/%d' % new_part_file.fileno()
+      # Diff from source partition.
+      old_file_name = '/dev/fd/%d' % old_part_file.fileno()
+
+      # Invoke bspatch on partition file with extents args.
+      bspatch_cmd = [self.bspatch_path, old_file_name, new_file_name,
+                     patch_file_name, in_extents_arg, out_extents_arg]
+      subprocess.check_call(bspatch_cmd)
+
+      # Pad with zeros past the total output length.
+      if pad_len:
+        new_part_file.seek(pad_off)
+        new_part_file.write('\0' * pad_len)
+    else:
+      # Gather input raw data and write to a temp file.
+      input_part_file = old_part_file if old_part_file else new_part_file
+      in_data = _ReadExtents(input_part_file, op.src_extents, block_size,
+                             max_length=op.src_length)
+      with tempfile.NamedTemporaryFile(delete=False) as in_file:
+        in_file_name = in_file.name
+        in_file.write(in_data)
+
+      # Allocate temporary output file.
+      with tempfile.NamedTemporaryFile(delete=False) as out_file:
+        out_file_name = out_file.name
+
+      # Invoke bspatch.
+      patch_cmd = [self.bspatch_path, in_file_name, out_file_name,
+                   patch_file_name]
+      if op.type == common.OpType.IMGDIFF:
+        patch_cmd[0] = self.imgpatch_path
+      subprocess.check_call(patch_cmd)
+
+      # Read output.
+      with open(out_file_name, 'rb') as out_file:
+        out_data = out_file.read()
+        if len(out_data) != op.dst_length:
+          raise PayloadError(
+              '%s: actual patched data length (%d) not as expected (%d)' %
+              (op_name, len(out_data), op.dst_length))
+
+      # Write output back to partition, with padding.
+      unaligned_out_len = len(out_data) % block_size
+      if unaligned_out_len:
+        out_data += '\0' * (block_size - unaligned_out_len)
+      _WriteExtents(new_part_file, out_data, op.dst_extents, block_size,
+                    '%s.dst_extents' % op_name)
+
+      # Delete input/output files.
+      os.remove(in_file_name)
+      os.remove(out_file_name)
+
+    # Delete patch file.
+    os.remove(patch_file_name)
+
+  def _ApplyOperations(self, operations, base_name, old_part_file,
+                       new_part_file, part_size):
+    """Applies a sequence of update operations to a partition.
+
+    This assumes an in-place update semantics for MOVE and BSDIFF, namely all
+    reads are performed first, then the data is processed and written back to
+    the same file.
+
+    Args:
+      operations: the sequence of operations
+      base_name: the name of the operation sequence
+      old_part_file: the old partition file object, open for reading/writing
+      new_part_file: the new partition file object, open for reading/writing
+      part_size: the partition size
+
+    Raises:
+      PayloadError if anything goes wrong while processing the payload.
+    """
+    for op, op_name in common.OperationIter(operations, base_name):
+      # Read data blob.
+      data = self.payload.ReadDataBlob(op.data_offset, op.data_length)
+
+      if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
+        self._ApplyReplaceOperation(op, op_name, data, new_part_file, part_size)
+      elif op.type == common.OpType.MOVE:
+        self._ApplyMoveOperation(op, op_name, new_part_file)
+      elif op.type == common.OpType.BSDIFF:
+        self._ApplyBsdiffOperation(op, op_name, data, new_part_file)
+      elif op.type == common.OpType.SOURCE_COPY:
+        self._ApplySourceCopyOperation(op, op_name, old_part_file,
+                                       new_part_file)
+      elif op.type in (common.OpType.SOURCE_BSDIFF, common.OpType.IMGDIFF):
+        self._ApplyDiffOperation(op, op_name, data, old_part_file,
+                                 new_part_file)
+      else:
+        raise PayloadError('%s: unknown operation type (%d)' %
+                           (op_name, op.type))
+
+  def _ApplyToPartition(self, operations, part_name, base_name,
+                        new_part_file_name, new_part_info,
+                        old_part_file_name=None, old_part_info=None):
+    """Applies an update to a partition.
+
+    Args:
+      operations: the sequence of update operations to apply
+      part_name: the name of the partition, for error reporting
+      base_name: the name of the operation sequence
+      new_part_file_name: file name to write partition data to
+      new_part_info: size and expected hash of dest partition
+      old_part_file_name: file name of source partition (optional)
+      old_part_info: size and expected hash of source partition (optional)
+
+    Raises:
+      PayloadError if anything goes wrong with the update.
+    """
+    # Do we have a source partition?
+    if old_part_file_name:
+      # Verify the source partition.
+      with open(old_part_file_name, 'rb') as old_part_file:
+        _VerifySha256(old_part_file, old_part_info.hash,
+                      'old ' + part_name, length=old_part_info.size)
+      new_part_file_mode = 'r+b'
+      if self.minor_version == common.INPLACE_MINOR_PAYLOAD_VERSION:
+        # Copy the src partition to the dst one; make sure we don't truncate it.
+        shutil.copyfile(old_part_file_name, new_part_file_name)
+      elif (self.minor_version == common.SOURCE_MINOR_PAYLOAD_VERSION or
+            self.minor_version == common.OPSRCHASH_MINOR_PAYLOAD_VERSION or
+            self.minor_version == common.IMGDIFF_MINOR_PAYLOAD_VERSION):
+        # In minor version >= 2, we don't want to copy the partitions, so
+        # instead just make the new partition file.
+        open(new_part_file_name, 'w').close()
+      else:
+        raise PayloadError("Unknown minor version: %d" % self.minor_version)
+    else:
+      # We need to create/truncate the dst partition file.
+      new_part_file_mode = 'w+b'
+
+    # Apply operations.
+    with open(new_part_file_name, new_part_file_mode) as new_part_file:
+      old_part_file = (open(old_part_file_name, 'r+b')
+                       if old_part_file_name else None)
+      try:
+        self._ApplyOperations(operations, base_name, old_part_file,
+                              new_part_file, new_part_info.size)
+      finally:
+        if old_part_file:
+          old_part_file.close()
+
+      # Truncate the result, if so instructed.
+      if self.truncate_to_expected_size:
+        new_part_file.seek(0, 2)
+        if new_part_file.tell() > new_part_info.size:
+          new_part_file.seek(new_part_info.size)
+          new_part_file.truncate()
+
+    # Verify the resulting partition.
+    with open(new_part_file_name, 'rb') as new_part_file:
+      _VerifySha256(new_part_file, new_part_info.hash,
+                    'new ' + part_name, length=new_part_info.size)
+
+  def Run(self, new_kernel_part, new_rootfs_part, old_kernel_part=None,
+          old_rootfs_part=None):
+    """Applier entry point, invoking all update operations.
+
+    Args:
+      new_kernel_part: name of dest kernel partition file
+      new_rootfs_part: name of dest rootfs partition file
+      old_kernel_part: name of source kernel partition file (optional)
+      old_rootfs_part: name of source rootfs partition file (optional)
+
+    Raises:
+      PayloadError if payload application failed.
+    """
+    self.payload.ResetFile()
+
+    # Make sure the arguments are sane and match the payload.
+    if not (new_kernel_part and new_rootfs_part):
+      raise PayloadError('missing dst {kernel,rootfs} partitions')
+
+    if not (old_kernel_part or old_rootfs_part):
+      if not self.payload.IsFull():
+        raise PayloadError('trying to apply a non-full update without src '
+                           '{kernel,rootfs} partitions')
+    elif old_kernel_part and old_rootfs_part:
+      if not self.payload.IsDelta():
+        raise PayloadError('trying to apply a non-delta update onto src '
+                           '{kernel,rootfs} partitions')
+    else:
+      raise PayloadError('not all src partitions provided')
+
+    # Apply update to rootfs.
+    self._ApplyToPartition(
+        self.payload.manifest.install_operations, 'rootfs',
+        'install_operations', new_rootfs_part,
+        self.payload.manifest.new_rootfs_info, old_rootfs_part,
+        self.payload.manifest.old_rootfs_info)
+
+    # Apply update to kernel update.
+    self._ApplyToPartition(
+        self.payload.manifest.kernel_install_operations, 'kernel',
+        'kernel_install_operations', new_kernel_part,
+        self.payload.manifest.new_kernel_info, old_kernel_part,
+        self.payload.manifest.old_kernel_info)
diff --git a/update_engine/scripts/update_payload/block_tracer.py b/update_engine/scripts/update_payload/block_tracer.py
new file mode 100644
index 0000000..f222b21
--- /dev/null
+++ b/update_engine/scripts/update_payload/block_tracer.py
@@ -0,0 +1,113 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Tracing block data source through a Chrome OS update payload.
+
+This module is used internally by the main Payload class for tracing block
+content through an update payload. This is a useful feature in debugging
+payload applying functionality in this package. The interface for invoking the
+tracer is as follows:
+
+  tracer = PayloadBlockTracer(payload)
+  tracer.Run(...)
+
+"""
+
+from __future__ import print_function
+
+import common
+
+
+#
+# Payload block tracing.
+#
+class PayloadBlockTracer(object):
+  """Tracing the origin of block data through update instructions.
+
+  This is a short-lived object whose purpose is to isolate the logic used for
+  tracing the origin of destination partition blocks.
+
+  """
+
+  def __init__(self, payload):
+    assert payload.is_init, 'uninitialized update payload'
+    self.payload = payload
+
+  @staticmethod
+  def _TraceBlock(block, skip, trace_out_file, operations, base_name):
+    """Trace the origin of a given block through a sequence of operations.
+
+    This method tries to map the given dest block to the corresponding source
+    block from which its content originates in the course of an update. It
+    further tries to trace transitive origins through MOVE operations. It is
+    rather efficient, doing the actual tracing by means of a single reverse
+    sweep through the operation sequence. It dumps a log of operations and
+    source blocks responsible for the data in the given dest block to the
+    provided output file.
+
+    Args:
+      block: the block number to trace
+      skip: number of initial transitive origins to ignore
+      trace_out_file: a file object to dump the trace to
+      operations: the sequence of operations
+      base_name: name of the operation sequence
+    """
+    # Traverse operations backwards.
+    for op, op_name in common.OperationIter(operations, base_name,
+                                            reverse=True):
+      total_block_offset = 0
+      found = False
+
+      # Is the traced block mentioned in the dest extents?
+      for dst_ex, dst_ex_name in common.ExtentIter(op.dst_extents,
+                                                   op_name + '.dst_extents'):
+        if (block >= dst_ex.start_block
+            and block < dst_ex.start_block + dst_ex.num_blocks):
+          if skip:
+            skip -= 1
+          else:
+            total_block_offset += block - dst_ex.start_block
+            trace_out_file.write(
+                '%d: %s: found %s (total block offset: %d)\n' %
+                (block, dst_ex_name, common.FormatExtent(dst_ex),
+                 total_block_offset))
+            found = True
+            break
+
+        total_block_offset += dst_ex.num_blocks
+
+      if found:
+        # Don't trace further, unless it's a MOVE.
+        if op.type != common.OpType.MOVE:
+          break
+
+        # For MOVE, find corresponding source block and keep tracing.
+        for src_ex, src_ex_name in common.ExtentIter(op.src_extents,
+                                                     op_name + '.src_extents'):
+          if total_block_offset < src_ex.num_blocks:
+            block = src_ex.start_block + total_block_offset
+            trace_out_file.write(
+                '%s:  mapped to %s (%d)\n' %
+                (src_ex_name, common.FormatExtent(src_ex), block))
+            break
+
+          total_block_offset -= src_ex.num_blocks
+
+  def Run(self, block, skip, trace_out_file, is_kernel):
+    """Block tracer entry point, invoking the actual search.
+
+    Args:
+      block: the block number whose origin to trace
+      skip: the number of first origin mappings to skip
+      trace_out_file: file object to dump the trace to
+      is_kernel: trace through kernel (True) or rootfs (False) operations
+    """
+    if is_kernel:
+      operations = self.payload.manifest.kernel_install_operations
+      base_name = 'kernel_install_operations'
+    else:
+      operations = self.payload.manifest.install_operations
+      base_name = 'install_operations'
+
+    self._TraceBlock(block, skip, trace_out_file, operations, base_name)
diff --git a/update_engine/scripts/update_payload/checker.py b/update_engine/scripts/update_payload/checker.py
new file mode 100644
index 0000000..e13ea13
--- /dev/null
+++ b/update_engine/scripts/update_payload/checker.py
@@ -0,0 +1,1275 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Verifying the integrity of a Chrome OS update payload.
+
+This module is used internally by the main Payload class for verifying the
+integrity of an update payload. The interface for invoking the checks is as
+follows:
+
+  checker = PayloadChecker(payload)
+  checker.Run(...)
+"""
+
+from __future__ import print_function
+
+import array
+import base64
+import hashlib
+import itertools
+import os
+import subprocess
+
+import common
+import error
+import format_utils
+import histogram
+import update_metadata_pb2
+
+
+#
+# Constants.
+#
+
+_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents'
+_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block'
+_CHECK_PAYLOAD_SIG = 'payload-sig'
+CHECKS_TO_DISABLE = (
+    _CHECK_DST_PSEUDO_EXTENTS,
+    _CHECK_MOVE_SAME_SRC_DST_BLOCK,
+    _CHECK_PAYLOAD_SIG,
+)
+
+_TYPE_FULL = 'full'
+_TYPE_DELTA = 'delta'
+
+_DEFAULT_BLOCK_SIZE = 4096
+
+_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem'
+_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
+                                         _DEFAULT_PUBKEY_BASE_NAME)
+
+# Supported minor version map to payload types allowed to be using them.
+_SUPPORTED_MINOR_VERSIONS = {
+    0: (_TYPE_FULL,),
+    1: (_TYPE_DELTA,),
+    2: (_TYPE_DELTA,),
+    3: (_TYPE_DELTA,),
+    4: (_TYPE_DELTA,),
+}
+
+_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
+
+#
+# Helper functions.
+#
+
+def _IsPowerOfTwo(val):
+  """Returns True iff val is a power of two."""
+  return val > 0 and (val & (val - 1)) == 0
+
+
+def _AddFormat(format_func, value):
+  """Adds a custom formatted representation to ordinary string representation.
+
+  Args:
+    format_func: A value formatter.
+    value: Value to be formatted and returned.
+
+  Returns:
+    A string 'x (y)' where x = str(value) and y = format_func(value).
+  """
+  ret = str(value)
+  formatted_str = format_func(value)
+  if formatted_str:
+    ret += ' (%s)' % formatted_str
+  return ret
+
+
+def _AddHumanReadableSize(size):
+  """Adds a human readable representation to a byte size value."""
+  return _AddFormat(format_utils.BytesToHumanReadable, size)
+
+
+#
+# Payload report generator.
+#
+
+class _PayloadReport(object):
+  """A payload report generator.
+
+  A report is essentially a sequence of nodes, which represent data points. It
+  is initialized to have a "global", untitled section. A node may be a
+  sub-report itself.
+  """
+
+  # Report nodes: Field, sub-report, section.
+  class Node(object):
+    """A report node interface."""
+
+    @staticmethod
+    def _Indent(indent, line):
+      """Indents a line by a given indentation amount.
+
+      Args:
+        indent: The indentation amount.
+        line: The line content (string).
+
+      Returns:
+        The properly indented line (string).
+      """
+      return '%*s%s' % (indent, '', line)
+
+    def GenerateLines(self, base_indent, sub_indent, curr_section):
+      """Generates the report lines for this node.
+
+      Args:
+        base_indent: Base indentation for each line.
+        sub_indent: Additional indentation for sub-nodes.
+        curr_section: The current report section object.
+
+      Returns:
+        A pair consisting of a list of properly indented report lines and a new
+        current section object.
+      """
+      raise NotImplementedError
+
+  class FieldNode(Node):
+    """A field report node, representing a (name, value) pair."""
+
+    def __init__(self, name, value, linebreak, indent):
+      super(_PayloadReport.FieldNode, self).__init__()
+      self.name = name
+      self.value = value
+      self.linebreak = linebreak
+      self.indent = indent
+
+    def GenerateLines(self, base_indent, sub_indent, curr_section):
+      """Generates a properly formatted 'name : value' entry."""
+      report_output = ''
+      if self.name:
+        report_output += self.name.ljust(curr_section.max_field_name_len) + ' :'
+      value_lines = str(self.value).splitlines()
+      if self.linebreak and self.name:
+        report_output += '\n' + '\n'.join(
+            ['%*s%s' % (self.indent, '', line) for line in value_lines])
+      else:
+        if self.name:
+          report_output += ' '
+        report_output += '%*s' % (self.indent, '')
+        cont_line_indent = len(report_output)
+        indented_value_lines = [value_lines[0]]
+        indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line)
+                                     for line in value_lines[1:]])
+        report_output += '\n'.join(indented_value_lines)
+
+      report_lines = [self._Indent(base_indent, line + '\n')
+                      for line in report_output.split('\n')]
+      return report_lines, curr_section
+
+  class SubReportNode(Node):
+    """A sub-report node, representing a nested report."""
+
+    def __init__(self, title, report):
+      super(_PayloadReport.SubReportNode, self).__init__()
+      self.title = title
+      self.report = report
+
+    def GenerateLines(self, base_indent, sub_indent, curr_section):
+      """Recurse with indentation."""
+      report_lines = [self._Indent(base_indent, self.title + ' =>\n')]
+      report_lines.extend(self.report.GenerateLines(base_indent + sub_indent,
+                                                    sub_indent))
+      return report_lines, curr_section
+
+  class SectionNode(Node):
+    """A section header node."""
+
+    def __init__(self, title=None):
+      super(_PayloadReport.SectionNode, self).__init__()
+      self.title = title
+      self.max_field_name_len = 0
+
+    def GenerateLines(self, base_indent, sub_indent, curr_section):
+      """Dump a title line, return self as the (new) current section."""
+      report_lines = []
+      if self.title:
+        report_lines.append(self._Indent(base_indent,
+                                         '=== %s ===\n' % self.title))
+      return report_lines, self
+
+  def __init__(self):
+    self.report = []
+    self.last_section = self.global_section = self.SectionNode()
+    self.is_finalized = False
+
+  def GenerateLines(self, base_indent, sub_indent):
+    """Generates the lines in the report, properly indented.
+
+    Args:
+      base_indent: The indentation used for root-level report lines.
+      sub_indent: The indentation offset used for sub-reports.
+
+    Returns:
+      A list of indented report lines.
+    """
+    report_lines = []
+    curr_section = self.global_section
+    for node in self.report:
+      node_report_lines, curr_section = node.GenerateLines(
+          base_indent, sub_indent, curr_section)
+      report_lines.extend(node_report_lines)
+
+    return report_lines
+
+  def Dump(self, out_file, base_indent=0, sub_indent=2):
+    """Dumps the report to a file.
+
+    Args:
+      out_file: File object to output the content to.
+      base_indent: Base indentation for report lines.
+      sub_indent: Added indentation for sub-reports.
+    """
+    report_lines = self.GenerateLines(base_indent, sub_indent)
+    if report_lines and not self.is_finalized:
+      report_lines.append('(incomplete report)\n')
+
+    for line in report_lines:
+      out_file.write(line)
+
+  def AddField(self, name, value, linebreak=False, indent=0):
+    """Adds a field/value pair to the payload report.
+
+    Args:
+      name: The field's name.
+      value: The field's value.
+      linebreak: Whether the value should be printed on a new line.
+      indent: Amount of extra indent for each line of the value.
+    """
+    assert not self.is_finalized
+    if name and self.last_section.max_field_name_len < len(name):
+      self.last_section.max_field_name_len = len(name)
+    self.report.append(self.FieldNode(name, value, linebreak, indent))
+
+  def AddSubReport(self, title):
+    """Adds and returns a sub-report with a title."""
+    assert not self.is_finalized
+    sub_report = self.SubReportNode(title, type(self)())
+    self.report.append(sub_report)
+    return sub_report.report
+
+  def AddSection(self, title):
+    """Adds a new section title."""
+    assert not self.is_finalized
+    self.last_section = self.SectionNode(title)
+    self.report.append(self.last_section)
+
+  def Finalize(self):
+    """Seals the report, marking it as complete."""
+    self.is_finalized = True
+
+
+#
+# Payload verification.
+#
+
+class PayloadChecker(object):
+  """Checking the integrity of an update payload.
+
+  This is a short-lived object whose purpose is to isolate the logic used for
+  verifying the integrity of an update payload.
+  """
+
+  def __init__(self, payload, assert_type=None, block_size=0,
+               allow_unhashed=False, disabled_tests=()):
+    """Initialize the checker.
+
+    Args:
+      payload: The payload object to check.
+      assert_type: Assert that payload is either 'full' or 'delta' (optional).
+      block_size: Expected filesystem / payload block size (optional).
+      allow_unhashed: Allow operations with unhashed data blobs.
+      disabled_tests: Sequence of tests to disable.
+    """
+    if not payload.is_init:
+      raise ValueError('Uninitialized update payload.')
+
+    # Set checker configuration.
+    self.payload = payload
+    self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE
+    if not _IsPowerOfTwo(self.block_size):
+      raise error.PayloadError(
+          'Expected block (%d) size is not a power of two.' % self.block_size)
+    if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA):
+      raise error.PayloadError('Invalid assert_type value (%r).' %
+                               assert_type)
+    self.payload_type = assert_type
+    self.allow_unhashed = allow_unhashed
+
+    # Disable specific tests.
+    self.check_dst_pseudo_extents = (
+        _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests)
+    self.check_move_same_src_dst_block = (
+        _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests)
+    self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests
+
+    # Reset state; these will be assigned when the manifest is checked.
+    self.sigs_offset = 0
+    self.sigs_size = 0
+    self.old_rootfs_fs_size = 0
+    self.old_kernel_fs_size = 0
+    self.new_rootfs_fs_size = 0
+    self.new_kernel_fs_size = 0
+    self.minor_version = None
+
+  @staticmethod
+  def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
+                 msg_name=None, linebreak=False, indent=0):
+    """Adds an element from a protobuf message to the payload report.
+
+    Checks to see whether a message contains a given element, and if so adds
+    the element value to the provided report. A missing mandatory element
+    causes an exception to be raised.
+
+    Args:
+      msg: The message containing the element.
+      name: The name of the element.
+      report: A report object to add the element name/value to.
+      is_mandatory: Whether or not this element must be present.
+      is_submsg: Whether this element is itself a message.
+      convert: A function for converting the element value for reporting.
+      msg_name: The name of the message object (for error reporting).
+      linebreak: Whether the value report should induce a line break.
+      indent: Amount of indent used for reporting the value.
+
+    Returns:
+      A pair consisting of the element value and the generated sub-report for
+      it (if the element is a sub-message, None otherwise). If the element is
+      missing, returns (None, None).
+
+    Raises:
+      error.PayloadError if a mandatory element is missing.
+    """
+    if not msg.HasField(name):
+      if is_mandatory:
+        raise error.PayloadError('%smissing mandatory %s %r.' %
+                                 (msg_name + ' ' if msg_name else '',
+                                  'sub-message' if is_submsg else 'field',
+                                  name))
+      return None, None
+
+    value = getattr(msg, name)
+    if is_submsg:
+      return value, report and report.AddSubReport(name)
+    else:
+      if report:
+        report.AddField(name, convert(value), linebreak=linebreak,
+                        indent=indent)
+      return value, None
+
+  @staticmethod
+  def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
+                           linebreak=False, indent=0):
+    """Adds a mandatory field; returning first component from _CheckElem."""
+    return PayloadChecker._CheckElem(msg, field_name, report, True, False,
+                                     convert=convert, msg_name=msg_name,
+                                     linebreak=linebreak, indent=indent)[0]
+
+  @staticmethod
+  def _CheckOptionalField(msg, field_name, report, convert=str,
+                          linebreak=False, indent=0):
+    """Adds an optional field; returning first component from _CheckElem."""
+    return PayloadChecker._CheckElem(msg, field_name, report, False, False,
+                                     convert=convert, linebreak=linebreak,
+                                     indent=indent)[0]
+
+  @staticmethod
+  def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name):
+    """Adds a mandatory sub-message; wrapper for _CheckElem."""
+    return PayloadChecker._CheckElem(msg, submsg_name, report, True, True,
+                                     msg_name)
+
+  @staticmethod
+  def _CheckOptionalSubMsg(msg, submsg_name, report):
+    """Adds an optional sub-message; wrapper for _CheckElem."""
+    return PayloadChecker._CheckElem(msg, submsg_name, report, False, True)
+
+  @staticmethod
+  def _CheckPresentIff(val1, val2, name1, name2, obj_name):
+    """Checks that val1 is None iff val2 is None.
+
+    Args:
+      val1: first value to be compared.
+      val2: second value to be compared.
+      name1: name of object holding the first value.
+      name2: name of object holding the second value.
+      obj_name: Name of the object containing these values.
+
+    Raises:
+      error.PayloadError if assertion does not hold.
+    """
+    if None in (val1, val2) and val1 is not val2:
+      present, missing = (name1, name2) if val2 is None else (name2, name1)
+      raise error.PayloadError('%r present without %r%s.' %
+                               (present, missing,
+                                ' in ' + obj_name if obj_name else ''))
+
+  @staticmethod
+  def _Run(cmd, send_data=None):
+    """Runs a subprocess, returns its output.
+
+    Args:
+      cmd: Sequence of command-line argument for invoking the subprocess.
+      send_data: Data to feed to the process via its stdin.
+
+    Returns:
+      A tuple containing the stdout and stderr output of the process.
+    """
+    run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE,
+                                   stdout=subprocess.PIPE)
+    try:
+      result = run_process.communicate(input=send_data)
+    finally:
+      exit_code = run_process.wait()
+
+    if exit_code:
+      raise RuntimeError('Subprocess %r failed with code %r.' %
+                         (cmd, exit_code))
+
+    return result
+
+  @staticmethod
+  def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name):
+    """Verifies an actual hash against a signed one.
+
+    Args:
+      sig_data: The raw signature data.
+      pubkey_file_name: Public key used for verifying signature.
+      actual_hash: The actual hash digest.
+      sig_name: Signature name for error reporting.
+
+    Raises:
+      error.PayloadError if signature could not be verified.
+    """
+    if len(sig_data) != 256:
+      raise error.PayloadError(
+          '%s: signature size (%d) not as expected (256).' %
+          (sig_name, len(sig_data)))
+    signed_data, _ = PayloadChecker._Run(
+        ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name],
+        send_data=sig_data)
+
+    if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32:
+      raise error.PayloadError('%s: unexpected signed data length (%d).' %
+                               (sig_name, len(signed_data)))
+
+    if not signed_data.startswith(common.SIG_ASN1_HEADER):
+      raise error.PayloadError('%s: not containing standard ASN.1 prefix.' %
+                               sig_name)
+
+    signed_hash = signed_data[len(common.SIG_ASN1_HEADER):]
+    if signed_hash != actual_hash:
+      raise error.PayloadError(
+          '%s: signed hash (%s) different from actual (%s).' %
+          (sig_name, common.FormatSha256(signed_hash),
+           common.FormatSha256(actual_hash)))
+
+  @staticmethod
+  def _CheckBlocksFitLength(length, num_blocks, block_size, length_name,
+                            block_name=None):
+    """Checks that a given length fits given block space.
+
+    This ensures that the number of blocks allocated is appropriate for the
+    length of the data residing in these blocks.
+
+    Args:
+      length: The actual length of the data.
+      num_blocks: The number of blocks allocated for it.
+      block_size: The size of each block in bytes.
+      length_name: Name of length (used for error reporting).
+      block_name: Name of block (used for error reporting).
+
+    Raises:
+      error.PayloadError if the aforementioned invariant is not satisfied.
+    """
+    # Check: length <= num_blocks * block_size.
+    if length > num_blocks * block_size:
+      raise error.PayloadError(
+          '%s (%d) > num %sblocks (%d) * block_size (%d).' %
+          (length_name, length, block_name or '', num_blocks, block_size))
+
+    # Check: length > (num_blocks - 1) * block_size.
+    if length <= (num_blocks - 1) * block_size:
+      raise error.PayloadError(
+          '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' %
+          (length_name, length, block_name or '', num_blocks - 1, block_size))
+
+  def _CheckManifestMinorVersion(self, report):
+    """Checks the payload manifest minor_version field.
+
+    Args:
+      report: The report object to add to.
+
+    Raises:
+      error.PayloadError if any of the checks fail.
+    """
+    self.minor_version = self._CheckOptionalField(self.payload.manifest,
+                                                  'minor_version', report)
+    if self.minor_version in _SUPPORTED_MINOR_VERSIONS:
+      if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]:
+        raise error.PayloadError(
+            'Minor version %d not compatible with payload type %s.' %
+            (self.minor_version, self.payload_type))
+    elif self.minor_version is None:
+      raise error.PayloadError('Minor version is not set.')
+    else:
+      raise error.PayloadError('Unsupported minor version: %d' %
+                               self.minor_version)
+
+  def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
+    """Checks the payload manifest.
+
+    Args:
+      report: A report object to add to.
+      rootfs_part_size: Size of the rootfs partition in bytes.
+      kernel_part_size: Size of the kernel partition in bytes.
+
+    Returns:
+      A tuple consisting of the partition block size used during the update
+      (integer), the signatures block offset and size.
+
+    Raises:
+      error.PayloadError if any of the checks fail.
+    """
+    manifest = self.payload.manifest
+    report.AddSection('manifest')
+
+    # Check: block_size must exist and match the expected value.
+    actual_block_size = self._CheckMandatoryField(manifest, 'block_size',
+                                                  report, 'manifest')
+    if actual_block_size != self.block_size:
+      raise error.PayloadError('Block_size (%d) not as expected (%d).' %
+                               (actual_block_size, self.block_size))
+
+    # Check: signatures_offset <==> signatures_size.
+    self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset',
+                                                report)
+    self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size',
+                                              report)
+    self._CheckPresentIff(self.sigs_offset, self.sigs_size,
+                          'signatures_offset', 'signatures_size', 'manifest')
+
+    # Check: old_kernel_info <==> old_rootfs_info.
+    oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
+                                                    'old_kernel_info', report)
+    ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
+                                                    'old_rootfs_info', report)
+    self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
+                          'old_rootfs_info', 'manifest')
+    if oki_msg:  # equivalently, ori_msg
+      # Assert/mark delta payload.
+      if self.payload_type == _TYPE_FULL:
+        raise error.PayloadError(
+            'Apparent full payload contains old_{kernel,rootfs}_info.')
+      self.payload_type = _TYPE_DELTA
+
+      # Check: {size, hash} present in old_{kernel,rootfs}_info.
+      self.old_kernel_fs_size = self._CheckMandatoryField(
+          oki_msg, 'size', oki_report, 'old_kernel_info')
+      self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
+                                convert=common.FormatSha256)
+      self.old_rootfs_fs_size = self._CheckMandatoryField(
+          ori_msg, 'size', ori_report, 'old_rootfs_info')
+      self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
+                                convert=common.FormatSha256)
+
+      # Check: old_{kernel,rootfs} size must fit in respective partition.
+      if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
+        raise error.PayloadError(
+            'Old kernel content (%d) exceed partition size (%d).' %
+            (self.old_kernel_fs_size, kernel_part_size))
+      if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
+        raise error.PayloadError(
+            'Old rootfs content (%d) exceed partition size (%d).' %
+            (self.old_rootfs_fs_size, rootfs_part_size))
+    else:
+      # Assert/mark full payload.
+      if self.payload_type == _TYPE_DELTA:
+        raise error.PayloadError(
+            'Apparent delta payload missing old_{kernel,rootfs}_info.')
+      self.payload_type = _TYPE_FULL
+
+    # Check: new_kernel_info present; contains {size, hash}.
+    nki_msg, nki_report = self._CheckMandatorySubMsg(
+        manifest, 'new_kernel_info', report, 'manifest')
+    self.new_kernel_fs_size = self._CheckMandatoryField(
+        nki_msg, 'size', nki_report, 'new_kernel_info')
+    self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
+                              convert=common.FormatSha256)
+
+    # Check: new_rootfs_info present; contains {size, hash}.
+    nri_msg, nri_report = self._CheckMandatorySubMsg(
+        manifest, 'new_rootfs_info', report, 'manifest')
+    self.new_rootfs_fs_size = self._CheckMandatoryField(
+        nri_msg, 'size', nri_report, 'new_rootfs_info')
+    self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
+                              convert=common.FormatSha256)
+
+    # Check: new_{kernel,rootfs} size must fit in respective partition.
+    if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
+      raise error.PayloadError(
+          'New kernel content (%d) exceed partition size (%d).' %
+          (self.new_kernel_fs_size, kernel_part_size))
+    if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
+      raise error.PayloadError(
+          'New rootfs content (%d) exceed partition size (%d).' %
+          (self.new_rootfs_fs_size, rootfs_part_size))
+
+    # Check: minor_version makes sense for the payload type. This check should
+    # run after the payload type has been set.
+    self._CheckManifestMinorVersion(report)
+
+  def _CheckLength(self, length, total_blocks, op_name, length_name):
+    """Checks whether a length matches the space designated in extents.
+
+    Args:
+      length: The total length of the data.
+      total_blocks: The total number of blocks in extents.
+      op_name: Operation name (for error reporting).
+      length_name: Length name (for error reporting).
+
+    Raises:
+      error.PayloadError is there a problem with the length.
+    """
+    # Check: length is non-zero.
+    if length == 0:
+      raise error.PayloadError('%s: %s is zero.' % (op_name, length_name))
+
+    # Check that length matches number of blocks.
+    self._CheckBlocksFitLength(length, total_blocks, self.block_size,
+                               '%s: %s' % (op_name, length_name))
+
+  def _CheckExtents(self, extents, usable_size, block_counters, name,
+                    allow_pseudo=False, allow_signature=False):
+    """Checks a sequence of extents.
+
+    Args:
+      extents: The sequence of extents to check.
+      usable_size: The usable size of the partition to which the extents apply.
+      block_counters: Array of counters corresponding to the number of blocks.
+      name: The name of the extent block.
+      allow_pseudo: Whether or not pseudo block numbers are allowed.
+      allow_signature: Whether or not the extents are used for a signature.
+
+    Returns:
+      The total number of blocks in the extents.
+
+    Raises:
+      error.PayloadError if any of the entailed checks fails.
+    """
+    total_num_blocks = 0
+    for ex, ex_name in common.ExtentIter(extents, name):
+      # Check: Mandatory fields.
+      start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block',
+                                                        None, ex_name)
+      num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None,
+                                                       ex_name)
+      end_block = start_block + num_blocks
+
+      # Check: num_blocks > 0.
+      if num_blocks == 0:
+        raise error.PayloadError('%s: extent length is zero.' % ex_name)
+
+      if start_block != common.PSEUDO_EXTENT_MARKER:
+        # Check: Make sure we're within the partition limit.
+        if usable_size and end_block * self.block_size > usable_size:
+          raise error.PayloadError(
+              '%s: extent (%s) exceeds usable partition size (%d).' %
+              (ex_name, common.FormatExtent(ex, self.block_size), usable_size))
+
+        # Record block usage.
+        for i in xrange(start_block, end_block):
+          block_counters[i] += 1
+      elif not (allow_pseudo or (allow_signature and len(extents) == 1)):
+        # Pseudo-extents must be allowed explicitly, or otherwise be part of a
+        # signature operation (in which case there has to be exactly one).
+        raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name)
+
+      total_num_blocks += num_blocks
+
+    return total_num_blocks
+
+  def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name):
+    """Specific checks for REPLACE/REPLACE_BZ operations.
+
+    Args:
+      op: The operation object from the manifest.
+      data_length: The length of the data blob associated with the operation.
+      total_dst_blocks: Total number of blocks in dst_extents.
+      op_name: Operation name for error reporting.
+
+    Raises:
+      error.PayloadError if any check fails.
+    """
+    # Check: Does not contain src extents.
+    if op.src_extents:
+      raise error.PayloadError('%s: contains src_extents.' % op_name)
+
+    # Check: Contains data.
+    if data_length is None:
+      raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
+
+    if op.type == common.OpType.REPLACE:
+      PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks,
+                                           self.block_size,
+                                           op_name + '.data_length', 'dst')
+    else:
+      # Check: data_length must be smaller than the alotted dst blocks.
+      if data_length >= total_dst_blocks * self.block_size:
+        raise error.PayloadError(
+            '%s: data_length (%d) must be less than allotted dst block '
+            'space (%d * %d).' %
+            (op_name, data_length, total_dst_blocks, self.block_size))
+
+  def _CheckMoveOperation(self, op, data_offset, total_src_blocks,
+                          total_dst_blocks, op_name):
+    """Specific checks for MOVE operations.
+
+    Args:
+      op: The operation object from the manifest.
+      data_offset: The offset of a data blob for the operation.
+      total_src_blocks: Total number of blocks in src_extents.
+      total_dst_blocks: Total number of blocks in dst_extents.
+      op_name: Operation name for error reporting.
+
+    Raises:
+      error.PayloadError if any check fails.
+    """
+    # Check: No data_{offset,length}.
+    if data_offset is not None:
+      raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
+
+    # Check: total_src_blocks == total_dst_blocks.
+    if total_src_blocks != total_dst_blocks:
+      raise error.PayloadError(
+          '%s: total src blocks (%d) != total dst blocks (%d).' %
+          (op_name, total_src_blocks, total_dst_blocks))
+
+    # Check: For all i, i-th src block index != i-th dst block index.
+    i = 0
+    src_extent_iter = iter(op.src_extents)
+    dst_extent_iter = iter(op.dst_extents)
+    src_extent = dst_extent = None
+    src_idx = src_num = dst_idx = dst_num = 0
+    while i < total_src_blocks:
+      # Get the next source extent, if needed.
+      if not src_extent:
+        try:
+          src_extent = src_extent_iter.next()
+        except StopIteration:
+          raise error.PayloadError('%s: ran out of src extents (%d/%d).' %
+                                   (op_name, i, total_src_blocks))
+        src_idx = src_extent.start_block
+        src_num = src_extent.num_blocks
+
+      # Get the next dest extent, if needed.
+      if not dst_extent:
+        try:
+          dst_extent = dst_extent_iter.next()
+        except StopIteration:
+          raise error.PayloadError('%s: ran out of dst extents (%d/%d).' %
+                                   (op_name, i, total_dst_blocks))
+        dst_idx = dst_extent.start_block
+        dst_num = dst_extent.num_blocks
+
+      # Check: start block is not 0. See crbug/480751; there are still versions
+      # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll,
+      # so we need to fail payloads that try to MOVE to/from block 0.
+      if src_idx == 0 or dst_idx == 0:
+        raise error.PayloadError(
+            '%s: MOVE operation cannot have extent with start block 0' %
+            op_name)
+
+      if self.check_move_same_src_dst_block and src_idx == dst_idx:
+        raise error.PayloadError(
+            '%s: src/dst block number %d is the same (%d).' %
+            (op_name, i, src_idx))
+
+      advance = min(src_num, dst_num)
+      i += advance
+
+      src_idx += advance
+      src_num -= advance
+      if src_num == 0:
+        src_extent = None
+
+      dst_idx += advance
+      dst_num -= advance
+      if dst_num == 0:
+        dst_extent = None
+
+    # Make sure we've exhausted all src/dst extents.
+    if src_extent:
+      raise error.PayloadError('%s: excess src blocks.' % op_name)
+    if dst_extent:
+      raise error.PayloadError('%s: excess dst blocks.' % op_name)
+
+  def _CheckAnyDiffOperation(self, data_length, total_dst_blocks, op_name):
+    """Specific checks for BSDIFF, SOURCE_BSDIFF and IMGDIFF operations.
+
+    Args:
+      data_length: The length of the data blob associated with the operation.
+      total_dst_blocks: Total number of blocks in dst_extents.
+      op_name: Operation name for error reporting.
+
+    Raises:
+      error.PayloadError if any check fails.
+    """
+    # Check: data_{offset,length} present.
+    if data_length is None:
+      raise error.PayloadError('%s: missing data_{offset,length}.' % op_name)
+
+    # Check: data_length is strictly smaller than the alotted dst blocks.
+    if data_length >= total_dst_blocks * self.block_size:
+      raise error.PayloadError(
+          '%s: data_length (%d) must be smaller than allotted dst space '
+          '(%d * %d = %d).' %
+          (op_name, data_length, total_dst_blocks, self.block_size,
+           total_dst_blocks * self.block_size))
+
+  def _CheckSourceCopyOperation(self, data_offset, total_src_blocks,
+                                total_dst_blocks, op_name):
+    """Specific checks for SOURCE_COPY.
+
+    Args:
+      data_offset: The offset of a data blob for the operation.
+      total_src_blocks: Total number of blocks in src_extents.
+      total_dst_blocks: Total number of blocks in dst_extents.
+      op_name: Operation name for error reporting.
+
+    Raises:
+      error.PayloadError if any check fails.
+    """
+    # Check: No data_{offset,length}.
+    if data_offset is not None:
+      raise error.PayloadError('%s: contains data_{offset,length}.' % op_name)
+
+    # Check: total_src_blocks == total_dst_blocks.
+    if total_src_blocks != total_dst_blocks:
+      raise error.PayloadError(
+          '%s: total src blocks (%d) != total dst blocks (%d).' %
+          (op_name, total_src_blocks, total_dst_blocks))
+
+  def _CheckAnySourceOperation(self, op, total_src_blocks, op_name):
+    """Specific checks for SOURCE_* operations.
+
+    Args:
+      op: The operation object from the manifest.
+      total_src_blocks: Total number of blocks in src_extents.
+      op_name: Operation name for error reporting.
+
+    Raises:
+      error.PayloadError if any check fails.
+    """
+    # Check: total_src_blocks != 0.
+    if total_src_blocks == 0:
+      raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
+
+    # Check: src_sha256_hash present in minor version >= 3.
+    if self.minor_version >= 3 and op.src_sha256_hash is None:
+      raise error.PayloadError('%s: source hash missing.' % op_name)
+
+  def _CheckOperation(self, op, op_name, is_last, old_block_counters,
+                      new_block_counters, old_usable_size, new_usable_size,
+                      prev_data_offset, allow_signature, blob_hash_counts):
+    """Checks a single update operation.
+
+    Args:
+      op: The operation object.
+      op_name: Operation name string for error reporting.
+      is_last: Whether this is the last operation in the sequence.
+      old_block_counters: Arrays of block read counters.
+      new_block_counters: Arrays of block write counters.
+      old_usable_size: The overall usable size for src data in bytes.
+      new_usable_size: The overall usable size for dst data in bytes.
+      prev_data_offset: Offset of last used data bytes.
+      allow_signature: Whether this may be a signature operation.
+      blob_hash_counts: Counters for hashed/unhashed blobs.
+
+    Returns:
+      The amount of data blob associated with the operation.
+
+    Raises:
+      error.PayloadError if any check has failed.
+    """
+    # Check extents.
+    total_src_blocks = self._CheckExtents(
+        op.src_extents, old_usable_size, old_block_counters,
+        op_name + '.src_extents', allow_pseudo=True)
+    allow_signature_in_extents = (allow_signature and is_last and
+                                  op.type == common.OpType.REPLACE)
+    total_dst_blocks = self._CheckExtents(
+        op.dst_extents, new_usable_size, new_block_counters,
+        op_name + '.dst_extents',
+        allow_pseudo=(not self.check_dst_pseudo_extents),
+        allow_signature=allow_signature_in_extents)
+
+    # Check: data_offset present <==> data_length present.
+    data_offset = self._CheckOptionalField(op, 'data_offset', None)
+    data_length = self._CheckOptionalField(op, 'data_length', None)
+    self._CheckPresentIff(data_offset, data_length, 'data_offset',
+                          'data_length', op_name)
+
+    # Check: At least one dst_extent.
+    if not op.dst_extents:
+      raise error.PayloadError('%s: dst_extents is empty.' % op_name)
+
+    # Check {src,dst}_length, if present.
+    if op.HasField('src_length'):
+      self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length')
+    if op.HasField('dst_length'):
+      self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length')
+
+    if op.HasField('data_sha256_hash'):
+      blob_hash_counts['hashed'] += 1
+
+      # Check: Operation carries data.
+      if data_offset is None:
+        raise error.PayloadError(
+            '%s: data_sha256_hash present but no data_{offset,length}.' %
+            op_name)
+
+      # Check: Hash verifies correctly.
+      # pylint cannot find the method in hashlib, for some reason.
+      # pylint: disable=E1101
+      actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset,
+                                                             data_length))
+      if op.data_sha256_hash != actual_hash.digest():
+        raise error.PayloadError(
+            '%s: data_sha256_hash (%s) does not match actual hash (%s).' %
+            (op_name, common.FormatSha256(op.data_sha256_hash),
+             common.FormatSha256(actual_hash.digest())))
+    elif data_offset is not None:
+      if allow_signature_in_extents:
+        blob_hash_counts['signature'] += 1
+      elif self.allow_unhashed:
+        blob_hash_counts['unhashed'] += 1
+      else:
+        raise error.PayloadError('%s: unhashed operation not allowed.' %
+                                 op_name)
+
+    if data_offset is not None:
+      # Check: Contiguous use of data section.
+      if data_offset != prev_data_offset:
+        raise error.PayloadError(
+            '%s: data offset (%d) not matching amount used so far (%d).' %
+            (op_name, data_offset, prev_data_offset))
+
+    # Type-specific checks.
+    if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
+      self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
+    elif op.type == common.OpType.MOVE and self.minor_version == 1:
+      self._CheckMoveOperation(op, data_offset, total_src_blocks,
+                               total_dst_blocks, op_name)
+    elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
+      self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
+    elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
+      self._CheckSourceCopyOperation(data_offset, total_src_blocks,
+                                     total_dst_blocks, op_name)
+      self._CheckAnySourceOperation(op, total_src_blocks, op_name)
+    elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
+      self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
+      self._CheckAnySourceOperation(op, total_src_blocks, op_name)
+    elif op.type == common.OpType.IMGDIFF and self.minor_version >= 4:
+      self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
+      self._CheckAnySourceOperation(op, total_src_blocks, op_name)
+    else:
+      raise error.PayloadError(
+          'Operation %s (type %d) not allowed in minor version %d' %
+          (op_name, op.type, self.minor_version))
+    return data_length if data_length is not None else 0
+
+  def _SizeToNumBlocks(self, size):
+    """Returns the number of blocks needed to contain a given byte size."""
+    return (size + self.block_size - 1) / self.block_size
+
+  def _AllocBlockCounters(self, total_size):
+    """Returns a freshly initialized array of block counters.
+
+    Note that the generated array is not portable as is due to byte-ordering
+    issues, hence it should not be serialized.
+
+    Args:
+      total_size: The total block size in bytes.
+
+    Returns:
+      An array of unsigned short elements initialized to zero, one for each of
+      the blocks necessary for containing the partition.
+    """
+    return array.array('H',
+                       itertools.repeat(0, self._SizeToNumBlocks(total_size)))
+
+  def _CheckOperations(self, operations, report, base_name, old_fs_size,
+                       new_fs_size, new_usable_size, prev_data_offset,
+                       allow_signature):
+    """Checks a sequence of update operations.
+
+    Args:
+      operations: The sequence of operations to check.
+      report: The report object to add to.
+      base_name: The name of the operation block.
+      old_fs_size: The old filesystem size in bytes.
+      new_fs_size: The new filesystem size in bytes.
+      new_usable_size: The overall usable size of the new partition in bytes.
+      prev_data_offset: Offset of last used data bytes.
+      allow_signature: Whether this sequence may contain signature operations.
+
+    Returns:
+      The total data blob size used.
+
+    Raises:
+      error.PayloadError if any of the checks fails.
+    """
+    # The total size of data blobs used by operations scanned thus far.
+    total_data_used = 0
+    # Counts of specific operation types.
+    op_counts = {
+        common.OpType.REPLACE: 0,
+        common.OpType.REPLACE_BZ: 0,
+        common.OpType.MOVE: 0,
+        common.OpType.BSDIFF: 0,
+        common.OpType.SOURCE_COPY: 0,
+        common.OpType.SOURCE_BSDIFF: 0,
+        common.OpType.IMGDIFF: 0,
+    }
+    # Total blob sizes for each operation type.
+    op_blob_totals = {
+        common.OpType.REPLACE: 0,
+        common.OpType.REPLACE_BZ: 0,
+        # MOVE operations don't have blobs.
+        common.OpType.BSDIFF: 0,
+        # SOURCE_COPY operations don't have blobs.
+        common.OpType.SOURCE_BSDIFF: 0,
+        common.OpType.IMGDIFF: 0,
+    }
+    # Counts of hashed vs unhashed operations.
+    blob_hash_counts = {
+        'hashed': 0,
+        'unhashed': 0,
+    }
+    if allow_signature:
+      blob_hash_counts['signature'] = 0
+
+    # Allocate old and new block counters.
+    old_block_counters = (self._AllocBlockCounters(new_usable_size)
+                          if old_fs_size else None)
+    new_block_counters = self._AllocBlockCounters(new_usable_size)
+
+    # Process and verify each operation.
+    op_num = 0
+    for op, op_name in common.OperationIter(operations, base_name):
+      op_num += 1
+
+      # Check: Type is valid.
+      if op.type not in op_counts.keys():
+        raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type))
+      op_counts[op.type] += 1
+
+      is_last = op_num == len(operations)
+      curr_data_used = self._CheckOperation(
+          op, op_name, is_last, old_block_counters, new_block_counters,
+          new_usable_size if old_fs_size else 0, new_usable_size,
+          prev_data_offset + total_data_used, allow_signature,
+          blob_hash_counts)
+      if curr_data_used:
+        op_blob_totals[op.type] += curr_data_used
+        total_data_used += curr_data_used
+
+    # Report totals and breakdown statistics.
+    report.AddField('total operations', op_num)
+    report.AddField(
+        None,
+        histogram.Histogram.FromCountDict(op_counts,
+                                          key_names=common.OpType.NAMES),
+        indent=1)
+    report.AddField('total blobs', sum(blob_hash_counts.values()))
+    report.AddField(None,
+                    histogram.Histogram.FromCountDict(blob_hash_counts),
+                    indent=1)
+    report.AddField('total blob size', _AddHumanReadableSize(total_data_used))
+    report.AddField(
+        None,
+        histogram.Histogram.FromCountDict(op_blob_totals,
+                                          formatter=_AddHumanReadableSize,
+                                          key_names=common.OpType.NAMES),
+        indent=1)
+
+    # Report read/write histograms.
+    if old_block_counters:
+      report.AddField('block read hist',
+                      histogram.Histogram.FromKeyList(old_block_counters),
+                      linebreak=True, indent=1)
+
+    new_write_hist = histogram.Histogram.FromKeyList(
+        new_block_counters[:self._SizeToNumBlocks(new_fs_size)])
+    report.AddField('block write hist', new_write_hist, linebreak=True,
+                    indent=1)
+
+    # Check: Full update must write each dst block once.
+    if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]:
+      raise error.PayloadError(
+          '%s: not all blocks written exactly once during full update.' %
+          base_name)
+
+    return total_data_used
+
+  def _CheckSignatures(self, report, pubkey_file_name):
+    """Checks a payload's signature block."""
+    sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size)
+    sigs = update_metadata_pb2.Signatures()
+    sigs.ParseFromString(sigs_raw)
+    report.AddSection('signatures')
+
+    # Check: At least one signature present.
+    # pylint cannot see through the protobuf object, it seems.
+    # pylint: disable=E1101
+    if not sigs.signatures:
+      raise error.PayloadError('Signature block is empty.')
+
+    last_ops_section = (self.payload.manifest.kernel_install_operations or
+                        self.payload.manifest.install_operations)
+    fake_sig_op = last_ops_section[-1]
+    # Check: signatures_{offset,size} must match the last (fake) operation.
+    if not (fake_sig_op.type == common.OpType.REPLACE and
+            self.sigs_offset == fake_sig_op.data_offset and
+            self.sigs_size == fake_sig_op.data_length):
+      raise error.PayloadError(
+          'Signatures_{offset,size} (%d+%d) does not match last operation '
+          '(%d+%d).' %
+          (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
+           fake_sig_op.data_length))
+
+    # Compute the checksum of all data up to signature blob.
+    # TODO(garnold) we're re-reading the whole data section into a string
+    # just to compute the checksum; instead, we could do it incrementally as
+    # we read the blobs one-by-one, under the assumption that we're reading
+    # them in order (which currently holds). This should be reconsidered.
+    payload_hasher = self.payload.manifest_hasher.copy()
+    common.Read(self.payload.payload_file, self.sigs_offset,
+                offset=self.payload.data_offset, hasher=payload_hasher)
+
+    for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'):
+      sig_report = report.AddSubReport(sig_name)
+
+      # Check: Signature contains mandatory fields.
+      self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
+      self._CheckMandatoryField(sig, 'data', None, sig_name)
+      sig_report.AddField('data len', len(sig.data))
+
+      # Check: Signatures pertains to actual payload hash.
+      if sig.version == 1:
+        self._CheckSha256Signature(sig.data, pubkey_file_name,
+                                   payload_hasher.digest(), sig_name)
+      else:
+        raise error.PayloadError('Unknown signature version (%d).' %
+                                 sig.version)
+
+  def Run(self, pubkey_file_name=None, metadata_sig_file=None,
+          rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
+    """Checker entry point, invoking all checks.
+
+    Args:
+      pubkey_file_name: Public key used for signature verification.
+      metadata_sig_file: Metadata signature, if verification is desired.
+      rootfs_part_size: The size of rootfs partitions in bytes (default: infer
+                        based on payload type and version).
+      kernel_part_size: The size of kernel partitions in bytes (default: use
+                        reported filesystem size).
+      report_out_file: File object to dump the report to.
+
+    Raises:
+      error.PayloadError if payload verification failed.
+    """
+    if not pubkey_file_name:
+      pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME
+
+    report = _PayloadReport()
+
+    # Get payload file size.
+    self.payload.payload_file.seek(0, 2)
+    payload_file_size = self.payload.payload_file.tell()
+    self.payload.ResetFile()
+
+    try:
+      # Check metadata signature (if provided).
+      if metadata_sig_file:
+        metadata_sig = base64.b64decode(metadata_sig_file.read())
+        self._CheckSha256Signature(metadata_sig, pubkey_file_name,
+                                   self.payload.manifest_hasher.digest(),
+                                   'metadata signature')
+
+      # Part 1: Check the file header.
+      report.AddSection('header')
+      # Check: Payload version is valid.
+      if self.payload.header.version != 1:
+        raise error.PayloadError('Unknown payload version (%d).' %
+                                 self.payload.header.version)
+      report.AddField('version', self.payload.header.version)
+      report.AddField('manifest len', self.payload.header.manifest_len)
+
+      # Part 2: Check the manifest.
+      self._CheckManifest(report, rootfs_part_size, kernel_part_size)
+      assert self.payload_type, 'payload type should be known by now'
+
+      # Infer the usable partition size when validating rootfs operations:
+      # - If rootfs partition size was provided, use that.
+      # - Otherwise, if this is an older delta (minor version < 2), stick with
+      #   a known constant size. This is necessary because older deltas may
+      #   exceed the filesystem size when moving data blocks around.
+      # - Otherwise, use the encoded filesystem size.
+      new_rootfs_usable_size = self.new_rootfs_fs_size
+      if rootfs_part_size:
+        new_rootfs_usable_size = rootfs_part_size
+      elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
+        new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
+
+      # Part 3: Examine rootfs operations.
+      # TODO(garnold)(chromium:243559) only default to the filesystem size if
+      # no explicit size provided *and* the partition size is not embedded in
+      # the payload; see issue for more details.
+      report.AddSection('rootfs operations')
+      total_blob_size = self._CheckOperations(
+          self.payload.manifest.install_operations, report,
+          'install_operations', self.old_rootfs_fs_size,
+          self.new_rootfs_fs_size, new_rootfs_usable_size, 0, False)
+
+      # Part 4: Examine kernel operations.
+      # TODO(garnold)(chromium:243559) as above.
+      report.AddSection('kernel operations')
+      total_blob_size += self._CheckOperations(
+          self.payload.manifest.kernel_install_operations, report,
+          'kernel_install_operations', self.old_kernel_fs_size,
+          self.new_kernel_fs_size,
+          kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
+          total_blob_size, True)
+
+      # Check: Operations data reach the end of the payload file.
+      used_payload_size = self.payload.data_offset + total_blob_size
+      if used_payload_size != payload_file_size:
+        raise error.PayloadError(
+            'Used payload size (%d) different from actual file size (%d).' %
+            (used_payload_size, payload_file_size))
+
+      # Part 5: Handle payload signatures message.
+      if self.check_payload_sig and self.sigs_size:
+        self._CheckSignatures(report, pubkey_file_name)
+
+      # Part 6: Summary.
+      report.AddSection('summary')
+      report.AddField('update type', self.payload_type)
+
+      report.Finalize()
+    finally:
+      if report_out_file:
+        report.Dump(report_out_file)
diff --git a/update_engine/scripts/update_payload/checker_unittest.py b/update_engine/scripts/update_payload/checker_unittest.py
new file mode 100755
index 0000000..56b1a30
--- /dev/null
+++ b/update_engine/scripts/update_payload/checker_unittest.py
@@ -0,0 +1,1326 @@
+#!/usr/bin/python2
+#
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit testing checker.py."""
+
+from __future__ import print_function
+
+import array
+import collections
+import cStringIO
+import hashlib
+import itertools
+import os
+import unittest
+
+# pylint cannot find mox.
+# pylint: disable=F0401
+import mox
+
+import checker
+import common
+import payload as update_payload  # Avoid name conflicts later.
+import test_utils
+import update_metadata_pb2
+
+
+def _OpTypeByName(op_name):
+  op_name_to_type = {
+      'REPLACE': common.OpType.REPLACE,
+      'REPLACE_BZ': common.OpType.REPLACE_BZ,
+      'MOVE': common.OpType.MOVE,
+      'BSDIFF': common.OpType.BSDIFF,
+      'SOURCE_COPY': common.OpType.SOURCE_COPY,
+      'SOURCE_BSDIFF': common.OpType.SOURCE_BSDIFF,
+      'ZERO': common.OpType.ZERO,
+      'DISCARD': common.OpType.DISCARD,
+      'REPLACE_XZ': common.OpType.REPLACE_XZ,
+      'IMGDIFF': common.OpType.IMGDIFF,
+  }
+  return op_name_to_type[op_name]
+
+
+def _GetPayloadChecker(payload_gen_write_to_file_func, payload_gen_dargs=None,
+                       checker_init_dargs=None):
+  """Returns a payload checker from a given payload generator."""
+  if payload_gen_dargs is None:
+    payload_gen_dargs = {}
+  if checker_init_dargs is None:
+    checker_init_dargs = {}
+
+  payload_file = cStringIO.StringIO()
+  payload_gen_write_to_file_func(payload_file, **payload_gen_dargs)
+  payload_file.seek(0)
+  payload = update_payload.Payload(payload_file)
+  payload.Init()
+  return checker.PayloadChecker(payload, **checker_init_dargs)
+
+
+def _GetPayloadCheckerWithData(payload_gen):
+  """Returns a payload checker from a given payload generator."""
+  payload_file = cStringIO.StringIO()
+  payload_gen.WriteToFile(payload_file)
+  payload_file.seek(0)
+  payload = update_payload.Payload(payload_file)
+  payload.Init()
+  return checker.PayloadChecker(payload)
+
+
+# This class doesn't need an __init__().
+# pylint: disable=W0232
+# Unit testing is all about running protected methods.
+# pylint: disable=W0212
+# Don't bark about missing members of classes you cannot import.
+# pylint: disable=E1101
+class PayloadCheckerTest(mox.MoxTestBase):
+  """Tests the PayloadChecker class.
+
+  In addition to ordinary testFoo() methods, which are automatically invoked by
+  the unittest framework, in this class we make use of DoBarTest() calls that
+  implement parametric tests of certain features. In order to invoke each test,
+  which embodies a unique combination of parameter values, as a complete unit
+  test, we perform explicit enumeration of the parameter space and create
+  individual invocation contexts for each, which are then bound as
+  testBar__param1=val1__param2=val2(). The enumeration of parameter spaces for
+  all such tests is done in AddAllParametricTests().
+  """
+
+  def MockPayload(self):
+    """Create a mock payload object, complete with a mock manifest."""
+    payload = self.mox.CreateMock(update_payload.Payload)
+    payload.is_init = True
+    payload.manifest = self.mox.CreateMock(
+        update_metadata_pb2.DeltaArchiveManifest)
+    return payload
+
+  @staticmethod
+  def NewExtent(start_block, num_blocks):
+    """Returns an Extent message.
+
+    Each of the provided fields is set iff it is >= 0; otherwise, it's left at
+    its default state.
+
+    Args:
+      start_block: The starting block of the extent.
+      num_blocks: The number of blocks in the extent.
+
+    Returns:
+      An Extent message.
+    """
+    ex = update_metadata_pb2.Extent()
+    if start_block >= 0:
+      ex.start_block = start_block
+    if num_blocks >= 0:
+      ex.num_blocks = num_blocks
+    return ex
+
+  @staticmethod
+  def NewExtentList(*args):
+    """Returns an list of extents.
+
+    Args:
+      *args: (start_block, num_blocks) pairs defining the extents.
+
+    Returns:
+      A list of Extent objects.
+    """
+    ex_list = []
+    for start_block, num_blocks in args:
+      ex_list.append(PayloadCheckerTest.NewExtent(start_block, num_blocks))
+    return ex_list
+
+  @staticmethod
+  def AddToMessage(repeated_field, field_vals):
+    for field_val in field_vals:
+      new_field = repeated_field.add()
+      new_field.CopyFrom(field_val)
+
+  def SetupAddElemTest(self, is_present, is_submsg, convert=str,
+                       linebreak=False, indent=0):
+    """Setup for testing of _CheckElem() and its derivatives.
+
+    Args:
+      is_present: Whether or not the element is found in the message.
+      is_submsg: Whether the element is a sub-message itself.
+      convert: A representation conversion function.
+      linebreak: Whether or not a linebreak is to be used in the report.
+      indent: Indentation used for the report.
+
+    Returns:
+      msg: A mock message object.
+      report: A mock report object.
+      subreport: A mock sub-report object.
+      name: An element name to check.
+      val: Expected element value.
+    """
+    name = 'foo'
+    val = 'fake submsg' if is_submsg else 'fake field'
+    subreport = 'fake subreport'
+
+    # Create a mock message.
+    msg = self.mox.CreateMock(update_metadata_pb2._message.Message)
+    msg.HasField(name).AndReturn(is_present)
+    setattr(msg, name, val)
+
+    # Create a mock report.
+    report = self.mox.CreateMock(checker._PayloadReport)
+    if is_present:
+      if is_submsg:
+        report.AddSubReport(name).AndReturn(subreport)
+      else:
+        report.AddField(name, convert(val), linebreak=linebreak, indent=indent)
+
+    self.mox.ReplayAll()
+    return (msg, report, subreport, name, val)
+
+  def DoAddElemTest(self, is_present, is_mandatory, is_submsg, convert,
+                    linebreak, indent):
+    """Parametric testing of _CheckElem().
+
+    Args:
+      is_present: Whether or not the element is found in the message.
+      is_mandatory: Whether or not it's a mandatory element.
+      is_submsg: Whether the element is a sub-message itself.
+      convert: A representation conversion function.
+      linebreak: Whether or not a linebreak is to be used in the report.
+      indent: Indentation used for the report.
+    """
+    msg, report, subreport, name, val = self.SetupAddElemTest(
+        is_present, is_submsg, convert, linebreak, indent)
+
+    args = (msg, name, report, is_mandatory, is_submsg)
+    kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent}
+    if is_mandatory and not is_present:
+      self.assertRaises(update_payload.PayloadError,
+                        checker.PayloadChecker._CheckElem, *args, **kwargs)
+    else:
+      ret_val, ret_subreport = checker.PayloadChecker._CheckElem(*args,
+                                                                 **kwargs)
+      self.assertEquals(val if is_present else None, ret_val)
+      self.assertEquals(subreport if is_present and is_submsg else None,
+                        ret_subreport)
+
+  def DoAddFieldTest(self, is_mandatory, is_present, convert, linebreak,
+                     indent):
+    """Parametric testing of _Check{Mandatory,Optional}Field().
+
+    Args:
+      is_mandatory: Whether we're testing a mandatory call.
+      is_present: Whether or not the element is found in the message.
+      convert: A representation conversion function.
+      linebreak: Whether or not a linebreak is to be used in the report.
+      indent: Indentation used for the report.
+    """
+    msg, report, _, name, val = self.SetupAddElemTest(
+        is_present, False, convert, linebreak, indent)
+
+    # Prepare for invocation of the tested method.
+    args = [msg, name, report]
+    kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent}
+    if is_mandatory:
+      args.append('bar')
+      tested_func = checker.PayloadChecker._CheckMandatoryField
+    else:
+      tested_func = checker.PayloadChecker._CheckOptionalField
+
+    # Test the method call.
+    if is_mandatory and not is_present:
+      self.assertRaises(update_payload.PayloadError, tested_func, *args,
+                        **kwargs)
+    else:
+      ret_val = tested_func(*args, **kwargs)
+      self.assertEquals(val if is_present else None, ret_val)
+
+  def DoAddSubMsgTest(self, is_mandatory, is_present):
+    """Parametrized testing of _Check{Mandatory,Optional}SubMsg().
+
+    Args:
+      is_mandatory: Whether we're testing a mandatory call.
+      is_present: Whether or not the element is found in the message.
+    """
+    msg, report, subreport, name, val = self.SetupAddElemTest(is_present, True)
+
+    # Prepare for invocation of the tested method.
+    args = [msg, name, report]
+    if is_mandatory:
+      args.append('bar')
+      tested_func = checker.PayloadChecker._CheckMandatorySubMsg
+    else:
+      tested_func = checker.PayloadChecker._CheckOptionalSubMsg
+
+    # Test the method call.
+    if is_mandatory and not is_present:
+      self.assertRaises(update_payload.PayloadError, tested_func, *args)
+    else:
+      ret_val, ret_subreport = tested_func(*args)
+      self.assertEquals(val if is_present else None, ret_val)
+      self.assertEquals(subreport if is_present else None, ret_subreport)
+
+  def testCheckPresentIff(self):
+    """Tests _CheckPresentIff()."""
+    self.assertIsNone(checker.PayloadChecker._CheckPresentIff(
+        None, None, 'foo', 'bar', 'baz'))
+    self.assertIsNone(checker.PayloadChecker._CheckPresentIff(
+        'a', 'b', 'foo', 'bar', 'baz'))
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckPresentIff,
+                      'a', None, 'foo', 'bar', 'baz')
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckPresentIff,
+                      None, 'b', 'foo', 'bar', 'baz')
+
+  def DoCheckSha256SignatureTest(self, expect_pass, expect_subprocess_call,
+                                 sig_data, sig_asn1_header,
+                                 returned_signed_hash, expected_signed_hash):
+    """Parametric testing of _CheckSha256SignatureTest().
+
+    Args:
+      expect_pass: Whether or not it should pass.
+      expect_subprocess_call: Whether to expect the openssl call to happen.
+      sig_data: The signature raw data.
+      sig_asn1_header: The ASN1 header.
+      returned_signed_hash: The signed hash data retuned by openssl.
+      expected_signed_hash: The signed hash data to compare against.
+    """
+    try:
+      # Stub out the subprocess invocation.
+      self.mox.StubOutWithMock(checker.PayloadChecker, '_Run')
+      if expect_subprocess_call:
+        checker.PayloadChecker._Run(
+            mox.IsA(list), send_data=sig_data).AndReturn(
+                (sig_asn1_header + returned_signed_hash, None))
+
+      self.mox.ReplayAll()
+      if expect_pass:
+        self.assertIsNone(checker.PayloadChecker._CheckSha256Signature(
+            sig_data, 'foo', expected_signed_hash, 'bar'))
+      else:
+        self.assertRaises(update_payload.PayloadError,
+                          checker.PayloadChecker._CheckSha256Signature,
+                          sig_data, 'foo', expected_signed_hash, 'bar')
+    finally:
+      self.mox.UnsetStubs()
+
+  def testCheckSha256Signature_Pass(self):
+    """Tests _CheckSha256Signature(); pass case."""
+    sig_data = 'fake-signature'.ljust(256)
+    signed_hash = hashlib.sha256('fake-data').digest()
+    self.DoCheckSha256SignatureTest(True, True, sig_data,
+                                    common.SIG_ASN1_HEADER, signed_hash,
+                                    signed_hash)
+
+  def testCheckSha256Signature_FailBadSignature(self):
+    """Tests _CheckSha256Signature(); fails due to malformed signature."""
+    sig_data = 'fake-signature'  # Malformed (not 256 bytes in length).
+    signed_hash = hashlib.sha256('fake-data').digest()
+    self.DoCheckSha256SignatureTest(False, False, sig_data,
+                                    common.SIG_ASN1_HEADER, signed_hash,
+                                    signed_hash)
+
+  def testCheckSha256Signature_FailBadOutputLength(self):
+    """Tests _CheckSha256Signature(); fails due to unexpected output length."""
+    sig_data = 'fake-signature'.ljust(256)
+    signed_hash = 'fake-hash'  # Malformed (not 32 bytes in length).
+    self.DoCheckSha256SignatureTest(False, True, sig_data,
+                                    common.SIG_ASN1_HEADER, signed_hash,
+                                    signed_hash)
+
+  def testCheckSha256Signature_FailBadAsnHeader(self):
+    """Tests _CheckSha256Signature(); fails due to bad ASN1 header."""
+    sig_data = 'fake-signature'.ljust(256)
+    signed_hash = hashlib.sha256('fake-data').digest()
+    bad_asn1_header = 'bad-asn-header'.ljust(len(common.SIG_ASN1_HEADER))
+    self.DoCheckSha256SignatureTest(False, True, sig_data, bad_asn1_header,
+                                    signed_hash, signed_hash)
+
+  def testCheckSha256Signature_FailBadHash(self):
+    """Tests _CheckSha256Signature(); fails due to bad hash returned."""
+    sig_data = 'fake-signature'.ljust(256)
+    expected_signed_hash = hashlib.sha256('fake-data').digest()
+    returned_signed_hash = hashlib.sha256('bad-fake-data').digest()
+    self.DoCheckSha256SignatureTest(False, True, sig_data,
+                                    common.SIG_ASN1_HEADER,
+                                    expected_signed_hash, returned_signed_hash)
+
+  def testCheckBlocksFitLength_Pass(self):
+    """Tests _CheckBlocksFitLength(); pass case."""
+    self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
+        64, 4, 16, 'foo'))
+    self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
+        60, 4, 16, 'foo'))
+    self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
+        49, 4, 16, 'foo'))
+    self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
+        48, 3, 16, 'foo'))
+
+  def testCheckBlocksFitLength_TooManyBlocks(self):
+    """Tests _CheckBlocksFitLength(); fails due to excess blocks."""
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckBlocksFitLength,
+                      64, 5, 16, 'foo')
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckBlocksFitLength,
+                      60, 5, 16, 'foo')
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckBlocksFitLength,
+                      49, 5, 16, 'foo')
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckBlocksFitLength,
+                      48, 4, 16, 'foo')
+
+  def testCheckBlocksFitLength_TooFewBlocks(self):
+    """Tests _CheckBlocksFitLength(); fails due to insufficient blocks."""
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckBlocksFitLength,
+                      64, 3, 16, 'foo')
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckBlocksFitLength,
+                      60, 3, 16, 'foo')
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckBlocksFitLength,
+                      49, 3, 16, 'foo')
+    self.assertRaises(update_payload.PayloadError,
+                      checker.PayloadChecker._CheckBlocksFitLength,
+                      48, 2, 16, 'foo')
+
+  def DoCheckManifestTest(self, fail_mismatched_block_size, fail_bad_sigs,
+                          fail_mismatched_oki_ori, fail_bad_oki, fail_bad_ori,
+                          fail_bad_nki, fail_bad_nri, fail_old_kernel_fs_size,
+                          fail_old_rootfs_fs_size, fail_new_kernel_fs_size,
+                          fail_new_rootfs_fs_size):
+    """Parametric testing of _CheckManifest().
+
+    Args:
+      fail_mismatched_block_size: Simulate a missing block_size field.
+      fail_bad_sigs: Make signatures descriptor inconsistent.
+      fail_mismatched_oki_ori: Make old rootfs/kernel info partially present.
+      fail_bad_oki: Tamper with old kernel info.
+      fail_bad_ori: Tamper with old rootfs info.
+      fail_bad_nki: Tamper with new kernel info.
+      fail_bad_nri: Tamper with new rootfs info.
+      fail_old_kernel_fs_size: Make old kernel fs size too big.
+      fail_old_rootfs_fs_size: Make old rootfs fs size too big.
+      fail_new_kernel_fs_size: Make new kernel fs size too big.
+      fail_new_rootfs_fs_size: Make new rootfs fs size too big.
+    """
+    # Generate a test payload. For this test, we only care about the manifest
+    # and don't need any data blobs, hence we can use a plain paylaod generator
+    # (which also gives us more control on things that can be screwed up).
+    payload_gen = test_utils.PayloadGenerator()
+
+    # Tamper with block size, if required.
+    if fail_mismatched_block_size:
+      payload_gen.SetBlockSize(test_utils.KiB(1))
+    else:
+      payload_gen.SetBlockSize(test_utils.KiB(4))
+
+    # Add some operations.
+    payload_gen.AddOperation(False, common.OpType.MOVE,
+                             src_extents=[(0, 16), (16, 497)],
+                             dst_extents=[(16, 496), (0, 16)])
+    payload_gen.AddOperation(True, common.OpType.MOVE,
+                             src_extents=[(0, 8), (8, 8)],
+                             dst_extents=[(8, 8), (0, 8)])
+
+    # Set an invalid signatures block (offset but no size), if required.
+    if fail_bad_sigs:
+      payload_gen.SetSignatures(32, None)
+
+    # Set partition / filesystem sizes.
+    rootfs_part_size = test_utils.MiB(8)
+    kernel_part_size = test_utils.KiB(512)
+    old_rootfs_fs_size = new_rootfs_fs_size = rootfs_part_size
+    old_kernel_fs_size = new_kernel_fs_size = kernel_part_size
+    if fail_old_kernel_fs_size:
+      old_kernel_fs_size += 100
+    if fail_old_rootfs_fs_size:
+      old_rootfs_fs_size += 100
+    if fail_new_kernel_fs_size:
+      new_kernel_fs_size += 100
+    if fail_new_rootfs_fs_size:
+      new_rootfs_fs_size += 100
+
+    # Add old kernel/rootfs partition info, as required.
+    if fail_mismatched_oki_ori or fail_old_kernel_fs_size or fail_bad_oki:
+      oki_hash = (None if fail_bad_oki
+                  else hashlib.sha256('fake-oki-content').digest())
+      payload_gen.SetPartInfo(True, False, old_kernel_fs_size, oki_hash)
+    if not fail_mismatched_oki_ori and (fail_old_rootfs_fs_size or
+                                        fail_bad_ori):
+      ori_hash = (None if fail_bad_ori
+                  else hashlib.sha256('fake-ori-content').digest())
+      payload_gen.SetPartInfo(False, False, old_rootfs_fs_size, ori_hash)
+
+    # Add new kernel/rootfs partition info.
+    payload_gen.SetPartInfo(
+        True, True, new_kernel_fs_size,
+        None if fail_bad_nki else hashlib.sha256('fake-nki-content').digest())
+    payload_gen.SetPartInfo(
+        False, True, new_rootfs_fs_size,
+        None if fail_bad_nri else hashlib.sha256('fake-nri-content').digest())
+
+    # Set the minor version.
+    payload_gen.SetMinorVersion(0)
+
+    # Create the test object.
+    payload_checker = _GetPayloadChecker(payload_gen.WriteToFile)
+    report = checker._PayloadReport()
+
+    should_fail = (fail_mismatched_block_size or fail_bad_sigs or
+                   fail_mismatched_oki_ori or fail_bad_oki or fail_bad_ori or
+                   fail_bad_nki or fail_bad_nri or fail_old_kernel_fs_size or
+                   fail_old_rootfs_fs_size or fail_new_kernel_fs_size or
+                   fail_new_rootfs_fs_size)
+    if should_fail:
+      self.assertRaises(update_payload.PayloadError,
+                        payload_checker._CheckManifest, report,
+                        rootfs_part_size, kernel_part_size)
+    else:
+      self.assertIsNone(payload_checker._CheckManifest(report,
+                                                       rootfs_part_size,
+                                                       kernel_part_size))
+
+  def testCheckLength(self):
+    """Tests _CheckLength()."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    block_size = payload_checker.block_size
+
+    # Passes.
+    self.assertIsNone(payload_checker._CheckLength(
+        int(3.5 * block_size), 4, 'foo', 'bar'))
+    # Fails, too few blocks.
+    self.assertRaises(update_payload.PayloadError,
+                      payload_checker._CheckLength,
+                      int(3.5 * block_size), 3, 'foo', 'bar')
+    # Fails, too many blocks.
+    self.assertRaises(update_payload.PayloadError,
+                      payload_checker._CheckLength,
+                      int(3.5 * block_size), 5, 'foo', 'bar')
+
+  def testCheckExtents(self):
+    """Tests _CheckExtents()."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    block_size = payload_checker.block_size
+
+    # Passes w/ all real extents.
+    extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
+    self.assertEquals(
+        23,
+        payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
+                                      collections.defaultdict(int), 'foo'))
+
+    # Passes w/ pseudo-extents (aka sparse holes).
+    extents = self.NewExtentList((0, 4), (common.PSEUDO_EXTENT_MARKER, 5),
+                                 (8, 3))
+    self.assertEquals(
+        12,
+        payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
+                                      collections.defaultdict(int), 'foo',
+                                      allow_pseudo=True))
+
+    # Passes w/ pseudo-extent due to a signature.
+    extents = self.NewExtentList((common.PSEUDO_EXTENT_MARKER, 2))
+    self.assertEquals(
+        2,
+        payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
+                                      collections.defaultdict(int), 'foo',
+                                      allow_signature=True))
+
+    # Fails, extent missing a start block.
+    extents = self.NewExtentList((-1, 4), (8, 3), (1024, 16))
+    self.assertRaises(
+        update_payload.PayloadError, payload_checker._CheckExtents,
+        extents, (1024 + 16) * block_size, collections.defaultdict(int),
+        'foo')
+
+    # Fails, extent missing block count.
+    extents = self.NewExtentList((0, -1), (8, 3), (1024, 16))
+    self.assertRaises(
+        update_payload.PayloadError, payload_checker._CheckExtents,
+        extents, (1024 + 16) * block_size, collections.defaultdict(int),
+        'foo')
+
+    # Fails, extent has zero blocks.
+    extents = self.NewExtentList((0, 4), (8, 3), (1024, 0))
+    self.assertRaises(
+        update_payload.PayloadError, payload_checker._CheckExtents,
+        extents, (1024 + 16) * block_size, collections.defaultdict(int),
+        'foo')
+
+    # Fails, extent exceeds partition boundaries.
+    extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
+    self.assertRaises(
+        update_payload.PayloadError, payload_checker._CheckExtents,
+        extents, (1024 + 15) * block_size, collections.defaultdict(int),
+        'foo')
+
+  def testCheckReplaceOperation(self):
+    """Tests _CheckReplaceOperation() where op.type == REPLACE."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    block_size = payload_checker.block_size
+    data_length = 10000
+
+    op = self.mox.CreateMock(
+        update_metadata_pb2.InstallOperation)
+    op.type = common.OpType.REPLACE
+
+    # Pass.
+    op.src_extents = []
+    self.assertIsNone(
+        payload_checker._CheckReplaceOperation(
+            op, data_length, (data_length + block_size - 1) / block_size,
+            'foo'))
+
+    # Fail, src extents founds.
+    op.src_extents = ['bar']
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckReplaceOperation,
+        op, data_length, (data_length + block_size - 1) / block_size, 'foo')
+
+    # Fail, missing data.
+    op.src_extents = []
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckReplaceOperation,
+        op, None, (data_length + block_size - 1) / block_size, 'foo')
+
+    # Fail, length / block number mismatch.
+    op.src_extents = ['bar']
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckReplaceOperation,
+        op, data_length, (data_length + block_size - 1) / block_size + 1, 'foo')
+
+  def testCheckReplaceBzOperation(self):
+    """Tests _CheckReplaceOperation() where op.type == REPLACE_BZ."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    block_size = payload_checker.block_size
+    data_length = block_size * 3
+
+    op = self.mox.CreateMock(
+        update_metadata_pb2.InstallOperation)
+    op.type = common.OpType.REPLACE_BZ
+
+    # Pass.
+    op.src_extents = []
+    self.assertIsNone(
+        payload_checker._CheckReplaceOperation(
+            op, data_length, (data_length + block_size - 1) / block_size + 5,
+            'foo'))
+
+    # Fail, src extents founds.
+    op.src_extents = ['bar']
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckReplaceOperation,
+        op, data_length, (data_length + block_size - 1) / block_size + 5, 'foo')
+
+    # Fail, missing data.
+    op.src_extents = []
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckReplaceOperation,
+        op, None, (data_length + block_size - 1) / block_size, 'foo')
+
+    # Fail, too few blocks to justify BZ.
+    op.src_extents = []
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckReplaceOperation,
+        op, data_length, (data_length + block_size - 1) / block_size, 'foo')
+
+  def testCheckMoveOperation_Pass(self):
+    """Tests _CheckMoveOperation(); pass case."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    op = update_metadata_pb2.InstallOperation()
+    op.type = common.OpType.MOVE
+
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((1, 4), (12, 2), (1024, 128)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((16, 128), (512, 6)))
+    self.assertIsNone(
+        payload_checker._CheckMoveOperation(op, None, 134, 134, 'foo'))
+
+  def testCheckMoveOperation_FailContainsData(self):
+    """Tests _CheckMoveOperation(); fails, message contains data."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    op = update_metadata_pb2.InstallOperation()
+    op.type = common.OpType.MOVE
+
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((1, 4), (12, 2), (1024, 128)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((16, 128), (512, 6)))
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckMoveOperation,
+        op, 1024, 134, 134, 'foo')
+
+  def testCheckMoveOperation_FailInsufficientSrcBlocks(self):
+    """Tests _CheckMoveOperation(); fails, not enough actual src blocks."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    op = update_metadata_pb2.InstallOperation()
+    op.type = common.OpType.MOVE
+
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((1, 4), (12, 2), (1024, 127)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((16, 128), (512, 6)))
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckMoveOperation,
+        op, None, 134, 134, 'foo')
+
+  def testCheckMoveOperation_FailInsufficientDstBlocks(self):
+    """Tests _CheckMoveOperation(); fails, not enough actual dst blocks."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    op = update_metadata_pb2.InstallOperation()
+    op.type = common.OpType.MOVE
+
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((1, 4), (12, 2), (1024, 128)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((16, 128), (512, 5)))
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckMoveOperation,
+        op, None, 134, 134, 'foo')
+
+  def testCheckMoveOperation_FailExcessSrcBlocks(self):
+    """Tests _CheckMoveOperation(); fails, too many actual src blocks."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    op = update_metadata_pb2.InstallOperation()
+    op.type = common.OpType.MOVE
+
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((1, 4), (12, 2), (1024, 128)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((16, 128), (512, 5)))
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckMoveOperation,
+        op, None, 134, 134, 'foo')
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((1, 4), (12, 2), (1024, 129)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((16, 128), (512, 6)))
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckMoveOperation,
+        op, None, 134, 134, 'foo')
+
+  def testCheckMoveOperation_FailExcessDstBlocks(self):
+    """Tests _CheckMoveOperation(); fails, too many actual dst blocks."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    op = update_metadata_pb2.InstallOperation()
+    op.type = common.OpType.MOVE
+
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((1, 4), (12, 2), (1024, 128)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((16, 128), (512, 7)))
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckMoveOperation,
+        op, None, 134, 134, 'foo')
+
+  def testCheckMoveOperation_FailStagnantBlocks(self):
+    """Tests _CheckMoveOperation(); fails, there are blocks that do not move."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    op = update_metadata_pb2.InstallOperation()
+    op.type = common.OpType.MOVE
+
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((1, 4), (12, 2), (1024, 128)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((8, 128), (512, 6)))
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckMoveOperation,
+        op, None, 134, 134, 'foo')
+
+  def testCheckMoveOperation_FailZeroStartBlock(self):
+    """Tests _CheckMoveOperation(); fails, has extent with start block 0."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    op = update_metadata_pb2.InstallOperation()
+    op.type = common.OpType.MOVE
+
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((0, 4), (12, 2), (1024, 128)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((8, 128), (512, 6)))
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckMoveOperation,
+        op, None, 134, 134, 'foo')
+
+    self.AddToMessage(op.src_extents,
+                      self.NewExtentList((1, 4), (12, 2), (1024, 128)))
+    self.AddToMessage(op.dst_extents,
+                      self.NewExtentList((0, 128), (512, 6)))
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckMoveOperation,
+        op, None, 134, 134, 'foo')
+
+  def testCheckAnyDiff(self):
+    """Tests _CheckAnyDiffOperation()."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+
+    # Pass.
+    self.assertIsNone(
+        payload_checker._CheckAnyDiffOperation(10000, 3, 'foo'))
+
+    # Fail, missing data blob.
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckAnyDiffOperation,
+        None, 3, 'foo')
+
+    # Fail, too big of a diff blob (unjustified).
+    self.assertRaises(
+        update_payload.PayloadError,
+        payload_checker._CheckAnyDiffOperation,
+        10000, 2, 'foo')
+
+  def testCheckSourceCopyOperation_Pass(self):
+    """Tests _CheckSourceCopyOperation(); pass case."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    self.assertIsNone(
+        payload_checker._CheckSourceCopyOperation(None, 134, 134, 'foo'))
+
+  def testCheckSourceCopyOperation_FailContainsData(self):
+    """Tests _CheckSourceCopyOperation(); message contains data."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    self.assertRaises(update_payload.PayloadError,
+                      payload_checker._CheckSourceCopyOperation,
+                      134, 0, 0, 'foo')
+
+  def testCheckSourceCopyOperation_FailBlockCountsMismatch(self):
+    """Tests _CheckSourceCopyOperation(); src and dst block totals not equal."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    self.assertRaises(update_payload.PayloadError,
+                      payload_checker._CheckSourceCopyOperation,
+                      None, 0, 1, 'foo')
+
+  def DoCheckOperationTest(self, op_type_name, is_last, allow_signature,
+                           allow_unhashed, fail_src_extents, fail_dst_extents,
+                           fail_mismatched_data_offset_length,
+                           fail_missing_dst_extents, fail_src_length,
+                           fail_dst_length, fail_data_hash,
+                           fail_prev_data_offset, fail_bad_minor_version):
+    """Parametric testing of _CheckOperation().
+
+    Args:
+      op_type_name: 'REPLACE', 'REPLACE_BZ', 'MOVE', 'BSDIFF', 'SOURCE_COPY',
+        or 'SOURCE_BSDIFF'.
+      is_last: Whether we're testing the last operation in a sequence.
+      allow_signature: Whether we're testing a signature-capable operation.
+      allow_unhashed: Whether we're allowing to not hash the data.
+      fail_src_extents: Tamper with src extents.
+      fail_dst_extents: Tamper with dst extents.
+      fail_mismatched_data_offset_length: Make data_{offset,length}
+        inconsistent.
+      fail_missing_dst_extents: Do not include dst extents.
+      fail_src_length: Make src length inconsistent.
+      fail_dst_length: Make dst length inconsistent.
+      fail_data_hash: Tamper with the data blob hash.
+      fail_prev_data_offset: Make data space uses incontiguous.
+      fail_bad_minor_version: Make minor version incompatible with op.
+    """
+    op_type = _OpTypeByName(op_type_name)
+
+    # Create the test object.
+    payload = self.MockPayload()
+    payload_checker = checker.PayloadChecker(payload,
+                                             allow_unhashed=allow_unhashed)
+    block_size = payload_checker.block_size
+
+    # Create auxiliary arguments.
+    old_part_size = test_utils.MiB(4)
+    new_part_size = test_utils.MiB(8)
+    old_block_counters = array.array(
+        'B', [0] * ((old_part_size + block_size - 1) / block_size))
+    new_block_counters = array.array(
+        'B', [0] * ((new_part_size + block_size - 1) / block_size))
+    prev_data_offset = 1876
+    blob_hash_counts = collections.defaultdict(int)
+
+    # Create the operation object for the test.
+    op = update_metadata_pb2.InstallOperation()
+    op.type = op_type
+
+    total_src_blocks = 0
+    if op_type in (common.OpType.MOVE, common.OpType.BSDIFF,
+                   common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF):
+      if fail_src_extents:
+        self.AddToMessage(op.src_extents,
+                          self.NewExtentList((1, 0)))
+      else:
+        self.AddToMessage(op.src_extents,
+                          self.NewExtentList((1, 16)))
+        total_src_blocks = 16
+
+    if op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
+      payload_checker.minor_version = 0
+    elif op_type in (common.OpType.MOVE, common.OpType.BSDIFF):
+      payload_checker.minor_version = 2 if fail_bad_minor_version else 1
+    elif op_type in (common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF):
+      payload_checker.minor_version = 1 if fail_bad_minor_version else 2
+
+    if op_type not in (common.OpType.MOVE, common.OpType.SOURCE_COPY):
+      if not fail_mismatched_data_offset_length:
+        op.data_length = 16 * block_size - 8
+      if fail_prev_data_offset:
+        op.data_offset = prev_data_offset + 16
+      else:
+        op.data_offset = prev_data_offset
+
+      fake_data = 'fake-data'.ljust(op.data_length)
+      if not (allow_unhashed or (is_last and allow_signature and
+                                 op_type == common.OpType.REPLACE)):
+        if not fail_data_hash:
+          # Create a valid data blob hash.
+          op.data_sha256_hash = hashlib.sha256(fake_data).digest()
+          payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
+              fake_data)
+      elif fail_data_hash:
+        # Create an invalid data blob hash.
+        op.data_sha256_hash = hashlib.sha256(
+            fake_data.replace(' ', '-')).digest()
+        payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
+            fake_data)
+
+    total_dst_blocks = 0
+    if not fail_missing_dst_extents:
+      total_dst_blocks = 16
+      if fail_dst_extents:
+        self.AddToMessage(op.dst_extents,
+                          self.NewExtentList((4, 16), (32, 0)))
+      else:
+        self.AddToMessage(op.dst_extents,
+                          self.NewExtentList((4, 8), (64, 8)))
+
+    if total_src_blocks:
+      if fail_src_length:
+        op.src_length = total_src_blocks * block_size + 8
+      else:
+        op.src_length = total_src_blocks * block_size
+    elif fail_src_length:
+      # Add an orphaned src_length.
+      op.src_length = 16
+
+    if total_dst_blocks:
+      if fail_dst_length:
+        op.dst_length = total_dst_blocks * block_size + 8
+      else:
+        op.dst_length = total_dst_blocks * block_size
+
+    self.mox.ReplayAll()
+    should_fail = (fail_src_extents or fail_dst_extents or
+                   fail_mismatched_data_offset_length or
+                   fail_missing_dst_extents or fail_src_length or
+                   fail_dst_length or fail_data_hash or fail_prev_data_offset or
+                   fail_bad_minor_version)
+    args = (op, 'foo', is_last, old_block_counters, new_block_counters,
+            old_part_size, new_part_size, prev_data_offset, allow_signature,
+            blob_hash_counts)
+    if should_fail:
+      self.assertRaises(update_payload.PayloadError,
+                        payload_checker._CheckOperation, *args)
+    else:
+      self.assertEqual(op.data_length if op.HasField('data_length') else 0,
+                       payload_checker._CheckOperation(*args))
+
+  def testAllocBlockCounters(self):
+    """Tests _CheckMoveOperation()."""
+    payload_checker = checker.PayloadChecker(self.MockPayload())
+    block_size = payload_checker.block_size
+
+    # Check allocation for block-aligned partition size, ensure it's integers.
+    result = payload_checker._AllocBlockCounters(16 * block_size)
+    self.assertEqual(16, len(result))
+    self.assertEqual(int, type(result[0]))
+
+    # Check allocation of unaligned partition sizes.
+    result = payload_checker._AllocBlockCounters(16 * block_size - 1)
+    self.assertEqual(16, len(result))
+    result = payload_checker._AllocBlockCounters(16 * block_size + 1)
+    self.assertEqual(17, len(result))
+
+  def DoCheckOperationsTest(self, fail_nonexhaustive_full_update):
+    # Generate a test payload. For this test, we only care about one
+    # (arbitrary) set of operations, so we'll only be generating kernel and
+    # test with them.
+    payload_gen = test_utils.PayloadGenerator()
+
+    block_size = test_utils.KiB(4)
+    payload_gen.SetBlockSize(block_size)
+
+    rootfs_part_size = test_utils.MiB(8)
+
+    # Fake rootfs operations in a full update, tampered with as required.
+    rootfs_op_type = common.OpType.REPLACE
+    rootfs_data_length = rootfs_part_size
+    if fail_nonexhaustive_full_update:
+      rootfs_data_length -= block_size
+
+    payload_gen.AddOperation(False, rootfs_op_type,
+                             dst_extents=[(0, rootfs_data_length / block_size)],
+                             data_offset=0,
+                             data_length=rootfs_data_length)
+
+    # Create the test object.
+    payload_checker = _GetPayloadChecker(payload_gen.WriteToFile,
+                                         checker_init_dargs={
+                                             'allow_unhashed': True})
+    payload_checker.payload_type = checker._TYPE_FULL
+    report = checker._PayloadReport()
+
+    args = (payload_checker.payload.manifest.install_operations, report,
+            'foo', 0, rootfs_part_size, rootfs_part_size, 0, False)
+    if fail_nonexhaustive_full_update:
+      self.assertRaises(update_payload.PayloadError,
+                        payload_checker._CheckOperations, *args)
+    else:
+      self.assertEqual(rootfs_data_length,
+                       payload_checker._CheckOperations(*args))
+
+  def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_missing_pseudo_op,
+                            fail_mismatched_pseudo_op, fail_sig_missing_fields,
+                            fail_unknown_sig_version, fail_incorrect_sig):
+    # Generate a test payload. For this test, we only care about the signature
+    # block and how it relates to the payload hash. Therefore, we're generating
+    # a random (otherwise useless) payload for this purpose.
+    payload_gen = test_utils.EnhancedPayloadGenerator()
+    block_size = test_utils.KiB(4)
+    payload_gen.SetBlockSize(block_size)
+    rootfs_part_size = test_utils.MiB(2)
+    kernel_part_size = test_utils.KiB(16)
+    payload_gen.SetPartInfo(False, True, rootfs_part_size,
+                            hashlib.sha256('fake-new-rootfs-content').digest())
+    payload_gen.SetPartInfo(True, True, kernel_part_size,
+                            hashlib.sha256('fake-new-kernel-content').digest())
+    payload_gen.SetMinorVersion(0)
+    payload_gen.AddOperationWithData(
+        False, common.OpType.REPLACE,
+        dst_extents=[(0, rootfs_part_size / block_size)],
+        data_blob=os.urandom(rootfs_part_size))
+
+    do_forge_pseudo_op = (fail_missing_pseudo_op or fail_mismatched_pseudo_op)
+    do_forge_sigs_data = (do_forge_pseudo_op or fail_empty_sigs_blob or
+                          fail_sig_missing_fields or fail_unknown_sig_version
+                          or fail_incorrect_sig)
+
+    sigs_data = None
+    if do_forge_sigs_data:
+      sigs_gen = test_utils.SignaturesGenerator()
+      if not fail_empty_sigs_blob:
+        if fail_sig_missing_fields:
+          sig_data = None
+        else:
+          sig_data = test_utils.SignSha256('fake-payload-content',
+                                           test_utils._PRIVKEY_FILE_NAME)
+        sigs_gen.AddSig(5 if fail_unknown_sig_version else 1, sig_data)
+
+      sigs_data = sigs_gen.ToBinary()
+      payload_gen.SetSignatures(payload_gen.curr_offset, len(sigs_data))
+
+    if do_forge_pseudo_op:
+      assert sigs_data is not None, 'should have forged signatures blob by now'
+      sigs_len = len(sigs_data)
+      payload_gen.AddOperation(
+          False, common.OpType.REPLACE,
+          data_offset=payload_gen.curr_offset / 2,
+          data_length=sigs_len / 2,
+          dst_extents=[(0, (sigs_len / 2 + block_size - 1) / block_size)])
+
+    # Generate payload (complete w/ signature) and create the test object.
+    payload_checker = _GetPayloadChecker(
+        payload_gen.WriteToFileWithData,
+        payload_gen_dargs={
+            'sigs_data': sigs_data,
+            'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
+            'do_add_pseudo_operation': not do_forge_pseudo_op})
+    payload_checker.payload_type = checker._TYPE_FULL
+    report = checker._PayloadReport()
+
+    # We have to check the manifest first in order to set signature attributes.
+    payload_checker._CheckManifest(report, rootfs_part_size, kernel_part_size)
+
+    should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or
+                   fail_mismatched_pseudo_op or fail_sig_missing_fields or
+                   fail_unknown_sig_version or fail_incorrect_sig)
+    args = (report, test_utils._PUBKEY_FILE_NAME)
+    if should_fail:
+      self.assertRaises(update_payload.PayloadError,
+                        payload_checker._CheckSignatures, *args)
+    else:
+      self.assertIsNone(payload_checker._CheckSignatures(*args))
+
+  def DoCheckManifestMinorVersionTest(self, minor_version, payload_type):
+    """Parametric testing for CheckManifestMinorVersion().
+
+    Args:
+      minor_version: The payload minor version to test with.
+      payload_type: The type of the payload we're testing, delta or full.
+    """
+    # Create the test object.
+    payload = self.MockPayload()
+    payload.manifest.minor_version = minor_version
+    payload_checker = checker.PayloadChecker(payload)
+    payload_checker.payload_type = payload_type
+    report = checker._PayloadReport()
+
+    should_succeed = (
+        (minor_version == 0 and payload_type == checker._TYPE_FULL) or
+        (minor_version == 1 and payload_type == checker._TYPE_DELTA) or
+        (minor_version == 2 and payload_type == checker._TYPE_DELTA) or
+        (minor_version == 3 and payload_type == checker._TYPE_DELTA) or
+        (minor_version == 4 and payload_type == checker._TYPE_DELTA))
+    args = (report,)
+
+    if should_succeed:
+      self.assertIsNone(payload_checker._CheckManifestMinorVersion(*args))
+    else:
+      self.assertRaises(update_payload.PayloadError,
+                        payload_checker._CheckManifestMinorVersion, *args)
+
+  def DoRunTest(self, rootfs_part_size_provided, kernel_part_size_provided,
+                fail_wrong_payload_type, fail_invalid_block_size,
+                fail_mismatched_block_size, fail_excess_data,
+                fail_rootfs_part_size_exceeded,
+                fail_kernel_part_size_exceeded):
+    # Generate a test payload. For this test, we generate a full update that
+    # has sample kernel and rootfs operations. Since most testing is done with
+    # internal PayloadChecker methods that are tested elsewhere, here we only
+    # tamper with what's actually being manipulated and/or tested in the Run()
+    # method itself. Note that the checker doesn't verify partition hashes, so
+    # they're safe to fake.
+    payload_gen = test_utils.EnhancedPayloadGenerator()
+    block_size = test_utils.KiB(4)
+    payload_gen.SetBlockSize(block_size)
+    kernel_filesystem_size = test_utils.KiB(16)
+    rootfs_filesystem_size = test_utils.MiB(2)
+    payload_gen.SetPartInfo(False, True, rootfs_filesystem_size,
+                            hashlib.sha256('fake-new-rootfs-content').digest())
+    payload_gen.SetPartInfo(True, True, kernel_filesystem_size,
+                            hashlib.sha256('fake-new-kernel-content').digest())
+    payload_gen.SetMinorVersion(0)
+
+    rootfs_part_size = 0
+    if rootfs_part_size_provided:
+      rootfs_part_size = rootfs_filesystem_size + block_size
+    rootfs_op_size = rootfs_part_size or rootfs_filesystem_size
+    if fail_rootfs_part_size_exceeded:
+      rootfs_op_size += block_size
+    payload_gen.AddOperationWithData(
+        False, common.OpType.REPLACE,
+        dst_extents=[(0, rootfs_op_size / block_size)],
+        data_blob=os.urandom(rootfs_op_size))
+
+    kernel_part_size = 0
+    if kernel_part_size_provided:
+      kernel_part_size = kernel_filesystem_size + block_size
+    kernel_op_size = kernel_part_size or kernel_filesystem_size
+    if fail_kernel_part_size_exceeded:
+      kernel_op_size += block_size
+    payload_gen.AddOperationWithData(
+        True, common.OpType.REPLACE,
+        dst_extents=[(0, kernel_op_size / block_size)],
+        data_blob=os.urandom(kernel_op_size))
+
+    # Generate payload (complete w/ signature) and create the test object.
+    if fail_invalid_block_size:
+      use_block_size = block_size + 5  # Not a power of two.
+    elif fail_mismatched_block_size:
+      use_block_size = block_size * 2  # Different that payload stated.
+    else:
+      use_block_size = block_size
+
+    kwargs = {
+        'payload_gen_dargs': {
+            'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
+            'do_add_pseudo_operation': True,
+            'is_pseudo_in_kernel': True,
+            'padding': os.urandom(1024) if fail_excess_data else None},
+        'checker_init_dargs': {
+            'assert_type': 'delta' if fail_wrong_payload_type else 'full',
+            'block_size': use_block_size}}
+    if fail_invalid_block_size:
+      self.assertRaises(update_payload.PayloadError, _GetPayloadChecker,
+                        payload_gen.WriteToFileWithData, **kwargs)
+    else:
+      payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData,
+                                           **kwargs)
+
+      kwargs = {'pubkey_file_name': test_utils._PUBKEY_FILE_NAME,
+                'rootfs_part_size': rootfs_part_size,
+                'kernel_part_size': kernel_part_size}
+      should_fail = (fail_wrong_payload_type or fail_mismatched_block_size or
+                     fail_excess_data or
+                     fail_rootfs_part_size_exceeded or
+                     fail_kernel_part_size_exceeded)
+      if should_fail:
+        self.assertRaises(update_payload.PayloadError, payload_checker.Run,
+                          **kwargs)
+      else:
+        self.assertIsNone(payload_checker.Run(**kwargs))
+
+# This implements a generic API, hence the occasional unused args.
+# pylint: disable=W0613
+def ValidateCheckOperationTest(op_type_name, is_last, allow_signature,
+                               allow_unhashed, fail_src_extents,
+                               fail_dst_extents,
+                               fail_mismatched_data_offset_length,
+                               fail_missing_dst_extents, fail_src_length,
+                               fail_dst_length, fail_data_hash,
+                               fail_prev_data_offset, fail_bad_minor_version):
+  """Returns True iff the combination of arguments represents a valid test."""
+  op_type = _OpTypeByName(op_type_name)
+
+  # REPLACE/REPLACE_BZ operations don't read data from src partition. They are
+  # compatible with all valid minor versions, so we don't need to check that.
+  if (op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ) and (
+      fail_src_extents or fail_src_length or fail_bad_minor_version)):
+    return False
+
+  # MOVE and SOURCE_COPY operations don't carry data.
+  if (op_type in (common.OpType.MOVE, common.OpType.SOURCE_COPY) and (
+      fail_mismatched_data_offset_length or fail_data_hash or
+      fail_prev_data_offset)):
+    return False
+
+  return True
+
+
+def TestMethodBody(run_method_name, run_dargs):
+  """Returns a function that invokes a named method with named arguments."""
+  return lambda self: getattr(self, run_method_name)(**run_dargs)
+
+
+def AddParametricTests(tested_method_name, arg_space, validate_func=None):
+  """Enumerates and adds specific parametric tests to PayloadCheckerTest.
+
+  This function enumerates a space of test parameters (defined by arg_space),
+  then binds a new, unique method name in PayloadCheckerTest to a test function
+  that gets handed the said parameters. This is a preferable approach to doing
+  the enumeration and invocation during the tests because this way each test is
+  treated as a complete run by the unittest framework, and so benefits from the
+  usual setUp/tearDown mechanics.
+
+  Args:
+    tested_method_name: Name of the tested PayloadChecker method.
+    arg_space: A dictionary containing variables (keys) and lists of values
+               (values) associated with them.
+    validate_func: A function used for validating test argument combinations.
+  """
+  for value_tuple in itertools.product(*arg_space.itervalues()):
+    run_dargs = dict(zip(arg_space.iterkeys(), value_tuple))
+    if validate_func and not validate_func(**run_dargs):
+      continue
+    run_method_name = 'Do%sTest' % tested_method_name
+    test_method_name = 'test%s' % tested_method_name
+    for arg_key, arg_val in run_dargs.iteritems():
+      if arg_val or type(arg_val) is int:
+        test_method_name += '__%s=%s' % (arg_key, arg_val)
+    setattr(PayloadCheckerTest, test_method_name,
+            TestMethodBody(run_method_name, run_dargs))
+
+
+def AddAllParametricTests():
+  """Enumerates and adds all parametric tests to PayloadCheckerTest."""
+  # Add all _CheckElem() test cases.
+  AddParametricTests('AddElem',
+                     {'linebreak': (True, False),
+                      'indent': (0, 1, 2),
+                      'convert': (str, lambda s: s[::-1]),
+                      'is_present': (True, False),
+                      'is_mandatory': (True, False),
+                      'is_submsg': (True, False)})
+
+  # Add all _Add{Mandatory,Optional}Field tests.
+  AddParametricTests('AddField',
+                     {'is_mandatory': (True, False),
+                      'linebreak': (True, False),
+                      'indent': (0, 1, 2),
+                      'convert': (str, lambda s: s[::-1]),
+                      'is_present': (True, False)})
+
+  # Add all _Add{Mandatory,Optional}SubMsg tests.
+  AddParametricTests('AddSubMsg',
+                     {'is_mandatory': (True, False),
+                      'is_present': (True, False)})
+
+  # Add all _CheckManifest() test cases.
+  AddParametricTests('CheckManifest',
+                     {'fail_mismatched_block_size': (True, False),
+                      'fail_bad_sigs': (True, False),
+                      'fail_mismatched_oki_ori': (True, False),
+                      'fail_bad_oki': (True, False),
+                      'fail_bad_ori': (True, False),
+                      'fail_bad_nki': (True, False),
+                      'fail_bad_nri': (True, False),
+                      'fail_old_kernel_fs_size': (True, False),
+                      'fail_old_rootfs_fs_size': (True, False),
+                      'fail_new_kernel_fs_size': (True, False),
+                      'fail_new_rootfs_fs_size': (True, False)})
+
+  # Add all _CheckOperation() test cases.
+  AddParametricTests('CheckOperation',
+                     {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'MOVE',
+                                       'BSDIFF', 'SOURCE_COPY',
+                                       'SOURCE_BSDIFF'),
+                      'is_last': (True, False),
+                      'allow_signature': (True, False),
+                      'allow_unhashed': (True, False),
+                      'fail_src_extents': (True, False),
+                      'fail_dst_extents': (True, False),
+                      'fail_mismatched_data_offset_length': (True, False),
+                      'fail_missing_dst_extents': (True, False),
+                      'fail_src_length': (True, False),
+                      'fail_dst_length': (True, False),
+                      'fail_data_hash': (True, False),
+                      'fail_prev_data_offset': (True, False),
+                      'fail_bad_minor_version': (True, False)},
+                     validate_func=ValidateCheckOperationTest)
+
+  # Add all _CheckOperations() test cases.
+  AddParametricTests('CheckOperations',
+                     {'fail_nonexhaustive_full_update': (True, False)})
+
+  # Add all _CheckOperations() test cases.
+  AddParametricTests('CheckSignatures',
+                     {'fail_empty_sigs_blob': (True, False),
+                      'fail_missing_pseudo_op': (True, False),
+                      'fail_mismatched_pseudo_op': (True, False),
+                      'fail_sig_missing_fields': (True, False),
+                      'fail_unknown_sig_version': (True, False),
+                      'fail_incorrect_sig': (True, False)})
+
+  # Add all _CheckManifestMinorVersion() test cases.
+  AddParametricTests('CheckManifestMinorVersion',
+                     {'minor_version': (None, 0, 1, 2, 3, 4, 555),
+                      'payload_type': (checker._TYPE_FULL,
+                                       checker._TYPE_DELTA)})
+
+  # Add all Run() test cases.
+  AddParametricTests('Run',
+                     {'rootfs_part_size_provided': (True, False),
+                      'kernel_part_size_provided': (True, False),
+                      'fail_wrong_payload_type': (True, False),
+                      'fail_invalid_block_size': (True, False),
+                      'fail_mismatched_block_size': (True, False),
+                      'fail_excess_data': (True, False),
+                      'fail_rootfs_part_size_exceeded': (True, False),
+                      'fail_kernel_part_size_exceeded': (True, False)})
+
+
+if __name__ == '__main__':
+  AddAllParametricTests()
+  unittest.main()
diff --git a/update_engine/scripts/update_payload/common.py b/update_engine/scripts/update_payload/common.py
new file mode 100644
index 0000000..678fc5d
--- /dev/null
+++ b/update_engine/scripts/update_payload/common.py
@@ -0,0 +1,204 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Utilities for update payload processing."""
+
+from __future__ import print_function
+
+from error import PayloadError
+import update_metadata_pb2
+
+
+#
+# Constants.
+#
+PSEUDO_EXTENT_MARKER = (1L << 64) - 1  # UINT64_MAX
+
+SIG_ASN1_HEADER = (
+    '\x30\x31\x30\x0d\x06\x09\x60\x86'
+    '\x48\x01\x65\x03\x04\x02\x01\x05'
+    '\x00\x04\x20'
+)
+
+CHROMEOS_MAJOR_PAYLOAD_VERSION = 1
+BRILLO_MAJOR_PAYLOAD_VERSION = 2
+
+INPLACE_MINOR_PAYLOAD_VERSION = 1
+SOURCE_MINOR_PAYLOAD_VERSION = 2
+OPSRCHASH_MINOR_PAYLOAD_VERSION = 3
+IMGDIFF_MINOR_PAYLOAD_VERSION = 4
+
+#
+# Payload operation types.
+#
+class OpType(object):
+  """Container for operation type constants."""
+  _CLASS = update_metadata_pb2.InstallOperation
+  # pylint: disable=E1101
+  REPLACE = _CLASS.REPLACE
+  REPLACE_BZ = _CLASS.REPLACE_BZ
+  MOVE = _CLASS.MOVE
+  BSDIFF = _CLASS.BSDIFF
+  SOURCE_COPY = _CLASS.SOURCE_COPY
+  SOURCE_BSDIFF = _CLASS.SOURCE_BSDIFF
+  ZERO = _CLASS.ZERO
+  DISCARD = _CLASS.DISCARD
+  REPLACE_XZ = _CLASS.REPLACE_XZ
+  IMGDIFF = _CLASS.IMGDIFF
+  ALL = (REPLACE, REPLACE_BZ, MOVE, BSDIFF, SOURCE_COPY, SOURCE_BSDIFF, ZERO,
+         DISCARD, REPLACE_XZ, IMGDIFF)
+  NAMES = {
+      REPLACE: 'REPLACE',
+      REPLACE_BZ: 'REPLACE_BZ',
+      MOVE: 'MOVE',
+      BSDIFF: 'BSDIFF',
+      SOURCE_COPY: 'SOURCE_COPY',
+      SOURCE_BSDIFF: 'SOURCE_BSDIFF',
+      ZERO: 'ZERO',
+      DISCARD: 'DISCARD',
+      REPLACE_XZ: 'REPLACE_XZ',
+      IMGDIFF: 'IMGDIFF',
+  }
+
+  def __init__(self):
+    pass
+
+
+#
+# Checked and hashed reading of data.
+#
+def IntPackingFmtStr(size, is_unsigned):
+  """Returns an integer format string for use by the struct module.
+
+  Args:
+    size: the integer size in bytes (2, 4 or 8)
+    is_unsigned: whether it is signed or not
+
+  Returns:
+    A format string for packing/unpacking integer values; assumes network byte
+    order (big-endian).
+
+  Raises:
+    PayloadError if something is wrong with the arguments.
+  """
+  # Determine the base conversion format.
+  if size == 2:
+    fmt = 'h'
+  elif size == 4:
+    fmt = 'i'
+  elif size == 8:
+    fmt = 'q'
+  else:
+    raise PayloadError('unsupport numeric field size (%s)' % size)
+
+  # Signed or unsigned?
+  if is_unsigned:
+    fmt = fmt.upper()
+
+  # Make it network byte order (big-endian).
+  fmt = '!' + fmt
+
+  return fmt
+
+
+def Read(file_obj, length, offset=None, hasher=None):
+  """Reads binary data from a file.
+
+  Args:
+    file_obj: an open file object
+    length: the length of the data to read
+    offset: an offset to seek to prior to reading; this is an absolute offset
+            from either the beginning (non-negative) or end (negative) of the
+            file.  (optional)
+    hasher: a hashing object to pass the read data through (optional)
+
+  Returns:
+    A string containing the read data.
+
+  Raises:
+    PayloadError if a read error occurred or not enough data was read.
+  """
+  if offset is not None:
+    if offset >= 0:
+      file_obj.seek(offset)
+    else:
+      file_obj.seek(offset, 2)
+
+  try:
+    data = file_obj.read(length)
+  except IOError, e:
+    raise PayloadError('error reading from file (%s): %s' % (file_obj.name, e))
+
+  if len(data) != length:
+    raise PayloadError(
+        'reading from file (%s) too short (%d instead of %d bytes)' %
+        (file_obj.name, len(data), length))
+
+  if hasher:
+    hasher.update(data)
+
+  return data
+
+
+#
+# Formatting functions.
+#
+def FormatExtent(ex, block_size=0):
+  end_block = ex.start_block + ex.num_blocks
+  if block_size:
+    return '%d->%d * %d' % (ex.start_block, end_block, block_size)
+  else:
+    return '%d->%d' % (ex.start_block, end_block)
+
+
+def FormatSha256(digest):
+  """Returns a canonical string representation of a SHA256 digest."""
+  return digest.encode('base64').strip()
+
+
+#
+# Useful iterators.
+#
+def _ObjNameIter(items, base_name, reverse=False, name_format_func=None):
+  """A generic (item, name) tuple iterators.
+
+  Args:
+    items: the sequence of objects to iterate on
+    base_name: the base name for all objects
+    reverse: whether iteration should be in reverse order
+    name_format_func: a function to apply to the name string
+
+  Yields:
+    An iterator whose i-th invocation returns (items[i], name), where name ==
+    base_name + '[i]' (with a formatting function optionally applied to it).
+  """
+  idx, inc = (len(items), -1) if reverse else (1, 1)
+  if reverse:
+    items = reversed(items)
+  for item in items:
+    item_name = '%s[%d]' % (base_name, idx)
+    if name_format_func:
+      item_name = name_format_func(item, item_name)
+    yield (item, item_name)
+    idx += inc
+
+
+def _OperationNameFormatter(op, op_name):
+  return '%s(%s)' % (op_name, OpType.NAMES.get(op.type, '?'))
+
+
+def OperationIter(operations, base_name, reverse=False):
+  """An (item, name) iterator for update operations."""
+  return _ObjNameIter(operations, base_name, reverse=reverse,
+                      name_format_func=_OperationNameFormatter)
+
+
+def ExtentIter(extents, base_name, reverse=False):
+  """An (item, name) iterator for operation extents."""
+  return _ObjNameIter(extents, base_name, reverse=reverse)
+
+
+def SignatureIter(sigs, base_name, reverse=False):
+  """An (item, name) iterator for signatures."""
+  return _ObjNameIter(sigs, base_name, reverse=reverse)
diff --git a/update_engine/scripts/update_payload/error.py b/update_engine/scripts/update_payload/error.py
new file mode 100644
index 0000000..8b9cadd
--- /dev/null
+++ b/update_engine/scripts/update_payload/error.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Payload handling errors."""
+
+
+class PayloadError(Exception):
+  """An update payload general processing error."""
diff --git a/update_engine/scripts/update_payload/format_utils.py b/update_engine/scripts/update_payload/format_utils.py
new file mode 100644
index 0000000..2c3775c
--- /dev/null
+++ b/update_engine/scripts/update_payload/format_utils.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Various formatting functions."""
+
+
+def NumToPercent(num, total, min_precision=1, max_precision=5):
+  """Returns the percentage (string) of |num| out of |total|.
+
+  If the percentage includes a fraction, it will be computed down to the least
+  precision that yields a non-zero and ranging between |min_precision| and
+  |max_precision|. Values are always rounded down. All arithmetic operations
+  are integer built-ins. Examples (using default precision):
+
+    (1, 1) => 100%
+    (3, 10) => 30%
+    (3, 9) => 33.3%
+    (3, 900) => 0.3%
+    (3, 9000000) => 0.00003%
+    (3, 900000000) => 0%
+    (5, 2) => 250%
+
+  Args:
+    num: the value of the part
+    total: the value of the whole
+    min_precision: minimum precision for fractional percentage
+    max_precision: maximum precision for fractional percentage
+  Returns:
+    Percentage string, or None if percent cannot be computed (i.e. total is
+    zero).
+
+  """
+  if total == 0:
+    return None
+
+  percent = 0
+  precision = min(min_precision, max_precision)
+  factor = 10 ** precision
+  while precision <= max_precision:
+    percent = num * 100 * factor / total
+    if percent:
+      break
+    factor *= 10
+    precision += 1
+
+  whole, frac = divmod(percent, factor)
+  while frac and not frac % 10:
+    frac /= 10
+    precision -= 1
+
+  return '%d%s%%' % (whole, '.%0*d' % (precision, frac) if frac else '')
+
+
+def BytesToHumanReadable(size, precision=1, decimal=False):
+  """Returns a human readable representation of a given |size|.
+
+  The returned string includes unit notations in either binary (KiB, MiB, etc)
+  or decimal (kB, MB, etc), based on the value of |decimal|. The chosen unit is
+  the largest that yields a whole (or mixed) number. It may contain up to
+  |precision| fractional digits. Values are always rounded down. Largest unit
+  is an exabyte. All arithmetic operations are integer built-ins. Examples
+  (using default precision and binary units):
+
+    4096 => 4 KiB
+    5000 => 4.8 KiB
+    500000 => 488.2 KiB
+    5000000 => 4.7 MiB
+
+  Args:
+    size: the size in bytes
+    precision: the number of digits past the decimal point
+    decimal: whether to compute/present decimal or binary units
+  Returns:
+    Readable size string, or None if no conversion is applicable (i.e. size is
+    less than the smallest unit).
+
+  """
+  constants = (
+      (('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'), 1024),
+      (('kB', 'MB', 'GB', 'TB', 'PB', 'EB'), 1000)
+  )
+  suffixes, base = constants[decimal]
+  exp, magnitude = 0, 1
+  while exp < len(suffixes):
+    next_magnitude = magnitude * base
+    if size < next_magnitude:
+      break
+    exp += 1
+    magnitude = next_magnitude
+
+  if exp != 0:
+    whole = size / magnitude
+    frac = (size % magnitude) * (10 ** precision) / magnitude
+    while frac and not frac % 10:
+      frac /= 10
+    return '%d%s %s' % (whole, '.%d' % frac if frac else '', suffixes[exp - 1])
diff --git a/update_engine/scripts/update_payload/format_utils_unittest.py b/update_engine/scripts/update_payload/format_utils_unittest.py
new file mode 100755
index 0000000..8c5ba8e
--- /dev/null
+++ b/update_engine/scripts/update_payload/format_utils_unittest.py
@@ -0,0 +1,76 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for format_utils.py."""
+
+import unittest
+
+import format_utils
+
+
+class NumToPercentTest(unittest.TestCase):
+  def testHundredPercent(self):
+    self.assertEqual(format_utils.NumToPercent(1, 1), '100%')
+
+  def testOverHundredPercent(self):
+    self.assertEqual(format_utils.NumToPercent(5, 2), '250%')
+
+  def testWholePercent(self):
+    self.assertEqual(format_utils.NumToPercent(3, 10), '30%')
+
+  def testDefaultMinPrecision(self):
+    self.assertEqual(format_utils.NumToPercent(3, 9), '33.3%')
+    self.assertEqual(format_utils.NumToPercent(3, 900), '0.3%')
+
+  def testDefaultMaxPrecision(self):
+    self.assertEqual(format_utils.NumToPercent(3, 9000000), '0.00003%')
+    self.assertEqual(format_utils.NumToPercent(3, 90000000), '0%')
+
+  def testCustomMinPrecision(self):
+    self.assertEqual(format_utils.NumToPercent(3, 9, min_precision=3),
+                     '33.333%')
+    self.assertEqual(format_utils.NumToPercent(3, 9, min_precision=0),
+                     '33%')
+
+  def testCustomMaxPrecision(self):
+    self.assertEqual(format_utils.NumToPercent(3, 900, max_precision=1),
+                     '0.3%')
+    self.assertEqual(format_utils.NumToPercent(3, 9000, max_precision=1),
+                     '0%')
+
+
+class BytesToHumanReadableTest(unittest.TestCase):
+  def testBaseTwo(self):
+    self.assertEqual(format_utils.BytesToHumanReadable(0x1000), '4 KiB')
+    self.assertEqual(format_utils.BytesToHumanReadable(0x400000), '4 MiB')
+    self.assertEqual(format_utils.BytesToHumanReadable(0x100000000), '4 GiB')
+    self.assertEqual(format_utils.BytesToHumanReadable(0x40000000000), '4 TiB')
+
+  def testDecimal(self):
+    self.assertEqual(format_utils.BytesToHumanReadable(5000, decimal=True),
+                     '5 kB')
+    self.assertEqual(format_utils.BytesToHumanReadable(5000000, decimal=True),
+                     '5 MB')
+    self.assertEqual(format_utils.BytesToHumanReadable(5000000000,
+                                                       decimal=True),
+                     '5 GB')
+
+  def testDefaultPrecision(self):
+    self.assertEqual(format_utils.BytesToHumanReadable(5000), '4.8 KiB')
+    self.assertEqual(format_utils.BytesToHumanReadable(500000), '488.2 KiB')
+    self.assertEqual(format_utils.BytesToHumanReadable(5000000), '4.7 MiB')
+
+  def testCustomPrecision(self):
+    self.assertEqual(format_utils.BytesToHumanReadable(5000, precision=3),
+                     '4.882 KiB')
+    self.assertEqual(format_utils.BytesToHumanReadable(500000, precision=0),
+                     '488 KiB')
+    self.assertEqual(format_utils.BytesToHumanReadable(5000000, precision=5),
+                     '4.76837 MiB')
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/update_engine/scripts/update_payload/histogram.py b/update_engine/scripts/update_payload/histogram.py
new file mode 100644
index 0000000..9916329
--- /dev/null
+++ b/update_engine/scripts/update_payload/histogram.py
@@ -0,0 +1,117 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Histogram generation tools."""
+
+from collections import defaultdict
+
+import format_utils
+
+
+class Histogram(object):
+  """A histogram generating object.
+
+  This object serves the sole purpose of formatting (key, val) pairs as an
+  ASCII histogram, including bars and percentage markers, and taking care of
+  label alignment, scaling, etc. In addition to the standard __init__
+  interface, two static methods are provided for conveniently converting data
+  in different formats into a histogram. Histogram generation is exported via
+  its __str__ method, and looks as follows:
+
+    Yes |################    | 5 (83.3%)
+    No  |###                 | 1 (16.6%)
+
+  TODO(garnold) we may want to add actual methods for adding data or tweaking
+  the output layout and formatting. For now, though, this is fine.
+
+  """
+
+  def __init__(self, data, scale=20, formatter=None):
+    """Initialize a histogram object.
+
+    Args:
+      data: list of (key, count) pairs constituting the histogram
+      scale: number of characters used to indicate 100%
+      formatter: function used for formatting raw histogram values
+
+    """
+    self.data = data
+    self.scale = scale
+    self.formatter = formatter or str
+    self.max_key_len = max([len(str(key)) for key, count in self.data])
+    self.total = sum([count for key, count in self.data])
+
+  @staticmethod
+  def FromCountDict(count_dict, scale=20, formatter=None, key_names=None):
+    """Takes a dictionary of counts and returns a histogram object.
+
+    This simply converts a mapping from names to counts into a list of (key,
+    count) pairs, optionally translating keys into name strings, then
+    generating and returning a histogram for them. This is a useful convenience
+    call for clients that update a dictionary of counters as they (say) scan a
+    data stream.
+
+    Args:
+      count_dict: dictionary mapping keys to occurrence counts
+      scale: number of characters used to indicate 100%
+      formatter: function used for formatting raw histogram values
+      key_names: dictionary mapping keys to name strings
+    Returns:
+      A histogram object based on the given data.
+
+    """
+    namer = None
+    if key_names:
+      namer = lambda key: key_names[key]
+    else:
+      namer = lambda key: key
+
+    hist = [(namer(key), count) for key, count in count_dict.items()]
+    return Histogram(hist, scale, formatter)
+
+  @staticmethod
+  def FromKeyList(key_list, scale=20, formatter=None, key_names=None):
+    """Takes a list of (possibly recurring) keys and returns a histogram object.
+
+    This converts the list into a dictionary of counters, then uses
+    FromCountDict() to generate the actual histogram. For example:
+
+      ['a', 'a', 'b', 'a', 'b'] --> {'a': 3, 'b': 2} --> ...
+
+    Args:
+      key_list: list of (possibly recurring) keys
+      scale: number of characters used to indicate 100%
+      formatter: function used for formatting raw histogram values
+      key_names: dictionary mapping keys to name strings
+    Returns:
+      A histogram object based on the given data.
+
+    """
+    count_dict = defaultdict(int)  # Unset items default to zero
+    for key in key_list:
+      count_dict[key] += 1
+    return Histogram.FromCountDict(count_dict, scale, formatter, key_names)
+
+  def __str__(self):
+    hist_lines = []
+    hist_bar = '|'
+    for key, count in self.data:
+      if self.total:
+        bar_len = count * self.scale / self.total
+        hist_bar = '|%s|' % ('#' * bar_len).ljust(self.scale)
+
+      line = '%s %s %s' % (
+          str(key).ljust(self.max_key_len),
+          hist_bar,
+          self.formatter(count))
+      percent_str = format_utils.NumToPercent(count, self.total)
+      if percent_str:
+        line += ' (%s)' % percent_str
+      hist_lines.append(line)
+
+    return '\n'.join(hist_lines)
+
+  def GetKeys(self):
+    """Returns the keys of the histogram."""
+    return [key for key, _ in self.data]
diff --git a/update_engine/scripts/update_payload/histogram_unittest.py b/update_engine/scripts/update_payload/histogram_unittest.py
new file mode 100755
index 0000000..421ff20
--- /dev/null
+++ b/update_engine/scripts/update_payload/histogram_unittest.py
@@ -0,0 +1,60 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for histogram.py."""
+
+import unittest
+
+import format_utils
+import histogram
+
+
+class HistogramTest(unittest.TestCase):
+
+  @staticmethod
+  def AddHumanReadableSize(size):
+    fmt = format_utils.BytesToHumanReadable(size)
+    return '%s (%s)' % (size, fmt) if fmt else str(size)
+
+  def CompareToExpectedDefault(self, actual_str):
+    expected_str = (
+        'Yes |################    | 5 (83.3%)\n'
+        'No  |###                 | 1 (16.6%)'
+    )
+    self.assertEqual(actual_str, expected_str)
+
+  def testExampleHistogram(self):
+    self.CompareToExpectedDefault(str(histogram.Histogram(
+        [('Yes', 5), ('No', 1)])))
+
+  def testFromCountDict(self):
+    self.CompareToExpectedDefault(str(histogram.Histogram.FromCountDict(
+        {'Yes': 5, 'No': 1})))
+
+  def testFromKeyList(self):
+    self.CompareToExpectedDefault(str(histogram.Histogram.FromKeyList(
+        ['Yes', 'Yes', 'No', 'Yes', 'Yes', 'Yes'])))
+
+  def testCustomScale(self):
+    expected_str = (
+        'Yes |#### | 5 (83.3%)\n'
+        'No  |     | 1 (16.6%)'
+    )
+    actual_str = str(histogram.Histogram([('Yes', 5), ('No', 1)], scale=5))
+    self.assertEqual(actual_str, expected_str)
+
+  def testCustomFormatter(self):
+    expected_str = (
+        'Yes |################    | 5000 (4.8 KiB) (83.3%)\n'
+        'No  |###                 | 1000 (16.6%)'
+    )
+    actual_str = str(histogram.Histogram(
+        [('Yes', 5000), ('No', 1000)], formatter=self.AddHumanReadableSize))
+    self.assertEqual(actual_str, expected_str)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/update_engine/scripts/update_payload/payload-test-key.pem b/update_engine/scripts/update_payload/payload-test-key.pem
new file mode 100644
index 0000000..342e923
--- /dev/null
+++ b/update_engine/scripts/update_payload/payload-test-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAvtGHtqO21Uhy2wGz9fluIpIUR8G7dZoCZhZukGkm4mlfgL71
+xPSArjx02/w/FhYxOusV6/XQeKgL3i8cni3HCkCOurZLpi2L5Ver6qrxKFh6WBVZ
+0Dj7N6P/Mf5jZdhfvVyweLlsNK8Ypeb+RazfrsXhd4cy3dBMxouGwH7R7QQXTFCo
+Cc8kgJBTxILl3jfvY8OrNKgYiCETa7tQdFkP0bfPwH9cAXuMjHXiZatim0tF+ivp
+kM2v/6LTxtD6Rq1wks/N6CHi8efrRaviFp7c0mNmBNFaV54cHEUW2SlNIiRun7L0
+1nAz/D8kuoHfx4E3Mtj0DbvngZJMX/X+rJQ5cQIDAQABAoIBADmE2X7hbJxwAUcp
+BUExFdTP6dMTf9lcOjrhqiRXvgPjtYkOhvD+rsdWq/cf2zhiKibTdEEzUMr+BM3N
+r7eyntvlR+DaUIVgF1pjigvryVPbD837aZ5NftRv194PC5FInttq1Dsf0ZEz8p8X
+uS/xg1+ggG1SUK/yOSJkLpNZ5xelbclQJ9bnJST8PR8XbEieA83xt5M2DcooPzq0
+/99m/daA5hmSWs6n8sFrIZDQxDhLyyW4J72jjoNTE87eCpwK855yXMelpEPDZNQi
+nB3x5Y/bGbl81PInqL2q14lekrVYdYZ7bOBVlsmyvz6f1e4OOE1aaAM+w6ArA4az
+6elZQE0CgYEA4GOU6BBu9jLqFdqV9jIkWsgz5ZWINz8PLJPtZzk5I9KO1m+GAUy2
+h/1IGGR6qRQR49hMtq4C0lUifxquq0xivzJ87U9oxKC9yEeTxkmDe5csVHsnAtqT
+xRgVM7Ysrut5NLU1zm0q3jBmkDu7d99LvscM/3n7eJ6RiYpnA54O6I8CgYEA2bNA
+34PTvxBS2deRoxKQNlVU14FtirE+q0+k0wcE85wr7wIMpR13al8T1TpE8J1yvvZM
+92HMGFGfYNDB46b8VfJ5AxEUFwdruec6sTVVfkMZMOqM/A08yiaLzQ1exDxNwaja
+fLuG5FAVRD/2g7fLBcsmosyNgcgNr1XA8Q/nvf8CgYEAwaSOg7py19rWcqehlMZu
+4z00tCNYWzz7LmA2l0clzYlPJTU3MvXt6+ujhRFpXXJpgfRPN7Nx0ewQihoPtNqF
+uTSr5OwLoOyK+0Tx/UPByS2L3xgscWUJ8yQ2X9sOMqIZhmf/mDZTsU2ZpU03GlrE
+dk43JF4zq0NEm6qp/dAwU3cCgYEAvECl+KKmmLIk8vvWlI2Y52Mi2rixYR2kc7+L
+aHDJd1+1HhlHlgDFItbU765Trz5322phZArN0rnCeJYNFC9yRWBIBL7gAIoKPdgW
+iOb15xlez04EXHGV/7kVa1wEdu0u0CiTxwjivMwDl+E36u8kQP5LirwYIgI800H0
+doCqhUECgYEAjvA38OS7hy56Q4LQtmHFBuRIn4E5SrIGMwNIH6TGbEKQix3ajTCQ
+0fSoLDGTkU6dH+T4v0WheveN2a2Kofqm0UQx5V2rfnY/Ut1fAAWgL/lsHLDnzPUZ
+bvTOANl8TbT49xAfNXTaGWe7F7nYz+bK0UDif1tJNDLQw7USD5I8lbQ=
+-----END RSA PRIVATE KEY-----
diff --git a/update_engine/scripts/update_payload/payload-test-key.pub b/update_engine/scripts/update_payload/payload-test-key.pub
new file mode 100644
index 0000000..fdae963
--- /dev/null
+++ b/update_engine/scripts/update_payload/payload-test-key.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvtGHtqO21Uhy2wGz9flu
+IpIUR8G7dZoCZhZukGkm4mlfgL71xPSArjx02/w/FhYxOusV6/XQeKgL3i8cni3H
+CkCOurZLpi2L5Ver6qrxKFh6WBVZ0Dj7N6P/Mf5jZdhfvVyweLlsNK8Ypeb+Razf
+rsXhd4cy3dBMxouGwH7R7QQXTFCoCc8kgJBTxILl3jfvY8OrNKgYiCETa7tQdFkP
+0bfPwH9cAXuMjHXiZatim0tF+ivpkM2v/6LTxtD6Rq1wks/N6CHi8efrRaviFp7c
+0mNmBNFaV54cHEUW2SlNIiRun7L01nAz/D8kuoHfx4E3Mtj0DbvngZJMX/X+rJQ5
+cQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/update_engine/scripts/update_payload/payload.py b/update_engine/scripts/update_payload/payload.py
new file mode 100644
index 0000000..f76c0de
--- /dev/null
+++ b/update_engine/scripts/update_payload/payload.py
@@ -0,0 +1,341 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Tools for reading, verifying and applying Chrome OS update payloads."""
+
+from __future__ import print_function
+
+import hashlib
+import struct
+
+import applier
+import block_tracer
+import checker
+import common
+from error import PayloadError
+import update_metadata_pb2
+
+
+#
+# Helper functions.
+#
+def _ReadInt(file_obj, size, is_unsigned, hasher=None):
+  """Reads a binary-encoded integer from a file.
+
+  It will do the correct conversion based on the reported size and whether or
+  not a signed number is expected. Assumes a network (big-endian) byte
+  ordering.
+
+  Args:
+    file_obj: a file object
+    size: the integer size in bytes (2, 4 or 8)
+    is_unsigned: whether it is signed or not
+    hasher: an optional hasher to pass the value through
+
+  Returns:
+    An "unpacked" (Python) integer value.
+
+  Raises:
+    PayloadError if an read error occurred.
+  """
+  return struct.unpack(common.IntPackingFmtStr(size, is_unsigned),
+                       common.Read(file_obj, size, hasher=hasher))[0]
+
+
+#
+# Update payload.
+#
+class Payload(object):
+  """Chrome OS update payload processor."""
+
+  class _PayloadHeader(object):
+    """Update payload header struct."""
+
+    # Header constants; sizes are in bytes.
+    _MAGIC = 'CrAU'
+    _VERSION_SIZE = 8
+    _MANIFEST_LEN_SIZE = 8
+    _METADATA_SIGNATURE_LEN_SIZE = 4
+
+    def __init__(self):
+      self.version = None
+      self.manifest_len = None
+      self.metadata_signature_len = None
+      self.size = None
+
+    def ReadFromPayload(self, payload_file, hasher=None):
+      """Reads the payload header from a file.
+
+      Reads the payload header from the |payload_file| and updates the |hasher|
+      if one is passed. The parsed header is stored in the _PayloadHeader
+      instance attributes.
+
+      Args:
+        payload_file: a file object
+        hasher: an optional hasher to pass the value through
+
+      Returns:
+        None.
+
+      Raises:
+        PayloadError if a read error occurred or the header is invalid.
+      """
+      # Verify magic
+      magic = common.Read(payload_file, len(self._MAGIC), hasher=hasher)
+      if magic != self._MAGIC:
+        raise PayloadError('invalid payload magic: %s' % magic)
+
+      self.version = _ReadInt(payload_file, self._VERSION_SIZE, True,
+                              hasher=hasher)
+      self.manifest_len = _ReadInt(payload_file, self._MANIFEST_LEN_SIZE, True,
+                                   hasher=hasher)
+      self.size = (len(self._MAGIC) + self._VERSION_SIZE +
+                   self._MANIFEST_LEN_SIZE)
+      self.metadata_signature_len = 0
+
+      if self.version == common.BRILLO_MAJOR_PAYLOAD_VERSION:
+        self.size += self._METADATA_SIGNATURE_LEN_SIZE
+        self.metadata_signature_len = _ReadInt(
+            payload_file, self._METADATA_SIGNATURE_LEN_SIZE, True,
+            hasher=hasher)
+
+
+  def __init__(self, payload_file):
+    """Initialize the payload object.
+
+    Args:
+      payload_file: update payload file object open for reading
+    """
+    self.payload_file = payload_file
+    self.manifest_hasher = None
+    self.is_init = False
+    self.header = None
+    self.manifest = None
+    self.data_offset = None
+    self.metadata_signature = None
+    self.metadata_size = None
+
+  def _ReadHeader(self):
+    """Reads and returns the payload header.
+
+    Returns:
+      A payload header object.
+
+    Raises:
+      PayloadError if a read error occurred.
+    """
+    header = self._PayloadHeader()
+    header.ReadFromPayload(self.payload_file, self.manifest_hasher)
+    return header
+
+  def _ReadManifest(self):
+    """Reads and returns the payload manifest.
+
+    Returns:
+      A string containing the payload manifest in binary form.
+
+    Raises:
+      PayloadError if a read error occurred.
+    """
+    if not self.header:
+      raise PayloadError('payload header not present')
+
+    return common.Read(self.payload_file, self.header.manifest_len,
+                       hasher=self.manifest_hasher)
+
+  def _ReadMetadataSignature(self):
+    """Reads and returns the metadata signatures.
+
+    Returns:
+      A string containing the metadata signatures protobuf in binary form or
+      an empty string if no metadata signature found in the payload.
+
+    Raises:
+      PayloadError if a read error occurred.
+    """
+    if not self.header:
+      raise PayloadError('payload header not present')
+
+    return common.Read(
+        self.payload_file, self.header.metadata_signature_len,
+        offset=self.header.size + self.header.manifest_len)
+
+  def ReadDataBlob(self, offset, length):
+    """Reads and returns a single data blob from the update payload.
+
+    Args:
+      offset: offset to the beginning of the blob from the end of the manifest
+      length: the blob's length
+
+    Returns:
+      A string containing the raw blob data.
+
+    Raises:
+      PayloadError if a read error occurred.
+    """
+    return common.Read(self.payload_file, length,
+                       offset=self.data_offset + offset)
+
+  def Init(self):
+    """Initializes the payload object.
+
+    This is a prerequisite for any other public API call.
+
+    Raises:
+      PayloadError if object already initialized or fails to initialize
+      correctly.
+    """
+    if self.is_init:
+      raise PayloadError('payload object already initialized')
+
+    # Initialize hash context.
+    # pylint: disable=E1101
+    self.manifest_hasher = hashlib.sha256()
+
+    # Read the file header.
+    self.header = self._ReadHeader()
+
+    # Read the manifest.
+    manifest_raw = self._ReadManifest()
+    self.manifest = update_metadata_pb2.DeltaArchiveManifest()
+    self.manifest.ParseFromString(manifest_raw)
+
+    # Read the metadata signature (if any).
+    metadata_signature_raw = self._ReadMetadataSignature()
+    if metadata_signature_raw:
+      self.metadata_signature = update_metadata_pb2.Signatures()
+      self.metadata_signature.ParseFromString(metadata_signature_raw)
+
+    self.metadata_size = self.header.size + self.header.manifest_len
+    self.data_offset = self.metadata_size + self.header.metadata_signature_len
+
+    self.is_init = True
+
+  def Describe(self):
+    """Emits the payload embedded description data to standard output."""
+    def _DescribeImageInfo(description, image_info):
+      def _DisplayIndentedValue(name, value):
+        print('  {:<14} {}'.format(name+':', value))
+
+      print('%s:' % description)
+      _DisplayIndentedValue('Channel', image_info.channel)
+      _DisplayIndentedValue('Board', image_info.board)
+      _DisplayIndentedValue('Version', image_info.version)
+      _DisplayIndentedValue('Key', image_info.key)
+
+      if image_info.build_channel != image_info.channel:
+        _DisplayIndentedValue('Build channel', image_info.build_channel)
+
+      if image_info.build_version != image_info.version:
+        _DisplayIndentedValue('Build version', image_info.build_version)
+
+    if self.manifest.HasField('old_image_info'):
+      # pylint: disable=E1101
+      _DescribeImageInfo('Old Image', self.manifest.old_image_info)
+
+    if self.manifest.HasField('new_image_info'):
+      # pylint: disable=E1101
+      _DescribeImageInfo('New Image', self.manifest.new_image_info)
+
+  def _AssertInit(self):
+    """Raises an exception if the object was not initialized."""
+    if not self.is_init:
+      raise PayloadError('payload object not initialized')
+
+  def ResetFile(self):
+    """Resets the offset of the payload file to right past the manifest."""
+    self.payload_file.seek(self.data_offset)
+
+  def IsDelta(self):
+    """Returns True iff the payload appears to be a delta."""
+    self._AssertInit()
+    return (self.manifest.HasField('old_kernel_info') or
+            self.manifest.HasField('old_rootfs_info') or
+            any(partition.HasField('old_partition_info')
+                for partition in self.manifest.partitions))
+
+  def IsFull(self):
+    """Returns True iff the payload appears to be a full."""
+    return not self.IsDelta()
+
+  def Check(self, pubkey_file_name=None, metadata_sig_file=None,
+            report_out_file=None, assert_type=None, block_size=0,
+            rootfs_part_size=0, kernel_part_size=0, allow_unhashed=False,
+            disabled_tests=()):
+    """Checks the payload integrity.
+
+    Args:
+      pubkey_file_name: public key used for signature verification
+      metadata_sig_file: metadata signature, if verification is desired
+      report_out_file: file object to dump the report to
+      assert_type: assert that payload is either 'full' or 'delta'
+      block_size: expected filesystem / payload block size
+      rootfs_part_size: the size of (physical) rootfs partitions in bytes
+      kernel_part_size: the size of (physical) kernel partitions in bytes
+      allow_unhashed: allow unhashed operation blobs
+      disabled_tests: list of tests to disable
+
+    Raises:
+      PayloadError if payload verification failed.
+    """
+    self._AssertInit()
+
+    # Create a short-lived payload checker object and run it.
+    helper = checker.PayloadChecker(
+        self, assert_type=assert_type, block_size=block_size,
+        allow_unhashed=allow_unhashed, disabled_tests=disabled_tests)
+    helper.Run(pubkey_file_name=pubkey_file_name,
+               metadata_sig_file=metadata_sig_file,
+               rootfs_part_size=rootfs_part_size,
+               kernel_part_size=kernel_part_size,
+               report_out_file=report_out_file)
+
+  def Apply(self, new_kernel_part, new_rootfs_part, old_kernel_part=None,
+            old_rootfs_part=None, bsdiff_in_place=True, bspatch_path=None,
+            truncate_to_expected_size=True):
+    """Applies the update payload.
+
+    Args:
+      new_kernel_part: name of dest kernel partition file
+      new_rootfs_part: name of dest rootfs partition file
+      old_kernel_part: name of source kernel partition file (optional)
+      old_rootfs_part: name of source rootfs partition file (optional)
+      bsdiff_in_place: whether to perform BSDIFF operations in-place (optional)
+      bspatch_path: path to the bspatch binary (optional)
+      truncate_to_expected_size: whether to truncate the resulting partitions
+                                 to their expected sizes, as specified in the
+                                 payload (optional)
+
+    Raises:
+      PayloadError if payload application failed.
+    """
+    self._AssertInit()
+
+    # Create a short-lived payload applier object and run it.
+    helper = applier.PayloadApplier(
+        self, bsdiff_in_place=bsdiff_in_place, bspatch_path=bspatch_path,
+        truncate_to_expected_size=truncate_to_expected_size)
+    helper.Run(new_kernel_part, new_rootfs_part,
+               old_kernel_part=old_kernel_part,
+               old_rootfs_part=old_rootfs_part)
+
+  def TraceBlock(self, block, skip, trace_out_file, is_kernel):
+    """Traces the origin(s) of a given dest partition block.
+
+    The tracing tries to find origins transitively, when possible (it currently
+    only works for move operations, where the mapping of src/dst is
+    one-to-one). It will dump a list of operations and source blocks
+    responsible for the data in the given dest block.
+
+    Args:
+      block: the block number whose origin to trace
+      skip: the number of first origin mappings to skip
+      trace_out_file: file object to dump the trace to
+      is_kernel: trace through kernel (True) or rootfs (False) operations
+    """
+    self._AssertInit()
+
+    # Create a short-lived payload block tracer object and run it.
+    helper = block_tracer.PayloadBlockTracer(self)
+    helper.Run(block, skip, trace_out_file, is_kernel)
diff --git a/update_engine/scripts/update_payload/test_utils.py b/update_engine/scripts/update_payload/test_utils.py
new file mode 100644
index 0000000..61a91f5
--- /dev/null
+++ b/update_engine/scripts/update_payload/test_utils.py
@@ -0,0 +1,364 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Utilities for unit testing."""
+
+from __future__ import print_function
+
+import cStringIO
+import hashlib
+import os
+import struct
+import subprocess
+
+import common
+import payload
+import update_metadata_pb2
+
+
+class TestError(Exception):
+  """An error during testing of update payload code."""
+
+
+# Private/public RSA keys used for testing.
+_PRIVKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
+                                  'payload-test-key.pem')
+_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__),
+                                 'payload-test-key.pub')
+
+
+def KiB(count):
+  return count << 10
+
+
+def MiB(count):
+  return count << 20
+
+
+def GiB(count):
+  return count << 30
+
+
+def _WriteInt(file_obj, size, is_unsigned, val):
+  """Writes a binary-encoded integer to a file.
+
+  It will do the correct conversion based on the reported size and whether or
+  not a signed number is expected. Assumes a network (big-endian) byte
+  ordering.
+
+  Args:
+    file_obj: a file object
+    size: the integer size in bytes (2, 4 or 8)
+    is_unsigned: whether it is signed or not
+    val: integer value to encode
+
+  Raises:
+    PayloadError if a write error occurred.
+  """
+  try:
+    file_obj.write(struct.pack(common.IntPackingFmtStr(size, is_unsigned), val))
+  except IOError, e:
+    raise payload.PayloadError('error writing to file (%s): %s' %
+                               (file_obj.name, e))
+
+
+def _SetMsgField(msg, field_name, val):
+  """Sets or clears a field in a protobuf message."""
+  if val is None:
+    msg.ClearField(field_name)
+  else:
+    setattr(msg, field_name, val)
+
+
+def SignSha256(data, privkey_file_name):
+  """Signs the data's SHA256 hash with an RSA private key.
+
+  Args:
+    data: the data whose SHA256 hash we want to sign
+    privkey_file_name: private key used for signing data
+
+  Returns:
+    The signature string, prepended with an ASN1 header.
+
+  Raises:
+    TestError if something goes wrong.
+  """
+  # pylint: disable=E1101
+  data_sha256_hash = common.SIG_ASN1_HEADER + hashlib.sha256(data).digest()
+  sign_cmd = ['openssl', 'rsautl', '-sign', '-inkey', privkey_file_name]
+  try:
+    sign_process = subprocess.Popen(sign_cmd, stdin=subprocess.PIPE,
+                                    stdout=subprocess.PIPE)
+    sig, _ = sign_process.communicate(input=data_sha256_hash)
+  except Exception as e:
+    raise TestError('signing subprocess failed: %s' % e)
+
+  return sig
+
+
+class SignaturesGenerator(object):
+  """Generates a payload signatures data block."""
+
+  def __init__(self):
+    self.sigs = update_metadata_pb2.Signatures()
+
+  def AddSig(self, version, data):
+    """Adds a signature to the signature sequence.
+
+    Args:
+      version: signature version (None means do not assign)
+      data: signature binary data (None means do not assign)
+    """
+    # Pylint fails to identify a member of the Signatures message.
+    # pylint: disable=E1101
+    sig = self.sigs.signatures.add()
+    if version is not None:
+      sig.version = version
+    if data is not None:
+      sig.data = data
+
+  def ToBinary(self):
+    """Returns the binary representation of the signature block."""
+    return self.sigs.SerializeToString()
+
+
+class PayloadGenerator(object):
+  """Generates an update payload allowing low-level control.
+
+  Attributes:
+    manifest: the protobuf containing the payload manifest
+    version: the payload version identifier
+    block_size: the block size pertaining to update operations
+
+  """
+
+  def __init__(self, version=1):
+    self.manifest = update_metadata_pb2.DeltaArchiveManifest()
+    self.version = version
+    self.block_size = 0
+
+  @staticmethod
+  def _WriteExtent(ex, val):
+    """Returns an Extent message."""
+    start_block, num_blocks = val
+    _SetMsgField(ex, 'start_block', start_block)
+    _SetMsgField(ex, 'num_blocks', num_blocks)
+
+  @staticmethod
+  def _AddValuesToRepeatedField(repeated_field, values, write_func):
+    """Adds values to a repeated message field."""
+    if values:
+      for val in values:
+        new_item = repeated_field.add()
+        write_func(new_item, val)
+
+  @staticmethod
+  def _AddExtents(extents_field, values):
+    """Adds extents to an extents field."""
+    PayloadGenerator._AddValuesToRepeatedField(
+        extents_field, values, PayloadGenerator._WriteExtent)
+
+  def SetBlockSize(self, block_size):
+    """Sets the payload's block size."""
+    self.block_size = block_size
+    _SetMsgField(self.manifest, 'block_size', block_size)
+
+  def SetPartInfo(self, is_kernel, is_new, part_size, part_hash):
+    """Set the partition info entry.
+
+    Args:
+      is_kernel: whether this is kernel partition info
+      is_new: whether to set old (False) or new (True) info
+      part_size: the partition size (in fact, filesystem size)
+      part_hash: the partition hash
+    """
+    if is_kernel:
+      # pylint: disable=E1101
+      part_info = (self.manifest.new_kernel_info if is_new
+                   else self.manifest.old_kernel_info)
+    else:
+      # pylint: disable=E1101
+      part_info = (self.manifest.new_rootfs_info if is_new
+                   else self.manifest.old_rootfs_info)
+    _SetMsgField(part_info, 'size', part_size)
+    _SetMsgField(part_info, 'hash', part_hash)
+
+  def AddOperation(self, is_kernel, op_type, data_offset=None,
+                   data_length=None, src_extents=None, src_length=None,
+                   dst_extents=None, dst_length=None, data_sha256_hash=None):
+    """Adds an InstallOperation entry."""
+    # pylint: disable=E1101
+    operations = (self.manifest.kernel_install_operations if is_kernel
+                  else self.manifest.install_operations)
+
+    op = operations.add()
+    op.type = op_type
+
+    _SetMsgField(op, 'data_offset', data_offset)
+    _SetMsgField(op, 'data_length', data_length)
+
+    self._AddExtents(op.src_extents, src_extents)
+    _SetMsgField(op, 'src_length', src_length)
+
+    self._AddExtents(op.dst_extents, dst_extents)
+    _SetMsgField(op, 'dst_length', dst_length)
+
+    _SetMsgField(op, 'data_sha256_hash', data_sha256_hash)
+
+  def SetSignatures(self, sigs_offset, sigs_size):
+    """Set the payload's signature block descriptors."""
+    _SetMsgField(self.manifest, 'signatures_offset', sigs_offset)
+    _SetMsgField(self.manifest, 'signatures_size', sigs_size)
+
+  def SetMinorVersion(self, minor_version):
+    """Set the payload's minor version field."""
+    _SetMsgField(self.manifest, 'minor_version', minor_version)
+
+  def _WriteHeaderToFile(self, file_obj, manifest_len):
+    """Writes a payload heaer to a file."""
+    # We need to access protected members in Payload for writing the header.
+    # pylint: disable=W0212
+    file_obj.write(payload.Payload._PayloadHeader._MAGIC)
+    _WriteInt(file_obj, payload.Payload._PayloadHeader._VERSION_SIZE, True,
+              self.version)
+    _WriteInt(file_obj, payload.Payload._PayloadHeader._MANIFEST_LEN_SIZE, True,
+              manifest_len)
+
+  def WriteToFile(self, file_obj, manifest_len=-1, data_blobs=None,
+                  sigs_data=None, padding=None):
+    """Writes the payload content to a file.
+
+    Args:
+      file_obj: a file object open for writing
+      manifest_len: manifest len to dump (otherwise computed automatically)
+      data_blobs: a list of data blobs to be concatenated to the payload
+      sigs_data: a binary Signatures message to be concatenated to the payload
+      padding: stuff to dump past the normal data blobs provided (optional)
+    """
+    manifest = self.manifest.SerializeToString()
+    if manifest_len < 0:
+      manifest_len = len(manifest)
+    self._WriteHeaderToFile(file_obj, manifest_len)
+    file_obj.write(manifest)
+    if data_blobs:
+      for data_blob in data_blobs:
+        file_obj.write(data_blob)
+    if sigs_data:
+      file_obj.write(sigs_data)
+    if padding:
+      file_obj.write(padding)
+
+
+class EnhancedPayloadGenerator(PayloadGenerator):
+  """Payload generator with automatic handling of data blobs.
+
+  Attributes:
+    data_blobs: a list of blobs, in the order they were added
+    curr_offset: the currently consumed offset of blobs added to the payload
+  """
+
+  def __init__(self):
+    super(EnhancedPayloadGenerator, self).__init__()
+    self.data_blobs = []
+    self.curr_offset = 0
+
+  def AddData(self, data_blob):
+    """Adds a (possibly orphan) data blob."""
+    data_length = len(data_blob)
+    data_offset = self.curr_offset
+    self.curr_offset += data_length
+    self.data_blobs.append(data_blob)
+    return data_length, data_offset
+
+  def AddOperationWithData(self, is_kernel, op_type, src_extents=None,
+                           src_length=None, dst_extents=None, dst_length=None,
+                           data_blob=None, do_hash_data_blob=True):
+    """Adds an install operation and associated data blob.
+
+    This takes care of obtaining a hash of the data blob (if so instructed)
+    and appending it to the internally maintained list of blobs, including the
+    necessary offset/length accounting.
+
+    Args:
+      is_kernel: whether this is a kernel (True) or rootfs (False) operation
+      op_type: one of REPLACE, REPLACE_BZ, MOVE or BSDIFF
+      src_extents: list of (start, length) pairs indicating src block ranges
+      src_length: size of the src data in bytes (needed for BSDIFF)
+      dst_extents: list of (start, length) pairs indicating dst block ranges
+      dst_length: size of the dst data in bytes (needed for BSDIFF)
+      data_blob: a data blob associated with this operation
+      do_hash_data_blob: whether or not to compute and add a data blob hash
+    """
+    data_offset = data_length = data_sha256_hash = None
+    if data_blob is not None:
+      if do_hash_data_blob:
+        # pylint: disable=E1101
+        data_sha256_hash = hashlib.sha256(data_blob).digest()
+      data_length, data_offset = self.AddData(data_blob)
+
+    self.AddOperation(is_kernel, op_type, data_offset=data_offset,
+                      data_length=data_length, src_extents=src_extents,
+                      src_length=src_length, dst_extents=dst_extents,
+                      dst_length=dst_length, data_sha256_hash=data_sha256_hash)
+
+  def WriteToFileWithData(self, file_obj, sigs_data=None,
+                          privkey_file_name=None,
+                          do_add_pseudo_operation=False,
+                          is_pseudo_in_kernel=False, padding=None):
+    """Writes the payload content to a file, optionally signing the content.
+
+    Args:
+      file_obj: a file object open for writing
+      sigs_data: signatures blob to be appended to the payload (optional;
+                 payload signature fields assumed to be preset by the caller)
+      privkey_file_name: key used for signing the payload (optional; used only
+                         if explicit signatures blob not provided)
+      do_add_pseudo_operation: whether a pseudo-operation should be added to
+                               account for the signature blob
+      is_pseudo_in_kernel: whether the pseudo-operation should be added to
+                           kernel (True) or rootfs (False) operations
+      padding: stuff to dump past the normal data blobs provided (optional)
+
+    Raises:
+      TestError: if arguments are inconsistent or something goes wrong.
+    """
+    sigs_len = len(sigs_data) if sigs_data else 0
+
+    # Do we need to generate a genuine signatures blob?
+    do_generate_sigs_data = sigs_data is None and privkey_file_name
+
+    if do_generate_sigs_data:
+      # First, sign some arbitrary data to obtain the size of a signature blob.
+      fake_sig = SignSha256('fake-payload-data', privkey_file_name)
+      fake_sigs_gen = SignaturesGenerator()
+      fake_sigs_gen.AddSig(1, fake_sig)
+      sigs_len = len(fake_sigs_gen.ToBinary())
+
+      # Update the payload with proper signature attributes.
+      self.SetSignatures(self.curr_offset, sigs_len)
+
+    # Add a pseudo-operation to account for the signature blob, if requested.
+    if do_add_pseudo_operation:
+      if not self.block_size:
+        raise TestError('cannot add pseudo-operation without knowing the '
+                        'payload block size')
+      self.AddOperation(
+          is_pseudo_in_kernel, common.OpType.REPLACE,
+          data_offset=self.curr_offset, data_length=sigs_len,
+          dst_extents=[(common.PSEUDO_EXTENT_MARKER,
+                        (sigs_len + self.block_size - 1) / self.block_size)])
+
+    if do_generate_sigs_data:
+      # Once all payload fields are updated, dump and sign it.
+      temp_payload_file = cStringIO.StringIO()
+      self.WriteToFile(temp_payload_file, data_blobs=self.data_blobs)
+      sig = SignSha256(temp_payload_file.getvalue(), privkey_file_name)
+      sigs_gen = SignaturesGenerator()
+      sigs_gen.AddSig(1, sig)
+      sigs_data = sigs_gen.ToBinary()
+      assert len(sigs_data) == sigs_len, 'signature blob lengths mismatch'
+
+    # Dump the whole thing, complete with data and signature blob, to a file.
+    self.WriteToFile(file_obj, data_blobs=self.data_blobs, sigs_data=sigs_data,
+                     padding=padding)
diff --git a/update_engine/scripts/update_payload/update-payload-key.pub.pem b/update_engine/scripts/update_payload/update-payload-key.pub.pem
new file mode 100644
index 0000000..7ac369f
--- /dev/null
+++ b/update_engine/scripts/update_payload/update-payload-key.pub.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Bg9BnjWhX3jJyECeXqF
+O28nkYTF1NHWLlFHgzAGg+ysva22BL3S5LlsNejnYVg/xzx3izvAQyOF3I1TJVOy
+2fH1DoZOWyKuckMyUrFQbO6OV1VIvPUPKckHadWcXSsHj2lBdDPH9xRDEBsXeztf
+nAGBD8GlAyTU7iH+Bf+xzyK9k4BmITf4Nx4xWhRZ6gm2Fc2SEP3x5N5fohkLv5ZP
+kFr0fj5wUK+0XF95rkGFBLIq2XACS3dmxMFToFl1HMM1HonUg9TAH+3dVH93zue1
+y81mkTuGnNX+zYya5ov2kD8zW1V10iTOSJfOlho5T8FpKbG37o3yYcUiyMHKO1Iv
+PQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/update_engine/scripts/update_payload/update_metadata_pb2.py b/update_engine/scripts/update_payload/update_metadata_pb2.py
new file mode 100644
index 0000000..46c475e
--- /dev/null
+++ b/update_engine/scripts/update_payload/update_metadata_pb2.py
@@ -0,0 +1,620 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: update_metadata.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='update_metadata.proto',
+  package='chromeos_update_engine',
+  serialized_pb='\n\x15update_metadata.proto\x12\x16\x63hromeos_update_engine\"1\n\x06\x45xtent\x12\x13\n\x0bstart_block\x18\x01 \x01(\x04\x12\x12\n\nnum_blocks\x18\x02 \x01(\x04\"z\n\nSignatures\x12@\n\nsignatures\x18\x01 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x1a*\n\tSignature\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"+\n\rPartitionInfo\x12\x0c\n\x04size\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"w\n\tImageInfo\x12\r\n\x05\x62oard\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\t\x12\x15\n\rbuild_channel\x18\x05 \x01(\t\x12\x15\n\rbuild_version\x18\x06 \x01(\t\"\xd2\x03\n\x10InstallOperation\x12;\n\x04type\x18\x01 \x02(\x0e\x32-.chromeos_update_engine.InstallOperation.Type\x12\x13\n\x0b\x64\x61ta_offset\x18\x02 \x01(\r\x12\x13\n\x0b\x64\x61ta_length\x18\x03 \x01(\r\x12\x33\n\x0bsrc_extents\x18\x04 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\nsrc_length\x18\x05 \x01(\x04\x12\x33\n\x0b\x64st_extents\x18\x06 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\ndst_length\x18\x07 \x01(\x04\x12\x18\n\x10\x64\x61ta_sha256_hash\x18\x08 \x01(\x0c\x12\x17\n\x0fsrc_sha256_hash\x18\t \x01(\x0c\"\x91\x01\n\x04Type\x12\x0b\n\x07REPLACE\x10\x00\x12\x0e\n\nREPLACE_BZ\x10\x01\x12\x08\n\x04MOVE\x10\x02\x12\n\n\x06\x42SDIFF\x10\x03\x12\x0f\n\x0bSOURCE_COPY\x10\x04\x12\x11\n\rSOURCE_BSDIFF\x10\x05\x12\x08\n\x04ZERO\x10\x06\x12\x0b\n\x07\x44ISCARD\x10\x07\x12\x0e\n\nREPLACE_XZ\x10\x08\x12\x0b\n\x07IMGDIFF\x10\t\"\x88\x03\n\x0fPartitionUpdate\x12\x16\n\x0epartition_name\x18\x01 \x02(\t\x12\x17\n\x0frun_postinstall\x18\x02 \x01(\x08\x12\x18\n\x10postinstall_path\x18\x03 \x01(\t\x12\x17\n\x0f\x66ilesystem_type\x18\x04 \x01(\t\x12M\n\x17new_partition_signature\x18\x05 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x12\x41\n\x12old_partition_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12\x41\n\x12new_partition_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12<\n\noperations\x18\x08 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\"\xc4\x05\n\x14\x44\x65ltaArchiveManifest\x12\x44\n\x12install_operations\x18\x01 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\x12K\n\x19kernel_install_operations\x18\x02 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\x12\x18\n\nblock_size\x18\x03 \x01(\r:\x04\x34\x30\x39\x36\x12\x19\n\x11signatures_offset\x18\x04 \x01(\x04\x12\x17\n\x0fsignatures_size\x18\x05 \x01(\x04\x12>\n\x0fold_kernel_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12>\n\x0fnew_kernel_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12>\n\x0fold_rootfs_info\x18\x08 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12>\n\x0fnew_rootfs_info\x18\t \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12\x39\n\x0eold_image_info\x18\n \x01(\x0b\x32!.chromeos_update_engine.ImageInfo\x12\x39\n\x0enew_image_info\x18\x0b \x01(\x0b\x32!.chromeos_update_engine.ImageInfo\x12\x18\n\rminor_version\x18\x0c \x01(\r:\x01\x30\x12;\n\npartitions\x18\r \x03(\x0b\x32\'.chromeos_update_engine.PartitionUpdateB\x02H\x03')
+
+
+
+_INSTALLOPERATION_TYPE = _descriptor.EnumDescriptor(
+  name='Type',
+  full_name='chromeos_update_engine.InstallOperation.Type',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='REPLACE', index=0, number=0,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='REPLACE_BZ', index=1, number=1,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MOVE', index=2, number=2,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='BSDIFF', index=3, number=3,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SOURCE_COPY', index=4, number=4,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SOURCE_BSDIFF', index=5, number=5,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ZERO', index=6, number=6,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DISCARD', index=7, number=7,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='REPLACE_XZ', index=8, number=8,
+      options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='IMGDIFF', index=9, number=9,
+      options=None,
+      type=None),
+  ],
+  containing_type=None,
+  options=None,
+  serialized_start=712,
+  serialized_end=857,
+)
+
+
+_EXTENT = _descriptor.Descriptor(
+  name='Extent',
+  full_name='chromeos_update_engine.Extent',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='start_block', full_name='chromeos_update_engine.Extent.start_block', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='num_blocks', full_name='chromeos_update_engine.Extent.num_blocks', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=49,
+  serialized_end=98,
+)
+
+
+_SIGNATURES_SIGNATURE = _descriptor.Descriptor(
+  name='Signature',
+  full_name='chromeos_update_engine.Signatures.Signature',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='version', full_name='chromeos_update_engine.Signatures.Signature.version', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='data', full_name='chromeos_update_engine.Signatures.Signature.data', index=1,
+      number=2, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value="",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=180,
+  serialized_end=222,
+)
+
+_SIGNATURES = _descriptor.Descriptor(
+  name='Signatures',
+  full_name='chromeos_update_engine.Signatures',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='signatures', full_name='chromeos_update_engine.Signatures.signatures', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[_SIGNATURES_SIGNATURE, ],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=100,
+  serialized_end=222,
+)
+
+
+_PARTITIONINFO = _descriptor.Descriptor(
+  name='PartitionInfo',
+  full_name='chromeos_update_engine.PartitionInfo',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='size', full_name='chromeos_update_engine.PartitionInfo.size', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='hash', full_name='chromeos_update_engine.PartitionInfo.hash', index=1,
+      number=2, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value="",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=224,
+  serialized_end=267,
+)
+
+
+_IMAGEINFO = _descriptor.Descriptor(
+  name='ImageInfo',
+  full_name='chromeos_update_engine.ImageInfo',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='board', full_name='chromeos_update_engine.ImageInfo.board', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=unicode("", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='key', full_name='chromeos_update_engine.ImageInfo.key', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=unicode("", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='channel', full_name='chromeos_update_engine.ImageInfo.channel', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=unicode("", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='version', full_name='chromeos_update_engine.ImageInfo.version', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=unicode("", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='build_channel', full_name='chromeos_update_engine.ImageInfo.build_channel', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=unicode("", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='build_version', full_name='chromeos_update_engine.ImageInfo.build_version', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=unicode("", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=269,
+  serialized_end=388,
+)
+
+
+_INSTALLOPERATION = _descriptor.Descriptor(
+  name='InstallOperation',
+  full_name='chromeos_update_engine.InstallOperation',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='type', full_name='chromeos_update_engine.InstallOperation.type', index=0,
+      number=1, type=14, cpp_type=8, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='data_offset', full_name='chromeos_update_engine.InstallOperation.data_offset', index=1,
+      number=2, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='data_length', full_name='chromeos_update_engine.InstallOperation.data_length', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='src_extents', full_name='chromeos_update_engine.InstallOperation.src_extents', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='src_length', full_name='chromeos_update_engine.InstallOperation.src_length', index=4,
+      number=5, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='dst_extents', full_name='chromeos_update_engine.InstallOperation.dst_extents', index=5,
+      number=6, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='dst_length', full_name='chromeos_update_engine.InstallOperation.dst_length', index=6,
+      number=7, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='data_sha256_hash', full_name='chromeos_update_engine.InstallOperation.data_sha256_hash', index=7,
+      number=8, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value="",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='src_sha256_hash', full_name='chromeos_update_engine.InstallOperation.src_sha256_hash', index=8,
+      number=9, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value="",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _INSTALLOPERATION_TYPE,
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=391,
+  serialized_end=857,
+)
+
+
+_PARTITIONUPDATE = _descriptor.Descriptor(
+  name='PartitionUpdate',
+  full_name='chromeos_update_engine.PartitionUpdate',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='partition_name', full_name='chromeos_update_engine.PartitionUpdate.partition_name', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=unicode("", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='run_postinstall', full_name='chromeos_update_engine.PartitionUpdate.run_postinstall', index=1,
+      number=2, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='postinstall_path', full_name='chromeos_update_engine.PartitionUpdate.postinstall_path', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=unicode("", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='filesystem_type', full_name='chromeos_update_engine.PartitionUpdate.filesystem_type', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=unicode("", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='new_partition_signature', full_name='chromeos_update_engine.PartitionUpdate.new_partition_signature', index=4,
+      number=5, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='old_partition_info', full_name='chromeos_update_engine.PartitionUpdate.old_partition_info', index=5,
+      number=6, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='new_partition_info', full_name='chromeos_update_engine.PartitionUpdate.new_partition_info', index=6,
+      number=7, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='operations', full_name='chromeos_update_engine.PartitionUpdate.operations', index=7,
+      number=8, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=860,
+  serialized_end=1252,
+)
+
+
+_DELTAARCHIVEMANIFEST = _descriptor.Descriptor(
+  name='DeltaArchiveManifest',
+  full_name='chromeos_update_engine.DeltaArchiveManifest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='install_operations', full_name='chromeos_update_engine.DeltaArchiveManifest.install_operations', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='kernel_install_operations', full_name='chromeos_update_engine.DeltaArchiveManifest.kernel_install_operations', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='block_size', full_name='chromeos_update_engine.DeltaArchiveManifest.block_size', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=True, default_value=4096,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='signatures_offset', full_name='chromeos_update_engine.DeltaArchiveManifest.signatures_offset', index=3,
+      number=4, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='signatures_size', full_name='chromeos_update_engine.DeltaArchiveManifest.signatures_size', index=4,
+      number=5, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='old_kernel_info', full_name='chromeos_update_engine.DeltaArchiveManifest.old_kernel_info', index=5,
+      number=6, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='new_kernel_info', full_name='chromeos_update_engine.DeltaArchiveManifest.new_kernel_info', index=6,
+      number=7, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='old_rootfs_info', full_name='chromeos_update_engine.DeltaArchiveManifest.old_rootfs_info', index=7,
+      number=8, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='new_rootfs_info', full_name='chromeos_update_engine.DeltaArchiveManifest.new_rootfs_info', index=8,
+      number=9, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='old_image_info', full_name='chromeos_update_engine.DeltaArchiveManifest.old_image_info', index=9,
+      number=10, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='new_image_info', full_name='chromeos_update_engine.DeltaArchiveManifest.new_image_info', index=10,
+      number=11, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='minor_version', full_name='chromeos_update_engine.DeltaArchiveManifest.minor_version', index=11,
+      number=12, type=13, cpp_type=3, label=1,
+      has_default_value=True, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='partitions', full_name='chromeos_update_engine.DeltaArchiveManifest.partitions', index=12,
+      number=13, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=1255,
+  serialized_end=1963,
+)
+
+_SIGNATURES_SIGNATURE.containing_type = _SIGNATURES;
+_SIGNATURES.fields_by_name['signatures'].message_type = _SIGNATURES_SIGNATURE
+_INSTALLOPERATION.fields_by_name['type'].enum_type = _INSTALLOPERATION_TYPE
+_INSTALLOPERATION.fields_by_name['src_extents'].message_type = _EXTENT
+_INSTALLOPERATION.fields_by_name['dst_extents'].message_type = _EXTENT
+_INSTALLOPERATION_TYPE.containing_type = _INSTALLOPERATION;
+_PARTITIONUPDATE.fields_by_name['new_partition_signature'].message_type = _SIGNATURES_SIGNATURE
+_PARTITIONUPDATE.fields_by_name['old_partition_info'].message_type = _PARTITIONINFO
+_PARTITIONUPDATE.fields_by_name['new_partition_info'].message_type = _PARTITIONINFO
+_PARTITIONUPDATE.fields_by_name['operations'].message_type = _INSTALLOPERATION
+_DELTAARCHIVEMANIFEST.fields_by_name['install_operations'].message_type = _INSTALLOPERATION
+_DELTAARCHIVEMANIFEST.fields_by_name['kernel_install_operations'].message_type = _INSTALLOPERATION
+_DELTAARCHIVEMANIFEST.fields_by_name['old_kernel_info'].message_type = _PARTITIONINFO
+_DELTAARCHIVEMANIFEST.fields_by_name['new_kernel_info'].message_type = _PARTITIONINFO
+_DELTAARCHIVEMANIFEST.fields_by_name['old_rootfs_info'].message_type = _PARTITIONINFO
+_DELTAARCHIVEMANIFEST.fields_by_name['new_rootfs_info'].message_type = _PARTITIONINFO
+_DELTAARCHIVEMANIFEST.fields_by_name['old_image_info'].message_type = _IMAGEINFO
+_DELTAARCHIVEMANIFEST.fields_by_name['new_image_info'].message_type = _IMAGEINFO
+_DELTAARCHIVEMANIFEST.fields_by_name['partitions'].message_type = _PARTITIONUPDATE
+DESCRIPTOR.message_types_by_name['Extent'] = _EXTENT
+DESCRIPTOR.message_types_by_name['Signatures'] = _SIGNATURES
+DESCRIPTOR.message_types_by_name['PartitionInfo'] = _PARTITIONINFO
+DESCRIPTOR.message_types_by_name['ImageInfo'] = _IMAGEINFO
+DESCRIPTOR.message_types_by_name['InstallOperation'] = _INSTALLOPERATION
+DESCRIPTOR.message_types_by_name['PartitionUpdate'] = _PARTITIONUPDATE
+DESCRIPTOR.message_types_by_name['DeltaArchiveManifest'] = _DELTAARCHIVEMANIFEST
+
+class Extent(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _EXTENT
+
+  # @@protoc_insertion_point(class_scope:chromeos_update_engine.Extent)
+
+class Signatures(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+
+  class Signature(_message.Message):
+    __metaclass__ = _reflection.GeneratedProtocolMessageType
+    DESCRIPTOR = _SIGNATURES_SIGNATURE
+
+    # @@protoc_insertion_point(class_scope:chromeos_update_engine.Signatures.Signature)
+  DESCRIPTOR = _SIGNATURES
+
+  # @@protoc_insertion_point(class_scope:chromeos_update_engine.Signatures)
+
+class PartitionInfo(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _PARTITIONINFO
+
+  # @@protoc_insertion_point(class_scope:chromeos_update_engine.PartitionInfo)
+
+class ImageInfo(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _IMAGEINFO
+
+  # @@protoc_insertion_point(class_scope:chromeos_update_engine.ImageInfo)
+
+class InstallOperation(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _INSTALLOPERATION
+
+  # @@protoc_insertion_point(class_scope:chromeos_update_engine.InstallOperation)
+
+class PartitionUpdate(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _PARTITIONUPDATE
+
+  # @@protoc_insertion_point(class_scope:chromeos_update_engine.PartitionUpdate)
+
+class DeltaArchiveManifest(_message.Message):
+  __metaclass__ = _reflection.GeneratedProtocolMessageType
+  DESCRIPTOR = _DELTAARCHIVEMANIFEST
+
+  # @@protoc_insertion_point(class_scope:chromeos_update_engine.DeltaArchiveManifest)
+
+
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), 'H\003')
+# @@protoc_insertion_point(module_scope)
diff --git a/update_engine/service_delegate_android_interface.h b/update_engine/service_delegate_android_interface.h
new file mode 100644
index 0000000..7dae40f
--- /dev/null
+++ b/update_engine/service_delegate_android_interface.h
@@ -0,0 +1,79 @@
+//
+// 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_SERVICE_DELEGATE_ANDROID_INTERFACE_H_
+#define UPDATE_ENGINE_SERVICE_DELEGATE_ANDROID_INTERFACE_H_
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/errors/error.h>
+
+namespace chromeos_update_engine {
+
+// This class defines the interface exposed by the Android version of the
+// daemon service. This interface only includes the method calls that such
+// daemon exposes. For asynchronous events initiated by a class implementing
+// this interface see the ServiceObserverInterface class.
+class ServiceDelegateAndroidInterface {
+ public:
+  virtual ~ServiceDelegateAndroidInterface() = default;
+
+  // Start an update attempt to download an apply the provided |payload_url| if
+  // no other update is running. The extra |key_value_pair_headers| will be
+  // included when fetching the payload. Returns whether the update was started
+  // successfully, which means that no other update was running and the passed
+  // parameters were correct, but not necessarily that the update finished
+  // correctly.
+  virtual bool ApplyPayload(
+      const std::string& payload_url,
+      int64_t payload_offset,
+      int64_t payload_size,
+      const std::vector<std::string>& key_value_pair_headers,
+      brillo::ErrorPtr* error) = 0;
+
+  // Suspend an ongoing update. Returns true if there was an update ongoing and
+  // it was suspended. In case of failure, it returns false and sets |error|
+  // accordingly.
+  virtual bool SuspendUpdate(brillo::ErrorPtr* error) = 0;
+
+  // Resumes an update suspended with SuspendUpdate(). The update can't be
+  // suspended after it finished and this method will fail in that case.
+  // Returns whether the resume operation was successful, which only implies
+  // that there was a suspended update. In case of error, returns false and sets
+  // |error| accordingly.
+  virtual bool ResumeUpdate(brillo::ErrorPtr* error) = 0;
+
+  // Cancel the ongoing update. The update could be running or suspended, but it
+  // can't be canceled after it was done. In case of error, returns false and
+  // sets |error| accordingly.
+  virtual bool CancelUpdate(brillo::ErrorPtr* error) = 0;
+
+  // Reset the already applied update back to an idle state. This method can
+  // only be called when no update attempt is going on, and it will reset the
+  // status back to idle, deleting the currently applied update if any. In case
+  // of error, returns false and sets |error| accordingly.
+  virtual bool ResetStatus(brillo::ErrorPtr* error) = 0;
+
+ protected:
+  ServiceDelegateAndroidInterface() = default;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_SERVICE_DELEGATE_ANDROID_INTERFACE_H_
diff --git a/update_engine/service_observer_interface.h b/update_engine/service_observer_interface.h
new file mode 100644
index 0000000..75a739f
--- /dev/null
+++ b/update_engine/service_observer_interface.h
@@ -0,0 +1,52 @@
+//
+// 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_SERVICE_OBSERVER_INTERFACE_H_
+#define UPDATE_ENGINE_SERVICE_OBSERVER_INTERFACE_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/client_library/include/update_engine/update_status.h"
+#include "update_engine/common/error_code.h"
+
+namespace chromeos_update_engine {
+
+class ServiceObserverInterface {
+ public:
+  virtual ~ServiceObserverInterface() = default;
+
+  // Called whenever the value of these parameters changes. For |progress|
+  // value changes, this method will be called only if it changes significantly.
+  virtual void SendStatusUpdate(int64_t last_checked_time,
+                                double progress,
+                                update_engine::UpdateStatus status,
+                                const std::string& new_version,
+                                int64_t new_size) = 0;
+
+  // Called whenever an update attempt is completed.
+  virtual void SendPayloadApplicationComplete(ErrorCode error_code) = 0;
+
+  // Called whenever the channel we are tracking changes.
+  virtual void SendChannelChangeUpdate(const std::string& tracking_channel) = 0;
+
+ protected:
+  ServiceObserverInterface() = default;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_SERVICE_OBSERVER_INTERFACE_H_
diff --git a/update_engine/shill_proxy.cc b/update_engine/shill_proxy.cc
new file mode 100644
index 0000000..d398bba
--- /dev/null
+++ b/update_engine/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/shill_proxy.h"
+
+#include "update_engine/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/update_engine/shill_proxy.h b/update_engine/shill_proxy.h
new file mode 100644
index 0000000..4b466c9
--- /dev/null
+++ b/update_engine/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_SHILL_PROXY_H_
+#define UPDATE_ENGINE_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/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_SHILL_PROXY_H_
diff --git a/update_engine/shill_proxy_interface.h b/update_engine/shill_proxy_interface.h
new file mode 100644
index 0000000..5f6b44e
--- /dev/null
+++ b/update_engine/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_SHILL_PROXY_INTERFACE_H_
+#define UPDATE_ENGINE_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_SHILL_PROXY_INTERFACE_H_
diff --git a/update_engine/sideload_main.cc b/update_engine/sideload_main.cc
new file mode 100644
index 0000000..d02af0e
--- /dev/null
+++ b/update_engine/sideload_main.cc
@@ -0,0 +1,226 @@
+//
+// 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 <xz.h>
+
+#include <string>
+#include <vector>
+
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/asynchronous_signal_handler.h>
+#include <brillo/flag_helper.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/streams/file_stream.h>
+#include <brillo/streams/stream.h>
+
+#include "update_engine/common/boot_control.h"
+#include "update_engine/common/error_code_utils.h"
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/terminator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_attempter_android.h"
+
+using std::string;
+using std::vector;
+using update_engine::UpdateStatus;
+
+namespace {
+// The root directory used for temporary files in update_engine_sideload.
+const char kSideloadRootTempDir[] = "/tmp/update_engine_sideload";
+}  // namespace
+
+namespace chromeos_update_engine {
+namespace {
+
+void SetupLogging() {
+  string log_file;
+  logging::LoggingSettings log_settings;
+  log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+  log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+  log_settings.log_file = nullptr;
+  log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+
+  logging::InitLogging(log_settings);
+}
+
+class SideloadDaemonState : public DaemonStateInterface,
+                            public ServiceObserverInterface {
+ public:
+  explicit SideloadDaemonState(brillo::StreamPtr status_stream)
+      : status_stream_(std::move(status_stream)) {
+    // Add this class as the only observer.
+    observers_.insert(this);
+  }
+  ~SideloadDaemonState() override = default;
+
+  // DaemonStateInterface overrides.
+  bool StartUpdater() override { return true; }
+  void AddObserver(ServiceObserverInterface* observer) override {}
+  void RemoveObserver(ServiceObserverInterface* observer) override {}
+  const std::set<ServiceObserverInterface*>& service_observers() override {
+    return observers_;
+  }
+
+  // ServiceObserverInterface overrides.
+  void SendStatusUpdate(int64_t last_checked_time,
+                        double progress,
+                        UpdateStatus status,
+                        const string& new_version,
+                        int64_t new_size) override {
+    if (status_ != status && (status == UpdateStatus::DOWNLOADING ||
+                              status == UpdateStatus::FINALIZING)) {
+      // Split the progress bar in two parts for the two stages DOWNLOADING and
+      // FINALIZING.
+      ReportStatus(base::StringPrintf(
+          "ui_print Step %d/2", status == UpdateStatus::DOWNLOADING ? 1 : 2));
+      ReportStatus(base::StringPrintf("progress 0.5 0"));
+    }
+    if (status_ != status || fabs(progress - progress_) > 0.005) {
+      ReportStatus(base::StringPrintf("set_progress %.lf", progress));
+    }
+    progress_ = progress;
+    status_ = status;
+  }
+
+  void SendPayloadApplicationComplete(ErrorCode error_code) override {
+    if (error_code != ErrorCode::kSuccess) {
+      ReportStatus(
+          base::StringPrintf("ui_print Error applying update: %d (%s)",
+                             error_code,
+                             utils::ErrorCodeToString(error_code).c_str()));
+    }
+    error_code_ = error_code;
+    brillo::MessageLoop::current()->BreakLoop();
+  }
+
+  void SendChannelChangeUpdate(const string& tracking_channel) override {}
+
+  // Getters.
+  UpdateStatus status() { return status_; }
+  ErrorCode error_code() { return error_code_; }
+
+ private:
+  // Report a status message in the status_stream_, if any. These messages
+  // should conform to the specification defined in the Android recovery.
+  void ReportStatus(const string& message) {
+    if (!status_stream_)
+      return;
+    string status_line = message + "\n";
+    status_stream_->WriteAllBlocking(
+        status_line.data(), status_line.size(), nullptr);
+  }
+
+  std::set<ServiceObserverInterface*> observers_;
+  brillo::StreamPtr status_stream_;
+
+  // The last status and error code reported.
+  UpdateStatus status_{UpdateStatus::IDLE};
+  ErrorCode error_code_{ErrorCode::kSuccess};
+  double progress_{-1.};
+};
+
+// Apply an update payload directly from the given payload URI.
+bool ApplyUpdatePayload(const string& payload,
+                        int64_t payload_offset,
+                        int64_t payload_size,
+                        const vector<string>& headers,
+                        int64_t status_fd) {
+  brillo::BaseMessageLoop loop;
+  loop.SetAsCurrent();
+
+  // Setup the subprocess handler.
+  brillo::AsynchronousSignalHandler handler;
+  handler.Init();
+  Subprocess subprocess;
+  subprocess.Init(&handler);
+
+  SideloadDaemonState sideload_daemon_state(
+      brillo::FileStream::FromFileDescriptor(status_fd, true, nullptr));
+
+  // During the sideload we don't access the prefs persisted on disk but instead
+  // use a temporary memory storage.
+  MemoryPrefs prefs;
+
+  std::unique_ptr<BootControlInterface> boot_control =
+      boot_control::CreateBootControl();
+  if (!boot_control) {
+    LOG(ERROR) << "Error initializing the BootControlInterface.";
+    return false;
+  }
+
+  std::unique_ptr<HardwareInterface> hardware = hardware::CreateHardware();
+  if (!hardware) {
+    LOG(ERROR) << "Error initializing the HardwareInterface.";
+    return false;
+  }
+
+  UpdateAttempterAndroid update_attempter(
+      &sideload_daemon_state, &prefs, boot_control.get(), hardware.get());
+  update_attempter.Init();
+
+  TEST_AND_RETURN_FALSE(update_attempter.ApplyPayload(
+      payload, payload_offset, payload_size, headers, nullptr));
+
+  loop.Run();
+  return sideload_daemon_state.status() == UpdateStatus::UPDATED_NEED_REBOOT;
+}
+
+}  // namespace
+}  // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+  DEFINE_string(payload,
+                "file:///data/payload.bin",
+                "The URI to the update payload to use.");
+  DEFINE_int64(
+      offset, 0, "The offset in the payload where the CrAU update starts. ");
+  DEFINE_int64(size,
+               0,
+               "The size of the CrAU part of the payload. If 0 is passed, it "
+               "will be autodetected.");
+  DEFINE_string(headers,
+                "",
+                "A list of key-value pairs, one element of the list per line.");
+  DEFINE_int64(status_fd, -1, "A file descriptor to notify the update status.");
+
+  chromeos_update_engine::Terminator::Init();
+  chromeos_update_engine::SetupLogging();
+  brillo::FlagHelper::Init(argc, argv, "Update Engine Sideload");
+
+  LOG(INFO) << "Update Engine Sideloading starting";
+
+  // xz-embedded requires to initialize its CRC-32 table once on startup.
+  xz_crc32_init();
+
+  // When called from recovery, /data is not accessible, so we need to use
+  // /tmp for temporary files.
+  chromeos_update_engine::utils::SetRootTempDir(kSideloadRootTempDir);
+
+  vector<string> headers = base::SplitString(
+      FLAGS_headers, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  if (!chromeos_update_engine::ApplyUpdatePayload(
+          FLAGS_payload, FLAGS_offset, FLAGS_size, headers, FLAGS_status_fd))
+    return 1;
+
+  return 0;
+}
diff --git a/update_engine/system_state.h b/update_engine/system_state.h
new file mode 100644
index 0000000..4d040ec
--- /dev/null
+++ b/update_engine/system_state.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_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_SYSTEM_STATE_H_
+
+class MetricsLibraryInterface;
+
+namespace chromeos_update_manager {
+
+class UpdateManager;
+
+}  // namespace chromeos_update_manager
+
+namespace policy {
+
+class DevicePolicy;
+
+}  // namespace policy
+
+namespace chromeos_update_engine {
+
+// SystemState is the root class within the update engine. So we should avoid
+// any circular references in header file inclusion. Hence forward-declaring
+// the required classes.
+class BootControlInterface;
+class ClockInterface;
+class ConnectionManagerInterface;
+class HardwareInterface;
+class OmahaRequestParams;
+class P2PManager;
+class PayloadStateInterface;
+class PowerManagerInterface;
+class PrefsInterface;
+class UpdateAttempter;
+class WeaveServiceInterface;
+
+// An interface to global system context, including platform resources,
+// the current state of the system, high-level objects whose lifetime is same
+// as main, system interfaces, etc.
+// Carved out separately so it can be mocked for unit tests.
+// Currently it has only one method, but we should start migrating other
+// methods to use this as and when needed to unit test them.
+// TODO(jaysri): Consider renaming this to something like GlobalContext.
+class SystemState {
+ public:
+  // Destructs this object.
+  virtual ~SystemState() {}
+
+  // Sets or gets the latest device policy.
+  virtual void set_device_policy(const policy::DevicePolicy* device_policy) = 0;
+  virtual const policy::DevicePolicy* device_policy() = 0;
+
+  // Gets the interface object for the bootloader control interface.
+  virtual BootControlInterface* boot_control() = 0;
+
+  // Gets the interface object for the clock.
+  virtual ClockInterface* clock() = 0;
+
+  // Gets the connection manager object.
+  virtual ConnectionManagerInterface* connection_manager() = 0;
+
+  // Gets the hardware interface object.
+  virtual HardwareInterface* hardware() = 0;
+
+  // Gets the Metrics Library interface for reporting UMA stats.
+  virtual MetricsLibraryInterface* metrics_lib() = 0;
+
+  // Gets the interface object for persisted store.
+  virtual PrefsInterface* prefs() = 0;
+
+  // Gets the interface object for the persisted store that persists across
+  // powerwashes. Please note that this should be used very seldomly and must
+  // be forwards and backwards compatible as powerwash is used to go back and
+  // forth in system versions.
+  virtual PrefsInterface* powerwash_safe_prefs() = 0;
+
+  // Gets the interface for the payload state object.
+  virtual PayloadStateInterface* payload_state() = 0;
+
+  // Returns a pointer to the update attempter object.
+  virtual UpdateAttempter* update_attempter() = 0;
+
+  // Returns a pointer to the WeaveServiceInterface class or nullptr if none.
+  virtual WeaveServiceInterface* weave_service() = 0;
+
+  // Returns a pointer to the object that stores the parameters that are
+  // common to all Omaha requests.
+  virtual OmahaRequestParams* request_params() = 0;
+
+  // Returns a pointer to the P2PManager singleton.
+  virtual P2PManager* p2p_manager() = 0;
+
+  // Returns a pointer to the UpdateManager singleton.
+  virtual chromeos_update_manager::UpdateManager* update_manager() = 0;
+
+  // Gets the power manager object. Mocked during test.
+  virtual PowerManagerInterface* power_manager() = 0;
+
+  // If true, this is the first instance of the update engine since the system
+  // restarted. Important for tracking whether you are running instance of the
+  // update engine on first boot or due to a crash/restart.
+  virtual bool system_rebooted() = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_SYSTEM_STATE_H_
diff --git a/update_engine/tar_bunzip2.gypi b/update_engine/tar_bunzip2.gypi
new file mode 100644
index 0000000..8c6614a
--- /dev/null
+++ b/update_engine/tar_bunzip2.gypi
@@ -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.
+#
+{
+  'variables': {
+    'out_dir': '<(SHARED_INTERMEDIATE_DIR)/<(image_out_dir)',
+  },
+  'rules': [
+    {
+      'rule_name': 'tar-bunzip2',
+      'extension': 'bz2',
+      'inputs': [
+        '<(RULE_INPUT_PATH)',
+      ],
+      'outputs': [
+        # The .flag file is used to mark the timestamp of the file extraction
+        # and re-run this action if a new .bz2 file is generated.
+        '<(out_dir)/<(RULE_INPUT_ROOT).flag',
+      ],
+      'action': [
+        'sh',
+        '-c',
+        'tar -xvf "<(RULE_INPUT_PATH)" -C "<(out_dir)" && touch <(out_dir)/<(RULE_INPUT_ROOT).flag',
+      ],
+      'msvs_cygwin_shell': 0,
+      'process_outputs_as_sources': 1,
+      'message': 'Unpacking file <(RULE_INPUT_PATH)',
+    },
+  ],
+}
diff --git a/update_engine/test_http_server.cc b/update_engine/test_http_server.cc
new file mode 100644
index 0000000..2955e79
--- /dev/null
+++ b/update_engine/test_http_server.cc
@@ -0,0 +1,647 @@
+//
+// 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.
+//
+
+// This file implements a simple HTTP server. It can exhibit odd behavior
+// that's useful for testing. For example, it's useful to test that
+// the updater can continue a connection if it's dropped, or that it
+// handles very slow data transfers.
+
+// To use this, simply make an HTTP connection to localhost:port and
+// GET a url.
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/http_common.h"
+
+
+// HTTP end-of-line delimiter; sorry, this needs to be a macro.
+#define EOL "\r\n"
+
+using std::string;
+using std::vector;
+
+
+namespace chromeos_update_engine {
+
+static const char* kListeningMsgPrefix = "listening on port ";
+
+enum {
+  RC_OK = 0,
+  RC_BAD_ARGS,
+  RC_ERR_READ,
+  RC_ERR_SETSOCKOPT,
+  RC_ERR_BIND,
+  RC_ERR_LISTEN,
+  RC_ERR_GETSOCKNAME,
+  RC_ERR_REPORT,
+};
+
+struct HttpRequest {
+  string raw_headers;
+  string host;
+  string url;
+  off_t start_offset{0};
+  off_t end_offset{0};  // non-inclusive, zero indicates unspecified.
+  HttpResponseCode return_code{kHttpResponseOk};
+};
+
+bool ParseRequest(int fd, HttpRequest* request) {
+  string headers;
+  do {
+    char buf[1024];
+    ssize_t r = read(fd, buf, sizeof(buf));
+    if (r < 0) {
+      perror("read");
+      exit(RC_ERR_READ);
+    }
+    headers.append(buf, r);
+  } while (!base::EndsWith(headers, EOL EOL, base::CompareCase::SENSITIVE));
+
+  LOG(INFO) << "got headers:\n--8<------8<------8<------8<----\n"
+            << headers
+            << "\n--8<------8<------8<------8<----";
+  request->raw_headers = headers;
+
+  // Break header into lines.
+  vector<string> lines;
+  base::SplitStringUsingSubstr(
+      headers.substr(0, headers.length() - strlen(EOL EOL)), EOL, &lines);
+
+  // Decode URL line.
+  vector<string> terms = base::SplitString(lines[0], base::kWhitespaceASCII,
+                                           base::KEEP_WHITESPACE,
+                                           base::SPLIT_WANT_NONEMPTY);
+  CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(3));
+  CHECK_EQ(terms[0], "GET");
+  request->url = terms[1];
+  LOG(INFO) << "URL: " << request->url;
+
+  // Decode remaining lines.
+  size_t i;
+  for (i = 1; i < lines.size(); i++) {
+    terms = base::SplitString(lines[i], base::kWhitespaceASCII,
+                              base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+    if (terms[0] == "Range:") {
+      CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2));
+      string &range = terms[1];
+      LOG(INFO) << "range attribute: " << range;
+      CHECK(base::StartsWith(range, "bytes=", base::CompareCase::SENSITIVE) &&
+            range.find('-') != string::npos);
+      request->start_offset = atoll(range.c_str() + strlen("bytes="));
+      // Decode end offset and increment it by one (so it is non-inclusive).
+      if (range.find('-') < range.length() - 1)
+        request->end_offset = atoll(range.c_str() + range.find('-') + 1) + 1;
+      request->return_code = kHttpResponsePartialContent;
+      string tmp_str = base::StringPrintf("decoded range offsets: "
+                                               "start=%jd end=",
+                                               (intmax_t)request->start_offset);
+      if (request->end_offset > 0)
+        base::StringAppendF(&tmp_str, "%jd (non-inclusive)",
+                            (intmax_t)request->end_offset);
+      else
+        base::StringAppendF(&tmp_str, "unspecified");
+      LOG(INFO) << tmp_str;
+    } else if (terms[0] == "Host:") {
+      CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2));
+      request->host = terms[1];
+      LOG(INFO) << "host attribute: " << request->host;
+    } else {
+      LOG(WARNING) << "ignoring HTTP attribute: `" << lines[i] << "'";
+    }
+  }
+
+  return true;
+}
+
+string Itoa(off_t num) {
+  char buf[100] = {0};
+  snprintf(buf, sizeof(buf), "%" PRIi64, num);
+  return buf;
+}
+
+// Writes a string into a file. Returns total number of bytes written or -1 if a
+// write error occurred.
+ssize_t WriteString(int fd, const string& str) {
+  const size_t total_size = str.size();
+  size_t remaining_size = total_size;
+  char const *data = str.data();
+
+  while (remaining_size) {
+    ssize_t written = write(fd, data, remaining_size);
+    if (written < 0) {
+      perror("write");
+      LOG(INFO) << "write failed";
+      return -1;
+    }
+    data += written;
+    remaining_size -= written;
+  }
+
+  return total_size;
+}
+
+// Writes the headers of an HTTP response into a file.
+ssize_t WriteHeaders(int fd, const off_t start_offset, const off_t end_offset,
+                     HttpResponseCode return_code) {
+  ssize_t written = 0, ret;
+
+  ret = WriteString(fd,
+                    string("HTTP/1.1 ") + Itoa(return_code) + " " +
+                    GetHttpResponseDescription(return_code) +
+                    EOL
+                    "Content-Type: application/octet-stream" EOL);
+  if (ret < 0)
+    return -1;
+  written += ret;
+
+  // Compute content legnth.
+  const off_t content_length = end_offset - start_offset;;
+
+  // A start offset that equals the end offset indicates that the response
+  // should contain the full range of bytes in the requested resource.
+  if (start_offset || start_offset == end_offset) {
+    ret = WriteString(fd,
+                      string("Accept-Ranges: bytes" EOL
+                             "Content-Range: bytes ") +
+                      Itoa(start_offset == end_offset ? 0 : start_offset) +
+                      "-" + Itoa(end_offset - 1) + "/" + Itoa(end_offset) +
+                      EOL);
+    if (ret < 0)
+      return -1;
+    written += ret;
+  }
+
+  ret = WriteString(fd, string("Content-Length: ") + Itoa(content_length) +
+                    EOL EOL);
+  if (ret < 0)
+    return -1;
+  written += ret;
+
+  return written;
+}
+
+// Writes a predetermined payload of lines of ascending bytes to a file. The
+// first byte of output is appropriately offset with respect to the request line
+// length.  Returns the number of successfully written bytes.
+size_t WritePayload(int fd, const off_t start_offset, const off_t end_offset,
+                    const char first_byte, const size_t line_len) {
+  CHECK_LE(start_offset, end_offset);
+  CHECK_GT(line_len, static_cast<size_t>(0));
+
+  LOG(INFO) << "writing payload: " << line_len << "-byte lines starting with `"
+            << first_byte << "', offset range " << start_offset << " -> "
+            << end_offset;
+
+  // Populate line of ascending characters.
+  string line;
+  line.reserve(line_len);
+  char byte = first_byte;
+  size_t i;
+  for (i = 0; i < line_len; i++)
+    line += byte++;
+
+  const size_t total_len = end_offset - start_offset;
+  size_t remaining_len = total_len;
+  bool success = true;
+
+  // If start offset is not aligned with line boundary, output partial line up
+  // to the first line boundary.
+  size_t start_modulo = start_offset % line_len;
+  if (start_modulo) {
+    string partial = line.substr(start_modulo, remaining_len);
+    ssize_t ret = WriteString(fd, partial);
+    if ((success = (ret >= 0 && (size_t) ret == partial.length())))
+      remaining_len -= partial.length();
+  }
+
+  // Output full lines up to the maximal line boundary below the end offset.
+  while (success && remaining_len >= line_len) {
+    ssize_t ret = WriteString(fd, line);
+    if ((success = (ret >= 0 && (size_t) ret == line_len)))
+      remaining_len -= line_len;
+  }
+
+  // Output a partial line up to the end offset.
+  if (success && remaining_len) {
+    string partial = line.substr(0, remaining_len);
+    ssize_t ret = WriteString(fd, partial);
+    if ((success = (ret >= 0 && (size_t) ret == partial.length())))
+      remaining_len -= partial.length();
+  }
+
+  return (total_len - remaining_len);
+}
+
+// Write default payload lines of the form 'abcdefghij'.
+inline size_t WritePayload(int fd, const off_t start_offset,
+                           const off_t end_offset) {
+  return WritePayload(fd, start_offset, end_offset, 'a', 10);
+}
+
+// Send an empty response, then kill the server.
+void HandleQuit(int fd) {
+  WriteHeaders(fd, 0, 0, kHttpResponseOk);
+  LOG(INFO) << "pid(" << getpid() <<  "): HTTP server exiting ...";
+  exit(RC_OK);
+}
+
+
+// Generates an HTTP response with payload corresponding to requested offsets
+// and length.  Optionally, truncate the payload at a given length and add a
+// pause midway through the transfer.  Returns the total number of bytes
+// delivered or -1 for error.
+ssize_t HandleGet(int fd, const HttpRequest& request, const size_t total_length,
+                  const size_t truncate_length, const int sleep_every,
+                  const int sleep_secs) {
+  ssize_t ret;
+  size_t written = 0;
+
+  // Obtain start offset, make sure it is within total payload length.
+  const size_t start_offset = request.start_offset;
+  if (start_offset >= total_length) {
+    LOG(WARNING) << "start offset (" << start_offset
+                 << ") exceeds total length (" << total_length
+                 << "), generating error response ("
+                 << kHttpResponseReqRangeNotSat << ")";
+    return WriteHeaders(fd, total_length, total_length,
+                        kHttpResponseReqRangeNotSat);
+  }
+
+  // Obtain end offset, adjust to fit in total payload length and ensure it does
+  // not preceded the start offset.
+  size_t end_offset = (request.end_offset > 0 ?
+                       request.end_offset : total_length);
+  if (end_offset < start_offset) {
+    LOG(WARNING) << "end offset (" << end_offset << ") precedes start offset ("
+                 << start_offset << "), generating error response";
+    return WriteHeaders(fd, 0, 0, kHttpResponseBadRequest);
+  }
+  if (end_offset > total_length) {
+    LOG(INFO) << "requested end offset (" << end_offset
+              << ") exceeds total length (" << total_length << "), adjusting";
+    end_offset = total_length;
+  }
+
+  // Generate headers
+  LOG(INFO) << "generating response header: range=" << start_offset << "-"
+            << (end_offset - 1) << "/" << (end_offset - start_offset)
+            << ", return code=" << request.return_code;
+  if ((ret = WriteHeaders(fd, start_offset, end_offset,
+                          request.return_code)) < 0)
+    return -1;
+  LOG(INFO) << ret << " header bytes written";
+  written += ret;
+
+  // Compute payload length, truncate as necessary.
+  size_t payload_length = end_offset - start_offset;
+  if (truncate_length > 0 && truncate_length < payload_length) {
+    LOG(INFO) << "truncating request payload length (" << payload_length
+              << ") at " << truncate_length;
+    payload_length = truncate_length;
+    end_offset = start_offset + payload_length;
+  }
+
+  LOG(INFO) << "generating response payload: range=" << start_offset << "-"
+            << (end_offset - 1) << "/" << (end_offset - start_offset);
+
+  // Decide about optional midway delay.
+  if (truncate_length > 0 && sleep_every > 0 && sleep_secs >= 0 &&
+      start_offset % (truncate_length * sleep_every) == 0) {
+    const off_t midway_offset = start_offset + payload_length / 2;
+
+    if ((ret = WritePayload(fd, start_offset, midway_offset)) < 0)
+      return -1;
+    LOG(INFO) << ret << " payload bytes written (first chunk)";
+    written += ret;
+
+    LOG(INFO) << "sleeping for " << sleep_secs << " seconds...";
+    sleep(sleep_secs);
+
+    if ((ret = WritePayload(fd, midway_offset, end_offset)) < 0)
+      return -1;
+    LOG(INFO) << ret << " payload bytes written (second chunk)";
+    written += ret;
+  } else {
+    if ((ret = WritePayload(fd, start_offset, end_offset)) < 0)
+      return -1;
+    LOG(INFO) << ret << " payload bytes written";
+    written += ret;
+  }
+
+  LOG(INFO) << "response generation complete, " << written
+            << " total bytes written";
+  return written;
+}
+
+ssize_t HandleGet(int fd, const HttpRequest& request,
+                  const size_t total_length) {
+  return HandleGet(fd, request, total_length, 0, 0, 0);
+}
+
+// Handles /redirect/<code>/<url> requests by returning the specified
+// redirect <code> with a location pointing to /<url>.
+void HandleRedirect(int fd, const HttpRequest& request) {
+  LOG(INFO) << "Redirecting...";
+  string url = request.url;
+  CHECK_EQ(static_cast<size_t>(0), url.find("/redirect/"));
+  url.erase(0, strlen("/redirect/"));
+  string::size_type url_start = url.find('/');
+  CHECK_NE(url_start, string::npos);
+  HttpResponseCode code = StringToHttpResponseCode(url.c_str());
+  url.erase(0, url_start);
+  url = "http://" + request.host + url;
+  const char *status = GetHttpResponseDescription(code);
+  if (!status)
+    CHECK(false) << "Unrecognized redirection code: " << code;
+  LOG(INFO) << "Code: " << code << " " << status;
+  LOG(INFO) << "New URL: " << url;
+
+  ssize_t ret;
+  if ((ret = WriteString(fd, "HTTP/1.1 " + Itoa(code) + " " +
+                         status + EOL)) < 0)
+    return;
+  WriteString(fd, "Location: " + url + EOL);
+}
+
+// Generate a page not found error response with actual text payload. Return
+// number of bytes written or -1 for error.
+ssize_t HandleError(int fd, const HttpRequest& request) {
+  LOG(INFO) << "Generating error HTTP response";
+
+  ssize_t ret;
+  size_t written = 0;
+
+  const string data("This is an error page.");
+
+  if ((ret = WriteHeaders(fd, 0, data.size(), kHttpResponseNotFound)) < 0)
+    return -1;
+  written += ret;
+
+  if ((ret = WriteString(fd, data)) < 0)
+    return -1;
+  written += ret;
+
+  return written;
+}
+
+// Generate an error response if the requested offset is nonzero, up to a given
+// maximal number of successive failures.  The error generated is an "Internal
+// Server Error" (500).
+ssize_t HandleErrorIfOffset(int fd, const HttpRequest& request,
+                            size_t end_offset, int max_fails) {
+  static int num_fails = 0;
+
+  if (request.start_offset > 0 && num_fails < max_fails) {
+    LOG(INFO) << "Generating error HTTP response";
+
+    ssize_t ret;
+    size_t written = 0;
+
+    const string data("This is an error page.");
+
+    if ((ret = WriteHeaders(fd, 0, data.size(),
+                            kHttpResponseInternalServerError)) < 0)
+      return -1;
+    written += ret;
+
+    if ((ret = WriteString(fd, data)) < 0)
+      return -1;
+    written += ret;
+
+    num_fails++;
+    return written;
+  } else {
+    num_fails = 0;
+    return HandleGet(fd, request, end_offset);
+  }
+}
+
+// Returns a valid response echoing in the body of the response all the headers
+// sent by the client.
+void HandleEchoHeaders(int fd, const HttpRequest& request) {
+  WriteHeaders(fd, 0, request.raw_headers.size(), kHttpResponseOk);
+  WriteString(fd, request.raw_headers);
+}
+
+void HandleHang(int fd) {
+  LOG(INFO) << "Hanging until the other side of the connection is closed.";
+  char c;
+  while (HANDLE_EINTR(read(fd, &c, 1)) > 0) {}
+}
+
+void HandleDefault(int fd, const HttpRequest& request) {
+  const off_t start_offset = request.start_offset;
+  const string data("unhandled path");
+  const size_t size = data.size();
+  ssize_t ret;
+
+  if ((ret = WriteHeaders(fd, start_offset, size, request.return_code)) < 0)
+    return;
+  WriteString(fd, (start_offset < static_cast<off_t>(size) ?
+                   data.substr(start_offset) : ""));
+}
+
+
+// Break a URL into terms delimited by slashes.
+class UrlTerms {
+ public:
+  UrlTerms(const string &url, size_t num_terms) {
+    // URL must be non-empty and start with a slash.
+    CHECK_GT(url.size(), static_cast<size_t>(0));
+    CHECK_EQ(url[0], '/');
+
+    // Split it into terms delimited by slashes, omitting the preceding slash.
+    terms = base::SplitString(url.substr(1), "/", base::KEEP_WHITESPACE,
+                              base::SPLIT_WANT_ALL);
+
+    // Ensure expected length.
+    CHECK_EQ(terms.size(), num_terms);
+  }
+
+  inline string Get(const off_t index) const {
+    return terms[index];
+  }
+  inline const char *GetCStr(const off_t index) const {
+    return Get(index).c_str();
+  }
+  inline int GetInt(const off_t index) const {
+    return atoi(GetCStr(index));
+  }
+  inline size_t GetSizeT(const off_t index) const {
+    return static_cast<size_t>(atol(GetCStr(index)));
+  }
+
+ private:
+  vector<string> terms;
+};
+
+void HandleConnection(int fd) {
+  HttpRequest request;
+  ParseRequest(fd, &request);
+
+  string &url = request.url;
+  LOG(INFO) << "pid(" << getpid() <<  "): handling url " << url;
+  if (url == "/quitquitquit") {
+    HandleQuit(fd);
+  } else if (base::StartsWith(
+                 url, "/download/", base::CompareCase::SENSITIVE)) {
+    const UrlTerms terms(url, 2);
+    HandleGet(fd, request, terms.GetSizeT(1));
+  } else if (base::StartsWith(url, "/flaky/", base::CompareCase::SENSITIVE)) {
+    const UrlTerms terms(url, 5);
+    HandleGet(fd, request, terms.GetSizeT(1), terms.GetSizeT(2),
+              terms.GetInt(3), terms.GetInt(4));
+  } else if (url.find("/redirect/") == 0) {
+    HandleRedirect(fd, request);
+  } else if (url == "/error") {
+    HandleError(fd, request);
+  } else if (base::StartsWith(url, "/error-if-offset/",
+                              base::CompareCase::SENSITIVE)) {
+    const UrlTerms terms(url, 3);
+    HandleErrorIfOffset(fd, request, terms.GetSizeT(1), terms.GetInt(2));
+  } else if (url == "/echo-headers") {
+    HandleEchoHeaders(fd, request);
+  } else if (url == "/hang") {
+    HandleHang(fd);
+  } else {
+    HandleDefault(fd, request);
+  }
+
+  close(fd);
+}
+
+}  // namespace chromeos_update_engine
+
+using namespace chromeos_update_engine;  // NOLINT(build/namespaces)
+
+
+void usage(const char *prog_arg) {
+  fprintf(
+      stderr,
+      "Usage: %s [ FILE ]\n"
+      "Once accepting connections, the following is written to FILE (or "
+      "stdout):\n"
+      "\"%sN\" (where N is an integer port number)\n",
+      basename(prog_arg), kListeningMsgPrefix);
+}
+
+int main(int argc, char** argv) {
+  // Check invocation.
+  if (argc > 2)
+    errx(RC_BAD_ARGS, "unexpected number of arguments (use -h for usage)");
+
+  // Parse (optional) argument.
+  int report_fd = STDOUT_FILENO;
+  if (argc == 2) {
+    if (!strcmp(argv[1], "-h")) {
+      usage(argv[0]);
+      exit(RC_OK);
+    }
+
+    report_fd = open(argv[1], O_WRONLY | O_CREAT, 00644);
+  }
+
+  // Ignore SIGPIPE on write() to sockets.
+  signal(SIGPIPE, SIG_IGN);
+
+  int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
+  if (listen_fd < 0)
+    LOG(FATAL) << "socket() failed";
+
+  struct sockaddr_in server_addr = sockaddr_in();
+  server_addr.sin_family = AF_INET;
+  server_addr.sin_addr.s_addr = INADDR_ANY;
+  server_addr.sin_port = 0;
+
+  {
+    // Get rid of "Address in use" error
+    int tr = 1;
+    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr,
+                   sizeof(int)) == -1) {
+      perror("setsockopt");
+      exit(RC_ERR_SETSOCKOPT);
+    }
+  }
+
+  // Bind the socket and set for listening.
+  if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
+           sizeof(server_addr)) < 0) {
+    perror("bind");
+    exit(RC_ERR_BIND);
+  }
+  if (listen(listen_fd, 5) < 0) {
+    perror("listen");
+    exit(RC_ERR_LISTEN);
+  }
+
+  // Check the actual port.
+  struct sockaddr_in bound_addr = sockaddr_in();
+  socklen_t bound_addr_len = sizeof(bound_addr);
+  if (getsockname(listen_fd, reinterpret_cast<struct sockaddr*>(&bound_addr),
+                  &bound_addr_len) < 0) {
+    perror("getsockname");
+    exit(RC_ERR_GETSOCKNAME);
+  }
+  in_port_t port = ntohs(bound_addr.sin_port);
+
+  // Output the listening port, indicating that the server is processing
+  // requests. IMPORTANT! (a) the format of this message is as expected by some
+  // unit tests, avoid unilateral changes; (b) it is necessary to flush/sync the
+  // file to prevent the spawning process from waiting indefinitely for this
+  // message.
+  string listening_msg = base::StringPrintf("%s%hu", kListeningMsgPrefix, port);
+  LOG(INFO) << listening_msg;
+  CHECK_EQ(write(report_fd, listening_msg.c_str(), listening_msg.length()),
+           static_cast<int>(listening_msg.length()));
+  CHECK_EQ(write(report_fd, "\n", 1), 1);
+  if (report_fd == STDOUT_FILENO)
+    fsync(report_fd);
+  else
+    close(report_fd);
+
+  while (1) {
+    LOG(INFO) << "pid(" << getpid() <<  "): waiting to accept new connection";
+    int client_fd = accept(listen_fd, nullptr, nullptr);
+    LOG(INFO) << "got past accept";
+    if (client_fd < 0)
+      LOG(FATAL) << "ERROR on accept";
+    HandleConnection(client_fd);
+  }
+  return 0;
+}
diff --git a/update_engine/test_subprocess.cc b/update_engine/test_subprocess.cc
new file mode 100644
index 0000000..c7f4a37
--- /dev/null
+++ b/update_engine/test_subprocess.cc
@@ -0,0 +1,59 @@
+//
+// 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.
+//
+
+// This is a simple program used to test interaction with update_engine when
+// executing other programs. This program receives pre-programmed actions in the
+// command line and executes them in order.
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#define EX_USAGE_ERROR 100
+
+void usage(const char* program, const char* error) {
+  if (error)
+    fprintf(stderr, "ERROR: %s\n", error);
+  fprintf(stderr, "Usage: %s <cmd> [args..]\n", program);
+  exit(EX_USAGE_ERROR);
+}
+
+int main(int argc, char** argv, char** envp) {
+  if (argc < 2)
+    usage(argv[0], "No command passed");
+
+  std::string cmd(argv[1]);
+  if (cmd == "fstat") {
+    // Call fstat on the passed file descriptor number
+    if (argc < 3)
+      usage(argv[0], "No fd passed to fstat");
+    int fd = atoi(argv[2]);
+    struct stat buf;
+    int rc = fstat(fd, &buf);
+    if (rc < 0) {
+      int ret = errno;
+      perror("fstat");
+      return ret;
+    }
+    return 0;
+  }
+
+  usage(argv[0], "Unknown command");
+}
diff --git a/update_engine/testrunner.cc b/update_engine/testrunner.cc
new file mode 100644
index 0000000..934ea91
--- /dev/null
+++ b/update_engine/testrunner.cc
@@ -0,0 +1,64 @@
+//
+// 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.
+//
+
+// based on pam_google_testrunner.cc
+
+#include <string>
+
+#include <xz.h>
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/environment.h>
+#include <brillo/test_helpers.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/terminator.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_generator/xz.h"
+
+int main(int argc, char **argv) {
+  LOG(INFO) << "started";
+  base::AtExitManager exit_manager;
+  // xz-embedded requires to initialize its CRC-32 table once on startup.
+  xz_crc32_init();
+  // The LZMA SDK-based Xz compressor used in the payload generation requires
+  // this one-time initialization.
+  chromeos_update_engine::XzCompressInit();
+  // TODO(garnold) temporarily cause the unittest binary to exit with status
+  // code 2 upon catching a SIGTERM. This will help diagnose why the unittest
+  // binary is perceived as failing by the buildbot.  We should revert it to use
+  // the default exit status of 1.  Corresponding reverts are necessary in
+  // terminator_unittest.cc.
+  chromeos_update_engine::Terminator::Init(2);
+  // In Android bsdiff is located in update_engine_unittests, add it to PATH.
+#ifdef __ANDROID__
+  std::unique_ptr<base::Environment> env(base::Environment::Create());
+  std::string path_env;
+  CHECK(env->GetVar("PATH", &path_env));
+  path_env +=
+      ":" + chromeos_update_engine::test_utils::GetBuildArtifactsPath().value();
+  CHECK(env->SetVar("PATH", path_env));
+#endif
+  LOG(INFO) << "parsing command line arguments";
+  base::CommandLine::Init(argc, argv);
+  LOG(INFO) << "initializing gtest";
+  SetUpTests(&argc, argv, true);
+  LOG(INFO) << "running unit tests";
+  int test_result = RUN_ALL_TESTS();
+  LOG(INFO) << "unittest return value: " << test_result;
+  return test_result;
+}
diff --git a/update_engine/unittest_key.pem b/update_engine/unittest_key.pem
new file mode 100644
index 0000000..224d3c3
--- /dev/null
+++ b/update_engine/unittest_key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAx6hqS+Hbc7jN82ek+OBkHJNvlWXktyS2XCuQtGtnHjEV7Ou5
+hHNF96s5wEn8RDtrdLocm0a+SqXXf7KycFRTZxLdDbqWAMUlPXOaPI+PZLWkB7/K
+V7Cdj6vPGbeq7elu+aT/budhzTvq2stYrrAlcr/21tOVDPiWtfCdykh7FCXi6FaY
+HoNy5A6EKQLfLBuJoU/QoCufldmwlFaFDKl+Koos6R1QYJfCNZfgocrW1PIh+8t1
+JIvw6Izo8+fTme7nev7Ol2YZiMWJpRYKx8MgxW2UgTXlRpmQN56pc9LUsnVA8FND
+BN57+gMJj+XmdDmbtMpOsyXfSNEgnuwMPcEcmwIDAQABAoIBABPkwgKhlH4pUcwI
+7bUmlpMKVbnrFyjwbYMtjBOOCA5IEckzi56Y5cXRt8VjGdGqogBVcvg9ykQh1iER
+KxpqLI0+oev2RW/6NMW0uQ+DtmPwfVGQWJb4MBraoZ4MYOmnsrkJKbJhN6t9Zt86
+F7IANxsB6ZRqLJXIRywFt5MqOak+GAnQJ8C8eSQg70NhbEhSOrD8wrD6tfvgIqta
+XxhtlQWUAILIWetnWrJsalMqnreGn7vhc7+iihhMtXh1xNBMTA+gzpov/Cx21iH5
+DM9ppSA6HHDXrMhauryypIRrhjOUWRyDws/kIHgIW4TCbULOlxqsputQeTmdf0ti
+7lpwqAECgYEA7nNKkct3Vs51Ugk4GUPC4NOyYRPNc9UQAfHViB9gSDRacCo9Ax9J
+83hJGqDXlNGzELOnhzMn8jQMyF13eWzOsMozK6Fj3uW7DBvelg5bfgsZDUUO5WUF
+6BYbOheVqf12rIHR9BKBmCfLEKyxbKmw5bnB0uNo7IuBPBNuhPbvkgECgYEA1lo5
+XHWJpQnVl+JzXLHpXBK2nfnFAOtvzlTW+7gteeU12X2HcFASrzp7C1ULVV+i1Kcz
+tDFIA5yiFjEqmSJ/TsO8aqAhL5BXJjylCepQK7XkEOGCR8eQjlt7E4DulAsQbfpt
+k30HVVlIOFqLCWKSW8M3dy/Plodq/Gyq26rntpsCgYAzsNyGdIQfVkxKh2MY3v6c
+/Gdb8g4EwThiI4m1o4+ct3SvggiN57eBRx8Z3ao+QaM+yKNVhLpxH+VxfgmLUhIQ
+cxTarXbX+BcvTc9X2i7tSPyaStEq21aHdFtcoYY5Po/+X3ojHevoDyBPMhCYTMTj
+V/xzegbh2HAglNnNizZuAQKBgQCyOxEpBP5vgS7d/MgJklFEYrb/wjgBnMI5oSek
+5C7EBUdyUUM1qw7uLsUy1gL3eO7uvRxrvvJvNmU76KPP8vRCLNTVH9KYNv+P5qsg
+BHmm7rX1J11pi9Fx3TUIMZOu+0gs+ib0lOhtGjDH0tl680BZFohfDR0hv/XAcCbd
+Qk0q8wKBgQCGbURFFW/5RZUA77MmpY6jsEMw2gJmVtjO5IWZg9VPvLQQPgCr4Ho/
+bS2LIsT6pS+hrLOoz5KI0YueRS0jQYI+WkRqNf5wYNjxPql9632FiDLHO+Xv8PBe
+kHrPHy0GGT1igXScY4eemw4ZC1OSdZfkVn6Ui/JvBHrydJ2LrutMWQ==
+-----END RSA PRIVATE KEY-----
diff --git a/update_engine/unittest_key2.pem b/update_engine/unittest_key2.pem
new file mode 100644
index 0000000..d1f9a78
--- /dev/null
+++ b/update_engine/unittest_key2.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAqdM7gnd43cWfQzyrt16QDHE+T0yxg18OZO+9sc8jWpjC1zFt
+oMFokESiu9TLUp+KUC274f+DxIgAfSCO6U5DJFRPXUzve1vaXYfqljUBxc+JYcGd
+d6PCUl4Ap9f02DhkrGnf/rkHOCEhFNpm53dovZ9A6Q5KBfcg1HeQ088opVceQGtU
+mL+bONq5tLvNFLUU0Ru0Am4ADJ8Xmw4rpvqvmXJaFmVuf/Gx7+TOnaJvZTeOO9AJ
+K6q6V8E7+ZZiMIc5F4EOs5AX/lZfNO3RVgG2O7Dhzza+nOzJ3ZJGK5R4WwZduhn9
+QRYogYPB0c62878qXpf2Bn3NpUPi8Cf/RLMMmQIDAQABAoIBACyLUWKpP7S770hN
+k6TnUtVQps1aCn2w4y+qipEnCdjrlL+pIV43HNwqhJzL9gDYBAl/1XYz9TYJjkdD
+0Ph1JLtUufR5B5/NufsqeWeow6xFAX34sPr+oyvDqFxeEsTcFdv7cVt44OHiHrE/
+kBpKgdiq+vWmX9gsuBnCuuQzxC+Juo6nupwZXcpa/ow9lC4QsgKqcjaUGrXXy2t9
+Er+9aSl8NdTjK76BXQsDgNkDyJZwNN14xrdS8eFsS4twskaOEYI4hEM0g62NOjgd
+Po8Ap/MnPpGSGcAd3d3Fq8KgT1lpyMKedLFU+k0H+/Y4RBl7grz1XXvSTzGi3Qy6
+38F4eVkCgYEA4mo4iiXSfrov9QffsVM17O0t9hUsOJHnUsEScxWLDm4IzaObyTtv
+tWW33iQSeFu4Wsol0nzjqWo3BaqiRidRUd42yZ07LJvfUDxUX9xPaUPFRs25iwhZ
+6tKAVqGk7/CFrN+R44sIwbsSvbExMAyW6gnj93EWUmMWWYp02hLbN0sCgYEAwAQI
+awVoc56OCtRpfYtlAPD/VOP1mbNzRmVl/UyZ4XYmz6f/hEz63Bk5PhYSZftlmK/r
+nj4qnl7HZ8jrJgZn2e97rPNpk7KDVU1+csCgLWZBTOXl/o9tOTyjh9LoRAjKtBB7
+x6CkWyiyd94xIq5VbnXhvL3a4d4o6OxMWdG5aSsCgYEAo44z1afIzP7WkdzkPIZt
+l/8linR1A1BymBccqsHPN9dIyLP9X3puEc2u6uuH5CXtoLgSZmENXF577L38h0zz
+s34gebf4/RqEUMOj97OAMfxgz+rgs4yO19DEINCYAzPufJjsHEFdTAVFXn5Xl+wg
+QGRwp1ir1Uv64yffjYC9ls0CgYEAjvIxpiKniPNvwUYypmDgt5uyKetvCpaaabzQ
++YpOQJep+wuRYFfCpZotkDf0SHGoR8wnd23GYpIilvPvgyZfp9HuW2n2nhrWROnl
+Cd63IDUwxeOcni7+XA71mwb7HLMC3Jws2geQc8DPZAdIww3P0eT2QYGBcobmI8jO
+akuEYXMCgYAm79Kb/r+3Hew5oAS1Whw70DskVlOutSgNsDPfW9MtDcnETBcGep7A
+1jCL5jjdUYRonimVMFjh1K+UFzV/DQHkgNzjxz9Inbh02y67vL2X836dS9esOcbx
+uZhf+8rL+GnSNqYDqCEuP7qCIloDhguJq9NKyTB4yc59qIkY2zPAzQ==
+-----END RSA PRIVATE KEY-----
diff --git a/update_engine/update_attempter.cc b/update_engine/update_attempter.cc
new file mode 100644
index 0000000..8afc395
--- /dev/null
+++ b/update_engine/update_attempter.cc
@@ -0,0 +1,1592 @@
+//
+// 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/update_attempter.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/rand_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/make_unique_ptr.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/hardware_interface.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/metrics.h"
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/omaha_response_handler_action.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_consumer/download_action.h"
+#include "update_engine/payload_consumer/filesystem_verifier_action.h"
+#include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/power_manager_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/update_status_utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using brillo::MessageLoop;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateCheckParams;
+using std::set;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+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
+
+// 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) {
+  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,
+                                 LibCrosProxy* libcros_proxy)
+    : processor_(new ActionProcessor()),
+      system_state_(system_state),
+#if USE_LIBCROS
+      cert_checker_(cert_checker),
+      chrome_proxy_resolver_(libcros_proxy) {
+#else
+      cert_checker_(cert_checker) {
+#endif  // USE_LIBCROS
+}
+
+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;
+
+#if USE_LIBCROS
+  chrome_proxy_resolver_.Init();
+#endif  // USE_LIBCROS
+}
+
+void UpdateAttempter::ScheduleUpdates() {
+  if (IsUpdateRunningOrScheduled())
+    return;
+
+  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->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+  waiting_for_scheduled_check_ = true;
+}
+
+void UpdateAttempter::CertificateChecked(ServerToCheck server_to_check,
+                                         CertificateCheckResult result) {
+  metrics::ReportCertificateCheckMetrics(system_state_,
+                                         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 = utils::TimeFromStructTimespec(&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;
+  }
+
+  metrics::ReportDailyMetrics(system_state_, age);
+}
+
+void UpdateAttempter::Update(const string& app_version,
+                             const string& omaha_url,
+                             const string& target_channel,
+                             const string& target_version_prefix,
+                             bool obey_proxies,
+                             bool interactive) {
+  // 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();
+
+  // Notify of the new update attempt, clearing prior interactive requests.
+  if (forced_update_pending_callback_.get())
+    forced_update_pending_callback_->Run(false, false);
+
+  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";
+    metrics::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(app_version,
+                             omaha_url,
+                             target_channel,
+                             target_version_prefix,
+                             obey_proxies,
+                             interactive)) {
+    return;
+  }
+
+  BuildUpdateActions(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();
+
+  // Just in case we didn't update boot flags yet, make sure they're updated
+  // before any update processing starts.
+  start_action_processor_ = true;
+  UpdateBootFlags();
+}
+
+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 he's working on the
+  // update_engine or p2p codebases so he can actually test his
+  // 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 string& app_version,
+                                            const string& omaha_url,
+                                            const string& target_channel,
+                                            const string& target_version_prefix,
+                                            bool obey_proxies,
+                                            bool interactive) {
+  http_response_code_ = 0;
+  PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+  // Refresh the policy before computing all the update parameters.
+  RefreshDevicePolicy();
+
+  // Set the target version prefix, if provided.
+  if (!target_version_prefix.empty())
+    omaha_request_params_->set_target_version_prefix(target_version_prefix);
+
+  CalculateScatteringParams(interactive);
+
+  CalculateP2PParams(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(app_version,
+                                   omaha_url,
+                                   interactive)) {
+    LOG(ERROR) << "Unable to initialize Omaha request params.";
+    return false;
+  }
+
+  // Set the target channel, if one was provided.
+  if (target_channel.empty()) {
+    LOG(INFO) << "No target channel mandated by policy.";
+  } else {
+    LOG(INFO) << "Setting target channel as mandated: " << target_channel;
+    // Pass in false for powerwash_allowed until we add it to the policy
+    // protobuf.
+    string error_message;
+    if (!omaha_request_params_->SetTargetChannel(target_channel, false,
+                                                 &error_message)) {
+      LOG(ERROR) << "Setting the channel failed: " << error_message;
+    }
+    // Notify observers the target channel change.
+    BroadcastChannel();
+
+    // 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.
+    omaha_request_params_->UpdateDownloadChannel();
+  }
+
+  LOG(INFO) << "target_version_prefix = "
+            << omaha_request_params_->target_version_prefix()
+            << ", 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 (obey_proxies || 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(kPrefsWallClockWaitPeriod, &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(kPrefsWallClockWaitPeriod);
+    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::BuildPostInstallActions(
+    InstallPlanAction* previous_action) {
+  shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
+      new PostinstallRunnerAction(system_state_->boot_control(),
+                                  system_state_->hardware()));
+  postinstall_runner_action->set_delegate(this);
+  actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
+  BondActions(previous_action,
+              postinstall_runner_action.get());
+}
+
+void UpdateAttempter::BuildUpdateActions(bool interactive) {
+  CHECK(!processor_->IsRunning());
+  processor_->set_delegate(this);
+
+  // Actions:
+  std::unique_ptr<LibcurlHttpFetcher> update_check_fetcher(
+      new 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);
+  shared_ptr<OmahaRequestAction> update_check_action(
+      new OmahaRequestAction(system_state_,
+                             nullptr,
+                             std::move(update_check_fetcher),
+                             false));
+  shared_ptr<OmahaResponseHandlerAction> response_handler_action(
+      new OmahaResponseHandlerAction(system_state_));
+
+  shared_ptr<OmahaRequestAction> download_started_action(
+      new OmahaRequestAction(system_state_,
+                             new OmahaEvent(
+                                 OmahaEvent::kTypeUpdateDownloadStarted),
+                             brillo::make_unique_ptr(new LibcurlHttpFetcher(
+                                 GetProxyResolver(),
+                                 system_state_->hardware())),
+                             false));
+
+  LibcurlHttpFetcher* download_fetcher =
+      new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware());
+  download_fetcher->set_server_to_check(ServerToCheck::kDownload);
+  shared_ptr<DownloadAction> download_action(new DownloadAction(
+      prefs_,
+      system_state_->boot_control(),
+      system_state_->hardware(),
+      system_state_,
+      new MultiRangeHttpFetcher(download_fetcher)));  // passes ownership
+  shared_ptr<OmahaRequestAction> download_finished_action(
+      new OmahaRequestAction(
+          system_state_,
+          new OmahaEvent(OmahaEvent::kTypeUpdateDownloadFinished),
+          brillo::make_unique_ptr(
+              new LibcurlHttpFetcher(GetProxyResolver(),
+                                     system_state_->hardware())),
+          false));
+  shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
+      new FilesystemVerifierAction());
+  shared_ptr<OmahaRequestAction> update_complete_action(
+      new OmahaRequestAction(
+          system_state_,
+          new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+          brillo::make_unique_ptr(
+              new LibcurlHttpFetcher(GetProxyResolver(),
+                                     system_state_->hardware())),
+          false));
+
+  download_action->set_delegate(this);
+  response_handler_action_ = response_handler_action;
+  download_action_ = download_action;
+
+  actions_.push_back(shared_ptr<AbstractAction>(update_check_action));
+  actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
+  actions_.push_back(shared_ptr<AbstractAction>(download_started_action));
+  actions_.push_back(shared_ptr<AbstractAction>(download_action));
+  actions_.push_back(shared_ptr<AbstractAction>(download_finished_action));
+  actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
+
+  // 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());
+  BuildPostInstallActions(filesystem_verifier_action.get());
+
+  actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
+
+  // Enqueue the actions
+  for (const shared_ptr<AbstractAction>& action : actions_) {
+    processor_->EnqueueAction(action.get());
+  }
+}
+
+bool UpdateAttempter::Rollback(bool powerwash) {
+  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("", "", true)) {
+    LOG(ERROR) << "Unable to initialize Omaha request params.";
+    return false;
+  }
+
+  LOG(INFO) << "Setting rollback options.";
+  InstallPlan install_plan;
+
+  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();
+
+  shared_ptr<InstallPlanAction> install_plan_action(
+      new InstallPlanAction(install_plan));
+  actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
+
+  BuildPostInstallActions(install_plan_action.get());
+
+  // Enqueue the actions
+  for (const shared_ptr<AbstractAction>& action : actions_) {
+    processor_->EnqueueAction(action.get());
+  }
+
+  // Update the payload state for Rollback.
+  system_state_->payload_state()->Rollback();
+
+  SetStatusAndNotify(UpdateStatus::ATTEMPTING_ROLLBACK);
+
+  // Just in case we didn't update boot flags yet, make sure they're updated
+  // before any update processing starts. This also schedules the start of the
+  // actions we just posted.
+  start_action_processor_ = true;
+  UpdateBootFlags();
+  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;
+}
+
+void UpdateAttempter::CheckForUpdate(const string& app_version,
+                                     const string& omaha_url,
+                                     bool interactive) {
+  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 (forced_update_pending_callback_.get()) {
+    // Make sure that a scheduling request is made prior to calling the forced
+    // update pending callback.
+    ScheduleUpdates();
+    forced_update_pending_callback_->Run(true, interactive);
+  }
+}
+
+bool UpdateAttempter::RebootIfNeeded() {
+  if (status_ != UpdateStatus::UPDATED_NEED_REBOOT) {
+    LOG(INFO) << "Reboot requested, but status is "
+              << UpdateStatusToString(status_) << ", so not rebooting.";
+    return false;
+  }
+
+  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;
+  command.push_back("/sbin/shutdown");
+  command.push_back("-r");
+  command.push_back("now");
+  LOG(INFO) << "Running \"" << base::JoinString(command, " ") << "\"";
+  int rc = 0;
+  Subprocess::SynchronousExec(command, &rc, 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.is_interactive ? "interactive" : "periodic")
+              << " update.";
+
+    Update(forced_app_version_, forced_omaha_url_, params.target_channel,
+           params.target_version_prefix, false, params.is_interactive);
+    // 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(IsUpdateRunningOrScheduled());
+}
+
+void UpdateAttempter::UpdateLastCheckedTime() {
+  last_checked_time_ = system_state_->clock()->GetWallclockTime().ToTimeT();
+}
+
+// Delegate methods:
+void UpdateAttempter::ProcessingDone(const ActionProcessor* processor,
+                                     ErrorCode code) {
+  LOG(INFO) << "Processing Done.";
+  actions_.clear();
+
+  // Reset cpu shares back to normal.
+  cpu_limiter_.StopLimiter();
+
+  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.";
+  }
+
+  if (code == ErrorCode::kSuccess) {
+    WriteUpdateCompletedMarker();
+    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());
+    prefs_->Delete(kPrefsUpdateFirstSeenAt);
+
+    SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+    ScheduleUpdates();
+    LOG(INFO) << "Update successfully applied, waiting to reboot.";
+
+    // This pointer is null during rollback operations, and the stats
+    // don't make much sense then anyway.
+    if (response_handler_action_) {
+      const InstallPlan& install_plan =
+          response_handler_action_->install_plan();
+
+      // Generate an unique payload identifier.
+      const string target_version_uid =
+          install_plan.payload_hash + ":" + install_plan.metadata_signature;
+
+      // 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 expected "
+            "response_handler_action_ missing.";
+      }
+    }
+    return;
+  }
+
+  if (ScheduleErrorEventAction()) {
+    return;
+  }
+  LOG(INFO) << "No update.";
+  SetStatusAndNotify(UpdateStatus::IDLE);
+  ScheduleUpdates();
+}
+
+void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) {
+  // Reset cpu shares back to normal.
+  cpu_limiter_.StopLimiter();
+  download_progress_ = 0.0;
+  SetStatusAndNotify(UpdateStatus::IDLE);
+  ScheduleUpdates();
+  actions_.clear();
+  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;
+      }
+
+      // Store the server-dictated poll interval, if any.
+      server_dictated_poll_interval_ =
+          std::max(0, omaha_request_action->GetOutputObject().poll_interval);
+    }
+  }
+  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 (status_ >= UpdateStatus::DOWNLOADING &&
+        code != ErrorCode::kDownloadTransferError) {
+      MarkDeltaUpdateFailure();
+    }
+    // On failure, schedule an error event to be sent to Omaha.
+    CreatePendingErrorEvent(action, code);
+    return;
+  }
+  // Find out which action completed.
+  if (type == OmahaResponseHandlerAction::StaticType()) {
+    // 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.
+    CHECK(action == response_handler_action_.get());
+    const InstallPlan& plan = response_handler_action_->install_plan();
+    UpdateLastCheckedTime();
+    new_version_ = plan.version;
+    new_payload_size_ = plan.payload_size;
+    SetupDownload();
+    cpu_limiter_.StartLimiter();
+    SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
+  } else if (type == DownloadAction::StaticType()) {
+    SetStatusAndNotify(UpdateStatus::FINALIZING);
+  }
+}
+
+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();
+}
+
+bool UpdateAttempter::OnCheckForUpdates(brillo::ErrorPtr* error) {
+  CheckForUpdate(
+      "" /* app_version */, "" /* omaha_url */, true /* interactive */);
+  return true;
+}
+
+bool UpdateAttempter::OnTrackChannel(const string& channel,
+                                     brillo::ErrorPtr* error) {
+  LOG(INFO) << "Setting destination channel to: " << channel;
+  string error_message;
+  if (!system_state_->request_params()->SetTargetChannel(
+          channel, false /* powerwash_allowed */, &error_message)) {
+    brillo::Error::AddTo(error,
+                         FROM_HERE,
+                         brillo::errors::dbus::kDomain,
+                         "set_target_error",
+                         error_message);
+    return false;
+  }
+  // Notify observers the target channel change.
+  BroadcastChannel();
+  return true;
+}
+
+bool UpdateAttempter::GetWeaveState(int64_t* last_checked_time,
+                                    double* progress,
+                                    UpdateStatus* update_status,
+                                    string* current_channel,
+                                    string* tracking_channel) {
+  *last_checked_time = last_checked_time_;
+  *progress = download_progress_;
+  *update_status = status_;
+  OmahaRequestParams* rp = system_state_->request_params();
+  *current_channel = rp->current_channel();
+  *tracking_channel = rp->target_channel();
+  return true;
+}
+
+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();
+  }
+}
+
+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;
+
+      // 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(int64_t* last_checked_time,
+                                double* progress,
+                                string* current_operation,
+                                string* new_version,
+                                int64_t* new_payload_size) {
+  *last_checked_time = last_checked_time_;
+  *progress = download_progress_;
+  *current_operation = UpdateStatusToString(status_);
+  *new_version = new_version_;
+  *new_payload_size = new_payload_size_;
+  return true;
+}
+
+void UpdateAttempter::UpdateBootFlags() {
+  if (update_boot_flags_running_) {
+    LOG(INFO) << "Update boot flags running, nothing to do.";
+    return;
+  }
+  if (updated_boot_flags_) {
+    LOG(INFO) << "Already updated boot flags. Skipping.";
+    if (start_action_processor_) {
+      ScheduleProcessingStart();
+    }
+    return;
+  }
+  // This is purely best effort. Failures should be logged by Subprocess. Run
+  // the script asynchronously to avoid blocking the event loop regardless of
+  // the script runtime.
+  update_boot_flags_running_ = true;
+  LOG(INFO) << "Marking booted slot as good.";
+  if (!system_state_->boot_control()->MarkBootSuccessfulAsync(Bind(
+          &UpdateAttempter::CompleteUpdateBootFlags, base::Unretained(this)))) {
+    LOG(ERROR) << "Failed to mark current boot as successful.";
+    CompleteUpdateBootFlags(false);
+  }
+}
+
+void UpdateAttempter::CompleteUpdateBootFlags(bool successful) {
+  update_boot_flags_running_ = false;
+  updated_boot_flags_ = true;
+  if (start_action_processor_) {
+    ScheduleProcessingStart();
+  }
+}
+
+void UpdateAttempter::BroadcastStatus() {
+  for (const auto& observer : service_observers_) {
+    observer->SendStatusUpdate(last_checked_time_,
+                               download_progress_,
+                               status_,
+                               new_version_,
+                               new_payload_size_);
+  }
+  last_notify_time_ = TimeTicks::Now();
+}
+
+void UpdateAttempter::BroadcastChannel() {
+  for (const auto& observer : service_observers_) {
+    observer->SendChannelChangeUpdate(
+        system_state_->request_params()->target_channel());
+  }
+}
+
+uint32_t UpdateAttempter::GetErrorCodeFlags()  {
+  uint32_t flags = 0;
+
+  if (!system_state_->hardware()->IsNormalBootMode())
+    flags |= static_cast<uint32_t>(ErrorCode::kDevModeFlag);
+
+  if (response_handler_action_.get() &&
+      response_handler_action_->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_->update_url() !=
+      constants::kOmahaDefaultProductionURL) {
+    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()) {
+    // This shouldn't really happen.
+    LOG(WARNING) << "There's already an existing pending error event.";
+    return;
+  }
+
+  // For now assume that a generic Omaha response action failure means that
+  // there's no update so don't send an event. Also, double check that the
+  // failure has not occurred while sending an error event -- in which case
+  // don't schedule another. This shouldn't really happen but just in case...
+  if ((action->Type() == OmahaResponseHandlerAction::StaticType() &&
+       code == ErrorCode::kError) ||
+      status_ == UpdateStatus::REPORTING_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 it to Omaha.
+  LOG(INFO) << "Reporting the error event";
+  shared_ptr<OmahaRequestAction> error_event_action(
+      new OmahaRequestAction(system_state_,
+                             error_event_.release(),  // Pass ownership.
+                             brillo::make_unique_ptr(new LibcurlHttpFetcher(
+                                 GetProxyResolver(),
+                                 system_state_->hardware())),
+                             false));
+  actions_.push_back(shared_ptr<AbstractAction>(error_event_action));
+  processor_->EnqueueAction(error_event_action.get());
+  SetStatusAndNotify(UpdateStatus::REPORTING_ERROR_EVENT);
+  processor_->StartProcessing();
+  return true;
+}
+
+void UpdateAttempter::ScheduleProcessingStart() {
+  LOG(INFO) << "Scheduling an action processor start.";
+  start_action_processor_ = false;
+  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::SetupDownload() {
+  MultiRangeHttpFetcher* fetcher =
+      static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher());
+  fetcher->ClearRanges();
+  if (response_handler_action_->install_plan().is_resume) {
+    // Resuming an update so fetch the update manifest metadata first.
+    int64_t manifest_metadata_size = 0;
+    int64_t manifest_signature_size = 0;
+    prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
+    prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size);
+    fetcher->AddRange(0, manifest_metadata_size + manifest_signature_size);
+    // If there're remaining unprocessed data blobs, fetch them. Be careful not
+    // to request data beyond the end of the payload to avoid 416 HTTP response
+    // error codes.
+    int64_t next_data_offset = 0;
+    prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
+    uint64_t resume_offset =
+        manifest_metadata_size + manifest_signature_size + next_data_offset;
+    if (resume_offset < response_handler_action_->install_plan().payload_size) {
+      fetcher->AddRange(resume_offset);
+    }
+  } else {
+    fetcher->AddRange(0);
+  }
+}
+
+void UpdateAttempter::PingOmaha() {
+  if (!processor_->IsRunning()) {
+    shared_ptr<OmahaRequestAction> ping_action(new OmahaRequestAction(
+        system_state_,
+        nullptr,
+        brillo::make_unique_ptr(new LibcurlHttpFetcher(
+            GetProxyResolver(),
+            system_state_->hardware())),
+        true));
+    actions_.push_back(shared_ptr<OmahaRequestAction>(ping_action));
+    processor_->set_delegate(nullptr);
+    processor_->EnqueueAction(ping_action.get());
+    // 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 te 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();
+}
+
+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::IsUpdateRunningOrScheduled() {
+  return ((status_ != UpdateStatus::IDLE &&
+           status_ != UpdateStatus::UPDATED_NEED_REBOOT) ||
+          waiting_for_scheduled_check_);
+}
+
+bool UpdateAttempter::IsAnyUpdateSourceAllowed() {
+  // 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;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/update_attempter.h b/update_engine/update_attempter.h
new file mode 100644
index 0000000..104975c
--- /dev/null
+++ b/update_engine/update_attempter.h
@@ -0,0 +1,519 @@
+//
+// 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_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
+
+#include <time.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#if USE_LIBCROS
+#include "update_engine/chrome_browser_proxy_resolver.h"
+#endif  // USE_LIBCROS
+#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/omaha_request_params.h"
+#include "update_engine/omaha_response_handler_action.h"
+#include "update_engine/payload_consumer/download_action.h"
+#include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/proxy_resolver.h"
+#include "update_engine/service_observer_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/weave_service_interface.h"
+
+class MetricsLibraryInterface;
+
+namespace policy {
+class PolicyProvider;
+}
+
+namespace chromeos_update_engine {
+
+class LibCrosProxy;
+class UpdateEngineAdaptor;
+
+class UpdateAttempter : public ActionProcessorDelegate,
+                        public DownloadActionDelegate,
+                        public CertificateChecker::Observer,
+                        public WeaveServiceInterface::DelegateInterface,
+                        public PostinstallRunnerAction::DelegateInterface {
+ public:
+  using UpdateStatus = update_engine::UpdateStatus;
+  static const int kMaxDeltaUpdateFailures;
+
+  UpdateAttempter(SystemState* system_state,
+                  CertificateChecker* cert_checker,
+                  LibCrosProxy* libcros_proxy);
+  ~UpdateAttempter() override;
+
+  // Further initialization to be done post construction.
+  void Init();
+
+  // Initiates scheduling of update checks.
+  virtual void ScheduleUpdates();
+
+  // Checks for update and, if a newer version is available, attempts to update
+  // the system. Non-empty |in_app_version| or |in_update_url| prevents
+  // automatic detection of the parameter.  |target_channel| denotes a
+  // policy-mandated channel we are updating to, if not empty. If |obey_proxies|
+  // is true, the update will likely respect Chrome's proxy setting. For
+  // security reasons, we may still not honor them. |interactive| should be true
+  // if this was called from the user (ie dbus).
+  virtual void Update(const std::string& app_version,
+                      const std::string& omaha_url,
+                      const std::string& target_channel,
+                      const std::string& target_version_prefix,
+                      bool obey_proxies,
+                      bool interactive);
+
+  // 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;
+
+  // WeaveServiceInterface::DelegateInterface overrides.
+  bool OnCheckForUpdates(brillo::ErrorPtr* error) override;
+  bool OnTrackChannel(const std::string& channel,
+                      brillo::ErrorPtr* error) override;
+  bool GetWeaveState(int64_t* last_checked_time,
+                     double* progress,
+                     UpdateStatus* update_status,
+                     std::string* current_channel,
+                     std::string* tracking_channel) 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 params. Returns true on success.
+  virtual bool GetStatus(int64_t* last_checked_time,
+                         double* progress,
+                         std::string* current_operation,
+                         std::string* new_version,
+                         int64_t* new_size);
+
+  // Runs chromeos-setgoodkernel, whose responsibility it is to mark the
+  // currently booted partition has high priority/permanent/etc. The execution
+  // is asynchronous. On completion, the action processor may be started
+  // depending on the |start_action_processor_| field. Note that every update
+  // attempt goes through this method.
+  void UpdateBootFlags();
+
+  // Called when the boot flags have been updated.
+  void CompleteUpdateBootFlags(bool success);
+
+  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; }
+
+  // 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.
+  virtual void CheckForUpdate(const std::string& app_version,
+                              const std::string& omaha_url,
+                              bool is_interactive);
+
+  // 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 sucess, false otherwise.
+  bool RebootIfNeeded();
+
+  // 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();
+
+  // Broadcasts the current tracking channel to all observers.
+  void BroadcastChannel();
+
+  // Returns the special flags to be added to ErrorCode values based on the
+  // parameters used in the current update attempt.
+  uint32_t GetErrorCodeFlags();
+
+  // Called at update_engine startup to do various house-keeping.
+  void UpdateEngineStarted();
+
+  // 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 happended to be into an update (current version).
+  // This will return an empty string otherwise.
+  std::string const& 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)>*  // NOLINT(readability/function)
+      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).
+  virtual bool IsAnyUpdateSourceAllowed();
+
+  // 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:
+  // Update server URL for automated lab test.
+  static const char* const kTestUpdateUrl;
+
+  // 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, CreatePendingErrorEventTest);
+  FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+  FRIEND_TEST(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest);
+  FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
+  FRIEND_TEST(UpdateAttempterTest, PingOmahaTest);
+  FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest);
+  FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
+  FRIEND_TEST(UpdateAttempterTest, UpdateTest);
+  FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
+  FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
+
+  // 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);
+
+  // Sets up the download parameters after receiving the update check response.
+  void SetupDownload();
+
+  // 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_LIBCROS
+    return obeying_proxies_ ?
+        reinterpret_cast<ProxyResolver*>(&chrome_proxy_resolver_) :
+        reinterpret_cast<ProxyResolver*>(&direct_proxy_resolver_);
+#else
+    return &direct_proxy_resolver_;
+#endif  // USE_LIBCROS
+  }
+
+  // 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 parametes.
+  bool CalculateUpdateParams(const std::string& app_version,
+                             const std::string& omaha_url,
+                             const std::string& target_channel,
+                             const std::string& target_version_prefix,
+                             bool obey_proxies,
+                             bool interactive);
+
+  // 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 is_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() and Rollback() to construct the sequence of
+  // actions to be performed for the postinstall.
+  // |previous_action| is the previous action to get
+  // bonded with the install_plan that gets passed to postinstall.
+  void BuildPostInstallActions(InstallPlanAction* previous_action);
+
+  // 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();
+
+  // Returns whether an update is currently running or scheduled.
+  bool IsUpdateRunningOrScheduled();
+
+  // 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_;
+
+  std::vector<std::shared_ptr<AbstractAction>> actions_;
+  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_;
+
+  // Pointer to the OmahaResponseHandlerAction in the actions_ vector.
+  std::shared_ptr<OmahaResponseHandlerAction> response_handler_action_;
+
+  // Pointer to the DownloadAction in the actions_ vector.
+  std::shared_ptr<DownloadAction> download_action_;
+
+  // 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;
+
+  // 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";
+  int64_t new_payload_size_ = 0;
+
+  // 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;
+
+  // Our two proxy resolvers
+  DirectProxyResolver direct_proxy_resolver_;
+#if USE_LIBCROS
+  ChromeBrowserProxyResolver chrome_proxy_resolver_;
+#endif  // USE_LIBCROS
+
+  // Originally, both of these flags are false. Once UpdateBootFlags is called,
+  // |update_boot_flags_running_| is set to true. As soon as UpdateBootFlags
+  // completes its asynchronous run, |update_boot_flags_running_| is reset to
+  // false and |updated_boot_flags_| is set to true. From that point on there
+  // will be no more changes to these flags.
+  //
+  // True if UpdateBootFlags has completed.
+  bool updated_boot_flags_ = false;
+  // True if UpdateBootFlags is running.
+  bool update_boot_flags_running_ = false;
+
+  // True if the action processor needs to be started by the boot flag updater.
+  bool start_action_processor_ = false;
+
+  // 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_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
diff --git a/update_engine/update_attempter_android.cc b/update_engine/update_attempter_android.cc
new file mode 100644
index 0000000..2de2667
--- /dev/null
+++ b/update_engine/update_attempter_android.cc
@@ -0,0 +1,542 @@
+//
+// 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/update_attempter_android.h"
+
+#include <algorithm>
+#include <map>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/strings/string_utils.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/file_fetcher.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/daemon_state_interface.h"
+#include "update_engine/network_selector.h"
+#include "update_engine/payload_consumer/download_action.h"
+#include "update_engine/payload_consumer/filesystem_verifier_action.h"
+#include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/update_status_utils.h"
+
+#ifndef _UE_SIDELOAD
+// Do not include support for external HTTP(s) urls when building
+// update_engine_sideload.
+#include "update_engine/libcurl_http_fetcher.h"
+#endif
+
+using base::Bind;
+using base::TimeDelta;
+using base::TimeTicks;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Minimum threshold to broadcast an status update in progress and time.
+const double kBroadcastThresholdProgress = 0.01;  // 1%
+const int kBroadcastThresholdSeconds = 10;
+
+const char* const kErrorDomain = "update_engine";
+// TODO(deymo): Convert the different errors to a numeric value to report them
+// back on the service error.
+const char* const kGenericError = "generic_error";
+
+// Log and set the error on the passed ErrorPtr.
+bool LogAndSetError(brillo::ErrorPtr* error,
+                    const tracked_objects::Location& location,
+                    const string& reason) {
+  brillo::Error::AddTo(error, location, kErrorDomain, kGenericError, reason);
+  LOG(ERROR) << "Replying with failure: " << location.ToString() << ": "
+             << reason;
+  return false;
+}
+
+}  // namespace
+
+UpdateAttempterAndroid::UpdateAttempterAndroid(
+    DaemonStateInterface* daemon_state,
+    PrefsInterface* prefs,
+    BootControlInterface* boot_control,
+    HardwareInterface* hardware)
+    : daemon_state_(daemon_state),
+      prefs_(prefs),
+      boot_control_(boot_control),
+      hardware_(hardware),
+      processor_(new ActionProcessor()) {
+  network_selector_ = network::CreateNetworkSelector();
+}
+
+UpdateAttempterAndroid::~UpdateAttempterAndroid() {
+  // Release ourselves as the ActionProcessor's delegate to prevent
+  // re-scheduling the updates due to the processing stopped.
+  processor_->set_delegate(nullptr);
+}
+
+void UpdateAttempterAndroid::Init() {
+  // In case of update_engine restart without a reboot we need to restore the
+  // reboot needed state.
+  if (UpdateCompletedOnThisBoot())
+    SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+  else
+    SetStatusAndNotify(UpdateStatus::IDLE);
+}
+
+bool UpdateAttempterAndroid::ApplyPayload(
+    const string& payload_url,
+    int64_t payload_offset,
+    int64_t payload_size,
+    const vector<string>& key_value_pair_headers,
+    brillo::ErrorPtr* error) {
+  if (status_ == UpdateStatus::UPDATED_NEED_REBOOT) {
+    return LogAndSetError(
+        error, FROM_HERE, "An update already applied, waiting for reboot");
+  }
+  if (ongoing_update_) {
+    return LogAndSetError(
+        error, FROM_HERE, "Already processing an update, cancel it first.");
+  }
+  DCHECK(status_ == UpdateStatus::IDLE);
+
+  std::map<string, string> headers;
+  for (const string& key_value_pair : key_value_pair_headers) {
+    string key;
+    string value;
+    if (!brillo::string_utils::SplitAtFirst(
+            key_value_pair, "=", &key, &value, false)) {
+      return LogAndSetError(
+          error, FROM_HERE, "Passed invalid header: " + key_value_pair);
+    }
+    if (!headers.emplace(key, value).second)
+      return LogAndSetError(error, FROM_HERE, "Passed repeated key: " + key);
+  }
+
+  // Unique identifier for the payload. An empty string means that the payload
+  // can't be resumed.
+  string payload_id = (headers[kPayloadPropertyFileHash] +
+                       headers[kPayloadPropertyMetadataHash]);
+
+  // Setup the InstallPlan based on the request.
+  install_plan_ = InstallPlan();
+
+  install_plan_.download_url = payload_url;
+  install_plan_.version = "";
+  base_offset_ = payload_offset;
+  install_plan_.payload_size = payload_size;
+  if (!install_plan_.payload_size) {
+    if (!base::StringToUint64(headers[kPayloadPropertyFileSize],
+                              &install_plan_.payload_size)) {
+      install_plan_.payload_size = 0;
+    }
+  }
+  install_plan_.payload_hash = headers[kPayloadPropertyFileHash];
+  if (!base::StringToUint64(headers[kPayloadPropertyMetadataSize],
+                            &install_plan_.metadata_size)) {
+    install_plan_.metadata_size = 0;
+  }
+  install_plan_.metadata_signature = "";
+  // The |public_key_rsa| key would override the public key stored on disk.
+  install_plan_.public_key_rsa = "";
+
+  install_plan_.hash_checks_mandatory = hardware_->IsOfficialBuild();
+  install_plan_.is_resume = !payload_id.empty() &&
+                            DeltaPerformer::CanResumeUpdate(prefs_, payload_id);
+  if (!install_plan_.is_resume) {
+    if (!DeltaPerformer::ResetUpdateProgress(prefs_, false)) {
+      LOG(WARNING) << "Unable to reset the update progress.";
+    }
+    if (!prefs_->SetString(kPrefsUpdateCheckResponseHash, payload_id)) {
+      LOG(WARNING) << "Unable to save the update check response hash.";
+    }
+  }
+  // The |payload_type| is not used anymore since minor_version 3.
+  install_plan_.payload_type = InstallPayloadType::kUnknown;
+
+  install_plan_.source_slot = boot_control_->GetCurrentSlot();
+  install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
+
+  int data_wipe = 0;
+  install_plan_.powerwash_required =
+      base::StringToInt(headers[kPayloadPropertyPowerwash], &data_wipe) &&
+      data_wipe != 0;
+
+  NetworkId network_id = kDefaultNetworkId;
+  if (!headers[kPayloadPropertyNetworkId].empty()) {
+    if (!base::StringToUint64(headers[kPayloadPropertyNetworkId],
+                              &network_id)) {
+      return LogAndSetError(
+          error,
+          FROM_HERE,
+          "Invalid network_id: " + headers[kPayloadPropertyNetworkId]);
+    }
+    if (!network_selector_->SetProcessNetwork(network_id)) {
+      LOG(WARNING) << "Unable to set network_id, continuing with the update.";
+    }
+  }
+
+  LOG(INFO) << "Using this install plan:";
+  install_plan_.Dump();
+
+  BuildUpdateActions(payload_url);
+  SetupDownload();
+  // Setup extra headers.
+  HttpFetcher* fetcher = download_action_->http_fetcher();
+  if (!headers[kPayloadPropertyAuthorization].empty())
+    fetcher->SetHeader("Authorization", headers[kPayloadPropertyAuthorization]);
+  if (!headers[kPayloadPropertyUserAgent].empty())
+    fetcher->SetHeader("User-Agent", headers[kPayloadPropertyUserAgent]);
+
+  cpu_limiter_.StartLimiter();
+  SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
+  ongoing_update_ = true;
+
+  // Just in case we didn't update boot flags yet, make sure they're updated
+  // before any update processing starts. This will start the update process.
+  UpdateBootFlags();
+  return true;
+}
+
+bool UpdateAttempterAndroid::SuspendUpdate(brillo::ErrorPtr* error) {
+  if (!ongoing_update_)
+    return LogAndSetError(error, FROM_HERE, "No ongoing update to suspend.");
+  processor_->SuspendProcessing();
+  return true;
+}
+
+bool UpdateAttempterAndroid::ResumeUpdate(brillo::ErrorPtr* error) {
+  if (!ongoing_update_)
+    return LogAndSetError(error, FROM_HERE, "No ongoing update to resume.");
+  processor_->ResumeProcessing();
+  return true;
+}
+
+bool UpdateAttempterAndroid::CancelUpdate(brillo::ErrorPtr* error) {
+  if (!ongoing_update_)
+    return LogAndSetError(error, FROM_HERE, "No ongoing update to cancel.");
+  processor_->StopProcessing();
+  return true;
+}
+
+bool UpdateAttempterAndroid::ResetStatus(brillo::ErrorPtr* error) {
+  LOG(INFO) << "Attempting to reset state from "
+            << UpdateStatusToString(status_) << " to UpdateStatus::IDLE";
+
+  switch (status_) {
+    case UpdateStatus::IDLE:
+      return true;
+
+    case UpdateStatus::UPDATED_NEED_REBOOT:  {
+      // 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.
+      bool ret_value = prefs_->Delete(kPrefsUpdateCompletedOnBootId);
+
+      // Update the boot flags so the current slot has higher priority.
+      if (!boot_control_->SetActiveBootSlot(boot_control_->GetCurrentSlot()))
+        ret_value = false;
+
+      if (!ret_value) {
+        return LogAndSetError(
+            error,
+            FROM_HERE,
+            "Failed to reset the status to ");
+      }
+
+      SetStatusAndNotify(UpdateStatus::IDLE);
+      LOG(INFO) << "Reset status successful";
+      return true;
+    }
+
+    default:
+      return LogAndSetError(
+          error,
+          FROM_HERE,
+          "Reset not allowed in this state. Cancel the ongoing update first");
+  }
+}
+
+void UpdateAttempterAndroid::ProcessingDone(const ActionProcessor* processor,
+                                            ErrorCode code) {
+  LOG(INFO) << "Processing Done.";
+
+  switch (code) {
+    case ErrorCode::kSuccess:
+      // Update succeeded.
+      WriteUpdateCompletedMarker();
+      prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
+      DeltaPerformer::ResetUpdateProgress(prefs_, false);
+
+      LOG(INFO) << "Update successfully applied, waiting to reboot.";
+      break;
+
+    case ErrorCode::kFilesystemCopierError:
+    case ErrorCode::kNewRootfsVerificationError:
+    case ErrorCode::kNewKernelVerificationError:
+    case ErrorCode::kFilesystemVerifierError:
+    case ErrorCode::kDownloadStateInitializationError:
+      // Reset the ongoing update for these errors so it starts from the
+      // beginning next time.
+      DeltaPerformer::ResetUpdateProgress(prefs_, false);
+      LOG(INFO) << "Resetting update progress.";
+      break;
+
+    default:
+      // Ignore all other error codes.
+      break;
+  }
+
+  TerminateUpdateAndNotify(code);
+}
+
+void UpdateAttempterAndroid::ProcessingStopped(
+    const ActionProcessor* processor) {
+  TerminateUpdateAndNotify(ErrorCode::kUserCanceled);
+}
+
+void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor,
+                                             AbstractAction* action,
+                                             ErrorCode code) {
+  // Reset download progress regardless of whether or not the download
+  // action succeeded.
+  const string type = action->Type();
+  if (type == DownloadAction::StaticType()) {
+    download_progress_ = 0;
+  }
+  if (code != ErrorCode::kSuccess) {
+    // If an action failed, the ActionProcessor will cancel the whole thing.
+    return;
+  }
+  if (type == DownloadAction::StaticType()) {
+    SetStatusAndNotify(UpdateStatus::FINALIZING);
+  }
+}
+
+void UpdateAttempterAndroid::BytesReceived(uint64_t bytes_progressed,
+                                           uint64_t bytes_received,
+                                           uint64_t total) {
+  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);
+  }
+}
+
+bool UpdateAttempterAndroid::ShouldCancel(ErrorCode* cancel_reason) {
+  // TODO(deymo): Notify the DownloadAction that it should cancel the update
+  // download.
+  return false;
+}
+
+void UpdateAttempterAndroid::DownloadComplete() {
+  // Nothing needs to be done when the download completes.
+}
+
+void UpdateAttempterAndroid::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;
+    SetStatusAndNotify(status_);
+  }
+}
+
+void UpdateAttempterAndroid::UpdateBootFlags() {
+  if (updated_boot_flags_) {
+    LOG(INFO) << "Already updated boot flags. Skipping.";
+    CompleteUpdateBootFlags(true);
+    return;
+  }
+  // This is purely best effort.
+  LOG(INFO) << "Marking booted slot as good.";
+  if (!boot_control_->MarkBootSuccessfulAsync(
+          Bind(&UpdateAttempterAndroid::CompleteUpdateBootFlags,
+               base::Unretained(this)))) {
+    LOG(ERROR) << "Failed to mark current boot as successful.";
+    CompleteUpdateBootFlags(false);
+  }
+}
+
+void UpdateAttempterAndroid::CompleteUpdateBootFlags(bool successful) {
+  updated_boot_flags_ = true;
+  ScheduleProcessingStart();
+}
+
+void UpdateAttempterAndroid::ScheduleProcessingStart() {
+  LOG(INFO) << "Scheduling an action processor start.";
+  brillo::MessageLoop::current()->PostTask(
+      FROM_HERE,
+      Bind([](ActionProcessor* processor) { processor->StartProcessing(); },
+           base::Unretained(processor_.get())));
+}
+
+void UpdateAttempterAndroid::TerminateUpdateAndNotify(ErrorCode error_code) {
+  if (status_ == UpdateStatus::IDLE) {
+    LOG(ERROR) << "No ongoing update, but TerminatedUpdate() called.";
+    return;
+  }
+
+  // Reset cpu shares back to normal.
+  cpu_limiter_.StopLimiter();
+  download_progress_ = 0;
+  actions_.clear();
+  UpdateStatus new_status =
+      (error_code == ErrorCode::kSuccess ? UpdateStatus::UPDATED_NEED_REBOOT
+                                         : UpdateStatus::IDLE);
+  SetStatusAndNotify(new_status);
+  ongoing_update_ = false;
+
+  for (auto observer : daemon_state_->service_observers())
+    observer->SendPayloadApplicationComplete(error_code);
+}
+
+void UpdateAttempterAndroid::SetStatusAndNotify(UpdateStatus status) {
+  status_ = status;
+  for (auto observer : daemon_state_->service_observers()) {
+    observer->SendStatusUpdate(
+        0, download_progress_, status_, "", install_plan_.payload_size);
+  }
+  last_notify_time_ = TimeTicks::Now();
+}
+
+void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
+  CHECK(!processor_->IsRunning());
+  processor_->set_delegate(this);
+
+  // Actions:
+  shared_ptr<InstallPlanAction> install_plan_action(
+      new InstallPlanAction(install_plan_));
+
+  HttpFetcher* download_fetcher = nullptr;
+  if (FileFetcher::SupportedUrl(url)) {
+    DLOG(INFO) << "Using FileFetcher for file URL.";
+    download_fetcher = new FileFetcher();
+  } else {
+#ifdef _UE_SIDELOAD
+    LOG(FATAL) << "Unsupported sideload URI: " << url;
+#else
+    LibcurlHttpFetcher* libcurl_fetcher =
+        new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
+    libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
+    download_fetcher = libcurl_fetcher;
+#endif  // _UE_SIDELOAD
+  }
+  shared_ptr<DownloadAction> download_action(new DownloadAction(
+      prefs_,
+      boot_control_,
+      hardware_,
+      nullptr,                                        // system_state, not used.
+      new MultiRangeHttpFetcher(download_fetcher)));  // passes ownership
+  shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
+      new FilesystemVerifierAction());
+
+  shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
+      new PostinstallRunnerAction(boot_control_, hardware_));
+
+  download_action->set_delegate(this);
+  download_action_ = download_action;
+  postinstall_runner_action->set_delegate(this);
+
+  actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
+  actions_.push_back(shared_ptr<AbstractAction>(download_action));
+  actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
+  actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
+
+  // Bond them together. We have to use the leaf-types when calling
+  // BondActions().
+  BondActions(install_plan_action.get(), download_action.get());
+  BondActions(download_action.get(), filesystem_verifier_action.get());
+  BondActions(filesystem_verifier_action.get(),
+              postinstall_runner_action.get());
+
+  // Enqueue the actions.
+  for (const shared_ptr<AbstractAction>& action : actions_)
+    processor_->EnqueueAction(action.get());
+}
+
+void UpdateAttempterAndroid::SetupDownload() {
+  MultiRangeHttpFetcher* fetcher =
+      static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher());
+  fetcher->ClearRanges();
+  if (install_plan_.is_resume) {
+    // Resuming an update so fetch the update manifest metadata first.
+    int64_t manifest_metadata_size = 0;
+    int64_t manifest_signature_size = 0;
+    prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
+    prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size);
+    fetcher->AddRange(base_offset_,
+                      manifest_metadata_size + manifest_signature_size);
+    // If there're remaining unprocessed data blobs, fetch them. Be careful not
+    // to request data beyond the end of the payload to avoid 416 HTTP response
+    // error codes.
+    int64_t next_data_offset = 0;
+    prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
+    uint64_t resume_offset =
+        manifest_metadata_size + manifest_signature_size + next_data_offset;
+    if (!install_plan_.payload_size) {
+      fetcher->AddRange(base_offset_ + resume_offset);
+    } else if (resume_offset < install_plan_.payload_size) {
+      fetcher->AddRange(base_offset_ + resume_offset,
+                        install_plan_.payload_size - resume_offset);
+    }
+  } else {
+    if (install_plan_.payload_size) {
+      fetcher->AddRange(base_offset_, install_plan_.payload_size);
+    } else {
+      // If no payload size is passed we assume we read until the end of the
+      // stream.
+      fetcher->AddRange(base_offset_);
+    }
+  }
+}
+
+bool UpdateAttempterAndroid::WriteUpdateCompletedMarker() {
+  string boot_id;
+  TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id));
+  prefs_->SetString(kPrefsUpdateCompletedOnBootId, boot_id);
+  return true;
+}
+
+bool UpdateAttempterAndroid::UpdateCompletedOnThisBoot() {
+  // 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;
+  return (prefs_->Exists(kPrefsUpdateCompletedOnBootId) &&
+          prefs_->GetString(kPrefsUpdateCompletedOnBootId,
+                            &update_completed_on_boot_id) &&
+          update_completed_on_boot_id == boot_id);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/update_attempter_android.h b/update_engine/update_attempter_android.h
new file mode 100644
index 0000000..2617318
--- /dev/null
+++ b/update_engine/update_attempter_android.h
@@ -0,0 +1,178 @@
+//
+// 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_UPDATE_ATTEMPTER_ANDROID_H_
+#define UPDATE_ENGINE_UPDATE_ATTEMPTER_ANDROID_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+#include "update_engine/client_library/include/update_engine/update_status.h"
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/cpu_limiter.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/daemon_state_interface.h"
+#include "update_engine/network_selector_interface.h"
+#include "update_engine/payload_consumer/download_action.h"
+#include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/service_delegate_android_interface.h"
+#include "update_engine/service_observer_interface.h"
+
+namespace chromeos_update_engine {
+
+class UpdateAttempterAndroid
+    : public ServiceDelegateAndroidInterface,
+      public ActionProcessorDelegate,
+      public DownloadActionDelegate,
+      public PostinstallRunnerAction::DelegateInterface {
+ public:
+  using UpdateStatus = update_engine::UpdateStatus;
+
+  UpdateAttempterAndroid(DaemonStateInterface* daemon_state,
+                         PrefsInterface* prefs,
+                         BootControlInterface* boot_control_,
+                         HardwareInterface* hardware_);
+  ~UpdateAttempterAndroid() override;
+
+  // Further initialization to be done post construction.
+  void Init();
+
+  // ServiceDelegateAndroidInterface overrides.
+  bool ApplyPayload(const std::string& payload_url,
+                    int64_t payload_offset,
+                    int64_t payload_size,
+                    const std::vector<std::string>& key_value_pair_headers,
+                    brillo::ErrorPtr* error) override;
+  bool SuspendUpdate(brillo::ErrorPtr* error) override;
+  bool ResumeUpdate(brillo::ErrorPtr* error) override;
+  bool CancelUpdate(brillo::ErrorPtr* error) override;
+  bool ResetStatus(brillo::ErrorPtr* error) override;
+
+  // 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;
+
+  // DownloadActionDelegate overrides.
+  void BytesReceived(uint64_t bytes_progressed,
+                     uint64_t bytes_received,
+                     uint64_t total) override;
+  bool ShouldCancel(ErrorCode* cancel_reason) override;
+  void DownloadComplete() override;
+
+  // PostinstallRunnerAction::DelegateInterface
+  void ProgressUpdate(double progress) override;
+
+ private:
+  // Asynchronously marks the current slot as successful if needed. If already
+  // marked as good, CompleteUpdateBootFlags() is called starting the action
+  // processor.
+  void UpdateBootFlags();
+
+  // Called when the boot flags have been updated.
+  void CompleteUpdateBootFlags(bool success);
+
+  // Schedules an event loop callback to start the action processor. This is
+  // scheduled asynchronously to unblock the event loop.
+  void ScheduleProcessingStart();
+
+  // Notifies an update request completed with the given error |code| to all
+  // observers.
+  void TerminateUpdateAndNotify(ErrorCode error_code);
+
+  // Sets the status to the given |status| and notifies a status update to
+  // all observers.
+  void SetStatusAndNotify(UpdateStatus status);
+
+  // Helper method to construct the sequence of actions to be performed for
+  // applying an update from the given |url|.
+  void BuildUpdateActions(const std::string& url);
+
+  // Sets up the download parameters based on the update requested on the
+  // |install_plan_|.
+  void SetupDownload();
+
+  // Writes to the processing completed marker. Does nothing if
+  // |update_completed_marker_| is empty.
+  bool WriteUpdateCompletedMarker();
+
+  // Returns whether an update was completed in the current boot.
+  bool UpdateCompletedOnThisBoot();
+
+  DaemonStateInterface* daemon_state_;
+
+  // DaemonStateAndroid pointers.
+  PrefsInterface* prefs_;
+  BootControlInterface* boot_control_;
+  HardwareInterface* hardware_;
+
+  // 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_;
+
+  // The list of actions and action processor that runs them asynchronously.
+  // Only used when |ongoing_update_| is true.
+  std::vector<std::shared_ptr<AbstractAction>> actions_;
+  std::unique_ptr<ActionProcessor> processor_;
+
+  // Pointer to the DownloadAction in the actions_ vector.
+  std::shared_ptr<DownloadAction> download_action_;
+
+  // Whether there is an ongoing update. This implies that an update was started
+  // but not finished yet. This value will be true even if the update was
+  // suspended.
+  bool ongoing_update_{false};
+
+  // The InstallPlan used during the ongoing update.
+  InstallPlan install_plan_;
+
+  // For status:
+  UpdateStatus status_{UpdateStatus::IDLE};
+  double download_progress_{0.0};
+
+  // The offset in the payload file where the CrAU part starts.
+  int64_t base_offset_{0};
+
+  // Only direct proxy supported.
+  DirectProxyResolver proxy_resolver_;
+
+  // CPU limiter during the update.
+  CPULimiter cpu_limiter_;
+
+  // Helper class to select the network to use during the update.
+  std::unique_ptr<NetworkSelectorInterface> network_selector_;
+
+  // Whether we have marked the current slot as good. This step is required
+  // before applying an update to the other slot.
+  bool updated_boot_flags_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateAttempterAndroid);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_UPDATE_ATTEMPTER_ANDROID_H_
diff --git a/update_engine/update_attempter_nestlabs.cc b/update_engine/update_attempter_nestlabs.cc
new file mode 100644
index 0000000..047778a
--- /dev/null
+++ b/update_engine/update_attempter_nestlabs.cc
@@ -0,0 +1,841 @@
+//
+// 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/update_attempter_nestlabs.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/rand_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/message_loop.h>
+#include <policy/device_policy.h>
+#include <policy/libpolicy.h>
+#include <update_engine/dbus-constants-nestlabs.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/hardware_interface.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/metrics.h"
+#include "update_engine/payload_properties_request_action.h"
+#include "update_engine/payload_properties_handler_action.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_consumer/download_action.h"
+#include "update_engine/payload_consumer/filesystem_verifier_action.h"
+#include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/power_manager_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/update_status_utils.h"
+
+#define PAYLOAD_PROPERTIES_OFFSET 0
+#define PAYLOAD_PROPERTIES_SIZE 512
+
+
+using base::Bind;
+using base::Callback;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using brillo::MessageLoop;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateCheckParams;
+using std::set;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const int UpdateAttempter::kMaxDeltaUpdateFailures = 3;
+
+namespace {
+// Minimum threshold to broadcast an status update in progress and time.
+const double kBroadcastThresholdProgress = 0.01;  // 1%
+const int kBroadcastThresholdSeconds = 10;
+}  // namespace
+
+UpdateAttempter::UpdateAttempter(SystemState* system_state,
+                                 CertificateChecker* cert_checker,
+                                 LibCrosProxy* libcros_proxy)
+    : processor_(new ActionProcessor()),
+      system_state_(system_state),
+      cert_checker_(cert_checker) {
+}
+
+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;
+}
+
+void UpdateAttempter::ScheduleUpdates() {
+  if (IsUpdateRunningOrScheduled())
+    return;
+
+  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->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+  waiting_for_scheduled_check_ = true;
+}
+
+void UpdateAttempter::CertificateChecked(ServerToCheck server_to_check,
+                                         CertificateCheckResult result) {
+  metrics::ReportCertificateCheckMetrics(system_state_,
+                                         server_to_check,
+                                         result);
+}
+
+void UpdateAttempter::Update(const string& app_version,
+                             const string& url,
+                             const string& target_channel,
+                             const string& target_version_prefix,
+                             bool obey_proxies,
+                             bool interactive) {
+
+  // Notify of the new update attempt, clearing prior interactive requests.
+  if (forced_update_pending_callback_.get())
+    forced_update_pending_callback_->Run(false, 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";
+    metrics::ReportUpdateCheckMetrics(system_state_,
+                                      metrics::CheckResult::kRebootPending,
+                                      metrics::CheckReaction::kUnset,
+                                      metrics::DownloadErrorCode::kUnset);
+    return;
+  }
+  if (status_ != UpdateStatus::IDLE) {
+    // Update in progress. Do nothing
+    return;
+  }
+
+  // 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();
+
+  if (!CalculateUpdateParams(app_version ,url)) {
+    return;
+  }
+
+  BuildUpdateActions(interactive);
+
+  SetStatusAndNotify(UpdateStatus::CHECKING_FOR_UPDATE);
+
+  // Just in case we didn't update boot flags yet, make sure they're updated
+  // before any update processing starts.
+  start_action_processor_ = true;
+  UpdateBootFlags();
+}
+
+bool UpdateAttempter::CalculateUpdateParams(const string& version,
+                                            const string& url) {
+
+  PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+  // Refresh the policy before computing all the update parameters.
+  RefreshDevicePolicy();
+
+  payload_properties_.payload_urls.clear();
+
+  // Skip update if `url' is empty (update_engine was not asked for update via DBUS),
+  // and URL from payload_state is empty (there is no restored URL
+  // from previos update)
+  if (url != "")
+  {
+    payload_properties_.payload_urls.push_back(url);
+  }
+  else if (payload_state->GetCurrentUrl() != "")
+  {
+    payload_properties_.payload_urls.push_back(payload_state->GetCurrentUrl());
+  }
+  else
+  {
+    LOG(INFO)
+        << "URL is not present. Nothing to do now. retrying.";
+    ScheduleUpdates();
+    return false;
+  }
+
+  // turn P2P off
+  payload_state->SetUsingP2PForDownloading(false);
+  payload_state->SetUsingP2PForSharing(false);
+
+  // reset code
+  http_response_code_ = 0;
+
+  return true;
+}
+
+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::BuildPostInstallActions(
+    InstallPlanAction* previous_action) {
+  shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
+      new PostinstallRunnerAction(system_state_->boot_control(),
+                                  system_state_->hardware()));
+  postinstall_runner_action->set_delegate(this);
+  actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
+  BondActions(previous_action,
+              postinstall_runner_action.get());
+}
+
+#define MAX_RETRIES 3
+
+void UpdateAttempter::BuildUpdateActions(bool interactive) {
+  CHECK(!processor_->IsRunning());
+  processor_->set_delegate(this);
+
+  // Actions:
+  LibcurlHttpFetcher * pp_base_fetcher =
+      new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware());
+  MultiRangeHttpFetcher * payload_properties_fetcher =
+      new MultiRangeHttpFetcher(pp_base_fetcher);
+
+  pp_base_fetcher->set_server_to_check(ServerToCheck::kDownload);
+  pp_base_fetcher->set_no_network_max_retries(MAX_RETRIES);
+  payload_properties_fetcher->AddRange(PAYLOAD_PROPERTIES_OFFSET, PAYLOAD_PROPERTIES_SIZE);
+  shared_ptr<PayloadPropertiesRequestAction> payload_properties_request_action(
+      new PayloadPropertiesRequestAction(system_state_,
+                             &payload_properties_,
+                             (LibcurlHttpFetcher *)payload_properties_fetcher));
+  payload_properties_request_action_ = payload_properties_request_action;
+
+  shared_ptr<PayloadPropertiesHandlerAction> payload_properties_handler_action(
+      new PayloadPropertiesHandlerAction(system_state_));
+  payload_properties_handler_action_ = payload_properties_handler_action;
+
+  LibcurlHttpFetcher* download_fetcher =
+      new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware());
+  download_fetcher->set_server_to_check(ServerToCheck::kDownload);
+  shared_ptr<DownloadAction> download_action(new DownloadAction(
+      prefs_,
+      system_state_->boot_control(),
+      system_state_->hardware(),
+      system_state_,
+      new MultiRangeHttpFetcher(download_fetcher)));  // passes ownership
+  download_action->set_delegate(this);
+  download_action_ = download_action;
+
+  shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
+      new FilesystemVerifierAction());
+
+  actions_.push_back(shared_ptr<AbstractAction>(payload_properties_request_action));
+  actions_.push_back(shared_ptr<AbstractAction>(payload_properties_handler_action));
+  actions_.push_back(shared_ptr<AbstractAction>(download_action));
+  actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
+
+  // Bond them together. We have to use the leaf-types when calling
+  // BondActions().
+  BondActions(payload_properties_request_action.get(),
+              payload_properties_handler_action.get());
+  BondActions(payload_properties_handler_action.get(),
+              download_action.get());
+  BondActions(download_action.get(),
+              filesystem_verifier_action.get());
+  BuildPostInstallActions(filesystem_verifier_action.get());
+
+  // Enqueue the actions
+  for (const shared_ptr<AbstractAction>& action : actions_) {
+    processor_->EnqueueAction(action.get());
+  }
+}
+
+bool UpdateAttempter::Rollback(bool powerwash) {
+  if (!CanRollback()) {
+    return false;
+  }
+
+  processor_->set_delegate(this);
+
+  LOG(INFO) << "Setting rollback options.";
+  InstallPlan install_plan;
+
+  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();
+
+  shared_ptr<InstallPlanAction> install_plan_action(
+      new InstallPlanAction(install_plan));
+  actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
+
+  BuildPostInstallActions(install_plan_action.get());
+
+  // Enqueue the actions
+  for (const shared_ptr<AbstractAction>& action : actions_) {
+    processor_->EnqueueAction(action.get());
+  }
+
+  // Update the payload state for Rollback.
+  system_state_->payload_state()->Rollback();
+
+  SetStatusAndNotify(UpdateStatus::ATTEMPTING_ROLLBACK);
+
+  // Just in case we didn't update boot flags yet, make sure they're updated
+  // before any update processing starts. This also schedules the start of the
+  // actions we just posted.
+  start_action_processor_ = true;
+  UpdateBootFlags();
+  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;
+}
+
+void UpdateAttempter::CheckForUpdate(const string& app_version,
+                                     const string& url,
+                                     bool interactive) {
+  LOG(INFO) << "Update requested.";
+  app_version_ = app_version;
+  url_ = url;
+
+  if (forced_update_pending_callback_.get()) {
+    // Make sure that a scheduling request is made prior to calling the forced
+    // update pending callback.
+    ScheduleUpdates();
+    forced_update_pending_callback_->Run(true, interactive);
+  }
+}
+
+bool UpdateAttempter::RebootIfNeeded() {
+  if (status_ != UpdateStatus::UPDATED_NEED_REBOOT) {
+    LOG(INFO) << "Reboot requested, but status is "
+              << UpdateStatusToString(status_) << ", so not rebooting.";
+    return false;
+  }
+
+  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;
+  command.push_back("/system/bin/reboot");
+  LOG(INFO) << "Running \"" << base::JoinString(command, " ") << "\"";
+  int rc = 0;
+  Subprocess::SynchronousExec(command, &rc, 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 update.";
+
+    Update(app_version_, url_);
+    // Always clear the forced app_version and url after an update attempt
+    // so the next update uses the defaults.
+    app_version_.clear();
+    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(IsUpdateRunningOrScheduled());
+}
+
+void UpdateAttempter::UpdateLastCheckedTime() {
+  last_checked_time_ = system_state_->clock()->GetWallclockTime().ToTimeT();
+}
+
+// Delegate methods:
+void UpdateAttempter::ProcessingDone(const ActionProcessor* processor,
+                                     ErrorCode code) {
+  LOG(INFO) << "Processing Done.";
+  actions_.clear();
+
+  // Reset cpu shares back to normal.
+  cpu_limiter_.StopLimiter();
+
+  if (code != ErrorCode::kSuccess) {
+      // update metrics
+      system_state_->payload_state()->UpdateFailed(code);
+      DeltaPerformer::ResetUpdateProgress(prefs_, false);
+      // send ERROR event and go into IDLE state otherwise update_engine
+      // will not able to handle other requests
+      // REPORTING_ERROR_EVENT is recoverable state which is used just for
+      // notifying about error.
+      SetStatusAndNotify(UpdateStatus::REPORTING_ERROR_EVENT);
+      // go back to IDLE
+      SetStatusAndNotify(UpdateStatus::IDLE);
+      ScheduleUpdates();
+      return;
+  }
+
+  WriteUpdateCompletedMarker();
+  prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
+  prefs_->SetString(kPrefsPreviousVersion,
+                    payload_properties_.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());
+  prefs_->Delete(kPrefsUpdateFirstSeenAt);
+
+  SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+  LOG(INFO) << "Update successfully applied, waiting to reboot.";
+  return;
+}
+
+void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) {
+  // Reset cpu shares back to normal.
+  cpu_limiter_.StopLimiter();
+  download_progress_ = 0.0;
+  SetStatusAndNotify(UpdateStatus::IDLE);
+  ScheduleUpdates();
+  actions_.clear();
+}
+
+// Called whenever an action has finished processing, either successfully
+// or otherwise.
+void UpdateAttempter::ActionCompleted(ActionProcessor* processor,
+                                      AbstractAction* action,
+                                      ErrorCode code) {
+  const string type = action->Type();
+
+  // 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).
+  if (type == DownloadAction::StaticType()) {
+    download_progress_ = 0.0;
+    http_response_code_ = download_action_->GetHTTPResponseCode();
+  }
+  else if (type == PayloadPropertiesRequestAction::StaticType())
+  {
+    http_response_code_ = payload_properties_request_action_->GetHTTPResponseCode();
+  }
+  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 (status_ >= UpdateStatus::DOWNLOADING &&
+        code != ErrorCode::kDownloadTransferError) {
+      MarkDeltaUpdateFailure();
+    }
+    return;
+  }
+
+  if (type == PayloadPropertiesHandlerAction::StaticType()) {
+    // 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.
+    CHECK(action == payload_properties_handler_action_.get());
+    const InstallPlan& plan = payload_properties_handler_action_->install_plan();
+    UpdateLastCheckedTime();
+    new_version_ = plan.version;
+    new_payload_size_ = plan.payload_size;
+    SetupDownload();
+    cpu_limiter_.StartLimiter();
+    SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
+  } else if (type == DownloadAction::StaticType()) {
+    SetStatusAndNotify(UpdateStatus::FINALIZING);
+  }
+}
+
+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();
+  }
+}
+
+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;
+
+      // 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(int64_t* last_checked_time,
+                                double* progress,
+                                string* current_operation,
+                                string* new_version,
+                                int64_t* new_payload_size) {
+  *last_checked_time = last_checked_time_;
+  *progress = download_progress_;
+  *current_operation = UpdateStatusToString(status_);
+  *new_version = new_version_;
+  *new_payload_size = new_payload_size_;
+  return true;
+}
+
+void UpdateAttempter::UpdateBootFlags() {
+  if (update_boot_flags_running_) {
+    LOG(INFO) << "Update boot flags running, nothing to do.";
+    return;
+  }
+  if (updated_boot_flags_) {
+    LOG(INFO) << "Already updated boot flags. Skipping.";
+    if (start_action_processor_) {
+      ScheduleProcessingStart();
+    }
+    return;
+  }
+  // This is purely best effort. Failures should be logged by Subprocess. Run
+  // the script asynchronously to avoid blocking the event loop regardless of
+  // the script runtime.
+  update_boot_flags_running_ = true;
+  LOG(INFO) << "Marking booted slot as good.";
+  if (!system_state_->boot_control()->MarkBootSuccessfulAsync(Bind(
+          &UpdateAttempter::CompleteUpdateBootFlags, base::Unretained(this)))) {
+    LOG(ERROR) << "Failed to mark current boot as successful.";
+    CompleteUpdateBootFlags(false);
+  }
+}
+
+void UpdateAttempter::CompleteUpdateBootFlags(bool successful) {
+  update_boot_flags_running_ = false;
+  updated_boot_flags_ = true;
+  if (start_action_processor_) {
+    ScheduleProcessingStart();
+  }
+}
+
+void UpdateAttempter::BroadcastStatus() {
+  for (const auto& observer : service_observers_) {
+    observer->SendStatusUpdate(last_checked_time_,
+                               download_progress_,
+                               status_,
+                               new_version_,
+                               new_payload_size_);
+  }
+  last_notify_time_ = TimeTicks::Now();
+}
+
+void UpdateAttempter::SetStatusAndNotify(UpdateStatus status) {
+  status_ = status;
+  BroadcastStatus();
+}
+
+void UpdateAttempter::ScheduleProcessingStart() {
+  LOG(INFO) << "Scheduling an action processor start.";
+  start_action_processor_ = false;
+  MessageLoop::current()->PostTask(
+      FROM_HERE,
+      Bind([](ActionProcessor* processor) { processor->StartProcessing(); },
+           base::Unretained(processor_.get())));
+}
+
+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::SetupDownload() {
+  MultiRangeHttpFetcher* fetcher =
+      static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher());
+  fetcher->ClearRanges();
+  //if (response_handler_action_->install_plan().is_resume) {
+  if (payload_properties_handler_action_->install_plan().is_resume) {
+    // Resuming an update so fetch the update manifest metadata first.
+    int64_t manifest_metadata_size = 0;
+    int64_t manifest_signature_size = 0;
+    prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
+    prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size);
+    fetcher->AddRange(PAYLOAD_PROPERTIES_SIZE, manifest_metadata_size + manifest_signature_size);
+    // If there're remaining unprocessed data blobs, fetch them. Be careful not
+    // to request data beyond the end of the payload to avoid 416 HTTP response
+    // error codes.
+    int64_t next_data_offset = 0;
+    prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
+    uint64_t resume_offset =
+        manifest_metadata_size + manifest_signature_size + next_data_offset;
+    if (resume_offset < payload_properties_handler_action_->install_plan().payload_size) {
+      fetcher->AddRange(resume_offset);
+    }
+  } else {
+    fetcher->AddRange(PAYLOAD_PROPERTIES_SIZE);
+  }
+}
+
+
+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();
+}
+
+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::IsUpdateRunningOrScheduled() {
+  return ((status_ != UpdateStatus::IDLE &&
+           status_ != UpdateStatus::UPDATED_NEED_REBOOT) ||
+          waiting_for_scheduled_check_);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/update_attempter_nestlabs.h b/update_engine/update_attempter_nestlabs.h
new file mode 100644
index 0000000..d490d74
--- /dev/null
+++ b/update_engine/update_attempter_nestlabs.h
@@ -0,0 +1,397 @@
+//
+// 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_UPDATE_ATTEMPTER_NESTLABS_H_
+#define UPDATE_ENGINE_UPDATE_ATTEMPTER_NESTLABS_H_
+
+#include <time.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.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/payload_properties_request_action.h"
+#include "update_engine/payload_properties_handler_action.h"
+#include "update_engine/payload_properties.h"
+#include "update_engine/payload_consumer/download_action.h"
+#include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/proxy_resolver.h"
+#include "update_engine/service_observer_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/weave_service_interface.h"
+
+class MetricsLibraryInterface;
+
+namespace policy {
+class PolicyProvider;
+}
+
+namespace chromeos_update_engine {
+
+class LibCrosProxy;
+class UpdateEngineAdaptor;
+
+class UpdateAttempter : public ActionProcessorDelegate,
+                        public DownloadActionDelegate,
+                        public CertificateChecker::Observer,
+                        public WeaveServiceInterface::DelegateInterface, // TODO remove
+                        public PostinstallRunnerAction::DelegateInterface {
+ public:
+  using UpdateStatus = update_engine::UpdateStatus;
+  static const int kMaxDeltaUpdateFailures;
+
+  UpdateAttempter(SystemState* system_state,
+                  CertificateChecker* cert_checker,
+                  LibCrosProxy* libcros_proxy);
+  ~UpdateAttempter() override;
+
+  // Further initialization to be done post construction.
+  void Init();
+
+  // Initiates scheduling of update checks.
+  virtual void ScheduleUpdates();
+
+  // Checks for update and, if a newer version is available, attempts to update
+  // the system. Non-empty |in_app_version| or |in_update_url| prevents
+  // automatic detection of the parameter.  |target_channel| denotes a
+  // policy-mandated channel we are updating to, if not empty. If |obey_proxies|
+  // is true, the update will likely respect Chrome's proxy setting. For
+  // security reasons, we may still not honor them. |interactive| should be true
+  // if this was called from the user (ie dbus).
+  virtual void Update(const std::string& app_version,
+                      const std::string& url,
+                      const std::string& target_channel = "",
+                      const std::string& target_version_prefix = "",
+                      bool obey_proxies = false,
+                      bool interactive = false);
+
+  // 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;
+  // WeaveServiceInterface::DelegateInterface overrides.
+  bool OnCheckForUpdates(brillo::ErrorPtr* error) override { return false; };
+  bool OnTrackChannel(const std::string& channel,
+                      brillo::ErrorPtr* error) override { return false; };
+  bool GetWeaveState(int64_t* last_checked_time,
+                     double* progress,
+                     UpdateStatus* update_status,
+                     std::string* current_channel,
+                     std::string* tracking_channel) override { return false;};
+
+  // 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 params. Returns true on success.
+  virtual bool GetStatus(int64_t* last_checked_time,
+                         double* progress,
+                         std::string* current_operation,
+                         std::string* new_version,
+                         int64_t* new_size);
+
+  // Runs chromeos-setgoodkernel, whose responsibility it is to mark the
+  // currently booted partition has high priority/permanent/etc. The execution
+  // is asynchronous. On completion, the action processor may be started
+  // depending on the |start_action_processor_| field. Note that every update
+  // attempt goes through this method.
+  void UpdateBootFlags();
+
+  // Called when the boot flags have been updated.
+  void CompleteUpdateBootFlags(bool success);
+
+  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; }
+
+  // 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.
+  virtual void CheckForUpdate(const std::string& app_version,
+                              const std::string& url,
+                              bool is_interactive);
+  // 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 sucess, false otherwise.
+  bool RebootIfNeeded();
+
+  // 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.
+  // TODO (dmitryya) figire out why do we need it
+  bool ShouldCancel(ErrorCode* cancel_reason) override { return false;};
+
+  void DownloadComplete() override;
+
+  // Broadcasts the current status to all observers.
+  void BroadcastStatus();
+
+  // Called at update_engine startup to do various house-keeping.
+  void UpdateEngineStarted();
+
+  // 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 happended to be into an update (current version).
+  // This will return an empty string otherwise.
+  std::string const& GetPrevVersion() const { return prev_version_; }
+
+  // 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)>*  // NOLINT(readability/function)
+      callback) {
+    forced_update_pending_callback_.reset(callback);
+  }
+
+  // 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(); }
+
+
+  // =================== TO REMOVE 
+  // Broadcasts the current tracking channel to all observers.
+  void BroadcastChannel() {};
+
+  // Returns the poll interval dictated by Omaha, if provided; zero otherwise.
+  virtual unsigned int server_dictated_poll_interval() const {
+    return 0;
+  }
+
+  // Returns the number of consecutive failed update checks.
+  virtual unsigned int consecutive_failed_update_checks() const {
+    return 0;
+  }
+
+ private:
+  // Update server URL for automated lab test.
+  static const char* const kTestUpdateUrl;
+
+  // Friend declarations for testing purposes.
+  friend class UpdateAttempterUnderTest;
+  friend class UpdateAttempterTest;
+  FRIEND_TEST(UpdateAttempterTest, ActionCompletedDownloadTest);
+  FRIEND_TEST(UpdateAttempterTest, ActionCompletedErrorTest);
+  FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
+  FRIEND_TEST(UpdateAttempterTest, UpdateTest);
+  FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
+  FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
+
+private:
+  bool CalculateUpdateParams(const std::string& version,
+                             const std::string& url);
+
+  // CertificateChecker::Observer method.
+  // Report metrics about the certificate being checked.
+  void CertificateChecked(ServerToCheck server_to_check,
+                          CertificateCheckResult result) override;
+
+  // Sets the status to the given status and notifies a status update over dbus.
+  void SetStatusAndNotify(UpdateStatus status);
+
+  // Sets up the download parameters after receiving the update check response.
+  void SetupDownload();
+
+  // Schedules an event loop callback to start the action processor. This is
+  // scheduled asynchronously to unblock the event loop.
+  void ScheduleProcessingStart();
+
+  // 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() {
+    return &direct_proxy_resolver_;
+  }
+
+  // Helper method of Update() and Rollback() to construct the sequence of
+  // actions to be performed for the postinstall.
+  // |previous_action| is the previous action to get
+  // bonded with the install_plan that gets passed to postinstall.
+  void BuildPostInstallActions(InstallPlanAction* previous_action);
+
+  // 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);
+
+  // 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();
+
+  // Returns whether an update is currently running or scheduled.
+  bool IsUpdateRunningOrScheduled();
+
+  // 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_;
+
+  std::vector<std::shared_ptr<AbstractAction>> actions_;
+  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_;
+
+  // Pointer to the DownloadAction in the actions_ vector.
+  std::shared_ptr<DownloadAction> download_action_;
+
+  std::shared_ptr<PayloadPropertiesHandlerAction> payload_properties_handler_action_;
+  std::shared_ptr<PayloadPropertiesRequestAction> payload_properties_request_action_;
+
+  // 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;
+
+  // HTTP server response code from the last HTTP request action.
+  int http_response_code_ = 0;
+
+  // 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_ = "0000000";
+  int64_t new_payload_size_ = 0;
+
+  // Our two proxy resolvers
+  DirectProxyResolver direct_proxy_resolver_;
+
+  // Originally, both of these flags are false. Once UpdateBootFlags is called,
+  // |update_boot_flags_running_| is set to true. As soon as UpdateBootFlags
+  // completes its asynchronous run, |update_boot_flags_running_| is reset to
+  // false and |updated_boot_flags_| is set to true. From that point on there
+  // will be no more changes to these flags.
+  //
+  // True if UpdateBootFlags has completed.
+  bool updated_boot_flags_ = false;
+  // True if UpdateBootFlags is running.
+  bool update_boot_flags_running_ = false;
+
+  // True if the action processor needs to be started by the boot flag updater.
+  bool start_action_processor_ = false;
+
+  // Used for fetching information about the device policy.
+  std::unique_ptr<policy::PolicyProvider> policy_provider_;
+
+  // 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_;
+
+  std::string app_version_;
+  std::string url_;
+
+  PayloadProperties payload_properties_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_UPDATE_ATTEMPTER_NESTLABS_H_
diff --git a/update_engine/update_attempter_unittest.cc b/update_engine/update_attempter_unittest.cc
new file mode 100644
index 0000000..94a1b3c
--- /dev/null
+++ b/update_engine/update_attempter_unittest.cc
@@ -0,0 +1,981 @@
+//
+// 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/update_attempter.h"
+
+#include <stdint.h>
+
+#include <memory>
+
+#include <base/files/file_util.h>
+#include <base/message_loop/message_loop.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/make_unique_ptr.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>
+
+#if USE_LIBCROS
+#include "libcros/dbus-proxies.h"
+#include "libcros/dbus-proxy-mocks.h"
+#include "update_engine/libcros_proxy.h"
+#endif // USE_LIBCROS
+#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/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/fake_system_state.h"
+#include "update_engine/mock_p2p_manager.h"
+#include "update_engine/mock_payload_state.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"
+
+using base::Time;
+using base::TimeDelta;
+#if USE_LIBCROS
+using org::chromium::LibCrosServiceInterfaceProxyMock;
+using org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyMock;
+#endif // USE_LIBCROS
+using std::string;
+using std::unique_ptr;
+using testing::DoAll;
+using testing::InSequence;
+using testing::Ne;
+using testing::NiceMock;
+using testing::Property;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::_;
+using update_engine::UpdateStatus;
+
+namespace chromeos_update_engine {
+
+// 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:
+  UpdateAttempterUnderTest(SystemState* system_state,
+                           LibCrosProxy* libcros_proxy)
+      : UpdateAttempter(system_state, nullptr, libcros_proxy) {}
+
+  // Wrap the update scheduling method, allowing us to opt out of scheduled
+  // updates for testing purposes.
+  void ScheduleUpdates() override {
+    schedule_updates_called_ = true;
+    if (do_schedule_updates_) {
+      UpdateAttempter::ScheduleUpdates();
+    } else {
+      LOG(INFO) << "[TEST] Update scheduling disabled.";
+    }
+  }
+  void EnableScheduleUpdates() { do_schedule_updates_ = true; }
+  void DisableScheduleUpdates() { do_schedule_updates_ = false; }
+
+  // Indicates whether ScheduleUpdates() was called.
+  bool schedule_updates_called() const { return schedule_updates_called_; }
+
+  // Need to expose forced_omaha_url_ so we can test it.
+  const string& forced_omaha_url() const { return forced_omaha_url_; }
+
+ private:
+  bool schedule_updates_called_ = false;
+  bool do_schedule_updates_ = true;
+};
+
+class UpdateAttempterTest : public ::testing::Test {
+ protected:
+  UpdateAttempterTest()
+      :
+#if USE_LIBCROS
+        service_interface_mock_(new LibCrosServiceInterfaceProxyMock()),
+        ue_proxy_resolved_interface_mock_(
+            new NiceMock<UpdateEngineLibcrosProxyResolvedInterfaceProxyMock>()),
+        libcros_proxy_(
+            brillo::make_unique_ptr(service_interface_mock_),
+            brillo::make_unique_ptr(ue_proxy_resolved_interface_mock_)),
+#endif  // USE_LIBCROS
+        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_);
+    loop_.SetAsCurrent();
+
+    certificate_checker_.Init();
+
+    // Finish initializing the attempter.
+    attempter_.Init();
+  }
+
+  void SetUp() override {
+    EXPECT_NE(nullptr, attempter_.system_state_);
+    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(0, attempter_.new_payload_size_);
+    processor_ = new NiceMock<MockActionProcessor>();
+    attempter_.processor_.reset(processor_);  // Transfers ownership.
+    prefs_ = fake_system_state_.mock_prefs();
+
+    // Set up 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();
+
+  bool actual_using_p2p_for_downloading() {
+    return actual_using_p2p_for_downloading_;
+  }
+  bool actual_using_p2p_for_sharing() {
+    return actual_using_p2p_for_sharing_;
+  }
+
+  base::MessageLoopForIO base_loop_;
+  brillo::BaseMessageLoop loop_{&base_loop_};
+
+  FakeSystemState fake_system_state_;
+#if USE_LIBCROS
+  LibCrosServiceInterfaceProxyMock* service_interface_mock_;
+  UpdateEngineLibcrosProxyResolvedInterfaceProxyMock*
+      ue_proxy_resolved_interface_mock_;
+  LibCrosProxy libcros_proxy_;
+  UpdateAttempterUnderTest attempter_{&fake_system_state_, &libcros_proxy_};
+#else
+  UpdateAttempterUnderTest attempter_{&fake_system_state_, nullptr};
+#endif  // USE_LIBCROS
+  OpenSSLWrapper openssl_wrapper_;
+  CertificateChecker certificate_checker_;
+
+  NiceMock<MockActionProcessor>* processor_;
+  NiceMock<MockPrefs>* prefs_;  // Shortcut to fake_system_state_->mock_prefs().
+  NiceMock<MockConnectionManager> mock_connection_manager;
+
+  bool actual_using_p2p_for_downloading_;
+  bool actual_using_p2p_for_sharing_;
+};
+
+void UpdateAttempterTest::ScheduleQuitMainLoop() {
+  loop_.PostTask(
+      FROM_HERE,
+      base::Bind([](brillo::BaseMessageLoop* loop) { loop->BreakLoop(); },
+                 base::Unretained(&loop_)));
+}
+
+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());
+  EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
+  attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess);
+  EXPECT_EQ(503, attempter_.http_response_code());
+  EXPECT_EQ(UpdateStatus::FINALIZING, attempter_.status());
+  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, 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) {
+  extern ErrorCode GetErrorCodeForAction(AbstractAction* action,
+                                              ErrorCode code);
+  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));
+  FilesystemVerifierAction filesystem_verifier_action;
+  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(
+          SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures - 1),
+          Return(true)));
+  attempter_.DisableDeltaUpdateIfNeeded();
+  EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay());
+  EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<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(SetArgumentPointee<1>(-1), Return(true)))
+      .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(true)))
+      .WillOnce(DoAll(
+          SetArgumentPointee<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.payload_urls.push_back(url1);
+  response.payload_urls.push_back("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(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.
+const string kUpdateActionTypes[] = {  // NOLINT(runtime/string)
+  OmahaRequestAction::StaticType(),
+  OmahaResponseHandlerAction::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.
+const string kRollbackActionTypes[] = {  // NOLINT(runtime/string)
+  InstallPlanAction::StaticType(),
+  PostinstallRunnerAction::StaticType(),
+};
+
+}  // 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.
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+  EXPECT_CALL(*device_policy, LoadPolicy())
+      .Times(testing::AtLeast(1)).WillRepeatedly(Return(true));
+
+  {
+    InSequence s;
+    for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
+      EXPECT_CALL(*processor_,
+                  EnqueueAction(Property(&AbstractAction::Type,
+                                         kUpdateActionTypes[i])));
+    }
+    EXPECT_CALL(*processor_, StartProcessing());
+  }
+
+  attempter_.Update("", "", "", "", false, false);
+  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(arraysize(kUpdateActionTypes), attempter_.actions_.size());
+  for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
+    EXPECT_EQ(kUpdateActionTypes[i], attempter_.actions_[i]->Type());
+  }
+  EXPECT_EQ(attempter_.response_handler_action_.get(),
+            attempter_.actions_[1].get());
+  AbstractAction* action_3 = attempter_.actions_[3].get();
+  ASSERT_NE(nullptr, action_3);
+  ASSERT_EQ(DownloadAction::StaticType(), action_3->Type());
+  DownloadAction* download_action = static_cast<DownloadAction*>(action_3);
+  EXPECT_EQ(&attempter_, download_action->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.
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(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 (enterprise_rollback) {
+    // We return an empty owner as this is an enterprise.
+    EXPECT_CALL(*device_policy, GetOwner(_)).WillRepeatedly(
+        DoAll(SetArgumentPointee<0>(string("")),
+        Return(true)));
+  } else {
+    // We return a fake owner as this is an owned consumer device.
+    EXPECT_CALL(*device_policy, GetOwner(_)).WillRepeatedly(
+        DoAll(SetArgumentPointee<0>(string("fake.mail@fake.com")),
+        Return(true)));
+  }
+
+  if (is_rollback_allowed) {
+    InSequence s;
+    for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
+      EXPECT_CALL(*processor_,
+                  EnqueueAction(Property(&AbstractAction::Type,
+                                         kRollbackActionTypes[i])));
+    }
+    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(arraysize(kRollbackActionTypes), attempter_.actions_.size());
+  for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
+    EXPECT_EQ(kRollbackActionTypes[i], attempter_.actions_[i]->Type());
+  }
+  EXPECT_EQ(UpdateStatus::ATTEMPTING_ROLLBACK, attempter_.status());
+  AbstractAction* action_0 = attempter_.actions_[0].get();
+  ASSERT_NE(nullptr, action_0);
+  ASSERT_EQ(InstallPlanAction::StaticType(), action_0->Type());
+  InstallPlanAction* install_plan_action =
+      static_cast<InstallPlanAction*>(action_0);
+  InstallPlan* install_plan = install_plan_action->install_plan();
+  EXPECT_EQ(0U, install_plan->partitions.size());
+  EXPECT_EQ(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(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_.schedule_updates_called());
+  // 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_.schedule_updates_called());
+}
+
+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) {
+  OmahaResponseHandlerAction *response_action =
+      new OmahaResponseHandlerAction(&fake_system_state_);
+  response_action->install_plan_.is_resume = true;
+  attempter_.response_handler_action_.reset(response_action);
+  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("", "", "", "", false, false);
+  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("", "", "", "", false, false);
+  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("", "", "", "", false, false);
+  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("", "", "", "", false, false);
+  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("", "", "", "", false, true /* interactive */);
+  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;
+
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(device_policy);
+
+  EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+      .WillRepeatedly(DoAll(
+          SetArgumentPointee<0>(scatter_factor_in_seconds),
+          Return(true)));
+
+  attempter_.Update("", "", "", "", false, false);
+  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;
+
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(device_policy);
+
+  EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+      .WillRepeatedly(DoAll(
+          SetArgumentPointee<0>(scatter_factor_in_seconds),
+          Return(true)));
+
+  attempter_.Update("", "", "", "", false, false);
+  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("", "", "", "", false, false);
+  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(kPrefsWallClockWaitPeriod, 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;
+
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(device_policy);
+
+  EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+      .WillRepeatedly(DoAll(
+          SetArgumentPointee<0>(scatter_factor_in_seconds),
+          Return(true)));
+
+  // Trigger an interactive check so we can test that scattering is disabled.
+  attempter_.Update("", "", "", "", false, 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(kPrefsWallClockWaitPeriod));
+  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();
+}
+
+// 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());
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateAUTest) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false);
+  attempter_.CheckForUpdate("", "autest", true);
+  EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateScheduledAUTest) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false);
+  attempter_.CheckForUpdate("", "autest-scheduled", true);
+  EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/update_engine-client.gyp b/update_engine/update_engine-client.gyp
new file mode 100644
index 0000000..588fc63
--- /dev/null
+++ b/update_engine/update_engine-client.gyp
@@ -0,0 +1,41 @@
+#
+# 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.
+#
+{
+  'targets': [
+    # update_engine client library generated headers. Used by other daemons and
+    # by the update_engine_client console program to interact with
+    # update_engine.
+    {
+      'target_name': 'libupdate_engine-client-headers',
+      'type': 'none',
+      'actions': [
+        {
+          'action_name': 'update_engine_client-dbus-proxies',
+          'variables': {
+            'dbus_service_config': 'dbus_bindings/dbus-service-config.json',
+            'proxy_output_file': 'include/update_engine/dbus-proxies.h',
+            'mock_output_file': 'include/update_engine/dbus-proxy-mocks.h',
+            'proxy_path_in_mocks': 'update_engine/dbus-proxies.h',
+          },
+          'sources': [
+            'dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml',
+          ],
+          'includes': ['../../../platform2/common-mk/generate-dbus-proxies.gypi'],
+        },
+      ],
+    },
+  ],
+}
diff --git a/update_engine/update_engine.conf b/update_engine/update_engine.conf
new file mode 100644
index 0000000..449e669
--- /dev/null
+++ b/update_engine/update_engine.conf
@@ -0,0 +1,2 @@
+PAYLOAD_MAJOR_VERSION=2
+PAYLOAD_MINOR_VERSION=3
diff --git a/update_engine/update_engine.gyp b/update_engine/update_engine.gyp
new file mode 100644
index 0000000..38d6ba1
--- /dev/null
+++ b/update_engine/update_engine.gyp
@@ -0,0 +1,581 @@
+#
+# 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.
+#
+{
+  'target_defaults': {
+    'variables': {
+      'deps': [
+        'libbrillo-<(libbase_ver)',
+        'libchrome-<(libbase_ver)',
+      ],
+      # The -DUSE_* flags are passed from platform2.py. We use sane defaults
+      # here when these USE flags are not defined. You can set the default value
+      # for the USE flag in the ebuild.
+      'USE_binder%': '0',
+      'USE_dbus%': '1',
+      'USE_hwid_override%': '0',
+      'USE_libcros%': '1',
+      'USE_mtd%': '0',
+      'USE_power_management%': '0',
+      'USE_buffet%': '0',
+    },
+    'cflags': [
+      '-g',
+      '-ffunction-sections',
+      '-Wall',
+      '-Wextra',
+      '-Werror',
+      '-Wno-unused-parameter',
+    ],
+    'cflags_cc': [
+      '-fno-strict-aliasing',
+      '-Wnon-virtual-dtor',
+    ],
+    'ldflags': [
+      '-Wl,--gc-sections',
+    ],
+    'defines': [
+      '__CHROMEOS__',
+      '_FILE_OFFSET_BITS=64',
+      '_POSIX_C_SOURCE=199309L',
+      'USE_BINDER=<(USE_binder)',
+      'USE_DBUS=<(USE_dbus)',
+      'USE_HWID_OVERRIDE=<(USE_hwid_override)',
+      'USE_LIBCROS=<(USE_libcros)',
+      'USE_MTD=<(USE_mtd)',
+      'USE_OMAHA=1',
+      'USE_SHILL=1',
+      'USE_WEAVE=<(USE_buffet)',
+    ],
+    'include_dirs': [
+      # We need this include dir because we include all the local code as
+      # "update_engine/...".
+      '<(platform2_root)/../aosp/system',
+      '<(platform2_root)/../aosp/system/update_engine/client_library/include',
+    ],
+  },
+  'targets': [
+    # Protobufs.
+    {
+      'target_name': 'update_metadata-protos',
+      'type': 'static_library',
+      'variables': {
+        'proto_in_dir': '.',
+        'proto_out_dir': 'include/update_engine',
+        'exported_deps': [
+          'protobuf-lite',
+        ],
+        'deps': ['<@(exported_deps)'],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'sources': [
+        'update_metadata.proto'
+      ],
+      'includes': ['../../../platform2/common-mk/protoc.gypi'],
+    },
+    # Chrome D-Bus bindings.
+    {
+      'target_name': 'update_engine-dbus-adaptor',
+      'type': 'none',
+      'variables': {
+        'dbus_adaptors_out_dir': 'include/dbus_bindings',
+        'dbus_xml_extension': 'dbus-xml',
+      },
+      'sources': [
+        'dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml',
+      ],
+      'includes': ['../../../platform2/common-mk/generate-dbus-adaptors.gypi'],
+    },
+    {
+      'target_name': 'update_engine-other-dbus-proxies',
+      'type': 'none',
+      'actions': [
+        {
+          'action_name': 'update_engine-dbus-libcros-client',
+          'variables': {
+            'mock_output_file': 'include/libcros/dbus-proxy-mocks.h',
+            'proxy_output_file': 'include/libcros/dbus-proxies.h'
+          },
+          'sources': [
+            'dbus_bindings/org.chromium.LibCrosService.dbus-xml',
+          ],
+          'includes': ['../../../platform2/common-mk/generate-dbus-proxies.gypi'],
+        },
+      ],
+    },
+    # The payload application component and common dependencies.
+    {
+      'target_name': 'libpayload_consumer',
+      'type': 'static_library',
+      'dependencies': [
+        'update_metadata-protos',
+      ],
+      #TODO(deymo): Remove unused dependencies once we stop including files
+      # from the root directory.
+      'variables': {
+        'exported_deps': [
+          'libcrypto',
+          'libimgpatch',
+          'xz-embedded',
+        ],
+        'deps': ['<@(exported_deps)'],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'link_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+        'libraries': [
+          '-lbz2',
+          '-lrt',
+        ],
+      },
+      'sources': [
+        'common/action_processor.cc',
+        'common/boot_control_stub.cc',
+        'common/clock.cc',
+        'common/constants.cc',
+        'common/cpu_limiter.cc',
+        'common/error_code_utils.cc',
+        'common/hash_calculator.cc',
+        'common/http_common.cc',
+        'common/http_fetcher.cc',
+        'common/hwid_override.cc',
+        'common/multi_range_http_fetcher.cc',
+        'common/platform_constants_chromeos.cc',
+        'common/prefs.cc',
+        'common/subprocess.cc',
+        'common/terminator.cc',
+        'common/utils.cc',
+        'payload_consumer/bzip_extent_writer.cc',
+        'payload_consumer/delta_performer.cc',
+        'payload_consumer/download_action.cc',
+        'payload_consumer/extent_writer.cc',
+        'payload_consumer/file_descriptor.cc',
+        'payload_consumer/file_writer.cc',
+        'payload_consumer/filesystem_verifier_action.cc',
+        'payload_consumer/install_plan.cc',
+        'payload_consumer/payload_constants.cc',
+        'payload_consumer/payload_verifier.cc',
+        'payload_consumer/postinstall_runner_action.cc',
+        'payload_consumer/xz_extent_writer.cc',
+      ],
+      'conditions': [
+        ['USE_mtd == 1', {
+          'sources': [
+            'payload_consumer/mtd_file_descriptor.cc',
+          ],
+          'link_settings': {
+            'libraries': [
+              '-lmtdutils',
+            ],
+          },
+        }],
+      ],
+    },
+    # The main daemon static_library with all the code used to check for updates
+    # with Omaha and expose a DBus daemon.
+    {
+      'target_name': 'libupdate_engine',
+      'type': 'static_library',
+      'dependencies': [
+        'libpayload_consumer',
+        'update_metadata-protos',
+        'update_engine-dbus-adaptor',
+        'update_engine-other-dbus-proxies',
+      ],
+      'variables': {
+        'exported_deps': [
+          'dbus-1',
+          'expat',
+          'libcurl',
+          'libdebugd-client',
+          'libmetrics-<(libbase_ver)',
+          'libpower_manager-client',
+          'libsession_manager-client',
+          'libshill-client',
+          'libssl',
+          'libupdate_engine-client',
+        ],
+        'deps': ['<@(exported_deps)'],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'link_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+        'libraries': [
+          '-lbz2',
+          '-lpolicy-<(libbase_ver)',
+          '-lrootdev',
+          '-lrt',
+          '-lvboot_host',
+        ],
+      },
+      'sources': [
+        'boot_control_chromeos.cc',
+        'certificate_checker.cc',
+        'common_service.cc',
+        'connection_manager.cc',
+        'connection_utils.cc',
+        'daemon.cc',
+        'dbus_connection.cc',
+        'dbus_service.cc',
+        'hardware_chromeos.cc',
+        'image_properties_chromeos.cc',
+        'libcros_proxy.cc',
+        'libcurl_http_fetcher.cc',
+        'metrics.cc',
+        'metrics_utils.cc',
+        'omaha_request_action.cc',
+        'omaha_request_params.cc',
+        'omaha_response_handler_action.cc',
+        'omaha_utils.cc',
+        'p2p_manager.cc',
+        'payload_state.cc',
+        'power_manager_chromeos.cc',
+        'proxy_resolver.cc',
+        'real_system_state.cc',
+        'shill_proxy.cc',
+        'update_attempter.cc',
+        'update_manager/boxed_value.cc',
+        'update_manager/chromeos_policy.cc',
+        'update_manager/default_policy.cc',
+        'update_manager/evaluation_context.cc',
+        'update_manager/policy.cc',
+        'update_manager/real_config_provider.cc',
+        'update_manager/real_device_policy_provider.cc',
+        'update_manager/real_random_provider.cc',
+        'update_manager/real_shill_provider.cc',
+        'update_manager/real_system_provider.cc',
+        'update_manager/real_time_provider.cc',
+        'update_manager/real_updater_provider.cc',
+        'update_manager/state_factory.cc',
+        'update_manager/update_manager.cc',
+        'update_status_utils.cc',
+        'weave_service_factory.cc',
+      ],
+      'conditions': [
+        ['USE_buffet == 1', {
+          'sources': [
+            'weave_service.cc',
+          ],
+          'variables': {
+            'exported_deps': [
+              'libweave-<(libbase_ver)',
+            ],
+          },
+        }],
+        ['USE_libcros == 1', {
+          'dependencies': [
+            'update_engine-other-dbus-proxies',
+          ],
+          'sources': [
+            'chrome_browser_proxy_resolver.cc',
+          ],
+        }],
+      ],
+    },
+    # update_engine daemon.
+    {
+      'target_name': 'update_engine',
+      'type': 'executable',
+      'dependencies': [
+        'libupdate_engine',
+      ],
+      'sources': [
+        'main.cc',
+      ],
+    },
+    # update_engine client library.
+    {
+      'target_name': 'libupdate_engine_client',
+      'type': 'static_library',
+      'variables': {
+        'deps': [
+          'dbus-1',
+          'libupdate_engine-client',
+        ],
+      },
+      'sources': [
+        'client_library/client.cc',
+        'client_library/client_dbus.cc',
+        'update_status_utils.cc',
+      ],
+      'include_dirs': [
+        'client_library/include',
+      ],
+    },
+    # update_engine console client.
+    {
+      'target_name': 'update_engine_client',
+      'type': 'executable',
+      'dependencies': [
+        'libupdate_engine_client',
+      ],
+      'sources': [
+        'common/error_code_utils.cc',
+        'omaha_utils.cc',
+        'update_engine_client.cc',
+     ],
+    },
+    # server-side code. This is used for delta_generator and unittests but not
+    # for any client code.
+    {
+      'target_name': 'libpayload_generator',
+      'type': 'static_library',
+      'dependencies': [
+        'libpayload_consumer',
+        'update_metadata-protos',
+      ],
+      'variables': {
+        'exported_deps': [
+          'ext2fs',
+        ],
+        'deps': ['<@(exported_deps)'],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'link_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'sources': [
+        'payload_generator/ab_generator.cc',
+        'payload_generator/annotated_operation.cc',
+        'payload_generator/blob_file_writer.cc',
+        'payload_generator/block_mapping.cc',
+        'payload_generator/bzip.cc',
+        'payload_generator/cycle_breaker.cc',
+        'payload_generator/delta_diff_generator.cc',
+        'payload_generator/delta_diff_utils.cc',
+        'payload_generator/ext2_filesystem.cc',
+        'payload_generator/extent_ranges.cc',
+        'payload_generator/extent_utils.cc',
+        'payload_generator/full_update_generator.cc',
+        'payload_generator/graph_types.cc',
+        'payload_generator/graph_utils.cc',
+        'payload_generator/inplace_generator.cc',
+        'payload_generator/payload_file.cc',
+        'payload_generator/payload_generation_config.cc',
+        'payload_generator/payload_signer.cc',
+        'payload_generator/raw_filesystem.cc',
+        'payload_generator/tarjan.cc',
+        'payload_generator/topological_sort.cc',
+        'payload_generator/xz_chromeos.cc',
+      ],
+    },
+    # server-side delta generator.
+    {
+      'target_name': 'delta_generator',
+      'type': 'executable',
+      'dependencies': [
+        'libpayload_consumer',
+        'libpayload_generator',
+      ],
+      'link_settings': {
+        'ldflags!': [
+          '-pie',
+        ],
+      },
+      'sources': [
+        'payload_generator/generate_delta_main.cc',
+      ],
+    },
+  ],
+  'conditions': [
+    ['USE_test == 1', {
+      'targets': [
+        # Public keys used for unit testing.
+        {
+          'target_name': 'update_engine-testkeys',
+          'type': 'none',
+          'variables': {
+            'openssl_pem_in_dir': '.',
+            'openssl_pem_out_dir': 'include/update_engine',
+          },
+          'sources': [
+            'unittest_key.pem',
+            'unittest_key2.pem',
+          ],
+          'includes': ['../../../platform2/common-mk/openssl_pem.gypi'],
+        },
+        # Unpacks sample images used for testing.
+        {
+          'target_name': 'update_engine-test_images',
+          'type': 'none',
+          'variables': {
+            'image_out_dir': '.',
+          },
+          'sources': [
+            'sample_images/sample_images.tar.bz2',
+          ],
+          'includes': ['tar_bunzip2.gypi'],
+        },
+        # Test HTTP Server.
+        {
+          'target_name': 'test_http_server',
+          'type': 'executable',
+          'sources': [
+            'common/http_common.cc',
+            'test_http_server.cc',
+          ],
+        },
+        # Test subprocess helper.
+        {
+          'target_name': 'test_subprocess',
+          'type': 'executable',
+          'sources': [
+            'test_subprocess.cc',
+          ],
+        },
+        # Main unittest file.
+        {
+          'target_name': 'update_engine_unittests',
+          'type': 'executable',
+          'includes': ['../../../platform2/common-mk/common_test.gypi'],
+          'variables': {
+            'deps': [
+              'libbrillo-test-<(libbase_ver)',
+              'libchrome-test-<(libbase_ver)',
+              'libdebugd-client-test',
+              'libpower_manager-client-test',
+              'libsession_manager-client-test',
+              'libshill-client-test',
+            ],
+          },
+          'dependencies': [
+            'libupdate_engine',
+            'libpayload_generator',
+          ],
+          'includes': ['../../../platform2/common-mk/common_test.gypi'],
+          'sources': [
+            'boot_control_chromeos_unittest.cc',
+            'certificate_checker_unittest.cc',
+            'common/action_pipe_unittest.cc',
+            'common/action_processor_unittest.cc',
+            'common/action_unittest.cc',
+            'common/cpu_limiter_unittest.cc',
+            'common/fake_prefs.cc',
+            'common/file_fetcher.cc',  # Only required for tests.
+            'common/hash_calculator_unittest.cc',
+            'common/http_fetcher_unittest.cc',
+            'common/hwid_override_unittest.cc',
+            'common/mock_http_fetcher.cc',
+            'common/prefs_unittest.cc',
+            'common/subprocess_unittest.cc',
+            'common/terminator_unittest.cc',
+            'common/test_utils.cc',
+            'common/utils_unittest.cc',
+            'common_service_unittest.cc',
+            'connection_manager_unittest.cc',
+            'fake_shill_proxy.cc',
+            'fake_system_state.cc',
+            'hardware_chromeos_unittest.cc',
+            'image_properties_chromeos_unittest.cc',
+            'metrics_utils_unittest.cc',
+            'omaha_request_action_unittest.cc',
+            'omaha_request_params_unittest.cc',
+            'omaha_response_handler_action_unittest.cc',
+            'omaha_utils_unittest.cc',
+            'p2p_manager_unittest.cc',
+            'payload_consumer/bzip_extent_writer_unittest.cc',
+            'payload_consumer/delta_performer_integration_test.cc',
+            'payload_consumer/delta_performer_unittest.cc',
+            'payload_consumer/download_action_unittest.cc',
+            'payload_consumer/extent_writer_unittest.cc',
+            'payload_consumer/file_writer_unittest.cc',
+            'payload_consumer/filesystem_verifier_action_unittest.cc',
+            'payload_consumer/postinstall_runner_action_unittest.cc',
+            'payload_consumer/xz_extent_writer_unittest.cc',
+            'payload_generator/ab_generator_unittest.cc',
+            'payload_generator/blob_file_writer_unittest.cc',
+            'payload_generator/block_mapping_unittest.cc',
+            'payload_generator/cycle_breaker_unittest.cc',
+            'payload_generator/delta_diff_utils_unittest.cc',
+            'payload_generator/ext2_filesystem_unittest.cc',
+            'payload_generator/extent_ranges_unittest.cc',
+            'payload_generator/extent_utils_unittest.cc',
+            'payload_generator/fake_filesystem.cc',
+            'payload_generator/full_update_generator_unittest.cc',
+            'payload_generator/graph_utils_unittest.cc',
+            'payload_generator/inplace_generator_unittest.cc',
+            'payload_generator/payload_file_unittest.cc',
+            'payload_generator/payload_generation_config_unittest.cc',
+            'payload_generator/payload_signer_unittest.cc',
+            'payload_generator/tarjan_unittest.cc',
+            'payload_generator/topological_sort_unittest.cc',
+            'payload_generator/zip_unittest.cc',
+            'payload_state_unittest.cc',
+            'update_attempter_unittest.cc',
+            'update_manager/boxed_value_unittest.cc',
+            'update_manager/chromeos_policy_unittest.cc',
+            'update_manager/evaluation_context_unittest.cc',
+            'update_manager/generic_variables_unittest.cc',
+            'update_manager/prng_unittest.cc',
+            'update_manager/real_device_policy_provider_unittest.cc',
+            'update_manager/real_random_provider_unittest.cc',
+            'update_manager/real_shill_provider_unittest.cc',
+            'update_manager/real_system_provider_unittest.cc',
+            'update_manager/real_time_provider_unittest.cc',
+            'update_manager/real_updater_provider_unittest.cc',
+            'update_manager/umtest_utils.cc',
+            'update_manager/update_manager_unittest.cc',
+            'update_manager/variable_unittest.cc',
+            # Main entry point for runnning tests.
+            'testrunner.cc',
+          ],
+          'conditions': [
+            ['USE_libcros == 1', {
+              'sources': [
+                'chrome_browser_proxy_resolver_unittest.cc',
+              ],
+            }],
+          ],
+        },
+      ],
+    }],
+  ],
+}
diff --git a/update_engine/update_engine.rc b/update_engine/update_engine.rc
new file mode 100644
index 0000000..e6f1eba
--- /dev/null
+++ b/update_engine/update_engine.rc
@@ -0,0 +1,15 @@
+service update_engine /system/bin/update_engine --logtostderr --foreground
+    class late_start
+    user root
+    group root system wakelock dbus inet cache
+
+    writepid /dev/cpuctl/update_engine/tasks
+
+on post-fs
+    # update_engine
+    mkdir /dev/cpuctl/update_engine
+    chown system system /dev/cpuctl/update_engine/tasks
+    chmod 0666 /dev/cpuctl/update_engine/tasks
+    write /dev/cpuctl/update_engine/cpu.shares 1024
+    write /dev/cpuctl/update_engine/cpu.rt_runtime_us 700000
+    write /dev/cpuctl/update_engine/cpu.rt_period_us 1000000
diff --git a/update_engine/update_engine_client.cc b/update_engine/update_engine_client.cc
new file mode 100644
index 0000000..31a1fe2
--- /dev/null
+++ b/update_engine/update_engine_client.cc
@@ -0,0 +1,639 @@
+//
+// 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 <brillo/daemons/daemon.h>
+#include <brillo/flag_helper.h>
+
+#include "update_engine/client.h"
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/error_code_utils.h"
+#include "update_engine/omaha_utils.h"
+#include "update_engine/status_update_handler.h"
+#include "update_engine/update_status.h"
+#include "update_engine/update_status_utils.h"
+
+using chromeos_update_engine::EolStatus;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::UpdateStatusToString;
+using chromeos_update_engine::utils::ErrorCodeToString;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using update_engine::UpdateStatus;
+
+namespace {
+
+// Constant to signal that we need to continue running the daemon after
+// initialization.
+const int kContinueRunning = -1;
+
+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::MessageLoop::current()->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(int64_t last_checked_time,
+                          double progress,
+                          UpdateStatus current_operation,
+                          const string& new_version,
+                          int64_t new_size) override;
+};
+
+void WatchingStatusUpdateHandler::HandleStatusUpdate(
+    int64_t last_checked_time, double progress, UpdateStatus current_operation,
+    const string& new_version, int64_t new_size) {
+  LOG(INFO) << "Got status update:";
+  LOG(INFO) << "  last_checked_time: " << last_checked_time;
+  LOG(INFO) << "  progress: " << progress;
+  LOG(INFO) << "  current_operation: "
+            << UpdateStatusToString(current_operation);
+  LOG(INFO) << "  new_version: " << new_version;
+  LOG(INFO) << "  new_size: " << new_size;
+}
+
+bool UpdateEngineClient::ShowStatus() {
+  int64_t last_checked_time = 0;
+  double progress = 0.0;
+  UpdateStatus current_op;
+  string new_version;
+  int64_t new_size = 0;
+
+  if (!client_->GetStatus(&last_checked_time, &progress, &current_op,
+                          &new_version, &new_size)) {
+    return false;
+  }
+
+  printf("LAST_CHECKED_TIME=%" PRIi64
+         "\nPROGRESS=%f\nCURRENT_OP=%s\n"
+         "NEW_VERSION=%s\nNEW_SIZE=%" PRIi64 "\n",
+         last_checked_time, progress, UpdateStatusToString(current_op),
+         new_version.c_str(), new_size);
+
+  return true;
+}
+
+int UpdateEngineClient::GetNeedReboot() {
+  int64_t last_checked_time = 0;
+  double progress = 0.0;
+  UpdateStatus current_op;
+  string new_version;
+  int64_t new_size = 0;
+
+  if (!client_->GetStatus(&last_checked_time, &progress, &current_op,
+                          &new_version, &new_size)) {
+    return 1;
+  }
+
+  if (current_op == 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(int64_t last_checked_time,
+                          double progress,
+                          UpdateStatus current_operation,
+                          const string& new_version,
+                          int64_t new_size) override;
+
+ private:
+  bool exit_on_error_;
+  update_engine::UpdateEngineClient* client_;
+};
+
+void UpdateWaitHandler::HandleStatusUpdate(int64_t /* last_checked_time */,
+                                           double /* progress */,
+                                           UpdateStatus current_operation,
+                                           const string& /* new_version */,
+                                           int64_t /* new_size */) {
+  if (exit_on_error_ && current_operation == UpdateStatus::IDLE) {
+    int last_attempt_error;
+    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(current_operation)
+               << ", last error code is " << ErrorCodeToString(code) << "("
+               << last_attempt_error << ")";
+    exit(1);
+  }
+  if (current_operation == UpdateStatus::UPDATED_NEED_REBOOT) {
+    LOG(INFO) << "Update succeeded -- reboot needed.";
+    exit(0);
+  }
+}
+
+int UpdateEngineClient::ProcessFlags() {
+  DEFINE_string(app_version, "", "Force the current app version.");
+#ifndef USE_NESTLABS
+  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.");
+#endif
+  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.");
+#ifdef USE_NESTLABS
+  DEFINE_string(url, "", "The URL of OTA update.");
+  DEFINE_bool(mark_boot_successful, false, "Mark current boot successful");
+#else
+  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.");
+#endif
+  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.");
+#ifndef USE_NESTLABS
+  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.");
+#endif
+  DEFINE_bool(status, false, "Print the status to stdout.");
+#ifndef USE_NESTLABS
+  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.");
+#endif
+  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.");
+#ifndef USE_NESTLABS
+  DEFINE_bool(eol_status, false, "Show the current end-of-life status.");
+#endif
+
+  // Boilerplate init commands.
+  base::CommandLine::Init(argc_, argv_);
+#ifndef USE_NESTLABS
+  brillo::FlagHelper::Init(argc_, argv_, "Chromium OS Update Engine Client");
+#else
+  brillo::FlagHelper::Init(argc_, argv_, "Nestlabs Update Engine Client");
+#endif
+
+  // 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;
+    }
+  }
+
+#ifndef USE_NESTLABS
+  // 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;
+      }
+    }
+  }
+#endif
+
+  // Show the rollback availability.
+  if (FLAGS_can_rollback) {
+    string rollback_partition;
+
+    if (!client_->GetRollbackPartition(&rollback_partition)) {
+      LOG(ERROR) << "Error while querying rollback partition availabilty.";
+      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;
+    }
+  }
+
+#ifndef USE_NESTLABS
+  // 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(&current_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;
+  }
+#endif
+
+#ifndef USE_NESTLABS
+  bool do_update_request = FLAGS_check_for_update | FLAGS_update |
+                           !FLAGS_app_version.empty() |
+                           !FLAGS_omaha_url.empty();
+
+  if (FLAGS_update) FLAGS_follow = true;
+#else
+  bool do_update_request = !FLAGS_app_version.empty() |
+                           !FLAGS_url.empty();
+#endif
+
+  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;
+    }
+  }
+
+#ifndef USE_NESTLABS
+  // 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 and install.";
+    if (!client_->AttemptUpdate(app_version, FLAGS_omaha_url,
+                                FLAGS_interactive)) {
+      LOG(ERROR) << "Error checking for update.";
+      return 1;
+    }
+  }
+#else
+  // Initiate an update check, if necessary.
+  if (do_update_request) {
+    LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored.";
+    string app_version = FLAGS_app_version;
+    LOG(INFO) << "Initiating update check and install.";
+    if (!client_->AttemptUpdate(app_version, FLAGS_url,
+                                FLAGS_interactive)) {
+      LOG(ERROR) << "Error checking for update.";
+      return 1;
+    }
+  }
+
+  if (FLAGS_mark_boot_successful) {
+    LOG(INFO) << "Mark current slot successful";
+    if (!client_->MarkBootSuccessful()) {
+      LOG(ERROR) << "Error marking the current slot successful";
+      return 1;
+    }
+  }
+#endif
+
+  // 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);
+      printf(
+          "ERROR_CODE=%i\n"
+          "ERROR_MESSAGE=%s\n",
+          last_attempt_error,
+          ErrorCodeToString(code).c_str());
+    }
+  }
+
+#ifndef USE_NESTLABS
+  if (FLAGS_eol_status) {
+    int eol_status;
+    if (!client_->GetEolStatus(&eol_status)) {
+      LOG(ERROR) << "Error getting the end-of-life status.";
+    } else {
+      EolStatus eol_status_code = static_cast<EolStatus>(eol_status);
+      printf("EOL_STATUS=%s\n", EolStatusToString(eol_status_code));
+    }
+  }
+#endif
+
+  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();
+}
diff --git a/update_engine/update_engine_client_android.cc b/update_engine/update_engine_client_android.cc
new file mode 100644
index 0000000..989a97e
--- /dev/null
+++ b/update_engine/update_engine_client_android.cc
@@ -0,0 +1,250 @@
+//
+// 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 <sysexits.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <binder/IServiceManager.h>
+#include <binderwrapper/binder_wrapper.h>
+#include <brillo/binder_watcher.h>
+#include <brillo/daemons/daemon.h>
+#include <brillo/flag_helper.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/syslog_logging.h>
+#include <utils/String16.h>
+#include <utils/StrongPointer.h>
+
+#include "android/os/BnUpdateEngineCallback.h"
+#include "android/os/IUpdateEngine.h"
+#include "update_engine/client_library/include/update_engine/update_status.h"
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/error_code_utils.h"
+#include "update_engine/update_status_utils.h"
+
+using android::binder::Status;
+
+namespace chromeos_update_engine {
+namespace internal {
+
+class UpdateEngineClientAndroid : public brillo::Daemon {
+ public:
+  UpdateEngineClientAndroid(int argc, char** argv) : argc_(argc), argv_(argv) {
+  }
+
+  int ExitWhenIdle(const Status& status);
+  int ExitWhenIdle(int return_code);
+
+ private:
+  class UECallback : public android::os::BnUpdateEngineCallback {
+   public:
+    explicit UECallback(UpdateEngineClientAndroid* client) : client_(client) {}
+
+    // android::os::BnUpdateEngineCallback overrides.
+    Status onStatusUpdate(int status_code, float progress) override;
+    Status onPayloadApplicationComplete(int error_code) override;
+
+   private:
+    UpdateEngineClientAndroid* client_;
+  };
+
+  int OnInit() override;
+
+  // Called whenever the UpdateEngine daemon dies.
+  void UpdateEngineServiceDied();
+
+  // Copy of argc and argv passed to main().
+  int argc_;
+  char** argv_;
+
+  android::sp<android::os::IUpdateEngine> service_;
+  android::sp<android::os::BnUpdateEngineCallback> callback_;
+
+  brillo::BinderWatcher binder_watcher_;
+};
+
+Status UpdateEngineClientAndroid::UECallback::onStatusUpdate(
+    int status_code, float progress) {
+  update_engine::UpdateStatus status =
+      static_cast<update_engine::UpdateStatus>(status_code);
+  LOG(INFO) << "onStatusUpdate(" << UpdateStatusToString(status) << " ("
+            << status_code << "), " << progress << ")";
+  return Status::ok();
+}
+
+Status UpdateEngineClientAndroid::UECallback::onPayloadApplicationComplete(
+    int error_code) {
+  ErrorCode code = static_cast<ErrorCode>(error_code);
+  LOG(INFO) << "onPayloadApplicationComplete(" << utils::ErrorCodeToString(code)
+            << " (" << error_code << "))";
+  client_->ExitWhenIdle(code == ErrorCode::kSuccess ? EX_OK : 1);
+  return Status::ok();
+}
+
+int UpdateEngineClientAndroid::OnInit() {
+  int ret = Daemon::OnInit();
+  if (ret != EX_OK)
+    return ret;
+
+  DEFINE_bool(update, false, "Start a new update, if no update in progress.");
+  DEFINE_string(payload,
+                "http://127.0.0.1:8080/payload",
+                "The URI to the update payload to use.");
+  DEFINE_int64(offset, 0,
+               "The offset in the payload where the CrAU update starts. "
+               "Used when --update is passed.");
+  DEFINE_int64(size, 0,
+               "The size of the CrAU part of the payload. If 0 is passed, it "
+               "will be autodetected. Used when --update is passed.");
+  DEFINE_string(headers,
+                "",
+                "A list of key-value pairs, one element of the list per line. "
+                "Used when --update is passed.");
+
+  DEFINE_bool(suspend, false, "Suspend an ongoing update and exit.");
+  DEFINE_bool(resume, false, "Resume a suspended update.");
+  DEFINE_bool(cancel, false, "Cancel the ongoing update and exit.");
+  DEFINE_bool(reset_status, false, "Reset an already applied update and exit.");
+  DEFINE_bool(follow,
+              false,
+              "Follow status update changes until a final state is reached. "
+              "Exit status is 0 if the update succeeded, and 1 otherwise.");
+
+  // Boilerplate init commands.
+  base::CommandLine::Init(argc_, argv_);
+  brillo::FlagHelper::Init(argc_, argv_, "Android Update Engine Client");
+  if (argc_ == 1) {
+    LOG(ERROR) << "Nothing to do. Run with --help for help.";
+    return 1;
+  }
+
+  // Ensure there are no positional arguments.
+  const std::vector<std::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;
+  }
+
+  bool keep_running = false;
+  brillo::InitLog(brillo::kLogToStderr);
+
+  // Initialize a binder watcher early in the process before any interaction
+  // with the binder driver.
+  binder_watcher_.Init();
+
+  android::status_t status = android::getService(
+      android::String16("android.os.UpdateEngineService"), &service_);
+  if (status != android::OK) {
+    LOG(ERROR) << "Failed to get IUpdateEngine binder from service manager: "
+               << Status::fromStatusT(status).toString8();
+    return ExitWhenIdle(1);
+  }
+
+  if (FLAGS_suspend) {
+    return ExitWhenIdle(service_->suspend());
+  }
+
+  if (FLAGS_resume) {
+    return ExitWhenIdle(service_->resume());
+  }
+
+  if (FLAGS_cancel) {
+    return ExitWhenIdle(service_->cancel());
+  }
+
+  if (FLAGS_reset_status) {
+    return ExitWhenIdle(service_->resetStatus());
+  }
+
+  if (FLAGS_follow) {
+    // Register a callback object with the service.
+    callback_ = new UECallback(this);
+    bool bound;
+    if (!service_->bind(callback_, &bound).isOk() || !bound) {
+      LOG(ERROR) << "Failed to bind() the UpdateEngine daemon.";
+      return 1;
+    }
+    keep_running = true;
+  }
+
+  if (FLAGS_update) {
+    std::vector<std::string> headers = base::SplitString(
+        FLAGS_headers, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+    std::vector<android::String16> and_headers;
+    for (const auto& header : headers) {
+      and_headers.push_back(android::String16{header.data(), header.size()});
+    }
+    Status status = service_->applyPayload(
+        android::String16{FLAGS_payload.data(), FLAGS_payload.size()},
+        FLAGS_offset,
+        FLAGS_size,
+        and_headers);
+    if (!status.isOk())
+      return ExitWhenIdle(status);
+  }
+
+  if (!keep_running)
+    return ExitWhenIdle(EX_OK);
+
+  // When following updates status changes, exit if the update_engine daemon
+  // dies.
+  android::BinderWrapper::Create();
+  android::BinderWrapper::Get()->RegisterForDeathNotifications(
+      android::os::IUpdateEngine::asBinder(service_),
+      base::Bind(&UpdateEngineClientAndroid::UpdateEngineServiceDied,
+                 base::Unretained(this)));
+
+  return EX_OK;
+}
+
+int UpdateEngineClientAndroid::ExitWhenIdle(const Status& status) {
+  if (status.isOk())
+    return ExitWhenIdle(EX_OK);
+  LOG(ERROR) << status.toString8();
+  return ExitWhenIdle(status.exceptionCode());
+}
+
+int UpdateEngineClientAndroid::ExitWhenIdle(int return_code) {
+  auto delayed_exit = base::Bind(
+      &Daemon::QuitWithExitCode, base::Unretained(this), return_code);
+  if (!brillo::MessageLoop::current()->PostTask(delayed_exit))
+    return 1;
+  return EX_OK;
+}
+
+void UpdateEngineClientAndroid::UpdateEngineServiceDied() {
+  LOG(ERROR) << "UpdateEngineService died.";
+  QuitWithExitCode(1);
+}
+
+}  // namespace internal
+}  // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+  chromeos_update_engine::internal::UpdateEngineClientAndroid client(
+      argc, argv);
+  return client.Run();
+}
diff --git a/update_engine/update_manager/boxed_value.cc b/update_engine/update_manager/boxed_value.cc
new file mode 100644
index 0000000..9758d33
--- /dev/null
+++ b/update_engine/update_manager/boxed_value.cc
@@ -0,0 +1,179 @@
+//
+// 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/update_manager/boxed_value.h"
+
+#include <stdint.h>
+
+#include <set>
+#include <string>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/connection_utils.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+using chromeos_update_engine::ConnectionTethering;
+using chromeos_update_engine::ConnectionType;
+using chromeos_update_engine::connection_utils::StringForConnectionType;
+using std::set;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// Template instantiation for common types; used in BoxedValue::ToString().
+// Keep in sync with boxed_value_unitttest.cc.
+
+template<>
+string BoxedValue::ValuePrinter<string>(const void* value) {
+  const string* val = reinterpret_cast<const string*>(value);
+  return *val;
+}
+
+template<>
+string BoxedValue::ValuePrinter<int>(const void* value) {
+  const int* val = reinterpret_cast<const int*>(value);
+  return base::IntToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<unsigned int>(const void* value) {
+  const unsigned int* val = reinterpret_cast<const unsigned int*>(value);
+  return base::UintToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<int64_t>(const void* value) {
+  const int64_t* val = reinterpret_cast<const int64_t*>(value);
+  return base::Int64ToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<uint64_t>(const void* value) {
+  const uint64_t* val =
+    reinterpret_cast<const uint64_t*>(value);
+  return base::Uint64ToString(static_cast<uint64_t>(*val));
+}
+
+template<>
+string BoxedValue::ValuePrinter<bool>(const void* value) {
+  const bool* val = reinterpret_cast<const bool*>(value);
+  return *val ? "true" : "false";
+}
+
+template<>
+string BoxedValue::ValuePrinter<double>(const void* value) {
+  const double* val = reinterpret_cast<const double*>(value);
+  return base::DoubleToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<base::Time>(const void* value) {
+  const base::Time* val = reinterpret_cast<const base::Time*>(value);
+  return chromeos_update_engine::utils::ToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<base::TimeDelta>(const void* value) {
+  const base::TimeDelta* val = reinterpret_cast<const base::TimeDelta*>(value);
+  return chromeos_update_engine::utils::FormatTimeDelta(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<ConnectionType>(const void* value) {
+  const ConnectionType* val = reinterpret_cast<const ConnectionType*>(value);
+  return StringForConnectionType(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<set<ConnectionType>>(const void* value) {
+  string ret = "";
+  const set<ConnectionType>* val =
+      reinterpret_cast<const set<ConnectionType>*>(value);
+  for (auto& it : *val) {
+    ConnectionType type = it;
+    if (ret.size() > 0)
+      ret += ",";
+    ret += StringForConnectionType(type);
+  }
+  return ret;
+}
+
+template<>
+string BoxedValue::ValuePrinter<ConnectionTethering>(const void* value) {
+  const ConnectionTethering* val =
+      reinterpret_cast<const ConnectionTethering*>(value);
+  switch (*val) {
+    case ConnectionTethering::kNotDetected:
+      return "Not Detected";
+    case ConnectionTethering::kSuspected:
+      return "Suspected";
+    case ConnectionTethering::kConfirmed:
+      return "Confirmed";
+    case ConnectionTethering::kUnknown:
+      return "Unknown";
+  }
+  NOTREACHED();
+  return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<Stage>(const void* value) {
+  const Stage* val = reinterpret_cast<const Stage*>(value);
+  switch (*val) {
+    case Stage::kIdle:
+      return "Idle";
+    case Stage::kCheckingForUpdate:
+      return "Checking For Update";
+    case Stage::kUpdateAvailable:
+      return "Update Available";
+    case Stage::kDownloading:
+      return "Downloading";
+    case Stage::kVerifying:
+      return "Verifying";
+    case Stage::kFinalizing:
+      return "Finalizing";
+    case Stage::kUpdatedNeedReboot:
+      return "Updated, Need Reboot";
+    case Stage::kReportingErrorEvent:
+      return "Reporting Error Event";
+    case Stage::kAttemptingRollback:
+      return "Attempting Rollback";
+  }
+  NOTREACHED();
+  return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<UpdateRequestStatus>(const void* value) {
+  const UpdateRequestStatus* val =
+      reinterpret_cast<const UpdateRequestStatus*>(value);
+  switch (*val) {
+    case UpdateRequestStatus::kNone:
+      return "None";
+    case UpdateRequestStatus::kInteractive:
+      return "Interactive";
+    case UpdateRequestStatus::kPeriodic:
+      return "Periodic";
+  }
+  NOTREACHED();
+  return "Unknown";
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/boxed_value.h b/update_engine/update_manager/boxed_value.h
new file mode 100644
index 0000000..5f41835
--- /dev/null
+++ b/update_engine/update_manager/boxed_value.h
@@ -0,0 +1,124 @@
+//
+// 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_UPDATE_MANAGER_BOXED_VALUE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_BOXED_VALUE_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+
+namespace chromeos_update_manager {
+
+// BoxedValue is a class to hold pointers of a given type that deletes them when
+// the instance goes out of scope, as std::unique_ptr<T> does. The main
+// difference with it is that the type T is not part of the class, i.e., this
+// isn't a parametric class. The class has a parametric constructor that accepts
+// a const T* which will define the type of the object passed on delete.
+//
+// It is safe to use this class in linked containers such as std::list and
+// std::map but the object can't be copied. This means that you need to
+// construct the BoxedValue in place using a container method like emplace()
+// or move it with std::move().
+//
+//   list<BoxedValue> lst;
+//   lst.emplace_back(new const int(42));
+//   lst.emplace_back(new const string("Hello world!"));
+//
+//   map<int, BoxedValue> m;
+//   m.emplace(123, std::move(BoxedValue(new const string("Hola mundo!"))));
+//
+//   auto it = m.find(42);
+//   if (it != m.end())
+//     cout << "m[42] points to " << it->second.value() << endl;
+//   cout << "m[33] points to " << m[33].value() << endl;
+//
+// Since copy and assign are not allowed, you can't create a copy of the
+// BoxedValue which means that you can only use a reference to it.
+//
+
+class BoxedValue {
+ public:
+  // Creates an empty BoxedValue. Since the pointer can't be assigned from other
+  // BoxedValues or pointers, this is only useful in places where a default
+  // constructor is required, such as std::map::operator[].
+  BoxedValue() : value_(nullptr), deleter_(nullptr), printer_(nullptr) {}
+
+  // Creates a BoxedValue for the passed pointer |value|. The BoxedValue keeps
+  // the ownership of this pointer and can't be released.
+  template<typename T>
+  explicit BoxedValue(const T* value)
+    : value_(static_cast<const void*>(value)), deleter_(ValueDeleter<T>),
+      printer_(ValuePrinter<T>) {}
+
+  // The move constructor takes ownership of the pointer since the semantics of
+  // it allows to render the passed BoxedValue undefined. You need to use the
+  // move constructor explicitly preventing it from accidental references,
+  // like in:
+  //   BoxedValue new_box(std::move(other_box));
+  BoxedValue(BoxedValue&& other)  // NOLINT(build/c++11)
+      : value_(other.value_), deleter_(other.deleter_),
+        printer_(other.printer_) {
+    other.value_ = nullptr;
+    other.deleter_ = nullptr;
+    other.printer_ = nullptr;
+  }
+
+  // Deletes the |value| passed on construction using the delete for the passed
+  // type.
+  ~BoxedValue() {
+    if (deleter_)
+      deleter_(value_);
+  }
+
+  const void* value() const { return value_; }
+
+  std::string ToString() const {
+    if (!printer_)
+      return "(no printer)";
+    if (!value_)
+      return "(no value)";
+    return printer_(value_);
+  }
+
+  // Static method to call the destructor of the right type.
+  template<typename T>
+  static void ValueDeleter(const void* value) {
+    delete reinterpret_cast<const T*>(value);
+  }
+
+  // Static method to print a type. See boxed_value.cc for common
+  // instantiations.
+  template<typename T>
+  static std::string ValuePrinter(const void* value);
+
+ private:
+  // A pointer to the cached value.
+  const void* value_;
+
+  // A function that calls delete for the right type of value_.
+  void (*deleter_)(const void*);
+
+  // A function that converts value_ to a string.
+  std::string (*printer_)(const void*);
+
+  DISALLOW_COPY_AND_ASSIGN(BoxedValue);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_BOXED_VALUE_H_
diff --git a/update_engine/update_manager/boxed_value_unittest.cc b/update_engine/update_manager/boxed_value_unittest.cc
new file mode 100644
index 0000000..2a086a6
--- /dev/null
+++ b/update_engine/update_manager/boxed_value_unittest.cc
@@ -0,0 +1,234 @@
+//
+// 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/update_manager/boxed_value.h"
+
+#include <gtest/gtest.h>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/umtest_utils.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ConnectionTethering;
+using chromeos_update_engine::ConnectionType;
+using std::list;
+using std::map;
+using std::set;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// The DeleterMarker flags a bool variable when the class is destroyed.
+class DeleterMarker {
+ public:
+  explicit DeleterMarker(bool* marker) : marker_(marker) { *marker_ = false; }
+
+  ~DeleterMarker() { *marker_ = true; }
+
+ private:
+  friend string BoxedValue::ValuePrinter<DeleterMarker>(const void *);
+
+  // Pointer to the bool marker.
+  bool* marker_;
+};
+
+template<>
+string BoxedValue::ValuePrinter<DeleterMarker>(const void *value) {
+  const DeleterMarker* val = reinterpret_cast<const DeleterMarker*>(value);
+  return base::StringPrintf("DeleterMarker:%s",
+                            *val->marker_ ? "true" : "false");
+}
+
+TEST(UmBoxedValueTest, Deleted) {
+  bool marker = true;
+  const DeleterMarker* deleter_marker = new DeleterMarker(&marker);
+
+  EXPECT_FALSE(marker);
+  BoxedValue* box = new BoxedValue(deleter_marker);
+  EXPECT_FALSE(marker);
+  delete box;
+  EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MoveConstructor) {
+  bool marker = true;
+  const DeleterMarker* deleter_marker = new DeleterMarker(&marker);
+
+  BoxedValue* box = new BoxedValue(deleter_marker);
+  BoxedValue* new_box = new BoxedValue(std::move(*box));
+  // box is now undefined but valid.
+  delete box;
+  EXPECT_FALSE(marker);
+  // The deleter_marker gets deleted at this point.
+  delete new_box;
+  EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MixedList) {
+  list<BoxedValue> lst;
+  // This is mostly a compile test.
+  lst.emplace_back(new const int{42});
+  lst.emplace_back(new const string("Hello world!"));
+  bool marker;
+  lst.emplace_back(new const DeleterMarker(&marker));
+  EXPECT_FALSE(marker);
+  lst.clear();
+  EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MixedMap) {
+  map<int, BoxedValue> m;
+  m.emplace(42, BoxedValue(new const string("Hola mundo!")));
+
+  auto it = m.find(42);
+  ASSERT_NE(it, m.end());
+  EXPECT_NE(nullptr, it->second.value());
+  EXPECT_EQ(nullptr, m[33].value());
+}
+
+TEST(UmBoxedValueTest, StringToString) {
+  EXPECT_EQ("Hej Verden!",
+            BoxedValue(new string("Hej Verden!")).ToString());
+}
+
+TEST(UmBoxedValueTest, IntToString) {
+  EXPECT_EQ("42", BoxedValue(new int(42)).ToString());
+}
+
+TEST(UmBoxedValueTest, Int64ToString) {
+  // -123456789012345 doesn't fit in 32-bit integers.
+  EXPECT_EQ("-123456789012345", BoxedValue(
+      new int64_t(-123456789012345LL)).ToString());
+}
+
+TEST(UmBoxedValueTest, UnsignedIntToString) {
+  // 4294967295 is the biggest possible 32-bit unsigned integer.
+  EXPECT_EQ("4294967295",
+            BoxedValue(new unsigned int(4294967295U)).ToString());  // NOLINT
+}
+
+TEST(UmBoxedValueTest, UnsignedInt64ToString) {
+  // 18446744073709551615 is the biggest possible 64-bit unsigned integer.
+  EXPECT_EQ("18446744073709551615", BoxedValue(
+      new uint64_t(18446744073709551615ULL)).ToString());
+}
+
+TEST(UmBoxedValueTest, BoolToString) {
+  EXPECT_EQ("false", BoxedValue(new bool(false)).ToString());
+  EXPECT_EQ("true", BoxedValue(new bool(true)).ToString());
+}
+
+TEST(UmBoxedValueTest, DoubleToString) {
+  EXPECT_EQ("1.501", BoxedValue(new double(1.501)).ToString());
+}
+
+TEST(UmBoxedValueTest, TimeToString) {
+  // Tue Apr 29 22:30:55 UTC 2014 is 1398810655 seconds since the Unix Epoch.
+  EXPECT_EQ("4/29/2014 22:30:55 GMT",
+            BoxedValue(new Time(Time::FromTimeT(1398810655))).ToString());
+}
+
+TEST(UmBoxedValueTest, TimeDeltaToString) {
+  // 12345 seconds is 3 hours, 25 minutes and 45 seconds.
+  EXPECT_EQ("3h25m45s",
+            BoxedValue(new TimeDelta(TimeDelta::FromSeconds(12345)))
+            .ToString());
+}
+
+TEST(UmBoxedValueTest, ConnectionTypeToString) {
+  EXPECT_EQ("ethernet",
+            BoxedValue(new ConnectionType(ConnectionType::kEthernet))
+            .ToString());
+  EXPECT_EQ("wifi",
+            BoxedValue(new ConnectionType(ConnectionType::kWifi)).ToString());
+  EXPECT_EQ("wimax",
+            BoxedValue(new ConnectionType(ConnectionType::kWimax)).ToString());
+  EXPECT_EQ("bluetooth",
+            BoxedValue(new ConnectionType(ConnectionType::kBluetooth))
+            .ToString());
+  EXPECT_EQ("cellular",
+            BoxedValue(new ConnectionType(ConnectionType::kCellular))
+            .ToString());
+  EXPECT_EQ("Unknown",
+            BoxedValue(new ConnectionType(ConnectionType::kUnknown))
+            .ToString());
+}
+
+TEST(UmBoxedValueTest, ConnectionTetheringToString) {
+  EXPECT_EQ("Not Detected",
+            BoxedValue(new ConnectionTethering(
+                ConnectionTethering::kNotDetected)).ToString());
+  EXPECT_EQ("Suspected",
+            BoxedValue(new ConnectionTethering(ConnectionTethering::kSuspected))
+            .ToString());
+  EXPECT_EQ("Confirmed",
+            BoxedValue(new ConnectionTethering(ConnectionTethering::kConfirmed))
+            .ToString());
+  EXPECT_EQ("Unknown",
+            BoxedValue(new ConnectionTethering(ConnectionTethering::kUnknown))
+            .ToString());
+}
+
+TEST(UmBoxedValueTest, SetConnectionTypeToString) {
+  set<ConnectionType>* set1 = new set<ConnectionType>;
+  set1->insert(ConnectionType::kWimax);
+  set1->insert(ConnectionType::kEthernet);
+  EXPECT_EQ("ethernet,wimax", BoxedValue(set1).ToString());
+
+  set<ConnectionType>* set2 = new set<ConnectionType>;
+  set2->insert(ConnectionType::kWifi);
+  EXPECT_EQ("wifi", BoxedValue(set2).ToString());
+}
+
+TEST(UmBoxedValueTest, StageToString) {
+  EXPECT_EQ("Idle",
+            BoxedValue(new Stage(Stage::kIdle)).ToString());
+  EXPECT_EQ("Checking For Update",
+            BoxedValue(new Stage(Stage::kCheckingForUpdate)).ToString());
+  EXPECT_EQ("Update Available",
+            BoxedValue(new Stage(Stage::kUpdateAvailable)).ToString());
+  EXPECT_EQ("Downloading",
+            BoxedValue(new Stage(Stage::kDownloading)).ToString());
+  EXPECT_EQ("Verifying",
+            BoxedValue(new Stage(Stage::kVerifying)).ToString());
+  EXPECT_EQ("Finalizing",
+            BoxedValue(new Stage(Stage::kFinalizing)).ToString());
+  EXPECT_EQ("Updated, Need Reboot",
+            BoxedValue(new Stage(Stage::kUpdatedNeedReboot)).ToString());
+  EXPECT_EQ("Reporting Error Event",
+            BoxedValue(new Stage(Stage::kReportingErrorEvent)).ToString());
+  EXPECT_EQ("Attempting Rollback",
+            BoxedValue(new Stage(Stage::kAttemptingRollback)).ToString());
+}
+
+TEST(UmBoxedValueTest, DeleterMarkerToString) {
+  bool marker = false;
+  BoxedValue value = BoxedValue(new DeleterMarker(&marker));
+  EXPECT_EQ("DeleterMarker:false", value.ToString());
+  marker = true;
+  EXPECT_EQ("DeleterMarker:true", value.ToString());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/chromeos_policy.cc b/update_engine/update_manager/chromeos_policy.cc
new file mode 100644
index 0000000..ec2b9f0
--- /dev/null
+++ b/update_engine/update_manager/chromeos_policy.cc
@@ -0,0 +1,979 @@
+//
+// 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/update_manager/chromeos_policy.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/error_code_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/policy_utils.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ConnectionTethering;
+using chromeos_update_engine::ConnectionType;
+using chromeos_update_engine::ErrorCode;
+using std::get;
+using std::max;
+using std::min;
+using std::set;
+using std::string;
+
+namespace {
+
+// Examines |err_code| and decides whether the URL index needs to be advanced,
+// the error count for the URL incremented, or none of the above. In the first
+// case, returns true; in the second case, increments |*url_num_error_p| and
+// returns false; otherwise just returns false.
+//
+// TODO(garnold) Adapted from PayloadState::UpdateFailed() (to be retired).
+bool HandleErrorCode(ErrorCode err_code, int* url_num_error_p) {
+  err_code = chromeos_update_engine::utils::GetBaseErrorCode(err_code);
+  switch (err_code) {
+    // 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:
+      LOG(INFO) << "Advancing download URL due to error "
+                << chromeos_update_engine::utils::ErrorCodeToString(err_code)
+                << " (" << static_cast<int>(err_code) << ")";
+      return true;
+
+    // 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
+    // an 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.
+      LOG(INFO) << "Incrementing URL failure count due to error "
+                << chromeos_update_engine::utils::ErrorCodeToString(err_code)
+                << " (" << static_cast<int>(err_code) << ")";
+      *url_num_error_p += 1;
+      return false;
+
+    // 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 back-off 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:
+      LOG(INFO) << "Not changing URL index or failure count due to error "
+                << chromeos_update_engine::utils::ErrorCodeToString(err_code)
+                << " (" << static_cast<int>(err_code) << ")";
+      return false;
+
+    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 "
+                   << chromeos_update_engine::utils::ErrorCodeToString(err_code)
+                   << " (" << static_cast<int>(err_code) << ")";
+    // 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.
+  }
+  return false;
+}
+
+// Checks whether |url| can be used under given download restrictions.
+bool IsUrlUsable(const string& url, bool http_allowed) {
+  return http_allowed ||
+         !base::StartsWith(url, "http://",
+                           base::CompareCase::INSENSITIVE_ASCII);
+}
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+const int ChromeOSPolicy::kTimeoutInitialInterval =  7 * 60;
+
+// TODO(deymo): Split the update_manager policies for Brillo and ChromeOS and
+// make the update check periodic interval configurable.
+#ifdef __ANDROID__
+const int ChromeOSPolicy::kTimeoutPeriodicInterval = 5 * 60 * 60;
+const int ChromeOSPolicy::kTimeoutMaxBackoffInterval = 26 * 60 * 60;
+#else
+const int ChromeOSPolicy::kTimeoutPeriodicInterval = 45 * 60;
+const int ChromeOSPolicy::kTimeoutMaxBackoffInterval = 4 * 60 * 60;
+#endif  // __ANDROID__
+
+const int ChromeOSPolicy::kTimeoutRegularFuzz = 10 * 60;
+const int ChromeOSPolicy::kAttemptBackoffMaxIntervalInDays = 16;
+const int ChromeOSPolicy::kAttemptBackoffFuzzInHours = 12;
+const int ChromeOSPolicy::kMaxP2PAttempts = 10;
+const int ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds = 5 * 24 * 60 * 60;
+
+EvalStatus ChromeOSPolicy::UpdateCheckAllowed(
+    EvaluationContext* ec, State* state, string* error,
+    UpdateCheckParams* result) const {
+  // Set the default return values.
+  result->updates_enabled = true;
+  result->target_channel.clear();
+  result->target_version_prefix.clear();
+  result->is_interactive = false;
+
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+  UpdaterProvider* const updater_provider = state->updater_provider();
+  SystemProvider* const system_provider = state->system_provider();
+
+  // Do not perform any updates if booted from removable device. This decision
+  // is final.
+  const unsigned int* num_slots_p = ec->GetValue(
+      system_provider->var_num_slots());
+  if (!num_slots_p || *num_slots_p < 2) {
+    LOG(INFO) << "Not enough slots for A/B updates, disabling update checks.";
+    result->updates_enabled = false;
+    return EvalStatus::kSucceeded;
+  }
+
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+    bool kiosk_app_control_chrome_version = false;
+
+    // Check whether updates are disabled by policy.
+    const bool* update_disabled_p = ec->GetValue(
+        dp_provider->var_update_disabled());
+    if (update_disabled_p && *update_disabled_p) {
+      // Check whether allow kiosk app to control chrome version policy. This
+      // policy is only effective when AU is disabled by admin.
+      const bool* allow_kiosk_app_control_chrome_version_p = ec->GetValue(
+          dp_provider->var_allow_kiosk_app_control_chrome_version());
+      kiosk_app_control_chrome_version =
+          allow_kiosk_app_control_chrome_version_p &&
+          *allow_kiosk_app_control_chrome_version_p;
+      if (!kiosk_app_control_chrome_version) {
+        // No kiosk pin chrome version policy. AU is really disabled.
+        LOG(INFO) << "Updates disabled by policy, blocking update checks.";
+        return EvalStatus::kAskMeAgainLater;
+      }
+    }
+
+    if (kiosk_app_control_chrome_version) {
+      // Get the required platform version from Chrome.
+      const string* kiosk_required_platform_version_p =
+          ec->GetValue(system_provider->var_kiosk_required_platform_version());
+      if (!kiosk_required_platform_version_p) {
+        LOG(INFO) << "Kiosk app required platform version is not fetched, "
+                     "blocking update checks";
+        return EvalStatus::kAskMeAgainLater;
+      }
+
+      result->target_version_prefix = *kiosk_required_platform_version_p;
+      LOG(INFO) << "Allow kiosk app to control Chrome version policy is set,"
+                << ", target version is "
+                << (kiosk_required_platform_version_p
+                        ? *kiosk_required_platform_version_p
+                        : std::string("latest"));
+    } else {
+      // Determine whether a target version prefix is dictated by policy.
+      const string* target_version_prefix_p = ec->GetValue(
+          dp_provider->var_target_version_prefix());
+      if (target_version_prefix_p)
+        result->target_version_prefix = *target_version_prefix_p;
+    }
+
+    // Determine whether a target channel is dictated by policy.
+    const bool* release_channel_delegated_p = ec->GetValue(
+        dp_provider->var_release_channel_delegated());
+    if (release_channel_delegated_p && !(*release_channel_delegated_p)) {
+      const string* release_channel_p = ec->GetValue(
+          dp_provider->var_release_channel());
+      if (release_channel_p)
+        result->target_channel = *release_channel_p;
+    }
+  }
+
+  // First, check to see if an interactive update was requested.
+  const UpdateRequestStatus* forced_update_requested_p = ec->GetValue(
+      updater_provider->var_forced_update_requested());
+  if (forced_update_requested_p &&
+      *forced_update_requested_p != UpdateRequestStatus::kNone) {
+    result->is_interactive =
+        (*forced_update_requested_p == UpdateRequestStatus::kInteractive);
+    LOG(INFO) << "Forced update signaled ("
+              << (result->is_interactive ?  "interactive" : "periodic")
+              << "), allowing update check.";
+    return EvalStatus::kSucceeded;
+  }
+
+  // The logic thereafter applies to periodic updates. Bear in mind that we
+  // should not return a final "no" if any of these criteria are not satisfied,
+  // because the system may still update due to an interactive update request.
+
+  // Unofficial builds should not perform periodic update checks.
+  const bool* is_official_build_p = ec->GetValue(
+      system_provider->var_is_official_build());
+  if (is_official_build_p && !(*is_official_build_p)) {
+    LOG(INFO) << "Unofficial build, blocking periodic update checks.";
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+  // If OOBE is enabled, wait until it is completed.
+  const bool* is_oobe_enabled_p = ec->GetValue(
+      state->config_provider()->var_is_oobe_enabled());
+  if (is_oobe_enabled_p && *is_oobe_enabled_p) {
+    const bool* is_oobe_complete_p = ec->GetValue(
+        system_provider->var_is_oobe_complete());
+    if (is_oobe_complete_p && !(*is_oobe_complete_p)) {
+      LOG(INFO) << "OOBE not completed, blocking update checks.";
+      return EvalStatus::kAskMeAgainLater;
+    }
+  }
+
+  // Ensure that periodic update checks are timed properly.
+  Time next_update_check;
+  if (NextUpdateCheckTime(ec, state, error, &next_update_check) !=
+      EvalStatus::kSucceeded) {
+    return EvalStatus::kFailed;
+  }
+  if (!ec->IsWallclockTimeGreaterThan(next_update_check)) {
+    LOG(INFO) << "Periodic check interval not satisfied, blocking until "
+              << chromeos_update_engine::utils::ToString(next_update_check);
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+  // It is time to check for an update.
+  LOG(INFO) << "Allowing update check.";
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::UpdateCanStart(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    UpdateDownloadParams* result,
+    const UpdateState update_state) const {
+  // Set the default return values. Note that we set persisted values (backoff,
+  // scattering) to the same values presented in the update state. The reason is
+  // that preemptive returns, such as the case where an update check is due,
+  // should not clear off the said values; rather, it is the deliberate
+  // inference of new values that should cause them to be reset.
+  result->update_can_start = false;
+  result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+  result->download_url_idx = -1;
+  result->download_url_allowed = true;
+  result->download_url_num_errors = 0;
+  result->p2p_downloading_allowed = false;
+  result->p2p_sharing_allowed = false;
+  result->do_increment_failures = false;
+  result->backoff_expiry = update_state.backoff_expiry;
+  result->scatter_wait_period = update_state.scatter_wait_period;
+  result->scatter_check_threshold = update_state.scatter_check_threshold;
+
+  // Make sure that we're not due for an update check.
+  UpdateCheckParams check_result;
+  EvalStatus check_status = UpdateCheckAllowed(ec, state, error, &check_result);
+  if (check_status == EvalStatus::kFailed)
+    return EvalStatus::kFailed;
+  bool is_check_due = (check_status == EvalStatus::kSucceeded &&
+                       check_result.updates_enabled == true);
+
+  // Check whether backoff applies, and if not then which URL can be used for
+  // downloading. These require scanning the download error log, and so they are
+  // done together.
+  UpdateBackoffAndDownloadUrlResult backoff_url_result;
+  EvalStatus backoff_url_status = UpdateBackoffAndDownloadUrl(
+      ec, state, error, &backoff_url_result, update_state);
+  if (backoff_url_status == EvalStatus::kFailed)
+    return EvalStatus::kFailed;
+  result->download_url_idx = backoff_url_result.url_idx;
+  result->download_url_num_errors = backoff_url_result.url_num_errors;
+  result->do_increment_failures = backoff_url_result.do_increment_failures;
+  result->backoff_expiry = backoff_url_result.backoff_expiry;
+  bool is_backoff_active =
+      (backoff_url_status == EvalStatus::kAskMeAgainLater) ||
+      !backoff_url_result.backoff_expiry.is_null();
+
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+  bool is_scattering_active = false;
+  EvalStatus scattering_status = EvalStatus::kSucceeded;
+
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+    // Check whether scattering applies to this update attempt. We should not be
+    // scattering if this is an interactive update check, or if OOBE is enabled
+    // but not completed.
+    //
+    // Note: current code further suppresses scattering if a "deadline"
+    // attribute is found in the Omaha response. However, it appears that the
+    // presence of this attribute is merely indicative of an OOBE update, during
+    // which we suppress scattering anyway.
+    bool is_scattering_applicable = false;
+    result->scatter_wait_period = kZeroInterval;
+    result->scatter_check_threshold = 0;
+    if (!update_state.is_interactive) {
+      const bool* is_oobe_enabled_p = ec->GetValue(
+          state->config_provider()->var_is_oobe_enabled());
+      if (is_oobe_enabled_p && !(*is_oobe_enabled_p)) {
+        is_scattering_applicable = true;
+      } else {
+        const bool* is_oobe_complete_p = ec->GetValue(
+            state->system_provider()->var_is_oobe_complete());
+        is_scattering_applicable = (is_oobe_complete_p && *is_oobe_complete_p);
+      }
+    }
+
+    // Compute scattering values.
+    if (is_scattering_applicable) {
+      UpdateScatteringResult scatter_result;
+      scattering_status = UpdateScattering(ec, state, error, &scatter_result,
+                                           update_state);
+      if (scattering_status == EvalStatus::kFailed) {
+        return EvalStatus::kFailed;
+      } else {
+        result->scatter_wait_period = scatter_result.wait_period;
+        result->scatter_check_threshold = scatter_result.check_threshold;
+        if (scattering_status == EvalStatus::kAskMeAgainLater ||
+            scatter_result.is_scattering)
+          is_scattering_active = true;
+      }
+    }
+  }
+
+  // Find out whether P2P is globally enabled.
+  bool p2p_enabled;
+  EvalStatus p2p_enabled_status = P2PEnabled(ec, state, error, &p2p_enabled);
+  if (p2p_enabled_status != EvalStatus::kSucceeded)
+    return EvalStatus::kFailed;
+
+  // Is P2P is enabled, consider allowing it for downloading and/or sharing.
+  if (p2p_enabled) {
+    // Sharing via P2P is allowed if not disabled by Omaha.
+    if (update_state.p2p_sharing_disabled) {
+      LOG(INFO) << "Blocked P2P sharing because it is disabled by Omaha.";
+    } else {
+      result->p2p_sharing_allowed = true;
+    }
+
+    // Downloading via P2P is allowed if not disabled by Omaha, an update is not
+    // interactive, and other limits haven't been reached.
+    if (update_state.p2p_downloading_disabled) {
+      LOG(INFO) << "Blocked P2P downloading because it is disabled by Omaha.";
+    } else if (update_state.is_interactive) {
+      LOG(INFO) << "Blocked P2P downloading because update is interactive.";
+    } else if (update_state.p2p_num_attempts >= kMaxP2PAttempts) {
+      LOG(INFO) << "Blocked P2P downloading as it was attempted too many "
+                   "times.";
+    } else if (!update_state.p2p_first_attempted.is_null() &&
+               ec->IsWallclockTimeGreaterThan(
+                   update_state.p2p_first_attempted +
+                   TimeDelta::FromSeconds(kMaxP2PAttemptsPeriodInSeconds))) {
+      LOG(INFO) << "Blocked P2P downloading as its usage timespan exceeds "
+                   "limit.";
+    } else {
+      // P2P download is allowed; if backoff or scattering are active, be sure
+      // to suppress them, yet prevent any download URL from being used.
+      result->p2p_downloading_allowed = true;
+      if (is_backoff_active || is_scattering_active) {
+        is_backoff_active = is_scattering_active = false;
+        result->download_url_allowed = false;
+      }
+    }
+  }
+
+  // Check for various deterrents.
+  if (is_check_due) {
+    result->cannot_start_reason = UpdateCannotStartReason::kCheckDue;
+    return EvalStatus::kSucceeded;
+  }
+  if (is_backoff_active) {
+    result->cannot_start_reason = UpdateCannotStartReason::kBackoff;
+    return backoff_url_status;
+  }
+  if (is_scattering_active) {
+    result->cannot_start_reason = UpdateCannotStartReason::kScattering;
+    return scattering_status;
+  }
+  if (result->download_url_idx < 0 && !result->p2p_downloading_allowed) {
+    result->cannot_start_reason = UpdateCannotStartReason::kCannotDownload;
+    return EvalStatus::kSucceeded;
+  }
+
+  // Update is good to go.
+  result->update_can_start = true;
+  return EvalStatus::kSucceeded;
+}
+
+// TODO(garnold) Logic in this method is based on
+// ConnectionManager::IsUpdateAllowedOver(); be sure to deprecate the latter.
+//
+// TODO(garnold) The current logic generally treats the list of allowed
+// connections coming from the device policy as a whitelist, meaning that it
+// can only be used for enabling connections, but not disable them. Further,
+// certain connection types (like Bluetooth) cannot be enabled even by policy.
+// In effect, the only thing that device policy can change is to enable
+// updates over a cellular network (disabled by default). We may want to
+// revisit this semantics, allowing greater flexibility in defining specific
+// permissions over all types of networks.
+EvalStatus ChromeOSPolicy::UpdateDownloadAllowed(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    bool* result) const {
+  // Get the current connection type.
+  ShillProvider* const shill_provider = state->shill_provider();
+  const ConnectionType* conn_type_p = ec->GetValue(
+      shill_provider->var_conn_type());
+  POLICY_CHECK_VALUE_AND_FAIL(conn_type_p, error);
+  ConnectionType conn_type = *conn_type_p;
+
+  // If we're tethering, treat it as a cellular connection.
+  if (conn_type != ConnectionType::kCellular) {
+    const ConnectionTethering* conn_tethering_p = ec->GetValue(
+        shill_provider->var_conn_tethering());
+    POLICY_CHECK_VALUE_AND_FAIL(conn_tethering_p, error);
+    if (*conn_tethering_p == ConnectionTethering::kConfirmed)
+      conn_type = ConnectionType::kCellular;
+  }
+
+  // By default, we allow updates for all connection types, with exceptions as
+  // noted below. This also determines whether a device policy can override the
+  // default.
+  *result = true;
+  bool device_policy_can_override = false;
+  switch (conn_type) {
+    case ConnectionType::kBluetooth:
+      *result = false;
+      break;
+
+    case ConnectionType::kCellular:
+      *result = false;
+      device_policy_can_override = true;
+      break;
+
+    case ConnectionType::kUnknown:
+      if (error)
+        *error = "Unknown connection type";
+      return EvalStatus::kFailed;
+
+    default:
+      break;  // Nothing to do.
+  }
+
+  // If update is allowed, we're done.
+  if (*result)
+    return EvalStatus::kSucceeded;
+
+  // Check whether the device policy specifically allows this connection.
+  if (device_policy_can_override) {
+    DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+    const bool* device_policy_is_loaded_p = ec->GetValue(
+        dp_provider->var_device_policy_is_loaded());
+    if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+      const set<ConnectionType>* allowed_conn_types_p = ec->GetValue(
+          dp_provider->var_allowed_connection_types_for_update());
+      if (allowed_conn_types_p) {
+        if (allowed_conn_types_p->count(conn_type)) {
+          *result = true;
+          return EvalStatus::kSucceeded;
+        }
+      } else if (conn_type == ConnectionType::kCellular) {
+        // Local user settings can allow updates over cellular iff a policy was
+        // loaded but no allowed connections were specified in it.
+        const bool* update_over_cellular_allowed_p = ec->GetValue(
+            state->updater_provider()->var_cellular_enabled());
+        if (update_over_cellular_allowed_p && *update_over_cellular_allowed_p)
+          *result = true;
+      }
+    }
+  }
+
+  return (*result ? EvalStatus::kSucceeded : EvalStatus::kAskMeAgainLater);
+}
+
+EvalStatus ChromeOSPolicy::P2PEnabled(EvaluationContext* ec,
+                                      State* state,
+                                      string* error,
+                                      bool* result) const {
+  bool enabled = false;
+
+  // Determine whether use of P2P is allowed by policy. Even if P2P is not
+  // explicitly allowed, we allow it if the device is enterprise enrolled (that
+  // is, missing or empty owner string).
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+    const bool* policy_au_p2p_enabled_p = ec->GetValue(
+        dp_provider->var_au_p2p_enabled());
+    if (policy_au_p2p_enabled_p) {
+      enabled = *policy_au_p2p_enabled_p;
+    } else {
+      const string* policy_owner_p = ec->GetValue(dp_provider->var_owner());
+      if (!policy_owner_p || policy_owner_p->empty())
+        enabled = true;
+    }
+  }
+
+  // Enable P2P, if so mandated by the updater configuration. This is additive
+  // to whether or not P2P is enabled by device policy.
+  if (!enabled) {
+    const bool* updater_p2p_enabled_p = ec->GetValue(
+        state->updater_provider()->var_p2p_enabled());
+    enabled = updater_p2p_enabled_p && *updater_p2p_enabled_p;
+  }
+
+  *result = enabled;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::P2PEnabledChanged(EvaluationContext* ec,
+                                             State* state,
+                                             string* error,
+                                             bool* result,
+                                             bool prev_result) const {
+  EvalStatus status = P2PEnabled(ec, state, error, result);
+  if (status == EvalStatus::kSucceeded && *result == prev_result)
+    return EvalStatus::kAskMeAgainLater;
+  return status;
+}
+
+EvalStatus ChromeOSPolicy::NextUpdateCheckTime(EvaluationContext* ec,
+                                               State* state, string* error,
+                                               Time* next_update_check) const {
+  UpdaterProvider* const updater_provider = state->updater_provider();
+
+  // Don't check for updates too often. We limit the update checks to once every
+  // some interval. The interval is kTimeoutInitialInterval the first time and
+  // kTimeoutPeriodicInterval for the subsequent update checks. If the update
+  // check fails, we increase the interval between the update checks
+  // exponentially until kTimeoutMaxBackoffInterval. Finally, to avoid having
+  // many chromebooks running update checks at the exact same time, we add some
+  // fuzz to the interval.
+  const Time* updater_started_time =
+      ec->GetValue(updater_provider->var_updater_started_time());
+  POLICY_CHECK_VALUE_AND_FAIL(updater_started_time, error);
+
+  const Time* last_checked_time =
+      ec->GetValue(updater_provider->var_last_checked_time());
+
+  const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+  POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+
+  PRNG prng(*seed);
+
+  // If this is the first attempt, compute and return an initial value.
+  if (!last_checked_time || *last_checked_time < *updater_started_time) {
+    *next_update_check = *updater_started_time + FuzzedInterval(
+        &prng, kTimeoutInitialInterval, kTimeoutRegularFuzz);
+    return EvalStatus::kSucceeded;
+  }
+
+  // Check whether the server is enforcing a poll interval; if not, this value
+  // will be zero.
+  const unsigned int* server_dictated_poll_interval = ec->GetValue(
+      updater_provider->var_server_dictated_poll_interval());
+  POLICY_CHECK_VALUE_AND_FAIL(server_dictated_poll_interval, error);
+
+  int interval = *server_dictated_poll_interval;
+  int fuzz = 0;
+
+  // If no poll interval was dictated by server compute a back-off period,
+  // starting from a predetermined base periodic interval and increasing
+  // exponentially by the number of consecutive failed attempts.
+  if (interval == 0) {
+    const unsigned int* consecutive_failed_update_checks = ec->GetValue(
+        updater_provider->var_consecutive_failed_update_checks());
+    POLICY_CHECK_VALUE_AND_FAIL(consecutive_failed_update_checks, error);
+
+    interval = kTimeoutPeriodicInterval;
+    unsigned int num_failures = *consecutive_failed_update_checks;
+    while (interval < kTimeoutMaxBackoffInterval && num_failures) {
+      interval *= 2;
+      num_failures--;
+    }
+  }
+
+  // We cannot back off longer than the predetermined maximum interval.
+  if (interval > kTimeoutMaxBackoffInterval)
+    interval = kTimeoutMaxBackoffInterval;
+
+  // We cannot back off shorter than the predetermined periodic interval. Also,
+  // in this case set the fuzz to a predetermined regular value.
+  if (interval <= kTimeoutPeriodicInterval) {
+    interval = kTimeoutPeriodicInterval;
+    fuzz = kTimeoutRegularFuzz;
+  }
+
+  // If not otherwise determined, defer to a fuzz of +/-(interval / 2).
+  if (fuzz == 0)
+    fuzz = interval;
+
+  *next_update_check = *last_checked_time + FuzzedInterval(
+      &prng, interval, fuzz);
+  return EvalStatus::kSucceeded;
+}
+
+TimeDelta ChromeOSPolicy::FuzzedInterval(PRNG* prng, int interval, int fuzz) {
+  DCHECK_GE(interval, 0);
+  DCHECK_GE(fuzz, 0);
+  int half_fuzz = fuzz / 2;
+  // This guarantees the output interval is non negative.
+  int interval_min = max(interval - half_fuzz, 0);
+  int interval_max = interval + half_fuzz;
+  return TimeDelta::FromSeconds(prng->RandMinMax(interval_min, interval_max));
+}
+
+EvalStatus ChromeOSPolicy::UpdateBackoffAndDownloadUrl(
+    EvaluationContext* ec, State* state, string* error,
+    UpdateBackoffAndDownloadUrlResult* result,
+    const UpdateState& update_state) const {
+  // Sanity checks.
+  DCHECK_GE(update_state.download_errors_max, 0);
+
+  // Set default result values.
+  result->do_increment_failures = false;
+  result->backoff_expiry = update_state.backoff_expiry;
+  result->url_idx = -1;
+  result->url_num_errors = 0;
+
+  const bool* is_official_build_p = ec->GetValue(
+      state->system_provider()->var_is_official_build());
+  bool is_official_build = (is_official_build_p ? *is_official_build_p : true);
+
+  // Check whether backoff is enabled.
+  bool may_backoff = false;
+  if (update_state.is_backoff_disabled) {
+    LOG(INFO) << "Backoff disabled by Omaha.";
+  } else if (update_state.is_interactive) {
+    LOG(INFO) << "No backoff for interactive updates.";
+  } else if (update_state.is_delta_payload) {
+    LOG(INFO) << "No backoff for delta payloads.";
+  } else if (!is_official_build) {
+    LOG(INFO) << "No backoff for unofficial builds.";
+  } else {
+    may_backoff = true;
+  }
+
+  // If previous backoff still in effect, block.
+  if (may_backoff && !update_state.backoff_expiry.is_null() &&
+      !ec->IsWallclockTimeGreaterThan(update_state.backoff_expiry)) {
+    LOG(INFO) << "Previous backoff has not expired, waiting.";
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+  // Determine whether HTTP downloads are forbidden by policy. This only
+  // applies to official system builds; otherwise, HTTP is always enabled.
+  bool http_allowed = true;
+  if (is_official_build) {
+    DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+    const bool* device_policy_is_loaded_p = ec->GetValue(
+        dp_provider->var_device_policy_is_loaded());
+    if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+      const bool* policy_http_downloads_enabled_p = ec->GetValue(
+          dp_provider->var_http_downloads_enabled());
+      http_allowed = (!policy_http_downloads_enabled_p ||
+                      *policy_http_downloads_enabled_p);
+    }
+  }
+
+  int url_idx = update_state.last_download_url_idx;
+  if (url_idx < 0)
+    url_idx = -1;
+  bool do_advance_url = false;
+  bool is_failure_occurred = false;
+  Time err_time;
+
+  // Scan the relevant part of the download error log, tracking which URLs are
+  // being used, and accounting the number of errors for each URL. Note that
+  // this process may not traverse all errors provided, as it may decide to bail
+  // out midway depending on the particular errors exhibited, the number of
+  // failures allowed, etc. When this ends, |url_idx| will point to the last URL
+  // used (-1 if starting fresh), |do_advance_url| will determine whether the
+  // URL needs to be advanced, and |err_time| the point in time when the last
+  // reported error occurred.  Additionally, if the error log indicates that an
+  // update attempt has failed (abnormal), then |is_failure_occurred| will be
+  // set to true.
+  const int num_urls = update_state.download_urls.size();
+  int prev_url_idx = -1;
+  int url_num_errors = update_state.last_download_url_num_errors;
+  Time prev_err_time;
+  bool is_first = true;
+  for (const auto& err_tuple : update_state.download_errors) {
+    // Do some sanity checks.
+    int used_url_idx = get<0>(err_tuple);
+    if (is_first && url_idx >= 0 && used_url_idx != url_idx) {
+      LOG(WARNING) << "First URL in error log (" << used_url_idx
+                   << ") not as expected (" << url_idx << ")";
+    }
+    is_first = false;
+    url_idx = used_url_idx;
+    if (url_idx < 0 || url_idx >= num_urls) {
+      LOG(ERROR) << "Download error log contains an invalid URL index ("
+                 << url_idx << ")";
+      return EvalStatus::kFailed;
+    }
+    err_time = get<2>(err_tuple);
+    if (!(prev_err_time.is_null() || err_time >= prev_err_time)) {
+      // TODO(garnold) Monotonicity cannot really be assumed when dealing with
+      // wallclock-based timestamps. However, we're making a simplifying
+      // assumption so as to keep the policy implementation straightforward, for
+      // now. In general, we should convert all timestamp handling in the
+      // UpdateManager to use monotonic time (instead of wallclock), including
+      // the computation of various expiration times (backoff, scattering, etc).
+      // The client will do whatever conversions necessary when
+      // persisting/retrieving these values across reboots. See chromium:408794.
+      LOG(ERROR) << "Download error timestamps not monotonically increasing.";
+      return EvalStatus::kFailed;
+    }
+    prev_err_time = err_time;
+
+    // Ignore errors that happened before the last known failed attempt.
+    if (!update_state.failures_last_updated.is_null() &&
+        err_time <= update_state.failures_last_updated)
+      continue;
+
+    if (prev_url_idx >= 0) {
+      if (url_idx < prev_url_idx) {
+        LOG(ERROR) << "The URLs in the download error log have wrapped around ("
+                   << prev_url_idx << "->" << url_idx
+                   << "). This should not have happened and means that there's "
+                      "a bug. To be conservative, we record a failed attempt "
+                      "(invalidating the rest of the error log) and resume "
+                      "download from the first usable URL.";
+        url_idx = -1;
+        is_failure_occurred = true;
+        break;
+      }
+
+      if (url_idx > prev_url_idx) {
+        url_num_errors = 0;
+        do_advance_url = false;
+      }
+    }
+
+    if (HandleErrorCode(get<1>(err_tuple), &url_num_errors) ||
+        url_num_errors > update_state.download_errors_max)
+      do_advance_url = true;
+
+    prev_url_idx = url_idx;
+  }
+
+  // If required, advance to the next usable URL. If the URLs wraparound, we
+  // mark an update attempt failure. Also be sure to set the download error
+  // count to zero.
+  if (url_idx < 0 || do_advance_url) {
+    url_num_errors = 0;
+    int start_url_idx = -1;
+    do {
+      if (++url_idx == num_urls) {
+        url_idx = 0;
+        // We only mark failure if an actual advancing of a URL was required.
+        if (do_advance_url)
+          is_failure_occurred = true;
+      }
+
+      if (start_url_idx < 0)
+        start_url_idx = url_idx;
+      else if (url_idx == start_url_idx)
+        url_idx = -1;  // No usable URL.
+    } while (url_idx >= 0 &&
+             !IsUrlUsable(update_state.download_urls[url_idx], http_allowed));
+  }
+
+  // If we have a download URL but a failure was observed, compute a new backoff
+  // expiry (if allowed). The backoff period is generally 2 ^ (num_failures - 1)
+  // days, bounded by the size of int and kAttemptBackoffMaxIntervalInDays, and
+  // fuzzed by kAttemptBackoffFuzzInHours hours. Backoff expiry is computed from
+  // the latest recorded time of error.
+  Time backoff_expiry;
+  if (url_idx >= 0 && is_failure_occurred && may_backoff) {
+    CHECK(!err_time.is_null())
+        << "We must have an error timestamp if a failure occurred!";
+    const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+    POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+    PRNG prng(*seed);
+    int exp = min(update_state.num_failures,
+                       static_cast<int>(sizeof(int)) * 8 - 2);
+    TimeDelta backoff_interval = TimeDelta::FromDays(
+        min(1 << exp, kAttemptBackoffMaxIntervalInDays));
+    TimeDelta backoff_fuzz = TimeDelta::FromHours(kAttemptBackoffFuzzInHours);
+    TimeDelta wait_period = FuzzedInterval(&prng, backoff_interval.InSeconds(),
+                                           backoff_fuzz.InSeconds());
+    backoff_expiry = err_time + wait_period;
+
+    // If the newly computed backoff already expired, nullify it.
+    if (ec->IsWallclockTimeGreaterThan(backoff_expiry))
+      backoff_expiry = Time();
+  }
+
+  result->do_increment_failures = is_failure_occurred;
+  result->backoff_expiry = backoff_expiry;
+  result->url_idx = url_idx;
+  result->url_num_errors = url_num_errors;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::UpdateScattering(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    UpdateScatteringResult* result,
+    const UpdateState& update_state) const {
+  // Preconditions. These stem from the postconditions and usage contract.
+  DCHECK(update_state.scatter_wait_period >= kZeroInterval);
+  DCHECK_GE(update_state.scatter_check_threshold, 0);
+
+  // Set default result values.
+  result->is_scattering = false;
+  result->wait_period = kZeroInterval;
+  result->check_threshold = 0;
+
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+
+  // Ensure that a device policy is loaded.
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (!(device_policy_is_loaded_p && *device_policy_is_loaded_p))
+    return EvalStatus::kSucceeded;
+
+  // Is scattering enabled by policy?
+  const TimeDelta* scatter_factor_p = ec->GetValue(
+      dp_provider->var_scatter_factor());
+  if (!scatter_factor_p || *scatter_factor_p == kZeroInterval)
+    return EvalStatus::kSucceeded;
+
+  // Obtain a pseudo-random number generator.
+  const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+  POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+  PRNG prng(*seed);
+
+  // Step 1: Maintain the scattering wait period.
+  //
+  // If no wait period was previously determined, or it no longer fits in the
+  // scatter factor, then generate a new one. Otherwise, keep the one we have.
+  TimeDelta wait_period = update_state.scatter_wait_period;
+  if (wait_period == kZeroInterval || wait_period > *scatter_factor_p) {
+    wait_period = TimeDelta::FromSeconds(
+        prng.RandMinMax(1, scatter_factor_p->InSeconds()));
+  }
+
+  // If we surpassed the wait period or the max scatter period associated with
+  // the update, then no wait is needed.
+  Time wait_expires = (update_state.first_seen +
+                       min(wait_period, update_state.scatter_wait_period_max));
+  if (ec->IsWallclockTimeGreaterThan(wait_expires))
+    wait_period = kZeroInterval;
+
+  // Step 2: Maintain the update check threshold count.
+  //
+  // If an update check threshold is not specified then generate a new
+  // one.
+  int check_threshold = update_state.scatter_check_threshold;
+  if (check_threshold == 0) {
+    check_threshold = prng.RandMinMax(
+        update_state.scatter_check_threshold_min,
+        update_state.scatter_check_threshold_max);
+  }
+
+  // If the update check threshold is not within allowed range then nullify it.
+  // TODO(garnold) This is compliant with current logic found in
+  // OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied(). We may want
+  // to change it so that it behaves similarly to the wait period case, namely
+  // if the current value exceeds the maximum, we set a new one within range.
+  if (check_threshold > update_state.scatter_check_threshold_max)
+    check_threshold = 0;
+
+  // If the update check threshold is non-zero and satisfied, then nullify it.
+  if (check_threshold > 0 && update_state.num_checks >= check_threshold)
+    check_threshold = 0;
+
+  bool is_scattering = (wait_period != kZeroInterval || check_threshold);
+  EvalStatus ret = EvalStatus::kSucceeded;
+  if (is_scattering && wait_period == update_state.scatter_wait_period &&
+      check_threshold == update_state.scatter_check_threshold)
+    ret = EvalStatus::kAskMeAgainLater;
+  result->is_scattering = is_scattering;
+  result->wait_period = wait_period;
+  result->check_threshold = check_threshold;
+  return ret;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/chromeos_policy.h b/update_engine/update_manager/chromeos_policy.h
new file mode 100644
index 0000000..b4370c4
--- /dev/null
+++ b/update_engine/update_manager/chromeos_policy.h
@@ -0,0 +1,203 @@
+//
+// 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_UPDATE_MANAGER_CHROMEOS_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_CHROMEOS_POLICY_H_
+
+#include <string>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/prng.h"
+
+namespace chromeos_update_manager {
+
+// Output information from UpdateBackoffAndDownloadUrl.
+struct UpdateBackoffAndDownloadUrlResult {
+  // Whether the failed attempt count (maintained by the caller) needs to be
+  // incremented.
+  bool do_increment_failures;
+  // The current backoff expiry. Null if backoff is not in effect.
+  base::Time backoff_expiry;
+  // The new URL index to use and number of download errors associated with it.
+  // Significant iff |do_increment_failures| is false and |backoff_expiry| is
+  // null. Negative value means no usable URL was found.
+  int url_idx;
+  int url_num_errors;
+};
+
+// Parameters for update scattering, as returned by UpdateScattering.
+struct UpdateScatteringResult {
+  bool is_scattering;
+  base::TimeDelta wait_period;
+  int check_threshold;
+};
+
+// ChromeOSPolicy implements the policy-related logic used in ChromeOS.
+class ChromeOSPolicy : public Policy {
+ public:
+  ChromeOSPolicy() {}
+  ~ChromeOSPolicy() override {}
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateCheckParams* result) const override;
+
+  EvalStatus UpdateCanStart(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      UpdateDownloadParams* result,
+      UpdateState update_state) const override;
+
+  EvalStatus UpdateDownloadAllowed(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      bool* result) const override;
+
+  EvalStatus P2PEnabled(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      bool* result) const override;
+
+  EvalStatus P2PEnabledChanged(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      bool* result,
+      bool prev_result) const override;
+
+ protected:
+  // Policy override.
+  std::string PolicyName() const override { return "ChromeOSPolicy"; }
+
+ private:
+  friend class UmChromeOSPolicyTest;
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              FirstCheckIsAtMostInitialIntervalAfterStart);
+  FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz);
+  FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBackoffIntervalAndFuzz);
+  FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval);
+  FRIEND_TEST(UmChromeOSPolicyTest, ExponentialBackoffIsCapped);
+  FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout);
+  FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringNewCountThresholdApplies);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies);
+  FRIEND_TEST(UmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartAllowedInteractivePreventsScattering);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod);
+
+  // Auxiliary constant (zero by default).
+  const base::TimeDelta kZeroInterval;
+
+  // Default update check timeout interval/fuzz values used to compute the
+  // NextUpdateCheckTime(), in seconds. Actual fuzz is within +/- half of the
+  // indicated value.
+  static const int kTimeoutInitialInterval;
+  static const int kTimeoutPeriodicInterval;
+  static const int kTimeoutMaxBackoffInterval;
+  static const int kTimeoutRegularFuzz;
+
+  // Maximum update attempt backoff interval and fuzz.
+  static const int kAttemptBackoffMaxIntervalInDays;
+  static const int kAttemptBackoffFuzzInHours;
+
+  // Maximum number of times we'll allow using P2P for the same update payload.
+  static const int kMaxP2PAttempts;
+  // Maximum period of time allowed for download a payload via P2P, in seconds.
+  static const int kMaxP2PAttemptsPeriodInSeconds;
+
+  // A private policy implementation returning the wallclock timestamp when
+  // the next update check should happen.
+  // TODO(garnold) We should probably change that to infer a monotonic
+  // timestamp, which will make the update check intervals more resilient to
+  // clock skews. Might require switching some of the variables exported by the
+  // UpdaterProvider to report monotonic time, as well.
+  EvalStatus NextUpdateCheckTime(EvaluationContext* ec, State* state,
+                                 std::string* error,
+                                 base::Time* next_update_check) const;
+
+  // Returns a TimeDelta based on the provided |interval| seconds +/- half
+  // |fuzz| seconds. The return value is guaranteed to be a non-negative
+  // TimeDelta.
+  static base::TimeDelta FuzzedInterval(PRNG* prng, int interval, int fuzz);
+
+  // A private policy for determining backoff and the download URL to use.
+  // Within |update_state|, |backoff_expiry| and |is_backoff_disabled| are used
+  // for determining whether backoff is still in effect; if not,
+  // |download_errors| is scanned past |failures_last_updated|, and a new
+  // download URL from |download_urls| is found and written to |result->url_idx|
+  // (-1 means no usable URL exists); |download_errors_max| determines the
+  // maximum number of attempts per URL, according to the Omaha response. If an
+  // update failure is identified then |result->do_increment_failures| is set to
+  // true; if backoff is enabled, a new backoff period is computed (from the
+  // time of failure) based on |num_failures|. Otherwise, backoff expiry is
+  // nullified, indicating that no backoff is in effect.
+  //
+  // If backing off but the previous backoff expiry is unchanged, returns
+  // |EvalStatus::kAskMeAgainLater|. Otherwise:
+  //
+  // * If backing off with a new expiry time, then |result->backoff_expiry| is
+  //   set to this time.
+  //
+  // * Else, |result->backoff_expiry| is set to null, indicating that no backoff
+  //   is in effect.
+  //
+  // In any of these cases, returns |EvalStatus::kSucceeded|. If an error
+  // occurred, returns |EvalStatus::kFailed|.
+  EvalStatus UpdateBackoffAndDownloadUrl(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateBackoffAndDownloadUrlResult* result,
+      const UpdateState& update_state) const;
+
+  // A private policy for checking whether scattering is due. Writes in |result|
+  // the decision as to whether or not to scatter; a wallclock-based scatter
+  // wait period, which ranges from zero (do not wait) and no greater than the
+  // current scatter factor provided by the device policy (if available) or the
+  // maximum wait period determined by Omaha; and an update check-based
+  // threshold between zero (no threshold) and the maximum number determined by
+  // the update engine. Within |update_state|, |scatter_wait_period| should
+  // contain the last scattering period returned by this function, or zero if no
+  // wait period is known; |scatter_check_threshold| is the last update check
+  // threshold, or zero if no such threshold is known. If not scattering, or if
+  // any of the scattering values has changed, returns |EvalStatus::kSucceeded|;
+  // otherwise, |EvalStatus::kAskMeAgainLater|.
+  EvalStatus UpdateScattering(EvaluationContext* ec, State* state,
+                              std::string* error,
+                              UpdateScatteringResult* result,
+                              const UpdateState& update_state) const;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeOSPolicy);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_CHROMEOS_POLICY_H_
diff --git a/update_engine/update_manager/chromeos_policy_unittest.cc b/update_engine/update_manager/chromeos_policy_unittest.cc
new file mode 100644
index 0000000..0c38700
--- /dev/null
+++ b/update_engine/update_manager/chromeos_policy_unittest.cc
@@ -0,0 +1,1699 @@
+//
+// 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/update_manager/chromeos_policy.h"
+
+#include <set>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <base/time/time.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/fake_state.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ConnectionTethering;
+using chromeos_update_engine::ConnectionType;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::FakeClock;
+using std::set;
+using std::string;
+using std::tuple;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+class UmChromeOSPolicyTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    SetUpDefaultClock();
+    eval_ctx_ = new EvaluationContext(&fake_clock_, TimeDelta::FromSeconds(5));
+    SetUpDefaultState();
+    SetUpDefaultDevicePolicy();
+  }
+
+  void TearDown() override {
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  // Sets the clock to fixed values.
+  void SetUpDefaultClock() {
+    fake_clock_.SetMonotonicTime(Time::FromInternalValue(12345678L));
+    fake_clock_.SetWallclockTime(Time::FromInternalValue(12345678901234L));
+  }
+
+  void SetUpDefaultState() {
+    fake_state_.updater_provider()->var_updater_started_time()->reset(
+        new Time(fake_clock_.GetWallclockTime()));
+    fake_state_.updater_provider()->var_last_checked_time()->reset(
+        new Time(fake_clock_.GetWallclockTime()));
+    fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+        reset(new unsigned int{0});
+    fake_state_.updater_provider()->var_server_dictated_poll_interval()->
+        reset(new unsigned int{0});
+    fake_state_.updater_provider()->var_forced_update_requested()->
+        reset(new UpdateRequestStatus{UpdateRequestStatus::kNone});
+
+    fake_state_.random_provider()->var_seed()->reset(
+        new uint64_t(4));  // chosen by fair dice roll.
+                           // guaranteed to be random.
+
+    // No device policy loaded by default.
+    fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+        new bool(false));
+
+    // OOBE is enabled by default.
+    fake_state_.config_provider()->var_is_oobe_enabled()->reset(
+        new bool(true));
+
+    // For the purpose of the tests, this is an official build and OOBE was
+    // completed.
+    fake_state_.system_provider()->var_is_official_build()->reset(
+        new bool(true));
+    fake_state_.system_provider()->var_is_oobe_complete()->reset(
+        new bool(true));
+    fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(2));
+
+    // Connection is wifi, untethered.
+    fake_state_.shill_provider()->var_conn_type()->
+        reset(new ConnectionType(ConnectionType::kWifi));
+    fake_state_.shill_provider()->var_conn_tethering()->
+        reset(new ConnectionTethering(ConnectionTethering::kNotDetected));
+  }
+
+  // Sets up a default device policy that does not impose any restrictions
+  // (HTTP) nor enables any features (P2P).
+  void SetUpDefaultDevicePolicy() {
+    fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+        new bool(true));
+    fake_state_.device_policy_provider()->var_update_disabled()->reset(
+        new bool(false));
+    fake_state_.device_policy_provider()->
+        var_allowed_connection_types_for_update()->reset(nullptr);
+    fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+        new TimeDelta());
+    fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+        new bool(true));
+    fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+        new bool(false));
+    fake_state_.device_policy_provider()->var_release_channel_delegated()->
+        reset(new bool(true));
+  }
+
+  // Configures the UpdateCheckAllowed policy to return a desired value by
+  // faking the current wall clock time as needed. Restores the default state.
+  // This is used when testing policies that depend on this one.
+  void SetUpdateCheckAllowed(bool allow_check) {
+    Time next_update_check;
+    ExpectPolicyStatus(EvalStatus::kSucceeded,
+                       &ChromeOSPolicy::NextUpdateCheckTime,
+                       &next_update_check);
+    SetUpDefaultState();
+    SetUpDefaultDevicePolicy();
+    Time curr_time = next_update_check;
+    if (allow_check)
+      curr_time += TimeDelta::FromSeconds(1);
+    else
+      curr_time -= TimeDelta::FromSeconds(1);
+    fake_clock_.SetWallclockTime(curr_time);
+  }
+
+  // Returns a default UpdateState structure:
+  UpdateState GetDefaultUpdateState(TimeDelta first_seen_period) {
+    Time first_seen_time = fake_clock_.GetWallclockTime() - first_seen_period;
+    UpdateState update_state = UpdateState();
+
+    // This is a non-interactive check returning a delta payload, seen for the
+    // first time (|first_seen_period| ago). Clearly, there were no failed
+    // attempts so far.
+    update_state.is_interactive = false;
+    update_state.is_delta_payload = false;
+    update_state.first_seen = first_seen_time;
+    update_state.num_checks = 1;
+    update_state.num_failures = 0;
+    update_state.failures_last_updated = Time();  // Needs to be zero.
+    // There's a single HTTP download URL with a maximum of 10 retries.
+    update_state.download_urls = vector<string>{"http://fake/url/"};
+    update_state.download_errors_max = 10;
+    // Download was never attempted.
+    update_state.last_download_url_idx = -1;
+    update_state.last_download_url_num_errors = 0;
+    // There were no download errors.
+    update_state.download_errors = vector<tuple<int, ErrorCode, Time>>();
+    // P2P is not disabled by Omaha.
+    update_state.p2p_downloading_disabled = false;
+    update_state.p2p_sharing_disabled = false;
+    // P2P was not attempted.
+    update_state.p2p_num_attempts = 0;
+    update_state.p2p_first_attempted = Time();
+    // No active backoff period, backoff is not disabled by Omaha.
+    update_state.backoff_expiry = Time();
+    update_state.is_backoff_disabled = false;
+    // There is no active scattering wait period (max 7 days allowed) nor check
+    // threshold (none allowed).
+    update_state.scatter_wait_period = TimeDelta();
+    update_state.scatter_check_threshold = 0;
+    update_state.scatter_wait_period_max = TimeDelta::FromDays(7);
+    update_state.scatter_check_threshold_min = 0;
+    update_state.scatter_check_threshold_max = 0;
+
+    return update_state;
+  }
+
+  // Runs the passed |policy_method| policy and expects it to return the
+  // |expected| return value.
+  template<typename T, typename R, typename... Args>
+  void ExpectPolicyStatus(
+      EvalStatus expected,
+      T policy_method,
+      R* result, Args... args) {
+    string error = "<None>";
+    eval_ctx_->ResetEvaluation();
+    EXPECT_EQ(expected,
+              (policy_.*policy_method)(eval_ctx_.get(), &fake_state_, &error,
+                                       result, args...))
+        << "Returned error: " << error
+        << "\nEvaluation context: " << eval_ctx_->DumpContext();
+  }
+
+  brillo::FakeMessageLoop loop_{nullptr};
+  FakeClock fake_clock_;
+  FakeState fake_state_;
+  scoped_refptr<EvaluationContext> eval_ctx_;
+  ChromeOSPolicy policy_;  // ChromeOSPolicy under test.
+};
+
+TEST_F(UmChromeOSPolicyTest, FirstCheckIsAtMostInitialIntervalAfterStart) {
+  Time next_update_check;
+
+  // Set the last update time so it'll appear as if this is a first update check
+  // in the lifetime of the current updater.
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(fake_clock_.GetWallclockTime() - TimeDelta::FromMinutes(10)));
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  EXPECT_LE(fake_clock_.GetWallclockTime(), next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutInitialInterval +
+          ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz) {
+  // Ensure that we're using the correct interval (kPeriodicInterval) and fuzz
+  // (kTimeoutRegularFuzz) as base values for period updates.
+  Time next_update_check;
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  EXPECT_LE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutPeriodicInterval -
+          ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+      next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutPeriodicInterval +
+          ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckBackoffIntervalAndFuzz) {
+  // Ensure that we're properly backing off and fuzzing in the presence of
+  // failed updates attempts.
+  Time next_update_check;
+
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+      reset(new unsigned int{2});
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  int expected_interval = ChromeOSPolicy::kTimeoutPeriodicInterval * 4;
+  EXPECT_LE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          expected_interval - expected_interval / 2),
+      next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          expected_interval + expected_interval / 2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval) {
+  // Policy honors the server provided check poll interval.
+  Time next_update_check;
+
+  const unsigned int kInterval = ChromeOSPolicy::kTimeoutPeriodicInterval * 4;
+  fake_state_.updater_provider()->var_server_dictated_poll_interval()->
+      reset(new unsigned int{kInterval});
+  // We should not be backing off in this case.
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+      reset(new unsigned int{2});
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  EXPECT_LE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          kInterval - kInterval / 2),
+      next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          kInterval + kInterval / 2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, ExponentialBackoffIsCapped) {
+  Time next_update_check;
+
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+      reset(new unsigned int{100});
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  EXPECT_LE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutMaxBackoffInterval -
+          ChromeOSPolicy::kTimeoutMaxBackoffInterval / 2),
+      next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutMaxBackoffInterval +
+          ChromeOSPolicy::kTimeoutMaxBackoffInterval /2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) {
+  // We get the next update_check timestamp from the policy's private method
+  // and then we check the public method respects that value on the normal
+  // case.
+  Time next_update_check;
+  Time last_checked_time =
+      fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234);
+
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  UpdateCheckParams result;
+
+  // Check that the policy blocks until the next_update_check is reached.
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check - TimeDelta::FromSeconds(1));
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE) {
+  // Update checks are deferred until OOBE is completed.
+
+  // Ensure that update is not allowed even if wait period is satisfied.
+  Time next_update_check;
+  Time last_checked_time =
+      fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234);
+
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+  fake_state_.system_provider()->var_is_oobe_complete()->reset(
+      new bool(false));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+
+  // Now check that it is allowed if OOBE is completed.
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWithAttributes) {
+  // Update check is allowed, response includes attributes for use in the
+  // request.
+  SetUpdateCheckAllowed(true);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_target_version_prefix()->
+      reset(new string("1.2"));
+  fake_state_.device_policy_provider()->var_release_channel_delegated()->
+      reset(new bool(false));
+  fake_state_.device_policy_provider()->var_release_channel()->
+      reset(new string("foo-channel"));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_EQ("1.2", result.target_version_prefix);
+  EXPECT_EQ("foo-channel", result.target_channel);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedUpdatesDisabledForUnofficialBuilds) {
+  // UpdateCheckAllowed should return kAskMeAgainLater if this is an unofficial
+  // build; we don't want periodic update checks on developer images.
+
+  fake_state_.system_provider()->var_is_official_build()->reset(
+      new bool(false));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedUpdatesDisabledForRemovableBootDevice) {
+  // UpdateCheckAllowed should return false (kSucceeded) if the image booted
+  // from a removable device.
+
+  fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(1));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_FALSE(result.updates_enabled);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedUpdatesDisabledByPolicy) {
+  // UpdateCheckAllowed should return kAskMeAgainLater because a device policy
+  // is loaded and prohibits updates.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_update_disabled()->reset(
+      new bool(true));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedForcedUpdateRequestedInteractive) {
+  // UpdateCheckAllowed should return true because a forced update request was
+  // signaled for an interactive update.
+
+  SetUpdateCheckAllowed(true);
+  fake_state_.updater_provider()->var_forced_update_requested()->reset(
+      new UpdateRequestStatus(UpdateRequestStatus::kInteractive));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_TRUE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedForcedUpdateRequestedPeriodic) {
+  // UpdateCheckAllowed should return true because a forced update request was
+  // signaled for a periodic check.
+
+  SetUpdateCheckAllowed(true);
+  fake_state_.updater_provider()->var_forced_update_requested()->reset(
+      new UpdateRequestStatus(UpdateRequestStatus::kPeriodic));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedKioskPin) {
+  // Update check is allowed.
+  SetUpdateCheckAllowed(true);
+
+  // A typical setup for kiosk pin policy: AU disabled, allow kiosk to pin
+  // and there is a kiosk required platform version.
+  fake_state_.device_policy_provider()->var_update_disabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()
+      ->var_allow_kiosk_app_control_chrome_version()
+      ->reset(new bool(true));
+  fake_state_.system_provider()->var_kiosk_required_platform_version()->reset(
+      new string("1234.0.0"));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_EQ("1234.0.0", result.target_version_prefix);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedDisabledWhenNoKioskPin) {
+  // Update check is allowed.
+  SetUpdateCheckAllowed(true);
+
+  // Disable AU policy is set but kiosk pin policy is set to false. Update is
+  // disabled in such case.
+  fake_state_.device_policy_provider()->var_update_disabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()
+      ->var_allow_kiosk_app_control_chrome_version()
+      ->reset(new bool(false));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedKioskPinWithNoRequiredVersion) {
+  // Update check is allowed.
+  SetUpdateCheckAllowed(true);
+
+  // AU disabled, allow kiosk to pin but there is no kiosk required platform
+  // version (i.e. app does not provide the info). Update to latest in such
+  // case.
+  fake_state_.device_policy_provider()->var_update_disabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()
+      ->var_allow_kiosk_app_control_chrome_version()
+      ->reset(new bool(true));
+  fake_state_.system_provider()->var_kiosk_required_platform_version()->reset(
+      new string());
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_TRUE(result.target_version_prefix.empty());
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedKioskPinWithFailedGetRequiredVersionCall) {
+  // AU disabled, allow kiosk to pin but D-Bus call to get required platform
+  // version failed. Defer update check in this case.
+  fake_state_.device_policy_provider()->var_update_disabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()
+      ->var_allow_kiosk_app_control_chrome_version()
+      ->reset(new bool(true));
+  fake_state_.system_provider()->var_kiosk_required_platform_version()->reset(
+      nullptr);
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartFailsCheckAllowedError) {
+  // The UpdateCanStart policy fails, not being able to query
+  // UpdateCheckAllowed.
+
+  // Configure the UpdateCheckAllowed policy to fail.
+  fake_state_.updater_provider()->var_updater_started_time()->reset(nullptr);
+
+  // Check that the UpdateCanStart fails.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kFailed,
+                     &Policy::UpdateCanStart, &result, update_state);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartNotAllowedCheckDue) {
+  // The UpdateCanStart policy returns false because we are due for another
+  // update check. Ensure that download related values are still returned.
+
+  SetUpdateCheckAllowed(true);
+
+  // Check that the UpdateCanStart returns false.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCanStart, &result, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kCheckDue, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_EQ(0, result.download_url_num_errors);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoDevicePolicy) {
+  // The UpdateCanStart policy returns true; no device policy is loaded.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+      new bool(false));
+
+  // Check that the UpdateCanStart returns true with no further attributes.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCanStart, &result, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_FALSE(result.p2p_sharing_allowed);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBlankPolicy) {
+  // The UpdateCanStart policy returns true; device policy is loaded but imposes
+  // no restrictions on updating.
+
+  SetUpdateCheckAllowed(false);
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCanStart, &result, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_FALSE(result.p2p_sharing_allowed);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedBackoffNewWaitPeriodApplies) {
+  // The UpdateCanStart policy returns false; failures are reported and a new
+  // backoff period is enacted.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kBackoff, result.cannot_start_reason);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedBackoffPrevWaitPeriodStillApplies) {
+  // The UpdateCanStart policy returns false; a previously enacted backoff
+  // period still applies.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.failures_last_updated = curr_time;
+  update_state.backoff_expiry = curr_time + TimeDelta::FromMinutes(3);
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::UpdateCanStart,
+                     &result, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kBackoff, result.cannot_start_reason);
+  EXPECT_FALSE(result.do_increment_failures);
+  EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffSatisfied) {
+  // The UpdateCanStart policy returns true; a previously enacted backoff period
+  // has elapsed, we're good to go.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.failures_last_updated = curr_time - TimeDelta::FromSeconds(1);
+  update_state.backoff_expiry = curr_time - TimeDelta::FromSeconds(1);
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart,
+                     &result, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffDisabled) {
+  // The UpdateCanStart policy returns false; failures are reported but backoff
+  // is disabled.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.is_backoff_disabled = true;
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffInteractive) {
+  // The UpdateCanStart policy returns false; failures are reported but this is
+  // an interactive update check.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.is_interactive = true;
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffDelta) {
+  // The UpdateCanStart policy returns false; failures are reported but this is
+  // a delta payload.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.is_delta_payload = true;
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffUnofficialBuild) {
+  // The UpdateCanStart policy returns false; failures are reported but this is
+  // an unofficial build.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+
+  fake_state_.system_provider()->var_is_official_build()->
+      reset(new bool(false));
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartFailsScatteringFailed) {
+  // The UpdateCanStart policy fails because the UpdateScattering policy it
+  // depends on fails (unset variable).
+
+  SetUpdateCheckAllowed(false);
+
+  // Override the default seed variable with a null value so that the policy
+  // request would fail.
+  // TODO(garnold) This failure may or may not fail a number
+  // sub-policies/decisions, like scattering and backoff. We'll need a more
+  // deliberate setup to ensure that we're failing what we want to be failing.
+  fake_state_.random_provider()->var_seed()->reset(nullptr);
+
+  // Check that the UpdateCanStart fails.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kFailed,
+                     &Policy::UpdateCanStart, &result, update_state);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies) {
+  // The UpdateCanStart policy returns false; device policy is loaded and
+  // scattering applies due to an unsatisfied wait period, which was newly
+  // generated.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromMinutes(2)));
+
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+
+  // Check that the UpdateCanStart returns false and a new wait period
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_LT(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies) {
+  // The UpdateCanStart policy returns false w/ kAskMeAgainLater; device policy
+  // is loaded and a previously generated scattering period still applies, none
+  // of the scattering values has changed.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromMinutes(2)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(35);
+
+  // Check that the UpdateCanStart returns false and a new wait period
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::UpdateCanStart,
+                     &result, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_EQ(TimeDelta::FromSeconds(35), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringNewCountThresholdApplies) {
+  // The UpdateCanStart policy returns false; device policy is loaded and
+  // scattering applies due to an unsatisfied update check count threshold.
+  //
+  // This ensures a non-zero check threshold, which may or may not be combined
+  // with a non-zero wait period (for which we cannot reliably control).
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns false.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_LE(2, result.scatter_check_threshold);
+  EXPECT_GE(5, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies) {
+  // The UpdateCanStart policy returns false; device policy is loaded and
+  // scattering due to a previously generated count threshold still applies.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_check_threshold = 3;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns false.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_EQ(3, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied) {
+  // The UpdateCanStart policy returns true; device policy is loaded and
+  // scattering is enabled, but both wait period and check threshold are
+  // satisfied.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(120)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(75));
+  update_state.num_checks = 4;
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(60);
+  update_state.scatter_check_threshold = 3;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedInteractivePreventsScattering) {
+  // The UpdateCanStart policy returns true; device policy is loaded and
+  // scattering would have applied, except that the update check is interactive
+  // and so it is suppressed.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.is_interactive = true;
+  update_state.scatter_check_threshold = 0;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedOobePreventsScattering) {
+  // The UpdateCanStart policy returns true; device policy is loaded and
+  // scattering would have applied, except that OOBE was not completed and so it
+  // is suppressed.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+  fake_state_.system_provider()->var_is_oobe_complete()->reset(new bool(false));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.is_interactive = true;
+  update_state.scatter_check_threshold = 0;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithAttributes) {
+  // The UpdateCanStart policy returns true; device policy permits both HTTP and
+  // P2P updates, as well as a non-empty target channel string.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithP2PFromUpdater) {
+  // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+  // P2P updates, but the updater is configured to allow P2P and overrules the
+  // setting.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedP2PDownloadingBlockedDueToOmaha) {
+  // The UpdateCanStart policy returns true; device policy permits HTTP, but
+  // policy blocks P2P downloading because Omaha forbids it.  P2P sharing is
+  // still permitted.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.p2p_downloading_disabled = true;
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedP2PSharingBlockedDueToOmaha) {
+  // The UpdateCanStart policy returns true; device policy permits HTTP, but
+  // policy blocks P2P sharing because Omaha forbids it.  P2P downloading is
+  // still permitted.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.p2p_sharing_disabled = true;
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_FALSE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts) {
+  // The UpdateCanStart policy returns true; device policy permits HTTP but
+  // blocks P2P download, because the max number of P2P downloads have been
+  // attempted. P2P sharing is still permitted.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.p2p_num_attempts = ChromeOSPolicy::kMaxP2PAttempts;
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod) {
+  // The UpdateCanStart policy returns true; device policy permits HTTP but
+  // blocks P2P download, because the max period for attempt to download via P2P
+  // has elapsed. P2P sharing is still permitted.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.p2p_num_attempts = 1;
+  update_state.p2p_first_attempted =
+      fake_clock_.GetWallclockTime() -
+      TimeDelta::FromSeconds(
+          ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds + 1);
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedWithHttpUrlForUnofficialBuild) {
+  // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+  // P2P updates, but marking this an unofficial build overrules the HTTP
+  // setting.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+  fake_state_.system_provider()->var_is_official_build()->
+      reset(new bool(false));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithHttpsUrl) {
+  // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+  // P2P updates, but an HTTPS URL is provided and selected for download.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+
+  // Add an HTTPS URL.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.download_urls.emplace_back("https://secure/url/");
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(1, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedMaxErrorsNotExceeded) {
+  // The UpdateCanStart policy returns true; the first URL has download errors
+  // but does not exceed the maximum allowed number of failures, so it is stilli
+  // usable.
+
+  SetUpdateCheckAllowed(false);
+
+  // Add a second URL; update with this URL attempted and failed enough times to
+  // disqualify the current (first) URL.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.num_checks = 5;
+  update_state.download_urls.emplace_back("http://another/fake/url/");
+  Time t = fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(12);
+  for (int i = 0; i < 5; i++) {
+    update_state.download_errors.emplace_back(
+        0, ErrorCode::kDownloadTransferError, t);
+    t += TimeDelta::FromSeconds(1);
+  }
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(5, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlMaxExceeded) {
+  // The UpdateCanStart policy returns true; the first URL exceeded the maximum
+  // allowed number of failures, but a second URL is available.
+
+  SetUpdateCheckAllowed(false);
+
+  // Add a second URL; update with this URL attempted and failed enough times to
+  // disqualify the current (first) URL.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.num_checks = 10;
+  update_state.download_urls.emplace_back("http://another/fake/url/");
+  Time t = fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(12);
+  for (int i = 0; i < 11; i++) {
+    update_state.download_errors.emplace_back(
+        0, ErrorCode::kDownloadTransferError, t);
+    t += TimeDelta::FromSeconds(1);
+  }
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(1, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlHardError) {
+  // The UpdateCanStart policy returns true; the first URL fails with a hard
+  // error, but a second URL is available.
+
+  SetUpdateCheckAllowed(false);
+
+  // Add a second URL; update with this URL attempted and failed in a way that
+  // causes it to switch directly to the next URL.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.num_checks = 10;
+  update_state.download_urls.emplace_back("http://another/fake/url/");
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kPayloadHashMismatchError,
+      fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(1, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedUrlWrapsAround) {
+  // The UpdateCanStart policy returns true; URL search properly wraps around
+  // the last one on the list.
+
+  SetUpdateCheckAllowed(false);
+
+  // Add a second URL; update with this URL attempted and failed in a way that
+  // causes it to switch directly to the next URL. We must disable backoff in
+  // order for it not to interfere.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.num_checks = 1;
+  update_state.is_backoff_disabled = true;
+  update_state.download_urls.emplace_back("http://another/fake/url/");
+  update_state.download_errors.emplace_back(
+      1, ErrorCode::kPayloadHashMismatchError,
+      fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartNotAllowedNoUsableUrls) {
+  // The UpdateCanStart policy returns false; there's a single HTTP URL but its
+  // use is forbidden by policy.
+  //
+  // Note: In the case where no usable URLs are found, the policy should not
+  // increment the number of failed attempts! Doing so would result in a
+  // non-idempotent semantics, and does not fall within the intended purpose of
+  // the backoff mechanism anyway.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+
+  // Check that the UpdateCanStart returns false.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kCannotDownload,
+            result.cannot_start_reason);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoUsableUrlsButP2PEnabled) {
+  // The UpdateCanStart policy returns true; there's a single HTTP URL but its
+  // use is forbidden by policy, however P2P is enabled. The result indicates
+  // that no URL can be used.
+  //
+  // Note: The number of failed attempts should not increase in this case (see
+  // above test).
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_GT(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedNoUsableUrlsButEnterpriseEnrolled) {
+  // The UpdateCanStart policy returns true; there's a single HTTP URL but its
+  // use is forbidden by policy, and P2P is unset on the policy, however the
+  // device is enterprise-enrolled so P2P is allowed. The result indicates that
+  // no URL can be used.
+  //
+  // Note: The number of failed attempts should not increase in this case (see
+  // above test).
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(nullptr);
+  fake_state_.device_policy_provider()->var_owner()->reset(nullptr);
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_GT(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedEthernetDefault) {
+  // Ethernet is always allowed.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kEthernet));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedWifiDefault) {
+  // Wifi is allowed if not tethered.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kWifi));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCurrentConnectionNotAllowedWifiTetheredDefault) {
+  // Tethered wifi is not allowed by default.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kWifi));
+  fake_state_.shill_provider()->var_conn_tethering()->
+      reset(new ConnectionTethering(ConnectionTethering::kConfirmed));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateDownloadAllowedWifiTetheredPolicyOverride) {
+  // Tethered wifi can be allowed by policy.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kWifi));
+  fake_state_.shill_provider()->var_conn_tethering()->
+      reset(new ConnectionTethering(ConnectionTethering::kConfirmed));
+  set<ConnectionType> allowed_connections;
+  allowed_connections.insert(ConnectionType::kCellular);
+  fake_state_.device_policy_provider()->
+      var_allowed_connection_types_for_update()->
+      reset(new set<ConnectionType>(allowed_connections));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedWimaxDefault) {
+  // Wimax is always allowed.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kWifi));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCurrentConnectionNotAllowedBluetoothDefault) {
+  // Bluetooth is never allowed.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kBluetooth));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCurrentConnectionNotAllowedBluetoothPolicyCannotOverride) {
+  // Bluetooth cannot be allowed even by policy.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kBluetooth));
+  set<ConnectionType> allowed_connections;
+  allowed_connections.insert(ConnectionType::kBluetooth);
+  fake_state_.device_policy_provider()->
+      var_allowed_connection_types_for_update()->
+      reset(new set<ConnectionType>(allowed_connections));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCurrentConnectionNotAllowedCellularDefault) {
+  // Cellular is not allowed by default.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kCellular));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateDownloadAllowedCellularPolicyOverride) {
+  // Update over cellular can be enabled by policy.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kCellular));
+  set<ConnectionType> allowed_connections;
+  allowed_connections.insert(ConnectionType::kCellular);
+  fake_state_.device_policy_provider()->
+      var_allowed_connection_types_for_update()->
+      reset(new set<ConnectionType>(allowed_connections));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateDownloadAllowedCellularUserOverride) {
+  // Update over cellular can be enabled by user settings, but only if policy
+  // is present and does not determine allowed connections.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kCellular));
+  set<ConnectionType> allowed_connections;
+  allowed_connections.insert(ConnectionType::kCellular);
+  fake_state_.updater_provider()->var_cellular_enabled()->
+      reset(new bool(true));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedScatteringSupressedDueToP2P) {
+  // The UpdateCanStart policy returns true; scattering should have applied, but
+  // P2P download is allowed. Scattering values are nonetheless returned, and so
+  // are download URL values, albeit the latter are not allowed to be used.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromMinutes(2)));
+  fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(35);
+
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart,
+                     &result, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_FALSE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_FALSE(result.do_increment_failures);
+  EXPECT_EQ(TimeDelta::FromSeconds(35), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedBackoffSupressedDueToP2P) {
+  // The UpdateCanStart policy returns true; backoff should have applied, but
+  // P2P download is allowed. Backoff values are nonetheless returned, and so
+  // are download URL values, albeit the latter are not allowed to be used.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_FALSE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledNotAllowed) {
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+  EXPECT_FALSE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedByDevicePolicy) {
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedByUpdater) {
+  fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedDeviceEnterpriseEnrolled) {
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(nullptr);
+  fake_state_.device_policy_provider()->var_owner()->reset(nullptr);
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledChangedBlocks) {
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::P2PEnabledChanged,
+                     &result, false);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/config_provider.h b/update_engine/update_manager/config_provider.h
new file mode 100644
index 0000000..36d57a7
--- /dev/null
+++ b/update_engine/update_manager/config_provider.h
@@ -0,0 +1,43 @@
+//
+// 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_UPDATE_MANAGER_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_CONFIG_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for const system configurations. This provider reads the
+// configuration from a file on /etc.
+class ConfigProvider : public Provider {
+ public:
+  // Returns a variable stating whether the out of the box experience (OOBE) is
+  // enabled on this device. A value of false means that the device doesn't have
+  // an OOBE workflow.
+  virtual Variable<bool>* var_is_oobe_enabled() = 0;
+
+ protected:
+  ConfigProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConfigProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_CONFIG_PROVIDER_H_
diff --git a/update_engine/update_manager/default_policy.cc b/update_engine/update_manager/default_policy.cc
new file mode 100644
index 0000000..9a5ce7e
--- /dev/null
+++ b/update_engine/update_manager/default_policy.cc
@@ -0,0 +1,107 @@
+//
+// 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/update_manager/default_policy.h"
+
+namespace {
+
+// A fixed minimum interval between consecutive allowed update checks. This
+// needs to be long enough to prevent busywork and/or DDoS attacks on Omaha, but
+// at the same time short enough to allow the machine to update itself
+// reasonably soon.
+const int kCheckIntervalInSeconds = 15 * 60;
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+DefaultPolicy::DefaultPolicy(chromeos_update_engine::ClockInterface* clock)
+    : clock_(clock), aux_state_(new DefaultPolicyState()) {}
+
+EvalStatus DefaultPolicy::UpdateCheckAllowed(
+    EvaluationContext* ec, State* state, std::string* error,
+    UpdateCheckParams* result) const {
+  result->updates_enabled = true;
+  result->target_channel.clear();
+  result->target_version_prefix.clear();
+  result->is_interactive = false;
+
+  // Ensure that the minimum interval is set. If there's no clock, this defaults
+  // to always allowing the update.
+  if (!aux_state_->IsLastCheckAllowedTimeSet() ||
+      ec->IsMonotonicTimeGreaterThan(
+          aux_state_->last_check_allowed_time() +
+          base::TimeDelta::FromSeconds(kCheckIntervalInSeconds))) {
+    if (clock_)
+      aux_state_->set_last_check_allowed_time(clock_->GetMonotonicTime());
+    return EvalStatus::kSucceeded;
+  }
+
+  return EvalStatus::kAskMeAgainLater;
+}
+
+EvalStatus DefaultPolicy::UpdateCanStart(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    UpdateDownloadParams* result,
+    const UpdateState update_state) const {
+  result->update_can_start = true;
+  result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+  result->download_url_idx = 0;
+  result->download_url_allowed = true;
+  result->download_url_num_errors = 0;
+  result->p2p_downloading_allowed = false;
+  result->p2p_sharing_allowed = false;
+  result->do_increment_failures = false;
+  result->backoff_expiry = base::Time();
+  result->scatter_wait_period = base::TimeDelta();
+  result->scatter_check_threshold = 0;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::UpdateDownloadAllowed(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    bool* result) const {
+  *result = true;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::P2PEnabled(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    bool* result) const {
+  *result = false;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::P2PEnabledChanged(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    bool* result,
+    bool prev_result) const {
+  // This policy will always prohibit P2P, so this is signaling to the caller
+  // that the decision is final (because the current value is the same as the
+  // previous one) and there's no need to issue another call.
+  *result = false;
+  return EvalStatus::kSucceeded;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/default_policy.h b/update_engine/update_manager/default_policy.h
new file mode 100644
index 0000000..3f41178
--- /dev/null
+++ b/update_engine/update_manager/default_policy.h
@@ -0,0 +1,105 @@
+//
+// 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_UPDATE_MANAGER_DEFAULT_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_DEFAULT_POLICY_H_
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/update_manager/policy.h"
+
+namespace chromeos_update_manager {
+
+// Auxiliary state class for DefaultPolicy evaluations.
+//
+// IMPORTANT: The use of a state object in policies is generally forbidden, as
+// it was a design decision to keep policy calls side-effect free. We make an
+// exception here to ensure that DefaultPolicy indeed serves as a safe (and
+// secure) fallback option. This practice should be avoided when imlpementing
+// other policies.
+class DefaultPolicyState {
+ public:
+  DefaultPolicyState() {}
+
+  bool IsLastCheckAllowedTimeSet() const {
+    return last_check_allowed_time_ != base::Time::Max();
+  }
+
+  // Sets/returns the point time on the monotonic time scale when the latest
+  // check allowed was recorded.
+  void set_last_check_allowed_time(base::Time timestamp) {
+    last_check_allowed_time_ = timestamp;
+  }
+  base::Time last_check_allowed_time() const {
+    return last_check_allowed_time_;
+  }
+
+ private:
+  base::Time last_check_allowed_time_ = base::Time::Max();
+};
+
+// The DefaultPolicy is a safe Policy implementation that doesn't fail. The
+// values returned by this policy are safe default in case of failure of the
+// actual policy being used by the UpdateManager.
+class DefaultPolicy : public Policy {
+ public:
+  explicit DefaultPolicy(chromeos_update_engine::ClockInterface* clock);
+  DefaultPolicy() : DefaultPolicy(nullptr) {}
+  ~DefaultPolicy() override {}
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateCheckParams* result) const override;
+
+  EvalStatus UpdateCanStart(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateDownloadParams* result,
+      UpdateState update_state) const override;
+
+  EvalStatus UpdateDownloadAllowed(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result) const override;
+
+  EvalStatus P2PEnabled(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result) const override;
+
+  EvalStatus P2PEnabledChanged(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result, bool prev_result) const override;
+
+ protected:
+  // Policy override.
+  std::string PolicyName() const override { return "DefaultPolicy"; }
+
+ private:
+  // A clock interface.
+  chromeos_update_engine::ClockInterface* clock_;
+
+  // An auxiliary state object.
+  std::unique_ptr<DefaultPolicyState> aux_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(DefaultPolicy);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_DEFAULT_POLICY_H_
diff --git a/update_engine/update_manager/device_policy_provider.h b/update_engine/update_manager/device_policy_provider.h
new file mode 100644
index 0000000..3537d13
--- /dev/null
+++ b/update_engine/update_manager/device_policy_provider.h
@@ -0,0 +1,77 @@
+//
+// 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_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include <base/time/time.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provides access to the current DevicePolicy.
+class DevicePolicyProvider : public Provider {
+ public:
+  ~DevicePolicyProvider() override {}
+
+  // Variable stating whether the DevicePolicy was loaded.
+  virtual Variable<bool>* var_device_policy_is_loaded() = 0;
+
+  // Variables mapping the information received on the DevicePolicy protobuf.
+  virtual Variable<std::string>* var_release_channel() = 0;
+
+  virtual Variable<bool>* var_release_channel_delegated() = 0;
+
+  virtual Variable<bool>* var_update_disabled() = 0;
+
+  virtual Variable<std::string>* var_target_version_prefix() = 0;
+
+  // Returns a non-negative scatter interval used for updates.
+  virtual Variable<base::TimeDelta>* var_scatter_factor() = 0;
+
+  // Variable returning the set of connection types allowed for updates. The
+  // identifiers returned are consistent with the ones returned by the
+  // ShillProvider.
+  virtual Variable<std::set<chromeos_update_engine::ConnectionType>>*
+      var_allowed_connection_types_for_update() = 0;
+
+  // Variable stating the name of the device owner. For enterprise enrolled
+  // devices, this will be an empty string.
+  virtual Variable<std::string>* var_owner() = 0;
+
+  virtual Variable<bool>* var_http_downloads_enabled() = 0;
+
+  virtual Variable<bool>* var_au_p2p_enabled() = 0;
+
+  virtual Variable<bool>* var_allow_kiosk_app_control_chrome_version() = 0;
+
+ protected:
+  DevicePolicyProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DevicePolicyProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_engine/update_manager/evaluation_context-inl.h b/update_engine/update_manager/evaluation_context-inl.h
new file mode 100644
index 0000000..937adf4
--- /dev/null
+++ b/update_engine/update_manager/evaluation_context-inl.h
@@ -0,0 +1,55 @@
+//
+// 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_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
+
+#include <string>
+
+#include <base/logging.h>
+
+namespace chromeos_update_manager {
+
+template<typename T>
+const T* EvaluationContext::GetValue(Variable<T>* var) {
+  if (var == nullptr) {
+    LOG(ERROR) << "GetValue received an uninitialized variable.";
+    return nullptr;
+  }
+
+  // Search for the value on the cache first.
+  ValueCacheMap::iterator it = value_cache_.find(var);
+  if (it != value_cache_.end())
+    return reinterpret_cast<const T*>(it->second.value());
+
+  // Get the value from the variable if not found on the cache.
+  std::string errmsg;
+  const T* result = var->GetValue(RemainingTime(evaluation_monotonic_deadline_),
+                                  &errmsg);
+  if (result == nullptr) {
+    LOG(WARNING) << "Error reading Variable " << var->GetName() << ": \""
+        << errmsg << "\"";
+  }
+  // Cache the value for the next time. The map of CachedValues keeps the
+  // ownership of the pointer until the map is destroyed.
+  value_cache_.emplace(
+    static_cast<BaseVariable*>(var), BoxedValue(result));
+  return result;
+}
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
diff --git a/update_engine/update_manager/evaluation_context.cc b/update_engine/update_manager/evaluation_context.cc
new file mode 100644
index 0000000..63f7d9b
--- /dev/null
+++ b/update_engine/update_manager/evaluation_context.cc
@@ -0,0 +1,252 @@
+//
+// 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/update_manager/evaluation_context.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/json/json_writer.h>
+#include <base/location.h>
+#include <base/strings/string_util.h>
+#include <base/values.h>
+
+#include "update_engine/common/utils.h"
+
+using base::Callback;
+using base::Closure;
+using base::Time;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using chromeos_update_engine::ClockInterface;
+using std::string;
+using std::unique_ptr;
+
+namespace {
+
+// Returns whether |curr_time| surpassed |ref_time|; if not, also checks whether
+// |ref_time| is sooner than the current value of |*reeval_time|, in which case
+// the latter is updated to the former.
+bool IsTimeGreaterThanHelper(Time ref_time, Time curr_time,
+                             Time* reeval_time) {
+  if (curr_time > ref_time)
+    return true;
+  // Remember the nearest reference we've checked against in this evaluation.
+  if (*reeval_time > ref_time)
+    *reeval_time = ref_time;
+  return false;
+}
+
+// If |expires| never happens (maximal value), returns the maximal interval;
+// otherwise, returns the difference between |expires| and |curr|.
+TimeDelta GetTimeout(Time curr, Time expires) {
+  if (expires.is_max())
+    return TimeDelta::Max();
+  return expires - curr;
+}
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+EvaluationContext::EvaluationContext(
+    ClockInterface* clock,
+    TimeDelta evaluation_timeout,
+    TimeDelta expiration_timeout,
+    unique_ptr<Callback<void(EvaluationContext*)>> unregister_cb)
+    : clock_(clock),
+      evaluation_timeout_(evaluation_timeout),
+      expiration_timeout_(expiration_timeout),
+      unregister_cb_(std::move(unregister_cb)),
+      weak_ptr_factory_(this) {
+  ResetEvaluation();
+  ResetExpiration();
+}
+
+EvaluationContext::~EvaluationContext() {
+  RemoveObserversAndTimeout();
+  if (unregister_cb_.get())
+    unregister_cb_->Run(this);
+}
+
+unique_ptr<Closure> EvaluationContext::RemoveObserversAndTimeout() {
+  for (auto& it : value_cache_) {
+    if (it.first->GetMode() == kVariableModeAsync)
+      it.first->RemoveObserver(this);
+  }
+  MessageLoop::current()->CancelTask(timeout_event_);
+  timeout_event_ = MessageLoop::kTaskIdNull;
+
+  return std::move(callback_);
+}
+
+TimeDelta EvaluationContext::RemainingTime(Time monotonic_deadline) const {
+  if (monotonic_deadline.is_max())
+    return TimeDelta::Max();
+  TimeDelta remaining = monotonic_deadline - clock_->GetMonotonicTime();
+  return std::max(remaining, TimeDelta());
+}
+
+Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) {
+  return (timeout.is_max() ? Time::Max() :
+          clock_->GetMonotonicTime() + timeout);
+}
+
+void EvaluationContext::ValueChanged(BaseVariable* var) {
+  DLOG(INFO) << "ValueChanged() called for variable " << var->GetName();
+  OnValueChangedOrTimeout();
+}
+
+void EvaluationContext::OnTimeout() {
+  DLOG(INFO) << "OnTimeout() called due to "
+             << (timeout_marks_expiration_ ? "expiration" : "poll interval");
+  timeout_event_ = MessageLoop::kTaskIdNull;
+  is_expired_ = timeout_marks_expiration_;
+  OnValueChangedOrTimeout();
+}
+
+void EvaluationContext::OnValueChangedOrTimeout() {
+  // Copy the callback handle locally, allowing it to be reassigned.
+  unique_ptr<Closure> callback = RemoveObserversAndTimeout();
+
+  if (callback.get())
+    callback->Run();
+}
+
+bool EvaluationContext::IsWallclockTimeGreaterThan(Time timestamp) {
+  return IsTimeGreaterThanHelper(timestamp, evaluation_start_wallclock_,
+                                 &reevaluation_time_wallclock_);
+}
+
+bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) {
+  return IsTimeGreaterThanHelper(timestamp, evaluation_start_monotonic_,
+                                 &reevaluation_time_monotonic_);
+}
+
+void EvaluationContext::ResetEvaluation() {
+  evaluation_start_wallclock_ = clock_->GetWallclockTime();
+  evaluation_start_monotonic_ = clock_->GetMonotonicTime();
+  reevaluation_time_wallclock_ = Time::Max();
+  reevaluation_time_monotonic_ = Time::Max();
+  evaluation_monotonic_deadline_ = MonotonicDeadline(evaluation_timeout_);
+
+  // Remove the cached values of non-const variables
+  for (auto it = value_cache_.begin(); it != value_cache_.end(); ) {
+    if (it->first->GetMode() == kVariableModeConst) {
+      ++it;
+    } else {
+      it = value_cache_.erase(it);
+    }
+  }
+}
+
+void EvaluationContext::ResetExpiration() {
+  expiration_monotonic_deadline_ = MonotonicDeadline(expiration_timeout_);
+  is_expired_ = false;
+}
+
+bool EvaluationContext::RunOnValueChangeOrTimeout(Closure callback) {
+  // Check that the method was not called more than once.
+  if (callback_.get()) {
+    LOG(ERROR) << "RunOnValueChangeOrTimeout called more than once.";
+    return false;
+  }
+
+  // Check that the context did not yet expire.
+  if (is_expired()) {
+    LOG(ERROR) << "RunOnValueChangeOrTimeout called on an expired context.";
+    return false;
+  }
+
+  // Handle reevaluation due to a Is{Wallclock,Monotonic}TimeGreaterThan(). We
+  // choose the smaller of the differences between evaluation start time and
+  // reevaluation time among the wallclock and monotonic scales.
+  TimeDelta timeout = std::min(
+      GetTimeout(evaluation_start_wallclock_, reevaluation_time_wallclock_),
+      GetTimeout(evaluation_start_monotonic_, reevaluation_time_monotonic_));
+
+  // Handle reevaluation due to async or poll variables.
+  bool waiting_for_value_change = false;
+  for (auto& it : value_cache_) {
+    switch (it.first->GetMode()) {
+      case kVariableModeAsync:
+        DLOG(INFO) << "Waiting for value on " << it.first->GetName();
+        it.first->AddObserver(this);
+        waiting_for_value_change = true;
+        break;
+      case kVariableModePoll:
+        timeout = std::min(timeout, it.first->GetPollInterval());
+        break;
+      case kVariableModeConst:
+        // Ignored.
+        break;
+    }
+  }
+
+  // Check if the re-evaluation is actually being scheduled. If there are no
+  // events waited for, this function should return false.
+  if (!waiting_for_value_change && timeout.is_max())
+    return false;
+
+  // Ensure that we take into account the expiration timeout.
+  TimeDelta expiration = RemainingTime(expiration_monotonic_deadline_);
+  timeout_marks_expiration_ = expiration < timeout;
+  if (timeout_marks_expiration_)
+    timeout = expiration;
+
+  // Store the reevaluation callback.
+  callback_.reset(new Closure(callback));
+
+  // Schedule a timeout event, if one is set.
+  if (!timeout.is_max()) {
+    DLOG(INFO) << "Waiting for timeout in "
+               << chromeos_update_engine::utils::FormatTimeDelta(timeout);
+    timeout_event_ = MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&EvaluationContext::OnTimeout,
+                   weak_ptr_factory_.GetWeakPtr()),
+        timeout);
+  }
+
+  return true;
+}
+
+string EvaluationContext::DumpContext() const {
+  base::DictionaryValue* variables = new base::DictionaryValue();
+  for (auto& it : value_cache_) {
+    variables->SetString(it.first->GetName(), it.second.ToString());
+  }
+
+  base::DictionaryValue value;
+  value.Set("variables", variables);  // Adopts |variables|.
+  value.SetString(
+      "evaluation_start_wallclock",
+      chromeos_update_engine::utils::ToString(evaluation_start_wallclock_));
+  value.SetString(
+      "evaluation_start_monotonic",
+      chromeos_update_engine::utils::ToString(evaluation_start_monotonic_));
+
+  string json_str;
+  base::JSONWriter::WriteWithOptions(
+      value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_str);
+  base::TrimWhitespaceASCII(json_str, base::TRIM_TRAILING, &json_str);
+
+  return json_str;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/evaluation_context.h b/update_engine/update_manager/evaluation_context.h
new file mode 100644
index 0000000..df5816a
--- /dev/null
+++ b/update_engine/update_manager/evaluation_context.h
@@ -0,0 +1,221 @@
+//
+// 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_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/weak_ptr.h>
+#include <base/time/time.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/update_manager/boxed_value.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// The EvaluationContext class is the interface between a policy implementation
+// and the state. The EvaluationContext tracks the variables used by a policy
+// request and caches the returned values, owning those cached values.
+// The same EvaluationContext should be re-used for all the evaluations of the
+// same policy request (an AsyncPolicyRequest might involve several
+// re-evaluations). Each evaluation of the EvaluationContext is run at a given
+// point in time, which is used as a reference for the evaluation timeout and
+// the time based queries of the policy, such as
+// Is{Wallclock,Monotonic}TimeGreaterThan().
+//
+// Example:
+//
+//   scoped_refptr<EvaluationContext> ec = new EvaluationContext(...);
+//
+//   ...
+//   // The following call to ResetEvaluation() is optional. Use it to reset the
+//   // evaluation time if the EvaluationContext isn't used right after its
+//   // construction.
+//   ec->ResetEvaluation();
+//   EvalStatus status = policy->SomeMethod(ec, state, &result, args...);
+//
+//   ...
+//   // Run a closure when any of the used async variables changes its value or
+//   // the timeout for re-query the values happens again.
+//   ec->RunOnValueChangeOrTimeout(closure);
+//   // If the provided |closure| wants to re-evaluate the policy, it should
+//   // call ec->ResetEvaluation() to start a new evaluation.
+//
+class EvaluationContext : public base::RefCounted<EvaluationContext>,
+                          private BaseVariable::ObserverInterface {
+ public:
+  EvaluationContext(
+      chromeos_update_engine::ClockInterface* clock,
+      base::TimeDelta evaluation_timeout,
+      base::TimeDelta expiration_timeout,
+      std::unique_ptr<base::Callback<void(EvaluationContext*)>> unregister_cb);
+  EvaluationContext(chromeos_update_engine::ClockInterface* clock,
+                    base::TimeDelta evaluation_timeout)
+      : EvaluationContext(
+          clock, evaluation_timeout, base::TimeDelta::Max(),
+          std::unique_ptr<base::Callback<void(EvaluationContext*)>>()) {}
+  ~EvaluationContext();
+
+  // Returns a pointer to the value returned by the passed variable |var|. The
+  // EvaluationContext instance keeps the ownership of the returned object. The
+  // returned object is valid during the life of the evaluation, even if the
+  // passed Variable changes it.
+  //
+  // In case of error, a null value is returned.
+  template<typename T>
+  const T* GetValue(Variable<T>* var);
+
+  // Returns whether the evaluation time has surpassed |timestamp|, on either
+  // the ClockInterface::GetWallclockTime() or
+  // ClockInterface::GetMonotonicTime() scales, respectively.
+  bool IsWallclockTimeGreaterThan(base::Time timestamp);
+  bool IsMonotonicTimeGreaterThan(base::Time timestamp);
+
+  // Returns whether the evaluation context has expired.
+  bool is_expired() const { return is_expired_; }
+
+  // TODO(deymo): Move the following methods to an interface only visible by the
+  // UpdateManager class and not the policy implementations.
+
+  // Resets the EvaluationContext to its initial state removing all the
+  // non-const cached variables and re-setting the evaluation time. This should
+  // be called right before any new evaluation starts.
+  void ResetEvaluation();
+
+  // Clears the expiration status of the EvaluationContext and resets its
+  // expiration timeout based on |expiration_timeout_|. This should be called if
+  // expiration occurred, prior to re-evaluating the policy.
+  void ResetExpiration();
+
+  // Schedules the passed |callback| closure to be called when a cached
+  // variable changes its value, a polling interval passes, or the context
+  // expiration occurs. If none of these events can happen, for example if
+  // there's no cached variable, this method returns false.
+  //
+  // Right before the passed closure is called the EvaluationContext is
+  // reseted, removing all the non-const cached values.
+  bool RunOnValueChangeOrTimeout(base::Closure callback);
+
+  // Returns a textual representation of the evaluation context,
+  // including the variables and their values. This is intended only
+  // to help with debugging and the format may change in the future.
+  std::string DumpContext() const;
+
+  // Removes all the Observers callbacks and timeout events scheduled by
+  // RunOnValueChangeOrTimeout(). Also releases and returns the closure
+  // associated with these events. This method is idempotent.
+  std::unique_ptr<base::Closure> RemoveObserversAndTimeout();
+
+ private:
+  friend class UmEvaluationContextTest;
+
+  // BaseVariable::ObserverInterface override.
+  void ValueChanged(BaseVariable* var) override;
+
+  // Called from the main loop when a scheduled timeout has passed.
+  void OnTimeout();
+
+  // Removes the observers from the used Variables and cancels the timeout,
+  // then executes the scheduled callback.
+  void OnValueChangedOrTimeout();
+
+  // If |monotonic_deadline| is not Time::Max(), returns the remaining time
+  // until it is reached, or zero if it has passed. Otherwise, returns
+  // TimeDelta::Max().
+  base::TimeDelta RemainingTime(base::Time monotonic_deadline) const;
+
+  // Returns a monotonic clock timestamp at which |timeout| will have elapsed
+  // since the current time.
+  base::Time MonotonicDeadline(base::TimeDelta timeout);
+
+  // A map to hold the cached values for every variable.
+  typedef std::map<BaseVariable*, BoxedValue> ValueCacheMap;
+
+  // The cached values of the called Variables.
+  ValueCacheMap value_cache_;
+
+  // A callback used for triggering re-evaluation upon a value change or poll
+  // timeout, or notifying about the evaluation context expiration. It is up to
+  // the caller to determine whether or not expiration occurred via
+  // is_expired().
+  std::unique_ptr<base::Closure> callback_;
+
+  // The TaskId returned by the message loop identifying the timeout callback.
+  // Used for canceling the timeout callback.
+  brillo::MessageLoop::TaskId timeout_event_ =
+      brillo::MessageLoop::kTaskIdNull;
+
+  // Whether a timeout event firing marks the expiration of the evaluation
+  // context.
+  bool timeout_marks_expiration_;
+
+  // Whether the evaluation context has indeed expired.
+  bool is_expired_ = false;
+
+  // Pointer to the mockable clock interface;
+  chromeos_update_engine::ClockInterface* const clock_;
+
+  // The timestamps when the evaluation of this EvaluationContext started,
+  // corresponding to ClockInterface::GetWallclockTime() and
+  // ClockInterface::GetMonotonicTime(), respectively. These values are reset
+  // every time ResetEvaluation() is called.
+  base::Time evaluation_start_wallclock_;
+  base::Time evaluation_start_monotonic_;
+
+  // The timestamps when a reevaluation should be triggered due to various
+  // expected value changes, corresponding to ClockInterface::GetWallclockTime()
+  // and ClockInterface::GetMonotonicTIme(), respectively. These timestamps are
+  // greater or equal to corresponding |evaluation_start_{wallclock,monotonic}_|
+  // counterparts since they are in the future; however, they may be smaller
+  // than the current corresponding times during the course of evaluation.
+  base::Time reevaluation_time_wallclock_;
+  base::Time reevaluation_time_monotonic_;
+
+  // The timeout of an evaluation.
+  const base::TimeDelta evaluation_timeout_;
+
+  // The timestamp in the ClockInterface::GetMonotonicTime() scale at which the
+  // current evaluation should finish.
+  base::Time evaluation_monotonic_deadline_;
+
+  // The expiration timeout of the evaluation context.
+  const base::TimeDelta expiration_timeout_;
+
+  // The monotonic clock deadline at which expiration occurs.
+  base::Time expiration_monotonic_deadline_;
+
+  // A callback for unregistering the context upon destruction.
+  std::unique_ptr<base::Callback<void(EvaluationContext*)>> unregister_cb_;
+
+  base::WeakPtrFactory<EvaluationContext> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(EvaluationContext);
+};
+
+}  // namespace chromeos_update_manager
+
+// Include the implementation of the template methods.
+#include "update_engine/update_manager/evaluation_context-inl.h"
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
diff --git a/update_engine/update_manager/evaluation_context_unittest.cc b/update_engine/update_manager/evaluation_context_unittest.cc
new file mode 100644
index 0000000..1e61db7
--- /dev/null
+++ b/update_engine/update_manager/evaluation_context_unittest.cc
@@ -0,0 +1,489 @@
+//
+// 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/update_manager/evaluation_context.h"
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/mock_variable.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Bind;
+using base::Closure;
+using base::Time;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using brillo::MessageLoopRunMaxIterations;
+using brillo::MessageLoopRunUntil;
+using chromeos_update_engine::FakeClock;
+using std::string;
+using std::unique_ptr;
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+namespace chromeos_update_manager {
+
+namespace {
+
+void DoNothing() {}
+
+// Sets the value of the passed pointer to true.
+void SetTrue(bool* value) {
+  *value = true;
+}
+
+bool GetBoolean(bool* value) {
+  return *value;
+}
+
+template<typename T>
+void ReadVar(scoped_refptr<EvaluationContext> ec, Variable<T>* var) {
+  ec->GetValue(var);
+}
+
+// Runs |evaluation|; if the value pointed by |count_p| is greater than zero,
+// decrement it and schedule a reevaluation; otherwise, writes true to |done_p|.
+void EvaluateRepeatedly(Closure evaluation, scoped_refptr<EvaluationContext> ec,
+                        int* count_p, bool* done_p) {
+  evaluation.Run();
+
+  // Schedule reevaluation if needed.
+  if (*count_p > 0) {
+    Closure closure = Bind(EvaluateRepeatedly, evaluation, ec, count_p, done_p);
+    ASSERT_TRUE(ec->RunOnValueChangeOrTimeout(closure))
+        << "Failed to schedule reevaluation, count_p=" << *count_p;
+    (*count_p)--;
+  } else {
+    *done_p = true;
+  }
+}
+
+}  // namespace
+
+class UmEvaluationContextTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    // Apr 22, 2009 19:25:00 UTC (this is a random reference point).
+    fake_clock_.SetMonotonicTime(Time::FromTimeT(1240428300));
+    // Mar 2, 2006 1:23:45 UTC.
+    fake_clock_.SetWallclockTime(Time::FromTimeT(1141262625));
+    eval_ctx_ = new EvaluationContext(
+        &fake_clock_, default_timeout_, default_timeout_,
+        unique_ptr<base::Callback<void(EvaluationContext*)>>(nullptr));
+  }
+
+  void TearDown() override {
+    // Ensure that the evaluation context did not leak and is actually being
+    // destroyed.
+    if (eval_ctx_) {
+      base::WeakPtr<EvaluationContext> eval_ctx_weak_alias =
+          eval_ctx_->weak_ptr_factory_.GetWeakPtr();
+      ASSERT_NE(nullptr, eval_ctx_weak_alias.get());
+      eval_ctx_ = nullptr;
+      EXPECT_EQ(nullptr, eval_ctx_weak_alias.get())
+          << "The evaluation context was not destroyed! This is likely a bug "
+             "in how the test was written, look for leaking handles to the EC, "
+             "possibly through closure objects.";
+    }
+
+    // Check that the evaluation context removed all the observers.
+    EXPECT_TRUE(fake_int_var_.observer_list_.empty());
+    EXPECT_TRUE(fake_async_var_.observer_list_.empty());
+    EXPECT_TRUE(fake_const_var_.observer_list_.empty());
+    EXPECT_TRUE(fake_poll_var_.observer_list_.empty());
+
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  TimeDelta default_timeout_ = TimeDelta::FromSeconds(5);
+
+  brillo::FakeMessageLoop loop_{nullptr};
+  FakeClock fake_clock_;
+  scoped_refptr<EvaluationContext> eval_ctx_;
+
+  // FakeVariables used for testing the EvaluationContext. These are required
+  // here to prevent them from going away *before* the EvaluationContext under
+  // test does, which keeps a reference to them.
+  FakeVariable<bool> fail_var_ = {"fail_var", kVariableModePoll};
+  FakeVariable<int> fake_int_var_ = {"fake_int", kVariableModePoll};
+  FakeVariable<string> fake_async_var_ = {"fake_async", kVariableModeAsync};
+  FakeVariable<string> fake_const_var_ = {"fake_const", kVariableModeConst};
+  FakeVariable<string> fake_poll_var_ = {"fake_poll",
+                                         TimeDelta::FromSeconds(1)};
+  StrictMock<MockVariable<string>> mock_var_async_ {
+    "mock_var_async", kVariableModeAsync};
+  StrictMock<MockVariable<string>> mock_var_poll_ {
+    "mock_var_poll", kVariableModePoll};
+};
+
+TEST_F(UmEvaluationContextTest, GetValueFails) {
+  // FakeVariable is initialized as returning null.
+  EXPECT_EQ(nullptr, eval_ctx_->GetValue(&fake_int_var_));
+}
+
+TEST_F(UmEvaluationContextTest, GetValueFailsWithInvalidVar) {
+  EXPECT_EQ(nullptr, eval_ctx_->GetValue(static_cast<Variable<int>*>(nullptr)));
+}
+
+TEST_F(UmEvaluationContextTest, GetValueReturns) {
+  const int* p_fake_int;
+
+  fake_int_var_.reset(new int(42));
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  ASSERT_NE(nullptr, p_fake_int);
+  EXPECT_EQ(42, *p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueCached) {
+  const int* p_fake_int;
+
+  fake_int_var_.reset(new int(42));
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+
+  // Check that if the variable changes, the EvaluationContext keeps returning
+  // the cached value.
+  fake_int_var_.reset(new int(5));
+
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  ASSERT_NE(nullptr, p_fake_int);
+  EXPECT_EQ(42, *p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueCachesNull) {
+  const int* p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  EXPECT_EQ(nullptr, p_fake_int);
+
+  fake_int_var_.reset(new int(42));
+  // A second attempt to read the variable should not work because this
+  // EvaluationContext already got a null value.
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  EXPECT_EQ(nullptr, p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueMixedTypes) {
+  const int* p_fake_int;
+  const string* p_fake_string;
+
+  fake_int_var_.reset(new int(42));
+  fake_poll_var_.reset(new string("Hello world!"));
+  // Check that the EvaluationContext can handle multiple Variable types. This
+  // is mostly a compile-time check due to the template nature of this method.
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  p_fake_string = eval_ctx_->GetValue(&fake_poll_var_);
+
+  ASSERT_NE(nullptr, p_fake_int);
+  EXPECT_EQ(42, *p_fake_int);
+
+  ASSERT_NE(nullptr, p_fake_string);
+  EXPECT_EQ("Hello world!", *p_fake_string);
+}
+
+// Test that we don't schedule an event if there's no variable to wait for.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithoutVariables) {
+  fake_const_var_.reset(new string("Hello world!"));
+  EXPECT_EQ(*eval_ctx_->GetValue(&fake_const_var_), "Hello world!");
+
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+// Test that reevaluation occurs when an async variable it depends on changes.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithVariables) {
+  fake_async_var_.reset(new string("Async value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  // Check that the scheduled callback isn't run until we signal a ValueChaged.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_FALSE(value);
+
+  fake_async_var_.NotifyValueChanged();
+  EXPECT_FALSE(value);
+  // Ensure that the scheduled callback isn't run until we are back on the main
+  // loop.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_TRUE(value);
+}
+
+// Test that we don't re-schedule the events if we are attending one.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutCalledTwice) {
+  fake_async_var_.reset(new string("Async value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+
+  // The scheduled event should still work.
+  fake_async_var_.NotifyValueChanged();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_TRUE(value);
+}
+
+// Test that reevaluation occurs when a polling timeout fires.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutRunsFromTimeout) {
+  fake_poll_var_.reset(new string("Polled value"));
+  eval_ctx_->GetValue(&fake_poll_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  // Check that the scheduled callback isn't run until the timeout occurs.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_FALSE(value);
+  MessageLoopRunUntil(MessageLoop::current(),
+                      TimeDelta::FromSeconds(10),
+                      Bind(&GetBoolean, &value));
+  EXPECT_TRUE(value);
+}
+
+// Test that callback is called when evaluation context expires, and that it
+// cannot be used again unless the expiration deadline is reset.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutExpires) {
+  fake_async_var_.reset(new string("Async value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  // Check that the scheduled callback isn't run until the timeout occurs.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_FALSE(value);
+  MessageLoopRunUntil(MessageLoop::current(),
+                      TimeDelta::FromSeconds(10),
+                      Bind(&GetBoolean, &value));
+  EXPECT_TRUE(value);
+
+  // Ensure that we cannot reschedule an evaluation.
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+
+  // Ensure that we can reschedule an evaluation after resetting expiration.
+  eval_ctx_->ResetExpiration();
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+// Test that we clear the events when destroying the EvaluationContext.
+TEST_F(UmEvaluationContextTest, RemoveObserversAndTimeoutTest) {
+  fake_async_var_.reset(new string("Async value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  eval_ctx_ = nullptr;
+
+  // This should not trigger the callback since the EvaluationContext waiting
+  // for it is gone, and it should have remove all its observers.
+  fake_async_var_.NotifyValueChanged();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_FALSE(value);
+}
+
+// Scheduling two reevaluations from the callback should succeed.
+TEST_F(UmEvaluationContextTest,
+       RunOnValueChangeOrTimeoutReevaluatesRepeatedly) {
+  fake_poll_var_.reset(new string("Polled value"));
+  Closure evaluation = Bind(ReadVar<string>, eval_ctx_, &fake_poll_var_);
+  int num_reevaluations = 2;
+  bool done = false;
+
+  // Run the evaluation once.
+  evaluation.Run();
+
+  // Schedule repeated reevaluations.
+  Closure closure = Bind(EvaluateRepeatedly, evaluation, eval_ctx_,
+                         &num_reevaluations, &done);
+  ASSERT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(closure));
+  MessageLoopRunUntil(MessageLoop::current(),
+                      TimeDelta::FromSeconds(10),
+                      Bind(&GetBoolean, &done));
+  EXPECT_EQ(0, num_reevaluations);
+}
+
+// Test that we can delete the EvaluationContext while having pending events.
+TEST_F(UmEvaluationContextTest, ObjectDeletedWithPendingEventsTest) {
+  fake_async_var_.reset(new string("Async value"));
+  fake_poll_var_.reset(new string("Polled value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+  eval_ctx_->GetValue(&fake_poll_var_);
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+  // TearDown() checks for leaked observers on this async_variable, which means
+  // that our object is still alive after removing its reference.
+}
+
+// Test that timed events fired after removal of the EvaluationContext don't
+// crash.
+TEST_F(UmEvaluationContextTest, TimeoutEventAfterDeleteTest) {
+  FakeVariable<string> fake_short_poll_var = {"fake_short_poll",
+                                              TimeDelta::FromSeconds(1)};
+  fake_short_poll_var.reset(new string("Polled value"));
+  eval_ctx_->GetValue(&fake_short_poll_var);
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  // Remove the last reference to the EvaluationContext and run the loop for
+  // 10 seconds to give time to the main loop to trigger the timeout Event (of 1
+  // second). Our callback should not be called because the EvaluationContext
+  // was removed before the timeout event is attended.
+  eval_ctx_ = nullptr;
+  MessageLoopRunUntil(MessageLoop::current(),
+                      TimeDelta::FromSeconds(10),
+                      Bind(&GetBoolean, &value));
+  EXPECT_FALSE(value);
+}
+
+TEST_F(UmEvaluationContextTest, DefaultTimeout) {
+  // Test that the evaluation timeout calculation uses the default timeout on
+  // setup.
+  EXPECT_CALL(mock_var_async_, GetValue(default_timeout_, _))
+      .WillOnce(Return(nullptr));
+  EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
+}
+
+TEST_F(UmEvaluationContextTest, TimeoutUpdatesWithMonotonicTime) {
+  fake_clock_.SetMonotonicTime(
+      fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1));
+
+  TimeDelta timeout = default_timeout_ - TimeDelta::FromSeconds(1);
+
+  EXPECT_CALL(mock_var_async_, GetValue(timeout, _))
+      .WillOnce(Return(nullptr));
+  EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
+}
+
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesWallclock) {
+  Time cur_time = fake_clock_.GetWallclockTime();
+  // Advance the time on the clock but don't call ResetEvaluation yet.
+  fake_clock_.SetWallclockTime(cur_time + TimeDelta::FromSeconds(4));
+
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+  // Call ResetEvaluation now, which should use the new evaluation time.
+  eval_ctx_->ResetEvaluation();
+
+  cur_time = fake_clock_.GetWallclockTime();
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+}
+
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesMonotonic) {
+  Time cur_time = fake_clock_.GetMonotonicTime();
+  // Advance the time on the clock but don't call ResetEvaluation yet.
+  fake_clock_.SetMonotonicTime(cur_time + TimeDelta::FromSeconds(4));
+
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+  // Call ResetEvaluation now, which should use the new evaluation time.
+  eval_ctx_->ResetEvaluation();
+
+  cur_time = fake_clock_.GetMonotonicTime();
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsWallclockTimeGreaterThanSignalsTriggerReevaluation) {
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(1)));
+
+  // The "false" from IsWallclockTimeGreaterThan means that's not that timestamp
+  // yet, so this should schedule a callback for when that happens.
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsMonotonicTimeGreaterThanSignalsTriggerReevaluation) {
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+      fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1)));
+
+  // The "false" from IsMonotonicTimeGreaterThan means that's not that timestamp
+  // yet, so this should schedule a callback for when that happens.
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsWallclockTimeGreaterThanDoesntRecordPastTimestamps) {
+  // IsWallclockTimeGreaterThan() should ignore timestamps on the past for
+  // reevaluation.
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+      fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(20)));
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+      fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1)));
+
+  // Callback should not be scheduled.
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsMonotonicTimeGreaterThanDoesntRecordPastTimestamps) {
+  // IsMonotonicTimeGreaterThan() should ignore timestamps on the past for
+  // reevaluation.
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+      fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(20)));
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+      fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(1)));
+
+  // Callback should not be scheduled.
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest, DumpContext) {
+  // |fail_var_| yield "(no value)" since it is unset.
+  eval_ctx_->GetValue(&fail_var_);
+
+  // Check that this is included.
+  fake_int_var_.reset(new int(42));
+  eval_ctx_->GetValue(&fake_int_var_);
+
+  // Check that double-quotes are escaped properly.
+  fake_poll_var_.reset(new string("Hello \"world\"!"));
+  eval_ctx_->GetValue(&fake_poll_var_);
+
+  // Note that the variables are printed in alphabetical order. Also
+  // see UmEvaluationContextText::SetUp() where the values used for
+  // |evaluation_start_{monotonic,wallclock| are set.
+  EXPECT_EQ("{\n"
+            "   \"evaluation_start_monotonic\": \"4/22/2009 19:25:00 GMT\",\n"
+            "   \"evaluation_start_wallclock\": \"3/2/2006 1:23:45 GMT\",\n"
+            "   \"variables\": {\n"
+            "      \"fail_var\": \"(no value)\",\n"
+            "      \"fake_int\": \"42\",\n"
+            "      \"fake_poll\": \"Hello \\\"world\\\"!\"\n"
+            "   }\n"
+            "}",
+            eval_ctx_->DumpContext());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/fake_config_provider.h b/update_engine/update_manager/fake_config_provider.h
new file mode 100644
index 0000000..6a324df
--- /dev/null
+++ b/update_engine/update_manager/fake_config_provider.h
@@ -0,0 +1,43 @@
+//
+// 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_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
+
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/fake_variable.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the ConfigProvider base class.
+class FakeConfigProvider : public ConfigProvider {
+ public:
+  FakeConfigProvider() {}
+
+  FakeVariable<bool>* var_is_oobe_enabled() override {
+    return &var_is_oobe_enabled_;
+  }
+
+ private:
+  FakeVariable<bool> var_is_oobe_enabled_{  // NOLINT(whitespace/braces)
+      "is_oobe_enabled", kVariableModeConst};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeConfigProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
diff --git a/update_engine/update_manager/fake_device_policy_provider.h b/update_engine/update_manager/fake_device_policy_provider.h
new file mode 100644
index 0000000..9e4f5b7
--- /dev/null
+++ b/update_engine/update_manager/fake_device_policy_provider.h
@@ -0,0 +1,106 @@
+//
+// 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_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/fake_variable.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the DevicePolicyProvider base class.
+class FakeDevicePolicyProvider : public DevicePolicyProvider {
+ public:
+  FakeDevicePolicyProvider() {}
+
+  FakeVariable<bool>* var_device_policy_is_loaded() override {
+    return &var_device_policy_is_loaded_;
+  }
+
+  FakeVariable<std::string>* var_release_channel() override {
+    return &var_release_channel_;
+  }
+
+  FakeVariable<bool>* var_release_channel_delegated() override {
+    return &var_release_channel_delegated_;
+  }
+
+  FakeVariable<bool>* var_update_disabled() override {
+    return &var_update_disabled_;
+  }
+
+  FakeVariable<std::string>* var_target_version_prefix() override {
+    return &var_target_version_prefix_;
+  }
+
+  FakeVariable<base::TimeDelta>* var_scatter_factor() override {
+    return &var_scatter_factor_;
+  }
+
+  FakeVariable<std::set<chromeos_update_engine::ConnectionType>>*
+      var_allowed_connection_types_for_update() override {
+    return &var_allowed_connection_types_for_update_;
+  }
+
+  FakeVariable<std::string>* var_owner() override {
+    return &var_owner_;
+  }
+
+  FakeVariable<bool>* var_http_downloads_enabled() override {
+    return &var_http_downloads_enabled_;
+  }
+
+  FakeVariable<bool>* var_au_p2p_enabled() override {
+    return &var_au_p2p_enabled_;
+  }
+
+  FakeVariable<bool>* var_allow_kiosk_app_control_chrome_version() override {
+    return &var_allow_kiosk_app_control_chrome_version_;
+  }
+
+ private:
+  FakeVariable<bool> var_device_policy_is_loaded_{
+      "policy_is_loaded", kVariableModePoll};
+  FakeVariable<std::string> var_release_channel_{
+      "release_channel", kVariableModePoll};
+  FakeVariable<bool> var_release_channel_delegated_{
+      "release_channel_delegated", kVariableModePoll};
+  FakeVariable<bool> var_update_disabled_{
+      "update_disabled", kVariableModePoll};
+  FakeVariable<std::string> var_target_version_prefix_{
+      "target_version_prefix", kVariableModePoll};
+  FakeVariable<base::TimeDelta> var_scatter_factor_{
+      "scatter_factor", kVariableModePoll};
+  FakeVariable<std::set<chromeos_update_engine::ConnectionType>>
+      var_allowed_connection_types_for_update_{
+          "allowed_connection_types_for_update", kVariableModePoll};
+  FakeVariable<std::string> var_owner_{"owner", kVariableModePoll};
+  FakeVariable<bool> var_http_downloads_enabled_{
+      "http_downloads_enabled", kVariableModePoll};
+  FakeVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled", kVariableModePoll};
+  FakeVariable<bool> var_allow_kiosk_app_control_chrome_version_{
+      "allow_kiosk_app_control_chrome_version", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeDevicePolicyProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_engine/update_manager/fake_random_provider.h b/update_engine/update_manager/fake_random_provider.h
new file mode 100644
index 0000000..643a194
--- /dev/null
+++ b/update_engine/update_manager/fake_random_provider.h
@@ -0,0 +1,40 @@
+//
+// 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_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/random_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the RandomProvider base class.
+class FakeRandomProvider : public RandomProvider {
+ public:
+  FakeRandomProvider() {}
+
+  FakeVariable<uint64_t>* var_seed() override { return &var_seed_; }
+
+ private:
+  FakeVariable<uint64_t> var_seed_{"seed", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeRandomProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
diff --git a/update_engine/update_manager/fake_shill_provider.h b/update_engine/update_manager/fake_shill_provider.h
new file mode 100644
index 0000000..7f1c8f5
--- /dev/null
+++ b/update_engine/update_manager/fake_shill_provider.h
@@ -0,0 +1,62 @@
+//
+// 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_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the ShillProvider base class.
+class FakeShillProvider : public ShillProvider {
+ public:
+  FakeShillProvider() {}
+
+  FakeVariable<bool>* var_is_connected() override {
+    return &var_is_connected_;
+  }
+
+  FakeVariable<chromeos_update_engine::ConnectionType>* var_conn_type()
+      override {
+    return &var_conn_type_;
+  }
+
+  FakeVariable<chromeos_update_engine::ConnectionTethering>*
+      var_conn_tethering() override {
+    return &var_conn_tethering_;
+  }
+
+  FakeVariable<base::Time>* var_conn_last_changed() override {
+    return &var_conn_last_changed_;
+  }
+
+ private:
+  FakeVariable<bool> var_is_connected_{"is_connected", kVariableModePoll};
+  FakeVariable<chromeos_update_engine::ConnectionType> var_conn_type_{
+      "conn_type", kVariableModePoll};
+  FakeVariable<chromeos_update_engine::ConnectionTethering> var_conn_tethering_{
+      "conn_tethering", kVariableModePoll};
+  FakeVariable<base::Time> var_conn_last_changed_{
+      "conn_last_changed", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeShillProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
diff --git a/update_engine/update_manager/fake_state.h b/update_engine/update_manager/fake_state.h
new file mode 100644
index 0000000..fd7a88c
--- /dev/null
+++ b/update_engine/update_manager/fake_state.h
@@ -0,0 +1,91 @@
+//
+// 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_UPDATE_MANAGER_FAKE_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_STATE_H_
+
+#include "update_engine/update_manager/fake_config_provider.h"
+#include "update_engine/update_manager/fake_device_policy_provider.h"
+#include "update_engine/update_manager/fake_random_provider.h"
+#include "update_engine/update_manager/fake_shill_provider.h"
+#include "update_engine/update_manager/fake_system_provider.h"
+#include "update_engine/update_manager/fake_time_provider.h"
+#include "update_engine/update_manager/fake_updater_provider.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// A fake State class that creates fake providers for all the providers.
+// This fake can be used in unit testing of Policy subclasses. To fake out the
+// value a variable is exposing, just call FakeVariable<T>::SetValue() on the
+// variable you fake out. For example:
+//
+//   FakeState fake_state_;
+//   fake_state_.random_provider_->var_seed()->SetValue(new uint64_t(12345));
+//
+// You can call SetValue more than once and the FakeVariable will take care of
+// the memory, but only the last value will remain.
+class FakeState : public State {
+ public:
+  // Creates and initializes the FakeState using fake providers.
+  FakeState() {}
+
+  ~FakeState() override {}
+
+  // Downcasted getters to access the fake instances during testing.
+  FakeConfigProvider* config_provider() override {
+    return &config_provider_;
+  }
+
+  FakeDevicePolicyProvider* device_policy_provider() override {
+    return &device_policy_provider_;
+  }
+
+  FakeRandomProvider* random_provider() override {
+    return &random_provider_;
+  }
+
+  FakeShillProvider* shill_provider() override {
+    return &shill_provider_;
+  }
+
+  FakeSystemProvider* system_provider() override {
+    return &system_provider_;
+  }
+
+  FakeTimeProvider* time_provider() override {
+    return &time_provider_;
+  }
+
+  FakeUpdaterProvider* updater_provider() override {
+    return &updater_provider_;
+  }
+
+ private:
+  FakeConfigProvider config_provider_;
+  FakeDevicePolicyProvider device_policy_provider_;
+  FakeRandomProvider random_provider_;
+  FakeShillProvider shill_provider_;
+  FakeSystemProvider system_provider_;
+  FakeTimeProvider time_provider_;
+  FakeUpdaterProvider updater_provider_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeState);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_STATE_H_
diff --git a/update_engine/update_manager/fake_system_provider.h b/update_engine/update_manager/fake_system_provider.h
new file mode 100644
index 0000000..0f4dff4
--- /dev/null
+++ b/update_engine/update_manager/fake_system_provider.h
@@ -0,0 +1,66 @@
+//
+// 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_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/system_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the SystemProvider base class.
+class FakeSystemProvider : public SystemProvider {
+ public:
+  FakeSystemProvider() {}
+
+  FakeVariable<bool>* var_is_normal_boot_mode() override {
+    return &var_is_normal_boot_mode_;
+  }
+
+  FakeVariable<bool>* var_is_official_build() override {
+    return &var_is_official_build_;
+  }
+
+  FakeVariable<bool>* var_is_oobe_complete() override {
+    return &var_is_oobe_complete_;
+  }
+
+  FakeVariable<unsigned int>* var_num_slots() override {
+    return &var_num_slots_;
+  }
+
+  FakeVariable<std::string>* var_kiosk_required_platform_version() override {
+    return &var_kiosk_required_platform_version_;
+  }
+
+ private:
+  FakeVariable<bool> var_is_normal_boot_mode_{  // NOLINT(whitespace/braces)
+    "is_normal_boot_mode", kVariableModeConst};
+  FakeVariable<bool> var_is_official_build_{  // NOLINT(whitespace/braces)
+    "is_official_build", kVariableModeConst};
+  FakeVariable<bool> var_is_oobe_complete_{  // NOLINT(whitespace/braces)
+    "is_oobe_complete", kVariableModePoll};
+  FakeVariable<unsigned int> var_num_slots_{"num_slots", kVariableModePoll};
+  FakeVariable<std::string> var_kiosk_required_platform_version_{
+      "kiosk_required_platform_version", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeSystemProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
diff --git a/update_engine/update_manager/fake_time_provider.h b/update_engine/update_manager/fake_time_provider.h
new file mode 100644
index 0000000..2aea2e7
--- /dev/null
+++ b/update_engine/update_manager/fake_time_provider.h
@@ -0,0 +1,42 @@
+//
+// 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_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/time_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the TimeProvider base class.
+class FakeTimeProvider : public TimeProvider {
+ public:
+  FakeTimeProvider() {}
+
+  FakeVariable<base::Time>* var_curr_date() override { return &var_curr_date_; }
+  FakeVariable<int>* var_curr_hour() override { return &var_curr_hour_; }
+
+ private:
+  FakeVariable<base::Time> var_curr_date_{"curr_date", kVariableModePoll};
+  FakeVariable<int> var_curr_hour_{"curr_hour", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeTimeProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
diff --git a/update_engine/update_manager/fake_update_manager.h b/update_engine/update_manager/fake_update_manager.h
new file mode 100644
index 0000000..2ea00b6
--- /dev/null
+++ b/update_engine/update_manager/fake_update_manager.h
@@ -0,0 +1,49 @@
+//
+// 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_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
+
+#include "update_engine/update_manager/update_manager.h"
+
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/fake_state.h"
+
+namespace chromeos_update_manager {
+
+class FakeUpdateManager : public UpdateManager {
+ public:
+  explicit FakeUpdateManager(chromeos_update_engine::ClockInterface* clock)
+      : UpdateManager(clock, base::TimeDelta::FromSeconds(5),
+                      base::TimeDelta::FromHours(1), new FakeState()) {
+    // The FakeUpdateManager uses a DefaultPolicy.
+    set_policy(new DefaultPolicy(clock));
+  }
+
+  // UpdateManager overrides.
+  using UpdateManager::set_policy;
+
+  FakeState* state() {
+    return reinterpret_cast<FakeState*>(UpdateManager::state());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeUpdateManager);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
diff --git a/update_engine/update_manager/fake_updater_provider.h b/update_engine/update_manager/fake_updater_provider.h
new file mode 100644
index 0000000..44389f4
--- /dev/null
+++ b/update_engine/update_manager/fake_updater_provider.h
@@ -0,0 +1,130 @@
+//
+// 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_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
+
+#include <string>
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the UpdaterProvider base class.
+class FakeUpdaterProvider : public UpdaterProvider {
+ public:
+  FakeUpdaterProvider() {}
+
+  FakeVariable<base::Time>* var_updater_started_time() override {
+    return &var_updater_started_time_;
+  }
+
+  FakeVariable<base::Time>* var_last_checked_time() override {
+    return &var_last_checked_time_;
+  }
+
+  FakeVariable<base::Time>* var_update_completed_time() override {
+    return &var_update_completed_time_;
+  }
+
+  FakeVariable<double>* var_progress() override {
+    return &var_progress_;
+  }
+
+  FakeVariable<Stage>* var_stage() override {
+    return &var_stage_;
+  }
+
+  FakeVariable<std::string>* var_new_version() override {
+    return &var_new_version_;
+  }
+
+  FakeVariable<int64_t>* var_payload_size() override {
+    return &var_payload_size_;
+  }
+
+  FakeVariable<std::string>* var_curr_channel() override {
+    return &var_curr_channel_;
+  }
+
+  FakeVariable<std::string>* var_new_channel() override {
+    return &var_new_channel_;
+  }
+
+  FakeVariable<bool>* var_p2p_enabled() override {
+    return &var_p2p_enabled_;
+  }
+
+  FakeVariable<bool>* var_cellular_enabled() override {
+    return &var_cellular_enabled_;
+  }
+
+  FakeVariable<unsigned int>* var_consecutive_failed_update_checks() override {
+    return &var_consecutive_failed_update_checks_;
+  }
+
+  FakeVariable<unsigned int>* var_server_dictated_poll_interval() override {
+    return &var_server_dictated_poll_interval_;
+  }
+
+  FakeVariable<UpdateRequestStatus>* var_forced_update_requested() override {
+    return &var_forced_update_requested_;
+  }
+
+ private:
+  FakeVariable<base::Time>
+      var_updater_started_time_{  // NOLINT(whitespace/braces)
+    "updater_started_time", kVariableModePoll};
+  FakeVariable<base::Time> var_last_checked_time_{  // NOLINT(whitespace/braces)
+    "last_checked_time", kVariableModePoll};
+  FakeVariable<base::Time>
+      var_update_completed_time_{  // NOLINT(whitespace/braces)
+    "update_completed_time", kVariableModePoll};
+  FakeVariable<double> var_progress_{  // NOLINT(whitespace/braces)
+    "progress", kVariableModePoll};
+  FakeVariable<Stage> var_stage_{  // NOLINT(whitespace/braces)
+    "stage", kVariableModePoll};
+  FakeVariable<std::string> var_new_version_{  // NOLINT(whitespace/braces)
+    "new_version", kVariableModePoll};
+  FakeVariable<int64_t> var_payload_size_{  // NOLINT(whitespace/braces)
+    "payload_size", kVariableModePoll};
+  FakeVariable<std::string> var_curr_channel_{  // NOLINT(whitespace/braces)
+    "curr_channel", kVariableModePoll};
+  FakeVariable<std::string> var_new_channel_{  // NOLINT(whitespace/braces)
+    "new_channel", kVariableModePoll};
+  FakeVariable<bool> var_p2p_enabled_{// NOLINT(whitespace/braces)
+                                      "p2p_enabled",
+                                      kVariableModeAsync};
+  FakeVariable<bool> var_cellular_enabled_{// NOLINT(whitespace/braces)
+                                           "cellular_enabled",
+                                           kVariableModeAsync};
+  FakeVariable<unsigned int>
+      var_consecutive_failed_update_checks_{  // NOLINT(whitespace/braces)
+    "consecutive_failed_update_checks", kVariableModePoll};
+  FakeVariable<unsigned int>
+      var_server_dictated_poll_interval_{  // NOLINT(whitespace/braces)
+    "server_dictated_poll_interval", kVariableModePoll};
+  FakeVariable<UpdateRequestStatus>
+      var_forced_update_requested_{  // NOLINT(whitespace/braces)
+    "forced_update_requested", kVariableModeAsync};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeUpdaterProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
diff --git a/update_engine/update_manager/fake_variable.h b/update_engine/update_manager/fake_variable.h
new file mode 100644
index 0000000..2f8e079
--- /dev/null
+++ b/update_engine/update_manager/fake_variable.h
@@ -0,0 +1,74 @@
+//
+// 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_UPDATE_MANAGER_FAKE_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_VARIABLE_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// A fake typed variable to use while testing policy implementations. The
+// variable can be instructed to return any object of its type.
+template<typename T>
+class FakeVariable : public Variable<T> {
+ public:
+  FakeVariable(const std::string& name, VariableMode mode)
+      : Variable<T>(name, mode) {}
+  FakeVariable(const std::string& name, base::TimeDelta poll_interval)
+      : Variable<T>(name, poll_interval) {}
+  ~FakeVariable() override {}
+
+  // Sets the next value of this variable to the passed |p_value| pointer. Once
+  // returned by GetValue(), the pointer is released and has to be set again.
+  // A value of null means that the GetValue() call will fail and return
+  // null.
+  void reset(const T* p_value) {
+    ptr_.reset(p_value);
+  }
+
+  // Make the NotifyValueChanged() public for FakeVariables.
+  void NotifyValueChanged() {
+    Variable<T>::NotifyValueChanged();
+  }
+
+ protected:
+  // Variable<T> overrides.
+  // Returns the pointer set with reset(). The ownership of the object is passed
+  // to the caller and the pointer is release from the FakeVariable. A second
+  // call to GetValue() without reset() will return null and set the error
+  // message.
+  const T* GetValue(base::TimeDelta /* timeout */,
+                    std::string* errmsg) override {
+    if (ptr_ == nullptr && errmsg != nullptr)
+      *errmsg = this->GetName() + " is an empty FakeVariable";
+    // Passes the pointer ownership to the caller.
+    return ptr_.release();
+  }
+
+ private:
+  // The pointer returned by GetValue().
+  std::unique_ptr<const T> ptr_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeVariable);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_VARIABLE_H_
diff --git a/update_engine/update_manager/generic_variables.h b/update_engine/update_manager/generic_variables.h
new file mode 100644
index 0000000..f87a05e
--- /dev/null
+++ b/update_engine/update_manager/generic_variables.h
@@ -0,0 +1,218 @@
+//
+// 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.
+//
+
+// Generic and provider-independent Variable subclasses. These variables can be
+// used by any state provider to implement simple variables to avoid repeat the
+// same common code on different state providers.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
+
+#include <string>
+
+#include <base/callback.h>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Variable class returning a copy of a given object using the copy constructor.
+// This template class can be used to define variables that expose as a variable
+// any fixed object, such as the a provider's private member. The variable will
+// create copies of the provided object using the copy constructor of that
+// class.
+//
+// For example, a state provider exposing a private member as a variable can
+// implement this as follows:
+//
+//   class SomethingProvider {
+//    public:
+//      SomethingProvider(...) {
+//        var_something_foo = new PollCopyVariable<MyType>(foo_);
+//      }
+//      ...
+//    private:
+//     MyType foo_;
+//   };
+template<typename T>
+class PollCopyVariable : public Variable<T> {
+ public:
+  // Creates the variable returning copies of the passed |ref|. The reference to
+  // this object is kept and it should be available whenever the GetValue()
+  // method is called. If |is_set_p| is not null, then this flag will be
+  // consulted prior to returning the value, and an |errmsg| will be returned if
+  // it is not set.
+  PollCopyVariable(const std::string& name, const T& ref, const bool* is_set_p,
+                   const std::string& errmsg)
+      : Variable<T>(name, kVariableModePoll), ref_(ref), is_set_p_(is_set_p),
+        errmsg_(errmsg) {}
+  PollCopyVariable(const std::string& name, const T& ref, const bool* is_set_p)
+      : PollCopyVariable(name, ref, is_set_p, std::string()) {}
+  PollCopyVariable(const std::string& name, const T& ref)
+      : PollCopyVariable(name, ref, nullptr) {}
+
+  PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+                   const T& ref, const bool* is_set_p,
+                   const std::string& errmsg)
+      : Variable<T>(name, poll_interval), ref_(ref), is_set_p_(is_set_p),
+        errmsg_(errmsg) {}
+  PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+                   const T& ref, const bool* is_set_p)
+      : PollCopyVariable(name, poll_interval, ref, is_set_p, std::string()) {}
+  PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+                   const T& ref)
+      : PollCopyVariable(name, poll_interval, ref, nullptr) {}
+
+ protected:
+  FRIEND_TEST(UmPollCopyVariableTest, SimpleTest);
+  FRIEND_TEST(UmPollCopyVariableTest, UseCopyConstructorTest);
+
+  // Variable override.
+  inline const T* GetValue(base::TimeDelta /* timeout */,
+                           std::string* errmsg) override {
+    if (is_set_p_ && !(*is_set_p_)) {
+      if (errmsg) {
+        if (errmsg_.empty())
+          *errmsg = "No value set for " + this->GetName();
+        else
+          *errmsg = errmsg_;
+      }
+      return nullptr;
+    }
+    return new T(ref_);
+  }
+
+ private:
+  // Reference to the object to be copied by GetValue().
+  const T& ref_;
+
+  // A pointer to a flag indicating whether the value is set. If null, then the
+  // value is assumed to be set.
+  const bool* const is_set_p_;
+
+  // An error message to be returned when attempting to get an unset value.
+  const std::string errmsg_;
+};
+
+// Variable class returning a constant value that is cached on the variable when
+// it is created.
+template<typename T>
+class ConstCopyVariable : public Variable<T> {
+ public:
+  // Creates the variable returning copies of the passed |obj|. The value passed
+  // is copied in this variable, and new copies of it will be returned by
+  // GetValue().
+  ConstCopyVariable(const std::string& name, const T& obj)
+      : Variable<T>(name, kVariableModeConst), obj_(obj) {}
+
+ protected:
+  // Variable override.
+  const T* GetValue(base::TimeDelta /* timeout */,
+                    std::string* /* errmsg */) override {
+    return new T(obj_);
+  }
+
+ private:
+  // Value to be copied by GetValue().
+  const T obj_;
+};
+
+// Variable class returning a copy of a value returned by a given function. The
+// function is called every time the variable is being polled.
+template<typename T>
+class CallCopyVariable : public Variable<T> {
+ public:
+  CallCopyVariable(const std::string& name, base::Callback<T(void)> func)
+      : Variable<T>(name, kVariableModePoll), func_(func) {}
+  CallCopyVariable(const std::string& name,
+                   const base::TimeDelta poll_interval,
+                   base::Callback<T(void)> func)
+      : Variable<T>(name, poll_interval), func_(func) {}
+
+ protected:
+  // Variable override.
+  const T* GetValue(base::TimeDelta /* timeout */,
+                    std::string* /* errmsg */) override {
+    if (func_.is_null())
+      return nullptr;
+    return new T(func_.Run());
+  }
+
+ private:
+  FRIEND_TEST(UmCallCopyVariableTest, SimpleTest);
+
+  // The function to be called, stored as a base::Callback.
+  base::Callback<T(void)> func_;
+
+  DISALLOW_COPY_AND_ASSIGN(CallCopyVariable);
+};
+
+
+// A Variable class to implement simple Async variables. It provides two methods
+// SetValue and UnsetValue to modify the current value of the variable and
+// notify the registered observers whenever the value changed.
+//
+// The type T needs to be copy-constructible, default-constructible and have an
+// operator== (to determine if the value changed), which makes this class
+// suitable for basic types.
+template<typename T>
+class AsyncCopyVariable : public Variable<T> {
+ public:
+  explicit AsyncCopyVariable(const std::string& name)
+      : Variable<T>(name, kVariableModeAsync), has_value_(false) {}
+
+  AsyncCopyVariable(const std::string& name, const T value)
+      : Variable<T>(name, kVariableModeAsync),
+        has_value_(true), value_(value) {}
+
+  void SetValue(const T& new_value) {
+    bool should_notify = !(has_value_ && new_value == value_);
+    value_ = new_value;
+    has_value_ = true;
+    if (should_notify)
+      this->NotifyValueChanged();
+  }
+
+  void UnsetValue() {
+    if (has_value_) {
+      has_value_ = false;
+      this->NotifyValueChanged();
+    }
+  }
+
+ protected:
+  // Variable override.
+  const T* GetValue(base::TimeDelta /* timeout */,
+                    std::string* errmsg) override {
+    if (!has_value_) {
+      if (errmsg)
+        *errmsg = "No value set for " + this->GetName();
+      return nullptr;
+    }
+    return new T(value_);
+  }
+
+ private:
+  // Whether the variable has a value set.
+  bool has_value_;
+
+  // Copy of the object to be returned by GetValue().
+  T value_;
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
diff --git a/update_engine/update_manager/generic_variables_unittest.cc b/update_engine/update_manager/generic_variables_unittest.cc
new file mode 100644
index 0000000..cb0c48f
--- /dev/null
+++ b/update_engine/update_manager/generic_variables_unittest.cc
@@ -0,0 +1,224 @@
+//
+// 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/update_manager/generic_variables.h"
+
+#include <memory>
+
+#include <base/callback.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 <gtest/gtest.h>
+
+#include "update_engine/update_manager/umtest_utils.h"
+
+using brillo::MessageLoop;
+using brillo::MessageLoopRunMaxIterations;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmPollCopyVariableTest : public ::testing::Test {};
+
+
+TEST_F(UmPollCopyVariableTest, SimpleTest) {
+  // Tests that copies are generated as intended.
+  int source = 5;
+  PollCopyVariable<int> var("var", source);
+
+  // Generate and validate a copy.
+  unique_ptr<const int> copy_1(var.GetValue(
+          UmTestUtils::DefaultTimeout(), nullptr));
+  ASSERT_NE(nullptr, copy_1.get());
+  EXPECT_EQ(5, *copy_1);
+
+  // Assign a different value to the source variable.
+  source = 42;
+
+  // Check that the content of the copy was not affected (distinct instance).
+  EXPECT_EQ(5, *copy_1);
+
+  // Generate and validate a second copy.
+  UmTestUtils::ExpectVariableHasValue(42, &var);
+}
+
+TEST_F(UmPollCopyVariableTest, SetFlagTest) {
+  // Tests that the set flag is being referred to as expected.
+  int source = 5;
+  bool is_set = false;
+  PollCopyVariable<int> var("var", source, &is_set);
+
+  // Flag marked unset, nothing should be returned.
+  UmTestUtils::ExpectVariableNotSet(&var);
+
+  // Flag marked set, we should be getting a value.
+  is_set = true;
+  UmTestUtils::ExpectVariableHasValue(5, &var);
+}
+
+
+class CopyConstructorTestClass {
+ public:
+  CopyConstructorTestClass(void) : copied_(false) {}
+  CopyConstructorTestClass(const CopyConstructorTestClass& other)
+      : copied_(true), val_(other.val_ * 2) {}
+
+  // Tells if the instance was constructed using the copy-constructor.
+  const bool copied_;
+
+  // An auxiliary internal value.
+  int val_ = 0;
+};
+
+
+TEST_F(UmPollCopyVariableTest, UseCopyConstructorTest) {
+  // Ensures that CopyVariables indeed uses the copy constructor.
+  const CopyConstructorTestClass source;
+  ASSERT_FALSE(source.copied_);
+
+  PollCopyVariable<CopyConstructorTestClass> var("var", source);
+  unique_ptr<const CopyConstructorTestClass> copy(
+      var.GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+  ASSERT_NE(nullptr, copy.get());
+  EXPECT_TRUE(copy->copied_);
+}
+
+
+class UmConstCopyVariableTest : public ::testing::Test {};
+
+TEST_F(UmConstCopyVariableTest, SimpleTest) {
+  int source = 5;
+  ConstCopyVariable<int> var("var", source);
+  UmTestUtils::ExpectVariableHasValue(5, &var);
+
+  // Ensure the value is cached.
+  source = 42;
+  UmTestUtils::ExpectVariableHasValue(5, &var);
+}
+
+
+class UmCallCopyVariableTest : public ::testing::Test {};
+
+CopyConstructorTestClass test_func(CopyConstructorTestClass* obj) {
+  obj->val_++;  // So we can check that the function was called.
+  return *obj;
+}
+
+TEST_F(UmCallCopyVariableTest, SimpleTest) {
+  // Tests that the returned value is generated by copying the value returned by
+  // the function call.
+
+  CopyConstructorTestClass test_obj;
+  ASSERT_FALSE(test_obj.copied_);
+  test_obj.val_ = 5;
+
+  base::Callback<CopyConstructorTestClass(void)> cb = base::Bind(
+      test_func, &test_obj);
+  CallCopyVariable<CopyConstructorTestClass> var("var", cb);
+
+  unique_ptr<const CopyConstructorTestClass> copy(
+      var.GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+  EXPECT_EQ(6, test_obj.val_);  // Check that the function was called.
+  ASSERT_NE(nullptr, copy.get());
+  EXPECT_TRUE(copy->copied_);
+  EXPECT_EQ(12, copy->val_);  // Check that copying occurred once.
+}
+
+TEST_F(UmCallCopyVariableTest, NullTest) {
+  // Ensures that the variable returns null when the callback is null.
+
+  base::Callback<bool(void)> cb;
+  CallCopyVariable<bool> var("var", cb);
+  UmTestUtils::ExpectVariableNotSet(&var);
+}
+
+class UmAsyncCopyVariableTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  void TearDown() override {
+    // No remaining event on the main loop.
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+
+  brillo::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(UmAsyncCopyVariableTest, ConstructorTest) {
+  AsyncCopyVariable<int> var("var");
+  UmTestUtils::ExpectVariableNotSet(&var);
+  EXPECT_EQ(kVariableModeAsync, var.GetMode());
+}
+
+TEST_F(UmAsyncCopyVariableTest, SetValueTest) {
+  AsyncCopyVariable<int> var("var");
+  var.SetValue(5);
+  UmTestUtils::ExpectVariableHasValue(5, &var);
+  // Execute all the pending observers.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+}
+
+TEST_F(UmAsyncCopyVariableTest, UnsetValueTest) {
+  AsyncCopyVariable<int> var("var", 42);
+  var.UnsetValue();
+  UmTestUtils::ExpectVariableNotSet(&var);
+  // Execute all the pending observers.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+}
+
+class CallCounterObserver : public BaseVariable::ObserverInterface {
+ public:
+  void ValueChanged(BaseVariable* variable) {
+    calls_count_++;
+  }
+
+  int calls_count_ = 0;
+};
+
+TEST_F(UmAsyncCopyVariableTest, ObserverCalledTest) {
+  AsyncCopyVariable<int> var("var", 42);
+  CallCounterObserver observer;
+  var.AddObserver(&observer);
+  EXPECT_EQ(0, observer.calls_count_);
+
+  // Check that a different value fires the notification.
+  var.SetValue(5);
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1, observer.calls_count_);
+
+  // Check the same value doesn't.
+  var.SetValue(5);
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1, observer.calls_count_);
+
+  // Check that unsetting a previously set value fires the notification.
+  var.UnsetValue();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(2, observer.calls_count_);
+
+  // Check that unsetting again doesn't.
+  var.UnsetValue();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(2, observer.calls_count_);
+
+  var.RemoveObserver(&observer);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/mock_policy.h b/update_engine/update_manager/mock_policy.h
new file mode 100644
index 0000000..14470e9
--- /dev/null
+++ b/update_engine/update_manager/mock_policy.h
@@ -0,0 +1,92 @@
+//
+// 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_UPDATE_MANAGER_MOCK_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_MOCK_POLICY_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/policy.h"
+
+namespace chromeos_update_manager {
+
+// A mocked implementation of Policy.
+class MockPolicy : public Policy {
+ public:
+  explicit MockPolicy(chromeos_update_engine::ClockInterface* clock)
+      : default_policy_(clock) {
+    // We defer to the corresponding DefaultPolicy methods, by default.
+    ON_CALL(*this, UpdateCheckAllowed(testing::_, testing::_, testing::_,
+                                      testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::UpdateCheckAllowed));
+    ON_CALL(*this, UpdateCanStart(testing::_, testing::_, testing::_,
+                                  testing::_, testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::UpdateCanStart));
+    ON_CALL(*this, UpdateDownloadAllowed(testing::_, testing::_, testing::_,
+                                         testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::UpdateDownloadAllowed));
+    ON_CALL(*this, P2PEnabled(testing::_, testing::_, testing::_, testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::P2PEnabled));
+    ON_CALL(*this, P2PEnabledChanged(testing::_, testing::_, testing::_,
+                                     testing::_, testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::P2PEnabledChanged));
+  }
+
+  MockPolicy() : MockPolicy(nullptr) {}
+  ~MockPolicy() override {}
+
+  // Policy overrides.
+  MOCK_CONST_METHOD4(UpdateCheckAllowed,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                UpdateCheckParams*));
+
+  MOCK_CONST_METHOD5(UpdateCanStart,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                UpdateDownloadParams*, UpdateState));
+
+  MOCK_CONST_METHOD4(UpdateDownloadAllowed,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                bool*));
+
+  MOCK_CONST_METHOD4(P2PEnabled,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                bool*));
+
+  MOCK_CONST_METHOD5(P2PEnabledChanged,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                bool*, bool));
+
+ protected:
+  // Policy override.
+  std::string PolicyName() const override { return "MockPolicy"; }
+
+ private:
+  DefaultPolicy default_policy_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockPolicy);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_MOCK_POLICY_H_
diff --git a/update_engine/update_manager/mock_variable.h b/update_engine/update_manager/mock_variable.h
new file mode 100644
index 0000000..1493491
--- /dev/null
+++ b/update_engine/update_manager/mock_variable.h
@@ -0,0 +1,42 @@
+//
+// 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_UPDATE_MANAGER_MOCK_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_MOCK_VARIABLE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// This is a generic mock of the Variable class.
+template<typename T>
+class MockVariable : public Variable<T> {
+ public:
+  using Variable<T>::Variable;
+
+  MOCK_METHOD2_T(GetValue, const T*(base::TimeDelta, std::string*));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockVariable);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_MOCK_VARIABLE_H_
diff --git a/update_engine/update_manager/policy.cc b/update_engine/update_manager/policy.cc
new file mode 100644
index 0000000..151c225
--- /dev/null
+++ b/update_engine/update_manager/policy.cc
@@ -0,0 +1,37 @@
+//
+// 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/update_manager/policy.h"
+
+#include <string>
+
+using std::string;
+
+namespace chromeos_update_manager {
+
+string ToString(EvalStatus status) {
+  switch (status) {
+    case EvalStatus::kFailed:
+      return "kFailed";
+    case EvalStatus::kSucceeded:
+      return "kSucceeded";
+    case EvalStatus::kAskMeAgainLater:
+      return "kAskMeAgainLater";
+  }
+  return "Invalid";
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/policy.h b/update_engine/update_manager/policy.h
new file mode 100644
index 0000000..fae1494
--- /dev/null
+++ b/update_engine/update_manager/policy.h
@@ -0,0 +1,290 @@
+//
+// 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_UPDATE_MANAGER_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// The three different results of a policy request.
+enum class EvalStatus {
+  kFailed,
+  kSucceeded,
+  kAskMeAgainLater,
+};
+
+std::string ToString(EvalStatus status);
+
+// Parameters of an update check. These parameters are determined by the
+// UpdateCheckAllowed policy.
+struct UpdateCheckParams {
+  bool updates_enabled;  // Whether the auto-updates are enabled on this build.
+
+  // Attributes pertaining to the case where update checks are allowed.
+  //
+  // A target version prefix, if imposed by policy; otherwise, an empty string.
+  std::string target_version_prefix;
+  // A target channel, if so imposed by policy; otherwise, an empty string.
+  std::string target_channel;
+
+  // Whether the allowed update is interactive (user-initiated) or periodic.
+  bool is_interactive;
+};
+
+// Input arguments to UpdateCanStart.
+//
+// A snapshot of the state of the current update process. This includes
+// everything that a policy might need and that occurred since the first time
+// the current payload was first seen and attempted (consecutively).
+struct UpdateState {
+  // Information pertaining to the current update payload and/or check.
+  //
+  // Whether the current update check is an interactive one. The caller should
+  // feed the value returned by the preceding call to UpdateCheckAllowed().
+  bool is_interactive;
+  // Whether it is a delta payload.
+  bool is_delta_payload;
+  // Wallclock time when payload was first (consecutively) offered by Omaha.
+  base::Time first_seen;
+  // Number of consecutive update checks returning the current update.
+  int num_checks;
+  // Number of update payload failures and the wallclock time when it was last
+  // updated by the updater. These should both be nullified whenever a new
+  // update is seen; they are updated at the policy's descretion (via
+  // UpdateDownloadParams.do_increment_failures) once all of the usable download
+  // URLs for the payload have been used without success. They should be
+  // persisted across reboots.
+  int num_failures;
+  base::Time failures_last_updated;
+
+  // Information pertaining to downloading and applying of the current update.
+  //
+  // An array of download URLs provided by Omaha.
+  std::vector<std::string> download_urls;
+  // Max number of errors allowed per download URL.
+  int download_errors_max;
+  // The index of the URL to download from, as determined in the previous call
+  // to the policy. For a newly seen payload, this should be -1.
+  int last_download_url_idx;
+  // The number of successive download errors pertaining to this last URL, as
+  // determined in the previous call to the policy. For a newly seen payload,
+  // this should be zero.
+  int last_download_url_num_errors;
+  // An array of errors that occurred while trying to download this update since
+  // the previous call to this policy has returned, or since this payload was
+  // first seen, or since the updater process has started (whichever is later).
+  // Includes the URL index attempted, the error code, and the wallclock-based
+  // timestamp when it occurred.
+  std::vector<std::tuple<int, chromeos_update_engine::ErrorCode, base::Time>>
+      download_errors;
+  // Whether Omaha forbids use of P2P for downloading and/or sharing.
+  bool p2p_downloading_disabled;
+  bool p2p_sharing_disabled;
+  // The number of P2P download attempts and wallclock-based time when P2P
+  // download was first attempted.
+  int p2p_num_attempts;
+  base::Time p2p_first_attempted;
+
+  // Information pertaining to update backoff mechanism.
+  //
+  // The currently known (persisted) wallclock-based backoff expiration time;
+  // zero if none.
+  base::Time backoff_expiry;
+  // Whether backoff is disabled by Omaha.
+  bool is_backoff_disabled;
+
+  // Information pertaining to update scattering.
+  //
+  // The currently knwon (persisted) scattering wallclock-based wait period and
+  // update check threshold; zero if none.
+  base::TimeDelta scatter_wait_period;
+  int scatter_check_threshold;
+  // Maximum wait period allowed for this update, as determined by Omaha.
+  base::TimeDelta scatter_wait_period_max;
+  // Minimum/maximum check threshold values.
+  // TODO(garnold) These appear to not be related to the current update and so
+  // should probably be obtained as variables via UpdaterProvider.
+  int scatter_check_threshold_min;
+  int scatter_check_threshold_max;
+};
+
+// Results regarding the downloading and applying of an update, as determined by
+// UpdateCanStart.
+//
+// An enumerator for the reasons of not allowing an update to start.
+enum class UpdateCannotStartReason {
+  kUndefined,
+  kCheckDue,
+  kScattering,
+  kBackoff,
+  kCannotDownload,
+};
+
+struct UpdateDownloadParams {
+  // Whether the update attempt is allowed to proceed.
+  bool update_can_start;
+  // If update cannot proceed, a reason code for why it cannot do so.
+  UpdateCannotStartReason cannot_start_reason;
+
+  // Download related attributes. The update engine uses them to choose the
+  // means for downloading and applying an update.
+  //
+  // The index of the download URL to use (-1 means no suitable URL was found)
+  // and whether it can be used. Even if there's no URL or its use is not
+  // allowed (backoff, scattering) there may still be other means for download
+  // (like P2P).  The URL index needs to be persisted and handed back to the
+  // policy on the next time it is called.
+  int download_url_idx;
+  bool download_url_allowed;
+  // The number of download errors associated with this download URL. This value
+  // needs to be persisted and handed back to the policy on the next time it is
+  // called.
+  int download_url_num_errors;
+  // Whether P2P download and sharing are allowed.
+  bool p2p_downloading_allowed;
+  bool p2p_sharing_allowed;
+
+  // Other values that need to be persisted and handed to the policy as need on
+  // the next call.
+  //
+  // Whether an update failure has been identified by the policy. The client
+  // should increment and persist its update failure count, and record the time
+  // when this was done; it needs to hand these values back to the policy
+  // (UpdateState.{num_failures,failures_last_updated}) on the next time it is
+  // called.
+  bool do_increment_failures;
+  // The current backof expiry.
+  base::Time backoff_expiry;
+  // The scattering wait period and check threshold.
+  base::TimeDelta scatter_wait_period;
+  int scatter_check_threshold;
+};
+
+// The Policy class is an interface to the ensemble of policy requests that the
+// client can make. A derived class includes the policy implementations of
+// these.
+//
+// When compile-time selection of the policy is required due to missing or extra
+// parts in a given platform, a different Policy subclass can be used.
+class Policy {
+ public:
+  virtual ~Policy() {}
+
+  // Returns the name of a public policy request.
+  // IMPORTANT: Be sure to add a conditional for each new public policy that is
+  // being added to this class in the future.
+  template<typename R, typename... Args>
+  std::string PolicyRequestName(
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          Args...) const) const {
+    std::string class_name = PolicyName() + "::";
+
+    if (reinterpret_cast<typeof(&Policy::UpdateCheckAllowed)>(
+            policy_method) == &Policy::UpdateCheckAllowed)
+      return class_name + "UpdateCheckAllowed";
+    if (reinterpret_cast<typeof(&Policy::UpdateCanStart)>(
+            policy_method) == &Policy::UpdateCanStart)
+      return class_name + "UpdateCanStart";
+    if (reinterpret_cast<typeof(&Policy::UpdateDownloadAllowed)>(
+            policy_method) == &Policy::UpdateDownloadAllowed)
+      return class_name + "UpdateDownloadAllowed";
+    if (reinterpret_cast<typeof(&Policy::P2PEnabled)>(
+            policy_method) == &Policy::P2PEnabled)
+      return class_name + "P2PEnabled";
+    if (reinterpret_cast<typeof(&Policy::P2PEnabledChanged)>(
+            policy_method) == &Policy::P2PEnabledChanged)
+      return class_name + "P2PEnabledChanged";
+
+    NOTREACHED();
+    return class_name + "(unknown)";
+  }
+
+
+  // List of policy requests. A policy request takes an EvaluationContext as the
+  // first argument, a State instance, a returned error message, a returned
+  // value and optionally followed by one or more arbitrary constant arguments.
+  //
+  // When the implementation fails, the method returns EvalStatus::kFailed and
+  // sets the |error| string.
+
+  // UpdateCheckAllowed returns whether it is allowed to request an update check
+  // to Omaha.
+  virtual EvalStatus UpdateCheckAllowed(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateCheckParams* result) const = 0;
+
+  // Returns EvalStatus::kSucceeded if either an update can start being
+  // processed, or the attempt needs to be aborted. In cases where the update
+  // needs to wait for some condition to be satisfied, but none of the values
+  // that need to be persisted has changed, returns
+  // EvalStatus::kAskMeAgainLater. Arguments include an |update_state| that
+  // encapsulates data pertaining to the current ongoing update process.
+  virtual EvalStatus UpdateCanStart(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      UpdateDownloadParams* result,
+      UpdateState update_state) const = 0;
+
+  // Checks whether downloading of an update is allowed; currently, this checks
+  // whether the network connection type is suitable for updating over.  May
+  // consult the shill provider as well as the device policy (if available).
+  // Returns |EvalStatus::kSucceeded|, setting |result| according to whether or
+  // not the current connection can be used; on error, returns
+  // |EvalStatus::kFailed| and sets |error| accordingly.
+  virtual EvalStatus UpdateDownloadAllowed(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      bool* result) const = 0;
+
+  // Checks whether P2P is enabled. This may consult device policy and other
+  // global settings.
+  virtual EvalStatus P2PEnabled(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result) const = 0;
+
+  // Checks whether P2P is enabled, but blocks (returns
+  // |EvalStatus::kAskMeAgainLater|) until it is different from |prev_result|.
+  // If the P2P enabled status is not expected to change, will return
+  // immediately with |EvalStatus::kSucceeded|. This internally uses the
+  // P2PEnabled() policy above.
+  virtual EvalStatus P2PEnabledChanged(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result, bool prev_result) const = 0;
+
+ protected:
+  Policy() {}
+
+  // Returns the name of the actual policy class.
+  virtual std::string PolicyName() const = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Policy);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_
diff --git a/update_engine/update_manager/policy_utils.h b/update_engine/update_manager/policy_utils.h
new file mode 100644
index 0000000..960987e
--- /dev/null
+++ b/update_engine/update_manager/policy_utils.h
@@ -0,0 +1,38 @@
+//
+// 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_UPDATE_MANAGER_POLICY_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
+
+#include "update_engine/update_manager/policy.h"
+
+// Checks that the passed pointer value is not null, returning kFailed on the
+// current context and setting the *error description when it is null. The
+// intended use is to validate variable failures while using
+// EvaluationContext::GetValue, for example:
+//
+//   const int* my_value = ec->GetValue(state->my_provider()->var_my_value());
+//   POLICY_CHECK_VALUE_AND_FAIL(my_value, error);
+//
+#define POLICY_CHECK_VALUE_AND_FAIL(ptr, error) \
+    do { \
+      if ((ptr) == nullptr) { \
+        *(error) = #ptr " is required but is null."; \
+        return EvalStatus::kFailed; \
+      } \
+    } while (false)
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
diff --git a/update_engine/update_manager/prng.h b/update_engine/update_manager/prng.h
new file mode 100644
index 0000000..64f886c
--- /dev/null
+++ b/update_engine/update_manager/prng.h
@@ -0,0 +1,51 @@
+//
+// 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_UPDATE_MANAGER_PRNG_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_PRNG_H_
+
+#include <random>
+
+#include <base/logging.h>
+
+namespace chromeos_update_manager {
+
+// A thread-safe, unsecure, 32-bit pseudo-random number generator based on
+// std::mt19937.
+class PRNG {
+ public:
+  // Initializes the generator with the passed |seed| value.
+  explicit PRNG(uint32_t seed) : gen_(seed) {}
+
+  // Returns a random unsigned 32-bit integer.
+  uint32_t Rand() { return gen_(); }
+
+  // Returns a random integer uniformly distributed in the range [min, max].
+  int RandMinMax(int min, int max) {
+    DCHECK_LE(min, max);
+    return std::uniform_int_distribution<>(min, max)(gen_);
+  }
+
+ private:
+  // A pseudo-random number generator.
+  std::mt19937 gen_;
+
+  DISALLOW_COPY_AND_ASSIGN(PRNG);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_PRNG_H_
diff --git a/update_engine/update_manager/prng_unittest.cc b/update_engine/update_manager/prng_unittest.cc
new file mode 100644
index 0000000..2a3f689
--- /dev/null
+++ b/update_engine/update_manager/prng_unittest.cc
@@ -0,0 +1,79 @@
+//
+// 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/update_manager/prng.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using std::vector;
+
+namespace chromeos_update_manager {
+
+TEST(UmPRNGTest, ShouldBeDeterministic) {
+  PRNG a(42);
+  PRNG b(42);
+
+  for (int i = 0; i < 1000; ++i) {
+    EXPECT_EQ(a.Rand(), b.Rand()) << "Iteration i=" << i;
+  }
+}
+
+TEST(UmPRNGTest, SeedChangesGeneratedSequence) {
+  PRNG a(42);
+  PRNG b(5);
+
+  vector<uint32_t> values_a;
+  vector<uint32_t> values_b;
+
+  for (int i = 0; i < 100; ++i) {
+    values_a.push_back(a.Rand());
+    values_b.push_back(b.Rand());
+  }
+  EXPECT_NE(values_a, values_b);
+}
+
+TEST(UmPRNGTest, IsNotConstant) {
+  PRNG prng(5);
+
+  uint32_t initial_value = prng.Rand();
+  bool prng_is_constant = true;
+  for (int i = 0; i < 100; ++i) {
+    if (prng.Rand() != initial_value) {
+      prng_is_constant = false;
+      break;
+    }
+  }
+  EXPECT_FALSE(prng_is_constant) << "After 100 iterations.";
+}
+
+TEST(UmPRNGTest, RandCoversRange) {
+  PRNG a(42);
+  int hits[11] = { 0 };
+
+  for (int i = 0; i < 1000; i++) {
+    int r = a.RandMinMax(0, 10);
+    ASSERT_LE(0, r);
+    ASSERT_GE(10, r);
+    hits[r]++;
+  }
+
+  for (auto& hit : hits)
+    EXPECT_LT(0, hit);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/provider.h b/update_engine/update_manager/provider.h
new file mode 100644
index 0000000..84335a2
--- /dev/null
+++ b/update_engine/update_manager/provider.h
@@ -0,0 +1,38 @@
+//
+// 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_UPDATE_MANAGER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_PROVIDER_H_
+
+#include <base/macros.h>
+
+namespace chromeos_update_manager {
+
+// Abstract base class for a policy provider.
+class Provider {
+ public:
+  virtual ~Provider() {}
+
+ protected:
+  Provider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Provider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_PROVIDER_H_
diff --git a/update_engine/update_manager/random_provider.h b/update_engine/update_manager/random_provider.h
new file mode 100644
index 0000000..60df62d
--- /dev/null
+++ b/update_engine/update_manager/random_provider.h
@@ -0,0 +1,45 @@
+//
+// 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_UPDATE_MANAGER_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_RANDOM_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider of random values.
+class RandomProvider : public Provider {
+ public:
+  ~RandomProvider() override {}
+
+  // Return a random number every time it is requested. Note that values
+  // returned by the variables are cached by the EvaluationContext, so the
+  // returned value will be the same during the same policy request. If more
+  // random values are needed use a PRNG seeded with this value.
+  virtual Variable<uint64_t>* var_seed() = 0;
+
+ protected:
+  RandomProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RandomProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_RANDOM_PROVIDER_H_
diff --git a/update_engine/update_manager/real_config_provider.cc b/update_engine/update_manager/real_config_provider.cc
new file mode 100644
index 0000000..97e624e
--- /dev/null
+++ b/update_engine/update_manager/real_config_provider.cc
@@ -0,0 +1,30 @@
+//
+// 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/update_manager/real_config_provider.h"
+
+#include "update_engine/update_manager/generic_variables.h"
+
+namespace chromeos_update_manager {
+
+bool RealConfigProvider::Init() {
+  var_is_oobe_enabled_.reset(new ConstCopyVariable<bool>(
+      "is_oobe_enabled", hardware_->IsOOBEEnabled()));
+
+  return true;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_config_provider.h b/update_engine/update_manager/real_config_provider.h
new file mode 100644
index 0000000..e79ae60
--- /dev/null
+++ b/update_engine/update_manager/real_config_provider.h
@@ -0,0 +1,52 @@
+//
+// 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_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
+
+#include <memory>
+
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+namespace chromeos_update_manager {
+
+// ConfigProvider concrete implementation.
+class RealConfigProvider : public ConfigProvider {
+ public:
+  explicit RealConfigProvider(
+      chromeos_update_engine::HardwareInterface* hardware)
+      : hardware_(hardware) {}
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<bool>* var_is_oobe_enabled() override {
+    return var_is_oobe_enabled_.get();
+  }
+
+ private:
+  std::unique_ptr<ConstCopyVariable<bool>> var_is_oobe_enabled_;
+
+  chromeos_update_engine::HardwareInterface* hardware_;
+
+  DISALLOW_COPY_AND_ASSIGN(RealConfigProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
diff --git a/update_engine/update_manager/real_device_policy_provider.cc b/update_engine/update_manager/real_device_policy_provider.cc
new file mode 100644
index 0000000..d9880c3
--- /dev/null
+++ b/update_engine/update_manager/real_device_policy_provider.cc
@@ -0,0 +1,192 @@
+//
+// 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/update_manager/real_device_policy_provider.h"
+
+#include <stdint.h>
+
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/connection_utils.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using chromeos_update_engine::ConnectionType;
+using policy::DevicePolicy;
+using std::set;
+using std::string;
+
+namespace {
+
+const int kDevicePolicyRefreshRateInMinutes = 60;
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+RealDevicePolicyProvider::~RealDevicePolicyProvider() {
+  MessageLoop::current()->CancelTask(scheduled_refresh_);
+}
+
+bool RealDevicePolicyProvider::Init() {
+  CHECK(policy_provider_ != nullptr);
+
+  // On Init() we try to get the device policy and keep updating it.
+  RefreshDevicePolicyAndReschedule();
+
+#if USE_DBUS
+  // We also listen for signals from the session manager to force a device
+  // policy refresh.
+  session_manager_proxy_->RegisterPropertyChangeCompleteSignalHandler(
+      base::Bind(&RealDevicePolicyProvider::OnPropertyChangedCompletedSignal,
+                 base::Unretained(this)),
+      base::Bind(&RealDevicePolicyProvider::OnSignalConnected,
+                 base::Unretained(this)));
+#endif  // USE_DBUS
+  return true;
+}
+
+void RealDevicePolicyProvider::OnPropertyChangedCompletedSignal(
+    const string& success) {
+  if (success != "success") {
+    LOG(WARNING) << "Received device policy updated signal with a failure.";
+  }
+  // We refresh the policy file even if the payload string is kSignalFailure.
+  LOG(INFO) << "Reloading and re-scheduling device policy due to signal "
+               "received.";
+  MessageLoop::current()->CancelTask(scheduled_refresh_);
+  scheduled_refresh_ = MessageLoop::kTaskIdNull;
+  RefreshDevicePolicyAndReschedule();
+}
+
+void RealDevicePolicyProvider::OnSignalConnected(const string& interface_name,
+                                                 const string& signal_name,
+                                                 bool successful) {
+  if (!successful) {
+    LOG(WARNING) << "We couldn't connect to SessionManager signal for updates "
+                    "on the device policy blob. We will reload the policy file "
+                    "periodically.";
+  }
+  // We do a one-time refresh of the DevicePolicy just in case we missed a
+  // signal between the first refresh and the time the signal handler was
+  // actually connected.
+  RefreshDevicePolicy();
+}
+
+void RealDevicePolicyProvider::RefreshDevicePolicyAndReschedule() {
+  RefreshDevicePolicy();
+  scheduled_refresh_ = MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&RealDevicePolicyProvider::RefreshDevicePolicyAndReschedule,
+                 base::Unretained(this)),
+      TimeDelta::FromMinutes(kDevicePolicyRefreshRateInMinutes));
+}
+
+template<typename T>
+void RealDevicePolicyProvider::UpdateVariable(
+    AsyncCopyVariable<T>* var,
+    bool (DevicePolicy::*getter_method)(T*) const) {
+  T new_value;
+  if (policy_provider_->device_policy_is_loaded() &&
+      (policy_provider_->GetDevicePolicy().*getter_method)(&new_value)) {
+    var->SetValue(new_value);
+  } else {
+    var->UnsetValue();
+  }
+}
+
+template<typename T>
+void RealDevicePolicyProvider::UpdateVariable(
+    AsyncCopyVariable<T>* var,
+    bool (RealDevicePolicyProvider::*getter_method)(T*) const) {
+  T new_value;
+  if (policy_provider_->device_policy_is_loaded() &&
+      (this->*getter_method)(&new_value)) {
+    var->SetValue(new_value);
+  } else {
+    var->UnsetValue();
+  }
+}
+
+bool RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate(
+      set<ConnectionType>* allowed_types) const {
+  set<string> allowed_types_str;
+  if (!policy_provider_->GetDevicePolicy()
+      .GetAllowedConnectionTypesForUpdate(&allowed_types_str)) {
+    return false;
+  }
+  allowed_types->clear();
+  for (auto& type_str : allowed_types_str) {
+    ConnectionType type =
+        chromeos_update_engine::connection_utils::ParseConnectionType(type_str);
+    if (type != ConnectionType::kUnknown) {
+      allowed_types->insert(type);
+    } else {
+      LOG(WARNING) << "Policy includes unknown connection type: " << type_str;
+    }
+  }
+  return true;
+}
+
+bool RealDevicePolicyProvider::ConvertScatterFactor(
+    TimeDelta* scatter_factor) const {
+  int64_t scatter_factor_in_seconds;
+  if (!policy_provider_->GetDevicePolicy().GetScatterFactorInSeconds(
+      &scatter_factor_in_seconds)) {
+    return false;
+  }
+  if (scatter_factor_in_seconds < 0) {
+    LOG(WARNING) << "Ignoring negative scatter factor: "
+                 << scatter_factor_in_seconds;
+    return false;
+  }
+  *scatter_factor = TimeDelta::FromSeconds(scatter_factor_in_seconds);
+  return true;
+}
+
+void RealDevicePolicyProvider::RefreshDevicePolicy() {
+  if (!policy_provider_->Reload()) {
+    LOG(INFO) << "No device policies/settings present.";
+  }
+
+  var_device_policy_is_loaded_.SetValue(
+      policy_provider_->device_policy_is_loaded());
+
+  UpdateVariable(&var_release_channel_, &DevicePolicy::GetReleaseChannel);
+  UpdateVariable(&var_release_channel_delegated_,
+                 &DevicePolicy::GetReleaseChannelDelegated);
+  UpdateVariable(&var_update_disabled_, &DevicePolicy::GetUpdateDisabled);
+  UpdateVariable(&var_target_version_prefix_,
+                 &DevicePolicy::GetTargetVersionPrefix);
+  UpdateVariable(&var_scatter_factor_,
+                 &RealDevicePolicyProvider::ConvertScatterFactor);
+  UpdateVariable(
+      &var_allowed_connection_types_for_update_,
+      &RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate);
+  UpdateVariable(&var_owner_, &DevicePolicy::GetOwner);
+  UpdateVariable(&var_http_downloads_enabled_,
+                 &DevicePolicy::GetHttpDownloadsEnabled);
+  UpdateVariable(&var_au_p2p_enabled_, &DevicePolicy::GetAuP2PEnabled);
+  UpdateVariable(&var_allow_kiosk_app_control_chrome_version_,
+                 &DevicePolicy::GetAllowKioskAppControlChromeVersion);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_device_policy_provider.h b/update_engine/update_manager/real_device_policy_provider.h
new file mode 100644
index 0000000..5b5ee58
--- /dev/null
+++ b/update_engine/update_manager/real_device_policy_provider.h
@@ -0,0 +1,182 @@
+//
+// 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_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include <brillo/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+#include <policy/libpolicy.h>
+#if USE_DBUS
+#include <session_manager/dbus-proxies.h>
+#endif  // USE_DBUS
+
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+namespace chromeos_update_manager {
+
+// DevicePolicyProvider concrete implementation.
+class RealDevicePolicyProvider : public DevicePolicyProvider {
+ public:
+#if USE_DBUS
+  RealDevicePolicyProvider(
+      std::unique_ptr<org::chromium::SessionManagerInterfaceProxyInterface>
+          session_manager_proxy,
+      policy::PolicyProvider* policy_provider)
+      : policy_provider_(policy_provider),
+        session_manager_proxy_(std::move(session_manager_proxy)) {}
+#endif  // USE_DBUS
+  explicit RealDevicePolicyProvider(policy::PolicyProvider* policy_provider)
+      : policy_provider_(policy_provider) {}
+  ~RealDevicePolicyProvider();
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<bool>* var_device_policy_is_loaded() override {
+    return &var_device_policy_is_loaded_;
+  }
+
+  Variable<std::string>* var_release_channel() override {
+    return &var_release_channel_;
+  }
+
+  Variable<bool>* var_release_channel_delegated() override {
+    return &var_release_channel_delegated_;
+  }
+
+  Variable<bool>* var_update_disabled() override {
+    return &var_update_disabled_;
+  }
+
+  Variable<std::string>* var_target_version_prefix() override {
+    return &var_target_version_prefix_;
+  }
+
+  Variable<base::TimeDelta>* var_scatter_factor() override {
+    return &var_scatter_factor_;
+  }
+
+  Variable<std::set<chromeos_update_engine::ConnectionType>>*
+      var_allowed_connection_types_for_update() override {
+    return &var_allowed_connection_types_for_update_;
+  }
+
+  Variable<std::string>* var_owner() override {
+    return &var_owner_;
+  }
+
+  Variable<bool>* var_http_downloads_enabled() override {
+    return &var_http_downloads_enabled_;
+  }
+
+  Variable<bool>* var_au_p2p_enabled() override {
+    return &var_au_p2p_enabled_;
+  }
+
+  Variable<bool>* var_allow_kiosk_app_control_chrome_version() override {
+    return &var_allow_kiosk_app_control_chrome_version_;
+  }
+
+ private:
+  FRIEND_TEST(UmRealDevicePolicyProviderTest, RefreshScheduledTest);
+  FRIEND_TEST(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded);
+  FRIEND_TEST(UmRealDevicePolicyProviderTest, ValuesUpdated);
+
+  // A static handler for the PropertyChangedCompleted signal from the session
+  // manager used as a callback.
+  void OnPropertyChangedCompletedSignal(const std::string& success);
+
+  // Called when the signal in UpdateEngineLibcrosProxyResolvedInterface is
+  // connected.
+  void OnSignalConnected(const std::string& interface_name,
+                         const std::string& signal_name,
+                         bool successful);
+
+  // Schedules a call to periodically refresh the device policy.
+  void RefreshDevicePolicyAndReschedule();
+
+  // Reloads the device policy and updates all the exposed variables.
+  void RefreshDevicePolicy();
+
+  // Updates the async variable |var| based on the result value of the method
+  // passed, which is a DevicePolicy getter method.
+  template<typename T>
+  void UpdateVariable(AsyncCopyVariable<T>* var,
+                      bool (policy::DevicePolicy::*getter_method)(T*) const);
+
+  // Updates the async variable |var| based on the result value of the getter
+  // method passed, which is a wrapper getter on this class.
+  template<typename T>
+  void UpdateVariable(
+      AsyncCopyVariable<T>* var,
+      bool (RealDevicePolicyProvider::*getter_method)(T*) const);
+
+  // Wrapper for DevicePolicy::GetScatterFactorInSeconds() that converts the
+  // result to a base::TimeDelta. It returns the same value as
+  // GetScatterFactorInSeconds().
+  bool ConvertScatterFactor(base::TimeDelta* scatter_factor) const;
+
+  // Wrapper for DevicePolicy::GetAllowedConnectionTypesForUpdate() that
+  // converts the result to a set of ConnectionType elements instead of strings.
+  bool ConvertAllowedConnectionTypesForUpdate(
+      std::set<chromeos_update_engine::ConnectionType>* allowed_types) const;
+
+  // Used for fetching information about the device policy.
+  policy::PolicyProvider* policy_provider_;
+
+  // Used to schedule refreshes of the device policy.
+  brillo::MessageLoop::TaskId scheduled_refresh_{
+      brillo::MessageLoop::kTaskIdNull};
+
+#if USE_DBUS
+  // The DBus (mockable) session manager proxy.
+  std::unique_ptr<org::chromium::SessionManagerInterfaceProxyInterface>
+      session_manager_proxy_;
+#endif  // USE_DBUS
+
+  // Variable exposing whether the policy is loaded.
+  AsyncCopyVariable<bool> var_device_policy_is_loaded_{
+      "policy_is_loaded", false};
+
+  // Variables mapping the exposed methods from the policy::DevicePolicy.
+  AsyncCopyVariable<std::string> var_release_channel_{"release_channel"};
+  AsyncCopyVariable<bool> var_release_channel_delegated_{
+      "release_channel_delegated"};
+  AsyncCopyVariable<bool> var_update_disabled_{"update_disabled"};
+  AsyncCopyVariable<std::string> var_target_version_prefix_{
+      "target_version_prefix"};
+  AsyncCopyVariable<base::TimeDelta> var_scatter_factor_{"scatter_factor"};
+  AsyncCopyVariable<std::set<chromeos_update_engine::ConnectionType>>
+      var_allowed_connection_types_for_update_{
+          "allowed_connection_types_for_update"};
+  AsyncCopyVariable<std::string> var_owner_{"owner"};
+  AsyncCopyVariable<bool> var_http_downloads_enabled_{"http_downloads_enabled"};
+  AsyncCopyVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled"};
+  AsyncCopyVariable<bool> var_allow_kiosk_app_control_chrome_version_{
+      "allow_kiosk_app_control_chrome_version"};
+
+  DISALLOW_COPY_AND_ASSIGN(RealDevicePolicyProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_engine/update_manager/real_device_policy_provider_unittest.cc b/update_engine/update_manager/real_device_policy_provider_unittest.cc
new file mode 100644
index 0000000..71c95bb
--- /dev/null
+++ b/update_engine/update_manager/real_device_policy_provider_unittest.cc
@@ -0,0 +1,272 @@
+//
+// 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/update_manager/real_device_policy_provider.h"
+
+#include <memory>
+
+#include <brillo/make_unique_ptr.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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <policy/mock_device_policy.h>
+#include <policy/mock_libpolicy.h>
+#if USE_DBUS
+#include <session_manager/dbus-proxies.h>
+#include <session_manager/dbus-proxy-mocks.h>
+#endif  // USE_DBUS
+
+#include "update_engine/common/test_utils.h"
+#if USE_DBUS
+#include "update_engine/dbus_test_utils.h"
+#endif  // USE_DBUS
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using chromeos_update_engine::ConnectionType;
+#if USE_DBUS
+using chromeos_update_engine::dbus_test_utils::MockSignalHandler;
+#endif  // USE_DBUS
+using std::set;
+using std::string;
+using std::unique_ptr;
+using testing::DoAll;
+using testing::Mock;
+using testing::Return;
+using testing::ReturnRef;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace chromeos_update_manager {
+
+class UmRealDevicePolicyProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+#if USE_DBUS
+    auto session_manager_proxy_mock =
+        new org::chromium::SessionManagerInterfaceProxyMock();
+    provider_.reset(new RealDevicePolicyProvider(
+        brillo::make_unique_ptr(session_manager_proxy_mock),
+        &mock_policy_provider_));
+#else
+    provider_.reset(new RealDevicePolicyProvider(&mock_policy_provider_));
+#endif  // USE_DBUS
+    // By default, we have a device policy loaded. Tests can call
+    // SetUpNonExistentDevicePolicy() to override this.
+    SetUpExistentDevicePolicy();
+
+#if USE_DBUS
+    // Setup the session manager_proxy such that it will accept the signal
+    // handler and store it in the |property_change_complete_| once registered.
+    MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER(property_change_complete_,
+                                              *session_manager_proxy_mock,
+                                              PropertyChangeComplete);
+#endif  // USE_DBUS
+  }
+
+  void TearDown() override {
+    provider_.reset();
+    // Check for leaked callbacks on the main loop.
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  void SetUpNonExistentDevicePolicy() {
+    ON_CALL(mock_policy_provider_, Reload())
+        .WillByDefault(Return(false));
+    ON_CALL(mock_policy_provider_, device_policy_is_loaded())
+        .WillByDefault(Return(false));
+    EXPECT_CALL(mock_policy_provider_, GetDevicePolicy()).Times(0);
+  }
+
+  void SetUpExistentDevicePolicy() {
+    // Setup the default behavior of the mocked PolicyProvider.
+    ON_CALL(mock_policy_provider_, Reload())
+        .WillByDefault(Return(true));
+    ON_CALL(mock_policy_provider_, device_policy_is_loaded())
+        .WillByDefault(Return(true));
+    ON_CALL(mock_policy_provider_, GetDevicePolicy())
+        .WillByDefault(ReturnRef(mock_device_policy_));
+  }
+
+  brillo::FakeMessageLoop loop_{nullptr};
+  testing::NiceMock<policy::MockDevicePolicy> mock_device_policy_;
+  testing::NiceMock<policy::MockPolicyProvider> mock_policy_provider_;
+  unique_ptr<RealDevicePolicyProvider> provider_;
+
+#if USE_DBUS
+  // The registered signal handler for the signal.
+  MockSignalHandler<void(const string&)> property_change_complete_;
+#endif  // USE_DBUS
+};
+
+TEST_F(UmRealDevicePolicyProviderTest, RefreshScheduledTest) {
+  // Check that the RefreshPolicy gets scheduled by checking the TaskId.
+  EXPECT_TRUE(provider_->Init());
+  EXPECT_NE(MessageLoop::kTaskIdNull, provider_->scheduled_refresh_);
+  loop_.RunOnce(false);
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, FirstReload) {
+  // Checks that the policy is reloaded and the DevicePolicy is consulted twice:
+  // once on Init() and once again when the signal is connected.
+  EXPECT_CALL(mock_policy_provider_, Reload());
+  EXPECT_TRUE(provider_->Init());
+  Mock::VerifyAndClearExpectations(&mock_policy_provider_);
+  // We won't be notified that signal is connected without DBus.
+#if USE_DBUS
+  EXPECT_CALL(mock_policy_provider_, Reload());
+#endif  // USE_DBUS
+  loop_.RunOnce(false);
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded) {
+  // Checks that the policy is reloaded by RefreshDevicePolicy().
+  SetUpNonExistentDevicePolicy();
+  // We won't be notified that signal is connected without DBus.
+#if USE_DBUS
+  EXPECT_CALL(mock_policy_provider_, Reload()).Times(3);
+#else
+  EXPECT_CALL(mock_policy_provider_, Reload()).Times(2);
+#endif  // USE_DBUS
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+  // Force the policy refresh.
+  provider_->RefreshDevicePolicy();
+}
+
+#if USE_DBUS
+TEST_F(UmRealDevicePolicyProviderTest, SessionManagerSignalForcesReload) {
+  // Checks that a signal from the SessionManager forces a reload.
+  SetUpNonExistentDevicePolicy();
+  EXPECT_CALL(mock_policy_provider_, Reload()).Times(2);
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+  Mock::VerifyAndClearExpectations(&mock_policy_provider_);
+
+  EXPECT_CALL(mock_policy_provider_, Reload());
+  ASSERT_TRUE(property_change_complete_.IsHandlerRegistered());
+  property_change_complete_.signal_callback().Run("success");
+}
+#endif  // USE_DBUS
+
+TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyEmptyVariables) {
+  SetUpNonExistentDevicePolicy();
+  EXPECT_CALL(mock_policy_provider_, GetDevicePolicy()).Times(0);
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(false,
+                                      provider_->var_device_policy_is_loaded());
+
+  UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel_delegated());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_update_disabled());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_target_version_prefix());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor());
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_allowed_connection_types_for_update());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_owner());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_http_downloads_enabled());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_au_p2p_enabled());
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_allow_kiosk_app_control_chrome_version());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ValuesUpdated) {
+  SetUpNonExistentDevicePolicy();
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+  Mock::VerifyAndClearExpectations(&mock_policy_provider_);
+
+  // Reload the policy with a good one and set some values as present. The
+  // remaining values are false.
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetReleaseChannel(_))
+      .WillOnce(DoAll(SetArgPointee<0>(string("mychannel")), Return(true)));
+  EXPECT_CALL(mock_device_policy_, GetAllowedConnectionTypesForUpdate(_))
+      .WillOnce(Return(false));
+  EXPECT_CALL(mock_device_policy_, GetAllowKioskAppControlChromeVersion(_))
+      .WillOnce(DoAll(SetArgPointee<0>(true), Return(true)));
+
+  provider_->RefreshDevicePolicy();
+
+  UmTestUtils::ExpectVariableHasValue(true,
+                                      provider_->var_device_policy_is_loaded());
+
+  // Test that at least one variable is set, to ensure the refresh occurred.
+  UmTestUtils::ExpectVariableHasValue(string("mychannel"),
+                                      provider_->var_release_channel());
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_allowed_connection_types_for_update());
+  UmTestUtils::ExpectVariableHasValue(
+      true, provider_->var_allow_kiosk_app_control_chrome_version());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ScatterFactorConverted) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetScatterFactorInSeconds(_))
+#if USE_DBUS
+      .Times(2)
+#else
+      .Times(1)
+#endif  // USE_DBUS
+      .WillRepeatedly(DoAll(SetArgPointee<0>(1234), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(TimeDelta::FromSeconds(1234),
+                                      provider_->var_scatter_factor());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NegativeScatterFactorIgnored) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetScatterFactorInSeconds(_))
+#if USE_DBUS
+      .Times(2)
+#else
+      .Times(1)
+#endif  // USE_DBUS
+      .WillRepeatedly(DoAll(SetArgPointee<0>(-1), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, AllowedTypesConverted) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetAllowedConnectionTypesForUpdate(_))
+#if USE_DBUS
+      .Times(2)
+#else
+      .Times(1)
+#endif  // USE_DBUS
+      .WillRepeatedly(DoAll(
+          SetArgPointee<0>(set<string>{"bluetooth", "wifi", "not-a-type"}),
+          Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(
+      set<ConnectionType>{ConnectionType::kWifi, ConnectionType::kBluetooth},
+      provider_->var_allowed_connection_types_for_update());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_random_provider.cc b/update_engine/update_manager/real_random_provider.cc
new file mode 100644
index 0000000..ed0eb4d
--- /dev/null
+++ b/update_engine/update_manager/real_random_provider.cc
@@ -0,0 +1,89 @@
+//
+// 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/update_manager/real_random_provider.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/scoped_file.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/update_manager/variable.h"
+
+using std::string;
+
+namespace {
+
+// The device providing randomness.
+const char* kRandomDevice = "/dev/urandom";
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+// A random seed variable.
+class RandomSeedVariable : public Variable<uint64_t> {
+ public:
+  // RandomSeedVariable is initialized as kVariableModeConst to let the
+  // EvaluationContext cache the value between different evaluations of the same
+  // policy request.
+  RandomSeedVariable(const string& name, FILE* fp)
+      : Variable<uint64_t>(name, kVariableModeConst), fp_(fp) {}
+  ~RandomSeedVariable() override {}
+
+ protected:
+  const uint64_t* GetValue(base::TimeDelta /* timeout */,
+                           string* errmsg) override {
+    uint64_t result;
+    // Aliasing via char pointer abides by the C/C++ strict-aliasing rules.
+    char* const buf = reinterpret_cast<char*>(&result);
+    unsigned int buf_rd = 0;
+
+    while (buf_rd < sizeof(result)) {
+      int rd = fread(buf + buf_rd, 1, sizeof(result) - buf_rd, fp_.get());
+      if (rd == 0 || ferror(fp_.get())) {
+        // Either EOF on fp or read failed.
+        if (errmsg) {
+          *errmsg = base::StringPrintf(
+              "Error reading from the random device: %s", kRandomDevice);
+        }
+        return nullptr;
+      }
+      buf_rd += rd;
+    }
+
+    return new uint64_t(result);
+  }
+
+ private:
+  base::ScopedFILE fp_;
+
+  DISALLOW_COPY_AND_ASSIGN(RandomSeedVariable);
+};
+
+bool RealRandomProvider::Init(void) {
+  FILE* fp = fopen(kRandomDevice, "r");
+  if (!fp)
+    return false;
+  var_seed_.reset(new RandomSeedVariable("seed", fp));
+  return true;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_random_provider.h b/update_engine/update_manager/real_random_provider.h
new file mode 100644
index 0000000..14ce7a3
--- /dev/null
+++ b/update_engine/update_manager/real_random_provider.h
@@ -0,0 +1,45 @@
+//
+// 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_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
+
+#include <memory>
+
+#include "update_engine/update_manager/random_provider.h"
+
+namespace chromeos_update_manager {
+
+// RandomProvider implementation class.
+class RealRandomProvider : public RandomProvider {
+ public:
+  RealRandomProvider() {}
+
+  Variable<uint64_t>* var_seed() override { return var_seed_.get(); }
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+ private:
+  // The seed() scoped variable.
+  std::unique_ptr<Variable<uint64_t>> var_seed_;
+
+  DISALLOW_COPY_AND_ASSIGN(RealRandomProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
diff --git a/update_engine/update_manager/real_random_provider_unittest.cc b/update_engine/update_manager/real_random_provider_unittest.cc
new file mode 100644
index 0000000..ca67da6
--- /dev/null
+++ b/update_engine/update_manager/real_random_provider_unittest.cc
@@ -0,0 +1,67 @@
+//
+// 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/update_manager/real_random_provider.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+
+#include "update_engine/update_manager/umtest_utils.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealRandomProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    // The provider initializes correctly.
+    provider_.reset(new RealRandomProvider());
+    ASSERT_NE(nullptr, provider_.get());
+    ASSERT_TRUE(provider_->Init());
+
+    provider_->var_seed();
+  }
+
+  unique_ptr<RealRandomProvider> provider_;
+};
+
+TEST_F(UmRealRandomProviderTest, InitFinalize) {
+  // The provider initializes all variables with valid objects.
+  EXPECT_NE(nullptr, provider_->var_seed());
+}
+
+TEST_F(UmRealRandomProviderTest, GetRandomValues) {
+  // Should not return the same random seed repeatedly.
+  unique_ptr<const uint64_t> value(
+      provider_->var_seed()->GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+  ASSERT_NE(nullptr, value.get());
+
+  // Test that at least the returned values are different. This test fails,
+  // by design, once every 2^320 runs.
+  bool is_same_value = true;
+  for (int i = 0; i < 5; i++) {
+    unique_ptr<const uint64_t> other_value(
+        provider_->var_seed()->GetValue(UmTestUtils::DefaultTimeout(),
+                                        nullptr));
+    ASSERT_NE(nullptr, other_value.get());
+    is_same_value = is_same_value && *other_value == *value;
+  }
+  EXPECT_FALSE(is_same_value);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_shill_provider.cc b/update_engine/update_manager/real_shill_provider.cc
new file mode 100644
index 0000000..2c58a7e
--- /dev/null
+++ b/update_engine/update_manager/real_shill_provider.cc
@@ -0,0 +1,167 @@
+//
+// 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/update_manager/real_shill_provider.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/type_name_undecorate.h>
+#include <shill/dbus-constants.h>
+#include <shill/dbus-proxies.h>
+
+using chromeos_update_engine::connection_utils::ParseConnectionType;
+using org::chromium::flimflam::ManagerProxyInterface;
+using org::chromium::flimflam::ServiceProxyInterface;
+using std::string;
+
+namespace chromeos_update_manager {
+
+bool RealShillProvider::Init() {
+  ManagerProxyInterface* manager_proxy = shill_proxy_->GetManagerProxy();
+  if (!manager_proxy)
+    return false;
+
+  // Subscribe to the manager's PropertyChanged signal.
+  manager_proxy->RegisterPropertyChangedSignalHandler(
+      base::Bind(&RealShillProvider::OnManagerPropertyChanged,
+                 base::Unretained(this)),
+      base::Bind(&RealShillProvider::OnSignalConnected,
+                 base::Unretained(this)));
+
+  // Attempt to read initial connection status. Even if this fails because shill
+  // is not responding (e.g. it is down) we'll be notified via "PropertyChanged"
+  // signal as soon as it comes up, so this is not a critical step.
+  brillo::VariantDictionary properties;
+  brillo::ErrorPtr error;
+  if (!manager_proxy->GetProperties(&properties, &error))
+    return true;
+
+  const auto& prop_default_service =
+      properties.find(shill::kDefaultServiceProperty);
+  if (prop_default_service != properties.end()) {
+    OnManagerPropertyChanged(prop_default_service->first,
+                             prop_default_service->second);
+  }
+
+  return true;
+}
+
+void RealShillProvider::OnManagerPropertyChanged(const string& name,
+                                                 const brillo::Any& value) {
+  if (name == shill::kDefaultServiceProperty) {
+    dbus::ObjectPath service_path = value.TryGet<dbus::ObjectPath>();
+    if (!service_path.IsValid()) {
+      LOG(WARNING) << "Got an invalid DefaultService path. The property value "
+                      "contains a "
+                   << value.GetUndecoratedTypeName()
+                   << ", read as the object path: '" << service_path.value()
+                   << "'";
+    }
+    ProcessDefaultService(service_path);
+  }
+}
+
+void RealShillProvider::OnSignalConnected(const string& interface_name,
+                                          const string& signal_name,
+                                          bool successful) {
+  if (!successful) {
+    LOG(ERROR) << "Couldn't connect to the signal " << interface_name << "."
+               << signal_name;
+  }
+}
+
+bool RealShillProvider::ProcessDefaultService(
+    const dbus::ObjectPath& default_service_path) {
+  // We assume that if the service path didn't change, then the connection
+  // type and the tethering status of it also didn't change.
+  if (default_service_path_ == default_service_path)
+    return true;
+
+  // Update the connection status.
+  default_service_path_ = default_service_path;
+  bool is_connected = (default_service_path_.IsValid() &&
+                       default_service_path_.value() != "/");
+  var_is_connected_.SetValue(is_connected);
+  var_conn_last_changed_.SetValue(clock_->GetWallclockTime());
+
+  if (!is_connected) {
+    var_conn_type_.UnsetValue();
+    var_conn_tethering_.UnsetValue();
+    return true;
+  }
+
+  // We create and dispose the ServiceProxyInterface on every request.
+  std::unique_ptr<ServiceProxyInterface> service =
+      shill_proxy_->GetServiceForPath(default_service_path_);
+
+  // Get the connection properties synchronously.
+  brillo::VariantDictionary properties;
+  brillo::ErrorPtr error;
+  if (!service->GetProperties(&properties, &error)) {
+    var_conn_type_.UnsetValue();
+    var_conn_tethering_.UnsetValue();
+    return false;
+  }
+
+  // Get the connection tethering mode.
+  const auto& prop_tethering = properties.find(shill::kTetheringProperty);
+  if (prop_tethering == properties.end()) {
+    // Remove the value if not present on the service. This most likely means an
+    // error in shill and the policy will handle it, but we will print a log
+    // message as well for accessing an unused variable.
+    var_conn_tethering_.UnsetValue();
+    LOG(ERROR) << "Could not find connection type (service: "
+               << default_service_path_.value() << ")";
+  } else {
+    // If the property doesn't contain a string value, the empty string will
+    // become kUnknown.
+    var_conn_tethering_.SetValue(
+        chromeos_update_engine::connection_utils::ParseConnectionTethering(
+            prop_tethering->second.TryGet<string>()));
+  }
+
+  // Get the connection type.
+  const auto& prop_type = properties.find(shill::kTypeProperty);
+  if (prop_type == properties.end()) {
+    var_conn_type_.UnsetValue();
+    LOG(ERROR) << "Could not find connection tethering mode (service: "
+               << default_service_path_.value() << ")";
+  } else {
+    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: " << default_service_path_.value()
+                   << "). Using default kUnknown value.";
+        var_conn_type_.SetValue(
+            chromeos_update_engine::ConnectionType::kUnknown);
+      } else {
+        var_conn_type_.SetValue(
+            ParseConnectionType(prop_physical->second.TryGet<string>()));
+      }
+    } else {
+      var_conn_type_.SetValue(ParseConnectionType(type_str));
+    }
+  }
+
+  return true;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_shill_provider.h b/update_engine/update_manager/real_shill_provider.h
new file mode 100644
index 0000000..e7708c8
--- /dev/null
+++ b/update_engine/update_manager/real_shill_provider.h
@@ -0,0 +1,101 @@
+//
+// 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_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
+
+// TODO(garnold) Much of the functionality in this module was adapted from the
+// update engine's connection_manager.  We need to make sure to deprecate use of
+// connection manager when the time comes.
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+#include <dbus/object_path.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/shill_proxy_interface.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+namespace chromeos_update_manager {
+
+// ShillProvider concrete implementation.
+class RealShillProvider : public ShillProvider {
+ public:
+  RealShillProvider(chromeos_update_engine::ShillProxyInterface* shill_proxy,
+                    chromeos_update_engine::ClockInterface* clock)
+      : shill_proxy_(shill_proxy), clock_(clock) {}
+
+  ~RealShillProvider() override = default;
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<bool>* var_is_connected() override {
+    return &var_is_connected_;
+  }
+
+  Variable<chromeos_update_engine::ConnectionType>* var_conn_type() override {
+    return &var_conn_type_;
+  }
+
+  Variable<chromeos_update_engine::ConnectionTethering>* var_conn_tethering() override {
+    return &var_conn_tethering_;
+  }
+
+  Variable<base::Time>* var_conn_last_changed() override {
+    return &var_conn_last_changed_;
+  }
+
+ private:
+  // A handler for ManagerProxy.PropertyChanged signal.
+  void OnManagerPropertyChanged(const std::string& name,
+                                const brillo::Any& value);
+
+  // Called when the signal in ManagerProxy.PropertyChanged is connected.
+  void OnSignalConnected(const std::string& interface_name,
+                         const std::string& signal_name,
+                         bool successful);
+
+  // Get the connection and populate the type and tethering status of the given
+  // default connection.
+  bool ProcessDefaultService(const dbus::ObjectPath& default_service_path);
+
+  // The current default service path, if connected. "/" means not connected.
+  dbus::ObjectPath default_service_path_{"uninitialized"};
+
+  // The mockable interface to access the shill DBus proxies.
+  std::unique_ptr<chromeos_update_engine::ShillProxyInterface> shill_proxy_;
+
+  // A clock abstraction (mockable).
+  chromeos_update_engine::ClockInterface* const clock_;
+
+  // The provider's variables.
+  AsyncCopyVariable<bool> var_is_connected_{"is_connected"};
+  AsyncCopyVariable<chromeos_update_engine::ConnectionType> var_conn_type_{
+      "conn_type"};
+  AsyncCopyVariable<chromeos_update_engine::ConnectionTethering>
+      var_conn_tethering_{"conn_tethering"};
+  AsyncCopyVariable<base::Time> var_conn_last_changed_{"conn_last_changed"};
+
+  DISALLOW_COPY_AND_ASSIGN(RealShillProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
diff --git a/update_engine/update_manager/real_shill_provider_unittest.cc b/update_engine/update_manager/real_shill_provider_unittest.cc
new file mode 100644
index 0000000..e821dc7
--- /dev/null
+++ b/update_engine/update_manager/real_shill_provider_unittest.cc
@@ -0,0 +1,533 @@
+//
+// 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/update_manager/real_shill_provider.h"
+
+#include <memory>
+#include <utility>
+
+#include <base/time/time.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/fake_message_loop.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/fake_clock.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/dbus_test_utils.h"
+#include "update_engine/fake_shill_proxy.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ConnectionTethering;
+using chromeos_update_engine::ConnectionType;
+using chromeos_update_engine::FakeClock;
+using org::chromium::flimflam::ManagerProxyMock;
+using org::chromium::flimflam::ServiceProxyMock;
+using std::unique_ptr;
+using testing::Mock;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace {
+
+// Fake service paths.
+const char* const kFakeEthernetServicePath = "/fake/ethernet/service";
+const char* const kFakeWifiServicePath = "/fake/wifi/service";
+const char* const kFakeWimaxServicePath = "/fake/wimax/service";
+const char* const kFakeBluetoothServicePath = "/fake/bluetooth/service";
+const char* const kFakeCellularServicePath = "/fake/cellular/service";
+const char* const kFakeVpnServicePath = "/fake/vpn/service";
+const char* const kFakeUnknownServicePath = "/fake/unknown/service";
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+class UmRealShillProviderTest : public ::testing::Test {
+ protected:
+  // Initialize the RealShillProvider under test.
+  void SetUp() override {
+    fake_clock_.SetWallclockTime(InitTime());
+    loop_.SetAsCurrent();
+    fake_shill_proxy_ = new chromeos_update_engine::FakeShillProxy();
+    provider_.reset(new RealShillProvider(fake_shill_proxy_, &fake_clock_));
+
+    ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_->GetManagerProxy();
+
+    // The PropertyChanged signal should be subscribed to.
+    MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER(
+        manager_property_changed_, *manager_proxy_mock, PropertyChanged);
+  }
+
+  void TearDown() override {
+    provider_.reset();
+    // Check for leaked callbacks on the main loop.
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  // These methods generate fixed timestamps for use in faking the current time.
+  Time InitTime() {
+    Time::Exploded now_exp;
+    now_exp.year = 2014;
+    now_exp.month = 3;
+    now_exp.day_of_week = 2;
+    now_exp.day_of_month = 18;
+    now_exp.hour = 8;
+    now_exp.minute = 5;
+    now_exp.second = 33;
+    now_exp.millisecond = 675;
+    return Time::FromLocalExploded(now_exp);
+  }
+
+  Time ConnChangedTime() {
+    return InitTime() + TimeDelta::FromSeconds(10);
+  }
+
+  // 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.
+  // Returns the mock object pointer, owned by the |fake_shill_proxy_|.
+  ServiceProxyMock* SetServiceReply(const std::string& service_path,
+                                    const char* service_type,
+                                    const char* physical_technology,
+                                    const char* service_tethering);
+
+  void InitWithDefaultService(const char* default_service) {
+    SetManagerReply(default_service, true);
+    // Check that provider initializes correctly.
+    EXPECT_TRUE(provider_->Init());
+    // RunOnce to notify the signal handler was connected properly.
+    EXPECT_TRUE(loop_.RunOnce(false));
+  }
+
+  // Sends a signal informing the provider about a default connection
+  // |service_path|. Sets the fake connection change time in
+  // |conn_change_time_p| if provided.
+  void SendDefaultServiceSignal(const std::string& service_path,
+                                Time* conn_change_time_p) {
+    const Time conn_change_time = ConnChangedTime();
+    fake_clock_.SetWallclockTime(conn_change_time);
+    ASSERT_TRUE(manager_property_changed_.IsHandlerRegistered());
+    manager_property_changed_.signal_callback().Run(
+        shill::kDefaultServiceProperty, dbus::ObjectPath(service_path));
+    fake_clock_.SetWallclockTime(conn_change_time + TimeDelta::FromSeconds(5));
+    if (conn_change_time_p)
+      *conn_change_time_p = conn_change_time;
+  }
+
+  // Sets up expectations for detection of a connection |service_path| with type
+  // |shill_type_str| and tethering mode |shill_tethering_str|. Ensures that the
+  // new connection status and change time are properly detected by the
+  // provider. Writes the fake connection change time to |conn_change_time_p|,
+  // if provided.
+  void SetupConnectionAndAttrs(const std::string& service_path,
+                               const char* shill_type,
+                               const char* shill_tethering,
+                               Time* conn_change_time_p) {
+    SetServiceReply(service_path, shill_type, nullptr, shill_tethering);
+    // Note: We don't setup this |service_path| as the default service path but
+    // we instead send a signal notifying the change since the code won't call
+    // GetProperties on the Manager object at this point.
+
+    // Send a signal about a new default service.
+    Time conn_change_time;
+    SendDefaultServiceSignal(service_path, &conn_change_time);
+
+    // Query the connection status, ensure last change time reported correctly.
+    UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+    UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                        provider_->var_conn_last_changed());
+
+    // Write the connection change time to the output argument.
+    if (conn_change_time_p)
+      *conn_change_time_p = conn_change_time;
+  }
+
+  // Sets up a connection and tests that its type is being properly detected by
+  // the provider.
+  void SetupConnectionAndTestType(const char* service_path,
+                                  const char* shill_type,
+                                  ConnectionType expected_conn_type) {
+    // Set up and test the connection, record the change time.
+    Time conn_change_time;
+    SetupConnectionAndAttrs(service_path,
+                            shill_type,
+                            shill::kTetheringNotDetectedState,
+                            &conn_change_time);
+
+    // Query the connection type, ensure last change time did not change.
+    UmTestUtils::ExpectVariableHasValue(expected_conn_type,
+                                        provider_->var_conn_type());
+    UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                        provider_->var_conn_last_changed());
+  }
+
+  // Sets up a connection and tests that its tethering mode is being properly
+  // detected by the provider.
+  void SetupConnectionAndTestTethering(
+      const char* service_path,
+      const char* shill_tethering,
+      ConnectionTethering expected_conn_tethering) {
+    // Set up and test the connection, record the change time.
+    Time conn_change_time;
+    SetupConnectionAndAttrs(
+        service_path, shill::kTypeEthernet, shill_tethering, &conn_change_time);
+
+    // Query the connection tethering, ensure last change time did not change.
+    UmTestUtils::ExpectVariableHasValue(expected_conn_tethering,
+                                        provider_->var_conn_tethering());
+    UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                        provider_->var_conn_last_changed());
+  }
+
+  brillo::FakeMessageLoop loop_{nullptr};
+  FakeClock fake_clock_;
+  chromeos_update_engine::FakeShillProxy* fake_shill_proxy_;
+
+  // The registered signal handler for the signal Manager.PropertyChanged.
+  chromeos_update_engine::dbus_test_utils::MockSignalHandler<
+      void(const std::string&, const brillo::Any&)> manager_property_changed_;
+
+  unique_ptr<RealShillProvider> provider_;
+};
+
+void UmRealShillProviderTest::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)));
+}
+
+ServiceProxyMock* UmRealShillProviderTest::SetServiceReply(
+    const std::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] = std::string(service_type);
+
+  if (physical_technology) {
+    reply_dict[shill::kPhysicalTechnologyProperty] =
+        std::string(physical_technology);
+  }
+
+  if (service_tethering)
+    reply_dict[shill::kTetheringProperty] = std::string(service_tethering);
+
+  ServiceProxyMock* service_proxy_mock = new ServiceProxyMock();
+
+  // Plumb return value into mock object.
+  EXPECT_CALL(*service_proxy_mock, GetProperties(_, _, _))
+      .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true)));
+
+  fake_shill_proxy_->SetServiceForPath(
+      dbus::ObjectPath(service_path),
+      brillo::make_unique_ptr(service_proxy_mock));
+  return service_proxy_mock;
+}
+
+
+// Query the connection status, type and time last changed, as they were set
+// during initialization (no signals).
+TEST_F(UmRealShillProviderTest, ReadBaseValues) {
+  InitWithDefaultService("/");
+  // Query the provider variables.
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+  UmTestUtils::ExpectVariableHasValue(InitTime(),
+                                      provider_->var_conn_last_changed());
+}
+
+// Ensure that invalid DBus paths are ignored.
+TEST_F(UmRealShillProviderTest, InvalidServicePath) {
+  InitWithDefaultService("invalid");
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+  UmTestUtils::ExpectVariableHasValue(InitTime(),
+                                      provider_->var_conn_last_changed());
+}
+
+// Ensure that a service path property including a different type is ignored.
+TEST_F(UmRealShillProviderTest, InvalidServicePathType) {
+  ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_->GetManagerProxy();
+  brillo::VariantDictionary reply_dict;
+  reply_dict[shill::kDefaultServiceProperty] = "/not/an/object/path";
+  EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _))
+      .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true)));
+
+  EXPECT_TRUE(provider_->Init());
+  EXPECT_TRUE(loop_.RunOnce(false));
+
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected());
+}
+
+// Test that Ethernet connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeEthernet) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestType(kFakeEthernetServicePath,
+                             shill::kTypeEthernet,
+                             ConnectionType::kEthernet);
+}
+
+// Test that Wifi connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeWifi) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestType(kFakeWifiServicePath,
+                             shill::kTypeWifi,
+                             ConnectionType::kWifi);
+}
+
+// Test that Wimax connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeWimax) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestType(kFakeWimaxServicePath,
+                             shill::kTypeWimax,
+                             ConnectionType::kWimax);
+}
+
+// Test that Bluetooth connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeBluetooth) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestType(kFakeBluetoothServicePath,
+                             shill::kTypeBluetooth,
+                             ConnectionType::kBluetooth);
+}
+
+// Test that Cellular connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeCellular) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestType(kFakeCellularServicePath,
+                             shill::kTypeCellular,
+                             ConnectionType::kCellular);
+}
+
+// Test that an unknown connection is identified as such.
+TEST_F(UmRealShillProviderTest, ReadConnTypeUnknown) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestType(kFakeUnknownServicePath,
+                             "FooConnectionType",
+                             ConnectionType::kUnknown);
+}
+
+// Tests that VPN connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeVpn) {
+  InitWithDefaultService("/");
+  // Mock logic for returning a default service path and its type.
+  SetServiceReply(kFakeVpnServicePath,
+                  shill::kTypeVPN,
+                  shill::kTypeWifi,
+                  shill::kTetheringNotDetectedState);
+
+  // Send a signal about a new default service.
+  Time conn_change_time;
+  SendDefaultServiceSignal(kFakeVpnServicePath, &conn_change_time);
+
+  // Query the connection type, ensure last change time reported correctly.
+  UmTestUtils::ExpectVariableHasValue(ConnectionType::kWifi,
+                                      provider_->var_conn_type());
+  UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                      provider_->var_conn_last_changed());
+}
+
+// Ensure that the connection type is properly cached in the provider through
+// subsequent variable readings.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheUsed) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestType(kFakeEthernetServicePath,
+                             shill::kTypeEthernet,
+                             ConnectionType::kEthernet);
+
+  UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet,
+                                      provider_->var_conn_type());
+}
+
+// Ensure that the cached connection type remains valid even when a default
+// connection signal occurs but the connection is not changed.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheRemainsValid) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestType(kFakeEthernetServicePath,
+                             shill::kTypeEthernet,
+                             ConnectionType::kEthernet);
+
+  SendDefaultServiceSignal(kFakeEthernetServicePath, nullptr);
+
+  UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet,
+                                      provider_->var_conn_type());
+}
+
+// Ensure that the cached connection type is invalidated and re-read when the
+// default connection changes.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheInvalidated) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestType(kFakeEthernetServicePath,
+                             shill::kTypeEthernet,
+                             ConnectionType::kEthernet);
+
+  SetupConnectionAndTestType(kFakeWifiServicePath,
+                             shill::kTypeWifi,
+                             ConnectionType::kWifi);
+}
+
+// Test that a non-tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringNotDetected) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  shill::kTetheringNotDetectedState,
+                                  ConnectionTethering::kNotDetected);
+}
+
+// Test that a suspected tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringSuspected) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  shill::kTetheringSuspectedState,
+                                  ConnectionTethering::kSuspected);
+}
+
+// Test that a confirmed tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringConfirmed) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  shill::kTetheringConfirmedState,
+                                  ConnectionTethering::kConfirmed);
+}
+
+// Test that an unknown tethering mode is identified as such.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringUnknown) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  "FooConnTethering",
+                                  ConnectionTethering::kUnknown);
+}
+
+// Ensure that the connection tethering mode is properly cached in the provider.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheUsed) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+                                  shill::kTetheringNotDetectedState,
+                                  ConnectionTethering::kNotDetected);
+
+  UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected,
+                                      provider_->var_conn_tethering());
+}
+
+// Ensure that the cached connection tethering mode remains valid even when a
+// default connection signal occurs but the connection is not changed.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheRemainsValid) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+                                  shill::kTetheringNotDetectedState,
+                                  ConnectionTethering::kNotDetected);
+
+  SendDefaultServiceSignal(kFakeEthernetServicePath, nullptr);
+
+  UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected,
+                                      provider_->var_conn_tethering());
+}
+
+// Ensure that the cached connection tethering mode is invalidated and re-read
+// when the default connection changes.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheInvalidated) {
+  InitWithDefaultService("/");
+  SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+                                  shill::kTetheringNotDetectedState,
+                                  ConnectionTethering::kNotDetected);
+
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  shill::kTetheringConfirmedState,
+                                  ConnectionTethering::kConfirmed);
+}
+
+// Fake two DBus signals prompting a default connection change, but otherwise
+// give the same service path. Check connection status and the time it was last
+// changed, making sure that it is the time when the first signal was sent (and
+// not the second).
+TEST_F(UmRealShillProviderTest, ReadLastChangedTimeTwoSignals) {
+  InitWithDefaultService("/");
+  // Send a default service signal twice, advancing the clock in between.
+  Time conn_change_time;
+  SetupConnectionAndAttrs(kFakeEthernetServicePath,
+                          shill::kTypeEthernet,
+                          shill::kTetheringNotDetectedState,
+                          &conn_change_time);
+  // This will set the service path to the same value, so it should not call
+  // GetProperties() again.
+  SendDefaultServiceSignal(kFakeEthernetServicePath, nullptr);
+
+  // Query the connection status, ensure last change time reported as the first
+  // time the signal was sent.
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+  UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                      provider_->var_conn_last_changed());
+}
+
+// Make sure that the provider initializes correctly even if shill is not
+// responding, that variables can be obtained, and that they all return a null
+// value (indicating that the underlying values were not set).
+TEST_F(UmRealShillProviderTest, NoInitConnStatusReadBaseValues) {
+  // Initialize the provider, no initial connection status response.
+  SetManagerReply(nullptr, false);
+  EXPECT_TRUE(provider_->Init());
+  EXPECT_TRUE(loop_.RunOnce(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_is_connected());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_conn_last_changed());
+}
+
+// Test that, once a signal is received, the connection status and other info
+// can be read correctly.
+TEST_F(UmRealShillProviderTest, NoInitConnStatusReadConnTypeEthernet) {
+  // Initialize the provider with no initial connection status response.
+  SetManagerReply(nullptr, false);
+  EXPECT_TRUE(provider_->Init());
+  EXPECT_TRUE(loop_.RunOnce(false));
+
+  SetupConnectionAndAttrs(kFakeEthernetServicePath,
+                          shill::kTypeEthernet,
+                          shill::kTetheringNotDetectedState,
+                          nullptr);
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_state.h b/update_engine/update_manager/real_state.h
new file mode 100644
index 0000000..e83c49d
--- /dev/null
+++ b/update_engine/update_manager/real_state.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_UPDATE_MANAGER_REAL_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_STATE_H_
+
+#include <memory>
+
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// State concrete implementation.
+class RealState : public State {
+ public:
+  ~RealState() override {}
+
+  RealState(ConfigProvider* config_provider,
+            DevicePolicyProvider* device_policy_provider,
+            RandomProvider* random_provider,
+            ShillProvider* shill_provider,
+            SystemProvider* system_provider,
+            TimeProvider* time_provider,
+            UpdaterProvider* updater_provider) :
+      config_provider_(config_provider),
+      device_policy_provider_(device_policy_provider),
+      random_provider_(random_provider),
+      shill_provider_(shill_provider),
+      system_provider_(system_provider),
+      time_provider_(time_provider),
+      updater_provider_(updater_provider) {}
+
+  // These methods return the given provider.
+  ConfigProvider* config_provider() override {
+    return config_provider_.get();
+  }
+  DevicePolicyProvider* device_policy_provider() override {
+    return device_policy_provider_.get();
+  }
+  RandomProvider* random_provider() override {
+    return random_provider_.get();
+  }
+  ShillProvider* shill_provider() override {
+    return shill_provider_.get();
+  }
+  SystemProvider* system_provider() override {
+    return system_provider_.get();
+  }
+  TimeProvider* time_provider() override {
+    return time_provider_.get();
+  }
+  UpdaterProvider* updater_provider() override {
+    return updater_provider_.get();
+  }
+
+ private:
+  // Instances of the providers.
+  std::unique_ptr<ConfigProvider> config_provider_;
+  std::unique_ptr<DevicePolicyProvider> device_policy_provider_;
+  std::unique_ptr<RandomProvider> random_provider_;
+  std::unique_ptr<ShillProvider> shill_provider_;
+  std::unique_ptr<SystemProvider> system_provider_;
+  std::unique_ptr<TimeProvider> time_provider_;
+  std::unique_ptr<UpdaterProvider> updater_provider_;
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_STATE_H_
diff --git a/update_engine/update_manager/real_system_provider.cc b/update_engine/update_manager/real_system_provider.cc
new file mode 100644
index 0000000..44d5566
--- /dev/null
+++ b/update_engine/update_manager/real_system_provider.cc
@@ -0,0 +1,141 @@
+//
+// 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/update_manager/real_system_provider.h"
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/utils.h"
+#if USE_LIBCROS
+#include "update_engine/libcros_proxy.h"
+#endif
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/variable.h"
+
+using std::string;
+
+namespace chromeos_update_manager {
+
+namespace {
+
+// The maximum number of consecutive failures before returning the default
+// constructor value for T instead of failure.
+const int kRetryPollVariableMaxRetry = 5;
+
+// The polling interval to be used whenever GetValue() returns an error.
+const int kRetryPollVariableRetryIntervalSeconds = 5 * 60;
+
+// The RetryPollVariable variable is a polling variable that allows the function
+// returning the value to fail a few times and shortens the polling rate when
+// that happens.
+template <typename T>
+class RetryPollVariable : public Variable<T> {
+ public:
+  RetryPollVariable(const string& name,
+                    const base::TimeDelta poll_interval,
+                    base::Callback<bool(T* res)> func)
+      : Variable<T>(name, poll_interval),
+        func_(func),
+        base_interval_(poll_interval) {
+    DCHECK_LT(kRetryPollVariableRetryIntervalSeconds,
+              base_interval_.InSeconds());
+  }
+
+ protected:
+  // Variable override.
+  const T* GetValue(base::TimeDelta /* timeout */,
+                    string* /* errmsg */) override {
+    std::unique_ptr<T> result(new T());
+    if (!func_.Run(result.get())) {
+      if (failed_attempts_ >= kRetryPollVariableMaxRetry) {
+        // Give up on the retries, set back the desired polling interval and
+        // return the default.
+        this->SetPollInterval(base_interval_);
+        return result.release();
+      }
+      this->SetPollInterval(
+          base::TimeDelta::FromSeconds(kRetryPollVariableRetryIntervalSeconds));
+      failed_attempts_++;
+      return nullptr;
+    }
+    failed_attempts_ = 0;
+    this->SetPollInterval(base_interval_);
+    return result.release();
+  }
+
+ private:
+  // The function to be called, stored as a base::Callback.
+  base::Callback<bool(T*)> func_;
+
+  // The desired polling interval when |func_| works and returns true.
+  base::TimeDelta base_interval_;
+
+  // The number of consecutive failed attempts made.
+  int failed_attempts_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(RetryPollVariable);
+};
+
+}  // namespace
+
+bool RealSystemProvider::Init() {
+  var_is_normal_boot_mode_.reset(
+      new ConstCopyVariable<bool>("is_normal_boot_mode",
+                                  hardware_->IsNormalBootMode()));
+
+  var_is_official_build_.reset(
+      new ConstCopyVariable<bool>("is_official_build",
+                                  hardware_->IsOfficialBuild()));
+
+  var_is_oobe_complete_.reset(
+      new CallCopyVariable<bool>(
+          "is_oobe_complete",
+          base::Bind(&chromeos_update_engine::HardwareInterface::IsOOBEComplete,
+                     base::Unretained(hardware_), nullptr)));
+
+  var_num_slots_.reset(
+      new ConstCopyVariable<unsigned int>(
+          "num_slots", boot_control_->GetNumSlots()));
+
+  var_kiosk_required_platform_version_.reset(new RetryPollVariable<string>(
+      "kiosk_required_platform_version",
+      base::TimeDelta::FromHours(5),  // Same as Chrome's CWS poll.
+      base::Bind(&RealSystemProvider::GetKioskAppRequiredPlatformVersion,
+                 base::Unretained(this))));
+
+  return true;
+}
+
+bool RealSystemProvider::GetKioskAppRequiredPlatformVersion(
+    string* required_platform_version) {
+#if USE_LIBCROS
+  brillo::ErrorPtr error;
+  if (!libcros_proxy_->service_interface_proxy()
+           ->GetKioskAppRequiredPlatformVersion(required_platform_version,
+                                                &error)) {
+    LOG(WARNING) << "Failed to get kiosk required platform version";
+    required_platform_version->clear();
+    return false;
+  }
+#endif
+
+  return true;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_system_provider.h b/update_engine/update_manager/real_system_provider.h
new file mode 100644
index 0000000..083943b
--- /dev/null
+++ b/update_engine/update_manager/real_system_provider.h
@@ -0,0 +1,85 @@
+//
+// 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_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/update_manager/system_provider.h"
+
+namespace chromeos_update_engine {
+class LibCrosProxy;
+}
+
+namespace chromeos_update_manager {
+
+// SystemProvider concrete implementation.
+class RealSystemProvider : public SystemProvider {
+ public:
+  RealSystemProvider(chromeos_update_engine::HardwareInterface* hardware,
+                     chromeos_update_engine::BootControlInterface* boot_control,
+                     chromeos_update_engine::LibCrosProxy* libcros_proxy)
+      : hardware_(hardware),
+        boot_control_(boot_control),
+        libcros_proxy_(libcros_proxy) {}
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<bool>* var_is_normal_boot_mode() override {
+    return var_is_normal_boot_mode_.get();
+  }
+
+  Variable<bool>* var_is_official_build() override {
+    return var_is_official_build_.get();
+  }
+
+  Variable<bool>* var_is_oobe_complete() override {
+    return var_is_oobe_complete_.get();
+  }
+
+  Variable<unsigned int>* var_num_slots() override {
+    return var_num_slots_.get();
+  }
+
+  Variable<std::string>* var_kiosk_required_platform_version() override {
+    return var_kiosk_required_platform_version_.get();
+  }
+
+ private:
+  bool GetKioskAppRequiredPlatformVersion(
+      std::string* required_platform_version);
+
+  std::unique_ptr<Variable<bool>> var_is_normal_boot_mode_;
+  std::unique_ptr<Variable<bool>> var_is_official_build_;
+  std::unique_ptr<Variable<bool>> var_is_oobe_complete_;
+  std::unique_ptr<Variable<unsigned int>> var_num_slots_;
+  std::unique_ptr<Variable<std::string>> var_kiosk_required_platform_version_;
+
+  chromeos_update_engine::HardwareInterface* const hardware_;
+  chromeos_update_engine::BootControlInterface* const boot_control_;
+  chromeos_update_engine::LibCrosProxy* const libcros_proxy_ ALLOW_UNUSED_TYPE;
+
+  DISALLOW_COPY_AND_ASSIGN(RealSystemProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
diff --git a/update_engine/update_manager/real_system_provider_unittest.cc b/update_engine/update_manager/real_system_provider_unittest.cc
new file mode 100644
index 0000000..c997ad8
--- /dev/null
+++ b/update_engine/update_manager/real_system_provider_unittest.cc
@@ -0,0 +1,144 @@
+//
+// 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/update_manager/real_system_provider.h"
+
+#include <memory>
+
+#include <base/time/time.h>
+#include <brillo/make_unique_ptr.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/update_manager/umtest_utils.h"
+#if USE_LIBCROS
+#include "libcros/dbus-proxies.h"
+#include "libcros/dbus-proxy-mocks.h"
+#include "update_engine/libcros_proxy.h"
+
+using org::chromium::LibCrosServiceInterfaceProxyMock;
+#endif  // USE_LIBCROS
+using std::unique_ptr;
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+
+#if USE_LIBCROS
+namespace {
+const char kRequiredPlatformVersion[] ="1234.0.0";
+}  // namespace
+#endif  // USE_LIBCROS
+
+namespace chromeos_update_manager {
+
+class UmRealSystemProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+#if USE_LIBCROS
+    service_interface_mock_ = new LibCrosServiceInterfaceProxyMock();
+    libcros_proxy_.reset(new chromeos_update_engine::LibCrosProxy(
+        brillo::make_unique_ptr(service_interface_mock_),
+        unique_ptr<
+            org::chromium::
+                UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface>()));
+    ON_CALL(*service_interface_mock_,
+            GetKioskAppRequiredPlatformVersion(_, _, _))
+        .WillByDefault(
+            DoAll(SetArgPointee<0>(kRequiredPlatformVersion), Return(true)));
+
+    provider_.reset(new RealSystemProvider(
+        &fake_hardware_, &fake_boot_control_, libcros_proxy_.get()));
+#else
+    provider_.reset(
+        new RealSystemProvider(&fake_hardware_, &fake_boot_control_, nullptr));
+#endif  // USE_LIBCROS
+    EXPECT_TRUE(provider_->Init());
+  }
+
+  chromeos_update_engine::FakeHardware fake_hardware_;
+  chromeos_update_engine::FakeBootControl fake_boot_control_;
+  unique_ptr<RealSystemProvider> provider_;
+
+#if USE_LIBCROS
+  // Local pointers to the mocks. The instances are owned by the
+  // |libcros_proxy_|.
+  LibCrosServiceInterfaceProxyMock* service_interface_mock_;
+
+  unique_ptr<chromeos_update_engine::LibCrosProxy> libcros_proxy_;
+#endif  // USE_LIBCROS
+};
+
+TEST_F(UmRealSystemProviderTest, InitTest) {
+  EXPECT_NE(nullptr, provider_->var_is_normal_boot_mode());
+  EXPECT_NE(nullptr, provider_->var_is_official_build());
+  EXPECT_NE(nullptr, provider_->var_is_oobe_complete());
+  EXPECT_NE(nullptr, provider_->var_kiosk_required_platform_version());
+}
+
+TEST_F(UmRealSystemProviderTest, IsOOBECompleteTrue) {
+  fake_hardware_.SetIsOOBEComplete(base::Time());
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_complete());
+}
+
+TEST_F(UmRealSystemProviderTest, IsOOBECompleteFalse) {
+  fake_hardware_.UnsetIsOOBEComplete();
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_complete());
+}
+
+#if USE_LIBCROS
+TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersion) {
+  UmTestUtils::ExpectVariableHasValue(
+      std::string(kRequiredPlatformVersion),
+      provider_->var_kiosk_required_platform_version());
+}
+
+TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersionFailure) {
+  EXPECT_CALL(*service_interface_mock_,
+              GetKioskAppRequiredPlatformVersion(_, _, _))
+      .WillOnce(Return(false));
+
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_kiosk_required_platform_version());
+}
+
+TEST_F(UmRealSystemProviderTest,
+       KioskRequiredPlatformVersionRecoveryFromFailure) {
+  EXPECT_CALL(*service_interface_mock_,
+              GetKioskAppRequiredPlatformVersion(_, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_kiosk_required_platform_version());
+  testing::Mock::VerifyAndClearExpectations(service_interface_mock_);
+
+  EXPECT_CALL(*service_interface_mock_,
+              GetKioskAppRequiredPlatformVersion(_, _, _))
+      .WillOnce(
+          DoAll(SetArgPointee<0>(kRequiredPlatformVersion), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(
+      std::string(kRequiredPlatformVersion),
+      provider_->var_kiosk_required_platform_version());
+}
+#else
+TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersion) {
+  UmTestUtils::ExpectVariableHasValue(
+      std::string(), provider_->var_kiosk_required_platform_version());
+}
+#endif
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_time_provider.cc b/update_engine/update_manager/real_time_provider.cc
new file mode 100644
index 0000000..ca3acad
--- /dev/null
+++ b/update_engine/update_manager/real_time_provider.cc
@@ -0,0 +1,83 @@
+//
+// 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/update_manager/real_time_provider.h"
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ClockInterface;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// A variable returning the current date.
+class CurrDateVariable : public Variable<Time> {
+ public:
+  // TODO(garnold) Turn this into an async variable with the needed callback
+  // logic for when it value changes.
+  CurrDateVariable(const string& name, ClockInterface* clock)
+      : Variable<Time>(name, TimeDelta::FromHours(1)), clock_(clock) {}
+
+ protected:
+  virtual const Time* GetValue(TimeDelta /* timeout */,
+                               string* /* errmsg */) {
+    Time::Exploded now_exp;
+    clock_->GetWallclockTime().LocalExplode(&now_exp);
+    now_exp.hour = now_exp.minute = now_exp.second = now_exp.millisecond = 0;
+    return new Time(Time::FromLocalExploded(now_exp));
+  }
+
+ private:
+  ClockInterface* clock_;
+
+  DISALLOW_COPY_AND_ASSIGN(CurrDateVariable);
+};
+
+// A variable returning the current hour in local time.
+class CurrHourVariable : public Variable<int> {
+ public:
+  // TODO(garnold) Turn this into an async variable with the needed callback
+  // logic for when it value changes.
+  CurrHourVariable(const string& name, ClockInterface* clock)
+      : Variable<int>(name, TimeDelta::FromMinutes(5)), clock_(clock) {}
+
+ protected:
+  virtual const int* GetValue(TimeDelta /* timeout */,
+                              string* /* errmsg */) {
+    Time::Exploded exploded;
+    clock_->GetWallclockTime().LocalExplode(&exploded);
+    return new int(exploded.hour);
+  }
+
+ private:
+  ClockInterface* clock_;
+
+  DISALLOW_COPY_AND_ASSIGN(CurrHourVariable);
+};
+
+bool RealTimeProvider::Init() {
+  var_curr_date_.reset(new CurrDateVariable("curr_date", clock_));
+  var_curr_hour_.reset(new CurrHourVariable("curr_hour", clock_));
+  return true;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_time_provider.h b/update_engine/update_manager/real_time_provider.h
new file mode 100644
index 0000000..e7cae94
--- /dev/null
+++ b/update_engine/update_manager/real_time_provider.h
@@ -0,0 +1,58 @@
+//
+// 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_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
+
+#include <memory>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/update_manager/time_provider.h"
+
+namespace chromeos_update_manager {
+
+// TimeProvider concrete implementation.
+class RealTimeProvider : public TimeProvider {
+ public:
+  explicit RealTimeProvider(chromeos_update_engine::ClockInterface* clock)
+      : clock_(clock) {}
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<base::Time>* var_curr_date() override {
+    return var_curr_date_.get();
+  }
+
+  Variable<int>* var_curr_hour() override {
+    return var_curr_hour_.get();
+  }
+
+ private:
+  // A clock abstraction (fakeable).
+  chromeos_update_engine::ClockInterface* const clock_;
+
+  std::unique_ptr<Variable<base::Time>> var_curr_date_;
+  std::unique_ptr<Variable<int>> var_curr_hour_;
+
+  DISALLOW_COPY_AND_ASSIGN(RealTimeProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
diff --git a/update_engine/update_manager/real_time_provider_unittest.cc b/update_engine/update_manager/real_time_provider_unittest.cc
new file mode 100644
index 0000000..0e1ef34
--- /dev/null
+++ b/update_engine/update_manager/real_time_provider_unittest.cc
@@ -0,0 +1,84 @@
+//
+// 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/update_manager/real_time_provider.h"
+
+#include <memory>
+
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using chromeos_update_engine::FakeClock;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealTimeProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    // The provider initializes correctly.
+    provider_.reset(new RealTimeProvider(&fake_clock_));
+    ASSERT_NE(nullptr, provider_.get());
+    ASSERT_TRUE(provider_->Init());
+  }
+
+  // Generates a fixed timestamp for use in faking the current time.
+  Time CurrTime() {
+    Time::Exploded now_exp;
+    now_exp.year = 2014;
+    now_exp.month = 3;
+    now_exp.day_of_week = 2;
+    now_exp.day_of_month = 18;
+    now_exp.hour = 8;
+    now_exp.minute = 5;
+    now_exp.second = 33;
+    now_exp.millisecond = 675;
+    return Time::FromLocalExploded(now_exp);
+  }
+
+  FakeClock fake_clock_;
+  unique_ptr<RealTimeProvider> provider_;
+};
+
+TEST_F(UmRealTimeProviderTest, CurrDateValid) {
+  const Time now = CurrTime();
+  Time::Exploded exploded;
+  now.LocalExplode(&exploded);
+  exploded.hour = 0;
+  exploded.minute = 0;
+  exploded.second = 0;
+  exploded.millisecond = 0;
+  const Time expected = Time::FromLocalExploded(exploded);
+
+  fake_clock_.SetWallclockTime(now);
+  UmTestUtils::ExpectVariableHasValue(expected, provider_->var_curr_date());
+}
+
+TEST_F(UmRealTimeProviderTest, CurrHourValid) {
+  const Time now = CurrTime();
+  Time::Exploded expected;
+  now.LocalExplode(&expected);
+  fake_clock_.SetWallclockTime(now);
+  UmTestUtils::ExpectVariableHasValue(expected.hour,
+                                      provider_->var_curr_hour());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_updater_provider.cc b/update_engine/update_manager/real_updater_provider.cc
new file mode 100644
index 0000000..096b067
--- /dev/null
+++ b/update_engine/update_manager/real_updater_provider.cc
@@ -0,0 +1,458 @@
+//
+// 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/update_manager/real_updater_provider.h"
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/omaha_request_params.h"
+#if USE_NESTLABS
+#include <update_engine/dbus-constants-nestlabs.h>
+#include "update_engine/update_attempter_nestlabs.h"
+#else
+#include <update_engine/dbus-constants.h>
+#include "update_engine/update_attempter.h"
+#endif
+
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::OmahaRequestParams;
+using chromeos_update_engine::SystemState;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// A templated base class for all update related variables. Provides uniform
+// construction and a system state handle.
+template<typename T>
+class UpdaterVariableBase : public Variable<T> {
+ public:
+  UpdaterVariableBase(const string& name, VariableMode mode,
+                      SystemState* system_state)
+      : Variable<T>(name, mode), system_state_(system_state) {}
+
+ protected:
+  // The system state used for pulling information from the updater.
+  inline SystemState* system_state() const { return system_state_; }
+
+ private:
+  SystemState* const system_state_;
+};
+
+// Helper class for issuing a GetStatus() to the UpdateAttempter.
+class GetStatusHelper {
+ public:
+  GetStatusHelper(SystemState* system_state, string* errmsg) {
+    is_success_ = system_state->update_attempter()->GetStatus(
+        &last_checked_time_, &progress_, &update_status_, &new_version_,
+        &payload_size_);
+    if (!is_success_ && errmsg)
+      *errmsg = "Failed to get a status update from the update engine";
+  }
+
+  inline bool is_success() { return is_success_; }
+  inline int64_t last_checked_time() { return last_checked_time_; }
+  inline double progress() { return progress_; }
+  inline const string& update_status() { return update_status_; }
+  inline const string& new_version() { return new_version_; }
+  inline int64_t payload_size() { return payload_size_; }
+
+ private:
+  bool is_success_;
+  int64_t last_checked_time_;
+  double progress_;
+  string update_status_;
+  string new_version_;
+  int64_t payload_size_;
+};
+
+// A variable reporting the time when a last update check was issued.
+class LastCheckedTimeVariable : public UpdaterVariableBase<Time> {
+ public:
+  LastCheckedTimeVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    GetStatusHelper raw(system_state(), errmsg);
+    if (!raw.is_success())
+      return nullptr;
+
+    return new Time(Time::FromTimeT(raw.last_checked_time()));
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(LastCheckedTimeVariable);
+};
+
+// A variable reporting the update (download) progress as a decimal fraction
+// between 0.0 and 1.0.
+class ProgressVariable : public UpdaterVariableBase<double> {
+ public:
+  ProgressVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<double>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const double* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    GetStatusHelper raw(system_state(), errmsg);
+    if (!raw.is_success())
+      return nullptr;
+
+    if (raw.progress() < 0.0 || raw.progress() > 1.0) {
+      if (errmsg) {
+        *errmsg = StringPrintf("Invalid progress value received: %f",
+                               raw.progress());
+      }
+      return nullptr;
+    }
+
+    return new double(raw.progress());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ProgressVariable);
+};
+
+// A variable reporting the stage in which the update process is.
+class StageVariable : public UpdaterVariableBase<Stage> {
+ public:
+  StageVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<Stage>(name, kVariableModePoll, system_state) {}
+
+ private:
+  struct CurrOpStrToStage {
+    const char* str;
+    Stage stage;
+  };
+  static const CurrOpStrToStage curr_op_str_to_stage[];
+
+  // Note: the method is defined outside the class so arraysize can work.
+  const Stage* GetValue(TimeDelta /* timeout */, string* errmsg) override;
+
+  DISALLOW_COPY_AND_ASSIGN(StageVariable);
+};
+
+const StageVariable::CurrOpStrToStage StageVariable::curr_op_str_to_stage[] = {
+  {update_engine::kUpdateStatusIdle, Stage::kIdle},
+  {update_engine::kUpdateStatusCheckingForUpdate, Stage::kCheckingForUpdate},
+  {update_engine::kUpdateStatusUpdateAvailable, Stage::kUpdateAvailable},
+  {update_engine::kUpdateStatusDownloading, Stage::kDownloading},
+  {update_engine::kUpdateStatusVerifying, Stage::kVerifying},
+  {update_engine::kUpdateStatusFinalizing, Stage::kFinalizing},
+  {update_engine::kUpdateStatusUpdatedNeedReboot, Stage::kUpdatedNeedReboot},
+  {  // NOLINT(whitespace/braces)
+    update_engine::kUpdateStatusReportingErrorEvent,
+    Stage::kReportingErrorEvent
+  },
+  {update_engine::kUpdateStatusAttemptingRollback, Stage::kAttemptingRollback},
+};
+
+const Stage* StageVariable::GetValue(TimeDelta /* timeout */,
+                                     string* errmsg) {
+  GetStatusHelper raw(system_state(), errmsg);
+  if (!raw.is_success())
+    return nullptr;
+
+  for (auto& key_val : curr_op_str_to_stage)
+    if (raw.update_status() == key_val.str)
+      return new Stage(key_val.stage);
+
+  if (errmsg)
+    *errmsg = string("Unknown update status: ") + raw.update_status();
+  return nullptr;
+}
+
+// A variable reporting the version number that an update is updating to.
+class NewVersionVariable : public UpdaterVariableBase<string> {
+ public:
+  NewVersionVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    GetStatusHelper raw(system_state(), errmsg);
+    if (!raw.is_success())
+      return nullptr;
+
+    return new string(raw.new_version());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(NewVersionVariable);
+};
+
+// A variable reporting the size of the update being processed in bytes.
+class PayloadSizeVariable : public UpdaterVariableBase<int64_t> {
+ public:
+  PayloadSizeVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<int64_t>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const int64_t* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    GetStatusHelper raw(system_state(), errmsg);
+    if (!raw.is_success())
+      return nullptr;
+
+    if (raw.payload_size() < 0) {
+      if (errmsg)
+        *errmsg = string("Invalid payload size: %" PRId64, raw.payload_size());
+      return nullptr;
+    }
+
+    return new int64_t(raw.payload_size());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(PayloadSizeVariable);
+};
+
+// A variable reporting the point in time an update last completed in the
+// current boot cycle.
+//
+// TODO(garnold) In general, both the current boottime and wallclock time
+// readings should come from the time provider and be moderated by the
+// evaluation context, so that they are uniform throughout the evaluation of a
+// policy request.
+class UpdateCompletedTimeVariable : public UpdaterVariableBase<Time> {
+ public:
+  UpdateCompletedTimeVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    Time update_boottime;
+    if (!system_state()->update_attempter()->GetBootTimeAtUpdate(
+            &update_boottime)) {
+      if (errmsg)
+        *errmsg = "Update completed time could not be read";
+      return nullptr;
+    }
+
+    chromeos_update_engine::ClockInterface* clock = system_state()->clock();
+    Time curr_boottime = clock->GetBootTime();
+    if (curr_boottime < update_boottime) {
+      if (errmsg)
+        *errmsg = "Update completed time more recent than current time";
+      return nullptr;
+    }
+    TimeDelta duration_since_update = curr_boottime - update_boottime;
+    return new Time(clock->GetWallclockTime() - duration_since_update);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateCompletedTimeVariable);
+};
+
+// Variables reporting the current image channel.
+class CurrChannelVariable : public UpdaterVariableBase<string> {
+ public:
+  CurrChannelVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    OmahaRequestParams* request_params = system_state()->request_params();
+    string channel = request_params->current_channel();
+    if (channel.empty()) {
+      if (errmsg)
+        *errmsg = "No current channel";
+      return nullptr;
+    }
+    return new string(channel);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(CurrChannelVariable);
+};
+
+// Variables reporting the new image channel.
+class NewChannelVariable : public UpdaterVariableBase<string> {
+ public:
+  NewChannelVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    OmahaRequestParams* request_params = system_state()->request_params();
+    string channel = request_params->target_channel();
+    if (channel.empty()) {
+      if (errmsg)
+        *errmsg = "No new channel";
+      return nullptr;
+    }
+    return new string(channel);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(NewChannelVariable);
+};
+
+// A variable class for reading Boolean prefs values.
+class BooleanPrefVariable
+    : public AsyncCopyVariable<bool>,
+      public chromeos_update_engine::PrefsInterface::ObserverInterface {
+ public:
+  BooleanPrefVariable(const string& name,
+                      chromeos_update_engine::PrefsInterface* prefs,
+                      const char* key,
+                      bool default_val)
+      : AsyncCopyVariable<bool>(name),
+        prefs_(prefs),
+        key_(key),
+        default_val_(default_val) {
+    prefs->AddObserver(key, this);
+    OnPrefSet(key);
+  }
+  ~BooleanPrefVariable() {
+    prefs_->RemoveObserver(key_, this);
+  }
+
+ private:
+  // Reads the actual value from the Prefs instance and updates the Variable
+  // value.
+  void OnPrefSet(const string& key) override {
+    bool result = default_val_;
+    if (prefs_ && prefs_->Exists(key_) && !prefs_->GetBoolean(key_, &result))
+      result = default_val_;
+    // AsyncCopyVariable will take care of values that didn't change.
+    SetValue(result);
+  }
+
+  void OnPrefDeleted(const string& key) override {
+    SetValue(default_val_);
+  }
+
+  chromeos_update_engine::PrefsInterface* prefs_;
+
+  // The Boolean preference key and default value.
+  const char* const key_;
+  const bool default_val_;
+
+  DISALLOW_COPY_AND_ASSIGN(BooleanPrefVariable);
+};
+
+// A variable returning the number of consecutive failed update checks.
+class ConsecutiveFailedUpdateChecksVariable
+    : public UpdaterVariableBase<unsigned int> {
+ public:
+  ConsecutiveFailedUpdateChecksVariable(const string& name,
+                                        SystemState* system_state)
+      : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
+                                          system_state) {}
+
+ private:
+  const unsigned int* GetValue(TimeDelta /* timeout */,
+                               string* /* errmsg */) override {
+    return new unsigned int(
+        system_state()->update_attempter()->consecutive_failed_update_checks());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ConsecutiveFailedUpdateChecksVariable);
+};
+
+// A variable returning the server-dictated poll interval.
+class ServerDictatedPollIntervalVariable
+    : public UpdaterVariableBase<unsigned int> {
+ public:
+  ServerDictatedPollIntervalVariable(const string& name,
+                                     SystemState* system_state)
+      : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
+                                          system_state) {}
+
+ private:
+  const unsigned int* GetValue(TimeDelta /* timeout */,
+                               string* /* errmsg */) override {
+    return new unsigned int(
+        system_state()->update_attempter()->server_dictated_poll_interval());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ServerDictatedPollIntervalVariable);
+};
+
+// An async variable that tracks changes to forced update requests.
+class ForcedUpdateRequestedVariable
+    : public UpdaterVariableBase<UpdateRequestStatus> {
+ public:
+  ForcedUpdateRequestedVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<UpdateRequestStatus>::UpdaterVariableBase(
+          name, kVariableModeAsync, system_state) {
+    system_state->update_attempter()->set_forced_update_pending_callback(
+        new base::Callback<void(bool, bool)>(  // NOLINT(readability/function)
+            base::Bind(&ForcedUpdateRequestedVariable::Reset,
+                       base::Unretained(this))));
+  }
+
+ private:
+  const UpdateRequestStatus* GetValue(TimeDelta /* timeout */,
+                                      string* /* errmsg */) override {
+    return new UpdateRequestStatus(update_request_status_);
+  }
+
+  void Reset(bool forced_update_requested, bool is_interactive) {
+    UpdateRequestStatus new_value = UpdateRequestStatus::kNone;
+    if (forced_update_requested)
+      new_value = (is_interactive ? UpdateRequestStatus::kInteractive :
+                   UpdateRequestStatus::kPeriodic);
+    if (update_request_status_ != new_value) {
+      update_request_status_ = new_value;
+      NotifyValueChanged();
+    }
+  }
+
+  UpdateRequestStatus update_request_status_ = UpdateRequestStatus::kNone;
+
+  DISALLOW_COPY_AND_ASSIGN(ForcedUpdateRequestedVariable);
+};
+
+// RealUpdaterProvider methods.
+
+RealUpdaterProvider::RealUpdaterProvider(SystemState* system_state)
+  : system_state_(system_state),
+    var_updater_started_time_("updater_started_time",
+                              system_state->clock()->GetWallclockTime()),
+    var_last_checked_time_(
+        new LastCheckedTimeVariable("last_checked_time", system_state_)),
+    var_update_completed_time_(
+        new UpdateCompletedTimeVariable("update_completed_time",
+                                        system_state_)),
+    var_progress_(new ProgressVariable("progress", system_state_)),
+    var_stage_(new StageVariable("stage", system_state_)),
+    var_new_version_(new NewVersionVariable("new_version", system_state_)),
+    var_payload_size_(new PayloadSizeVariable("payload_size", system_state_)),
+    var_curr_channel_(new CurrChannelVariable("curr_channel", system_state_)),
+    var_new_channel_(new NewChannelVariable("new_channel", system_state_)),
+    var_p2p_enabled_(
+        new BooleanPrefVariable("p2p_enabled", system_state_->prefs(),
+                                chromeos_update_engine::kPrefsP2PEnabled,
+                                false)),
+    var_cellular_enabled_(
+        new BooleanPrefVariable(
+            "cellular_enabled", system_state_->prefs(),
+            chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+            false)),
+    var_consecutive_failed_update_checks_(
+        new ConsecutiveFailedUpdateChecksVariable(
+            "consecutive_failed_update_checks", system_state_)),
+    var_server_dictated_poll_interval_(
+        new ServerDictatedPollIntervalVariable(
+            "server_dictated_poll_interval", system_state_)),
+    var_forced_update_requested_(
+        new ForcedUpdateRequestedVariable(
+            "forced_update_requested", system_state_)) {}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_updater_provider.h b/update_engine/update_manager/real_updater_provider.h
new file mode 100644
index 0000000..b99bcc5
--- /dev/null
+++ b/update_engine/update_manager/real_updater_provider.h
@@ -0,0 +1,124 @@
+//
+// 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_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// A concrete UpdaterProvider implementation using local (in-process) bindings.
+class RealUpdaterProvider : public UpdaterProvider {
+ public:
+  // We assume that any other object handle we get from the system state is
+  // "volatile", and so must be re-acquired whenever access is needed; this
+  // guarantees that parts of the system state can be mocked out at any time
+  // during testing. We further assume that, by the time Init() is called, the
+  // system state object is fully populated and usable.
+  explicit RealUpdaterProvider(
+      chromeos_update_engine::SystemState* system_state);
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init() { return true; }
+
+  Variable<base::Time>* var_updater_started_time() override {
+    return &var_updater_started_time_;
+  }
+
+  Variable<base::Time>* var_last_checked_time() override {
+    return var_last_checked_time_.get();
+  }
+
+  Variable<base::Time>* var_update_completed_time() override {
+    return var_update_completed_time_.get();
+  }
+
+  Variable<double>* var_progress() override {
+    return var_progress_.get();
+  }
+
+  Variable<Stage>* var_stage() override {
+    return var_stage_.get();
+  }
+
+  Variable<std::string>* var_new_version() override {
+    return var_new_version_.get();
+  }
+
+  Variable<int64_t>* var_payload_size() override {
+    return var_payload_size_.get();
+  }
+
+  Variable<std::string>* var_curr_channel() override {
+    return var_curr_channel_.get();
+  }
+
+  Variable<std::string>* var_new_channel() override {
+    return var_new_channel_.get();
+  }
+
+  Variable<bool>* var_p2p_enabled() override {
+    return var_p2p_enabled_.get();
+  }
+
+  Variable<bool>* var_cellular_enabled() override {
+    return var_cellular_enabled_.get();
+  }
+
+  Variable<unsigned int>* var_consecutive_failed_update_checks() override {
+    return var_consecutive_failed_update_checks_.get();
+  }
+
+  Variable<unsigned int>* var_server_dictated_poll_interval() override {
+    return var_server_dictated_poll_interval_.get();
+  }
+
+  Variable<UpdateRequestStatus>* var_forced_update_requested() override {
+    return var_forced_update_requested_.get();
+  }
+
+ private:
+  // A pointer to the update engine's system state aggregator.
+  chromeos_update_engine::SystemState* system_state_;
+
+  // Variable implementations.
+  ConstCopyVariable<base::Time> var_updater_started_time_;
+  std::unique_ptr<Variable<base::Time>> var_last_checked_time_;
+  std::unique_ptr<Variable<base::Time>> var_update_completed_time_;
+  std::unique_ptr<Variable<double>> var_progress_;
+  std::unique_ptr<Variable<Stage>> var_stage_;
+  std::unique_ptr<Variable<std::string>> var_new_version_;
+  std::unique_ptr<Variable<int64_t>> var_payload_size_;
+  std::unique_ptr<Variable<std::string>> var_curr_channel_;
+  std::unique_ptr<Variable<std::string>> var_new_channel_;
+  std::unique_ptr<Variable<bool>> var_p2p_enabled_;
+  std::unique_ptr<Variable<bool>> var_cellular_enabled_;
+  std::unique_ptr<Variable<unsigned int>> var_consecutive_failed_update_checks_;
+  std::unique_ptr<Variable<unsigned int>> var_server_dictated_poll_interval_;
+  std::unique_ptr<Variable<UpdateRequestStatus>> var_forced_update_requested_;
+
+  DISALLOW_COPY_AND_ASSIGN(RealUpdaterProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
diff --git a/update_engine/update_manager/real_updater_provider_unittest.cc b/update_engine/update_manager/real_updater_provider_unittest.cc
new file mode 100644
index 0000000..bfbacb8
--- /dev/null
+++ b/update_engine/update_manager/real_updater_provider_unittest.cc
@@ -0,0 +1,447 @@
+//
+// 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/update_manager/real_updater_provider.h"
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+#ifdef USE_NESTLABS
+#include <update_engine/dbus-constants-nestlabs.h>
+#else
+#include <update_engine/dbus-constants.h>
+#endif
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_update_attempter.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::FakeClock;
+using chromeos_update_engine::FakePrefs;
+using chromeos_update_engine::FakeSystemState;
+using chromeos_update_engine::OmahaRequestParams;
+using std::string;
+using std::unique_ptr;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace {
+
+// Generates a fixed timestamp for use in faking the current time.
+Time FixedTime() {
+  Time::Exploded now_exp;
+  now_exp.year = 2014;
+  now_exp.month = 3;
+  now_exp.day_of_week = 2;
+  now_exp.day_of_month = 18;
+  now_exp.hour = 8;
+  now_exp.minute = 5;
+  now_exp.second = 33;
+  now_exp.millisecond = 675;
+  return Time::FromLocalExploded(now_exp);
+}
+
+// Rounds down a timestamp to the nearest second. This is useful when faking
+// times that are converted to time_t (no sub-second resolution).
+Time RoundedToSecond(Time time) {
+  Time::Exploded exp;
+  time.LocalExplode(&exp);
+  exp.millisecond = 0;
+  return Time::FromLocalExploded(exp);
+}
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+class UmRealUpdaterProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    fake_clock_ = fake_sys_state_.fake_clock();
+    fake_sys_state_.set_prefs(&fake_prefs_);
+    provider_.reset(new RealUpdaterProvider(&fake_sys_state_));
+    ASSERT_NE(nullptr, provider_.get());
+    // Check that provider initializes correctly.
+    ASSERT_TRUE(provider_->Init());
+  }
+
+  // Sets up mock expectations for testing the update completed time reporting.
+  // |valid| determines whether the returned time is valid. Returns the expected
+  // update completed time value.
+  Time SetupUpdateCompletedTime(bool valid) {
+    const TimeDelta kDurationSinceUpdate = TimeDelta::FromMinutes(7);
+    const Time kUpdateBootTime = Time() + kDurationSinceUpdate * 2;
+    const Time kCurrBootTime = (valid ?
+                                kUpdateBootTime + kDurationSinceUpdate :
+                                kUpdateBootTime - kDurationSinceUpdate);
+    const Time kCurrWallclockTime = FixedTime();
+    EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+                GetBootTimeAtUpdate(_))
+        .WillOnce(DoAll(SetArgPointee<0>(kUpdateBootTime), Return(true)));
+    fake_clock_->SetBootTime(kCurrBootTime);
+    fake_clock_->SetWallclockTime(kCurrWallclockTime);
+    return kCurrWallclockTime - kDurationSinceUpdate;
+  }
+
+  FakeSystemState fake_sys_state_;
+  FakeClock* fake_clock_;  // Short for fake_sys_state_.fake_clock()
+  FakePrefs fake_prefs_;
+  unique_ptr<RealUpdaterProvider> provider_;
+};
+
+TEST_F(UmRealUpdaterProviderTest, UpdaterStartedTimeIsWallclockTime) {
+  fake_clock_->SetWallclockTime(Time::FromDoubleT(123.456));
+  fake_clock_->SetMonotonicTime(Time::FromDoubleT(456.123));
+  // Run SetUp again to re-setup the provider under test to use these values.
+  SetUp();
+  UmTestUtils::ExpectVariableHasValue(Time::FromDoubleT(123.456),
+                                      provider_->var_updater_started_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeOkay) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<0>(FixedTime().ToTimeT()), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(RoundedToSecond(FixedTime()),
+                                      provider_->var_last_checked_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_last_checked_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMin) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(0.0), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(0.0, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMid) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(0.3), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(0.3, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMax) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(1.0), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(1.0, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooSmall) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(-2.0), Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooBig) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(2.0), Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayIdle) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusIdle),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kIdle, provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayCheckingForUpdate) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusCheckingForUpdate),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kCheckingForUpdate,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdateAvailable) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusUpdateAvailable),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kUpdateAvailable,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayDownloading) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusDownloading),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kDownloading,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayVerifying) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusVerifying),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kVerifying,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayFinalizing) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusFinalizing),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kFinalizing,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdatedNeedReboot) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusUpdatedNeedReboot),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kUpdatedNeedReboot,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayReportingErrorEvent) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusReportingErrorEvent),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kReportingErrorEvent,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayAttemptingRollback) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusAttemptingRollback),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kAttemptingRollback,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailUnknown) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>("FooUpdateEngineState"),
+                      Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailEmpty) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(""), Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewVersionOkay) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<3>("1.2.0"), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(string("1.2.0"),
+                                      provider_->var_new_version());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewVersionFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_new_version());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayZero) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(0)), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(0),
+                                      provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayArbitrary) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(567890)),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(567890),
+                                      provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayTwoGigabytes) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(1) << 31),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(1) << 31,
+                                      provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNegative) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(-1024)),
+                      Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCurrChannelOkay) {
+  const string kChannelName("foo-channel");
+  OmahaRequestParams request_params(&fake_sys_state_);
+  request_params.Init("", "", false);
+  request_params.set_current_channel(kChannelName);
+  fake_sys_state_.set_request_params(&request_params);
+  UmTestUtils::ExpectVariableHasValue(kChannelName,
+                                      provider_->var_curr_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCurrChannelFailEmpty) {
+  OmahaRequestParams request_params(&fake_sys_state_);
+  request_params.Init("", "", false);
+  request_params.set_current_channel("");
+  fake_sys_state_.set_request_params(&request_params);
+  UmTestUtils::ExpectVariableNotSet(provider_->var_curr_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewChannelOkay) {
+  const string kChannelName("foo-channel");
+  OmahaRequestParams request_params(&fake_sys_state_);
+  request_params.Init("", "", false);
+  request_params.set_target_channel(kChannelName);
+  fake_sys_state_.set_request_params(&request_params);
+  UmTestUtils::ExpectVariableHasValue(kChannelName,
+                                      provider_->var_new_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewChannelFailEmpty) {
+  OmahaRequestParams request_params(&fake_sys_state_);
+  request_params.Init("", "", false);
+  request_params.set_target_channel("");
+  fake_sys_state_.set_request_params(&request_params);
+  UmTestUtils::ExpectVariableNotSet(provider_->var_new_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefDoesntExist) {
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefReadsFalse) {
+  fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, false);
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledReadWhenInitialized) {
+  fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, true);
+  SetUp();
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledUpdated) {
+  fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, false);
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+  fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, true);
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_p2p_enabled());
+  fake_prefs_.Delete(chromeos_update_engine::kPrefsP2PEnabled);
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefDoesntExist) {
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefReadsTrue) {
+  fake_prefs_.SetBoolean(
+      chromeos_update_engine::kPrefsUpdateOverCellularPermission, true);
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeOkay) {
+  Time expected = SetupUpdateCompletedTime(true);
+  UmTestUtils::ExpectVariableHasValue(expected,
+                                      provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetBootTimeAtUpdate(_))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailInvalidValue) {
+  SetupUpdateCompletedTime(false);
+  UmTestUtils::ExpectVariableNotSet(provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetConsecutiveFailedUpdateChecks) {
+  const unsigned int kNumFailedChecks = 3;
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              consecutive_failed_update_checks())
+      .WillRepeatedly(Return(kNumFailedChecks));
+  UmTestUtils::ExpectVariableHasValue(
+      kNumFailedChecks, provider_->var_consecutive_failed_update_checks());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetServerDictatedPollInterval) {
+  const unsigned int kPollInterval = 2 * 60 * 60;  // Two hours.
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              server_dictated_poll_interval())
+      .WillRepeatedly(Return(kPollInterval));
+  UmTestUtils::ExpectVariableHasValue(
+      kPollInterval, provider_->var_server_dictated_poll_interval());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/shill_provider.h b/update_engine/update_manager/shill_provider.h
new file mode 100644
index 0000000..e6f4628
--- /dev/null
+++ b/update_engine/update_manager/shill_provider.h
@@ -0,0 +1,58 @@
+//
+// 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_UPDATE_MANAGER_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/connection_utils.h"
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for networking related information.
+class ShillProvider : public Provider {
+ public:
+  ~ShillProvider() override {}
+
+  // A variable returning whether we currently have network connectivity.
+  virtual Variable<bool>* var_is_connected() = 0;
+
+  // A variable returning the current network connection type. Unknown if not
+  // connected.
+  virtual Variable<chromeos_update_engine::ConnectionType>* var_conn_type() = 0;
+
+  // A variable returning the tethering mode of a network connection. Unknown if
+  // not connected.
+  virtual Variable<chromeos_update_engine::ConnectionTethering>*
+      var_conn_tethering() = 0;
+
+  // A variable returning the time when network connection last changed.
+  // Initialized to current time.
+  virtual Variable<base::Time>* var_conn_last_changed() = 0;
+
+ protected:
+  ShillProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ShillProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_
diff --git a/update_engine/update_manager/state.h b/update_engine/update_manager/state.h
new file mode 100644
index 0000000..d428059
--- /dev/null
+++ b/update_engine/update_manager/state.h
@@ -0,0 +1,54 @@
+//
+// 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_UPDATE_MANAGER_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_STATE_H_
+
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/random_provider.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/system_provider.h"
+#include "update_engine/update_manager/time_provider.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// The State class is an interface to the ensemble of providers. This class
+// gives visibility of the state providers to policy implementations.
+class State {
+ public:
+  virtual ~State() {}
+
+  // These methods return the given provider.
+  virtual ConfigProvider* config_provider() = 0;
+  virtual DevicePolicyProvider* device_policy_provider() = 0;
+  virtual RandomProvider* random_provider() = 0;
+  virtual ShillProvider* shill_provider() = 0;
+  virtual SystemProvider* system_provider() = 0;
+  virtual TimeProvider* time_provider() = 0;
+  virtual UpdaterProvider* updater_provider() = 0;
+
+ protected:
+  State() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(State);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_STATE_H_
diff --git a/update_engine/update_manager/state_factory.cc b/update_engine/update_manager/state_factory.cc
new file mode 100644
index 0000000..2b3ce63
--- /dev/null
+++ b/update_engine/update_manager/state_factory.cc
@@ -0,0 +1,102 @@
+//
+// 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/update_manager/state_factory.h"
+
+#include <memory>
+
+#include <base/logging.h>
+#include <brillo/make_unique_ptr.h>
+#if USE_DBUS
+#include <session_manager/dbus-proxies.h>
+#endif  // USE_DBUS
+
+#include "update_engine/common/clock_interface.h"
+#if USE_DBUS
+#include "update_engine/dbus_connection.h"
+#endif  // USE_DBUS
+#include "update_engine/update_manager/fake_shill_provider.h"
+#include "update_engine/update_manager/real_config_provider.h"
+#include "update_engine/update_manager/real_device_policy_provider.h"
+#include "update_engine/update_manager/real_random_provider.h"
+#include "update_engine/update_manager/real_state.h"
+#include "update_engine/update_manager/real_system_provider.h"
+#include "update_engine/update_manager/real_time_provider.h"
+#include "update_engine/update_manager/real_updater_provider.h"
+#if USE_SHILL
+#include "update_engine/shill_proxy.h"
+#include "update_engine/update_manager/real_shill_provider.h"
+#endif  // USE_SHILL
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+State* DefaultStateFactory(
+    policy::PolicyProvider* policy_provider,
+    chromeos_update_engine::LibCrosProxy* libcros_proxy,
+    chromeos_update_engine::SystemState* system_state) {
+  chromeos_update_engine::ClockInterface* const clock = system_state->clock();
+  unique_ptr<RealConfigProvider> config_provider(
+      new RealConfigProvider(system_state->hardware()));
+#if USE_DBUS
+  scoped_refptr<dbus::Bus> bus =
+      chromeos_update_engine::DBusConnection::Get()->GetDBus();
+  unique_ptr<RealDevicePolicyProvider> device_policy_provider(
+      new RealDevicePolicyProvider(
+          brillo::make_unique_ptr(
+              new org::chromium::SessionManagerInterfaceProxy(bus)),
+          policy_provider));
+#else
+  unique_ptr<RealDevicePolicyProvider> device_policy_provider(
+      new RealDevicePolicyProvider(policy_provider));
+#endif  // USE_DBUS
+#if USE_SHILL
+  unique_ptr<RealShillProvider> shill_provider(
+      new RealShillProvider(new chromeos_update_engine::ShillProxy(), clock));
+#else
+  unique_ptr<FakeShillProvider> shill_provider(new FakeShillProvider());
+#endif  // USE_SHILL
+  unique_ptr<RealRandomProvider> random_provider(new RealRandomProvider());
+  unique_ptr<RealSystemProvider> system_provider(new RealSystemProvider(
+      system_state->hardware(), system_state->boot_control(), libcros_proxy));
+  unique_ptr<RealTimeProvider> time_provider(new RealTimeProvider(clock));
+  unique_ptr<RealUpdaterProvider> updater_provider(
+      new RealUpdaterProvider(system_state));
+
+  if (!(config_provider->Init() &&
+        device_policy_provider->Init() &&
+        random_provider->Init() &&
+#if USE_SHILL
+        shill_provider->Init() &&
+#endif  // USE_SHILL
+        system_provider->Init() &&
+        time_provider->Init() &&
+        updater_provider->Init())) {
+    LOG(ERROR) << "Error initializing providers";
+    return nullptr;
+  }
+
+  return new RealState(config_provider.release(),
+                       device_policy_provider.release(),
+                       random_provider.release(),
+                       shill_provider.release(),
+                       system_provider.release(),
+                       time_provider.release(),
+                       updater_provider.release());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/state_factory.h b/update_engine/update_manager/state_factory.h
new file mode 100644
index 0000000..f1b576c
--- /dev/null
+++ b/update_engine/update_manager/state_factory.h
@@ -0,0 +1,41 @@
+//
+// 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_UPDATE_MANAGER_STATE_FACTORY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
+
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_engine {
+class LibCrosProxy;
+}
+
+namespace chromeos_update_manager {
+
+// Creates and initializes a new UpdateManager State instance containing real
+// providers instantiated using the passed interfaces. The State doesn't take
+// ownership of the passed interfaces, which need to remain available during the
+// life of this instance.  Returns null if one of the underlying providers fails
+// to initialize.
+State* DefaultStateFactory(
+    policy::PolicyProvider* policy_provider,
+    chromeos_update_engine::LibCrosProxy* libcros_proxy,
+    chromeos_update_engine::SystemState* system_state);
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
diff --git a/update_engine/update_manager/system_provider.h b/update_engine/update_manager/system_provider.h
new file mode 100644
index 0000000..13e188b
--- /dev/null
+++ b/update_engine/update_manager/system_provider.h
@@ -0,0 +1,58 @@
+//
+// 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_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for system information, mostly constant, such as the information
+// reported by crossystem, the kernel boot command line and the partition table.
+class SystemProvider : public Provider {
+ public:
+  ~SystemProvider() override {}
+
+  // Returns true if the boot mode is normal or if it's unable to
+  // determine the boot mode. Returns false if the boot mode is
+  // developer.
+  virtual Variable<bool>* var_is_normal_boot_mode() = 0;
+
+  // Returns whether this is an official Chrome OS build.
+  virtual Variable<bool>* var_is_official_build() = 0;
+
+  // Returns a variable that tells whether OOBE was completed.
+  virtual Variable<bool>* var_is_oobe_complete() = 0;
+
+  // Returns a variable that tells the number of slots in the system.
+  virtual Variable<unsigned int>* var_num_slots() = 0;
+
+  // Returns the required platform version of the configured auto launch
+  // with zero delay kiosk app if any.
+  virtual Variable<std::string>* var_kiosk_required_platform_version() = 0;
+
+ protected:
+  SystemProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SystemProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
diff --git a/update_engine/update_manager/time_provider.h b/update_engine/update_manager/time_provider.h
new file mode 100644
index 0000000..663ec2c
--- /dev/null
+++ b/update_engine/update_manager/time_provider.h
@@ -0,0 +1,48 @@
+//
+// 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_UPDATE_MANAGER_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_TIME_PROVIDER_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for time related information.
+class TimeProvider : public Provider {
+ public:
+  ~TimeProvider() override {}
+
+  // Returns the current date. The time of day component will be zero.
+  virtual Variable<base::Time>* var_curr_date() = 0;
+
+  // Returns the current hour (0 to 23) in local time. The type is int to keep
+  // consistent with base::Time.
+  virtual Variable<int>* var_curr_hour() = 0;
+
+ protected:
+  TimeProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TimeProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_TIME_PROVIDER_H_
diff --git a/update_engine/update_manager/umtest_utils.cc b/update_engine/update_manager/umtest_utils.cc
new file mode 100644
index 0000000..aa88141
--- /dev/null
+++ b/update_engine/update_manager/umtest_utils.cc
@@ -0,0 +1,29 @@
+//
+// 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/update_manager/umtest_utils.h"
+
+#include <base/time/time.h>
+
+namespace chromeos_update_manager {
+
+const unsigned UmTestUtils::kDefaultTimeoutInSeconds = 1;
+
+void PrintTo(const EvalStatus& status, ::std::ostream* os) {
+  *os << ToString(status);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/umtest_utils.h b/update_engine/update_manager/umtest_utils.h
new file mode 100644
index 0000000..80693db
--- /dev/null
+++ b/update_engine/update_manager/umtest_utils.h
@@ -0,0 +1,68 @@
+//
+// 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_UPDATE_MANAGER_UMTEST_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UMTEST_UTILS_H_
+
+#include <iostream>  // NOLINT(readability/streams)
+#include <memory>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// A help class with common functionality for use in Update Manager testing.
+class UmTestUtils {
+ public:
+  // A default timeout to use when making various queries.
+  static const base::TimeDelta DefaultTimeout() {
+    return base::TimeDelta::FromSeconds(kDefaultTimeoutInSeconds);
+  }
+
+  // Calls GetValue on |variable| and expects its result to be |expected|.
+  template<typename T>
+  static void ExpectVariableHasValue(const T& expected, Variable<T>* variable) {
+    ASSERT_NE(nullptr, variable);
+    std::unique_ptr<const T> value(
+        variable->GetValue(DefaultTimeout(), nullptr));
+    ASSERT_NE(nullptr, value.get()) << "Variable: " << variable->GetName();
+    EXPECT_EQ(expected, *value) << "Variable: " << variable->GetName();
+  }
+
+  // Calls GetValue on |variable| and expects its result to be null.
+  template<typename T>
+  static void ExpectVariableNotSet(Variable<T>* variable) {
+    ASSERT_NE(nullptr, variable);
+    std::unique_ptr<const T> value(
+        variable->GetValue(DefaultTimeout(), nullptr));
+    EXPECT_EQ(nullptr, value.get()) << "Variable: " << variable->GetName();
+  }
+
+ private:
+  static const unsigned kDefaultTimeoutInSeconds;
+};
+
+// PrintTo() functions are used by gtest to print these values. They need to be
+// defined on the same namespace where the type was defined.
+void PrintTo(const EvalStatus& status, ::std::ostream* os);
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UMTEST_UTILS_H_
diff --git a/update_engine/update_manager/update_manager-inl.h b/update_engine/update_manager/update_manager-inl.h
new file mode 100644
index 0000000..77224cf
--- /dev/null
+++ b/update_engine/update_manager/update_manager-inl.h
@@ -0,0 +1,165 @@
+//
+// 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_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/update_manager/evaluation_context.h"
+
+namespace chromeos_update_manager {
+
+template<typename R, typename... Args>
+EvalStatus UpdateManager::EvaluatePolicy(
+    EvaluationContext* ec,
+    EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                        std::string*, R*,
+                                        Args...) const,
+    R* result, Args... args) {
+  // If expiration timeout fired, dump the context and reset expiration.
+  // IMPORTANT: We must still proceed with evaluation of the policy in this
+  // case, so that the evaluation time (and corresponding reevaluation timeouts)
+  // are readjusted.
+  if (ec->is_expired()) {
+    LOG(WARNING) << "Request timed out, evaluation context: "
+                 << ec->DumpContext();
+    ec->ResetExpiration();
+  }
+
+  // Reset the evaluation context.
+  ec->ResetEvaluation();
+
+  const std::string policy_name = policy_->PolicyRequestName(policy_method);
+  LOG(INFO) << policy_name << ": START";
+
+  // First try calling the actual policy.
+  std::string error;
+  EvalStatus status = (policy_.get()->*policy_method)(ec, state_.get(), &error,
+                                                      result, args...);
+  // If evaluating the main policy failed, defer to the default policy.
+  if (status == EvalStatus::kFailed) {
+    LOG(WARNING) << "Evaluating policy failed: " << error
+                 << "\nEvaluation context: " << ec->DumpContext();
+    error.clear();
+    status = (default_policy_.*policy_method)(ec, state_.get(), &error, result,
+                                              args...);
+    if (status == EvalStatus::kFailed) {
+      LOG(WARNING) << "Evaluating default policy failed: " << error;
+    } else if (status == EvalStatus::kAskMeAgainLater) {
+      LOG(ERROR)
+          << "Default policy would block; this is a bug, forcing failure.";
+      status = EvalStatus::kFailed;
+    }
+  }
+
+  LOG(INFO) << policy_name << ": END";
+
+  return status;
+}
+
+template<typename R, typename... Args>
+void UpdateManager::OnPolicyReadyToEvaluate(
+    scoped_refptr<EvaluationContext> ec,
+    base::Callback<void(EvalStatus status, const R& result)> callback,
+    EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                        std::string*, R*,
+                                        Args...) const,
+    Args... args) {
+  // Evaluate the policy.
+  R result;
+  EvalStatus status = EvaluatePolicy(ec.get(), policy_method, &result, args...);
+
+  if (status != EvalStatus::kAskMeAgainLater) {
+    // AsyncPolicyRequest finished.
+    callback.Run(status, result);
+    return;
+  }
+
+  // Re-schedule the policy request based on used variables.
+  base::Closure reeval_callback = base::Bind(
+      &UpdateManager::OnPolicyReadyToEvaluate<R, Args...>,
+      base::Unretained(this), ec, callback,
+      policy_method, args...);
+  if (ec->RunOnValueChangeOrTimeout(reeval_callback))
+    return;  // Reevaluation scheduled successfully.
+
+  // Scheduling a reevaluation can fail because policy method didn't use any
+  // non-const variable nor there's any time-based event that will change the
+  // status of evaluation.  Alternatively, this may indicate an error in the use
+  // of the scheduling interface.
+  LOG(ERROR) << "Failed to schedule a reevaluation of policy "
+             << policy_->PolicyRequestName(policy_method) << "; this is a bug.";
+  callback.Run(status, result);
+}
+
+template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+EvalStatus UpdateManager::PolicyRequest(
+    EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                        std::string*, R*,
+                                        ExpectedArgs...) const,
+    R* result, ActualArgs... args) {
+  scoped_refptr<EvaluationContext> ec(
+      new EvaluationContext(clock_, evaluation_timeout_));
+  // A PolicyRequest always consists on a single evaluation on a new
+  // EvaluationContext.
+  // IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we
+  // explicitly instantiate EvaluatePolicy with the latter in lieu of the
+  // former.
+  EvalStatus ret = EvaluatePolicy<R, ExpectedArgs...>(ec.get(), policy_method,
+                                                      result, args...);
+  // Sync policy requests must not block, if they do then this is an error.
+  DCHECK(EvalStatus::kAskMeAgainLater != ret);
+  LOG_IF(WARNING, EvalStatus::kAskMeAgainLater == ret)
+      << "Sync request used with an async policy; this is a bug";
+  return ret;
+}
+
+template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+void UpdateManager::AsyncPolicyRequest(
+    base::Callback<void(EvalStatus, const R& result)> callback,
+    EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                        std::string*, R*,
+                                        ExpectedArgs...) const,
+    ActualArgs... args) {
+  scoped_refptr<EvaluationContext> ec =
+      new EvaluationContext(
+          clock_, evaluation_timeout_, expiration_timeout_,
+          std::unique_ptr<base::Callback<void(EvaluationContext*)>>(
+              new base::Callback<void(EvaluationContext*)>(
+                  base::Bind(&UpdateManager::UnregisterEvalContext,
+                             weak_ptr_factory_.GetWeakPtr()))));
+  if (!ec_repo_.insert(ec.get()).second) {
+    LOG(ERROR) << "Failed to register evaluation context; this is a bug.";
+  }
+
+  // IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we
+  // explicitly instantiate UpdateManager::OnPolicyReadyToEvaluate with the
+  // latter in lieu of the former.
+  base::Closure eval_callback = base::Bind(
+      &UpdateManager::OnPolicyReadyToEvaluate<R, ExpectedArgs...>,
+      base::Unretained(this), ec, callback, policy_method, args...);
+  brillo::MessageLoop::current()->PostTask(FROM_HERE, eval_callback);
+}
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
diff --git a/update_engine/update_manager/update_manager.cc b/update_engine/update_manager/update_manager.cc
new file mode 100644
index 0000000..8e9b221
--- /dev/null
+++ b/update_engine/update_manager/update_manager.cc
@@ -0,0 +1,50 @@
+//
+// 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/update_manager/update_manager.h"
+
+#include "update_engine/update_manager/chromeos_policy.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+UpdateManager::UpdateManager(chromeos_update_engine::ClockInterface* clock,
+                             base::TimeDelta evaluation_timeout,
+                             base::TimeDelta expiration_timeout, State* state)
+      : default_policy_(clock), state_(state), clock_(clock),
+        evaluation_timeout_(evaluation_timeout),
+        expiration_timeout_(expiration_timeout),
+        weak_ptr_factory_(this) {
+  // TODO(deymo): Make it possible to replace this policy with a different
+  // implementation with a build-time flag.
+  policy_.reset(new ChromeOSPolicy());
+}
+
+UpdateManager::~UpdateManager() {
+  // Remove pending main loop events associated with any of the outstanding
+  // evaluation contexts. This will prevent dangling pending events, causing
+  // these contexts to be destructed once the repo itself is destructed.
+  for (auto& ec : ec_repo_)
+    ec->RemoveObserversAndTimeout();
+}
+
+void UpdateManager::UnregisterEvalContext(EvaluationContext* ec) {
+  if (!ec_repo_.erase(ec)) {
+    LOG(ERROR) << "Unregistering an unknown evaluation context, this is a bug.";
+  }
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/update_manager.conf.example b/update_engine/update_manager/update_manager.conf.example
new file mode 100644
index 0000000..2d77974
--- /dev/null
+++ b/update_engine/update_manager/update_manager.conf.example
@@ -0,0 +1,18 @@
+# Configuration file for the update-manager component of update_engine.
+#
+# Normally this file is loaded from /etc/update_manager.conf. If
+# running update_engine in developer mode (and only if running in
+# developer mode), we attempt to load
+#
+#  /mnt/stateful_partition/etc/update_manager.conf
+#
+# and use it if it exists. If it doesn't exist, we fall back to
+# /etc/update_manager.conf.
+#
+# Note: changes to this file are not automatically applied. Use the
+# command "restart update-engine" from a root shell to make your
+# changes take effect.
+
+# Set to true if the device supports the concept of OOBE
+# (Out-Of-the-Box-Experience), false if it doesn't.
+is_oobe_enabled=true
diff --git a/update_engine/update_manager/update_manager.h b/update_engine/update_manager/update_manager.h
new file mode 100644
index 0000000..a2f35df
--- /dev/null
+++ b/update_engine/update_manager/update_manager.h
@@ -0,0 +1,178 @@
+//
+// 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_UPDATE_MANAGER_UPDATE_MANAGER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// Comparator for scoped_refptr objects.
+template<typename T>
+struct ScopedRefPtrLess {
+  bool operator()(const scoped_refptr<T>& first,
+                  const scoped_refptr<T>& second) const {
+    return first.get() < second.get();
+  }
+};
+
+// The main Update Manager singleton class.
+class UpdateManager {
+ public:
+  // Creates the UpdateManager instance, assuming ownership on the provided
+  // |state|.
+  UpdateManager(chromeos_update_engine::ClockInterface* clock,
+                base::TimeDelta evaluation_timeout,
+                base::TimeDelta expiration_timeout, State* state);
+
+  virtual ~UpdateManager();
+
+  // PolicyRequest() evaluates the given policy with the provided arguments and
+  // returns the result. The |policy_method| is the pointer-to-method of the
+  // Policy class for the policy request to call. The UpdateManager will call
+  // this method on the right policy. The pointer |result| must not be null
+  // and the remaining |args| depend on the arguments required by the passed
+  // |policy_method|.
+  //
+  // When the policy request succeeds, the |result| is set and the method
+  // returns EvalStatus::kSucceeded, otherwise, the |result| may not be set.  A
+  // policy called with this method should not block (i.e. return
+  // EvalStatus::kAskMeAgainLater), which is considered a programming error. On
+  // failure, EvalStatus::kFailed is returned.
+  //
+  // An example call to this method is:
+  //   um.PolicyRequest(&Policy::SomePolicyMethod, &bool_result, arg1, arg2);
+  template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+  EvalStatus PolicyRequest(
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          ExpectedArgs...) const,
+      R* result, ActualArgs...);
+
+  // Evaluates the given |policy_method| policy with the provided |args|
+  // arguments and calls the |callback| callback with the result when done.
+  //
+  // If the policy implementation should block, returning a
+  // EvalStatus::kAskMeAgainLater status the Update Manager will re-evaluate the
+  // policy until another status is returned. If the policy implementation based
+  // its return value solely on const variables, the callback will be called
+  // with the EvalStatus::kAskMeAgainLater status (which indicates an error).
+  template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+  void AsyncPolicyRequest(
+      base::Callback<void(EvalStatus, const R& result)> callback,
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          ExpectedArgs...) const,
+      ActualArgs... args);
+
+ protected:
+  // The UpdateManager receives ownership of the passed Policy instance.
+  void set_policy(const Policy* policy) {
+    policy_.reset(policy);
+  }
+
+  // State getter used for testing.
+  State* state() { return state_.get(); }
+
+ private:
+  FRIEND_TEST(UmUpdateManagerTest, PolicyRequestCallsPolicy);
+  FRIEND_TEST(UmUpdateManagerTest, PolicyRequestCallsDefaultOnError);
+  FRIEND_TEST(UmUpdateManagerTest, PolicyRequestDoesntBlockDeathTest);
+  FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestDelaysEvaluation);
+  FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestTimeoutDoesNotFire);
+  FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestTimesOut);
+
+  // EvaluatePolicy() evaluates the passed |policy_method| method on the current
+  // policy with the given |args| arguments. If the method fails, the default
+  // policy is used instead.
+  template<typename R, typename... Args>
+  EvalStatus EvaluatePolicy(
+      EvaluationContext* ec,
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          Args...) const,
+      R* result, Args... args);
+
+  // OnPolicyReadyToEvaluate() is called by the main loop when the evaluation
+  // of the given |policy_method| should be executed. If the evaluation finishes
+  // the |callback| callback is called passing the |result| and the |status|
+  // returned by the policy. If the evaluation returns an
+  // EvalStatus::kAskMeAgainLater state, the |callback| will NOT be called and
+  // the evaluation will be re-scheduled to be called later.
+  template<typename R, typename... Args>
+  void OnPolicyReadyToEvaluate(
+      scoped_refptr<EvaluationContext> ec,
+      base::Callback<void(EvalStatus status, const R& result)> callback,
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          Args...) const,
+      Args... args);
+
+  // Unregisters (removes from repo) a previously created EvaluationContext.
+  void UnregisterEvalContext(EvaluationContext* ec);
+
+  // The policy used by the UpdateManager. Note that since it is a const Policy,
+  // policy implementations are not allowed to persist state on this class.
+  std::unique_ptr<const Policy> policy_;
+
+  // A safe default value to the current policy. This policy is used whenever
+  // a policy implementation fails with EvalStatus::kFailed.
+  const DefaultPolicy default_policy_;
+
+  // State Providers.
+  std::unique_ptr<State> state_;
+
+  // Pointer to the mockable clock interface;
+  chromeos_update_engine::ClockInterface* clock_;
+
+  // Timeout for a policy evaluation.
+  const base::TimeDelta evaluation_timeout_;
+
+  // Timeout for expiration of the evaluation context, used for async requests.
+  const base::TimeDelta expiration_timeout_;
+
+  // Repository of previously created EvaluationContext objects. These are being
+  // unregistered (and the reference released) when the context is being
+  // destructed; alternatively, when the UpdateManager instance is destroyed, it
+  // will remove all pending events associated with all outstanding contexts
+  // (which should, in turn, trigger their destruction).
+  std::set<scoped_refptr<EvaluationContext>,
+           ScopedRefPtrLess<EvaluationContext>> ec_repo_;
+
+  base::WeakPtrFactory<UpdateManager> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateManager);
+};
+
+}  // namespace chromeos_update_manager
+
+// Include the implementation of the template methods.
+#include "update_engine/update_manager/update_manager-inl.h"
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_H_
diff --git a/update_engine/update_manager/update_manager_unittest.cc b/update_engine/update_manager/update_manager_unittest.cc
new file mode 100644
index 0000000..03f1610
--- /dev/null
+++ b/update_engine/update_manager/update_manager_unittest.cc
@@ -0,0 +1,325 @@
+//
+// 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/update_manager/update_manager.h"
+
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/test/simple_test_clock.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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/fake_state.h"
+#include "update_engine/update_manager/mock_policy.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::Time;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using brillo::MessageLoopRunMaxIterations;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::FakeClock;
+using std::pair;
+using std::string;
+using std::tuple;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+// Generates a fixed timestamp for use in faking the current time.
+Time FixedTime() {
+  Time::Exploded now_exp;
+  now_exp.year = 2014;
+  now_exp.month = 3;
+  now_exp.day_of_week = 2;
+  now_exp.day_of_month = 18;
+  now_exp.hour = 8;
+  now_exp.minute = 5;
+  now_exp.second = 33;
+  now_exp.millisecond = 675;
+  return Time::FromLocalExploded(now_exp);
+}
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+class UmUpdateManagerTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    fake_state_ = new FakeState();
+    umut_.reset(new UpdateManager(&fake_clock_, TimeDelta::FromSeconds(5),
+                                  TimeDelta::FromSeconds(1), fake_state_));
+  }
+
+  void TearDown() override {
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  base::SimpleTestClock test_clock_;
+  brillo::FakeMessageLoop loop_{&test_clock_};
+  FakeState* fake_state_;  // Owned by the umut_.
+  FakeClock fake_clock_;
+  unique_ptr<UpdateManager> umut_;
+};
+
+// The FailingPolicy implements a single method and make it always fail. This
+// class extends the DefaultPolicy class to allow extensions of the Policy
+// class without extending nor changing this test.
+class FailingPolicy : public DefaultPolicy {
+ public:
+  explicit FailingPolicy(int* num_called_p) : num_called_p_(num_called_p) {}
+  FailingPolicy() : FailingPolicy(nullptr) {}
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+                                string* error,
+                                UpdateCheckParams* result) const override {
+    if (num_called_p_)
+      (*num_called_p_)++;
+    *error = "FailingPolicy failed.";
+    return EvalStatus::kFailed;
+  }
+
+ protected:
+  string PolicyName() const override { return "FailingPolicy"; }
+
+ private:
+  int* num_called_p_;
+};
+
+// The LazyPolicy always returns EvalStatus::kAskMeAgainLater.
+class LazyPolicy : public DefaultPolicy {
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+                                string* error,
+                                UpdateCheckParams* result) const override {
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+ protected:
+  string PolicyName() const override { return "LazyPolicy"; }
+};
+
+// A policy that sleeps for a predetermined amount of time, then checks for a
+// wallclock-based time threshold (if given) and returns
+// EvalStatus::kAskMeAgainLater if not passed; otherwise, returns
+// EvalStatus::kSucceeded. Increments a counter every time it is being queried,
+// if a pointer to it is provided.
+class DelayPolicy : public DefaultPolicy {
+ public:
+  DelayPolicy(int sleep_secs, Time time_threshold, int* num_called_p)
+      : sleep_secs_(sleep_secs), time_threshold_(time_threshold),
+        num_called_p_(num_called_p) {}
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+                                string* error,
+                                UpdateCheckParams* result) const override {
+    if (num_called_p_)
+      (*num_called_p_)++;
+
+    // Sleep for a predetermined amount of time.
+    if (sleep_secs_ > 0)
+      sleep(sleep_secs_);
+
+    // Check for a time threshold. This can be used to ensure that the policy
+    // has some non-constant dependency.
+    if (time_threshold_ < Time::Max() &&
+        ec->IsWallclockTimeGreaterThan(time_threshold_))
+      return EvalStatus::kSucceeded;
+
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+ protected:
+  string PolicyName() const override { return "DelayPolicy"; }
+
+ private:
+  int sleep_secs_;
+  Time time_threshold_;
+  int* num_called_p_;
+};
+
+// AccumulateCallsCallback() adds to the passed |acc| accumulator vector pairs
+// of EvalStatus and T instances. This allows to create a callback that keeps
+// track of when it is called and the arguments passed to it, to be used with
+// the UpdateManager::AsyncPolicyRequest().
+template<typename T>
+static void AccumulateCallsCallback(vector<pair<EvalStatus, T>>* acc,
+                                    EvalStatus status, const T& result) {
+  acc->push_back(std::make_pair(status, result));
+}
+
+// Tests that policy requests are completed successfully. It is important that
+// this tests cover all policy requests as defined in Policy.
+TEST_F(UmUpdateManagerTest, PolicyRequestCallUpdateCheckAllowed) {
+  UpdateCheckParams result;
+  EXPECT_EQ(EvalStatus::kSucceeded, umut_->PolicyRequest(
+      &Policy::UpdateCheckAllowed, &result));
+}
+
+TEST_F(UmUpdateManagerTest, PolicyRequestCallUpdateCanStart) {
+  UpdateState update_state = UpdateState();
+  update_state.is_interactive = true;
+  update_state.is_delta_payload = false;
+  update_state.first_seen = FixedTime();
+  update_state.num_checks = 1;
+  update_state.num_failures = 0;
+  update_state.failures_last_updated = Time();
+  update_state.download_urls = vector<string>{"http://fake/url/"};
+  update_state.download_errors_max = 10;
+  update_state.p2p_downloading_disabled = false;
+  update_state.p2p_sharing_disabled = false;
+  update_state.p2p_num_attempts = 0;
+  update_state.p2p_first_attempted = Time();
+  update_state.last_download_url_idx = -1;
+  update_state.last_download_url_num_errors = 0;
+  update_state.download_errors = vector<tuple<int, ErrorCode, Time>>();
+  update_state.backoff_expiry = Time();
+  update_state.is_backoff_disabled = false;
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(15);
+  update_state.scatter_check_threshold = 4;
+  update_state.scatter_wait_period_max = TimeDelta::FromSeconds(60);
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 8;
+
+  UpdateDownloadParams result;
+  EXPECT_EQ(EvalStatus::kSucceeded,
+            umut_->PolicyRequest(&Policy::UpdateCanStart, &result,
+                                 update_state));
+}
+
+TEST_F(UmUpdateManagerTest, PolicyRequestCallsDefaultOnError) {
+  umut_->set_policy(new FailingPolicy());
+
+  // Tests that the DefaultPolicy instance is called when the method fails,
+  // which will set this as true.
+  UpdateCheckParams result;
+  result.updates_enabled = false;
+  EvalStatus status = umut_->PolicyRequest(
+      &Policy::UpdateCheckAllowed, &result);
+  EXPECT_EQ(EvalStatus::kSucceeded, status);
+  EXPECT_TRUE(result.updates_enabled);
+}
+
+// This test only applies to debug builds where DCHECK is enabled.
+#if DCHECK_IS_ON
+TEST_F(UmUpdateManagerTest, PolicyRequestDoesntBlockDeathTest) {
+  // The update manager should die (DCHECK) if a policy called synchronously
+  // returns a kAskMeAgainLater value.
+  UpdateCheckParams result;
+  umut_->set_policy(new LazyPolicy());
+  EXPECT_DEATH(umut_->PolicyRequest(&Policy::UpdateCheckAllowed, &result), "");
+}
+#endif  // DCHECK_IS_ON
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestDelaysEvaluation) {
+  // To avoid differences in code execution order between an AsyncPolicyRequest
+  // call on a policy that returns AskMeAgainLater the first time and one that
+  // succeeds the first time, we ensure that the passed callback is called from
+  // the main loop in both cases even when we could evaluate it right now.
+  umut_->set_policy(new FailingPolicy());
+
+  vector<pair<EvalStatus, UpdateCheckParams>> calls;
+  Callback<void(EvalStatus, const UpdateCheckParams&)> callback = Bind(
+      AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+  umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+  // The callback should wait until we run the main loop for it to be executed.
+  EXPECT_EQ(0U, calls.size());
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1U, calls.size());
+}
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimeoutDoesNotFire) {
+  // Set up an async policy call to return immediately, then wait a little and
+  // ensure that the timeout event does not fire.
+  int num_called = 0;
+  umut_->set_policy(new FailingPolicy(&num_called));
+
+  vector<pair<EvalStatus, UpdateCheckParams>> calls;
+  Callback<void(EvalStatus, const UpdateCheckParams&)> callback =
+      Bind(AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+  umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+  // Run the main loop, ensure that policy was attempted once before deferring
+  // to the default.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1, num_called);
+  ASSERT_EQ(1U, calls.size());
+  EXPECT_EQ(EvalStatus::kSucceeded, calls[0].first);
+  // Wait for the timeout to expire, run the main loop again, ensure that
+  // nothing happened.
+  test_clock_.Advance(TimeDelta::FromSeconds(2));
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_EQ(1, num_called);
+  EXPECT_EQ(1U, calls.size());
+}
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimesOut) {
+  // Set up an async policy call to exceed its expiration timeout, make sure
+  // that the default policy was not used (no callback) and that evaluation is
+  // reattempted.
+  int num_called = 0;
+  umut_->set_policy(new DelayPolicy(
+          0, fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(3),
+          &num_called));
+
+  vector<pair<EvalStatus, UpdateCheckParams>> calls;
+  Callback<void(EvalStatus, const UpdateCheckParams&)> callback =
+      Bind(AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+  umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+  // Run the main loop, ensure that policy was attempted once but the callback
+  // was not invoked.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1, num_called);
+  EXPECT_EQ(0U, calls.size());
+  // Wait for the expiration timeout to expire, run the main loop again,
+  // ensure that reevaluation occurred but callback was not invoked (i.e.
+  // default policy was not consulted).
+  test_clock_.Advance(TimeDelta::FromSeconds(2));
+  fake_clock_.SetWallclockTime(fake_clock_.GetWallclockTime() +
+                               TimeDelta::FromSeconds(2));
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_EQ(2, num_called);
+  EXPECT_EQ(0U, calls.size());
+  // Wait for reevaluation due to delay to happen, ensure that it occurs and
+  // that the callback is invoked.
+  test_clock_.Advance(TimeDelta::FromSeconds(2));
+  fake_clock_.SetWallclockTime(fake_clock_.GetWallclockTime() +
+                               TimeDelta::FromSeconds(2));
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_EQ(3, num_called);
+  ASSERT_EQ(1U, calls.size());
+  EXPECT_EQ(EvalStatus::kSucceeded, calls[0].first);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/updater_provider.h b/update_engine/update_manager/updater_provider.h
new file mode 100644
index 0000000..8048d38
--- /dev/null
+++ b/update_engine/update_manager/updater_provider.h
@@ -0,0 +1,117 @@
+//
+// 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_UPDATE_MANAGER_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATER_PROVIDER_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+enum class Stage {
+  kIdle,
+  kCheckingForUpdate,
+  kUpdateAvailable,
+  kDownloading,
+  kVerifying,
+  kFinalizing,
+  kUpdatedNeedReboot,
+  kReportingErrorEvent,
+  kAttemptingRollback,
+};
+
+enum class UpdateRequestStatus {
+  kNone,
+  kInteractive,
+  kPeriodic,
+};
+
+// Provider for Chrome OS update related information.
+class UpdaterProvider : public Provider {
+ public:
+  ~UpdaterProvider() override {}
+
+  // A variable returning the timestamp when the update engine was started in
+  // wallclock time.
+  virtual Variable<base::Time>* var_updater_started_time() = 0;
+
+  // A variable returning the last update check time.
+  virtual Variable<base::Time>* var_last_checked_time() = 0;
+
+  // A variable reporting the time when an update was last completed in the
+  // current boot cycle. Returns an error if an update completed time could not
+  // be read (e.g. no update was completed in the current boot cycle) or is
+  // invalid.
+  //
+  // IMPORTANT: The time reported is not the wallclock time reading at the time
+  // of the update, rather it is the point in time when the update completed
+  // relative to the current wallclock time reading. Therefore, the gap between
+  // the reported value and the current wallclock time is guaranteed to be
+  // monotonically increasing.
+  virtual Variable<base::Time>* var_update_completed_time() = 0;
+
+  // A variable returning the update progress (0.0 to 1.0).
+  virtual Variable<double>* var_progress() = 0;
+
+  // A variable returning the current update status.
+  virtual Variable<Stage>* var_stage() = 0;
+
+  // A variable returning the update target version.
+  virtual Variable<std::string>* var_new_version() = 0;
+
+  // A variable returning the update payload size. The payload size is
+  // guaranteed to be non-negative.
+  virtual Variable<int64_t>* var_payload_size() = 0;
+
+  // A variable returning the current channel.
+  virtual Variable<std::string>* var_curr_channel() = 0;
+
+  // A variable returning the update target channel.
+  virtual Variable<std::string>* var_new_channel() = 0;
+
+  // A variable indicating whether user settings allow P2P updates.
+  virtual Variable<bool>* var_p2p_enabled() = 0;
+
+  // A variable indicating whether user settings allow updates over a cellular
+  // network.
+  virtual Variable<bool>* var_cellular_enabled() = 0;
+
+  // A variable returning the number of consecutive failed update checks.
+  virtual Variable<unsigned int>* var_consecutive_failed_update_checks() = 0;
+
+  // A server-dictated update check interval in seconds, if one was given.
+  virtual Variable<unsigned int>* var_server_dictated_poll_interval() = 0;
+
+  // A variable denoting whether a forced update was request but no update check
+  // performed yet; also tells whether this request is for an interactive or
+  // scheduled update.
+  virtual Variable<UpdateRequestStatus>* var_forced_update_requested() = 0;
+
+ protected:
+  UpdaterProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UpdaterProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UPDATER_PROVIDER_H_
diff --git a/update_engine/update_manager/variable.h b/update_engine/update_manager/variable.h
new file mode 100644
index 0000000..7109692
--- /dev/null
+++ b/update_engine/update_manager/variable.h
@@ -0,0 +1,224 @@
+//
+// 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_UPDATE_MANAGER_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_VARIABLE_H_
+
+#include <algorithm>
+#include <list>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <brillo/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace chromeos_update_manager {
+
+// The VariableMode specifies important behavior of the variable in terms of
+// whether, how and when the value of the variable changes.
+enum VariableMode {
+  // Const variables never changes during the life of a policy request, so the
+  // EvaluationContext caches the value even between different evaluations of
+  // the same policy request.
+  kVariableModeConst,
+
+  // Poll variables, or synchronous variables, represent a variable with a value
+  // that can be queried at any time, but it is not known when the value
+  // changes on the source of information. In order to detect if the value of
+  // the variable changes, it has to be queried again.
+  kVariableModePoll,
+
+  // Async variables are able to produce a signal or callback whenever the
+  // value changes. This means that it's not required to poll the value to
+  // detect when it changes, instead, you should register an observer to get
+  // a notification when that happens.
+  kVariableModeAsync,
+};
+
+// This class is a base class with the common functionality that doesn't
+// depend on the variable's type, implemented by all the variables.
+class BaseVariable {
+ public:
+  // Interface for observing changes on variable value.
+  class ObserverInterface {
+   public:
+    virtual ~ObserverInterface() {}
+
+    // Called when the value on the variable changes.
+    virtual void ValueChanged(BaseVariable* variable) = 0;
+  };
+
+  virtual ~BaseVariable() {
+    if (!observer_list_.empty()) {
+      LOG(WARNING) << "Variable " << name_ << " deleted with "
+                   << observer_list_.size() << " observers.";
+    }
+    DCHECK(observer_list_.empty()) << "Don't destroy the variable without "
+                                      "removing the observers.";
+  }
+
+  // Returns the variable name as a string.
+  const std::string& GetName() const {
+    return name_;
+  }
+
+  // Returns the variable mode.
+  VariableMode GetMode() const {
+    return mode_;
+  }
+
+  // For VariableModePoll variables, it returns the polling interval of this
+  // variable. In other case, it returns 0.
+  base::TimeDelta GetPollInterval() const {
+    return poll_interval_;
+  }
+
+  // Adds and removes observers for value changes on the variable. This only
+  // works for kVariableAsync variables since the other modes don't track value
+  // changes. Adding the same observer twice has no effect.
+  virtual void AddObserver(BaseVariable::ObserverInterface* observer) {
+    if (std::find(observer_list_.begin(), observer_list_.end(), observer) ==
+        observer_list_.end()) {
+      observer_list_.push_back(observer);
+    }
+  }
+
+  virtual void RemoveObserver(BaseVariable::ObserverInterface* observer) {
+    observer_list_.remove(observer);
+  }
+
+ protected:
+  // Creates a BaseVariable using the default polling interval (5 minutes).
+  BaseVariable(const std::string& name, VariableMode mode)
+      : BaseVariable(name, mode,
+                     base::TimeDelta::FromMinutes(kDefaultPollMinutes)) {}
+
+  // Creates a BaseVariable with mode kVariableModePoll and the provided
+  // polling interval.
+  BaseVariable(const std::string& name, base::TimeDelta poll_interval)
+      : BaseVariable(name, kVariableModePoll, poll_interval) {}
+
+  // Reset the poll interval on a polling variable to the given one.
+  void SetPollInterval(base::TimeDelta poll_interval) {
+    DCHECK_EQ(kVariableModePoll, mode_) << "Can't set the poll_interval on a "
+                                        << mode_ << " variable";
+    poll_interval_ = poll_interval;
+  }
+
+  // Calls ValueChanged on all the observers.
+  void NotifyValueChanged() {
+    // Fire all the observer methods from the main loop as single call. In order
+    // to avoid scheduling these callbacks when it is not needed, we check
+    // first the list of observers.
+    if (!observer_list_.empty()) {
+      brillo::MessageLoop::current()->PostTask(
+          FROM_HERE,
+          base::Bind(&BaseVariable::OnValueChangedNotification,
+                     base::Unretained(this)));
+    }
+  }
+
+ private:
+  friend class UmEvaluationContextTest;
+  FRIEND_TEST(UmBaseVariableTest, RepeatedObserverTest);
+  FRIEND_TEST(UmBaseVariableTest, NotifyValueChangedTest);
+  FRIEND_TEST(UmBaseVariableTest, NotifyValueRemovesObserversTest);
+
+  BaseVariable(const std::string& name, VariableMode mode,
+               base::TimeDelta poll_interval)
+    : name_(name), mode_(mode),
+      poll_interval_(mode == kVariableModePoll ?
+                     poll_interval : base::TimeDelta()) {}
+
+  void OnValueChangedNotification() {
+    // A ValueChanged() method can change the list of observers, for example
+    // removing itself and invalidating the iterator, so we create a snapshot
+    // of the observers first. Also, to support the case when *another* observer
+    // is removed, we check for them.
+    std::list<BaseVariable::ObserverInterface*> observer_list_copy(
+        observer_list_);
+
+    for (auto& observer : observer_list_copy) {
+      if (std::find(observer_list_.begin(), observer_list_.end(), observer) !=
+          observer_list_.end()) {
+        observer->ValueChanged(this);
+      }
+    }
+  }
+
+  // The default PollInterval in minutes.
+  static constexpr int kDefaultPollMinutes = 5;
+
+  // The variable's name as a string.
+  const std::string name_;
+
+  // The variable's mode.
+  const VariableMode mode_;
+
+  // The variable's polling interval for VariableModePoll variable and 0 for
+  // other modes.
+  base::TimeDelta poll_interval_;
+
+  // The list of value changes observers.
+  std::list<BaseVariable::ObserverInterface*> observer_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(BaseVariable);
+};
+
+// Interface to an Update Manager variable of a given type. Implementation
+// internals are hidden as protected members, since policies should not be
+// using them directly.
+template<typename T>
+class Variable : public BaseVariable {
+ public:
+  ~Variable() override {}
+
+ protected:
+  // Only allow to get values through the EvaluationContext class and not
+  // directly from the variable.
+  friend class EvaluationContext;
+
+  // Needed to be able to verify variable contents during unit testing.
+  friend class UmTestUtils;
+  FRIEND_TEST(UmRealRandomProviderTest, GetRandomValues);
+
+  Variable(const std::string& name, VariableMode mode)
+      : BaseVariable(name, mode) {}
+
+  Variable(const std::string& name, const base::TimeDelta poll_interval)
+      : BaseVariable(name, poll_interval) {}
+
+  // Gets the current value of the variable. The current value is copied to a
+  // new object and returned. The caller of this method owns the object and
+  // should delete it.
+  //
+  // In case of and error getting the current value or the |timeout| timeout is
+  // exceeded, a null value is returned and the |errmsg| is set.
+  //
+  // The caller can pass a null value for |errmsg|, in which case the error
+  // message won't be set.
+  virtual const T* GetValue(base::TimeDelta timeout, std::string* errmsg) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Variable);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_VARIABLE_H_
diff --git a/update_engine/update_manager/variable_unittest.cc b/update_engine/update_manager/variable_unittest.cc
new file mode 100644
index 0000000..144002a
--- /dev/null
+++ b/update_engine/update_manager/variable_unittest.cc
@@ -0,0 +1,181 @@
+//
+// 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/update_manager/variable.h"
+
+#include <vector>
+
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using brillo::MessageLoopRunMaxIterations;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+// Variable class that returns a value constructed with the default value.
+template <typename T>
+class DefaultVariable : public Variable<T> {
+ public:
+  DefaultVariable(const string& name, VariableMode mode)
+      : Variable<T>(name, mode) {}
+  DefaultVariable(const string& name, const TimeDelta& poll_interval)
+      : Variable<T>(name, poll_interval) {}
+  ~DefaultVariable() override {}
+
+ protected:
+  const T* GetValue(TimeDelta /* timeout */,
+                    string* /* errmsg */) override {
+    return new T();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DefaultVariable);
+};
+
+class UmBaseVariableTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  brillo::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(UmBaseVariableTest, GetNameTest) {
+  DefaultVariable<int> var("var", kVariableModeConst);
+  EXPECT_EQ(var.GetName(), string("var"));
+}
+
+TEST_F(UmBaseVariableTest, GetModeTest) {
+  DefaultVariable<int> var("var", kVariableModeConst);
+  EXPECT_EQ(var.GetMode(), kVariableModeConst);
+  DefaultVariable<int> other_var("other_var", kVariableModePoll);
+  EXPECT_EQ(other_var.GetMode(), kVariableModePoll);
+}
+
+TEST_F(UmBaseVariableTest, DefaultPollIntervalTest) {
+  DefaultVariable<int> const_var("const_var", kVariableModeConst);
+  EXPECT_EQ(const_var.GetPollInterval(), TimeDelta());
+  DefaultVariable<int> poll_var("poll_var", kVariableModePoll);
+  EXPECT_EQ(poll_var.GetPollInterval(), TimeDelta::FromMinutes(5));
+}
+
+TEST_F(UmBaseVariableTest, GetPollIntervalTest) {
+  DefaultVariable<int> var("var", TimeDelta::FromMinutes(3));
+  EXPECT_EQ(var.GetMode(), kVariableModePoll);
+  EXPECT_EQ(var.GetPollInterval(), TimeDelta::FromMinutes(3));
+}
+
+class BaseVariableObserver : public BaseVariable::ObserverInterface {
+ public:
+  void ValueChanged(BaseVariable* variable) {
+    calls_.push_back(variable);
+  }
+
+  // List of called functions.
+  vector<BaseVariable*> calls_;
+};
+
+TEST_F(UmBaseVariableTest, RepeatedObserverTest) {
+  DefaultVariable<int> var("var", kVariableModeAsync);
+  BaseVariableObserver observer;
+  var.AddObserver(&observer);
+  EXPECT_EQ(1U, var.observer_list_.size());
+  var.AddObserver(&observer);
+  EXPECT_EQ(1U, var.observer_list_.size());
+  var.RemoveObserver(&observer);
+  EXPECT_EQ(0U, var.observer_list_.size());
+  var.RemoveObserver(&observer);
+  EXPECT_EQ(0U, var.observer_list_.size());
+}
+
+TEST_F(UmBaseVariableTest, NotifyValueChangedTest) {
+  DefaultVariable<int> var("var", kVariableModeAsync);
+  BaseVariableObserver observer1;
+  var.AddObserver(&observer1);
+  // Simulate a value change on the variable's implementation.
+  var.NotifyValueChanged();
+  ASSERT_EQ(0U, observer1.calls_.size());
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+  ASSERT_EQ(1U, observer1.calls_.size());
+  // Check that the observer is called with the right argument.
+  EXPECT_EQ(&var, observer1.calls_[0]);
+
+  BaseVariableObserver observer2;
+  var.AddObserver(&observer2);
+  var.NotifyValueChanged();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+  // Check that all the observers are called.
+  EXPECT_EQ(2U, observer1.calls_.size());
+  EXPECT_EQ(1U, observer2.calls_.size());
+
+  var.RemoveObserver(&observer1);
+  var.RemoveObserver(&observer2);
+}
+
+class BaseVariableObserverRemover : public BaseVariable::ObserverInterface {
+ public:
+  BaseVariableObserverRemover() : calls_(0) {}
+
+  void ValueChanged(BaseVariable* variable) override {
+    for (auto& observer : remove_observers_) {
+      variable->RemoveObserver(observer);
+    }
+    calls_++;
+  }
+
+  void OnCallRemoveObserver(BaseVariable::ObserverInterface* observer) {
+    remove_observers_.push_back(observer);
+  }
+
+  int get_calls() { return calls_; }
+
+ private:
+  vector<BaseVariable::ObserverInterface*> remove_observers_;
+  int calls_;
+};
+
+// Tests that we can remove an observer from a Variable on the ValueChanged()
+// call to that observer.
+TEST_F(UmBaseVariableTest, NotifyValueRemovesObserversTest) {
+  DefaultVariable<int> var("var", kVariableModeAsync);
+  BaseVariableObserverRemover observer1;
+  BaseVariableObserverRemover observer2;
+
+  var.AddObserver(&observer1);
+  var.AddObserver(&observer2);
+
+  // Make each observer remove both observers on ValueChanged.
+  observer1.OnCallRemoveObserver(&observer1);
+  observer1.OnCallRemoveObserver(&observer2);
+  observer2.OnCallRemoveObserver(&observer1);
+  observer2.OnCallRemoveObserver(&observer2);
+
+  var.NotifyValueChanged();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+  EXPECT_EQ(1, observer1.get_calls() + observer2.get_calls());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_engine/update_metadata.proto b/update_engine/update_metadata.proto
new file mode 100644
index 0000000..454c736
--- /dev/null
+++ b/update_engine/update_metadata.proto
@@ -0,0 +1,284 @@
+//
+// 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.
+//
+
+// Update file format: A delta update file contains all the deltas needed
+// to update a system from one specific version to another specific
+// version. The update format is represented by this struct pseudocode:
+// struct delta_update_file {
+//   char magic[4] = "CrAU";
+//   uint64 file_format_version;
+//   uint64 manifest_size;  // Size of protobuf DeltaArchiveManifest
+//
+//   // Only present if format_version > 1:
+//   uint32 metadata_signature_size;
+//
+//   // The Bzip2 compressed DeltaArchiveManifest
+//   char manifest[];
+//
+//   // The signature of the metadata (from the beginning of the payload up to
+//   // this location, not including the signature itself). This is a serialized
+//   // Signatures message.
+//   char medatada_signature_message[metadata_signature_size];
+//
+//   // Data blobs for files, no specific format. The specific offset
+//   // and length of each data blob is recorded in the DeltaArchiveManifest.
+//   struct {
+//     char data[];
+//   } blobs[];
+//
+//   // These two are not signed:
+//   uint64 payload_signatures_message_size;
+//   char payload_signatures_message[];
+//
+// };
+
+// The DeltaArchiveManifest protobuf is an ordered list of InstallOperation
+// objects. These objects are stored in a linear array in the
+// DeltaArchiveManifest. Each operation is applied in order by the client.
+
+// The DeltaArchiveManifest also contains the initial and final
+// checksums for the device.
+
+// The client will perform each InstallOperation in order, beginning even
+// before the entire delta file is downloaded (but after at least the
+// protobuf is downloaded). The types of operations are explained:
+// - REPLACE: Replace the dst_extents on the drive with the attached data,
+//   zero padding out to block size.
+// - REPLACE_BZ: bzip2-uncompress the attached data and write it into
+//   dst_extents on the drive, zero padding to block size.
+// - MOVE: Copy the data in src_extents to dst_extents. Extents may overlap,
+//   so it may be desirable to read all src_extents data into memory before
+//   writing it out.
+// - SOURCE_COPY: Copy the data in src_extents in the old partition to
+//   dst_extents in the new partition. There's no overlapping of data because
+//   the extents are in different partitions.
+// - BSDIFF: Read src_length bytes from src_extents into memory, perform
+//   bspatch with attached data, write new data to dst_extents, zero padding
+//   to block size.
+// - SOURCE_BSDIFF: Read the data in src_extents in the old partition, perform
+//   bspatch with the attached data and write the new data to dst_extents in the
+//   new partition.
+// - ZERO: Write zeros to the destination dst_extents.
+// - DISCARD: Discard the destination dst_extents blocks on the physical medium.
+//   the data read from those block is undefined.
+// - REPLACE_XZ: Replace the dst_extents with the contents of the attached
+//   xz file after decompression. The xz file should only use crc32 or no crc at
+//   all to be compatible with xz-embedded.
+//
+// The operations allowed in the payload (supported by the client) depend on the
+// major and minor version. See InstallOperation.Type bellow for details.
+
+package chromeos_update_engine;
+option optimize_for = LITE_RUNTIME;
+
+// Data is packed into blocks on disk, always starting from the beginning
+// of the block. If a file's data is too large for one block, it overflows
+// into another block, which may or may not be the following block on the
+// physical partition. An ordered list of extents is another
+// representation of an ordered list of blocks. For example, a file stored
+// in blocks 9, 10, 11, 2, 18, 12 (in that order) would be stored in
+// extents { {9, 3}, {2, 1}, {18, 1}, {12, 1} } (in that order).
+// In general, files are stored sequentially on disk, so it's more efficient
+// to use extents to encode the block lists (this is effectively
+// run-length encoding).
+// A sentinel value (kuint64max) as the start block denotes a sparse-hole
+// in a file whose block-length is specified by num_blocks.
+
+// Signatures: Updates may be signed by the OS vendor. The client verifies
+// an update's signature by hashing the entire download. The section of the
+// download that contains the signature is at the end of the file, so when
+// signing a file, only the part up to the signature part is signed.
+// Then, the client looks inside the download's Signatures message for a
+// Signature message that it knows how to handle. Generally, a client will
+// only know how to handle one type of signature, but an update may contain
+// many signatures to support many different types of client. Then client
+// selects a Signature message and uses that, along with a known public key,
+// to verify the download. The public key is expected to be part of the
+// client.
+
+message Extent {
+  optional uint64 start_block = 1;
+  optional uint64 num_blocks = 2;
+}
+
+message Signatures {
+  message Signature {
+    optional uint32 version = 1;
+    optional bytes data = 2;
+  }
+  repeated Signature signatures = 1;
+}
+
+message PartitionInfo {
+  optional uint64 size = 1;
+  optional bytes hash = 2;
+}
+
+// Describe an image we are based on in a human friendly way.
+// Examples:
+//   dev-channel, x86-alex, 1.2.3, mp-v3
+//   nplusone-channel, x86-alex, 1.2.4, mp-v3, dev-channel, 1.2.3
+//
+// All fields will be set, if this message is present.
+message ImageInfo {
+  optional string board = 1;
+  optional string key = 2;
+  optional string channel = 3;
+  optional string version = 4;
+
+  // If these values aren't present, they should be assumed to match
+  // the equivalent value above. They are normally only different for
+  // special image types such as nplusone images.
+  optional string build_channel = 5;
+  optional string build_version = 6;
+}
+
+message InstallOperation {
+  enum Type {
+    REPLACE = 0;  // Replace destination extents w/ attached data
+    REPLACE_BZ = 1;  // Replace destination extents w/ attached bzipped data
+    MOVE = 2;  // Move source extents to destination extents
+    BSDIFF = 3;  // The data is a bsdiff binary diff
+
+    // On minor version 2 or newer, these operations are supported:
+    SOURCE_COPY = 4; // Copy from source to target partition
+    SOURCE_BSDIFF = 5; // Like BSDIFF, but read from source partition
+
+    // On minor version 3 or newer and on major version 2 or newer, these
+    // operations are supported:
+    ZERO = 6;  // Write zeros in the destination.
+    DISCARD = 7;  // Discard the destination blocks, reading as undefined.
+    REPLACE_XZ = 8; // Replace destination extents w/ attached xz data.
+
+    // On minor version 4 or newer, these operations are supported:
+    IMGDIFF = 9; // The data is in imgdiff format.
+  }
+  required Type type = 1;
+  // The offset into the delta file (after the protobuf)
+  // where the data (if any) is stored
+  optional uint32 data_offset = 2;
+  // The length of the data in the delta file
+  optional uint32 data_length = 3;
+
+  // Ordered list of extents that are read from (if any) and written to.
+  repeated Extent src_extents = 4;
+  // Byte length of src, equal to the number of blocks in src_extents *
+  // block_size. It is used for BSDIFF, because we need to pass that
+  // external program the number of bytes to read from the blocks we pass it.
+  // This is not used in any other operation.
+  optional uint64 src_length = 5;
+
+  repeated Extent dst_extents = 6;
+  // Byte length of dst, equal to the number of blocks in dst_extents *
+  // block_size. Used for BSDIFF, but not in any other operation.
+  optional uint64 dst_length = 7;
+
+  // Optional SHA 256 hash of the blob associated with this operation.
+  // This is used as a primary validation for http-based downloads and
+  // as a defense-in-depth validation for https-based downloads. If
+  // the operation doesn't refer to any blob, this field will have
+  // zero bytes.
+  optional bytes data_sha256_hash = 8;
+
+  // Indicates the SHA 256 hash of the source data referenced in src_extents at
+  // the time of applying the operation. If present, the update_engine daemon
+  // MUST read and verify the source data before applying the operation.
+  optional bytes src_sha256_hash = 9;
+}
+
+// Describes the update to apply to a single partition.
+message PartitionUpdate {
+  // A platform-specific name to identify the partition set being updated. For
+  // example, in Chrome OS this could be "ROOT" or "KERNEL".
+  required string partition_name = 1;
+
+  // Whether this partition carries a filesystem with post-install program that
+  // must be run to finalize the update process. See also |postinstall_path| and
+  // |filesystem_type|.
+  optional bool run_postinstall = 2;
+
+  // The path of the executable program to run during the post-install step,
+  // relative to the root of this filesystem. If not set, the default "postinst"
+  // will be used. This setting is only used when |run_postinstall| is set and
+  // true.
+  optional string postinstall_path = 3;
+
+  // The filesystem type as passed to the mount(2) syscall when mounting the new
+  // filesystem to run the post-install program. If not set, a fixed list of
+  // filesystems will be attempted. This setting is only used if
+  // |run_postinstall| is set and true.
+  optional string filesystem_type = 4;
+
+  // If present, a list of signatures of the new_partition_info.hash signed with
+  // different keys. If the update_engine daemon requires vendor-signed images
+  // and has its public key installed, one of the signatures should be valid
+  // for /postinstall to run.
+  repeated Signatures.Signature new_partition_signature = 5;
+
+  optional PartitionInfo old_partition_info = 6;
+  optional PartitionInfo new_partition_info = 7;
+
+  // The list of operations to be performed to apply this PartitionUpdate. The
+  // associated operation blobs (in operations[i].data_offset, data_length)
+  // should be stored contiguously and in the same order.
+  repeated InstallOperation operations = 8;
+
+  // Whether a failure in the postinstall step for this partition should be
+  // ignored.
+  optional bool postinstall_optional = 9;
+}
+
+message DeltaArchiveManifest {
+  // Only present in major version = 1. List of install operations for the
+  // kernel and rootfs partitions. For major version = 2 see the |partitions|
+  // field.
+  repeated InstallOperation install_operations = 1;
+  repeated InstallOperation kernel_install_operations = 2;
+
+  // (At time of writing) usually 4096
+  optional uint32 block_size = 3 [default = 4096];
+
+  // If signatures are present, the offset into the blobs, generally
+  // tacked onto the end of the file, and the length. We use an offset
+  // rather than a bool to allow for more flexibility in future file formats.
+  // If either is absent, it means signatures aren't supported in this
+  // file.
+  optional uint64 signatures_offset = 4;
+  optional uint64 signatures_size = 5;
+
+  // Only present in major version = 1. Partition metadata used to validate the
+  // update. For major version = 2 see the |partitions| field.
+  optional PartitionInfo old_kernel_info = 6;
+  optional PartitionInfo new_kernel_info = 7;
+  optional PartitionInfo old_rootfs_info = 8;
+  optional PartitionInfo new_rootfs_info = 9;
+
+  // old_image_info will only be present for delta images.
+  optional ImageInfo old_image_info = 10;
+
+  optional ImageInfo new_image_info = 11;
+
+  // The minor version, also referred as "delta version", of the payload.
+  optional uint32 minor_version = 12 [default = 0];
+
+  // Only present in major version >= 2. List of partitions that will be
+  // updated, in the order they will be updated. This field replaces the
+  // |install_operations|, |kernel_install_operations| and the
+  // |{old,new}_{kernel,rootfs}_info| fields used in major version = 1. This
+  // array can have more than two partitions if needed, and they are identified
+  // by the partition name.
+  repeated PartitionUpdate partitions = 13;
+}
diff --git a/update_engine/update_payload_key/README b/update_engine/update_payload_key/README
new file mode 100644
index 0000000..cf87148
--- /dev/null
+++ b/update_engine/update_payload_key/README
@@ -0,0 +1,2 @@
+This directory contains the public half of the payload signing key. This is
+baked into the system image as part of update_engine install.
diff --git a/update_engine/update_payload_key/brillo-update-payload-key.pub.pem b/update_engine/update_payload_key/brillo-update-payload-key.pub.pem
new file mode 100644
index 0000000..7e5164c
--- /dev/null
+++ b/update_engine/update_payload_key/brillo-update-payload-key.pub.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxAxPqfII4vIe3cqKzdvl
+gwjBhj9kyF+6ig73yZq0o4wLOq3nsRUToaIOtQmcjr1G+hhSXBU3WTbfZLlm07Fb
+B535o2zhYghs8Br7xobjX+gikEnxnFuTtB2sB4Gpan4hKwU+BuZhJDSl1oZwUJJ4
+eiGJpH5xJswbyO/bA81BCMjU3rm+G6SzOLQTK0YEnhn7bB69UucM57GM7l+dCl8r
+RhKjbpP7E1fVtgX++BGs6pKciPLxYfXVup0MgH0h8VdSDMiHkshIXYvcCV1KOBFX
+9GrYvXLtq41Hm5hC5l48mwLi0ALdIfbPQ5oHLl2u+etLmGwbMpzhybTCZQA/SgEl
+HwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/update_engine/update_status_utils.cc b/update_engine/update_status_utils.cc
new file mode 100644
index 0000000..97408ef
--- /dev/null
+++ b/update_engine/update_status_utils.cc
@@ -0,0 +1,136 @@
+//
+// 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/update_status_utils.h"
+
+#include <base/logging.h>
+#ifdef USE_NESTLABS
+#include <update_engine/dbus-constants-nestlabs.h>
+#else
+#include <update_engine/dbus-constants.h>
+#endif
+
+using update_engine::UpdateStatus;
+
+namespace {
+
+const char kWeaveStatusIdle[] = "idle";
+const char kWeaveStatusCheckingForUpdate[] = "checkingForUpdate";
+const char kWeaveStatusUpdateAvailable[] = "updateAvailable";
+const char kWeaveStatusDownloading[] = "downloading";
+const char kWeaveStatusVerifying[] = "verifying";
+const char kWeaveStatusFinalizing[] = "finalizing";
+const char kWeaveStatusUpdatedNeedReboot[] = "updatedNeedReboot";
+const char kWeaveStatusReportingErrorEvent[] = "reportingErrorEvent";
+const char kWeaveStatusAttemptingRollback[] = "attemptingRollback";
+const char kWeaveStatusDisabled[] = "disabled";
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+const char* UpdateStatusToString(const UpdateStatus& status) {
+  switch (status) {
+    case UpdateStatus::IDLE:
+      return update_engine::kUpdateStatusIdle;
+    case UpdateStatus::CHECKING_FOR_UPDATE:
+      return update_engine::kUpdateStatusCheckingForUpdate;
+    case UpdateStatus::UPDATE_AVAILABLE:
+      return update_engine::kUpdateStatusUpdateAvailable;
+    case UpdateStatus::DOWNLOADING:
+      return update_engine::kUpdateStatusDownloading;
+    case UpdateStatus::VERIFYING:
+      return update_engine::kUpdateStatusVerifying;
+    case UpdateStatus::FINALIZING:
+      return update_engine::kUpdateStatusFinalizing;
+    case UpdateStatus::UPDATED_NEED_REBOOT:
+      return update_engine::kUpdateStatusUpdatedNeedReboot;
+    case UpdateStatus::REPORTING_ERROR_EVENT:
+      return update_engine::kUpdateStatusReportingErrorEvent;
+    case UpdateStatus::ATTEMPTING_ROLLBACK:
+      return update_engine::kUpdateStatusAttemptingRollback;
+    case UpdateStatus::DISABLED:
+      return update_engine::kUpdateStatusDisabled;
+  }
+
+  NOTREACHED();
+  return nullptr;
+}
+
+const char* UpdateStatusToWeaveStatus(const UpdateStatus& status) {
+  switch (status) {
+    case UpdateStatus::IDLE:
+      return kWeaveStatusIdle;
+    case UpdateStatus::CHECKING_FOR_UPDATE:
+      return kWeaveStatusCheckingForUpdate;
+    case UpdateStatus::UPDATE_AVAILABLE:
+      return kWeaveStatusUpdateAvailable;
+    case UpdateStatus::DOWNLOADING:
+      return kWeaveStatusDownloading;
+    case UpdateStatus::VERIFYING:
+      return kWeaveStatusVerifying;
+    case UpdateStatus::FINALIZING:
+      return kWeaveStatusFinalizing;
+    case UpdateStatus::UPDATED_NEED_REBOOT:
+      return kWeaveStatusUpdatedNeedReboot;
+    case UpdateStatus::REPORTING_ERROR_EVENT:
+      return kWeaveStatusReportingErrorEvent;
+    case UpdateStatus::ATTEMPTING_ROLLBACK:
+      return kWeaveStatusAttemptingRollback;
+    case UpdateStatus::DISABLED:
+      return kWeaveStatusDisabled;
+  }
+
+  NOTREACHED();
+  return nullptr;
+}
+
+bool StringToUpdateStatus(const std::string& s,
+                          UpdateStatus* status) {
+  if (s == update_engine::kUpdateStatusIdle) {
+    *status = UpdateStatus::IDLE;
+    return true;
+  } else if (s == update_engine::kUpdateStatusCheckingForUpdate) {
+    *status = UpdateStatus::CHECKING_FOR_UPDATE;
+    return true;
+  } else if (s == update_engine::kUpdateStatusUpdateAvailable) {
+    *status = UpdateStatus::UPDATE_AVAILABLE;
+    return true;
+  } else if (s == update_engine::kUpdateStatusDownloading) {
+    *status = UpdateStatus::DOWNLOADING;
+    return true;
+  } else if (s == update_engine::kUpdateStatusVerifying) {
+    *status = UpdateStatus::VERIFYING;
+    return true;
+  } else if (s == update_engine::kUpdateStatusFinalizing) {
+    *status = UpdateStatus::FINALIZING;
+    return true;
+  } else if (s == update_engine::kUpdateStatusUpdatedNeedReboot) {
+    *status = UpdateStatus::UPDATED_NEED_REBOOT;
+    return true;
+  } else if (s == update_engine::kUpdateStatusReportingErrorEvent) {
+    *status = UpdateStatus::REPORTING_ERROR_EVENT;
+    return true;
+  } else if (s == update_engine::kUpdateStatusAttemptingRollback) {
+    *status = UpdateStatus::ATTEMPTING_ROLLBACK;
+    return true;
+  } else if (s == update_engine::kUpdateStatusDisabled) {
+    *status = UpdateStatus::DISABLED;
+    return true;
+  }
+  return false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/update_status_utils.h b/update_engine/update_status_utils.h
new file mode 100644
index 0000000..9d85144
--- /dev/null
+++ b/update_engine/update_status_utils.h
@@ -0,0 +1,37 @@
+//
+// 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_UPDATE_STATUS_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_STATUS_UTILS_H_
+
+#include <string>
+
+#include "update_engine/client_library/include/update_engine/update_status.h"
+
+namespace chromeos_update_engine {
+
+const char* UpdateStatusToString(const update_engine::UpdateStatus& status);
+
+// Convert the UpdateStatus |status| to the string reported in the weave status.
+const char* UpdateStatusToWeaveStatus(
+    const update_engine::UpdateStatus& status);
+
+bool StringToUpdateStatus(const std::string& update_status_as_string,
+                          update_engine::UpdateStatus* status);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_UPDATE_STATUS_UTILS_H_
diff --git a/update_engine/utils_android.cc b/update_engine/utils_android.cc
new file mode 100644
index 0000000..a4f1ea8
--- /dev/null
+++ b/update_engine/utils_android.cc
@@ -0,0 +1,71 @@
+//
+// 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/utils_android.h"
+
+#include <cutils/properties.h>
+#include <fs_mgr.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Open the appropriate fstab file and fallback to /fstab.device if
+// that's what's being used.
+static struct fstab* OpenFSTab() {
+  char propbuf[PROPERTY_VALUE_MAX];
+  struct fstab* fstab;
+
+  property_get("ro.hardware", propbuf, "");
+  string fstab_name = string("/fstab.") + propbuf;
+  fstab = fs_mgr_read_fstab(fstab_name.c_str());
+  if (fstab != nullptr)
+    return fstab;
+
+  fstab = fs_mgr_read_fstab("/fstab.device");
+  return fstab;
+}
+
+}  // namespace
+
+namespace utils {
+
+bool DeviceForMountPoint(const string& mount_point, base::FilePath* device) {
+  struct fstab* fstab;
+  struct fstab_rec* record;
+
+  fstab = OpenFSTab();
+  if (fstab == nullptr) {
+    LOG(ERROR) << "Error opening fstab file.";
+    return false;
+  }
+  record = fs_mgr_get_entry_for_mount_point(fstab, mount_point.c_str());
+  if (record == nullptr) {
+    LOG(ERROR) << "Error finding " << mount_point << " entry in fstab file.";
+    fs_mgr_free_fstab(fstab);
+    return false;
+  }
+
+  *device = base::FilePath(record->blk_device);
+  fs_mgr_free_fstab(fstab);
+  return true;
+}
+
+}  // namespace utils
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/utils_android.h b/update_engine/utils_android.h
new file mode 100644
index 0000000..18dd8ab
--- /dev/null
+++ b/update_engine/utils_android.h
@@ -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.
+//
+
+#ifndef UPDATE_ENGINE_UTILS_ANDROID_H_
+#define UPDATE_ENGINE_UTILS_ANDROID_H_
+
+#include <string>
+
+#include <base/files/file_util.h>
+
+namespace chromeos_update_engine {
+
+namespace utils {
+
+// Find the block device that should be mounted in the |mount_point| path and
+// store it in |device|. Returns whether a device was found on the fstab.
+bool DeviceForMountPoint(const std::string& mount_point,
+                         base::FilePath* device);
+
+}  // namespace utils
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_UTILS_ANDROID_H_
diff --git a/update_engine/weave_service.cc b/update_engine/weave_service.cc
new file mode 100644
index 0000000..0a145e4
--- /dev/null
+++ b/update_engine/weave_service.cc
@@ -0,0 +1,133 @@
+//
+// 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/weave_service.h"
+
+#include <cmath>
+#include <string>
+
+#include <base/bind.h>
+#include <brillo/errors/error.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/update_status_utils.h"
+
+using std::string;
+
+namespace {
+
+const char kWeaveComponent[] = "updater";
+const char kWeaveTrait[] = "_updater";
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+bool WeaveService::Init(DelegateInterface* delegate) {
+  delegate_ = delegate;
+  weave_service_subscription_ = weaved::Service::Connect(
+      brillo::MessageLoop::current(),
+      base::Bind(&WeaveService::OnWeaveServiceConnected,
+                 base::Unretained(this)));
+  return true;
+}
+
+void WeaveService::OnWeaveServiceConnected(
+    const std::weak_ptr<weaved::Service>& service) {
+  weave_service_ = service;
+  auto weave_service = weave_service_.lock();
+  if (!weave_service)
+    return;
+
+  weave_service->AddComponent(kWeaveComponent, {kWeaveTrait}, nullptr);
+  weave_service->AddCommandHandler(
+      kWeaveComponent, kWeaveTrait, "checkForUpdates",
+      base::Bind(&WeaveService::OnCheckForUpdates, base::Unretained(this)));
+  weave_service->AddCommandHandler(
+      kWeaveComponent, kWeaveTrait, "trackChannel",
+      base::Bind(&WeaveService::OnTrackChannel, base::Unretained(this)));
+  UpdateWeaveState();
+}
+
+void WeaveService::SendStatusUpdate(int64_t /* last_checked_time */,
+                                    double /* progress */,
+                                    update_engine::UpdateStatus /* status */,
+                                    const string& /* new_version */,
+                                    int64_t /* new_size */) {
+  // We query the Weave
+  UpdateWeaveState();
+}
+
+void WeaveService::SendChannelChangeUpdate(
+    const string& /* tracking_channel */) {
+  UpdateWeaveState();
+}
+
+void WeaveService::UpdateWeaveState() {
+  auto weave_service = weave_service_.lock();
+  if (!weave_service || !delegate_)
+    return;
+
+  int64_t last_checked_time;
+  double progress;
+  update_engine::UpdateStatus update_status;
+  string current_channel;
+  string tracking_channel;
+
+  if (!delegate_->GetWeaveState(&last_checked_time,
+                                &progress,
+                                &update_status,
+                                &current_channel,
+                                &tracking_channel))
+    return;
+
+  // Round to progress to 1% (0.01) to avoid excessive and meaningless state
+  // changes.
+  progress = std::floor(progress * 100.) / 100.;
+
+  base::DictionaryValue state;
+  state.SetString("_updater.currentChannel", current_channel);
+  state.SetString("_updater.trackingChannel", tracking_channel);
+  state.SetString("_updater.status", UpdateStatusToWeaveStatus(update_status));
+  state.SetDouble("_updater.progress", progress);
+  state.SetDouble("_updater.lastUpdateCheckTimestamp",
+                  static_cast<double>(last_checked_time));
+
+  if (!weave_service->SetStateProperties(kWeaveComponent, state, nullptr)) {
+    LOG(ERROR) << "Failed to update _updater state.";
+  }
+}
+
+void WeaveService::OnCheckForUpdates(std::unique_ptr<weaved::Command> command) {
+  brillo::ErrorPtr error;
+  if (!delegate_->OnCheckForUpdates(&error)) {
+    command->AbortWithCustomError(error.get(), nullptr);
+    return;
+  }
+  command->Complete({}, nullptr);
+}
+
+void WeaveService::OnTrackChannel(std::unique_ptr<weaved::Command> command) {
+  string channel = command->GetParameter<string>("channel");
+  brillo::ErrorPtr error;
+  if (!delegate_->OnTrackChannel(channel, &error)) {
+    command->AbortWithCustomError(error.get(), nullptr);
+    return;
+  }
+  command->Complete({}, nullptr);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/weave_service.h b/update_engine/weave_service.h
new file mode 100644
index 0000000..5a3aff3
--- /dev/null
+++ b/update_engine/weave_service.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_WEAVE_SERVICE_H_
+#define UPDATE_ENGINE_WEAVE_SERVICE_H_
+
+#include <memory>
+#include <string>
+
+#include <base/memory/weak_ptr.h>
+#include <libweaved/command.h>
+#include <libweaved/service.h>
+
+#include "update_engine/weave_service_interface.h"
+
+namespace chromeos_update_engine {
+
+class WeaveService : public WeaveServiceInterface {
+ public:
+  WeaveService() = default;
+  ~WeaveService() override = default;
+
+  bool Init(DelegateInterface* delegate);
+
+  // ServiceObserverInterface overrides.
+  void SendStatusUpdate(int64_t last_checked_time,
+                        double progress,
+                        update_engine::UpdateStatus status,
+                        const std::string& new_version,
+                        int64_t new_size) override;
+  void SendChannelChangeUpdate(const std::string& tracking_channel) override;
+  void SendPayloadApplicationComplete(ErrorCode error_code) override {}
+
+ private:
+  // Force a weave update.
+  void UpdateWeaveState();
+
+  void OnWeaveServiceConnected(const std::weak_ptr<weaved::Service>& service);
+
+  // Weave command handlers. These are called from the message loop whenever a
+  // command is received and dispatch the synchronous call to the |delegate_|.
+  void OnCheckForUpdates(std::unique_ptr<weaved::Command> cmd);
+  void OnTrackChannel(std::unique_ptr<weaved::Command> cmd);
+
+  WeaveServiceInterface::DelegateInterface* delegate_{nullptr};
+
+  std::unique_ptr<weaved::Service::Subscription> weave_service_subscription_;
+  std::weak_ptr<weaved::Service> weave_service_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_WEAVE_SERVICE_H_
diff --git a/update_engine/weave_service_factory.cc b/update_engine/weave_service_factory.cc
new file mode 100644
index 0000000..24b9b79
--- /dev/null
+++ b/update_engine/weave_service_factory.cc
@@ -0,0 +1,40 @@
+//
+// 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/weave_service_factory.h"
+
+#if USE_WEAVE
+#include "update_engine/weave_service.h"
+#endif
+
+namespace chromeos_update_engine {
+
+std::unique_ptr<WeaveServiceInterface> ConstructWeaveService(
+    WeaveServiceInterface::DelegateInterface* delegate) {
+  std::unique_ptr<WeaveServiceInterface> result;
+  if (!delegate)
+    return result;
+
+#if USE_WEAVE
+  WeaveService* weave_service = new WeaveService();
+  result.reset(weave_service);
+  if (!weave_service->Init(delegate))
+    result.reset();
+#endif
+  return result;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/weave_service_factory.h b/update_engine/weave_service_factory.h
new file mode 100644
index 0000000..7b129ab
--- /dev/null
+++ b/update_engine/weave_service_factory.h
@@ -0,0 +1,35 @@
+//
+// 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_WEAVE_SERVICE_FACTORY_H_
+#define UPDATE_ENGINE_WEAVE_SERVICE_FACTORY_H_
+
+#include <memory>
+
+#include <base/memory/ref_counted.h>
+
+#include "update_engine/weave_service_interface.h"
+
+namespace chromeos_update_engine {
+
+// Create a new WeaveServiceInterface instance. In case of error or when weaved
+// is disabled, returns an empty pointer.
+std::unique_ptr<WeaveServiceInterface> ConstructWeaveService(
+    WeaveServiceInterface::DelegateInterface* delegate);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_WEAVE_SERVICE_FACTORY_H_
diff --git a/update_engine/weave_service_interface.h b/update_engine/weave_service_interface.h
new file mode 100644
index 0000000..a7e603e
--- /dev/null
+++ b/update_engine/weave_service_interface.h
@@ -0,0 +1,62 @@
+//
+// 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_WEAVE_SERVICE_INTERFACE_H_
+#define UPDATE_ENGINE_WEAVE_SERVICE_INTERFACE_H_
+
+#include <string>
+
+#include <brillo/errors/error.h>
+
+#include "update_engine/client_library/include/update_engine/update_status.h"
+#include "update_engine/service_observer_interface.h"
+
+namespace chromeos_update_engine {
+
+// A WeaveServiceInterface instance allows to register the daemon with weaved,
+// handle commands and update the weave status. This class only handles the
+// registration with weaved and the connection, the actual work to handle the
+// commands is implemented by the DelegateInterface, which will be called from
+// this class.
+class WeaveServiceInterface : public ServiceObserverInterface {
+ public:
+  // The delegate class that actually handles the command execution from
+  class DelegateInterface {
+   public:
+    virtual ~DelegateInterface() = default;
+
+    virtual bool OnCheckForUpdates(brillo::ErrorPtr* error) = 0;
+
+    virtual bool OnTrackChannel(const std::string& channel,
+                                brillo::ErrorPtr* error) = 0;
+
+    // Return the current status.
+    virtual bool GetWeaveState(int64_t* last_checked_time,
+                               double* progress,
+                               update_engine::UpdateStatus* update_status,
+                               std::string* current_channel,
+                               std::string* tracking_channel) = 0;
+  };
+
+  virtual ~WeaveServiceInterface() = default;
+
+ protected:
+  WeaveServiceInterface() = default;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_WEAVE_SERVICE_INTERFACE_H_
diff --git a/update_engine/weaved/traits/updater.json b/update_engine/weaved/traits/updater.json
new file mode 100644
index 0000000..52b1a55
--- /dev/null
+++ b/update_engine/weaved/traits/updater.json
@@ -0,0 +1,52 @@
+{
+  "_updater": {
+    "commands": {
+      "checkForUpdates": {
+        "minimalRole": "manager",
+        "parameters": {}
+      },
+      "trackChannel": {
+        "minimalRole": "manager",
+        "parameters": {
+          "channel": {
+            "type": "string",
+            "enum": ["stable-channel", "beta-channel", "dev-channel", "canary-channel"]
+          }
+        }
+      }
+    },
+    "state": {
+      "currentChannel": {
+        "type": "string",
+        "enum": ["stable-channel", "beta-channel", "dev-channel", "canary-channel"]
+      },
+      "trackingChannel": {
+        "type": "string",
+        "enum": ["stable-channel", "beta-channel", "dev-channel", "canary-channel"]
+      },
+      "status": {
+        "type": "string",
+        "enum": [
+          "idle",
+          "checkingForUpdate",
+          "updateAvailable",
+          "downloading",
+          "verifying",
+          "finalizing",
+          "updatedNeedReboot",
+          "reportingErrorEvent",
+          "attemptingRollback",
+          "disabled"
+        ]
+      },
+      "progress": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 1
+      },
+      "lastUpdateCheckTimestamp": {
+        "type": "number"
+      }
+    }
+  }
+}