Home | History | Annotate | Download | only in protocol
      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 
     24 using buzz::XmlElement;
     25 
     26 namespace remoting {
     27 namespace protocol {
     28 
     29 namespace {
     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.
     35 const 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|.
     39 const int kDefaultMessageTimeout = 10;
     40 
     41 // Timeout for the transport-info messages.
     42 const int kTransportInfoTimeout = 10 * 60;
     43 
     44 // Name of the multiplexed channel.
     45 const char kMuxChannelName[] = "mux";
     46 
     47 ErrorCode 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 
     61 JingleSession::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 
     69 JingleSession::~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 
     79 void JingleSession::SetEventHandler(Session::EventHandler* event_handler) {
     80   DCHECK(CalledOnValidThread());
     81   DCHECK(event_handler);
     82   event_handler_ = event_handler;
     83 }
     84 
     85 ErrorCode JingleSession::error() {
     86   DCHECK(CalledOnValidThread());
     87   return error_;
     88 }
     89 
     90 void 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 
    120 void 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 
    136 void 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 
    156 void 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 
    189 const std::string& JingleSession::jid() {
    190   DCHECK(CalledOnValidThread());
    191   return peer_jid_;
    192 }
    193 
    194 const CandidateSessionConfig* JingleSession::candidate_config() {
    195   DCHECK(CalledOnValidThread());
    196   return candidate_config_.get();
    197 }
    198 
    199 const SessionConfig& JingleSession::config() {
    200   DCHECK(CalledOnValidThread());
    201   return config_;
    202 }
    203 
    204 void JingleSession::set_config(const SessionConfig& config) {
    205   DCHECK(CalledOnValidThread());
    206   DCHECK(!config_is_set_);
    207   config_ = config;
    208   config_is_set_ = true;
    209 }
    210 
    211 ChannelFactory* JingleSession::GetTransportChannelFactory() {
    212   DCHECK(CalledOnValidThread());
    213   return this;
    214 }
    215 
    216 ChannelFactory* 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 
    223 void JingleSession::Close() {
    224   DCHECK(CalledOnValidThread());
    225 
    226   CloseInternal(OK);
    227 }
    228 
    229 void JingleSession::AddPendingRemoteCandidates(Transport* channel,
    230                                                const std::string& name) {
    231   std::list<JingleMessage::NamedCandidate>::iterator it =
    232       pending_remote_candidates_.begin();
    233   while(it != pending_remote_candidates_.end()) {
    234     if (it->name == name) {
    235       channel->AddRemoteCandidate(it->candidate);
    236       it = pending_remote_candidates_.erase(it);
    237     } else {
    238       ++it;
    239     }
    240   }
    241 }
    242 
    243 void JingleSession::CreateStreamChannel(
    244       const std::string& name,
    245       const StreamChannelCallback& callback) {
    246   DCHECK(!channels_[name]);
    247 
    248   scoped_ptr<ChannelAuthenticator> channel_authenticator =
    249       authenticator_->CreateChannelAuthenticator();
    250   scoped_ptr<StreamTransport> channel =
    251       session_manager_->transport_factory_->CreateStreamTransport();
    252   channel->Initialize(name, this, channel_authenticator.Pass());
    253   channel->Connect(callback);
    254   AddPendingRemoteCandidates(channel.get(), name);
    255   channels_[name] = channel.release();
    256 }
    257 
    258 void JingleSession::CreateDatagramChannel(
    259     const std::string& name,
    260     const DatagramChannelCallback& callback) {
    261   DCHECK(!channels_[name]);
    262 
    263   scoped_ptr<ChannelAuthenticator> channel_authenticator =
    264       authenticator_->CreateChannelAuthenticator();
    265   scoped_ptr<DatagramTransport> channel =
    266       session_manager_->transport_factory_->CreateDatagramTransport();
    267   channel->Initialize(name, this, channel_authenticator.Pass());
    268   channel->Connect(callback);
    269   AddPendingRemoteCandidates(channel.get(), name);
    270   channels_[name] = channel.release();
    271 }
    272 
    273 void JingleSession::CancelChannelCreation(const std::string& name) {
    274   ChannelsMap::iterator it = channels_.find(name);
    275   if (it != channels_.end() && !it->second->is_connected()) {
    276     delete it->second;
    277     DCHECK(!channels_[name]);
    278   }
    279 }
    280 
    281 void JingleSession::OnTransportCandidate(Transport* transport,
    282                                          const cricket::Candidate& candidate) {
    283   pending_candidates_.push_back(JingleMessage::NamedCandidate(
    284       transport->name(), candidate));
    285 
    286   if (!transport_infos_timer_.IsRunning()) {
    287     // Delay sending the new candidates in case we get more candidates
    288     // that we can send in one message.
    289     transport_infos_timer_.Start(
    290         FROM_HERE, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs),
    291         this, &JingleSession::SendTransportInfo);
    292   }
    293 }
    294 
    295 void JingleSession::OnTransportRouteChange(Transport* transport,
    296                                            const TransportRoute& route) {
    297   if (event_handler_)
    298     event_handler_->OnSessionRouteChange(transport->name(), route);
    299 }
    300 
    301 void JingleSession::OnTransportReady(Transport* transport, bool ready) {
    302   if (event_handler_)
    303     event_handler_->OnSessionChannelReady(transport->name(), ready);
    304 }
    305 
    306 void JingleSession::OnTransportFailed(Transport* transport) {
    307   CloseInternal(CHANNEL_CONNECTION_ERROR);
    308 }
    309 
    310 void JingleSession::OnTransportDeleted(Transport* transport) {
    311   ChannelsMap::iterator it = channels_.find(transport->name());
    312   DCHECK_EQ(it->second, transport);
    313   channels_.erase(it);
    314 }
    315 
    316 void JingleSession::SendMessage(const JingleMessage& message) {
    317   scoped_ptr<IqRequest> request = session_manager_->iq_sender()->SendIq(
    318       message.ToXml(),
    319       base::Bind(&JingleSession::OnMessageResponse,
    320                  base::Unretained(this), message.action));
    321   if (request) {
    322     request->SetTimeout(base::TimeDelta::FromSeconds(kDefaultMessageTimeout));
    323     pending_requests_.insert(request.release());
    324   } else {
    325     LOG(ERROR) << "Failed to send a "
    326                << JingleMessage::GetActionName(message.action) << " message";
    327   }
    328 }
    329 
    330 void JingleSession::OnMessageResponse(
    331     JingleMessage::ActionType request_type,
    332     IqRequest* request,
    333     const buzz::XmlElement* response) {
    334   std::string type_str = JingleMessage::GetActionName(request_type);
    335 
    336   // Delete the request from the list of pending requests.
    337   pending_requests_.erase(request);
    338   delete request;
    339 
    340   // |response| will be NULL if the request timed out.
    341   if (!response) {
    342     LOG(ERROR) << type_str << " request timed out.";
    343     CloseInternal(SIGNALING_TIMEOUT);
    344     return;
    345   } else {
    346     const std::string& type =
    347         response->Attr(buzz::QName(std::string(), "type"));
    348     if (type != "result") {
    349       LOG(ERROR) << "Received error in response to " << type_str
    350                  << " message: \"" << response->Str()
    351                  << "\". Terminating the session.";
    352 
    353       switch (request_type) {
    354         case JingleMessage::SESSION_INFO:
    355           // session-info is used for the new authentication protocol,
    356           // and wasn't previously supported.
    357           CloseInternal(INCOMPATIBLE_PROTOCOL);
    358           break;
    359 
    360         default:
    361           // TODO(sergeyu): There may be different reasons for error
    362           // here. Parse the response stanza to find failure reason.
    363           CloseInternal(PEER_IS_OFFLINE);
    364       }
    365     }
    366   }
    367 }
    368 
    369 void JingleSession::SendTransportInfo() {
    370   JingleMessage message(peer_jid_, JingleMessage::TRANSPORT_INFO, session_id_);
    371   message.candidates.swap(pending_candidates_);
    372 
    373   scoped_ptr<IqRequest> request = session_manager_->iq_sender()->SendIq(
    374       message.ToXml(),
    375       base::Bind(&JingleSession::OnTransportInfoResponse,
    376                  base::Unretained(this)));
    377   if (request) {
    378     request->SetTimeout(base::TimeDelta::FromSeconds(kTransportInfoTimeout));
    379     transport_info_requests_.push_back(request.release());
    380   } else {
    381     LOG(ERROR) << "Failed to send a transport-info message";
    382   }
    383 }
    384 
    385 void JingleSession::OnTransportInfoResponse(IqRequest* request,
    386                                             const buzz::XmlElement* response) {
    387   DCHECK(!transport_info_requests_.empty());
    388 
    389   // Consider transport-info requests sent before this one lost and delete
    390   // corresponding IqRequest objects.
    391   while (transport_info_requests_.front() != request) {
    392     delete transport_info_requests_.front();
    393     transport_info_requests_.pop_front();
    394   }
    395 
    396   // Delete the |request| itself.
    397   DCHECK_EQ(request, transport_info_requests_.front());
    398   delete request;
    399   transport_info_requests_.pop_front();
    400 
    401   // Ignore transport-info timeouts.
    402   if (!response) {
    403     LOG(ERROR) << "transport-info request has timed out.";
    404     return;
    405   }
    406 
    407   const std::string& type = response->Attr(buzz::QName(std::string(), "type"));
    408   if (type != "result") {
    409     LOG(ERROR) << "Received error in response to transport-info message: \""
    410                << response->Str() << "\". Terminating the session.";
    411     CloseInternal(PEER_IS_OFFLINE);
    412   }
    413 }
    414 
    415 void JingleSession::OnIncomingMessage(const JingleMessage& message,
    416                                       const ReplyCallback& reply_callback) {
    417   DCHECK(CalledOnValidThread());
    418 
    419   if (message.from != peer_jid_) {
    420     // Ignore messages received from a different Jid.
    421     reply_callback.Run(JingleMessageReply::INVALID_SID);
    422     return;
    423   }
    424 
    425   switch (message.action) {
    426     case JingleMessage::SESSION_ACCEPT:
    427       OnAccept(message, reply_callback);
    428       break;
    429 
    430     case JingleMessage::SESSION_INFO:
    431       OnSessionInfo(message, reply_callback);
    432       break;
    433 
    434     case JingleMessage::TRANSPORT_INFO:
    435       reply_callback.Run(JingleMessageReply::NONE);
    436       ProcessTransportInfo(message);
    437       break;
    438 
    439     case JingleMessage::SESSION_TERMINATE:
    440       OnTerminate(message, reply_callback);
    441       break;
    442 
    443     default:
    444       reply_callback.Run(JingleMessageReply::UNEXPECTED_REQUEST);
    445   }
    446 }
    447 
    448 void JingleSession::OnAccept(const JingleMessage& message,
    449                              const ReplyCallback& reply_callback) {
    450   if (state_ != CONNECTING) {
    451     reply_callback.Run(JingleMessageReply::UNEXPECTED_REQUEST);
    452     return;
    453   }
    454 
    455   reply_callback.Run(JingleMessageReply::NONE);
    456 
    457   const buzz::XmlElement* auth_message =
    458       message.description->authenticator_message();
    459   if (!auth_message) {
    460     DLOG(WARNING) << "Received session-accept without authentication message ";
    461     CloseInternal(INCOMPATIBLE_PROTOCOL);
    462     return;
    463   }
    464 
    465   if (!InitializeConfigFromDescription(message.description.get())) {
    466     CloseInternal(INCOMPATIBLE_PROTOCOL);
    467     return;
    468   }
    469 
    470   // In case there is transport information in the accept message.
    471   ProcessTransportInfo(message);
    472 
    473   SetState(CONNECTED);
    474 
    475   DCHECK(authenticator_->state() == Authenticator::WAITING_MESSAGE);
    476   authenticator_->ProcessMessage(auth_message, base::Bind(
    477       &JingleSession::ProcessAuthenticationStep,base::Unretained(this)));
    478 }
    479 
    480 void JingleSession::OnSessionInfo(const JingleMessage& message,
    481                                   const ReplyCallback& reply_callback) {
    482   if (!message.info.get() ||
    483       !Authenticator::IsAuthenticatorMessage(message.info.get())) {
    484     reply_callback.Run(JingleMessageReply::UNSUPPORTED_INFO);
    485     return;
    486   }
    487 
    488   if (state_ != CONNECTED ||
    489       authenticator_->state() != Authenticator::WAITING_MESSAGE) {
    490     LOG(WARNING) << "Received unexpected authenticator message "
    491                  << message.info->Str();
    492     reply_callback.Run(JingleMessageReply::UNEXPECTED_REQUEST);
    493     CloseInternal(INCOMPATIBLE_PROTOCOL);
    494     return;
    495   }
    496 
    497   reply_callback.Run(JingleMessageReply::NONE);
    498 
    499   authenticator_->ProcessMessage(message.info.get(), base::Bind(
    500       &JingleSession::ProcessAuthenticationStep, base::Unretained(this)));
    501 }
    502 
    503 void JingleSession::ProcessTransportInfo(const JingleMessage& message) {
    504   for (std::list<JingleMessage::NamedCandidate>::const_iterator it =
    505            message.candidates.begin();
    506        it != message.candidates.end(); ++it) {
    507     ChannelsMap::iterator channel = channels_.find(it->name);
    508     if (channel != channels_.end()) {
    509       channel->second->AddRemoteCandidate(it->candidate);
    510     } else {
    511       // Transport info was received before the channel was created.
    512       // This could happen due to messages being reordered on the wire.
    513       pending_remote_candidates_.push_back(*it);
    514     }
    515   }
    516 }
    517 
    518 void JingleSession::OnTerminate(const JingleMessage& message,
    519                                 const ReplyCallback& reply_callback) {
    520   if (state_ != CONNECTING && state_ != ACCEPTING && state_ != CONNECTED &&
    521       state_ != AUTHENTICATED) {
    522     LOG(WARNING) << "Received unexpected session-terminate message.";
    523     reply_callback.Run(JingleMessageReply::UNEXPECTED_REQUEST);
    524     return;
    525   }
    526 
    527   reply_callback.Run(JingleMessageReply::NONE);
    528 
    529   switch (message.reason) {
    530     case JingleMessage::SUCCESS:
    531       if (state_ == CONNECTING) {
    532         error_ = SESSION_REJECTED;
    533       } else {
    534         error_ = OK;
    535       }
    536       break;
    537     case JingleMessage::DECLINE:
    538       error_ = AUTHENTICATION_FAILED;
    539       break;
    540     case JingleMessage::CANCEL:
    541       error_ = HOST_OVERLOAD;
    542       break;
    543     case JingleMessage::GENERAL_ERROR:
    544       error_ = CHANNEL_CONNECTION_ERROR;
    545       break;
    546     case JingleMessage::INCOMPATIBLE_PARAMETERS:
    547       error_ = INCOMPATIBLE_PROTOCOL;
    548       break;
    549     default:
    550       error_ = UNKNOWN_ERROR;
    551   }
    552 
    553   if (error_ != OK) {
    554     SetState(FAILED);
    555   } else {
    556     SetState(CLOSED);
    557   }
    558 }
    559 
    560 bool JingleSession::InitializeConfigFromDescription(
    561     const ContentDescription* description) {
    562   DCHECK(description);
    563 
    564   if (!description->config()->GetFinalConfig(&config_)) {
    565     LOG(ERROR) << "session-accept does not specify configuration";
    566     return false;
    567   }
    568   if (!candidate_config()->IsSupported(config_)) {
    569     LOG(ERROR) << "session-accept specifies an invalid configuration";
    570     return false;
    571   }
    572 
    573   return true;
    574 }
    575 
    576 void JingleSession::ProcessAuthenticationStep() {
    577   DCHECK(CalledOnValidThread());
    578   DCHECK_NE(authenticator_->state(), Authenticator::PROCESSING_MESSAGE);
    579 
    580   if (state_ != CONNECTED) {
    581     DCHECK(state_ == FAILED || state_ == CLOSED);
    582     // The remote host closed the connection while the authentication was being
    583     // processed asynchronously, nothing to do.
    584     return;
    585   }
    586 
    587   if (authenticator_->state() == Authenticator::MESSAGE_READY) {
    588     JingleMessage message(peer_jid_, JingleMessage::SESSION_INFO, session_id_);
    589     message.info = authenticator_->GetNextMessage();
    590     DCHECK(message.info.get());
    591     SendMessage(message);
    592   }
    593   DCHECK_NE(authenticator_->state(), Authenticator::MESSAGE_READY);
    594 
    595   if (authenticator_->state() == Authenticator::ACCEPTED) {
    596     SetState(AUTHENTICATED);
    597   } else if (authenticator_->state() == Authenticator::REJECTED) {
    598     CloseInternal(AuthRejectionReasonToErrorCode(
    599         authenticator_->rejection_reason()));
    600   }
    601 }
    602 
    603 void JingleSession::CloseInternal(ErrorCode error) {
    604   DCHECK(CalledOnValidThread());
    605 
    606   if (state_ == CONNECTING || state_ == ACCEPTING || state_ == CONNECTED ||
    607       state_ == AUTHENTICATED) {
    608     // Send session-terminate message with the appropriate error code.
    609     JingleMessage::Reason reason;
    610     switch (error) {
    611       case OK:
    612         reason = JingleMessage::SUCCESS;
    613         break;
    614       case SESSION_REJECTED:
    615       case AUTHENTICATION_FAILED:
    616         reason = JingleMessage::DECLINE;
    617         break;
    618       case INCOMPATIBLE_PROTOCOL:
    619         reason = JingleMessage::INCOMPATIBLE_PARAMETERS;
    620         break;
    621       case HOST_OVERLOAD:
    622         reason = JingleMessage::CANCEL;
    623         break;
    624       default:
    625         reason = JingleMessage::GENERAL_ERROR;
    626     }
    627 
    628     JingleMessage message(peer_jid_, JingleMessage::SESSION_TERMINATE,
    629                           session_id_);
    630     message.reason = reason;
    631     SendMessage(message);
    632   }
    633 
    634   error_ = error;
    635 
    636   if (state_ != FAILED && state_ != CLOSED) {
    637     if (error != OK) {
    638       SetState(FAILED);
    639     } else {
    640       SetState(CLOSED);
    641     }
    642   }
    643 }
    644 
    645 void JingleSession::SetState(State new_state) {
    646   DCHECK(CalledOnValidThread());
    647 
    648   if (new_state != state_) {
    649     DCHECK_NE(state_, CLOSED);
    650     DCHECK_NE(state_, FAILED);
    651 
    652     state_ = new_state;
    653     if (event_handler_)
    654       event_handler_->OnSessionStateChange(new_state);
    655   }
    656 }
    657 
    658 }  // namespace protocol
    659 }  // namespace remoting
    660