Add integration test for XOR ops

Test: th
Bug: 201099341
Change-Id: I36f4c2da25f23b3dcdfa0852a9dc01f896a292f1
diff --git a/aosp/dynamic_partition_control_android.h b/aosp/dynamic_partition_control_android.h
index ad3e056..d0842a9 100644
--- a/aosp/dynamic_partition_control_android.h
+++ b/aosp/dynamic_partition_control_android.h
@@ -108,6 +108,7 @@
                               const std::optional<std::string>&,
                               bool is_append = false) override;
 
+  bool MapAllPartitions() override;
   bool UnmapAllPartitions() override;
 
   bool IsDynamicPartition(const std::string& part_name, uint32_t slot) override;
@@ -227,8 +228,6 @@
       const DeltaArchiveManifest& manifest,
       bool delete_source);
 
-  bool MapAllPartitions() override;
-
   void SetSourceSlot(uint32_t slot) { source_slot_ = slot; }
   void SetTargetSlot(uint32_t slot) { target_slot_ = slot; }
 
diff --git a/aosp/update_attempter_android_integration_test.cc b/aosp/update_attempter_android_integration_test.cc
index de42623..46f35f1 100644
--- a/aosp/update_attempter_android_integration_test.cc
+++ b/aosp/update_attempter_android_integration_test.cc
@@ -14,15 +14,18 @@
 // limitations under the License.
 //
 
+#include <algorithm>
 #include <memory>
 #include <string>
 #include <utility>
 
 #include <sys/fcntl.h>
+#include <sys/sendfile.h>
 #include <unistd.h>
 
 #include <brillo/data_encoding.h>
 #include <brillo/message_loops/fake_message_loop.h>
+#include <bsdiff/bsdiff.h>
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
 #include <fs_mgr.h>
@@ -39,6 +42,7 @@
 #include "update_engine/common/fake_hardware.h"
 #include "update_engine/common/test_utils.h"
 #include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/payload_generator/delta_diff_generator.h"
@@ -72,7 +76,26 @@
     builder_->RemovePartition("fake_b");
     builder_->RemoveGroupAndPartitions("fake_group");
   }
+
+  // Fill partition |path| with arbitrary data.
+  void FillPartition(const std::string& path, const bool is_source) {
+    std::array<uint8_t, kBlockSize> data;
+    EintrSafeFileDescriptor fd;
+    fd.Open(path.c_str(), O_RDWR);
+    for (size_t i = 0; i < kFakePartitionSize / kBlockSize; i++) {
+      if (is_source) {
+        std::fill(data.begin(), data.end(), i);
+      } else {
+        std::fill(
+            data.begin(), data.end(), kFakePartitionSize / kBlockSize - i - 1);
+      }
+      fd.Write(data.data(), kBlockSize);
+    }
+  }
+
   void SetUp() override {
+    FillPartition(old_part_.path(), true);
+    FillPartition(new_part_.path(), false);
     ASSERT_TRUE(boot_control_.Init());
     if (!DynamicPartitionEnabled()) {
       return;
@@ -99,6 +122,7 @@
     manifest_.mutable_dynamic_partition_metadata()->set_cow_version(
         android::snapshot::kCowVersionMajor);
   }
+
   void TearDown() override {
     if (!builder_ || !DynamicPartitionEnabled()) {
       return;
@@ -114,10 +138,23 @@
 
   void CreateFakePartition() {
     // Create a fake partition for testing purposes
-    auto partition_a = builder_->AddPartition("fake_a", "fake_group", 0);
+    auto partition_a = builder_->AddPartition(
+        boot_control_.GetCurrentSlot() == 0 ? "fake_a" : "fake_b",
+        "fake_group",
+        0);
     ASSERT_NE(partition_a, nullptr);
     ASSERT_TRUE(builder_->ResizePartition(partition_a, kFakePartitionSize));
     ExportPartitionTable();
+    std::string source_part;
+    ASSERT_TRUE(
+        dynamic_control_->GetPartitionDevice("fake",
+                                             boot_control_.GetCurrentSlot(),
+                                             boot_control_.GetCurrentSlot(),
+                                             &source_part));
+    int out_fd = open(source_part.c_str(), O_RDWR);
+    ScopedFdCloser closer{&out_fd};
+    ASSERT_GE(out_fd, 0) << utils::ErrnoNumberAsString(errno);
+    ASSERT_TRUE(utils::SendFile(out_fd, old_part_.fd(), kFakePartitionSize));
   }
 
   void SendStatusUpdate(
@@ -166,13 +203,69 @@
           total_blob_size, signature_blob_length, manifest);
     }
   }
+
+  // Generate blob data according to ops specified in the manifest.
+  // Also update |new_part_|'s content to match expectation of ops.
+  void HydratePayload(DeltaArchiveManifest* manifest) {
+    for (auto& partition : *manifest->mutable_partitions()) {
+      for (auto& op : *partition.mutable_operations()) {
+        if (op.type() == InstallOperation::REPLACE) {
+          ASSERT_GE(lseek64(blob_file_.fd(), op.data_offset(), SEEK_SET), 0);
+          ASSERT_TRUE(utils::SendFile(
+              new_part_.fd(), blob_file_.fd(), op.data_length()));
+        } else if (op.type() == InstallOperation::BROTLI_BSDIFF) {
+          brillo::Blob old_data;
+          ASSERT_TRUE(utils::ReadExtents(
+              old_part_.path(), op.src_extents(), &old_data, kBlockSize))
+              << "Failed to read source data: "
+              << utils::ErrnoNumberAsString(errno);
+          brillo::Blob new_data;
+          ASSERT_TRUE(utils::ReadExtents(
+              new_part_.path(), op.dst_extents(), &new_data, kBlockSize))
+              << "Failed to read target data: "
+              << utils::ErrnoNumberAsString(errno);
+          ScopedTempFile patch_file{"bspatch.XXXXXX", true};
+          ASSERT_EQ(bsdiff::bsdiff(old_data.data(),
+                                   old_data.size(),
+                                   new_data.data(),
+                                   new_data.size(),
+                                   patch_file.path().c_str(),
+                                   nullptr),
+                    0);
+          op.set_data_length(utils::FileSize(patch_file.fd()));
+          const auto offset = lseek64(blob_file_.fd(), 0, SEEK_CUR);
+          ASSERT_GE(offset, 0);
+          op.set_data_offset(offset);
+          brillo::Blob src_data_hash;
+          HashCalculator::RawHashOfData(old_data, &src_data_hash);
+          op.set_src_sha256_hash(src_data_hash.data(), src_data_hash.size());
+          utils::SendFile(blob_file_.fd(), patch_file.fd(), op.data_length());
+
+        } else if (op.type() == InstallOperation::ZERO) {
+          auto zero = utils::GetReadonlyZeroString(
+              utils::BlocksInExtents(op.dst_extents()) * kBlockSize);
+          for (const auto& ext : op.dst_extents()) {
+            utils::PWriteAll(new_part_.fd(),
+                             zero.data(),
+                             ext.num_blocks() * kBlockSize,
+                             ext.start_block() * kBlockSize);
+          }
+        } else if (op.type() == InstallOperation::SOURCE_COPY) {
+          brillo::Blob data;
+          ASSERT_TRUE(utils::ReadExtents(
+              old_part_.path(), op.src_extents(), &data, kBlockSize));
+          ASSERT_TRUE(utils::WriteExtents(
+              new_part_.path(), op.dst_extents(), data, kBlockSize));
+        } else {
+          FAIL() << "Unsupported install op type: " << op.type();
+        }
+      }
+    }
+  }
+
   void ApplyPayload(DeltaArchiveManifest* manifest) {
     ASSERT_FALSE(manifest->partitions().empty());
-    if (manifest->partitions().begin()->has_old_partition_info()) {
-      // Only create fake partition if the update is incremental
-      LOG(INFO) << "Creating fake partition";
-      ASSERT_NO_FATAL_FAILURE(CreateFakePartition());
-    }
+    ASSERT_NO_FATAL_FAILURE(HydratePayload(manifest));
     const auto private_key_path =
         test_utils::GetBuildArtifactsPath(kUnittestPrivateKeyPath);
     ASSERT_NO_FATAL_FAILURE(
@@ -183,6 +276,19 @@
     auto partition = &manifest->mutable_partitions()->at(0);
     partition->mutable_new_partition_info()->set_size(kFakePartitionSize);
     partition->mutable_new_partition_info()->set_hash(hash.data(), hash.size());
+    const bool source_exist =
+        std::any_of(partition->operations().begin(),
+                    partition->operations().end(),
+                    [](const auto& op) { return op.src_extents_size() > 0; });
+    if (source_exist) {
+      HashCalculator::RawHashOfFile(old_part_.path(), &hash);
+      partition->mutable_old_partition_info()->set_size(kFakePartitionSize);
+      partition->mutable_old_partition_info()->set_hash(hash.data(),
+                                                        hash.size());
+      // Only create fake partition if the update is incremental
+      LOG(INFO) << "Creating fake partition";
+      ASSERT_NO_FATAL_FAILURE(CreateFakePartition());
+    }
     uint64_t metadata_size = 0;
     ASSERT_TRUE(PayloadFile::WritePayload(payload_file_.path(),
                                           blob_file_.path(),
@@ -210,6 +316,36 @@
     ASSERT_EQ(error, nullptr);
     ASSERT_EQ(completion_code_, ErrorCode::kSuccess);
   }
+
+  // Compare contents of fake_b partition to |new_part_| and print difference
+  void DumpTargetPartitionDiff() {
+    dynamic_control_->MapAllPartitions();
+    auto partition_device =
+        dynamic_control_->GetPartitionDevice("fake",
+                                             1 - boot_control_.GetCurrentSlot(),
+                                             boot_control_.GetCurrentSlot(),
+                                             false);
+    if (!partition_device.has_value()) {
+      LOG(INFO) << "Failed to get target fake partition, skip diff report";
+      return;
+    }
+
+    EintrSafeFileDescriptor actual_part;
+    CHECK(actual_part.Open(partition_device->readonly_device_path.c_str(),
+                           O_RDONLY));
+    EintrSafeFileDescriptor expected_part;
+    CHECK(expected_part.Open(new_part_.path().c_str(), O_RDONLY));
+
+    std::array<uint8_t, kBlockSize> actual_block;
+    std::array<uint8_t, kBlockSize> expected_block;
+    for (size_t i = 0; i < kFakePartitionSize / kBlockSize; i++) {
+      actual_part.Read(actual_block.data(), actual_block.size());
+      expected_part.Read(expected_block.data(), expected_block.size());
+      if (actual_block != expected_block) {
+        LOG(ERROR) << "Block " << i << " differs.";
+      }
+    }
+  }
   // use 25MB max to avoid super not having enough space
   static constexpr size_t kFakePartitionSize = 1024 * 1024 * 25;
   static_assert(kFakePartitionSize % kBlockSize == 0);
@@ -219,9 +355,12 @@
   std::string super_device_;
   FakeHardware hardware_;
   ScopedTempFile payload_file_;
-  ScopedTempFile blob_file_;
-  // Contains expected data for old and new partition
+  ScopedTempFile blob_file_{"blob_file.XXXXXX", true};
+  // Contains expected data for old partition. Will be copied to fake_a on test
+  // start.
   ScopedTempFile old_part_{"old_part.XXXXXX", true, kFakePartitionSize};
+  // Expected data for new partition, will be compared against actual data in
+  // fake_b once test finishes.
   ScopedTempFile new_part_{"new_part.XXXXXX", true, kFakePartitionSize};
   DaemonStateAndroid daemon_state_;
   MemoryPrefs prefs_;
@@ -259,6 +398,57 @@
   }
 
   ApplyPayload(&manifest_);
+  if (completion_code_ == ErrorCode::kNewRootfsVerificationError) {
+    DumpTargetPartitionDiff();
+  }
+}
+
+TEST_F(UpdateAttempterAndroidIntegrationTest, XorOpsTest) {
+  if (!DynamicPartitionEnabled()) {
+    return;
+  }
+  auto partition = manifest_.add_partitions();
+  partition->set_partition_name("fake");
+  partition->set_estimate_cow_size(kFakePartitionSize);
+  {
+    auto op = partition->add_operations();
+    op->set_type(InstallOperation::BROTLI_BSDIFF);
+    *op->add_src_extents() = ExtentForRange(0, 10);
+    *op->add_dst_extents() = ExtentForRange(0, 10);
+  }
+  {
+    auto op = partition->add_operations();
+    op->set_type(InstallOperation::BROTLI_BSDIFF);
+    *op->add_src_extents() = ExtentForRange(10, 10);
+    *op->add_dst_extents() = ExtentForRange(10, 10);
+  }
+  {
+    auto op = partition->add_operations();
+    op->set_type(InstallOperation::SOURCE_COPY);
+    *op->add_src_extents() =
+        ExtentForRange(20, kFakePartitionSize / kBlockSize - 20);
+    *op->add_dst_extents() =
+        ExtentForRange(20, kFakePartitionSize / kBlockSize - 20);
+  }
+  {
+    auto op = partition->add_merge_operations();
+    op->set_type(CowMergeOperation::COW_XOR);
+    op->set_src_offset(123);
+    *op->mutable_src_extent() = ExtentForRange(2, 8);
+    *op->mutable_dst_extent() = ExtentForRange(0, 8);
+  }
+  {
+    auto op = partition->add_merge_operations();
+    op->set_type(CowMergeOperation::COW_XOR);
+    op->set_src_offset(456);
+    *op->mutable_src_extent() = ExtentForRange(10, 8);
+    *op->mutable_dst_extent() = ExtentForRange(12, 8);
+  }
+
+  ApplyPayload(&manifest_);
+  if (completion_code_ == ErrorCode::kNewRootfsVerificationError) {
+    DumpTargetPartitionDiff();
+  }
 }
 
 }  // namespace
diff --git a/common/utils.cc b/common/utils.cc
index 7a4a836..e47b70b 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -397,6 +397,19 @@
   return size;
 }
 
+bool SendFile(int out_fd, int in_fd, size_t count) {
+  off64_t offset = lseek(in_fd, 0, SEEK_CUR);
+  TEST_AND_RETURN_FALSE_ERRNO(offset >= 0);
+  constexpr size_t BUFFER_SIZE = 4096;
+  while (count > 0) {
+    const auto bytes_written =
+        sendfile(out_fd, in_fd, &offset, std::min(count, BUFFER_SIZE));
+    TEST_AND_RETURN_FALSE_ERRNO(bytes_written > 0);
+    count -= bytes_written;
+  }
+  return true;
+}
+
 void HexDumpArray(const uint8_t* const arr, const size_t length) {
   LOG(INFO) << "Logging array of length: " << length;
   const unsigned int bytes_per_line = 16;
@@ -892,6 +905,34 @@
   return false;
 }
 
+bool ReadExtents(const std::string& path,
+                 const google::protobuf::RepeatedPtrField<Extent>& extents,
+                 brillo::Blob* out_data,
+                 size_t block_size) {
+  return ReadExtents(path,
+                     {extents.begin(), extents.end()},
+                     out_data,
+                     utils::BlocksInExtents(extents) * block_size,
+                     block_size);
+}
+
+bool WriteExtents(const std::string& path,
+                  const google::protobuf::RepeatedPtrField<Extent>& extents,
+                  const brillo::Blob& data,
+                  size_t block_size) {
+  EintrSafeFileDescriptor fd;
+  TEST_AND_RETURN_FALSE(fd.Open(path.c_str(), O_RDWR));
+  size_t bytes_written = 0;
+  for (const auto& ext : extents) {
+    TEST_AND_RETURN_FALSE_ERRNO(
+        fd.Seek(ext.start_block() * block_size, SEEK_SET));
+    TEST_AND_RETURN_FALSE_ERRNO(
+        fd.Write(data.data() + bytes_written, ext.num_blocks() * block_size));
+    bytes_written += ext.num_blocks() * block_size;
+  }
+  return true;
+}
+
 bool ReadExtents(const string& path,
                  const vector<Extent>& extents,
                  brillo::Blob* out_data,
@@ -906,7 +947,7 @@
   for (const Extent& extent : extents) {
     ssize_t bytes_read_this_iteration = 0;
     ssize_t bytes = extent.num_blocks() * block_size;
-    TEST_AND_RETURN_FALSE(bytes_read + bytes <= out_data_size);
+    TEST_LE(bytes_read + bytes, out_data_size);
     TEST_AND_RETURN_FALSE(utils::PReadAll(fd,
                                           &data[bytes_read],
                                           bytes,
diff --git a/common/utils.h b/common/utils.h
index 50b6cb1..4ff4050 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -38,6 +38,7 @@
 #include <brillo/secure_blob.h>
 
 #include "android-base/mapped_file.h"
+#include "google/protobuf/repeated_field.h"
 #include "update_engine/common/action.h"
 #include "update_engine/common/action_processor.h"
 #include "update_engine/common/constants.h"
@@ -137,6 +138,8 @@
 off_t FileSize(const std::string& path);
 off_t FileSize(int fd);
 
+bool SendFile(int out_fd, int in_fd, size_t count);
+
 std::string ErrnoNumberAsString(int err);
 
 // Returns true if the file exists for sure. Returns false if it doesn't exist,
@@ -308,6 +311,27 @@
                  ssize_t out_data_size,
                  size_t block_size);
 
+bool WriteExtents(const std::string& path,
+                  const google::protobuf::RepeatedPtrField<Extent>& extents,
+                  const brillo::Blob& data,
+                  size_t block_size);
+
+constexpr bool ReadExtents(const std::string& path,
+                           const std::vector<Extent>& extents,
+                           brillo::Blob* out_data,
+                           size_t block_size) {
+  return ReadExtents(path,
+                     extents,
+                     out_data,
+                     utils::BlocksInExtents(extents) * block_size,
+                     block_size);
+}
+
+bool ReadExtents(const std::string& path,
+                 const google::protobuf::RepeatedPtrField<Extent>& extents,
+                 brillo::Blob* out_data,
+                 size_t block_size);
+
 // Read the current boot identifier and store it in |boot_id|. This identifier
 // is constants during the same boot of the kernel and is regenerated after
 // reboot. Returns whether it succeeded getting the boot_id.
@@ -485,6 +509,11 @@
 std::string HexEncode(const brillo::Blob& blob) noexcept;
 std::string HexEncode(const std::string_view blob) noexcept;
 
+template <size_t kSize>
+std::string HexEncode(const std::array<uint8_t, kSize> blob) noexcept {
+  return base::HexEncode(blob.data(), blob.size());
+}
+
 }  // namespace chromeos_update_engine
 
 #define TEST_AND_RETURN_FALSE_ERRNO(_x)                              \
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index cc36a4e..2ac783a 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -498,7 +498,10 @@
     // |num_total_operations_| limit yet.
     if (next_operation_num_ >= acc_num_operations_[current_partition_]) {
       if (partition_writer_) {
-        TEST_AND_RETURN_FALSE(partition_writer_->FinishedInstallOps());
+        if (!partition_writer_->FinishedInstallOps()) {
+          *error = ErrorCode::kDownloadWriteError;
+          return false;
+        }
       }
       CloseCurrentPartition();
       // Skip until there are operations for current_partition_.
diff --git a/payload_consumer/vabc_partition_writer.cc b/payload_consumer/vabc_partition_writer.cc
index a11bdf4..9db88a9 100644
--- a/payload_consumer/vabc_partition_writer.cc
+++ b/payload_consumer/vabc_partition_writer.cc
@@ -314,7 +314,8 @@
   TEST_AND_RETURN_FALSE(cow_writer_ != nullptr);
   TEST_AND_RETURN_FALSE(cow_writer_->AddLabel(kEndOfInstallLabel));
   TEST_AND_RETURN_FALSE(cow_writer_->Finalize());
-  return cow_writer_->VerifyMergeOps();
+  TEST_AND_RETURN_FALSE(cow_writer_->VerifyMergeOps());
+  return true;
 }
 
 VABCPartitionWriter::~VABCPartitionWriter() {