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

#include "chrome/updater/update_service_impl.h"

#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/containers/contains.h"
#include "base/containers/queue.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/task/bind_post_task.h"
#include "base/task/post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/version.h"
#include "chrome/updater/check_for_updates_task.h"
#include "chrome/updater/configurator.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/installer.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/update_block_check.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/updater_version.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"

namespace updater {

namespace {

// The functions below are various adaptors between |update_client| and
// |UpdateService| types.
update_client::Callback MakeUpdateClientCallback(
    UpdateService::Callback callback) {
  return base::BindOnce(
      [](UpdateService::Callback callback, update_client::Error error) {
        std::move(callback).Run(static_cast<UpdateService::Result>(error));
      },
      std::move(callback));
}

UpdateService::UpdateState::State ToUpdateState(
    update_client::ComponentState component_state) {
  switch (component_state) {
    case update_client::ComponentState::kNew:
      return UpdateService::UpdateState::State::kNotStarted;

    case update_client::ComponentState::kChecking:
      return UpdateService::UpdateState::State::kCheckingForUpdates;

    case update_client::ComponentState::kDownloading:
    case update_client::ComponentState::kDownloadingDiff:
    case update_client::ComponentState::kDownloaded:
      return UpdateService::UpdateState::State::kDownloading;

    case update_client::ComponentState::kCanUpdate:
      return UpdateService::UpdateState::State::kUpdateAvailable;

    case update_client::ComponentState::kUpdating:
    case update_client::ComponentState::kUpdatingDiff:
      return UpdateService::UpdateState::State::kInstalling;

    case update_client::ComponentState::kUpdated:
      return UpdateService::UpdateState::State::kUpdated;

    case update_client::ComponentState::kUpToDate:
      return UpdateService::UpdateState::State::kNoUpdate;

    case update_client::ComponentState::kUpdateError:
      return UpdateService::UpdateState::State::kUpdateError;

    case update_client::ComponentState::kUninstalled:
    case update_client::ComponentState::kRegistration:
    case update_client::ComponentState::kRun:
    case update_client::ComponentState::kLastStatus:
      NOTREACHED();
      return UpdateService::UpdateState::State::kUnknown;
  }
}

UpdateService::ErrorCategory ToErrorCategory(
    update_client::ErrorCategory error_category) {
  switch (error_category) {
    case update_client::ErrorCategory::kNone:
      return UpdateService::ErrorCategory::kNone;
    case update_client::ErrorCategory::kDownload:
      return UpdateService::ErrorCategory::kDownload;
    case update_client::ErrorCategory::kUnpack:
      return UpdateService::ErrorCategory::kUnpack;
    case update_client::ErrorCategory::kInstall:
      return UpdateService::ErrorCategory::kInstall;
    case update_client::ErrorCategory::kService:
      return UpdateService::ErrorCategory::kService;
    case update_client::ErrorCategory::kUpdateCheck:
      return UpdateService::ErrorCategory::kUpdateCheck;
  }
}

update_client::UpdateClient::CrxStateChangeCallback
MakeUpdateClientCrxStateChangeCallback(
    scoped_refptr<update_client::Configurator> config,
    UpdateService::StateChangeCallback callback) {
  return base::BindRepeating(
      [](scoped_refptr<update_client::Configurator> config,
         UpdateService::StateChangeCallback callback,
         update_client::CrxUpdateItem crx_update_item) {
        UpdateService::UpdateState update_state;
        update_state.app_id = crx_update_item.id;
        update_state.state = ToUpdateState(crx_update_item.state);
        update_state.next_version = crx_update_item.next_version;
        update_state.downloaded_bytes = crx_update_item.downloaded_bytes;
        update_state.total_bytes = crx_update_item.total_bytes;
        update_state.install_progress = crx_update_item.install_progress;
        update_state.error_category =
            ToErrorCategory(crx_update_item.error_category);
        update_state.error_code = crx_update_item.error_code;
        update_state.extra_code1 = crx_update_item.extra_code1;

        // Commit the prefs values written by |update_client| when the
        // update has completed, such as `pv` and `fingerprint`.
        if (update_state.state == UpdateService::UpdateState::State::kUpdated) {
          config->GetPrefService()->CommitPendingWrite();
        }

        callback.Run(update_state);
      },
      config, callback);
}

std::vector<absl::optional<update_client::CrxComponent>> GetComponents(
    scoped_refptr<Configurator> config,
    scoped_refptr<PersistedData> persisted_data,
    bool foreground,
    bool update_blocked,
    UpdateService::PolicySameVersionUpdate policy_same_version_update,
    const std::vector<std::string>& ids) {
  VLOG(1) << __func__
          << ". Same version update: " << policy_same_version_update;

  std::vector<absl::optional<update_client::CrxComponent>> components;
  for (const auto& id : ids) {
    components.push_back(
        base::MakeRefCounted<Installer>(
            id,
            [&config, &id]() {
              std::string component_channel;
              return config->GetPolicyService()->GetTargetChannel(
                         id, nullptr, &component_channel)
                         ? component_channel
                         : std::string();
            }(),
            [&config, &id]() {
              std::string target_version_prefix;
              return config->GetPolicyService()->GetTargetVersionPrefix(
                         id, nullptr, &target_version_prefix)
                         ? target_version_prefix
                         : std::string();
            }(),
            [&config, &id]() {
              bool rollback_allowed;
              return config->GetPolicyService()
                             ->IsRollbackToTargetVersionAllowed(
                                 id, nullptr, &rollback_allowed)
                         ? rollback_allowed
                         : false;
            }(),
            [&config, &id, &foreground, update_blocked]() {
              if (update_blocked)
                return true;
              int policy = kPolicyEnabled;
              return config->GetPolicyService()
                         ->GetEffectivePolicyForAppUpdates(id, nullptr,
                                                           &policy) &&
                     (policy == kPolicyDisabled ||
                      (!foreground && policy == kPolicyManualUpdatesOnly) ||
                      (foreground && policy == kPolicyAutomaticUpdatesOnly));
            }(),
            policy_same_version_update, persisted_data,
            config->GetCrxVerifierFormat())
            ->MakeCrxComponent());
  }
  return components;
}

}  // namespace

UpdateServiceImpl::UpdateServiceImpl(scoped_refptr<Configurator> config)
    : config_(config),
      persisted_data_(
          base::MakeRefCounted<PersistedData>(config_->GetPrefService())),
      main_task_runner_(base::SequencedTaskRunnerHandle::Get()),
      update_client_(update_client::UpdateClientFactory(config)) {}

void UpdateServiceImpl::GetVersion(
    base::OnceCallback<void(const base::Version&)> callback) const {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), base::Version(kUpdaterVersion)));
}

void UpdateServiceImpl::RegisterApp(
    const RegistrationRequest& request,
    base::OnceCallback<void(const RegistrationResponse&)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (request.app_id != kUpdaterAppId) {
    persisted_data_->SetHadApps();
  }
  base::Version current_version =
      persisted_data_->GetProductVersion(request.app_id);
  if (current_version.IsValid() &&
      current_version.CompareTo(request.version) == 1) {
    main_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback),
                       RegistrationResponse(kRegistrationAlreadyRegistered)));
    return;
  }
  persisted_data_->RegisterApp(request);
  update_client::CrxComponent crx_component;
  crx_component.app_id = request.app_id;
  crx_component.version = request.version;
  crx_component.requires_network_encryption = false;
  crx_component.ap = request.ap;
  crx_component.brand = request.brand_code;
  update_client_->SendRegistrationPing(
      crx_component,
      base::BindOnce(
          [](base::OnceCallback<void(const RegistrationResponse&)> callback,
             update_client::Error /*error*/) {
            // Ping failures do not count as registration failures.
            std::move(callback).Run(RegistrationResponse(kRegistrationSuccess));
          },
          std::move(callback)));
}

void UpdateServiceImpl::GetAppStates(
    base::OnceCallback<void(const std::vector<AppState>&)> callback) const {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  std::vector<std::string> app_ids = persisted_data_->GetAppIds();
  std::vector<AppState> apps;
  for (const std::string& app_id : app_ids) {
    AppState app_state;
    app_state.app_id = app_id;
    app_state.version = persisted_data_->GetProductVersion(app_id);
    app_state.ap = persisted_data_->GetAP(app_id);
    app_state.brand_code = persisted_data_->GetBrandCode(app_id);
    app_state.brand_path = persisted_data_->GetBrandPath(app_id);
    app_state.ecp = persisted_data_->GetExistenceCheckerPath(app_id);
    apps.push_back(app_state);
  }
  main_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::move(apps)));
}

void UpdateServiceImpl::RunPeriodicTasks(base::OnceClosure callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // The installer should make an updater registration, but in case it halts
  // before it does, synthesize a registration if necessary here.
  if (!base::Contains(persisted_data_->GetAppIds(), kUpdaterAppId)) {
    RegistrationRequest updater_request;
    updater_request.app_id = kUpdaterAppId;
    updater_request.version = base::Version(kUpdaterVersion);
    RegisterApp(updater_request, base::DoNothing());
  }

  tasks_.push(base::MakeRefCounted<CheckForUpdatesTask>(
      config_,
      base::BindOnce(&UpdateServiceImpl::UpdateAll, this, base::DoNothing()),
      base::BindOnce(&UpdateServiceImpl::TaskDone, this, std::move(callback))));
  if (tasks_.size() == 1)
    TaskStart();
}

void UpdateServiceImpl::TaskStart() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!tasks_.empty()) {
    tasks_.front()->Run();
  }
}

void UpdateServiceImpl::TaskDone(base::OnceClosure callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  std::move(callback).Run();
  tasks_.pop();
  TaskStart();
}

void UpdateServiceImpl::UpdateAll(StateChangeCallback state_update,
                                  Callback callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const auto app_ids = persisted_data_->GetAppIds();
  DCHECK(base::Contains(app_ids, kUpdaterAppId));

  const Priority priority = Priority::kBackground;
  ShouldBlockUpdateForMeteredNetwork(
      priority,
      base::BindOnce(&UpdateServiceImpl::OnShouldBlockUpdateForMeteredNetwork,
                     this, state_update, std::move(callback), app_ids, priority,
                     UpdateService::PolicySameVersionUpdate::kNotAllowed));
}

void UpdateServiceImpl::Update(
    const std::string& app_id,
    Priority priority,
    PolicySameVersionUpdate policy_same_version_update,
    StateChangeCallback state_update,
    Callback callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  int policy = kPolicyEnabled;
  if (IsUpdateDisabledByPolicy(app_id, priority, policy_same_version_update,
                               policy)) {
    HandleUpdateDisabledByPolicy(app_id, policy, policy_same_version_update,
                                 state_update, std::move(callback));
    return;
  }

  std::vector<std::string> ids = {app_id};
  ShouldBlockUpdateForMeteredNetwork(
      priority,
      base::BindOnce(&UpdateServiceImpl::OnShouldBlockUpdateForMeteredNetwork,
                     this, state_update, std::move(callback), ids, priority,
                     policy_same_version_update));
}

bool UpdateServiceImpl::IsUpdateDisabledByPolicy(
    const std::string& app_id,
    Priority priority,
    PolicySameVersionUpdate policy_same_version_update,
    int& policy) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  policy = kPolicyEnabled;

  // The Install case is inferred by the presence of
  // `PolicySameVersionUpdate::kAllowed`.
  if (policy_same_version_update == PolicySameVersionUpdate::kAllowed) {
    return config_->GetPolicyService()->GetEffectivePolicyForAppInstalls(
               app_id, nullptr, &policy) &&
           (policy == kPolicyDisabled || (config_->IsPerUserInstall() &&
                                          policy == kPolicyEnabledMachineOnly));
  } else {
    return config_->GetPolicyService()->GetEffectivePolicyForAppUpdates(
               app_id, nullptr, &policy) &&
           (policy == kPolicyDisabled ||
            ((policy == kPolicyManualUpdatesOnly) &&
             (priority != Priority::kForeground)) ||
            ((policy == kPolicyAutomaticUpdatesOnly) &&
             (priority == Priority::kForeground)));
  }
}

void UpdateServiceImpl::HandleUpdateDisabledByPolicy(
    const std::string& app_id,
    int policy,
    PolicySameVersionUpdate policy_same_version_update,
    StateChangeCallback state_update,
    Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  UpdateState update_state;
  update_state.app_id = app_id;
  update_state.state = UpdateService::UpdateState::State::kUpdateError;
  update_state.error_category = UpdateService::ErrorCategory::kUpdateCheck;
  update_state.error_code =
      policy_same_version_update == PolicySameVersionUpdate::kAllowed
          ? GOOPDATE_E_APP_INSTALL_DISABLED_BY_POLICY
          : policy != kPolicyAutomaticUpdatesOnly
                ? GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY
                : GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL;
  update_state.extra_code1 = 0;

  base::BindPostTask(main_task_runner_, state_update).Run(update_state);
  base::BindPostTask(main_task_runner_, std::move(callback))
      .Run(UpdateService::Result::kUpdateCheckFailed);
}

void UpdateServiceImpl::OnShouldBlockUpdateForMeteredNetwork(
    StateChangeCallback state_update,
    Callback callback,
    const std::vector<std::string>& ids,
    Priority priority,
    PolicySameVersionUpdate policy_same_version_update,
    bool update_blocked) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &update_client::UpdateClient::Update, update_client_, ids,
          base::BindOnce(&GetComponents, config_, persisted_data_, false,
                         update_blocked, policy_same_version_update),
          MakeUpdateClientCrxStateChangeCallback(config_, state_update),
          priority == Priority::kForeground,
          MakeUpdateClientCallback(std::move(callback))));
}

void UpdateServiceImpl::Uninitialize() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  PrefsCommitPendingWrites(config_->GetPrefService());
}

UpdateServiceImpl::~UpdateServiceImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  config_->GetPrefService()->SchedulePendingLossyWrites();
}

}  // namespace updater