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