//
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "update_engine/payload_consumer/filesystem_verifier_action.h"

#include <algorithm>
#include <cstring>
#include <memory>
#include <string>
#include <utility>

#include <base/bind.h>
#include <base/posix/eintr_wrapper.h>
#include <brillo/message_loops/fake_message_loop.h>
#include <brillo/message_loops/message_loop_utils.h>
#include <brillo/secure_blob.h>
#include <fec/ecc.h>
#include <gtest/gtest.h>
#include <libsnapshot/cow_writer.h>
#include <sys/stat.h>

#include "update_engine/common/dynamic_partition_control_stub.h"
#include "update_engine/common/hash_calculator.h"
#include "update_engine/common/mock_dynamic_partition_control.h"
#include "update_engine/common/test_utils.h"
#include "update_engine/common/utils.h"
#include "update_engine/payload_consumer/install_plan.h"
#include "update_engine/payload_consumer/verity_writer_android.h"

using brillo::MessageLoop;
using std::string;
using testing::_;
using testing::AtLeast;
using testing::DoAll;
using testing::NiceMock;
using testing::Return;
using testing::SetArgPointee;

namespace chromeos_update_engine {

class FilesystemVerifierActionTest : public ::testing::Test {
 public:
  static constexpr size_t BLOCK_SIZE = 4096;
  // We use SHA256 for testing, so hash size is 256bits / 8
  static constexpr size_t HASH_SIZE = 256 / 8;
  static constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 1024;
  static constexpr size_t HASH_TREE_START_OFFSET = 800 * BLOCK_SIZE;
  size_t hash_tree_size = 0;
  size_t fec_start_offset = 0;
  size_t fec_data_size = 0;
  static constexpr size_t FEC_ROOTS = 2;
  size_t fec_rounds = 0;
  size_t fec_size = 0;

 protected:
  void SetUp() override {
    hash_tree_size = HashTreeBuilder::CalculateSize(
        HASH_TREE_START_OFFSET, BLOCK_SIZE, HASH_SIZE);
    fec_start_offset = HASH_TREE_START_OFFSET + hash_tree_size;
    fec_data_size = fec_start_offset;
    static constexpr size_t FEC_ROOTS = 2;
    fec_rounds =
        utils::DivRoundUp(fec_data_size / BLOCK_SIZE, FEC_RSM - FEC_ROOTS);
    fec_size = fec_rounds * FEC_ROOTS * BLOCK_SIZE;

    fec_data_.resize(fec_size);
    hash_tree_data_.resize(hash_tree_size);
    // Globally readable writable, as we want to write data
    ASSERT_EQ(0, fchmod(source_part_.fd(), 0666))
        << " Failed to set " << source_part_.path() << " as writable "
        << strerror(errno);
    ASSERT_EQ(0, fchmod(target_part_.fd(), 0666))
        << " Failed to set " << target_part_.path() << " as writable "
        << strerror(errno);
    brillo::Blob part_data(PARTITION_SIZE);
    test_utils::FillWithData(&part_data);
    ASSERT_TRUE(utils::WriteFile(
        source_part_.path().c_str(), part_data.data(), part_data.size()));
    // FillWithData() will fill with different data next call. We want
    // source/target partitions to contain different data for testing.
    test_utils::FillWithData(&part_data);
    ASSERT_TRUE(utils::WriteFile(
        target_part_.path().c_str(), part_data.data(), part_data.size()));
    loop_.SetAsCurrent();
  }

  void TearDown() override {
    EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
  }

  void DoTestVABC(bool clear_target_hash, bool enable_verity);

  // Returns true iff test has completed successfully.
  bool DoTest(bool terminate_early, bool hash_fail);

  void BuildActions(const InstallPlan& install_plan);
  void BuildActions(const InstallPlan& install_plan,
                    DynamicPartitionControlInterface* dynamic_control);

  InstallPlan::Partition* AddFakePartition(InstallPlan* install_plan,
                                           std::string name = "fake_part") {
    InstallPlan::Partition& part = install_plan->partitions.emplace_back();
    part.name = name;
    part.target_path = target_part_.path();
    part.readonly_target_path = part.target_path;
    part.target_size = PARTITION_SIZE;
    part.block_size = BLOCK_SIZE;
    part.source_path = source_part_.path();
    part.source_size = PARTITION_SIZE;
    EXPECT_TRUE(
        HashCalculator::RawHashOfFile(source_part_.path(), &part.source_hash));
    EXPECT_TRUE(
        HashCalculator::RawHashOfFile(target_part_.path(), &part.target_hash));
    return &part;
  }
  static void ZeroRange(FileDescriptorPtr fd,
                        size_t start_block,
                        size_t num_blocks) {
    std::vector<unsigned char> buffer(BLOCK_SIZE);
    ASSERT_EQ((ssize_t)(start_block * BLOCK_SIZE),
              fd->Seek(start_block * BLOCK_SIZE, SEEK_SET));
    for (size_t i = 0; i < num_blocks; i++) {
      ASSERT_TRUE(utils::WriteAll(fd, buffer.data(), buffer.size()));
    }
  }

  void SetHashWithVerity(InstallPlan::Partition* partition) {
    partition->hash_tree_algorithm = "sha256";
    partition->hash_tree_size = hash_tree_size;
    partition->hash_tree_offset = HASH_TREE_START_OFFSET;
    partition->hash_tree_data_offset = 0;
    partition->hash_tree_data_size = HASH_TREE_START_OFFSET;
    partition->fec_size = fec_size;
    partition->fec_offset = fec_start_offset;
    partition->fec_data_offset = 0;
    partition->fec_data_size = fec_data_size;
    partition->fec_roots = FEC_ROOTS;
    VerityWriterAndroid verity_writer;
    ASSERT_TRUE(verity_writer.Init(*partition));
    LOG(INFO) << "Opening " << partition->readonly_target_path;
    auto fd = std::make_shared<EintrSafeFileDescriptor>();
    ASSERT_TRUE(fd->Open(partition->readonly_target_path.c_str(), O_RDWR))
        << "Failed to open " << partition->target_path.c_str() << " "
        << strerror(errno);
    std::vector<unsigned char> buffer(BLOCK_SIZE);
    // Only need to read up to hash tree
    auto bytes_to_read = HASH_TREE_START_OFFSET;
    auto offset = 0;
    while (bytes_to_read > 0) {
      const auto bytes_read = fd->Read(
          buffer.data(), std::min<size_t>(buffer.size(), bytes_to_read));
      ASSERT_GT(bytes_read, 0)
          << "offset: " << offset << " bytes to read: " << bytes_to_read
          << " error: " << strerror(errno);
      ASSERT_TRUE(verity_writer.Update(offset, buffer.data(), bytes_read));
      bytes_to_read -= bytes_read;
      offset += bytes_read;
    }
    ASSERT_TRUE(verity_writer.Finalize(fd.get(), fd.get()));
    ASSERT_TRUE(fd->IsOpen());
    ASSERT_TRUE(HashCalculator::RawHashOfFile(target_part_.path(),
                                              &partition->target_hash));

    ASSERT_TRUE(fd->Seek(HASH_TREE_START_OFFSET, SEEK_SET));
    ASSERT_EQ(fd->Read(hash_tree_data_.data(), hash_tree_data_.size()),
              static_cast<ssize_t>(hash_tree_data_.size()))
        << "Failed to read hashtree " << strerror(errno);
    ASSERT_TRUE(fd->Seek(fec_start_offset, SEEK_SET));
    ASSERT_EQ(fd->Read(fec_data_.data(), fec_data_.size()),
              static_cast<ssize_t>(fec_data_.size()))
        << "Failed to read FEC " << strerror(errno);
    // Fs verification action is expected to write them, so clear verity data to
    // ensure that they are re-created correctly.
    ZeroRange(
        fd, HASH_TREE_START_OFFSET / BLOCK_SIZE, hash_tree_size / BLOCK_SIZE);
    ZeroRange(fd, fec_start_offset / BLOCK_SIZE, fec_size / BLOCK_SIZE);
  }

  brillo::FakeMessageLoop loop_{nullptr};
  ActionProcessor processor_;
  DynamicPartitionControlStub dynamic_control_stub_;
  std::vector<unsigned char> fec_data_;
  std::vector<unsigned char> hash_tree_data_;
  static ScopedTempFile source_part_;
  static ScopedTempFile target_part_;
  InstallPlan install_plan_;
};

ScopedTempFile FilesystemVerifierActionTest::source_part_{
    "source_part.XXXXXX", true, PARTITION_SIZE};
ScopedTempFile FilesystemVerifierActionTest::target_part_{
    "target_part.XXXXXX", true, PARTITION_SIZE};

static void EnableVABC(MockDynamicPartitionControl* dynamic_control,
                       const std::string& part_name) {
  ON_CALL(*dynamic_control, GetDynamicPartitionsFeatureFlag())
      .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
  ON_CALL(*dynamic_control, UpdateUsesSnapshotCompression())
      .WillByDefault(Return(true));
  ON_CALL(*dynamic_control, IsDynamicPartition(part_name, _))
      .WillByDefault(Return(true));
}

class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
 public:
  FilesystemVerifierActionTestDelegate()
      : ran_(false), code_(ErrorCode::kError) {}

  void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
    MessageLoop::current()->BreakLoop();
  }
  void ProcessingStopped(const ActionProcessor* processor) {
    MessageLoop::current()->BreakLoop();
  }
  void ActionCompleted(ActionProcessor* processor,
                       AbstractAction* action,
                       ErrorCode code) {
    if (action->Type() == FilesystemVerifierAction::StaticType()) {
      ran_ = true;
      code_ = code;
      EXPECT_FALSE(
          static_cast<FilesystemVerifierAction*>(action)->partition_fd_);
    } else if (action->Type() ==
               ObjectCollectorAction<InstallPlan>::StaticType()) {
      auto collector_action =
          static_cast<ObjectCollectorAction<InstallPlan>*>(action);
      install_plan_.reset(new InstallPlan(collector_action->object()));
    }
  }
  bool ran() const { return ran_; }
  ErrorCode code() const { return code_; }

  std::unique_ptr<InstallPlan> install_plan_;

 private:
  bool ran_;
  ErrorCode code_;
};

bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
                                          bool hash_fail) {
  ScopedTempFile a_loop_file("a_loop_file.XXXXXX");

  // Make random data for a.
  const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
  brillo::Blob a_loop_data(kLoopFileSize);
  test_utils::FillWithData(&a_loop_data);

  // Write data to disk
  if (!(test_utils::WriteFileVector(a_loop_file.path(), a_loop_data))) {
    ADD_FAILURE();
    return false;
  }

  // Attach loop devices to the files
  string a_dev;
  test_utils::ScopedLoopbackDeviceBinder a_dev_releaser(
      a_loop_file.path(), false, &a_dev);
  if (!(a_dev_releaser.is_bound())) {
    ADD_FAILURE();
    return false;
  }

  LOG(INFO) << "verifying: " << a_loop_file.path() << " (" << a_dev << ")";

  bool success = true;

  // Set up the action objects
  install_plan_.source_slot = 0;
  install_plan_.target_slot = 1;
  InstallPlan::Partition part;
  part.name = "part";
  part.target_size = kLoopFileSize - (hash_fail ? 1 : 0);
  part.target_path = a_dev;
  if (!HashCalculator::RawHashOfData(a_loop_data, &part.target_hash)) {
    ADD_FAILURE();
    success = false;
  }
  part.source_size = kLoopFileSize;
  part.source_path = a_dev;
  if (!HashCalculator::RawHashOfData(a_loop_data, &part.source_hash)) {
    ADD_FAILURE();
    success = false;
  }
  install_plan_.partitions = {part};

  BuildActions(install_plan_);

  FilesystemVerifierActionTestDelegate delegate;
  processor_.set_delegate(&delegate);

  loop_.PostTask(base::Bind(&ActionProcessor::StartProcessing,
                            base::Unretained(&processor_)));
  if (terminate_early) {
    loop_.PostTask(base::Bind(&ActionProcessor::StopProcessing,
                              base::Unretained(&processor_)));
  }
  loop_.Run();

  if (!terminate_early) {
    bool is_delegate_ran = delegate.ran();
    EXPECT_TRUE(is_delegate_ran);
    success = success && is_delegate_ran;
  } else {
    EXPECT_EQ(ErrorCode::kError, delegate.code());
    return (ErrorCode::kError == delegate.code());
  }
  if (hash_fail) {
    ErrorCode expected_exit_code = ErrorCode::kNewRootfsVerificationError;
    EXPECT_EQ(expected_exit_code, delegate.code());
    return (expected_exit_code == delegate.code());
  }
  EXPECT_EQ(ErrorCode::kSuccess, delegate.code());

  // Make sure everything in the out_image is there
  brillo::Blob a_out;
  if (!utils::ReadFile(a_dev, &a_out)) {
    ADD_FAILURE();
    return false;
  }
  const bool is_a_file_reading_eq =
      test_utils::ExpectVectorsEq(a_loop_data, a_out);
  EXPECT_TRUE(is_a_file_reading_eq);
  success = success && is_a_file_reading_eq;

  bool is_install_plan_eq = (*delegate.install_plan_ == install_plan_);
  EXPECT_TRUE(is_install_plan_eq);
  success = success && is_install_plan_eq;
  return success;
}

void FilesystemVerifierActionTest::BuildActions(
    const InstallPlan& install_plan,
    DynamicPartitionControlInterface* dynamic_control) {
  auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
  auto verifier_action =
      std::make_unique<FilesystemVerifierAction>(dynamic_control);
  auto collector_action =
      std::make_unique<ObjectCollectorAction<InstallPlan>>();

  feeder_action->set_obj(install_plan);

  BondActions(feeder_action.get(), verifier_action.get());
  BondActions(verifier_action.get(), collector_action.get());

  processor_.EnqueueAction(std::move(feeder_action));
  processor_.EnqueueAction(std::move(verifier_action));
  processor_.EnqueueAction(std::move(collector_action));
}

void FilesystemVerifierActionTest::BuildActions(
    const InstallPlan& install_plan) {
  BuildActions(install_plan, &dynamic_control_stub_);
}

class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate {
 public:
  void ActionCompleted(ActionProcessor* processor,
                       AbstractAction* action,
                       ErrorCode code) {
    if (action->Type() == FilesystemVerifierAction::StaticType()) {
      ran_ = true;
      code_ = code;
    }
  }
  bool ran_;
  ErrorCode code_;
};

TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) {
  auto copier_action =
      std::make_unique<FilesystemVerifierAction>(&dynamic_control_stub_);
  auto collector_action =
      std::make_unique<ObjectCollectorAction<InstallPlan>>();

  BondActions(copier_action.get(), collector_action.get());

  processor_.EnqueueAction(std::move(copier_action));
  processor_.EnqueueAction(std::move(collector_action));

  FilesystemVerifierActionTest2Delegate delegate;
  processor_.set_delegate(&delegate);

  processor_.StartProcessing();
  ASSERT_FALSE(processor_.IsRunning());
  ASSERT_TRUE(delegate.ran_);
  EXPECT_EQ(ErrorCode::kError, delegate.code_);
}

TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
  InstallPlan::Partition part;
  part.name = "nope";
  part.source_path = "/no/such/file";
  part.target_path = "/no/such/file";
  install_plan_.partitions = {part};

  BuildActions(install_plan_);

  FilesystemVerifierActionTest2Delegate delegate;
  processor_.set_delegate(&delegate);

  processor_.StartProcessing();
  EXPECT_FALSE(processor_.IsRunning());
  EXPECT_TRUE(delegate.ran_);
  EXPECT_EQ(ErrorCode::kFilesystemVerifierError, delegate.code_);
}

TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) {
  ASSERT_EQ(0U, getuid());
  EXPECT_TRUE(DoTest(false, false));
}

TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) {
  ASSERT_EQ(0U, getuid());
  EXPECT_TRUE(DoTest(false, true));
}

TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) {
  ASSERT_EQ(0U, getuid());
  EXPECT_TRUE(DoTest(true, false));
  // TerminateEarlyTest may leak some null callbacks from the Stream class.
  while (loop_.RunOnce(false)) {
  }
}

#ifdef __ANDROID__
TEST_F(FilesystemVerifierActionTest, RunAsRootWriteVerityTest) {
  ScopedTempFile part_file("part_file.XXXXXX");
  constexpr size_t filesystem_size = 200 * 4096;
  constexpr size_t part_size = 256 * 4096;
  brillo::Blob part_data(filesystem_size, 0x1);
  part_data.resize(part_size);
  ASSERT_TRUE(test_utils::WriteFileVector(part_file.path(), part_data));
  string target_path;
  test_utils::ScopedLoopbackDeviceBinder target_device(
      part_file.path(), true, &target_path);

  InstallPlan::Partition part;
  part.name = "part";
  part.target_path = target_path;
  part.target_size = part_size;
  part.block_size = 4096;
  part.hash_tree_algorithm = "sha1";
  part.hash_tree_data_offset = 0;
  part.hash_tree_data_size = filesystem_size;
  part.hash_tree_offset = filesystem_size;
  part.hash_tree_size = 3 * 4096;
  part.fec_data_offset = 0;
  part.fec_data_size = filesystem_size + part.hash_tree_size;
  part.fec_offset = part.fec_data_size;
  part.fec_size = 2 * 4096;
  part.fec_roots = 2;
  // for i in {1..$((200 * 4096))}; do echo -n -e '\x1' >> part; done
  // avbtool add_hashtree_footer --image part --partition_size $((256 * 4096))
  //     --partition_name part --do_not_append_vbmeta_image
  //     --output_vbmeta_image vbmeta
  // truncate -s $((256 * 4096)) part
  // sha256sum part | xxd -r -p | hexdump -v -e '/1 "0x%02x, "'
  part.target_hash = {0x28, 0xd4, 0x96, 0x75, 0x4c, 0xf5, 0x8a, 0x3e,
                      0x31, 0x85, 0x08, 0x92, 0x85, 0x62, 0xf0, 0x37,
                      0xbc, 0x8d, 0x7e, 0xa4, 0xcb, 0x24, 0x18, 0x7b,
                      0xf3, 0xeb, 0xb5, 0x8d, 0x6f, 0xc8, 0xd8, 0x1a};
  // avbtool info_image --image vbmeta | grep Salt | cut -d':' -f 2 |
  //     xxd -r -p | hexdump -v -e '/1 "0x%02x, "'
  part.hash_tree_salt = {0x9e, 0xcb, 0xf8, 0xd5, 0x0b, 0xb4, 0x43,
                         0x0a, 0x7a, 0x10, 0xad, 0x96, 0xd7, 0x15,
                         0x70, 0xba, 0xed, 0x27, 0xe2, 0xae};
  install_plan_.partitions = {part};

  BuildActions(install_plan_);

  FilesystemVerifierActionTestDelegate delegate;
  processor_.set_delegate(&delegate);

  loop_.PostTask(
      FROM_HERE,
      base::Bind(
          [](ActionProcessor* processor) { processor->StartProcessing(); },
          base::Unretained(&processor_)));
  loop_.Run();

  EXPECT_FALSE(processor_.IsRunning());
  EXPECT_TRUE(delegate.ran());
  EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
}
#endif  // __ANDROID__

TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) {
  ScopedTempFile part_file("part_file.XXXXXX");
  constexpr size_t filesystem_size = 200 * 4096;
  constexpr size_t part_size = 256 * 4096;
  brillo::Blob part_data(part_size);
  test_utils::FillWithData(&part_data);
  ASSERT_TRUE(test_utils::WriteFileVector(part_file.path(), part_data));
  string target_path;
  test_utils::ScopedLoopbackDeviceBinder target_device(
      part_file.path(), true, &target_path);

  install_plan_.write_verity = false;
  InstallPlan::Partition part;
  part.name = "part";
  part.target_path = target_path;
  part.target_size = part_size;
  part.block_size = 4096;
  part.hash_tree_data_offset = 0;
  part.hash_tree_data_size = filesystem_size;
  part.hash_tree_offset = filesystem_size;
  part.hash_tree_size = 3 * 4096;
  part.fec_data_offset = 0;
  part.fec_data_size = filesystem_size + part.hash_tree_size;
  part.fec_offset = part.fec_data_size;
  part.fec_size = 2 * 4096;
  EXPECT_TRUE(HashCalculator::RawHashOfData(part_data, &part.target_hash));
  install_plan_.partitions = {part};

  BuildActions(install_plan_);

  FilesystemVerifierActionTestDelegate delegate;
  processor_.set_delegate(&delegate);

  loop_.PostTask(
      FROM_HERE,
      base::Bind(
          [](ActionProcessor* processor) { processor->StartProcessing(); },
          base::Unretained(&processor_)));
  loop_.Run();

  ASSERT_FALSE(processor_.IsRunning());
  ASSERT_TRUE(delegate.ran());
  ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
}

void FilesystemVerifierActionTest::DoTestVABC(bool clear_target_hash,
                                              bool enable_verity) {
  auto part_ptr = AddFakePartition(&install_plan_);
  if (::testing::Test::HasFailure()) {
    return;
  }
  ASSERT_NE(part_ptr, nullptr);
  InstallPlan::Partition& part = *part_ptr;
  part.target_path = "Shouldn't attempt to open this path";
  if (enable_verity) {
    install_plan_.write_verity = true;
    ASSERT_NO_FATAL_FAILURE(SetHashWithVerity(&part));
  }
  if (clear_target_hash) {
    part.target_hash.clear();
  }

  NiceMock<MockDynamicPartitionControl> dynamic_control;

  EnableVABC(&dynamic_control, part.name);
  auto open_cow = [part]() {
    auto cow_fd = std::make_unique<EintrSafeFileDescriptor>();
    EXPECT_TRUE(cow_fd->Open(part.readonly_target_path.c_str(), O_RDWR))
        << "Failed to open part " << part.readonly_target_path
        << strerror(errno);
    return cow_fd;
  };

  EXPECT_CALL(dynamic_control, UpdateUsesSnapshotCompression())
      .Times(AtLeast(1));
  auto cow_fd = open_cow();
  if (HasFailure()) {
    return;
  }

  if (enable_verity) {
    ON_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
        .WillByDefault(open_cow);
    EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
        .Times(AtLeast(1));

    // fs verification isn't supposed to write to |readonly_target_path|. All
    // writes should go through fd returned by |OpenCowFd|. Therefore we set
    // target part as read-only to make sure.
    ASSERT_EQ(0, chmod(part.readonly_target_path.c_str(), 0444))
        << " Failed to set " << part.readonly_target_path << " as read-only "
        << strerror(errno);
  } else {
    // Since we are not writing verity, we should not attempt to OpenCowFd()
    // reads should go through regular file descriptors on mapped partitions.
    EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
        .Times(0);
    EXPECT_CALL(dynamic_control, MapAllPartitions()).Times(AtLeast(1));
  }
  EXPECT_CALL(dynamic_control, ListDynamicPartitionsForSlot(_, _, _))
      .WillRepeatedly(
          DoAll(SetArgPointee<2, std::vector<std::string>>({part.name}),
                Return(true)));

  BuildActions(install_plan_, &dynamic_control);

  FilesystemVerifierActionTestDelegate delegate;
  processor_.set_delegate(&delegate);

  loop_.PostTask(FROM_HERE,
                 base::Bind(&ActionProcessor::StartProcessing,
                            base::Unretained(&processor_)));
  loop_.Run();

  ASSERT_FALSE(processor_.IsRunning());
  ASSERT_TRUE(delegate.ran());
  if (enable_verity) {
    std::vector<unsigned char> actual_fec(fec_size);
    ssize_t bytes_read = 0;
    ASSERT_TRUE(utils::PReadAll(cow_fd.get(),
                                actual_fec.data(),
                                actual_fec.size(),
                                fec_start_offset,
                                &bytes_read));
    ASSERT_EQ(actual_fec, fec_data_);
    std::vector<unsigned char> actual_hash_tree(hash_tree_size);
    ASSERT_TRUE(utils::PReadAll(cow_fd.get(),
                                actual_hash_tree.data(),
                                actual_hash_tree.size(),
                                HASH_TREE_START_OFFSET,
                                &bytes_read));
    ASSERT_EQ(actual_hash_tree, hash_tree_data_);
  }
  if (clear_target_hash) {
    ASSERT_EQ(ErrorCode::kNewRootfsVerificationError, delegate.code());
  } else {
    ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
  }
}

TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Success) {
  DoTestVABC(false, false);
}

TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Target_Mismatch) {
  DoTestVABC(true, false);
}

TEST_F(FilesystemVerifierActionTest, VABC_Verity_Success) {
  DoTestVABC(false, true);
}

TEST_F(FilesystemVerifierActionTest, VABC_Verity_ReadAfterWrite) {
  ASSERT_NO_FATAL_FAILURE(DoTestVABC(false, true));
  // Run FS verification again, w/o writing verity. We have seen a bug where
  // attempting to run fs again will cause previously written verity data to be
  // dropped, so cover this scenario.
  ASSERT_GE(install_plan_.partitions.size(), 1UL);
  auto& part = install_plan_.partitions[0];
  install_plan_.write_verity = false;
  part.readonly_target_path = target_part_.path();
  NiceMock<MockDynamicPartitionControl> dynamic_control;
  EnableVABC(&dynamic_control, part.name);

  // b/186196758 is only visible if we repeatedely run FS verification w/o
  // writing verity
  for (int i = 0; i < 3; i++) {
    BuildActions(install_plan_, &dynamic_control);

    FilesystemVerifierActionTestDelegate delegate;
    processor_.set_delegate(&delegate);
    loop_.PostTask(
        FROM_HERE,
        base::Bind(
            [](ActionProcessor* processor) { processor->StartProcessing(); },
            base::Unretained(&processor_)));
    loop_.Run();
    ASSERT_FALSE(processor_.IsRunning());
    ASSERT_TRUE(delegate.ran());
    ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
  }
}

TEST_F(FilesystemVerifierActionTest, VABC_Verity_Target_Mismatch) {
  DoTestVABC(true, true);
}

}  // namespace chromeos_update_engine
