1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "remoting/protocol/connection_to_host.h" 6 7#include "base/bind.h" 8#include "base/callback.h" 9#include "base/location.h" 10#include "remoting/base/constants.h" 11#include "remoting/protocol/audio_reader.h" 12#include "remoting/protocol/audio_stub.h" 13#include "remoting/protocol/auth_util.h" 14#include "remoting/protocol/authenticator.h" 15#include "remoting/protocol/client_control_dispatcher.h" 16#include "remoting/protocol/client_event_dispatcher.h" 17#include "remoting/protocol/client_stub.h" 18#include "remoting/protocol/client_video_dispatcher.h" 19#include "remoting/protocol/clipboard_stub.h" 20#include "remoting/protocol/errors.h" 21#include "remoting/protocol/jingle_session_manager.h" 22#include "remoting/protocol/transport.h" 23#include "remoting/protocol/video_stub.h" 24 25namespace remoting { 26namespace protocol { 27 28ConnectionToHost::ConnectionToHost() 29 : event_callback_(NULL), 30 client_stub_(NULL), 31 clipboard_stub_(NULL), 32 audio_stub_(NULL), 33 signal_strategy_(NULL), 34 state_(INITIALIZING), 35 error_(OK) { 36} 37 38ConnectionToHost::~ConnectionToHost() { 39 CloseChannels(); 40 41 if (session_.get()) 42 session_.reset(); 43 44 if (session_manager_.get()) 45 session_manager_.reset(); 46 47 if (signal_strategy_) 48 signal_strategy_->RemoveListener(this); 49} 50 51void ConnectionToHost::Connect(SignalStrategy* signal_strategy, 52 scoped_ptr<TransportFactory> transport_factory, 53 scoped_ptr<Authenticator> authenticator, 54 const std::string& host_jid, 55 HostEventCallback* event_callback) { 56 DCHECK(client_stub_); 57 DCHECK(clipboard_stub_); 58 DCHECK(monitored_video_stub_); 59 60 // Initialize default |candidate_config_| if set_candidate_config() wasn't 61 // called. 62 if (!candidate_config_) { 63 candidate_config_ = CandidateSessionConfig::CreateDefault(); 64 if (!audio_stub_) { 65 candidate_config_->DisableAudioChannel(); 66 } 67 candidate_config_->EnableVideoCodec(ChannelConfig::CODEC_VP9); 68 } 69 70 signal_strategy_ = signal_strategy; 71 event_callback_ = event_callback; 72 authenticator_ = authenticator.Pass(); 73 74 // Save jid of the host. The actual connection is created later after 75 // |signal_strategy_| is connected. 76 host_jid_ = host_jid; 77 78 signal_strategy_->AddListener(this); 79 signal_strategy_->Connect(); 80 81 session_manager_.reset(new JingleSessionManager(transport_factory.Pass())); 82 session_manager_->Init(signal_strategy_, this); 83 84 SetState(CONNECTING, OK); 85} 86 87void ConnectionToHost::set_candidate_config( 88 scoped_ptr<CandidateSessionConfig> config) { 89 DCHECK_EQ(state_, INITIALIZING); 90 91 candidate_config_ = config.Pass(); 92} 93 94 95const SessionConfig& ConnectionToHost::config() { 96 return session_->config(); 97} 98 99ClipboardStub* ConnectionToHost::clipboard_forwarder() { 100 return &clipboard_forwarder_; 101} 102 103HostStub* ConnectionToHost::host_stub() { 104 // TODO(wez): Add a HostFilter class, equivalent to input filter. 105 return control_dispatcher_.get(); 106} 107 108InputStub* ConnectionToHost::input_stub() { 109 return &event_forwarder_; 110} 111 112void ConnectionToHost::set_client_stub(ClientStub* client_stub) { 113 client_stub_ = client_stub; 114} 115 116void ConnectionToHost::set_clipboard_stub(ClipboardStub* clipboard_stub) { 117 clipboard_stub_ = clipboard_stub; 118} 119 120void ConnectionToHost::set_video_stub(VideoStub* video_stub) { 121 DCHECK(video_stub); 122 monitored_video_stub_.reset(new MonitoredVideoStub( 123 video_stub, 124 base::TimeDelta::FromSeconds( 125 MonitoredVideoStub::kConnectivityCheckDelaySeconds), 126 base::Bind(&ConnectionToHost::OnVideoChannelStatus, 127 base::Unretained(this)))); 128} 129 130void ConnectionToHost::set_audio_stub(AudioStub* audio_stub) { 131 audio_stub_ = audio_stub; 132} 133 134void ConnectionToHost::OnSignalStrategyStateChange( 135 SignalStrategy::State state) { 136 DCHECK(CalledOnValidThread()); 137 DCHECK(event_callback_); 138 139 if (state == SignalStrategy::CONNECTED) { 140 VLOG(1) << "Connected as: " << signal_strategy_->GetLocalJid(); 141 } else if (state == SignalStrategy::DISCONNECTED) { 142 VLOG(1) << "Connection closed."; 143 CloseOnError(SIGNALING_ERROR); 144 } 145} 146 147bool ConnectionToHost::OnSignalStrategyIncomingStanza( 148 const buzz::XmlElement* stanza) { 149 return false; 150} 151 152void ConnectionToHost::OnSessionManagerReady() { 153 DCHECK(CalledOnValidThread()); 154 155 // After SessionManager is initialized we can try to connect to the host. 156 session_ = session_manager_->Connect( 157 host_jid_, authenticator_.Pass(), candidate_config_.Pass()); 158 session_->SetEventHandler(this); 159} 160 161void ConnectionToHost::OnIncomingSession( 162 Session* session, 163 SessionManager::IncomingSessionResponse* response) { 164 DCHECK(CalledOnValidThread()); 165 // Client always rejects incoming sessions. 166 *response = SessionManager::DECLINE; 167} 168 169void ConnectionToHost::OnSessionStateChange( 170 Session::State state) { 171 DCHECK(CalledOnValidThread()); 172 DCHECK(event_callback_); 173 174 switch (state) { 175 case Session::INITIALIZING: 176 case Session::CONNECTING: 177 case Session::ACCEPTING: 178 case Session::CONNECTED: 179 case Session::AUTHENTICATING: 180 // Don't care about these events. 181 break; 182 183 case Session::AUTHENTICATED: 184 SetState(AUTHENTICATED, OK); 185 186 control_dispatcher_.reset(new ClientControlDispatcher()); 187 control_dispatcher_->Init( 188 session_.get(), session_->config().control_config(), 189 base::Bind(&ConnectionToHost::OnChannelInitialized, 190 base::Unretained(this))); 191 control_dispatcher_->set_client_stub(client_stub_); 192 control_dispatcher_->set_clipboard_stub(clipboard_stub_); 193 194 event_dispatcher_.reset(new ClientEventDispatcher()); 195 event_dispatcher_->Init( 196 session_.get(), session_->config().event_config(), 197 base::Bind(&ConnectionToHost::OnChannelInitialized, 198 base::Unretained(this))); 199 200 video_dispatcher_.reset( 201 new ClientVideoDispatcher(monitored_video_stub_.get())); 202 video_dispatcher_->Init(session_.get(), session_->config().video_config(), 203 base::Bind(&ConnectionToHost::OnChannelInitialized, 204 base::Unretained(this))); 205 206 audio_reader_ = AudioReader::Create(session_->config()); 207 if (audio_reader_.get()) { 208 audio_reader_->Init(session_.get(), session_->config().audio_config(), 209 base::Bind(&ConnectionToHost::OnChannelInitialized, 210 base::Unretained(this))); 211 audio_reader_->set_audio_stub(audio_stub_); 212 } 213 break; 214 215 case Session::CLOSED: 216 CloseChannels(); 217 SetState(CLOSED, OK); 218 break; 219 220 case Session::FAILED: 221 // If we were connected then treat signaling timeout error as if 222 // the connection was closed by the peer. 223 // 224 // TODO(sergeyu): This logic belongs to the webapp, but we 225 // currently don't expose this error code to the webapp, and it 226 // would be hard to add it because client plugin and webapp 227 // versions may not be in sync. It should be easy to do after we 228 // are finished moving the client plugin to NaCl. 229 if (state_ == CONNECTED && session_->error() == SIGNALING_TIMEOUT) { 230 CloseChannels(); 231 SetState(CLOSED, OK); 232 } else { 233 CloseOnError(session_->error()); 234 } 235 break; 236 } 237} 238 239void ConnectionToHost::OnSessionRouteChange(const std::string& channel_name, 240 const TransportRoute& route) { 241 event_callback_->OnRouteChanged(channel_name, route); 242} 243 244void ConnectionToHost::OnVideoChannelStatus(bool active) { 245 event_callback_->OnConnectionReady(active); 246} 247 248ConnectionToHost::State ConnectionToHost::state() const { 249 return state_; 250} 251 252void ConnectionToHost::OnChannelInitialized(bool successful) { 253 if (!successful) { 254 LOG(ERROR) << "Failed to connect video channel"; 255 CloseOnError(CHANNEL_CONNECTION_ERROR); 256 return; 257 } 258 259 NotifyIfChannelsReady(); 260} 261 262void ConnectionToHost::NotifyIfChannelsReady() { 263 if (!control_dispatcher_.get() || !control_dispatcher_->is_connected()) 264 return; 265 if (!event_dispatcher_.get() || !event_dispatcher_->is_connected()) 266 return; 267 if (!video_dispatcher_.get() || !video_dispatcher_->is_connected()) 268 return; 269 if ((!audio_reader_.get() || !audio_reader_->is_connected()) && 270 session_->config().is_audio_enabled()) { 271 return; 272 } 273 if (state_ != AUTHENTICATED) 274 return; 275 276 // Start forwarding clipboard and input events. 277 clipboard_forwarder_.set_clipboard_stub(control_dispatcher_.get()); 278 event_forwarder_.set_input_stub(event_dispatcher_.get()); 279 SetState(CONNECTED, OK); 280} 281 282void ConnectionToHost::CloseOnError(ErrorCode error) { 283 CloseChannels(); 284 SetState(FAILED, error); 285} 286 287void ConnectionToHost::CloseChannels() { 288 control_dispatcher_.reset(); 289 event_dispatcher_.reset(); 290 clipboard_forwarder_.set_clipboard_stub(NULL); 291 event_forwarder_.set_input_stub(NULL); 292 video_dispatcher_.reset(); 293 audio_reader_.reset(); 294} 295 296void ConnectionToHost::SetState(State state, ErrorCode error) { 297 DCHECK(CalledOnValidThread()); 298 // |error| should be specified only when |state| is set to FAILED. 299 DCHECK(state == FAILED || error == OK); 300 301 if (state != state_) { 302 state_ = state; 303 error_ = error; 304 event_callback_->OnConnectionState(state_, error_); 305 } 306} 307 308} // namespace protocol 309} // namespace remoting 310