chromium/chrome/updater/app/server/win/com_classes_legacy.cc

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

#include "chrome/updater/app/server/win/com_classes_legacy.h"

#include <windows.h>

#include <oleauto.h>
#include <shellapi.h>
#include <wrl/client.h>

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

#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_variant.h"
#include "chrome/updater/activity.h"
#include "chrome/updater/app/app_server_win.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/policy/manager.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/update_usage_stats_task.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/progress_sampler.h"
#include "chrome/updater/util/util.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/app_command_runner.h"
#include "chrome/updater/win/scoped_handle.h"
#include "chrome/updater/win/setup/setup_util.h"
#include "chrome/updater/win/ui/l10n_util.h"
#include "chrome/updater/win/ui/resources/updater_installer_strings.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/update_client.h"

namespace {

HRESULT OpenCallerProcessHandle(DWORD proc_id,
                                base::win::ScopedHandle& proc_handle) {
  proc_handle.Set(::OpenProcess(PROCESS_DUP_HANDLE, false, proc_id));
  return proc_handle.IsValid() ? S_OK : updater::HRESULTFromLastError();
}

// Extracts a string from a VARIANT if the VARIANT is VT_BSTR or VT_BSTR |
// VT_BYREF. Returns std::nullopt if the VARIANT is not a BSTR.
std::optional<std::wstring> StringFromVariant(const VARIANT& source) {
  if (V_VT(&source) == VT_BSTR) {
    return V_BSTR(&source);
  }

  if (V_VT(&source) == (VT_BSTR | VT_BYREF)) {
    return *(V_BSTRREF(&source));
  }

  return {};
}

std::string GetStringFromValue(const std::string& value) {
  return value;
}

std::string GetStringFromValue(const int& value) {
  return base::NumberToString(value);
}

std::string GetStringFromValue(const bool& value) {
  return value ? "true" : "false";
}

std::string GetStringFromValue(const updater::UpdatesSuppressedTimes& value) {
  return base::StringPrintf("%d, %d, %d", value.start_hour_,
                            value.start_minute_, value.duration_minute_);
}

std::string GetStringFromValue(const std::vector<std::string>& value) {
  return base::JoinString(value, ";");
}

}  // namespace

namespace updater {

// Implements `IAppVersionWeb`.
class AppVersionWebImpl : public IDispatchImpl<IAppVersionWeb> {
 public:
  AppVersionWebImpl()
      : IDispatchImpl<IAppVersionWeb>(IID_MAPS_USERSYSTEM(IAppVersionWeb)) {}
  AppVersionWebImpl(const AppVersionWebImpl&) = delete;
  AppVersionWebImpl& operator=(const AppVersionWebImpl&) = delete;

  HRESULT RuntimeClassInitialize(const std::wstring& version) {
    version_ = version;

    return S_OK;
  }

  // Overrides for IAppVersionWeb.
  IFACEMETHODIMP get_version(BSTR* version) override {
    CHECK(version);

    *version = base::win::ScopedBstr(version_).Release();
    return S_OK;
  }

  IFACEMETHODIMP get_packageCount(long* count) override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

  IFACEMETHODIMP get_packageWeb(long index, IDispatch** package) override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

 private:
  ~AppVersionWebImpl() override = default;

  std::wstring version_;
};

// Implements `ICurrentState`. Initialized with a snapshot of the current state
// of the install.
class CurrentStateImpl : public IDispatchImpl<ICurrentState> {
 public:
  CurrentStateImpl()
      : IDispatchImpl<ICurrentState>(IID_MAPS_USERSYSTEM(ICurrentState)) {}
  CurrentStateImpl(const CurrentStateImpl&) = delete;
  CurrentStateImpl& operator=(const CurrentStateImpl&) = delete;

  HRESULT RuntimeClassInitialize(
      LONG state_value,
      const std::wstring& available_version,
      ULONGLONG bytes_downloaded,
      ULONGLONG total_bytes_to_download,
      LONG download_time_remaining_ms,
      ULONGLONG next_retry_time,
      LONG install_progress_percentage,
      LONG install_time_remaining_ms,
      bool is_canceled,
      LONG error_code,
      LONG extra_code1,
      const std::wstring& completion_message,
      LONG installer_result_code,
      LONG installer_result_extra_code1,
      const std::wstring& post_install_launch_command_line,
      const std::wstring& post_install_url,
      LONG post_install_action) {
    state_value_ = state_value;
    available_version_ = available_version;
    bytes_downloaded_ = bytes_downloaded;
    total_bytes_to_download_ = total_bytes_to_download;
    download_time_remaining_ms_ = download_time_remaining_ms;
    next_retry_time_ = next_retry_time;
    install_progress_percentage_ = install_progress_percentage;
    install_time_remaining_ms_ = install_time_remaining_ms;
    is_canceled_ = is_canceled ? VARIANT_TRUE : VARIANT_FALSE;
    error_code_ = error_code;
    extra_code1_ = extra_code1;
    completion_message_ = completion_message;
    installer_result_code_ = installer_result_code;
    installer_result_extra_code1_ = installer_result_extra_code1;
    post_install_launch_command_line_ = post_install_launch_command_line;
    post_install_url_ = post_install_url;
    post_install_action_ = post_install_action;

    return S_OK;
  }

  // Overrides for ICurrentState.
  IFACEMETHODIMP get_stateValue(LONG* state_value) override {
    CHECK(state_value);

    *state_value = state_value_;
    return S_OK;
  }

  IFACEMETHODIMP get_availableVersion(BSTR* available_version) override {
    CHECK(available_version);

    *available_version = base::win::ScopedBstr(available_version_).Release();
    return S_OK;
  }

  IFACEMETHODIMP get_bytesDownloaded(ULONG* bytes_downloaded) override {
    CHECK(bytes_downloaded);

    *bytes_downloaded = bytes_downloaded_;
    return S_OK;
  }

  IFACEMETHODIMP get_totalBytesToDownload(
      ULONG* total_bytes_to_download) override {
    CHECK(total_bytes_to_download);

    *total_bytes_to_download = total_bytes_to_download_;
    return S_OK;
  }

  IFACEMETHODIMP get_downloadTimeRemainingMs(
      LONG* download_time_remaining_ms) override {
    CHECK(download_time_remaining_ms);

    *download_time_remaining_ms = download_time_remaining_ms_;
    return S_OK;
  }

  IFACEMETHODIMP get_nextRetryTime(ULONGLONG* next_retry_time) override {
    CHECK(next_retry_time);

    *next_retry_time = next_retry_time_;
    return S_OK;
  }

  IFACEMETHODIMP get_installProgress(
      LONG* install_progress_percentage) override {
    CHECK(install_progress_percentage);

    *install_progress_percentage = install_progress_percentage_;
    return S_OK;
  }

  IFACEMETHODIMP get_installTimeRemainingMs(
      LONG* install_time_remaining_ms) override {
    CHECK(install_time_remaining_ms);

    *install_time_remaining_ms = install_time_remaining_ms_;
    return S_OK;
  }

  IFACEMETHODIMP get_isCanceled(VARIANT_BOOL* is_canceled) override {
    CHECK(is_canceled);

    *is_canceled = is_canceled_;
    return S_OK;
  }

  IFACEMETHODIMP get_errorCode(LONG* error_code) override {
    CHECK(error_code);

    *error_code = error_code_;
    return S_OK;
  }

  IFACEMETHODIMP get_extraCode1(LONG* extra_code1) override {
    CHECK(extra_code1);

    *extra_code1 = extra_code1_;
    return S_OK;
  }

  IFACEMETHODIMP get_completionMessage(BSTR* completion_message) override {
    CHECK(completion_message);

    *completion_message = base::win::ScopedBstr(completion_message_).Release();
    return S_OK;
  }

  IFACEMETHODIMP get_installerResultCode(LONG* installer_result_code) override {
    CHECK(installer_result_code);

    *installer_result_code = installer_result_code_;
    return S_OK;
  }

  IFACEMETHODIMP get_installerResultExtraCode1(
      LONG* installer_result_extra_code1) override {
    CHECK(installer_result_extra_code1);

    *installer_result_extra_code1 = installer_result_extra_code1_;
    return S_OK;
  }

  IFACEMETHODIMP get_postInstallLaunchCommandLine(
      BSTR* post_install_launch_command_line) override {
    CHECK(post_install_launch_command_line);

    *post_install_launch_command_line =
        base::win::ScopedBstr(post_install_launch_command_line_).Release();
    return S_OK;
  }

  IFACEMETHODIMP get_postInstallUrl(BSTR* post_install_url) override {
    CHECK(post_install_url);

    *post_install_url = base::win::ScopedBstr(post_install_url_).Release();
    return S_OK;
  }

  IFACEMETHODIMP get_postInstallAction(LONG* post_install_action) override {
    CHECK(post_install_action);

    *post_install_action = post_install_action_;
    return S_OK;
  }

 private:
  ~CurrentStateImpl() override = default;

  LONG state_value_;
  std::wstring available_version_;
  ULONGLONG bytes_downloaded_;
  ULONGLONG total_bytes_to_download_;
  LONG download_time_remaining_ms_;
  ULONGLONG next_retry_time_;
  LONG install_progress_percentage_;
  LONG install_time_remaining_ms_;
  VARIANT_BOOL is_canceled_;
  LONG error_code_;
  LONG extra_code1_;
  std::wstring completion_message_;
  LONG installer_result_code_;
  LONG installer_result_extra_code1_;
  std::wstring post_install_launch_command_line_;
  std::wstring post_install_url_;
  LONG post_install_action_;
};

// This class implements the legacy Omaha3 IAppWeb interface as expected by
// Chrome's on-demand client.
class AppWebImpl : public IDispatchImpl<IAppWeb> {
 public:
  AppWebImpl()
      : IDispatchImpl<IAppWeb>(IID_MAPS_USERSYSTEM(IAppWeb)),
        task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
            {base::MayBlock(), base::WithBaseSyncPrimitives()})),
        download_progress_sampler_(base::Seconds(5), base::Seconds(1)),
        install_progress_sampler_(base::Seconds(5), base::Seconds(1)) {}
  AppWebImpl(const AppWebImpl&) = delete;
  AppWebImpl& operator=(const AppWebImpl&) = delete;

  HRESULT RuntimeClassInitialize(
      const bool is_install,
      const std::wstring& app_id,
      const std::wstring& brand_code,
      const std::wstring& ap,
      UpdateService::PolicySameVersionUpdate policy_same_version_update) {
    if (is_install && FAILED(IsCOMCallerAllowed())) {
      VLOG(1) << __func__ << ": admin rights required for installs";
      return E_ACCESSDENIED;
    }

    is_install_ = is_install;
    app_id_ = base::WideToUTF8(app_id);
    brand_code_ = base::WideToUTF8(brand_code);
    ap_ = base::WideToUTF8(ap);
    policy_same_version_update_ = policy_same_version_update;

    // Holds the result of the IPC to register an app.
    struct RegisterAppResult
        : public base::RefCountedThreadSafe<RegisterAppResult> {
      bool new_install = false;
      base::WaitableEvent completion_event;

     private:
      friend class base::RefCountedThreadSafe<RegisterAppResult>;
      virtual ~RegisterAppResult() = default;
    };

    auto result = base::MakeRefCounted<RegisterAppResult>();
    AppServerWin::PostRpcTask(base::BindOnce(
        [](AppWebImplPtr obj, scoped_refptr<RegisterAppResult> result) {
          const base::ScopedClosureRunner signal_event(base::BindOnce(
              [](scoped_refptr<RegisterAppResult> result) {
                result->completion_event.Signal();
              },
              result));

          // Always update ap.
          RegistrationRequest request;
          request.app_id = obj->app_id_;
          request.ap = obj->ap_;

          // Pre-register the app with a version of "0.0.0.0" if there is no
          // registration for it. This app registration is removed later if
          // the app install does not happen.
          scoped_refptr<PersistedData> persisted_data =
              GetAppServerWinInstance()->config()->GetUpdaterPersistedData();
          if (!persisted_data->GetProductVersion(obj->app_id_).IsValid()) {
            result->new_install = true;
            request.brand_code = obj->brand_code_;
            request.version = base::Version(kNullVersion);
          }

          persisted_data->RegisterApp(request);
        },
        AppWebImplPtr(this), result));

    if (!result->completion_event.TimedWait(base::Seconds(60))) {
      return E_FAIL;
    }

    new_install_ = result->new_install;

    VLOG(1) << __func__ << ": new_install_: " << new_install_;
    return S_OK;
  }

  // For backward-compatibility purposes, the `CheckForUpdate` call assumes
  // foreground priority and disallows same version updates.
  HRESULT CheckForUpdate() {
    current_operation_ = CurrentOperation::kCheckingForUpdates;
    AppWebImplPtr obj(this);
    base::RepeatingCallback<void(const UpdateService::UpdateState&)>
        state_change_callback = base::BindRepeating(
            [](AppWebImplPtr obj,
               const UpdateService::UpdateState& state_update) {
              obj->task_runner_->PostTask(
                  FROM_HERE, base::BindOnce(&AppWebImpl::UpdateStateCallback,
                                            obj, state_update));
            },
            obj);
    base::OnceCallback<void(UpdateService::Result)> complete_callback =
        base::BindOnce(
            [](AppWebImplPtr obj, UpdateService::Result result) {
              obj->task_runner_->PostTask(
                  FROM_HERE, base::BindOnce(&AppWebImpl::UpdateResultCallback,
                                            obj, result));
            },
            obj);
    AppServerWin::PostRpcTask(base::BindOnce(
        [](base::RepeatingCallback<void(const UpdateService::UpdateState&)>
               state_change_callback,
           base::OnceCallback<void(UpdateService::Result)> complete_callback,
           AppWebImplPtr obj) {
          scoped_refptr<UpdateService> update_service =
              GetAppServerWinInstance()->update_service();
          if (!update_service) {
            std::move(complete_callback)
                .Run(UpdateService::Result::kServiceStopped);
            return;
          }
          update_service->CheckForUpdate(
              obj->app_id_, UpdateService::Priority::kForeground,
              obj->policy_same_version_update_,
              std::move(state_change_callback), std::move(complete_callback));
        },
        std::move(state_change_callback), std::move(complete_callback), obj));
    return S_OK;
  }

  HRESULT UpdateOrInstall() {
    current_operation_ = CurrentOperation::kUpdatingOrInstalling;
    return is_install_ ? Install() : Update();
  }

  HRESULT Install() {
    AppWebImplPtr obj(this);
    base::RepeatingCallback<void(const UpdateService::UpdateState&)>
        state_change_callback = base::BindRepeating(
            [](AppWebImplPtr obj,
               const UpdateService::UpdateState& state_update) {
              obj->task_runner_->PostTask(
                  FROM_HERE, base::BindOnce(&AppWebImpl::UpdateStateCallback,
                                            obj, state_update));
            },
            obj);
    base::OnceCallback<void(UpdateService::Result)> complete_callback =
        base::BindOnce(
            [](AppWebImplPtr obj, UpdateService::Result result) {
              obj->task_runner_->PostTask(
                  FROM_HERE, base::BindOnce(&AppWebImpl::UpdateResultCallback,
                                            obj, result));
            },
            obj);

    AppServerWin::PostRpcTask(base::BindOnce(
        [](base::RepeatingCallback<void(const UpdateService::UpdateState&)>
               state_change_callback,
           base::OnceCallback<void(UpdateService::Result)> complete_callback,
           AppWebImplPtr obj) {
          scoped_refptr<UpdateService> update_service =
              GetAppServerWinInstance()->update_service();
          if (!update_service) {
            std::move(complete_callback)
                .Run(UpdateService::Result::kServiceStopped);
            return;
          }

          RegistrationRequest request;
          request.app_id = obj->app_id_;
          request.version = base::Version(kNullVersion);
          request.brand_code = obj->brand_code_;
          request.ap = obj->ap_;

          update_service->Install(request, {}, obj->install_data_index_,
                                  UpdateService::Priority::kForeground,
                                  std::move(state_change_callback),
                                  std::move(complete_callback));
        },
        std::move(state_change_callback), std::move(complete_callback), obj));
    return S_OK;
  }

  HRESULT Update() {
    AppWebImplPtr obj(this);
    base::RepeatingCallback<void(const UpdateService::UpdateState&)>
        state_change_callback = base::BindRepeating(
            [](AppWebImplPtr obj,
               const UpdateService::UpdateState& state_update) {
              obj->task_runner_->PostTask(
                  FROM_HERE, base::BindOnce(&AppWebImpl::UpdateStateCallback,
                                            obj, state_update));
            },
            obj);
    base::OnceCallback<void(UpdateService::Result)> complete_callback =
        base::BindOnce(
            [](AppWebImplPtr obj, UpdateService::Result result) {
              obj->task_runner_->PostTask(
                  FROM_HERE, base::BindOnce(&AppWebImpl::UpdateResultCallback,
                                            obj, result));
            },
            obj);
    AppServerWin::PostRpcTask(base::BindOnce(
        [](base::RepeatingCallback<void(const UpdateService::UpdateState&)>
               state_change_callback,
           base::OnceCallback<void(UpdateService::Result)> complete_callback,
           AppWebImplPtr obj) {
          scoped_refptr<UpdateService> update_service =
              GetAppServerWinInstance()->update_service();
          if (!update_service) {
            std::move(complete_callback)
                .Run(UpdateService::Result::kServiceStopped);
            return;
          }
          update_service->Update(obj->app_id_, obj->install_data_index_,
                                 UpdateService::Priority::kForeground,
                                 obj->policy_same_version_update_,
                                 std::move(state_change_callback),
                                 std::move(complete_callback));
        },
        std::move(state_change_callback), std::move(complete_callback), obj));
    return S_OK;
  }

  // Legacy compatibility: sets a flag that causes `get_currentState` to return
  // `STATE_READY_TO_INSTALL` when the update state is `kUpdateAvailable`.
  void SetReadyToInstall() { set_ready_to_install_ = true; }

  // Overrides for IAppWeb.
  IFACEMETHODIMP get_appId(BSTR* app_id) override {
    CHECK(app_id);

    *app_id = base::win::ScopedBstr(base::UTF8ToWide(app_id_)).Release();
    return S_OK;
  }

  IFACEMETHODIMP get_currentVersionWeb(IDispatch** current) override {
    // Holds the result of the IPC to retrieve the current version.
    struct CurrentVersionResult
        : public base::RefCountedThreadSafe<CurrentVersionResult> {
      std::optional<base::Version> current_version;
      base::WaitableEvent completion_event;

     private:
      friend class base::RefCountedThreadSafe<CurrentVersionResult>;
      virtual ~CurrentVersionResult() = default;
    };

    auto result = base::MakeRefCounted<CurrentVersionResult>();
    AppServerWin::PostRpcTask(base::BindOnce(
        [](const std::string app_id,
           scoped_refptr<CurrentVersionResult> result) {
          const base::ScopedClosureRunner signal_event(base::BindOnce(
              [](scoped_refptr<CurrentVersionResult> result) {
                result->completion_event.Signal();
              },
              result));

          const base::Version current_version =
              base::MakeRefCounted<const PersistedData>(
                  GetUpdaterScope(),
                  GetAppServerWinInstance()->prefs()->GetPrefService(), nullptr)
                  ->GetProductVersion(app_id);
          if (!current_version.IsValid()) {
            return;
          }

          result->current_version = current_version;
        },
        app_id_, result));

    if (!result->completion_event.TimedWait(base::Seconds(60)) ||
        !result->current_version.has_value()) {
      return E_FAIL;
    }

    return MakeAndInitializeComObject<AppVersionWebImpl>(
        current, base::UTF8ToWide(result->current_version->GetString()));
  }

  IFACEMETHODIMP get_nextVersionWeb(IDispatch** next) override {
    base::AutoLock lock{lock_};

    if (!state_update_ || !state_update_->next_version.IsValid()) {
      return E_FAIL;
    }

    return MakeAndInitializeComObject<AppVersionWebImpl>(
        next, base::UTF8ToWide(state_update_->next_version.GetString()));
  }

  IFACEMETHODIMP get_command(BSTR command_id, IDispatch** command) override {
    return MakeAndInitializeComObject<LegacyAppCommandWebImpl>(
        command, GetUpdaterScope(), base::UTF8ToWide(app_id_), command_id);
  }

  IFACEMETHODIMP cancel() override {
    AppServerWin::PostRpcTask(base::BindOnce(
        [](const std::string& app_id) {
          scoped_refptr<UpdateService> update_service =
              GetAppServerWinInstance()->update_service();
          if (!update_service) {
            return;
          }
          update_service->CancelInstalls(app_id);
        },
        app_id_));
    return S_OK;
  }

  IFACEMETHODIMP get_currentState(IDispatch** current_state) override {
    CHECK(current_state);

    base::AutoLock lock{lock_};

    LONG state_value = STATE_INIT;
    std::wstring available_version;
    ULONG bytes_downloaded = -1;
    ULONG total_bytes_to_download = -1;
    std::optional<base::TimeDelta> remaining_download_time;
    LONG install_progress_percentage = -1;
    std::optional<base::TimeDelta> remaining_install_time;
    LONG error_code = 0;
    LONG extra_code1 = 0;
    std::wstring installer_text;
    std::wstring installer_cmd_line;

    if (state_update_) {
      // `state_value` is set to the state of update as seen by the on-demand
      // client:
      // - if the repeating callback has been received: set to the specific
      // state.
      // - if the completion callback has been received, but no repeating
      // callback, then it is set to STATE_ERROR. This is an error state and it
      // indicates that update is not going to be further handled and repeating
      // callbacks posted.
      // - if no callback has been received at all: set to STATE_INIT.
      switch (state_update_.value().state) {
        case UpdateService::UpdateState::State::kUnknown:  // Fall
                                                           // through.
        case UpdateService::UpdateState::State::kNotStarted:
          state_value = STATE_INIT;
          break;
        case UpdateService::UpdateState::State::kCheckingForUpdates:
          state_value = STATE_CHECKING_FOR_UPDATE;
          break;
        case UpdateService::UpdateState::State::kUpdateAvailable:
          state_value =
              set_ready_to_install_ ? STATE_READY_TO_INSTALL
              : current_operation_ == CurrentOperation::kCheckingForUpdates
                  ? (result_ ? STATE_UPDATE_AVAILABLE
                             : STATE_CHECKING_FOR_UPDATE)
                  : STATE_UPDATE_AVAILABLE;
          break;
        case UpdateService::UpdateState::State::kDownloading:
          state_value = STATE_DOWNLOADING;
          break;
        case UpdateService::UpdateState::State::kInstalling:
          state_value = STATE_INSTALLING;
          break;
        case UpdateService::UpdateState::State::kUpdated:
          state_value = result_ ? STATE_INSTALL_COMPLETE : STATE_INSTALLING;
          break;
        case UpdateService::UpdateState::State::kNoUpdate:
          state_value = result_ ? STATE_NO_UPDATE : STATE_CHECKING_FOR_UPDATE;
          break;
        case UpdateService::UpdateState::State::kUpdateError:
          state_value = STATE_ERROR;
          break;
      }

      available_version =
          base::UTF8ToWide(state_update_->next_version.GetString());
      bytes_downloaded = state_update_->downloaded_bytes;
      total_bytes_to_download = state_update_->total_bytes;
      install_progress_percentage = state_update_->install_progress;

      download_progress_sampler_.AddSample(bytes_downloaded);
      remaining_download_time =
          download_progress_sampler_.GetRemainingTime(total_bytes_to_download);
      install_progress_sampler_.AddSample(install_progress_percentage);
      remaining_install_time = install_progress_sampler_.GetRemainingTime(100);

      error_code = state_update_->error_code;
      extra_code1 = state_update_->extra_code1;
      installer_text = base::UTF8ToWide(state_update_->installer_text);
      installer_cmd_line = base::UTF8ToWide(state_update_->installer_cmd_line);
    } else if (result_) {
      CHECK_NE(result_.value(), UpdateService::Result::kSuccess);
      state_value = STATE_ERROR;
      error_code =
          (result_.value() == UpdateService::Result::kSuccess) ? 0 : -1;
    }

    return MakeAndInitializeComObject<CurrentStateImpl>(
        current_state, state_value, available_version, bytes_downloaded,
        total_bytes_to_download,
        remaining_download_time ? remaining_download_time->InMilliseconds()
                                : -1,
        /*next_retry_time=*/-1, install_progress_percentage,
        remaining_install_time ? remaining_install_time->InMilliseconds() : -1,
        /*is_canceled=*/VARIANT_FALSE, error_code, extra_code1,
        /*completion_message=*/installer_text,
        /*installer_result_code=*/error_code,
        /*installer_result_extra_code1=*/extra_code1,
        /*post_install_launch_command_line=*/installer_cmd_line,
        /*post_install_url=*/L"",
        /*post_install_action=*/0);
  }

  IFACEMETHODIMP launch() override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

  IFACEMETHODIMP uninstall() override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

  IFACEMETHODIMP get_serverInstallDataIndex(BSTR* install_data_index) override {
    CHECK(install_data_index);

    *install_data_index =
        base::win::ScopedBstr(base::UTF8ToWide(install_data_index_)).Release();
    return S_OK;
  }

  IFACEMETHODIMP put_serverInstallDataIndex(BSTR install_data_index) override {
    install_data_index_ = base::WideToUTF8(install_data_index);
    return S_OK;
  }

 private:
  using AppWebImplPtr = Microsoft::WRL::ComPtr<AppWebImpl>;

  enum class CurrentOperation {
    kUnknown = 0,

    // The COM client has started an update check.
    kCheckingForUpdates = 1,

    // The COM client has started an update or install.
    kUpdatingOrInstalling = 2,
  };

  ~AppWebImpl() override {
    // If a new install has not happened, the app id registered in
    // `RuntimeClassInitialize` needs to be removed here. Otherwise
    // the updater may remain installed even if there are no other apps to
    // manage, and try to update the app even though the app was not
    // installed.
    VLOG(1) << __func__ << ": new_install_: " << new_install_;

    if (!new_install_) {
      return;
    }

    // Holds the result of the IPC to remove an app whose version is not valid.
    struct RemoveAppResult
        : public base::RefCountedThreadSafe<RemoveAppResult> {
      base::WaitableEvent completion_event;

     private:
      friend class base::RefCountedThreadSafe<RemoveAppResult>;
      virtual ~RemoveAppResult() = default;
    };

    auto result = base::MakeRefCounted<RemoveAppResult>();
    AppServerWin::PostRpcTask(base::BindOnce(
        [](const std::string& app_id, scoped_refptr<RemoveAppResult> result) {
          const base::ScopedClosureRunner signal_event(base::BindOnce(
              [](scoped_refptr<RemoveAppResult> result) {
                result->completion_event.Signal();
              },
              result));

          scoped_refptr<PersistedData> persisted_data =
              GetAppServerWinInstance()->config()->GetUpdaterPersistedData();
          const base::Version version =
              persisted_data->GetProductVersion(app_id);
          if (version.IsValid() && version != base::Version(kNullVersion)) {
            return;
          }
          persisted_data->RemoveApp(app_id);
        },
        app_id_, result));

    result->completion_event.TimedWait(base::Seconds(60));
  }

  void UpdateStateCallback(UpdateService::UpdateState state_update) {
    base::AutoLock lock{lock_};
    state_update_ = state_update;
  }

  void UpdateResultCallback(UpdateService::Result result) {
    base::AutoLock lock{lock_};
    result_ = result;
  }

  // Handles the update service callbacks.
  scoped_refptr<base::SequencedTaskRunner> task_runner_;

  bool new_install_ = false;
  bool is_install_ = false;
  std::string app_id_;
  std::string brand_code_;
  std::string ap_;
  std::string install_data_index_;
  UpdateService::PolicySameVersionUpdate policy_same_version_update_ =
      UpdateService::PolicySameVersionUpdate::kNotAllowed;
  bool set_ready_to_install_ = false;
  ProgressSampler download_progress_sampler_;
  ProgressSampler install_progress_sampler_;

  // Access to `state_update_` and `result_` must be serialized by using the
  // lock.
  mutable base::Lock lock_;
  std::optional<UpdateService::UpdateState> state_update_;
  std::optional<UpdateService::Result> result_;
  CurrentOperation current_operation_ = CurrentOperation::kUnknown;
};

// This class implements the legacy Omaha3 IAppBundleWeb interface as expected
// by Chrome's on-demand client.
class AppBundleWebImpl : public IDispatchImpl<IAppBundleWeb> {
 public:
  AppBundleWebImpl()
      : IDispatchImpl<IAppBundleWeb>(IID_MAPS_USERSYSTEM(IAppBundleWeb)) {}
  AppBundleWebImpl(const AppBundleWebImpl&) = delete;
  AppBundleWebImpl& operator=(const AppBundleWebImpl&) = delete;

  HRESULT RuntimeClassInitialize() { return S_OK; }

  // Overrides for IAppBundleWeb.
  IFACEMETHODIMP createApp(BSTR app_id,
                           BSTR brand_code,
                           BSTR /* language */,
                           BSTR ap) override {
    base::AutoLock lock{lock_};

    if (app_web_) {
      return E_UNEXPECTED;
    }

    return MakeAndInitializeComObject<AppWebImpl>(
        app_web_, /*is_install=*/true, app_id, brand_code, ap,
        UpdateService::PolicySameVersionUpdate::kAllowed);
  }

  IFACEMETHODIMP createInstalledApp(BSTR app_id) override {
    base::AutoLock lock{lock_};

    if (app_web_) {
      return E_UNEXPECTED;
    }

    return MakeAndInitializeComObject<AppWebImpl>(
        app_web_, /*is_install=*/false, app_id, L"", L"",
        UpdateService::PolicySameVersionUpdate::kNotAllowed);
  }

  IFACEMETHODIMP createAllInstalledApps() override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

  IFACEMETHODIMP get_displayLanguage(BSTR* language) override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

  IFACEMETHODIMP put_displayLanguage(BSTR language) override { return S_OK; }

  IFACEMETHODIMP put_parentHWND(ULONG_PTR hwnd) override { return S_OK; }

  IFACEMETHODIMP get_length(int* number) override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

  IFACEMETHODIMP get_appWeb(int index, IDispatch** app_web) override {
    base::AutoLock lock{lock_};

    if (index != 0 || !app_web_) {
      return E_UNEXPECTED;
    }

    return app_web_.CopyTo(app_web);
  }

  IFACEMETHODIMP initialize() override { return S_OK; }

  IFACEMETHODIMP checkForUpdate() override {
    base::AutoLock lock{lock_};
    if (!app_web_) {
      return E_UNEXPECTED;
    }
    return app_web_->CheckForUpdate();
  }

  IFACEMETHODIMP download() override {
    VLOG(1) << "`install()` implements the download: " << __func__;

    base::AutoLock lock{lock_};
    if (!app_web_) {
      return E_UNEXPECTED;
    }
    app_web_->SetReadyToInstall();

    return S_OK;
  }

  IFACEMETHODIMP install() override {
    base::AutoLock lock{lock_};

    if (!app_web_) {
      return E_UNEXPECTED;
    }

    return app_web_->UpdateOrInstall();
  }

  IFACEMETHODIMP pause() override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

  IFACEMETHODIMP resume() override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

  IFACEMETHODIMP cancel() override {
    base::AutoLock lock{lock_};
    return app_web_ ? app_web_->cancel() : E_UNEXPECTED;
  }

  IFACEMETHODIMP downloadPackage(BSTR app_id, BSTR package_name) override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

  IFACEMETHODIMP get_currentState(VARIANT* current_state) override {
    LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
    return E_NOTIMPL;
  }

 private:
  ~AppBundleWebImpl() override = default;

  // Access to the object members must be serialized by using the lock.
  mutable base::Lock lock_;

  // Only a single app at a time is supported.
  Microsoft::WRL::ComPtr<AppWebImpl> app_web_;
};

LegacyOnDemandImpl::LegacyOnDemandImpl()
    : IDispatchImpl<IGoogleUpdate3Web>(IID_MAPS_USERSYSTEM(IGoogleUpdate3Web)) {
}

LegacyOnDemandImpl::~LegacyOnDemandImpl() = default;

STDMETHODIMP LegacyOnDemandImpl::createAppBundleWeb(
    IDispatch** app_bundle_web) {
  CHECK(app_bundle_web);
  return MakeAndInitializeComObject<AppBundleWebImpl>(app_bundle_web);
}

LegacyProcessLauncherImpl::LegacyProcessLauncherImpl() = default;
LegacyProcessLauncherImpl::~LegacyProcessLauncherImpl() = default;

STDMETHODIMP LegacyProcessLauncherImpl::LaunchCmdLine(const WCHAR* cmd_line) {
  LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
  return E_NOTIMPL;
}

STDMETHODIMP LegacyProcessLauncherImpl::LaunchBrowser(DWORD browser_type,
                                                      const WCHAR* url) {
  LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
  return E_NOTIMPL;
}

STDMETHODIMP LegacyProcessLauncherImpl::LaunchCmdElevated(
    const WCHAR* app_id,
    const WCHAR* command_id,
    DWORD caller_proc_id,
    ULONG_PTR* proc_handle) {
  base::win::ScopedHandle caller_proc_handle;
  if (HRESULT hr = OpenCallerProcessHandle(caller_proc_id, caller_proc_handle);
      FAILED(hr)) {
    VLOG(1) << "failed to open caller's handle " << hr;
    return hr;
  }

  Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl> app_command_web;
  if (HRESULT hr = MakeAndInitializeComObject<LegacyAppCommandWebImpl>(
          app_command_web, UpdaterScope::kSystem, app_id, command_id);
      FAILED(hr)) {
    return hr;
  }

  if (HRESULT hr =
          app_command_web->execute(base::win::ScopedVariant::kEmptyVariant,
                                   base::win::ScopedVariant::kEmptyVariant,
                                   base::win::ScopedVariant::kEmptyVariant,
                                   base::win::ScopedVariant::kEmptyVariant,
                                   base::win::ScopedVariant::kEmptyVariant,
                                   base::win::ScopedVariant::kEmptyVariant,
                                   base::win::ScopedVariant::kEmptyVariant,
                                   base::win::ScopedVariant::kEmptyVariant,
                                   base::win::ScopedVariant::kEmptyVariant);
      FAILED(hr)) {
    return hr;
  }

  ScopedKernelHANDLE duplicate_proc_handle;
  if (!::DuplicateHandle(
          ::GetCurrentProcess(), app_command_web->process().Handle(),
          caller_proc_handle.Get(),
          ScopedKernelHANDLE::Receiver(duplicate_proc_handle).get(),
          PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, 0)) {
    HRESULT hr = HRESULTFromLastError();
    VLOG(1) << "Failed to duplicate the handle " << hr;
    return hr;
  }

  // The caller must close this handle.
  *proc_handle = reinterpret_cast<ULONG_PTR>(duplicate_proc_handle.release());
  return S_OK;
}

STDMETHODIMP LegacyProcessLauncherImpl::LaunchCmdLineEx(
    const WCHAR* cmd_line,
    DWORD* /*server_proc_id*/,
    ULONG_PTR* /*proc_handle*/,
    ULONG_PTR* /*stdout_handle*/) {
  LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
  return E_NOTIMPL;
}

LegacyAppCommandWebImpl::LegacyAppCommandWebImpl()
    : IDispatchImpl<IAppCommandWeb>(IID_MAPS_USERSYSTEM(IAppCommandWeb)) {}
LegacyAppCommandWebImpl::~LegacyAppCommandWebImpl() = default;

HRESULT LegacyAppCommandWebImpl::RuntimeClassInitialize(
    UpdaterScope scope,
    const std::wstring& app_id,
    const std::wstring& command_id,
    PingSender ping_sender) {
  app_command_runner_ =
      AppCommandRunner::LoadAppCommand(scope, app_id, command_id);
  scope_ = scope;
  app_id_ = base::WideToUTF8(app_id);
  command_id_ = base::WideToUTF8(command_id);
  ping_sender_ = std::move(ping_sender);
  return app_command_runner_.error_or(S_OK);
}

STDMETHODIMP LegacyAppCommandWebImpl::get_status(UINT* status) {
  CHECK(status);

  if (!process_.IsValid()) {
    *status = COMMAND_STATUS_INIT;
  } else if (process_.IsRunning()) {
    *status = COMMAND_STATUS_RUNNING;
  } else {
    *status = COMMAND_STATUS_COMPLETE;
  }

  return S_OK;
}

STDMETHODIMP LegacyAppCommandWebImpl::get_exitCode(DWORD* exit_code) {
  CHECK(exit_code);

  int code = -1;
  if (!process_.IsValid() ||
      !process_.WaitForExitWithTimeout(base::TimeDelta(), &code)) {
    return S_FALSE;
  }

  *exit_code = code;
  return S_OK;
}

STDMETHODIMP LegacyAppCommandWebImpl::get_output(BSTR* output) {
  LOG(ERROR) << "Reached unimplemented COM method: " << __func__;
  return E_NOTIMPL;
}

namespace {


}  // namespace

STDMETHODIMP LegacyAppCommandWebImpl::execute(VARIANT substitution1,
                                              VARIANT substitution2,
                                              VARIANT substitution3,
                                              VARIANT substitution4,
                                              VARIANT substitution5,
                                              VARIANT substitution6,
                                              VARIANT substitution7,
                                              VARIANT substitution8,
                                              VARIANT substitution9) {
  CHECK(app_command_runner_.has_value());
  if (process_.IsValid()) {
    return E_UNEXPECTED;
  }

  std::vector<std::wstring> substitutions;
  for (const VARIANT& substitution :
       {substitution1, substitution2, substitution3, substitution4,
        substitution5, substitution6, substitution7, substitution8,
        substitution9}) {
    const std::optional<std::wstring> substitution_string =
        StringFromVariant(substitution);
    if (!substitution_string) {
      break;
    }

    VLOG(2) << __func__
            << " substitution_string: " << substitution_string.value();
    substitutions.push_back(substitution_string.value());
  }

  const HRESULT hr = app_command_runner_->Run(substitutions, process_);
  if (FAILED(hr)) {
    VLOG(2) << __func__ << ": AppCommand failed to launch: " << hr;
    ping_sender_.Run(scope_, app_id_, command_id_,
                     {
                         .error_code = hr,
                         .extra_code1 = kErrorAppCommandLaunchFailed,
                     });
    return hr;
  }

  base::ThreadPool::CreateSequencedTaskRunner(
      {base::MayBlock(), base::WithBaseSyncPrimitives()})
      ->PostTask(FROM_HERE,
                 base::BindOnce(
                     [](base::Process process) -> ErrorParams {
                       int exit_code = -1;
                       if (process.WaitForExitWithTimeout(kWaitForAppInstaller,
                                                          &exit_code)) {
                         VLOG(2) << "AppCommand completed: " << exit_code;
                         return {
                             .error_code = exit_code,
                             .extra_code1 = 0,
                         };
                       }
                       VLOG(2) << "AppCommand timed out.";
                       return {
                           .error_code = HRESULT_FROM_WIN32(ERROR_TIMEOUT),
                           .extra_code1 = kErrorAppCommandTimedOut,
                       };
                     },
                     process_.Duplicate())
                     .Then(base::BindOnce(ping_sender_, scope_, app_id_,
                                          command_id_)));
  return hr;
}

void LegacyAppCommandWebImpl::SendPing(UpdaterScope scope,
                                       const std::string& app_id,
                                       const std::string& command_id,
                                       ErrorParams error_params) {
  AppServerWin::PostRpcTask(base::BindOnce(
      [](UpdaterScope scope, const std::string& app_id,
         const std::string& command_id, ErrorParams error_params) {
        scoped_refptr<Configurator> config =
            GetAppServerWinInstance()->config();
        scoped_refptr<PersistedData> persisted_data =
            config->GetUpdaterPersistedData();
        if (!persisted_data->GetUsageStatsEnabled() &&
            !AreRawUsageStatsEnabled(scope)) {
          return;
        }

        update_client::CrxComponent app_command_data;
        app_command_data.ap = persisted_data->GetAP(app_id);
        app_command_data.app_id = app_id;
        app_command_data.brand = persisted_data->GetBrandCode(app_id);
        app_command_data.requires_network_encryption = false;
        app_command_data.version = persisted_data->GetProductVersion(app_id);

        update_client::UpdateClientFactory(config)->SendPing(
            app_command_data,
            {
                .event_type =
                    update_client::protocol_request::kEventAppCommandComplete,
                .result = SUCCEEDED(error_params.error_code),
                .error_code = error_params.error_code,
                .extra_code1 = error_params.extra_code1,
                .app_command_id = command_id,
            },
            base::BindOnce([](update_client::Error error) {
              VLOG(1) << "App command ping completed: " << error;
            }));
      },
      scope, app_id, command_id, error_params));
}

PolicyStatusImpl::PolicyStatusImpl()
    : IDispatchImpl<IPolicyStatus4,
                    IPolicyStatus3,
                    IPolicyStatus2,
                    IPolicyStatus>({IID_MAP_ENTRY_USER(IPolicyStatus4),
                                    IID_MAP_ENTRY_USER(IPolicyStatus3),
                                    IID_MAP_ENTRY_USER(IPolicyStatus2),
                                    IID_MAP_ENTRY_USER(IPolicyStatus)},
                                   {IID_MAP_ENTRY_SYSTEM(IPolicyStatus4),
                                    IID_MAP_ENTRY_SYSTEM(IPolicyStatus3),
                                    IID_MAP_ENTRY_SYSTEM(IPolicyStatus2),
                                    IID_MAP_ENTRY_SYSTEM(IPolicyStatus)}),
      policy_service_(GetAppServerWinInstance()->config()->GetPolicyService()) {
}
PolicyStatusImpl::~PolicyStatusImpl() = default;

HRESULT PolicyStatusImpl::RuntimeClassInitialize() {
  return S_OK;
}

// IPolicyStatus.
STDMETHODIMP PolicyStatusImpl::get_lastCheckPeriodMinutes(DWORD* minutes) {
  CHECK(minutes);

  PolicyStatus<base::TimeDelta> period = policy_service_->GetLastCheckPeriod();
  if (!period) {
    return E_FAIL;
  }

  *minutes = period.policy().InMinutes();
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_updatesSuppressedTimes(
    DWORD* start_hour,
    DWORD* start_min,
    DWORD* duration_min,
    VARIANT_BOOL* are_updates_suppressed) {
  CHECK(start_hour);
  CHECK(start_min);
  CHECK(duration_min);
  CHECK(are_updates_suppressed);

  PolicyStatus<UpdatesSuppressedTimes> updates_suppressed_times =
      policy_service_->GetUpdatesSuppressedTimes();
  if (!updates_suppressed_times || !updates_suppressed_times.policy().valid()) {
    return E_FAIL;
  }

  *start_hour = updates_suppressed_times.policy().start_hour_;
  *start_min = updates_suppressed_times.policy().start_minute_;
  *duration_min = updates_suppressed_times.policy().duration_minute_;
  *are_updates_suppressed =
      policy_service_->AreUpdatesSuppressedNow() ? VARIANT_TRUE : VARIANT_FALSE;

  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_downloadPreferenceGroupPolicy(BSTR* pref) {
  CHECK(pref);

  PolicyStatus<std::string> download_preference =
      policy_service_->GetDownloadPreference();
  if (!download_preference) {
    return E_FAIL;
  }

  *pref = base::win::ScopedBstr(base::UTF8ToWide(download_preference.policy()))
              .Release();
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_packageCacheSizeLimitMBytes(DWORD* limit) {
  CHECK(limit);

  PolicyStatus<int> cache_size_limit =
      policy_service_->GetPackageCacheSizeLimitMBytes();
  if (!cache_size_limit) {
    return E_FAIL;
  }

  *limit = cache_size_limit.policy();
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_packageCacheExpirationTimeDays(DWORD* days) {
  CHECK(days);

  PolicyStatus<int> cache_life_limit =
      policy_service_->GetPackageCacheExpirationTimeDays();
  if (!cache_life_limit) {
    return E_FAIL;
  }

  *days = cache_life_limit.policy();
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_effectivePolicyForAppInstalls(
    BSTR app_id,
    DWORD* policy) {
  CHECK(policy);

  PolicyStatus<int> install_policy =
      policy_service_->GetPolicyForAppInstalls(base::WideToUTF8(app_id));
  if (!install_policy) {
    return E_FAIL;
  }

  *policy = install_policy.policy();
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_effectivePolicyForAppUpdates(BSTR app_id,
                                                                DWORD* policy) {
  CHECK(policy);

  PolicyStatus<int> update_policy =
      policy_service_->GetPolicyForAppUpdates(base::WideToUTF8(app_id));
  if (!update_policy) {
    return E_FAIL;
  }

  *policy = update_policy.policy();
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_targetVersionPrefix(BSTR app_id,
                                                       BSTR* prefix) {
  CHECK(prefix);

  PolicyStatus<std::string> target_version_prefix =
      policy_service_->GetTargetVersionPrefix(base::WideToUTF8(app_id));
  if (!target_version_prefix) {
    return E_FAIL;
  }

  *prefix =
      base::win::ScopedBstr(base::UTF8ToWide(target_version_prefix.policy()))
          .Release();
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_isRollbackToTargetVersionAllowed(
    BSTR app_id,
    VARIANT_BOOL* rollback_allowed) {
  CHECK(rollback_allowed);

  PolicyStatus<bool> is_rollback_allowed =
      policy_service_->IsRollbackToTargetVersionAllowed(
          base::WideToUTF8(app_id));
  if (!is_rollback_allowed) {
    return E_FAIL;
  }

  *rollback_allowed =
      is_rollback_allowed.policy() ? VARIANT_TRUE : VARIANT_FALSE;
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_updaterVersion(BSTR* version) {
  CHECK(version);

  *version = base::win::ScopedBstr(kUpdaterVersionUtf16).Release();
  return S_OK;
}

namespace {

// Holds the result of the IPC to retrieve `last checked time`.
struct LastCheckedTimeResult
    : public base::RefCountedThreadSafe<LastCheckedTimeResult> {
  std::optional<DATE> last_checked_time;
  base::WaitableEvent completion_event;

 private:
  friend class base::RefCountedThreadSafe<LastCheckedTimeResult>;
  virtual ~LastCheckedTimeResult() = default;
};

// Holds the result of the IPC to retrieve PolicyService data.
template <typename T>
class PolicyStatusResult
    : public base::RefCountedThreadSafe<PolicyStatusResult<T>> {
 public:
  using ValueGetter = base::RepeatingCallback<PolicyStatus<T>()>;

  static auto Get(ValueGetter value_getter) {
    auto result = base::WrapRefCounted(new PolicyStatusResult<T>(value_getter));
    AppServerWin::PostRpcTask(
        base::BindOnce(&PolicyStatusResult::GetValueOnSequence, result));
    result->completion_event.TimedWait(base::Seconds(60));
    return result->value;
  }

 private:
  friend base::RefCountedThreadSafe<PolicyStatusResult<T>>;
  virtual ~PolicyStatusResult() = default;

  explicit PolicyStatusResult(ValueGetter value_getter)
      : value_getter(value_getter) {}

  void GetValueOnSequence() {
    PolicyStatus<T> policy_status = value_getter.Run();
    if (policy_status) {
      value = policy_status;
    }
    completion_event.Signal();
  }

  ValueGetter value_getter;
  std::optional<PolicyStatus<T>> value;
  base::WaitableEvent completion_event;
};

}  // namespace

STDMETHODIMP PolicyStatusImpl::get_lastCheckedTime(DATE* last_checked) {
  CHECK(last_checked);

  using PolicyStatusImplPtr = Microsoft::WRL::ComPtr<PolicyStatusImpl>;
  auto result = base::MakeRefCounted<LastCheckedTimeResult>();
  AppServerWin::PostRpcTask(base::BindOnce(
      [](PolicyStatusImplPtr obj, scoped_refptr<LastCheckedTimeResult> result) {
        const base::ScopedClosureRunner signal_event(base::BindOnce(
            [](scoped_refptr<LastCheckedTimeResult> result) {
              result->completion_event.Signal();
            },
            result));

        const base::Time last_checked_time =
            base::MakeRefCounted<const PersistedData>(
                GetUpdaterScope(),
                GetAppServerWinInstance()->prefs()->GetPrefService(), nullptr)
                ->GetLastChecked();
        if (last_checked_time.is_null()) {
          return;
        }

        const FILETIME last_checked_filetime = last_checked_time.ToFileTime();
        FILETIME file_time_local = {};
        SYSTEMTIME system_time = {};
        DATE last_checked_variant_time = {};
        if (::FileTimeToLocalFileTime(&last_checked_filetime,
                                      &file_time_local) &&
            ::FileTimeToSystemTime(&file_time_local, &system_time) &&
            ::SystemTimeToVariantTime(&system_time,
                                      &last_checked_variant_time)) {
          result->last_checked_time = last_checked_variant_time;
        }
      },
      PolicyStatusImplPtr(this), result));

  if (!result->completion_event.TimedWait(base::Seconds(60)) ||
      !result->last_checked_time.has_value()) {
    return E_FAIL;
  }

  *last_checked = *result->last_checked_time;
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::refreshPolicies() {
  // Capture `this` object throughout the policy fetch to have an outstanding
  // self reference of the COM object, otherwise the server could shutdown if
  // the caller releases its interface pointer when this function returns.
  using PolicyStatusImplPtr = Microsoft::WRL::ComPtr<PolicyStatusImpl>;
  AppServerWin::PostRpcTask(base::BindOnce(
      [](PolicyStatusImplPtr obj) {
        scoped_refptr<UpdateService> update_service =
            GetAppServerWinInstance()->update_service();
        if (!update_service) {
          return;
        }
        update_service->FetchPolicies(base::DoNothing());
      },
      PolicyStatusImplPtr(this)));
  return S_OK;
}

STDMETHODIMP PolicyStatusImpl::get_lastCheckPeriodMinutes(
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<int>::Get(base::BindRepeating(
      &PolicyService::DeprecatedGetLastCheckPeriodMinutes, policy_service_));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_updatesSuppressedTimes(
    IPolicyStatusValue** value,
    VARIANT_BOOL* are_updates_suppressed) {
  CHECK(value);
  CHECK(are_updates_suppressed);

  auto policy_status =
      PolicyStatusResult<UpdatesSuppressedTimes>::Get(base::BindRepeating(
          &PolicyService::GetUpdatesSuppressedTimes, policy_service_));
  if (!policy_status.has_value()) {
    return E_FAIL;
  }
  const UpdatesSuppressedTimes updates_suppressed_times =
      policy_status->effective_policy()->policy;
  if (!updates_suppressed_times.valid()) {
    return E_FAIL;
  }
  *are_updates_suppressed =
      policy_service_->AreUpdatesSuppressedNow() ? VARIANT_TRUE : VARIANT_FALSE;
  return PolicyStatusValueImpl::Create(*policy_status, value);
}

STDMETHODIMP PolicyStatusImpl::get_downloadPreferenceGroupPolicy(
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<std::string>::Get(base::BindRepeating(
      &PolicyService::GetDownloadPreference, policy_service_));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_packageCacheSizeLimitMBytes(
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<int>::Get(base::BindRepeating(
      &PolicyService::GetPackageCacheSizeLimitMBytes, policy_service_));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_packageCacheExpirationTimeDays(
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<int>::Get(base::BindRepeating(
      &PolicyService::GetPackageCacheExpirationTimeDays, policy_service_));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_proxyMode(IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<std::string>::Get(
      base::BindRepeating(&PolicyService::GetProxyMode, policy_service_));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_proxyPacUrl(IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<std::string>::Get(
      base::BindRepeating(&PolicyService::GetProxyPacUrl, policy_service_));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_proxyServer(IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<std::string>::Get(
      base::BindRepeating(&PolicyService::GetProxyServer, policy_service_));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_effectivePolicyForAppInstalls(
    BSTR app_id,
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<int>::Get(
      base::BindRepeating(&PolicyService::GetPolicyForAppInstalls,
                          policy_service_, base::WideToUTF8(app_id)));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_effectivePolicyForAppUpdates(
    BSTR app_id,
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<int>::Get(
      base::BindRepeating(&PolicyService::GetPolicyForAppUpdates,
                          policy_service_, base::WideToUTF8(app_id)));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_targetVersionPrefix(
    BSTR app_id,
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<std::string>::Get(
      base::BindRepeating(&PolicyService::GetTargetVersionPrefix,
                          policy_service_, base::WideToUTF8(app_id)));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_isRollbackToTargetVersionAllowed(
    BSTR app_id,
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<bool>::Get(
      base::BindRepeating(&PolicyService::IsRollbackToTargetVersionAllowed,
                          policy_service_, base::WideToUTF8(app_id)));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_targetChannel(BSTR app_id,
                                                 IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<std::string>::Get(
      base::BindRepeating(&PolicyService::GetTargetChannel, policy_service_,
                          base::WideToUTF8(app_id)));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_forceInstallApps(
    VARIANT_BOOL is_machine,
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status =
      PolicyStatusResult<std::vector<std::string>>::Get(base::BindRepeating(
          &PolicyService::GetForceInstallApps, policy_service_));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

STDMETHODIMP PolicyStatusImpl::get_cloudPolicyOverridesPlatformPolicy(
    IPolicyStatusValue** value) {
  CHECK(value);
  auto policy_status = PolicyStatusResult<bool>::Get(base::BindRepeating(
      &PolicyService::CloudPolicyOverridesPlatformPolicy, policy_service_));
  return policy_status.has_value()
             ? PolicyStatusValueImpl::Create(*policy_status, value)
             : E_FAIL;
}

PolicyStatusValueImpl::PolicyStatusValueImpl()
    : IDispatchImpl<IPolicyStatusValue>(
          {{__uuidof(IPolicyStatusValueUser), __uuidof(IPolicyStatusValue)}},
          {{__uuidof(IPolicyStatusValueSystem),
            __uuidof(IPolicyStatusValue)}}) {}
PolicyStatusValueImpl::~PolicyStatusValueImpl() = default;

template <typename T>
[[nodiscard]] HRESULT PolicyStatusValueImpl::Create(
    const T& value,
    IPolicyStatusValue** policy_status_value) {
  return MakeAndInitializeComObject<PolicyStatusValueImpl>(
      policy_status_value,
      value.effective_policy() ? value.effective_policy()->source : "",
      value.effective_policy()
          ? GetStringFromValue(value.effective_policy()->policy)
          : "",
      value.conflict_policy() != std::nullopt,
      value.conflict_policy() ? value.conflict_policy()->source : "",
      value.conflict_policy()
          ? GetStringFromValue(value.conflict_policy()->policy)
          : "");
}

HRESULT PolicyStatusValueImpl::RuntimeClassInitialize(
    const std::string& source,
    const std::string& value,
    bool has_conflict,
    const std::string& conflict_source,
    const std::string& conflict_value) {
  source_ = base::UTF8ToWide(source);
  value_ = base::UTF8ToWide(value);
  has_conflict_ = has_conflict ? VARIANT_TRUE : VARIANT_FALSE;
  conflict_source_ = base::UTF8ToWide(conflict_source);
  conflict_value_ = base::UTF8ToWide(conflict_value);

  return S_OK;
}

// IPolicyStatusValue.
STDMETHODIMP PolicyStatusValueImpl::get_source(BSTR* source) {
  CHECK(source);

  *source = base::win::ScopedBstr(source_).Release();
  return S_OK;
}

STDMETHODIMP PolicyStatusValueImpl::get_value(BSTR* value) {
  CHECK(value);

  *value = base::win::ScopedBstr(value_).Release();
  return S_OK;
}

STDMETHODIMP PolicyStatusValueImpl::get_hasConflict(
    VARIANT_BOOL* has_conflict) {
  CHECK(has_conflict);

  *has_conflict = has_conflict_;
  return S_OK;
}

STDMETHODIMP PolicyStatusValueImpl::get_conflictSource(BSTR* conflict_source) {
  CHECK(conflict_source);

  *conflict_source = base::win::ScopedBstr(conflict_source_).Release();
  return S_OK;
}

STDMETHODIMP PolicyStatusValueImpl::get_conflictValue(BSTR* conflict_value) {
  CHECK(conflict_value);

  *conflict_value = base::win::ScopedBstr(conflict_value_).Release();
  return S_OK;
}

}  // namespace updater