chromium/chromeos/ash/components/fwupd/firmware_update_manager.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/fwupd/firmware_update_manager.h"

#include <algorithm>
#include <optional>
#include <string_view>
#include <utility>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/fwupd_download_client.h"
#include "ash/webui/firmware_update_ui/mojom/firmware_update.mojom.h"
#include "base/base_paths.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_map.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/json/json_string_value_serializer.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "chromeos/ash/components/dbus/fwupd/dbus_constants.h"
#include "chromeos/ash/components/dbus/fwupd/fwupd_client.h"
#include "chromeos/ash/components/fwupd/histogram_util.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "components/device_event_log/device_event_log.h"
#include "crypto/sha2.h"
#include "dbus/message.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/zlib/google/compression_utils.h"
#include "url/gurl.h"

namespace ash {

namespace {

static constexpr auto FwupdStatusStringMap =
    base::MakeFixedFlatMap<FwupdStatus, const char*>(
        {{FwupdStatus::kUnknown, "Unknown state"},
         {FwupdStatus::kIdle, "Idle state"},
         {FwupdStatus::kLoading, "Loading a resource"},
         {FwupdStatus::kDecompressing, "Decompressing firmware"},
         {FwupdStatus::kDeviceRestart, "Restarting the device"},
         {FwupdStatus::kDeviceWrite, "Writing to a device"},
         {FwupdStatus::kDeviceVerify, "Verifying (reading) a device"},
         {FwupdStatus::kScheduling, "Scheduling an offline update"},
         {FwupdStatus::kDownloading, "A file is downloading"},
         {FwupdStatus::kDeviceRead, "Reading from a device"},
         {FwupdStatus::kDeviceErase, "Erasing a device"},
         {FwupdStatus::kWaitingForAuth, "Waiting for authentication"},
         {FwupdStatus::kDeviceBusy, "The device is busy"},
         {FwupdStatus::kShutdown, "The daemon is shutting down"},
         {FwupdStatus::kWaitingForUser, "Waiting for user action"}});

const char* GetFwupdStatusString(FwupdStatus enum_val) {
  DCHECK(base::Contains(FwupdStatusStringMap, enum_val));
  return FwupdStatusStringMap.at(enum_val);
}

const char kBaseRootPath[] = "firmware-updates";
const char kCachePath[] = "cache";
const char kCabFileExtension[] = ".cab";
const char kAllowedFilepathChars[] =
    "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-._";
const char kHttpsComponent[] = "https:";
const char kFileComponent[] = "file:";
const char kLVFSRemoteId[] = "lvfs";
const char kLVFSMirrorBaseURL[] =
    "https://storage.googleapis.com/chromeos-localmirror/lvfs/";
constexpr std::string_view kMirrorJcatFileName = "firmware.xml.xz.jcat";
constexpr std::string_view kMirrorZipFileName = "firmware.xml.gz";
const char kLocalFirmwareBasePath[] = "/var/lib/fwupd/metadata/";
const char kLocalMetadataFileName[] = "metadata.xml.zst";

FirmwareUpdateManager* g_instance = nullptr;

MethodResult GetMethodResultFromFwupdDbusResult(FwupdDbusResult error) {
  switch (error) {
    case FwupdDbusResult::kSuccess:
      return MethodResult::kSuccess;
    case FwupdDbusResult::kInternalError:
      return MethodResult::kInternalError;
    case FwupdDbusResult::kVersionNewerError:
      return MethodResult::kVersionNewerError;
    case FwupdDbusResult::kVersionSameError:
      return MethodResult::kVersionSameError;
    case FwupdDbusResult::kAlreadyPendingError:
      return MethodResult::kAlreadyPendingError;
    case FwupdDbusResult::kAuthFailedError:
      return MethodResult::kAuthFailedError;
    case FwupdDbusResult::kReadError:
      return MethodResult::kReadError;
    case FwupdDbusResult::kWriteError:
      return MethodResult::kWriteError;
    case FwupdDbusResult::kInvalidFileError:
      return MethodResult::kInvalidFileError;
    case FwupdDbusResult::kNotFoundError:
      return MethodResult::kNotFoundError;
    case FwupdDbusResult::kNothingToDoError:
      return MethodResult::kNothingToDoError;
    case FwupdDbusResult::kNotSupportedError:
      return MethodResult::kNotSupportedError;
    case FwupdDbusResult::kSignatureInvalidError:
      return MethodResult::kSignatureInvalidError;
    case FwupdDbusResult::kAcPowerRequiredError:
      return MethodResult::kAcPowerRequiredError;
    case FwupdDbusResult::kPermissionDeniedError:
      return MethodResult::kPermissionDeniedError;
    case FwupdDbusResult::kBrokenSystemError:
      return MethodResult::kBrokenSystemError;
    case FwupdDbusResult::kBatteryLevelTooLowError:
      return MethodResult::kBatteryLevelTooLowError;
    case FwupdDbusResult::kNeedsUserActionError:
      return MethodResult::kNeedsUserActionError;
    case FwupdDbusResult::kAuthExpiredError:
      return MethodResult::kAuthExpiredError;
    case FwupdDbusResult::kUnknownError:
      return MethodResult::kUnknownError;
  }
}

base::File OpenAndGetFile(const base::FilePath& download_path) {
  return base::File(download_path,
                    base::File::FLAG_OPEN | base::File::FLAG_READ);
}

base::File VerifyChecksum(base::File file, const std::string& checksum) {
  // Sha256 is 32 bytes, if it isn't Sha256 then return false.
  // The Sha256 string representation is 64 bytes, Sha256 is 32 bytes long.
  if (checksum.length() != crypto::kSHA256Length * 2) {
    return base::File();
  }

  const int64_t raw_file_length = file.GetLength();

  // Length of the file should not exceed int::max.
  if (raw_file_length > std::numeric_limits<int>::max()) {
    return base::File();
  }

  // Safe to truncate down to <int>.
  int file_length = raw_file_length;

  // Check checksum of the file.
  std::vector<char> buf(file_length);
  if (UNSAFE_TODO(file.Read(0, buf.data(), file_length)) != file_length) {
    return base::File();
  }

  const std::string_view contents(buf.data(), file_length);

  const std::string sha_contents = crypto::SHA256HashString(contents);

  const std::string encoded_sha =
      base::ToLowerASCII(base::HexEncode(sha_contents));

  if (encoded_sha != checksum) {
    FIRMWARE_LOG(ERROR) << "Wrong checksum, expected: " << checksum
                        << ", got: " << encoded_sha;
    return base::File();
  }

  // Reset current pointer of file so that it can be read again.
  if (file.Seek(base::File::FROM_BEGIN, 0) != 0) {
    return base::File();
  }

  return file;
}

bool CreateDirIfNotExists(const base::FilePath& path) {
  return base::DirectoryExists(path) || base::CreateDirectory(path);
}

firmware_update::mojom::FirmwareUpdatePtr CreateUpdate(
    const FwupdUpdate& update_details,
    const std::string& device_id,
    const std::string& device_name) {
  auto update = firmware_update::mojom::FirmwareUpdate::New();
  update->device_id = device_id;
  update->device_name = base::UTF8ToUTF16(device_name);
  update->device_version = update_details.version;
  update->device_description = base::UTF8ToUTF16(update_details.description);
  update->priority =
      firmware_update::mojom::UpdatePriority(update_details.priority);
  update->filepath = update_details.filepath;
  update->checksum = update_details.checksum;
  return update;
}

constexpr net::NetworkTrafficAnnotationTag kFwupdFirmwareUpdateNetworkTag =
    net::DefineNetworkTrafficAnnotation("fwupd_firmware_update", R"(
        semantics {
          sender: "FWUPD firmware update"
          description:
            "Get the firmware update patch file from url and store it in the "
            "the device cache. This is used to update a specific peripheral's "
            "firmware."

          trigger:
            "Triggered by the user when they explicitly use the Firmware Update"
            " UI to update their peripheral."
          data: "None."
          destination: GOOGLE_OWNED_SERVICE
        }
        policy {
          cookies_allowed: NO
          setting:
             "This feature is used when the user updates their firmware."
          policy_exception_justification:
             "This request is made based on the user decision to update "
             "firmware."
        })");

std::unique_ptr<network::SimpleURLLoader> CreateSimpleURLLoader(GURL url) {
  auto resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = url;
  resource_request->method = "GET";
  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;

  return network::SimpleURLLoader::Create(std::move(resource_request),
                                          kFwupdFirmwareUpdateNetworkTag);
}

int GetResponseCode(network::SimpleURLLoader* simple_loader) {
  if (simple_loader->ResponseInfo() && simple_loader->ResponseInfo()->headers) {
    return simple_loader->ResponseInfo()->headers->response_code();
  } else {
    return -1;
  }
}

// TODO(michaelcheco): Determine if more granular states are needed.
firmware_update::mojom::UpdateState GetUpdateState(FwupdStatus fwupd_status) {
  switch (fwupd_status) {
    case FwupdStatus::kUnknown:
      return firmware_update::mojom::UpdateState::kUnknown;
    case FwupdStatus::kIdle:
    case FwupdStatus::kLoading:
    case FwupdStatus::kDecompressing:
    case FwupdStatus::kDeviceVerify:
    case FwupdStatus::kScheduling:
    case FwupdStatus::kDownloading:
    case FwupdStatus::kDeviceRead:
    case FwupdStatus::kDeviceErase:
    case FwupdStatus::kWaitingForAuth:
    case FwupdStatus::kDeviceBusy:
    case FwupdStatus::kShutdown:
      return firmware_update::mojom::UpdateState::kIdle;
    case FwupdStatus::kDeviceRestart:
      return firmware_update::mojom::UpdateState::kRestarting;
    case FwupdStatus::kDeviceWrite:
      return firmware_update::mojom::UpdateState::kUpdating;
    case FwupdStatus::kWaitingForUser:
      return firmware_update::mojom::UpdateState::kWaitingForUser;
  }
}

bool IsValidFirmwarePatchFile(const base::FilePath& filepath) {
  // Check if the extension is .cab.
  std::string extension = filepath.Extension();
  if (extension != kCabFileExtension) {
    return false;
  }

  // Check for invalid characters in filepath.
  return base::ContainsOnlyChars(filepath.BaseName().value(),
                                 kAllowedFilepathChars);
}

// Converts a FwupdRequest into a mojom DeviceRequest.
firmware_update::mojom::DeviceRequestPtr GetDeviceRequest(
    FwupdRequest request) {
  return firmware_update::mojom::DeviceRequest::New(
      static_cast<firmware_update::mojom::DeviceRequestId>(request.id),
      static_cast<firmware_update::mojom::DeviceRequestKind>(request.kind));
}

bool GetMetadataFileInfo(base::FilePath filepath, base::File::Info* info) {
  if (!base::PathExists(filepath)) {
    FIRMWARE_LOG(DEBUG) << "Local firmware file not found at: " << filepath;
    return false;
  }
  base::File file(filepath, base::File::FLAG_OPEN | base::File::FLAG_READ);
  if (!file.IsValid()) {
    FIRMWARE_LOG(DEBUG) << "Couldn't open file: " << filepath;
    return false;
  }
  if (!file.GetInfo(info)) {
    FIRMWARE_LOG(DEBUG) << "Couldn't get info for file: " << filepath;
    return false;
  }
  return true;
}

std::string GetFirmwareFileNameFromJsonString(std::string json_content) {
  if (json_content == "") {
    FIRMWARE_LOG(ERROR) << "Failed to deserialize json for empty string";
    return "";
  }

  std::string error;
  JSONStringValueDeserializer messages_deserializer(json_content);
  std::unique_ptr<base::Value> value =
      messages_deserializer.Deserialize(/*error_code=*/nullptr, &error);
  if (error != "") {
    FIRMWARE_LOG(ERROR) << "Failed to deserialize json string with error: "
                        << error;
    return "";
  }
  DCHECK(value);
  auto dictionary =
      std::make_unique<base::Value::Dict>(std::move(*value).TakeDict());
  base::Value::List* items = dictionary->FindList("Items");
  if (items == nullptr || items->empty()) {
    FIRMWARE_LOG(ERROR) << "Couldn't find 'Items' key in checksum json file";
    return "";
  }
  auto* filename = items->front().GetDict().FindString("Id");
  if (filename == nullptr) {
    FIRMWARE_LOG(ERROR) << "Couldn't find 'Id' key in checksum json file";
    return "";
  }
  return *filename;
}

bool CreateAndClearFile(base::FilePath filepath) {
  // TODO(michaelcheco): Verify that creating the empty file is
  // necessary.
  return base::WriteFile(filepath, /*data=*/"");
}

// File errors are expected on non ChromeOS devices because access may
// not be permitted.
device_event_log::LogLevel LogLevelForFileErrors() {
  return base::SysInfo::IsRunningOnChromeOS()
             ? device_event_log::LOG_LEVEL_ERROR
             : device_event_log::LOG_LEVEL_DEBUG;
}

void CleanUpTempFiles(base::FilePath checksum_filepath,
                      base::File checksum_file,
                      base::FilePath firmware_filepath,
                      base::File firmware_file) {
  if (!checksum_filepath.empty()) {
    base::DeleteFile(checksum_filepath);
  }
  if (checksum_file.IsValid()) {
    checksum_file.Close();
  }
  if (!firmware_filepath.empty()) {
    base::DeleteFile(firmware_filepath);
  }
  if (firmware_file.IsValid()) {
    firmware_file.Close();
  }
}

std::string ReadFileToString(const base::FilePath& filename) {
  FIRMWARE_LOG(DEBUG) << "ReadFileToString: " << filename;
  std::string file_contents;
  base::ReadFileToString(filename, &file_contents);
  return file_contents;
}

std::string UncompressFileAndGetFilename(std::string file_contents) {
  // Log an EVENT here in case b/339310876 comes up again.
  FIRMWARE_LOG(EVENT) << "GzipUncompress: " << file_contents.size();
  std::string content;
  compression::GzipUncompress(file_contents, &content);
  std::string firmware_filename = GetFirmwareFileNameFromJsonString(content);
  return firmware_filename;
}

bool RefreshRemoteAllowed(FirmwareUpdateManager::Source source,
                          bool refresh_remote_for_testing,
                          bool is_metered) {
  FIRMWARE_LOG(DEBUG) << "RefreshRemoteAllowed()";
  DCHECK(NetworkHandler::IsInitialized());
  const bool connection_ok =
      !is_metered || source == FirmwareUpdateManager::Source::kUI;
  FIRMWARE_LOG(DEBUG) << "Connection metered: " << is_metered
                      << ", Source: " << static_cast<int>(source)
                      << ", Refresh Remote connection okay: " << connection_ok;
  if (!connection_ok) {
    return false;
  }

  // Always refresh the remote in tests for consistent results.
  if (refresh_remote_for_testing) {
    return true;
  }
  const base::FilePath local_firmware_path =
      base::FilePath(kLocalFirmwareBasePath)
          .Append(kLVFSRemoteId)
          .Append(kLocalMetadataFileName);
  base::File::Info info;
  if (!GetMetadataFileInfo(local_firmware_path, &info)) {
    // Allow RefreshRemote if file not found or info not found
    return true;
  }
  base::TimeDelta age = base::Time::Now() - info.last_modified;
  if (age >= base::Hours(0) && age <= base::Hours(24)) {
    FIRMWARE_LOG(DEBUG) << "Local firmware file age < 24 hours, age: "
                        << static_cast<int>(age.InHours());
    return false;
  }
  FIRMWARE_LOG(DEBUG) << "Local firmware file age > 24 hours, age: "
                      << static_cast<int>(age.InHours());
  return true;
}

}  // namespace

FirmwareUpdateManager::FirmwareUpdateManager()
    : task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})) {
  FIRMWARE_LOG(EVENT) << "FirmwareUpdateManager()";
  if (FwupdClient::Get()) {
    FwupdClient::Get()->AddObserver(this);
  }

  DCHECK_EQ(nullptr, g_instance);
  g_instance = this;
}

FirmwareUpdateManager::~FirmwareUpdateManager() {
  DCHECK_EQ(this, g_instance);
  if (FwupdClient::Get()) {
    FwupdClient::Get()->RemoveObserver(this);
  }
  g_instance = nullptr;
}

// static
FirmwareUpdateManager* FirmwareUpdateManager::Get() {
  DCHECK(g_instance);
  return g_instance;
}

// static
bool FirmwareUpdateManager::IsInitialized() {
  return g_instance;
}

void FirmwareUpdateManager::AddObserver(Observer* observer) {
  observer_list_.AddObserver(observer);
}

void FirmwareUpdateManager::RemoveObserver(Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

void FirmwareUpdateManager::NotifyCriticalFirmwareUpdateReceived() {
  for (auto& observer : observer_list_) {
    observer.OnFirmwareUpdateReceived();
  }
}

void FirmwareUpdateManager::RecordDeviceMetrics(int num_devices) {
  firmware_update::metrics::EmitDeviceCount(num_devices, is_first_response_);
}

void FirmwareUpdateManager::RecordUpdateMetrics() {
  firmware_update::metrics::EmitUpdateCount(
      updates_.size(), GetNumCriticalUpdates(), is_first_response_);
}

int FirmwareUpdateManager::GetNumCriticalUpdates() {
  int critical_update_count = 0;
  for (const auto& update : updates_) {
    if (update->priority == firmware_update::mojom::UpdatePriority::kCritical) {
      critical_update_count++;
    }
  }
  return critical_update_count;
}

const base::FilePath FirmwareUpdateManager::GetCacheDirPath() {
  base::FilePath root_dir;
  CHECK(base::PathService::Get(base::DIR_TEMP, &root_dir));
  base::FilePath cache_path = root_dir.Append(FILE_PATH_LITERAL(kBaseRootPath))
                                  .Append(FILE_PATH_LITERAL(kCachePath));
  if (refresh_remote_for_testing_) {
    // Generate a unique name in the cache while testing to avoid collisions
    // with other tests.
    return cache_path.Append(
        base::Uuid::GenerateRandomV4().AsLowercaseString());
  }
  return cache_path;
}

void FirmwareUpdateManager::NotifyUpdateListObservers() {
  for (auto& observer : update_list_observers_) {
    observer->OnUpdateListChanged(mojo::Clone(updates_));
  }
  is_fetching_updates_ = false;
}

bool FirmwareUpdateManager::HasPendingUpdates() {
  return !devices_pending_update_.empty();
}

void FirmwareUpdateManager::ObservePeripheralUpdates(
    mojo::PendingRemote<firmware_update::mojom::UpdateObserver> observer) {
  FIRMWARE_LOG(USER) << "ObservePeripheralUpdates. Observers: "
                     << update_list_observers_.size();
  update_list_observers_.Add(std::move(observer));
  if (!HasPendingUpdates()) {
    RequestAllUpdates(FirmwareUpdateManager::Source::kUI);
  }
}

// TODO(michaelcheco): Handle the case where the app is closed during an
// install.
void FirmwareUpdateManager::ResetInstallState() {
  install_controller_receiver_.reset();
  update_progress_observer_.reset();
  device_request_observer_.reset();
  last_fwupd_status_ = FwupdStatus::kUnknown;
  last_device_request_ = nullptr;
  last_request_started_timestamp_ = std::nullopt;
}

void FirmwareUpdateManager::PrepareForUpdate(
    const std::string& device_id,
    PrepareForUpdateCallback callback) {
  DCHECK(!device_id.empty());

  mojo::PendingRemote<firmware_update::mojom::InstallController>
      pending_remote = install_controller_receiver_.BindNewPipeAndPassRemote();
  install_controller_receiver_.set_disconnect_handler(base::BindOnce(
      &FirmwareUpdateManager::ResetInstallState, base::Unretained(this)));
  std::move(callback).Run(std::move(pending_remote));
}

void FirmwareUpdateManager::FetchInProgressUpdate(
    FetchInProgressUpdateCallback callback) {
  std::move(callback).Run(mojo::Clone(inflight_update_));
}

// Query all updates for all devices.
void FirmwareUpdateManager::RequestAllUpdates(Source source) {
  // Return if FwupdClient or NetworkHandler not initialized for unittests
  if (!FwupdClient::Get() || !NetworkHandler::IsInitialized()) {
    return;
  }

  if (should_show_notification_for_test_) {
    // Short circuit to immediately display notification.
    NotifyCriticalFirmwareUpdateReceived();
    return;
  }

  if (is_fetching_updates_) {
    FIRMWARE_LOG(DEBUG)
        << "One instance of RequestAllUpdates already is progress; skipped";
    return;
  }

  FIRMWARE_LOG(USER) << "RequestAllUpdates: " << static_cast<int>(source);
  is_fetching_updates_ = true;
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&RefreshRemoteAllowed, source, refresh_remote_for_testing_,
                     NetworkHandler::Get()
                         ->network_state_handler()
                         ->default_network_is_metered()),
      base::BindOnce(&FirmwareUpdateManager::MaybeRefreshRemote,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FirmwareUpdateManager::MaybeRefreshRemote(bool refresh_allowed) {
  if (refresh_allowed) {
    FIRMWARE_LOG(USER) << "Refreshing LVFS remote";
    RefreshRemote();
  } else {
    FIRMWARE_LOG(USER) << "RequestAllUpdates: Skipping refresh remote for LVFS";
    RequestDevices();
  }
}

void FirmwareUpdateManager::RequestDevices() {
  if (FwupdClient::Get()) {
    FIRMWARE_LOG(USER) << "RequestDevices";
    FwupdClient::Get()->RequestDevices();
  } else {
    FIRMWARE_LOG(USER) << "RequestDevices: No FwupdCleint";
  }
}

void FirmwareUpdateManager::RequestUpdates(const std::string& device_id) {
  if (FwupdClient::Get()) {
    FIRMWARE_LOG(USER) << "RequestUpdates";
    FwupdClient::Get()->RequestUpdates(device_id);
  } else {
    FIRMWARE_LOG(USER) << "RequestDevices: No FwupdCleint";
  }
}

void FirmwareUpdateManager::StartInstall(const std::string& device_id,
                                         const base::FilePath& filepath,
                                         MethodCallback callback) {
  base::FilePath root_dir;
  CHECK(base::PathService::Get(base::DIR_TEMP, &root_dir));
  const base::FilePath cache_path = GetCacheDirPath();

  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](const base::FilePath& path) { return CreateDirIfNotExists(path); },
          cache_path),
      base::BindOnce(&FirmwareUpdateManager::CreateLocalPatchFile,
                     weak_ptr_factory_.GetWeakPtr(), cache_path, device_id,
                     filepath, std::move(callback)));
}

void FirmwareUpdateManager::CreateLocalPatchFile(
    const base::FilePath& cache_path,
    const std::string& device_id,
    const base::FilePath& filepath,
    MethodCallback callback,
    bool create_dir_success) {
  if (!create_dir_success) {
    FIRMWARE_LOG(ERROR)
        << "Firmware update directory does not exist and cannot be created.";
    std::move(callback).Run(MethodResult::kFailedToCreateUpdateDirectory);
    return;
  }
  const base::FilePath patch_path =
      cache_path.Append(filepath.BaseName().value());

  // Create the patch file.
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&CreateAndClearFile, patch_path),
      base::BindOnce(&FirmwareUpdateManager::MaybeDownloadFileToInternal,
                     weak_ptr_factory_.GetWeakPtr(), patch_path, device_id,
                     filepath, std::move(callback)));
}

void FirmwareUpdateManager::MaybeDownloadFileToInternal(
    const base::FilePath& patch_path,
    const std::string& device_id,
    const base::FilePath& filepath,
    MethodCallback callback,
    bool write_file_success) {
  if (!write_file_success) {
    FIRMWARE_LOG(ERROR) << "Writing to file failed: " << patch_path;
    std::move(callback).Run(MethodResult::kFailedToCreatePatchFile);
    return;
  }

  std::vector<base::FilePath::StringType> components = filepath.GetComponents();

  if (components[0] == kHttpsComponent) {
    // Firmware patch is available for download.
    DownloadFileToInternal(patch_path, device_id, filepath,
                           std::move(callback));
    return;
  }

  if (components[0] == kFileComponent) {
    // Firmware patch is already available from the local filesystem.
    size_t filepath_start = filepath.value().find(components[1]);
    if (filepath_start == std::string::npos) {
      FIRMWARE_LOG(ERROR) << "Empty patch file: " << filepath.value();
      std::move(callback).Run(MethodResult::kEmptyPatchFile);
      return;
    }
    const base::FilePath file(filepath.value().substr(filepath_start - 1));
    std::map<std::string, bool> options = {
        {"none", false}, {"force", true}, {"allow-reinstall", true}};
    task_runner_->PostTaskAndReplyWithResult(
        FROM_HERE, base::BindOnce(&OpenAndGetFile, file),
        base::BindOnce(&FirmwareUpdateManager::OnGetFile,
                       weak_ptr_factory_.GetWeakPtr(), device_id,
                       std::move(options), std::move(callback)));
    return;
  }

  FIRMWARE_LOG(ERROR) << "Invalid file or download URI: " << filepath.value();
  std::move(callback).Run(MethodResult::kInvalidPatchFileUri);
}

void FirmwareUpdateManager::DownloadFileToInternal(
    const base::FilePath& patch_path,
    const std::string& device_id,
    const base::FilePath& filepath,
    MethodCallback callback) {
  const std::string url = filepath.value();
  GURL download_url(fake_url_for_testing_.empty() ? url
                                                  : fake_url_for_testing_);

  std::unique_ptr<network::SimpleURLLoader> simple_loader =
      CreateSimpleURLLoader(download_url);
  DCHECK(FwupdDownloadClient::Get());

  scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
      FwupdDownloadClient::Get()->GetURLLoaderFactory();
  if (!loader_factory) {
    DEVICE_LOG(device_event_log::LOG_TYPE_FIRMWARE, LogLevelForFileErrors())
        << "Url loader factory not found";
    std::move(callback).Run(MethodResult::kFailedToDownloadToFile);
    return;
  }
  // Save the pointer before moving `simple_loader` in the following call to
  // `DownloadToFile()`.
  auto* loader_ptr = simple_loader.get();

  loader_ptr->DownloadToFile(
      loader_factory.get(),
      base::BindOnce(&FirmwareUpdateManager::OnUrlDownloadedToFile,
                     weak_ptr_factory_.GetWeakPtr(), device_id,
                     std::move(simple_loader), std::move(callback)),
      patch_path);
}

void FirmwareUpdateManager::OnUrlDownloadedToFile(
    const std::string& device_id,
    std::unique_ptr<network::SimpleURLLoader> simple_loader,
    MethodCallback callback,
    base::FilePath download_path) {
  if (simple_loader->NetError() != net::OK) {
    FIRMWARE_LOG(ERROR) << "Downloading to file failed with error code: "
                        << GetResponseCode(simple_loader.get())
                        << ", network error " << simple_loader->NetError();
    std::move(callback).Run(MethodResult::kFailedToDownloadToFile);
    return;
  }

  // TODO(jimmyxgong): Determine if this options map can be static or will need
  // to remain dynamic.
  // Fwupd Install Dbus flags, flag documentation can be found in
  // https://github.com/fwupd/fwupd/blob/main/libfwupd/fwupd-enums.h#L749.
  std::map<std::string, bool> options = {{"none", false},
                                         {"force", true},
                                         {"allow-older", true},
                                         {"allow-reinstall", true}};

  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&OpenAndGetFile, download_path),
      base::BindOnce(&FirmwareUpdateManager::OnGetFile,
                     weak_ptr_factory_.GetWeakPtr(), device_id,
                     std::move(options), std::move(callback)));
}

void FirmwareUpdateManager::OnGetFile(const std::string& device_id,
                                      FirmwareInstallOptions options,
                                      MethodCallback callback,
                                      base::File file) {
  if (!file.IsValid()) {
    FIRMWARE_LOG(ERROR) << "Invalid file for device: " << device_id;
    std::move(callback).Run(MethodResult::kInvalidFile);
    return;
  }

  DCHECK(inflight_update_.is_null());
  for (const auto& update : updates_) {
    if (update->device_id == device_id) {
      inflight_update_ = mojo::Clone(update);
      break;
    }
  }

  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&VerifyChecksum, std::move(file),
                     inflight_update_->checksum),
      base::BindOnce(&FirmwareUpdateManager::InstallUpdate,
                     weak_ptr_factory_.GetWeakPtr(), device_id,
                     std::move(options), std::move(callback)));
}

void FirmwareUpdateManager::InstallUpdate(const std::string& device_id,
                                          FirmwareInstallOptions options,
                                          MethodCallback callback,
                                          base::File patch_file) {
  if (!patch_file.IsValid()) {
    inflight_update_.reset();
    std::move(callback).Run(MethodResult::kInvalidPatchFile);
    return;
  }

  FwupdClient::Get()->InstallUpdate(
      device_id, base::ScopedFD(patch_file.TakePlatformFile()), options,
      base::BindOnce(&FirmwareUpdateManager::OnInstallResponse,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void FirmwareUpdateManager::OnDeviceListResponse(FwupdDeviceList* devices) {
  DCHECK(devices);
  DCHECK(!HasPendingUpdates());
  FIRMWARE_LOG(EVENT) << "OnDeviceListResponse(). Devices: " << devices->size();

  // Clear all cached updates prior to fetching the new update list.
  updates_.clear();

  RecordDeviceMetrics(devices->size());

  // Fire the observer with an empty list if there are no devices in the
  // response.
  if (devices->empty()) {
    NotifyUpdateListObservers();
    return;
  }

  for (const auto& device : *devices) {
    devices_pending_update_[device.id] = device;
    RequestUpdates(device.id);
  }
}

void FirmwareUpdateManager::ShowNotificationIfRequired() {
  for (const auto& update : updates_) {
    if (update->priority == firmware_update::mojom::UpdatePriority::kCritical &&
        !base::Contains(devices_already_notified_, update->device_id)) {
      devices_already_notified_.insert(update->device_id);
      NotifyCriticalFirmwareUpdateReceived();
    }
  }
}

void FirmwareUpdateManager::OnUpdateListResponse(const std::string& device_id,
                                                 FwupdUpdateList* updates) {
  DCHECK(updates);
  DCHECK(base::Contains(devices_pending_update_, device_id));

  // If there are updates, then choose the first one.
  if (!updates->empty()) {
    auto device_name = devices_pending_update_[device_id].device_name;
    // Create a complete FirmwareUpdate and add to updates_.
    updates_.push_back(CreateUpdate(updates->front(), device_id, device_name));
  }

  // Remove the pending device.
  devices_pending_update_.erase(device_id);

  if (HasPendingUpdates()) {
    return;
  }

  FIRMWARE_LOG(EVENT) << "OnUpdateListResponse(). Updates: " << updates_.size();
  RecordUpdateMetrics();

  // We only want to show the notification once, at startup.
  if (is_first_response_) {
    ShowNotificationIfRequired();
    is_first_response_ = false;
  }

  // Fire the observer since there are no remaining devices pending updates.
  NotifyUpdateListObservers();
}

void FirmwareUpdateManager::OnInstallResponse(MethodCallback callback,
                                              FwupdDbusResult result) {
  MethodResult install_result = GetMethodResultFromFwupdDbusResult(result);
  bool success = install_result == MethodResult::kSuccess;
  FIRMWARE_LOG(EVENT) << "OnInstallResponse(). Success: " << success;

  if (!success) {
    firmware_update::metrics::EmitInstallFailedWithStatus(last_fwupd_status_);

    // If the install failed and the last fwupd status was WaitingForUser,
    // this install failure probably occurred because of a timeout waiting for
    // user action from a device request, so we record the duration of that
    // request.
    if (last_request_started_timestamp_.has_value() &&
        !last_request_started_timestamp_->is_null() &&
        !last_device_request_.is_null() &&
        last_fwupd_status_ == FwupdStatus::kWaitingForUser) {
      const base::TimeDelta request_duration =
          base::Time::Now() - last_request_started_timestamp_.value();
      firmware_update::metrics::EmitFailedDeviceRequestDuration(
          request_duration, last_device_request_->id);
      std::move(callback).Run(MethodResult::kInstallFailedTimeout);
    } else {
      std::move(callback).Run(install_result);
    }
    return;
  }
  std::move(callback).Run(MethodResult::kSuccess);
}

void FirmwareUpdateManager::InstallComplete(MethodResult result) {
  if (result != MethodResult::kSuccess) {
    FIRMWARE_LOG(ERROR) << "Install failed: " << static_cast<int>(result);
  } else {
    FIRMWARE_LOG(USER) << "Install complete";
  }
  firmware_update::metrics::EmitInstallResult(result);

  // If the firmware update app is closed, the observer is no longer bound.
  if (update_progress_observer_.is_bound()) {
    auto state = result == MethodResult::kSuccess
                     ? firmware_update::mojom::UpdateState::kSuccess
                     : firmware_update::mojom::UpdateState::kFailed;
    auto update = ash::firmware_update::mojom::InstallationProgress::New(
        /**percentage=*/100, state);
    update_progress_observer_->OnStatusChanged(std::move(update));
  }

  // Any updates are completed at this point, reset all cached.
  ResetInstallState();

  if (inflight_update_) {
    devices_already_notified_.erase(inflight_update_->device_id);
    inflight_update_.reset();
  }

  // Request all updates to refresh the update list after an install.
  RequestAllUpdates(FirmwareUpdateManager::Source::kInstallComplete);
}

void FirmwareUpdateManager::BindInterface(
    mojo::PendingReceiver<firmware_update::mojom::UpdateProvider>
        pending_receiver) {
  // Clear any bound receiver, since this service is a singleton and is bound
  // to the firmware updater UI it's possible that the app can be closed and
  // reopened multiple times resulting in multiple attempts to bind to this
  // receiver.
  receiver_.reset();

  receiver_.Bind(std::move(pending_receiver));
}

void FirmwareUpdateManager::OnDeviceRequestResponse(FwupdRequest request) {
  if (!device_request_observer_.is_bound()) {
    FIRMWARE_LOG(ERROR)
        << "OnDeviceRequestResponse triggered with unbound observer";
    return;
  }
  FIRMWARE_LOG(EVENT) << "OnDeviceRequestResponse(). Id: " << request.id
                      << ", Kind: " << request.kind;

  // Convert the FwupdRequest into a mojom DeviceRequest, then record the metric
  // and pass that request to observers.
  firmware_update::metrics::EmitDeviceRequest(GetDeviceRequest(request));
  device_request_observer_->OnDeviceRequest(GetDeviceRequest(request));

  // Save details about the request for metrics purposes.
  last_device_request_ = GetDeviceRequest(request);
  last_request_started_timestamp_ = base::Time::Now();
}

void FirmwareUpdateManager::OnPropertiesChangedResponse(
    FwupdProperties* properties) {
  if (!properties || !update_progress_observer_.is_bound() ||
      !properties->IsStatusValid() || !properties->IsPercentageValid()) {
    return;
  }
  const auto status = FwupdStatus(properties->GetStatus());

  // If the FwupdStatus just switched from WaitingForUser to anything else,
  // consider the request successful and record a metric.
  if (last_fwupd_status_ == FwupdStatus::kWaitingForUser &&
      status != FwupdStatus::kWaitingForUser &&
      last_request_started_timestamp_.has_value() &&
      !last_request_started_timestamp_->is_null() &&
      !last_device_request_.is_null()) {
    const base::TimeDelta request_duration =
        base::Time::Now() - last_request_started_timestamp_.value();
    firmware_update::metrics::EmitDeviceRequestSuccessfulWithDuration(
        request_duration, last_device_request_->id);

    // Reset these tracking variables now that we've used them.
    last_device_request_ = nullptr;
    last_request_started_timestamp_ = std::nullopt;
  }

  last_fwupd_status_ = status;
  const auto percentage = properties->GetPercentage();
  FIRMWARE_LOG(EVENT) << "OnPropertiesChangedResponse(). Status: "
                      << GetFwupdStatusString(static_cast<FwupdStatus>(status))
                      << ", Percentage: " << percentage;
  update_progress_observer_->OnStatusChanged(
      ash::firmware_update::mojom::InstallationProgress::New(
          percentage, GetUpdateState(status)));
}

void FirmwareUpdateManager::BeginUpdate(const std::string& device_id,
                                        const base::FilePath& filepath) {
  DCHECK(!filepath.empty());

  if (!IsValidFirmwarePatchFile(filepath)) {
    InstallComplete(MethodResult::kInvalidPatchFile);
    return;
  }

  StartInstall(device_id, filepath,
               base::BindOnce(&FirmwareUpdateManager::InstallComplete,
                              weak_ptr_factory_.GetWeakPtr()));
}

void FirmwareUpdateManager::RefreshRemote() {
  const base::FilePath cache_path = GetCacheDirPath();
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](const base::FilePath& path) { return CreateDirIfNotExists(path); },
          cache_path),
      base::BindOnce(&FirmwareUpdateManager::CreateTempFileAndDownload,
                     weak_ptr_factory_.GetWeakPtr(),
                     cache_path.Append(kMirrorZipFileName),
                     std::string(kMirrorJcatFileName),
                     base::BindOnce(&FirmwareUpdateManager::OnGetChecksumFile,
                                    weak_ptr_factory_.GetWeakPtr())));
}

void FirmwareUpdateManager::CreateTempFileAndDownload(
    base::FilePath local_path,
    std::string download_filename,
    DownloadCompleteCallback callback,
    bool create_dir_success) {
  if (!create_dir_success) {
    FIRMWARE_LOG(ERROR)
        << "Firmware update directory does not exist and cannot be created.";
    RefreshRemoteComplete(MethodResult::kFailedToCreateUpdateDirectory);
    return;
  }

  // Create the patch file.
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&CreateAndClearFile, local_path),
      base::BindOnce(&FirmwareUpdateManager::DownloadLvfsMirrorFile,
                     weak_ptr_factory_.GetWeakPtr(), download_filename,
                     local_path, std::move(callback)));
}

void FirmwareUpdateManager::DownloadLvfsMirrorFile(
    std::string filename,
    base::FilePath download_filepath,
    DownloadCompleteCallback callback,
    bool write_file_success) {
  if (!write_file_success) {
    FIRMWARE_LOG(ERROR) << "Writing to file failed: " << download_filepath;
    RefreshRemoteComplete(MethodResult::kFailedToCreatePatchFile);
    return;
  }
  FIRMWARE_LOG(DEBUG) << "File created at: " << download_filepath;
  GURL download_url(base::StrCat({kLVFSMirrorBaseURL, filename}));
  FIRMWARE_LOG(DEBUG) << "Downloading URL: " << download_url;

  std::unique_ptr<network::SimpleURLLoader> simple_loader =
      CreateSimpleURLLoader(download_url);
  DCHECK(FwupdDownloadClient::Get());

  scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
      FwupdDownloadClient::Get()->GetURLLoaderFactory();
  // This may happen in tests
  if (!loader_factory) {
    DEVICE_LOG(device_event_log::LOG_TYPE_FIRMWARE, LogLevelForFileErrors())
        << "Url loader factory not found";
    RefreshRemoteComplete(MethodResult::kFailedToDownloadToFile);
    return;
  }
  // Save the pointer before moving `simple_loader` in the following call to
  // `DownloadToFile()`.
  auto* loader_ptr = simple_loader.get();

  loader_ptr->DownloadToFile(
      loader_factory.get(),
      base::BindOnce(&FirmwareUpdateManager::GetFileDescriptor,
                     weak_ptr_factory_.GetWeakPtr(), std::move(simple_loader),
                     std::move(callback)),
      download_filepath);
}

void FirmwareUpdateManager::GetFileDescriptor(
    std::unique_ptr<network::SimpleURLLoader> simple_loader,
    DownloadCompleteCallback callback,
    base::FilePath download_path) {
  if (simple_loader->NetError() != net::OK) {
    DEVICE_LOG(device_event_log::LOG_TYPE_FIRMWARE, LogLevelForFileErrors())
        << "Downloading to file failed with error code: "
        << GetResponseCode(simple_loader.get()) << ", network error "
        << simple_loader->NetError();
    RefreshRemoteComplete(MethodResult::kFailedToDownloadToFile);
    return;
  }
  FIRMWARE_LOG(DEBUG) << "File downloaded to " << download_path;
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&OpenAndGetFile, download_path),
      base::BindOnce(std::move(callback), std::move(download_path)));
}

void FirmwareUpdateManager::OnGetChecksumFile(base::FilePath checksum_filepath,
                                              base::File checksum_file) {
  checksum_filepath_ = std::move(checksum_filepath);
  checksum_file_ = std::move(checksum_file);
  if (!checksum_file_.IsValid()) {
    FIRMWARE_LOG(ERROR) << "Invalid file: " << checksum_filepath_;
    RefreshRemoteComplete(MethodResult::kInvalidPatchFile);
    return;
  }
  FIRMWARE_LOG(DEBUG) << "OnGetChecksumFile: " << checksum_filepath_
                      << ", Reading file to string.";
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&ReadFileToString, checksum_filepath_),
      base::BindOnce(&FirmwareUpdateManager::GetFirmwareFilename,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FirmwareUpdateManager::GetFirmwareFilename(
    std::string file_contents) {
  size_t file_len = file_contents.size();
  if (file_len == 0) {
    FIRMWARE_LOG(ERROR) << "Invalid file contents: " << checksum_filepath_;
    RefreshRemoteComplete(MethodResult::kInvalidPatchFile);
    return;
  }
  if (file_len > 8) {
    // LVFS may append 8 bytes of garbage data that we need to ignore. See:
    // https://gitlab.com/fwupd/lvfs-website/-/blob/9430bd058f06eee468acf7230dcca4c6108c46c6/jcat/jcatfile.py#L56
    if (file_contents.substr(file_len - 8, 8) == "IHATECDN") {
      // Log an EVENT here in case b/339310876 comes up again.
      FIRMWARE_LOG(EVENT) << "GetFirmwareFilename: Truncating last 8 bytes.";
      file_contents = file_contents.substr(0, file_len - 8);
    }
  }

  FIRMWARE_LOG(DEBUG) << "GetFirmwareFilename: " << checksum_filepath_
                      << ", Uncompressing and parsing checksum file.";
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&UncompressFileAndGetFilename, file_contents),
      base::BindOnce(&FirmwareUpdateManager::TriggerDownloadOfFirmwareFile,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FirmwareUpdateManager::TriggerDownloadOfFirmwareFile(
    std::string firmware_filename) {
  if (firmware_filename.empty()) {
    FIRMWARE_LOG(ERROR)
        << "Failed to get firmware file name from checksum file";
    RefreshRemoteComplete(MethodResult::kFailedToGetFirmwareFilename);
    return;
  }
  FIRMWARE_LOG(DEBUG) << "Got firmware filename: " << firmware_filename;
  const base::FilePath cache_path = GetCacheDirPath();
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](const base::FilePath& path) { return CreateDirIfNotExists(path); },
          cache_path),
      base::BindOnce(&FirmwareUpdateManager::CreateTempFileAndDownload,
                     weak_ptr_factory_.GetWeakPtr(),
                     cache_path.Append(firmware_filename),
                     std::move(firmware_filename),
                     base::BindOnce(&FirmwareUpdateManager::UpdateMetadata,
                                    weak_ptr_factory_.GetWeakPtr())));
}

void FirmwareUpdateManager::UpdateMetadata(base::FilePath firmware_filepath,
                                           base::File firmware_file) {
  firmware_filepath_ = std::move(firmware_filepath);
  firmware_file_ = std::move(firmware_file);
  FIRMWARE_LOG(EVENT) << "UpdateMetadata: " << firmware_filepath_;
  if (!firmware_file_.IsValid()) {
    FIRMWARE_LOG(ERROR) << "Invalid file: " << firmware_filepath_;
    RefreshRemoteComplete(MethodResult::kInvalidPatchFile);
    return;
  }
  FwupdClient::Get()->UpdateMetadata(
      kLVFSRemoteId, base::ScopedFD(firmware_file_.TakePlatformFile()),
      base::ScopedFD(checksum_file_.TakePlatformFile()),
      base::BindOnce(&FirmwareUpdateManager::OnUpdateMetadataResponse,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FirmwareUpdateManager::OnUpdateMetadataResponse(FwupdDbusResult result) {
  RefreshRemoteComplete(GetMethodResultFromFwupdDbusResult(result));
}

void FirmwareUpdateManager::RefreshRemoteComplete(MethodResult result) {
  if (result != MethodResult::kSuccess) {
    DEVICE_LOG(device_event_log::LOG_TYPE_FIRMWARE, LogLevelForFileErrors())
        << "Refreshing LVFS remote failed: " << static_cast<int>(result);
  } else {
    FIRMWARE_LOG(USER) << "RefreshRemote completed";
  }
  firmware_update::metrics::EmitRefreshRemoteResult(result);

  // Cleanup and continue requesting devices after refresh remote is complete.
  task_runner_->PostTaskAndReply(
      FROM_HERE,
      base::BindOnce(&CleanUpTempFiles, std::move(checksum_filepath_),
                     std::move(checksum_file_), std::move(firmware_filepath_),
                     std::move(firmware_file_)),
      base::BindOnce(&FirmwareUpdateManager::RequestDevices,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FirmwareUpdateManager::AddDeviceRequestObserver(
    mojo::PendingRemote<firmware_update::mojom::DeviceRequestObserver>
        observer) {
  device_request_observer_.reset();
  device_request_observer_.Bind(std::move(observer));
}

void FirmwareUpdateManager::AddUpdateProgressObserver(
    mojo::PendingRemote<firmware_update::mojom::UpdateProgressObserver>
        observer) {
  update_progress_observer_.reset();
  update_progress_observer_.Bind(std::move(observer));
}

}  // namespace ash