diff options
Diffstat (limited to 'extensions')
34 files changed, 6601 insertions, 17 deletions
diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS index f445ff6..3f59f38 100644 --- a/extensions/browser/DEPS +++ b/extensions/browser/DEPS @@ -2,6 +2,7 @@ include_rules = [ "+components/browser_context_keyed_service", "+components/user_prefs", "+content/public/browser", + "+net", "+sync", "+third_party/leveldatabase", @@ -13,14 +14,17 @@ include_rules = [ # TODO(jamescook): Remove these. http://crbug.com/162530 "+chrome/browser/chrome_notification_types.h", "+chrome/browser/extensions/api/content_settings/content_settings_store.h", + "+chrome/browser/extensions/api/dns/host_resolver_wrapper.h", "+chrome/browser/extensions/api/preference/preference_api.h", "+chrome/browser/extensions/api/runtime/runtime_api.h", "+chrome/browser/extensions/extension_function_dispatcher.h", "+chrome/browser/extensions/extension_host.h", "+chrome/browser/extensions/extension_service.h", "+chrome/browser/renderer_host/chrome_render_message_filter.h", + "+chrome/common/extensions/api/sockets/sockets_manifest_data.h", "+chrome/common/extensions/extension_messages.h", "+chrome/common/extensions/features/feature_channel.h", + "+chrome/common/extensions/permissions", "+grit/generated_resources.h", ] diff --git a/extensions/browser/api/api_resource_manager.h b/extensions/browser/api/api_resource_manager.h index 0513c2c..39e9b8e 100644 --- a/extensions/browser/api/api_resource_manager.h +++ b/extensions/browser/api/api_resource_manager.h @@ -23,15 +23,16 @@ #include "extensions/common/extension.h" namespace extensions { + namespace api { class SerialEventDispatcher; +} + +namespace core_api { class TCPServerSocketEventDispatcher; class TCPSocketEventDispatcher; class UDPSocketEventDispatcher; } -} - -namespace extensions { // An ApiResourceManager manages the lifetime of a set of resources that // ApiFunctions use. Examples are sockets or USB connections. @@ -149,13 +150,17 @@ class ApiResourceManager : public BrowserContextKeyedAPI, } private: + // TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and + // we could avoid maintaining a friends list here. friend class api::SerialEventDispatcher; - friend class api::TCPServerSocketEventDispatcher; - friend class api::TCPSocketEventDispatcher; - friend class api::UDPSocketEventDispatcher; + friend class core_api::TCPServerSocketEventDispatcher; + friend class core_api::TCPSocketEventDispatcher; + friend class core_api::UDPSocketEventDispatcher; friend class BrowserContextKeyedAPIFactory<ApiResourceManager<T> >; + // BrowserContextKeyedAPI implementation. static const char* service_name() { return T::service_name(); } + static const bool kServiceHasOwnInstanceInIncognito = true; static const bool kServiceIsNULLWhileTesting = true; diff --git a/extensions/browser/api/socket/socket.cc b/extensions/browser/api/socket/socket.cc new file mode 100644 index 0000000..afec38f --- /dev/null +++ b/extensions/browser/api/socket/socket.cc @@ -0,0 +1,152 @@ +// Copyright 2014 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 "extensions/browser/api/socket/socket.h" + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "extensions/browser/api/api_resource_manager.h" +#include "net/base/address_list.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/socket/socket.h" + +namespace extensions { + +const char kSocketTypeNotSupported[] = "Socket type does not support this API"; + +static base::LazyInstance< + BrowserContextKeyedAPIFactory<ApiResourceManager<Socket> > > g_factory = + LAZY_INSTANCE_INITIALIZER; + +// static +template <> +BrowserContextKeyedAPIFactory<ApiResourceManager<Socket> >* +ApiResourceManager<Socket>::GetFactoryInstance() { + return g_factory.Pointer(); +} + +Socket::Socket(const std::string& owner_extension_id) + : ApiResource(owner_extension_id), is_connected_(false) {} + +Socket::~Socket() { + // Derived destructors should make sure the socket has been closed. + DCHECK(!is_connected_); +} + +void Socket::Write(scoped_refptr<net::IOBuffer> io_buffer, + int byte_count, + const CompletionCallback& callback) { + DCHECK(!callback.is_null()); + write_queue_.push(WriteRequest(io_buffer, byte_count, callback)); + WriteData(); +} + +void Socket::WriteData() { + // IO is pending. + if (io_buffer_write_.get()) + return; + + WriteRequest& request = write_queue_.front(); + + DCHECK(request.byte_count >= request.bytes_written); + io_buffer_write_ = new net::WrappedIOBuffer(request.io_buffer->data() + + request.bytes_written); + int result = + WriteImpl(io_buffer_write_.get(), + request.byte_count - request.bytes_written, + base::Bind(&Socket::OnWriteComplete, base::Unretained(this))); + + if (result != net::ERR_IO_PENDING) + OnWriteComplete(result); +} + +void Socket::OnWriteComplete(int result) { + io_buffer_write_ = NULL; + + WriteRequest& request = write_queue_.front(); + + if (result >= 0) { + request.bytes_written += result; + if (request.bytes_written < request.byte_count) { + WriteData(); + return; + } + DCHECK(request.bytes_written == request.byte_count); + result = request.bytes_written; + } + + request.callback.Run(result); + write_queue_.pop(); + + if (!write_queue_.empty()) + WriteData(); +} + +bool Socket::SetKeepAlive(bool enable, int delay) { return false; } + +bool Socket::SetNoDelay(bool no_delay) { return false; } + +int Socket::Listen(const std::string& address, + int port, + int backlog, + std::string* error_msg) { + *error_msg = kSocketTypeNotSupported; + return net::ERR_FAILED; +} + +void Socket::Accept(const AcceptCompletionCallback& callback) { + callback.Run(net::ERR_FAILED, NULL); +} + +// static +bool Socket::StringAndPortToIPEndPoint(const std::string& ip_address_str, + int port, + net::IPEndPoint* ip_end_point) { + DCHECK(ip_end_point); + net::IPAddressNumber ip_number; + if (!net::ParseIPLiteralToNumber(ip_address_str, &ip_number)) + return false; + + *ip_end_point = net::IPEndPoint(ip_number, port); + return true; +} + +bool Socket::StringAndPortToAddressList(const std::string& ip_address_str, + int port, + net::AddressList* address_list) { + DCHECK(address_list); + net::IPAddressNumber ip_number; + if (!net::ParseIPLiteralToNumber(ip_address_str, &ip_number)) + return false; + + *address_list = net::AddressList::CreateFromIPAddress(ip_number, port); + return true; +} + +void Socket::IPEndPointToStringAndPort(const net::IPEndPoint& address, + std::string* ip_address_str, + int* port) { + DCHECK(ip_address_str); + DCHECK(port); + *ip_address_str = address.ToStringWithoutPort(); + if (ip_address_str->empty()) { + *port = 0; + } else { + *port = address.port(); + } +} + +Socket::WriteRequest::WriteRequest(scoped_refptr<net::IOBuffer> io_buffer, + int byte_count, + const CompletionCallback& callback) + : io_buffer(io_buffer), + byte_count(byte_count), + callback(callback), + bytes_written(0) {} + +Socket::WriteRequest::~WriteRequest() {} + +} // namespace extensions diff --git a/extensions/browser/api/socket/socket.h b/extensions/browser/api/socket/socket.h new file mode 100644 index 0000000..9ae45e7 --- /dev/null +++ b/extensions/browser/api/socket/socket.h @@ -0,0 +1,126 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKET_SOCKET_H_ +#define EXTENSIONS_BROWSER_API_SOCKET_SOCKET_H_ + +#include <queue> +#include <string> +#include <utility> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "extensions/browser/api/api_resource.h" +#include "extensions/browser/api/api_resource_manager.h" +#include "net/base/completion_callback.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/socket/tcp_client_socket.h" + +namespace net { +class AddressList; +class IPEndPoint; +class Socket; +} + +namespace extensions { + +typedef base::Callback<void(int)> CompletionCallback; +typedef base::Callback<void(int, scoped_refptr<net::IOBuffer> io_buffer)> + ReadCompletionCallback; +typedef base::Callback< + void(int, scoped_refptr<net::IOBuffer> io_buffer, const std::string&, int)> + RecvFromCompletionCallback; +typedef base::Callback<void(int, net::TCPClientSocket*)> + AcceptCompletionCallback; + +// A Socket wraps a low-level socket and includes housekeeping information that +// we need to manage it in the context of an extension. +class Socket : public ApiResource { + public: + enum SocketType { TYPE_TCP, TYPE_UDP, }; + + virtual ~Socket(); + virtual void Connect(const std::string& address, + int port, + const CompletionCallback& callback) = 0; + virtual void Disconnect() = 0; + virtual int Bind(const std::string& address, int port) = 0; + + // The |callback| will be called with the number of bytes read into the + // buffer, or a negative number if an error occurred. + virtual void Read(int count, const ReadCompletionCallback& callback) = 0; + + // The |callback| will be called with |byte_count| or a negative number if an + // error occurred. + void Write(scoped_refptr<net::IOBuffer> io_buffer, + int byte_count, + const CompletionCallback& callback); + + virtual void RecvFrom(int count, + const RecvFromCompletionCallback& callback) = 0; + virtual void SendTo(scoped_refptr<net::IOBuffer> io_buffer, + int byte_count, + const std::string& address, + int port, + const CompletionCallback& callback) = 0; + + virtual bool SetKeepAlive(bool enable, int delay); + virtual bool SetNoDelay(bool no_delay); + virtual int Listen(const std::string& address, + int port, + int backlog, + std::string* error_msg); + virtual void Accept(const AcceptCompletionCallback& callback); + + virtual bool IsConnected() = 0; + + virtual bool GetPeerAddress(net::IPEndPoint* address) = 0; + virtual bool GetLocalAddress(net::IPEndPoint* address) = 0; + + virtual SocketType GetSocketType() const = 0; + + static bool StringAndPortToAddressList(const std::string& ip_address_str, + int port, + net::AddressList* address_list); + static bool StringAndPortToIPEndPoint(const std::string& ip_address_str, + int port, + net::IPEndPoint* ip_end_point); + static void IPEndPointToStringAndPort(const net::IPEndPoint& address, + std::string* ip_address_str, + int* port); + + protected: + explicit Socket(const std::string& owner_extension_id_); + + void WriteData(); + virtual int WriteImpl(net::IOBuffer* io_buffer, + int io_buffer_size, + const net::CompletionCallback& callback) = 0; + virtual void OnWriteComplete(int result); + + const std::string address_; + bool is_connected_; + + private: + friend class ApiResourceManager<Socket>; + static const char* service_name() { return "SocketManager"; } + + struct WriteRequest { + WriteRequest(scoped_refptr<net::IOBuffer> io_buffer, + int byte_count, + const CompletionCallback& callback); + ~WriteRequest(); + scoped_refptr<net::IOBuffer> io_buffer; + int byte_count; + CompletionCallback callback; + int bytes_written; + }; + std::queue<WriteRequest> write_queue_; + scoped_refptr<net::IOBuffer> io_buffer_write_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKET_SOCKET_H_ diff --git a/extensions/browser/api/socket/socket/OWNERS b/extensions/browser/api/socket/socket/OWNERS new file mode 100644 index 0000000..b0df7e7 --- /dev/null +++ b/extensions/browser/api/socket/socket/OWNERS @@ -0,0 +1,2 @@ +ikarienator@chromium.org +rpaquay@chromium.org diff --git a/extensions/browser/api/socket/socket_api.cc b/extensions/browser/api/socket/socket_api.cc new file mode 100644 index 0000000..34a5517 --- /dev/null +++ b/extensions/browser/api/socket/socket_api.cc @@ -0,0 +1,891 @@ +// Copyright 2014 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 "extensions/browser/api/socket/socket_api.h" + +#include <vector> + +#include "base/bind.h" +#include "base/containers/hash_tables.h" +#include "chrome/browser/extensions/api/dns/host_resolver_wrapper.h" +#include "chrome/common/extensions/permissions/socket_permission.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/resource_context.h" +#include "extensions/browser/api/socket/socket.h" +#include "extensions/browser/api/socket/tcp_socket.h" +#include "extensions/browser/api/socket/udp_socket.h" +#include "extensions/browser/extension_system.h" +#include "extensions/common/extension.h" +#include "extensions/common/permissions/permissions_data.h" +#include "net/base/host_port_pair.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/base/net_util.h" + +namespace extensions { + +using content::SocketPermissionRequest; + +const char kAddressKey[] = "address"; +const char kPortKey[] = "port"; +const char kBytesWrittenKey[] = "bytesWritten"; +const char kDataKey[] = "data"; +const char kResultCodeKey[] = "resultCode"; +const char kSocketIdKey[] = "socketId"; + +const char kSocketNotFoundError[] = "Socket not found"; +const char kDnsLookupFailedError[] = "DNS resolution failed"; +const char kPermissionError[] = "App does not have permission"; +const char kNetworkListError[] = "Network lookup failed or unsupported"; +const char kTCPSocketBindError[] = + "TCP socket does not support bind. For TCP server please use listen."; +const char kMulticastSocketTypeError[] = "Only UDP socket supports multicast."; +const char kWildcardAddress[] = "*"; +const int kWildcardPort = 0; + +SocketAsyncApiFunction::SocketAsyncApiFunction() {} + +SocketAsyncApiFunction::~SocketAsyncApiFunction() {} + +bool SocketAsyncApiFunction::PrePrepare() { + manager_ = CreateSocketResourceManager(); + return manager_->SetBrowserContext(browser_context()); +} + +bool SocketAsyncApiFunction::Respond() { return error_.empty(); } + +scoped_ptr<SocketResourceManagerInterface> +SocketAsyncApiFunction::CreateSocketResourceManager() { + return scoped_ptr<SocketResourceManagerInterface>( + new SocketResourceManager<Socket>()).Pass(); +} + +int SocketAsyncApiFunction::AddSocket(Socket* socket) { + return manager_->Add(socket); +} + +Socket* SocketAsyncApiFunction::GetSocket(int api_resource_id) { + return manager_->Get(extension_->id(), api_resource_id); +} + +base::hash_set<int>* SocketAsyncApiFunction::GetSocketIds() { + return manager_->GetResourceIds(extension_->id()); +} + +void SocketAsyncApiFunction::RemoveSocket(int api_resource_id) { + manager_->Remove(extension_->id(), api_resource_id); +} + +SocketExtensionWithDnsLookupFunction::SocketExtensionWithDnsLookupFunction() + : resource_context_(NULL), + request_handle_(new net::HostResolver::RequestHandle), + addresses_(new net::AddressList) {} + +SocketExtensionWithDnsLookupFunction::~SocketExtensionWithDnsLookupFunction() {} + +bool SocketExtensionWithDnsLookupFunction::PrePrepare() { + if (!SocketAsyncApiFunction::PrePrepare()) + return false; + resource_context_ = browser_context()->GetResourceContext(); + return resource_context_ != NULL; +} + +void SocketExtensionWithDnsLookupFunction::StartDnsLookup( + const std::string& hostname) { + net::HostResolver* host_resolver = + extensions::HostResolverWrapper::GetInstance()->GetHostResolver( + resource_context_->GetHostResolver()); + DCHECK(host_resolver); + + // Yes, we are passing zero as the port. There are some interesting but not + // presently relevant reasons why HostResolver asks for the port of the + // hostname you'd like to resolve, even though it doesn't use that value in + // determining its answer. + net::HostPortPair host_port_pair(hostname, 0); + + net::HostResolver::RequestInfo request_info(host_port_pair); + int resolve_result = host_resolver->Resolve( + request_info, + net::DEFAULT_PRIORITY, + addresses_.get(), + base::Bind(&SocketExtensionWithDnsLookupFunction::OnDnsLookup, this), + request_handle_.get(), + net::BoundNetLog()); + + if (resolve_result != net::ERR_IO_PENDING) + OnDnsLookup(resolve_result); +} + +void SocketExtensionWithDnsLookupFunction::OnDnsLookup(int resolve_result) { + if (resolve_result == net::OK) { + DCHECK(!addresses_->empty()); + resolved_address_ = addresses_->front().ToStringWithoutPort(); + } else { + error_ = kDnsLookupFailedError; + } + AfterDnsLookup(resolve_result); +} + +SocketCreateFunction::SocketCreateFunction() + : socket_type_(kSocketTypeInvalid) {} + +SocketCreateFunction::~SocketCreateFunction() {} + +bool SocketCreateFunction::Prepare() { + params_ = core_api::socket::Create::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + + switch (params_->type) { + case extensions::core_api::socket::SOCKET_TYPE_TCP: + socket_type_ = kSocketTypeTCP; + break; + case extensions::core_api::socket::SOCKET_TYPE_UDP: + socket_type_ = kSocketTypeUDP; + break; + case extensions::core_api::socket::SOCKET_TYPE_NONE: + NOTREACHED(); + break; + } + + return true; +} + +void SocketCreateFunction::Work() { + Socket* socket = NULL; + if (socket_type_ == kSocketTypeTCP) { + socket = new TCPSocket(extension_->id()); + } else if (socket_type_ == kSocketTypeUDP) { + socket = new UDPSocket(extension_->id()); + } + DCHECK(socket); + + base::DictionaryValue* result = new base::DictionaryValue(); + result->SetInteger(kSocketIdKey, AddSocket(socket)); + SetResult(result); +} + +bool SocketDestroyFunction::Prepare() { + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_)); + return true; +} + +void SocketDestroyFunction::Work() { RemoveSocket(socket_id_); } + +SocketConnectFunction::SocketConnectFunction() + : socket_id_(0), hostname_(), port_(0), socket_(NULL) {} + +SocketConnectFunction::~SocketConnectFunction() {} + +bool SocketConnectFunction::Prepare() { + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_)); + EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &hostname_)); + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(2, &port_)); + return true; +} + +void SocketConnectFunction::AsyncWorkStart() { + socket_ = GetSocket(socket_id_); + if (!socket_) { + error_ = kSocketNotFoundError; + SetResult(new base::FundamentalValue(-1)); + AsyncWorkCompleted(); + return; + } + + SocketPermissionRequest::OperationType operation_type; + switch (socket_->GetSocketType()) { + case Socket::TYPE_TCP: + operation_type = SocketPermissionRequest::TCP_CONNECT; + break; + case Socket::TYPE_UDP: + operation_type = SocketPermissionRequest::UDP_SEND_TO; + break; + default: + NOTREACHED() << "Unknown socket type."; + operation_type = SocketPermissionRequest::NONE; + break; + } + + SocketPermission::CheckParam param(operation_type, hostname_, port_); + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), APIPermission::kSocket, ¶m)) { + error_ = kPermissionError; + SetResult(new base::FundamentalValue(-1)); + AsyncWorkCompleted(); + return; + } + + StartDnsLookup(hostname_); +} + +void SocketConnectFunction::AfterDnsLookup(int lookup_result) { + if (lookup_result == net::OK) { + StartConnect(); + } else { + SetResult(new base::FundamentalValue(lookup_result)); + AsyncWorkCompleted(); + } +} + +void SocketConnectFunction::StartConnect() { + socket_->Connect(resolved_address_, + port_, + base::Bind(&SocketConnectFunction::OnConnect, this)); +} + +void SocketConnectFunction::OnConnect(int result) { + SetResult(new base::FundamentalValue(result)); + AsyncWorkCompleted(); +} + +bool SocketDisconnectFunction::Prepare() { + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_)); + return true; +} + +void SocketDisconnectFunction::Work() { + Socket* socket = GetSocket(socket_id_); + if (socket) + socket->Disconnect(); + else + error_ = kSocketNotFoundError; + SetResult(base::Value::CreateNullValue()); +} + +bool SocketBindFunction::Prepare() { + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_)); + EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &address_)); + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(2, &port_)); + return true; +} + +void SocketBindFunction::Work() { + int result = -1; + Socket* socket = GetSocket(socket_id_); + + if (!socket) { + error_ = kSocketNotFoundError; + SetResult(new base::FundamentalValue(result)); + return; + } + + if (socket->GetSocketType() == Socket::TYPE_UDP) { + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_BIND, address_, port_); + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), APIPermission::kSocket, ¶m)) { + error_ = kPermissionError; + SetResult(new base::FundamentalValue(result)); + return; + } + } else if (socket->GetSocketType() == Socket::TYPE_TCP) { + error_ = kTCPSocketBindError; + SetResult(new base::FundamentalValue(result)); + return; + } + + result = socket->Bind(address_, port_); + SetResult(new base::FundamentalValue(result)); +} + +SocketListenFunction::SocketListenFunction() {} + +SocketListenFunction::~SocketListenFunction() {} + +bool SocketListenFunction::Prepare() { + params_ = core_api::socket::Listen::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketListenFunction::Work() { + int result = -1; + + Socket* socket = GetSocket(params_->socket_id); + if (socket) { + SocketPermission::CheckParam param( + SocketPermissionRequest::TCP_LISTEN, params_->address, params_->port); + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), APIPermission::kSocket, ¶m)) { + error_ = kPermissionError; + SetResult(new base::FundamentalValue(result)); + return; + } + + result = + socket->Listen(params_->address, + params_->port, + params_->backlog.get() ? *params_->backlog.get() : 5, + &error_); + } else { + error_ = kSocketNotFoundError; + } + + SetResult(new base::FundamentalValue(result)); +} + +SocketAcceptFunction::SocketAcceptFunction() {} + +SocketAcceptFunction::~SocketAcceptFunction() {} + +bool SocketAcceptFunction::Prepare() { + params_ = core_api::socket::Accept::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketAcceptFunction::AsyncWorkStart() { + Socket* socket = GetSocket(params_->socket_id); + if (socket) { + socket->Accept(base::Bind(&SocketAcceptFunction::OnAccept, this)); + } else { + error_ = kSocketNotFoundError; + OnAccept(-1, NULL); + } +} + +void SocketAcceptFunction::OnAccept(int result_code, + net::TCPClientSocket* socket) { + base::DictionaryValue* result = new base::DictionaryValue(); + result->SetInteger(kResultCodeKey, result_code); + if (socket) { + Socket* client_socket = new TCPSocket(socket, extension_id(), true); + result->SetInteger(kSocketIdKey, AddSocket(client_socket)); + } + SetResult(result); + + AsyncWorkCompleted(); +} + +SocketReadFunction::SocketReadFunction() {} + +SocketReadFunction::~SocketReadFunction() {} + +bool SocketReadFunction::Prepare() { + params_ = core_api::socket::Read::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketReadFunction::AsyncWorkStart() { + Socket* socket = GetSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + OnCompleted(-1, NULL); + return; + } + + socket->Read(params_->buffer_size.get() ? *params_->buffer_size.get() : 4096, + base::Bind(&SocketReadFunction::OnCompleted, this)); +} + +void SocketReadFunction::OnCompleted(int bytes_read, + scoped_refptr<net::IOBuffer> io_buffer) { + base::DictionaryValue* result = new base::DictionaryValue(); + result->SetInteger(kResultCodeKey, bytes_read); + if (bytes_read > 0) { + result->Set(kDataKey, + base::BinaryValue::CreateWithCopiedBuffer(io_buffer->data(), + bytes_read)); + } else { + result->Set(kDataKey, new base::BinaryValue()); + } + SetResult(result); + + AsyncWorkCompleted(); +} + +SocketWriteFunction::SocketWriteFunction() + : socket_id_(0), io_buffer_(NULL), io_buffer_size_(0) {} + +SocketWriteFunction::~SocketWriteFunction() {} + +bool SocketWriteFunction::Prepare() { + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_)); + base::BinaryValue* data = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetBinary(1, &data)); + + io_buffer_size_ = data->GetSize(); + io_buffer_ = new net::WrappedIOBuffer(data->GetBuffer()); + return true; +} + +void SocketWriteFunction::AsyncWorkStart() { + Socket* socket = GetSocket(socket_id_); + + if (!socket) { + error_ = kSocketNotFoundError; + OnCompleted(-1); + return; + } + + socket->Write(io_buffer_, + io_buffer_size_, + base::Bind(&SocketWriteFunction::OnCompleted, this)); +} + +void SocketWriteFunction::OnCompleted(int bytes_written) { + base::DictionaryValue* result = new base::DictionaryValue(); + result->SetInteger(kBytesWrittenKey, bytes_written); + SetResult(result); + + AsyncWorkCompleted(); +} + +SocketRecvFromFunction::SocketRecvFromFunction() {} + +SocketRecvFromFunction::~SocketRecvFromFunction() {} + +bool SocketRecvFromFunction::Prepare() { + params_ = core_api::socket::RecvFrom::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketRecvFromFunction::AsyncWorkStart() { + Socket* socket = GetSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + OnCompleted(-1, NULL, std::string(), 0); + return; + } + + socket->RecvFrom(params_->buffer_size.get() ? *params_->buffer_size : 4096, + base::Bind(&SocketRecvFromFunction::OnCompleted, this)); +} + +void SocketRecvFromFunction::OnCompleted(int bytes_read, + scoped_refptr<net::IOBuffer> io_buffer, + const std::string& address, + int port) { + base::DictionaryValue* result = new base::DictionaryValue(); + result->SetInteger(kResultCodeKey, bytes_read); + if (bytes_read > 0) { + result->Set(kDataKey, + base::BinaryValue::CreateWithCopiedBuffer(io_buffer->data(), + bytes_read)); + } else { + result->Set(kDataKey, new base::BinaryValue()); + } + result->SetString(kAddressKey, address); + result->SetInteger(kPortKey, port); + SetResult(result); + + AsyncWorkCompleted(); +} + +SocketSendToFunction::SocketSendToFunction() + : socket_id_(0), + io_buffer_(NULL), + io_buffer_size_(0), + port_(0), + socket_(NULL) {} + +SocketSendToFunction::~SocketSendToFunction() {} + +bool SocketSendToFunction::Prepare() { + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &socket_id_)); + base::BinaryValue* data = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetBinary(1, &data)); + EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &hostname_)); + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(3, &port_)); + + io_buffer_size_ = data->GetSize(); + io_buffer_ = new net::WrappedIOBuffer(data->GetBuffer()); + return true; +} + +void SocketSendToFunction::AsyncWorkStart() { + socket_ = GetSocket(socket_id_); + if (!socket_) { + error_ = kSocketNotFoundError; + SetResult(new base::FundamentalValue(-1)); + AsyncWorkCompleted(); + return; + } + + if (socket_->GetSocketType() == Socket::TYPE_UDP) { + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_SEND_TO, hostname_, port_); + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), APIPermission::kSocket, ¶m)) { + error_ = kPermissionError; + SetResult(new base::FundamentalValue(-1)); + AsyncWorkCompleted(); + return; + } + } + + StartDnsLookup(hostname_); +} + +void SocketSendToFunction::AfterDnsLookup(int lookup_result) { + if (lookup_result == net::OK) { + StartSendTo(); + } else { + SetResult(new base::FundamentalValue(lookup_result)); + AsyncWorkCompleted(); + } +} + +void SocketSendToFunction::StartSendTo() { + socket_->SendTo(io_buffer_, + io_buffer_size_, + resolved_address_, + port_, + base::Bind(&SocketSendToFunction::OnCompleted, this)); +} + +void SocketSendToFunction::OnCompleted(int bytes_written) { + base::DictionaryValue* result = new base::DictionaryValue(); + result->SetInteger(kBytesWrittenKey, bytes_written); + SetResult(result); + + AsyncWorkCompleted(); +} + +SocketSetKeepAliveFunction::SocketSetKeepAliveFunction() {} + +SocketSetKeepAliveFunction::~SocketSetKeepAliveFunction() {} + +bool SocketSetKeepAliveFunction::Prepare() { + params_ = core_api::socket::SetKeepAlive::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketSetKeepAliveFunction::Work() { + bool result = false; + Socket* socket = GetSocket(params_->socket_id); + if (socket) { + int delay = 0; + if (params_->delay.get()) + delay = *params_->delay; + result = socket->SetKeepAlive(params_->enable, delay); + } else { + error_ = kSocketNotFoundError; + } + SetResult(new base::FundamentalValue(result)); +} + +SocketSetNoDelayFunction::SocketSetNoDelayFunction() {} + +SocketSetNoDelayFunction::~SocketSetNoDelayFunction() {} + +bool SocketSetNoDelayFunction::Prepare() { + params_ = core_api::socket::SetNoDelay::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketSetNoDelayFunction::Work() { + bool result = false; + Socket* socket = GetSocket(params_->socket_id); + if (socket) + result = socket->SetNoDelay(params_->no_delay); + else + error_ = kSocketNotFoundError; + SetResult(new base::FundamentalValue(result)); +} + +SocketGetInfoFunction::SocketGetInfoFunction() {} + +SocketGetInfoFunction::~SocketGetInfoFunction() {} + +bool SocketGetInfoFunction::Prepare() { + params_ = core_api::socket::GetInfo::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketGetInfoFunction::Work() { + Socket* socket = GetSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + core_api::socket::SocketInfo info; + // This represents what we know about the socket, and does not call through + // to the system. + if (socket->GetSocketType() == Socket::TYPE_TCP) + info.socket_type = extensions::core_api::socket::SOCKET_TYPE_TCP; + else + info.socket_type = extensions::core_api::socket::SOCKET_TYPE_UDP; + info.connected = socket->IsConnected(); + + // Grab the peer address as known by the OS. This and the call below will + // always succeed while the socket is connected, even if the socket has + // been remotely closed by the peer; only reading the socket will reveal + // that it should be closed locally. + net::IPEndPoint peerAddress; + if (socket->GetPeerAddress(&peerAddress)) { + info.peer_address.reset(new std::string(peerAddress.ToStringWithoutPort())); + info.peer_port.reset(new int(peerAddress.port())); + } + + // Grab the local address as known by the OS. + net::IPEndPoint localAddress; + if (socket->GetLocalAddress(&localAddress)) { + info.local_address.reset( + new std::string(localAddress.ToStringWithoutPort())); + info.local_port.reset(new int(localAddress.port())); + } + + SetResult(info.ToValue().release()); +} + +bool SocketGetNetworkListFunction::RunImpl() { + content::BrowserThread::PostTask( + content::BrowserThread::FILE, + FROM_HERE, + base::Bind(&SocketGetNetworkListFunction::GetNetworkListOnFileThread, + this)); + return true; +} + +void SocketGetNetworkListFunction::GetNetworkListOnFileThread() { + net::NetworkInterfaceList interface_list; + if (GetNetworkList(&interface_list, + net::INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES)) { + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&SocketGetNetworkListFunction::SendResponseOnUIThread, + this, + interface_list)); + return; + } + + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&SocketGetNetworkListFunction::HandleGetNetworkListError, + this)); +} + +void SocketGetNetworkListFunction::HandleGetNetworkListError() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + error_ = kNetworkListError; + SendResponse(false); +} + +void SocketGetNetworkListFunction::SendResponseOnUIThread( + const net::NetworkInterfaceList& interface_list) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + std::vector<linked_ptr<core_api::socket::NetworkInterface> > create_arg; + create_arg.reserve(interface_list.size()); + for (net::NetworkInterfaceList::const_iterator i = interface_list.begin(); + i != interface_list.end(); + ++i) { + linked_ptr<core_api::socket::NetworkInterface> info = + make_linked_ptr(new core_api::socket::NetworkInterface); + info->name = i->name; + info->address = net::IPAddressToString(i->address); + info->prefix_length = i->network_prefix; + create_arg.push_back(info); + } + + results_ = core_api::socket::GetNetworkList::Results::Create(create_arg); + SendResponse(true); +} + +SocketJoinGroupFunction::SocketJoinGroupFunction() {} + +SocketJoinGroupFunction::~SocketJoinGroupFunction() {} + +bool SocketJoinGroupFunction::Prepare() { + params_ = core_api::socket::JoinGroup::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketJoinGroupFunction::Work() { + int result = -1; + Socket* socket = GetSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + SetResult(new base::FundamentalValue(result)); + return; + } + + if (socket->GetSocketType() != Socket::TYPE_UDP) { + error_ = kMulticastSocketTypeError; + SetResult(new base::FundamentalValue(result)); + return; + } + + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, + kWildcardAddress, + kWildcardPort); + + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), APIPermission::kSocket, ¶m)) { + error_ = kPermissionError; + SetResult(new base::FundamentalValue(result)); + return; + } + + result = static_cast<UDPSocket*>(socket)->JoinGroup(params_->address); + if (result != 0) { + error_ = net::ErrorToString(result); + } + SetResult(new base::FundamentalValue(result)); +} + +SocketLeaveGroupFunction::SocketLeaveGroupFunction() {} + +SocketLeaveGroupFunction::~SocketLeaveGroupFunction() {} + +bool SocketLeaveGroupFunction::Prepare() { + params_ = core_api::socket::LeaveGroup::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketLeaveGroupFunction::Work() { + int result = -1; + Socket* socket = GetSocket(params_->socket_id); + + if (!socket) { + error_ = kSocketNotFoundError; + SetResult(new base::FundamentalValue(result)); + return; + } + + if (socket->GetSocketType() != Socket::TYPE_UDP) { + error_ = kMulticastSocketTypeError; + SetResult(new base::FundamentalValue(result)); + return; + } + + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, + kWildcardAddress, + kWildcardPort); + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), APIPermission::kSocket, ¶m)) { + error_ = kPermissionError; + SetResult(new base::FundamentalValue(result)); + return; + } + + result = static_cast<UDPSocket*>(socket)->LeaveGroup(params_->address); + if (result != 0) + error_ = net::ErrorToString(result); + SetResult(new base::FundamentalValue(result)); +} + +SocketSetMulticastTimeToLiveFunction::SocketSetMulticastTimeToLiveFunction() {} + +SocketSetMulticastTimeToLiveFunction::~SocketSetMulticastTimeToLiveFunction() {} + +bool SocketSetMulticastTimeToLiveFunction::Prepare() { + params_ = core_api::socket::SetMulticastTimeToLive::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} +void SocketSetMulticastTimeToLiveFunction::Work() { + int result = -1; + Socket* socket = GetSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + SetResult(new base::FundamentalValue(result)); + return; + } + + if (socket->GetSocketType() != Socket::TYPE_UDP) { + error_ = kMulticastSocketTypeError; + SetResult(new base::FundamentalValue(result)); + return; + } + + result = + static_cast<UDPSocket*>(socket)->SetMulticastTimeToLive(params_->ttl); + if (result != 0) + error_ = net::ErrorToString(result); + SetResult(new base::FundamentalValue(result)); +} + +SocketSetMulticastLoopbackModeFunction:: + SocketSetMulticastLoopbackModeFunction() {} + +SocketSetMulticastLoopbackModeFunction:: + ~SocketSetMulticastLoopbackModeFunction() {} + +bool SocketSetMulticastLoopbackModeFunction::Prepare() { + params_ = core_api::socket::SetMulticastLoopbackMode::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketSetMulticastLoopbackModeFunction::Work() { + int result = -1; + Socket* socket = GetSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + SetResult(new base::FundamentalValue(result)); + return; + } + + if (socket->GetSocketType() != Socket::TYPE_UDP) { + error_ = kMulticastSocketTypeError; + SetResult(new base::FundamentalValue(result)); + return; + } + + result = static_cast<UDPSocket*>(socket) + ->SetMulticastLoopbackMode(params_->enabled); + if (result != 0) + error_ = net::ErrorToString(result); + SetResult(new base::FundamentalValue(result)); +} + +SocketGetJoinedGroupsFunction::SocketGetJoinedGroupsFunction() {} + +SocketGetJoinedGroupsFunction::~SocketGetJoinedGroupsFunction() {} + +bool SocketGetJoinedGroupsFunction::Prepare() { + params_ = core_api::socket::GetJoinedGroups::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketGetJoinedGroupsFunction::Work() { + int result = -1; + Socket* socket = GetSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + SetResult(new base::FundamentalValue(result)); + return; + } + + if (socket->GetSocketType() != Socket::TYPE_UDP) { + error_ = kMulticastSocketTypeError; + SetResult(new base::FundamentalValue(result)); + return; + } + + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, + kWildcardAddress, + kWildcardPort); + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), APIPermission::kSocket, ¶m)) { + error_ = kPermissionError; + SetResult(new base::FundamentalValue(result)); + return; + } + + base::ListValue* values = new base::ListValue(); + values->AppendStrings((std::vector<std::string>&)static_cast<UDPSocket*>( + socket)->GetJoinedGroups()); + SetResult(values); +} + +} // namespace extensions diff --git a/extensions/browser/api/socket/socket_api.h b/extensions/browser/api/socket/socket_api.h new file mode 100644 index 0000000..60bd765 --- /dev/null +++ b/extensions/browser/api/socket/socket_api.h @@ -0,0 +1,507 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKET_SOCKET_API_H_ +#define EXTENSIONS_BROWSER_API_SOCKET_SOCKET_API_H_ + +#include <string> + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "extensions/browser/api/api_resource_manager.h" +#include "extensions/browser/api/async_api_function.h" +#include "extensions/browser/extension_function.h" +#include "extensions/common/api/socket.h" +#include "net/base/address_list.h" +#include "net/dns/host_resolver.h" +#include "net/socket/tcp_client_socket.h" + +namespace content { +class BrowserContext; +class ResourceContext; +} + +namespace net { +class IOBuffer; +} + +namespace extensions { + +class Socket; + +// A simple interface to ApiResourceManager<Socket> or derived class. The goal +// of this interface is to allow Socket API functions to use distinct instances +// of ApiResourceManager<> depending on the type of socket (old version in +// "socket" namespace vs new version in "socket.xxx" namespaces). +class SocketResourceManagerInterface { + public: + virtual ~SocketResourceManagerInterface() {} + + virtual bool SetBrowserContext(content::BrowserContext* context) = 0; + virtual int Add(Socket* socket) = 0; + virtual Socket* Get(const std::string& extension_id, int api_resource_id) = 0; + virtual void Remove(const std::string& extension_id, int api_resource_id) = 0; + virtual base::hash_set<int>* GetResourceIds( + const std::string& extension_id) = 0; +}; + +// Implementation of SocketResourceManagerInterface using an +// ApiResourceManager<T> instance (where T derives from Socket). +template <typename T> +class SocketResourceManager : public SocketResourceManagerInterface { + public: + SocketResourceManager() : manager_(NULL) {} + + virtual bool SetBrowserContext(content::BrowserContext* context) OVERRIDE { + manager_ = ApiResourceManager<T>::Get(context); + DCHECK(manager_) + << "There is no socket manager. " + "If this assertion is failing during a test, then it is likely that " + "TestExtensionSystem is failing to provide an instance of " + "ApiResourceManager<Socket>."; + return manager_ != NULL; + } + + virtual int Add(Socket* socket) OVERRIDE { + // Note: Cast needed here, because "T" may be a subclass of "Socket". + return manager_->Add(static_cast<T*>(socket)); + } + + virtual Socket* Get(const std::string& extension_id, + int api_resource_id) OVERRIDE { + return manager_->Get(extension_id, api_resource_id); + } + + virtual void Remove(const std::string& extension_id, + int api_resource_id) OVERRIDE { + manager_->Remove(extension_id, api_resource_id); + } + + virtual base::hash_set<int>* GetResourceIds(const std::string& extension_id) + OVERRIDE { + return manager_->GetResourceIds(extension_id); + } + + private: + ApiResourceManager<T>* manager_; +}; + +class SocketAsyncApiFunction : public AsyncApiFunction { + public: + SocketAsyncApiFunction(); + + protected: + virtual ~SocketAsyncApiFunction(); + + // AsyncApiFunction: + virtual bool PrePrepare() OVERRIDE; + virtual bool Respond() OVERRIDE; + + virtual scoped_ptr<SocketResourceManagerInterface> + CreateSocketResourceManager(); + + int AddSocket(Socket* socket); + Socket* GetSocket(int api_resource_id); + void RemoveSocket(int api_resource_id); + base::hash_set<int>* GetSocketIds(); + + private: + scoped_ptr<SocketResourceManagerInterface> manager_; +}; + +class SocketExtensionWithDnsLookupFunction : public SocketAsyncApiFunction { + protected: + SocketExtensionWithDnsLookupFunction(); + virtual ~SocketExtensionWithDnsLookupFunction(); + + // AsyncApiFunction: + virtual bool PrePrepare() OVERRIDE; + + void StartDnsLookup(const std::string& hostname); + virtual void AfterDnsLookup(int lookup_result) = 0; + + std::string resolved_address_; + + private: + void OnDnsLookup(int resolve_result); + + // Weak pointer to the resource context. + content::ResourceContext* resource_context_; + + scoped_ptr<net::HostResolver::RequestHandle> request_handle_; + scoped_ptr<net::AddressList> addresses_; +}; + +class SocketCreateFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.create", SOCKET_CREATE) + + SocketCreateFunction(); + + protected: + virtual ~SocketCreateFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(SocketUnitTest, Create); + enum SocketType { kSocketTypeInvalid = -1, kSocketTypeTCP, kSocketTypeUDP }; + + scoped_ptr<core_api::socket::Create::Params> params_; + SocketType socket_type_; +}; + +class SocketDestroyFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.destroy", SOCKET_DESTROY) + + protected: + virtual ~SocketDestroyFunction() {} + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + int socket_id_; +}; + +class SocketConnectFunction : public SocketExtensionWithDnsLookupFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.connect", SOCKET_CONNECT) + + SocketConnectFunction(); + + protected: + virtual ~SocketConnectFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + + // SocketExtensionWithDnsLookupFunction: + virtual void AfterDnsLookup(int lookup_result) OVERRIDE; + + private: + void StartConnect(); + void OnConnect(int result); + + int socket_id_; + std::string hostname_; + int port_; + Socket* socket_; +}; + +class SocketDisconnectFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.disconnect", SOCKET_DISCONNECT) + + protected: + virtual ~SocketDisconnectFunction() {} + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + int socket_id_; +}; + +class SocketBindFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.bind", SOCKET_BIND) + + protected: + virtual ~SocketBindFunction() {} + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + int socket_id_; + std::string address_; + int port_; +}; + +class SocketListenFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.listen", SOCKET_LISTEN) + + SocketListenFunction(); + + protected: + virtual ~SocketListenFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<core_api::socket::Listen::Params> params_; +}; + +class SocketAcceptFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.accept", SOCKET_ACCEPT) + + SocketAcceptFunction(); + + protected: + virtual ~SocketAcceptFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + + private: + void OnAccept(int result_code, net::TCPClientSocket* socket); + + scoped_ptr<core_api::socket::Accept::Params> params_; +}; + +class SocketReadFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.read", SOCKET_READ) + + SocketReadFunction(); + + protected: + virtual ~SocketReadFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + void OnCompleted(int result, scoped_refptr<net::IOBuffer> io_buffer); + + private: + scoped_ptr<core_api::socket::Read::Params> params_; +}; + +class SocketWriteFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.write", SOCKET_WRITE) + + SocketWriteFunction(); + + protected: + virtual ~SocketWriteFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + void OnCompleted(int result); + + private: + int socket_id_; + scoped_refptr<net::IOBuffer> io_buffer_; + size_t io_buffer_size_; +}; + +class SocketRecvFromFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.recvFrom", SOCKET_RECVFROM) + + SocketRecvFromFunction(); + + protected: + virtual ~SocketRecvFromFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + void OnCompleted(int result, + scoped_refptr<net::IOBuffer> io_buffer, + const std::string& address, + int port); + + private: + scoped_ptr<core_api::socket::RecvFrom::Params> params_; +}; + +class SocketSendToFunction : public SocketExtensionWithDnsLookupFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.sendTo", SOCKET_SENDTO) + + SocketSendToFunction(); + + protected: + virtual ~SocketSendToFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + void OnCompleted(int result); + + // SocketExtensionWithDnsLookupFunction: + virtual void AfterDnsLookup(int lookup_result) OVERRIDE; + + private: + void StartSendTo(); + + int socket_id_; + scoped_refptr<net::IOBuffer> io_buffer_; + size_t io_buffer_size_; + std::string hostname_; + int port_; + Socket* socket_; +}; + +class SocketSetKeepAliveFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.setKeepAlive", SOCKET_SETKEEPALIVE) + + SocketSetKeepAliveFunction(); + + protected: + virtual ~SocketSetKeepAliveFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<core_api::socket::SetKeepAlive::Params> params_; +}; + +class SocketSetNoDelayFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.setNoDelay", SOCKET_SETNODELAY) + + SocketSetNoDelayFunction(); + + protected: + virtual ~SocketSetNoDelayFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<core_api::socket::SetNoDelay::Params> params_; +}; + +class SocketGetInfoFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.getInfo", SOCKET_GETINFO) + + SocketGetInfoFunction(); + + protected: + virtual ~SocketGetInfoFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<core_api::socket::GetInfo::Params> params_; +}; + +class SocketGetNetworkListFunction : public AsyncExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.getNetworkList", SOCKET_GETNETWORKLIST) + + protected: + virtual ~SocketGetNetworkListFunction() {} + virtual bool RunImpl() OVERRIDE; + + private: + void GetNetworkListOnFileThread(); + void HandleGetNetworkListError(); + void SendResponseOnUIThread(const net::NetworkInterfaceList& interface_list); +}; + +class SocketJoinGroupFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.joinGroup", SOCKET_MULTICAST_JOIN_GROUP) + + SocketJoinGroupFunction(); + + protected: + virtual ~SocketJoinGroupFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<core_api::socket::JoinGroup::Params> params_; +}; + +class SocketLeaveGroupFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.leaveGroup", SOCKET_MULTICAST_LEAVE_GROUP) + + SocketLeaveGroupFunction(); + + protected: + virtual ~SocketLeaveGroupFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<core_api::socket::LeaveGroup::Params> params_; +}; + +class SocketSetMulticastTimeToLiveFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.setMulticastTimeToLive", + SOCKET_MULTICAST_SET_TIME_TO_LIVE) + + SocketSetMulticastTimeToLiveFunction(); + + protected: + virtual ~SocketSetMulticastTimeToLiveFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<core_api::socket::SetMulticastTimeToLive::Params> params_; +}; + +class SocketSetMulticastLoopbackModeFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.setMulticastLoopbackMode", + SOCKET_MULTICAST_SET_LOOPBACK_MODE) + + SocketSetMulticastLoopbackModeFunction(); + + protected: + virtual ~SocketSetMulticastLoopbackModeFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<core_api::socket::SetMulticastLoopbackMode::Params> params_; +}; + +class SocketGetJoinedGroupsFunction : public SocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("socket.getJoinedGroups", + SOCKET_MULTICAST_GET_JOINED_GROUPS) + + SocketGetJoinedGroupsFunction(); + + protected: + virtual ~SocketGetJoinedGroupsFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<core_api::socket::GetJoinedGroups::Params> params_; +}; +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKET_SOCKET_API_H_ diff --git a/extensions/browser/api/socket/tcp_socket.cc b/extensions/browser/api/socket/tcp_socket.cc new file mode 100644 index 0000000..49f17dc --- /dev/null +++ b/extensions/browser/api/socket/tcp_socket.cc @@ -0,0 +1,329 @@ +// Copyright 2014 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 "extensions/browser/api/socket/tcp_socket.h" + +#include "extensions/browser/api/api_resource.h" +#include "net/base/address_list.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/base/rand_callback.h" +#include "net/socket/tcp_client_socket.h" + +namespace extensions { + +const char kTCPSocketTypeInvalidError[] = + "Cannot call both connect and listen on the same socket."; +const char kSocketListenError[] = "Could not listen on the specified port."; + +static base::LazyInstance< + BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableTCPSocket> > > + g_factory = LAZY_INSTANCE_INITIALIZER; + +// static +template <> +BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableTCPSocket> >* +ApiResourceManager<ResumableTCPSocket>::GetFactoryInstance() { + return g_factory.Pointer(); +} + +static base::LazyInstance<BrowserContextKeyedAPIFactory< + ApiResourceManager<ResumableTCPServerSocket> > > g_server_factory = + LAZY_INSTANCE_INITIALIZER; + +// static +template <> +BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableTCPServerSocket> >* +ApiResourceManager<ResumableTCPServerSocket>::GetFactoryInstance() { + return g_server_factory.Pointer(); +} + +TCPSocket::TCPSocket(const std::string& owner_extension_id) + : Socket(owner_extension_id), socket_mode_(UNKNOWN) {} + +TCPSocket::TCPSocket(net::TCPClientSocket* tcp_client_socket, + const std::string& owner_extension_id, + bool is_connected) + : Socket(owner_extension_id), + socket_(tcp_client_socket), + socket_mode_(CLIENT) { + this->is_connected_ = is_connected; +} + +TCPSocket::TCPSocket(net::TCPServerSocket* tcp_server_socket, + const std::string& owner_extension_id) + : Socket(owner_extension_id), + server_socket_(tcp_server_socket), + socket_mode_(SERVER) {} + +// static +TCPSocket* TCPSocket::CreateSocketForTesting( + net::TCPClientSocket* tcp_client_socket, + const std::string& owner_extension_id, + bool is_connected) { + return new TCPSocket(tcp_client_socket, owner_extension_id, is_connected); +} + +// static +TCPSocket* TCPSocket::CreateServerSocketForTesting( + net::TCPServerSocket* tcp_server_socket, + const std::string& owner_extension_id) { + return new TCPSocket(tcp_server_socket, owner_extension_id); +} + +TCPSocket::~TCPSocket() { Disconnect(); } + +void TCPSocket::Connect(const std::string& address, + int port, + const CompletionCallback& callback) { + DCHECK(!callback.is_null()); + + if (socket_mode_ == SERVER || !connect_callback_.is_null()) { + callback.Run(net::ERR_CONNECTION_FAILED); + return; + } + DCHECK(!server_socket_.get()); + socket_mode_ = CLIENT; + connect_callback_ = callback; + + int result = net::ERR_CONNECTION_FAILED; + do { + if (is_connected_) + break; + + net::AddressList address_list; + if (!StringAndPortToAddressList(address, port, &address_list)) { + result = net::ERR_ADDRESS_INVALID; + break; + } + + socket_.reset( + new net::TCPClientSocket(address_list, NULL, net::NetLog::Source())); + + connect_callback_ = callback; + result = socket_->Connect( + base::Bind(&TCPSocket::OnConnectComplete, base::Unretained(this))); + } while (false); + + if (result != net::ERR_IO_PENDING) + OnConnectComplete(result); +} + +void TCPSocket::Disconnect() { + is_connected_ = false; + if (socket_.get()) + socket_->Disconnect(); + server_socket_.reset(NULL); + connect_callback_.Reset(); + read_callback_.Reset(); + accept_callback_.Reset(); + accept_socket_.reset(NULL); +} + +int TCPSocket::Bind(const std::string& address, int port) { + return net::ERR_FAILED; +} + +void TCPSocket::Read(int count, const ReadCompletionCallback& callback) { + DCHECK(!callback.is_null()); + + if (socket_mode_ != CLIENT) { + callback.Run(net::ERR_FAILED, NULL); + return; + } + + if (!read_callback_.is_null()) { + callback.Run(net::ERR_IO_PENDING, NULL); + return; + } + + if (count < 0) { + callback.Run(net::ERR_INVALID_ARGUMENT, NULL); + return; + } + + if (!socket_.get() || !IsConnected()) { + callback.Run(net::ERR_SOCKET_NOT_CONNECTED, NULL); + return; + } + + read_callback_ = callback; + scoped_refptr<net::IOBuffer> io_buffer = new net::IOBuffer(count); + int result = socket_->Read( + io_buffer.get(), + count, + base::Bind( + &TCPSocket::OnReadComplete, base::Unretained(this), io_buffer)); + + if (result != net::ERR_IO_PENDING) + OnReadComplete(io_buffer, result); +} + +void TCPSocket::RecvFrom(int count, + const RecvFromCompletionCallback& callback) { + callback.Run(net::ERR_FAILED, NULL, NULL, 0); +} + +void TCPSocket::SendTo(scoped_refptr<net::IOBuffer> io_buffer, + int byte_count, + const std::string& address, + int port, + const CompletionCallback& callback) { + callback.Run(net::ERR_FAILED); +} + +bool TCPSocket::SetKeepAlive(bool enable, int delay) { + if (!socket_.get()) + return false; + return socket_->SetKeepAlive(enable, delay); +} + +bool TCPSocket::SetNoDelay(bool no_delay) { + if (!socket_.get()) + return false; + return socket_->SetNoDelay(no_delay); +} + +int TCPSocket::Listen(const std::string& address, + int port, + int backlog, + std::string* error_msg) { + if (socket_mode_ == CLIENT) { + *error_msg = kTCPSocketTypeInvalidError; + return net::ERR_NOT_IMPLEMENTED; + } + DCHECK(!socket_.get()); + socket_mode_ = SERVER; + + scoped_ptr<net::IPEndPoint> bind_address(new net::IPEndPoint()); + if (!StringAndPortToIPEndPoint(address, port, bind_address.get())) + return net::ERR_INVALID_ARGUMENT; + + if (!server_socket_.get()) { + server_socket_.reset(new net::TCPServerSocket(NULL, net::NetLog::Source())); + } + int result = server_socket_->Listen(*bind_address, backlog); + if (result) + *error_msg = kSocketListenError; + return result; +} + +void TCPSocket::Accept(const AcceptCompletionCallback& callback) { + if (socket_mode_ != SERVER || !server_socket_.get()) { + callback.Run(net::ERR_FAILED, NULL); + return; + } + + // Limits to only 1 blocked accept call. + if (!accept_callback_.is_null()) { + callback.Run(net::ERR_FAILED, NULL); + return; + } + + int result = server_socket_->Accept( + &accept_socket_, + base::Bind(&TCPSocket::OnAccept, base::Unretained(this))); + if (result == net::ERR_IO_PENDING) { + accept_callback_ = callback; + } else if (result == net::OK) { + accept_callback_ = callback; + this->OnAccept(result); + } else { + callback.Run(result, NULL); + } +} + +bool TCPSocket::IsConnected() { + RefreshConnectionStatus(); + return is_connected_; +} + +bool TCPSocket::GetPeerAddress(net::IPEndPoint* address) { + if (!socket_.get()) + return false; + return !socket_->GetPeerAddress(address); +} + +bool TCPSocket::GetLocalAddress(net::IPEndPoint* address) { + if (socket_.get()) { + return !socket_->GetLocalAddress(address); + } else if (server_socket_.get()) { + return !server_socket_->GetLocalAddress(address); + } else { + return false; + } +} + +Socket::SocketType TCPSocket::GetSocketType() const { return Socket::TYPE_TCP; } + +int TCPSocket::WriteImpl(net::IOBuffer* io_buffer, + int io_buffer_size, + const net::CompletionCallback& callback) { + if (socket_mode_ != CLIENT) + return net::ERR_FAILED; + else if (!socket_.get() || !IsConnected()) + return net::ERR_SOCKET_NOT_CONNECTED; + else + return socket_->Write(io_buffer, io_buffer_size, callback); +} + +void TCPSocket::RefreshConnectionStatus() { + if (!is_connected_) + return; + if (server_socket_) + return; + if (!socket_->IsConnected()) { + Disconnect(); + } +} + +void TCPSocket::OnConnectComplete(int result) { + DCHECK(!connect_callback_.is_null()); + DCHECK(!is_connected_); + is_connected_ = result == net::OK; + connect_callback_.Run(result); + connect_callback_.Reset(); +} + +void TCPSocket::OnReadComplete(scoped_refptr<net::IOBuffer> io_buffer, + int result) { + DCHECK(!read_callback_.is_null()); + read_callback_.Run(result, io_buffer); + read_callback_.Reset(); +} + +void TCPSocket::OnAccept(int result) { + DCHECK(!accept_callback_.is_null()); + if (result == net::OK && accept_socket_.get()) { + accept_callback_.Run( + result, static_cast<net::TCPClientSocket*>(accept_socket_.release())); + } else { + accept_callback_.Run(result, NULL); + } + accept_callback_.Reset(); +} + +ResumableTCPSocket::ResumableTCPSocket(const std::string& owner_extension_id) + : TCPSocket(owner_extension_id), + persistent_(false), + buffer_size_(0), + 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::IsPersistent() const { return persistent(); } + +ResumableTCPServerSocket::ResumableTCPServerSocket( + const std::string& owner_extension_id) + : TCPSocket(owner_extension_id), persistent_(false), paused_(false) {} + +bool ResumableTCPServerSocket::IsPersistent() const { return persistent(); } + +} // namespace extensions diff --git a/extensions/browser/api/socket/tcp_socket.h b/extensions/browser/api/socket/tcp_socket.h new file mode 100644 index 0000000..fbb8c54 --- /dev/null +++ b/extensions/browser/api/socket/tcp_socket.h @@ -0,0 +1,173 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKET_TCP_SOCKET_H_ +#define EXTENSIONS_BROWSER_API_SOCKET_TCP_SOCKET_H_ + +#include <string> + +#include "extensions/browser/api/socket/socket.h" + +// This looks like it should be forward-declarable, but it does some tricky +// moves that make it easier to just include it. +#include "net/socket/tcp_client_socket.h" +#include "net/socket/tcp_server_socket.h" + +namespace net { +class Socket; +} + +namespace extensions { + +class TCPSocket : public Socket { + public: + explicit TCPSocket(const std::string& owner_extension_id); + TCPSocket(net::TCPClientSocket* tcp_client_socket, + const std::string& owner_extension_id, + bool is_connected = false); + + virtual ~TCPSocket(); + + virtual void Connect(const std::string& address, + int port, + const CompletionCallback& callback) OVERRIDE; + virtual void Disconnect() OVERRIDE; + virtual int Bind(const std::string& address, int port) OVERRIDE; + virtual void Read(int count, const ReadCompletionCallback& callback) OVERRIDE; + virtual void RecvFrom(int count, + const RecvFromCompletionCallback& callback) OVERRIDE; + virtual void SendTo(scoped_refptr<net::IOBuffer> io_buffer, + int byte_count, + const std::string& address, + int port, + const CompletionCallback& callback) OVERRIDE; + virtual bool SetKeepAlive(bool enable, int delay) OVERRIDE; + virtual bool SetNoDelay(bool no_delay) OVERRIDE; + virtual int Listen(const std::string& address, + int port, + int backlog, + std::string* error_msg) OVERRIDE; + virtual void Accept(const AcceptCompletionCallback& callback) OVERRIDE; + + virtual bool IsConnected() OVERRIDE; + + virtual bool GetPeerAddress(net::IPEndPoint* address) OVERRIDE; + virtual bool GetLocalAddress(net::IPEndPoint* address) OVERRIDE; + virtual Socket::SocketType GetSocketType() const OVERRIDE; + + static TCPSocket* CreateSocketForTesting( + net::TCPClientSocket* tcp_client_socket, + const std::string& owner_extension_id, + bool is_connected = false); + static TCPSocket* CreateServerSocketForTesting( + net::TCPServerSocket* tcp_server_socket, + const std::string& owner_extension_id); + + protected: + virtual int WriteImpl(net::IOBuffer* io_buffer, + int io_buffer_size, + const net::CompletionCallback& callback) OVERRIDE; + + private: + void RefreshConnectionStatus(); + void OnConnectComplete(int result); + void OnReadComplete(scoped_refptr<net::IOBuffer> io_buffer, int result); + void OnAccept(int result); + + TCPSocket(net::TCPServerSocket* tcp_server_socket, + const std::string& owner_extension_id); + + scoped_ptr<net::TCPClientSocket> socket_; + scoped_ptr<net::TCPServerSocket> server_socket_; + + enum SocketMode { UNKNOWN = 0, CLIENT, SERVER, }; + SocketMode socket_mode_; + + CompletionCallback connect_callback_; + + ReadCompletionCallback read_callback_; + + scoped_ptr<net::StreamSocket> accept_socket_; + AcceptCompletionCallback accept_callback_; +}; + +// TCP Socket instances from the "sockets.tcp" namespace. These are regular +// socket objects with additional properties related to the behavior defined in +// the "sockets.tcp" namespace. +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); + + // Overriden from ApiResource + virtual bool IsPersistent() const OVERRIDE; + + const std::string& name() const { return name_; } + void set_name(const std::string& name) { name_ = name; } + + bool persistent() const { return persistent_; } + void set_persistent(bool persistent) { persistent_ = persistent; } + + int buffer_size() const { return buffer_size_; } + void set_buffer_size(int buffer_size) { buffer_size_ = buffer_size; } + + bool paused() const { return paused_; } + void set_paused(bool paused) { paused_ = paused; } + + private: + friend class ApiResourceManager<ResumableTCPSocket>; + static const char* service_name() { return "ResumableTCPSocketManager"; } + + // Application-defined string - see sockets_tcp.idl. + std::string name_; + // Flag indicating whether the socket is left open when the application is + // suspended - see sockets_tcp.idl. + bool persistent_; + // The size of the buffer used to receive data - see sockets_tcp.idl. + int buffer_size_; + // Flag indicating whether a connected socket blocks its peer from sending + // more data - see sockets_tcp.idl. + 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); + + // Overriden from ApiResource + virtual bool IsPersistent() const OVERRIDE; + + const std::string& name() const { return name_; } + void set_name(const std::string& name) { name_ = name; } + + bool persistent() const { return persistent_; } + void set_persistent(bool persistent) { persistent_ = persistent; } + + bool paused() const { return paused_; } + void set_paused(bool paused) { paused_ = paused; } + + private: + friend class ApiResourceManager<ResumableTCPServerSocket>; + 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 // EXTENSIONS_BROWSER_API_SOCKET_TCP_SOCKET_H_ diff --git a/extensions/browser/api/socket/udp_socket.cc b/extensions/browser/api/socket/udp_socket.cc new file mode 100644 index 0000000..8e36500 --- /dev/null +++ b/extensions/browser/api/socket/udp_socket.cc @@ -0,0 +1,296 @@ +// Copyright 2014 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 "extensions/browser/api/socket/udp_socket.h" + +#include <algorithm> + +#include "extensions/browser/api/api_resource.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/udp/datagram_socket.h" +#include "net/udp/udp_client_socket.h" + +namespace extensions { + +static base::LazyInstance< + BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableUDPSocket> > > + g_factory = LAZY_INSTANCE_INITIALIZER; + +// static +template <> +BrowserContextKeyedAPIFactory<ApiResourceManager<ResumableUDPSocket> >* +ApiResourceManager<ResumableUDPSocket>::GetFactoryInstance() { + return g_factory.Pointer(); +} + +UDPSocket::UDPSocket(const std::string& owner_extension_id) + : Socket(owner_extension_id), + socket_(net::DatagramSocket::DEFAULT_BIND, + net::RandIntCallback(), + NULL, + net::NetLog::Source()) {} + +UDPSocket::~UDPSocket() { Disconnect(); } + +void UDPSocket::Connect(const std::string& address, + int port, + const CompletionCallback& callback) { + int result = net::ERR_CONNECTION_FAILED; + do { + if (is_connected_) + break; + + net::IPEndPoint ip_end_point; + if (!StringAndPortToIPEndPoint(address, port, &ip_end_point)) { + result = net::ERR_ADDRESS_INVALID; + break; + } + + result = socket_.Connect(ip_end_point); + is_connected_ = (result == net::OK); + } while (false); + + callback.Run(result); +} + +int UDPSocket::Bind(const std::string& address, int port) { + if (IsBound()) + return net::ERR_CONNECTION_FAILED; + + net::IPEndPoint ip_end_point; + if (!StringAndPortToIPEndPoint(address, port, &ip_end_point)) + return net::ERR_INVALID_ARGUMENT; + + return socket_.Bind(ip_end_point); +} + +void UDPSocket::Disconnect() { + is_connected_ = false; + socket_.Close(); + read_callback_.Reset(); + recv_from_callback_.Reset(); + send_to_callback_.Reset(); + multicast_groups_.clear(); +} + +void UDPSocket::Read(int count, const ReadCompletionCallback& callback) { + DCHECK(!callback.is_null()); + + if (!read_callback_.is_null()) { + callback.Run(net::ERR_IO_PENDING, NULL); + return; + } else { + read_callback_ = callback; + } + + int result = net::ERR_FAILED; + scoped_refptr<net::IOBuffer> io_buffer; + do { + if (count < 0) { + result = net::ERR_INVALID_ARGUMENT; + break; + } + + if (!socket_.is_connected()) { + result = net::ERR_SOCKET_NOT_CONNECTED; + break; + } + + io_buffer = new net::IOBuffer(count); + result = socket_.Read( + io_buffer.get(), + count, + base::Bind( + &UDPSocket::OnReadComplete, base::Unretained(this), io_buffer)); + } while (false); + + if (result != net::ERR_IO_PENDING) + OnReadComplete(io_buffer, result); +} + +int UDPSocket::WriteImpl(net::IOBuffer* io_buffer, + int io_buffer_size, + const net::CompletionCallback& callback) { + if (!socket_.is_connected()) + return net::ERR_SOCKET_NOT_CONNECTED; + else + return socket_.Write(io_buffer, io_buffer_size, callback); +} + +void UDPSocket::RecvFrom(int count, + const RecvFromCompletionCallback& callback) { + DCHECK(!callback.is_null()); + + if (!recv_from_callback_.is_null()) { + callback.Run(net::ERR_IO_PENDING, NULL, std::string(), 0); + return; + } else { + recv_from_callback_ = callback; + } + + int result = net::ERR_FAILED; + scoped_refptr<net::IOBuffer> io_buffer; + scoped_refptr<IPEndPoint> address; + do { + if (count < 0) { + result = net::ERR_INVALID_ARGUMENT; + break; + } + + if (!socket_.is_connected()) { + result = net::ERR_SOCKET_NOT_CONNECTED; + break; + } + + io_buffer = new net::IOBuffer(count); + address = new IPEndPoint(); + result = socket_.RecvFrom(io_buffer.get(), + count, + &address->data, + base::Bind(&UDPSocket::OnRecvFromComplete, + base::Unretained(this), + io_buffer, + address)); + } while (false); + + if (result != net::ERR_IO_PENDING) + OnRecvFromComplete(io_buffer, address, result); +} + +void UDPSocket::SendTo(scoped_refptr<net::IOBuffer> io_buffer, + int byte_count, + const std::string& address, + int port, + const CompletionCallback& callback) { + DCHECK(!callback.is_null()); + + if (!send_to_callback_.is_null()) { + // TODO(penghuang): Put requests in a pending queue to support multiple + // sendTo calls. + callback.Run(net::ERR_IO_PENDING); + return; + } else { + send_to_callback_ = callback; + } + + int result = net::ERR_FAILED; + do { + net::IPEndPoint ip_end_point; + if (!StringAndPortToIPEndPoint(address, port, &ip_end_point)) { + result = net::ERR_ADDRESS_INVALID; + break; + } + + if (!socket_.is_connected()) { + result = net::ERR_SOCKET_NOT_CONNECTED; + break; + } + + result = socket_.SendTo( + io_buffer.get(), + byte_count, + ip_end_point, + base::Bind(&UDPSocket::OnSendToComplete, base::Unretained(this))); + } while (false); + + if (result != net::ERR_IO_PENDING) + OnSendToComplete(result); +} + +bool UDPSocket::IsConnected() { return is_connected_; } + +bool UDPSocket::GetPeerAddress(net::IPEndPoint* address) { + return !socket_.GetPeerAddress(address); +} + +bool UDPSocket::GetLocalAddress(net::IPEndPoint* address) { + return !socket_.GetLocalAddress(address); +} + +Socket::SocketType UDPSocket::GetSocketType() const { return Socket::TYPE_UDP; } + +void UDPSocket::OnReadComplete(scoped_refptr<net::IOBuffer> io_buffer, + int result) { + DCHECK(!read_callback_.is_null()); + read_callback_.Run(result, io_buffer); + read_callback_.Reset(); +} + +void UDPSocket::OnRecvFromComplete(scoped_refptr<net::IOBuffer> io_buffer, + scoped_refptr<IPEndPoint> address, + int result) { + DCHECK(!recv_from_callback_.is_null()); + std::string ip; + int port = 0; + if (result > 0 && address.get()) { + IPEndPointToStringAndPort(address->data, &ip, &port); + } + recv_from_callback_.Run(result, io_buffer, ip, port); + recv_from_callback_.Reset(); +} + +void UDPSocket::OnSendToComplete(int result) { + DCHECK(!send_to_callback_.is_null()); + send_to_callback_.Run(result); + send_to_callback_.Reset(); +} + +bool UDPSocket::IsBound() { return socket_.is_connected(); } + +int UDPSocket::JoinGroup(const std::string& address) { + net::IPAddressNumber ip; + if (!net::ParseIPLiteralToNumber(address, &ip)) + return net::ERR_ADDRESS_INVALID; + + std::string normalized_address = net::IPAddressToString(ip); + std::vector<std::string>::iterator find_result = std::find( + multicast_groups_.begin(), multicast_groups_.end(), normalized_address); + if (find_result != multicast_groups_.end()) + return net::ERR_ADDRESS_INVALID; + + int rv = socket_.JoinGroup(ip); + if (rv == 0) + multicast_groups_.push_back(normalized_address); + return rv; +} + +int UDPSocket::LeaveGroup(const std::string& address) { + net::IPAddressNumber ip; + if (!net::ParseIPLiteralToNumber(address, &ip)) + return net::ERR_ADDRESS_INVALID; + + std::string normalized_address = net::IPAddressToString(ip); + std::vector<std::string>::iterator find_result = std::find( + multicast_groups_.begin(), multicast_groups_.end(), normalized_address); + if (find_result == multicast_groups_.end()) + return net::ERR_ADDRESS_INVALID; + + int rv = socket_.LeaveGroup(ip); + if (rv == 0) + multicast_groups_.erase(find_result); + return rv; +} + +int UDPSocket::SetMulticastTimeToLive(int ttl) { + return socket_.SetMulticastTimeToLive(ttl); +} + +int UDPSocket::SetMulticastLoopbackMode(bool loopback) { + return socket_.SetMulticastLoopbackMode(loopback); +} + +const std::vector<std::string>& UDPSocket::GetJoinedGroups() const { + return multicast_groups_; +} + +ResumableUDPSocket::ResumableUDPSocket(const std::string& owner_extension_id) + : UDPSocket(owner_extension_id), + persistent_(false), + buffer_size_(0), + paused_(false) {} + +bool ResumableUDPSocket::IsPersistent() const { return persistent(); } + +} // namespace extensions diff --git a/extensions/browser/api/socket/udp_socket.h b/extensions/browser/api/socket/udp_socket.h new file mode 100644 index 0000000..7c1450f --- /dev/null +++ b/extensions/browser/api/socket/udp_socket.h @@ -0,0 +1,117 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKET_UDP_SOCKET_H_ +#define EXTENSIONS_BROWSER_API_SOCKET_UDP_SOCKET_H_ + +#include <string> +#include <vector> + +#include "extensions/browser/api/socket/socket.h" +#include "net/udp/udp_socket.h" + +namespace extensions { + +class UDPSocket : public Socket { + public: + explicit UDPSocket(const std::string& owner_extension_id); + virtual ~UDPSocket(); + + virtual void Connect(const std::string& address, + int port, + const CompletionCallback& callback) OVERRIDE; + virtual void Disconnect() OVERRIDE; + virtual int Bind(const std::string& address, int port) OVERRIDE; + virtual void Read(int count, const ReadCompletionCallback& callback) OVERRIDE; + virtual void RecvFrom(int count, + const RecvFromCompletionCallback& callback) OVERRIDE; + virtual void SendTo(scoped_refptr<net::IOBuffer> io_buffer, + int byte_count, + const std::string& address, + int port, + const CompletionCallback& callback) OVERRIDE; + + virtual bool IsConnected() OVERRIDE; + + virtual bool GetPeerAddress(net::IPEndPoint* address) OVERRIDE; + virtual bool GetLocalAddress(net::IPEndPoint* address) OVERRIDE; + virtual Socket::SocketType GetSocketType() const OVERRIDE; + + bool IsBound(); + + int JoinGroup(const std::string& address); + int LeaveGroup(const std::string& address); + + int SetMulticastTimeToLive(int ttl); + int SetMulticastLoopbackMode(bool loopback); + + const std::vector<std::string>& GetJoinedGroups() const; + + protected: + virtual int WriteImpl(net::IOBuffer* io_buffer, + int io_buffer_size, + const net::CompletionCallback& callback) OVERRIDE; + + private: + // Make net::IPEndPoint can be refcounted + typedef base::RefCountedData<net::IPEndPoint> IPEndPoint; + + void OnReadComplete(scoped_refptr<net::IOBuffer> io_buffer, int result); + void OnRecvFromComplete(scoped_refptr<net::IOBuffer> io_buffer, + scoped_refptr<IPEndPoint> address, + int result); + void OnSendToComplete(int result); + + net::UDPSocket socket_; + + ReadCompletionCallback read_callback_; + + RecvFromCompletionCallback recv_from_callback_; + + CompletionCallback send_to_callback_; + + std::vector<std::string> multicast_groups_; +}; + +// UDP Socket instances from the "sockets.udp" namespace. These are regular +// socket objects with additional properties related to the behavior defined in +// the "sockets.udp" namespace. +class ResumableUDPSocket : public UDPSocket { + public: + explicit ResumableUDPSocket(const std::string& owner_extension_id); + + // Overriden from ApiResource + virtual bool IsPersistent() const OVERRIDE; + + const std::string& name() const { return name_; } + void set_name(const std::string& name) { name_ = name; } + + bool persistent() const { return persistent_; } + void set_persistent(bool persistent) { persistent_ = persistent; } + + int buffer_size() const { return buffer_size_; } + void set_buffer_size(int buffer_size) { buffer_size_ = buffer_size; } + + bool paused() const { return paused_; } + void set_paused(bool paused) { paused_ = paused; } + + private: + friend class ApiResourceManager<ResumableUDPSocket>; + static const char* service_name() { return "ResumableUDPSocketManager"; } + + // Application-defined string - see sockets_udp.idl. + std::string name_; + // Flag indicating whether the socket is left open when the application is + // suspended - see sockets_udp.idl. + bool persistent_; + // The size of the buffer used to receive data - see sockets_udp.idl. + int buffer_size_; + // Flag indicating whether a connected socket blocks its peer from sending + // more data - see sockets_udp.idl. + bool paused_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKET_UDP_SOCKET_H_ diff --git a/extensions/browser/api/sockets_tcp/sockets_tcp/OWNERS b/extensions/browser/api/sockets_tcp/sockets_tcp/OWNERS new file mode 100644 index 0000000..3e30c82 --- /dev/null +++ b/extensions/browser/api/sockets_tcp/sockets_tcp/OWNERS @@ -0,0 +1 @@ +rpaquay@chromium.org diff --git a/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc b/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc new file mode 100644 index 0000000..fabb950 --- /dev/null +++ b/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc @@ -0,0 +1,445 @@ +// Copyright 2014 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 "extensions/browser/api/sockets_tcp/sockets_tcp_api.h" + +#include "chrome/common/extensions/api/sockets/sockets_manifest_data.h" +#include "content/public/common/socket_permission_request.h" +#include "extensions/browser/api/socket/tcp_socket.h" +#include "extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h" +#include "net/base/net_errors.h" + +using extensions::ResumableTCPSocket; +using extensions::core_api::sockets_tcp::SocketInfo; +using extensions::core_api::sockets_tcp::SocketProperties; + +namespace { + +const char kSocketNotFoundError[] = "Socket not found"; +const char kPermissionError[] = "Does not have permission"; + +linked_ptr<SocketInfo> CreateSocketInfo(int socket_id, + ResumableTCPSocket* socket) { + linked_ptr<SocketInfo> 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(); + if (socket->buffer_size() > 0) { + socket_info->buffer_size.reset(new int(socket->buffer_size())); + } + socket_info->paused = socket->paused(); + socket_info->connected = socket->IsConnected(); + + // 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())); + } + + // Grab the peer address as known by the OS. This and the call below will + // always succeed while the socket is connected, even if the socket has + // been remotely closed by the peer; only reading the socket will reveal + // that it should be closed locally. + net::IPEndPoint peerAddress; + if (socket->GetPeerAddress(&peerAddress)) { + socket_info->peer_address.reset( + new std::string(peerAddress.ToStringWithoutPort())); + socket_info->peer_port.reset(new int(peerAddress.port())); + } + + return socket_info; +} + +void SetSocketProperties(ResumableTCPSocket* socket, + SocketProperties* properties) { + if (properties->name.get()) { + socket->set_name(*properties->name.get()); + } + if (properties->persistent.get()) { + socket->set_persistent(*properties->persistent.get()); + } + if (properties->buffer_size.get()) { + // buffer size is validated when issuing the actual Recv operation + // on the socket. + socket->set_buffer_size(*properties->buffer_size.get()); + } +} + +} // namespace + +namespace extensions { +namespace core_api { + +using content::SocketPermissionRequest; + +TCPSocketAsyncApiFunction::~TCPSocketAsyncApiFunction() {} + +scoped_ptr<SocketResourceManagerInterface> +TCPSocketAsyncApiFunction::CreateSocketResourceManager() { + return scoped_ptr<SocketResourceManagerInterface>( + new SocketResourceManager<ResumableTCPSocket>()).Pass(); +} + +ResumableTCPSocket* TCPSocketAsyncApiFunction::GetTcpSocket(int socket_id) { + return static_cast<ResumableTCPSocket*>(GetSocket(socket_id)); +} + +TCPSocketExtensionWithDnsLookupFunction:: + ~TCPSocketExtensionWithDnsLookupFunction() {} + +scoped_ptr<SocketResourceManagerInterface> +TCPSocketExtensionWithDnsLookupFunction::CreateSocketResourceManager() { + return scoped_ptr<SocketResourceManagerInterface>( + new SocketResourceManager<ResumableTCPSocket>()).Pass(); +} + +ResumableTCPSocket* TCPSocketExtensionWithDnsLookupFunction::GetTcpSocket( + int socket_id) { + return static_cast<ResumableTCPSocket*>(GetSocket(socket_id)); +} + +SocketsTcpCreateFunction::SocketsTcpCreateFunction() {} + +SocketsTcpCreateFunction::~SocketsTcpCreateFunction() {} + +bool SocketsTcpCreateFunction::Prepare() { + params_ = sockets_tcp::Create::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpCreateFunction::Work() { + ResumableTCPSocket* socket = new ResumableTCPSocket(extension_->id()); + + sockets_tcp::SocketProperties* properties = params_.get()->properties.get(); + if (properties) { + SetSocketProperties(socket, properties); + } + + sockets_tcp::CreateInfo create_info; + create_info.socket_id = AddSocket(socket); + results_ = sockets_tcp::Create::Results::Create(create_info); +} + +SocketsTcpUpdateFunction::SocketsTcpUpdateFunction() {} + +SocketsTcpUpdateFunction::~SocketsTcpUpdateFunction() {} + +bool SocketsTcpUpdateFunction::Prepare() { + params_ = sockets_tcp::Update::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpUpdateFunction::Work() { + ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + SetSocketProperties(socket, ¶ms_.get()->properties); + results_ = sockets_tcp::Update::Results::Create(); +} + +SocketsTcpSetPausedFunction::SocketsTcpSetPausedFunction() + : socket_event_dispatcher_(NULL) {} + +SocketsTcpSetPausedFunction::~SocketsTcpSetPausedFunction() {} + +bool SocketsTcpSetPausedFunction::Prepare() { + params_ = core_api::sockets_tcp::SetPaused::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + + socket_event_dispatcher_ = TCPSocketEventDispatcher::Get(browser_context()); + 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 " + "TCPSocketEventDispatcher."; + return socket_event_dispatcher_ != NULL; +} + +void SocketsTcpSetPausedFunction::Work() { + ResumableTCPSocket* 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_->OnSocketResume(extension_->id(), + params_->socket_id); + } + } + + results_ = sockets_tcp::SetPaused::Results::Create(); +} + +SocketsTcpSetKeepAliveFunction::SocketsTcpSetKeepAliveFunction() {} + +SocketsTcpSetKeepAliveFunction::~SocketsTcpSetKeepAliveFunction() {} + +bool SocketsTcpSetKeepAliveFunction::Prepare() { + params_ = core_api::sockets_tcp::SetKeepAlive::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpSetKeepAliveFunction::Work() { + ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + int delay = params_->delay ? *params_->delay.get() : 0; + + bool success = socket->SetKeepAlive(params_->enable, delay); + int net_result = (success ? net::OK : net::ERR_FAILED); + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_tcp::SetKeepAlive::Results::Create(net_result); +} + +SocketsTcpSetNoDelayFunction::SocketsTcpSetNoDelayFunction() {} + +SocketsTcpSetNoDelayFunction::~SocketsTcpSetNoDelayFunction() {} + +bool SocketsTcpSetNoDelayFunction::Prepare() { + params_ = core_api::sockets_tcp::SetNoDelay::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpSetNoDelayFunction::Work() { + ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + bool success = socket->SetNoDelay(params_->no_delay); + int net_result = (success ? net::OK : net::ERR_FAILED); + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_tcp::SetNoDelay::Results::Create(net_result); +} + +SocketsTcpConnectFunction::SocketsTcpConnectFunction() + : socket_event_dispatcher_(NULL) {} + +SocketsTcpConnectFunction::~SocketsTcpConnectFunction() {} + +bool SocketsTcpConnectFunction::Prepare() { + params_ = sockets_tcp::Connect::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + + socket_event_dispatcher_ = TCPSocketEventDispatcher::Get(browser_context()); + 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 " + "TCPSocketEventDispatcher."; + return socket_event_dispatcher_ != NULL; +} + +void SocketsTcpConnectFunction::AsyncWorkStart() { + ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + AsyncWorkCompleted(); + return; + } + + content::SocketPermissionRequest param(SocketPermissionRequest::TCP_CONNECT, + params_->peer_address, + params_->peer_port); + if (!SocketsManifestData::CheckRequest(GetExtension(), param)) { + error_ = kPermissionError; + AsyncWorkCompleted(); + return; + } + + StartDnsLookup(params_->peer_address); +} + +void SocketsTcpConnectFunction::AfterDnsLookup(int lookup_result) { + if (lookup_result == net::OK) { + StartConnect(); + } else { + OnCompleted(lookup_result); + } +} + +void SocketsTcpConnectFunction::StartConnect() { + ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + AsyncWorkCompleted(); + return; + } + + socket->Connect(resolved_address_, + params_->peer_port, + base::Bind(&SocketsTcpConnectFunction::OnCompleted, this)); +} + +void SocketsTcpConnectFunction::OnCompleted(int net_result) { + if (net_result == net::OK) { + socket_event_dispatcher_->OnSocketConnect(extension_->id(), + params_->socket_id); + } + + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_tcp::Connect::Results::Create(net_result); + AsyncWorkCompleted(); +} + +SocketsTcpDisconnectFunction::SocketsTcpDisconnectFunction() {} + +SocketsTcpDisconnectFunction::~SocketsTcpDisconnectFunction() {} + +bool SocketsTcpDisconnectFunction::Prepare() { + params_ = sockets_tcp::Disconnect::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpDisconnectFunction::Work() { + ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + socket->Disconnect(); + results_ = sockets_tcp::Disconnect::Results::Create(); +} + +SocketsTcpSendFunction::SocketsTcpSendFunction() : io_buffer_size_(0) {} + +SocketsTcpSendFunction::~SocketsTcpSendFunction() {} + +bool SocketsTcpSendFunction::Prepare() { + params_ = sockets_tcp::Send::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + io_buffer_size_ = params_->data.size(); + io_buffer_ = new net::WrappedIOBuffer(params_->data.data()); + return true; +} + +void SocketsTcpSendFunction::AsyncWorkStart() { + ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + AsyncWorkCompleted(); + return; + } + + socket->Write(io_buffer_, + io_buffer_size_, + base::Bind(&SocketsTcpSendFunction::OnCompleted, this)); +} + +void SocketsTcpSendFunction::OnCompleted(int net_result) { + if (net_result >= net::OK) { + SetSendResult(net::OK, net_result); + } else { + SetSendResult(net_result, -1); + } +} + +void SocketsTcpSendFunction::SetSendResult(int net_result, int bytes_sent) { + CHECK(net_result <= net::OK) << "Network status code must be <= net::OK"; + + sockets_tcp::SendInfo send_info; + send_info.result_code = net_result; + if (net_result == net::OK) { + send_info.bytes_sent.reset(new int(bytes_sent)); + } + + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_tcp::Send::Results::Create(send_info); + AsyncWorkCompleted(); +} + +SocketsTcpCloseFunction::SocketsTcpCloseFunction() {} + +SocketsTcpCloseFunction::~SocketsTcpCloseFunction() {} + +bool SocketsTcpCloseFunction::Prepare() { + params_ = sockets_tcp::Close::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpCloseFunction::Work() { + ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + RemoveSocket(params_->socket_id); + results_ = sockets_tcp::Close::Results::Create(); +} + +SocketsTcpGetInfoFunction::SocketsTcpGetInfoFunction() {} + +SocketsTcpGetInfoFunction::~SocketsTcpGetInfoFunction() {} + +bool SocketsTcpGetInfoFunction::Prepare() { + params_ = sockets_tcp::GetInfo::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsTcpGetInfoFunction::Work() { + ResumableTCPSocket* socket = GetTcpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + linked_ptr<sockets_tcp::SocketInfo> socket_info = + CreateSocketInfo(params_->socket_id, socket); + results_ = sockets_tcp::GetInfo::Results::Create(*socket_info); +} + +SocketsTcpGetSocketsFunction::SocketsTcpGetSocketsFunction() {} + +SocketsTcpGetSocketsFunction::~SocketsTcpGetSocketsFunction() {} + +bool SocketsTcpGetSocketsFunction::Prepare() { return true; } + +void SocketsTcpGetSocketsFunction::Work() { + std::vector<linked_ptr<sockets_tcp::SocketInfo> > socket_infos; + base::hash_set<int>* resource_ids = GetSocketIds(); + if (resource_ids != NULL) { + for (base::hash_set<int>::iterator it = resource_ids->begin(); + it != resource_ids->end(); + ++it) { + int socket_id = *it; + ResumableTCPSocket* socket = GetTcpSocket(socket_id); + if (socket) { + socket_infos.push_back(CreateSocketInfo(socket_id, socket)); + } + } + } + results_ = sockets_tcp::GetSockets::Results::Create(socket_infos); +} + +} // namespace core_api +} // namespace extensions diff --git a/extensions/browser/api/sockets_tcp/sockets_tcp_api.h b/extensions/browser/api/sockets_tcp/sockets_tcp_api.h new file mode 100644 index 0000000..8195ca4 --- /dev/null +++ b/extensions/browser/api/sockets_tcp/sockets_tcp_api.h @@ -0,0 +1,244 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKETS_TCP_SOCKETS_TCP_API_H_ +#define EXTENSIONS_BROWSER_API_SOCKETS_TCP_SOCKETS_TCP_API_H_ + +#include "extensions/browser/api/socket/socket_api.h" +#include "extensions/common/api/sockets_tcp.h" + +namespace extensions { +class ResumableTCPSocket; +} + +namespace extensions { +namespace core_api { + +class TCPSocketEventDispatcher; + +class TCPSocketAsyncApiFunction : public SocketAsyncApiFunction { + protected: + virtual ~TCPSocketAsyncApiFunction(); + + virtual scoped_ptr<SocketResourceManagerInterface> + CreateSocketResourceManager() OVERRIDE; + + ResumableTCPSocket* GetTcpSocket(int socket_id); +}; + +class TCPSocketExtensionWithDnsLookupFunction + : public SocketExtensionWithDnsLookupFunction { + protected: + virtual ~TCPSocketExtensionWithDnsLookupFunction(); + + virtual scoped_ptr<SocketResourceManagerInterface> + CreateSocketResourceManager() OVERRIDE; + + ResumableTCPSocket* GetTcpSocket(int socket_id); +}; + +class SocketsTcpCreateFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.create", SOCKETS_TCP_CREATE) + + SocketsTcpCreateFunction(); + + protected: + virtual ~SocketsTcpCreateFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(SocketsTcpUnitTest, Create); + scoped_ptr<sockets_tcp::Create::Params> params_; +}; + +class SocketsTcpUpdateFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.update", SOCKETS_TCP_UPDATE) + + SocketsTcpUpdateFunction(); + + protected: + virtual ~SocketsTcpUpdateFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_tcp::Update::Params> params_; +}; + +class SocketsTcpSetPausedFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.setPaused", SOCKETS_TCP_SETPAUSED) + + SocketsTcpSetPausedFunction(); + + protected: + virtual ~SocketsTcpSetPausedFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_tcp::SetPaused::Params> params_; + TCPSocketEventDispatcher* socket_event_dispatcher_; +}; + +class SocketsTcpSetKeepAliveFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.setKeepAlive", + SOCKETS_TCP_SETKEEPALIVE) + + SocketsTcpSetKeepAliveFunction(); + + protected: + virtual ~SocketsTcpSetKeepAliveFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_tcp::SetKeepAlive::Params> params_; +}; + +class SocketsTcpSetNoDelayFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.setNoDelay", SOCKETS_TCP_SETNODELAY) + + SocketsTcpSetNoDelayFunction(); + + protected: + virtual ~SocketsTcpSetNoDelayFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_tcp::SetNoDelay::Params> params_; +}; + +class SocketsTcpConnectFunction + : public TCPSocketExtensionWithDnsLookupFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.connect", SOCKETS_TCP_CONNECT) + + SocketsTcpConnectFunction(); + + protected: + virtual ~SocketsTcpConnectFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + + // SocketExtensionWithDnsLookupFunction: + virtual void AfterDnsLookup(int lookup_result) OVERRIDE; + + private: + void StartConnect(); + void OnCompleted(int net_result); + + scoped_ptr<sockets_tcp::Connect::Params> params_; + TCPSocketEventDispatcher* socket_event_dispatcher_; +}; + +class SocketsTcpDisconnectFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.disconnect", SOCKETS_TCP_DISCONNECT) + + SocketsTcpDisconnectFunction(); + + protected: + virtual ~SocketsTcpDisconnectFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_tcp::Disconnect::Params> params_; +}; + +class SocketsTcpSendFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.send", SOCKETS_TCP_SEND) + + SocketsTcpSendFunction(); + + protected: + virtual ~SocketsTcpSendFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + + private: + void OnCompleted(int net_result); + void SetSendResult(int net_result, int bytes_sent); + + scoped_ptr<sockets_tcp::Send::Params> params_; + scoped_refptr<net::IOBuffer> io_buffer_; + size_t io_buffer_size_; +}; + +class SocketsTcpCloseFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.close", SOCKETS_TCP_CLOSE) + + SocketsTcpCloseFunction(); + + protected: + virtual ~SocketsTcpCloseFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_tcp::Close::Params> params_; +}; + +class SocketsTcpGetInfoFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.getInfo", SOCKETS_TCP_GETINFO) + + SocketsTcpGetInfoFunction(); + + protected: + virtual ~SocketsTcpGetInfoFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_tcp::GetInfo::Params> params_; +}; + +class SocketsTcpGetSocketsFunction : public TCPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.tcp.getSockets", SOCKETS_TCP_GETSOCKETS) + + SocketsTcpGetSocketsFunction(); + + protected: + virtual ~SocketsTcpGetSocketsFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; +}; + +} // namespace core_api +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKETS_TCP_SOCKETS_TCP_API_H_ diff --git a/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.cc b/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.cc new file mode 100644 index 0000000..11d4cee --- /dev/null +++ b/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.cc @@ -0,0 +1,198 @@ +// Copyright 2014 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 "extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h" + +#include "extensions/browser/api/socket/tcp_socket.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/extensions_browser_client.h" +#include "net/base/net_errors.h" + +namespace { +int kDefaultBufferSize = 4096; +} + +namespace extensions { +namespace core_api { + +using content::BrowserThread; + +static base::LazyInstance< + BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher> > g_factory = + LAZY_INSTANCE_INITIALIZER; + +// static +BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher>* +TCPSocketEventDispatcher::GetFactoryInstance() { + return g_factory.Pointer(); +} + +// static +TCPSocketEventDispatcher* TCPSocketEventDispatcher::Get( + content::BrowserContext* context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + return BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher>::Get(context); +} + +TCPSocketEventDispatcher::TCPSocketEventDispatcher( + content::BrowserContext* context) + : thread_id_(Socket::kThreadId), browser_context_(context) { + ApiResourceManager<ResumableTCPSocket>* manager = + ApiResourceManager<ResumableTCPSocket>::Get(browser_context_); + DCHECK(manager) + << "There is no socket manager. " + "If this assertion is failing during a test, then it is likely that " + "TestExtensionSystem is failing to provide an instance of " + "ApiResourceManager<ResumableTCPSocket>."; + sockets_ = manager->data_; +} + +TCPSocketEventDispatcher::~TCPSocketEventDispatcher() {} + +TCPSocketEventDispatcher::ReadParams::ReadParams() {} + +TCPSocketEventDispatcher::ReadParams::~ReadParams() {} + +void TCPSocketEventDispatcher::OnSocketConnect(const std::string& extension_id, + int socket_id) { + DCHECK(BrowserThread::CurrentlyOn(thread_id_)); + + StartSocketRead(extension_id, socket_id); +} + +void TCPSocketEventDispatcher::OnSocketResume(const std::string& extension_id, + int socket_id) { + DCHECK(BrowserThread::CurrentlyOn(thread_id_)); + + StartSocketRead(extension_id, socket_id); +} + +void TCPSocketEventDispatcher::StartSocketRead(const std::string& extension_id, + int socket_id) { + DCHECK(BrowserThread::CurrentlyOn(thread_id_)); + + ReadParams params; + params.thread_id = thread_id_; + params.browser_context_id = browser_context_; + params.extension_id = extension_id; + params.sockets = sockets_; + params.socket_id = socket_id; + + StartRead(params); +} + +// static +void TCPSocketEventDispatcher::StartRead(const ReadParams& params) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + ResumableTCPSocket* socket = + params.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 read if the socket has been paused. + if (socket->paused()) + return; + + int buffer_size = socket->buffer_size(); + if (buffer_size <= 0) + buffer_size = kDefaultBufferSize; + socket->Read(buffer_size, + base::Bind(&TCPSocketEventDispatcher::ReadCallback, params)); +} + +// static +void TCPSocketEventDispatcher::ReadCallback( + const ReadParams& params, + int bytes_read, + scoped_refptr<net::IOBuffer> io_buffer) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + // If |bytes_read| == 0, the connection has been closed by the peer. + // If |bytes_read| < 0, there was a network error, and |bytes_read| is a value + // from "net::ERR_". + + if (bytes_read == 0) { + bytes_read = net::ERR_CONNECTION_CLOSED; + } + + if (bytes_read > 0) { + // Dispatch "onReceive" event. + sockets_tcp::ReceiveInfo receive_info; + receive_info.socket_id = params.socket_id; + receive_info.data = std::string(io_buffer->data(), bytes_read); + scoped_ptr<base::ListValue> args = + sockets_tcp::OnReceive::Create(receive_info); + scoped_ptr<Event> event( + new Event(sockets_tcp::OnReceive::kEventName, args.Pass())); + PostEvent(params, event.Pass()); + + // Post a task to delay the read until the socket is available, as + // calling StartReceive at this point would error with ERR_IO_PENDING. + BrowserThread::PostTask( + params.thread_id, + FROM_HERE, + base::Bind(&TCPSocketEventDispatcher::StartRead, params)); + } else if (bytes_read == net::ERR_IO_PENDING) { + // This happens when resuming a socket which already had an + // active "read" callback. + } else { + // Dispatch "onReceiveError" event but don't start another read to avoid + // potential infinite reads if we have a persistent network error. + sockets_tcp::ReceiveErrorInfo receive_error_info; + receive_error_info.socket_id = params.socket_id; + receive_error_info.result_code = bytes_read; + scoped_ptr<base::ListValue> args = + sockets_tcp::OnReceiveError::Create(receive_error_info); + scoped_ptr<Event> event( + new Event(sockets_tcp::OnReceiveError::kEventName, args.Pass())); + PostEvent(params, event.Pass()); + + // Since we got an error, the socket is now "paused" until the application + // "resumes" it. + ResumableTCPSocket* socket = + params.sockets->Get(params.extension_id, params.socket_id); + if (socket) { + socket->set_paused(true); + } + } +} + +// static +void TCPSocketEventDispatcher::PostEvent(const ReadParams& params, + scoped_ptr<Event> event) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + BrowserThread::PostTask(BrowserThread::UI, + FROM_HERE, + base::Bind(&DispatchEvent, + params.browser_context_id, + params.extension_id, + base::Passed(event.Pass()))); +} + +// static +void TCPSocketEventDispatcher::DispatchEvent(void* browser_context_id, + const std::string& extension_id, + scoped_ptr<Event> event) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + content::BrowserContext* context = + reinterpret_cast<content::BrowserContext*>(browser_context_id); + if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(context)) + return; + + EventRouter* router = ExtensionSystem::Get(context)->event_router(); + if (router) + router->DispatchEventToExtension(extension_id, event.Pass()); +} + +} // namespace core_api +} // namespace extensions diff --git a/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h b/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h new file mode 100644 index 0000000..0e16690 --- /dev/null +++ b/extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h @@ -0,0 +1,94 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKETS_TCP_TCP_SOCKET_EVENT_DISPATCHER_H_ +#define EXTENSIONS_BROWSER_API_SOCKETS_TCP_TCP_SOCKET_EVENT_DISPATCHER_H_ + +#include "extensions/browser/api/api_resource_manager.h" +#include "extensions/browser/api/sockets_tcp/sockets_tcp_api.h" + +namespace content { +class BrowserContext; +} + +namespace extensions { +struct Event; +class ResumableTCPSocket; +} + +namespace extensions { +namespace core_api { + +// Dispatch events related to "sockets.tcp" sockets from callback on native +// socket instances. There is one instance per profile. +class TCPSocketEventDispatcher + : public BrowserContextKeyedAPI, + public base::SupportsWeakPtr<TCPSocketEventDispatcher> { + public: + explicit TCPSocketEventDispatcher(content::BrowserContext* context); + virtual ~TCPSocketEventDispatcher(); + + // Socket is active, start receving from it. + void OnSocketConnect(const std::string& extension_id, int socket_id); + + // Socket is active again, start receiving data from it. + void OnSocketResume(const std::string& extension_id, int socket_id); + + // BrowserContextKeyedAPI implementation. + static BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher>* + GetFactoryInstance(); + + // Convenience method to get the SocketEventDispatcher for a profile. + static TCPSocketEventDispatcher* Get(content::BrowserContext* context); + + private: + typedef ApiResourceManager<ResumableTCPSocket>::ApiResourceData SocketData; + friend class BrowserContextKeyedAPIFactory<TCPSocketEventDispatcher>; + // BrowserContextKeyedAPI implementation. + static const char* service_name() { return "TCPSocketEventDispatcher"; } + static const bool kServiceHasOwnInstanceInIncognito = true; + static const bool kServiceIsNULLWhileTesting = true; + + // base::Bind supports methods with up to 6 parameters. ReadParams is used + // as a workaround that limitation for invoking StartReceive. + struct ReadParams { + ReadParams(); + ~ReadParams(); + + content::BrowserThread::ID thread_id; + void* browser_context_id; + std::string extension_id; + scoped_refptr<SocketData> sockets; + int socket_id; + }; + + // Start a receive and register a callback. + void StartSocketRead(const std::string& extension_id, int socket_id); + + // Start a receive and register a callback. + static void StartRead(const ReadParams& params); + + // Called when socket receive data. + static void ReadCallback(const ReadParams& params, + int bytes_read, + scoped_refptr<net::IOBuffer> io_buffer); + + // Post an extension event from IO to UI thread + static void PostEvent(const ReadParams& params, scoped_ptr<Event> event); + + // Dispatch an extension event on to EventRouter instance on UI thread. + static void DispatchEvent(void* browser_context_id, + const std::string& extension_id, + scoped_ptr<Event> event); + + // Usually IO thread (except for unit testing). + content::BrowserThread::ID thread_id_; + content::BrowserContext* const browser_context_; + scoped_refptr<SocketData> sockets_; +}; + +} // namespace core_api +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKETS_TCP_TCP_SOCKET_EVENT_DISPATCHER_H_ diff --git a/extensions/browser/api/sockets_tcp_server/sockets_tcp_server/OWNERS b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server/OWNERS new file mode 100644 index 0000000..3e30c82 --- /dev/null +++ b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server/OWNERS @@ -0,0 +1 @@ +rpaquay@chromium.org diff --git a/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc new file mode 100644 index 0000000..6adc0fc --- /dev/null +++ b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.cc @@ -0,0 +1,298 @@ +// Copyright 2014 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 "extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h" + +#include "chrome/common/extensions/api/sockets/sockets_manifest_data.h" +#include "chrome/common/extensions/permissions/socket_permission.h" +#include "content/public/common/socket_permission_request.h" +#include "extensions/browser/api/socket/tcp_socket.h" +#include "extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h" +#include "extensions/common/permissions/permissions_data.h" +#include "net/base/net_errors.h" + +using content::SocketPermissionRequest; +using extensions::ResumableTCPServerSocket; +using extensions::core_api::sockets_tcp_server::SocketInfo; +using extensions::core_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<SocketInfo> CreateSocketInfo(int socket_id, + ResumableTCPServerSocket* socket) { + linked_ptr<SocketInfo> 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 core_api { + +TCPServerSocketAsyncApiFunction::~TCPServerSocketAsyncApiFunction() {} + +scoped_ptr<SocketResourceManagerInterface> +TCPServerSocketAsyncApiFunction::CreateSocketResourceManager() { + return scoped_ptr<SocketResourceManagerInterface>( + new SocketResourceManager<ResumableTCPServerSocket>()).Pass(); +} + +ResumableTCPServerSocket* TCPServerSocketAsyncApiFunction::GetTcpSocket( + int socket_id) { + return static_cast<ResumableTCPServerSocket*>(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_ = core_api::sockets_tcp_server::SetPaused::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + + socket_event_dispatcher_ = + TCPServerSocketEventDispatcher::Get(browser_context()); + 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_ = core_api::sockets_tcp_server::Listen::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + + socket_event_dispatcher_ = + TCPServerSocketEventDispatcher::Get(browser_context()); + 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<sockets_tcp_server::SocketInfo> 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<linked_ptr<sockets_tcp_server::SocketInfo> > socket_infos; + base::hash_set<int>* resource_ids = GetSocketIds(); + if (resource_ids != NULL) { + for (base::hash_set<int>::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 core_api +} // namespace extensions diff --git a/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h new file mode 100644 index 0000000..89d8cb8 --- /dev/null +++ b/extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h @@ -0,0 +1,178 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_ +#define EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_ + +#include "extensions/browser/api/socket/socket_api.h" +#include "extensions/common/api/sockets_tcp_server.h" + +namespace extensions { +class ResumableTCPServerSocket; +} + +namespace extensions { +namespace core_api { + +class TCPServerSocketAsyncApiFunction : public SocketAsyncApiFunction { + protected: + virtual ~TCPServerSocketAsyncApiFunction(); + + virtual scoped_ptr<SocketResourceManagerInterface> + 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<sockets_tcp_server::Create::Params> 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<sockets_tcp_server::Update::Params> 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<sockets_tcp_server::SetPaused::Params> 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<sockets_tcp_server::Listen::Params> 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<sockets_tcp_server::Disconnect::Params> 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<sockets_tcp_server::Close::Params> 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<sockets_tcp_server::GetInfo::Params> 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 core_api +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_ diff --git a/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc b/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc new file mode 100644 index 0000000..31eeeab --- /dev/null +++ b/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc @@ -0,0 +1,199 @@ +// Copyright 2014 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 "extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h" + +#include "extensions/browser/api/socket/tcp_socket.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/extensions_browser_client.h" +#include "net/base/net_errors.h" + +namespace extensions { +namespace core_api { + +using content::BrowserThread; + +static base::LazyInstance< + BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher> > g_factory = + LAZY_INSTANCE_INITIALIZER; + +// static +BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher>* +TCPServerSocketEventDispatcher::GetFactoryInstance() { + return g_factory.Pointer(); +} + +// static +TCPServerSocketEventDispatcher* TCPServerSocketEventDispatcher::Get( + content::BrowserContext* context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + return BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher>::Get( + context); +} + +TCPServerSocketEventDispatcher::TCPServerSocketEventDispatcher( + content::BrowserContext* context) + : thread_id_(Socket::kThreadId), browser_context_(context) { + ApiResourceManager<ResumableTCPServerSocket>* server_manager = + ApiResourceManager<ResumableTCPServerSocket>::Get(browser_context_); + 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<ResumableTCPServerSocket>."; + server_sockets_ = server_manager->data_; + + ApiResourceManager<ResumableTCPSocket>* client_manager = + ApiResourceManager<ResumableTCPSocket>::Get(browser_context_); + 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<ResumableTCPSocket>."; + 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.browser_context_id = browser_context_; + 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<base::ListValue> args = + sockets_tcp_server::OnAccept::Create(accept_info); + scoped_ptr<Event> 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<base::ListValue> args = + sockets_tcp_server::OnAcceptError::Create(accept_error_info); + scoped_ptr<Event> 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> event) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + BrowserThread::PostTask(BrowserThread::UI, + FROM_HERE, + base::Bind(&DispatchEvent, + params.browser_context_id, + params.extension_id, + base::Passed(event.Pass()))); +} + +// static +void TCPServerSocketEventDispatcher::DispatchEvent( + void* browser_context_id, + const std::string& extension_id, + scoped_ptr<Event> event) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + content::BrowserContext* context = + reinterpret_cast<content::BrowserContext*>(browser_context_id); + if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(context)) + return; + EventRouter* router = ExtensionSystem::Get(context)->event_router(); + if (router) + router->DispatchEventToExtension(extension_id, event.Pass()); +} + +} // namespace core_api +} // namespace extensions diff --git a/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h b/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h new file mode 100644 index 0000000..d4d604c --- /dev/null +++ b/extensions/browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h @@ -0,0 +1,100 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_ +#define EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_ + +#include "extensions/browser/api/api_resource_manager.h" +#include "extensions/browser/api/sockets_tcp/sockets_tcp_api.h" +#include "extensions/browser/api/sockets_tcp_server/sockets_tcp_server_api.h" + +namespace content { +class BrowserContext; +} + +namespace extensions { +struct Event; +class ResumableTCPSocket; +} + +namespace extensions { +namespace core_api { + +// Dispatch events related to "sockets.tcp" sockets from callback on native +// socket instances. There is one instance per profile. +class TCPServerSocketEventDispatcher + : public BrowserContextKeyedAPI, + public base::SupportsWeakPtr<TCPServerSocketEventDispatcher> { + public: + explicit TCPServerSocketEventDispatcher(content::BrowserContext* context); + 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); + + // BrowserContextKeyedAPI implementation. + static BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher>* + GetFactoryInstance(); + + // Convenience method to get the SocketEventDispatcher for a profile. + static TCPServerSocketEventDispatcher* Get(content::BrowserContext* context); + + private: + typedef ApiResourceManager<ResumableTCPServerSocket>::ApiResourceData + ServerSocketData; + typedef ApiResourceManager<ResumableTCPSocket>::ApiResourceData + ClientSocketData; + friend class BrowserContextKeyedAPIFactory<TCPServerSocketEventDispatcher>; + // BrowserContextKeyedAPI 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* browser_context_id; + std::string extension_id; + scoped_refptr<ServerSocketData> server_sockets; + scoped_refptr<ClientSocketData> 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> event); + + // Dispatch an extension event on to EventRouter instance on UI thread. + static void DispatchEvent(void* browser_context_id, + const std::string& extension_id, + scoped_ptr<Event> event); + + // Usually IO thread (except for unit testing). + content::BrowserThread::ID thread_id_; + content::BrowserContext* const browser_context_; + scoped_refptr<ServerSocketData> server_sockets_; + scoped_refptr<ClientSocketData> client_sockets_; +}; + +} // namespace core_api +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_ diff --git a/extensions/browser/api/sockets_udp/sockets_udp/OWNERS b/extensions/browser/api/sockets_udp/sockets_udp/OWNERS new file mode 100644 index 0000000..3e30c82 --- /dev/null +++ b/extensions/browser/api/sockets_udp/sockets_udp/OWNERS @@ -0,0 +1 @@ +rpaquay@chromium.org diff --git a/extensions/browser/api/sockets_udp/sockets_udp_api.cc b/extensions/browser/api/sockets_udp/sockets_udp_api.cc new file mode 100644 index 0000000..9ccb4a7 --- /dev/null +++ b/extensions/browser/api/sockets_udp/sockets_udp_api.cc @@ -0,0 +1,505 @@ +// Copyright 2014 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 "extensions/browser/api/sockets_udp/sockets_udp_api.h" + +#include "chrome/common/extensions/api/sockets/sockets_manifest_data.h" +#include "content/public/common/socket_permission_request.h" +#include "extensions/browser/api/socket/udp_socket.h" +#include "extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h" +#include "net/base/net_errors.h" + +namespace extensions { +namespace core_api { + +using content::SocketPermissionRequest; + +const char kSocketNotFoundError[] = "Socket not found"; +const char kPermissionError[] = "App does not have permission"; +const char kWildcardAddress[] = "*"; +const int kWildcardPort = 0; + +UDPSocketAsyncApiFunction::~UDPSocketAsyncApiFunction() {} + +scoped_ptr<SocketResourceManagerInterface> +UDPSocketAsyncApiFunction::CreateSocketResourceManager() { + return scoped_ptr<SocketResourceManagerInterface>( + new SocketResourceManager<ResumableUDPSocket>()).Pass(); +} + +ResumableUDPSocket* UDPSocketAsyncApiFunction::GetUdpSocket(int socket_id) { + return static_cast<ResumableUDPSocket*>(GetSocket(socket_id)); +} + +UDPSocketExtensionWithDnsLookupFunction:: + ~UDPSocketExtensionWithDnsLookupFunction() {} + +scoped_ptr<SocketResourceManagerInterface> +UDPSocketExtensionWithDnsLookupFunction::CreateSocketResourceManager() { + return scoped_ptr<SocketResourceManagerInterface>( + new SocketResourceManager<ResumableUDPSocket>()).Pass(); +} + +ResumableUDPSocket* UDPSocketExtensionWithDnsLookupFunction::GetUdpSocket( + int socket_id) { + return static_cast<ResumableUDPSocket*>(GetSocket(socket_id)); +} + +linked_ptr<sockets_udp::SocketInfo> CreateSocketInfo( + int socket_id, + ResumableUDPSocket* socket) { + linked_ptr<sockets_udp::SocketInfo> socket_info( + new sockets_udp::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(); + if (socket->buffer_size() > 0) { + socket_info->buffer_size.reset(new int(socket->buffer_size())); + } + 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(ResumableUDPSocket* socket, + sockets_udp::SocketProperties* properties) { + if (properties->name.get()) { + socket->set_name(*properties->name.get()); + } + if (properties->persistent.get()) { + socket->set_persistent(*properties->persistent.get()); + } + if (properties->buffer_size.get()) { + socket->set_buffer_size(*properties->buffer_size.get()); + } +} + +SocketsUdpCreateFunction::SocketsUdpCreateFunction() {} + +SocketsUdpCreateFunction::~SocketsUdpCreateFunction() {} + +bool SocketsUdpCreateFunction::Prepare() { + params_ = sockets_udp::Create::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpCreateFunction::Work() { + ResumableUDPSocket* socket = new ResumableUDPSocket(extension_->id()); + + sockets_udp::SocketProperties* properties = params_.get()->properties.get(); + if (properties) { + SetSocketProperties(socket, properties); + } + + sockets_udp::CreateInfo create_info; + create_info.socket_id = AddSocket(socket); + results_ = sockets_udp::Create::Results::Create(create_info); +} + +SocketsUdpUpdateFunction::SocketsUdpUpdateFunction() {} + +SocketsUdpUpdateFunction::~SocketsUdpUpdateFunction() {} + +bool SocketsUdpUpdateFunction::Prepare() { + params_ = sockets_udp::Update::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpUpdateFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + SetSocketProperties(socket, ¶ms_.get()->properties); + results_ = sockets_udp::Update::Results::Create(); +} + +SocketsUdpSetPausedFunction::SocketsUdpSetPausedFunction() + : socket_event_dispatcher_(NULL) {} + +SocketsUdpSetPausedFunction::~SocketsUdpSetPausedFunction() {} + +bool SocketsUdpSetPausedFunction::Prepare() { + params_ = core_api::sockets_udp::SetPaused::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + + socket_event_dispatcher_ = UDPSocketEventDispatcher::Get(browser_context()); + 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 " + "UDPSocketEventDispatcher."; + return socket_event_dispatcher_ != NULL; +} + +void SocketsUdpSetPausedFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + if (socket->paused() != params_->paused) { + socket->set_paused(params_->paused); + if (socket->IsBound() && !params_->paused) { + socket_event_dispatcher_->OnSocketResume(extension_->id(), + params_->socket_id); + } + } + + results_ = sockets_udp::SetPaused::Results::Create(); +} + +SocketsUdpBindFunction::SocketsUdpBindFunction() + : socket_event_dispatcher_(NULL) {} + +SocketsUdpBindFunction::~SocketsUdpBindFunction() {} + +bool SocketsUdpBindFunction::Prepare() { + params_ = sockets_udp::Bind::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + + socket_event_dispatcher_ = UDPSocketEventDispatcher::Get(browser_context()); + 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 " + "UDPSocketEventDispatcher."; + return socket_event_dispatcher_ != NULL; +} + +void SocketsUdpBindFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + content::SocketPermissionRequest param( + SocketPermissionRequest::UDP_BIND, params_->address, params_->port); + if (!SocketsManifestData::CheckRequest(GetExtension(), param)) { + error_ = kPermissionError; + return; + } + + int net_result = socket->Bind(params_->address, params_->port); + if (net_result == net::OK) { + socket_event_dispatcher_->OnSocketBind(extension_->id(), + params_->socket_id); + } + + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_udp::Bind::Results::Create(net_result); +} + +SocketsUdpSendFunction::SocketsUdpSendFunction() : io_buffer_size_(0) {} + +SocketsUdpSendFunction::~SocketsUdpSendFunction() {} + +bool SocketsUdpSendFunction::Prepare() { + params_ = sockets_udp::Send::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + io_buffer_size_ = params_->data.size(); + io_buffer_ = new net::WrappedIOBuffer(params_->data.data()); + + return true; +} + +void SocketsUdpSendFunction::AsyncWorkStart() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + AsyncWorkCompleted(); + return; + } + + content::SocketPermissionRequest param( + SocketPermissionRequest::UDP_SEND_TO, params_->address, params_->port); + if (!SocketsManifestData::CheckRequest(GetExtension(), param)) { + error_ = kPermissionError; + AsyncWorkCompleted(); + return; + } + + StartDnsLookup(params_->address); +} + +void SocketsUdpSendFunction::AfterDnsLookup(int lookup_result) { + if (lookup_result == net::OK) { + StartSendTo(); + } else { + SetSendResult(lookup_result, -1); + } +} + +void SocketsUdpSendFunction::StartSendTo() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + AsyncWorkCompleted(); + return; + } + + socket->SendTo(io_buffer_, + io_buffer_size_, + resolved_address_, + params_->port, + base::Bind(&SocketsUdpSendFunction::OnCompleted, this)); +} + +void SocketsUdpSendFunction::OnCompleted(int net_result) { + if (net_result >= net::OK) { + SetSendResult(net::OK, net_result); + } else { + SetSendResult(net_result, -1); + } +} + +void SocketsUdpSendFunction::SetSendResult(int net_result, int bytes_sent) { + CHECK(net_result <= net::OK) << "Network status code must be < 0"; + + sockets_udp::SendInfo send_info; + send_info.result_code = net_result; + if (net_result == net::OK) { + send_info.bytes_sent.reset(new int(bytes_sent)); + } + + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_udp::Send::Results::Create(send_info); + AsyncWorkCompleted(); +} + +SocketsUdpCloseFunction::SocketsUdpCloseFunction() {} + +SocketsUdpCloseFunction::~SocketsUdpCloseFunction() {} + +bool SocketsUdpCloseFunction::Prepare() { + params_ = sockets_udp::Close::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpCloseFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + socket->Disconnect(); + RemoveSocket(params_->socket_id); + results_ = sockets_udp::Close::Results::Create(); +} + +SocketsUdpGetInfoFunction::SocketsUdpGetInfoFunction() {} + +SocketsUdpGetInfoFunction::~SocketsUdpGetInfoFunction() {} + +bool SocketsUdpGetInfoFunction::Prepare() { + params_ = sockets_udp::GetInfo::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpGetInfoFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + linked_ptr<sockets_udp::SocketInfo> socket_info = + CreateSocketInfo(params_->socket_id, socket); + results_ = sockets_udp::GetInfo::Results::Create(*socket_info); +} + +SocketsUdpGetSocketsFunction::SocketsUdpGetSocketsFunction() {} + +SocketsUdpGetSocketsFunction::~SocketsUdpGetSocketsFunction() {} + +bool SocketsUdpGetSocketsFunction::Prepare() { return true; } + +void SocketsUdpGetSocketsFunction::Work() { + std::vector<linked_ptr<sockets_udp::SocketInfo> > socket_infos; + base::hash_set<int>* resource_ids = GetSocketIds(); + if (resource_ids != NULL) { + for (base::hash_set<int>::iterator it = resource_ids->begin(); + it != resource_ids->end(); + ++it) { + int socket_id = *it; + ResumableUDPSocket* socket = GetUdpSocket(socket_id); + if (socket) { + socket_infos.push_back(CreateSocketInfo(socket_id, socket)); + } + } + } + results_ = sockets_udp::GetSockets::Results::Create(socket_infos); +} + +SocketsUdpJoinGroupFunction::SocketsUdpJoinGroupFunction() {} + +SocketsUdpJoinGroupFunction::~SocketsUdpJoinGroupFunction() {} + +bool SocketsUdpJoinGroupFunction::Prepare() { + params_ = sockets_udp::JoinGroup::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpJoinGroupFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + content::SocketPermissionRequest param( + SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, + kWildcardAddress, + kWildcardPort); + if (!SocketsManifestData::CheckRequest(GetExtension(), param)) { + error_ = kPermissionError; + return; + } + + int net_result = socket->JoinGroup(params_->address); + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_udp::JoinGroup::Results::Create(net_result); +} + +SocketsUdpLeaveGroupFunction::SocketsUdpLeaveGroupFunction() {} + +SocketsUdpLeaveGroupFunction::~SocketsUdpLeaveGroupFunction() {} + +bool SocketsUdpLeaveGroupFunction::Prepare() { + params_ = core_api::sockets_udp::LeaveGroup::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpLeaveGroupFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + content::SocketPermissionRequest param( + SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, + kWildcardAddress, + kWildcardPort); + if (!SocketsManifestData::CheckRequest(GetExtension(), param)) { + error_ = kPermissionError; + return; + } + + int net_result = socket->LeaveGroup(params_->address); + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_udp::LeaveGroup::Results::Create(net_result); +} + +SocketsUdpSetMulticastTimeToLiveFunction:: + SocketsUdpSetMulticastTimeToLiveFunction() {} + +SocketsUdpSetMulticastTimeToLiveFunction:: + ~SocketsUdpSetMulticastTimeToLiveFunction() {} + +bool SocketsUdpSetMulticastTimeToLiveFunction::Prepare() { + params_ = + core_api::sockets_udp::SetMulticastTimeToLive::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpSetMulticastTimeToLiveFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + int net_result = socket->SetMulticastTimeToLive(params_->ttl); + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_udp::SetMulticastTimeToLive::Results::Create(net_result); +} + +SocketsUdpSetMulticastLoopbackModeFunction:: + SocketsUdpSetMulticastLoopbackModeFunction() {} + +SocketsUdpSetMulticastLoopbackModeFunction:: + ~SocketsUdpSetMulticastLoopbackModeFunction() {} + +bool SocketsUdpSetMulticastLoopbackModeFunction::Prepare() { + params_ = + core_api::sockets_udp::SetMulticastLoopbackMode::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpSetMulticastLoopbackModeFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + int net_result = socket->SetMulticastLoopbackMode(params_->enabled); + if (net_result != net::OK) + error_ = net::ErrorToString(net_result); + results_ = sockets_udp::SetMulticastLoopbackMode::Results::Create(net_result); +} + +SocketsUdpGetJoinedGroupsFunction::SocketsUdpGetJoinedGroupsFunction() {} + +SocketsUdpGetJoinedGroupsFunction::~SocketsUdpGetJoinedGroupsFunction() {} + +bool SocketsUdpGetJoinedGroupsFunction::Prepare() { + params_ = core_api::sockets_udp::GetJoinedGroups::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpGetJoinedGroupsFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + content::SocketPermissionRequest param( + SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, + kWildcardAddress, + kWildcardPort); + if (!SocketsManifestData::CheckRequest(GetExtension(), param)) { + error_ = kPermissionError; + return; + } + + const std::vector<std::string>& groups = socket->GetJoinedGroups(); + results_ = sockets_udp::GetJoinedGroups::Results::Create(groups); +} + +} // namespace core_api +} // namespace extensions diff --git a/extensions/browser/api/sockets_udp/sockets_udp_api.h b/extensions/browser/api/sockets_udp/sockets_udp_api.h new file mode 100644 index 0000000..16e61d4 --- /dev/null +++ b/extensions/browser/api/sockets_udp/sockets_udp_api.h @@ -0,0 +1,279 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKETS_UDP_SOCKETS_UDP_API_H_ +#define EXTENSIONS_BROWSER_API_SOCKETS_UDP_SOCKETS_UDP_API_H_ + +#include "extensions/browser/api/socket/socket_api.h" +#include "extensions/common/api/sockets_udp.h" + +namespace extensions { +class ResumableUDPSocket; +} + +namespace extensions { +namespace core_api { + +class UDPSocketEventDispatcher; + +class UDPSocketAsyncApiFunction : public SocketAsyncApiFunction { + protected: + virtual ~UDPSocketAsyncApiFunction(); + + virtual scoped_ptr<SocketResourceManagerInterface> + CreateSocketResourceManager() OVERRIDE; + + ResumableUDPSocket* GetUdpSocket(int socket_id); +}; + +class UDPSocketExtensionWithDnsLookupFunction + : public SocketExtensionWithDnsLookupFunction { + protected: + virtual ~UDPSocketExtensionWithDnsLookupFunction(); + + virtual scoped_ptr<SocketResourceManagerInterface> + CreateSocketResourceManager() OVERRIDE; + + ResumableUDPSocket* GetUdpSocket(int socket_id); +}; + +class SocketsUdpCreateFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.create", SOCKETS_UDP_CREATE) + + SocketsUdpCreateFunction(); + + protected: + virtual ~SocketsUdpCreateFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(SocketsUdpUnitTest, Create); + scoped_ptr<sockets_udp::Create::Params> params_; +}; + +class SocketsUdpUpdateFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.update", SOCKETS_UDP_UPDATE) + + SocketsUdpUpdateFunction(); + + protected: + virtual ~SocketsUdpUpdateFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::Update::Params> params_; +}; + +class SocketsUdpSetPausedFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.setPaused", SOCKETS_UDP_SETPAUSED) + + SocketsUdpSetPausedFunction(); + + protected: + virtual ~SocketsUdpSetPausedFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::SetPaused::Params> params_; + UDPSocketEventDispatcher* socket_event_dispatcher_; +}; + +class SocketsUdpBindFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.bind", SOCKETS_UDP_BIND) + + SocketsUdpBindFunction(); + + protected: + virtual ~SocketsUdpBindFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::Bind::Params> params_; + UDPSocketEventDispatcher* socket_event_dispatcher_; +}; + +class SocketsUdpSendFunction : public UDPSocketExtensionWithDnsLookupFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.send", SOCKETS_UDP_SEND) + + SocketsUdpSendFunction(); + + protected: + virtual ~SocketsUdpSendFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void AsyncWorkStart() OVERRIDE; + void OnCompleted(int net_result); + void SetSendResult(int net_result, int bytes_sent); + + // SocketExtensionWithDnsLookupFunction: + virtual void AfterDnsLookup(int lookup_result) OVERRIDE; + + private: + void StartSendTo(); + + scoped_ptr<sockets_udp::Send::Params> params_; + scoped_refptr<net::IOBuffer> io_buffer_; + size_t io_buffer_size_; +}; + +class SocketsUdpCloseFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.close", SOCKETS_UDP_CLOSE) + + SocketsUdpCloseFunction(); + + protected: + virtual ~SocketsUdpCloseFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::Close::Params> params_; +}; + +class SocketsUdpGetInfoFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.getInfo", SOCKETS_UDP_GETINFO) + + SocketsUdpGetInfoFunction(); + + protected: + virtual ~SocketsUdpGetInfoFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::GetInfo::Params> params_; +}; + +class SocketsUdpGetSocketsFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.getSockets", SOCKETS_UDP_GETSOCKETS) + + SocketsUdpGetSocketsFunction(); + + protected: + virtual ~SocketsUdpGetSocketsFunction(); + + // AsyncApiFunction: + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; +}; + +class SocketsUdpJoinGroupFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.joinGroup", SOCKETS_UDP_JOINGROUP) + + SocketsUdpJoinGroupFunction(); + + protected: + virtual ~SocketsUdpJoinGroupFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::JoinGroup::Params> params_; +}; + +class SocketsUdpLeaveGroupFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.leaveGroup", SOCKETS_UDP_LEAVEGROUP) + + SocketsUdpLeaveGroupFunction(); + + protected: + virtual ~SocketsUdpLeaveGroupFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::LeaveGroup::Params> params_; +}; + +class SocketsUdpSetMulticastTimeToLiveFunction + : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.setMulticastTimeToLive", + SOCKETS_UDP_SETMULTICASTTIMETOLIVE) + + SocketsUdpSetMulticastTimeToLiveFunction(); + + protected: + virtual ~SocketsUdpSetMulticastTimeToLiveFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::SetMulticastTimeToLive::Params> params_; +}; + +class SocketsUdpSetMulticastLoopbackModeFunction + : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.setMulticastLoopbackMode", + SOCKETS_UDP_SETMULTICASTLOOPBACKMODE) + + SocketsUdpSetMulticastLoopbackModeFunction(); + + protected: + virtual ~SocketsUdpSetMulticastLoopbackModeFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::SetMulticastLoopbackMode::Params> params_; +}; + +class SocketsUdpGetJoinedGroupsFunction : public UDPSocketAsyncApiFunction { + public: + DECLARE_EXTENSION_FUNCTION("sockets.udp.getJoinedGroups", + SOCKETS_UDP_GETJOINEDGROUPS) + + SocketsUdpGetJoinedGroupsFunction(); + + protected: + virtual ~SocketsUdpGetJoinedGroupsFunction(); + + // AsyncApiFunction + virtual bool Prepare() OVERRIDE; + virtual void Work() OVERRIDE; + + private: + scoped_ptr<sockets_udp::GetJoinedGroups::Params> params_; +}; + +} // namespace core_api +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKETS_UDP_SOCKETS_UDP_API_H_ diff --git a/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.cc b/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.cc new file mode 100644 index 0000000..e985dfb --- /dev/null +++ b/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.cc @@ -0,0 +1,183 @@ +// Copyright 2014 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 "extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h" + +#include "extensions/browser/api/socket/udp_socket.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/extensions_browser_client.h" +#include "net/base/net_errors.h" + +namespace extensions { +namespace core_api { + +using content::BrowserThread; + +static base::LazyInstance< + BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher> > g_factory = + LAZY_INSTANCE_INITIALIZER; + +// static +BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher>* +UDPSocketEventDispatcher::GetFactoryInstance() { + return g_factory.Pointer(); +} + +// static +UDPSocketEventDispatcher* UDPSocketEventDispatcher::Get( + content::BrowserContext* context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + return BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher>::Get(context); +} + +UDPSocketEventDispatcher::UDPSocketEventDispatcher( + content::BrowserContext* context) + : thread_id_(Socket::kThreadId), browser_context_(context) { + ApiResourceManager<ResumableUDPSocket>* manager = + ApiResourceManager<ResumableUDPSocket>::Get(browser_context_); + DCHECK(manager) + << "There is no socket manager. " + "If this assertion is failing during a test, then it is likely that " + "TestExtensionSystem is failing to provide an instance of " + "ApiResourceManager<ResumableUDPSocket>."; + sockets_ = manager->data_; +} + +UDPSocketEventDispatcher::~UDPSocketEventDispatcher() {} + +UDPSocketEventDispatcher::ReceiveParams::ReceiveParams() {} + +UDPSocketEventDispatcher::ReceiveParams::~ReceiveParams() {} + +void UDPSocketEventDispatcher::OnSocketBind(const std::string& extension_id, + int socket_id) { + OnSocketResume(extension_id, socket_id); +} + +void UDPSocketEventDispatcher::OnSocketResume(const std::string& extension_id, + int socket_id) { + DCHECK(BrowserThread::CurrentlyOn(thread_id_)); + + ReceiveParams params; + params.thread_id = thread_id_; + params.browser_context_id = browser_context_; + params.extension_id = extension_id; + params.sockets = sockets_; + params.socket_id = socket_id; + + StartReceive(params); +} + +/* static */ +void UDPSocketEventDispatcher::StartReceive(const ReceiveParams& params) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + ResumableUDPSocket* socket = + params.sockets->Get(params.extension_id, params.socket_id); + if (socket == NULL) { + // 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 read if the socket has been paused. + if (socket->paused()) + return; + + int buffer_size = (socket->buffer_size() <= 0 ? 4096 : socket->buffer_size()); + socket->RecvFrom( + buffer_size, + base::Bind(&UDPSocketEventDispatcher::ReceiveCallback, params)); +} + +/* static */ +void UDPSocketEventDispatcher::ReceiveCallback( + const ReceiveParams& params, + int bytes_read, + scoped_refptr<net::IOBuffer> io_buffer, + const std::string& address, + int port) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + // If |bytes_read| == 0, the message contained no data. + // If |bytes_read| < 0, there was a network error, and |bytes_read| is a value + // from "net::ERR_". + + if (bytes_read >= 0) { + // Dispatch "onReceive" event. + sockets_udp::ReceiveInfo receive_info; + receive_info.socket_id = params.socket_id; + receive_info.data = std::string(io_buffer->data(), bytes_read); + receive_info.remote_address = address; + receive_info.remote_port = port; + scoped_ptr<base::ListValue> args = + sockets_udp::OnReceive::Create(receive_info); + scoped_ptr<Event> event( + new Event(sockets_udp::OnReceive::kEventName, args.Pass())); + PostEvent(params, event.Pass()); + + // Post a task to delay the read until the socket is available, as + // calling StartReceive at this point would error with ERR_IO_PENDING. + BrowserThread::PostTask( + params.thread_id, + FROM_HERE, + base::Bind(&UDPSocketEventDispatcher::StartReceive, params)); + } else if (bytes_read == net::ERR_IO_PENDING) { + // This happens when resuming a socket which already had an + // active "recv" callback. + } else { + // Dispatch "onReceiveError" event but don't start another read to avoid + // potential infinite reads if we have a persistent network error. + sockets_udp::ReceiveErrorInfo receive_error_info; + receive_error_info.socket_id = params.socket_id; + receive_error_info.result_code = bytes_read; + scoped_ptr<base::ListValue> args = + sockets_udp::OnReceiveError::Create(receive_error_info); + scoped_ptr<Event> event( + new Event(sockets_udp::OnReceiveError::kEventName, args.Pass())); + PostEvent(params, event.Pass()); + + // Since we got an error, the socket is now "paused" until the application + // "resumes" it. + ResumableUDPSocket* socket = + params.sockets->Get(params.extension_id, params.socket_id); + if (socket) { + socket->set_paused(true); + } + } +} + +/* static */ +void UDPSocketEventDispatcher::PostEvent(const ReceiveParams& params, + scoped_ptr<Event> event) { + DCHECK(BrowserThread::CurrentlyOn(params.thread_id)); + + BrowserThread::PostTask(BrowserThread::UI, + FROM_HERE, + base::Bind(&DispatchEvent, + params.browser_context_id, + params.extension_id, + base::Passed(event.Pass()))); +} + +/*static*/ +void UDPSocketEventDispatcher::DispatchEvent(void* browser_context_id, + const std::string& extension_id, + scoped_ptr<Event> event) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + content::BrowserContext* context = + reinterpret_cast<content::BrowserContext*>(browser_context_id); + if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(context)) + return; + EventRouter* router = ExtensionSystem::Get(context)->event_router(); + if (router) + router->DispatchEventToExtension(extension_id, event.Pass()); +} + +} // namespace core_api +} // namespace extensions diff --git a/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h b/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h new file mode 100644 index 0000000..e42ecb4 --- /dev/null +++ b/extensions/browser/api/sockets_udp/udp_socket_event_dispatcher.h @@ -0,0 +1,93 @@ +// Copyright 2014 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 EXTENSIONS_BROWSER_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_ +#define EXTENSIONS_BROWSER_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_ + +#include "extensions/browser/api/api_resource_manager.h" +#include "extensions/browser/api/sockets_udp/sockets_udp_api.h" + +namespace content { +class BrowserContext; +} + +namespace extensions { +struct Event; +class ResumableUDPSocket; +} + +namespace extensions { +namespace core_api { + +// Dispatch events related to "sockets.udp" sockets from callback on native +// socket instances. There is one instance per profile. +class UDPSocketEventDispatcher + : public BrowserContextKeyedAPI, + public base::SupportsWeakPtr<UDPSocketEventDispatcher> { + public: + explicit UDPSocketEventDispatcher(content::BrowserContext* context); + virtual ~UDPSocketEventDispatcher(); + + // Socket is active, start receving from it. + void OnSocketBind(const std::string& extension_id, int socket_id); + + // Socket is active again, start receiving data from it. + void OnSocketResume(const std::string& extension_id, int socket_id); + + // BrowserContextKeyedAPI implementation. + static BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher>* + GetFactoryInstance(); + + // Convenience method to get the SocketEventDispatcher for a profile. + static UDPSocketEventDispatcher* Get(content::BrowserContext* context); + + private: + typedef ApiResourceManager<ResumableUDPSocket>::ApiResourceData SocketData; + friend class BrowserContextKeyedAPIFactory<UDPSocketEventDispatcher>; + // BrowserContextKeyedAPI implementation. + static const char* service_name() { return "UDPSocketEventDispatcher"; } + static const bool kServiceHasOwnInstanceInIncognito = true; + static const bool kServiceIsNULLWhileTesting = true; + + // base::Bind supports methods with up to 6 parameters. ReceiveParams is used + // as a workaround that limitation for invoking StartReceive. + struct ReceiveParams { + ReceiveParams(); + ~ReceiveParams(); + + content::BrowserThread::ID thread_id; + void* browser_context_id; + std::string extension_id; + scoped_refptr<SocketData> sockets; + int socket_id; + }; + + // Start a receive and register a callback. + static void StartReceive(const ReceiveParams& params); + + // Called when socket receive data. + static void ReceiveCallback(const ReceiveParams& params, + int bytes_read, + scoped_refptr<net::IOBuffer> io_buffer, + const std::string& address, + int port); + + // Post an extension event from IO to UI thread + static void PostEvent(const ReceiveParams& params, scoped_ptr<Event> event); + + // Dispatch an extension event on to EventRouter instance on UI thread. + static void DispatchEvent(void* browser_context_id, + const std::string& extension_id, + scoped_ptr<Event> event); + + // Usually IO thread (except for unit testing). + content::BrowserThread::ID thread_id_; + content::BrowserContext* const browser_context_; + scoped_refptr<SocketData> sockets_; +}; + +} // namespace core_api +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_ diff --git a/extensions/common/api/api.gyp b/extensions/common/api/api.gyp new file mode 100644 index 0000000..e6406c7 --- /dev/null +++ b/extensions/common/api/api.gyp @@ -0,0 +1,36 @@ +# Copyright 2014 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. + +{ + 'targets': [ + { + 'target_name': 'extensions_api', + 'type': 'static_library', + 'sources': [ + '<@(schema_files)', + ], + 'includes': [ + '../../../build/json_schema_bundle_compile.gypi', + '../../../build/json_schema_compile.gypi', + ], + 'variables': { + 'chromium_code': 1, + 'non_compiled_schema_files': [ + ], + 'schema_files': [ + 'socket.idl', + 'sockets_tcp.idl', + 'sockets_tcp_server.idl', + 'sockets_udp.idl', + ], + 'cc_dir': 'extensions/common/api', + 'root_namespace': 'extensions::core_api', + 'impl_dir': 'extensions/browser/api', + }, + 'dependencies': [ + '<(DEPTH)/skia/skia.gyp:skia', + ], + }, + ], +} diff --git a/extensions/common/api/socket.idl b/extensions/common/api/socket.idl new file mode 100644 index 0000000..805996d --- /dev/null +++ b/extensions/common/api/socket.idl @@ -0,0 +1,339 @@ +// Copyright 2014 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 <code>chrome.socket</code> API to send and receive data over the +// network using TCP and UDP connections. <b>Note:</b> Starting with Chrome 33, +// this API is deprecated in favor of the $ref:sockets.udp, $ref:sockets.tcp and +// $ref:sockets.tcpServer APIs. +namespace socket { + enum SocketType { + tcp, + udp + }; + + // The socket options. + dictionary CreateOptions { + }; + + dictionary CreateInfo { + // The id of the newly created socket. + long socketId; + }; + + callback CreateCallback = void (CreateInfo createInfo); + + callback ConnectCallback = void (long result); + + callback BindCallback = void (long result); + + callback ListenCallback = void (long result); + + dictionary AcceptInfo { + long resultCode; + // The id of the accepted socket. + long? socketId; + }; + + callback AcceptCallback = void (AcceptInfo acceptInfo); + + dictionary ReadInfo { + // The resultCode returned from the underlying read() call. + long resultCode; + + ArrayBuffer data; + }; + + callback ReadCallback = void (ReadInfo readInfo); + + dictionary WriteInfo { + // The number of bytes sent, or a negative error code. + long bytesWritten; + }; + + callback WriteCallback = void (WriteInfo writeInfo); + + dictionary RecvFromInfo { + // The resultCode returned from the underlying recvfrom() call. + long resultCode; + + ArrayBuffer data; + + // The address of the remote machine. + DOMString address; + + long port; + }; + + dictionary SocketInfo { + // The type of the passed socket. This will be <code>tcp</code> or + // <code>udp</code>. + SocketType socketType; + + // Whether or not the underlying socket is connected. + // + // For <code>tcp</code> sockets, this will remain true even if the remote + // peer has disconnected. Reading or writing to the socket may then result + // in an error, hinting that this socket should be disconnected via + // <code>disconnect()</code>. + // + // For <code>udp</code> sockets, this just represents whether a default + // remote address has been specified for reading and writing packets. + boolean connected; + + // If the underlying socket is connected, contains the IPv4/6 address of + // the peer. + DOMString? peerAddress; + + // If the underlying socket is connected, contains the port of the + // connected peer. + long? peerPort; + + // If the underlying socket is bound or connected, contains its local + // IPv4/6 address. + DOMString? localAddress; + + // If the underlying socket is bound or connected, contains its local port. + long? localPort; + }; + + dictionary NetworkInterface { + // The underlying name of the adapter. On *nix, this will typically be + // "eth0", "lo", etc. + DOMString name; + + // The available IPv4/6 address. + DOMString address; + + // The prefix length + long prefixLength; + }; + + callback RecvFromCallback = void (RecvFromInfo recvFromInfo); + + callback SendToCallback = void (WriteInfo writeInfo); + + callback SetKeepAliveCallback = void (boolean result); + + callback SetNoDelayCallback = void (boolean result); + + callback GetInfoCallback = void (SocketInfo result); + + callback GetNetworkCallback = void (NetworkInterface[] result); + + callback JoinGroupCallback = void (long result); + + callback LeaveGroupCallback = void (long result); + + callback SetMulticastTimeToLiveCallback = void (long result); + + callback SetMulticastLoopbackModeCallback = void (long result); + + callback GetJoinedGroupsCallback = void (DOMString[] groups); + + interface Functions { + // Creates a socket of the specified type that will connect to the specified + // remote machine. + // |type| : The type of socket to create. Must be <code>tcp</code> or + // <code>udp</code>. + // |options| : The socket options. + // |callback| : Called when the socket has been created. + static void create(SocketType type, + optional CreateOptions options, + CreateCallback callback); + + // Destroys the socket. Each socket created should be destroyed after use. + // |socketId| : The socketId. + static void destroy(long socketId); + + // Connects the socket to the remote machine (for a <code>tcp</code> + // socket). For a <code>udp</code> socket, this sets the default address + // which packets are sent to and read from for <code>read()</code> + // and <code>write()</code> calls. + // |socketId| : The socketId. + // |hostname| : The hostname or IP address of the remote machine. + // |port| : The port of the remote machine. + // |callback| : Called when the connection attempt is complete. + static void connect(long socketId, + DOMString hostname, + long port, + ConnectCallback callback); + + // Binds the local address for socket. Currently, it does not support + // TCP socket. + // |socketId| : The socketId. + // |address| : The address of the local machine. + // |port| : The port of the local machine. + // |callback| : Called when the bind attempt is complete. + static void bind(long socketId, + DOMString address, + long port, + BindCallback callback); + + // Disconnects the socket. For UDP sockets, <code>disconnect</code> is a + // non-operation but is safe to call. + // |socketId| : The socketId. + static void disconnect(long socketId); + + // Reads data from the given connected socket. + // |socketId| : The socketId. + // |bufferSize| : The read buffer size. + // |callback| : Delivers data that was available to be read without + // blocking. + static void read(long socketId, + optional long bufferSize, + ReadCallback callback); + + // Writes data on the given connected socket. + // |socketId| : The socketId. + // |data| : The data to write. + // |callback| : Called when the write operation completes without blocking + // or an error occurs. + static void write(long socketId, + ArrayBuffer data, + WriteCallback callback); + + // Receives data from the given UDP socket. + // |socketId| : The socketId. + // |bufferSize| : The receive buffer size. + // |callback| : Returns result of the recvFrom operation. + static void recvFrom(long socketId, + optional long bufferSize, + RecvFromCallback callback); + + // Sends data on the given UDP socket to the given address and port. + // |socketId| : The socketId. + // |data| : The data to write. + // |address| : The address of the remote machine. + // |port| : The port of the remote machine. + // |callback| : Called when the send operation completes without blocking + // or an error occurs. + static void sendTo(long socketId, + ArrayBuffer data, + DOMString address, + long port, + SendToCallback callback); + + // This method applies to TCP sockets only. + // Listens for connections on the specified port and address. This + // effectively makes this a server socket, and client socket + // functions (connect, read, write) can no longer be used on this socket. + // |socketId| : The socketId. + // |address| : The address of the local machine. + // |port| : The port of the local machine. + // |backlog| : Length of the socket's listen queue. + // |callback| : Called when listen operation completes. + static void listen(long socketId, + DOMString address, + long port, + optional long backlog, + ListenCallback callback); + + // This method applies to TCP sockets only. + // Registers a callback function to be called when a connection is + // accepted on this listening server socket. Listen must be called first. + // If there is already an active accept callback, this callback will be + // invoked immediately with an error as the resultCode. + // |socketId| : The socketId. + // |callback| : The callback is invoked when a new socket is accepted. + static void accept(long socketId, + AcceptCallback callback); + + // Enables or disables the keep-alive functionality for a TCP connection. + // |socketId| : The socketId. + // |enable| : If true, enable keep-alive functionality. + // |delay| : Set the delay seconds between the last data packet received + // and the first keepalive probe. Default is 0. + // |callback| : Called when the setKeepAlive attempt is complete. + static void setKeepAlive(long socketId, + boolean enable, + optional long delay, + SetKeepAliveCallback callback); + + // Sets or clears <code>TCP_NODELAY</code> for a TCP connection. Nagle's + // algorithm will be disabled when <code>TCP_NODELAY</code> is set. + // |socketId| : The socketId. + // |noDelay| : If true, disables Nagle's algorithm. + // |callback| : Called when the setNoDelay attempt is complete. + static void setNoDelay(long socketId, + boolean noDelay, + SetNoDelayCallback callback); + + // Retrieves the state of the given socket. + // |socketId| : The socketId. + // |callback| : Called when the state is available. + static void getInfo(long socketId, + GetInfoCallback callback); + + // Retrieves information about local adapters on this system. + // |callback| : Called when local adapter information is available. + static void getNetworkList(GetNetworkCallback callback); + + // Join the multicast group and start to receive packets from that group. + // The socket must be of UDP type and must be bound to a local port + // before calling this method. + // |socketId| : The socketId. + // |address| : The group address to join. Domain names are not supported. + // |callback| : Called when the join group operation is done with an + // integer parameter indicating the platform-independent error code. + static void joinGroup(long socketId, + DOMString address, + JoinGroupCallback callback); + + // Leave the multicast group previously joined using <code>joinGroup</code>. + // It's not necessary to leave the multicast group before destroying the + // socket or exiting. This is automatically called by the OS. + // + // Leaving the group will prevent the router from sending multicast + // datagrams to the local host, presuming no other process on the host is + // still joined to the group. + // + // |socketId| : The socketId. + // |address| : The group address to leave. Domain names are not supported. + // |callback| : Called when the leave group operation is done with an + // integer parameter indicating the platform-independent error code. + static void leaveGroup(long socketId, DOMString address, + LeaveGroupCallback callback); + + // Set the time-to-live of multicast packets sent to the multicast group. + // + // Calling this method does not require multicast permissions. + // + // |socketId| : The socketId. + // |ttl| : The time-to-live value. + // |callback| : Called when the configuration operation is done. + static void setMulticastTimeToLive( + long socketId, + long ttl, + SetMulticastTimeToLiveCallback callback); + + // Set whether multicast packets sent from the host to the multicast + // group will be looped back to the host. + // + // Note: the behavior of <code>setMulticastLoopbackMode</code> is slightly + // different between Windows and Unix-like systems. The inconsistency + // happens only when there is more than one application on the same host + // joined to the same multicast group while having different settings on + // multicast loopback mode. On Windows, the applications with loopback off + // will not RECEIVE the loopback packets; while on Unix-like systems, the + // applications with loopback off will not SEND the loopback packets to + // other applications on the same host. See MSDN: http://goo.gl/6vqbj + // + // Calling this method does not require multicast permissions. + // + // |socketId| : The socketId. + // |enabled| : Indicate whether to enable loopback mode. + // |callback| : Called when the configuration operation is done. + static void setMulticastLoopbackMode( + long socketId, + boolean enabled, + SetMulticastLoopbackModeCallback callback); + + // Get the multicast group addresses the socket is currently joined to. + // |socketId| : The socketId. + // |callback| : Called with an array of strings of the result. + static void getJoinedGroups(long socketId, + GetJoinedGroupsCallback callback); + }; + +}; diff --git a/extensions/common/api/sockets_tcp.idl b/extensions/common/api/sockets_tcp.idl new file mode 100644 index 0000000..c1cff1e --- /dev/null +++ b/extensions/common/api/sockets_tcp.idl @@ -0,0 +1,252 @@ +// Copyright 2014 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 <code>chrome.sockets.tcp</code> API to send and receive data over the +// network using TCP connections. This API supersedes the TCP functionality +// previously found in the <code>chrome.socket</code> API. +namespace sockets.tcp { + // The socket properties specified in the <code>create</code> or + // <code>update</code> function. Each property is optional. If a property + // value is not specified, a default value is used when calling + // <code>create</code>, or the existing value if preserved when calling + // <code>update</code>. + dictionary SocketProperties { + // Flag indicating if the socket is left open when the event page of + // the application is unloaded (see + // <a href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App + // Lifecycle</a>). The default value is "false." When the application is + // loaded, any sockets previously opened with persistent=true can be fetched + // with <code>getSockets</code>. + boolean? persistent; + + // An application-defined string associated with the socket. + DOMString? name; + + // The size of the buffer used to receive data. The default value is 4096. + long? bufferSize; + }; + + // Result of <code>create</code> call. + dictionary CreateInfo { + // The ID of the newly created socket. Note that socket IDs created from + // this API are not compatible with socket IDs created from other APIs, such + // as the deprecated <code>$ref:socket</code> API. + long socketId; + }; + + // Callback from the <code>create</code> method. + // |createInfo| : The result of the socket creation. + callback CreateCallback = void (CreateInfo createInfo); + + // Callback from the <code>connect</code> method. + // |result| : The result code returned from the underlying network call. + // A negative value indicates an error. + callback ConnectCallback = void (long result); + + // Callback from the <code>disconnect</code> method. + callback DisconnectCallback = void (); + + // Result of the <code>send</code> method. + dictionary SendInfo { + // The result code returned from the underlying network call. + // A negative value indicates an error. + long resultCode; + + // The number of bytes sent (if result == 0) + long? bytesSent; + }; + + // Callback from the <code>send</code> method. + // |sendInfo| : Result of the <code>send</code> method. + callback SendCallback = void (SendInfo sendInfo); + + // Callback from the <code>close</code> method. + callback CloseCallback = void (); + + // Callback from the <code>update</code> method. + callback UpdateCallback = void (); + + // Callback from the <code>setPaused</code> method. + callback SetPausedCallback = void (); + + // Callback from the <code>setKeepAliveCallback</code> method. + // |result| : The result code returned from the underlying network call. + // A negative value indicates an error. + callback SetKeepAliveCallback = void (long result); + + // Callback from the <code>setNodeDelay</code> method. + // |result| : The result code returned from the underlying network call. + // A negative value indicates an error. + callback SetNoDelayCallback = void (long result); + + // Result of the <code>getInfo</code> method. + dictionary SocketInfo { + // The socket identifier. + long socketId; + + // Flag indicating whether the socket is left open when the application is + // suspended (see <code>SocketProperties.persistent</code>). + boolean persistent; + + // Application-defined string associated with the socket. + DOMString? name; + + // The size of the buffer used to receive data. If no buffer size has been + // specified explictly, the value is not provided. + long? bufferSize; + + // Flag indicating whether a connected socket blocks its peer from sending + // more data (see <code>setPaused</code>). + boolean paused; + + // Flag indicating whether the socket is connected to a remote peer. + boolean connected; + + // If the underlying socket is connected, contains its local IPv4/6 address. + DOMString? localAddress; + + // If the underlying socket is connected, contains its local port. + long? localPort; + + // If the underlying socket is connected, contains the peer/ IPv4/6 address. + DOMString? peerAddress; + + // If the underlying socket is connected, contains the peer port. + long? peerPort; + }; + + // Callback from the <code>getInfo</code> method. + // |socketInfo| : Object containing the socket information. + callback GetInfoCallback = void (SocketInfo socketInfo); + + // Callback from the <code>getSockets</code> method. + // |socketInfos| : Array of object containing socket information. + callback GetSocketsCallback = void (SocketInfo[] socketInfos); + + // Data from an <code>onReceive</code> event. + dictionary ReceiveInfo { + // The socket identifier. + long socketId; + + // The data received, with a maxium size of <code>bufferSize</code>. + ArrayBuffer data; + }; + + // Data from an <code>onReceiveError</code> event. + dictionary ReceiveErrorInfo { + // The socket identifier. + long socketId; + + // The result code returned from the underlying network call. + long resultCode; + }; + + interface Functions { + // Creates a TCP 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 the application from receiving messages from its + // peer. The default value is "false". Pausing a socket is typically used + // by an application to throttle data sent by its peer. When a socket is + // paused, no <code>onReceive</code> event is raised. When a socket is + // connected and un-paused, <code>onReceive</code> events are raised again + // when messages are received. + static void setPaused(long socketId, + boolean paused, + optional SetPausedCallback callback); + + // Enables or disables the keep-alive functionality for a TCP connection. + // |socketId| : The socket identifier. + // |enable| : If true, enable keep-alive functionality. + // |delay| : Set the delay seconds between the last data packet received + // and the first keepalive probe. Default is 0. + // |callback| : Called when the setKeepAlive attempt is complete. + static void setKeepAlive(long socketId, + boolean enable, + optional long delay, + SetKeepAliveCallback callback); + + // Sets or clears <code>TCP_NODELAY</code> for a TCP connection. Nagle's + // algorithm will be disabled when <code>TCP_NODELAY</code> is set. + // |socketId| : The socket identifier. + // |noDelay| : If true, disables Nagle's algorithm. + // |callback| : Called when the setNoDelay attempt is complete. + static void setNoDelay(long socketId, + boolean noDelay, + SetNoDelayCallback callback); + + // Connects the socket to a remote machine. When the <code>connect</code> + // operation completes successfully, <code>onReceive</code> events are + // raised when data is received from the peer. If a network error occurs + // while the runtime is receiving packets, a <code>onReceiveError</code> + // event is raised, at which point no more <code>onReceive</code> event will + // be raised for this socket until the <code>resume</code> method is called. + // |socketId| : The socket identifier. + // |peerAddress| : The address of the remote machine. DNS name, IPv4 and + // IPv6 formats are supported. + // |peerPort| : The port of the remote machine. + // |callback| : Called when the connect attempt is complete. + static void connect(long socketId, + DOMString peerAddress, + long peerPort, + ConnectCallback callback); + + // Disconnects the socket. + // |socketId| : The socket identifier. + // |callback| : Called when the disconnect attempt is complete. + static void disconnect(long socketId, + optional DisconnectCallback callback); + + // Sends data on the given TCP socket. + // |socketId| : The socket identifier. + // |data| : The data to send. + // |callback| : Called when the <code>send</code> operation completes. + static void send(long socketId, + ArrayBuffer data, + SendCallback callback); + + // Closes the socket and releases the address/port the socket is bound to. + // Each socket created should be closed after use. The socket id is no + // 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 <code>close</code> 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 data has been received for a given socket. + // |info| : The event data. + static void onReceive(ReceiveInfo info); + + // Event raised when a network error occured while the runtime was waiting + // for data on the socket address and port. Once this event is raised, the + // socket is set to <code>paused</code> and no more <code>onReceive</code> + // events are raised for this socket. + // |info| : The event data. + static void onReceiveError(ReceiveErrorInfo info); + }; +}; diff --git a/extensions/common/api/sockets_tcp_server.idl b/extensions/common/api/sockets_tcp_server.idl new file mode 100644 index 0000000..ab69b9c --- /dev/null +++ b/extensions/common/api/sockets_tcp_server.idl @@ -0,0 +1,193 @@ +// Copyright 2014 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 <code>chrome.sockets.tcpServer</code> API to create server +// applications using TCP connections. This API supersedes the TCP functionality +// previously found in the <code>chrome.socket</code> API. +namespace sockets.tcpServer { + // The socket properties specified in the <code>create</code> or + // <code>update</code> function. Each property is optional. If a property + // value is not specified, a default value is used when calling + // <code>create</code>, or the existing value if preserved when calling + // <code>update</code>. + dictionary SocketProperties { + // Flag indicating if the socket remains open when the event page of the + // application is unloaded (see + // <a href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App + // Lifecycle</a>). The default value is "false." When the application is + // loaded, any sockets previously opened with persistent=true can be fetched + // with <code>getSockets</code>. + boolean? persistent; + + // An application-defined string associated with the socket. + DOMString? name; + }; + + // Result of <code>create</code> call. + dictionary CreateInfo { + // The ID of the newly created server socket. Note that socket IDs created + // from this API are not compatible with socket IDs created from other APIs, + // such as the deprecated <code>$ref:socket</code> API. + long socketId; + }; + + // Callback from the <code>create</code> method. + // |createInfo| : The result of the socket creation. + callback CreateCallback = void (CreateInfo createInfo); + + // Callback from the <code>listen</code> 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 <code>disconnect</code> method. + callback DisconnectCallback = void (); + + // Callback from the <code>close</code> method. + callback CloseCallback = void (); + + // Callback from the <code>update</code> method. + callback UpdateCallback = void (); + + // Callback from the <code>setPaused</code> method. + callback SetPausedCallback = void (); + + // Result of the <code>getInfo</code> 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 <code>SocketProperties.persistent</code>). + // 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 <code>onAccept</code> event or queued up in the + // listen queue backlog. + // See <code>setPaused</code>. 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 <code>getInfo</code> method. + // |socketInfo| : Object containing the socket information. + callback GetInfoCallback = void (SocketInfo socketInfo); + + // Callback from the <code>getSockets</code> method. + // |socketInfos| : Array of object containing socket information. + callback GetSocketsCallback = void (SocketInfo[] socketInfos); + + // Data from an <code>onAccept</code> 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 <code>chrome.sockets.tcp</code> namespace. Note the + // client socket is initially paused and must be explictly un-paused by the + // application to start receiving data. + long clientSocketId; + }; + + // Data from an <code>onAcceptError</code> 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 <code>listen</code> function) is full then refuses additional + // connection requests. <code>onAccept</code> 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 <code>listen</code> 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 <code>close</code> 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 <code>paused</code> and no more + // <code>onAccept</code> events are raised for this socket until the socket + // is resumed. + // |info| : The event data. + static void onAcceptError(AcceptErrorInfo info); + }; +}; diff --git a/extensions/common/api/sockets_udp.idl b/extensions/common/api/sockets_udp.idl new file mode 100644 index 0000000..61abc88 --- /dev/null +++ b/extensions/common/api/sockets_udp.idl @@ -0,0 +1,308 @@ +// Copyright 2014 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 <code>chrome.sockets.udp</code> API to send and receive data over the +// network using UDP connections. This API supersedes the UDP functionality +// previously found in the "socket" API. +namespace sockets.udp { + // The socket properties specified in the <code>create</code> or + // <code>update</code> function. Each property is optional. If a property + // value is not specified, a default value is used when calling + // <code>create</code>, or the existing value if preserved when calling + // <code>update</code>. + dictionary SocketProperties { + // Flag indicating if the socket is left open when the event page of the + // application is unloaded (see + // <a href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App + // Lifecycle</a>). The default value is "false." When the application is + // loaded, any sockets previously opened with persistent=true can be fetched + // with <code>getSockets</code>. + boolean? persistent; + + // An application-defined string associated with the socket. + DOMString? name; + + // The size of the buffer used to receive data. If the buffer is too small + // to receive the UDP packet, data is lost. The default value is 4096. + long? bufferSize; + }; + + // Result of <code>create</code> call. + dictionary CreateInfo { + // The ID of the newly created socket. Note that socket IDs created from + // this API are not compatible with socket IDs created from other APIs, such + // as the deprecated <code>$ref:socket</code> API. + long socketId; + }; + + // Callback from the <code>create</code> method. + // |createInfo| : The result of the socket creation. + callback CreateCallback = void (CreateInfo createInfo); + + // Callback from the <code>bind</code> method. + // |result| : The result code returned from the underlying network call. + // A negative value indicates an error. + callback BindCallback = void (long result); + + // Result of the <code>send</code> method. + dictionary SendInfo { + // The result code returned from the underlying network call. + // A negative value indicates an error. + long resultCode; + + // The number of bytes sent (if result == 0) + long? bytesSent; + }; + + // Callback from the <code>send</code> method. + // |sendInfo| : Result of the <code>send</code> method. + callback SendCallback = void (SendInfo sendInfo); + + // Callback from the <code>close</code> method. + callback CloseCallback = void (); + + // Callback from the <code>update</code> method. + callback UpdateCallback = void (); + + // Callback from the <code>setPaused</code> method. + callback SetPausedCallback = void (); + + // Result of the <code>getInfo</code> method. + dictionary SocketInfo { + // The socket identifier. + long socketId; + + // Flag indicating whether the socket is left open when the application is + // suspended (see <code>SocketProperties.persistent</code>). + boolean persistent; + + // Application-defined string associated with the socket. + DOMString? name; + + // The size of the buffer used to receive data. If no buffer size has been + // specified explictly, the value is not provided. + long? bufferSize; + + // Flag indicating whether the socket is blocked from firing onReceive + // events. + boolean paused; + + // If the underlying socket is bound, contains its local + // IPv4/6 address. + DOMString? localAddress; + + // If the underlying socket is bound, contains its local port. + long? localPort; + }; + + // Callback from the <code>getInfo</code> method. + // |socketInfo| : Object containing the socket information. + callback GetInfoCallback = void (SocketInfo socketInfo); + + // Callback from the <code>getSockets</code> method. + // |socketInfos| : Array of object containing socket information. + callback GetSocketsCallback = void (SocketInfo[] socketInfos); + + // Callback from the <code>joinGroup</code> method. + // |result| : The result code returned from the underlying network call. + // A negative value indicates an error. + callback JoinGroupCallback = void (long result); + + // Callback from the <code>leaveGroup</code> method. + // |result| : The result code returned from the underlying network call. + // A negative value indicates an error. + callback LeaveGroupCallback = void (long result); + + // Callback from the <code>setMulticastTimeToLive</code> method. + // |result| : The result code returned from the underlying network call. + // A negative value indicates an error. + callback SetMulticastTimeToLiveCallback = void (long result); + + // Callback from the <code>setMulticastLoopbackMode</code> method. + // |result| : The result code returned from the underlying network call. + // A negative value indicates an error. + callback SetMulticastLoopbackModeCallback = void (long result); + + // Callback from the <code>getJoinedGroupsCallback</code> method. + // |groups| : Array of groups the socket joined. + callback GetJoinedGroupsCallback = void (DOMString[] groups); + + // Data from an <code>onReceive</code> event. + dictionary ReceiveInfo { + // The socket ID. + long socketId; + + // The UDP packet content (truncated to the current buffer size). + ArrayBuffer data; + + // The address of the host the packet comes from. + DOMString remoteAddress; + + // The port of the host the packet comes from. + long remotePort; + }; + + // Data from an <code>onReceiveError</code> event. + dictionary ReceiveErrorInfo { + // The socket ID. + long socketId; + + // The result code returned from the underlying recvfrom() call. + long resultCode; + }; + + interface Functions { + // Creates a UDP socket with the given properties. + // |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 ID. + // |properties| : The properties to update. + // |callback| : Called when the properties are updated. + static void update(long socketId, + SocketProperties properties, + optional UpdateCallback callback); + + // Pauses or unpauses a socket. A paused socket is blocked from firing + // <code>onReceive</code> events. + // |connectionId| : The socket ID. + // |paused| : Flag to indicate whether to pause or unpause. + // |callback| : Called when the socket has been successfully paused or + // unpaused. + static void setPaused(long socketId, + boolean paused, + optional SetPausedCallback callback); + + // Binds the local address and port for the socket. For a client socket, it + // is recommended to use port 0 to let the platform pick a free port. + // + // Once the <code>bind</code> operation completes successfully, + // <code>onReceive</code> events are raised when UDP packets arrive on the + // address/port specified -- unless the socket is paused. + // + // |socketId| : The socket ID. + // |address| : The address of the local machine. DNS name, IPv4 and IPv6 + // formats are supported. Use "0.0.0.0" to accept packets from all local + // available network interfaces. + // |port| : The port of the local machine. Use "0" to bind to a free port. + // |callback| : Called when the <code>bind</code> operation completes. + static void bind(long socketId, + DOMString address, + long port, + BindCallback callback); + + // Sends data on the given socket to the given address and port. The socket + // must be bound to a local port before calling this method. + // |socketId| : The socket ID. + // |data| : The data to send. + // |address| : The address of the remote machine. + // |port| : The port of the remote machine. + // |callback| : Called when the <code>send</code> operation completes. + static void send(long socketId, + ArrayBuffer data, + DOMString address, + long port, + SendCallback callback); + + // Closes the socket and releases the address/port the socket is bound to. + // 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 ID. + // |callback| : Called when the <code>close</code> operation completes. + static void close(long socketId, + optional CloseCallback callback); + + // Retrieves the state of the given socket. + // |socketId| : The socket ID. + // |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); + + // Joins the multicast group and starts to receive packets from that group. + // The socket must be bound to a local port before calling this method. + // |socketId| : The socket ID. + // |address| : The group address to join. Domain names are not supported. + // |callback| : Called when the <code>joinGroup</code> operation completes. + static void joinGroup(long socketId, + DOMString address, + JoinGroupCallback callback); + + // Leaves the multicast group previously joined using + // <code>joinGroup</code>. This is only necessary to call if you plan to + // keep using the socketafterwards, since it will be done automatically by + // the OS when the socket is closed. + // + // Leaving the group will prevent the router from sending multicast + // datagrams to the local host, presuming no other process on the host is + // still joined to the group. + // + // |socketId| : The socket ID. + // |address| : The group address to leave. Domain names are not supported. + // |callback| : Called when the <code>leaveGroup</code> operation completes. + static void leaveGroup(long socketId, + DOMString address, + LeaveGroupCallback callback); + + // Sets the time-to-live of multicast packets sent to the multicast group. + // + // Calling this method does not require multicast permissions. + // + // |socketId| : The socket ID. + // |ttl| : The time-to-live value. + // |callback| : Called when the configuration operation completes. + static void setMulticastTimeToLive( + long socketId, + long ttl, + SetMulticastTimeToLiveCallback callback); + + // Sets whether multicast packets sent from the host to the multicast group + // will be looped back to the host. + // + // Note: the behavior of <code>setMulticastLoopbackMode</code> is slightly + // different between Windows and Unix-like systems. The inconsistency + // happens only when there is more than one application on the same host + // joined to the same multicast group while having different settings on + // multicast loopback mode. On Windows, the applications with loopback off + // will not RECEIVE the loopback packets; while on Unix-like systems, the + // applications with loopback off will not SEND the loopback packets to + // other applications on the same host. See MSDN: http://goo.gl/6vqbj + // + // Calling this method does not require multicast permissions. + // + // |socketId| : The socket ID. + // |enabled| : Indicate whether to enable loopback mode. + // |callback| : Called when the configuration operation completes. + static void setMulticastLoopbackMode( + long socketId, + boolean enabled, + SetMulticastLoopbackModeCallback callback); + + // Gets the multicast group addresses the socket is currently joined to. + // |socketId| : The socket ID. + // |callback| : Called with an array of strings of the result. + static void getJoinedGroups(long socketId, + GetJoinedGroupsCallback callback); + }; + + interface Events { + // Event raised when a UDP packet has been received for the given socket. + // |info| : The event data. + static void onReceive(ReceiveInfo info); + + // Event raised when a network error occured while the runtime was waiting + // for data on the socket address and port. Once this event is raised, the + // socket is paused and no more <code>onReceive</code> events will be raised + // for this socket until the socket is resumed. + // |info| : The event data. + static void onReceiveError(ReceiveErrorInfo info); + }; +}; diff --git a/extensions/common/extension_api.cc b/extensions/common/extension_api.cc index 8e27f2b..b418734 100644 --- a/extensions/common/extension_api.cc +++ b/extensions/common/extension_api.cc @@ -16,8 +16,8 @@ #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/values.h" -#include "chrome/common/extensions/api/generated_schemas.h" #include "extensions/common/extension.h" +#include "extensions/common/extensions_client.h" #include "extensions/common/features/feature.h" #include "extensions/common/features/feature_provider.h" #include "extensions/common/permissions/permission_set.h" @@ -29,8 +29,6 @@ namespace extensions { -using api::GeneratedSchemas; - namespace { const char* kChildKinds[] = { @@ -200,7 +198,9 @@ void ExtensionAPI::LoadSchema(const std::string& name, const base::StringPiece& schema) { scoped_ptr<base::ListValue> schema_list(LoadSchemaList(name, schema)); std::string schema_namespace; - + extensions::ExtensionsClient* extensions_client = + extensions::ExtensionsClient::Get(); + DCHECK(extensions_client); while (!schema_list->empty()) { base::DictionaryValue* schema = NULL; { @@ -212,7 +212,7 @@ void ExtensionAPI::LoadSchema(const std::string& name, CHECK(schema->GetString("namespace", &schema_namespace)); PrefixWithNamespace(schema_namespace, schema); schemas_[schema_namespace] = make_linked_ptr(schema); - if (!GeneratedSchemas::IsGenerated(schema_namespace)) + if (!extensions_client->IsAPISchemaGenerated(schema_namespace)) CHECK_EQ(1u, unloaded_schemas_.erase(schema_namespace)); } } @@ -344,14 +344,17 @@ const base::DictionaryValue* ExtensionAPI::GetSchema( result = maybe_schema->second.get(); } else { // Might not have loaded yet; or might just not exist. - UnloadedSchemaMap::iterator maybe_schema_resource = + UnloadedSchemaMap::iterator maybe_schema_resource = unloaded_schemas_.find(api_name); + extensions::ExtensionsClient* extensions_client = + extensions::ExtensionsClient::Get(); + DCHECK(extensions_client); if (maybe_schema_resource != unloaded_schemas_.end()) { LoadSchema(maybe_schema_resource->first, ReadFromResource(maybe_schema_resource->second)); } else if (default_configuration_initialized_ && - GeneratedSchemas::IsGenerated(api_name)) { - LoadSchema(api_name, GeneratedSchemas::Get(api_name)); + extensions_client->IsAPISchemaGenerated(api_name)) { + LoadSchema(api_name, extensions_client->GetAPISchema(api_name)); } else { return NULL; } @@ -390,9 +393,12 @@ Feature* ExtensionAPI::GetFeatureDependency(const std::string& full_name) { std::string ExtensionAPI::GetAPINameFromFullName(const std::string& full_name, std::string* child_name) { std::string api_name_candidate = full_name; + extensions::ExtensionsClient* extensions_client = + extensions::ExtensionsClient::Get(); + DCHECK(extensions_client); while (true) { if (schemas_.find(api_name_candidate) != schemas_.end() || - GeneratedSchemas::IsGenerated(api_name_candidate) || + extensions_client->IsAPISchemaGenerated(api_name_candidate) || unloaded_schemas_.find(api_name_candidate) != unloaded_schemas_.end()) { std::string result = api_name_candidate; diff --git a/extensions/common/extensions_client.h b/extensions/common/extensions_client.h index d309fd7..843f985 100644 --- a/extensions/common/extensions_client.h +++ b/extensions/common/extensions_client.h @@ -9,6 +9,8 @@ #include <string> #include <vector> +#include "base/strings/string_piece.h" + class GURL; namespace extensions { @@ -71,6 +73,12 @@ class ExtensionsClient { // Returns false if content scripts are forbidden from running on |url|. virtual bool IsScriptableURL(const GURL& url, std::string* error) const = 0; + // Returns true iff a schema named |name| is generated. + virtual bool IsAPISchemaGenerated(const std::string& name) const = 0; + + // Gets the API schema named |name|. + virtual base::StringPiece GetAPISchema(const std::string& name) const = 0; + // Return the extensions client. static ExtensionsClient* Get(); diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index a5992ea..0bf3e27 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -11,6 +11,7 @@ 'target_name': 'extensions_common', 'type': 'static_library', 'dependencies': [ + 'common/api/api.gyp:extensions_api', '../third_party/re2/re2.gyp:re2', # TODO(benwells): figure out what to do with the api target and # api resources compiled into the chrome resource bundle. @@ -18,7 +19,7 @@ '../chrome/chrome_resources.gyp:chrome_resources', # TODO(jamescook|derat): Pull strings into extensions module. '../chrome/chrome_resources.gyp:chrome_strings', - '../chrome/common/extensions/api/api.gyp:api', + '../chrome/common/extensions/api/api.gyp:chrome_api', '../components/components.gyp:url_matcher', '../content/content.gyp:content_common', ], @@ -152,7 +153,7 @@ 'extensions_common', # TODO(jamescook|derat): Pull strings into extensions module. '../chrome/chrome_resources.gyp:chrome_strings', - '../chrome/common/extensions/api/api.gyp:api', + '../chrome/common/extensions/api/api.gyp:chrome_api', '../content/content.gyp:content_browser', '../skia/skia.gyp:skia', '../third_party/leveldatabase/leveldatabase.gyp:leveldatabase', @@ -175,6 +176,26 @@ 'browser/api/async_api_function.h', 'browser/api/extensions_api_client.cc', 'browser/api/extensions_api_client.h', + 'browser/api/socket/socket.cc', + 'browser/api/socket/socket.h', + 'browser/api/socket/socket_api.cc', + 'browser/api/socket/socket_api.h', + 'browser/api/socket/tcp_socket.cc', + 'browser/api/socket/tcp_socket.h', + 'browser/api/socket/udp_socket.cc', + 'browser/api/socket/udp_socket.h', + 'browser/api/sockets_tcp/sockets_tcp_api.cc', + 'browser/api/sockets_tcp/sockets_tcp_api.h', + 'browser/api/sockets_tcp/tcp_socket_event_dispatcher.cc', + 'browser/api/sockets_tcp/tcp_socket_event_dispatcher.h', + 'browser/api/sockets_tcp_server/sockets_tcp_server_api.cc', + 'browser/api/sockets_tcp_server/sockets_tcp_server_api.h', + 'browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc', + 'browser/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h', + 'browser/api/sockets_udp/sockets_udp_api.cc', + 'browser/api/sockets_udp/sockets_udp_api.h', + 'browser/api/sockets_udp/udp_socket_event_dispatcher.cc', + 'browser/api/sockets_udp/udp_socket_event_dispatcher.h', 'browser/api/storage/leveldb_settings_storage_factory.cc', 'browser/api/storage/leveldb_settings_storage_factory.h', 'browser/api/storage/local_storage_backend.cc', |