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