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.
5 #include "remoting/protocol/jingle_session.h"
8 #include "base/rand_util.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/stl_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "base/time/time.h"
14 #include "remoting/base/constants.h"
15 #include "remoting/protocol/authenticator.h"
16 #include "remoting/protocol/channel_authenticator.h"
17 #include "remoting/protocol/channel_multiplexer.h"
18 #include "remoting/protocol/content_description.h"
19 #include "remoting/protocol/jingle_messages.h"
20 #include "remoting/protocol/jingle_session_manager.h"
21 #include "remoting/protocol/pseudotcp_channel_factory.h"
22 #include "remoting/protocol/secure_channel_factory.h"
23 #include "remoting/protocol/session_config.h"
24 #include "remoting/protocol/stream_channel_factory.h"
25 #include "remoting/signaling/iq_sender.h"
26 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
27 #include "third_party/webrtc/p2p/base/candidate.h"
29 using buzz::XmlElement
;
35 // Delay after candidate creation before sending transport-info
36 // message. This is neccessary to be able to pack multiple candidates
37 // into one transport-info messages. The value needs to be greater
38 // than zero because ports are opened asynchronously in the browser
40 const int kTransportInfoSendDelayMs
= 2;
42 // How long we should wait for a response from the other end. This value is used
43 // for all requests except |transport-info|.
44 const int kDefaultMessageTimeout
= 10;
46 // During a reconnection, it usually takes longer for the peer to respond due to
47 // pending messages in the channel from the previous session. From experiment,
48 // it can take up to 20s for the session to reconnect. To make it safe, setting
49 // the timeout to 30s.
50 const int kSessionInitiateAndAcceptTimeout
= kDefaultMessageTimeout
* 3;
52 // Timeout for the transport-info messages.
53 const int kTransportInfoTimeout
= 10 * 60;
55 // Name of the multiplexed channel.
56 const char kMuxChannelName
[] = "mux";
58 ErrorCode
AuthRejectionReasonToErrorCode(
59 Authenticator::RejectionReason reason
) {
61 case Authenticator::INVALID_CREDENTIALS
:
62 return AUTHENTICATION_FAILED
;
63 case Authenticator::PROTOCOL_ERROR
:
64 return INCOMPATIBLE_PROTOCOL
;
72 JingleSession::JingleSession(JingleSessionManager
* session_manager
)
73 : session_manager_(session_manager
),
74 event_handler_(nullptr),
77 config_is_set_(false),
81 JingleSession::~JingleSession() {
82 channel_multiplexer_
.reset();
83 STLDeleteContainerPointers(pending_requests_
.begin(),
84 pending_requests_
.end());
85 STLDeleteContainerPointers(transport_info_requests_
.begin(),
86 transport_info_requests_
.end());
88 DCHECK(channels_
.empty());
90 session_manager_
->SessionDestroyed(this);
93 void JingleSession::SetEventHandler(Session::EventHandler
* event_handler
) {
94 DCHECK(CalledOnValidThread());
95 DCHECK(event_handler
);
96 event_handler_
= event_handler
;
99 ErrorCode
JingleSession::error() {
100 DCHECK(CalledOnValidThread());
104 void JingleSession::StartConnection(
105 const std::string
& peer_jid
,
106 scoped_ptr
<Authenticator
> authenticator
,
107 scoped_ptr
<CandidateSessionConfig
> config
) {
108 DCHECK(CalledOnValidThread());
109 DCHECK(authenticator
.get());
110 DCHECK_EQ(authenticator
->state(), Authenticator::MESSAGE_READY
);
112 peer_jid_
= peer_jid
;
113 authenticator_
= authenticator
.Pass();
114 candidate_config_
= config
.Pass();
116 // Generate random session ID. There are usually not more than 1
117 // concurrent session per host, so a random 64-bit integer provides
118 // enough entropy. In the worst case connection will fail when two
119 // clients generate the same session ID concurrently.
120 session_id_
= base::Int64ToString(base::RandGenerator(kint64max
));
122 // Send session-initiate message.
123 JingleMessage
message(peer_jid_
, JingleMessage::SESSION_INITIATE
,
125 message
.initiator
= session_manager_
->signal_strategy_
->GetLocalJid();
126 message
.description
.reset(
127 new ContentDescription(candidate_config_
->Clone(),
128 authenticator_
->GetNextMessage()));
129 SendMessage(message
);
131 SetState(CONNECTING
);
134 void JingleSession::InitializeIncomingConnection(
135 const JingleMessage
& initiate_message
,
136 scoped_ptr
<Authenticator
> authenticator
) {
137 DCHECK(CalledOnValidThread());
138 DCHECK(initiate_message
.description
.get());
139 DCHECK(authenticator
.get());
140 DCHECK_EQ(authenticator
->state(), Authenticator::WAITING_MESSAGE
);
142 peer_jid_
= initiate_message
.from
;
143 authenticator_
= authenticator
.Pass();
144 session_id_
= initiate_message
.sid
;
145 candidate_config_
= initiate_message
.description
->config()->Clone();
150 void JingleSession::AcceptIncomingConnection(
151 const JingleMessage
& initiate_message
) {
152 DCHECK(config_is_set_
);
154 // Process the first authentication message.
155 const buzz::XmlElement
* first_auth_message
=
156 initiate_message
.description
->authenticator_message();
158 if (!first_auth_message
) {
159 CloseInternal(INCOMPATIBLE_PROTOCOL
);
163 DCHECK_EQ(authenticator_
->state(), Authenticator::WAITING_MESSAGE
);
164 // |authenticator_| is owned, so Unretained() is safe here.
165 authenticator_
->ProcessMessage(first_auth_message
, base::Bind(
166 &JingleSession::ContinueAcceptIncomingConnection
,
167 base::Unretained(this)));
170 void JingleSession::ContinueAcceptIncomingConnection() {
171 DCHECK_NE(authenticator_
->state(), Authenticator::PROCESSING_MESSAGE
);
172 if (authenticator_
->state() == Authenticator::REJECTED
) {
173 CloseInternal(AuthRejectionReasonToErrorCode(
174 authenticator_
->rejection_reason()));
178 // Send the session-accept message.
179 JingleMessage
message(peer_jid_
, JingleMessage::SESSION_ACCEPT
,
182 scoped_ptr
<buzz::XmlElement
> auth_message
;
183 if (authenticator_
->state() == Authenticator::MESSAGE_READY
)
184 auth_message
= authenticator_
->GetNextMessage();
186 message
.description
.reset(
187 new ContentDescription(CandidateSessionConfig::CreateFrom(config_
),
188 auth_message
.Pass()));
189 SendMessage(message
);
194 if (authenticator_
->state() == Authenticator::ACCEPTED
) {
197 DCHECK_EQ(authenticator_
->state(), Authenticator::WAITING_MESSAGE
);
198 if (authenticator_
->started()) {
199 SetState(AUTHENTICATING
);
204 const std::string
& JingleSession::jid() {
205 DCHECK(CalledOnValidThread());
209 const CandidateSessionConfig
* JingleSession::candidate_config() {
210 DCHECK(CalledOnValidThread());
211 return candidate_config_
.get();
214 const SessionConfig
& JingleSession::config() {
215 DCHECK(CalledOnValidThread());
219 void JingleSession::set_config(const SessionConfig
& config
) {
220 DCHECK(CalledOnValidThread());
221 DCHECK(!config_is_set_
);
223 config_is_set_
= true;
226 StreamChannelFactory
* JingleSession::GetTransportChannelFactory() {
227 DCHECK(CalledOnValidThread());
228 return secure_channel_factory_
.get();
231 StreamChannelFactory
* JingleSession::GetMultiplexedChannelFactory() {
232 DCHECK(CalledOnValidThread());
233 if (!channel_multiplexer_
.get()) {
234 channel_multiplexer_
.reset(
235 new ChannelMultiplexer(GetTransportChannelFactory(), kMuxChannelName
));
237 return channel_multiplexer_
.get();
240 void JingleSession::Close() {
241 DCHECK(CalledOnValidThread());
246 void JingleSession::AddPendingRemoteCandidates(Transport
* channel
,
247 const std::string
& name
) {
248 std::list
<JingleMessage::NamedCandidate
>::iterator it
=
249 pending_remote_candidates_
.begin();
250 while(it
!= pending_remote_candidates_
.end()) {
251 if (it
->name
== name
) {
252 channel
->AddRemoteCandidate(it
->candidate
);
253 it
= pending_remote_candidates_
.erase(it
);
260 void JingleSession::CreateChannel(const std::string
& name
,
261 const ChannelCreatedCallback
& callback
) {
262 DCHECK(!channels_
[name
]);
264 scoped_ptr
<Transport
> channel
=
265 session_manager_
->transport_factory_
->CreateTransport();
266 channel
->Connect(name
, this, callback
);
267 AddPendingRemoteCandidates(channel
.get(), name
);
268 channels_
[name
] = channel
.release();
271 void JingleSession::CancelChannelCreation(const std::string
& name
) {
272 ChannelsMap::iterator it
= channels_
.find(name
);
273 if (it
!= channels_
.end()) {
274 DCHECK(!it
->second
->is_connected());
276 DCHECK(channels_
.find(name
) == channels_
.end());
280 void JingleSession::OnTransportCandidate(Transport
* transport
,
281 const cricket::Candidate
& candidate
) {
282 pending_candidates_
.push_back(JingleMessage::NamedCandidate(
283 transport
->name(), candidate
));
285 if (!transport_infos_timer_
.IsRunning()) {
286 // Delay sending the new candidates in case we get more candidates
287 // that we can send in one message.
288 transport_infos_timer_
.Start(
289 FROM_HERE
, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs
),
290 this, &JingleSession::SendTransportInfo
);
294 void JingleSession::OnTransportRouteChange(Transport
* transport
,
295 const TransportRoute
& route
) {
297 event_handler_
->OnSessionRouteChange(transport
->name(), route
);
300 void JingleSession::OnTransportFailed(Transport
* transport
) {
301 CloseInternal(CHANNEL_CONNECTION_ERROR
);
304 void JingleSession::OnTransportDeleted(Transport
* transport
) {
305 ChannelsMap::iterator it
= channels_
.find(transport
->name());
306 DCHECK_EQ(it
->second
, transport
);
310 void JingleSession::SendMessage(const JingleMessage
& message
) {
311 scoped_ptr
<IqRequest
> request
= session_manager_
->iq_sender()->SendIq(
313 base::Bind(&JingleSession::OnMessageResponse
,
314 base::Unretained(this),
317 int timeout
= kDefaultMessageTimeout
;
318 if (message
.action
== JingleMessage::SESSION_INITIATE
||
319 message
.action
== JingleMessage::SESSION_ACCEPT
) {
320 timeout
= kSessionInitiateAndAcceptTimeout
;
323 request
->SetTimeout(base::TimeDelta::FromSeconds(timeout
));
324 pending_requests_
.insert(request
.release());
326 LOG(ERROR
) << "Failed to send a "
327 << JingleMessage::GetActionName(message
.action
) << " message";
331 void JingleSession::OnMessageResponse(
332 JingleMessage::ActionType request_type
,
334 const buzz::XmlElement
* response
) {
335 // Delete the request from the list of pending requests.
336 pending_requests_
.erase(request
);
339 // Ignore all responses after session was closed.
340 if (state_
== CLOSED
|| state_
== FAILED
)
343 std::string type_str
= JingleMessage::GetActionName(request_type
);
345 // |response| will be nullptr if the request timed out.
347 LOG(ERROR
) << type_str
<< " request timed out.";
348 CloseInternal(SIGNALING_TIMEOUT
);
351 const std::string
& type
=
352 response
->Attr(buzz::QName(std::string(), "type"));
353 if (type
!= "result") {
354 LOG(ERROR
) << "Received error in response to " << type_str
355 << " message: \"" << response
->Str()
356 << "\". Terminating the session.";
358 // TODO(sergeyu): There may be different reasons for error
359 // here. Parse the response stanza to find failure reason.
360 CloseInternal(PEER_IS_OFFLINE
);
365 void JingleSession::SendTransportInfo() {
366 JingleMessage
message(peer_jid_
, JingleMessage::TRANSPORT_INFO
, session_id_
);
367 message
.candidates
.swap(pending_candidates_
);
369 scoped_ptr
<IqRequest
> request
= session_manager_
->iq_sender()->SendIq(
371 base::Bind(&JingleSession::OnTransportInfoResponse
,
372 base::Unretained(this)));
374 request
->SetTimeout(base::TimeDelta::FromSeconds(kTransportInfoTimeout
));
375 transport_info_requests_
.push_back(request
.release());
377 LOG(ERROR
) << "Failed to send a transport-info message";
381 void JingleSession::OnTransportInfoResponse(IqRequest
* request
,
382 const buzz::XmlElement
* response
) {
383 DCHECK(!transport_info_requests_
.empty());
385 // Consider transport-info requests sent before this one lost and delete
386 // corresponding IqRequest objects.
387 while (transport_info_requests_
.front() != request
) {
388 delete transport_info_requests_
.front();
389 transport_info_requests_
.pop_front();
392 // Delete the |request| itself.
393 DCHECK_EQ(request
, transport_info_requests_
.front());
395 transport_info_requests_
.pop_front();
397 // Ignore transport-info timeouts.
399 LOG(ERROR
) << "transport-info request has timed out.";
403 const std::string
& type
= response
->Attr(buzz::QName(std::string(), "type"));
404 if (type
!= "result") {
405 LOG(ERROR
) << "Received error in response to transport-info message: \""
406 << response
->Str() << "\". Terminating the session.";
407 CloseInternal(PEER_IS_OFFLINE
);
411 void JingleSession::OnIncomingMessage(const JingleMessage
& message
,
412 const ReplyCallback
& reply_callback
) {
413 DCHECK(CalledOnValidThread());
415 if (message
.from
!= peer_jid_
) {
416 // Ignore messages received from a different Jid.
417 reply_callback
.Run(JingleMessageReply::INVALID_SID
);
421 switch (message
.action
) {
422 case JingleMessage::SESSION_ACCEPT
:
423 OnAccept(message
, reply_callback
);
426 case JingleMessage::SESSION_INFO
:
427 OnSessionInfo(message
, reply_callback
);
430 case JingleMessage::TRANSPORT_INFO
:
431 reply_callback
.Run(JingleMessageReply::NONE
);
432 ProcessTransportInfo(message
);
435 case JingleMessage::SESSION_TERMINATE
:
436 OnTerminate(message
, reply_callback
);
440 reply_callback
.Run(JingleMessageReply::UNEXPECTED_REQUEST
);
444 void JingleSession::OnAccept(const JingleMessage
& message
,
445 const ReplyCallback
& reply_callback
) {
446 if (state_
!= CONNECTING
) {
447 reply_callback
.Run(JingleMessageReply::UNEXPECTED_REQUEST
);
451 reply_callback
.Run(JingleMessageReply::NONE
);
453 const buzz::XmlElement
* auth_message
=
454 message
.description
->authenticator_message();
456 DLOG(WARNING
) << "Received session-accept without authentication message ";
457 CloseInternal(INCOMPATIBLE_PROTOCOL
);
461 if (!InitializeConfigFromDescription(message
.description
.get())) {
462 CloseInternal(INCOMPATIBLE_PROTOCOL
);
466 // In case there is transport information in the accept message.
467 ProcessTransportInfo(message
);
471 DCHECK(authenticator_
->state() == Authenticator::WAITING_MESSAGE
);
472 authenticator_
->ProcessMessage(auth_message
, base::Bind(
473 &JingleSession::ProcessAuthenticationStep
,base::Unretained(this)));
476 void JingleSession::OnSessionInfo(const JingleMessage
& message
,
477 const ReplyCallback
& reply_callback
) {
478 if (!message
.info
.get() ||
479 !Authenticator::IsAuthenticatorMessage(message
.info
.get())) {
480 reply_callback
.Run(JingleMessageReply::UNSUPPORTED_INFO
);
484 if ((state_
!= CONNECTED
&& state_
!= AUTHENTICATING
) ||
485 authenticator_
->state() != Authenticator::WAITING_MESSAGE
) {
486 LOG(WARNING
) << "Received unexpected authenticator message "
487 << message
.info
->Str();
488 reply_callback
.Run(JingleMessageReply::UNEXPECTED_REQUEST
);
489 CloseInternal(INCOMPATIBLE_PROTOCOL
);
493 reply_callback
.Run(JingleMessageReply::NONE
);
495 authenticator_
->ProcessMessage(message
.info
.get(), base::Bind(
496 &JingleSession::ProcessAuthenticationStep
, base::Unretained(this)));
499 void JingleSession::ProcessTransportInfo(const JingleMessage
& message
) {
500 for (std::list
<JingleMessage::NamedCandidate
>::const_iterator it
=
501 message
.candidates
.begin();
502 it
!= message
.candidates
.end(); ++it
) {
503 ChannelsMap::iterator channel
= channels_
.find(it
->name
);
504 if (channel
!= channels_
.end()) {
505 channel
->second
->AddRemoteCandidate(it
->candidate
);
507 // Transport info was received before the channel was created.
508 // This could happen due to messages being reordered on the wire.
509 pending_remote_candidates_
.push_back(*it
);
514 void JingleSession::OnTerminate(const JingleMessage
& message
,
515 const ReplyCallback
& reply_callback
) {
516 if (!is_session_active()) {
517 LOG(WARNING
) << "Received unexpected session-terminate message.";
518 reply_callback
.Run(JingleMessageReply::UNEXPECTED_REQUEST
);
522 reply_callback
.Run(JingleMessageReply::NONE
);
524 switch (message
.reason
) {
525 case JingleMessage::SUCCESS
:
526 if (state_
== CONNECTING
) {
527 error_
= SESSION_REJECTED
;
532 case JingleMessage::DECLINE
:
533 error_
= AUTHENTICATION_FAILED
;
535 case JingleMessage::CANCEL
:
536 error_
= HOST_OVERLOAD
;
538 case JingleMessage::GENERAL_ERROR
:
539 error_
= CHANNEL_CONNECTION_ERROR
;
541 case JingleMessage::INCOMPATIBLE_PARAMETERS
:
542 error_
= INCOMPATIBLE_PROTOCOL
;
545 error_
= UNKNOWN_ERROR
;
555 bool JingleSession::InitializeConfigFromDescription(
556 const ContentDescription
* description
) {
559 if (!description
->config()->GetFinalConfig(&config_
)) {
560 LOG(ERROR
) << "session-accept does not specify configuration";
563 if (!candidate_config()->IsSupported(config_
)) {
564 LOG(ERROR
) << "session-accept specifies an invalid configuration";
571 void JingleSession::ProcessAuthenticationStep() {
572 DCHECK(CalledOnValidThread());
573 DCHECK_NE(authenticator_
->state(), Authenticator::PROCESSING_MESSAGE
);
575 if (state_
!= CONNECTED
&& state_
!= AUTHENTICATING
) {
576 DCHECK(state_
== FAILED
|| state_
== CLOSED
);
577 // The remote host closed the connection while the authentication was being
578 // processed asynchronously, nothing to do.
582 if (authenticator_
->state() == Authenticator::MESSAGE_READY
) {
583 JingleMessage
message(peer_jid_
, JingleMessage::SESSION_INFO
, session_id_
);
584 message
.info
= authenticator_
->GetNextMessage();
585 DCHECK(message
.info
.get());
586 SendMessage(message
);
588 DCHECK_NE(authenticator_
->state(), Authenticator::MESSAGE_READY
);
590 // The current JingleSession object can be destroyed by event_handler of
591 // SetState(AUTHENTICATING) and cause subsequent dereferencing of the this
592 // pointer to crash. To protect against it, we run ContinueAuthenticationStep
593 // asychronously using a weak pointer.
594 base::ThreadTaskRunnerHandle::Get()->PostTask(
596 base::Bind(&JingleSession::ContinueAuthenticationStep
,
597 weak_factory_
.GetWeakPtr()));
599 if (authenticator_
->started()) {
600 SetState(AUTHENTICATING
);
604 void JingleSession::ContinueAuthenticationStep() {
605 if (authenticator_
->state() == Authenticator::ACCEPTED
) {
607 } else if (authenticator_
->state() == Authenticator::REJECTED
) {
608 CloseInternal(AuthRejectionReasonToErrorCode(
609 authenticator_
->rejection_reason()));
613 void JingleSession::OnAuthenticated() {
614 pseudotcp_channel_factory_
.reset(new PseudoTcpChannelFactory(this));
615 secure_channel_factory_
.reset(
616 new SecureChannelFactory(pseudotcp_channel_factory_
.get(),
617 authenticator_
.get()));
619 SetState(AUTHENTICATED
);
622 void JingleSession::CloseInternal(ErrorCode error
) {
623 DCHECK(CalledOnValidThread());
625 if (is_session_active()) {
626 // Send session-terminate message with the appropriate error code.
627 JingleMessage::Reason reason
;
630 reason
= JingleMessage::SUCCESS
;
632 case SESSION_REJECTED
:
633 case AUTHENTICATION_FAILED
:
634 reason
= JingleMessage::DECLINE
;
636 case INCOMPATIBLE_PROTOCOL
:
637 reason
= JingleMessage::INCOMPATIBLE_PARAMETERS
;
640 reason
= JingleMessage::CANCEL
;
643 reason
= JingleMessage::GENERAL_ERROR
;
646 JingleMessage
message(peer_jid_
, JingleMessage::SESSION_TERMINATE
,
648 message
.reason
= reason
;
649 SendMessage(message
);
654 if (state_
!= FAILED
&& state_
!= CLOSED
) {
663 void JingleSession::SetState(State new_state
) {
664 DCHECK(CalledOnValidThread());
666 if (new_state
!= state_
) {
667 DCHECK_NE(state_
, CLOSED
);
668 DCHECK_NE(state_
, FAILED
);
672 event_handler_
->OnSessionStateChange(new_state
);
676 bool JingleSession::is_session_active() {
677 return state_
== CONNECTING
|| state_
== ACCEPTING
|| state_
== CONNECTED
||
678 state_
== AUTHENTICATING
|| state_
== AUTHENTICATED
;
681 } // namespace protocol
682 } // namespace remoting