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