| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "remoting/protocol/jingle_chromotocol_server.h" | 5 #include "remoting/protocol/jingle_session_manager.h" |
| 6 | 6 |
| 7 #include "base/message_loop.h" | 7 #include "base/message_loop.h" |
| 8 #include "base/string_number_conversions.h" | 8 #include "base/string_number_conversions.h" |
| 9 #include "remoting/base/constants.h" | 9 #include "remoting/base/constants.h" |
| 10 #include "remoting/jingle_glue/jingle_thread.h" | 10 #include "remoting/jingle_glue/jingle_thread.h" |
| 11 #include "third_party/libjingle/source/talk/p2p/base/constants.h" | 11 #include "third_party/libjingle/source/talk/p2p/base/constants.h" |
| 12 #include "third_party/libjingle/source/talk/p2p/base/transport.h" | 12 #include "third_party/libjingle/source/talk/p2p/base/transport.h" |
| 13 #include "third_party/libjingle/source/talk/xmllite/xmlelement.h" | 13 #include "third_party/libjingle/source/talk/xmllite/xmlelement.h" |
| 14 | 14 |
| 15 using cricket::ContentDescription; | 15 using cricket::ContentDescription; |
| 16 using cricket::SessionDescription; | 16 using cricket::SessionDescription; |
| 17 using cricket::Session; | |
| 18 using cricket::SessionManager; | |
| 19 using buzz::QName; | 17 using buzz::QName; |
| 20 using buzz::XmlElement; | 18 using buzz::XmlElement; |
| 21 | 19 |
| 22 namespace remoting { | 20 namespace remoting { |
| 23 | 21 |
| 22 namespace protocol { |
| 23 |
| 24 namespace { | 24 namespace { |
| 25 | 25 |
| 26 const char kDefaultNs[] = ""; | 26 const char kDefaultNs[] = ""; |
| 27 | 27 |
| 28 const char kChromotingContentName[] = "chromoting"; | 28 const char kChromotingContentName[] = "chromoting"; |
| 29 | 29 |
| 30 // Following constants are used to format session description in XML. | 30 // Following constants are used to format session description in XML. |
| 31 const char kDescriptionTag[] = "description"; | 31 const char kDescriptionTag[] = "description"; |
| 32 const char kControlTag[] = "control"; | 32 const char kControlTag[] = "control"; |
| 33 const char kEventTag[] = "event"; | 33 const char kEventTag[] = "event"; |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 143 } | 143 } |
| 144 } else { | 144 } else { |
| 145 config->codec = ChannelConfig::CODEC_UNDEFINED; | 145 config->codec = ChannelConfig::CODEC_UNDEFINED; |
| 146 } | 146 } |
| 147 | 147 |
| 148 return true; | 148 return true; |
| 149 } | 149 } |
| 150 | 150 |
| 151 } // namespace | 151 } // namespace |
| 152 | 152 |
| 153 ChromotocolContentDescription::ChromotocolContentDescription( | 153 ContentDescription::ContentDescription( |
| 154 const CandidateChromotocolConfig* config) | 154 const CandidateChromotocolConfig* config) |
| 155 : candidate_config_(config) { | 155 : candidate_config_(config) { |
| 156 } | 156 } |
| 157 | 157 |
| 158 ChromotocolContentDescription::~ChromotocolContentDescription() { } | 158 ContentDescription::~ContentDescription() { } |
| 159 | 159 |
| 160 JingleChromotocolServer::JingleChromotocolServer( | 160 JingleSessionManager::JingleSessionManager( |
| 161 JingleThread* jingle_thread) | 161 JingleThread* jingle_thread) |
| 162 : jingle_thread_(jingle_thread), | 162 : jingle_thread_(jingle_thread), |
| 163 session_manager_(NULL), | 163 cricket_session_manager_(NULL), |
| 164 allow_local_ips_(false), | 164 allow_local_ips_(false), |
| 165 closed_(false) { | 165 closed_(false) { |
| 166 DCHECK(jingle_thread_); | 166 DCHECK(jingle_thread_); |
| 167 } | 167 } |
| 168 | 168 |
| 169 void JingleChromotocolServer::Init( | 169 void JingleSessionManager::Init( |
| 170 const std::string& local_jid, | 170 const std::string& local_jid, |
| 171 cricket::SessionManager* session_manager, | 171 cricket::SessionManager* cricket_session_manager, |
| 172 IncomingConnectionCallback* incoming_connection_callback) { | 172 IncomingSessionCallback* incoming_session_callback) { |
| 173 if (MessageLoop::current() != message_loop()) { | 173 if (MessageLoop::current() != message_loop()) { |
| 174 message_loop()->PostTask( | 174 message_loop()->PostTask( |
| 175 FROM_HERE, NewRunnableMethod( | 175 FROM_HERE, NewRunnableMethod( |
| 176 this, &JingleChromotocolServer::Init, | 176 this, &JingleSessionManager::Init, |
| 177 local_jid, session_manager, incoming_connection_callback)); | 177 local_jid, cricket_session_manager, incoming_session_callback)); |
| 178 return; | 178 return; |
| 179 } | 179 } |
| 180 | 180 |
| 181 DCHECK(session_manager); | 181 DCHECK(cricket_session_manager); |
| 182 DCHECK(incoming_connection_callback); | 182 DCHECK(incoming_session_callback); |
| 183 | 183 |
| 184 local_jid_ = local_jid; | 184 local_jid_ = local_jid; |
| 185 incoming_connection_callback_.reset(incoming_connection_callback); | 185 incoming_session_callback_.reset(incoming_session_callback); |
| 186 session_manager_ = session_manager; | 186 cricket_session_manager_ = cricket_session_manager; |
| 187 session_manager_->AddClient(kChromotingXmlNamespace, this); | 187 cricket_session_manager_->AddClient(kChromotingXmlNamespace, this); |
| 188 } | 188 } |
| 189 | 189 |
| 190 JingleChromotocolServer::~JingleChromotocolServer() { | 190 JingleSessionManager::~JingleSessionManager() { |
| 191 DCHECK(closed_); | 191 DCHECK(closed_); |
| 192 } | 192 } |
| 193 | 193 |
| 194 void JingleChromotocolServer::Close(Task* closed_task) { | 194 void JingleSessionManager::Close(Task* closed_task) { |
| 195 if (MessageLoop::current() != message_loop()) { | 195 if (MessageLoop::current() != message_loop()) { |
| 196 message_loop()->PostTask( | 196 message_loop()->PostTask( |
| 197 FROM_HERE, NewRunnableMethod(this, &JingleChromotocolServer::Close, | 197 FROM_HERE, NewRunnableMethod(this, &JingleSessionManager::Close, |
| 198 closed_task)); | 198 closed_task)); |
| 199 return; | 199 return; |
| 200 } | 200 } |
| 201 | 201 |
| 202 if (!closed_) { | 202 if (!closed_) { |
| 203 // Close all connections. | 203 // Close all connections. |
| 204 session_manager_->RemoveClient(kChromotingXmlNamespace); | 204 cricket_session_manager_->RemoveClient(kChromotingXmlNamespace); |
| 205 while (!connections_.empty()) { | 205 while (!sessions_.empty()) { |
| 206 Session* session = connections_.front()->ReleaseSession(); | 206 cricket::Session* session = sessions_.front()->ReleaseSession(); |
| 207 session_manager_->DestroySession(session); | 207 cricket_session_manager_->DestroySession(session); |
| 208 connections_.pop_front(); | 208 sessions_.pop_front(); |
| 209 } | 209 } |
| 210 closed_ = true; | 210 closed_ = true; |
| 211 } | 211 } |
| 212 | 212 |
| 213 closed_task->Run(); | 213 closed_task->Run(); |
| 214 delete closed_task; | 214 delete closed_task; |
| 215 } | 215 } |
| 216 | 216 |
| 217 void JingleChromotocolServer::set_allow_local_ips(bool allow_local_ips) { | 217 void JingleSessionManager::set_allow_local_ips(bool allow_local_ips) { |
| 218 allow_local_ips_ = allow_local_ips; | 218 allow_local_ips_ = allow_local_ips; |
| 219 } | 219 } |
| 220 | 220 |
| 221 scoped_refptr<ChromotocolConnection> JingleChromotocolServer::Connect( | 221 scoped_refptr<protocol::Session> JingleSessionManager::Connect( |
| 222 const std::string& jid, | 222 const std::string& jid, |
| 223 CandidateChromotocolConfig* chromotocol_config, | 223 CandidateChromotocolConfig* chromotocol_config, |
| 224 ChromotocolConnection::StateChangeCallback* state_change_callback) { | 224 protocol::Session::StateChangeCallback* state_change_callback) { |
| 225 // Can be called from any thread. | 225 // Can be called from any thread. |
| 226 scoped_refptr<JingleChromotocolConnection> connection( | 226 scoped_refptr<JingleSession> jingle_session( |
| 227 new JingleChromotocolConnection(this)); | 227 new JingleSession(this)); |
| 228 connection->set_candidate_config(chromotocol_config); | 228 jingle_session->set_candidate_config(chromotocol_config); |
| 229 message_loop()->PostTask( | 229 message_loop()->PostTask( |
| 230 FROM_HERE, NewRunnableMethod(this, &JingleChromotocolServer::DoConnect, | 230 FROM_HERE, NewRunnableMethod(this, &JingleSessionManager::DoConnect, |
| 231 connection, jid, | 231 jingle_session, jid, |
| 232 state_change_callback)); | 232 state_change_callback)); |
| 233 return connection; | 233 return jingle_session; |
| 234 } | 234 } |
| 235 | 235 |
| 236 void JingleChromotocolServer::DoConnect( | 236 void JingleSessionManager::DoConnect( |
| 237 scoped_refptr<JingleChromotocolConnection> connection, | 237 scoped_refptr<JingleSession> jingle_session, |
| 238 const std::string& jid, | 238 const std::string& jid, |
| 239 ChromotocolConnection::StateChangeCallback* state_change_callback) { | 239 protocol::Session::StateChangeCallback* state_change_callback) { |
| 240 DCHECK_EQ(message_loop(), MessageLoop::current()); | 240 DCHECK_EQ(message_loop(), MessageLoop::current()); |
| 241 Session* session = session_manager_->CreateSession( | 241 cricket::Session* cricket_session = cricket_session_manager_->CreateSession( |
| 242 local_jid_, kChromotingXmlNamespace); | 242 local_jid_, kChromotingXmlNamespace); |
| 243 | 243 |
| 244 // Initialize connection object before we send initiate stanza. | 244 // Initialize connection object before we send initiate stanza. |
| 245 connection->SetStateChangeCallback(state_change_callback); | 245 jingle_session->SetStateChangeCallback(state_change_callback); |
| 246 connection->Init(session); | 246 jingle_session->Init(cricket_session); |
| 247 connections_.push_back(connection); | 247 sessions_.push_back(jingle_session); |
| 248 | 248 |
| 249 session->Initiate( | 249 cricket_session->Initiate( |
| 250 jid, CreateSessionDescription(connection->candidate_config()->Clone())); | 250 jid, |
| 251 CreateSessionDescription(jingle_session->candidate_config()->Clone())); |
| 251 } | 252 } |
| 252 | 253 |
| 253 JingleThread* JingleChromotocolServer::jingle_thread() { | 254 JingleThread* JingleSessionManager::jingle_thread() { |
| 254 return jingle_thread_; | 255 return jingle_thread_; |
| 255 } | 256 } |
| 256 | 257 |
| 257 MessageLoop* JingleChromotocolServer::message_loop() { | 258 MessageLoop* JingleSessionManager::message_loop() { |
| 258 return jingle_thread_->message_loop(); | 259 return jingle_thread_->message_loop(); |
| 259 } | 260 } |
| 260 | 261 |
| 261 void JingleChromotocolServer::OnSessionCreate( | 262 void JingleSessionManager::OnSessionCreate( |
| 262 Session* session, bool incoming) { | 263 cricket::Session* cricket_session, bool incoming) { |
| 263 DCHECK_EQ(message_loop(), MessageLoop::current()); | 264 DCHECK_EQ(message_loop(), MessageLoop::current()); |
| 264 | 265 |
| 265 // Allow local connections if neccessary. | 266 // Allow local connections if neccessary. |
| 266 session->set_allow_local_ips(allow_local_ips_); | 267 cricket_session->set_allow_local_ips(allow_local_ips_); |
| 267 | 268 |
| 268 // If this is an outcoming session the connection object is already | 269 // If this is an outcoming session the connection object is already |
| 269 // created. | 270 // created. |
| 270 if (incoming) { | 271 if (incoming) { |
| 271 JingleChromotocolConnection* connection = | 272 JingleSession* jingle_session = new JingleSession(this); |
| 272 new JingleChromotocolConnection(this); | 273 sessions_.push_back(make_scoped_refptr(jingle_session)); |
| 273 connections_.push_back(make_scoped_refptr(connection)); | 274 jingle_session->Init(cricket_session); |
| 274 connection->Init(session); | |
| 275 } | 275 } |
| 276 } | 276 } |
| 277 | 277 |
| 278 void JingleChromotocolServer::OnSessionDestroy(Session* session) { | 278 void JingleSessionManager::OnSessionDestroy(cricket::Session* cricket_session) { |
| 279 DCHECK_EQ(message_loop(), MessageLoop::current()); | 279 DCHECK_EQ(message_loop(), MessageLoop::current()); |
| 280 | 280 |
| 281 std::list<scoped_refptr<JingleChromotocolConnection> >::iterator it; | 281 std::list<scoped_refptr<JingleSession> >::iterator it; |
| 282 for (it = connections_.begin(); it != connections_.end(); ++it) { | 282 for (it = sessions_.begin(); it != sessions_.end(); ++it) { |
| 283 if ((*it)->HasSession(session)) { | 283 if ((*it)->HasSession(cricket_session)) { |
| 284 (*it)->ReleaseSession(); | 284 (*it)->ReleaseSession(); |
| 285 connections_.erase(it); | 285 sessions_.erase(it); |
| 286 return; | 286 return; |
| 287 } | 287 } |
| 288 } | 288 } |
| 289 } | 289 } |
| 290 | 290 |
| 291 void JingleChromotocolServer::AcceptConnection( | 291 void JingleSessionManager::AcceptConnection( |
| 292 JingleChromotocolConnection* connection, | 292 JingleSession* jingle_session, |
| 293 Session* session) { | 293 cricket::Session* cricket_session) { |
| 294 DCHECK_EQ(message_loop(), MessageLoop::current()); | 294 DCHECK_EQ(message_loop(), MessageLoop::current()); |
| 295 | 295 |
| 296 // Reject connection if we are closed. | 296 // Reject connection if we are closed. |
| 297 if (closed_) { | 297 if (closed_) { |
| 298 session->Reject(cricket::STR_TERMINATE_DECLINE); | 298 cricket_session->Reject(cricket::STR_TERMINATE_DECLINE); |
| 299 return; | 299 return; |
| 300 } | 300 } |
| 301 | 301 |
| 302 const cricket::SessionDescription* session_description = | 302 const cricket::SessionDescription* session_description = |
| 303 session->remote_description(); | 303 cricket_session->remote_description(); |
| 304 const cricket::ContentInfo* content = | 304 const cricket::ContentInfo* content = |
| 305 session_description->FirstContentByType(kChromotingXmlNamespace); | 305 session_description->FirstContentByType(kChromotingXmlNamespace); |
| 306 | 306 |
| 307 CHECK(content); | 307 CHECK(content); |
| 308 | 308 |
| 309 const ChromotocolContentDescription* content_description = | 309 const ContentDescription* content_description = |
| 310 static_cast<const ChromotocolContentDescription*>(content->description); | 310 static_cast<const ContentDescription*>(content->description); |
| 311 connection->set_candidate_config(content_description->config()->Clone()); | 311 jingle_session->set_candidate_config(content_description->config()->Clone()); |
| 312 | 312 |
| 313 IncomingConnectionResponse response = ChromotocolServer::DECLINE; | 313 IncomingSessionResponse response = protocol::SessionManager::DECLINE; |
| 314 | 314 |
| 315 // Always reject connection if there is no callback. | 315 // Always reject connection if there is no callback. |
| 316 if (incoming_connection_callback_.get()) | 316 if (incoming_session_callback_.get()) |
| 317 incoming_connection_callback_->Run(connection, &response); | 317 incoming_session_callback_->Run(jingle_session, &response); |
| 318 | 318 |
| 319 switch (response) { | 319 switch (response) { |
| 320 case ChromotocolServer::ACCEPT: { | 320 case protocol::SessionManager::ACCEPT: { |
| 321 // Connection must be configured by the callback. | 321 // Connection must be configured by the callback. |
| 322 DCHECK(connection->config()); | 322 DCHECK(jingle_session->config()); |
| 323 CandidateChromotocolConfig* candidate_config = | 323 CandidateChromotocolConfig* candidate_config = |
| 324 CandidateChromotocolConfig::CreateFrom(connection->config()); | 324 CandidateChromotocolConfig::CreateFrom(jingle_session->config()); |
| 325 session->Accept(CreateSessionDescription(candidate_config)); | 325 cricket_session->Accept(CreateSessionDescription(candidate_config)); |
| 326 break; | 326 break; |
| 327 } | 327 } |
| 328 | 328 |
| 329 case ChromotocolServer::INCOMPATIBLE: { | 329 case protocol::SessionManager::INCOMPATIBLE: { |
| 330 session->Reject(cricket::STR_TERMINATE_INCOMPATIBLE_PARAMETERS); | 330 cricket_session->Reject(cricket::STR_TERMINATE_INCOMPATIBLE_PARAMETERS); |
| 331 break; | 331 break; |
| 332 } | 332 } |
| 333 | 333 |
| 334 case ChromotocolServer::DECLINE: { | 334 case protocol::SessionManager::DECLINE: { |
| 335 session->Reject(cricket::STR_TERMINATE_DECLINE); | 335 cricket_session->Reject(cricket::STR_TERMINATE_DECLINE); |
| 336 break; | 336 break; |
| 337 } | 337 } |
| 338 | 338 |
| 339 default: { | 339 default: { |
| 340 NOTREACHED(); | 340 NOTREACHED(); |
| 341 } | 341 } |
| 342 } | 342 } |
| 343 } | 343 } |
| 344 | 344 |
| 345 // Parse content description generated by WriteContent(). | 345 // Parse content description generated by WriteContent(). |
| 346 bool JingleChromotocolServer::ParseContent( | 346 bool JingleSessionManager::ParseContent( |
| 347 cricket::SignalingProtocol protocol, | 347 cricket::SignalingProtocol protocol, |
| 348 const XmlElement* element, | 348 const XmlElement* element, |
| 349 const cricket::ContentDescription** content, | 349 const cricket::ContentDescription** content, |
| 350 cricket::ParseError* error) { | 350 cricket::ParseError* error) { |
| 351 if (element->Name() == QName(kChromotingXmlNamespace, kDescriptionTag)) { | 351 if (element->Name() == QName(kChromotingXmlNamespace, kDescriptionTag)) { |
| 352 scoped_ptr<CandidateChromotocolConfig> config( | 352 scoped_ptr<CandidateChromotocolConfig> config( |
| 353 CandidateChromotocolConfig::CreateEmpty()); | 353 CandidateChromotocolConfig::CreateEmpty()); |
| 354 const XmlElement* child = NULL; | 354 const XmlElement* child = NULL; |
| 355 | 355 |
| 356 // <control> tags. | 356 // <control> tags. |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 398 &height)) { | 398 &height)) { |
| 399 return false; | 399 return false; |
| 400 } | 400 } |
| 401 ScreenResolution resolution(width, height); | 401 ScreenResolution resolution(width, height); |
| 402 if (!resolution.IsValid()) { | 402 if (!resolution.IsValid()) { |
| 403 return false; | 403 return false; |
| 404 } | 404 } |
| 405 | 405 |
| 406 config->SetInitialResolution(resolution); | 406 config->SetInitialResolution(resolution); |
| 407 | 407 |
| 408 *content = new ChromotocolContentDescription(config.release()); | 408 *content = new ContentDescription(config.release()); |
| 409 return true; | 409 return true; |
| 410 } | 410 } |
| 411 LOG(ERROR) << "Invalid description: " << element->Str(); | 411 LOG(ERROR) << "Invalid description: " << element->Str(); |
| 412 return false; | 412 return false; |
| 413 } | 413 } |
| 414 | 414 |
| 415 // WriteContent creates content description for chromoting session. The | 415 // WriteContent creates content description for chromoting session. The |
| 416 // description looks as follows: | 416 // description looks as follows: |
| 417 // <description xmlns="google:remoting"> | 417 // <description xmlns="google:remoting"> |
| 418 // <control transport="stream" version="1" /> | 418 // <control transport="stream" version="1" /> |
| 419 // <event transport="datagram" version="1" /> | 419 // <event transport="datagram" version="1" /> |
| 420 // <video transport="srtp" codec="vp8" version="1" /> | 420 // <video transport="srtp" codec="vp8" version="1" /> |
| 421 // <initial-resolution width="800" height="600" /> | 421 // <initial-resolution width="800" height="600" /> |
| 422 // </description> | 422 // </description> |
| 423 // | 423 // |
| 424 bool JingleChromotocolServer::WriteContent( | 424 bool JingleSessionManager::WriteContent( |
| 425 cricket::SignalingProtocol protocol, | 425 cricket::SignalingProtocol protocol, |
| 426 const cricket::ContentDescription* content, | 426 const cricket::ContentDescription* content, |
| 427 XmlElement** elem, | 427 XmlElement** elem, |
| 428 cricket::WriteError* error) { | 428 cricket::WriteError* error) { |
| 429 const ChromotocolContentDescription* desc = | 429 const ContentDescription* desc = |
| 430 static_cast<const ChromotocolContentDescription*>(content); | 430 static_cast<const ContentDescription*>(content); |
| 431 | 431 |
| 432 XmlElement* root = new XmlElement( | 432 XmlElement* root = new XmlElement( |
| 433 QName(kChromotingXmlNamespace, kDescriptionTag), true); | 433 QName(kChromotingXmlNamespace, kDescriptionTag), true); |
| 434 | 434 |
| 435 const CandidateChromotocolConfig* config = desc->config(); | 435 const CandidateChromotocolConfig* config = desc->config(); |
| 436 std::vector<ChannelConfig>::const_iterator it; | 436 std::vector<ChannelConfig>::const_iterator it; |
| 437 | 437 |
| 438 for (it = config->control_configs().begin(); | 438 for (it = config->control_configs().begin(); |
| 439 it != config->control_configs().end(); ++it) { | 439 it != config->control_configs().end(); ++it) { |
| 440 root->AddElement(FormatChannelConfig(*it, kControlTag)); | 440 root->AddElement(FormatChannelConfig(*it, kControlTag)); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 457 config->initial_resolution().width)); | 457 config->initial_resolution().width)); |
| 458 resolution_tag->AddAttr(QName(kDefaultNs, kHeightAttr), | 458 resolution_tag->AddAttr(QName(kDefaultNs, kHeightAttr), |
| 459 base::IntToString( | 459 base::IntToString( |
| 460 config->initial_resolution().height)); | 460 config->initial_resolution().height)); |
| 461 root->AddElement(resolution_tag); | 461 root->AddElement(resolution_tag); |
| 462 | 462 |
| 463 *elem = root; | 463 *elem = root; |
| 464 return true; | 464 return true; |
| 465 } | 465 } |
| 466 | 466 |
| 467 SessionDescription* JingleChromotocolServer::CreateSessionDescription( | 467 SessionDescription* JingleSessionManager::CreateSessionDescription( |
| 468 const CandidateChromotocolConfig* config) { | 468 const CandidateChromotocolConfig* config) { |
| 469 SessionDescription* desc = new SessionDescription(); | 469 SessionDescription* desc = new SessionDescription(); |
| 470 desc->AddContent(JingleChromotocolConnection::kChromotingContentName, | 470 desc->AddContent(JingleSession::kChromotingContentName, |
| 471 kChromotingXmlNamespace, | 471 kChromotingXmlNamespace, |
| 472 new ChromotocolContentDescription(config)); | 472 new ContentDescription(config)); |
| 473 return desc; | 473 return desc; |
| 474 } | 474 } |
| 475 | 475 |
| 476 } // namespace protocol |
| 477 |
| 476 } // namespace remoting | 478 } // namespace remoting |
| OLD | NEW |