From 96a0c067d816576735856452608a03bd7e914504 Mon Sep 17 00:00:00 2001 From: "rpaquay@chromium.org" Date: Fri, 18 Oct 2013 03:49:15 +0000 Subject: [PATCH] sockets.tcpServer API implementation. Implement a new API for TCP *server* sockets. This CL is similar to sockets.tcp (see https://codereview.chromium.org/24684002) and follows the same design pattern. BUG=165273 BUG=173241 Review URL: https://codereview.chromium.org/27076004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@229304 0039d316-1c4b-4281-b951-d872f2087c98 --- .../extensions/api/api_resource_manager.h | 2 + .../extensions/api/socket/tcp_socket.cc | 31 ++ .../extensions/api/socket/tcp_socket.h | 36 +++ .../sockets_tcp_server_api.cc | 298 ++++++++++++++++++ .../sockets_tcp_server_api.h | 179 +++++++++++ .../sockets_tcp_server_api_unittest.cc | 94 ++++++ .../sockets_tcp_server_apitest.cc | 111 +++++++ .../tcp_server_socket_event_dispatcher.cc | 198 ++++++++++++ .../tcp_server_socket_event_dispatcher.h | 99 ++++++ .../extension_function_histogram_value.h | 8 + ...hrome_browser_main_extra_parts_profiles.cc | 4 + chrome/chrome_browser_extensions.gypi | 4 + chrome/chrome_tests.gypi | 1 + chrome/chrome_tests_unit.gypi | 1 + .../common/extensions/api/_api_features.json | 5 + chrome/common/extensions/api/api.gyp | 1 + .../common/extensions/api/manifest_types.json | 12 + .../extensions/api/sockets/sockets_handler.cc | 8 + .../extensions/api/sockets_tcp_server.idl | 191 +++++++++++ .../sockets_tcp_server/api/background.js | 134 ++++++++ .../sockets_tcp_server/api/manifest.json | 18 ++ .../sockets_tcp_server/unload/background.js | 24 ++ .../sockets_tcp_server/unload/manifest.json | 15 + 23 files changed, 1474 insertions(+) create mode 100644 chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc create mode 100644 chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h create mode 100644 chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc create mode 100644 chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc create mode 100644 chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc create mode 100644 chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h create mode 100644 chrome/common/extensions/api/sockets_tcp_server.idl create mode 100644 chrome/test/data/extensions/api_test/sockets_tcp_server/api/background.js create mode 100644 chrome/test/data/extensions/api_test/sockets_tcp_server/api/manifest.json create mode 100644 chrome/test/data/extensions/api_test/sockets_tcp_server/unload/background.js create mode 100644 chrome/test/data/extensions/api_test/sockets_tcp_server/unload/manifest.json diff --git a/chrome/browser/extensions/api/api_resource_manager.h b/chrome/browser/extensions/api/api_resource_manager.h index d39c41b35a32b8..28adefd19256dc 100644 --- a/chrome/browser/extensions/api/api_resource_manager.h +++ b/chrome/browser/extensions/api/api_resource_manager.h @@ -24,6 +24,7 @@ namespace extensions { namespace api { +class TCPServerSocketEventDispatcher; class TCPSocketEventDispatcher; class UDPSocketEventDispatcher; } @@ -153,6 +154,7 @@ class ApiResourceManager : public ProfileKeyedAPI, } private: + friend class api::TCPServerSocketEventDispatcher; friend class api::TCPSocketEventDispatcher; friend class api::UDPSocketEventDispatcher; friend class ProfileKeyedAPIFactory >; diff --git a/chrome/browser/extensions/api/socket/tcp_socket.cc b/chrome/browser/extensions/api/socket/tcp_socket.cc index 8401dc26744213..4c0de162b0252c 100644 --- a/chrome/browser/extensions/api/socket/tcp_socket.cc +++ b/chrome/browser/extensions/api/socket/tcp_socket.cc @@ -28,6 +28,17 @@ ApiResourceManager::GetFactoryInstance() { return &g_factory.Get(); } +static base::LazyInstance > > + g_server_factory = LAZY_INSTANCE_INITIALIZER; + +// static +template <> +ProfileKeyedAPIFactory >* +ApiResourceManager::GetFactoryInstance() { + return &g_server_factory.Get(); +} + TCPSocket::TCPSocket(const std::string& owner_extension_id) : Socket(owner_extension_id), socket_mode_(UNKNOWN) { @@ -304,8 +315,28 @@ ResumableTCPSocket::ResumableTCPSocket(const std::string& owner_extension_id) paused_(false) { } +ResumableTCPSocket::ResumableTCPSocket(net::TCPClientSocket* tcp_client_socket, + const std::string& owner_extension_id, + bool is_connected) + : TCPSocket(tcp_client_socket, owner_extension_id, is_connected), + persistent_(false), + buffer_size_(0), + paused_(false) { +} + bool ResumableTCPSocket::persistent() const { return persistent_; } +ResumableTCPServerSocket::ResumableTCPServerSocket( + const std::string& owner_extension_id) + : TCPSocket(owner_extension_id), + persistent_(false), + paused_(false) { +} + +bool ResumableTCPServerSocket::persistent() const { + return persistent_; +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/socket/tcp_socket.h b/chrome/browser/extensions/api/socket/tcp_socket.h index c6848243afad07..d5b4844bdc634e 100644 --- a/chrome/browser/extensions/api/socket/tcp_socket.h +++ b/chrome/browser/extensions/api/socket/tcp_socket.h @@ -102,10 +102,14 @@ class TCPSocket : public Socket { class ResumableTCPSocket : public TCPSocket { public: explicit ResumableTCPSocket(const std::string& owner_extension_id); + explicit ResumableTCPSocket(net::TCPClientSocket* tcp_client_socket, + const std::string& owner_extension_id, + bool is_connected); const std::string& name() const { return name_; } void set_name(const std::string& name) { name_ = name; } + // Overriden from ApiResource virtual bool persistent() const OVERRIDE; void set_persistent(bool persistent) { persistent_ = persistent; } @@ -133,6 +137,38 @@ class ResumableTCPSocket : public TCPSocket { bool paused_; }; +// TCP Socket instances from the "sockets.tcpServer" namespace. These are +// regular socket objects with additional properties related to the behavior +// defined in the "sockets.tcpServer" namespace. +class ResumableTCPServerSocket : public TCPSocket { + public: + explicit ResumableTCPServerSocket(const std::string& owner_extension_id); + + const std::string& name() const { return name_; } + void set_name(const std::string& name) { name_ = name; } + + virtual bool persistent() const OVERRIDE; + void set_persistent(bool persistent) { persistent_ = persistent; } + + bool paused() const { return paused_; } + void set_paused(bool paused) { paused_ = paused; } + + private: + friend class ApiResourceManager; + static const char* service_name() { + return "ResumableTCPServerSocketManager"; + } + + // Application-defined string - see sockets_tcp_server.idl. + std::string name_; + // Flag indicating whether the socket is left open when the application is + // suspended - see sockets_tcp_server.idl. + bool persistent_; + // Flag indicating whether a connected socket blocks its peer from sending + // more data - see sockets_tcp_server.idl. + bool paused_; +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_SOCKET_TCP_SOCKET_H_ diff --git a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc new file mode 100644 index 00000000000000..0827e29bed11c6 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc @@ -0,0 +1,298 @@ +// Copyright 2013 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 "chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h" + +#include "chrome/browser/extensions/api/socket/tcp_socket.h" +#include "chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h" +#include "chrome/common/extensions/api/sockets/sockets_handler.h" +#include "chrome/common/extensions/permissions/permissions_data.h" +#include "chrome/common/extensions/permissions/socket_permission.h" +#include "content/public/common/socket_permission_request.h" +#include "net/base/net_errors.h" + +using content::SocketPermissionRequest; +using extensions::ResumableTCPServerSocket; +using extensions::api::sockets_tcp_server::SocketInfo; +using extensions::api::sockets_tcp_server::SocketProperties; + +namespace { + +const char kSocketNotFoundError[] = "Socket not found"; +const char kPermissionError[] = "Does not have permission"; +const int kDefaultListenBacklog = SOMAXCONN; + +linked_ptr CreateSocketInfo(int socket_id, + ResumableTCPServerSocket* socket) { + linked_ptr socket_info(new SocketInfo()); + // This represents what we know about the socket, and does not call through + // to the system. + socket_info->socket_id = socket_id; + if (!socket->name().empty()) { + socket_info->name.reset(new std::string(socket->name())); + } + socket_info->persistent = socket->persistent(); + socket_info->paused = socket->paused(); + + // Grab the local address as known by the OS. + net::IPEndPoint localAddress; + if (socket->GetLocalAddress(&localAddress)) { + socket_info->local_address.reset( + new std::string(localAddress.ToStringWithoutPort())); + socket_info->local_port.reset(new int(localAddress.port())); + } + + return socket_info; +} + +void SetSocketProperties(ResumableTCPServerSocket* socket, + SocketProperties* properties) { + if (properties->name.get()) { + socket->set_name(*properties->name.get()); + } + if (properties->persistent.get()) { + socket->set_persistent(*properties->persistent.get()); + } +} + +} // namespace + +namespace extensions { +namespace api { + +TCPServerSocketAsyncApiFunction::~TCPServerSocketAsyncApiFunction() {} + +scoped_ptr + TCPServerSocketAsyncApiFunction::CreateSocketResourceManager() { + return scoped_ptr( + new SocketResourceManager()).Pass(); +} + +ResumableTCPServerSocket* TCPServerSocketAsyncApiFunction::GetTcpSocket( + int socket_id) { + return static_cast(GetSocket(socket_id)); +} + +SocketsTcpServerCreateFunction::SocketsTcpServerCreateFunction() {} + +SocketsTcpServerCreateFunction::~SocketsTcpServerCreateFunction() {} + +bool SocketsTcpServerCreateFunction::Prepare() { + params_ = sockets_tcp_server::Create::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpServerCreateFunction::Work() { + ResumableTCPServerSocket* socket = + new ResumableTCPServerSocket(extension_->id()); + + sockets_tcp_server::SocketProperties* properties = + params_.get()->properties.get(); + if (properties) { + SetSocketProperties(socket, properties); + } + + sockets_tcp_server::CreateInfo create_info; + create_info.socket_id = AddSocket(socket); + results_ = sockets_tcp_server::Create::Results::Create(create_info); +} + +SocketsTcpServerUpdateFunction::SocketsTcpServerUpdateFunction() {} + +SocketsTcpServerUpdateFunction::~SocketsTcpServerUpdateFunction() {} + +bool SocketsTcpServerUpdateFunction::Prepare() { + params_ = sockets_tcp_server::Update::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpServerUpdateFunction::Work() { + ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + SetSocketProperties(socket, ¶ms_.get()->properties); + results_ = sockets_tcp_server::Update::Results::Create(); +} + +SocketsTcpServerSetPausedFunction::SocketsTcpServerSetPausedFunction() + : socket_event_dispatcher_(NULL) {} + +SocketsTcpServerSetPausedFunction::~SocketsTcpServerSetPausedFunction() {} + +bool SocketsTcpServerSetPausedFunction::Prepare() { + params_ = api::sockets_tcp_server::SetPaused::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + + socket_event_dispatcher_ = TCPServerSocketEventDispatcher::Get(profile()); + DCHECK(socket_event_dispatcher_) << "There is no socket event dispatcher. " + "If this assertion is failing during a test, then it is likely that " + "TestExtensionSystem is failing to provide an instance of " + "TCPServerSocketEventDispatcher."; + return socket_event_dispatcher_ != NULL; +} + +void SocketsTcpServerSetPausedFunction::Work() { + ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + if (socket->paused() != params_->paused) { + socket->set_paused(params_->paused); + if (socket->IsConnected() && !params_->paused) { + socket_event_dispatcher_->OnServerSocketResume(extension_->id(), + params_->socket_id); + } + } + + results_ = sockets_tcp_server::SetPaused::Results::Create(); +} + +SocketsTcpServerListenFunction::SocketsTcpServerListenFunction() + : socket_event_dispatcher_(NULL) {} + +SocketsTcpServerListenFunction::~SocketsTcpServerListenFunction() {} + +bool SocketsTcpServerListenFunction::Prepare() { + params_ = api::sockets_tcp_server::Listen::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + + socket_event_dispatcher_ = TCPServerSocketEventDispatcher::Get(profile()); + DCHECK(socket_event_dispatcher_) << "There is no socket event dispatcher. " + "If this assertion is failing during a test, then it is likely that " + "TestExtensionSystem is failing to provide an instance of " + "TCPServerSocketEventDispatcher."; + return socket_event_dispatcher_ != NULL; +} + +void SocketsTcpServerListenFunction::Work() { + ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + SocketPermissionRequest param( + SocketPermissionRequest::TCP_LISTEN, + params_->address, + params_->port); + if (!SocketsManifestData::CheckRequest(GetExtension(), param)) { + error_ = kPermissionError; + return; + } + + int net_result = socket->Listen( + params_->address, + params_->port, + params_->backlog.get() ? *params_->backlog.get() : kDefaultListenBacklog, + &error_); + + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + + + if (net_result == net::OK) { + socket_event_dispatcher_->OnServerSocketListen(extension_->id(), + params_->socket_id); + } + + results_ = sockets_tcp_server::Listen::Results::Create(net_result); +} + +SocketsTcpServerDisconnectFunction::SocketsTcpServerDisconnectFunction() {} + +SocketsTcpServerDisconnectFunction::~SocketsTcpServerDisconnectFunction() {} + +bool SocketsTcpServerDisconnectFunction::Prepare() { + params_ = sockets_tcp_server::Disconnect::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpServerDisconnectFunction::Work() { + ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + socket->Disconnect(); + results_ = sockets_tcp_server::Disconnect::Results::Create(); +} + +SocketsTcpServerCloseFunction::SocketsTcpServerCloseFunction() {} + +SocketsTcpServerCloseFunction::~SocketsTcpServerCloseFunction() {} + +bool SocketsTcpServerCloseFunction::Prepare() { + params_ = sockets_tcp_server::Close::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpServerCloseFunction::Work() { + ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + RemoveSocket(params_->socket_id); + results_ = sockets_tcp_server::Close::Results::Create(); +} + +SocketsTcpServerGetInfoFunction::SocketsTcpServerGetInfoFunction() {} + +SocketsTcpServerGetInfoFunction::~SocketsTcpServerGetInfoFunction() {} + +bool SocketsTcpServerGetInfoFunction::Prepare() { + params_ = sockets_tcp_server::GetInfo::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpServerGetInfoFunction::Work() { + ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + linked_ptr socket_info = + CreateSocketInfo(params_->socket_id, socket); + results_ = sockets_tcp_server::GetInfo::Results::Create(*socket_info); +} + +SocketsTcpServerGetSocketsFunction::SocketsTcpServerGetSocketsFunction() {} + +SocketsTcpServerGetSocketsFunction::~SocketsTcpServerGetSocketsFunction() {} + +bool SocketsTcpServerGetSocketsFunction::Prepare() { + return true; +} + +void SocketsTcpServerGetSocketsFunction::Work() { + std::vector > socket_infos; + base::hash_set* resource_ids = GetSocketIds(); + if (resource_ids != NULL) { + for (base::hash_set::iterator it = resource_ids->begin(); + it != resource_ids->end(); ++it) { + int socket_id = *it; + ResumableTCPServerSocket* socket = GetTcpSocket(socket_id); + if (socket) { + socket_infos.push_back(CreateSocketInfo(socket_id, socket)); + } + } + } + results_ = sockets_tcp_server::GetSockets::Results::Create(socket_infos); +} + +} // namespace api +} // namespace extensions diff --git a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h new file mode 100644 index 00000000000000..439b85714cb141 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h @@ -0,0 +1,179 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_ + +#include "chrome/browser/extensions/api/socket/socket_api.h" +#include "chrome/common/extensions/api/sockets_tcp_server.h" + +namespace extensions { +class ResumableTCPServerSocket; +} + +namespace extensions { +namespace api { + +class TCPServerSocketAsyncApiFunction : public SocketAsyncApiFunction { + protected: + virtual ~TCPServerSocketAsyncApiFunction(); + + virtual scoped_ptr + CreateSocketResourceManager() OVERRIDE; + + ResumableTCPServerSocket* GetTcpSocket(int socket_id); +}; + +class SocketsTcpServerCreateFunction : public TCPServerSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.create", + SOCKETS_TCP_SERVER_CREATE) + + SocketsTcpServerCreateFunction(); + + protected: + virtual ~SocketsTcpServerCreateFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(SocketsTcpServerUnitTest, Create); + scoped_ptr params_; +}; + +class SocketsTcpServerUpdateFunction : public TCPServerSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.update", + SOCKETS_TCP_SERVER_UPDATE) + + SocketsTcpServerUpdateFunction(); + + protected: + virtual ~SocketsTcpServerUpdateFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr params_; +}; + +class SocketsTcpServerSetPausedFunction + : public TCPServerSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.setPaused", + SOCKETS_TCP_SERVER_SETPAUSED) + + SocketsTcpServerSetPausedFunction(); + + protected: + virtual ~SocketsTcpServerSetPausedFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr params_; + TCPServerSocketEventDispatcher* socket_event_dispatcher_; +}; + +class SocketsTcpServerListenFunction + : public TCPServerSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.listen", + SOCKETS_TCP_SERVER_LISTEN) + + SocketsTcpServerListenFunction(); + + protected: + virtual ~SocketsTcpServerListenFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr params_; + TCPServerSocketEventDispatcher* socket_event_dispatcher_; +}; + +class SocketsTcpServerDisconnectFunction + : public TCPServerSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.disconnect", + SOCKETS_TCP_SERVER_DISCONNECT) + + SocketsTcpServerDisconnectFunction(); + + protected: + virtual ~SocketsTcpServerDisconnectFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr params_; +}; + +class SocketsTcpServerCloseFunction : public TCPServerSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.close", + SOCKETS_TCP_SERVER_CLOSE) + + SocketsTcpServerCloseFunction(); + + protected: + virtual ~SocketsTcpServerCloseFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr params_; +}; + +class SocketsTcpServerGetInfoFunction : public TCPServerSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.getInfo", + SOCKETS_TCP_SERVER_GETINFO) + + SocketsTcpServerGetInfoFunction(); + + protected: + virtual ~SocketsTcpServerGetInfoFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr params_; +}; + +class SocketsTcpServerGetSocketsFunction + : public TCPServerSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.getSockets", + SOCKETS_TCP_SERVER_GETSOCKETS) + + SocketsTcpServerGetSocketsFunction(); + + protected: + virtual ~SocketsTcpServerGetSocketsFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; +}; + +} // namespace api +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_ diff --git a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc new file mode 100644 index 00000000000000..6ed0f9e08edea4 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc @@ -0,0 +1,94 @@ +// Copyright 2013 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 "base/values.h" +#include "chrome/browser/browser_process_impl.h" +#include "chrome/browser/extensions/api/api_function.h" +#include "chrome/browser/extensions/api/api_resource_manager.h" +#include "chrome/browser/extensions/api/socket/socket.h" +#include "chrome/browser/extensions/api/socket/tcp_socket.h" +#include "chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h" +#include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/extensions/test_extension_system.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/test/base/browser_with_test_window_test.h" +#include "chrome/test/base/testing_browser_process.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace utils = extension_function_test_utils; + +namespace extensions { +namespace api { + +static +BrowserContextKeyedService* ApiResourceManagerTestFactory( + content::BrowserContext* profile) { + content::BrowserThread::ID id; + CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id)); + return ApiResourceManager:: + CreateApiResourceManagerForTest(static_cast(profile), id); +} + +static +BrowserContextKeyedService* ApiResourceManagerTestServerFactory( + content::BrowserContext* profile) { + content::BrowserThread::ID id; + CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id)); + return ApiResourceManager:: + CreateApiResourceManagerForTest(static_cast(profile), id); +} + +class SocketsTcpServerUnitTest : public BrowserWithTestWindowTest { + public: + virtual void SetUp() { + BrowserWithTestWindowTest::SetUp(); + + ApiResourceManager::GetFactoryInstance()-> + SetTestingFactoryAndUse(browser()->profile(), + ApiResourceManagerTestFactory); + + ApiResourceManager::GetFactoryInstance()-> + SetTestingFactoryAndUse(browser()->profile(), + ApiResourceManagerTestServerFactory); + + extension_ = utils::CreateEmptyExtensionWithLocation( + extensions::Manifest::UNPACKED); + } + + base::Value* RunFunctionWithExtension( + UIThreadExtensionFunction* function, const std::string& args) { + scoped_refptr delete_function(function); + function->set_extension(extension_.get()); + return utils::RunFunctionAndReturnSingleResult(function, args, browser()); + } + + base::DictionaryValue* RunFunctionAndReturnDict( + UIThreadExtensionFunction* function, const std::string& args) { + base::Value* result = RunFunctionWithExtension(function, args); + return result ? utils::ToDictionary(result) : NULL; + } + + protected: + scoped_refptr extension_; +}; + +TEST_F(SocketsTcpServerUnitTest, Create) { + // Get BrowserThread + content::BrowserThread::ID id; + CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id)); + + // Create SocketCreateFunction and put it on BrowserThread + SocketsTcpServerCreateFunction *function = + new SocketsTcpServerCreateFunction(); + function->set_work_thread_id(id); + + // Run tests + scoped_ptr result(RunFunctionAndReturnDict( + function, "[{\"persistent\": true, \"name\": \"foo\"}]")); + ASSERT_TRUE(result.get()); +} + +} // namespace api +} // namespace extensions diff --git a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc new file mode 100644 index 00000000000000..6e36a8cacf35b3 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc @@ -0,0 +1,111 @@ +// Copyright 2013 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 "base/memory/ref_counted.h" +#include "base/path_service.h" +#include "base/strings/stringprintf.h" +#include "chrome/browser/extensions/api/dns/host_resolver_wrapper.h" +#include "chrome/browser/extensions/api/dns/mock_host_resolver_creator.h" +#include "chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_test_message_listener.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/extensions/application_launch.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/spawned_test_server/spawned_test_server.h" + +using extensions::Extension; +using extensions::api::SocketsTcpServerCreateFunction; + +namespace utils = extension_function_test_utils; + +namespace { + +// TODO(jschuh): Hanging plugin tests. crbug.com/244653 +#if defined(OS_WIN) && defined(ARCH_CPU_X86_64) +#define MAYBE(x) DISABLED_##x +#else +#define MAYBE(x) x +#endif + +const std::string kHostname = "127.0.0.1"; +const int kPort = 8888; + +class SocketsTcpServerApiTest : public ExtensionApiTest { + public: + SocketsTcpServerApiTest() : resolver_event_(true, false), + resolver_creator_( + new extensions::MockHostResolverCreator()) { + } + + virtual void SetUpOnMainThread() OVERRIDE { + extensions::HostResolverWrapper::GetInstance()->SetHostResolverForTesting( + resolver_creator_->CreateMockHostResolver()); + } + + virtual void CleanUpOnMainThread() OVERRIDE { + extensions::HostResolverWrapper::GetInstance()-> + SetHostResolverForTesting(NULL); + resolver_creator_->DeleteMockHostResolver(); + } + + private: + base::WaitableEvent resolver_event_; + + // The MockHostResolver asserts that it's used on the same thread on which + // it's created, which is actually a stronger rule than its real counterpart. + // But that's fine; it's good practice. + scoped_refptr resolver_creator_; +}; + +} // namespace + +IN_PROC_BROWSER_TEST_F(SocketsTcpServerApiTest, SocketTCPCreateGood) { + scoped_refptr + socket_create_function(new SocketsTcpServerCreateFunction()); + scoped_refptr empty_extension(utils::CreateEmptyExtension()); + + socket_create_function->set_extension(empty_extension.get()); + socket_create_function->set_has_callback(true); + + scoped_ptr result(utils::RunFunctionAndReturnSingleResult( + socket_create_function.get(), "[]", browser(), utils::NONE)); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, result->GetType()); + base::DictionaryValue *value = + static_cast(result.get()); + int socketId = -1; + EXPECT_TRUE(value->GetInteger("socketId", &socketId)); + ASSERT_TRUE(socketId > 0); +} + +IN_PROC_BROWSER_TEST_F(SocketsTcpServerApiTest, SocketTCPServerExtension) { + base::FilePath path = test_data_dir_.AppendASCII("sockets_tcp_server/api"); + ResultCatcher catcher; + catcher.RestrictToProfile(browser()->profile()); + ExtensionTestMessageListener listener("info_please", true); + ASSERT_TRUE(LoadExtension(path)); + EXPECT_TRUE(listener.WaitUntilSatisfied()); + listener.Reply( + base::StringPrintf("tcp_server:%s:%d", kHostname.c_str(), kPort)); + + EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); +} + +IN_PROC_BROWSER_TEST_F(SocketsTcpServerApiTest, SocketTCPServerUnbindOnUnload) { + base::FilePath path = test_data_dir_.AppendASCII("sockets_tcp_server/unload"); + ResultCatcher catcher; + const Extension* extension = LoadExtension(path); + ASSERT_TRUE(extension); + EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); + + UnloadExtension(extension->id()); + + ASSERT_TRUE(LoadExtension(path)) << message_; + EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); +} diff --git a/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc b/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc new file mode 100644 index 00000000000000..d849e8c0e2501c --- /dev/null +++ b/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc @@ -0,0 +1,198 @@ +// Copyright 2013 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 "chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h" + +#include "chrome/browser/browser_process.h" +#include "chrome/browser/extensions/api/socket/tcp_socket.h" +#include "chrome/browser/extensions/event_router.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "net/base/net_errors.h" + +namespace extensions { +namespace api { + +using content::BrowserThread; + +static + base::LazyInstance > + g_factory = LAZY_INSTANCE_INITIALIZER; + +// static +ProfileKeyedAPIFactory* + TCPServerSocketEventDispatcher::GetFactoryInstance() { + return &g_factory.Get(); +} + +// static +TCPServerSocketEventDispatcher* TCPServerSocketEventDispatcher::Get( + Profile* profile) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + return ProfileKeyedAPIFactory::GetForProfile( + profile); +} + +TCPServerSocketEventDispatcher::TCPServerSocketEventDispatcher(Profile* profile) + : thread_id_(Socket::kThreadId), + profile_(profile) { + ApiResourceManager* server_manager = + ApiResourceManager::Get(profile); + DCHECK(server_manager) << "There is no server socket manager. " + "If this assertion is failing during a test, then it is likely that " + "TestExtensionSystem is failing to provide an instance of " + "ApiResourceManager."; + server_sockets_ = server_manager->data_; + + ApiResourceManager* client_manager = + ApiResourceManager::Get(profile); + DCHECK(client_manager) << "There is no client socket manager. " + "If this assertion is failing during a test, then it is likely that " + "TestExtensionSystem is failing to provide an instance of " + "ApiResourceManager."; + client_sockets_ = client_manager->data_; +} + +TCPServerSocketEventDispatcher::~TCPServerSocketEventDispatcher() {} + +TCPServerSocketEventDispatcher::AcceptParams::AcceptParams() {} + +TCPServerSocketEventDispatcher::AcceptParams::~AcceptParams() {} + +void TCPServerSocketEventDispatcher::OnServerSocketListen( + const std::string& extension_id, + int socket_id) { + DCHECK(BrowserThread::CurrentlyOn(thread_id_)); + + StartSocketAccept(extension_id, socket_id); +} + +void TCPServerSocketEventDispatcher::OnServerSocketResume( + const std::string& extension_id, + int socket_id) { + DCHECK(BrowserThread::CurrentlyOn(thread_id_)); + + StartSocketAccept(extension_id, socket_id); +} + +void TCPServerSocketEventDispatcher::StartSocketAccept( + const std::string& extension_id, + int socket_id) { + DCHECK(BrowserThread::CurrentlyOn(thread_id_)); + + AcceptParams params; + params.thread_id = thread_id_; + params.profile_id = profile_; + params.extension_id = extension_id; + params.server_sockets = server_sockets_; + params.client_sockets = client_sockets_; + params.socket_id = socket_id; + + StartAccept(params); +} + +// static +void TCPServerSocketEventDispatcher::StartAccept(const AcceptParams& params) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + ResumableTCPServerSocket* socket = + params.server_sockets->Get(params.extension_id, params.socket_id); + if (!socket) { + // This can happen if the socket is closed while our callback is active. + return; + } + DCHECK(params.extension_id == socket->owner_extension_id()) + << "Socket has wrong owner."; + + // Don't start another accept if the socket has been paused. + if (socket->paused()) + return; + + socket->Accept(base::Bind(&TCPServerSocketEventDispatcher::AcceptCallback, + params)); +} + +// static +void TCPServerSocketEventDispatcher::AcceptCallback( + const AcceptParams& params, + int result_code, + net::TCPClientSocket *socket) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + if (result_code >= 0) { + ResumableTCPSocket *client_socket = + new ResumableTCPSocket(socket, params.extension_id, true); + client_socket->set_paused(true); + int client_socket_id = params.client_sockets->Add(client_socket); + + // Dispatch "onAccept" event. + sockets_tcp_server::AcceptInfo accept_info; + accept_info.socket_id = params.socket_id; + accept_info.client_socket_id = client_socket_id; + scoped_ptr args = + sockets_tcp_server::OnAccept::Create(accept_info); + scoped_ptr event( + new Event(sockets_tcp_server::OnAccept::kEventName, args.Pass())); + PostEvent(params, event.Pass()); + + // Post a task to delay the "accept" until the socket is available, as + // calling StartAccept at this point would error with ERR_IO_PENDING. + BrowserThread::PostTask( + params.thread_id, FROM_HERE, + base::Bind(&TCPServerSocketEventDispatcher::StartAccept, params)); + } else { + // Dispatch "onAcceptError" event but don't start another accept to avoid + // potential infinite "accepts" if we have a persistent network error. + sockets_tcp_server::AcceptErrorInfo accept_error_info; + accept_error_info.socket_id = params.socket_id; + accept_error_info.result_code = result_code; + scoped_ptr args = + sockets_tcp_server::OnAcceptError::Create(accept_error_info); + scoped_ptr event( + new Event(sockets_tcp_server::OnAcceptError::kEventName, args.Pass())); + PostEvent(params, event.Pass()); + + // Since we got an error, the socket is now "paused" until the application + // "resumes" it. + ResumableTCPServerSocket* socket = + params.server_sockets->Get(params.extension_id, params.socket_id); + if (socket) { + socket->set_paused(true); + } + } +} + +// static +void TCPServerSocketEventDispatcher::PostEvent(const AcceptParams& params, + scoped_ptr event) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&DispatchEvent, + params.profile_id, + params.extension_id, + base::Passed(event.Pass()))); +} + +// static +void TCPServerSocketEventDispatcher::DispatchEvent( + void* profile_id, + const std::string& extension_id, + scoped_ptr event) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + Profile* profile = reinterpret_cast(profile_id); + if (!g_browser_process->profile_manager()->IsValidProfile(profile)) + return; + + EventRouter* router = ExtensionSystem::Get(profile)->event_router(); + if (router) + router->DispatchEventToExtension(extension_id, event.Pass()); +} + +} // namespace api +} // namespace extensions diff --git a/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h b/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h new file mode 100644 index 00000000000000..4d15bc351bed09 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h @@ -0,0 +1,99 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_ + +#include "chrome/browser/extensions/api/api_resource_manager.h" +#include "chrome/browser/extensions/api/sockets_tcp/sockets_tcp_api.h" +#include "chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h" + +namespace extensions { +struct Event; +class ResumableTCPSocket; +} + +namespace extensions { +namespace api { + +// Dispatch events related to "sockets.tcp" sockets from callback on native +// socket instances. There is one instance per profile. +class TCPServerSocketEventDispatcher + : public ProfileKeyedAPI, + public base::SupportsWeakPtr { + public: + explicit TCPServerSocketEventDispatcher(Profile* profile); + virtual ~TCPServerSocketEventDispatcher(); + + // Server socket is active, start accepting connections from it. + void OnServerSocketListen(const std::string& extension_id, int socket_id); + + // Server socket is active again, start accepting connections from it. + void OnServerSocketResume(const std::string& extension_id, int socket_id); + + // ProfileKeyedAPI implementation. + static ProfileKeyedAPIFactory* + GetFactoryInstance(); + + // Convenience method to get the SocketEventDispatcher for a profile. + static TCPServerSocketEventDispatcher* Get(Profile* profile); + + private: + typedef ApiResourceManager::ApiResourceData + ServerSocketData; + typedef ApiResourceManager::ApiResourceData + ClientSocketData; + friend class ProfileKeyedAPIFactory; + // ProfileKeyedAPI implementation. + static const char* service_name() { + return "TCPServerSocketEventDispatcher"; + } + static const bool kServiceHasOwnInstanceInIncognito = true; + static const bool kServiceIsNULLWhileTesting = true; + + // base::Bind supports methods with up to 6 parameters. AcceptParams is used + // as a workaround that limitation for invoking StartAccept. + struct AcceptParams { + AcceptParams(); + ~AcceptParams(); + + content::BrowserThread::ID thread_id; + void* profile_id; + std::string extension_id; + scoped_refptr server_sockets; + scoped_refptr client_sockets; + int socket_id; + }; + + // Start an accept and register a callback. + void StartSocketAccept(const std::string& extension_id, int socket_id); + + // Start an accept and register a callback. + static void StartAccept(const AcceptParams& params); + + // Called when socket accepts a new connection. + static void AcceptCallback(const AcceptParams& params, + int result_code, + net::TCPClientSocket *socket); + + // Post an extension event from |thread_id| to UI thread + static void PostEvent(const AcceptParams& params, + scoped_ptr event); + + // Dispatch an extension event on to EventRouter instance on UI thread. + static void DispatchEvent(void* profile_id, + const std::string& extension_id, + scoped_ptr event); + + // Usually IO thread (except for unit testing). + content::BrowserThread::ID thread_id_; + Profile* const profile_; + scoped_refptr server_sockets_; + scoped_refptr client_sockets_; +}; + +} // namespace api +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_ diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h index 47ae02309a5a2e..5b9d5badfa6a9f 100644 --- a/chrome/browser/extensions/extension_function_histogram_value.h +++ b/chrome/browser/extensions/extension_function_histogram_value.h @@ -663,6 +663,14 @@ enum HistogramValue { NETWORKINGPRIVATE_GETENABLEDNETWORKTYPES, NETWORKINGPRIVATE_ENABLENETWORKTYPE, NETWORKINGPRIVATE_DISABLENETWORKTYPE, + SOCKETS_TCP_SERVER_CREATE, + SOCKETS_TCP_SERVER_UPDATE, + SOCKETS_TCP_SERVER_SETPAUSED, + SOCKETS_TCP_SERVER_LISTEN, + SOCKETS_TCP_SERVER_DISCONNECT, + SOCKETS_TCP_SERVER_CLOSE, + SOCKETS_TCP_SERVER_GETINFO, + SOCKETS_TCP_SERVER_GETSOCKETS, ENUM_BOUNDARY // Last entry: Add new entries above. }; diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc index 4d3f8cac469d83..f1ece9661fe029 100644 --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc @@ -89,6 +89,7 @@ #include "chrome/browser/extensions/api/socket/tcp_socket.h" #include "chrome/browser/extensions/api/socket/udp_socket.h" #include "chrome/browser/extensions/api/sockets_tcp/tcp_socket_event_dispatcher.h" +#include "chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h" #include "chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h" #include "chrome/browser/extensions/api/streams_private/streams_private_api.h" #include "chrome/browser/extensions/api/system_info/system_info_api.h" @@ -218,6 +219,8 @@ EnsureBrowserContextKeyedServiceFactoriesBuilt() { extensions::ActivityLogFactory::GetInstance(); extensions::ActivityLogAPI::GetFactoryInstance(); extensions::AlarmManager::GetFactoryInstance(); + extensions::ApiResourceManager:: + GetFactoryInstance(); extensions::ApiResourceManager:: GetFactoryInstance(); extensions::ApiResourceManager:: @@ -227,6 +230,7 @@ EnsureBrowserContextKeyedServiceFactoriesBuilt() { extensions::ApiResourceManager::GetFactoryInstance(); extensions::ApiResourceManager:: GetFactoryInstance(); + extensions::api::TCPServerSocketEventDispatcher::GetFactoryInstance(); extensions::api::TCPSocketEventDispatcher::GetFactoryInstance(); extensions::api::UDPSocketEventDispatcher::GetFactoryInstance(); extensions::AudioAPI::GetFactoryInstance(); diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 3343ca5517d82e..9f2d058a68c0ef 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -468,6 +468,10 @@ 'browser/extensions/api/sockets_tcp/tcp_socket_event_dispatcher.cc', 'browser/extensions/api/sockets_tcp/sockets_tcp_api.cc', 'browser/extensions/api/sockets_tcp/sockets_tcp_api.h', + 'browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h', + 'browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc', + 'browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc', + 'browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h', 'browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h', 'browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc', 'browser/extensions/api/sockets_udp/sockets_udp_api.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index baa183fc226074..c1a25591fe7618 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1118,6 +1118,7 @@ 'browser/extensions/api/sessions/sessions_apitest.cc', 'browser/extensions/api/socket/socket_apitest.cc', 'browser/extensions/api/sockets_tcp/sockets_tcp_apitest.cc', + 'browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc', 'browser/extensions/api/sockets_udp/sockets_udp_apitest.cc', 'browser/extensions/api/storage/settings_apitest.cc', 'browser/extensions/api/streams_private/streams_private_apitest.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index f97dc0c7d9ed77..d9f91eaa35558b 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -831,6 +831,7 @@ 'browser/extensions/api/socket/tcp_socket_unittest.cc', 'browser/extensions/api/socket/udp_socket_unittest.cc', 'browser/extensions/api/sockets_tcp/sockets_tcp_api_unittest.cc', + 'browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc', 'browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc', 'browser/extensions/api/storage/policy_value_store_unittest.cc', 'browser/extensions/api/storage/settings_frontend_unittest.cc', diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index 8e58146f74dfed..b6e72e71c7cb20 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json @@ -525,6 +525,11 @@ "channel": "dev", "contexts": ["blessed_extension"] }, + "sockets.tcpServer": { + "dependencies": ["manifest:sockets"], + "channel": "dev", + "contexts": ["blessed_extension"] + }, "sockets.udp": { "dependencies": ["manifest:sockets"], "channel": "dev", diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp index 84e2f280c54424..3c90b0d7375035 100644 --- a/chrome/common/extensions/api/api.gyp +++ b/chrome/common/extensions/api/api.gyp @@ -94,6 +94,7 @@ 'signed_in_devices.idl', 'socket.idl', 'sockets_tcp.idl', + 'sockets_tcp_server.idl', 'sockets_udp.idl', 'storage.json', 'sync_file_system.idl', diff --git a/chrome/common/extensions/api/manifest_types.json b/chrome/common/extensions/api/manifest_types.json index 28d89ee9520532..4a798a7ac28510 100644 --- a/chrome/common/extensions/api/manifest_types.json +++ b/chrome/common/extensions/api/manifest_types.json @@ -76,6 +76,18 @@ "type": "string" } } + }, + "tcpServer": { + "description": "The tcpServer manifest property declares which sockets.tcpServer operations an app can issue.", + "optional": true, + "type": "object", + "properties": { + "listen": { + "description": "

The host:port pattern for listen operations.

", + "optional": true, + "type": "string" + } + } } } } diff --git a/chrome/common/extensions/api/sockets/sockets_handler.cc b/chrome/common/extensions/api/sockets/sockets_handler.cc index b10a8de5387198..a1b51c21e3a097 100644 --- a/chrome/common/extensions/api/sockets/sockets_handler.cc +++ b/chrome/common/extensions/api/sockets/sockets_handler.cc @@ -107,6 +107,14 @@ scoped_ptr SocketsManifestData::FromValue( return scoped_ptr(); } } + if (sockets->tcp_server) { + if (!ParseHostPattern(result.get(), + content::SocketPermissionRequest::TCP_LISTEN, + sockets->tcp_server->listen, + error)) { + return scoped_ptr(); + } + } return result.Pass(); } diff --git a/chrome/common/extensions/api/sockets_tcp_server.idl b/chrome/common/extensions/api/sockets_tcp_server.idl new file mode 100644 index 00000000000000..60c9b61d1401ad --- /dev/null +++ b/chrome/common/extensions/api/sockets_tcp_server.idl @@ -0,0 +1,191 @@ +// Copyright 2013 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. + +// Use the chrome.sockets.tcpServer API to create server +// applications using TCP connections. This API supersedes the TCP functionality +// previously found in the chrome.socket API. Note that the socket +// ids created from this namespace are not compatible with ids created in other +// namespaces. +namespace sockets.tcpServer { + // The socket properties specified in the create or + // update function. Each property is optional. If a property + // value is not specified, a default value is used when calling + // create, or the existing value if preserved when calling + // update. + dictionary SocketProperties { + // Flag indicating if the socket remains open when the event page of the + // application is unloaded (see + // Manage App + // Lifecycle). The default value is "false." When the application is + // loaded, any sockets previously opened with persistent=true can be fetched + // with getSockets. + boolean? persistent; + + // An application-defined string associated with the socket. + DOMString? name; + }; + + // Result of create call. + dictionary CreateInfo { + // The ID of the newly created server socket. + long socketId; + }; + + // Callback from the create method. + // |createInfo| : The result of the socket creation. + callback CreateCallback = void (CreateInfo createInfo); + + // Callback from the listen method. + // |result| : The result code returned from the underlying network call. + // A negative value indicates an error. + callback ListenCallback = void (long result); + + // Callback from the disconnect method. + callback DisconnectCallback = void (); + + // Callback from the close method. + callback CloseCallback = void (); + + // Callback from the update method. + callback UpdateCallback = void (); + + // Callback from the setPaused method. + callback SetPausedCallback = void (); + + // Result of the getInfo method. + dictionary SocketInfo { + // The socket identifier. + long socketId; + + // Flag indicating if the socket remains open when the event page of the + // application is unloaded (see SocketProperties.persistent). + // The default value is "false". + boolean persistent; + + // Application-defined string associated with the socket. + DOMString? name; + + // Flag indicating whether connection requests on a listening socket are + // dispatched through the onAccept event or queued up in the + // listen queue backlog. + // See setPaused. The default value is "false". + boolean paused; + + // If the socket is listening, contains its local IPv4/6 address. + DOMString? localAddress; + + // If the socket is listening, contains its local port. + long? localPort; + }; + + // Callback from the getInfo method. + // |socketInfo| : Object containing the socket information. + callback GetInfoCallback = void (SocketInfo socketInfo); + + // Callback from the getSockets method. + // |socketInfos| : Array of object containing socket information. + callback GetSocketsCallback = void (SocketInfo[] socketInfos); + + // Data from an onAccept event. + dictionary AcceptInfo { + // The server socket identifier. + long socketId; + + // The client socket identifier, i.e. the socket identifier of the newly + // established connection. This socket identifier should be used only with + // functions from the chrome.sockets.tcp namespace. + long clientSocketId; + }; + + // Data from an onAcceptError event. + dictionary AcceptErrorInfo { + // The server socket identifier. + long socketId; + + // The result code returned from the underlying network call. + long resultCode; + }; + + interface Functions { + // Creates a TCP server socket. + // |properties| : The socket properties (optional). + // |callback| : Called when the socket has been created. + static void create(optional SocketProperties properties, + CreateCallback callback); + + // Updates the socket properties. + // |socketId| : The socket identifier. + // |properties| : The properties to update. + // |callback| : Called when the properties are updated. + static void update(long socketId, + SocketProperties properties, + optional UpdateCallback callback); + + // Enables or disables a listening socket from accepting new connections. + // When paused, a listening socket accepts new connections until its backlog + // (see listen function) is full then refuses additional + // connection requests. onAccept events are raised only when + // the socket is un-paused. + static void setPaused(long socketId, + boolean paused, + optional SetPausedCallback callback); + + // Listens for connections on the specified port and address. + // If the port/address is in use, the callback indicates a failure. + // |socketId| : The socket identifier. + // |address| : The address of the local machine. + // |port| : The port of the local machine. + // |backlog| : Length of the socket's listen queue. The default value + // depends on the Operating System (SOMAXCONN), which ensures a reasonable + // queue length for most applications. + // |callback| : Called when listen operation completes. + static void listen(long socketId, + DOMString address, + long port, + optional long backlog, + ListenCallback callback); + + // Disconnects the listening socket, i.e. stops accepting new connections + // and releases the address/port the socket is bound to. The socket + // identifier remains valid, e.g. it can be used with listen to + // accept connections on a new port and address. + // |socketId| : The socket identifier. + // |callback| : Called when the disconnect attempt is complete. + static void disconnect(long socketId, + optional DisconnectCallback callback); + + // Disconnects and destroys the socket. Each socket created should be + // closed after use. The socket id is no longer valid as soon at the + // function is called. However, the socket is guaranteed to be closed only + // when the callback is invoked. + // |socketId| : The socket identifier. + // |callback| : Called when the close operation completes. + static void close(long socketId, + optional CloseCallback callback); + + // Retrieves the state of the given socket. + // |socketId| : The socket identifier. + // |callback| : Called when the socket state is available. + static void getInfo(long socketId, + GetInfoCallback callback); + + // Retrieves the list of currently opened sockets owned by the application. + // |callback| : Called when the list of sockets is available. + static void getSockets(GetSocketsCallback callback); + }; + + interface Events { + // Event raised when a connection has been made to the server socket. + // |info| : The event data. + static void onAccept(AcceptInfo info); + + // Event raised when a network error occured while the runtime was waiting + // for new connections on the socket address and port. Once this event is + // raised, the socket is set to paused and no more + // onAccept events are raised for this socket until the socket + // is resumed. + // |info| : The event data. + static void onAcceptError(AcceptErrorInfo info); + }; +}; diff --git a/chrome/test/data/extensions/api_test/sockets_tcp_server/api/background.js b/chrome/test/data/extensions/api_test/sockets_tcp_server/api/background.js new file mode 100644 index 00000000000000..bd7ed4f7a11a3f --- /dev/null +++ b/chrome/test/data/extensions/api_test/sockets_tcp_server/api/background.js @@ -0,0 +1,134 @@ +// Copyright 2013 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. + +// net/tools/testserver/testserver.py is picky about the format of what it +// calls its "echo" messages. One might go so far as to mutter to oneself that +// it isn't an echo server at all. +// +// The response is based on the request but obfuscated using a random key. +const request = "0100000005320000005hello"; + +var address; +var port = -1; +var socketId = 0; +var succeeded = false; + +// Many thanks to Dennis for his StackOverflow answer: http://goo.gl/UDanx +// Since amended to handle BlobBuilder deprecation. +function string2ArrayBuffer(string, callback) { + var blob = new Blob([string]); + var f = new FileReader(); + f.onload = function(e) { + callback(e.target.result); + }; + f.readAsArrayBuffer(blob); +} + +function arrayBuffer2String(buf, callback) { + var blob = new Blob([new Uint8Array(buf)]); + var f = new FileReader(); + f.onload = function(e) { + callback(e.target.result); + }; + f.readAsText(blob); +} + +// Tests listening on a socket and sending/receiving from accepted sockets. +var testSocketListening = function() { + var tmpSocketId = 0; + + chrome.sockets.tcpServer.create({}, onServerSocketCreate); + + function onServerSocketCreate(socketInfo) { + console.log("Server socket created: sd=" + socketInfo.socketId); + socketId = socketInfo.socketId; + chrome.sockets.tcpServer.listen(socketId, address, port, onListen); + } + + function onListen(result) { + console.log("Server socket 'listen' completed: sd=" + socketId + + ", result=" + result); + chrome.test.assertEq(0, result, "Listen failed."); + chrome.sockets.tcpServer.onAccept.addListener(onServerSocketAccept); + chrome.sockets.tcp.onReceive.addListener(onReceive); + chrome.sockets.tcp.onReceiveError.addListener(onReceiveError); + + // Create a new socket to connect to the TCP server. + chrome.sockets.tcp.create({}, function(socketInfo) { + console.log("Client socket created: sd=" + socketInfo.socketId); + tmpSocketId = socketInfo.socketId; + chrome.sockets.tcp.connect(tmpSocketId, address, port, + function(result) { + console.log("Client socket connected: sd=" + tmpSocketId); + chrome.test.assertEq(0, result, "Connect failed"); + + // Write. + string2ArrayBuffer(request, function(buf) { + chrome.sockets.tcp.send(tmpSocketId, buf, function(sendInfo) { + console.log("Client socket data sent: sd=" + tmpSocketId + + ", result=" + sendInfo.resultCode); + chrome.sockets.tcp.disconnect(tmpSocketId, function() { + console.log("Client socket disconnected: sd=" + tmpSocketId); + }); + }); + }); + }); + }); + } + + var clientSocketId; + + function onServerSocketAccept(acceptInfo) { + console.log("Server socket 'accept' event: sd=" + acceptInfo.socketId + + ", client sd=" + acceptInfo.clientSocketId); + chrome.test.assertEq(socketId, acceptInfo.socketId, "Wrong server socket."); + chrome.test.assertTrue(acceptInfo.clientSocketId > 0); + clientSocketId = acceptInfo.clientSocketId; + chrome.sockets.tcp.setPaused(clientSocketId, false, function() {}); + } + + function onReceive(receiveInfo) { + console.log("Client socket 'receive' event: sd=" + receiveInfo.socketId + + ", bytes=" + receiveInfo.data.byteLength); + chrome.test.assertEq(clientSocketId, receiveInfo.socketId, + "Received data on wrong socket"); + if (receiveInfo.data.byteLength == 0) + return; + arrayBuffer2String(receiveInfo.data, function(s) { + var match = !!s.match(request); + chrome.test.assertTrue(match, "Received data does not match."); + succeeded = true; + // Test whether socket.getInfo correctly reflects the connection status + // if the peer has closed the connection. + setTimeout(function() { + chrome.sockets.tcp.getInfo(receiveInfo.socketId, function(info) { + chrome.test.assertFalse(info.connected); + chrome.test.succeed(); + }); + }, 500); + }); + } + + function onReceiveError(receiveInfo) { + console.log("Client socket 'receive error' event: sd=" + + receiveInfo.socketId + ", result=" + receiveInfo.resultCode); + //chrome.test.fail("Receive failed."); + } +}; + +var onMessageReply = function(message) { + var parts = message.split(":"); + var test_type = parts[0]; + address = parts[1]; + port = parseInt(parts[2]); + console.log("Running tests, protocol " + test_type + ", echo server " + + address + ":" + port); + if (test_type == 'tcp_server') { + chrome.test.runTests([testSocketListening]); + } +}; + +// Find out which protocol we're supposed to test, and which echo server we +// should be using, then kick off the tests. +chrome.test.sendMessage("info_please", onMessageReply); diff --git a/chrome/test/data/extensions/api_test/sockets_tcp_server/api/manifest.json b/chrome/test/data/extensions/api_test/sockets_tcp_server/api/manifest.json new file mode 100644 index 00000000000000..9e2923cf2c5c21 --- /dev/null +++ b/chrome/test/data/extensions/api_test/sockets_tcp_server/api/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "chrome.sockets.tcpServer extension", + "version": "0.1", + "description": "end-to-end browser test for chrome.sockets.tcpServer API", + "app": { + "background": { + "scripts": ["background.js"] + } + }, + "sockets": { + "tcp": { + "connect": "" + }, + "tcpServer": { + "listen": "" + } + } +} diff --git a/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/background.js b/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/background.js new file mode 100644 index 00000000000000..d7946bd10cb021 --- /dev/null +++ b/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/background.js @@ -0,0 +1,24 @@ +// Copyright 2013 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. + +var socketId; + +var onListen = function(result) { + console.log("Server socket 'listen' completed: sd=" + socketId + + ", result=" + result); + chrome.test.assertEq(0, result); + chrome.test.succeed(); +}; + +var onCreate = function (socketInfo) { + console.log("Server socket created: sd=" + socketInfo.socketId); + socketId = socketInfo.socketId; + chrome.sockets.tcpServer.listen(socketId, '0.0.0.0', 1234, onListen); +}; + +chrome.test.runTests([ + function bind() { + chrome.sockets.tcpServer.create({}, onCreate); + } +]); diff --git a/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/manifest.json b/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/manifest.json new file mode 100644 index 00000000000000..3e508ce1d0bb5d --- /dev/null +++ b/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/manifest.json @@ -0,0 +1,15 @@ +{ + "name": "chrome.sockets.tcpServer unload", + "version": "0.1", + "description": "browser test for chrome.sockets.tcpServer API to make sure sockets are free'd when extension is reloaded", + "app": { + "background": { + "scripts": ["background.js"] + } + }, + "sockets": { + "tcpServer": { + "listen": "" + } + } +}