jingle_session.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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/jingle_session.h"
6
7#include "base/bind.h"
8#include "base/rand_util.h"
9#include "base/stl_util.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/time/time.h"
12#include "remoting/base/constants.h"
13#include "remoting/jingle_glue/iq_sender.h"
14#include "remoting/protocol/authenticator.h"
15#include "remoting/protocol/channel_authenticator.h"
16#include "remoting/protocol/channel_multiplexer.h"
17#include "remoting/protocol/content_description.h"
18#include "remoting/protocol/jingle_messages.h"
19#include "remoting/protocol/jingle_session_manager.h"
20#include "remoting/protocol/session_config.h"
21#include "third_party/libjingle/source/talk/p2p/base/candidate.h"
22#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
23
24using buzz::XmlElement;
25
26namespace remoting {
27namespace protocol {
28
29namespace {
30// Delay after candidate creation before sending transport-info
31// message. This is neccessary to be able to pack multiple candidates
32// into one transport-info messages. The value needs to be greater
33// than zero because ports are opened asynchronously in the browser
34// process.
35const int kTransportInfoSendDelayMs = 2;
36
37// How long we should wait for a response from the other end. This value is used
38// for all requests except |transport-info|.
39const int kDefaultMessageTimeout = 10;
40
41// Timeout for the transport-info messages.
42const int kTransportInfoTimeout = 10 * 60;
43
44// Name of the multiplexed channel.
45const char kMuxChannelName[] = "mux";
46
47ErrorCode AuthRejectionReasonToErrorCode(
48    Authenticator::RejectionReason reason) {
49  switch (reason) {
50    case Authenticator::INVALID_CREDENTIALS:
51      return AUTHENTICATION_FAILED;
52    case Authenticator::PROTOCOL_ERROR:
53      return INCOMPATIBLE_PROTOCOL;
54  }
55  NOTREACHED();
56  return UNKNOWN_ERROR;
57}
58
59}  // namespace
60
61JingleSession::JingleSession(JingleSessionManager* session_manager)
62    : session_manager_(session_manager),
63      event_handler_(NULL),
64      state_(INITIALIZING),
65      error_(OK),
66      config_is_set_(false) {
67}
68
69JingleSession::~JingleSession() {
70  channel_multiplexer_.reset();
71  STLDeleteContainerPointers(pending_requests_.begin(),
72                             pending_requests_.end());
73  STLDeleteContainerPointers(transport_info_requests_.begin(),
74                             transport_info_requests_.end());
75  STLDeleteContainerPairSecondPointers(channels_.begin(), channels_.end());
76  session_manager_->SessionDestroyed(this);
77}
78
79void JingleSession::SetEventHandler(Session::EventHandler* event_handler) {
80  DCHECK(CalledOnValidThread());
81  DCHECK(event_handler);
82  event_handler_ = event_handler;
83}
84
85ErrorCode JingleSession::error() {
86  DCHECK(CalledOnValidThread());
87  return error_;
88}
89
90void JingleSession::StartConnection(
91    const std::string& peer_jid,
92    scoped_ptr<Authenticator> authenticator,
93    scoped_ptr<CandidateSessionConfig> config) {
94  DCHECK(CalledOnValidThread());
95  DCHECK(authenticator.get());
96  DCHECK_EQ(authenticator->state(), Authenticator::MESSAGE_READY);
97
98  peer_jid_ = peer_jid;
99  authenticator_ = authenticator.Pass();
100  candidate_config_ = config.Pass();
101
102  // Generate random session ID. There are usually not more than 1
103  // concurrent session per host, so a random 64-bit integer provides
104  // enough entropy. In the worst case connection will fail when two
105  // clients generate the same session ID concurrently.
106  session_id_ = base::Int64ToString(base::RandGenerator(kint64max));
107
108  // Send session-initiate message.
109  JingleMessage message(peer_jid_, JingleMessage::SESSION_INITIATE,
110                        session_id_);
111  message.initiator = session_manager_->signal_strategy_->GetLocalJid();
112  message.description.reset(
113      new ContentDescription(candidate_config_->Clone(),
114                             authenticator_->GetNextMessage()));
115  SendMessage(message);
116
117  SetState(CONNECTING);
118}
119
120void JingleSession::InitializeIncomingConnection(
121    const JingleMessage& initiate_message,
122    scoped_ptr<Authenticator> authenticator) {
123  DCHECK(CalledOnValidThread());
124  DCHECK(initiate_message.description.get());
125  DCHECK(authenticator.get());
126  DCHECK_EQ(authenticator->state(), Authenticator::WAITING_MESSAGE);
127
128  peer_jid_ = initiate_message.from;
129  authenticator_ = authenticator.Pass();
130  session_id_ = initiate_message.sid;
131  candidate_config_ = initiate_message.description->config()->Clone();
132
133  SetState(ACCEPTING);
134}
135
136void JingleSession::AcceptIncomingConnection(
137    const JingleMessage& initiate_message) {
138  DCHECK(config_is_set_);
139
140  // Process the first authentication message.
141  const buzz::XmlElement* first_auth_message =
142      initiate_message.description->authenticator_message();
143
144  if (!first_auth_message) {
145    CloseInternal(INCOMPATIBLE_PROTOCOL);
146    return;
147  }
148
149  DCHECK_EQ(authenticator_->state(), Authenticator::WAITING_MESSAGE);
150  // |authenticator_| is owned, so Unretained() is safe here.
151  authenticator_->ProcessMessage(first_auth_message, base::Bind(
152      &JingleSession::ContinueAcceptIncomingConnection,
153      base::Unretained(this)));
154}
155
156void JingleSession::ContinueAcceptIncomingConnection() {
157  DCHECK_NE(authenticator_->state(), Authenticator::PROCESSING_MESSAGE);
158  if (authenticator_->state() == Authenticator::REJECTED) {
159    CloseInternal(AuthRejectionReasonToErrorCode(
160        authenticator_->rejection_reason()));
161    return;
162  }
163
164  // Send the session-accept message.
165  JingleMessage message(peer_jid_, JingleMessage::SESSION_ACCEPT,
166                        session_id_);
167
168  scoped_ptr<buzz::XmlElement> auth_message;
169  if (authenticator_->state() == Authenticator::MESSAGE_READY)
170    auth_message = authenticator_->GetNextMessage();
171
172  message.description.reset(
173      new ContentDescription(CandidateSessionConfig::CreateFrom(config_),
174                             auth_message.Pass()));
175  SendMessage(message);
176
177  // Update state.
178  SetState(CONNECTED);
179
180  if (authenticator_->state() == Authenticator::ACCEPTED) {
181    SetState(AUTHENTICATED);
182  } else {
183    DCHECK_EQ(authenticator_->state(), Authenticator::WAITING_MESSAGE);
184  }
185
186  return;
187}
188
189const std::string& JingleSession::jid() {
190  DCHECK(CalledOnValidThread());
191  return peer_jid_;
192}
193
194const CandidateSessionConfig* JingleSession::candidate_config() {
195  DCHECK(CalledOnValidThread());
196  return candidate_config_.get();
197}
198
199const SessionConfig& JingleSession::config() {
200  DCHECK(CalledOnValidThread());
201  return config_;
202}
203
204void JingleSession::set_config(const SessionConfig& config) {
205  DCHECK(CalledOnValidThread());
206  DCHECK(!config_is_set_);
207  config_ = config;
208  config_is_set_ = true;
209}
210
211ChannelFactory* JingleSession::GetTransportChannelFactory() {
212  DCHECK(CalledOnValidThread());
213  return this;
214}
215
216ChannelFactory* JingleSession::GetMultiplexedChannelFactory() {
217  DCHECK(CalledOnValidThread());
218  if (!channel_multiplexer_.get())
219    channel_multiplexer_.reset(new ChannelMultiplexer(this, kMuxChannelName));
220  return channel_multiplexer_.get();
221}
222
223void JingleSession::Close() {
224  DCHECK(CalledOnValidThread());
225
226  CloseInternal(OK);
227}
228
229void JingleSession::CreateStreamChannel(
230      const std::string& name,
231      const StreamChannelCallback& callback) {
232  DCHECK(!channels_[name]);
233
234  scoped_ptr<ChannelAuthenticator> channel_authenticator =
235      authenticator_->CreateChannelAuthenticator();
236  scoped_ptr<StreamTransport> channel =
237      session_manager_->transport_factory_->CreateStreamTransport();
238  channel->Initialize(name, this, channel_authenticator.Pass());
239  channel->Connect(callback);
240  channels_[name] = channel.release();
241}
242
243void JingleSession::CreateDatagramChannel(
244    const std::string& name,
245    const DatagramChannelCallback& callback) {
246  DCHECK(!channels_[name]);
247
248  scoped_ptr<ChannelAuthenticator> channel_authenticator =
249      authenticator_->CreateChannelAuthenticator();
250  scoped_ptr<DatagramTransport> channel =
251      session_manager_->transport_factory_->CreateDatagramTransport();
252  channel->Initialize(name, this, channel_authenticator.Pass());
253  channel->Connect(callback);
254  channels_[name] = channel.release();
255}
256
257void JingleSession::CancelChannelCreation(const std::string& name) {
258  ChannelsMap::iterator it = channels_.find(name);
259  if (it != channels_.end() && !it->second->is_connected()) {
260    delete it->second;
261    DCHECK(!channels_[name]);
262  }
263}
264
265void JingleSession::OnTransportCandidate(Transport* transport,
266                                         const cricket::Candidate& candidate) {
267  pending_candidates_.push_back(JingleMessage::NamedCandidate(
268      transport->name(), candidate));
269
270  if (!transport_infos_timer_.IsRunning()) {
271    // Delay sending the new candidates in case we get more candidates
272    // that we can send in one message.
273    transport_infos_timer_.Start(
274        FROM_HERE, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs),
275        this, &JingleSession::SendTransportInfo);
276  }
277}
278
279void JingleSession::OnTransportRouteChange(Transport* transport,
280                                           const TransportRoute& route) {
281  if (event_handler_)
282    event_handler_->OnSessionRouteChange(transport->name(), route);
283}
284
285void JingleSession::OnTransportReady(Transport* transport, bool ready) {
286  if (event_handler_)
287    event_handler_->OnSessionChannelReady(transport->name(), ready);
288}
289
290void JingleSession::OnTransportFailed(Transport* transport) {
291  CloseInternal(CHANNEL_CONNECTION_ERROR);
292}
293
294void JingleSession::OnTransportDeleted(Transport* transport) {
295  ChannelsMap::iterator it = channels_.find(transport->name());
296  DCHECK_EQ(it->second, transport);
297  channels_.erase(it);
298}
299
300void JingleSession::SendMessage(const JingleMessage& message) {
301  scoped_ptr<IqRequest> request = session_manager_->iq_sender()->SendIq(
302      message.ToXml(),
303      base::Bind(&JingleSession::OnMessageResponse,
304                 base::Unretained(this), message.action));
305  if (request) {
306    request->SetTimeout(base::TimeDelta::FromSeconds(kDefaultMessageTimeout));
307    pending_requests_.insert(request.release());
308  } else {
309    LOG(ERROR) << "Failed to send a "
310               << JingleMessage::GetActionName(message.action) << " message";
311  }
312}
313
314void JingleSession::OnMessageResponse(
315    JingleMessage::ActionType request_type,
316    IqRequest* request,
317    const buzz::XmlElement* response) {
318  std::string type_str = JingleMessage::GetActionName(request_type);
319
320  // Delete the request from the list of pending requests.
321  pending_requests_.erase(request);
322  delete request;
323
324  // |response| will be NULL if the request timed out.
325  if (!response) {
326    LOG(ERROR) << type_str << " request timed out.";
327    CloseInternal(SIGNALING_TIMEOUT);
328    return;
329  } else {
330    const std::string& type =
331        response->Attr(buzz::QName(std::string(), "type"));
332    if (type != "result") {
333      LOG(ERROR) << "Received error in response to " << type_str
334                 << " message: \"" << response->Str()
335                 << "\". Terminating the session.";
336
337      switch (request_type) {
338        case JingleMessage::SESSION_INFO:
339          // session-info is used for the new authentication protocol,
340          // and wasn't previously supported.
341          CloseInternal(INCOMPATIBLE_PROTOCOL);
342          break;
343
344        default:
345          // TODO(sergeyu): There may be different reasons for error
346          // here. Parse the response stanza to find failure reason.
347          CloseInternal(PEER_IS_OFFLINE);
348      }
349    }
350  }
351}
352
353void JingleSession::SendTransportInfo() {
354  JingleMessage message(peer_jid_, JingleMessage::TRANSPORT_INFO, session_id_);
355  message.candidates.swap(pending_candidates_);
356
357  scoped_ptr<IqRequest> request = session_manager_->iq_sender()->SendIq(
358      message.ToXml(),
359      base::Bind(&JingleSession::OnTransportInfoResponse,
360                 base::Unretained(this)));
361  if (request) {
362    request->SetTimeout(base::TimeDelta::FromSeconds(kTransportInfoTimeout));
363    transport_info_requests_.push_back(request.release());
364  } else {
365    LOG(ERROR) << "Failed to send a transport-info message";
366  }
367}
368
369void JingleSession::OnTransportInfoResponse(IqRequest* request,
370                                            const buzz::XmlElement* response) {
371  DCHECK(!transport_info_requests_.empty());
372
373  // Consider transport-info requests sent before this one lost and delete
374  // corresponding IqRequest objects.
375  while (transport_info_requests_.front() != request) {
376    delete transport_info_requests_.front();
377    transport_info_requests_.pop_front();
378  }
379
380  // Delete the |request| itself.
381  DCHECK_EQ(request, transport_info_requests_.front());
382  delete request;
383  transport_info_requests_.pop_front();
384
385  // Ignore transport-info timeouts.
386  if (!response) {
387    LOG(ERROR) << "transport-info request has timed out.";
388    return;
389  }
390
391  const std::string& type = response->Attr(buzz::QName(std::string(), "type"));
392  if (type != "result") {
393    LOG(ERROR) << "Received error in response to transport-info message: \""
394               << response->Str() << "\". Terminating the session.";
395    CloseInternal(PEER_IS_OFFLINE);
396  }
397}
398
399void JingleSession::OnIncomingMessage(const JingleMessage& message,
400                                      const ReplyCallback& reply_callback) {
401  DCHECK(CalledOnValidThread());
402
403  if (message.from != peer_jid_) {
404    // Ignore messages received from a different Jid.
405    reply_callback.Run(JingleMessageReply::INVALID_SID);
406    return;
407  }
408
409  switch (message.action) {
410    case JingleMessage::SESSION_ACCEPT:
411      OnAccept(message, reply_callback);
412      break;
413
414    case JingleMessage::SESSION_INFO:
415      OnSessionInfo(message, reply_callback);
416      break;
417
418    case JingleMessage::TRANSPORT_INFO:
419      reply_callback.Run(JingleMessageReply::NONE);
420      ProcessTransportInfo(message);
421      break;
422
423    case JingleMessage::SESSION_TERMINATE:
424      OnTerminate(message, reply_callback);
425      break;
426
427    default:
428      reply_callback.Run(JingleMessageReply::UNEXPECTED_REQUEST);
429  }
430}
431
432void JingleSession::OnAccept(const JingleMessage& message,
433                             const ReplyCallback& reply_callback) {
434  if (state_ != CONNECTING) {
435    reply_callback.Run(JingleMessageReply::UNEXPECTED_REQUEST);
436    return;
437  }
438
439  reply_callback.Run(JingleMessageReply::NONE);
440
441  const buzz::XmlElement* auth_message =
442      message.description->authenticator_message();
443  if (!auth_message) {
444    DLOG(WARNING) << "Received session-accept without authentication message ";
445    CloseInternal(INCOMPATIBLE_PROTOCOL);
446    return;
447  }
448
449  if (!InitializeConfigFromDescription(message.description.get())) {
450    CloseInternal(INCOMPATIBLE_PROTOCOL);
451    return;
452  }
453
454  // In case there is transport information in the accept message.
455  ProcessTransportInfo(message);
456
457  SetState(CONNECTED);
458
459  DCHECK(authenticator_->state() == Authenticator::WAITING_MESSAGE);
460  authenticator_->ProcessMessage(auth_message, base::Bind(
461      &JingleSession::ProcessAuthenticationStep,base::Unretained(this)));
462}
463
464void JingleSession::OnSessionInfo(const JingleMessage& message,
465                                  const ReplyCallback& reply_callback) {
466  if (!message.info.get() ||
467      !Authenticator::IsAuthenticatorMessage(message.info.get())) {
468    reply_callback.Run(JingleMessageReply::UNSUPPORTED_INFO);
469    return;
470  }
471
472  if (state_ != CONNECTED ||
473      authenticator_->state() != Authenticator::WAITING_MESSAGE) {
474    LOG(WARNING) << "Received unexpected authenticator message "
475                 << message.info->Str();
476    reply_callback.Run(JingleMessageReply::UNEXPECTED_REQUEST);
477    CloseInternal(INCOMPATIBLE_PROTOCOL);
478    return;
479  }
480
481  reply_callback.Run(JingleMessageReply::NONE);
482
483  authenticator_->ProcessMessage(message.info.get(), base::Bind(
484      &JingleSession::ProcessAuthenticationStep, base::Unretained(this)));
485}
486
487void JingleSession::ProcessTransportInfo(const JingleMessage& message) {
488  for (std::list<JingleMessage::NamedCandidate>::const_iterator it =
489           message.candidates.begin();
490       it != message.candidates.end(); ++it) {
491    ChannelsMap::iterator channel = channels_.find(it->name);
492    if (channel == channels_.end()) {
493      LOG(WARNING) << "Received candidate for unknown channel " << it->name;
494      continue;
495    }
496    channel->second->AddRemoteCandidate(it->candidate);
497  }
498}
499
500void JingleSession::OnTerminate(const JingleMessage& message,
501                                const ReplyCallback& reply_callback) {
502  if (state_ != CONNECTING && state_ != ACCEPTING && state_ != CONNECTED &&
503      state_ != AUTHENTICATED) {
504    LOG(WARNING) << "Received unexpected session-terminate message.";
505    reply_callback.Run(JingleMessageReply::UNEXPECTED_REQUEST);
506    return;
507  }
508
509  reply_callback.Run(JingleMessageReply::NONE);
510
511  switch (message.reason) {
512    case JingleMessage::SUCCESS:
513      if (state_ == CONNECTING) {
514        error_ = SESSION_REJECTED;
515      } else {
516        error_ = OK;
517      }
518      break;
519    case JingleMessage::DECLINE:
520      error_ = AUTHENTICATION_FAILED;
521      break;
522    case JingleMessage::CANCEL:
523      error_ = HOST_OVERLOAD;
524      break;
525    case JingleMessage::GENERAL_ERROR:
526      error_ = CHANNEL_CONNECTION_ERROR;
527      break;
528    case JingleMessage::INCOMPATIBLE_PARAMETERS:
529      error_ = INCOMPATIBLE_PROTOCOL;
530      break;
531    default:
532      error_ = UNKNOWN_ERROR;
533  }
534
535  if (error_ != OK) {
536    SetState(FAILED);
537  } else {
538    SetState(CLOSED);
539  }
540}
541
542bool JingleSession::InitializeConfigFromDescription(
543    const ContentDescription* description) {
544  DCHECK(description);
545
546  if (!description->config()->GetFinalConfig(&config_)) {
547    LOG(ERROR) << "session-accept does not specify configuration";
548    return false;
549  }
550  if (!candidate_config()->IsSupported(config_)) {
551    LOG(ERROR) << "session-accept specifies an invalid configuration";
552    return false;
553  }
554
555  return true;
556}
557
558void JingleSession::ProcessAuthenticationStep() {
559  DCHECK(CalledOnValidThread());
560  DCHECK_NE(authenticator_->state(), Authenticator::PROCESSING_MESSAGE);
561
562  if (state_ != CONNECTED) {
563    DCHECK(state_ == FAILED || state_ == CLOSED);
564    // The remote host closed the connection while the authentication was being
565    // processed asynchronously, nothing to do.
566    return;
567  }
568
569  if (authenticator_->state() == Authenticator::MESSAGE_READY) {
570    JingleMessage message(peer_jid_, JingleMessage::SESSION_INFO, session_id_);
571    message.info = authenticator_->GetNextMessage();
572    DCHECK(message.info.get());
573    SendMessage(message);
574  }
575  DCHECK_NE(authenticator_->state(), Authenticator::MESSAGE_READY);
576
577  if (authenticator_->state() == Authenticator::ACCEPTED) {
578    SetState(AUTHENTICATED);
579  } else if (authenticator_->state() == Authenticator::REJECTED) {
580    CloseInternal(AuthRejectionReasonToErrorCode(
581        authenticator_->rejection_reason()));
582  }
583}
584
585void JingleSession::CloseInternal(ErrorCode error) {
586  DCHECK(CalledOnValidThread());
587
588  if (state_ == CONNECTING || state_ == ACCEPTING || state_ == CONNECTED ||
589      state_ == AUTHENTICATED) {
590    // Send session-terminate message with the appropriate error code.
591    JingleMessage::Reason reason;
592    switch (error) {
593      case OK:
594        reason = JingleMessage::SUCCESS;
595        break;
596      case SESSION_REJECTED:
597      case AUTHENTICATION_FAILED:
598        reason = JingleMessage::DECLINE;
599        break;
600      case INCOMPATIBLE_PROTOCOL:
601        reason = JingleMessage::INCOMPATIBLE_PARAMETERS;
602        break;
603      case HOST_OVERLOAD:
604        reason = JingleMessage::CANCEL;
605        break;
606      default:
607        reason = JingleMessage::GENERAL_ERROR;
608    }
609
610    JingleMessage message(peer_jid_, JingleMessage::SESSION_TERMINATE,
611                          session_id_);
612    message.reason = reason;
613    SendMessage(message);
614  }
615
616  error_ = error;
617
618  if (state_ != FAILED && state_ != CLOSED) {
619    if (error != OK) {
620      SetState(FAILED);
621    } else {
622      SetState(CLOSED);
623    }
624  }
625}
626
627void JingleSession::SetState(State new_state) {
628  DCHECK(CalledOnValidThread());
629
630  if (new_state != state_) {
631    DCHECK_NE(state_, CLOSED);
632    DCHECK_NE(state_, FAILED);
633
634    state_ = new_state;
635    if (event_handler_)
636      event_handler_->OnSessionStateChange(new_state);
637  }
638}
639
640}  // namespace protocol
641}  // namespace remoting
642