// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/client/plugin/pepper_port_allocator.h" #include "base/bind.h" #include "base/strings/string_number_conversions.h" #include "net/base/net_util.h" #include "ppapi/c/pp_errors.h" #include "ppapi/cpp/url_loader.h" #include "ppapi/cpp/url_request_info.h" #include "ppapi/cpp/url_response_info.h" #include "ppapi/utility/completion_callback_factory.h" #include "remoting/client/plugin/pepper_network_manager.h" #include "remoting/client/plugin/pepper_packet_socket_factory.h" #include "remoting/client/plugin/pepper_util.h" namespace remoting { namespace { // Read buffer we allocate per read when reading response from // URLLoader. Normally the response from URL loader is smaller than 1kB. const int kReadSize = 1024; class PepperPortAllocatorSession : public cricket::HttpPortAllocatorSessionBase { public: PepperPortAllocatorSession( cricket::HttpPortAllocatorBase* allocator, const std::string& content_name, int component, const std::string& ice_username_fragment, const std::string& ice_password, const std::vector& stun_hosts, const std::vector& relay_hosts, const std::string& relay_token, const pp::InstanceHandle& instance); ~PepperPortAllocatorSession() override; // cricket::HttpPortAllocatorBase overrides. void ConfigReady(cricket::PortConfiguration* config) override; void GetPortConfigurations() override; void SendSessionRequest(const std::string& host, int port) override; private: void OnUrlOpened(int32_t result); void ReadResponseBody(); void OnResponseBodyRead(int32_t result); pp::InstanceHandle instance_; cricket::ServerAddresses stun_hosts_; scoped_ptr relay_url_loader_; std::vector relay_response_body_; bool relay_response_received_; pp::CompletionCallbackFactory callback_factory_; DISALLOW_COPY_AND_ASSIGN(PepperPortAllocatorSession); }; PepperPortAllocatorSession::PepperPortAllocatorSession( cricket::HttpPortAllocatorBase* allocator, const std::string& content_name, int component, const std::string& ice_username_fragment, const std::string& ice_password, const std::vector& stun_hosts, const std::vector& relay_hosts, const std::string& relay_token, const pp::InstanceHandle& instance) : HttpPortAllocatorSessionBase(allocator, content_name, component, ice_username_fragment, ice_password, stun_hosts, relay_hosts, relay_token, std::string()), instance_(instance), stun_hosts_(stun_hosts.begin(), stun_hosts.end()), relay_response_received_(false), callback_factory_(this) { } PepperPortAllocatorSession::~PepperPortAllocatorSession() { } void PepperPortAllocatorSession::ConfigReady( cricket::PortConfiguration* config) { // Filter out non-UDP relay ports, so that we don't try using TCP. for (cricket::PortConfiguration::RelayList::iterator relay = config->relays.begin(); relay != config->relays.end(); ++relay) { cricket::PortList filtered_ports; for (cricket::PortList::iterator port = relay->ports.begin(); port != relay->ports.end(); ++port) { if (port->proto == cricket::PROTO_UDP) { filtered_ports.push_back(*port); } } relay->ports = filtered_ports; } cricket::BasicPortAllocatorSession::ConfigReady(config); } void PepperPortAllocatorSession::GetPortConfigurations() { // Add a configuration without relay response first so local and STUN // candidates can be allocated without waiting for the relay response. ConfigReady(new cricket::PortConfiguration( stun_hosts_, std::string(), std::string())); TryCreateRelaySession(); } void PepperPortAllocatorSession::SendSessionRequest( const std::string& host, int port) { relay_url_loader_.reset(new pp::URLLoader(instance_)); pp::URLRequestInfo request_info(instance_); std::string url = "https://" + host + ":" + base::IntToString(port) + GetSessionRequestUrl() + "&sn=1"; request_info.SetURL(url); request_info.SetMethod("GET"); std::stringstream headers; headers << "X-Talk-Google-Relay-Auth: " << relay_token() << "\n\r"; headers << "X-Google-Relay-Auth: " << relay_token() << "\n\r"; headers << "X-Stream-Type: " << "chromoting" << "\n\r"; request_info.SetHeaders(headers.str()); pp::CompletionCallback callback = callback_factory_.NewCallback(&PepperPortAllocatorSession::OnUrlOpened); int result = relay_url_loader_->Open(request_info, callback); DCHECK_EQ(result, PP_OK_COMPLETIONPENDING); } void PepperPortAllocatorSession::OnUrlOpened(int32_t result) { if (result == PP_ERROR_ABORTED) { return; } if (result < 0) { LOG(WARNING) << "URLLoader failed: " << result; // Retry creating session. TryCreateRelaySession(); return; } pp::URLResponseInfo response = relay_url_loader_->GetResponseInfo(); DCHECK(!response.is_null()); if (response.GetStatusCode() != 200) { LOG(WARNING) << "Received HTTP status code " << response.GetStatusCode(); // Retry creating session. TryCreateRelaySession(); return; } relay_response_body_.clear(); ReadResponseBody(); } void PepperPortAllocatorSession::ReadResponseBody() { int pos = relay_response_body_.size(); relay_response_body_.resize(pos + kReadSize); pp::CompletionCallback callback = callback_factory_.NewCallback( &PepperPortAllocatorSession::OnResponseBodyRead); int result = relay_url_loader_->ReadResponseBody(&relay_response_body_[pos], kReadSize, callback); DCHECK_EQ(result, PP_OK_COMPLETIONPENDING); } void PepperPortAllocatorSession::OnResponseBodyRead(int32_t result) { if (result == PP_ERROR_ABORTED) { return; } if (result < 0) { LOG(WARNING) << "Failed to read HTTP response body when " "creating relay session: " << result; // Retry creating session. TryCreateRelaySession(); return; } // Resize the buffer in case we've read less than was requested. CHECK_LE(result, kReadSize); CHECK_GE(static_cast(relay_response_body_.size()), kReadSize); relay_response_body_.resize(relay_response_body_.size() - kReadSize + result); if (result == 0) { relay_response_received_ = true; ReceiveSessionResponse(std::string(relay_response_body_.begin(), relay_response_body_.end())); return; } ReadResponseBody(); } } // namespace // static scoped_ptr PepperPortAllocator::Create( const pp::InstanceHandle& instance) { scoped_ptr network_manager( new PepperNetworkManager(instance)); scoped_ptr socket_factory( new PepperPacketSocketFactory(instance)); scoped_ptr result(new PepperPortAllocator( instance, network_manager.Pass(), socket_factory.Pass())); return result.Pass(); } PepperPortAllocator::PepperPortAllocator( const pp::InstanceHandle& instance, scoped_ptr network_manager, scoped_ptr socket_factory) : HttpPortAllocatorBase(network_manager.get(), socket_factory.get(), std::string()), instance_(instance), network_manager_(network_manager.Pass()), socket_factory_(socket_factory.Pass()) { // TCP transport is disabled becase PseudoTCP works poorly over // it. ENABLE_SHARED_UFRAG flag is specified so that the same // username fragment is shared between all candidates for this // channel. set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG| cricket::PORTALLOCATOR_ENABLE_IPV6); } PepperPortAllocator::~PepperPortAllocator() { } cricket::PortAllocatorSession* PepperPortAllocator::CreateSessionInternal( const std::string& content_name, int component, const std::string& ice_username_fragment, const std::string& ice_password) { return new PepperPortAllocatorSession( this, content_name, component, ice_username_fragment, ice_password, stun_hosts(), relay_hosts(), relay_token(), instance_); } } // namespace remoting