[automerger skipped] Don't check for FEC offset if fec is disabled am: b5f0996071 -s ours

am skip reason: Merged-In I0e37136313914f1ee9a4eae0e5db59807adc7dc5 with SHA-1 5e5ad39a15 is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/system/update_engine/+/15401790

Change-Id: I0608c32ebce408fff932ce26d838f2d43255c112
diff --git a/.gitignore b/.gitignore
index db4c370..84f29a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@
 /update_engine_client
 /update_engine_unittests
 *.pyc
+.vscode
diff --git a/Android.bp b/Android.bp
index d74e78f..90e7698 100644
--- a/Android.bp
+++ b/Android.bp
@@ -147,6 +147,7 @@
 cc_library_static {
     name: "update_metadata-protos",
     host_supported: true,
+    ramdisk_available: true,
     recovery_available: true,
 
     srcs: ["update_engine/update_metadata.proto"],
@@ -232,6 +233,7 @@
         "payload_consumer/file_descriptor_utils.cc",
         "payload_consumer/file_writer.cc",
         "payload_consumer/filesystem_verifier_action.cc",
+        "payload_consumer/install_operation_executor.cc",
         "payload_consumer/install_plan.cc",
         "payload_consumer/mount_history.cc",
         "payload_consumer/payload_constants.cc",
@@ -242,6 +244,7 @@
         "payload_consumer/vabc_partition_writer.cc",
         "payload_consumer/snapshot_extent_writer.cc",
         "payload_consumer/postinstall_runner_action.cc",
+        "payload_consumer/verified_source_fd.cc",
         "payload_consumer/verity_writer_android.cc",
         "payload_consumer/xz_extent_writer.cc",
         "payload_consumer/fec_file_descriptor.cc",
@@ -274,6 +277,7 @@
         "libutils",
         "android.hardware.boot@1.0",
         "android.hardware.boot@1.1",
+        "android.hardware.boot@1.2",
     ],
     header_libs: [
         "avb_headers",
@@ -805,6 +809,7 @@
         "payload_consumer/file_writer_unittest.cc",
         "payload_consumer/filesystem_verifier_action_unittest.cc",
         "payload_consumer/install_plan_unittest.cc",
+        "payload_consumer/install_operation_executor_unittest.cc",
         "payload_consumer/partition_update_generator_android_unittest.cc",
         "payload_consumer/postinstall_runner_action_unittest.cc",
         "payload_consumer/verity_writer_android_unittest.cc",
diff --git a/aosp/binder_service_android.cc b/aosp/binder_service_android.cc
index ed76c4a..8e87588 100644
--- a/aosp/binder_service_android.cc
+++ b/aosp/binder_service_android.cc
@@ -157,6 +157,17 @@
   return Status::ok();
 }
 
+Status BinderUpdateEngineAndroidService::setShouldSwitchSlotOnReboot(
+    const android::String16& metadata_filename) {
+  // TODO(187321613) Call the service_delegate_ for the actual implementation
+  return Status::ok();
+}
+
+Status BinderUpdateEngineAndroidService::resetShouldSwitchSlotOnReboot() {
+  // TODO(187321613) Call the service_delegate_ for the actual implementation
+  return Status::ok();
+}
+
 Status BinderUpdateEngineAndroidService::verifyPayloadApplicable(
     const android::String16& metadata_filename, bool* return_value) {
   const std::string payload_metadata{
diff --git a/aosp/binder_service_android.h b/aosp/binder_service_android.h
index f41fbdf..f1ce6b5 100644
--- a/aosp/binder_service_android.h
+++ b/aosp/binder_service_android.h
@@ -68,6 +68,9 @@
   android::binder::Status resume() override;
   android::binder::Status cancel() override;
   android::binder::Status resetStatus() override;
+  android::binder::Status setShouldSwitchSlotOnReboot(
+      const android::String16& metadata_filename) override;
+  android::binder::Status resetShouldSwitchSlotOnReboot() override;
   android::binder::Status verifyPayloadApplicable(
       const android::String16& metadata_filename, bool* return_value) override;
   android::binder::Status allocateSpaceForPayload(
diff --git a/aosp/boot_control_android.cc b/aosp/boot_control_android.cc
index c1ac0d4..88a9c17 100644
--- a/aosp/boot_control_android.cc
+++ b/aosp/boot_control_android.cc
@@ -20,6 +20,7 @@
 #include <utility>
 #include <vector>
 
+#include <android/hardware/boot/1.2/IBootControl.h>
 #include <base/bind.h>
 #include <base/logging.h>
 #include <bootloader_message/bootloader_message.h>
@@ -177,6 +178,18 @@
   return ret == BoolResult::TRUE;
 }
 
+Slot BootControlAndroid::GetActiveBootSlot() {
+  namespace V1_2 = android::hardware::boot::V1_2;
+  using android::sp;
+  sp<V1_2::IBootControl> v1_2_module = V1_2::IBootControl::castFrom(module_);
+  if (v1_2_module != nullptr) {
+    return v1_2_module->getActiveBootSlot();
+  }
+  LOG(WARNING) << "BootControl module version is lower than 1.2, "
+               << __FUNCTION__ << " failed";
+  return kInvalidSlot;
+}
+
 DynamicPartitionControlInterface*
 BootControlAndroid::GetDynamicPartitionControl() {
   return dynamic_control_.get();
diff --git a/aosp/boot_control_android.h b/aosp/boot_control_android.h
index 926023a..a65aa2e 100644
--- a/aosp/boot_control_android.h
+++ b/aosp/boot_control_android.h
@@ -62,6 +62,7 @@
   bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
   bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
   bool IsSlotMarkedSuccessful(BootControlInterface::Slot slot) const override;
+  Slot GetActiveBootSlot() override;
   DynamicPartitionControlInterface* GetDynamicPartitionControl() override;
 
  private:
diff --git a/aosp/cleanup_previous_update_action.cc b/aosp/cleanup_previous_update_action.cc
index 53cb993..55dba1e 100644
--- a/aosp/cleanup_previous_update_action.cc
+++ b/aosp/cleanup_previous_update_action.cc
@@ -276,7 +276,17 @@
 void CleanupPreviousUpdateAction::WaitForMergeOrSchedule() {
   AcknowledgeTaskExecuted();
   TEST_AND_RETURN(running_);
+
   auto update_uses_compression = snapshot_->UpdateUsesCompression();
+
+  // Propagate the merge failure code to the merge stats. If we wait until
+  // after ProcessUpdateState, then a successful merge could overwrite the
+  // state of the previous failure.
+  auto failure_code = snapshot_->ReadMergeFailureCode();
+  if (failure_code != android::snapshot::MergeFailureCode::Ok) {
+    merge_stats_->set_merge_failure_code(failure_code);
+  }
+
   auto state = snapshot_->ProcessUpdateState(
       std::bind(&CleanupPreviousUpdateAction::OnMergePercentageUpdate, this),
       std::bind(&CleanupPreviousUpdateAction::BeforeCancel, this));
diff --git a/aosp/update_attempter_android.cc b/aosp/update_attempter_android.cc
index 4636c43..28c193e 100644
--- a/aosp/update_attempter_android.cc
+++ b/aosp/update_attempter_android.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <map>
 #include <memory>
+#include <ostream>
 #include <utility>
 
 #include <android-base/properties.h>
@@ -152,14 +153,50 @@
   processor_->set_delegate(nullptr);
 }
 
+[[nodiscard]] static bool DidSystemReboot(PrefsInterface* prefs) {
+  string boot_id;
+  TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id));
+  string old_boot_id;
+  // If no previous boot id found, treat as a reboot and write boot ID.
+  if (!prefs->GetString(kPrefsBootId, &old_boot_id)) {
+    return true;
+  }
+  return old_boot_id != boot_id;
+}
+
+std::ostream& operator<<(std::ostream& out, OTAResult result) {
+  switch (result) {
+    case OTAResult::NOT_ATTEMPTED:
+      out << "OTAResult::NOT_ATTEMPTED";
+      break;
+    case OTAResult::ROLLED_BACK:
+      out << "OTAResult::ROLLED_BACK";
+      break;
+    case OTAResult::UPDATED_NEED_REBOOT:
+      out << "OTAResult::UPDATED_NEED_REBOOT";
+      break;
+    case OTAResult::OTA_SUCCESSFUL:
+      out << "OTAResult::OTA_SUCCESSFUL";
+      break;
+  }
+  return out;
+}
+
 void UpdateAttempterAndroid::Init() {
   // In case of update_engine restart without a reboot we need to restore the
   // reboot needed state.
   if (UpdateCompletedOnThisBoot()) {
+    LOG(INFO) << "Updated installed but update_engine is restarted without "
+                 "device reboot. Resuming old state.";
     SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
   } else {
+    const auto result = GetOTAUpdateResult();
+    LOG(INFO) << result;
     SetStatusAndNotify(UpdateStatus::IDLE);
-    UpdatePrefsAndReportUpdateMetricsOnReboot();
+    if (DidSystemReboot(prefs_)) {
+      UpdateStateAfterReboot(result);
+    }
+
 #ifdef _UE_SIDELOAD
     LOG(INFO) << "Skip ScheduleCleanupPreviousUpdate in sideload because "
               << "ApplyPayload will call it later.";
@@ -369,6 +406,15 @@
     std::vector<ApexInfo> apex_infos_blank;
     apex_handler_android_->AllocateSpace(apex_infos_blank);
   }
+  // 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.
+  if (!ClearUpdateCompletedMarker()) {
+    return LogAndSetError(error,
+                          FROM_HERE,
+                          "Failed to reset the status because "
+                          "ClearUpdateCompletedMarker() failed");
+  }
 
   switch (status_) {
     case UpdateStatus::IDLE: {
@@ -402,11 +448,6 @@
       if (!boot_control_->GetDynamicPartitionControl()->ResetUpdate(prefs_))
         ret_value = false;
 
-      // 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.
-      if (!prefs_->Delete(kPrefsUpdateCompletedOnBootId))
-        ret_value = false;
       ClearMetricsPrefs();
 
       if (!ret_value) {
@@ -553,7 +594,9 @@
   switch (code) {
     case ErrorCode::kSuccess:
       // Update succeeded.
-      WriteUpdateCompletedMarker();
+      if (!WriteUpdateCompletedMarker()) {
+        LOG(ERROR) << "Failed to write update completion marker";
+      }
       prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
 
       LOG(INFO) << "Update successfully applied, waiting to reboot.";
@@ -691,6 +734,7 @@
   }
 
   if (status_ == UpdateStatus::CLEANUP_PREVIOUS_UPDATE) {
+    ClearUpdateCompletedMarker();
     LOG(INFO) << "Terminating cleanup previous update.";
     SetStatusAndNotify(UpdateStatus::IDLE);
     for (auto observer : daemon_state_->service_observers())
@@ -784,9 +828,20 @@
 }
 
 bool UpdateAttempterAndroid::WriteUpdateCompletedMarker() {
+  LOG(INFO) << "Writing update complete marker.";
   string boot_id;
   TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id));
-  prefs_->SetString(kPrefsUpdateCompletedOnBootId, boot_id);
+  TEST_AND_RETURN_FALSE(
+      prefs_->SetString(kPrefsUpdateCompletedOnBootId, boot_id));
+  TEST_AND_RETURN_FALSE(
+      prefs_->SetInt64(kPrefsPreviousSlot, boot_control_->GetCurrentSlot()));
+  return true;
+}
+
+bool UpdateAttempterAndroid::ClearUpdateCompletedMarker() {
+  LOG(INFO) << "Clearing update complete marker.";
+  TEST_AND_RETURN_FALSE(prefs_->Delete(kPrefsUpdateCompletedOnBootId));
+  TEST_AND_RETURN_FALSE(prefs_->Delete(kPrefsPreviousSlot));
   return true;
 }
 
@@ -883,53 +938,105 @@
   }
 }
 
-void UpdateAttempterAndroid::UpdatePrefsAndReportUpdateMetricsOnReboot() {
-  string current_boot_id;
-  TEST_AND_RETURN(utils::GetBootId(&current_boot_id));
+bool UpdateAttempterAndroid::OTARebootSucceeded() const {
+  const auto current_slot = boot_control_->GetCurrentSlot();
+  const string current_version =
+      android::base::GetProperty("ro.build.version.incremental", "");
+  int64_t previous_slot = -1;
+  TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsPreviousSlot, &previous_slot));
+  string previous_version;
+  TEST_AND_RETURN_FALSE(
+      prefs_->GetString(kPrefsPreviousVersion, &previous_version));
+  if (previous_slot != current_slot) {
+    LOG(INFO) << "Detected a slot switch, OTA succeeded, device updated from "
+              << previous_version << " to " << current_version;
+    if (previous_version == current_version) {
+      LOG(INFO) << "Previous version is the same as current version, this is "
+                   "possibly a self-OTA.";
+    }
+    return true;
+  } else {
+    LOG(INFO) << "Slot didn't switch, either the OTA is rolled back, or slot "
+                 "switch never happened, or system not rebooted at all.";
+    if (previous_version != current_version) {
+      LOG(INFO) << "Slot didn't change, but version changed from "
+                << previous_version << " to " << current_version
+                << " device could be flashed.";
+    }
+    return false;
+  }
+}
+
+OTAResult UpdateAttempterAndroid::GetOTAUpdateResult() const {
+  // We only set |kPrefsSystemUpdatedMarker| if slot is actually switched, so
+  // existence of this pref is sufficient indicator. Given that we have to
+  // delete this pref after checking it. This is done in
+  // |DeltaPerformer::ResetUpdateProgress|
+  auto slot_switch_attempted = prefs_->Exists(kPrefsUpdateCompletedOnBootId);
+  auto system_rebooted = DidSystemReboot(prefs_);
+  auto ota_successful = OTARebootSucceeded();
+  if (ota_successful) {
+    return OTAResult::OTA_SUCCESSFUL;
+  }
+  if (slot_switch_attempted) {
+    if (system_rebooted) {
+      // If we attempted slot switch, but still end up on the same slot, we
+      // probably rolled back.
+      return OTAResult::ROLLED_BACK;
+    } else {
+      return OTAResult::UPDATED_NEED_REBOOT;
+    }
+  }
+  return OTAResult::NOT_ATTEMPTED;
+}
+
+void UpdateAttempterAndroid::UpdateStateAfterReboot(const OTAResult result) {
   // Example: [ro.build.version.incremental]: [4292972]
   string current_version =
       android::base::GetProperty("ro.build.version.incremental", "");
   TEST_AND_RETURN(!current_version.empty());
-  const auto current_slot = boot_control_->GetCurrentSlot();
+
+  // |UpdateStateAfterReboot()| is only called after system reboot, so record
+  // boot id unconditionally
+  string current_boot_id;
+  TEST_AND_RETURN(utils::GetBootId(&current_boot_id));
+  prefs_->SetString(kPrefsBootId, current_boot_id);
 
   // If there's no record of previous version (e.g. due to a data wipe), we
   // save the info of current boot and skip the metrics report.
   if (!prefs_->Exists(kPrefsPreviousVersion)) {
-    prefs_->SetString(kPrefsBootId, current_boot_id);
     prefs_->SetString(kPrefsPreviousVersion, current_version);
-    prefs_->SetInt64(std::string{kPrefsPreviousSlot},
-                     boot_control_->GetCurrentSlot());
+    prefs_->SetInt64(kPrefsPreviousSlot, boot_control_->GetCurrentSlot());
     ClearMetricsPrefs();
     return;
   }
-  int64_t previous_slot = -1;
-  prefs_->GetInt64(kPrefsPreviousSlot, &previous_slot);
-  string previous_version;
   // update_engine restarted under the same build and same slot.
-  // TODO(xunchang) identify and report rollback by checking UpdateMarker.
-  if (prefs_->GetString(kPrefsPreviousVersion, &previous_version) &&
-      previous_version == current_version && previous_slot == current_slot) {
-    string last_boot_id;
-    bool is_reboot = prefs_->Exists(kPrefsBootId) &&
-                     (prefs_->GetString(kPrefsBootId, &last_boot_id) &&
-                      last_boot_id != current_boot_id);
+  if (result != OTAResult::OTA_SUCCESSFUL) {
     // Increment the reboot number if |kPrefsNumReboots| exists. That pref is
     // set when we start a new update.
-    if (is_reboot && prefs_->Exists(kPrefsNumReboots)) {
-      prefs_->SetString(kPrefsBootId, current_boot_id);
+    if (prefs_->Exists(kPrefsNumReboots)) {
       int64_t reboot_count =
           metrics_utils::GetPersistedValue(kPrefsNumReboots, prefs_);
       metrics_utils::SetNumReboots(reboot_count + 1, prefs_);
     }
+
+    if (result == OTAResult::ROLLED_BACK) {
+      // This will release all space previously allocated for apex
+      // decompression. If we detect a rollback, we should release space and
+      // return the space to user. Any subsequent attempt to install OTA will
+      // allocate space again anyway.
+      LOG(INFO) << "Detected a rollback, releasing space allocated for apex "
+                   "deompression.";
+      apex_handler_android_->AllocateSpace({});
+      DeltaPerformer::ResetUpdateProgress(prefs_, false);
+    }
     return;
   }
 
   // Now that the build version changes, report the update metrics.
   // TODO(xunchang) check the build version is larger than the previous one.
-  prefs_->SetString(kPrefsBootId, current_boot_id);
   prefs_->SetString(kPrefsPreviousVersion, current_version);
-  prefs_->SetInt64(std::string{kPrefsPreviousSlot},
-                   boot_control_->GetCurrentSlot());
+  prefs_->SetInt64(kPrefsPreviousSlot, boot_control_->GetCurrentSlot());
 
   bool previous_attempt_exists = prefs_->Exists(kPrefsPayloadAttemptNumber);
   // |kPrefsPayloadAttemptNumber| should be cleared upon successful update.
@@ -960,6 +1067,7 @@
   }
   metrics_utils::SetUpdateTimestampStart(clock_->GetMonotonicTime(), prefs_);
   metrics_utils::SetUpdateBootTimestampStart(clock_->GetBootTime(), prefs_);
+  ClearUpdateCompletedMarker();
 }
 
 void UpdateAttempterAndroid::ClearMetricsPrefs() {
diff --git a/aosp/update_attempter_android.h b/aosp/update_attempter_android.h
index 70938bc..3633178 100644
--- a/aosp/update_attempter_android.h
+++ b/aosp/update_attempter_android.h
@@ -45,6 +45,13 @@
 
 namespace chromeos_update_engine {
 
+enum class OTAResult {
+  NOT_ATTEMPTED,
+  ROLLED_BACK,
+  UPDATED_NEED_REBOOT,
+  OTA_SUCCESSFUL,
+};
+
 class UpdateAttempterAndroid
     : public ServiceDelegateAndroidInterface,
       public ActionProcessorDelegate,
@@ -114,9 +121,24 @@
   // CleanupPreviousUpdateActionDelegateInterface
   void OnCleanupProgressUpdate(double progress) override;
 
+  // Check the result of an OTA update. Intended to be called after reboot, this
+  // will use prefs on disk to determine if OTA was installed, or rolledback.
+  [[nodiscard]] OTAResult GetOTAUpdateResult() const;
+  // Intended to be called:
+  // 1. When system rebooted and slot switch is attempted
+  // 2. When a new update is started
+  // 3. When user called |ResetStatus()|
+  bool ClearUpdateCompletedMarker();
+
  private:
   friend class UpdateAttempterAndroidTest;
 
+  // Return |true| only if slot switched successfully after an OTA reboot.
+  // This will return |false| if an downgrade OTA is applied. Because after a
+  // downgrade OTA, we wipe /data, and there's no way for update_engine to
+  // "remember" that a downgrade OTA took place.
+  [[nodiscard]] bool OTARebootSucceeded() const;
+
   // Schedules an event loop callback to start the action processor. This is
   // scheduled asynchronously to unblock the event loop.
   void ScheduleProcessingStart();
@@ -136,10 +158,10 @@
 
   // Writes to the processing completed marker. Does nothing if
   // |update_completed_marker_| is empty.
-  bool WriteUpdateCompletedMarker();
+  [[nodiscard]] bool WriteUpdateCompletedMarker();
 
   // Returns whether an update was completed in the current boot.
-  bool UpdateCompletedOnThisBoot();
+  [[nodiscard]] bool UpdateCompletedOnThisBoot();
 
   // Prefs to use for metrics report
   // |kPrefsPayloadAttemptNumber|: number of update attempts for the current
@@ -162,12 +184,16 @@
   //   |kPrefsSystemUpdatedMarker|
   void CollectAndReportUpdateMetricsOnUpdateFinished(ErrorCode error_code);
 
+  // This function is called after update_engine is started after device
+  // reboots. If update_engine is restarted w/o device reboot, this function
+  // would not be called.
+
   // Metrics report function to call:
   //   |ReportAbnormallyTerminatedUpdateAttemptMetrics|
   //   |ReportTimeToRebootMetrics|
   // Prefs to update:
   //   |kPrefsBootId|, |kPrefsPreviousVersion|
-  void UpdatePrefsAndReportUpdateMetricsOnReboot();
+  void UpdateStateAfterReboot(OTAResult result);
 
   // Prefs to update:
   //   |kPrefsPayloadAttemptNumber|, |kPrefsUpdateTimestampStart|,
diff --git a/aosp/update_attempter_android_unittest.cc b/aosp/update_attempter_android_unittest.cc
index f73df16..969f191 100644
--- a/aosp/update_attempter_android_unittest.cc
+++ b/aosp/update_attempter_android_unittest.cc
@@ -106,6 +106,7 @@
   prefs_.SetString(kPrefsPreviousVersion, "00001");  // Set the fake version
   prefs_.SetInt64(kPrefsPayloadAttemptNumber, 1);
   prefs_.SetInt64(kPrefsSystemUpdatedMarker, 23456);
+  prefs_.SetInt64(kPrefsPreviousSlot, 1);
 
   EXPECT_CALL(*metrics_reporter_,
               ReportAbnormallyTerminatedUpdateAttemptMetrics())
diff --git a/binder_bindings/android/os/IUpdateEngine.aidl b/binder_bindings/android/os/IUpdateEngine.aidl
index c9580da..4043b1a 100644
--- a/binder_bindings/android/os/IUpdateEngine.aidl
+++ b/binder_bindings/android/os/IUpdateEngine.aidl
@@ -44,6 +44,11 @@
   /** @hide */
   void resetStatus();
   /** @hide */
+  void setShouldSwitchSlotOnReboot(in String metadataFilename);
+  /** @hide */
+  void resetShouldSwitchSlotOnReboot();
+
+  /** @hide */
   boolean verifyPayloadApplicable(in String metadataFilename);
   /**
    * Allocate space on userdata partition.
diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h
index 321174e..2de21a1 100644
--- a/common/boot_control_interface.h
+++ b/common/boot_control_interface.h
@@ -93,6 +93,11 @@
   // 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;
+  // Get the active slot. In other words, the slot which will be used on
+  // next system reboot. This should match the |slot| parameter of last
+  // successful call to |SetActiveBootSlot|.
+  // Return 0xFFFFFFFF if underlying HAL doesn't support this operation.
+  virtual Slot GetActiveBootSlot() = 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
diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h
index dcddbae..3167115 100644
--- a/common/boot_control_stub.h
+++ b/common/boot_control_stub.h
@@ -56,6 +56,7 @@
   bool IsSlotBootable(BootControlInterface::Slot slot) const override;
   bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
   bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
+  Slot GetActiveBootSlot() override { return kInvalidSlot; }
   bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
   bool IsSlotMarkedSuccessful(BootControlInterface::Slot slot) const override;
   DynamicPartitionControlInterface* GetDynamicPartitionControl() override;
diff --git a/common/constants.cc b/common/constants.cc
index 0677e66..ff46755 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -16,133 +16,4 @@
 
 #include "update_engine/common/constants.h"
 
-namespace chromeos_update_engine {
-
-const char kExclusionPrefsSubDir[] = "exclusion";
-
-const char kDlcPrefsSubDir[] = "dlc";
-
-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 kPrefsDynamicPartitionMetadataUpdated[] =
-    "dynamic-partition-metadata-updated";
-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 kPrefsNoIgnoreBackoff[] = "no-ignore-backoff";
-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 kPrefsOmahaEolDate[] = "omaha-eol-date";
-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 kPrefsTestUpdateCheckIntervalTimeout[] =
-    "test-update-check-interval-timeout";
-// Keep |kPrefsPingActive| in sync with |kDlcMetadataFilePingActive| in
-// dlcservice.
-const char kPrefsPingActive[] = "active";
-const char kPrefsPingLastActive[] = "date_last_active";
-const char kPrefsPingLastRollcall[] = "date_last_rollcall";
-const char kPrefsLastFp[] = "last-fp";
-const char kPrefsPostInstallSucceeded[] = "post-install-succeeded";
-const char kPrefsPreviousVersion[] = "previous-version";
-const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
-const char kPrefsRollbackHappened[] = "rollback-happened";
-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 kPrefsUpdateOverCellularTargetVersion[] =
-    "update-over-cellular-target-version";
-const char kPrefsUpdateOverCellularTargetSize[] =
-    "update-over-cellular-target-size";
-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 kPrefsUpdateStatePayloadIndex[] = "update-state-payload-index";
-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 kPrefsUpdateBootTimestampStart[] = "update-boot-timestamp-start";
-const char kPrefsUpdateTimestampStart[] = "update-timestamp-start";
-const char kPrefsUrlSwitchCount[] = "url-switch-count";
-const char kPrefsVerityWritten[] = "verity-written";
-const char kPrefsWallClockScatteringWaitPeriod[] = "wall-clock-wait-period";
-const char kPrefsWallClockStagingWaitPeriod[] =
-    "wall-clock-staging-wait-period";
-const char kPrefsManifestBytes[] = "manifest-bytes";
-const char kPrefsPreviousSlot[] = "previous-slot";
-
-// These four fields are generated by scripts/brillo_update_payload.
-const char kPayloadPropertyFileSize[] = "FILE_SIZE";
-const char kPayloadPropertyFileHash[] = "FILE_HASH";
-const char kPayloadPropertyMetadataSize[] = "METADATA_SIZE";
-const char kPayloadPropertyMetadataHash[] = "METADATA_HASH";
-// The Authorization: HTTP header to be sent when downloading the payload.
-const char kPayloadPropertyAuthorization[] = "AUTHORIZATION";
-// The User-Agent HTTP header to be sent when downloading the payload.
-const char kPayloadPropertyUserAgent[] = "USER_AGENT";
-// Set "POWERWASH=1" to powerwash (factory data reset) the device after
-// applying the update.
-const char kPayloadPropertyPowerwash[] = "POWERWASH";
-// The network id to pass to android_setprocnetwork before downloading.
-// This can be used to zero-rate OTA traffic by sending it over the correct
-// network.
-const char kPayloadPropertyNetworkId[] = "NETWORK_ID";
-// Set "SWITCH_SLOT_ON_REBOOT=0" to skip marking the updated partitions active.
-// The default is 1 (always switch slot if update succeeded).
-const char kPayloadPropertySwitchSlotOnReboot[] = "SWITCH_SLOT_ON_REBOOT";
-// Set "RUN_POST_INSTALL=0" to skip running optional post install.
-// The default is 1 (always run post install).
-const char kPayloadPropertyRunPostInstall[] = "RUN_POST_INSTALL";
-
-const char kOmahaUpdaterVersion[] = "0.1.0.0";
-
-// X-Goog-Update headers.
-const char kXGoogleUpdateInteractivity[] = "X-Goog-Update-Interactivity";
-const char kXGoogleUpdateAppId[] = "X-Goog-Update-AppId";
-const char kXGoogleUpdateUpdater[] = "X-Goog-Update-Updater";
-const char kXGoogleUpdateSessionId[] = "X-Goog-SessionId";
-
-}  // namespace chromeos_update_engine
+namespace chromeos_update_engine {}  // namespace chromeos_update_engine
diff --git a/common/constants.h b/common/constants.h
index 68f720d..8c07fcf 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -20,116 +20,176 @@
 #include <cstdint>
 
 namespace chromeos_update_engine {
-
 // The root path of all exclusion prefs.
-extern const char kExclusionPrefsSubDir[];
+static constexpr const auto& kExclusionPrefsSubDir = "exclusion";
 
 // The root path of all DLC metadata.
-extern const char kDlcPrefsSubDir[];
+static constexpr const auto& kDlcPrefsSubDir = "dlc";
 
 // Directory for AU prefs that are preserved across powerwash.
-extern const char kPowerwashSafePrefsSubDirectory[];
+static constexpr const auto& kPowerwashSafePrefsSubDirectory =
+    "update_engine/prefs";
 
 // 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[];
+static constexpr const auto& kPrefsSubDirectory = "prefs";
 
 // Path to the stateful partition on the root filesystem.
-extern const char kStatefulPartition[];
+static constexpr const auto& kStatefulPartition = "/mnt/stateful_partition";
+
+// Path to the post install command, relative to the partition.
+static constexpr const auto& kPostinstallDefaultScript = "postinst";
 
 // 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 kPrefsDynamicPartitionMetadataUpdated[];
-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 kPrefsNoIgnoreBackoff[];
-extern const char kPrefsNumReboots[];
-extern const char kPrefsNumResponsesSeen[];
-extern const char kPrefsOmahaCohort[];
-extern const char kPrefsOmahaCohortHint[];
-extern const char kPrefsOmahaCohortName[];
-extern const char kPrefsOmahaEolDate[];
-extern const char kPrefsP2PEnabled[];
-extern const char kPrefsP2PFirstAttemptTimestamp[];
-extern const char kPrefsP2PNumAttempts[];
-extern const char kPrefsPayloadAttemptNumber[];
-extern const char kPrefsTestUpdateCheckIntervalTimeout[];
-extern const char kPrefsPingActive[];
-extern const char kPrefsPingLastActive[];
-extern const char kPrefsPingLastRollcall[];
-extern const char kPrefsLastFp[];
-extern const char kPrefsPostInstallSucceeded[];
-extern const char kPrefsPreviousVersion[];
-extern const char kPrefsPreviousSlot[];
-extern const char kPrefsResumedUpdateFailures[];
-extern const char kPrefsRollbackHappened[];
-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 kPrefsUpdateOverCellularTargetVersion[];
-extern const char kPrefsUpdateOverCellularTargetSize[];
-extern const char kPrefsUpdateServerCertificate[];
-extern const char kPrefsUpdateStateNextDataLength[];
-extern const char kPrefsUpdateStateNextDataOffset[];
-extern const char kPrefsUpdateStateNextOperation[];
-extern const char kPrefsUpdateStatePayloadIndex[];
-extern const char kPrefsUpdateStateSHA256Context[];
-extern const char kPrefsUpdateStateSignatureBlob[];
-extern const char kPrefsUpdateStateSignedSHA256Context[];
-extern const char kPrefsUpdateBootTimestampStart[];
-extern const char kPrefsUpdateTimestampStart[];
-extern const char kPrefsUrlSwitchCount[];
-extern const char kPrefsVerityWritten[];
-extern const char kPrefsWallClockScatteringWaitPeriod[];
-extern const char kPrefsWallClockStagingWaitPeriod[];
-extern const char kPrefsManifestBytes[];
+// Constants defining keys for the persisted state of update engine.
+static constexpr const auto& kPrefsAttemptInProgress = "attempt-in-progress";
+static constexpr const auto& kPrefsBackoffExpiryTime = "backoff-expiry-time";
+static constexpr const auto& kPrefsBootId = "boot-id";
+static constexpr const auto& kPrefsCurrentBytesDownloaded =
+    "current-bytes-downloaded";
+static constexpr const auto& kPrefsCurrentResponseSignature =
+    "current-response-signature";
+static constexpr const auto& kPrefsCurrentUrlFailureCount =
+    "current-url-failure-count";
+static constexpr const auto& kPrefsCurrentUrlIndex = "current-url-index";
+static constexpr const auto& kPrefsDailyMetricsLastReportedAt =
+    "daily-metrics-last-reported-at";
+static constexpr const auto& kPrefsDeltaUpdateFailures =
+    "delta-update-failures";
+static constexpr const auto& kPrefsDynamicPartitionMetadataUpdated =
+    "dynamic-partition-metadata-updated";
+static constexpr const auto& kPrefsFullPayloadAttemptNumber =
+    "full-payload-attempt-number";
+static constexpr const auto& kPrefsInstallDateDays = "install-date-days";
+static constexpr const auto& kPrefsLastActivePingDay = "last-active-ping-day";
+static constexpr const auto& kPrefsLastRollCallPingDay =
+    "last-roll-call-ping-day";
+static constexpr const auto& kPrefsManifestMetadataSize =
+    "manifest-metadata-size";
+static constexpr const auto& kPrefsManifestSignatureSize =
+    "manifest-signature-size";
+static constexpr const auto& kPrefsMetricsAttemptLastReportingTime =
+    "metrics-attempt-last-reporting-time";
+static constexpr const auto& kPrefsMetricsCheckLastReportingTime =
+    "metrics-check-last-reporting-time";
+static constexpr const auto& kPrefsNoIgnoreBackoff = "no-ignore-backoff";
+static constexpr const auto& kPrefsNumReboots = "num-reboots";
+static constexpr const auto& kPrefsNumResponsesSeen = "num-responses-seen";
+static constexpr const auto& kPrefsOmahaCohort = "omaha-cohort";
+static constexpr const auto& kPrefsOmahaCohortHint = "omaha-cohort-hint";
+static constexpr const auto& kPrefsOmahaCohortName = "omaha-cohort-name";
+static constexpr const auto& kPrefsOmahaEolDate = "omaha-eol-date";
+static constexpr const auto& kPrefsP2PEnabled = "p2p-enabled";
+static constexpr const auto& kPrefsP2PFirstAttemptTimestamp =
+    "p2p-first-attempt-timestamp";
+static constexpr const auto& kPrefsP2PNumAttempts = "p2p-num-attempts";
+static constexpr const auto& kPrefsPayloadAttemptNumber =
+    "payload-attempt-number";
+static constexpr const auto& kPrefsTestUpdateCheckIntervalTimeout =
+    "test-update-check-interval-timeout";
+// Keep |kPrefsPingActive| in sync with |kDlcMetadataFilePingActive| in
+// dlcservice.
+static constexpr const auto& kPrefsPingActive = "active";
+static constexpr const auto& kPrefsPingLastActive = "date_last_active";
+static constexpr const auto& kPrefsPingLastRollcall = "date_last_rollcall";
+static constexpr const auto& kPrefsLastFp = "last-fp";
+static constexpr const auto& kPrefsPostInstallSucceeded =
+    "post-install-succeeded";
+static constexpr const auto& kPrefsPreviousVersion = "previous-version";
+static constexpr const auto& kPrefsResumedUpdateFailures =
+    "resumed-update-failures";
+static constexpr const auto& kPrefsRollbackHappened = "rollback-happened";
+static constexpr const auto& kPrefsRollbackVersion = "rollback-version";
+static constexpr const auto& kPrefsChannelOnSlotPrefix = "channel-on-slot-";
+static constexpr const auto& kPrefsSystemUpdatedMarker =
+    "system-updated-marker";
+static constexpr const auto& kPrefsTargetVersionAttempt =
+    "target-version-attempt";
+static constexpr const auto& kPrefsTargetVersionInstalledFrom =
+    "target-version-installed-from";
+static constexpr const auto& kPrefsTargetVersionUniqueId =
+    "target-version-unique-id";
+static constexpr const auto& kPrefsTotalBytesDownloaded =
+    "total-bytes-downloaded";
+static constexpr const auto& kPrefsUpdateCheckCount = "update-check-count";
+static constexpr const auto& kPrefsUpdateCheckResponseHash =
+    "update-check-response-hash";
+static constexpr const auto& kPrefsUpdateCompletedBootTime =
+    "update-completed-boot-time";
+static constexpr const auto& kPrefsUpdateCompletedOnBootId =
+    "update-completed-on-boot-id";
+static constexpr const auto& kPrefsUpdateDurationUptime =
+    "update-duration-uptime";
+static constexpr const auto& kPrefsUpdateFirstSeenAt = "update-first-seen-at";
+static constexpr const auto& kPrefsUpdateOverCellularPermission =
+    "update-over-cellular-permission";
+static constexpr const auto& kPrefsUpdateOverCellularTargetVersion =
+    "update-over-cellular-target-version";
+static constexpr const auto& kPrefsUpdateOverCellularTargetSize =
+    "update-over-cellular-target-size";
+static constexpr const auto& kPrefsUpdateServerCertificate =
+    "update-server-cert";
+static constexpr const auto& kPrefsUpdateStateNextDataLength =
+    "update-state-next-data-length";
+static constexpr const auto& kPrefsUpdateStateNextDataOffset =
+    "update-state-next-data-offset";
+static constexpr const auto& kPrefsUpdateStateNextOperation =
+    "update-state-next-operation";
+static constexpr const auto& kPrefsUpdateStatePayloadIndex =
+    "update-state-payload-index";
+static constexpr const auto& kPrefsUpdateStateSHA256Context =
+    "update-state-sha-256-context";
+static constexpr const auto& kPrefsUpdateStateSignatureBlob =
+    "update-state-signature-blob";
+static constexpr const auto& kPrefsUpdateStateSignedSHA256Context =
+    "update-state-signed-sha-256-context";
+static constexpr const auto& kPrefsUpdateBootTimestampStart =
+    "update-boot-timestamp-start";
+static constexpr const auto& kPrefsUpdateTimestampStart =
+    "update-timestamp-start";
+static constexpr const auto& kPrefsUrlSwitchCount = "url-switch-count";
+static constexpr const auto& kPrefsVerityWritten = "verity-written";
+static constexpr const auto& kPrefsWallClockScatteringWaitPeriod =
+    "wall-clock-wait-period";
+static constexpr const auto& kPrefsWallClockStagingWaitPeriod =
+    "wall-clock-staging-wait-period";
+static constexpr const auto& kPrefsManifestBytes = "manifest-bytes";
+static constexpr const auto& kPrefsPreviousSlot = "previous-slot";
 
 // 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[];
-extern const char kPayloadPropertySwitchSlotOnReboot[];
-extern const char kPayloadPropertyRunPostInstall[];
+// These four fields are generated by scripts/brillo_update_payload.
+static constexpr const auto& kPayloadPropertyFileSize = "FILE_SIZE";
+static constexpr const auto& kPayloadPropertyFileHash = "FILE_HASH";
+static constexpr const auto& kPayloadPropertyMetadataSize = "METADATA_SIZE";
+static constexpr const auto& kPayloadPropertyMetadataHash = "METADATA_HASH";
+// The Authorization: HTTP header to be sent when downloading the payload.
+static constexpr const auto& kPayloadPropertyAuthorization = "AUTHORIZATION";
+// The User-Agent HTTP header to be sent when downloading the payload.
+static constexpr const auto& kPayloadPropertyUserAgent = "USER_AGENT";
+// Set "POWERWASH=1" to powerwash (factory data reset) the device after
+// applying the update.
+static constexpr const auto& kPayloadPropertyPowerwash = "POWERWASH";
+// The network id to pass to android_setprocnetwork before downloading.
+// This can be used to zero-rate OTA traffic by sending it over the correct
+// network.
+static constexpr const auto& kPayloadPropertyNetworkId = "NETWORK_ID";
+// Set "SWITCH_SLOT_ON_REBOOT=0" to skip marking the updated partitions active.
+// The default is 1 (always switch slot if update succeeded).
+static constexpr const auto& kPayloadPropertySwitchSlotOnReboot =
+    "SWITCH_SLOT_ON_REBOOT";
+// Set "RUN_POST_INSTALL=0" to skip running optional post install.
+// The default is 1 (always run post install).
+static constexpr const auto& kPayloadPropertyRunPostInstall =
+    "RUN_POST_INSTALL";
 
-extern const char kOmahaUpdaterVersion[];
+static constexpr const auto& kOmahaUpdaterVersion = "0.1.0.0";
 
 // X-Goog-Update headers.
-extern const char kXGoogleUpdateInteractivity[];
-extern const char kXGoogleUpdateAppId[];
-extern const char kXGoogleUpdateUpdater[];
-extern const char kXGoogleUpdateSessionId[];
+// X-Goog-Update headers.
+static constexpr const auto& kXGoogleUpdateInteractivity =
+    "X-Goog-Update-Interactivity";
+static constexpr const auto& kXGoogleUpdateAppId = "X-Goog-Update-AppId";
+static constexpr const auto& kXGoogleUpdateUpdater = "X-Goog-Update-Updater";
+static constexpr const auto& kXGoogleUpdateSessionId = "X-Goog-SessionId";
 
 // 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
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
index 79e2139..8a68501 100644
--- a/common/fake_boot_control.h
+++ b/common/fake_boot_control.h
@@ -83,6 +83,7 @@
   }
 
   bool SetActiveBootSlot(Slot slot) override { return true; }
+  Slot GetActiveBootSlot() override { return kInvalidSlot; }
 
   bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override {
     // We run the callback directly from here to avoid having to setup a message
diff --git a/common/fake_prefs.cc b/common/fake_prefs.cc
index ea6ea60..e87e0ec 100644
--- a/common/fake_prefs.cc
+++ b/common/fake_prefs.cc
@@ -28,7 +28,7 @@
 
 namespace {
 
-void CheckNotNull(const string& key, void* ptr) {
+void CheckNotNull(std::string_view key, void* ptr) {
   EXPECT_NE(nullptr, ptr) << "Called Get*() for key \"" << key
                           << "\" with a null parameter.";
 }
@@ -63,41 +63,41 @@
 bool FakePrefs::PrefValue::*const FakePrefs::PrefConsts<bool>::member =
     &FakePrefs::PrefValue::as_bool;
 
-bool FakePrefs::GetString(const string& key, string* value) const {
+bool FakePrefs::GetString(std::string_view key, string* value) const {
   return GetValue(key, value);
 }
 
-bool FakePrefs::SetString(const string& key, std::string_view value) {
+bool FakePrefs::SetString(std::string_view key, std::string_view value) {
   SetValue(key, std::string(value));
   return true;
 }
 
-bool FakePrefs::GetInt64(const string& key, int64_t* value) const {
+bool FakePrefs::GetInt64(std::string_view key, int64_t* value) const {
   return GetValue(key, value);
 }
 
-bool FakePrefs::SetInt64(const string& key, const int64_t value) {
+bool FakePrefs::SetInt64(std::string_view key, const int64_t value) {
   SetValue(key, value);
   return true;
 }
 
-bool FakePrefs::GetBoolean(const string& key, bool* value) const {
+bool FakePrefs::GetBoolean(std::string_view key, bool* value) const {
   return GetValue(key, value);
 }
 
-bool FakePrefs::SetBoolean(const string& key, const bool value) {
+bool FakePrefs::SetBoolean(std::string_view key, const bool value) {
   SetValue(key, value);
   return true;
 }
 
-bool FakePrefs::Exists(const string& key) const {
+bool FakePrefs::Exists(std::string_view key) const {
   return values_.find(key) != values_.end();
 }
 
-bool FakePrefs::Delete(const string& key) {
+bool FakePrefs::Delete(std::string_view key) {
   if (values_.find(key) == values_.end())
     return false;
-  values_.erase(key);
+  values_.erase(std::string{key});
   const auto observers_for_key = observers_.find(key);
   if (observers_for_key != observers_.end()) {
     std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
@@ -107,7 +107,7 @@
   return true;
 }
 
-bool FakePrefs::Delete(const string& key, const vector<string>& nss) {
+bool FakePrefs::Delete(std::string_view key, const vector<string>& nss) {
   bool success = Delete(key);
   for (const auto& ns : nss) {
     vector<string> ns_keys;
@@ -123,7 +123,7 @@
   return success;
 }
 
-bool FakePrefs::GetSubKeys(const string& ns, vector<string>* keys) const {
+bool FakePrefs::GetSubKeys(std::string_view ns, vector<string>* keys) const {
   for (const auto& pr : values_)
     if (pr.first.compare(0, ns.length(), ns) == 0)
       keys->push_back(pr.first);
@@ -142,7 +142,7 @@
   return "Unknown";
 }
 
-void FakePrefs::CheckKeyType(const string& key, PrefType type) const {
+void FakePrefs::CheckKeyType(std::string_view 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)
@@ -150,10 +150,11 @@
 }
 
 template <typename T>
-void FakePrefs::SetValue(const string& key, T value) {
+void FakePrefs::SetValue(std::string_view key, T value) {
+  std::string str_key{key};
   CheckKeyType(key, PrefConsts<T>::type);
-  values_[key].type = PrefConsts<T>::type;
-  values_[key].value.*(PrefConsts<T>::member) = std::move(value);
+  values_[str_key].type = PrefConsts<T>::type;
+  values_[str_key].value.*(PrefConsts<T>::member) = std::move(value);
   const auto observers_for_key = observers_.find(key);
   if (observers_for_key != observers_.end()) {
     std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
@@ -163,7 +164,7 @@
 }
 
 template <typename T>
-bool FakePrefs::GetValue(const string& key, T* value) const {
+bool FakePrefs::GetValue(std::string_view key, T* value) const {
   CheckKeyType(key, PrefConsts<T>::type);
   auto it = values_.find(key);
   if (it == values_.end())
@@ -173,12 +174,14 @@
   return true;
 }
 
-void FakePrefs::AddObserver(const string& key, ObserverInterface* observer) {
-  observers_[key].push_back(observer);
+void FakePrefs::AddObserver(std::string_view key, ObserverInterface* observer) {
+  observers_[string{key}].push_back(observer);
 }
 
-void FakePrefs::RemoveObserver(const string& key, ObserverInterface* observer) {
-  std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+void FakePrefs::RemoveObserver(std::string_view key,
+                               ObserverInterface* observer) {
+  string str_key{key};
+  std::vector<ObserverInterface*>& observers_for_key = observers_[str_key];
   auto observer_it =
       std::find(observers_for_key.begin(), observers_for_key.end(), observer);
   EXPECT_NE(observer_it, observers_for_key.end())
@@ -186,7 +189,7 @@
   if (observer_it != observers_for_key.end())
     observers_for_key.erase(observer_it);
   if (observers_for_key.empty())
-    observers_.erase(key);
+    observers_.erase(str_key);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/common/fake_prefs.h b/common/fake_prefs.h
index 430c291..7ae9fb9 100644
--- a/common/fake_prefs.h
+++ b/common/fake_prefs.h
@@ -17,6 +17,7 @@
 #ifndef UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
 #define UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
 
+#include <functional>
 #include <map>
 #include <string>
 #include <string_view>
@@ -40,24 +41,23 @@
   ~FakePrefs();
 
   // PrefsInterface methods.
-  bool GetString(const std::string& key, std::string* value) const override;
-  bool SetString(const std::string& key, std::string_view 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 GetString(std::string_view key, std::string* value) const override;
+  bool SetString(std::string_view key, std::string_view value) override;
+  bool GetInt64(std::string_view key, int64_t* value) const override;
+  bool SetInt64(std::string_view key, const int64_t value) override;
+  bool GetBoolean(std::string_view key, bool* value) const override;
+  bool SetBoolean(std::string_view key, const bool value) override;
 
-  bool Exists(const std::string& key) const override;
-  bool Delete(const std::string& key) override;
-  bool Delete(const std::string& key,
+  bool Exists(std::string_view key) const override;
+  bool Delete(std::string_view key) override;
+  bool Delete(std::string_view key,
               const std::vector<std::string>& nss) override;
 
-  bool GetSubKeys(const std::string& ns,
+  bool GetSubKeys(std::string_view ns,
                   std::vector<std::string>* keys) const override;
 
-  void AddObserver(const std::string& key,
-                   ObserverInterface* observer) override;
-  void RemoveObserver(const std::string& key,
+  void AddObserver(std::string_view key, ObserverInterface* observer) override;
+  void RemoveObserver(std::string_view key,
                       ObserverInterface* observer) override;
 
  private:
@@ -92,24 +92,25 @@
   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;
+  void CheckKeyType(std::string_view 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, T value);
+  void SetValue(std::string_view key, 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;
+  bool GetValue(std::string_view key, T* value) const;
 
   // Container for all the key/value pairs.
-  std::map<std::string, PrefTypeValue> values_;
+  std::map<std::string, PrefTypeValue, std::less<>> values_;
 
   // The registered observers watching for changes.
-  std::map<std::string, std::vector<ObserverInterface*>> observers_;
+  std::map<std::string, std::vector<ObserverInterface*>, std::less<>>
+      observers_;
 
   DISALLOW_COPY_AND_ASSIGN(FakePrefs);
 };
diff --git a/common/mock_prefs.h b/common/mock_prefs.h
index 49431fb..f308074 100644
--- a/common/mock_prefs.h
+++ b/common/mock_prefs.h
@@ -29,27 +29,24 @@
 
 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, std::string_view 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(GetString, bool(std::string_view key, std::string* value));
+  MOCK_METHOD2(SetString, bool(std::string_view key, std::string_view value));
+  MOCK_CONST_METHOD2(GetInt64, bool(std::string_view key, int64_t* value));
+  MOCK_METHOD2(SetInt64, bool(std::string_view 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_METHOD2(GetBoolean, bool(std::string_view key, bool* value));
+  MOCK_METHOD2(SetBoolean, bool(std::string_view key, const bool value));
 
-  MOCK_CONST_METHOD1(Exists, bool(const std::string& key));
-  MOCK_METHOD1(Delete, bool(const std::string& key));
+  MOCK_CONST_METHOD1(Exists, bool(std::string_view key));
+  MOCK_METHOD1(Delete, bool(std::string_view key));
   MOCK_METHOD2(Delete,
-               bool(const std::string& key,
-                    const std::vector<std::string>& nss));
+               bool(std::string_view key, const std::vector<std::string>& nss));
 
   MOCK_CONST_METHOD2(GetSubKeys,
-                     bool(const std::string&, std::vector<std::string>*));
+                     bool(std::string_view, std::vector<std::string>*));
 
-  MOCK_METHOD2(AddObserver, void(const std::string& key, ObserverInterface*));
-  MOCK_METHOD2(RemoveObserver,
-               void(const std::string& key, ObserverInterface*));
+  MOCK_METHOD2(AddObserver, void(std::string_view key, ObserverInterface*));
+  MOCK_METHOD2(RemoveObserver, void(std::string_view key, ObserverInterface*));
 };
 
 }  // namespace chromeos_update_engine
diff --git a/common/prefs.cc b/common/prefs.cc
index 1e06be4..f33a8a9 100644
--- a/common/prefs.cc
+++ b/common/prefs.cc
@@ -51,11 +51,11 @@
 
 }  // namespace
 
-bool PrefsBase::GetString(const string& key, string* value) const {
+bool PrefsBase::GetString(const std::string_view key, string* value) const {
   return storage_->GetKey(key, value);
 }
 
-bool PrefsBase::SetString(const string& key, std::string_view value) {
+bool PrefsBase::SetString(std::string_view key, std::string_view value) {
   TEST_AND_RETURN_FALSE(storage_->SetKey(key, value));
   const auto observers_for_key = observers_.find(key);
   if (observers_for_key != observers_.end()) {
@@ -66,7 +66,7 @@
   return true;
 }
 
-bool PrefsBase::GetInt64(const string& key, int64_t* value) const {
+bool PrefsBase::GetInt64(const std::string_view key, int64_t* value) const {
   string str_value;
   if (!GetString(key, &str_value))
     return false;
@@ -75,11 +75,11 @@
   return true;
 }
 
-bool PrefsBase::SetInt64(const string& key, const int64_t value) {
+bool PrefsBase::SetInt64(std::string_view key, const int64_t value) {
   return SetString(key, base::NumberToString(value));
 }
 
-bool PrefsBase::GetBoolean(const string& key, bool* value) const {
+bool PrefsBase::GetBoolean(std::string_view key, bool* value) const {
   string str_value;
   if (!GetString(key, &str_value))
     return false;
@@ -95,15 +95,15 @@
   return false;
 }
 
-bool PrefsBase::SetBoolean(const string& key, const bool value) {
+bool PrefsBase::SetBoolean(std::string_view key, const bool value) {
   return SetString(key, value ? "true" : "false");
 }
 
-bool PrefsBase::Exists(const string& key) const {
+bool PrefsBase::Exists(std::string_view key) const {
   return storage_->KeyExists(key);
 }
 
-bool PrefsBase::Delete(const string& key) {
+bool PrefsBase::Delete(std::string_view key) {
   TEST_AND_RETURN_FALSE(storage_->DeleteKey(key));
   const auto observers_for_key = observers_.find(key);
   if (observers_for_key != observers_.end()) {
@@ -114,7 +114,7 @@
   return true;
 }
 
-bool PrefsBase::Delete(const string& pref_key, const vector<string>& nss) {
+bool PrefsBase::Delete(std::string_view pref_key, const vector<string>& nss) {
   // Delete pref key for platform.
   bool success = Delete(pref_key);
   // Delete pref key in each namespace.
@@ -132,16 +132,18 @@
   return success;
 }
 
-bool PrefsBase::GetSubKeys(const string& ns, vector<string>* keys) const {
+bool PrefsBase::GetSubKeys(std::string_view ns, vector<string>* keys) const {
   return storage_->GetSubKeys(ns, keys);
 }
 
-void PrefsBase::AddObserver(const string& key, ObserverInterface* observer) {
-  observers_[key].push_back(observer);
+void PrefsBase::AddObserver(std::string_view key, ObserverInterface* observer) {
+  observers_[std::string{key}].push_back(observer);
 }
 
-void PrefsBase::RemoveObserver(const string& key, ObserverInterface* observer) {
-  std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+void PrefsBase::RemoveObserver(std::string_view key,
+                               ObserverInterface* observer) {
+  std::vector<ObserverInterface*>& observers_for_key =
+      observers_[std::string{key}];
   auto observer_it =
       std::find(observers_for_key.begin(), observers_for_key.end(), observer);
   if (observer_it != observers_for_key.end())
@@ -165,7 +167,7 @@
   return true;
 }
 
-bool Prefs::FileStorage::GetKey(const string& key, string* value) const {
+bool Prefs::FileStorage::GetKey(std::string_view key, string* value) const {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
   if (!base::ReadFileToString(filename, value)) {
@@ -174,7 +176,7 @@
   return true;
 }
 
-bool Prefs::FileStorage::GetSubKeys(const string& ns,
+bool Prefs::FileStorage::GetSubKeys(std::string_view ns,
                                     vector<string>* keys) const {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(ns, &filename));
@@ -192,7 +194,7 @@
   return true;
 }
 
-bool Prefs::FileStorage::SetKey(const string& key, std::string_view value) {
+bool Prefs::FileStorage::SetKey(std::string_view key, std::string_view value) {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
   if (!base::DirectoryExists(filename.DirName())) {
@@ -205,13 +207,13 @@
   return true;
 }
 
-bool Prefs::FileStorage::KeyExists(const string& key) const {
+bool Prefs::FileStorage::KeyExists(std::string_view key) const {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
   return base::PathExists(filename);
 }
 
-bool Prefs::FileStorage::DeleteKey(const string& key) {
+bool Prefs::FileStorage::DeleteKey(std::string_view key) {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
 #if BASE_VER < 800000
@@ -222,20 +224,21 @@
   return true;
 }
 
-bool Prefs::FileStorage::GetFileNameForKey(const string& key,
+bool Prefs::FileStorage::GetFileNameForKey(std::string_view key,
                                            base::FilePath* filename) const {
   // Allows only non-empty keys containing [A-Za-z0-9_-/].
   TEST_AND_RETURN_FALSE(!key.empty());
   for (char c : key)
     TEST_AND_RETURN_FALSE(base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) ||
                           c == '_' || c == '-' || c == kKeySeparator);
-  *filename = prefs_dir_.Append(key);
+  *filename = prefs_dir_.Append(
+      base::FilePath::StringPieceType(key.data(), key.size()));
   return true;
 }
 
 // MemoryPrefs
 
-bool MemoryPrefs::MemoryStorage::GetKey(const string& key,
+bool MemoryPrefs::MemoryStorage::GetKey(std::string_view key,
                                         string* value) const {
   auto it = values_.find(key);
   if (it == values_.end())
@@ -244,15 +247,13 @@
   return true;
 }
 
-bool MemoryPrefs::MemoryStorage::GetSubKeys(const string& ns,
+bool MemoryPrefs::MemoryStorage::GetSubKeys(std::string_view ns,
                                             vector<string>* keys) const {
-  using value_type = decltype(values_)::value_type;
-  using key_type = decltype(values_)::key_type;
-  auto lower_comp = [](const value_type& pr, const key_type& ns) {
-    return pr.first.substr(0, ns.length()) < ns;
+  auto lower_comp = [](const auto& pr, const auto& ns) {
+    return std::string_view{pr.first.data(), ns.length()} < ns;
   };
-  auto upper_comp = [](const key_type& ns, const value_type& pr) {
-    return ns < pr.first.substr(0, ns.length());
+  auto upper_comp = [](const auto& ns, const auto& pr) {
+    return ns < std::string_view{pr.first.data(), ns.length()};
   };
   auto lower_it =
       std::lower_bound(begin(values_), end(values_), ns, lower_comp);
@@ -262,17 +263,17 @@
   return true;
 }
 
-bool MemoryPrefs::MemoryStorage::SetKey(const string& key,
+bool MemoryPrefs::MemoryStorage::SetKey(std::string_view key,
                                         std::string_view value) {
-  values_[key] = value;
+  values_[std::string{key}] = value;
   return true;
 }
 
-bool MemoryPrefs::MemoryStorage::KeyExists(const string& key) const {
+bool MemoryPrefs::MemoryStorage::KeyExists(std::string_view key) const {
   return values_.find(key) != values_.end();
 }
 
-bool MemoryPrefs::MemoryStorage::DeleteKey(const string& key) {
+bool MemoryPrefs::MemoryStorage::DeleteKey(std::string_view key) {
   auto it = values_.find(key);
   if (it != values_.end())
     values_.erase(it);
diff --git a/common/prefs.h b/common/prefs.h
index 93477dd..c3105c6 100644
--- a/common/prefs.h
+++ b/common/prefs.h
@@ -17,6 +17,7 @@
 #ifndef UPDATE_ENGINE_COMMON_PREFS_H_
 #define UPDATE_ENGINE_COMMON_PREFS_H_
 
+#include <functional>
 #include <map>
 #include <string>
 #include <string_view>
@@ -41,23 +42,23 @@
 
     // 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;
+    virtual bool GetKey(std::string_view key, std::string* value) const = 0;
 
     // Get the keys stored within the namespace. If there are no keys in the
     // namespace, |keys| will be empty. Returns whether the operation succeeded.
-    virtual bool GetSubKeys(const std::string& ns,
+    virtual bool GetSubKeys(std::string_view ns,
                             std::vector<std::string>* keys) 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, std::string_view value) = 0;
+    virtual bool SetKey(std::string_view key, std::string_view value) = 0;
 
     // Returns whether the key named |key| exists.
-    virtual bool KeyExists(const std::string& key) const = 0;
+    virtual bool KeyExists(std::string_view 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;
+    virtual bool DeleteKey(std::string_view key) = 0;
 
    private:
     DISALLOW_COPY_AND_ASSIGN(StorageInterface);
@@ -66,29 +67,29 @@
   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, std::string_view 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 GetString(std::string_view key, std::string* value) const override;
+  bool SetString(std::string_view key, std::string_view value) override;
+  bool GetInt64(std::string_view key, int64_t* value) const override;
+  bool SetInt64(std::string_view key, const int64_t value) override;
+  bool GetBoolean(std::string_view key, bool* value) const override;
+  bool SetBoolean(std::string_view key, const bool value) override;
 
-  bool Exists(const std::string& key) const override;
-  bool Delete(const std::string& key) override;
-  bool Delete(const std::string& pref_key,
+  bool Exists(std::string_view key) const override;
+  bool Delete(std::string_view key) override;
+  bool Delete(std::string_view pref_key,
               const std::vector<std::string>& nss) override;
 
-  bool GetSubKeys(const std::string& ns,
+  bool GetSubKeys(std::string_view ns,
                   std::vector<std::string>* keys) const override;
 
-  void AddObserver(const std::string& key,
-                   ObserverInterface* observer) override;
-  void RemoveObserver(const std::string& key,
+  void AddObserver(std::string_view key, ObserverInterface* observer) override;
+  void RemoveObserver(std::string_view key,
                       ObserverInterface* observer) override;
 
  private:
   // The registered observers watching for changes.
-  std::map<std::string, std::vector<ObserverInterface*>> observers_;
+  std::map<std::string, std::vector<ObserverInterface*>, std::less<>>
+      observers_;
 
   // The concrete implementation of the storage used for the keys.
   StorageInterface* storage_;
@@ -121,12 +122,12 @@
     bool Init(const base::FilePath& prefs_dir);
 
     // PrefsBase::StorageInterface overrides.
-    bool GetKey(const std::string& key, std::string* value) const override;
-    bool GetSubKeys(const std::string& ns,
+    bool GetKey(std::string_view key, std::string* value) const override;
+    bool GetSubKeys(std::string_view ns,
                     std::vector<std::string>* keys) const override;
-    bool SetKey(const std::string& key, std::string_view value) override;
-    bool KeyExists(const std::string& key) const override;
-    bool DeleteKey(const std::string& key) override;
+    bool SetKey(std::string_view key, std::string_view value) override;
+    bool KeyExists(std::string_view key) const override;
+    bool DeleteKey(std::string_view key) override;
 
    private:
     FRIEND_TEST(PrefsTest, GetFileNameForKey);
@@ -135,7 +136,7 @@
 
     // 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,
+    bool GetFileNameForKey(std::string_view key,
                            base::FilePath* filename) const;
 
     // Preference store directory.
@@ -161,16 +162,16 @@
     MemoryStorage() = default;
 
     // PrefsBase::StorageInterface overrides.
-    bool GetKey(const std::string& key, std::string* value) const override;
-    bool GetSubKeys(const std::string& ns,
+    bool GetKey(std::string_view, std::string* value) const override;
+    bool GetSubKeys(std::string_view ns,
                     std::vector<std::string>* keys) const override;
-    bool SetKey(const std::string& key, std::string_view value) override;
-    bool KeyExists(const std::string& key) const override;
-    bool DeleteKey(const std::string& key) override;
+    bool SetKey(std::string_view key, std::string_view value) override;
+    bool KeyExists(std::string_view key) const override;
+    bool DeleteKey(std::string_view key) override;
 
    private:
     // The std::map holding the values in memory.
-    std::map<std::string, std::string> values_;
+    std::map<std::string, std::string, std::less<>> values_;
   };
 
   // The concrete memory storage implementation.
diff --git a/common/prefs_interface.h b/common/prefs_interface.h
index e773a35..69ccf68 100644
--- a/common/prefs_interface.h
+++ b/common/prefs_interface.h
@@ -37,10 +37,10 @@
     virtual ~ObserverInterface() = default;
 
     // Called when the value is set for the observed |key|.
-    virtual void OnPrefSet(const std::string& key) = 0;
+    virtual void OnPrefSet(std::string_view key) = 0;
 
     // Called when the observed |key| is deleted.
-    virtual void OnPrefDeleted(const std::string& key) = 0;
+    virtual void OnPrefDeleted(std::string_view key) = 0;
   };
 
   virtual ~PrefsInterface() = default;
@@ -48,61 +48,61 @@
   // 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;
+  virtual bool GetString(std::string_view 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, std::string_view value) = 0;
+  virtual bool SetString(std::string_view key, std::string_view 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;
+  virtual bool GetInt64(std::string_view 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;
+  virtual bool SetInt64(std::string_view 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;
+  virtual bool GetBoolean(std::string_view 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;
+  virtual bool SetBoolean(std::string_view 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;
+  virtual bool Exists(std::string_view 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;
+  virtual bool Delete(std::string_view key) = 0;
 
   // Deletes the pref key from platform and given namespace subdirectories.
   // Keys are matched against end of pref keys in each namespace.
   // Returns true if all deletes were successful.
-  virtual bool Delete(const std::string& pref_key,
+  virtual bool Delete(std::string_view pref_key,
                       const std::vector<std::string>& nss) = 0;
 
   // Creates a key which is part of a sub preference.
   static std::string CreateSubKey(const std::vector<std::string>& ns_with_key);
 
   // Returns a list of keys within the namespace.
-  virtual bool GetSubKeys(const std::string& ns,
+  virtual bool GetSubKeys(std::string_view ns,
                           std::vector<std::string>* keys) const = 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,
+  virtual void AddObserver(std::string_view 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,
+  virtual void RemoveObserver(std::string_view key,
                               ObserverInterface* observer) = 0;
 
  protected:
diff --git a/common/prefs_unittest.cc b/common/prefs_unittest.cc
index a5f46e5..cef6d44 100644
--- a/common/prefs_unittest.cc
+++ b/common/prefs_unittest.cc
@@ -507,8 +507,8 @@
 
 class MockPrefsObserver : public PrefsInterface::ObserverInterface {
  public:
-  MOCK_METHOD1(OnPrefSet, void(const string&));
-  MOCK_METHOD1(OnPrefDeleted, void(const string& key));
+  MOCK_METHOD1(OnPrefSet, void(std::string_view));
+  MOCK_METHOD1(OnPrefDeleted, void(std::string_view));
 };
 
 TEST_F(PrefsTest, ObserversCalled) {
diff --git a/metrics_utils.cc b/metrics_utils.cc
index 34da5a1..ade024a 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -294,7 +294,7 @@
   return metrics::ConnectionType::kUnknown;
 }
 
-int64_t GetPersistedValue(const std::string& key, PrefsInterface* prefs) {
+int64_t GetPersistedValue(std::string_view key, PrefsInterface* prefs) {
   CHECK(prefs);
   if (!prefs->Exists(key))
     return 0;
diff --git a/metrics_utils.h b/metrics_utils.h
index 3aac4e5..16e9eec 100644
--- a/metrics_utils.h
+++ b/metrics_utils.h
@@ -50,7 +50,7 @@
 
 // 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, PrefsInterface* prefs);
+int64_t GetPersistedValue(std::string_view key, PrefsInterface* prefs);
 
 // Persists the reboot count of the update attempt to |kPrefsNumReboots|.
 void SetNumReboots(int64_t num_reboots, PrefsInterface* prefs);
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index a57169b..31fa90e 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -32,8 +32,6 @@
 #include <base/format_macros.h>
 #include <base/metrics/histogram_macros.h>
 #include <base/strings/string_number_conversions.h>
-#include <base/strings/string_util.h>
-#include <base/strings/stringprintf.h>
 #include <base/time/time.h>
 #include <brillo/data_encoding.h>
 #include <bsdiff/bspatch.h>
@@ -873,44 +871,6 @@
   return partition_writer_->PerformZeroOrDiscardOperation(operation);
 }
 
-bool PartitionWriter::ValidateSourceHash(const brillo::Blob& calculated_hash,
-                                         const InstallOperation& operation,
-                                         const FileDescriptorPtr source_fd,
-                                         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, ",");
-
-    // Log remount history if this device is an ext4 partition.
-    LogMountHistory(source_fd);
-
-    *error = ErrorCode::kDownloadStateInitializationError;
-    return false;
-  }
-  return true;
-}
-
 bool DeltaPerformer::PerformSourceCopyOperation(
     const InstallOperation& operation, ErrorCode* error) {
   if (operation.has_src_length())
@@ -1531,7 +1491,7 @@
       part_name, slot);
 }
 
-std::unique_ptr<PartitionWriter> DeltaPerformer::CreatePartitionWriter(
+std::unique_ptr<PartitionWriterInterface> DeltaPerformer::CreatePartitionWriter(
     const PartitionUpdate& partition_update,
     const InstallPlan::Partition& install_part,
     DynamicPartitionControlInterface* dynamic_control,
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
index c54316b..30f5e9c 100644
--- a/payload_consumer/delta_performer.h
+++ b/payload_consumer/delta_performer.h
@@ -176,14 +176,6 @@
   // Exposed for testing purposes.
   bool CheckpointUpdateProgress(bool force);
 
-  // Compare |calculated_hash| with source hash in |operation|, return false and
-  // dump hash and set |error| if don't match.
-  // |source_fd| is the file descriptor of the source partition.
-  static bool ValidateSourceHash(const brillo::Blob& calculated_hash,
-                                 const InstallOperation& operation,
-                                 const FileDescriptorPtr source_fd,
-                                 ErrorCode* error);
-
   // Initialize partitions and allocate required space for an update with the
   // given |manifest|. |update_check_response_hash| is used to check if the
   // previous call to this function corresponds to the same payload.
@@ -204,7 +196,7 @@
 
  protected:
   // Exposed as virtual for testing purposes.
-  virtual std::unique_ptr<PartitionWriter> CreatePartitionWriter(
+  virtual std::unique_ptr<PartitionWriterInterface> CreatePartitionWriter(
       const PartitionUpdate& partition_update,
       const InstallPlan::Partition& install_part,
       DynamicPartitionControlInterface* dynamic_control,
@@ -434,7 +426,7 @@
       base::TimeDelta::FromSeconds(kCheckpointFrequencySeconds)};
   base::TimeTicks update_checkpoint_time_;
 
-  std::unique_ptr<PartitionWriter> partition_writer_;
+  std::unique_ptr<PartitionWriterInterface> partition_writer_;
 
   DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
 };
diff --git a/payload_consumer/delta_performer_integration_test.cc b/payload_consumer/delta_performer_integration_test.cc
index 4fab975..a72b8ae 100644
--- a/payload_consumer/delta_performer_integration_test.cc
+++ b/payload_consumer/delta_performer_integration_test.cc
@@ -156,7 +156,7 @@
     performer.manifest_.CopyFrom(manifest);
     performer.major_payload_version_ = major_version;
 
-    EXPECT_EQ(expected, performer.ValidateManifest());
+    ASSERT_EQ(expected, performer.ValidateManifest());
   }
   void AddPartition(DeltaArchiveManifest* manifest,
                     string name,
@@ -171,19 +171,19 @@
 static void CompareFilesByBlock(const string& a_file,
                                 const string& b_file,
                                 size_t image_size) {
-  EXPECT_EQ(0U, image_size % kBlockSize);
+  ASSERT_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;
+  ASSERT_TRUE(utils::ReadFile(a_file, &a_data)) << "file failed: " << a_file;
+  ASSERT_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);
+    ASSERT_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";
+    ASSERT_EQ(a_sub, b_sub) << "Block " << (i / kBlockSize) << " differs";
   }
   if (::testing::Test::HasNonfatalFailure()) {
     LOG(INFO) << "Compared filesystems with size " << image_size
@@ -207,8 +207,7 @@
   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;
+  return utils::PWriteAll(fd, "\0", 1, offset);
 }
 
 static bool InsertSignaturePlaceholder(size_t signature_size,
@@ -245,7 +244,7 @@
                                                    {metadata_signature},
                                                    payload_path,
                                                    out_metadata_size));
-  EXPECT_TRUE(PayloadSigner::VerifySignedPayload(
+  ASSERT_TRUE(PayloadSigner::VerifySignedPayload(
       payload_path, GetBuildArtifactsPath(kUnittestPublicKeyPath)));
 }
 
@@ -359,12 +358,12 @@
     // 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));
+    ASSERT_EQ(1, BN_set_word(e, RSA_F4));
+    ASSERT_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,
+    ASSERT_EQ(1,
               PEM_write_RSAPrivateKey(
                   fprikey, rsa, nullptr, nullptr, 0, nullptr, nullptr));
     fclose(fprikey);
@@ -405,7 +404,7 @@
   // in-place on A, we apply it to a new image, result_img.
   state->result_img.reset(new ScopedTempFile("result_img.XXXXXX"));
 
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"),
                      base::FilePath(state->a_img->path())));
 
@@ -422,28 +421,28 @@
                             std::begin(kRandomString),
                             std::end(kRandomString));
     }
-    EXPECT_TRUE(utils::WriteFile(
+    ASSERT_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()),
+    ASSERT_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(
+    ASSERT_TRUE(WriteSparseFile(
         base::StringPrintf("%s/move-from-sparse", a_mnt.c_str()), 16 * 1024));
 
-    EXPECT_TRUE(WriteByteAtOffset(
+    ASSERT_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(
+    ASSERT_TRUE(
         utils::WriteFile(base::StringPrintf("%s/ones", a_mnt.c_str()).c_str(),
                          ones.data(),
                          ones.size()));
@@ -451,12 +450,12 @@
 
   // Create a result image with image_size bytes of garbage.
   brillo::Blob ones(state->image_size, 0xff);
-  EXPECT_TRUE(utils::WriteFile(
+  ASSERT_TRUE(utils::WriteFile(
       state->result_img->path().c_str(), ones.data(), ones.size()));
-  EXPECT_EQ(utils::FileSize(state->a_img->path()),
+  ASSERT_EQ(utils::FileSize(state->a_img->path()),
             utils::FileSize(state->result_img->path()));
 
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"),
                      base::FilePath(state->b_img->path())));
   {
@@ -465,45 +464,45 @@
     ScopedLoopMounter b_mounter(state->b_img->path(), &b_mnt, 0);
     base::FilePath mnt_path(b_mnt);
 
-    EXPECT_TRUE(base::CopyFile(mnt_path.Append("regular-small"),
+    ASSERT_TRUE(base::CopyFile(mnt_path.Append("regular-small"),
                                mnt_path.Append("regular-small2")));
 #if BASE_VER < 800000
-    EXPECT_TRUE(base::DeleteFile(mnt_path.Append("regular-small"), false));
+    ASSERT_TRUE(base::DeleteFile(mnt_path.Append("regular-small"), false));
 #else
-    EXPECT_TRUE(base::DeleteFile(mnt_path.Append("regular-small")));
+    ASSERT_TRUE(base::DeleteFile(mnt_path.Append("regular-small")));
 #endif
-    EXPECT_TRUE(base::Move(mnt_path.Append("regular-small2"),
+    ASSERT_TRUE(base::Move(mnt_path.Append("regular-small2"),
                            mnt_path.Append("regular-small")));
-    EXPECT_TRUE(
+    ASSERT_TRUE(
         test_utils::WriteFileString(mnt_path.Append("foo").value(), "foo"));
-    EXPECT_EQ(0, base::WriteFile(mnt_path.Append("emptyfile"), "", 0));
+    ASSERT_EQ(0, base::WriteFile(mnt_path.Append("emptyfile"), "", 0));
 
-    EXPECT_TRUE(
+    ASSERT_TRUE(
         WriteSparseFile(mnt_path.Append("fullsparse").value(), 1024 * 1024));
-    EXPECT_TRUE(
+    ASSERT_TRUE(
         WriteSparseFile(mnt_path.Append("move-to-sparse").value(), 16 * 1024));
 
     brillo::Blob zeros(16 * 1024, 0);
-    EXPECT_EQ(static_cast<int>(zeros.size()),
+    ASSERT_EQ(static_cast<int>(zeros.size()),
               base::WriteFile(mnt_path.Append("move-from-sparse"),
                               reinterpret_cast<const char*>(zeros.data()),
                               zeros.size()));
 
-    EXPECT_TRUE(
+    ASSERT_TRUE(
         WriteByteAtOffset(mnt_path.Append("move-semi-sparse").value(), 4096));
-    EXPECT_TRUE(WriteByteAtOffset(mnt_path.Append("partsparse").value(), 4096));
+    ASSERT_TRUE(WriteByteAtOffset(mnt_path.Append("partsparse").value(), 4096));
 
-    EXPECT_TRUE(
+    ASSERT_TRUE(
         base::CopyFile(mnt_path.Append("regular-16k"), mnt_path.Append("tmp")));
-    EXPECT_TRUE(base::Move(mnt_path.Append("tmp"),
+    ASSERT_TRUE(base::Move(mnt_path.Append("tmp"),
                            mnt_path.Append("link-hard-regular-16k")));
 
 #if BASE_VER < 800000
-    EXPECT_TRUE(base::DeleteFile(mnt_path.Append("link-short_symlink"), false));
+    ASSERT_TRUE(base::DeleteFile(mnt_path.Append("link-short_symlink"), false));
 #else
-    EXPECT_TRUE(base::DeleteFile(mnt_path.Append("link-short_symlink")));
+    ASSERT_TRUE(base::DeleteFile(mnt_path.Append("link-short_symlink")));
 #endif
-    EXPECT_TRUE(test_utils::WriteFileString(
+    ASSERT_TRUE(test_utils::WriteFileString(
         mnt_path.Append("link-short_symlink").value(), "foobar"));
 
     brillo::Blob hardtocompress;
@@ -512,7 +511,7 @@
                             std::begin(kRandomString),
                             std::end(kRandomString));
     }
-    EXPECT_TRUE(utils::WriteFile(
+    ASSERT_TRUE(utils::WriteFile(
         base::StringPrintf("%s/hardtocompress", b_mnt.c_str()).c_str(),
         hardtocompress.data(),
         hardtocompress.size()));
@@ -534,13 +533,13 @@
       std::begin(kNewData), std::end(kNewData), state->new_kernel_data.begin());
 
   // Write kernels to disk
-  EXPECT_TRUE(utils::WriteFile(state->old_kernel->path().c_str(),
+  ASSERT_TRUE(utils::WriteFile(state->old_kernel->path().c_str(),
                                state->old_kernel_data.data(),
                                state->old_kernel_data.size()));
-  EXPECT_TRUE(utils::WriteFile(state->new_kernel->path().c_str(),
+  ASSERT_TRUE(utils::WriteFile(state->new_kernel->path().c_str(),
                                state->new_kernel_data.data(),
                                state->new_kernel_data.size()));
-  EXPECT_TRUE(utils::WriteFile(state->result_kernel->path().c_str(),
+  ASSERT_TRUE(utils::WriteFile(state->result_kernel->path().c_str(),
                                state->result_kernel_data.data(),
                                state->result_kernel_data.size()));
 
@@ -564,9 +563,9 @@
       if (!full_kernel)
         payload_config.source.partitions.back().path =
             state->old_kernel->path();
-      EXPECT_TRUE(payload_config.source.LoadImageSize());
+      ASSERT_TRUE(payload_config.source.LoadImageSize());
       for (PartitionConfig& part : payload_config.source.partitions)
-        EXPECT_TRUE(part.OpenFilesystem());
+        ASSERT_TRUE(part.OpenFilesystem());
     } else {
       if (payload_config.hard_chunk_size == -1)
         // Use 1 MiB chunk size for the full unittests.
@@ -576,26 +575,26 @@
     payload_config.target.partitions.back().path = state->b_img->path();
     payload_config.target.partitions.emplace_back(kPartitionNameKernel);
     payload_config.target.partitions.back().path = state->new_kernel->path();
-    EXPECT_TRUE(payload_config.target.LoadImageSize());
+    ASSERT_TRUE(payload_config.target.LoadImageSize());
     for (PartitionConfig& part : payload_config.target.partitions)
-      EXPECT_TRUE(part.OpenFilesystem());
+      ASSERT_TRUE(part.OpenFilesystem());
 
-    EXPECT_TRUE(payload_config.Validate());
-    EXPECT_TRUE(GenerateUpdatePayloadFile(payload_config,
+    ASSERT_TRUE(payload_config.Validate());
+    ASSERT_TRUE(GenerateUpdatePayloadFile(payload_config,
                                           state->delta_file->path(),
                                           private_key,
                                           &state->metadata_size));
   }
   // Extend the "partitions" holding the file system a bit.
-  EXPECT_EQ(0,
+  ASSERT_EQ(0,
             HANDLE_EINTR(truncate(state->a_img->path().c_str(),
                                   state->image_size + 1024 * 1024)));
-  EXPECT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024),
+  ASSERT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024),
             utils::FileSize(state->a_img->path()));
-  EXPECT_EQ(0,
+  ASSERT_EQ(0,
             HANDLE_EINTR(truncate(state->b_img->path().c_str(),
                                   state->image_size + 1024 * 1024)));
-  EXPECT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024),
+  ASSERT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024),
             utils::FileSize(state->b_img->path()));
 
   if (signature_test == kSignatureGeneratedPlaceholder ||
@@ -643,9 +642,9 @@
                            uint32_t minor_version) {
   // Check the metadata.
   {
-    EXPECT_TRUE(utils::ReadFile(state->delta_file->path(), &state->delta));
+    ASSERT_TRUE(utils::ReadFile(state->delta_file->path(), &state->delta));
     PayloadMetadata payload_metadata;
-    EXPECT_TRUE(payload_metadata.ParsePayloadHeader(state->delta));
+    ASSERT_TRUE(payload_metadata.ParsePayloadHeader(state->delta));
     state->metadata_size = payload_metadata.GetMetadataSize();
     LOG(INFO) << "Metadata size: " << state->metadata_size;
     state->metadata_signature_size =
@@ -653,23 +652,23 @@
     LOG(INFO) << "Metadata signature size: " << state->metadata_signature_size;
 
     DeltaArchiveManifest manifest;
-    EXPECT_TRUE(payload_metadata.GetManifest(state->delta, &manifest));
+    ASSERT_TRUE(payload_metadata.GetManifest(state->delta, &manifest));
     if (signature_test == kSignatureNone) {
-      EXPECT_FALSE(manifest.has_signatures_offset());
-      EXPECT_FALSE(manifest.has_signatures_size());
+      ASSERT_FALSE(manifest.has_signatures_offset());
+      ASSERT_FALSE(manifest.has_signatures_size());
     } else {
-      EXPECT_TRUE(manifest.has_signatures_offset());
-      EXPECT_TRUE(manifest.has_signatures_size());
+      ASSERT_TRUE(manifest.has_signatures_offset());
+      ASSERT_TRUE(manifest.has_signatures_size());
       Signatures sigs_message;
-      EXPECT_TRUE(sigs_message.ParseFromArray(
+      ASSERT_TRUE(sigs_message.ParseFromArray(
           &state->delta[state->metadata_size + state->metadata_signature_size +
                         manifest.signatures_offset()],
           manifest.signatures_size()));
       if (signature_test == kSignatureGeneratedShellRotateCl1 ||
           signature_test == kSignatureGeneratedShellRotateCl2)
-        EXPECT_EQ(2, sigs_message.signatures_size());
+        ASSERT_EQ(2, sigs_message.signatures_size());
       else
-        EXPECT_EQ(1, sigs_message.signatures_size());
+        ASSERT_EQ(1, sigs_message.signatures_size());
       const Signatures::Signature& signature = sigs_message.signatures(0);
 
       vector<string> key_paths{GetBuildArtifactsPath(kUnittestPrivateKeyPath)};
@@ -680,10 +679,10 @@
         key_paths.push_back(GetBuildArtifactsPath(kUnittestPrivateKey2Path));
       }
       uint64_t expected_sig_data_length = 0;
-      EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+      ASSERT_TRUE(PayloadSigner::SignatureBlobLength(
           key_paths, &expected_sig_data_length));
-      EXPECT_EQ(expected_sig_data_length, manifest.signatures_size());
-      EXPECT_FALSE(signature.data().empty());
+      ASSERT_EQ(expected_sig_data_length, manifest.signatures_size());
+      ASSERT_FALSE(signature.data().empty());
     }
 
     // TODO(ahassani): Make |DeltaState| into a partition list kind of struct
@@ -696,15 +695,15 @@
           return partition.partition_name() == kPartitionNameKernel;
         });
     if (full_kernel) {
-      EXPECT_FALSE(kernel_part.has_old_partition_info());
+      ASSERT_FALSE(kernel_part.has_old_partition_info());
     } else {
-      EXPECT_EQ(state->old_kernel_data.size(),
+      ASSERT_EQ(state->old_kernel_data.size(),
                 kernel_part.old_partition_info().size());
-      EXPECT_FALSE(kernel_part.old_partition_info().hash().empty());
+      ASSERT_FALSE(kernel_part.old_partition_info().hash().empty());
     }
-    EXPECT_EQ(state->new_kernel_data.size(),
+    ASSERT_EQ(state->new_kernel_data.size(),
               kernel_part.new_partition_info().size());
-    EXPECT_FALSE(kernel_part.new_partition_info().hash().empty());
+    ASSERT_FALSE(kernel_part.new_partition_info().hash().empty());
 
     const auto& rootfs_part =
         *std::find_if(manifest.partitions().begin(),
@@ -713,11 +712,11 @@
                         return partition.partition_name() == kPartitionNameRoot;
                       });
     if (full_rootfs) {
-      EXPECT_FALSE(rootfs_part.has_old_partition_info());
+      ASSERT_FALSE(rootfs_part.has_old_partition_info());
     } else {
-      EXPECT_FALSE(rootfs_part.old_partition_info().hash().empty());
+      ASSERT_FALSE(rootfs_part.old_partition_info().hash().empty());
     }
-    EXPECT_FALSE(rootfs_part.new_partition_info().hash().empty());
+    ASSERT_FALSE(rootfs_part.new_partition_info().hash().empty());
   }
 
   NiceMock<MockPrefs> prefs;
@@ -798,7 +797,7 @@
           ? GetBuildArtifactsPath(kUnittestPrivateKeyECPath)
           : GetBuildArtifactsPath(kUnittestPrivateKeyPath),
       &install_plan->payloads[0].metadata_signature));
-  EXPECT_FALSE(install_plan->payloads[0].metadata_signature.empty());
+  ASSERT_FALSE(install_plan->payloads[0].metadata_signature.empty());
 
   *performer = new DeltaPerformer(&prefs,
                                   &state->fake_boot_control_,
@@ -810,15 +809,15 @@
   string public_key_path = signature_test == kSignatureGeneratedShellECKey
                                ? GetBuildArtifactsPath(kUnittestPublicKeyECPath)
                                : GetBuildArtifactsPath(kUnittestPublicKeyPath);
-  EXPECT_TRUE(utils::FileExists(public_key_path.c_str()));
+  ASSERT_TRUE(utils::FileExists(public_key_path.c_str()));
   (*performer)->set_public_key_path(public_key_path);
   (*performer)->set_update_certificates_path("");
 
-  EXPECT_EQ(
+  ASSERT_EQ(
       static_cast<off_t>(state->image_size),
       HashCalculator::RawHashOfFile(
           state->a_img->path(), state->image_size, &root_part.source_hash));
-  EXPECT_TRUE(HashCalculator::RawHashOfData(state->old_kernel_data,
+  ASSERT_TRUE(HashCalculator::RawHashOfData(state->old_kernel_data,
                                             &kernel_part.source_hash));
 
   // The partitions should be empty before DeltaPerformer.
@@ -872,23 +871,23 @@
     // 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);
+      ASSERT_EQ(expected_error, actual_error);
       if (!continue_writing) {
         LOG(INFO) << "Cannot continue writing. Bailing out.";
         break;
       }
     }
 
-    EXPECT_EQ(ErrorCode::kSuccess, actual_error);
+    ASSERT_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);
+    ASSERT_EQ(0, close_result);
   else
-    EXPECT_LE(0, close_result);
+    ASSERT_LE(0, close_result);
 }
 
 void VerifyPayloadResult(DeltaPerformer* performer,
@@ -896,14 +895,14 @@
                          ErrorCode expected_result,
                          uint32_t minor_version) {
   if (!performer) {
-    EXPECT_TRUE(!"Skipping payload verification since performer is null.");
+    ASSERT_TRUE(!"Skipping payload verification since performer is null.");
     return;
   }
 
   LOG(INFO) << "Verifying payload for expected result " << expected_result;
   brillo::Blob expected_hash;
   HashCalculator::RawHashOfData(state->delta, &expected_hash);
-  EXPECT_EQ(expected_result,
+  ASSERT_EQ(expected_result,
             performer->VerifyPayload(expected_hash, state->delta.size()));
   LOG(INFO) << "Verified payload.";
 
@@ -919,31 +918,31 @@
       state->result_img->path(), state->b_img->path(), state->image_size);
 
   brillo::Blob updated_kernel_partition;
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       utils::ReadFile(state->result_kernel->path(), &updated_kernel_partition));
   ASSERT_GE(updated_kernel_partition.size(), base::size(kNewData));
-  EXPECT_TRUE(std::equal(std::begin(kNewData),
+  ASSERT_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(kPartitionNameRoot, partitions[0].name);
-  EXPECT_EQ(kPartitionNameKernel, partitions[1].name);
+  ASSERT_EQ(2U, partitions.size());
+  ASSERT_EQ(kPartitionNameRoot, partitions[0].name);
+  ASSERT_EQ(kPartitionNameKernel, partitions[1].name);
 
-  EXPECT_EQ(kDefaultKernelSize, partitions[1].target_size);
+  ASSERT_EQ(kDefaultKernelSize, partitions[1].target_size);
   brillo::Blob expected_new_kernel_hash;
-  EXPECT_TRUE(HashCalculator::RawHashOfData(state->new_kernel_data,
+  ASSERT_TRUE(HashCalculator::RawHashOfData(state->new_kernel_data,
                                             &expected_new_kernel_hash));
-  EXPECT_EQ(expected_new_kernel_hash, partitions[1].target_hash);
+  ASSERT_EQ(expected_new_kernel_hash, partitions[1].target_hash);
 
-  EXPECT_EQ(state->image_size, partitions[0].target_size);
+  ASSERT_EQ(state->image_size, partitions[0].target_size);
   brillo::Blob expected_new_rootfs_hash;
-  EXPECT_EQ(
+  ASSERT_EQ(
       static_cast<off_t>(state->image_size),
       HashCalculator::RawHashOfFile(
           state->b_img->path(), state->image_size, &expected_new_rootfs_hash));
-  EXPECT_EQ(expected_new_rootfs_hash, partitions[0].target_hash);
+  ASSERT_EQ(expected_new_rootfs_hash, partitions[0].target_hash);
 }
 
 void VerifyPayload(DeltaPerformer* performer,
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index 840ecf6..ed89f89 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -654,7 +654,9 @@
   brillo::Blob payload_data =
       GeneratePayload(brillo::Blob(), {aop}, false, &old_part);
 
-  EXPECT_EQ(actual_data, ApplyPayload(payload_data, source.path(), false));
+  // When source hash mismatches, PartitionWriter will refuse to write anything.
+  // Therefore we should expect an empty blob.
+  EXPECT_EQ(brillo::Blob{}, ApplyPayload(payload_data, source.path(), false));
 }
 
 TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) {
@@ -1114,7 +1116,7 @@
  public:
   using DeltaPerformer::DeltaPerformer;
 
-  std::unique_ptr<PartitionWriter> CreatePartitionWriter(
+  std::unique_ptr<PartitionWriterInterface> CreatePartitionWriter(
       const PartitionUpdate& partition_update,
       const InstallPlan::Partition& install_part,
       DynamicPartitionControlInterface* dynamic_control,
diff --git a/payload_consumer/file_descriptor_utils.cc b/payload_consumer/file_descriptor_utils.cc
index 9a6a601..91b5673 100644
--- a/payload_consumer/file_descriptor_utils.cc
+++ b/payload_consumer/file_descriptor_utils.cc
@@ -35,9 +35,12 @@
 // Size of the buffer used to copy blocks.
 const uint64_t kMaxCopyBufferSize = 1024 * 1024;
 
+}  // namespace
+namespace fd_utils {
+
 bool CommonHashExtents(FileDescriptorPtr source,
                        const RepeatedPtrField<Extent>& src_extents,
-                       DirectExtentWriter* writer,
+                       ExtentWriter* writer,
                        uint64_t block_size,
                        brillo::Blob* hash_out) {
   auto total_blocks = utils::BlocksInExtents(src_extents);
@@ -72,10 +75,6 @@
   return true;
 }
 
-}  // namespace
-
-namespace fd_utils {
-
 bool CopyAndHashExtents(FileDescriptorPtr source,
                         const RepeatedPtrField<Extent>& src_extents,
                         FileDescriptorPtr target,
diff --git a/payload_consumer/file_descriptor_utils.h b/payload_consumer/file_descriptor_utils.h
index 68fb001..3a4027d 100644
--- a/payload_consumer/file_descriptor_utils.h
+++ b/payload_consumer/file_descriptor_utils.h
@@ -19,12 +19,20 @@
 
 #include <brillo/secure_blob.h>
 
+#include "update_engine/payload_consumer/extent_writer.h"
 #include "update_engine/payload_consumer/file_descriptor.h"
 #include "update_engine/update_metadata.pb.h"
 
 namespace chromeos_update_engine {
 namespace fd_utils {
 
+bool CommonHashExtents(
+    FileDescriptorPtr source,
+    const google::protobuf::RepeatedPtrField<Extent>& src_extents,
+    ExtentWriter* writer,
+    uint64_t block_size,
+    brillo::Blob* hash_out);
+
 // Copy blocks from the |source| file to the |target| file and hashes the
 // contents. The blocks to copy from the |source| to the |target| files are
 // specified by the |src_extents| and |tgt_extents| list of Extents, which
diff --git a/payload_consumer/install_operation_executor.cc b/payload_consumer/install_operation_executor.cc
new file mode 100644
index 0000000..4bfcb5d
--- /dev/null
+++ b/payload_consumer/install_operation_executor.cc
@@ -0,0 +1,316 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_operation_executor.h"
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <fcntl.h>
+#include <glob.h>
+#include <linux/fs.h>
+
+#include <base/files/memory_mapped_file.h>
+#include <bsdiff/bspatch.h>
+#include <puffin/puffpatch.h>
+#include <sys/mman.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/bzip_extent_writer.h"
+#include "update_engine/payload_consumer/cached_file_descriptor.h"
+#include "update_engine/payload_consumer/extent_reader.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_consumer/file_descriptor_utils.h"
+#include "update_engine/payload_consumer/xz_extent_writer.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class BsdiffExtentFile : public bsdiff::FileInterface {
+ public:
+  BsdiffExtentFile(std::unique_ptr<ExtentReader> reader, size_t size)
+      : BsdiffExtentFile(std::move(reader), nullptr, size) {}
+  BsdiffExtentFile(std::unique_ptr<ExtentWriter> writer, size_t size)
+      : BsdiffExtentFile(nullptr, std::move(writer), size) {}
+
+  ~BsdiffExtentFile() override = default;
+
+  bool Read(void* buf, size_t count, size_t* bytes_read) override {
+    TEST_AND_RETURN_FALSE(reader_->Read(buf, count));
+    *bytes_read = count;
+    offset_ += count;
+    return true;
+  }
+
+  bool Write(const void* buf, size_t count, size_t* bytes_written) override {
+    TEST_AND_RETURN_FALSE(writer_->Write(buf, count));
+    *bytes_written = count;
+    offset_ += count;
+    return true;
+  }
+
+  bool Seek(off_t pos) override {
+    if (reader_ != nullptr) {
+      TEST_AND_RETURN_FALSE(reader_->Seek(pos));
+      offset_ = pos;
+    } else {
+      // For writes technically there should be no change of position, or it
+      // should be equivalent of current offset.
+      TEST_AND_RETURN_FALSE(offset_ == static_cast<uint64_t>(pos));
+    }
+    return true;
+  }
+
+  bool Close() override { return true; }
+
+  bool GetSize(uint64_t* size) override {
+    *size = size_;
+    return true;
+  }
+
+ private:
+  BsdiffExtentFile(std::unique_ptr<ExtentReader> reader,
+                   std::unique_ptr<ExtentWriter> writer,
+                   size_t size)
+      : reader_(std::move(reader)),
+        writer_(std::move(writer)),
+        size_(size),
+        offset_(0) {}
+
+  std::unique_ptr<ExtentReader> reader_;
+  std::unique_ptr<ExtentWriter> writer_;
+  uint64_t size_;
+  uint64_t offset_;
+
+  DISALLOW_COPY_AND_ASSIGN(BsdiffExtentFile);
+};
+// A class to be passed to |puffpatch| for reading from |source_fd_| and writing
+// into |target_fd_|.
+class PuffinExtentStream : public puffin::StreamInterface {
+ public:
+  // Constructor for creating a stream for reading from an |ExtentReader|.
+  PuffinExtentStream(std::unique_ptr<ExtentReader> reader, uint64_t size)
+      : PuffinExtentStream(std::move(reader), nullptr, size) {}
+
+  // Constructor for creating a stream for writing to an |ExtentWriter|.
+  PuffinExtentStream(std::unique_ptr<ExtentWriter> writer, uint64_t size)
+      : PuffinExtentStream(nullptr, std::move(writer), size) {}
+
+  ~PuffinExtentStream() override = default;
+
+  bool GetSize(uint64_t* size) const override {
+    *size = size_;
+    return true;
+  }
+
+  bool GetOffset(uint64_t* offset) const override {
+    *offset = offset_;
+    return true;
+  }
+
+  bool Seek(uint64_t offset) override {
+    if (is_read_) {
+      TEST_AND_RETURN_FALSE(reader_->Seek(offset));
+      offset_ = offset;
+    } else {
+      // For writes technically there should be no change of position, or it
+      // should equivalent of current offset.
+      TEST_AND_RETURN_FALSE(offset_ == offset);
+    }
+    return true;
+  }
+
+  bool Read(void* buffer, size_t count) override {
+    TEST_AND_RETURN_FALSE(is_read_);
+    TEST_AND_RETURN_FALSE(reader_->Read(buffer, count));
+    offset_ += count;
+    return true;
+  }
+
+  bool Write(const void* buffer, size_t count) override {
+    TEST_AND_RETURN_FALSE(!is_read_);
+    TEST_AND_RETURN_FALSE(writer_->Write(buffer, count));
+    offset_ += count;
+    return true;
+  }
+
+  bool Close() override { return true; }
+
+ private:
+  PuffinExtentStream(std::unique_ptr<ExtentReader> reader,
+                     std::unique_ptr<ExtentWriter> writer,
+                     uint64_t size)
+      : reader_(std::move(reader)),
+        writer_(std::move(writer)),
+        size_(size),
+        offset_(0),
+        is_read_(reader_ ? true : false) {}
+
+  std::unique_ptr<ExtentReader> reader_;
+  std::unique_ptr<ExtentWriter> writer_;
+  uint64_t size_;
+  uint64_t offset_;
+  bool is_read_;
+
+  DISALLOW_COPY_AND_ASSIGN(PuffinExtentStream);
+};
+
+bool InstallOperationExecutor::ExecuteInstallOp(
+    const InstallOperation& op,
+    std::unique_ptr<ExtentWriter> writer,
+    FileDescriptorPtr source_fd,
+    const void* data,
+    size_t size) {
+  switch (op.type()) {
+    case InstallOperation::REPLACE:
+    case InstallOperation::REPLACE_BZ:
+    case InstallOperation::REPLACE_XZ:
+      return ExecuteReplaceOperation(op, std::move(writer), data, size);
+    case InstallOperation::ZERO:
+    case InstallOperation::DISCARD:
+      return ExecuteZeroOrDiscardOperation(op, writer.get());
+    case InstallOperation::SOURCE_COPY:
+      return ExecuteSourceCopyOperation(op, writer.get(), source_fd);
+    case InstallOperation::SOURCE_BSDIFF:
+    case InstallOperation::BROTLI_BSDIFF:
+      return ExecuteSourceBsdiffOperation(
+          op, std::move(writer), source_fd, data, size);
+    case InstallOperation::PUFFDIFF:
+      return ExecutePuffDiffOperation(
+          op, std::move(writer), source_fd, data, size);
+      break;
+    default:
+      return false;
+  }
+  return false;
+}
+
+bool InstallOperationExecutor::ExecuteReplaceOperation(
+    const InstallOperation& operation,
+    std::unique_ptr<ExtentWriter> writer,
+    const void* data,
+    size_t count) {
+  TEST_AND_RETURN_FALSE(operation.type() == InstallOperation::REPLACE ||
+                        operation.type() == InstallOperation::REPLACE_BZ ||
+                        operation.type() == InstallOperation::REPLACE_XZ);
+  // Setup the ExtentWriter stack based on the operation type.
+  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)));
+  }
+  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
+  TEST_AND_RETURN_FALSE(writer->Write(data, operation.data_length()));
+
+  return true;
+}
+
+bool InstallOperationExecutor::ExecuteZeroOrDiscardOperation(
+    const InstallOperation& operation, ExtentWriter* writer) {
+  TEST_AND_RETURN_FALSE(operation.type() == InstallOperation::ZERO ||
+                        operation.type() == InstallOperation::DISCARD);
+  using base::MemoryMappedFile;
+  using Access = base::MemoryMappedFile::Access;
+  using Region = base::MemoryMappedFile::Region;
+  writer->Init(operation.dst_extents(), block_size_);
+  for (const auto& extent : operation.dst_extents()) {
+    // Mmap a region of /dev/zero, as we don't need any actual memory to store
+    // these 0s, so mmap a region of "free memory".
+    base::File dev_zero(base::FilePath("/dev/zero"),
+                        base::File::FLAG_OPEN | base::File::FLAG_READ);
+    MemoryMappedFile buffer;
+    TEST_AND_RETURN_FALSE_ERRNO(buffer.Initialize(
+        std::move(dev_zero),
+        Region{0, static_cast<size_t>(extent.num_blocks() * block_size_)},
+        Access::READ_ONLY));
+    writer->Write(buffer.data(), buffer.length());
+  }
+  return true;
+}
+
+bool InstallOperationExecutor::ExecuteSourceCopyOperation(
+    const InstallOperation& operation,
+    ExtentWriter* writer,
+    FileDescriptorPtr source_fd) {
+  TEST_AND_RETURN_FALSE(operation.type() == InstallOperation::SOURCE_COPY);
+  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
+  return fd_utils::CommonHashExtents(
+      source_fd, operation.src_extents(), writer, block_size_, nullptr);
+}
+
+bool InstallOperationExecutor::ExecuteSourceBsdiffOperation(
+    const InstallOperation& operation,
+    std::unique_ptr<ExtentWriter> writer,
+    FileDescriptorPtr source_fd,
+    const void* data,
+    size_t count) {
+  TEST_AND_RETURN_FALSE(operation.type() == InstallOperation::SOURCE_BSDIFF ||
+                        operation.type() == InstallOperation::BROTLI_BSDIFF ||
+                        operation.type() == InstallOperation::BSDIFF);
+  TEST_AND_RETURN_FALSE(source_fd != nullptr);
+
+  auto reader = std::make_unique<DirectExtentReader>();
+  TEST_AND_RETURN_FALSE(
+      reader->Init(source_fd, operation.src_extents(), block_size_));
+  auto src_file = std::make_unique<BsdiffExtentFile>(
+      std::move(reader),
+      utils::BlocksInExtents(operation.src_extents()) * block_size_);
+
+  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
+  auto dst_file = std::make_unique<BsdiffExtentFile>(
+      std::move(writer),
+      utils::BlocksInExtents(operation.dst_extents()) * block_size_);
+
+  TEST_AND_RETURN_FALSE(bsdiff::bspatch(std::move(src_file),
+                                        std::move(dst_file),
+                                        reinterpret_cast<const uint8_t*>(data),
+                                        count) == 0);
+  return true;
+}
+
+bool InstallOperationExecutor::ExecutePuffDiffOperation(
+    const InstallOperation& operation,
+    std::unique_ptr<ExtentWriter> writer,
+    FileDescriptorPtr source_fd,
+    const void* data,
+    size_t count) {
+  TEST_AND_RETURN_FALSE(operation.type() == InstallOperation::PUFFDIFF);
+  TEST_AND_RETURN_FALSE(source_fd != nullptr);
+
+  auto reader = std::make_unique<DirectExtentReader>();
+  TEST_AND_RETURN_FALSE(
+      reader->Init(source_fd, operation.src_extents(), block_size_));
+  puffin::UniqueStreamPtr src_stream(new PuffinExtentStream(
+      std::move(reader),
+      utils::BlocksInExtents(operation.src_extents()) * block_size_));
+
+  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
+  puffin::UniqueStreamPtr dst_stream(new PuffinExtentStream(
+      std::move(writer),
+      utils::BlocksInExtents(operation.dst_extents()) * block_size_));
+
+  constexpr size_t kMaxCacheSize = 5 * 1024 * 1024;  // Total 5MB cache.
+  TEST_AND_RETURN_FALSE(
+      puffin::PuffPatch(std::move(src_stream),
+                        std::move(dst_stream),
+                        reinterpret_cast<const uint8_t*>(data),
+                        count,
+                        kMaxCacheSize));
+  return true;
+}
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/install_operation_executor.h b/payload_consumer/install_operation_executor.h
new file mode 100644
index 0000000..a43139b
--- /dev/null
+++ b/payload_consumer/install_operation_executor.h
@@ -0,0 +1,64 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_INSTALL_OPERATION_EXECUTOR_H
+#define UPDATE_ENGINE_INSTALL_OPERATION_EXECUTOR_H
+
+#include <memory>
+
+#include "update_engine/payload_consumer/extent_writer.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class InstallOperationExecutor {
+ public:
+  explicit InstallOperationExecutor(size_t block_size)
+      : block_size_(block_size) {}
+
+  bool ExecuteInstallOp(const InstallOperation& op,
+                        std::unique_ptr<ExtentWriter> writer,
+                        FileDescriptorPtr source_fd,
+                        const void* data,
+                        size_t size);
+  bool ExecuteReplaceOperation(const InstallOperation& operation,
+                               std::unique_ptr<ExtentWriter> writer,
+                               const void* data,
+                               size_t count);
+  bool ExecuteZeroOrDiscardOperation(const InstallOperation& operation,
+                                     ExtentWriter* writer);
+  bool ExecuteSourceCopyOperation(const InstallOperation& operation,
+                                  ExtentWriter* writer,
+                                  FileDescriptorPtr source_fd);
+  bool ExecuteSourceBsdiffOperation(const InstallOperation& operation,
+                                    std::unique_ptr<ExtentWriter> writer,
+                                    FileDescriptorPtr source_fd,
+                                    const void* data,
+                                    size_t count);
+  bool ExecutePuffDiffOperation(const InstallOperation& operation,
+                                std::unique_ptr<ExtentWriter> writer,
+                                FileDescriptorPtr source_fd,
+                                const void* data,
+                                size_t count);
+
+ private:
+  size_t block_size_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif
diff --git a/payload_consumer/install_operation_executor_unittest.cc b/payload_consumer/install_operation_executor_unittest.cc
new file mode 100644
index 0000000..2a99782
--- /dev/null
+++ b/payload_consumer/install_operation_executor_unittest.cc
@@ -0,0 +1,208 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_operation_executor.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+#include <update_engine/update_metadata.pb.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+#include "update_engine/payload_consumer/file_descriptor.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"
+
+namespace chromeos_update_engine {
+
+std::ostream& operator<<(std::ostream& out,
+                         const chromeos_update_engine::InstallOperation& op) {
+  out << InstallOperationTypeName(op.type())
+      << " SRC: " << ExtentsToString(op.src_extents())
+      << " DST: " << ExtentsToString(op.dst_extents());
+  return out;
+}
+
+namespace {}  // namespace
+
+class InstallOperationExecutorTest : public ::testing::Test {
+ public:
+  static constexpr size_t NUM_BLOCKS = 10;
+  static constexpr size_t BLOCK_SIZE = 4096;
+  void SetUp() override {
+    // Fill source partition with arbitrary data.
+    std::array<uint8_t, BLOCK_SIZE> buffer{};
+    for (size_t i = 0; i < NUM_BLOCKS; i++) {
+      // Fill block with arbitrary data. We don't care about what data is being
+      // written to source partition, so as long as each block is slightly
+      // different.
+      std::fill(buffer.begin(), buffer.end(), i);
+      ASSERT_TRUE(utils::WriteAll(source_.fd(), buffer.data(), buffer.size()))
+          << "Failed to write to source partition file: " << strerror(errno);
+      std::fill(buffer.begin(), buffer.end(), NUM_BLOCKS + i);
+      ASSERT_TRUE(utils::WriteAll(target_.fd(), buffer.data(), buffer.size()))
+          << "Failed to write to target partition file: " << strerror(errno);
+    }
+    fsync(source_.fd());
+    fsync(target_.fd());
+
+    // set target partition to have same size as source partition.
+    // update_engine mostly assumes that target partition have the desired
+    // size, so we mock that.
+    ASSERT_GE(ftruncate64(target_.fd(), NUM_BLOCKS * BLOCK_SIZE), 0)
+        << strerror(errno) << " failed to set target partition size to "
+        << NUM_BLOCKS * BLOCK_SIZE;
+
+    source_fd_->Open(source_.path().c_str(), O_RDONLY);
+    target_fd_->Open(target_.path().c_str(), O_RDWR);
+  }
+
+  void VerityUntouchedExtents(const InstallOperation& op) {
+    ExtentRanges extent_set;
+    extent_set.AddExtent(ExtentForRange(0, 10));
+    extent_set.SubtractRepeatedExtents(op.dst_extents());
+    std::vector<Extent> untouched_extents{extent_set.extent_set().begin(),
+                                          extent_set.extent_set().end()};
+    brillo::Blob actual_data;
+    ASSERT_TRUE(utils::ReadExtents(target_.path(),
+                                   untouched_extents,
+                                   &actual_data,
+                                   extent_set.blocks() * BLOCK_SIZE,
+                                   BLOCK_SIZE));
+    const auto untouched_blocks = ExpandExtents(untouched_extents);
+    for (size_t i = 0; i < actual_data.size(); i++) {
+      const auto block_offset = i / BLOCK_SIZE;
+      const auto offset = i % BLOCK_SIZE;
+      ASSERT_EQ(
+          actual_data[i],
+          static_cast<uint8_t>(NUM_BLOCKS + untouched_blocks[block_offset]))
+          << "After performing op " << op << ", offset " << offset
+          << " in block " << GetNthBlock(untouched_extents, block_offset)
+          << " is modified but it shouldn't.";
+    }
+  }
+  ScopedTempFile source_{"source_partition.XXXXXXXX", true};
+  ScopedTempFile target_{"target_partition.XXXXXXXX", true};
+  FileDescriptorPtr source_fd_ = std::make_shared<EintrSafeFileDescriptor>();
+  FileDescriptorPtr target_fd_ = std::make_shared<EintrSafeFileDescriptor>();
+  InstallOperationExecutor executor_{BLOCK_SIZE};
+};
+
+TEST_F(InstallOperationExecutorTest, ReplaceOpTest) {
+  InstallOperation op;
+  op.set_type(InstallOperation::REPLACE);
+  *op.mutable_dst_extents()->Add() = ExtentForRange(2, 2);
+  *op.mutable_dst_extents()->Add() = ExtentForRange(6, 2);
+  op.set_data_length(BLOCK_SIZE * 4);
+  brillo::Blob expected_data;
+  expected_data.resize(BLOCK_SIZE * 4);
+  // Fill buffer with arbitrary data. Doesn't matter what it is. Each block
+  // needs to be different so that we can ensure the InstallOperationExecutor
+  // is reading data from the correct offset.
+  for (int i = 0; i < 4; i++) {
+    std::fill(&expected_data[i * BLOCK_SIZE],
+              &expected_data[(i + 1) * BLOCK_SIZE],
+              i + 99);
+  }
+  auto writer = std::make_unique<DirectExtentWriter>(target_fd_);
+  ASSERT_TRUE(executor_.ExecuteReplaceOperation(
+      op, std::move(writer), expected_data.data(), expected_data.size()));
+
+  brillo::Blob actual_data;
+  utils::ReadExtents(
+      target_.path(),
+      std::vector<Extent>{op.dst_extents().begin(), op.dst_extents().end()},
+      &actual_data,
+      BLOCK_SIZE * 4,
+      BLOCK_SIZE);
+  ASSERT_EQ(actual_data, expected_data);
+  VerityUntouchedExtents(op);
+}
+
+TEST_F(InstallOperationExecutorTest, ZeroOrDiscardeOpTest) {
+  InstallOperation op;
+  op.set_type(InstallOperation::ZERO);
+  *op.mutable_dst_extents()->Add() = ExtentForRange(2, 2);
+  *op.mutable_dst_extents()->Add() = ExtentForRange(6, 2);
+  auto writer = std::make_unique<DirectExtentWriter>(target_fd_);
+  ASSERT_TRUE(executor_.ExecuteZeroOrDiscardOperation(op, writer.get()));
+  brillo::Blob actual_data;
+  utils::ReadExtents(
+      target_.path(),
+      std::vector<Extent>{op.dst_extents().begin(), op.dst_extents().end()},
+      &actual_data,
+      BLOCK_SIZE * 4,
+      BLOCK_SIZE);
+  for (size_t i = 0; i < actual_data.size(); i++) {
+    ASSERT_EQ(actual_data[i], 0U) << "position " << i << " isn't zeroed!";
+  }
+  VerityUntouchedExtents(op);
+}
+
+TEST_F(InstallOperationExecutorTest, SourceCopyOpTest) {
+  InstallOperation op;
+  op.set_type(InstallOperation::SOURCE_COPY);
+  *op.mutable_src_extents()->Add() = ExtentForRange(1, 2);
+  *op.mutable_src_extents()->Add() = ExtentForRange(5, 1);
+  *op.mutable_src_extents()->Add() = ExtentForRange(7, 1);
+
+  *op.mutable_dst_extents()->Add() = ExtentForRange(2, 2);
+  *op.mutable_dst_extents()->Add() = ExtentForRange(6, 2);
+
+  auto writer = std::make_unique<DirectExtentWriter>(target_fd_);
+  ASSERT_TRUE(
+      executor_.ExecuteSourceCopyOperation(op, writer.get(), source_fd_));
+  brillo::Blob actual_data;
+  utils::ReadExtents(
+      target_.path(),
+      std::vector<Extent>{op.dst_extents().begin(), op.dst_extents().end()},
+      &actual_data,
+      BLOCK_SIZE * 4,
+      BLOCK_SIZE);
+  brillo::Blob expected_data;
+  utils::ReadExtents(
+      source_.path(),
+      std::vector<Extent>{op.src_extents().begin(), op.src_extents().end()},
+      &expected_data,
+      BLOCK_SIZE * 4,
+      BLOCK_SIZE);
+
+  ASSERT_EQ(expected_data.size(), actual_data.size());
+  for (size_t i = 0; i < actual_data.size(); i++) {
+    const auto block_offset = i / BLOCK_SIZE;
+    const auto offset = i % BLOCK_SIZE;
+    ASSERT_EQ(actual_data[i], expected_data[i])
+        << "After performing op " << op << ", offset " << offset << " in  ["
+        << GetNthBlock(op.src_extents(), block_offset) << " -> "
+        << GetNthBlock(op.dst_extents(), block_offset) << "]"
+        << " is not copied correctly";
+  }
+  VerityUntouchedExtents(op);
+}
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/partition_writer.cc b/payload_consumer/partition_writer.cc
index 6f98ba3..7922eb2 100644
--- a/payload_consumer/partition_writer.cc
+++ b/payload_consumer/partition_writer.cc
@@ -17,18 +17,20 @@
 
 #include <fcntl.h>
 #include <linux/fs.h>
+#include <sys/mman.h>
+
+#include <inttypes.h>
 
 #include <algorithm>
 #include <initializer_list>
 #include <memory>
+#include <string>
 #include <utility>
 #include <vector>
 
 #include <base/strings/string_number_conversions.h>
-#include <bsdiff/bspatch.h>
-#include <puffin/puffpatch.h>
-#include <bsdiff/file_interface.h>
-#include <puffin/stream.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
 
 #include "update_engine/common/terminator.h"
 #include "update_engine/common/utils.h"
@@ -36,12 +38,13 @@
 #include "update_engine/payload_consumer/cached_file_descriptor.h"
 #include "update_engine/payload_consumer/extent_reader.h"
 #include "update_engine/payload_consumer/extent_writer.h"
-#include "update_engine/payload_consumer/fec_file_descriptor.h"
 #include "update_engine/payload_consumer/file_descriptor_utils.h"
+#include "update_engine/payload_consumer/install_operation_executor.h"
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/payload_consumer/mount_history.h"
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_consumer/xz_extent_writer.h"
+#include "update_engine/payload_generator/extent_utils.h"
 
 namespace chromeos_update_engine {
 
@@ -83,6 +86,8 @@
 
 }  // namespace
 
+using google::protobuf::RepeatedPtrField;
+
 // 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,
@@ -108,135 +113,6 @@
   return fd;
 }
 
-class BsdiffExtentFile : public bsdiff::FileInterface {
- public:
-  BsdiffExtentFile(std::unique_ptr<ExtentReader> reader, size_t size)
-      : BsdiffExtentFile(std::move(reader), nullptr, size) {}
-  BsdiffExtentFile(std::unique_ptr<ExtentWriter> writer, size_t size)
-      : BsdiffExtentFile(nullptr, std::move(writer), size) {}
-
-  ~BsdiffExtentFile() override = default;
-
-  bool Read(void* buf, size_t count, size_t* bytes_read) override {
-    TEST_AND_RETURN_FALSE(reader_->Read(buf, count));
-    *bytes_read = count;
-    offset_ += count;
-    return true;
-  }
-
-  bool Write(const void* buf, size_t count, size_t* bytes_written) override {
-    TEST_AND_RETURN_FALSE(writer_->Write(buf, count));
-    *bytes_written = count;
-    offset_ += count;
-    return true;
-  }
-
-  bool Seek(off_t pos) override {
-    if (reader_ != nullptr) {
-      TEST_AND_RETURN_FALSE(reader_->Seek(pos));
-      offset_ = pos;
-    } else {
-      // For writes technically there should be no change of position, or it
-      // should be equivalent of current offset.
-      TEST_AND_RETURN_FALSE(offset_ == static_cast<uint64_t>(pos));
-    }
-    return true;
-  }
-
-  bool Close() override { return true; }
-
-  bool GetSize(uint64_t* size) override {
-    *size = size_;
-    return true;
-  }
-
- private:
-  BsdiffExtentFile(std::unique_ptr<ExtentReader> reader,
-                   std::unique_ptr<ExtentWriter> writer,
-                   size_t size)
-      : reader_(std::move(reader)),
-        writer_(std::move(writer)),
-        size_(size),
-        offset_(0) {}
-
-  std::unique_ptr<ExtentReader> reader_;
-  std::unique_ptr<ExtentWriter> writer_;
-  uint64_t size_;
-  uint64_t offset_;
-
-  DISALLOW_COPY_AND_ASSIGN(BsdiffExtentFile);
-};
-// A class to be passed to |puffpatch| for reading from |source_fd_| and writing
-// into |target_fd_|.
-class PuffinExtentStream : public puffin::StreamInterface {
- public:
-  // Constructor for creating a stream for reading from an |ExtentReader|.
-  PuffinExtentStream(std::unique_ptr<ExtentReader> reader, uint64_t size)
-      : PuffinExtentStream(std::move(reader), nullptr, size) {}
-
-  // Constructor for creating a stream for writing to an |ExtentWriter|.
-  PuffinExtentStream(std::unique_ptr<ExtentWriter> writer, uint64_t size)
-      : PuffinExtentStream(nullptr, std::move(writer), size) {}
-
-  ~PuffinExtentStream() override = default;
-
-  bool GetSize(uint64_t* size) const override {
-    *size = size_;
-    return true;
-  }
-
-  bool GetOffset(uint64_t* offset) const override {
-    *offset = offset_;
-    return true;
-  }
-
-  bool Seek(uint64_t offset) override {
-    if (is_read_) {
-      TEST_AND_RETURN_FALSE(reader_->Seek(offset));
-      offset_ = offset;
-    } else {
-      // For writes technically there should be no change of position, or it
-      // should equivalent of current offset.
-      TEST_AND_RETURN_FALSE(offset_ == offset);
-    }
-    return true;
-  }
-
-  bool Read(void* buffer, size_t count) override {
-    TEST_AND_RETURN_FALSE(is_read_);
-    TEST_AND_RETURN_FALSE(reader_->Read(buffer, count));
-    offset_ += count;
-    return true;
-  }
-
-  bool Write(const void* buffer, size_t count) override {
-    TEST_AND_RETURN_FALSE(!is_read_);
-    TEST_AND_RETURN_FALSE(writer_->Write(buffer, count));
-    offset_ += count;
-    return true;
-  }
-
-  bool Close() override { return true; }
-
- private:
-  PuffinExtentStream(std::unique_ptr<ExtentReader> reader,
-                     std::unique_ptr<ExtentWriter> writer,
-                     uint64_t size)
-      : reader_(std::move(reader)),
-        writer_(std::move(writer)),
-        size_(size),
-        offset_(0),
-        is_read_(reader_ ? true : false) {}
-
-  std::unique_ptr<ExtentReader> reader_;
-  std::unique_ptr<ExtentWriter> writer_;
-  uint64_t size_;
-  uint64_t offset_;
-  bool is_read_;
-
-  DISALLOW_COPY_AND_ASSIGN(PuffinExtentStream);
-};
-
 PartitionWriter::PartitionWriter(
     const PartitionUpdate& partition_update,
     const InstallPlan::Partition& install_part,
@@ -246,8 +122,10 @@
     : partition_update_(partition_update),
       install_part_(install_part),
       dynamic_control_(dynamic_control),
+      verified_source_fd_(block_size, install_part.source_path),
       interactive_(is_interactive),
-      block_size_(block_size) {}
+      block_size_(block_size),
+      install_op_executor_(block_size) {}
 
 PartitionWriter::~PartitionWriter() {
   Close();
@@ -261,9 +139,7 @@
   }
   if (install_part_.source_size > 0 && !install_part_.source_path.empty()) {
     source_path_ = install_part_.source_path;
-    int err;
-    source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, false, &err);
-    if (source_fd_ == nullptr) {
+    if (!verified_source_fd_.Open()) {
       LOG(ERROR) << "Unable to open source partition " << install_part_.name
                  << " on slot " << BootControlInterface::SlotName(source_slot)
                  << ", file " << source_path_;
@@ -319,161 +195,67 @@
                                               size_t count) {
   // Setup the ExtentWriter stack based on the operation type.
   std::unique_ptr<ExtentWriter> writer = CreateBaseExtentWriter();
-
-  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)));
-  }
-
-  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
-  TEST_AND_RETURN_FALSE(writer->Write(data, operation.data_length()));
-
-  return true;
+  writer->Init(operation.dst_extents(), block_size_);
+  return install_op_executor_.ExecuteReplaceOperation(
+      operation, std::move(writer), data, count);
 }
 
 bool PartitionWriter::PerformZeroOrDiscardOperation(
     const InstallOperation& operation) {
 #ifdef BLKZEROOUT
-  bool attempt_ioctl = true;
   int request =
       (operation.type() == InstallOperation::ZERO ? BLKZEROOUT : BLKDISCARD);
 #else   // !defined(BLKZEROOUT)
-  bool attempt_ioctl = false;
-  int request = 0;
+  auto writer = CreateBaseExtentWriter();
+  return install_op_executor_.ExecuteZeroOrDiscardOperation(operation,
+                                                            writer.get());
 #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;
+    int result = 0;
+    if (target_fd_->BlkIoctl(request, start, length, &result) && result == 0) {
+      continue;
     }
-    // In case of failure, we fall back to writing 0 to the selected region.
-    zeros.resize(16 * block_size_);
-    for (uint64_t offset = 0; offset < length; offset += zeros.size()) {
-      uint64_t chunk_length =
-          std::min(length - offset, static_cast<uint64_t>(zeros.size()));
-      TEST_AND_RETURN_FALSE(utils::WriteAll(
-          target_fd_, zeros.data(), chunk_length, start + offset));
-    }
+    // In case of failure, we fall back to writing 0 for the entire operation.
+    PLOG(WARNING) << "BlkIoctl failed. Falling back to write 0s for remainder "
+                     "of this operation.";
+    auto writer = CreateBaseExtentWriter();
+    writer->Init(operation.dst_extents(), block_size_);
+    return install_op_executor_.ExecuteZeroOrDiscardOperation(operation,
+                                                              writer.get());
   }
   return true;
 }
 
 bool PartitionWriter::PerformSourceCopyOperation(
     const InstallOperation& operation, ErrorCode* error) {
-  TEST_AND_RETURN_FALSE(source_fd_ != nullptr);
-
   // The device may optimize the SOURCE_COPY operation.
   // Being this a device-specific optimization let DynamicPartitionController
   // decide it the operation should be skipped.
   const PartitionUpdate& partition = partition_update_;
-  const auto& partition_control = dynamic_control_;
 
   InstallOperation buf;
-  const bool should_optimize = partition_control->OptimizeOperation(
+  const bool should_optimize = dynamic_control_->OptimizeOperation(
       partition.partition_name(), operation, &buf);
   const InstallOperation& optimized = should_optimize ? buf : operation;
 
-  if (operation.has_src_sha256_hash()) {
-    bool read_ok;
-    brillo::Blob source_hash;
-    brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
-                                      operation.src_sha256_hash().end());
-
-    // We fall back to use the error corrected device if the hash of the raw
-    // device doesn't match or there was an error reading the source partition.
-    // Note that this code will also fall back if writing the target partition
-    // fails.
-    if (should_optimize) {
-      // Hash operation.src_extents(), then copy optimized.src_extents to
-      // optimized.dst_extents.
-      read_ok =
-          fd_utils::ReadAndHashExtents(
-              source_fd_, operation.src_extents(), block_size_, &source_hash) &&
-          fd_utils::CopyAndHashExtents(source_fd_,
-                                       optimized.src_extents(),
-                                       target_fd_,
-                                       optimized.dst_extents(),
-                                       block_size_,
-                                       nullptr /* skip hashing */);
-    } else {
-      read_ok = fd_utils::CopyAndHashExtents(source_fd_,
-                                             operation.src_extents(),
-                                             target_fd_,
-                                             operation.dst_extents(),
-                                             block_size_,
-                                             &source_hash);
-    }
-    if (read_ok && expected_source_hash == source_hash)
-      return true;
-    LOG(WARNING) << "Source hash from RAW device mismatched, attempting to "
-                    "correct using ECC";
-    if (!OpenCurrentECCPartition()) {
-      // The following function call will return false since the source hash
-      // mismatches, but we still want to call it so it prints the appropriate
-      // log message.
-      return ValidateSourceHash(source_hash, operation, source_fd_, error);
-    }
-
-    LOG(WARNING) << "Source hash from RAW device mismatched: found "
-                 << base::HexEncode(source_hash.data(), source_hash.size())
-                 << ", expected "
-                 << base::HexEncode(expected_source_hash.data(),
-                                    expected_source_hash.size());
-    if (should_optimize) {
-      TEST_AND_RETURN_FALSE(fd_utils::ReadAndHashExtents(
-          source_ecc_fd_, operation.src_extents(), block_size_, &source_hash));
-      TEST_AND_RETURN_FALSE(
-          fd_utils::CopyAndHashExtents(source_ecc_fd_,
-                                       optimized.src_extents(),
-                                       target_fd_,
-                                       optimized.dst_extents(),
-                                       block_size_,
-                                       nullptr /* skip hashing */));
-    } else {
-      TEST_AND_RETURN_FALSE(
-          fd_utils::CopyAndHashExtents(source_ecc_fd_,
-                                       operation.src_extents(),
-                                       target_fd_,
-                                       operation.dst_extents(),
-                                       block_size_,
-                                       &source_hash));
-    }
-    TEST_AND_RETURN_FALSE(
-        ValidateSourceHash(source_hash, operation, source_ecc_fd_, error));
-    // At this point reading from the error corrected device worked, but
-    // reading from the raw device failed, so this is considered a recovered
-    // failure.
-    source_ecc_recovered_failures_++;
-  } else {
-    // When the operation doesn't include a source hash, we attempt the error
-    // corrected device first since we can't verify the block in the raw device
-    // at this point, but we fall back to the raw device since the error
-    // corrected device can be shorter or not available.
-
-    if (OpenCurrentECCPartition() &&
-        fd_utils::CopyAndHashExtents(source_ecc_fd_,
-                                     optimized.src_extents(),
-                                     target_fd_,
-                                     optimized.dst_extents(),
-                                     block_size_,
-                                     nullptr)) {
-      return true;
-    }
-    TEST_AND_RETURN_FALSE(fd_utils::CopyAndHashExtents(source_fd_,
-                                                       optimized.src_extents(),
-                                                       target_fd_,
-                                                       optimized.dst_extents(),
-                                                       block_size_,
-                                                       nullptr));
+  // Invoke ChooseSourceFD with original operation, so that it can properly
+  // verify source hashes. Optimized operation might contain a smaller set of
+  // extents, or completely empty.
+  auto source_fd = ChooseSourceFD(operation, error);
+  if (source_fd == nullptr) {
+    LOG(ERROR) << "Unrecoverable source hash mismatch found on partition "
+               << partition.partition_name()
+               << " extents: " << ExtentsToString(operation.src_extents());
+    return false;
   }
-  return true;
+
+  auto writer = CreateBaseExtentWriter();
+  writer->Init(optimized.dst_extents(), block_size_);
+  return install_op_executor_.ExecuteSourceCopyOperation(
+      optimized, writer.get(), source_fd);
 }
 
 bool PartitionWriter::PerformSourceBsdiffOperation(
@@ -484,24 +266,10 @@
   FileDescriptorPtr source_fd = ChooseSourceFD(operation, error);
   TEST_AND_RETURN_FALSE(source_fd != nullptr);
 
-  auto reader = std::make_unique<DirectExtentReader>();
-  TEST_AND_RETURN_FALSE(
-      reader->Init(source_fd, operation.src_extents(), block_size_));
-  auto src_file = std::make_unique<BsdiffExtentFile>(
-      std::move(reader),
-      utils::BlocksInExtents(operation.src_extents()) * block_size_);
-
   auto writer = CreateBaseExtentWriter();
-  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
-  auto dst_file = std::make_unique<BsdiffExtentFile>(
-      std::move(writer),
-      utils::BlocksInExtents(operation.dst_extents()) * block_size_);
-
-  TEST_AND_RETURN_FALSE(bsdiff::bspatch(std::move(src_file),
-                                        std::move(dst_file),
-                                        reinterpret_cast<const uint8_t*>(data),
-                                        count) == 0);
-  return true;
+  writer->Init(operation.dst_extents(), block_size_);
+  return install_op_executor_.ExecuteSourceBsdiffOperation(
+      operation, std::move(writer), source_fd, data, count);
 }
 
 bool PartitionWriter::PerformPuffDiffOperation(
@@ -512,122 +280,20 @@
   FileDescriptorPtr source_fd = ChooseSourceFD(operation, error);
   TEST_AND_RETURN_FALSE(source_fd != nullptr);
 
-  auto reader = std::make_unique<DirectExtentReader>();
-  TEST_AND_RETURN_FALSE(
-      reader->Init(source_fd, operation.src_extents(), block_size_));
-  puffin::UniqueStreamPtr src_stream(new PuffinExtentStream(
-      std::move(reader),
-      utils::BlocksInExtents(operation.src_extents()) * block_size_));
-
   auto writer = CreateBaseExtentWriter();
-  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
-  puffin::UniqueStreamPtr dst_stream(new PuffinExtentStream(
-      std::move(writer),
-      utils::BlocksInExtents(operation.dst_extents()) * block_size_));
-
-  constexpr size_t kMaxCacheSize = 5 * 1024 * 1024;  // Total 5MB cache.
-  TEST_AND_RETURN_FALSE(
-      puffin::PuffPatch(std::move(src_stream),
-                        std::move(dst_stream),
-                        reinterpret_cast<const uint8_t*>(data),
-                        count,
-                        kMaxCacheSize));
-  return true;
+  writer->Init(operation.dst_extents(), block_size_);
+  return install_op_executor_.ExecutePuffDiffOperation(
+      operation, std::move(writer), source_fd, data, count);
 }
 
 FileDescriptorPtr PartitionWriter::ChooseSourceFD(
     const InstallOperation& operation, ErrorCode* error) {
-  if (source_fd_ == nullptr) {
-    LOG(ERROR) << "ChooseSourceFD fail: source_fd_ == nullptr";
-    return nullptr;
-  }
-
-  if (!operation.has_src_sha256_hash()) {
-    // When the operation doesn't include a source hash, we attempt the error
-    // corrected device first since we can't verify the block in the raw device
-    // at this point, but we first need to make sure all extents are readable
-    // since the error corrected device can be shorter or not available.
-    if (OpenCurrentECCPartition() &&
-        fd_utils::ReadAndHashExtents(
-            source_ecc_fd_, operation.src_extents(), block_size_, nullptr)) {
-      return source_ecc_fd_;
-    }
-    return source_fd_;
-  }
-
-  brillo::Blob source_hash;
-  brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
-                                    operation.src_sha256_hash().end());
-  if (fd_utils::ReadAndHashExtents(
-          source_fd_, operation.src_extents(), block_size_, &source_hash) &&
-      source_hash == expected_source_hash) {
-    return source_fd_;
-  }
-  // We fall back to use the error corrected device if the hash of the raw
-  // device doesn't match or there was an error reading the source partition.
-  if (!OpenCurrentECCPartition()) {
-    // The following function call will return false since the source hash
-    // mismatches, but we still want to call it so it prints the appropriate
-    // log message.
-    ValidateSourceHash(source_hash, operation, source_fd_, error);
-    return nullptr;
-  }
-  LOG(WARNING) << "Source hash from RAW device mismatched: found "
-               << base::HexEncode(source_hash.data(), source_hash.size())
-               << ", expected "
-               << base::HexEncode(expected_source_hash.data(),
-                                  expected_source_hash.size());
-
-  if (fd_utils::ReadAndHashExtents(
-          source_ecc_fd_, operation.src_extents(), block_size_, &source_hash) &&
-      ValidateSourceHash(source_hash, operation, source_ecc_fd_, error)) {
-    // At this point reading from the error corrected device worked, but
-    // reading from the raw device failed, so this is considered a recovered
-    // failure.
-    source_ecc_recovered_failures_++;
-    return source_ecc_fd_;
-  }
-  return nullptr;
-}
-
-bool PartitionWriter::OpenCurrentECCPartition() {
-  // No support for ECC for full payloads.
-  // Full payload should not have any opeartion that requires ECC partitions.
-  if (source_ecc_fd_)
-    return true;
-
-  if (source_ecc_open_failure_)
-    return false;
-
-#if USE_FEC
-  const PartitionUpdate& partition = partition_update_;
-  const InstallPlan::Partition& install_part = install_part_;
-  std::string path = install_part.source_path;
-  FileDescriptorPtr fd(new FecFileDescriptor());
-  if (!fd->Open(path.c_str(), O_RDONLY, 0)) {
-    PLOG(ERROR) << "Unable to open ECC source partition "
-                << partition.partition_name() << ", file " << path;
-    source_ecc_open_failure_ = true;
-    return false;
-  }
-  source_ecc_fd_ = fd;
-#else
-  // No support for ECC compiled.
-  source_ecc_open_failure_ = true;
-#endif  // USE_FEC
-
-  return !source_ecc_open_failure_;
+  return verified_source_fd_.ChooseSourceFD(operation, error);
 }
 
 int PartitionWriter::Close() {
   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()) {
@@ -639,14 +305,6 @@
   target_fd_.reset();
   target_path_.clear();
 
-  if (source_ecc_fd_ && !source_ecc_fd_->Close()) {
-    err = errno;
-    PLOG(ERROR) << "Error closing ECC source partition";
-    if (!err)
-      err = 1;
-  }
-  source_ecc_fd_.reset();
-  source_ecc_open_failure_ = false;
   return -err;
 }
 
@@ -658,4 +316,44 @@
   return std::make_unique<DirectExtentWriter>(target_fd_);
 }
 
+bool PartitionWriter::ValidateSourceHash(const brillo::Blob& calculated_hash,
+                                         const InstallOperation& operation,
+                                         const FileDescriptorPtr source_fd,
+                                         ErrorCode* error) {
+  using std::string;
+  using std::vector;
+  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, ",");
+
+    // Log remount history if this device is an ext4 partition.
+    LogMountHistory(source_fd);
+
+    *error = ErrorCode::kDownloadStateInitializationError;
+    return false;
+  }
+  return true;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/payload_consumer/partition_writer.h b/payload_consumer/partition_writer.h
index 82e557a..e11d987 100644
--- a/payload_consumer/partition_writer.h
+++ b/payload_consumer/partition_writer.h
@@ -27,18 +27,21 @@
 #include "update_engine/common/dynamic_partition_control_interface.h"
 #include "update_engine/payload_consumer/extent_writer.h"
 #include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_consumer/install_operation_executor.h"
 #include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/payload_consumer/partition_writer_interface.h"
+#include "update_engine/payload_consumer/verified_source_fd.h"
 #include "update_engine/update_metadata.pb.h"
 
 namespace chromeos_update_engine {
-class PartitionWriter {
+class PartitionWriter : public PartitionWriterInterface {
  public:
   PartitionWriter(const PartitionUpdate& partition_update,
                   const InstallPlan::Partition& install_part,
                   DynamicPartitionControlInterface* dynamic_control,
                   size_t block_size,
                   bool is_interactive);
-  virtual ~PartitionWriter();
+  ~PartitionWriter();
   static bool ValidateSourceHash(const brillo::Blob& calculated_hash,
                                  const InstallOperation& operation,
                                  const FileDescriptorPtr source_fd,
@@ -46,95 +49,81 @@
 
   // Perform necessary initialization work before InstallOperation can be
   // applied to this partition
-  [[nodiscard]] virtual bool Init(const InstallPlan* install_plan,
-                                  bool source_may_exist,
-                                  size_t next_op_index);
+  [[nodiscard]] bool Init(const InstallPlan* install_plan,
+                          bool source_may_exist,
+                          size_t next_op_index) override;
 
   // |CheckpointUpdateProgress| will be called after SetNextOpIndex(), but it's
   // optional. DeltaPerformer may or may not call this everytime an operation is
   // applied.
   //   |next_op_index| is index of next operation that should be applied.
   // |next_op_index-1| is the last operation that is already applied.
-  virtual void CheckpointUpdateProgress(size_t next_op_index);
+  void CheckpointUpdateProgress(size_t next_op_index) override;
 
   // Close partition writer, when calling this function there's no guarantee
   // that all |InstallOperations| are sent to |PartitionWriter|. This function
   // will be called even if we are pausing/aborting the update.
-  int Close();
+  int Close() override;
 
   // 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.
-  [[nodiscard]] virtual bool PerformReplaceOperation(
-      const InstallOperation& operation, const void* data, size_t count);
-  [[nodiscard]] virtual bool PerformZeroOrDiscardOperation(
-      const InstallOperation& operation);
+  [[nodiscard]] bool PerformReplaceOperation(const InstallOperation& operation,
+                                             const void* data,
+                                             size_t count) override;
+  [[nodiscard]] bool PerformZeroOrDiscardOperation(
+      const InstallOperation& operation) override;
 
-  [[nodiscard]] virtual bool PerformSourceCopyOperation(
-      const InstallOperation& operation, ErrorCode* error);
-  [[nodiscard]] virtual bool PerformSourceBsdiffOperation(
+  [[nodiscard]] bool PerformSourceCopyOperation(
+      const InstallOperation& operation, ErrorCode* error) override;
+  [[nodiscard]] bool PerformSourceBsdiffOperation(
       const InstallOperation& operation,
       ErrorCode* error,
       const void* data,
-      size_t count);
-  [[nodiscard]] virtual bool PerformPuffDiffOperation(
-      const InstallOperation& operation,
-      ErrorCode* error,
-      const void* data,
-      size_t count);
+      size_t count) override;
+  [[nodiscard]] bool PerformPuffDiffOperation(const InstallOperation& operation,
+                                              ErrorCode* error,
+                                              const void* data,
+                                              size_t count) override;
 
   // |DeltaPerformer| calls this when all Install Ops are sent to partition
   // writer. No |Perform*Operation| methods will be called in the future, and
   // the partition writer is expected to be closed soon.
-  [[nodiscard]] virtual bool FinishedInstallOps() { return true; }
+  [[nodiscard]] bool FinishedInstallOps() override { return true; }
 
- protected:
+ private:
   friend class PartitionWriterTest;
   FRIEND_TEST(PartitionWriterTest, ChooseSourceFDTest);
 
-  bool OpenSourcePartition(uint32_t source_slot, bool source_may_exist);
-
-  bool OpenCurrentECCPartition();
-  // For a given operation, choose the source fd to be used (raw device or error
-  // correction device) based on the source operation hash.
-  // Returns nullptr if the source hash mismatch cannot be corrected, and set
-  // the |error| accordingly.
-  FileDescriptorPtr ChooseSourceFD(const InstallOperation& operation,
+  [[nodiscard]] bool OpenSourcePartition(uint32_t source_slot,
+                                         bool source_may_exist);
+  FileDescriptorPtr ChooseSourceFD(const InstallOperation& op,
                                    ErrorCode* error);
-  [[nodiscard]] virtual std::unique_ptr<ExtentWriter> CreateBaseExtentWriter();
+
+  [[nodiscard]] std::unique_ptr<ExtentWriter> CreateBaseExtentWriter();
 
   const PartitionUpdate& partition_update_;
   const InstallPlan::Partition& install_part_;
   DynamicPartitionControlInterface* dynamic_control_;
   // Path to source partition
   std::string source_path_;
+  VerifiedSourceFd verified_source_fd_;
   // Path to target partition
   std::string target_path_;
-  FileDescriptorPtr source_fd_;
   FileDescriptorPtr target_fd_;
   const bool interactive_;
   const size_t block_size_;
-  // File descriptor of the error corrected source partition. Only set while
-  // updating partition using a delta payload for a partition where error
-  // correction is available. The size of the error corrected device is smaller
-  // than the underlying raw device, since it doesn't include the error
-  // correction blocks.
-  FileDescriptorPtr source_ecc_fd_{nullptr};
 
-  // The total number of operations that failed source hash verification but
-  // passed after falling back to the error-corrected |source_ecc_fd_| device.
-  uint64_t source_ecc_recovered_failures_{0};
-
-  // Whether opening the current partition as an error-corrected device failed.
-  // Used to avoid re-opening the same source partition if it is not actually
-  // error corrected.
-  bool source_ecc_open_failure_{false};
+  // This instance handles decompression/bsdfif/puffdiff. It's responsible for
+  // constructing data which should be written to target partition, actual
+  // "writing" is handled by |PartitionWriter|
+  InstallOperationExecutor install_op_executor_;
 };
 
 namespace partition_writer {
 // Return a PartitionWriter instance for perform InstallOps on this partition.
 // Uses VABCPartitionWriter for Virtual AB Compression
-std::unique_ptr<PartitionWriter> CreatePartitionWriter(
+std::unique_ptr<PartitionWriterInterface> CreatePartitionWriter(
     const PartitionUpdate& partition_update,
     const InstallPlan::Partition& install_part,
     DynamicPartitionControlInterface* dynamic_control,
diff --git a/payload_consumer/partition_writer_factory_android.cc b/payload_consumer/partition_writer_factory_android.cc
index 184e2d5..0a9a3fb 100644
--- a/payload_consumer/partition_writer_factory_android.cc
+++ b/payload_consumer/partition_writer_factory_android.cc
@@ -23,7 +23,7 @@
 
 namespace chromeos_update_engine::partition_writer {
 
-std::unique_ptr<PartitionWriter> CreatePartitionWriter(
+std::unique_ptr<PartitionWriterInterface> CreatePartitionWriter(
     const PartitionUpdate& partition_update,
     const InstallPlan::Partition& install_part,
     DynamicPartitionControlInterface* dynamic_control,
diff --git a/payload_consumer/partition_writer_factory_chromeos.cc b/payload_consumer/partition_writer_factory_chromeos.cc
index 609f043..0f7a11c 100644
--- a/payload_consumer/partition_writer_factory_chromeos.cc
+++ b/payload_consumer/partition_writer_factory_chromeos.cc
@@ -22,7 +22,7 @@
 #include "update_engine/payload_consumer/partition_writer.h"
 
 namespace chromeos_update_engine::partition_writer {
-std::unique_ptr<PartitionWriter> CreatePartitionWriter(
+std::unique_ptr<PartitionWriterInterface> CreatePartitionWriter(
     const PartitionUpdate& partition_update,
     const InstallPlan::Partition& install_part,
     DynamicPartitionControlInterface* dynamic_control,
diff --git a/payload_consumer/partition_writer_interface.h b/payload_consumer/partition_writer_interface.h
new file mode 100644
index 0000000..f8d6b9c
--- /dev/null
+++ b/payload_consumer/partition_writer_interface.h
@@ -0,0 +1,83 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_PARTITION_WRITER_INTERFACE_H_
+#define UPDATE_ENGINE_PARTITION_WRITER_INTERFACE_H_
+
+#include <cstdint>
+#include <string>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest_prod.h>
+
+#include "update_engine/common/dynamic_partition_control_interface.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+class PartitionWriterInterface {
+ public:
+  virtual ~PartitionWriterInterface() = default;
+
+  // Perform necessary initialization work before InstallOperation can be
+  // applied to this partition
+  [[nodiscard]] virtual bool Init(const InstallPlan* install_plan,
+                                  bool source_may_exist,
+                                  size_t next_op_index) = 0;
+
+  // |CheckpointUpdateProgress| will be called after SetNextOpIndex(), but it's
+  // optional. DeltaPerformer may or may not call this everytime an operation is
+  // applied.
+  //   |next_op_index| is index of next operation that should be applied.
+  // |next_op_index-1| is the last operation that is already applied.
+  virtual void CheckpointUpdateProgress(size_t next_op_index) = 0;
+
+  // Close partition writer, when calling this function there's no guarantee
+  // that all |InstallOperations| are sent to |PartitionWriter|. This function
+  // will be called even if we are pausing/aborting the update.
+  virtual int Close() = 0;
+
+  // 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.
+  [[nodiscard]] virtual bool PerformReplaceOperation(
+      const InstallOperation& operation, const void* data, size_t count) = 0;
+  [[nodiscard]] virtual bool PerformZeroOrDiscardOperation(
+      const InstallOperation& operation) = 0;
+
+  [[nodiscard]] virtual bool PerformSourceCopyOperation(
+      const InstallOperation& operation, ErrorCode* error) = 0;
+  [[nodiscard]] virtual bool PerformSourceBsdiffOperation(
+      const InstallOperation& operation,
+      ErrorCode* error,
+      const void* data,
+      size_t count) = 0;
+  [[nodiscard]] virtual bool PerformPuffDiffOperation(
+      const InstallOperation& operation,
+      ErrorCode* error,
+      const void* data,
+      size_t count) = 0;
+
+  // |DeltaPerformer| calls this when all Install Ops are sent to partition
+  // writer. No |Perform*Operation| methods will be called in the future, and
+  // the partition writer is expected to be closed soon.
+  [[nodiscard]] virtual bool FinishedInstallOps() = 0;
+};
+}  // namespace chromeos_update_engine
+
+#endif
diff --git a/payload_consumer/partition_writer_unittest.cc b/payload_consumer/partition_writer_unittest.cc
index 263f338..331a061 100644
--- a/payload_consumer/partition_writer_unittest.cc
+++ b/payload_consumer/partition_writer_unittest.cc
@@ -46,18 +46,19 @@
   // Helper function to pretend that the ECC file descriptor was already opened.
   // Returns a pointer to the created file descriptor.
   FakeFileDescriptor* SetFakeECCFile(size_t size) {
-    EXPECT_FALSE(writer_.source_ecc_fd_) << "source_ecc_fd_ already open.";
+    EXPECT_FALSE(writer_.verified_source_fd_.source_ecc_fd_)
+        << "source_ecc_fdb already open.";
     FakeFileDescriptor* ret = new FakeFileDescriptor();
     fake_ecc_fd_.reset(ret);
     // Call open to simulate it was already opened.
     ret->Open("", 0);
     ret->SetFileSize(size);
-    writer_.source_ecc_fd_ = fake_ecc_fd_;
+    writer_.verified_source_fd_.source_ecc_fd_ = fake_ecc_fd_;
     return ret;
   }
 
   uint64_t GetSourceEccRecoveredFailures() const {
-    return writer_.source_ecc_recovered_failures_;
+    return writer_.verified_source_fd_.source_ecc_recovered_failures_;
   }
 
   AnnotatedOperation GenerateSourceCopyOp(const brillo::Blob& copied_data,
@@ -81,22 +82,31 @@
 
   brillo::Blob PerformSourceCopyOp(const InstallOperation& op,
                                    const brillo::Blob blob_data) {
-    ScopedTempFile source_partition("Blob-XXXXXX");
+    LOG(INFO) << "Using source part " << source_partition.path();
     FileDescriptorPtr fd(new EintrSafeFileDescriptor());
     DirectExtentWriter extent_writer{fd};
     EXPECT_TRUE(fd->Open(source_partition.path().c_str(), O_RDWR));
+    if (HasFailure()) {
+      return {};
+    }
     EXPECT_TRUE(extent_writer.Init(op.src_extents(), kBlockSize));
+    if (HasFailure()) {
+      return {};
+    }
     EXPECT_TRUE(extent_writer.Write(blob_data.data(), blob_data.size()));
+    if (HasFailure()) {
+      return {};
+    }
+    fd->Flush();
 
-    ScopedTempFile target_partition("Blob-XXXXXX");
-
-    install_part_.source_path = source_partition.path();
-    install_part_.target_path = target_partition.path();
     install_part_.source_size = blob_data.size();
     install_part_.target_size = blob_data.size();
 
     ErrorCode error;
     EXPECT_TRUE(writer_.Init(&install_plan_, true, 0));
+    if (HasFailure()) {
+      return {};
+    }
     EXPECT_TRUE(writer_.PerformSourceCopyOperation(op, &error));
     writer_.CheckpointUpdateProgress(1);
 
@@ -111,8 +121,11 @@
   DynamicPartitionControlStub dynamic_control_{};
   FileDescriptorPtr fake_ecc_fd_{};
   DeltaArchiveManifest manifest_{};
+  ScopedTempFile source_partition{"source-part-XXXXXX"};
+  ScopedTempFile target_partition{"target-part-XXXXXX"};
+  InstallPlan::Partition install_part_{.source_path = source_partition.path(),
+                                       .target_path = target_partition.path()};
   PartitionUpdate partition_update_{};
-  InstallPlan::Partition install_part_{};
   PartitionWriter writer_{
       partition_update_, install_part_, &dynamic_control_, kBlockSize, false};
 };
@@ -124,7 +137,7 @@
   ScopedTempFile source("Source-XXXXXX");
   // Setup the source path with the right expected data.
   brillo::Blob expected_data = FakeFileDescriptorData(kCopyOperationSize);
-  EXPECT_TRUE(test_utils::WriteFileVector(source.path(), expected_data));
+  ASSERT_TRUE(test_utils::WriteFileVector(source.path(), expected_data));
 
   // Setup the fec file descriptor as the fake stream, with smaller data than
   // the expected.
@@ -136,17 +149,18 @@
 
   // The payload operation doesn't include an operation hash.
   auto source_copy_op = GenerateSourceCopyOp(expected_data, false, &old_part);
-
+  ASSERT_NO_FATAL_FAILURE();
   auto output_data = PerformSourceCopyOp(source_copy_op.op, expected_data);
+  ASSERT_NO_FATAL_FAILURE();
   ASSERT_EQ(output_data, expected_data);
 
   // Verify that the fake_fec was attempted to be used. Since the file
   // descriptor is shorter it can actually do more than one read to realize it
   // reached the EOF.
-  EXPECT_LE(1U, fake_fec->GetReadOps().size());
+  ASSERT_LE(1U, fake_fec->GetReadOps().size());
   // This fallback doesn't count as an error-corrected operation since the
   // operation hash was not available.
-  EXPECT_EQ(0U, GetSourceEccRecoveredFailures());
+  ASSERT_EQ(0U, GetSourceEccRecoveredFailures());
 }
 
 // Test that the error-corrected file descriptor is used to read the partition
@@ -163,11 +177,13 @@
   brillo::Blob expected_data = FakeFileDescriptorData(kCopyOperationSize);
 
   auto source_copy_op = GenerateSourceCopyOp(expected_data, true);
+  ASSERT_NO_FATAL_FAILURE();
   auto output_data = PerformSourceCopyOp(source_copy_op.op, invalid_data);
+  ASSERT_NO_FATAL_FAILURE();
   ASSERT_EQ(output_data, expected_data);
 
   // Verify that the fake_fec was actually used.
-  EXPECT_EQ(1U, fake_fec->GetReadOps().size());
+  EXPECT_GE(fake_fec->GetReadOps().size(), 1U);
   EXPECT_EQ(1U, GetSourceEccRecoveredFailures());
 }
 
@@ -177,10 +193,11 @@
   // Write invalid data to the source image, which doesn't match the expected
   // hash.
   brillo::Blob invalid_data(kSourceSize, 0x55);
-  EXPECT_TRUE(test_utils::WriteFileVector(source.path(), invalid_data));
+  ASSERT_TRUE(test_utils::WriteFileVector(source.path(), invalid_data));
 
-  writer_.source_fd_ = std::make_shared<EintrSafeFileDescriptor>();
-  writer_.source_fd_->Open(source.path().c_str(), O_RDONLY);
+  writer_.verified_source_fd_.source_fd_ =
+      std::make_shared<EintrSafeFileDescriptor>();
+  writer_.verified_source_fd_.source_fd_->Open(source.path().c_str(), O_RDONLY);
 
   // Setup the fec file descriptor as the fake stream, which matches
   // |expected_data|.
@@ -190,15 +207,16 @@
   InstallOperation op;
   *(op.add_src_extents()) = ExtentForRange(0, kSourceSize / 4096);
   brillo::Blob src_hash;
-  EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
+  ASSERT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
   op.set_src_sha256_hash(src_hash.data(), src_hash.size());
 
   ErrorCode error = ErrorCode::kSuccess;
-  EXPECT_EQ(writer_.source_ecc_fd_, writer_.ChooseSourceFD(op, &error));
-  EXPECT_EQ(ErrorCode::kSuccess, error);
+  ASSERT_EQ(writer_.verified_source_fd_.source_ecc_fd_,
+            writer_.ChooseSourceFD(op, &error));
+  ASSERT_EQ(ErrorCode::kSuccess, error);
   // Verify that the fake_fec was actually used.
-  EXPECT_EQ(1U, fake_fec->GetReadOps().size());
-  EXPECT_EQ(1U, GetSourceEccRecoveredFailures());
+  ASSERT_EQ(1U, fake_fec->GetReadOps().size());
+  ASSERT_EQ(1U, GetSourceEccRecoveredFailures());
 }
 
 }  // namespace chromeos_update_engine
diff --git a/payload_consumer/snapshot_extent_writer_unittest.cc b/payload_consumer/snapshot_extent_writer_unittest.cc
index 2201043..51672ca 100644
--- a/payload_consumer/snapshot_extent_writer_unittest.cc
+++ b/payload_consumer/snapshot_extent_writer_unittest.cc
@@ -70,6 +70,10 @@
     return true;
   }
 
+  bool EmitSequenceData(size_t num_ops, const uint32_t* data) override {
+    return false;
+  }
+
   // Return number of bytes the cow image occupies on disk.
   uint64_t GetCowSize() override {
     return std::accumulate(
diff --git a/payload_consumer/vabc_partition_writer.cc b/payload_consumer/vabc_partition_writer.cc
index 0843fff..d181c75 100644
--- a/payload_consumer/vabc_partition_writer.cc
+++ b/payload_consumer/vabc_partition_writer.cc
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <libsnapshot/cow_writer.h>
@@ -55,12 +56,27 @@
 // label 3, Which contains all operation 2's data, but none of operation 3's
 // data.
 
+VABCPartitionWriter::VABCPartitionWriter(
+    const PartitionUpdate& partition_update,
+    const InstallPlan::Partition& install_part,
+    DynamicPartitionControlInterface* dynamic_control,
+    size_t block_size,
+    bool is_interactive)
+    : partition_update_(partition_update),
+      install_part_(install_part),
+      dynamic_control_(dynamic_control),
+      interactive_(is_interactive),
+      block_size_(block_size),
+      executor_(block_size),
+      verified_source_fd_(block_size, install_part.source_path) {}
+
 bool VABCPartitionWriter::Init(const InstallPlan* install_plan,
                                bool source_may_exist,
                                size_t next_op_index) {
   TEST_AND_RETURN_FALSE(install_plan != nullptr);
-  TEST_AND_RETURN_FALSE(
-      OpenSourcePartition(install_plan->source_slot, source_may_exist));
+  if (source_may_exist) {
+    TEST_AND_RETURN_FALSE(verified_source_fd_.Open());
+  }
   std::optional<std::string> source_path;
   if (!install_part_.source_path.empty()) {
     // TODO(zhangkelvin) Make |source_path| a std::optional<std::string>
@@ -90,7 +106,15 @@
   auto converted = ConvertToCowOperations(partition_update_.operations(),
                                           partition_update_.merge_operations());
 
-  WriteAllCowOps(block_size_, converted, cow_writer_.get(), source_fd_);
+  if (!converted.empty()) {
+    // Use source fd directly. Ideally we want to verify all extents used in
+    // source copy, but then what do we do if some extents contain correct
+    // hashes and some don't?
+    auto source_fd = std::make_shared<EintrSafeFileDescriptor>();
+    TEST_AND_RETURN_FALSE_ERRNO(
+        source_fd->Open(install_part_.source_path.c_str(), O_RDONLY));
+    WriteAllCowOps(block_size_, converted, cow_writer_.get(), source_fd);
+  }
   return true;
 }
 
@@ -150,6 +174,43 @@
   return true;
 }
 
+bool VABCPartitionWriter::PerformReplaceOperation(const InstallOperation& op,
+                                                  const void* data,
+                                                  size_t count) {
+  // Setup the ExtentWriter stack based on the operation type.
+  std::unique_ptr<ExtentWriter> writer = CreateBaseExtentWriter();
+
+  return executor_.ExecuteReplaceOperation(op, std::move(writer), data, count);
+}
+
+bool VABCPartitionWriter::PerformSourceBsdiffOperation(
+    const InstallOperation& operation,
+    ErrorCode* error,
+    const void* data,
+    size_t count) {
+  FileDescriptorPtr source_fd =
+      verified_source_fd_.ChooseSourceFD(operation, error);
+  TEST_AND_RETURN_FALSE(source_fd != nullptr);
+
+  auto writer = CreateBaseExtentWriter();
+  return executor_.ExecuteSourceBsdiffOperation(
+      operation, std::move(writer), source_fd, data, count);
+}
+
+bool VABCPartitionWriter::PerformPuffDiffOperation(
+    const InstallOperation& operation,
+    ErrorCode* error,
+    const void* data,
+    size_t count) {
+  FileDescriptorPtr source_fd =
+      verified_source_fd_.ChooseSourceFD(operation, error);
+  TEST_AND_RETURN_FALSE(source_fd != nullptr);
+
+  auto writer = CreateBaseExtentWriter();
+  return executor_.ExecutePuffDiffOperation(
+      operation, std::move(writer), source_fd, data, count);
+}
+
 void VABCPartitionWriter::CheckpointUpdateProgress(size_t next_op_index) {
   // No need to call fsync/sync, as CowWriter flushes after a label is added
   // added.
@@ -167,9 +228,15 @@
 }
 
 VABCPartitionWriter::~VABCPartitionWriter() {
+  Close();
+}
+
+int VABCPartitionWriter::Close() {
   if (cow_writer_) {
     cow_writer_->Finalize();
+    cow_writer_ = nullptr;
   }
+  return 0;
 }
 
 }  // namespace chromeos_update_engine
diff --git a/payload_consumer/vabc_partition_writer.h b/payload_consumer/vabc_partition_writer.h
index 7fb2a2c..e8601a9 100644
--- a/payload_consumer/vabc_partition_writer.h
+++ b/payload_consumer/vabc_partition_writer.h
@@ -18,25 +18,29 @@
 #define UPDATE_ENGINE_VABC_PARTITION_WRITER_H_
 
 #include <memory>
+#include <string>
 #include <vector>
 
 #include <libsnapshot/snapshot_writer.h>
 
 #include "update_engine/common/cow_operation_convert.h"
+#include "update_engine/payload_consumer/install_operation_executor.h"
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/payload_consumer/partition_writer.h"
 
 namespace chromeos_update_engine {
-class VABCPartitionWriter final : public PartitionWriter {
+class VABCPartitionWriter final : public PartitionWriterInterface {
  public:
-  using PartitionWriter::PartitionWriter;
+  VABCPartitionWriter(const PartitionUpdate& partition_update,
+                      const InstallPlan::Partition& install_part,
+                      DynamicPartitionControlInterface* dynamic_control,
+                      size_t block_size,
+                      bool is_interactive);
   [[nodiscard]] bool Init(const InstallPlan* install_plan,
                           bool source_may_exist,
                           size_t next_op_index) override;
   ~VABCPartitionWriter() override;
 
-  [[nodiscard]] std::unique_ptr<ExtentWriter> CreateBaseExtentWriter() override;
-
   // Only ZERO and SOURCE_COPY InstallOperations are treated special by VABC
   // Partition Writer. These operations correspond to COW_ZERO and COW_COPY. All
   // other operations just get converted to COW_REPLACE.
@@ -45,6 +49,20 @@
   [[nodiscard]] bool PerformSourceCopyOperation(
       const InstallOperation& operation, ErrorCode* error) override;
 
+  [[nodiscard]] bool PerformReplaceOperation(const InstallOperation& operation,
+                                             const void* data,
+                                             size_t count) override;
+
+  [[nodiscard]] bool PerformSourceBsdiffOperation(
+      const InstallOperation& operation,
+      ErrorCode* error,
+      const void* data,
+      size_t count) override;
+  [[nodiscard]] bool PerformPuffDiffOperation(const InstallOperation& operation,
+                                              ErrorCode* error,
+                                              const void* data,
+                                              size_t count) override;
+
   void CheckpointUpdateProgress(size_t next_op_index) override;
 
   static bool WriteAllCowOps(size_t block_size,
@@ -53,9 +71,24 @@
                              FileDescriptorPtr source_fd);
 
   [[nodiscard]] bool FinishedInstallOps() override;
+  int Close() override;
 
  private:
   std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer_;
+
+  bool OpenCurrentECCPartition();
+  [[nodiscard]] std::unique_ptr<ExtentWriter> CreateBaseExtentWriter();
+
+  const PartitionUpdate& partition_update_;
+  const InstallPlan::Partition& install_part_;
+  DynamicPartitionControlInterface* dynamic_control_;
+  // Path to source partition
+  std::string source_path_;
+
+  const bool interactive_;
+  const size_t block_size_;
+  InstallOperationExecutor executor_;
+  VerifiedSourceFd verified_source_fd_;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/payload_consumer/verified_source_fd.cc b/payload_consumer/verified_source_fd.cc
new file mode 100644
index 0000000..002bd07
--- /dev/null
+++ b/payload_consumer/verified_source_fd.cc
@@ -0,0 +1,124 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limi
+
+#include "update_engine/payload_consumer/verified_source_fd.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/fec_file_descriptor.h"
+#include "update_engine/payload_consumer/file_descriptor_utils.h"
+#include "update_engine/payload_consumer/mount_history.h"
+#include "update_engine/payload_consumer/partition_writer.h"
+
+namespace chromeos_update_engine {
+using std::string;
+
+bool VerifiedSourceFd::OpenCurrentECCPartition() {
+  // No support for ECC for full payloads.
+  // Full payload should not have any opeartion that requires ECC partitions.
+  if (source_ecc_fd_)
+    return true;
+
+  if (source_ecc_open_failure_)
+    return false;
+
+#if USE_FEC
+  FileDescriptorPtr fd(new FecFileDescriptor());
+  if (!fd->Open(source_path_.c_str(), O_RDONLY, 0)) {
+    PLOG(ERROR) << "Unable to open ECC source partition " << source_path_;
+    source_ecc_open_failure_ = true;
+    return false;
+  }
+  source_ecc_fd_ = fd;
+#else
+  // No support for ECC compiled.
+  source_ecc_open_failure_ = true;
+#endif  // USE_FEC
+
+  return !source_ecc_open_failure_;
+}
+
+FileDescriptorPtr VerifiedSourceFd::ChooseSourceFD(
+    const InstallOperation& operation, ErrorCode* error) {
+  if (source_fd_ == nullptr) {
+    LOG(ERROR) << "ChooseSourceFD fail: source_fd_ == nullptr";
+    return nullptr;
+  }
+  if (!operation.has_src_sha256_hash()) {
+    // When the operation doesn't include a source hash, we attempt the error
+    // corrected device first since we can't verify the block in the raw device
+    // at this point, but we first need to make sure all extents are readable
+    // since the error corrected device can be shorter or not available.
+    if (OpenCurrentECCPartition() &&
+        fd_utils::ReadAndHashExtents(
+            source_ecc_fd_, operation.src_extents(), block_size_, nullptr)) {
+      return source_ecc_fd_;
+    }
+    return source_fd_;
+  }
+
+  brillo::Blob source_hash;
+  brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
+                                    operation.src_sha256_hash().end());
+  if (fd_utils::ReadAndHashExtents(
+          source_fd_, operation.src_extents(), block_size_, &source_hash) &&
+      source_hash == expected_source_hash) {
+    return source_fd_;
+  }
+  // We fall back to use the error corrected device if the hash of the raw
+  // device doesn't match or there was an error reading the source partition.
+  if (!OpenCurrentECCPartition()) {
+    // The following function call will return false since the source hash
+    // mismatches, but we still want to call it so it prints the appropriate
+    // log message.
+    PartitionWriter::ValidateSourceHash(
+        source_hash, operation, source_fd_, error);
+    return nullptr;
+  }
+  LOG(WARNING) << "Source hash from RAW device mismatched: found "
+               << base::HexEncode(source_hash.data(), source_hash.size())
+               << ", expected "
+               << base::HexEncode(expected_source_hash.data(),
+                                  expected_source_hash.size());
+
+  if (fd_utils::ReadAndHashExtents(
+          source_ecc_fd_, operation.src_extents(), block_size_, &source_hash) &&
+      PartitionWriter::ValidateSourceHash(
+          source_hash, operation, source_ecc_fd_, error)) {
+    source_ecc_recovered_failures_++;
+    return source_ecc_fd_;
+  }
+  return nullptr;
+}
+
+bool VerifiedSourceFd::Open() {
+  source_fd_ = std::make_shared<EintrSafeFileDescriptor>();
+  if (source_fd_ == nullptr)
+    return false;
+  TEST_AND_RETURN_FALSE_ERRNO(source_fd_->Open(source_path_.c_str(), O_RDONLY));
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/verified_source_fd.h b/payload_consumer/verified_source_fd.h
new file mode 100644
index 0000000..f7d0620
--- /dev/null
+++ b/payload_consumer/verified_source_fd.h
@@ -0,0 +1,61 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limi
+
+#ifndef UPDATE_ENGINE_VERIFIED_SOURCE_FD_H__
+#define UPDATE_ENGINE_VERIFIED_SOURCE_FD_H__
+
+#include <cstddef>
+
+#include <string>
+#include <utility>
+
+#include <gtest/gtest_prod.h>
+#include <update_engine/update_metadata.pb.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+
+namespace chromeos_update_engine {
+
+class VerifiedSourceFd {
+ public:
+  explicit VerifiedSourceFd(size_t block_size, std::string source_path)
+      : block_size_(block_size), source_path_(std::move(source_path)) {}
+  FileDescriptorPtr ChooseSourceFD(const InstallOperation& operation,
+                                   ErrorCode* error);
+
+  [[nodiscard]] bool Open();
+
+ private:
+  bool OpenCurrentECCPartition();
+  const size_t block_size_;
+  const std::string source_path_;
+  FileDescriptorPtr source_ecc_fd_;
+  FileDescriptorPtr source_fd_;
+
+  friend class PartitionWriterTest;
+  FRIEND_TEST(PartitionWriterTest, ChooseSourceFDTest);
+  // The total number of operations that failed source hash verification but
+  // passed after falling back to the error-corrected |source_ecc_fd_| device.
+  uint64_t source_ecc_recovered_failures_{0};
+
+  // Whether opening the current partition as an error-corrected device failed.
+  // Used to avoid re-opening the same source partition if it is not actually
+  // error corrected.
+  bool source_ecc_open_failure_{false};
+};
+}  // namespace chromeos_update_engine
+
+#endif
diff --git a/payload_generator/ab_generator.cc b/payload_generator/ab_generator.cc
index d9b9d88..25cafe3 100644
--- a/payload_generator/ab_generator.cc
+++ b/payload_generator/ab_generator.cc
@@ -54,7 +54,7 @@
                                                        new_part,
                                                        hard_chunk_blocks,
                                                        soft_chunk_blocks,
-                                                       config.version,
+                                                       config,
                                                        blob_file));
   LOG(INFO) << "done reading " << new_part.name;
 
diff --git a/payload_generator/annotated_operation.h b/payload_generator/annotated_operation.h
index 653bab0..c57f249 100644
--- a/payload_generator/annotated_operation.h
+++ b/payload_generator/annotated_operation.h
@@ -19,6 +19,7 @@
 
 #include <ostream>  // NOLINT(readability/streams)
 #include <string>
+#include <vector>
 
 #include <brillo/secure_blob.h>
 
@@ -35,6 +36,11 @@
   // The InstallOperation, as defined by the protobuf.
   InstallOperation op;
 
+  // Array of blocks which should be converted to XOR during OTA install.
+  // All elements in this array should have |merge_op.type() == COW_XOR|.
+  // This information is typically derived from BSDIFF patch data.
+  std::vector<CowMergeOperation> xor_ops;
+
   // 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|.
diff --git a/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc
index 3c025e1..077fbfe 100644
--- a/payload_generator/delta_diff_utils.cc
+++ b/payload_generator/delta_diff_utils.cc
@@ -35,15 +35,17 @@
 #include <memory>
 #include <numeric>
 #include <utility>
+#include <vector>
 
 #include <base/files/file_util.h>
 #include <base/format_macros.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 #include <base/threading/simple_thread.h>
-#include <base/time/time.h>
 #include <brillo/data_encoding.h>
 #include <bsdiff/bsdiff.h>
+#include <bsdiff/control_entry.h>
+#include <bsdiff/patch_reader.h>
 #include <bsdiff/patch_writer_factory.h>
 #include <puffin/utils.h>
 
@@ -131,6 +133,44 @@
   }
   return distances.back();
 }
+
+static bool ShouldCreateNewOp(const std::vector<CowMergeOperation>& ops,
+                              size_t src_block,
+                              size_t dst_block,
+                              size_t src_offset) {
+  if (ops.empty()) {
+    return true;
+  }
+  const auto& op = ops.back();
+  if (op.src_offset() != src_offset) {
+    return true;
+  }
+  const auto& src_extent = op.src_extent();
+  const auto& dst_extent = op.dst_extent();
+  return src_extent.start_block() + src_extent.num_blocks() != src_block ||
+         dst_extent.start_block() + dst_extent.num_blocks() != dst_block;
+}
+
+static void AppendXorBlock(std::vector<CowMergeOperation>* ops,
+                           size_t src_block,
+                           size_t dst_block,
+                           size_t src_offset) {
+  if (ShouldCreateNewOp(*ops, src_block, dst_block, src_offset)) {
+    auto& op = ops->emplace_back();
+    op.mutable_src_extent()->set_start_block(src_block);
+    op.mutable_src_extent()->set_num_blocks(1);
+    op.mutable_dst_extent()->set_start_block(dst_block);
+    op.mutable_dst_extent()->set_num_blocks(1);
+    op.set_src_offset(src_offset);
+  } else {
+    auto& op = ops->back();
+    auto& src_extent = *op.mutable_src_extent();
+    auto& dst_extent = *op.mutable_dst_extent();
+    src_extent.set_num_blocks(src_extent.num_blocks() + 1);
+    dst_extent.set_num_blocks(dst_extent.num_blocks() + 1);
+  }
+}
+
 }  // namespace
 
 namespace diff_utils {
@@ -142,7 +182,7 @@
  public:
   FileDeltaProcessor(const string& old_part,
                      const string& new_part,
-                     const PayloadVersion& version,
+                     const PayloadGenerationConfig& config,
                      const vector<Extent>& old_extents,
                      const vector<Extent>& new_extents,
                      const vector<puffin::BitExtent>& old_deflates,
@@ -152,7 +192,7 @@
                      BlobFileWriter* blob_file)
       : old_part_(old_part),
         new_part_(new_part),
-        version_(version),
+        config_(config),
         old_extents_(old_extents),
         new_extents_(new_extents),
         new_extents_blocks_(utils::BlocksInExtents(new_extents)),
@@ -179,7 +219,7 @@
  private:
   const string& old_part_;  // NOLINT(runtime/member_string_references)
   const string& new_part_;  // NOLINT(runtime/member_string_references)
-  const PayloadVersion& version_;
+  const PayloadGenerationConfig& config_;
 
   // The block ranges of the old/new file within the src/tgt image
   const vector<Extent> old_extents_;
@@ -213,7 +253,7 @@
                      new_deflates_,
                      name_,
                      chunk_blocks_,
-                     version_,
+                     config_,
                      blob_file_)) {
     LOG(ERROR) << "Failed to generate delta for " << name_ << " ("
                << new_extents_blocks_ << " blocks)";
@@ -222,7 +262,7 @@
   }
 
   if (!ABGenerator::FragmentOperations(
-          version_, &file_aops_, new_part_, blob_file_)) {
+          config_.version, &file_aops_, new_part_, blob_file_)) {
     LOG(ERROR) << "Failed to fragment operations for " << name_;
     failed_ = true;
     return;
@@ -273,8 +313,9 @@
                         const PartitionConfig& new_part,
                         ssize_t hard_chunk_blocks,
                         size_t soft_chunk_blocks,
-                        const PayloadVersion& version,
+                        const PayloadGenerationConfig& config,
                         BlobFileWriter* blob_file) {
+  const auto& version = config.version;
   ExtentRanges old_visited_blocks;
   ExtentRanges new_visited_blocks;
 
@@ -297,7 +338,7 @@
                                                 old_part.size / kBlockSize,
                                                 new_part.size / kBlockSize,
                                                 soft_chunk_blocks,
-                                                version,
+                                                config,
                                                 blob_file,
                                                 &old_visited_blocks,
                                                 &new_visited_blocks,
@@ -355,7 +396,7 @@
 
     file_delta_processors.emplace_back(old_part.path,
                                        new_part.path,
-                                       version,
+                                       config,
                                        std::move(old_file_extents),
                                        std::move(new_file_extents),
                                        old_file.deflates,
@@ -385,7 +426,7 @@
     file_delta_processors.emplace_back(
         old_part.path,
         new_part.path,
-        version,
+        config,
         std::move(old_unvisited),
         std::move(new_unvisited),
         vector<puffin::BitExtent>{},  // old_deflates,
@@ -424,11 +465,12 @@
                              size_t old_num_blocks,
                              size_t new_num_blocks,
                              ssize_t chunk_blocks,
-                             const PayloadVersion& version,
+                             const PayloadGenerationConfig& config,
                              BlobFileWriter* blob_file,
                              ExtentRanges* old_visited_blocks,
                              ExtentRanges* new_visited_blocks,
                              ExtentRanges* old_zero_blocks) {
+  const auto& version = config.version;
   vector<BlockMapping::BlockId> old_block_ids;
   vector<BlockMapping::BlockId> new_block_ids;
   TEST_AND_RETURN_FALSE(MapPartitionBlocks(old_part,
@@ -516,7 +558,7 @@
                                           {},        // new_deflates
                                           "<zeros>",
                                           chunk_blocks,
-                                          version,
+                                          config,
                                           blob_file));
     }
   }
@@ -575,10 +617,9 @@
                    const vector<puffin::BitExtent>& new_deflates,
                    const string& name,
                    ssize_t chunk_blocks,
-                   const PayloadVersion& version,
+                   const PayloadGenerationConfig& config,
                    BlobFileWriter* blob_file) {
   brillo::Blob data;
-  InstallOperation operation;
 
   uint64_t total_blocks = utils::BlocksInExtents(new_extents);
   if (chunk_blocks == 0) {
@@ -602,30 +643,29 @@
     NormalizeExtents(&old_extents_chunk);
     NormalizeExtents(&new_extents_chunk);
 
+    // Now, insert into the list of operations.
+    AnnotatedOperation aop;
     TEST_AND_RETURN_FALSE(ReadExtentsToDiff(old_part,
                                             new_part,
                                             old_extents_chunk,
                                             new_extents_chunk,
                                             old_deflates,
                                             new_deflates,
-                                            version,
+                                            config,
                                             &data,
-                                            &operation));
+                                            &aop));
 
     // Check if the operation writes nothing.
-    if (operation.dst_extents_size() == 0) {
+    if (aop.op.dst_extents_size() == 0) {
       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));
@@ -688,16 +728,84 @@
   return true;
 }
 
+// Decide which blocks are similar from bsdiff patch.
+// Blocks included in out_op->xor_map will be converted to COW_XOR during OTA
+// installation
+bool PopulateXorOps(AnnotatedOperation* aop, const uint8_t* data, size_t size) {
+  bsdiff::BsdiffPatchReader patch_reader;
+  TEST_AND_RETURN_FALSE(patch_reader.Init(data, size));
+  ControlEntry entry;
+  size_t new_off = 0;
+  int64_t old_off = 0;
+  auto& xor_ops = aop->xor_ops;
+  size_t total_xor_blocks = 0;
+  const auto new_file_size =
+      utils::BlocksInExtents(aop->op.dst_extents()) * kBlockSize;
+  while (new_off < new_file_size) {
+    if (!patch_reader.ParseControlEntry(&entry)) {
+      LOG(ERROR)
+          << "Exhausted bsdiff patch data before reaching end of new file. "
+             "Current position: "
+          << new_off << " new file size: " << new_file_size;
+      return false;
+    }
+    if (old_off >= 0) {
+      auto dst_off_aligned = utils::RoundUp(new_off, kBlockSize);
+      const auto skip = dst_off_aligned - new_off;
+      auto src_off = old_off + skip;
+      const size_t chunk_size =
+          entry.diff_size - std::min(skip, entry.diff_size);
+      const auto xor_blocks = (chunk_size + kBlockSize / 2) / kBlockSize;
+      total_xor_blocks += xor_blocks;
+      // Append chunk_size/kBlockSize number of XOR blocks, subject to rounding
+      // rules: if decimal part of that division is >= 0.5, round up.
+      for (size_t i = 0; i < xor_blocks; i++) {
+        AppendXorBlock(
+            &xor_ops,
+            GetNthBlock(aop->op.src_extents(), src_off / kBlockSize),
+            GetNthBlock(aop->op.dst_extents(), dst_off_aligned / kBlockSize),
+            src_off % kBlockSize);
+        src_off += kBlockSize;
+        dst_off_aligned += kBlockSize;
+      }
+    }
+
+    old_off += entry.diff_size + entry.offset_increment;
+    new_off += entry.diff_size + entry.extra_size;
+  }
+
+  for (auto& op : xor_ops) {
+    CHECK_EQ(op.src_extent().num_blocks(), op.dst_extent().num_blocks());
+    // If |src_offset| is greater than 0, then we are reading 1
+    // extra block at the end of src_extent. This dependency must
+    // be honored during merge sequence generation, or we can end
+    // up with a corrupted device after merge.
+    if (op.src_offset() > 0) {
+      op.mutable_src_extent()->set_num_blocks(op.dst_extent().num_blocks() + 1);
+    }
+  }
+
+  if (xor_ops.size() > 0) {
+    // TODO(177104308) Filter out duplicate blocks in XOR op
+    LOG(INFO) << "Added " << total_xor_blocks << " XOR blocks, "
+              << total_xor_blocks * 100.0f / new_off * kBlockSize
+              << "% of blocks in this InstallOp are XOR";
+  }
+  return true;
+}
+
 bool ReadExtentsToDiff(const string& old_part,
                        const string& new_part,
                        const vector<Extent>& old_extents,
                        const vector<Extent>& new_extents,
                        const vector<puffin::BitExtent>& old_deflates,
                        const vector<puffin::BitExtent>& new_deflates,
-                       const PayloadVersion& version,
+                       const PayloadGenerationConfig& config,
                        brillo::Blob* out_data,
-                       InstallOperation* out_op) {
-  InstallOperation operation;
+                       AnnotatedOperation* out_op) {
+  const auto& version = config.version;
+  AnnotatedOperation aop;
+  InstallOperation& operation = aop.op;
 
   // We read blocks from old_extents and write blocks to new_extents.
   uint64_t blocks_to_read = utils::BlocksInExtents(old_extents);
@@ -721,9 +829,10 @@
     puffdiff_allowed = false;
   }
 
-  // Make copies of the extents so we can modify them.
-  vector<Extent> src_extents = old_extents;
-  vector<Extent> dst_extents = new_extents;
+  const vector<Extent>& src_extents = old_extents;
+  const vector<Extent>& dst_extents = new_extents;
+  // All operations have dst_extents.
+  StoreExtents(dst_extents, operation.mutable_dst_extents());
 
   // Read in bytes from new data.
   brillo::Blob new_data;
@@ -786,11 +895,15 @@
                                                   nullptr));
 
         TEST_AND_RETURN_FALSE(utils::ReadFile(patch.value(), &bsdiff_delta));
+
         CHECK_GT(bsdiff_delta.size(), static_cast<brillo::Blob::size_type>(0));
         if (IsDiffOperationBetter(operation,
                                   data_blob.size(),
                                   bsdiff_delta.size(),
                                   src_extents.size())) {
+          if (config.enable_vabc_xor) {
+            PopulateXorOps(&aop, bsdiff_delta);
+          }
           operation.set_type(operation_type);
           data_blob = std::move(bsdiff_delta);
         }
@@ -860,11 +973,9 @@
   if (!IsNoSourceOperation(operation.type())) {
     StoreExtents(src_extents, operation.mutable_src_extents());
   }
-  // All operations have dst_extents.
-  StoreExtents(dst_extents, operation.mutable_dst_extents());
 
   *out_data = std::move(data_blob);
-  *out_op = operation;
+  *out_op = aop;
   return true;
 }
 
diff --git a/payload_generator/delta_diff_utils.h b/payload_generator/delta_diff_utils.h
index c75d16d..f284530 100644
--- a/payload_generator/delta_diff_utils.h
+++ b/payload_generator/delta_diff_utils.h
@@ -48,7 +48,7 @@
                         const PartitionConfig& new_part,
                         ssize_t hard_chunk_blocks,
                         size_t soft_chunk_blocks,
-                        const PayloadVersion& version,
+                        const PayloadGenerationConfig& version,
                         BlobFileWriter* blob_file);
 
 // Create operations in |aops| for identical blocks that moved around in the old
@@ -67,7 +67,7 @@
                              size_t old_num_blocks,
                              size_t new_num_blocks,
                              ssize_t chunk_blocks,
-                             const PayloadVersion& version,
+                             const PayloadGenerationConfig& version,
                              BlobFileWriter* blob_file,
                              ExtentRanges* old_visited_blocks,
                              ExtentRanges* new_visited_blocks,
@@ -90,7 +90,7 @@
                    const std::vector<puffin::BitExtent>& new_deflates,
                    const std::string& name,
                    ssize_t chunk_blocks,
-                   const PayloadVersion& version,
+                   const PayloadGenerationConfig& config,
                    BlobFileWriter* blob_file);
 
 // Reads the blocks |old_extents| from |old_part| (if it exists) and the
@@ -108,9 +108,9 @@
                        const std::vector<Extent>& new_extents,
                        const std::vector<puffin::BitExtent>& old_deflates,
                        const std::vector<puffin::BitExtent>& new_deflates,
-                       const PayloadVersion& version,
+                       const PayloadGenerationConfig& version,
                        brillo::Blob* out_data,
-                       InstallOperation* out_op);
+                       AnnotatedOperation* out_op);
 
 // Generates the best allowed full operation to produce |new_data|. The allowed
 // operations are based on |payload_version|. The operation blob will be stored
@@ -149,6 +149,15 @@
     const std::map<std::string, FilesystemInterface::File>& old_files_map,
     const std::string& new_file_name);
 
+// Read BSDIFF patch data in |data|, compute list of blocks that can be COW_XOR,
+// store these blocks in |aop|.
+bool PopulateXorOps(AnnotatedOperation* aop, const uint8_t* data, size_t size);
+
+inline bool PopulateXorOps(AnnotatedOperation* aop,
+                           const brillo::Blob& patch_data) {
+  return PopulateXorOps(aop, patch_data.data(), patch_data.size());
+}
+
 }  // namespace diff_utils
 
 }  // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_utils_unittest.cc b/payload_generator/delta_diff_utils_unittest.cc
index f2db1bd..436e265 100644
--- a/payload_generator/delta_diff_utils_unittest.cc
+++ b/payload_generator/delta_diff_utils_unittest.cc
@@ -24,6 +24,7 @@
 #include <base/files/scoped_file.h>
 #include <base/format_macros.h>
 #include <base/strings/stringprintf.h>
+#include <bsdiff/patch_writer.h>
 #include <gtest/gtest.h>
 
 #include "update_engine/common/test_utils.h"
@@ -133,7 +134,7 @@
                                                old_part_.size / block_size_,
                                                new_part_.size / block_size_,
                                                chunk_blocks,
-                                               version,
+                                               {.version = version},
                                                &blob_file,
                                                &old_visited_blocks_,
                                                &new_visited_blocks_,
@@ -164,22 +165,22 @@
   new_part_.verity.fec_extent = ExtentForRange(40, 50);
 
   BlobFileWriter blob_file(tmp_blob_file_.fd(), &blob_size_);
-  EXPECT_TRUE(diff_utils::DeltaReadPartition(
+  ASSERT_TRUE(diff_utils::DeltaReadPartition(
       &aops_,
       old_part_,
       new_part_,
       -1,
       -1,
-      PayloadVersion(kMaxSupportedMajorPayloadVersion,
-                     kVerityMinorPayloadVersion),
+      {.version = PayloadVersion(kMaxSupportedMajorPayloadVersion,
+                                 kVerityMinorPayloadVersion)},
       &blob_file));
   for (const auto& aop : aops_) {
     new_visited_blocks_.AddRepeatedExtents(aop.op.dst_extents());
   }
   for (const auto& extent : new_visited_blocks_.extent_set()) {
-    EXPECT_FALSE(ExtentRanges::ExtentsOverlap(
+    ASSERT_FALSE(ExtentRanges::ExtentsOverlap(
         extent, new_part_.verity.hash_tree_extent));
-    EXPECT_FALSE(
+    ASSERT_FALSE(
         ExtentRanges::ExtentsOverlap(extent, new_part_.verity.fec_extent));
   }
 }
@@ -203,34 +204,36 @@
   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(
+    ASSERT_TRUE(
         WriteExtents(new_part_.path, new_extents, kBlockSize, data_to_test));
 
     brillo::Blob data;
-    InstallOperation op;
-    EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+    AnnotatedOperation aop;
+    InstallOperation& op = aop.op;
+    ASSERT_TRUE(diff_utils::ReadExtentsToDiff(
         old_part_.path,
         new_part_.path,
         old_extents,
         new_extents,
         {},  // old_deflates
         {},  // new_deflates
-        PayloadVersion(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion),
+        {.version = PayloadVersion(kBrilloMajorPayloadVersion,
+                                   kSourceMinorPayloadVersion)},
         &data,
-        &op));
-    EXPECT_FALSE(data.empty());
+        &aop));
+    ASSERT_FALSE(data.empty());
 
-    EXPECT_TRUE(op.has_type());
+    ASSERT_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_FALSE(op.has_dst_length());
-    EXPECT_EQ(1U, utils::BlocksInExtents(op.dst_extents()));
+    ASSERT_EQ(expected_type, op.type());
+    ASSERT_FALSE(op.has_data_offset());
+    ASSERT_FALSE(op.has_data_length());
+    ASSERT_EQ(0, op.src_extents_size());
+    ASSERT_FALSE(op.has_src_length());
+    ASSERT_EQ(1, op.dst_extents_size());
+    ASSERT_FALSE(op.has_dst_length());
+    ASSERT_EQ(1U, utils::BlocksInExtents(op.dst_extents()));
   }
 }
 
@@ -245,25 +248,27 @@
   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));
+  ASSERT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob));
+  ASSERT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob));
 
   brillo::Blob data;
-  InstallOperation op;
-  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+  AnnotatedOperation aop;
+  ASSERT_TRUE(diff_utils::ReadExtentsToDiff(
       old_part_.path,
       new_part_.path,
       old_extents,
       new_extents,
       {},  // old_deflates
       {},  // new_deflates
-      PayloadVersion(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion),
+      {.version = PayloadVersion(kBrilloMajorPayloadVersion,
+                                 kSourceMinorPayloadVersion)},
       &data,
-      &op));
-  EXPECT_TRUE(data.empty());
+      &aop));
+  InstallOperation& op = aop.op;
+  ASSERT_TRUE(data.empty());
 
-  EXPECT_TRUE(op.has_type());
-  EXPECT_EQ(InstallOperation::SOURCE_COPY, op.type());
+  ASSERT_TRUE(op.has_type());
+  ASSERT_EQ(InstallOperation::SOURCE_COPY, op.type());
 }
 
 TEST_F(DeltaDiffUtilsTest, SourceBsdiffTest) {
@@ -277,27 +282,28 @@
   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));
+  ASSERT_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));
+  ASSERT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob));
 
   brillo::Blob data;
-  InstallOperation op;
-  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+  AnnotatedOperation aop;
+  ASSERT_TRUE(diff_utils::ReadExtentsToDiff(
       old_part_.path,
       new_part_.path,
       old_extents,
       new_extents,
       {},  // old_deflates
       {},  // new_deflates
-      PayloadVersion(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion),
+      {.version = PayloadVersion(kBrilloMajorPayloadVersion,
+                                 kSourceMinorPayloadVersion)},
       &data,
-      &op));
-
-  EXPECT_FALSE(data.empty());
-  EXPECT_TRUE(op.has_type());
-  EXPECT_EQ(InstallOperation::SOURCE_BSDIFF, op.type());
+      &aop));
+  auto& op = aop.op;
+  ASSERT_FALSE(data.empty());
+  ASSERT_TRUE(op.has_type());
+  ASSERT_EQ(InstallOperation::SOURCE_BSDIFF, op.type());
 }
 
 TEST_F(DeltaDiffUtilsTest, PreferReplaceTest) {
@@ -307,28 +313,28 @@
   // Write something in the first 50 bytes so that REPLACE_BZ will be slightly
   // larger than BROTLI_BSDIFF.
   std::iota(data_blob.begin(), data_blob.begin() + 50, 0);
-  EXPECT_TRUE(WriteExtents(old_part_.path, extents, kBlockSize, data_blob));
+  ASSERT_TRUE(WriteExtents(old_part_.path, extents, kBlockSize, data_blob));
   // Shift the first 50 bytes in the new file by one.
   std::iota(data_blob.begin(), data_blob.begin() + 50, 1);
-  EXPECT_TRUE(WriteExtents(new_part_.path, extents, kBlockSize, data_blob));
+  ASSERT_TRUE(WriteExtents(new_part_.path, extents, kBlockSize, data_blob));
 
   brillo::Blob data;
-  InstallOperation op;
-  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+  AnnotatedOperation aop;
+  ASSERT_TRUE(diff_utils::ReadExtentsToDiff(
       old_part_.path,
       new_part_.path,
       extents,
       extents,
       {},  // old_deflates
       {},  // new_deflates
-      PayloadVersion(kMaxSupportedMajorPayloadVersion,
-                     kMaxSupportedMinorPayloadVersion),
+      {.version = PayloadVersion(kMaxSupportedMajorPayloadVersion,
+                                 kMaxSupportedMinorPayloadVersion)},
       &data,
-      &op));
-
-  EXPECT_FALSE(data.empty());
-  EXPECT_TRUE(op.has_type());
-  EXPECT_EQ(InstallOperation::REPLACE_BZ, op.type());
+      &aop));
+  auto& op = aop.op;
+  ASSERT_FALSE(data.empty());
+  ASSERT_TRUE(op.has_type());
+  ASSERT_EQ(InstallOperation::REPLACE_BZ, op.type());
 }
 
 // Test the simple case where all the blocks are different and no new blocks are
@@ -337,13 +343,13 @@
   InitializePartitionWithUniqueBlocks(old_part_, block_size_, 5);
   InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42);
 
-  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
+  ASSERT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
                                          kSourceMinorPayloadVersion));
 
-  EXPECT_EQ(0U, old_visited_blocks_.blocks());
-  EXPECT_EQ(0U, new_visited_blocks_.blocks());
-  EXPECT_EQ(0, blob_size_);
-  EXPECT_TRUE(aops_.empty());
+  ASSERT_EQ(0U, old_visited_blocks_.blocks());
+  ASSERT_EQ(0U, new_visited_blocks_.blocks());
+  ASSERT_EQ(0, blob_size_);
+  ASSERT_TRUE(aops_.empty());
 }
 
 // Test that when the partitions have identical blocks in the same positions
@@ -364,21 +370,21 @@
 
   // Override some of the old blocks with different data.
   vector<Extent> different_blocks = {ExtentForRange(40, 5)};
-  EXPECT_TRUE(WriteExtents(old_part_.path,
+  ASSERT_TRUE(WriteExtents(old_part_.path,
                            different_blocks,
                            kBlockSize,
                            brillo::Blob(5 * kBlockSize, 'a')));
 
-  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(10,  // chunk_blocks
+  ASSERT_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_);
+  ASSERT_EQ(expected_ranges.extent_set(), old_visited_blocks_.extent_set());
+  ASSERT_EQ(expected_ranges.extent_set(), new_visited_blocks_.extent_set());
+  ASSERT_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
@@ -391,15 +397,15 @@
       ExtentForRange(45, 5),
   };
 
-  EXPECT_EQ(expected_op_extents.size(), aops_.size());
+  ASSERT_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));
+    ASSERT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
+    ASSERT_EQ(1, aop.op.src_extents_size());
+    ASSERT_EQ(expected_op_extents[i], aop.op.src_extents(0));
+    ASSERT_EQ(1, aop.op.dst_extents_size());
+    ASSERT_EQ(expected_op_extents[i], aop.op.dst_extents(0));
   }
 }
 
@@ -419,28 +425,28 @@
     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));
+  ASSERT_TRUE(test_utils::WriteFileVector(old_part_.path, partition_data));
+  ASSERT_TRUE(test_utils::WriteFileVector(new_part_.path, partition_data));
 
-  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
+  ASSERT_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());
+  ASSERT_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());
+  ASSERT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
+  ASSERT_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));
+    ASSERT_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));
+  ASSERT_EQ(1, aop.op.dst_extents_size());
+  ASSERT_EQ(ExtentForRange(0, 50), aop.op.dst_extents(0));
 
-  EXPECT_EQ(0, blob_size_);
+  ASSERT_EQ(0, blob_size_);
 }
 
 // Test that all blocks with zeros are handled separately using REPLACE_BZ
@@ -460,21 +466,21 @@
   };
   brillo::Blob zeros_data(utils::BlocksInExtents(new_zeros) * block_size_,
                           '\0');
-  EXPECT_TRUE(WriteExtents(new_part_.path, new_zeros, block_size_, zeros_data));
+  ASSERT_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));
+  ASSERT_TRUE(WriteExtents(old_part_.path, old_zeros, block_size_, zeros_data));
 
-  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(5,  // chunk_blocks
+  ASSERT_TRUE(RunDeltaMovedAndZeroBlocks(5,  // chunk_blocks
                                          kSourceMinorPayloadVersion));
 
   // Zeroed blocks from |old_visited_blocks_| were copied over.
-  EXPECT_EQ(old_zeros,
+  ASSERT_EQ(old_zeros,
             old_visited_blocks_.GetExtentsForBlockCount(
                 old_visited_blocks_.blocks()));
 
   // All the new zeroed blocks should be used with REPLACE_BZ.
-  EXPECT_EQ(new_zeros,
+  ASSERT_EQ(new_zeros,
             new_visited_blocks_.GetExtentsForBlockCount(
                 new_visited_blocks_.blocks()));
 
@@ -488,16 +494,16 @@
       ExtentForRange(45, 5),
   };
 
-  EXPECT_EQ(expected_op_extents.size(), aops_.size());
+  ASSERT_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));
+    ASSERT_EQ(InstallOperation::REPLACE_BZ, aop.op.type());
+    ASSERT_EQ(0, aop.op.src_extents_size());
+    ASSERT_EQ(1, aop.op.dst_extents_size());
+    ASSERT_EQ(expected_op_extents[i], aop.op.dst_extents(0));
   }
-  EXPECT_NE(0, blob_size_);
+  ASSERT_NE(0, blob_size_);
 }
 
 TEST_F(DeltaDiffUtilsTest, ShuffledBlocksAreTracked) {
@@ -515,39 +521,39 @@
   // |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(
+  ASSERT_TRUE(utils::ReadFile(new_part_.path, &new_contents));
+  ASSERT_TRUE(
       WriteExtents(old_part_.path, perm_extents, block_size_, new_contents));
 
-  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
+  ASSERT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
                                          kSourceMinorPayloadVersion));
 
-  EXPECT_EQ(permutation.size(), old_visited_blocks_.blocks());
-  EXPECT_EQ(permutation.size(), new_visited_blocks_.blocks());
+  ASSERT_EQ(permutation.size(), old_visited_blocks_.blocks());
+  ASSERT_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());
+  ASSERT_EQ(1U, aops_.size());
   const AnnotatedOperation& aop = aops_[0];
-  EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
+  ASSERT_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);
+  ASSERT_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));
+  ASSERT_EQ(1, aop.op.dst_extents_size());
+  ASSERT_EQ(ExtentForRange(0, permutation.size()), aop.op.dst_extents(0));
 
-  EXPECT_EQ(0, blob_size_);
+  ASSERT_EQ(0, blob_size_);
 }
 
 TEST_F(DeltaDiffUtilsTest, IsExtFilesystemTest) {
-  EXPECT_TRUE(diff_utils::IsExtFilesystem(
+  ASSERT_TRUE(diff_utils::IsExtFilesystem(
       test_utils::GetBuildArtifactsPath("gen/disk_ext2_1k.img")));
-  EXPECT_TRUE(diff_utils::IsExtFilesystem(
+  ASSERT_TRUE(diff_utils::IsExtFilesystem(
       test_utils::GetBuildArtifactsPath("gen/disk_ext2_4k.img")));
 }
 
 TEST_F(DeltaDiffUtilsTest, GetOldFileEmptyTest) {
-  EXPECT_TRUE(diff_utils::GetOldFile({}, "filename").name.empty());
+  ASSERT_TRUE(diff_utils::GetOldFile({}, "filename").name.empty());
 }
 
 TEST_F(DeltaDiffUtilsTest, GetOldFileTest) {
@@ -569,24 +575,135 @@
 
   // Always return exact match if possible.
   for (const auto& name : file_list)
-    EXPECT_EQ(diff_utils::GetOldFile(old_files_map, name).name, name);
+    ASSERT_EQ(diff_utils::GetOldFile(old_files_map, name).name, name);
 
-  EXPECT_EQ(diff_utils::GetOldFile(old_files_map, "file_name").name,
+  ASSERT_EQ(diff_utils::GetOldFile(old_files_map, "file_name").name,
             "filename");
-  EXPECT_EQ(diff_utils::GetOldFile(old_files_map, "filename_new.zip").name,
+  ASSERT_EQ(diff_utils::GetOldFile(old_files_map, "filename_new.zip").name,
             "filename.zip");
-  EXPECT_EQ(diff_utils::GetOldFile(old_files_map, "version1.2").name,
+  ASSERT_EQ(diff_utils::GetOldFile(old_files_map, "version1.2").name,
             "version1.1");
-  EXPECT_EQ(diff_utils::GetOldFile(old_files_map, "version3.0").name,
+  ASSERT_EQ(diff_utils::GetOldFile(old_files_map, "version3.0").name,
             "version2.0");
-  EXPECT_EQ(diff_utils::GetOldFile(old_files_map, "_version").name, "version");
-  EXPECT_EQ(
+  ASSERT_EQ(diff_utils::GetOldFile(old_files_map, "_version").name, "version");
+  ASSERT_EQ(
       diff_utils::GetOldFile(old_files_map, "update_engine_unittest").name,
       "update_engine");
-  EXPECT_EQ(diff_utils::GetOldFile(old_files_map, "bin/delta_generator").name,
+  ASSERT_EQ(diff_utils::GetOldFile(old_files_map, "bin/delta_generator").name,
             "delta_generator");
   // Check file name with minimum size.
-  EXPECT_EQ(diff_utils::GetOldFile(old_files_map, "a").name, "filename");
+  ASSERT_EQ(diff_utils::GetOldFile(old_files_map, "a").name, "filename");
+}
+
+TEST_F(DeltaDiffUtilsTest, XorOpsSourceNotAligned) {
+  ScopedTempFile patch_file;
+  bsdiff::BsdiffPatchWriter writer{patch_file.path()};
+  ASSERT_TRUE(writer.Init(kBlockSize * 10));
+  ASSERT_TRUE(writer.AddControlEntry(ControlEntry(0, 0, 123 + kBlockSize)));
+  ASSERT_TRUE(writer.AddControlEntry(ControlEntry(kBlockSize, 0, 0)));
+  ASSERT_TRUE(writer.Close());
+
+  std::string patch_data;
+  utils::ReadFile(patch_file.path(), &patch_data);
+
+  AnnotatedOperation aop;
+  *aop.op.add_src_extents() = ExtentForRange(50, 10);
+  *aop.op.add_dst_extents() = ExtentForRange(500, 10);
+
+  diff_utils::PopulateXorOps(
+      &aop,
+      reinterpret_cast<const uint8_t*>(patch_data.data()),
+      patch_data.size());
+  ASSERT_EQ(aop.xor_ops.size(), 1UL) << "Only 1 block can possibly be XORed";
+  ASSERT_EQ(aop.xor_ops[0].src_extent().num_blocks(), 1UL);
+  ASSERT_EQ(aop.xor_ops[0].src_extent().start_block(), 51UL);
+  ASSERT_EQ(aop.xor_ops[0].src_offset(), 123UL);
+
+  ASSERT_EQ(aop.xor_ops[0].dst_extent().num_blocks(), 1UL);
+  ASSERT_EQ(aop.xor_ops[0].dst_extent().start_block(), 500UL);
+}
+
+TEST_F(DeltaDiffUtilsTest, XorOpsTargetNotAligned) {
+  ScopedTempFile patch_file;
+  bsdiff::BsdiffPatchWriter writer{patch_file.path()};
+  ASSERT_TRUE(writer.Init(kBlockSize * 10));
+  ASSERT_TRUE(writer.AddControlEntry(
+      ControlEntry(0, kBlockSize - 456, 123 + kBlockSize)));
+  ASSERT_TRUE(writer.AddControlEntry(ControlEntry(kBlockSize + 456, 0, 0)));
+  ASSERT_TRUE(writer.Close());
+
+  std::string patch_data;
+  utils::ReadFile(patch_file.path(), &patch_data);
+
+  AnnotatedOperation aop;
+  *aop.op.add_src_extents() = ExtentForRange(50, 10);
+  *aop.op.add_dst_extents() = ExtentForRange(500, 10);
+
+  diff_utils::PopulateXorOps(
+      &aop,
+      reinterpret_cast<const uint8_t*>(patch_data.data()),
+      patch_data.size());
+  ASSERT_EQ(aop.xor_ops.size(), 1UL) << "Only 1 block can possibly be XORed";
+  ASSERT_EQ(aop.xor_ops[0].src_extent().num_blocks(), 1UL);
+  ASSERT_EQ(aop.xor_ops[0].src_extent().start_block(), 51UL);
+  ASSERT_EQ(aop.xor_ops[0].src_offset(), 123UL + 456UL);
+
+  ASSERT_EQ(aop.xor_ops[0].dst_extent().num_blocks(), 1UL);
+  ASSERT_EQ(aop.xor_ops[0].dst_extent().start_block(), 501UL);
+}
+
+TEST_F(DeltaDiffUtilsTest, XorOpsStrided) {
+  ScopedTempFile patch_file;
+  bsdiff::BsdiffPatchWriter writer{patch_file.path()};
+  ASSERT_TRUE(writer.Init(kBlockSize * 10));
+  ASSERT_TRUE(writer.AddControlEntry(ControlEntry(0, kBlockSize - 456, 123)));
+  ASSERT_TRUE(
+      writer.AddControlEntry(ControlEntry(kBlockSize * 10 + 456, 0, 0)));
+  ASSERT_TRUE(writer.Close());
+
+  std::string patch_data;
+  utils::ReadFile(patch_file.path(), &patch_data);
+
+  AnnotatedOperation aop;
+  *aop.op.add_src_extents() = ExtentForRange(50, 5);
+  *aop.op.add_src_extents() = ExtentForRange(60, 5);
+
+  *aop.op.add_dst_extents() = ExtentForRange(500, 2);
+  *aop.op.add_dst_extents() = ExtentForRange(600, 2);
+  *aop.op.add_dst_extents() = ExtentForRange(700, 7);
+
+  diff_utils::PopulateXorOps(
+      &aop,
+      reinterpret_cast<const uint8_t*>(patch_data.data()),
+      patch_data.size());
+  ASSERT_EQ(aop.xor_ops.size(), 4UL);
+  for (const auto& op : aop.xor_ops) {
+    ASSERT_EQ(op.src_offset(), 123UL + 456UL);
+    LOG(INFO) << op.src_extent() << ", " << op.dst_extent();
+  }
+  ASSERT_EQ(aop.xor_ops[0].src_extent().num_blocks(), 2UL);
+  ASSERT_EQ(aop.xor_ops[0].src_extent().start_block(), 50UL);
+
+  ASSERT_EQ(aop.xor_ops[0].dst_extent().num_blocks(), 1UL);
+  ASSERT_EQ(aop.xor_ops[0].dst_extent().start_block(), 501UL);
+
+  ASSERT_EQ(aop.xor_ops[1].src_extent().num_blocks(), 3UL);
+  ASSERT_EQ(aop.xor_ops[1].src_extent().start_block(), 51UL);
+
+  ASSERT_EQ(aop.xor_ops[1].dst_extent().num_blocks(), 2UL);
+  ASSERT_EQ(aop.xor_ops[1].dst_extent().start_block(), 600UL);
+
+  ASSERT_EQ(aop.xor_ops[2].src_extent().num_blocks(), 3UL);
+  ASSERT_EQ(aop.xor_ops[2].src_extent().start_block(), 53UL);
+
+  ASSERT_EQ(aop.xor_ops[2].dst_extent().num_blocks(), 2UL);
+  ASSERT_EQ(aop.xor_ops[2].dst_extent().start_block(), 700UL);
+
+  ASSERT_EQ(aop.xor_ops[3].src_extent().num_blocks(), 6UL);
+  ASSERT_EQ(aop.xor_ops[3].src_extent().start_block(), 60UL);
+
+  ASSERT_EQ(aop.xor_ops[3].dst_extent().num_blocks(), 5UL);
+  ASSERT_EQ(aop.xor_ops[3].dst_extent().start_block(), 702UL);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/payload_generator/extent_utils.cc b/payload_generator/extent_utils.cc
index 2efef12..f4a9ff0 100644
--- a/payload_generator/extent_utils.cc
+++ b/payload_generator/extent_utils.cc
@@ -87,7 +87,8 @@
   }
 }
 
-string ExtentsToString(const vector<Extent>& extents) {
+template <typename Container>
+string ExtentsToStringTemplate(const Container& extents) {
   string ext_str;
   for (const Extent& e : extents)
     ext_str += base::StringPrintf("[%" PRIu64 ", %" PRIu64 "] ",
@@ -96,6 +97,15 @@
   return ext_str;
 }
 
+std::string ExtentsToString(const std::vector<Extent>& extents) {
+  return ExtentsToStringTemplate(extents);
+}
+
+std::string ExtentsToString(
+    const google::protobuf::RepeatedPtrField<Extent>& extents) {
+  return ExtentsToStringTemplate(extents);
+}
+
 void NormalizeExtents(vector<Extent>* extents) {
   vector<Extent> new_extents;
   for (const Extent& curr_ext : *extents) {
diff --git a/payload_generator/extent_utils.h b/payload_generator/extent_utils.h
index 7aa614a..2bd6626 100644
--- a/payload_generator/extent_utils.h
+++ b/payload_generator/extent_utils.h
@@ -22,6 +22,7 @@
 
 #include <base/logging.h>
 
+#include "google/protobuf/repeated_field.h"
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/update_metadata.pb.h"
 
@@ -63,6 +64,8 @@
 
 // Returns a string representing all extents in |extents|.
 std::string ExtentsToString(const std::vector<Extent>& extents);
+std::string ExtentsToString(
+    const google::protobuf::RepeatedPtrField<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.
@@ -124,6 +127,18 @@
 
 std::ostream& operator<<(std::ostream& out, const Extent& extent);
 
+template <typename Container>
+size_t GetNthBlock(const Container& extents, const size_t n) {
+  size_t cur_block_count = 0;
+  for (const auto& extent : extents) {
+    if (n - cur_block_count < extent.num_blocks()) {
+      return extent.start_block() + (n - cur_block_count);
+    }
+    cur_block_count += extent.num_blocks();
+  }
+  return std::numeric_limits<size_t>::max();
+}
+
 }  // namespace chromeos_update_engine
 
 #endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index b04fec0..1919a7e 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -425,6 +425,9 @@
       disable_vabc,
       false,
       "Whether to disable Virtual AB Compression when installing the OTA");
+  DEFINE_bool(enable_vabc_xor,
+              false,
+              "Whether to use Virtual AB Compression XOR feature");
   DEFINE_string(
       apex_info_file, "", "Path to META/apex_info.pb found in target build");
 
@@ -543,6 +546,8 @@
     payload_config.apex_info_file = FLAGS_apex_info_file;
   }
 
+  payload_config.enable_vabc_xor = FLAGS_enable_vabc_xor;
+
   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 "
diff --git a/payload_generator/merge_sequence_generator.cc b/payload_generator/merge_sequence_generator.cc
index 289e2f8..c5fd988 100644
--- a/payload_generator/merge_sequence_generator.cc
+++ b/payload_generator/merge_sequence_generator.cc
@@ -18,16 +18,22 @@
 
 #include <algorithm>
 
+#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/update_metadata.pb.h"
 
 namespace chromeos_update_engine {
 
 CowMergeOperation CreateCowMergeOperation(const Extent& src_extent,
-                                          const Extent& dst_extent) {
+                                          const Extent& dst_extent,
+                                          CowMergeOperation::Type op_type,
+                                          uint32_t src_offset) {
   CowMergeOperation ret;
-  ret.set_type(CowMergeOperation::COW_COPY);
+  ret.set_type(op_type);
   *ret.mutable_src_extent() = src_extent;
   *ret.mutable_dst_extent() = dst_extent;
+  ret.set_src_offset(src_offset);
   return ret;
 }
 
@@ -36,6 +42,17 @@
   os << "CowMergeOperation src extent: "
      << ExtentsToString({merge_operation.src_extent()})
      << ", dst extent: " << ExtentsToString({merge_operation.dst_extent()});
+  if (merge_operation.has_src_offset()) {
+    os << ", src offset: " << merge_operation.src_offset();
+  }
+  os << " op_type: ";
+  if (merge_operation.type() == CowMergeOperation::COW_COPY) {
+    os << "COW_COPY";
+  } else if (merge_operation.type() == CowMergeOperation::COW_XOR) {
+    os << "COW_XOR";
+  } else {
+    os << merge_operation.type();
+  }
   return os;
 }
 
@@ -56,12 +73,27 @@
   return abs_diff;
 }
 
+CowMergeOperation::Type GetCowOpType(InstallOperation::Type install_op_type) {
+  switch (install_op_type) {
+    case InstallOperation::SOURCE_COPY:
+      return CowMergeOperation::COW_COPY;
+    case InstallOperation::SOURCE_BSDIFF:
+    case InstallOperation::BROTLI_BSDIFF:
+    case InstallOperation::PUFFDIFF:
+      return CowMergeOperation::COW_XOR;
+    default:
+      CHECK(false) << "Unknown install op type: " << install_op_type;
+      return CowMergeOperation::COW_REPLACE;
+  }
+}
+
 void SplitSelfOverlapping(const Extent& src_extent,
                           const Extent& dst_extent,
                           std::vector<CowMergeOperation>* sequence) {
   CHECK_EQ(src_extent.num_blocks(), dst_extent.num_blocks());
   if (src_extent.start_block() == dst_extent.start_block()) {
-    sequence->emplace_back(CreateCowMergeOperation(src_extent, dst_extent));
+    sequence->emplace_back(CreateCowMergeOperation(
+        src_extent, dst_extent, CowMergeOperation::COW_COPY));
     return;
   }
 
@@ -71,51 +103,89 @@
     auto num_blocks = std::min<size_t>(diff, src_extent.num_blocks() - i);
     sequence->emplace_back(CreateCowMergeOperation(
         ExtentForRange(i + src_extent.start_block(), num_blocks),
-        ExtentForRange(i + dst_extent.start_block(), num_blocks)));
+        ExtentForRange(i + dst_extent.start_block(), num_blocks),
+        CowMergeOperation::COW_COPY));
   }
 }
 
+static bool ProcessXorOps(std::vector<CowMergeOperation>* sequence,
+                          const AnnotatedOperation& aop) {
+  const auto size_before = sequence->size();
+  sequence->insert(sequence->end(), aop.xor_ops.begin(), aop.xor_ops.end());
+  std::for_each(
+      sequence->begin() + size_before,
+      sequence->end(),
+      [](CowMergeOperation& op) {
+        CHECK_EQ(op.type(), CowMergeOperation::COW_XOR);
+        // If |src_offset| is greater than 0, then we are reading 1
+        // extra block at the end of src_extent. This dependency must
+        // be honored during merge sequence generation, or we can end
+        // up with a corrupted device after merge.
+        if (op.src_offset() > 0) {
+          if (op.src_extent().num_blocks() == op.dst_extent().num_blocks()) {
+            op.mutable_src_extent()->set_num_blocks(
+                op.src_extent().num_blocks() + 1);
+          }
+          CHECK_EQ(op.src_extent().num_blocks(),
+                   op.dst_extent().num_blocks() + 1);
+        }
+      });
+  return true;
+}
+
+static bool ProcessCopyOps(std::vector<CowMergeOperation>* sequence,
+                           const AnnotatedOperation& aop) {
+  CHECK_EQ(GetCowOpType(aop.op.type()), CowMergeOperation::COW_COPY);
+  if (aop.op.dst_extents().size() != 1) {
+    std::vector<Extent> out_extents;
+    ExtentsToVector(aop.op.dst_extents(), &out_extents);
+    LOG(ERROR)
+        << "The dst extents for source_copy are expected to be contiguous,"
+        << " dst extents: " << ExtentsToString(out_extents);
+    return false;
+  }
+  // Split the source extents.
+  size_t used_blocks = 0;
+  for (const auto& src_extent : aop.op.src_extents()) {
+    // The dst_extent in the merge sequence will be a subset of
+    // InstallOperation's dst_extent. This will simplify the OTA -> COW
+    // conversion when we install the payload.
+    Extent dst_extent =
+        ExtentForRange(aop.op.dst_extents(0).start_block() + used_blocks,
+                       src_extent.num_blocks());
+    // Self-overlapping operation, must split into multiple non
+    // self-overlapping ops
+    if (ExtentRanges::ExtentsOverlap(src_extent, dst_extent)) {
+      SplitSelfOverlapping(src_extent, dst_extent, sequence);
+    } else {
+      sequence->emplace_back(CreateCowMergeOperation(
+          src_extent, dst_extent, CowMergeOperation::COW_COPY));
+    }
+    used_blocks += src_extent.num_blocks();
+  }
+
+  if (used_blocks != aop.op.dst_extents(0).num_blocks()) {
+    LOG(ERROR) << "Number of blocks in src extents doesn't equal to the"
+               << " ones in the dst extents, src blocks " << used_blocks
+               << ", dst blocks " << aop.op.dst_extents(0).num_blocks();
+    return false;
+  }
+  return true;
+}
+
 std::unique_ptr<MergeSequenceGenerator> MergeSequenceGenerator::Create(
     const std::vector<AnnotatedOperation>& aops) {
   std::vector<CowMergeOperation> sequence;
+
   for (const auto& aop : aops) {
-    // Only handle SOURCE_COPY now for the cow size optimization.
-    if (aop.op.type() != InstallOperation::SOURCE_COPY) {
-      continue;
-    }
-    if (aop.op.dst_extents().size() != 1) {
-      std::vector<Extent> out_extents;
-      ExtentsToVector(aop.op.dst_extents(), &out_extents);
-      LOG(ERROR) << "The dst extents for source_copy expects to be contiguous,"
-                 << " dst extents: " << ExtentsToString(out_extents);
-      return nullptr;
-    }
-
-    // Split the source extents.
-    size_t used_blocks = 0;
-    for (const auto& src_extent : aop.op.src_extents()) {
-      // The dst_extent in the merge sequence will be a subset of
-      // InstallOperation's dst_extent. This will simplify the OTA -> COW
-      // conversion when we install the payload.
-      Extent dst_extent =
-          ExtentForRange(aop.op.dst_extents(0).start_block() + used_blocks,
-                         src_extent.num_blocks());
-
-      // Self-overlapping SOURCE_COPY, must split into multiple non
-      // self-overlapping ops
-      if (ExtentRanges::ExtentsOverlap(src_extent, dst_extent)) {
-        SplitSelfOverlapping(src_extent, dst_extent, &sequence);
-      } else {
-        sequence.emplace_back(CreateCowMergeOperation(src_extent, dst_extent));
+    if (aop.op.type() == InstallOperation::SOURCE_COPY) {
+      if (!ProcessCopyOps(&sequence, aop)) {
+        return nullptr;
       }
-      used_blocks += src_extent.num_blocks();
-    }
-
-    if (used_blocks != aop.op.dst_extents(0).num_blocks()) {
-      LOG(ERROR) << "Number of blocks in src extents doesn't equal to the"
-                 << " ones in the dst extents, src blocks " << used_blocks
-                 << ", dst blocks " << aop.op.dst_extents(0).num_blocks();
-      return nullptr;
+    } else if (!aop.xor_ops.empty()) {
+      if (!ProcessXorOps(&sequence, aop)) {
+        return nullptr;
+      }
     }
   }
 
@@ -167,7 +237,7 @@
       }
       auto ret = merge_after.emplace(op, std::move(operations));
       // Check the insertion indeed happens.
-      CHECK(ret.second);
+      CHECK(ret.second) << op;
     }
   }
 
@@ -221,9 +291,9 @@
     for (const auto& op : free_operations) {
       incoming_edges.erase(op);
 
-      // Now that this particular operation is merged, other operations blocked
-      // by this one may be free. Decrement the count of blocking operations,
-      // and set up the free operations for the next iteration.
+      // Now that this particular operation is merged, other operations
+      // blocked by this one may be free. Decrement the count of blocking
+      // operations, and set up the free operations for the next iteration.
       for (const auto& blocked : merge_after[op]) {
         auto it = incoming_edges.find(blocked);
         if (it == incoming_edges.end()) {
@@ -271,6 +341,7 @@
   LOG(INFO) << "Blocks in merge sequence " << blocks_in_sequence
             << ", blocks in raw " << blocks_in_raw;
   if (!ValidateSequence(merge_sequence)) {
+    LOG(ERROR) << "Invalid Sequence";
     return false;
   }
 
@@ -283,6 +354,16 @@
   LOG(INFO) << "Validating merge sequence";
   ExtentRanges visited;
   for (const auto& op : sequence) {
+    // If |src_offset| is greater than zero, dependency should include 1 extra
+    // block at end of src_extent, as the OP actually references data past
+    // original src_extent.
+    if (op.src_offset() > 0) {
+      CHECK_EQ(op.src_extent().num_blocks(), op.dst_extent().num_blocks() + 1)
+          << op;
+    } else {
+      CHECK_EQ(op.src_extent().num_blocks(), op.dst_extent().num_blocks())
+          << op;
+    }
     if (visited.OverlapsWithExtent(op.src_extent())) {
       LOG(ERROR) << "Transfer violates the merge sequence " << op
                  << "Visited extent ranges: ";
diff --git a/payload_generator/merge_sequence_generator.h b/payload_generator/merge_sequence_generator.h
index 385fcc3..d3b5eb9 100644
--- a/payload_generator/merge_sequence_generator.h
+++ b/payload_generator/merge_sequence_generator.h
@@ -31,7 +31,9 @@
 namespace chromeos_update_engine {
 // Constructs CowMergeOperation from src & dst extents
 CowMergeOperation CreateCowMergeOperation(const Extent& src_extent,
-                                          const Extent& dst_extent);
+                                          const Extent& dst_extent,
+                                          CowMergeOperation::Type op_type,
+                                          uint32_t src_offset = 0);
 
 // Comparator for CowMergeOperation.
 bool operator<(const CowMergeOperation& op1, const CowMergeOperation& op2);
@@ -67,7 +69,7 @@
   bool FindDependency(std::map<CowMergeOperation, std::set<CowMergeOperation>>*
                           merge_after) const;
   // The list of CowMergeOperations to sort.
-  std::vector<CowMergeOperation> operations_;
+  const std::vector<CowMergeOperation> operations_;
 };
 
 void SplitSelfOverlapping(const Extent& src_extent,
diff --git a/payload_generator/merge_sequence_generator_unittest.cc b/payload_generator/merge_sequence_generator_unittest.cc
index b8507ed..86d4fdd 100644
--- a/payload_generator/merge_sequence_generator_unittest.cc
+++ b/payload_generator/merge_sequence_generator_unittest.cc
@@ -20,11 +20,18 @@
 #include <gtest/gtest.h>
 
 #include "update_engine/payload_consumer/payload_constants.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/merge_sequence_generator.h"
+#include "update_engine/update_metadata.pb.h"
 
 namespace chromeos_update_engine {
+CowMergeOperation CreateCowMergeOperation(const Extent& src_extent,
+                                          const Extent& dst_extent) {
+  return CreateCowMergeOperation(
+      src_extent, dst_extent, CowMergeOperation::COW_COPY);
+}
 class MergeSequenceGeneratorTest : public ::testing::Test {
  protected:
   void VerifyTransfers(MergeSequenceGenerator* generator,
@@ -51,7 +58,7 @@
 };
 
 TEST_F(MergeSequenceGeneratorTest, Create) {
-  std::vector<AnnotatedOperation> aops{{"file1", {}}, {"file2", {}}};
+  std::vector<AnnotatedOperation> aops{{"file1", {}, {}}, {"file2", {}, {}}};
   aops[0].op.set_type(InstallOperation::SOURCE_COPY);
   *aops[0].op.add_src_extents() = ExtentForRange(10, 10);
   *aops[0].op.add_dst_extents() = ExtentForRange(30, 10);
@@ -81,7 +88,7 @@
   *(op.add_src_extents()) = ExtentForRange(8, 4);
   *(op.add_dst_extents()) = ExtentForRange(10, 8);
 
-  AnnotatedOperation aop{"file1", op};
+  AnnotatedOperation aop{"file1", op, {}};
   auto generator = MergeSequenceGenerator::Create({aop});
   ASSERT_TRUE(generator);
   std::vector<CowMergeOperation> expected = {
@@ -183,8 +190,8 @@
       CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)),
   };
 
-  // file 1,2,3 form a cycle. And file3, whose dst ext has smallest offset, will
-  // be converted to raw blocks
+  // file 1,2,3 form a cycle. And file3, whose dst ext has smallest offset,
+  // will be converted to raw blocks
   std::vector<CowMergeOperation> expected{
       transfers[3], transfers[1], transfers[0]};
   GenerateSequence(transfers, expected);
@@ -247,4 +254,174 @@
   ValidateSplitSequence(b, a);
 }
 
+TEST_F(MergeSequenceGeneratorTest, GenerateSequenceWithXor) {
+  std::vector<CowMergeOperation> transfers = {
+      // cycle 1
+      CreateCowMergeOperation(ExtentForRange(10, 10),
+                              ExtentForRange(25, 10),
+                              CowMergeOperation::COW_XOR),
+      CreateCowMergeOperation(ExtentForRange(24, 5), ExtentForRange(35, 5)),
+      CreateCowMergeOperation(ExtentForRange(30, 10),
+                              ExtentForRange(15, 10),
+                              CowMergeOperation::COW_XOR),
+      // cycle 2
+      CreateCowMergeOperation(ExtentForRange(55, 10), ExtentForRange(60, 10)),
+      CreateCowMergeOperation(ExtentForRange(60, 10),
+                              ExtentForRange(70, 10),
+                              CowMergeOperation::COW_XOR),
+      CreateCowMergeOperation(ExtentForRange(70, 10), ExtentForRange(55, 10)),
+  };
+
+  // file 3, 6 will be converted to raw.
+  std::vector<CowMergeOperation> expected{
+      transfers[1], transfers[0], transfers[4], transfers[3]};
+  GenerateSequence(transfers, expected);
+}
+
+TEST_F(MergeSequenceGeneratorTest, CreateGeneratorWithXor) {
+  std::vector<AnnotatedOperation> aops;
+  auto& aop = aops.emplace_back();
+  aop.op.set_type(InstallOperation::SOURCE_BSDIFF);
+  *aop.op.mutable_src_extents()->Add() = ExtentForRange(10, 5);
+  *aop.op.mutable_dst_extents()->Add() = ExtentForRange(20, 5);
+  auto& xor_map = aop.xor_ops;
+  {
+    // xor_map[i] = i * kBlockSize + 123;
+    auto& op = xor_map.emplace_back();
+    *op.mutable_src_extent() = ExtentForRange(10, 5);
+    *op.mutable_dst_extent() = ExtentForRange(20, 5);
+    op.set_src_offset(123);
+    op.set_type(CowMergeOperation::COW_XOR);
+  }
+  auto generator = MergeSequenceGenerator::Create(aops);
+  ASSERT_NE(generator, nullptr);
+  std::vector<CowMergeOperation> sequence;
+  ASSERT_TRUE(generator->Generate(&sequence));
+  ASSERT_EQ(sequence.size(), 1UL);
+  ASSERT_EQ(sequence[0].src_extent().start_block(), 10UL);
+  ASSERT_EQ(sequence[0].dst_extent().start_block(), 20UL);
+  ASSERT_EQ(sequence[0].src_extent().num_blocks(), 6UL);
+  ASSERT_EQ(sequence[0].dst_extent().num_blocks(), 5UL);
+  ASSERT_EQ(sequence[0].type(), CowMergeOperation::COW_XOR);
+  ASSERT_EQ(sequence[0].src_offset(), 123UL);
+
+  ASSERT_TRUE(generator->ValidateSequence(sequence));
+}
+
+TEST_F(MergeSequenceGeneratorTest, CreateGeneratorWithXorMultipleExtents) {
+  std::vector<AnnotatedOperation> aops;
+  auto& aop = aops.emplace_back();
+  aop.op.set_type(InstallOperation::SOURCE_BSDIFF);
+  *aop.op.mutable_src_extents()->Add() = ExtentForRange(10, 10);
+  *aop.op.mutable_dst_extents()->Add() = ExtentForRange(30, 5);
+  *aop.op.mutable_dst_extents()->Add() = ExtentForRange(45, 5);
+  auto& xor_map = aop.xor_ops;
+  {
+    // xor_map[i] = i * kBlockSize + 123;
+    auto& op = xor_map.emplace_back();
+    *op.mutable_src_extent() = ExtentForRange(10, 5);
+    *op.mutable_dst_extent() = ExtentForRange(30, 5);
+    op.set_src_offset(123);
+    op.set_type(CowMergeOperation::COW_XOR);
+  }
+  {
+    // xor_map[i] = i * kBlockSize + 123;
+    auto& op = xor_map.emplace_back();
+    *op.mutable_src_extent() = ExtentForRange(15, 5);
+    *op.mutable_dst_extent() = ExtentForRange(45, 5);
+    op.set_src_offset(123);
+    op.set_type(CowMergeOperation::COW_XOR);
+  }
+  auto generator = MergeSequenceGenerator::Create(aops);
+  ASSERT_NE(generator, nullptr);
+  std::vector<CowMergeOperation> sequence;
+  ASSERT_TRUE(generator->Generate(&sequence));
+  ASSERT_EQ(sequence.size(), 2UL);
+  ASSERT_EQ(sequence[0].src_extent().start_block(), 10UL);
+  ASSERT_EQ(sequence[0].dst_extent().start_block(), 30UL);
+  ASSERT_EQ(sequence[0].src_extent().num_blocks(), 6UL);
+  ASSERT_EQ(sequence[0].dst_extent().num_blocks(), 5UL);
+  ASSERT_EQ(sequence[0].type(), CowMergeOperation::COW_XOR);
+  ASSERT_EQ(sequence[0].src_offset(), 123UL);
+
+  ASSERT_EQ(sequence[1].src_extent().start_block(), 15UL);
+  ASSERT_EQ(sequence[1].dst_extent().start_block(), 45UL);
+  ASSERT_EQ(sequence[1].src_extent().num_blocks(), 6UL);
+  ASSERT_EQ(sequence[1].dst_extent().num_blocks(), 5UL);
+  ASSERT_EQ(sequence[1].type(), CowMergeOperation::COW_XOR);
+  ASSERT_EQ(sequence[1].src_offset(), 123UL);
+
+  ASSERT_TRUE(generator->ValidateSequence(sequence));
+}
+
+TEST_F(MergeSequenceGeneratorTest, CreateGeneratorXorAppendBlock) {
+  std::vector<AnnotatedOperation> aops;
+  auto& aop = aops.emplace_back();
+  aop.op.set_type(InstallOperation::SOURCE_BSDIFF);
+  *aop.op.mutable_src_extents()->Add() = ExtentForRange(10, 10);
+  *aop.op.mutable_dst_extents()->Add() = ExtentForRange(20, 10);
+  auto& xor_map = aop.xor_ops;
+  {
+    auto& op = xor_map.emplace_back();
+    *op.mutable_src_extent() = ExtentForRange(10, 5);
+    *op.mutable_dst_extent() = ExtentForRange(20, 5);
+    op.set_type(CowMergeOperation::COW_XOR);
+  }
+  {
+    auto& op = xor_map.emplace_back();
+    *op.mutable_src_extent() = ExtentForRange(15, 5);
+    *op.mutable_dst_extent() = ExtentForRange(25, 5);
+    op.set_src_offset(123);
+    op.set_type(CowMergeOperation::COW_XOR);
+  }
+  auto generator = MergeSequenceGenerator::Create(aops);
+  ASSERT_NE(generator, nullptr);
+  std::vector<CowMergeOperation> sequence;
+  ASSERT_TRUE(generator->Generate(&sequence));
+  ASSERT_EQ(sequence.size(), 2UL);
+  ASSERT_EQ(sequence[0].src_extent().start_block(), 15UL);
+  ASSERT_EQ(sequence[0].dst_extent().start_block(), 25UL);
+  ASSERT_EQ(sequence[0].src_extent().num_blocks(), 6UL);
+  ASSERT_EQ(sequence[0].dst_extent().num_blocks(), 5UL);
+  ASSERT_EQ(sequence[0].type(), CowMergeOperation::COW_XOR);
+  ASSERT_EQ(sequence[0].src_offset(), 123UL);
+
+  ASSERT_EQ(sequence[1].src_extent().start_block(), 10UL);
+  ASSERT_EQ(sequence[1].dst_extent().start_block(), 20UL);
+  ASSERT_EQ(sequence[1].src_extent().num_blocks(), 5UL);
+  ASSERT_EQ(sequence[1].dst_extent().num_blocks(), 5UL);
+  ASSERT_EQ(sequence[1].type(), CowMergeOperation::COW_XOR);
+
+  ASSERT_TRUE(generator->ValidateSequence(sequence));
+}
+
+TEST_F(MergeSequenceGeneratorTest, CreateGeneratorXorAlreadyPlusOne) {
+  std::vector<AnnotatedOperation> aops;
+  auto& aop = aops.emplace_back();
+  aop.op.set_type(InstallOperation::SOURCE_BSDIFF);
+  *aop.op.mutable_src_extents()->Add() = ExtentForRange(10, 10);
+  *aop.op.mutable_dst_extents()->Add() = ExtentForRange(20, 10);
+  auto& xor_map = aop.xor_ops;
+  {
+    auto& op = xor_map.emplace_back();
+    *op.mutable_src_extent() = ExtentForRange(15, 6);
+    *op.mutable_dst_extent() = ExtentForRange(25, 5);
+    op.set_src_offset(123);
+    op.set_type(CowMergeOperation::COW_XOR);
+  }
+  auto generator = MergeSequenceGenerator::Create(aops);
+  ASSERT_NE(generator, nullptr);
+  std::vector<CowMergeOperation> sequence;
+  ASSERT_TRUE(generator->Generate(&sequence));
+  ASSERT_EQ(sequence.size(), 1UL);
+  ASSERT_EQ(sequence[0].src_extent().start_block(), 15UL);
+  ASSERT_EQ(sequence[0].dst_extent().start_block(), 25UL);
+  ASSERT_EQ(sequence[0].src_extent().num_blocks(), 6UL);
+  ASSERT_EQ(sequence[0].dst_extent().num_blocks(), 5UL);
+  ASSERT_EQ(sequence[0].type(), CowMergeOperation::COW_XOR);
+  ASSERT_EQ(sequence[0].src_offset(), 123UL);
+
+  ASSERT_TRUE(generator->ValidateSequence(sequence));
+}
+
 }  // namespace chromeos_update_engine
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
index 9c8c59f..c15ac78 100644
--- a/payload_generator/payload_generation_config.h
+++ b/payload_generator/payload_generation_config.h
@@ -233,6 +233,9 @@
 
   // Path to apex_info.pb, extracted from target_file.zip
   std::string apex_info_file;
+
+  // Whether to enable VABC xor op
+  bool enable_vabc_xor = false;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload
index 746cefb..f36e995 100755
--- a/scripts/brillo_update_payload
+++ b/scripts/brillo_update_payload
@@ -204,6 +204,8 @@
   DEFINE_string full_boot "" "Will include full boot image"
   DEFINE_string disable_vabc "" \
     "Optional: Disables Virtual AB Compression when installing the OTA"
+  DEFINE_string enable_vabc_xor "" \
+    "Optional: Enable the use of Virtual AB Compression XOR feature"
 fi
 if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then
   DEFINE_string unsigned_payload "" "Path to the input unsigned payload."
@@ -710,6 +712,11 @@
     fi
   fi
 
+  if [[ -n "${FLAGS_enable_vabc_xor}" ]]; then
+    GENERATOR_ARGS+=(
+      --enable_vabc_xor="${FLAGS_enable_vabc_xor}" )
+  fi
+
   if [[ -n "${FLAGS_disable_vabc}" ]]; then
     GENERATOR_ARGS+=(
       --disable_vabc="${FLAGS_disable_vabc}" )
diff --git a/scripts/update_device.py b/scripts/update_device.py
index f672cda..165bc97 100755
--- a/scripts/update_device.py
+++ b/scripts/update_device.py
@@ -30,6 +30,7 @@
 import sys
 import struct
 import tempfile
+import time
 import threading
 import xml.etree.ElementTree
 import zipfile
@@ -441,6 +442,8 @@
   logging.basicConfig(
       level=logging.WARNING if args.no_verbose else logging.INFO)
 
+  start_time = time.perf_counter()
+
   dut = AdbHost(args.s)
 
   server_thread = None
@@ -548,6 +551,7 @@
     for cmd in finalize_cmds:
       dut.adb(cmd, 5)
 
+  logging.info('Update took %.3f seconds', (time.perf_counter() - start_time))
   return 0
 
 
diff --git a/scripts/update_payload/payload.py b/scripts/update_payload/payload.py
index 998703a..2de8694 100644
--- a/scripts/update_payload/payload.py
+++ b/scripts/update_payload/payload.py
@@ -127,7 +127,8 @@
         self.payload_file = zfp.open("payload.bin", "r")
     elif isinstance(payload_file, str):
       payload_fp = open(payload_file, "rb")
-      payload_bytes = mmap.mmap(payload_fp.fileno(), 0, access=mmap.ACCESS_READ)
+      payload_bytes = mmap.mmap(
+          payload_fp.fileno(), 0, access=mmap.ACCESS_READ)
       self.payload_file = io.BytesIO(payload_bytes)
     else:
       self.payload_file = payload_file
@@ -138,6 +139,7 @@
     self.manifest = None
     self.data_offset = None
     self.metadata_signature = None
+    self.payload_signature = None
     self.metadata_size = None
 
   def _ReadHeader(self):
@@ -235,6 +237,13 @@
     self.metadata_size = self.header.size + self.header.manifest_len
     self.data_offset = self.metadata_size + self.header.metadata_signature_len
 
+    if self.manifest.signatures_offset and self.manifest.signatures_size:
+      payload_signature_blob = self.ReadDataBlob(
+          self.manifest.signatures_offset, self.manifest.signatures_size)
+      payload_signature = update_metadata_pb2.Signatures()
+      payload_signature.ParseFromString(payload_signature_blob)
+      self.payload_signature = payload_signature
+
     self.is_init = True
 
   def _AssertInit(self):
diff --git a/scripts/update_payload/update_metadata_pb2.py b/scripts/update_payload/update_metadata_pb2.py
index 9aef9f2..a8f1bff 100644
--- a/scripts/update_payload/update_metadata_pb2.py
+++ b/scripts/update_payload/update_metadata_pb2.py
@@ -20,7 +20,7 @@
   package='chromeos_update_engine',
   syntax='proto2',
   serialized_options=_b('H\003'),
-  serialized_pb=_b('\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\"\x9f\x01\n\nSignatures\x12@\n\nsignatures\x18\x01 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x1aO\n\tSignature\x12\x13\n\x07version\x18\x01 \x01(\rB\x02\x18\x01\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x1f\n\x17unpadded_signature_size\x18\x03 \x01(\x07\"+\n\rPartitionInfo\x12\x0c\n\x04size\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"\x8f\x01\n\tImageInfo\x12\x11\n\x05\x62oard\x18\x01 \x01(\tB\x02\x18\x01\x12\x0f\n\x03key\x18\x02 \x01(\tB\x02\x18\x01\x12\x13\n\x07\x63hannel\x18\x03 \x01(\tB\x02\x18\x01\x12\x13\n\x07version\x18\x04 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_channel\x18\x05 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_version\x18\x06 \x01(\tB\x02\x18\x01\"\xee\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(\x04\x12\x13\n\x0b\x64\x61ta_length\x18\x03 \x01(\x04\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\"\xad\x01\n\x04Type\x12\x0b\n\x07REPLACE\x10\x00\x12\x0e\n\nREPLACE_BZ\x10\x01\x12\x0c\n\x04MOVE\x10\x02\x1a\x02\x08\x01\x12\x0e\n\x06\x42SDIFF\x10\x03\x1a\x02\x08\x01\x12\x0f\n\x0bSOURCE_COPY\x10\x04\x12\x11\n\rSOURCE_BSDIFF\x10\x05\x12\x0e\n\nREPLACE_XZ\x10\x08\x12\x08\n\x04ZERO\x10\x06\x12\x0b\n\x07\x44ISCARD\x10\x07\x12\x11\n\rBROTLI_BSDIFF\x10\n\x12\x0c\n\x08PUFFDIFF\x10\t\"\xcf\x01\n\x11\x43owMergeOperation\x12<\n\x04type\x18\x01 \x01(\x0e\x32..chromeos_update_engine.CowMergeOperation.Type\x12\x32\n\nsrc_extent\x18\x02 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\ndst_extent\x18\x03 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\"\x14\n\x04Type\x12\x0c\n\x08\x43OW_COPY\x10\x00\"\xc8\x06\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\x12\x1c\n\x14postinstall_optional\x18\t \x01(\x08\x12=\n\x15hash_tree_data_extent\x18\n \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x38\n\x10hash_tree_extent\x18\x0b \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x1b\n\x13hash_tree_algorithm\x18\x0c \x01(\t\x12\x16\n\x0ehash_tree_salt\x18\r \x01(\x0c\x12\x37\n\x0f\x66\x65\x63_data_extent\x18\x0e \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\nfec_extent\x18\x0f \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x14\n\tfec_roots\x18\x10 \x01(\r:\x01\x32\x12\x0f\n\x07version\x18\x11 \x01(\t\x12\x43\n\x10merge_operations\x18\x12 \x03(\x0b\x32).chromeos_update_engine.CowMergeOperation\x12\x19\n\x11\x65stimate_cow_size\x18\x13 \x01(\x04\"L\n\x15\x44ynamicPartitionGroup\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0c\n\x04size\x18\x02 \x01(\x04\x12\x17\n\x0fpartition_names\x18\x03 \x03(\t\"\xa9\x01\n\x18\x44ynamicPartitionMetadata\x12=\n\x06groups\x18\x01 \x03(\x0b\x32-.chromeos_update_engine.DynamicPartitionGroup\x12\x18\n\x10snapshot_enabled\x18\x02 \x01(\x08\x12\x14\n\x0cvabc_enabled\x18\x03 \x01(\x08\x12\x1e\n\x16vabc_compression_param\x18\x04 \x01(\t\"c\n\x08\x41pexInfo\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x03\x12\x15\n\ris_compressed\x18\x03 \x01(\x08\x12\x19\n\x11\x64\x65\x63ompressed_size\x18\x04 \x01(\x03\"C\n\x0c\x41pexMetadata\x12\x33\n\tapex_info\x18\x01 \x03(\x0b\x32 .chromeos_update_engine.ApexInfo\"\x9e\x07\n\x14\x44\x65ltaArchiveManifest\x12H\n\x12install_operations\x18\x01 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12O\n\x19kernel_install_operations\x18\x02 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\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\x42\n\x0fold_kernel_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_kernel_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fold_rootfs_info\x18\x08 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_rootfs_info\x18\t \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12=\n\x0eold_image_info\x18\n \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\x12=\n\x0enew_image_info\x18\x0b \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\x12\x18\n\rminor_version\x18\x0c \x01(\r:\x01\x30\x12;\n\npartitions\x18\r \x03(\x0b\x32\'.chromeos_update_engine.PartitionUpdate\x12\x15\n\rmax_timestamp\x18\x0e \x01(\x03\x12T\n\x1a\x64ynamic_partition_metadata\x18\x0f \x01(\x0b\x32\x30.chromeos_update_engine.DynamicPartitionMetadata\x12\x16\n\x0epartial_update\x18\x10 \x01(\x08\x12\x33\n\tapex_info\x18\x11 \x03(\x0b\x32 .chromeos_update_engine.ApexInfoB\x02H\x03')
+  serialized_pb=_b('\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\"\x9f\x01\n\nSignatures\x12@\n\nsignatures\x18\x01 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x1aO\n\tSignature\x12\x13\n\x07version\x18\x01 \x01(\rB\x02\x18\x01\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x1f\n\x17unpadded_signature_size\x18\x03 \x01(\x07\"+\n\rPartitionInfo\x12\x0c\n\x04size\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"\x8f\x01\n\tImageInfo\x12\x11\n\x05\x62oard\x18\x01 \x01(\tB\x02\x18\x01\x12\x0f\n\x03key\x18\x02 \x01(\tB\x02\x18\x01\x12\x13\n\x07\x63hannel\x18\x03 \x01(\tB\x02\x18\x01\x12\x13\n\x07version\x18\x04 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_channel\x18\x05 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_version\x18\x06 \x01(\tB\x02\x18\x01\"\xee\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(\x04\x12\x13\n\x0b\x64\x61ta_length\x18\x03 \x01(\x04\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\"\xad\x01\n\x04Type\x12\x0b\n\x07REPLACE\x10\x00\x12\x0e\n\nREPLACE_BZ\x10\x01\x12\x0c\n\x04MOVE\x10\x02\x1a\x02\x08\x01\x12\x0e\n\x06\x42SDIFF\x10\x03\x1a\x02\x08\x01\x12\x0f\n\x0bSOURCE_COPY\x10\x04\x12\x11\n\rSOURCE_BSDIFF\x10\x05\x12\x0e\n\nREPLACE_XZ\x10\x08\x12\x08\n\x04ZERO\x10\x06\x12\x0b\n\x07\x44ISCARD\x10\x07\x12\x11\n\rBROTLI_BSDIFF\x10\n\x12\x0c\n\x08PUFFDIFF\x10\t\"\x81\x02\n\x11\x43owMergeOperation\x12<\n\x04type\x18\x01 \x01(\x0e\x32..chromeos_update_engine.CowMergeOperation.Type\x12\x32\n\nsrc_extent\x18\x02 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\ndst_extent\x18\x03 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\nsrc_offset\x18\x04 \x01(\x04\"2\n\x04Type\x12\x0c\n\x08\x43OW_COPY\x10\x00\x12\x0b\n\x07\x43OW_XOR\x10\x01\x12\x0f\n\x0b\x43OW_REPLACE\x10\x02\"\xc8\x06\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\x12\x1c\n\x14postinstall_optional\x18\t \x01(\x08\x12=\n\x15hash_tree_data_extent\x18\n \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x38\n\x10hash_tree_extent\x18\x0b \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x1b\n\x13hash_tree_algorithm\x18\x0c \x01(\t\x12\x16\n\x0ehash_tree_salt\x18\r \x01(\x0c\x12\x37\n\x0f\x66\x65\x63_data_extent\x18\x0e \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\nfec_extent\x18\x0f \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x14\n\tfec_roots\x18\x10 \x01(\r:\x01\x32\x12\x0f\n\x07version\x18\x11 \x01(\t\x12\x43\n\x10merge_operations\x18\x12 \x03(\x0b\x32).chromeos_update_engine.CowMergeOperation\x12\x19\n\x11\x65stimate_cow_size\x18\x13 \x01(\x04\"L\n\x15\x44ynamicPartitionGroup\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0c\n\x04size\x18\x02 \x01(\x04\x12\x17\n\x0fpartition_names\x18\x03 \x03(\t\"\xd5\x01\n\x18\x44ynamicPartitionMetadata\x12=\n\x06groups\x18\x01 \x03(\x0b\x32-.chromeos_update_engine.DynamicPartitionGroup\x12\x18\n\x10snapshot_enabled\x18\x02 \x01(\x08\x12\x14\n\x0cvabc_enabled\x18\x03 \x01(\x08\x12\x1e\n\x16vabc_compression_param\x18\x04 \x01(\t\x12\x13\n\x0b\x63ow_version\x18\x05 \x01(\r\x12\x15\n\rvabc_optional\x18\x06 \x01(\x08\"c\n\x08\x41pexInfo\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x03\x12\x15\n\ris_compressed\x18\x03 \x01(\x08\x12\x19\n\x11\x64\x65\x63ompressed_size\x18\x04 \x01(\x03\"C\n\x0c\x41pexMetadata\x12\x33\n\tapex_info\x18\x01 \x03(\x0b\x32 .chromeos_update_engine.ApexInfo\"\x9e\x07\n\x14\x44\x65ltaArchiveManifest\x12H\n\x12install_operations\x18\x01 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12O\n\x19kernel_install_operations\x18\x02 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\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\x42\n\x0fold_kernel_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_kernel_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fold_rootfs_info\x18\x08 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_rootfs_info\x18\t \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12=\n\x0eold_image_info\x18\n \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\x12=\n\x0enew_image_info\x18\x0b \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\x12\x18\n\rminor_version\x18\x0c \x01(\r:\x01\x30\x12;\n\npartitions\x18\r \x03(\x0b\x32\'.chromeos_update_engine.PartitionUpdate\x12\x15\n\rmax_timestamp\x18\x0e \x01(\x03\x12T\n\x1a\x64ynamic_partition_metadata\x18\x0f \x01(\x0b\x32\x30.chromeos_update_engine.DynamicPartitionMetadata\x12\x16\n\x0epartial_update\x18\x10 \x01(\x08\x12\x33\n\tapex_info\x18\x11 \x03(\x0b\x32 .chromeos_update_engine.ApexInfoB\x02H\x03')
 )
 
 
@@ -93,11 +93,19 @@
       name='COW_COPY', index=0, number=0,
       serialized_options=None,
       type=None),
+    _descriptor.EnumValueDescriptor(
+      name='COW_XOR', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='COW_REPLACE', index=2, number=2,
+      serialized_options=None,
+      type=None),
   ],
   containing_type=None,
   serialized_options=None,
-  serialized_start=1138,
-  serialized_end=1158,
+  serialized_start=1158,
+  serialized_end=1208,
 )
 _sym_db.RegisterEnumDescriptor(_COWMERGEOPERATION_TYPE)
 
@@ -435,6 +443,13 @@
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='src_offset', full_name='chromeos_update_engine.CowMergeOperation.src_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,
+      serialized_options=None, file=DESCRIPTOR),
   ],
   extensions=[
   ],
@@ -449,7 +464,7 @@
   oneofs=[
   ],
   serialized_start=951,
-  serialized_end=1158,
+  serialized_end=1208,
 )
 
 
@@ -605,8 +620,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1161,
-  serialized_end=2001,
+  serialized_start=1211,
+  serialized_end=2051,
 )
 
 
@@ -650,8 +665,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2003,
-  serialized_end=2079,
+  serialized_start=2053,
+  serialized_end=2129,
 )
 
 
@@ -690,6 +705,20 @@
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='cow_version', full_name='chromeos_update_engine.DynamicPartitionMetadata.cow_version', index=4,
+      number=5, 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,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='vabc_optional', full_name='chromeos_update_engine.DynamicPartitionMetadata.vabc_optional', index=5,
+      number=6, 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,
+      serialized_options=None, file=DESCRIPTOR),
   ],
   extensions=[
   ],
@@ -702,8 +731,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2082,
-  serialized_end=2251,
+  serialized_start=2132,
+  serialized_end=2345,
 )
 
 
@@ -754,8 +783,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2253,
-  serialized_end=2352,
+  serialized_start=2347,
+  serialized_end=2446,
 )
 
 
@@ -785,8 +814,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2354,
-  serialized_end=2421,
+  serialized_start=2448,
+  serialized_end=2515,
 )
 
 
@@ -928,8 +957,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2424,
-  serialized_end=3350,
+  serialized_start=2518,
+  serialized_end=3444,
 )
 
 _SIGNATURES_SIGNATURE.containing_type = _SIGNATURES
diff --git a/update_metadata.proto b/update_metadata.proto
index 93e4e2e..533f6f0 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -233,12 +233,22 @@
 // read after write, similar to the inplace update schema.
 message CowMergeOperation {
   enum Type {
-    COW_COPY = 0;  // identical blocks
+    COW_COPY = 0;     // identical blocks
+    COW_XOR = 1;      // used when src/dst blocks are highly similar
+    COW_REPLACE = 2;  // Raw replace operation
   }
   optional Type type = 1;
 
   optional Extent src_extent = 2;
   optional Extent dst_extent = 3;
+  // For COW_XOR, source location might be unaligned, so this field is in range
+  // [0, block_size), representing how much should the src_extent shift toward
+  // larger block number. If this field is non-zero, then src_extent will
+  // include 1 extra block in the end, as the merge op actually references the
+  // first |src_offset| bytes of that extra block. For example, if |dst_extent|
+  // is [10, 15], |src_offset| is 500, then src_extent might look like [25, 31].
+  // Note that |src_extent| contains 1 extra block than the |dst_extent|.
+  optional uint32 src_offset = 4;
 }
 
 // Describes the update to apply to a single partition.