// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/protocol/connection_to_host.h" #include "base/bind.h" #include "base/callback.h" #include "base/location.h" #include "remoting/base/constants.h" #include "remoting/protocol/audio_reader.h" #include "remoting/protocol/audio_stub.h" #include "remoting/protocol/auth_util.h" #include "remoting/protocol/authenticator.h" #include "remoting/protocol/client_control_dispatcher.h" #include "remoting/protocol/client_event_dispatcher.h" #include "remoting/protocol/client_stub.h" #include "remoting/protocol/client_video_dispatcher.h" #include "remoting/protocol/clipboard_stub.h" #include "remoting/protocol/errors.h" #include "remoting/protocol/jingle_session_manager.h" #include "remoting/protocol/transport.h" #include "remoting/protocol/video_stub.h" namespace remoting { namespace protocol { ConnectionToHost::ConnectionToHost() : event_callback_(NULL), client_stub_(NULL), clipboard_stub_(NULL), audio_stub_(NULL), signal_strategy_(NULL), state_(INITIALIZING), error_(OK) { } ConnectionToHost::~ConnectionToHost() { CloseChannels(); if (session_.get()) session_.reset(); if (session_manager_.get()) session_manager_.reset(); if (signal_strategy_) signal_strategy_->RemoveListener(this); } void ConnectionToHost::Connect(SignalStrategy* signal_strategy, scoped_ptr transport_factory, scoped_ptr authenticator, const std::string& host_jid, HostEventCallback* event_callback) { DCHECK(client_stub_); DCHECK(clipboard_stub_); DCHECK(monitored_video_stub_); // Initialize default |candidate_config_| if set_candidate_config() wasn't // called. if (!candidate_config_) { candidate_config_ = CandidateSessionConfig::CreateDefault(); if (!audio_stub_) { candidate_config_->DisableAudioChannel(); } candidate_config_->EnableVideoCodec(ChannelConfig::CODEC_VP9); } signal_strategy_ = signal_strategy; event_callback_ = event_callback; authenticator_ = authenticator.Pass(); // Save jid of the host. The actual connection is created later after // |signal_strategy_| is connected. host_jid_ = host_jid; signal_strategy_->AddListener(this); signal_strategy_->Connect(); session_manager_.reset(new JingleSessionManager(transport_factory.Pass())); session_manager_->Init(signal_strategy_, this); SetState(CONNECTING, OK); } void ConnectionToHost::set_candidate_config( scoped_ptr config) { DCHECK_EQ(state_, INITIALIZING); candidate_config_ = config.Pass(); } const SessionConfig& ConnectionToHost::config() { return session_->config(); } ClipboardStub* ConnectionToHost::clipboard_forwarder() { return &clipboard_forwarder_; } HostStub* ConnectionToHost::host_stub() { // TODO(wez): Add a HostFilter class, equivalent to input filter. return control_dispatcher_.get(); } InputStub* ConnectionToHost::input_stub() { return &event_forwarder_; } void ConnectionToHost::set_client_stub(ClientStub* client_stub) { client_stub_ = client_stub; } void ConnectionToHost::set_clipboard_stub(ClipboardStub* clipboard_stub) { clipboard_stub_ = clipboard_stub; } void ConnectionToHost::set_video_stub(VideoStub* video_stub) { DCHECK(video_stub); monitored_video_stub_.reset(new MonitoredVideoStub( video_stub, base::TimeDelta::FromSeconds( MonitoredVideoStub::kConnectivityCheckDelaySeconds), base::Bind(&ConnectionToHost::OnVideoChannelStatus, base::Unretained(this)))); } void ConnectionToHost::set_audio_stub(AudioStub* audio_stub) { audio_stub_ = audio_stub; } void ConnectionToHost::OnSignalStrategyStateChange( SignalStrategy::State state) { DCHECK(CalledOnValidThread()); DCHECK(event_callback_); if (state == SignalStrategy::CONNECTED) { VLOG(1) << "Connected as: " << signal_strategy_->GetLocalJid(); } else if (state == SignalStrategy::DISCONNECTED) { VLOG(1) << "Connection closed."; CloseOnError(SIGNALING_ERROR); } } bool ConnectionToHost::OnSignalStrategyIncomingStanza( const buzz::XmlElement* stanza) { return false; } void ConnectionToHost::OnSessionManagerReady() { DCHECK(CalledOnValidThread()); // After SessionManager is initialized we can try to connect to the host. session_ = session_manager_->Connect( host_jid_, authenticator_.Pass(), candidate_config_.Pass()); session_->SetEventHandler(this); } void ConnectionToHost::OnIncomingSession( Session* session, SessionManager::IncomingSessionResponse* response) { DCHECK(CalledOnValidThread()); // Client always rejects incoming sessions. *response = SessionManager::DECLINE; } void ConnectionToHost::OnSessionStateChange( Session::State state) { DCHECK(CalledOnValidThread()); DCHECK(event_callback_); switch (state) { case Session::INITIALIZING: case Session::CONNECTING: case Session::ACCEPTING: case Session::CONNECTED: case Session::AUTHENTICATING: // Don't care about these events. break; case Session::AUTHENTICATED: SetState(AUTHENTICATED, OK); control_dispatcher_.reset(new ClientControlDispatcher()); control_dispatcher_->Init( session_.get(), session_->config().control_config(), base::Bind(&ConnectionToHost::OnChannelInitialized, base::Unretained(this))); control_dispatcher_->set_client_stub(client_stub_); control_dispatcher_->set_clipboard_stub(clipboard_stub_); event_dispatcher_.reset(new ClientEventDispatcher()); event_dispatcher_->Init( session_.get(), session_->config().event_config(), base::Bind(&ConnectionToHost::OnChannelInitialized, base::Unretained(this))); video_dispatcher_.reset( new ClientVideoDispatcher(monitored_video_stub_.get())); video_dispatcher_->Init(session_.get(), session_->config().video_config(), base::Bind(&ConnectionToHost::OnChannelInitialized, base::Unretained(this))); audio_reader_ = AudioReader::Create(session_->config()); if (audio_reader_.get()) { audio_reader_->Init(session_.get(), session_->config().audio_config(), base::Bind(&ConnectionToHost::OnChannelInitialized, base::Unretained(this))); audio_reader_->set_audio_stub(audio_stub_); } break; case Session::CLOSED: CloseChannels(); SetState(CLOSED, OK); break; case Session::FAILED: // If we were connected then treat signaling timeout error as if // the connection was closed by the peer. // // TODO(sergeyu): This logic belongs to the webapp, but we // currently don't expose this error code to the webapp, and it // would be hard to add it because client plugin and webapp // versions may not be in sync. It should be easy to do after we // are finished moving the client plugin to NaCl. if (state_ == CONNECTED && session_->error() == SIGNALING_TIMEOUT) { CloseChannels(); SetState(CLOSED, OK); } else { CloseOnError(session_->error()); } break; } } void ConnectionToHost::OnSessionRouteChange(const std::string& channel_name, const TransportRoute& route) { event_callback_->OnRouteChanged(channel_name, route); } void ConnectionToHost::OnVideoChannelStatus(bool active) { event_callback_->OnConnectionReady(active); } ConnectionToHost::State ConnectionToHost::state() const { return state_; } void ConnectionToHost::OnChannelInitialized(bool successful) { if (!successful) { LOG(ERROR) << "Failed to connect video channel"; CloseOnError(CHANNEL_CONNECTION_ERROR); return; } NotifyIfChannelsReady(); } void ConnectionToHost::NotifyIfChannelsReady() { if (!control_dispatcher_.get() || !control_dispatcher_->is_connected()) return; if (!event_dispatcher_.get() || !event_dispatcher_->is_connected()) return; if (!video_dispatcher_.get() || !video_dispatcher_->is_connected()) return; if ((!audio_reader_.get() || !audio_reader_->is_connected()) && session_->config().is_audio_enabled()) { return; } if (state_ != AUTHENTICATED) return; // Start forwarding clipboard and input events. clipboard_forwarder_.set_clipboard_stub(control_dispatcher_.get()); event_forwarder_.set_input_stub(event_dispatcher_.get()); SetState(CONNECTED, OK); } void ConnectionToHost::CloseOnError(ErrorCode error) { CloseChannels(); SetState(FAILED, error); } void ConnectionToHost::CloseChannels() { control_dispatcher_.reset(); event_dispatcher_.reset(); clipboard_forwarder_.set_clipboard_stub(NULL); event_forwarder_.set_input_stub(NULL); video_dispatcher_.reset(); audio_reader_.reset(); } void ConnectionToHost::SetState(State state, ErrorCode error) { DCHECK(CalledOnValidThread()); // |error| should be specified only when |state| is set to FAILED. DCHECK(state == FAILED || error == OK); if (state != state_) { state_ = state; error_ = error; event_callback_->OnConnectionState(state_, error_); } } } // namespace protocol } // namespace remoting