| // 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 |