Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 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/client/plugin/pepper_port_allocator_session.h" | |
| 6 | |
| 7 #include <map> | |
| 8 | |
| 9 #include "base/callback.h" | |
| 10 #include "base/logging.h" | |
| 11 #include "base/stringprintf.h" | |
| 12 #include "base/synchronization/lock.h" | |
| 13 #include "ppapi/c/pp_errors.h" | |
| 14 #include "ppapi/c/trusted/ppb_url_loader_trusted.h" | |
| 15 #include "ppapi/cpp/url_loader.h" | |
| 16 #include "ppapi/cpp/url_request_info.h" | |
| 17 #include "ppapi/cpp/url_response_info.h" | |
| 18 #include "remoting/jingle_glue/http_port_allocator.h" | |
| 19 #include "remoting/client/plugin/chromoting_instance.h" | |
| 20 #include "remoting/client/plugin/pepper_entrypoints.h" | |
| 21 #include "remoting/client/plugin/pepper_util.h" | |
| 22 | |
| 23 namespace { | |
| 24 | |
| 25 static const int kHostPort = 80; | |
| 26 static const int kNumRetries = 5; | |
| 27 static const std::string kCreateSessionURL = "/create_session"; | |
| 28 | |
| 29 // Define a SessionFactory in the anonymouse namespace so we have a | |
| 30 // shorter name. | |
| 31 class SessionFactory : public remoting::PortAllocatorSessionFactory { | |
|
Sergey Ulanov
2011/03/07 12:58:45
TODO to move this to a separate file?
Alpha Left Google
2011/03/07 18:02:29
Done.
| |
| 32 public: | |
| 33 SessionFactory(remoting::ChromotingInstance* instance, | |
| 34 MessageLoop* message_loop) | |
| 35 : instance_(instance), | |
| 36 jingle_message_loop_(message_loop) { | |
| 37 } | |
| 38 | |
| 39 virtual cricket::PortAllocatorSession* CreateSession( | |
| 40 cricket::BasicPortAllocator* allocator, | |
| 41 const std::string& name, | |
| 42 const std::string& session_type, | |
| 43 const std::vector<talk_base::SocketAddress>& stun_hosts, | |
| 44 const std::vector<std::string>& relay_hosts, | |
| 45 const std::string& relay, | |
| 46 const std::string& agent) { | |
| 47 return new remoting::PepperPortAllocatorSession( | |
| 48 instance_, jingle_message_loop_, allocator, name, session_type, | |
| 49 stun_hosts, relay_hosts, relay, agent); | |
| 50 } | |
| 51 | |
| 52 private: | |
| 53 remoting::ChromotingInstance* instance_; | |
| 54 | |
| 55 // Message loop that jingle runs on. | |
| 56 MessageLoop* jingle_message_loop_; | |
| 57 | |
| 58 DISALLOW_COPY_AND_ASSIGN(SessionFactory); | |
| 59 }; | |
| 60 | |
| 61 typedef Callback3<bool, int, const std::string&>::Type FetchCallback; | |
| 62 | |
| 63 // Helper routine to remove whitespace from the ends of a string. | |
| 64 void Trim(std::string& str) { | |
|
Sergey Ulanov
2011/03/07 12:58:45
Can we use base::TrimString() instead?
Alpha Left Google
2011/03/07 18:02:29
Done.
| |
| 65 size_t first = str.find_first_not_of(" \t\r\n"); | |
| 66 if (first == std::string::npos) { | |
| 67 str.clear(); | |
| 68 return; | |
| 69 } | |
| 70 | |
| 71 ASSERT(str.find_last_not_of(" \t\r\n") != std::string::npos); | |
| 72 } | |
| 73 | |
| 74 // Parses the lines in the result of the HTTP request that are of the form | |
| 75 // 'a=b' and returns them in a map. | |
| 76 typedef std::map<std::string, std::string> StringMap; | |
| 77 void ParseMap(const std::string& string, StringMap& map) { | |
| 78 size_t start_of_line = 0; | |
| 79 size_t end_of_line = 0; | |
| 80 | |
| 81 for (;;) { // for each line | |
| 82 start_of_line = string.find_first_not_of("\r\n", end_of_line); | |
| 83 if (start_of_line == std::string::npos) | |
| 84 break; | |
| 85 | |
| 86 end_of_line = string.find_first_of("\r\n", start_of_line); | |
|
Sergey Ulanov
2011/03/07 12:58:45
Could it be that the result we receive from the se
Alpha Left Google
2011/03/07 18:02:29
HTTP use \r\n as line breaks and this is from libj
| |
| 87 if (end_of_line == std::string::npos) { | |
| 88 end_of_line = string.length(); | |
| 89 } | |
| 90 | |
| 91 size_t equals = string.find('=', start_of_line); | |
|
Sergey Ulanov
2011/03/07 12:58:45
equals_pos?
Alpha Left Google
2011/03/07 18:02:29
I'm reluctant to change this at the moment since t
| |
| 92 if ((equals >= end_of_line) || (equals == std::string::npos)) | |
| 93 continue; | |
| 94 | |
| 95 std::string key(string, start_of_line, equals - start_of_line); | |
| 96 std::string value(string, equals + 1, end_of_line - equals - 1); | |
| 97 | |
| 98 Trim(key); | |
|
Alpha Left Google
2011/03/07 18:02:29
I did change this to base::TrimString().
| |
| 99 Trim(value); | |
| 100 | |
| 101 if ((key.size() > 0) && (value.size() > 0)) | |
| 102 map[key] = value; | |
| 103 } | |
| 104 } | |
| 105 | |
| 106 } // namespace | |
| 107 | |
| 108 namespace remoting { | |
| 109 | |
| 110 // A URL Fetcher using Pepper. | |
| 111 class PepperURLFetcher { | |
|
Sergey Ulanov
2011/03/07 12:58:45
TODO to move this to a separate file?
Alpha Left Google
2011/03/07 18:02:29
Done.
| |
| 112 public: | |
| 113 PepperURLFetcher() : fetch_callback_(NULL) { | |
| 114 callback_factory_.Initialize(this); | |
| 115 } | |
| 116 | |
| 117 void Start(const pp::Instance& instance, | |
| 118 pp::URLRequestInfo request, | |
| 119 FetchCallback* fetch_callback) { | |
| 120 loader_ = pp::URLLoader(instance); | |
| 121 | |
| 122 // Grant access to external origins. | |
| 123 const struct PPB_URLLoaderTrusted* trusted_loader_interface = | |
| 124 reinterpret_cast<const PPB_URLLoaderTrusted*>( | |
| 125 PPP_GetBrowserInterface(PPB_URLLOADERTRUSTED_INTERFACE)); | |
| 126 trusted_loader_interface->GrantUniversalAccess( | |
| 127 loader_.pp_resource()); | |
| 128 | |
| 129 fetch_callback_.reset(fetch_callback); | |
| 130 | |
| 131 pp::CompletionCallback callback = | |
| 132 callback_factory_.NewCallback(&PepperURLFetcher::DidOpen); | |
| 133 int rv = loader_.Open(request, callback); | |
| 134 if (rv != PP_ERROR_WOULDBLOCK) | |
| 135 callback.Run(rv); | |
| 136 } | |
| 137 | |
| 138 private: | |
| 139 void ReadMore() { | |
| 140 pp::CompletionCallback callback = | |
| 141 callback_factory_.NewCallback(&PepperURLFetcher::DidRead); | |
| 142 int rv = loader_.ReadResponseBody(buf_, sizeof(buf_), callback); | |
| 143 if (rv != PP_ERROR_WOULDBLOCK) | |
| 144 callback.Run(rv); | |
| 145 } | |
| 146 | |
| 147 void DidOpen(int32_t result) { | |
| 148 if (result == PP_OK) { | |
| 149 ReadMore(); | |
| 150 } else { | |
| 151 DidFinish(result); | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 void DidRead(int32_t result) { | |
| 156 if (result > 0) { | |
| 157 data_.append(buf_, result); | |
| 158 ReadMore(); | |
| 159 } else { | |
| 160 DidFinish(result); | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 void DidFinish(int32_t result) { | |
| 165 if (fetch_callback_.get()) { | |
| 166 bool success = result == PP_OK; | |
| 167 int status_code = 0; | |
| 168 if (success) | |
| 169 status_code = loader_.GetResponseInfo().GetStatusCode(); | |
| 170 fetch_callback_->Run(success, status_code, data_); | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 pp::CompletionCallbackFactory<PepperURLFetcher> callback_factory_; | |
| 175 pp::URLLoader loader_; | |
| 176 scoped_ptr<FetchCallback> fetch_callback_; | |
| 177 char buf_[4096]; | |
| 178 std::string data_; | |
| 179 }; | |
| 180 | |
| 181 // A helper class to do HTTP request on the pepper thread and then delegate the | |
| 182 // result to PepperPortAllocatorSession on jingle thread safely. | |
| 183 class PepperCreateSessionTask | |
| 184 : public base::RefCountedThreadSafe<PepperCreateSessionTask> { | |
| 185 public: | |
| 186 PepperCreateSessionTask( | |
| 187 MessageLoop* jingle_message_loop, | |
| 188 PepperPortAllocatorSession* allocator_session, | |
| 189 ChromotingInstance* instance, | |
| 190 const std::string& host, | |
| 191 int port, | |
| 192 const std::string& relay_token, | |
| 193 const std::string& session_type, | |
| 194 const std::string& name) | |
| 195 : jingle_message_loop_(jingle_message_loop), | |
| 196 allocator_session_(allocator_session), | |
| 197 instance_(instance), | |
| 198 host_(host), | |
| 199 port_(port), | |
| 200 relay_token_(relay_token), | |
| 201 session_type_(session_type), | |
| 202 name_(name) { | |
| 203 } | |
| 204 | |
| 205 // Start doing the request. The request will start on the pepper thread. | |
| 206 void Start() { | |
| 207 if (!instance_->CurrentlyOnPluginThread()) { | |
| 208 RunTaskOnPluginThread( | |
| 209 NewRunnableMethod(this, &PepperCreateSessionTask::Start)); | |
| 210 return; | |
| 211 } | |
| 212 | |
| 213 // Perform the request here. | |
| 214 std::string url = base::StringPrintf("http://%s:%d/create_session", | |
| 215 host_.c_str(), port_); | |
| 216 pp::URLRequestInfo request(instance_); | |
| 217 request.SetURL(url.c_str()); | |
| 218 request.SetMethod("GET"); | |
| 219 request.SetHeaders(base::StringPrintf( | |
| 220 "X-Talk-Google-Relay-Auth: %s\r\n" | |
| 221 "X-Google-Relay-Auth: %s\r\n" | |
| 222 "X-Session-Type: %s\r\n" | |
| 223 "X-Stream-Type: %s\r\n", | |
| 224 relay_token_.c_str(), relay_token_.c_str(), session_type_.c_str(), | |
| 225 name_.c_str())); | |
| 226 | |
| 227 url_fetcher_.reset(new PepperURLFetcher()); | |
| 228 url_fetcher_->Start( | |
| 229 *instance_, request, | |
| 230 NewCallback(this, &PepperCreateSessionTask::OnRequestDone)); | |
| 231 } | |
| 232 | |
| 233 // Detach this task. This class will not access PepperPortAllocatorSession | |
| 234 // anymore. | |
| 235 void Detach() { | |
| 236 // Set the pointers to zero. | |
| 237 { | |
| 238 base::AutoLock auto_lock(lock_); | |
| 239 jingle_message_loop_ = NULL; | |
| 240 allocator_session_ = NULL; | |
| 241 instance_ = NULL; | |
| 242 } | |
| 243 | |
| 244 // IMPORTANT! | |
| 245 // Post a dummy task so that we destruct this class and associated pepper | |
| 246 // resources on pepper thread. | |
| 247 RunTaskOnPluginThread( | |
| 248 NewRunnableMethod(this, &PepperCreateSessionTask::Dummy)); | |
|
Sergey Ulanov
2011/03/07 12:58:45
This doesn't guarantee that the object will be des
Alpha Left Google
2011/03/07 18:02:29
sounds good.
| |
| 249 } | |
| 250 | |
| 251 private: | |
| 252 void OnRequestDone(bool success, int status_code, | |
| 253 const std::string& response) { | |
| 254 // IMPORTANT! | |
| 255 // This method is called on the pepper thread and we want the response to | |
| 256 // be delegated to the jingle thread. However jignle thread might have | |
| 257 // been destroyed and |allocator_session_| might be dangling too. So we | |
| 258 // put a lock here to access |jingle_message_loop_| and then do the | |
| 259 // remaining work on the jingle thread. | |
| 260 base::AutoLock auto_lock(lock_); | |
|
Sergey Ulanov
2011/03/07 12:58:45
Can we use MessageLoopProxy here? You would not ne
Alpha Left Google
2011/03/07 18:02:29
I believe we have to use message loop proxy everyw
| |
| 261 if (!jingle_message_loop_) | |
| 262 return; | |
| 263 | |
| 264 jingle_message_loop_->PostTask( | |
| 265 FROM_HERE, | |
| 266 NewRunnableMethod(this, &PepperCreateSessionTask::DelegateRequestDone, | |
| 267 success, status_code, response)); | |
| 268 } | |
| 269 | |
| 270 void DelegateRequestDone(bool success, int status_code, | |
| 271 const std::string& response) { | |
| 272 if (!allocator_session_) | |
| 273 return; | |
| 274 allocator_session_->OnRequestDone(success, status_code, response); | |
| 275 } | |
| 276 | |
| 277 // This is a dummy function just so that we can deref this class on pepper | |
| 278 // thread. | |
| 279 void Dummy() {} | |
| 280 | |
| 281 // Protects |jingle_message_loop_|. | |
| 282 base::Lock lock_; | |
| 283 | |
| 284 MessageLoop* jingle_message_loop_; | |
| 285 PepperPortAllocatorSession* allocator_session_; | |
| 286 ChromotingInstance* instance_; | |
| 287 std::string host_; | |
| 288 int port_; | |
| 289 std::string relay_token_; | |
| 290 std::string session_type_; | |
| 291 std::string name_; | |
| 292 | |
| 293 // Pepper resources for URL fetching. | |
| 294 scoped_ptr<PepperURLFetcher> url_fetcher_; | |
| 295 | |
| 296 DISALLOW_COPY_AND_ASSIGN(PepperCreateSessionTask); | |
| 297 }; | |
| 298 | |
| 299 PortAllocatorSessionFactory* PepperPortAllocatorSession::CreateFactory( | |
| 300 ChromotingInstance* instance) { | |
| 301 return new SessionFactory(instance, MessageLoop::current()); | |
| 302 } | |
| 303 | |
| 304 PepperPortAllocatorSession::PepperPortAllocatorSession( | |
| 305 ChromotingInstance* instance, | |
| 306 MessageLoop* message_loop, | |
| 307 cricket::BasicPortAllocator* allocator, | |
| 308 const std::string &name, | |
| 309 const std::string& session_type, | |
| 310 const std::vector<talk_base::SocketAddress>& stun_hosts, | |
| 311 const std::vector<std::string>& relay_hosts, | |
| 312 const std::string& relay_token, | |
| 313 const std::string& user_agent) | |
| 314 : BasicPortAllocatorSession(allocator, name, session_type), | |
| 315 instance_(instance), jingle_message_loop_(message_loop), | |
| 316 relay_hosts_(relay_hosts), stun_hosts_(stun_hosts), | |
| 317 relay_token_(relay_token), agent_(user_agent), attempts_(0) { | |
| 318 } | |
| 319 | |
| 320 PepperPortAllocatorSession::~PepperPortAllocatorSession() { | |
| 321 if (create_session_task_) { | |
| 322 create_session_task_->Detach(); | |
| 323 create_session_task_ = NULL; | |
| 324 } | |
| 325 } | |
| 326 | |
| 327 void PepperPortAllocatorSession::GetPortConfigurations() { | |
| 328 // Creating relay sessions can take time and is done asynchronously. | |
| 329 // Creating stun sessions could also take time and could be done aysnc also, | |
| 330 // but for now is done here and added to the initial config. Note any later | |
| 331 // configs will have unresolved stun ips and will be discarded by the | |
| 332 // AllocationSequence. | |
| 333 cricket::PortConfiguration* config = | |
| 334 new cricket::PortConfiguration(stun_hosts_[0], "", "", ""); | |
| 335 ConfigReady(config); | |
| 336 TryCreateRelaySession(); | |
| 337 } | |
| 338 | |
| 339 void PepperPortAllocatorSession::TryCreateRelaySession() { | |
| 340 if (attempts_ == kNumRetries) { | |
| 341 LOG(ERROR) << "PepperPortAllocator: maximum number of requests reached; " | |
| 342 << "giving up on relay."; | |
| 343 return; | |
| 344 } | |
| 345 | |
| 346 if (relay_hosts_.size() == 0) { | |
| 347 LOG(ERROR) << "PepperPortAllocator: no relay hosts configured."; | |
| 348 return; | |
| 349 } | |
| 350 | |
| 351 // Choose the next host to try. | |
| 352 std::string host = relay_hosts_[attempts_ % relay_hosts_.size()]; | |
| 353 attempts_++; | |
| 354 LOG(INFO) << "PepperPortAllocator: sending to relay host " << host; | |
| 355 if (relay_token_.empty()) { | |
| 356 LOG(WARNING) << "No relay auth token found."; | |
| 357 } | |
| 358 | |
| 359 SendSessionRequest(host, kHostPort); | |
| 360 } | |
| 361 | |
| 362 void PepperPortAllocatorSession::SendSessionRequest(const std::string& host, | |
| 363 int port) { | |
| 364 // Destroy the old PepperCreateSessionTask first. | |
| 365 if (create_session_task_) { | |
| 366 create_session_task_->Detach(); | |
| 367 create_session_task_ = NULL; | |
| 368 } | |
| 369 | |
| 370 // Construct a new one and start it. OnRequestDone() will be called when | |
| 371 // task has completed. | |
| 372 create_session_task_ = new PepperCreateSessionTask( | |
| 373 jingle_message_loop_, this, instance_, host, port, relay_token_, | |
| 374 session_type(), name()); | |
| 375 create_session_task_->Start(); | |
| 376 } | |
| 377 | |
| 378 void PepperPortAllocatorSession::OnRequestDone(bool success, | |
| 379 int status_code, | |
| 380 const std::string& response) { | |
| 381 DCHECK_EQ(jingle_message_loop_, MessageLoop::current()); | |
| 382 | |
| 383 if (!success || status_code != 200) { | |
| 384 LOG(WARNING) << "PepperPortAllocatorSession: failed."; | |
| 385 TryCreateRelaySession(); | |
| 386 return; | |
| 387 } | |
| 388 | |
| 389 LOG(INFO) << "PepperPortAllocatorSession: request succeeded."; | |
| 390 ReceiveSessionResponse(response); | |
| 391 } | |
| 392 | |
| 393 void PepperPortAllocatorSession::ReceiveSessionResponse( | |
| 394 const std::string& response) { | |
| 395 StringMap map; | |
| 396 ParseMap(response, map); | |
| 397 | |
| 398 std::string username = map["username"]; | |
| 399 std::string password = map["password"]; | |
| 400 std::string magic_cookie = map["magic_cookie"]; | |
| 401 | |
| 402 std::string relay_ip = map["relay.ip"]; | |
| 403 std::string relay_udp_port = map["relay.udp_port"]; | |
| 404 std::string relay_tcp_port = map["relay.tcp_port"]; | |
| 405 std::string relay_ssltcp_port = map["relay.ssltcp_port"]; | |
| 406 | |
| 407 cricket::PortConfiguration* config = | |
| 408 new cricket::PortConfiguration(stun_hosts_[0], username, | |
| 409 password, magic_cookie); | |
| 410 | |
| 411 cricket::PortConfiguration::PortList ports; | |
| 412 if (!relay_udp_port.empty()) { | |
| 413 talk_base::SocketAddress address(relay_ip, atoi(relay_udp_port.c_str())); | |
| 414 ports.push_back(cricket::ProtocolAddress(address, cricket::PROTO_UDP)); | |
| 415 } | |
| 416 if (!relay_tcp_port.empty()) { | |
| 417 talk_base::SocketAddress address(relay_ip, atoi(relay_tcp_port.c_str())); | |
| 418 ports.push_back(cricket::ProtocolAddress(address, cricket::PROTO_TCP)); | |
| 419 } | |
| 420 if (!relay_ssltcp_port.empty()) { | |
| 421 talk_base::SocketAddress address(relay_ip, atoi(relay_ssltcp_port.c_str())); | |
| 422 ports.push_back(cricket::ProtocolAddress(address, cricket::PROTO_SSLTCP)); | |
| 423 } | |
| 424 config->AddRelay(ports, 0.0f); | |
| 425 ConfigReady(config); | |
| 426 } | |
| 427 | |
| 428 } // namespace remoting | |
| 429 | |
| 430 DISABLE_RUNNABLE_METHOD_REFCOUNT(remoting::PepperPortAllocatorSession); | |
|
Sergey Ulanov
2011/03/07 12:58:45
I don't see why we would need this. Remove it?
| |
| OLD | NEW |