blob: 1866ece2f569fc4fda725645cc9d9021ceb797d9 [file] [log] [blame]
[email protected]6bad55c2012-01-24 20:50:271// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]22aae952011-09-12 23:47:512// 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/pepper_session.h"
6
7#include "base/bind.h"
8#include "base/rand_util.h"
9#include "base/stl_util.h"
10#include "base/string_number_conversions.h"
11#include "remoting/base/constants.h"
[email protected]b39e1822011-11-04 01:00:4912#include "remoting/jingle_glue/iq_sender.h"
[email protected]f44538512011-11-30 04:43:1713#include "remoting/protocol/authenticator.h"
[email protected]e3c97a62012-02-09 03:52:2014#include "remoting/protocol/channel_authenticator.h"
[email protected]22aae952011-09-12 23:47:5115#include "remoting/protocol/content_description.h"
16#include "remoting/protocol/jingle_messages.h"
17#include "remoting/protocol/pepper_session_manager.h"
[email protected]a2098232012-02-09 06:19:3318#include "remoting/protocol/session_config.h"
[email protected]22aae952011-09-12 23:47:5119#include "third_party/libjingle/source/talk/p2p/base/candidate.h"
20#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
21
22using buzz::XmlElement;
23
24namespace remoting {
25namespace protocol {
26
27namespace {
28// Delay after candidate creation before sending transport-info
29// message. This is neccessary to be able to pack multiple candidates
30// into one transport-info messages. The value needs to be greater
31// than zero because ports are opened asynchronously in the browser
32// process.
33const int kTransportInfoSendDelayMs = 2;
[email protected]a2098232012-02-09 06:19:3334
35Session::Error AuthRejectionReasonToError(
36 Authenticator::RejectionReason reason) {
37 switch (reason) {
38 case Authenticator::INVALID_CREDENTIALS:
39 return Session::AUTHENTICATION_FAILED;
40 case Authenticator::PROTOCOL_ERROR:
41 return Session::INCOMPATIBLE_PROTOCOL;
42 }
43 NOTREACHED();
44 return Session::UNKNOWN_ERROR;
45}
46
[email protected]22aae952011-09-12 23:47:5147} // namespace
48
49PepperSession::PepperSession(PepperSessionManager* session_manager)
50 : session_manager_(session_manager),
51 state_(INITIALIZING),
[email protected]a2098232012-02-09 06:19:3352 error_(OK),
53 config_is_set_(false) {
[email protected]22aae952011-09-12 23:47:5154}
55
56PepperSession::~PepperSession() {
[email protected]22aae952011-09-12 23:47:5157 STLDeleteContainerPairSecondPointers(channels_.begin(), channels_.end());
58 session_manager_->SessionDestroyed(this);
59}
60
[email protected]9e2a3132011-10-07 05:07:4061void PepperSession::SetStateChangeCallback(
62 const StateChangeCallback& callback) {
[email protected]22aae952011-09-12 23:47:5163 DCHECK(CalledOnValidThread());
[email protected]a2098232012-02-09 06:19:3364 DCHECK(!callback.is_null());
[email protected]9e2a3132011-10-07 05:07:4065 state_change_callback_ = callback;
[email protected]22aae952011-09-12 23:47:5166}
67
[email protected]3cb66772012-01-25 00:30:5568void PepperSession::SetRouteChangeCallback(
69 const RouteChangeCallback& callback) {
[email protected]a2098232012-02-09 06:19:3370 DCHECK(CalledOnValidThread());
71 route_change_callback_ = callback;
[email protected]3cb66772012-01-25 00:30:5572}
73
[email protected]e3f03d02011-09-27 20:10:5174Session::Error PepperSession::error() {
75 DCHECK(CalledOnValidThread());
76 return error_;
77}
78
[email protected]22aae952011-09-12 23:47:5179void PepperSession::StartConnection(
80 const std::string& peer_jid,
[email protected]5bf52312012-01-20 04:10:5281 scoped_ptr<Authenticator> authenticator,
82 scoped_ptr<CandidateSessionConfig> config,
[email protected]9e2a3132011-10-07 05:07:4083 const StateChangeCallback& state_change_callback) {
[email protected]22aae952011-09-12 23:47:5184 DCHECK(CalledOnValidThread());
[email protected]5bf52312012-01-20 04:10:5285 DCHECK(authenticator.get());
[email protected]f44538512011-11-30 04:43:1786 DCHECK_EQ(authenticator->state(), Authenticator::MESSAGE_READY);
[email protected]22aae952011-09-12 23:47:5187
88 peer_jid_ = peer_jid;
[email protected]5bf52312012-01-20 04:10:5289 authenticator_ = authenticator.Pass();
90 candidate_config_ = config.Pass();
[email protected]9e2a3132011-10-07 05:07:4091 state_change_callback_ = state_change_callback;
[email protected]22aae952011-09-12 23:47:5192
93 // Generate random session ID. There are usually not more than 1
94 // concurrent session per host, so a random 64-bit integer provides
95 // enough entropy. In the worst case connection will fail when two
96 // clients generate the same session ID concurrently.
97 session_id_ = base::Int64ToString(base::RandGenerator(kint64max));
98
99 // Send session-initiate message.
100 JingleMessage message(peer_jid_, JingleMessage::SESSION_INITIATE,
101 session_id_);
[email protected]22aae952011-09-12 23:47:51102 message.description.reset(
[email protected]f44538512011-11-30 04:43:17103 new ContentDescription(candidate_config_->Clone(),
104 authenticator_->GetNextMessage()));
[email protected]b39e1822011-11-04 01:00:49105 initiate_request_.reset(session_manager_->iq_sender()->SendIq(
106 message.ToXml(),
107 base::Bind(&PepperSession::OnSessionInitiateResponse,
108 base::Unretained(this))));
[email protected]22aae952011-09-12 23:47:51109
110 SetState(CONNECTING);
111}
112
[email protected]a2098232012-02-09 06:19:33113void PepperSession::InitializeIncomingConnection(
114 const JingleMessage& initiate_message,
115 scoped_ptr<Authenticator> authenticator) {
116 DCHECK(CalledOnValidThread());
117 DCHECK(initiate_message.description.get());
118 DCHECK(authenticator.get());
119 DCHECK_EQ(authenticator->state(), Authenticator::WAITING_MESSAGE);
120
121 peer_jid_ = initiate_message.from;
122 authenticator_ = authenticator.Pass();
123 session_id_ = initiate_message.sid;
124 candidate_config_ = initiate_message.description->config()->Clone();
125
126 SetState(CONNECTING);
127}
128
129void PepperSession::AcceptIncomingConnection(
130 const JingleMessage& initiate_message) {
131 DCHECK(config_is_set_);
132
133 // Process the first authentication message.
134 const buzz::XmlElement* first_auth_message =
135 initiate_message.description->authenticator_message();
136
137 if (!first_auth_message) {
138 CloseInternal(INCOMPATIBLE_PROTOCOL);
139 return;
140 }
141
142 DCHECK_EQ(authenticator_->state(), Authenticator::WAITING_MESSAGE);
143 authenticator_->ProcessMessage(first_auth_message);
144 if (authenticator_->state() == Authenticator::REJECTED) {
145 CloseInternal(AuthRejectionReasonToError(
146 authenticator_->rejection_reason()));
147 return;
148 }
149
150 // Send the session-accept message.
151 JingleMessage message(peer_jid_, JingleMessage::SESSION_ACCEPT,
152 session_id_);
153
154 scoped_ptr<buzz::XmlElement> auth_message;
155 if (authenticator_->state() == Authenticator::MESSAGE_READY)
156 auth_message = authenticator_->GetNextMessage();
157
158 message.description.reset(
159 new ContentDescription(CandidateSessionConfig::CreateFrom(config_),
160 auth_message.Pass()));
161 initiate_request_.reset(session_manager_->iq_sender()->SendIq(
162 message.ToXml(),
163 base::Bind(&PepperSession::OnSessionInitiateResponse,
164 base::Unretained(this))));
165
166 // Update state.
167 SetState(CONNECTED);
168
169 if (authenticator_->state() == Authenticator::ACCEPTED) {
170 SetState(AUTHENTICATED);
171 } else {
172 DCHECK_EQ(authenticator_->state(), Authenticator::WAITING_MESSAGE);
173 }
174
175 return;
176}
177
[email protected]22aae952011-09-12 23:47:51178void PepperSession::OnSessionInitiateResponse(
179 const buzz::XmlElement* response) {
180 const std::string& type = response->Attr(buzz::QName("", "type"));
181 if (type != "result") {
182 LOG(ERROR) << "Received error in response to session-initiate message: \""
183 << response->Str()
[email protected]349bea02011-11-05 00:52:27184 << "\". Terminating the session.";
[email protected]22aae952011-09-12 23:47:51185
186 // TODO(sergeyu): There may be different reasons for error
187 // here. Parse the response stanza to find failure reason.
[email protected]a2098232012-02-09 06:19:33188 CloseInternal(PEER_IS_OFFLINE);
[email protected]22aae952011-09-12 23:47:51189 }
190}
191
[email protected]22aae952011-09-12 23:47:51192void PepperSession::CreateStreamChannel(
193 const std::string& name,
194 const StreamChannelCallback& callback) {
195 DCHECK(!channels_[name]);
196
[email protected]5bf52312012-01-20 04:10:52197 scoped_ptr<ChannelAuthenticator> channel_authenticator =
[email protected]f44538512011-11-30 04:43:17198 authenticator_->CreateChannelAuthenticator();
[email protected]e3c97a62012-02-09 03:52:20199 scoped_ptr<StreamTransport> channel =
200 session_manager_->transport_factory_->CreateStreamTransport();
201 channel->Initialize(name, session_manager_->transport_config_,
202 this, channel_authenticator.Pass());
203 channel->Connect(callback);
204 channels_[name] = channel.release();
[email protected]22aae952011-09-12 23:47:51205}
206
207void PepperSession::CreateDatagramChannel(
208 const std::string& name,
209 const DatagramChannelCallback& callback) {
[email protected]e3c97a62012-02-09 03:52:20210 DCHECK(!channels_[name]);
211
212 scoped_ptr<ChannelAuthenticator> channel_authenticator =
213 authenticator_->CreateChannelAuthenticator();
214 scoped_ptr<DatagramTransport> channel =
215 session_manager_->transport_factory_->CreateDatagramTransport();
216 channel->Initialize(name, session_manager_->transport_config_,
217 this, channel_authenticator.Pass());
218 channel->Connect(callback);
219 channels_[name] = channel.release();
[email protected]22aae952011-09-12 23:47:51220}
221
[email protected]f2d6a0032011-11-17 00:48:12222void PepperSession::CancelChannelCreation(const std::string& name) {
223 ChannelsMap::iterator it = channels_.find(name);
224 if (it != channels_.end() && !it->second->is_connected()) {
225 delete it->second;
226 DCHECK(!channels_[name]);
227 }
228}
229
[email protected]22aae952011-09-12 23:47:51230const std::string& PepperSession::jid() {
231 DCHECK(CalledOnValidThread());
232 return peer_jid_;
233}
234
235const CandidateSessionConfig* PepperSession::candidate_config() {
236 DCHECK(CalledOnValidThread());
237 return candidate_config_.get();
238}
239
240const SessionConfig& PepperSession::config() {
241 DCHECK(CalledOnValidThread());
242 return config_;
243}
244
245void PepperSession::set_config(const SessionConfig& config) {
246 DCHECK(CalledOnValidThread());
[email protected]a2098232012-02-09 06:19:33247 DCHECK(!config_is_set_);
248 config_ = config;
249 config_is_set_ = true;
[email protected]22aae952011-09-12 23:47:51250}
251
[email protected]22aae952011-09-12 23:47:51252void PepperSession::Close() {
253 DCHECK(CalledOnValidThread());
254
[email protected]a2098232012-02-09 06:19:33255 CloseInternal(OK);
[email protected]22aae952011-09-12 23:47:51256}
257
[email protected]e3c97a62012-02-09 03:52:20258void PepperSession::OnTransportCandidate(Transport* transport,
259 const cricket::Candidate& candidate) {
260 pending_candidates_.push_back(candidate);
261
262 if (!transport_infos_timer_.IsRunning()) {
263 // Delay sending the new candidates in case we get more candidates
264 // that we can send in one message.
265 transport_infos_timer_.Start(
266 FROM_HERE, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs),
267 this, &PepperSession::SendTransportInfo);
268 }
269}
270
271void PepperSession::OnTransportDeleted(Transport* transport) {
272 ChannelsMap::iterator it = channels_.find(transport->name());
273 DCHECK_EQ(it->second, transport);
274 channels_.erase(it);
275}
276
[email protected]22aae952011-09-12 23:47:51277void PepperSession::OnIncomingMessage(const JingleMessage& message,
278 JingleMessageReply* reply) {
279 DCHECK(CalledOnValidThread());
280
281 if (message.from != peer_jid_) {
282 // Ignore messages received from a different Jid.
283 *reply = JingleMessageReply(JingleMessageReply::INVALID_SID);
284 return;
285 }
286
287 switch (message.action) {
288 case JingleMessage::SESSION_ACCEPT:
289 OnAccept(message, reply);
290 break;
291
[email protected]1bc9c7c2011-12-14 00:13:39292 case JingleMessage::SESSION_INFO:
293 OnSessionInfo(message, reply);
294 break;
295
[email protected]22aae952011-09-12 23:47:51296 case JingleMessage::TRANSPORT_INFO:
297 ProcessTransportInfo(message);
298 break;
299
[email protected]22aae952011-09-12 23:47:51300 case JingleMessage::SESSION_TERMINATE:
301 OnTerminate(message, reply);
302 break;
303
304 default:
305 *reply = JingleMessageReply(JingleMessageReply::UNEXPECTED_REQUEST);
306 }
307}
308
309void PepperSession::OnAccept(const JingleMessage& message,
310 JingleMessageReply* reply) {
311 if (state_ != CONNECTING) {
312 *reply = JingleMessageReply(JingleMessageReply::UNEXPECTED_REQUEST);
313 return;
314 }
315
[email protected]f44538512011-11-30 04:43:17316 const buzz::XmlElement* auth_message =
317 message.description->authenticator_message();
318 if (!auth_message) {
319 DLOG(WARNING) << "Received session-accept without authentication message "
320 << auth_message->Str();
[email protected]a2098232012-02-09 06:19:33321 CloseInternal(INCOMPATIBLE_PROTOCOL);
[email protected]f44538512011-11-30 04:43:17322 return;
323 }
324
325 DCHECK(authenticator_->state() == Authenticator::WAITING_MESSAGE);
326 authenticator_->ProcessMessage(auth_message);
[email protected]f44538512011-11-30 04:43:17327
[email protected]22aae952011-09-12 23:47:51328 if (!InitializeConfigFromDescription(message.description.get())) {
[email protected]a2098232012-02-09 06:19:33329 CloseInternal(INCOMPATIBLE_PROTOCOL);
[email protected]22aae952011-09-12 23:47:51330 return;
331 }
332
[email protected]1bc9c7c2011-12-14 00:13:39333 // In case there is transport information in the accept message.
334 ProcessTransportInfo(message);
335
[email protected]22aae952011-09-12 23:47:51336 SetState(CONNECTED);
337
[email protected]1bc9c7c2011-12-14 00:13:39338 // Process authentication.
339 if (authenticator_->state() == Authenticator::ACCEPTED) {
[email protected]0de37002011-12-06 07:30:16340 SetState(AUTHENTICATED);
[email protected]1bc9c7c2011-12-14 00:13:39341 } else {
342 ProcessAuthenticationStep();
343 }
344}
[email protected]0de37002011-12-06 07:30:16345
[email protected]1bc9c7c2011-12-14 00:13:39346void PepperSession::OnSessionInfo(const JingleMessage& message,
347 JingleMessageReply* reply) {
348 if (message.info.get() &&
349 Authenticator::IsAuthenticatorMessage(message.info.get())) {
350 if (state_ != CONNECTED ||
351 authenticator_->state() != Authenticator::WAITING_MESSAGE) {
352 LOG(WARNING) << "Received unexpected authenticator message "
353 << message.info->Str();
354 *reply = JingleMessageReply(JingleMessageReply::UNEXPECTED_REQUEST);
[email protected]a2098232012-02-09 06:19:33355 CloseInternal(INCOMPATIBLE_PROTOCOL);
[email protected]1bc9c7c2011-12-14 00:13:39356 return;
357 }
358
359 authenticator_->ProcessMessage(message.info.get());
360 ProcessAuthenticationStep();
361 } else {
362 *reply = JingleMessageReply(JingleMessageReply::UNSUPPORTED_INFO);
363 }
[email protected]22aae952011-09-12 23:47:51364}
365
366void PepperSession::ProcessTransportInfo(const JingleMessage& message) {
367 for (std::list<cricket::Candidate>::const_iterator it =
368 message.candidates.begin();
369 it != message.candidates.end(); ++it) {
370 ChannelsMap::iterator channel = channels_.find(it->name());
371 if (channel == channels_.end()) {
372 LOG(WARNING) << "Received candidate for unknown channel " << it->name();
373 continue;
374 }
[email protected]d7c6cc22012-02-05 05:19:27375 channel->second->AddRemoteCandidate(*it);
[email protected]22aae952011-09-12 23:47:51376 }
377}
378
[email protected]22aae952011-09-12 23:47:51379void PepperSession::OnTerminate(const JingleMessage& message,
380 JingleMessageReply* reply) {
[email protected]a2098232012-02-09 06:19:33381 if (state_ != CONNECTING && state_ != CONNECTED && state_ != AUTHENTICATED) {
382 LOG(WARNING) << "Received unexpected session-terminate message.";
383 CloseInternal(INCOMPATIBLE_PROTOCOL);
[email protected]22aae952011-09-12 23:47:51384 return;
385 }
386
[email protected]a2098232012-02-09 06:19:33387 switch (message.reason) {
388 case JingleMessage::SUCCESS:
389 if (state_ == CONNECTING) {
390 error_ = SESSION_REJECTED;
391 } else {
392 error_ = OK;
393 }
394 break;
395 case JingleMessage::DECLINE:
396 error_ = AUTHENTICATION_FAILED;
397 break;
398 case JingleMessage::GENERAL_ERROR:
399 error_ = CHANNEL_CONNECTION_ERROR;
400 break;
401 case JingleMessage::INCOMPATIBLE_PARAMETERS:
402 error_ = INCOMPATIBLE_PROTOCOL;
403 break;
404 default:
405 error_ = UNKNOWN_ERROR;
[email protected]455a61682011-11-12 01:45:19406 }
407
[email protected]a2098232012-02-09 06:19:33408 if (error_ != OK) {
409 SetState(FAILED);
[email protected]1bc9c7c2011-12-14 00:13:39410 } else {
[email protected]a2098232012-02-09 06:19:33411 SetState(CLOSED);
[email protected]1bc9c7c2011-12-14 00:13:39412 }
[email protected]22aae952011-09-12 23:47:51413}
414
415bool PepperSession::InitializeConfigFromDescription(
416 const ContentDescription* description) {
417 DCHECK(description);
418
[email protected]22aae952011-09-12 23:47:51419 if (!description->config()->GetFinalConfig(&config_)) {
420 LOG(ERROR) << "session-accept does not specify configuration";
421 return false;
422 }
423 if (!candidate_config()->IsSupported(config_)) {
424 LOG(ERROR) << "session-accept specifies an invalid configuration";
425 return false;
426 }
427
428 return true;
429}
430
[email protected]1bc9c7c2011-12-14 00:13:39431void PepperSession::ProcessAuthenticationStep() {
432 DCHECK_EQ(state_, CONNECTED);
433
434 if (authenticator_->state() == Authenticator::MESSAGE_READY) {
435 JingleMessage message(peer_jid_, JingleMessage::SESSION_INFO, session_id_);
[email protected]5bf52312012-01-20 04:10:52436 message.info = authenticator_->GetNextMessage();
[email protected]1bc9c7c2011-12-14 00:13:39437 DCHECK(message.info.get());
438
439 session_info_request_.reset(session_manager_->iq_sender()->SendIq(
440 message.ToXml(), base::Bind(
441 &PepperSession::OnSessionInfoResponse,
442 base::Unretained(this))));
443 }
444 DCHECK_NE(authenticator_->state(), Authenticator::MESSAGE_READY);
445
446 if (authenticator_->state() == Authenticator::ACCEPTED) {
447 SetState(AUTHENTICATED);
448 } else if (authenticator_->state() == Authenticator::REJECTED) {
[email protected]a2098232012-02-09 06:19:33449 CloseInternal(AuthRejectionReasonToError(
450 authenticator_->rejection_reason()));
[email protected]1bc9c7c2011-12-14 00:13:39451 }
452}
453
454void PepperSession::OnSessionInfoResponse(const buzz::XmlElement* response) {
455 const std::string& type = response->Attr(buzz::QName("", "type"));
456 if (type != "result") {
457 LOG(ERROR) << "Received error in response to session-info message: \""
458 << response->Str()
459 << "\". Terminating the session.";
[email protected]a2098232012-02-09 06:19:33460 CloseInternal(INCOMPATIBLE_PROTOCOL);
[email protected]1bc9c7c2011-12-14 00:13:39461 }
462}
463
[email protected]349bea02011-11-05 00:52:27464void PepperSession::OnTransportInfoResponse(const buzz::XmlElement* response) {
465 const std::string& type = response->Attr(buzz::QName("", "type"));
466 if (type != "result") {
467 LOG(ERROR) << "Received error in response to session-initiate message: \""
468 << response->Str()
469 << "\". Terminating the session.";
470
471 if (state_ == CONNECTING) {
[email protected]a2098232012-02-09 06:19:33472 CloseInternal(PEER_IS_OFFLINE);
[email protected]349bea02011-11-05 00:52:27473 } else {
474 // Host has disconnected without sending session-terminate message.
[email protected]a2098232012-02-09 06:19:33475 CloseInternal(OK);
[email protected]349bea02011-11-05 00:52:27476 }
477 }
478}
479
[email protected]22aae952011-09-12 23:47:51480void PepperSession::SendTransportInfo() {
481 JingleMessage message(peer_jid_, JingleMessage::TRANSPORT_INFO, session_id_);
482 message.candidates.swap(pending_candidates_);
[email protected]349bea02011-11-05 00:52:27483 transport_info_request_.reset(session_manager_->iq_sender()->SendIq(
484 message.ToXml(), base::Bind(
485 &PepperSession::OnTransportInfoResponse,
486 base::Unretained(this))));
[email protected]22aae952011-09-12 23:47:51487}
488
[email protected]22aae952011-09-12 23:47:51489
[email protected]a2098232012-02-09 06:19:33490void PepperSession::CloseInternal(Error error) {
[email protected]22aae952011-09-12 23:47:51491 DCHECK(CalledOnValidThread());
492
[email protected]a2098232012-02-09 06:19:33493 if (state_ == CONNECTING || state_ == CONNECTED || state_ == AUTHENTICATED) {
494 // Send session-terminate message with the appropriate error code.
495 JingleMessage::Reason reason;
496 switch (error) {
497 case OK:
498 reason = JingleMessage::SUCCESS;
499 break;
500 case SESSION_REJECTED:
501 case AUTHENTICATION_FAILED:
502 reason = JingleMessage::DECLINE;
503 break;
504 case INCOMPATIBLE_PROTOCOL:
505 reason = JingleMessage::INCOMPATIBLE_PARAMETERS;
506 break;
507 default:
508 reason = JingleMessage::GENERAL_ERROR;
509 }
510
511 JingleMessage message(peer_jid_, JingleMessage::SESSION_TERMINATE,
512 session_id_);
513 message.reason = reason;
514 scoped_ptr<IqRequest> terminate_request(
515 session_manager_->iq_sender()->SendIq(
516 message.ToXml(), IqSender::ReplyCallback()));
517 }
518
519 error_ = error;
520
[email protected]22aae952011-09-12 23:47:51521 if (state_ != FAILED && state_ != CLOSED) {
[email protected]a2098232012-02-09 06:19:33522 if (error != OK) {
[email protected]22aae952011-09-12 23:47:51523 SetState(FAILED);
[email protected]a2098232012-02-09 06:19:33524 } else {
[email protected]22aae952011-09-12 23:47:51525 SetState(CLOSED);
[email protected]a2098232012-02-09 06:19:33526 }
[email protected]22aae952011-09-12 23:47:51527 }
528}
529
530void PepperSession::SetState(State new_state) {
531 DCHECK(CalledOnValidThread());
532
533 if (new_state != state_) {
534 DCHECK_NE(state_, CLOSED);
535 DCHECK_NE(state_, FAILED);
536
537 state_ = new_state;
[email protected]9e2a3132011-10-07 05:07:40538 if (!state_change_callback_.is_null())
539 state_change_callback_.Run(new_state);
[email protected]22aae952011-09-12 23:47:51540 }
541}
542
543} // namespace protocol
544} // namespace remoting