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

#include "remoting/host/ipc_peer_session.h"

#include <utility>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "remoting/signaling/signaling_id_util.h"

namespace remoting {

IpcPeerSession::IpcPeerSession(
    mojo::PendingRemote<mojom::PeerSession> peer_session_remote,
    GetDesktopSessionCallback get_desktop_session_callback,
    std::unique_ptr<protocol::IceConfigFetcher> ice_config_fetcher,
    PeerSessionFactory::RequestPairingOnceCallback request_pairing_cb)
    : get_desktop_session_callback_(std::move(get_desktop_session_callback)),
      ice_config_fetcher_(std::move(ice_config_fetcher)),
      request_pairing_cb_(std::move(request_pairing_cb)) {
  CHECK(get_desktop_session_callback_);
  CHECK(ice_config_fetcher_);
  if (peer_session_remote) {
    remote_.Bind(std::move(peer_session_remote));
    remote_.set_disconnect_handler(base::BindOnce(
        &IpcPeerSession::OnPeerSessionDisconnected, base::Unretained(this)));
  }
}

IpcPeerSession::~IpcPeerSession() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void IpcPeerSession::Start(
    EventHandler* event_handler,
    std::string_view client_jid,
    const DesktopEnvironmentOptions& desktop_environment_options,
    const SessionPolicies& session_policies,
    const SessionOptions& session_options) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(event_handler);
  event_handler_ = event_handler;
  if (!remote_.is_bound()) {
    LOG(WARNING) << "Start called when PeerSession remote is not bound.";
    NotifySessionClosed(protocol::ErrorCode::CHANNEL_CONNECTION_ERROR,
                        "PeerSession remote is not bound.", FROM_HERE);
    return;
  }

  DCHECK(!event_handler_receiver_);

  desktop_session_options_ = mojom::DesktopSessionOptions::New();
  SplitSignalingIdResource(client_jid, &desktop_session_options_->client_id,
                           /*resource=*/nullptr);
  desktop_session_options_->is_curtained =
      desktop_environment_options.enable_curtaining();

  DCHECK(!desktop_session_requester_receiver_.is_bound());
  mojo::PendingRemote<mojom::DesktopSessionRequester>
      desktop_session_requester_remote =
          desktop_session_requester_receiver_.BindNewPipeAndPassRemote();

  mojo::PendingRemote<mojom::PeerSessionEventHandler> event_handler_remote;
  if (event_handler) {
    event_handler_ = event_handler;
    event_handler_receiver_ =
        std::make_unique<mojo::Receiver<mojom::PeerSessionEventHandler>>(
            event_handler);
    event_handler_remote = event_handler_receiver_->BindNewPipeAndPassRemote();
    event_handler_receiver_->set_disconnect_handler(base::BindOnce(
        &IpcPeerSession::OnEventHandlerDisconnected, base::Unretained(this)));
  }

  DCHECK(!ice_config_fetcher_receiver_.is_bound());
  mojo::PendingRemote<mojom::IceConfigFetcher> ice_config_fetcher_remote =
      ice_config_fetcher_receiver_.BindNewPipeAndPassRemote();

  DCHECK(!pairing_requester_receiver_.is_bound());
  mojo::PendingRemote<mojom::PairingRequester> pairing_requester_remote =
      pairing_requester_receiver_.BindNewPipeAndPassRemote();

  remote_->Start(
      std::string(client_jid), std::move(event_handler_remote),
      std::move(desktop_session_requester_remote),
      std::move(ice_config_fetcher_remote), std::move(pairing_requester_remote),
      desktop_environment_options, session_policies, session_options);
}

void IpcPeerSession::RequestDesktopSession(
    mojo::PendingReceiver<mojom::DesktopSession> control_receiver,
    mojo::PendingRemote<mojom::DesktopSessionEvents> events_remote,
    const ScreenResolution& screen_resolution) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!desktop_session_options_) {
    LOG(ERROR)
        << "Desktop session already requested or session is not started.";
    desktop_session_requester_receiver_.ReportBadMessage(
        "Desktop session already requested or session is not started.");
    return;
  }

  if (!get_desktop_session_callback_) {
    LOG(ERROR) << "RequestDesktopSession called without a valid callback.";
    return;
  }

  desktop_session_options_->screen_resolution = screen_resolution;

  get_desktop_session_callback_.Run(std::move(control_receiver),
                                    std::move(events_remote),
                                    std::move(desktop_session_options_));
}

void IpcPeerSession::GetIceConfig(GetIceConfigCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  ice_config_fetcher_->GetIceConfig(std::move(callback));
}

void IpcPeerSession::RequestPairing(const std::string& client_name,
                                    RequestPairingCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!request_pairing_cb_) {
    std::move(callback).Run(std::nullopt);
    return;
  }
  // Pairing can only be requested once per connection, so we reset the callback
  // to prevent possible DOS attacks.
  std::move(request_pairing_cb_).Run(client_name, std::move(callback));
}

void IpcPeerSession::DisconnectSession(protocol::ErrorCode error,
                                       std::string_view error_details,
                                       const SourceLocation& error_location) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!remote_.is_bound()) {
    LOG(WARNING)
        << "DisconnectSession called when PeerSession remote is not bound.";
    NotifySessionClosed(error, std::string(error_details), error_location);
    return;
  }
  remote_->DisconnectSession(error, std::string(error_details), error_location);
}

void IpcPeerSession::OnSessionServicesClientConnected(
    mojo::PendingReceiver<mojom::ChromotingSessionServices> receiver) {
  // In multi-process host, ChromotingSessionServices clients are connected
  // directly to the PC process via DesktopSessionEvents by the Daemon process,
  // so this method is never invoked on IpcPeerSession.
  NOTREACHED();
}

protocol::Transport* IpcPeerSession::transport() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return this;
}

void IpcPeerSession::Start(
    const std::string& auth_key,
    SendTransportInfoCallback send_transport_info_callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  send_transport_info_callback_ = std::move(send_transport_info_callback);
  if (remote_.is_bound()) {
    transport_event_handler_receiver_.reset();
    remote_->StartTransport(
        auth_key, transport_event_handler_receiver_.BindNewPipeAndPassRemote());
  }
}

bool IpcPeerSession::ProcessTransportInfo(
    const JingleTransportInfo& transport_info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (remote_.is_bound()) {
    remote_->ProcessTransportInfo(transport_info);
  }
  return true;
}

void IpcPeerSession::SendTransportInfo(
    const JingleTransportInfo& transport_info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (send_transport_info_callback_) {
    send_transport_info_callback_.Run(
        std::make_unique<JingleTransportInfo>(transport_info));
  }
}

void IpcPeerSession::OnPeerSessionDisconnected() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  LOG(ERROR) << "PeerSession remote dropped unexpectedly.";
  remote_.reset();
  NotifySessionClosed(protocol::ErrorCode::CHANNEL_CONNECTION_ERROR,
                      "PeerSession remote dropped unexpectedly.", FROM_HERE);
}

void IpcPeerSession::OnEventHandlerDisconnected() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  LOG(ERROR) << "PeerSessionEventHandler remote dropped unexpectedly.";
  event_handler_receiver_.reset();
  NotifySessionClosed(protocol::ErrorCode::CHANNEL_CONNECTION_ERROR,
                      "PeerSessionEventHandler dropped unexpectedly.",
                      FROM_HERE);
}

void IpcPeerSession::NotifySessionClosed(protocol::ErrorCode error,
                                         const std::string& error_details,
                                         const SourceLocation& error_location) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&IpcPeerSession::DoNotifySessionClosed,
                                weak_factory_.GetWeakPtr(), error,
                                error_details, error_location));
}

void IpcPeerSession::DoNotifySessionClosed(
    protocol::ErrorCode error,
    const std::string& error_details,
    const SourceLocation& error_location) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  event_handler_receiver_.reset();
  transport_event_handler_receiver_.reset();
  ice_config_fetcher_receiver_.reset();
  pairing_requester_receiver_.reset();
  desktop_session_requester_receiver_.reset();
  desktop_session_options_.reset();
  send_transport_info_callback_.Reset();
  if (event_handler_) {
    auto* handler = event_handler_.get();
    event_handler_ = nullptr;
    handler->OnSessionClosed(error, error_details, error_location);
  }
}

IpcPeerSessionFactory::IpcPeerSessionFactory(
    mojo::PendingAssociatedRemote<mojom::PeerSessionManager>
        peer_session_manager,
    mojo::PendingAssociatedRemote<mojom::DesktopSessionManager>
        desktop_session_manager,
    GetIceConfigFetcherCallback get_ice_config_fetcher_cb)
    : pending_peer_session_manager_(std::move(peer_session_manager)),
      pending_desktop_session_manager_(std::move(desktop_session_manager)),
      get_ice_config_fetcher_cb_(std::move(get_ice_config_fetcher_cb)) {
  CHECK(get_ice_config_fetcher_cb_);
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

IpcPeerSessionFactory::~IpcPeerSessionFactory() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void IpcPeerSessionFactory::EnsureBound() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (pending_peer_session_manager_) {
    peer_session_manager_.Bind(std::move(pending_peer_session_manager_));
    peer_session_manager_.set_disconnect_handler(
        base::BindOnce(&IpcPeerSessionFactory::OnPeerSessionManagerDisconnected,
                       base::Unretained(this)));
  }
  if (pending_desktop_session_manager_) {
    desktop_session_manager_.Bind(std::move(pending_desktop_session_manager_));
    desktop_session_manager_.set_disconnect_handler(base::BindOnce(
        &IpcPeerSessionFactory::OnDesktopSessionManagerDisconnected,
        base::Unretained(this)));
  }
}

void IpcPeerSessionFactory::OnPeerSessionManagerDisconnected() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  LOG(ERROR) << "PeerSessionManager associated remote disconnected.";
  peer_session_manager_.reset();
}

void IpcPeerSessionFactory::OnDesktopSessionManagerDisconnected() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  LOG(ERROR) << "DesktopSessionManager associated remote disconnected.";
  desktop_session_manager_.reset();
}

void IpcPeerSessionFactory::set_request_pairing_callback(
    const RequestPairingCallback& request_pairing_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  request_pairing_cb_ = request_pairing_cb;
}

void IpcPeerSessionFactory::SetRequiredUsername(std::string_view username) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  required_username_ = std::string(username);
}

void IpcPeerSessionFactory::GetDesktopSession(
    mojo::PendingReceiver<mojom::DesktopSession> control_receiver,
    mojo::PendingRemote<mojom::DesktopSessionEvents> events_remote,
    mojom::DesktopSessionOptionsPtr options) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureBound();
  if (!desktop_session_manager_.is_bound()) {
    LOG(ERROR) << "DesktopSessionManager is not bound.";
    return;
  }
  options->required_username = required_username_;
  desktop_session_manager_->GetDesktopSession(std::move(control_receiver),
                                              std::move(events_remote),
                                              std::move(options));
}

std::unique_ptr<PeerSession> IpcPeerSessionFactory::Create() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  EnsureBound();
  if (!peer_session_manager_.is_bound()) {
    LOG(ERROR) << "PeerSessionManager is not bound.";
    return nullptr;
  }
  if (!desktop_session_manager_.is_bound()) {
    LOG(ERROR) << "DesktopSessionManager is not bound.";
    return nullptr;
  }

  CHECK(get_ice_config_fetcher_cb_) << "Missing Ice Config Fetcher callback.";
  std::unique_ptr<protocol::IceConfigFetcher> ice_config_fetcher =
      get_ice_config_fetcher_cb_.Run();

  mojo::PendingRemote<mojom::PeerSession> pending_remote;
  peer_session_manager_->LaunchPeerSession(
      pending_remote.InitWithNewPipeAndPassReceiver());

  return std::make_unique<IpcPeerSession>(
      std::move(pending_remote),
      base::BindRepeating(&IpcPeerSessionFactory::GetDesktopSession,
                          weak_factory_.GetWeakPtr()),
      std::move(ice_config_fetcher), request_pairing_cb_);
}

}  // namespace remoting
