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/quic_channel_factory.h"
23 #include "remoting/protocol/secure_channel_factory.h"
24 #include "remoting/protocol/session_config.h"
25 #include "remoting/protocol/stream_channel_factory.h"
26 #include "remoting/signaling/iq_sender.h"
27 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
28 #include "third_party/webrtc/p2p/base/candidate.h"
30 using buzz::XmlElement
;
37 // Delay after candidate creation before sending transport-info message to
38 // accumulate multiple candidates. This is an optimization to reduce number of
39 // transport-info messages.
40 const int kTransportInfoSendDelayMs
= 20;
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),
80 JingleSession::~JingleSession() {
81 channel_multiplexer_
.reset();
82 quic_channel_factory_
.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(const std::string
& peer_jid
,
105 scoped_ptr
<Authenticator
> authenticator
) {
106 DCHECK(CalledOnValidThread());
107 DCHECK(authenticator
.get());
108 DCHECK_EQ(authenticator
->state(), Authenticator::MESSAGE_READY
);
110 peer_jid_
= peer_jid
;
111 authenticator_
= authenticator
.Pass();
113 // Generate random session ID. There are usually not more than 1
114 // concurrent session per host, so a random 64-bit integer provides
115 // enough entropy. In the worst case connection will fail when two
116 // clients generate the same session ID concurrently.
117 session_id_
= base::Uint64ToString(base::RandGenerator(kuint64max
));
119 quic_channel_factory_
.reset(new QuicChannelFactory(session_id_
, false));
121 // Send session-initiate message.
122 JingleMessage
message(peer_jid_
, JingleMessage::SESSION_INITIATE
,
124 message
.initiator
= session_manager_
->signal_strategy_
->GetLocalJid();
125 message
.description
.reset(new ContentDescription(
126 session_manager_
->protocol_config_
->Clone(),
127 authenticator_
->GetNextMessage(),
128 quic_channel_factory_
->CreateSessionInitiateConfigMessage()));
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
;
149 SessionConfig::SelectCommon(initiate_message
.description
->config(),
150 session_manager_
->protocol_config_
.get());
152 LOG(WARNING
) << "Rejecting connection from " << peer_jid_
153 << " because no compatible configuration has been found.";
154 CloseInternal(INCOMPATIBLE_PROTOCOL
);
158 if (config_
->is_using_quic()) {
159 quic_channel_factory_
.reset(new QuicChannelFactory(session_id_
, true));
160 if (!quic_channel_factory_
->ProcessSessionInitiateConfigMessage(
161 initiate_message
.description
->quic_config_message())) {
162 CloseInternal(INCOMPATIBLE_PROTOCOL
);
167 void JingleSession::AcceptIncomingConnection(
168 const JingleMessage
& initiate_message
) {
171 // Process the first authentication message.
172 const buzz::XmlElement
* first_auth_message
=
173 initiate_message
.description
->authenticator_message();
175 if (!first_auth_message
) {
176 CloseInternal(INCOMPATIBLE_PROTOCOL
);
180 DCHECK_EQ(authenticator_
->state(), Authenticator::WAITING_MESSAGE
);
181 // |authenticator_| is owned, so Unretained() is safe here.
182 authenticator_
->ProcessMessage(first_auth_message
, base::Bind(
183 &JingleSession::ContinueAcceptIncomingConnection
,
184 base::Unretained(this)));
187 void JingleSession::ContinueAcceptIncomingConnection() {
188 DCHECK_NE(authenticator_
->state(), Authenticator::PROCESSING_MESSAGE
);
189 if (authenticator_
->state() == Authenticator::REJECTED
) {
190 CloseInternal(AuthRejectionReasonToErrorCode(
191 authenticator_
->rejection_reason()));
195 // Send the session-accept message.
196 JingleMessage
message(peer_jid_
, JingleMessage::SESSION_ACCEPT
,
199 scoped_ptr
<buzz::XmlElement
> auth_message
;
200 if (authenticator_
->state() == Authenticator::MESSAGE_READY
)
201 auth_message
= authenticator_
->GetNextMessage();
203 std::string quic_config
;
204 if (config_
->is_using_quic())
205 quic_config
= quic_channel_factory_
->CreateSessionAcceptConfigMessage();
206 message
.description
.reset(
207 new ContentDescription(CandidateSessionConfig::CreateFrom(*config_
),
208 auth_message
.Pass(), quic_config
));
209 SendMessage(message
);
214 if (authenticator_
->state() == Authenticator::ACCEPTED
) {
217 DCHECK_EQ(authenticator_
->state(), Authenticator::WAITING_MESSAGE
);
218 if (authenticator_
->started()) {
219 SetState(AUTHENTICATING
);
224 const std::string
& JingleSession::jid() {
225 DCHECK(CalledOnValidThread());
229 const SessionConfig
& JingleSession::config() {
230 DCHECK(CalledOnValidThread());
234 StreamChannelFactory
* JingleSession::GetTransportChannelFactory() {
235 DCHECK(CalledOnValidThread());
236 return secure_channel_factory_
.get();
239 StreamChannelFactory
* JingleSession::GetMultiplexedChannelFactory() {
240 DCHECK(CalledOnValidThread());
241 if (!channel_multiplexer_
.get()) {
242 channel_multiplexer_
.reset(
243 new ChannelMultiplexer(GetTransportChannelFactory(), kMuxChannelName
));
245 return channel_multiplexer_
.get();
248 StreamChannelFactory
* JingleSession::GetQuicChannelFactory() {
249 DCHECK(CalledOnValidThread());
250 return quic_channel_factory_
.get();
253 void JingleSession::Close() {
254 DCHECK(CalledOnValidThread());
259 void JingleSession::AddPendingRemoteTransportInfo(Transport
* channel
) {
260 std::list
<JingleMessage::IceCredentials
>::iterator credentials
=
261 pending_remote_ice_credentials_
.begin();
262 while (credentials
!= pending_remote_ice_credentials_
.end()) {
263 if (credentials
->channel
== channel
->name()) {
264 channel
->SetRemoteCredentials(credentials
->ufrag
, credentials
->password
);
265 credentials
= pending_remote_ice_credentials_
.erase(credentials
);
271 std::list
<JingleMessage::NamedCandidate
>::iterator candidate
=
272 pending_remote_candidates_
.begin();
273 while (candidate
!= pending_remote_candidates_
.end()) {
274 if (candidate
->name
== channel
->name()) {
275 channel
->AddRemoteCandidate(candidate
->candidate
);
276 candidate
= pending_remote_candidates_
.erase(candidate
);
283 void JingleSession::CreateChannel(const std::string
& name
,
284 const ChannelCreatedCallback
& callback
) {
285 DCHECK(!channels_
[name
]);
287 scoped_ptr
<Transport
> channel
=
288 session_manager_
->transport_factory_
->CreateTransport();
289 channel
->Connect(name
, this, callback
);
290 AddPendingRemoteTransportInfo(channel
.get());
291 channels_
[name
] = channel
.release();
294 void JingleSession::CancelChannelCreation(const std::string
& name
) {
295 ChannelsMap::iterator it
= channels_
.find(name
);
296 if (it
!= channels_
.end()) {
297 DCHECK(!it
->second
->is_connected());
299 DCHECK(channels_
.find(name
) == channels_
.end());
303 void JingleSession::OnTransportIceCredentials(Transport
* transport
,
304 const std::string
& ufrag
,
305 const std::string
& password
) {
306 EnsurePendingTransportInfoMessage();
307 pending_transport_info_message_
->ice_credentials
.push_back(
308 JingleMessage::IceCredentials(transport
->name(), ufrag
, password
));
311 void JingleSession::OnTransportCandidate(Transport
* transport
,
312 const cricket::Candidate
& candidate
) {
313 EnsurePendingTransportInfoMessage();
314 pending_transport_info_message_
->candidates
.push_back(
315 JingleMessage::NamedCandidate(transport
->name(), candidate
));
318 void JingleSession::OnTransportRouteChange(Transport
* transport
,
319 const TransportRoute
& route
) {
321 event_handler_
->OnSessionRouteChange(transport
->name(), route
);
324 void JingleSession::OnTransportFailed(Transport
* transport
) {
325 CloseInternal(CHANNEL_CONNECTION_ERROR
);
328 void JingleSession::OnTransportDeleted(Transport
* transport
) {
329 ChannelsMap::iterator it
= channels_
.find(transport
->name());
330 DCHECK_EQ(it
->second
, transport
);
334 void JingleSession::SendMessage(const JingleMessage
& message
) {
335 scoped_ptr
<IqRequest
> request
= session_manager_
->iq_sender()->SendIq(
337 base::Bind(&JingleSession::OnMessageResponse
,
338 base::Unretained(this),
341 int timeout
= kDefaultMessageTimeout
;
342 if (message
.action
== JingleMessage::SESSION_INITIATE
||
343 message
.action
== JingleMessage::SESSION_ACCEPT
) {
344 timeout
= kSessionInitiateAndAcceptTimeout
;
347 request
->SetTimeout(base::TimeDelta::FromSeconds(timeout
));
348 pending_requests_
.insert(request
.release());
350 LOG(ERROR
) << "Failed to send a "
351 << JingleMessage::GetActionName(message
.action
) << " message";
355 void JingleSession::OnMessageResponse(
356 JingleMessage::ActionType request_type
,
358 const buzz::XmlElement
* response
) {
359 // Delete the request from the list of pending requests.
360 pending_requests_
.erase(request
);
363 // Ignore all responses after session was closed.
364 if (state_
== CLOSED
|| state_
== FAILED
)
367 std::string type_str
= JingleMessage::GetActionName(request_type
);
369 // |response| will be nullptr if the request timed out.
371 LOG(ERROR
) << type_str
<< " request timed out.";
372 CloseInternal(SIGNALING_TIMEOUT
);
375 const std::string
& type
=
376 response
->Attr(buzz::QName(std::string(), "type"));
377 if (type
!= "result") {
378 LOG(ERROR
) << "Received error in response to " << type_str
379 << " message: \"" << response
->Str()
380 << "\". Terminating the session.";
382 // TODO(sergeyu): There may be different reasons for error
383 // here. Parse the response stanza to find failure reason.
384 CloseInternal(PEER_IS_OFFLINE
);
389 void JingleSession::EnsurePendingTransportInfoMessage() {
390 // |transport_info_timer_| must be running iff
391 // |pending_transport_info_message_| exists.
392 DCHECK_EQ(pending_transport_info_message_
!= nullptr,
393 transport_info_timer_
.IsRunning());
395 if (!pending_transport_info_message_
) {
396 pending_transport_info_message_
.reset(new JingleMessage(
397 peer_jid_
, JingleMessage::TRANSPORT_INFO
, session_id_
));
398 // Delay sending the new candidates in case we get more candidates
399 // that we can send in one message.
400 transport_info_timer_
.Start(
401 FROM_HERE
, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs
),
402 this, &JingleSession::SendTransportInfo
);
406 void JingleSession::SendTransportInfo() {
407 DCHECK(pending_transport_info_message_
);
409 scoped_ptr
<IqRequest
> request
= session_manager_
->iq_sender()->SendIq(
410 pending_transport_info_message_
->ToXml(),
411 base::Bind(&JingleSession::OnTransportInfoResponse
,
412 base::Unretained(this)));
413 pending_transport_info_message_
.reset();
415 request
->SetTimeout(base::TimeDelta::FromSeconds(kTransportInfoTimeout
));
416 transport_info_requests_
.push_back(request
.release());
418 LOG(ERROR
) << "Failed to send a transport-info message";
422 void JingleSession::OnTransportInfoResponse(IqRequest
* request
,
423 const buzz::XmlElement
* response
) {
424 DCHECK(!transport_info_requests_
.empty());
426 // Consider transport-info requests sent before this one lost and delete
427 // corresponding IqRequest objects.
428 while (transport_info_requests_
.front() != request
) {
429 delete transport_info_requests_
.front();
430 transport_info_requests_
.pop_front();
433 // Delete the |request| itself.
434 DCHECK_EQ(request
, transport_info_requests_
.front());
436 transport_info_requests_
.pop_front();
438 // Ignore transport-info timeouts.
440 LOG(ERROR
) << "transport-info request has timed out.";
444 const std::string
& type
= response
->Attr(buzz::QName(std::string(), "type"));
445 if (type
!= "result") {
446 LOG(ERROR
) << "Received error in response to transport-info message: \""
447 << response
->Str() << "\". Terminating the session.";
448 CloseInternal(PEER_IS_OFFLINE
);
452 void JingleSession::OnIncomingMessage(const JingleMessage
& message
,
453 const ReplyCallback
& reply_callback
) {
454 DCHECK(CalledOnValidThread());
456 if (message
.from
!= peer_jid_
) {
457 // Ignore messages received from a different Jid.
458 reply_callback
.Run(JingleMessageReply::INVALID_SID
);
462 switch (message
.action
) {
463 case JingleMessage::SESSION_ACCEPT
:
464 OnAccept(message
, reply_callback
);
467 case JingleMessage::SESSION_INFO
:
468 OnSessionInfo(message
, reply_callback
);
471 case JingleMessage::TRANSPORT_INFO
:
472 reply_callback
.Run(JingleMessageReply::NONE
);
473 ProcessTransportInfo(message
);
476 case JingleMessage::SESSION_TERMINATE
:
477 OnTerminate(message
, reply_callback
);
481 reply_callback
.Run(JingleMessageReply::UNEXPECTED_REQUEST
);
485 void JingleSession::OnAccept(const JingleMessage
& message
,
486 const ReplyCallback
& reply_callback
) {
487 if (state_
!= CONNECTING
) {
488 reply_callback
.Run(JingleMessageReply::UNEXPECTED_REQUEST
);
492 reply_callback
.Run(JingleMessageReply::NONE
);
494 const buzz::XmlElement
* auth_message
=
495 message
.description
->authenticator_message();
497 DLOG(WARNING
) << "Received session-accept without authentication message ";
498 CloseInternal(INCOMPATIBLE_PROTOCOL
);
502 if (!InitializeConfigFromDescription(message
.description
.get())) {
503 CloseInternal(INCOMPATIBLE_PROTOCOL
);
507 if (config_
->is_using_quic()) {
508 if (!quic_channel_factory_
->ProcessSessionAcceptConfigMessage(
509 message
.description
->quic_config_message())) {
510 CloseInternal(INCOMPATIBLE_PROTOCOL
);
514 quic_channel_factory_
.reset();
519 DCHECK(authenticator_
->state() == Authenticator::WAITING_MESSAGE
);
520 authenticator_
->ProcessMessage(auth_message
, base::Bind(
521 &JingleSession::ProcessAuthenticationStep
,base::Unretained(this)));
524 void JingleSession::OnSessionInfo(const JingleMessage
& message
,
525 const ReplyCallback
& reply_callback
) {
526 if (!message
.info
.get() ||
527 !Authenticator::IsAuthenticatorMessage(message
.info
.get())) {
528 reply_callback
.Run(JingleMessageReply::UNSUPPORTED_INFO
);
532 if ((state_
!= CONNECTED
&& state_
!= AUTHENTICATING
) ||
533 authenticator_
->state() != Authenticator::WAITING_MESSAGE
) {
534 LOG(WARNING
) << "Received unexpected authenticator message "
535 << message
.info
->Str();
536 reply_callback
.Run(JingleMessageReply::UNEXPECTED_REQUEST
);
537 CloseInternal(INCOMPATIBLE_PROTOCOL
);
541 reply_callback
.Run(JingleMessageReply::NONE
);
543 authenticator_
->ProcessMessage(message
.info
.get(), base::Bind(
544 &JingleSession::ProcessAuthenticationStep
, base::Unretained(this)));
547 void JingleSession::ProcessTransportInfo(const JingleMessage
& message
) {
548 for (std::list
<JingleMessage::IceCredentials
>::const_iterator it
=
549 message
.ice_credentials
.begin();
550 it
!= message
.ice_credentials
.end(); ++it
) {
551 ChannelsMap::iterator channel
= channels_
.find(it
->channel
);
552 if (channel
!= channels_
.end()) {
553 channel
->second
->SetRemoteCredentials(it
->ufrag
, it
->password
);
555 // Transport info was received before the channel was created.
556 // This could happen due to messages being reordered on the wire.
557 pending_remote_ice_credentials_
.push_back(*it
);
561 for (std::list
<JingleMessage::NamedCandidate
>::const_iterator it
=
562 message
.candidates
.begin();
563 it
!= message
.candidates
.end(); ++it
) {
564 ChannelsMap::iterator channel
= channels_
.find(it
->name
);
565 if (channel
!= channels_
.end()) {
566 channel
->second
->AddRemoteCandidate(it
->candidate
);
568 // Transport info was received before the channel was created.
569 // This could happen due to messages being reordered on the wire.
570 pending_remote_candidates_
.push_back(*it
);
575 void JingleSession::OnTerminate(const JingleMessage
& message
,
576 const ReplyCallback
& reply_callback
) {
577 if (!is_session_active()) {
578 LOG(WARNING
) << "Received unexpected session-terminate message.";
579 reply_callback
.Run(JingleMessageReply::UNEXPECTED_REQUEST
);
583 reply_callback
.Run(JingleMessageReply::NONE
);
585 switch (message
.reason
) {
586 case JingleMessage::SUCCESS
:
587 if (state_
== CONNECTING
) {
588 error_
= SESSION_REJECTED
;
593 case JingleMessage::DECLINE
:
594 error_
= AUTHENTICATION_FAILED
;
596 case JingleMessage::CANCEL
:
597 error_
= HOST_OVERLOAD
;
599 case JingleMessage::GENERAL_ERROR
:
600 error_
= CHANNEL_CONNECTION_ERROR
;
602 case JingleMessage::INCOMPATIBLE_PARAMETERS
:
603 error_
= INCOMPATIBLE_PROTOCOL
;
606 error_
= UNKNOWN_ERROR
;
616 bool JingleSession::InitializeConfigFromDescription(
617 const ContentDescription
* description
) {
619 config_
= SessionConfig::GetFinalConfig(description
->config());
621 LOG(ERROR
) << "session-accept does not specify configuration";
624 if (!session_manager_
->protocol_config_
->IsSupported(*config_
)) {
625 LOG(ERROR
) << "session-accept specifies an invalid configuration";
632 void JingleSession::ProcessAuthenticationStep() {
633 DCHECK(CalledOnValidThread());
634 DCHECK_NE(authenticator_
->state(), Authenticator::PROCESSING_MESSAGE
);
636 if (state_
!= CONNECTED
&& state_
!= AUTHENTICATING
) {
637 DCHECK(state_
== FAILED
|| state_
== CLOSED
);
638 // The remote host closed the connection while the authentication was being
639 // processed asynchronously, nothing to do.
643 if (authenticator_
->state() == Authenticator::MESSAGE_READY
) {
644 JingleMessage
message(peer_jid_
, JingleMessage::SESSION_INFO
, session_id_
);
645 message
.info
= authenticator_
->GetNextMessage();
646 DCHECK(message
.info
.get());
647 SendMessage(message
);
649 DCHECK_NE(authenticator_
->state(), Authenticator::MESSAGE_READY
);
651 // The current JingleSession object can be destroyed by event_handler of
652 // SetState(AUTHENTICATING) and cause subsequent dereferencing of the this
653 // pointer to crash. To protect against it, we run ContinueAuthenticationStep
654 // asychronously using a weak pointer.
655 base::ThreadTaskRunnerHandle::Get()->PostTask(
657 base::Bind(&JingleSession::ContinueAuthenticationStep
,
658 weak_factory_
.GetWeakPtr()));
660 if (authenticator_
->started()) {
661 SetState(AUTHENTICATING
);
665 void JingleSession::ContinueAuthenticationStep() {
666 if (authenticator_
->state() == Authenticator::ACCEPTED
) {
668 } else if (authenticator_
->state() == Authenticator::REJECTED
) {
669 CloseInternal(AuthRejectionReasonToErrorCode(
670 authenticator_
->rejection_reason()));
674 void JingleSession::OnAuthenticated() {
675 pseudotcp_channel_factory_
.reset(new PseudoTcpChannelFactory(this));
676 secure_channel_factory_
.reset(
677 new SecureChannelFactory(pseudotcp_channel_factory_
.get(),
678 authenticator_
.get()));
680 if (quic_channel_factory_
)
681 quic_channel_factory_
->Start(this, authenticator_
->GetAuthKey());
683 SetState(AUTHENTICATED
);
686 void JingleSession::CloseInternal(ErrorCode error
) {
687 DCHECK(CalledOnValidThread());
689 if (is_session_active()) {
690 // Send session-terminate message with the appropriate error code.
691 JingleMessage::Reason reason
;
694 reason
= JingleMessage::SUCCESS
;
696 case SESSION_REJECTED
:
697 case AUTHENTICATION_FAILED
:
698 reason
= JingleMessage::DECLINE
;
700 case INCOMPATIBLE_PROTOCOL
:
701 reason
= JingleMessage::INCOMPATIBLE_PARAMETERS
;
704 reason
= JingleMessage::CANCEL
;
707 reason
= JingleMessage::GENERAL_ERROR
;
710 JingleMessage
message(peer_jid_
, JingleMessage::SESSION_TERMINATE
,
712 message
.reason
= reason
;
713 SendMessage(message
);
718 if (state_
!= FAILED
&& state_
!= CLOSED
) {
727 void JingleSession::SetState(State new_state
) {
728 DCHECK(CalledOnValidThread());
730 if (new_state
!= state_
) {
731 DCHECK_NE(state_
, CLOSED
);
732 DCHECK_NE(state_
, FAILED
);
736 event_handler_
->OnSessionStateChange(new_state
);
740 bool JingleSession::is_session_active() {
741 return state_
== CONNECTING
|| state_
== ACCEPTING
|| state_
== CONNECTED
||
742 state_
== AUTHENTICATING
|| state_
== AUTHENTICATED
;
745 } // namespace protocol
746 } // namespace remoting