// Copyright 2025 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/ipc/update_service_proxy_mojo.h"

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

#include "base/cancelable_callback.h"
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/containers/to_vector.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/bind_post_task.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/version.h"
#include "build/build_config.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/ipc/ipc_names.h"
#include "chrome/updater/ipc/update_service_dialer.h"
#include "chrome/updater/ipc/update_service_proxy.h"
#include "chrome/updater/mojom/updater_service.mojom.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/service_proxy_factory.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/posix_util.h"
#include "components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.h"
#include "components/policy/core/common/policy_types.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
#include "mojo/public/cpp/system/isolated_connection.h"
#include "mojo/public/cpp/system/message_pipe.h"

#if BUILDFLAG(IS_WIN)
#include <wrl/client.h>
#endif  // BUILDFLAG(IS_WIN)

namespace updater {
namespace {

// The maximum amount of time to poll the server's socket for a connection.
constexpr base::TimeDelta kConnectionTimeout = base::Minutes(3);

[[nodiscard]] UpdateService::UpdateState MakeUpdateState(
    const mojom::UpdateStatePtr& state_mojom) {
  updater::UpdateService::UpdateState state;
  state.app_id = state_mojom->app_id;
  state.state =
      static_cast<UpdateService::UpdateState::State>(state_mojom->state);
  state.next_version = state_mojom->next_version;
  state.downloaded_bytes = state_mojom->downloaded_bytes;
  state.total_bytes = state_mojom->total_bytes;
  state.install_progress = state_mojom->install_progress;
  state.error_category =
      static_cast<UpdateService::ErrorCategory>(state_mojom->error_category);
  state.error_code = state_mojom->error_code;
  state.extra_code1 = state_mojom->extra_code1;
  state.installer_text = state_mojom->installer_text;
  state.installer_cmd_line = state_mojom->installer_cmd_line;

  return state;
}

[[nodiscard]] UpdateService::AppState MakeAppState(
    const mojom::AppStatePtr& app_state_mojo) {
  UpdateService::AppState app_state;
  app_state.app_id = app_state_mojo->app_id;
  app_state.version = app_state_mojo->version;
  if (app_state_mojo->version_path) {
    app_state.version_path = *app_state_mojo->version_path;
  }
  if (app_state_mojo->version_key) {
    app_state.version_key = *app_state_mojo->version_key;
  }
  app_state.ap = app_state_mojo->ap;
  if (app_state_mojo->ap_path) {
    app_state.ap_path = *app_state_mojo->ap_path;
  }
  if (app_state_mojo->ap_key) {
    app_state.ap_key = *app_state_mojo->ap_key;
  }
  app_state.brand_code = app_state_mojo->brand_code;
  app_state.brand_path = app_state_mojo->brand_path;
  app_state.ecp = app_state_mojo->ecp;
  if (app_state_mojo->cohort) {
    app_state.cohort = *app_state_mojo->cohort;
  }

  return app_state;
}

[[nodiscard]] mojom::RegistrationRequestPtr MakeRegistrationRequest(
    const RegistrationRequest& request) {
  return mojom::RegistrationRequest::New(request);
}

class StateChangeObserverImpl : public mojom::StateChangeObserver {
 public:
  explicit StateChangeObserverImpl(
      base::RepeatingCallback<void(const UpdateService::UpdateState&)>
          state_change_callback,
      base::OnceCallback<void(UpdateService::Result)> complete_callback)
      : state_change_callback_(std::move(state_change_callback)),
        complete_callback_(std::move(complete_callback)) {}
  StateChangeObserverImpl(const StateChangeObserverImpl&) = delete;
  StateChangeObserverImpl& operator=(const StateChangeObserverImpl&) = delete;

  // Overrides for mojom::StateChangeObserver.
  void OnStateChange(mojom::UpdateStatePtr state_mojom) override {
    CHECK(complete_callback_) << "OnStateChange received after OnComplete";
    state_change_callback_.Run(MakeUpdateState(state_mojom));
  }

  void OnComplete(mojom::UpdateService::Result result) override {
    CHECK(complete_callback_) << "OnComplete received without a valid "
                                 "callback. Was OnComplete run twice?";
    if (complete_callback_) {
      std::move(complete_callback_)
          .Run(static_cast<updater::UpdateService::Result>(result));
    }
  }

 private:
  base::RepeatingCallback<void(const UpdateService::UpdateState&)>
      state_change_callback_;
  base::OnceCallback<void(UpdateService::Result)> complete_callback_;
};

template <typename T>
base::OnceCallback<void(T)> ToMojoCallback(
    base::OnceCallback<void(base::expected<T, RpcError>)> callback) {
  return base::BindOnce(
      [](base::OnceCallback<void(base::expected<T, RpcError>)> callback,
         T value) { std::move(callback).Run(base::ok(value)); },
      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
          std::move(callback), base::unexpected(kErrorIpcDisconnect)));
}

// Binds a callback which creates a self-owned StateChangeObserverImpl to
// forward RPC callbacks to the provided native callbacks.
[[nodiscard]] base::OnceCallback<
    void(mojo::PendingReceiver<mojom::StateChangeObserver>)>
MakeStateChangeObserver(
    base::RepeatingCallback<void(const UpdateService::UpdateState&)>
        state_change_callback,
    base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
        complete_callback) {
  return base::BindOnce(
      [](base::RepeatingCallback<void(const UpdateService::UpdateState&)>
             state_change_callback,
         base::OnceCallback<void(UpdateService::Result)> complete_callback,
         mojo::PendingReceiver<mojom::StateChangeObserver> receiver) {
        mojo::MakeSelfOwnedReceiver(
            std::make_unique<StateChangeObserverImpl>(
                state_change_callback,
                base::BindOnce(std::move(complete_callback))),
            std::move(receiver));
      },
      base::BindPostTaskToCurrentDefault(state_change_callback),
      base::BindPostTaskToCurrentDefault(
          ToMojoCallback(std::move(complete_callback))));
}

}  // namespace

UpdateServiceProxyMojoImpl::UpdateServiceProxyMojoImpl(
    UpdaterScope scope,
    base::TimeDelta get_version_timeout)
    : scope_(scope), get_version_timeout_(get_version_timeout) {}

void UpdateServiceProxyMojoImpl::GetVersion(
    base::OnceCallback<void(base::expected<base::Version, RpcError>)>
        callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();

  // Because GetVersion is used as a health checker, it has a special timeout
  // in the event that the server receives the call and hangs. If the timeout
  // elapses, this calls OnDisconnected to reset the connection and trigger the
  // DefaultInvokeIfNotRun wrapper around `callback`.
  auto timeout_callback = std::make_unique<base::CancelableOnceClosure>(
      base::BindOnce(&UpdateServiceProxyMojoImpl::OnDisconnected,
                     weak_factory_.GetWeakPtr()));

  // If get_version_timeout_ elapses, call the timeout callback.
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, timeout_callback->callback(), get_version_timeout_);

  remote_->GetVersion(base::BindOnce(
      [](base::OnceCallback<void(base::expected<base::Version, RpcError>)>
             callback,
         std::unique_ptr<base::CancelableOnceClosure> timeout_callback,
         const std::string& version) {
        timeout_callback->Cancel();
        std::move(callback).Run(base::Version(version));
      },
      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
          std::move(callback), base::unexpected(kErrorIpcDisconnect)),
      std::move(timeout_callback)));
}

void UpdateServiceProxyMojoImpl::FetchPolicies(
    policy::PolicyFetchReason reason,
    base::OnceCallback<void(base::expected<int, RpcError>)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->FetchPolicies(reason, ToMojoCallback(std::move(callback)));
}

void UpdateServiceProxyMojoImpl::RegisterApp(
    const RegistrationRequest& request,
    base::OnceCallback<void(base::expected<int, RpcError>)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->RegisterApp(MakeRegistrationRequest(request),
                       ToMojoCallback(std::move(callback)));
}

void UpdateServiceProxyMojoImpl::GetAppStates(
    base::OnceCallback<void(base::expected<std::vector<UpdateService::AppState>,
                                           RpcError>)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->GetAppStates(
      base::BindOnce([](std::vector<mojom::AppStatePtr> app_states_mojo) {
        return base::ToVector(app_states_mojo, &MakeAppState);
      }).Then(ToMojoCallback(std::move(callback))));
}

void UpdateServiceProxyMojoImpl::RunPeriodicTasks(
    base::OnceCallback<void(base::expected<int, RpcError>)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->RunPeriodicTasks(base::BindOnce(
      [](base::OnceCallback<void(int)> callback) {
        std::move(callback).Run(kErrorOk);
      },
      ToMojoCallback(std::move(callback))));
}

void UpdateServiceProxyMojoImpl::CheckForUpdate(
    const std::string& app_id,
    UpdateService::Priority priority,
    UpdateService::PolicySameVersionUpdate policy_same_version_update,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateService::UpdateState&)>
        state_update,
    base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
        callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->CheckForUpdate(
      app_id, static_cast<mojom::UpdateService::Priority>(priority),
      static_cast<mojom::UpdateService::PolicySameVersionUpdate>(
          policy_same_version_update),
      language, MakeStateChangeObserver(state_update, std::move(callback)));
}

void UpdateServiceProxyMojoImpl::Update(
    const std::string& app_id,
    const std::string& install_data_index,
    UpdateService::Priority priority,
    UpdateService::PolicySameVersionUpdate policy_same_version_update,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateService::UpdateState&)>
        state_update,
    base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
        callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->Update(app_id, install_data_index,
                  static_cast<mojom::UpdateService::Priority>(priority),
                  static_cast<mojom::UpdateService::PolicySameVersionUpdate>(
                      policy_same_version_update),
                  /*do_update_check_only=*/false, language,
                  MakeStateChangeObserver(state_update, std::move(callback)));
}

void UpdateServiceProxyMojoImpl::UpdateAll(
    base::RepeatingCallback<void(const UpdateService::UpdateState&)>
        state_update,
    base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
        callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->UpdateAll(
      MakeStateChangeObserver(state_update, std::move(callback)));
}

void UpdateServiceProxyMojoImpl::Install(
    const RegistrationRequest& registration,
    const std::string& client_install_data,
    const std::string& install_data_index,
    UpdateService::Priority priority,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateService::UpdateState&)>
        state_update,
    base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
        callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->Install(
      MakeRegistrationRequest(registration), client_install_data,
      install_data_index, static_cast<mojom::UpdateService::Priority>(priority),
      language, MakeStateChangeObserver(state_update, std::move(callback)));
}

void UpdateServiceProxyMojoImpl::CancelInstalls(const std::string& app_id) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->CancelInstalls(app_id);
}

void UpdateServiceProxyMojoImpl::RunInstaller(
    const std::string& app_id,
    const base::FilePath& installer_path,
    const std::string& install_args,
    const std::string& install_data,
    const std::string& install_settings,
    const std::string& language,
    base::RepeatingCallback<void(const UpdateService::UpdateState&)>
        state_update,
    base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
        callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->RunInstaller(
      app_id, installer_path, install_args, install_data, install_settings,
      language, MakeStateChangeObserver(state_update, std::move(callback)));
}

void UpdateServiceProxyMojoImpl::GetUpdaterState(
    base::OnceCallback<
        void(base::expected<UpdateService::UpdaterState, RpcError>)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->GetUpdaterState(
      base::BindOnce([](mojom::UpdaterStatePtr updater_state_mojo) {
        return *updater_state_mojo;
      }).Then(ToMojoCallback(std::move(callback))));
}

void UpdateServiceProxyMojoImpl::GetPoliciesJson(
    base::OnceCallback<void(base::expected<std::string, RpcError>)> callback) {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureConnecting();
  remote_->GetPoliciesJson(base::BindOnce([](const std::string& policies_json) {
                             return policies_json;
                           }).Then(ToMojoCallback(std::move(callback))));
}

#if BUILDFLAG(IS_WIN)
void UpdateServiceProxyMojoImpl::OnConnected(
    mojo::PendingReceiver<mojom::UpdateService> pending_receiver,
    std::optional<mojo::PlatformChannelEndpoint> endpoint,
    Microsoft::WRL::ComPtr<IUnknown> server) {
#else   // BUILDFLAG(IS_WIN)
void UpdateServiceProxyMojoImpl::OnConnected(
    mojo::PendingReceiver<mojom::UpdateService> pending_receiver,
    std::optional<mojo::PlatformChannelEndpoint> endpoint) {
#endif  // BUILDFLAG(IS_WIN)
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Verify remote_ has not been reset in the meantime.
  if (!remote_.is_bound()) {
    LOG(ERROR) << "Remote was reset during connection initialization.";
    return;
  }

  if (!endpoint) {
    remote_.reset();
    return;
  }

  auto connection = std::make_unique<mojo::IsolatedConnection>();
  // Connect `remote_` to the RPC server by fusing its message pipe to the one
  // created by `IsolatedConnection::Connect`.
  if (!mojo::FusePipes(
          std::move(pending_receiver),
          mojo::PendingRemote<mojom::UpdateService>(
              connection->Connect(std::move(endpoint.value())), 0))) {
    LOG(ERROR) << "Failed to fuse Mojo pipes for RPC.";
    remote_.reset();
    return;
  }

  connection_ = std::move(connection);

#if BUILDFLAG(IS_WIN)
  server_ = server;
#endif  // BUILDFLAG(IS_WIN)

  // A weak pointer is used here to prevent remote_ from forming a reference
  // cycle with this object.
  remote_.set_disconnect_handler(base::BindOnce(
      &UpdateServiceProxyMojoImpl::OnDisconnected, weak_factory_.GetWeakPtr()));
}

void UpdateServiceProxyMojoImpl::OnDisconnected() {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  connection_.reset();
  remote_.reset();
}

UpdateServiceProxyMojoImpl::~UpdateServiceProxyMojoImpl() {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void UpdateServiceProxyMojoImpl::EnsureConnecting() {
  VLOG(1) << __func__;
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (remote_) {
    return;
  }
  base::ThreadPool::PostTask(
      FROM_HERE, {base::MayBlock()},
      base::BindOnce(&ConnectMojo, scope_, /*internal=*/false,
                     base::Time::Now() + kConnectionTimeout,
                     base::BindPostTaskToCurrentDefault(base::BindOnce(
                         &UpdateServiceProxyMojoImpl::OnConnected,
                         weak_factory_.GetWeakPtr(),
                         remote_.BindNewPipeAndPassReceiver()))));
}

#if BUILDFLAG(IS_WIN)
scoped_refptr<UpdateService> CreateUpdateServiceProxyMojo(
    UpdaterScope scope,
    base::TimeDelta timeout) {
#else   // BUILDFLAG(IS_WIN)
scoped_refptr<UpdateService> CreateUpdateServiceProxy(UpdaterScope scope,
                                                      base::TimeDelta timeout) {
#endif  // BUILDFLAG(IS_WIN)
  return base::MakeRefCounted<UpdateServiceProxy>(
      base::MakeRefCounted<UpdateServiceProxyMojoImpl>(scope, timeout));
}

}  // namespace updater
