diff options
28 files changed, 2092 insertions, 20 deletions
diff --git a/chrome/browser/extensions/api/api_resource.cc b/chrome/browser/extensions/api/api_resource.cc index 8339367..ccbdd77 100644 --- a/chrome/browser/extensions/api/api_resource.cc +++ b/chrome/browser/extensions/api/api_resource.cc @@ -15,4 +15,8 @@ ApiResource::ApiResource(const std::string& owner_extension_id) ApiResource::~ApiResource() { } +bool ApiResource::persistent() const { + return true; // backward-compatible behavior. +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/api_resource.h b/chrome/browser/extensions/api/api_resource.h index 4a21083..2cfdffe 100644 --- a/chrome/browser/extensions/api/api_resource.h +++ b/chrome/browser/extensions/api/api_resource.h @@ -23,6 +23,8 @@ class ApiResource { return owner_extension_id_; } + virtual bool persistent() const; + static const content::BrowserThread::ID kThreadId = content::BrowserThread::IO; diff --git a/chrome/browser/extensions/api/api_resource_manager.h b/chrome/browser/extensions/api/api_resource_manager.h index d357ebe..a7c1624 100644 --- a/chrome/browser/extensions/api/api_resource_manager.h +++ b/chrome/browser/extensions/api/api_resource_manager.h @@ -7,11 +7,13 @@ #include <map> +#include "base/containers/hash_tables.h" #include "base/lazy_instance.h" #include "base/memory/linked_ptr.h" #include "base/threading/non_thread_safe.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/api/profile_keyed_api_factory.h" +#include "chrome/browser/extensions/extension_host.h" #include "chrome/common/extensions/extension.h" #include "components/browser_context_keyed_service/browser_context_keyed_service.h" #include "content/public/browser/browser_thread.h" @@ -69,6 +71,10 @@ class ApiResourceManager : public ProfileKeyedAPI, this, chrome::NOTIFICATION_EXTENSION_UNLOADED, content::NotificationService::AllSources()); + registrar_.Add( + this, + chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, + content::NotificationService::AllSources()); } // For Testing. @@ -113,6 +119,10 @@ class ApiResourceManager : public ProfileKeyedAPI, return data_->Get(extension_id, api_resource_id); } + base::hash_set<int>* GetResourceIds(const std::string& extension_id) { + return data_->GetResourceIds(extension_id); + } + protected: // content::NotificationObserver: virtual void Observe(int type, @@ -123,7 +133,12 @@ class ApiResourceManager : public ProfileKeyedAPI, std::string id = content::Details<extensions::UnloadedExtensionInfo>(details)-> extension->id(); - data_->InitiateCleanup(id); + data_->InitiateExtensionUnloadedCleanup(id); + break; + } + case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: { + ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); + data_->InitiateExtensionSuspendedCleanup(host->extension_id()); break; } } @@ -185,9 +200,20 @@ class ApiResourceManager : public ProfileKeyedAPI, return GetOwnedResource(extension_id, api_resource_id); } - void InitiateCleanup(const std::string& extension_id) { + base::hash_set<int>* GetResourceIds(const std::string& extension_id) { + DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); + return GetOwnedResourceIds(extension_id); + } + + void InitiateExtensionUnloadedCleanup(const std::string& extension_id) { + content::BrowserThread::PostTask(thread_id_, FROM_HERE, + base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension, + base::Unretained(this), extension_id)); + } + + void InitiateExtensionSuspendedCleanup(const std::string& extension_id) { content::BrowserThread::PostTask(thread_id_, FROM_HERE, - base::Bind(&ApiResourceData::CleanupResourcesFromExtension, + base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension, base::Unretained(this), extension_id)); } @@ -201,16 +227,59 @@ class ApiResourceManager : public ProfileKeyedAPI, return NULL; } - void CleanupResourcesFromExtension(const std::string& extension_id) { + base::hash_set<int>* GetOwnedResourceIds(const std::string& extension_id) { DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); - if (extension_resource_map_.find(extension_id) != + if (extension_resource_map_.find(extension_id) == + extension_resource_map_.end()) + return NULL; + + return &extension_resource_map_[extension_id]; + } + + void CleanupResourcesFromUnloadedExtension( + const std::string& extension_id) { + CleanupResourcesFromExtension(extension_id, true); + } + + void CleanupResourcesFromSuspendedExtension( + const std::string& extension_id) { + CleanupResourcesFromExtension(extension_id, false); + } + + void CleanupResourcesFromExtension(const std::string& extension_id, + bool remove_all) { + DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); + + if (extension_resource_map_.find(extension_id) == extension_resource_map_.end()) { - base::hash_set<int>& resource_ids = - extension_resource_map_[extension_id]; - for (base::hash_set<int>::iterator it = resource_ids.begin(); - it != resource_ids.end(); ++it) { + return; + } + + // Remove all resources, or the non persistent ones only if |remove_all| + // is false. + base::hash_set<int>& resource_ids = + extension_resource_map_[extension_id]; + for (base::hash_set<int>::iterator it = resource_ids.begin(); + it != resource_ids.end(); ) { + bool erase = false; + if (remove_all) { + erase = true; + } else { + linked_ptr<T> ptr = api_resource_map_[*it]; + T* resource = ptr.get(); + erase = (resource && !resource->persistent()); + } + + if (erase) { api_resource_map_.erase(*it); + resource_ids.erase(it++); + } else { + ++it; } + } // end for + + // Remove extension entry if we removed all its resources. + if (resource_ids.size() == 0) { extension_resource_map_.erase(extension_id); } } diff --git a/chrome/browser/extensions/api/socket/socket_api.cc b/chrome/browser/extensions/api/socket/socket_api.cc index 83f1e4e..8c1a55b 100644 --- a/chrome/browser/extensions/api/socket/socket_api.cc +++ b/chrome/browser/extensions/api/socket/socket_api.cc @@ -47,30 +47,39 @@ const char kMulticastSocketTypeError[] = const char kWildcardAddress[] = "*"; const int kWildcardPort = 0; -SocketAsyncApiFunction::SocketAsyncApiFunction() - : manager_(NULL) { +SocketAsyncApiFunction::SocketAsyncApiFunction() { } SocketAsyncApiFunction::~SocketAsyncApiFunction() { } bool SocketAsyncApiFunction::PrePrepare() { - manager_ = ApiResourceManager<Socket>::Get(profile()); - 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; + manager_ = CreateSocketResourceManager(); + return manager_->SetProfile(profile()); } 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); } @@ -152,7 +161,7 @@ void SocketCreateFunction::Work() { DCHECK(socket); base::DictionaryValue* result = new base::DictionaryValue(); - result->SetInteger(kSocketIdKey, manager_->Add(socket)); + result->SetInteger(kSocketIdKey, AddSocket(socket)); SetResult(result); } @@ -352,7 +361,7 @@ void SocketAcceptFunction::OnAccept(int result_code, result->SetInteger(kResultCodeKey, result_code); if (socket) { Socket *client_socket = new TCPSocket(socket, extension_id(), true); - result->SetInteger(kSocketIdKey, manager_->Add(client_socket)); + result->SetInteger(kSocketIdKey, AddSocket(client_socket)); } SetResult(result); diff --git a/chrome/browser/extensions/api/socket/socket_api.h b/chrome/browser/extensions/api/socket/socket_api.h index 4e68c58..396ddc6 100644 --- a/chrome/browser/extensions/api/socket/socket_api.h +++ b/chrome/browser/extensions/api/socket/socket_api.h @@ -27,6 +27,66 @@ 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 SetProfile(Profile* profile) = 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 SetProfile(Profile* profile) OVERRIDE { + manager_ = ApiResourceManager<T>::Get(profile); + 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(); @@ -38,10 +98,16 @@ class SocketAsyncApiFunction : public 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(); - ApiResourceManager<Socket>* manager_; + private: + scoped_ptr<SocketResourceManagerInterface> manager_; }; class SocketExtensionWithDnsLookupFunction : public SocketAsyncApiFunction { diff --git a/chrome/browser/extensions/api/socket/udp_socket.cc b/chrome/browser/extensions/api/socket/udp_socket.cc index 4a77aea..7d36ffc 100644 --- a/chrome/browser/extensions/api/socket/udp_socket.cc +++ b/chrome/browser/extensions/api/socket/udp_socket.cc @@ -14,6 +14,17 @@ namespace extensions { +static base::LazyInstance<ProfileKeyedAPIFactory< + ApiResourceManager<ResumableUDPSocket> > > + g_factory = LAZY_INSTANCE_INITIALIZER; + +// static +template <> +ProfileKeyedAPIFactory<ApiResourceManager<ResumableUDPSocket> >* +ApiResourceManager<ResumableUDPSocket>::GetFactoryInstance() { + return &g_factory.Get(); +} + UDPSocket::UDPSocket(const std::string& owner_extension_id) : Socket(owner_extension_id), socket_(net::DatagramSocket::DEFAULT_BIND, @@ -278,4 +289,34 @@ 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) { +} + +const std::string& ResumableUDPSocket::name() const { + return name_; +} + +void ResumableUDPSocket::set_name(const std::string& name) { + name_ = name; +} + +bool ResumableUDPSocket::persistent() const { + return persistent_; +} + +void ResumableUDPSocket::set_persistent(bool persistent) { + persistent_ = persistent; +} + +int ResumableUDPSocket::buffer_size() const { + return buffer_size_; +} + +void ResumableUDPSocket::set_buffer_size(int buffer_size) { + buffer_size_ = buffer_size; +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/socket/udp_socket.h b/chrome/browser/extensions/api/socket/udp_socket.h index b301cb2..4989e27 100644 --- a/chrome/browser/extensions/api/socket/udp_socket.h +++ b/chrome/browser/extensions/api/socket/udp_socket.h @@ -74,6 +74,33 @@ class UDPSocket : public Socket { 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); + + const std::string& name() const; + void set_name(const std::string& name); + + virtual bool persistent() const OVERRIDE; + void set_persistent(bool persistent); + + int buffer_size() const; + void set_buffer_size(int buffer_size); + + private: + friend class ApiResourceManager<ResumableUDPSocket>; + static const char* service_name() { + return "ResumableUDPSocketManager"; + } + + std::string name_; + bool persistent_; + int buffer_size_; +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_SOCKET_UDP_SOCKET_H_ diff --git a/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.cc b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.cc new file mode 100644 index 0000000..c7a6e81 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.cc @@ -0,0 +1,471 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h" + +#include "chrome/browser/extensions/api/socket/udp_socket.h" +#include "chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h" +#include "chrome/common/extensions/permissions/permissions_data.h" +#include "chrome/common/extensions/permissions/socket_permission.h" +#include "content/public/common/socket_permission_request.h" +#include "net/base/net_errors.h" + +namespace extensions { +namespace 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())); + } + + // 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(); +} + +SocketsUdpBindFunction::SocketsUdpBindFunction() {} + +SocketsUdpBindFunction::~SocketsUdpBindFunction() {} + +bool SocketsUdpBindFunction::Prepare() { + params_ = sockets_udp::Bind::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params_.get()); + return true; +} + +void SocketsUdpBindFunction::Work() { + ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id); + if (!socket) { + error_ = kSocketNotFoundError; + return; + } + + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_BIND, params_->address, params_->port); + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), + APIPermission::kSocket, + ¶m)) { + error_ = kPermissionError; + return; + } + + int net_result = socket->Bind(params_->address, params_->port); + if (net_result == net::OK) { + UDPSocketEventDispatcher::Get(profile())->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; + } + + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_SEND_TO, + params_->address, + params_->port); + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), + APIPermission::kSocket, + ¶m)) { + 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_written) { + CHECK(net_result <= net::OK) << "Network status code must be < 0"; + + sockets_udp::SendInfo send_info; + send_info.result = net_result; + if (net_result == net::OK) { + send_info.bytes_written.reset(new int(bytes_written)); + } + + 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; + } + + 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; + } + + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, + kWildcardAddress, + kWildcardPort); + + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), APIPermission::kSocket, ¶m)) { + 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_ = 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; + } + + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, + kWildcardAddress, + kWildcardPort); + if (!PermissionsData::CheckAPIPermissionWithParam(GetExtension(), + APIPermission::kSocket, + ¶m)) { + 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_ = 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_ = 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_ = 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; + } + + SocketPermission::CheckParam param( + SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, + kWildcardAddress, + kWildcardPort); + if (!PermissionsData::CheckAPIPermissionWithParam( + GetExtension(), + APIPermission::kSocket, + ¶m)) { + error_ = kPermissionError; + return; + } + + const std::vector<std::string>& groups = socket->GetJoinedGroups(); + results_ = sockets_udp::GetJoinedGroups::Results::Create(groups); +} + +} // namespace api +} // namespace extensions diff --git a/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h new file mode 100644 index 0000000..b971c4f --- /dev/null +++ b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h @@ -0,0 +1,258 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_SOCKETS_UDP_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_SOCKETS_UDP_API_H_ + +#include "chrome/browser/extensions/api/socket/socket_api.h" +#include "chrome/common/extensions/api/sockets_udp.h" + +namespace extensions { +class ResumableUDPSocket; +} + +namespace extensions { +namespace api { + +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 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_; +}; + +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_written); + + // 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 api +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_SOCKETS_UDP_API_H_ diff --git a/chrome/browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc new file mode 100644 index 0000000..08d3c83 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc @@ -0,0 +1,79 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/values.h" +#include "chrome/browser/browser_process_impl.h" +#include "chrome/browser/extensions/api/api_function.h" +#include "chrome/browser/extensions/api/api_resource_manager.h" +#include "chrome/browser/extensions/api/socket/socket.h" +#include "chrome/browser/extensions/api/socket/udp_socket.h" +#include "chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h" +#include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/extensions/test_extension_system.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/test/base/browser_with_test_window_test.h" +#include "chrome/test/base/testing_browser_process.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace utils = extension_function_test_utils; + +namespace extensions { +namespace api { + +BrowserContextKeyedService* ApiResourceManagerTestFactory( + content::BrowserContext* profile) { + content::BrowserThread::ID id; + CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id)); + return ApiResourceManager<ResumableUDPSocket>:: + CreateApiResourceManagerForTest(static_cast<Profile*>(profile), id); +} + +class SocketsUdpUnitTest : public BrowserWithTestWindowTest { + public: + virtual void SetUp() { + BrowserWithTestWindowTest::SetUp(); + + ApiResourceManager<ResumableUDPSocket>::GetFactoryInstance()-> + SetTestingFactoryAndUse(browser()->profile(), + ApiResourceManagerTestFactory); + + extension_ = utils::CreateEmptyExtensionWithLocation( + extensions::Manifest::UNPACKED); + } + + base::Value* RunFunctionWithExtension( + UIThreadExtensionFunction* function, const std::string& args) { + scoped_refptr<UIThreadExtensionFunction> delete_function(function); + function->set_extension(extension_.get()); + return utils::RunFunctionAndReturnSingleResult(function, args, browser()); + } + + base::DictionaryValue* RunFunctionAndReturnDict( + UIThreadExtensionFunction* function, const std::string& args) { + base::Value* result = RunFunctionWithExtension(function, args); + return result ? utils::ToDictionary(result) : NULL; + } + + protected: + scoped_refptr<extensions::Extension> extension_; +}; + +TEST_F(SocketsUdpUnitTest, Create) { + // Get BrowserThread + content::BrowserThread::ID id; + CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id)); + + // Create SocketCreateFunction and put it on BrowserThread + SocketsUdpCreateFunction *function = new SocketsUdpCreateFunction(); + function->set_work_thread_id(id); + + // Run tests + scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDict( + function, "[{\"resumable\": true, \"name\": \"foo\"}]")); + ASSERT_TRUE(result.get()); +} + +} // namespace api +} // namespace extensions diff --git a/chrome/browser/extensions/api/sockets_udp/sockets_udp_apitest.cc b/chrome/browser/extensions/api/sockets_udp/sockets_udp_apitest.cc new file mode 100644 index 0000000..943bbf1 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_udp/sockets_udp_apitest.cc @@ -0,0 +1,125 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted.h" +#include "base/path_service.h" +#include "base/strings/stringprintf.h" +#include "chrome/browser/extensions/api/dns/host_resolver_wrapper.h" +#include "chrome/browser/extensions/api/dns/mock_host_resolver_creator.h" +#include "chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_test_message_listener.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/extensions/application_launch.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/spawned_test_server/spawned_test_server.h" + +using extensions::Extension; + +namespace utils = extension_function_test_utils; + +namespace { + +// TODO(jschuh): Hanging plugin tests. crbug.com/244653 +#if defined(OS_WIN) && defined(ARCH_CPU_X86_64) +#define MAYBE(x) DISABLED_##x +#else +#define MAYBE(x) x +#endif + +const std::string kHostname = "127.0.0.1"; +const int kPort = 8888; + +class SocketsUdpApiTest : public ExtensionApiTest { + public: + SocketsUdpApiTest() : resolver_event_(true, false), + resolver_creator_( + new extensions::MockHostResolverCreator()) { + } + + virtual void SetUpOnMainThread() OVERRIDE { + extensions::HostResolverWrapper::GetInstance()->SetHostResolverForTesting( + resolver_creator_->CreateMockHostResolver()); + } + + virtual void CleanUpOnMainThread() OVERRIDE { + extensions::HostResolverWrapper::GetInstance()-> + SetHostResolverForTesting(NULL); + resolver_creator_->DeleteMockHostResolver(); + } + + private: + base::WaitableEvent resolver_event_; + + // The MockHostResolver asserts that it's used on the same thread on which + // it's created, which is actually a stronger rule than its real counterpart. + // But that's fine; it's good practice. + scoped_refptr<extensions::MockHostResolverCreator> resolver_creator_; +}; + +} // namespace + +IN_PROC_BROWSER_TEST_F(SocketsUdpApiTest, SocketsUdpCreateGood) { + scoped_refptr<extensions::api::SocketsUdpCreateFunction> + socket_create_function(new extensions::api::SocketsUdpCreateFunction()); + scoped_refptr<Extension> empty_extension(utils::CreateEmptyExtension()); + + socket_create_function->set_extension(empty_extension.get()); + socket_create_function->set_has_callback(true); + + scoped_ptr<base::Value> result(utils::RunFunctionAndReturnSingleResult( + socket_create_function.get(), "[]", browser(), utils::NONE)); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, result->GetType()); + base::DictionaryValue *value = + static_cast<base::DictionaryValue*>(result.get()); + int socketId = -1; + EXPECT_TRUE(value->GetInteger("socketId", &socketId)); + EXPECT_TRUE(socketId > 0); +} + +IN_PROC_BROWSER_TEST_F(SocketsUdpApiTest, SocketsUdpExtension) { + scoped_ptr<net::SpawnedTestServer> test_server( + new net::SpawnedTestServer( + net::SpawnedTestServer::TYPE_UDP_ECHO, + net::SpawnedTestServer::kLocalhost, + base::FilePath(FILE_PATH_LITERAL("net/data")))); + EXPECT_TRUE(test_server->Start()); + + net::HostPortPair host_port_pair = test_server->host_port_pair(); + int port = host_port_pair.port(); + ASSERT_TRUE(port > 0); + + // Test that sendTo() is properly resolving hostnames. + host_port_pair.set_host("LOCALhost"); + + ResultCatcher catcher; + catcher.RestrictToProfile(browser()->profile()); + + ExtensionTestMessageListener listener("info_please", true); + + ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("sockets_udp/api"))); + EXPECT_TRUE(listener.WaitUntilSatisfied()); + listener.Reply( + base::StringPrintf("udp:%s:%d", host_port_pair.host().c_str(), port)); + + EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); +} + +IN_PROC_BROWSER_TEST_F(SocketsUdpApiTest, SocketsUdpMulticast) { + ResultCatcher catcher; + catcher.RestrictToProfile(browser()->profile()); + ExtensionTestMessageListener listener("info_please", true); + ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("sockets_udp/api"))); + EXPECT_TRUE(listener.WaitUntilSatisfied()); + listener.Reply( + base::StringPrintf("multicast:%s:%d", kHostname.c_str(), kPort)); + + EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); +} diff --git a/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc b/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc new file mode 100644 index 0000000..2331417 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc @@ -0,0 +1,125 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h" + +#include "chrome/browser/extensions/api/socket/udp_socket.h" +#include "chrome/browser/extensions/event_router.h" +#include "chrome/browser/extensions/extension_system.h" +#include "net/base/net_errors.h" + +namespace extensions { +namespace api { + +static base::LazyInstance<ProfileKeyedAPIFactory<UDPSocketEventDispatcher> > +g_factory = LAZY_INSTANCE_INITIALIZER; + +// static +ProfileKeyedAPIFactory<UDPSocketEventDispatcher>* + UDPSocketEventDispatcher::GetFactoryInstance() { + return &g_factory.Get(); +} + +// static +UDPSocketEventDispatcher* UDPSocketEventDispatcher::Get(Profile* profile) { + return ProfileKeyedAPIFactory<UDPSocketEventDispatcher>::GetForProfile( + profile); +} + +UDPSocketEventDispatcher::UDPSocketEventDispatcher(Profile* profile) + : thread_id_(Socket::kThreadId), + profile_(profile) { +} + +UDPSocketEventDispatcher::~UDPSocketEventDispatcher() { +} + +ResumableUDPSocket* UDPSocketEventDispatcher::GetUdpSocket( + const std::string& extension_id, + int socket_id) { + DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); + + ApiResourceManager<ResumableUDPSocket>* manager = + ApiResourceManager<ResumableUDPSocket>::Get(profile_); + 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>."; + + return manager->Get(extension_id, socket_id); +} + +void UDPSocketEventDispatcher::OnSocketBind(const std::string& extension_id, + int socket_id) { + DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); + StartReceive(extension_id, socket_id); +} + +void UDPSocketEventDispatcher::StartReceive(const std::string& extension_id, + int socket_id) { + DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); + ResumableUDPSocket* socket = GetUdpSocket(extension_id, socket_id); + if (socket == NULL) { + // This can happen if the socket is closed while our callback is active. + return; + } + DCHECK(extension_id == socket->owner_extension_id()) + << "Socket has wrong owner."; + + int buffer_size = (socket->buffer_size() <= 0 ? 4096 : socket->buffer_size()); + socket->RecvFrom(buffer_size, + base::Bind(&UDPSocketEventDispatcher::ReceiveCallback, + AsWeakPtr(), + extension_id, + socket_id)); +} + +void UDPSocketEventDispatcher::ReceiveCallback( + const std::string& extension_id, + const int socket_id, + int bytes_read, + scoped_refptr<net::IOBuffer> io_buffer, + const std::string& address, + int port) { + DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); + + // Note: if "bytes_read" < 0, there was a network error, and "bytes_read" is + // a value from "net::ERR_". + + if (bytes_read >= 0) { + // Dispatch event. + sockets_udp::ReceiveInfo receive_info; + receive_info.socket_id = 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())); + ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension( + extension_id, 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. + content::BrowserThread::PostTask(thread_id_, FROM_HERE, + base::Bind(&UDPSocketEventDispatcher::StartReceive, + AsWeakPtr(), extension_id, socket_id)); + } else { + // Dispatch event but don't start another read to avoid infinite read if + // we have a persistent network error. + sockets_udp::ReceiveErrorInfo receive_error_info; + receive_error_info.socket_id = socket_id; + receive_error_info.result = 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())); + ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension( + extension_id, event.Pass()); + } +} + +} // namespace api +} // namespace extensions diff --git a/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h b/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h new file mode 100644 index 0000000..c5672a9 --- /dev/null +++ b/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h @@ -0,0 +1,66 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_ +#define CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_ + +#include "chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h" + +namespace extensions { +class ResumableUDPSocket; +} + +namespace extensions { +namespace api { + +// Dispatch events related to "sockets.udp" sockets from callback on native +// socket instances. There is one instance per profile. +class UDPSocketEventDispatcher + : public ProfileKeyedAPI, + public base::SupportsWeakPtr<UDPSocketEventDispatcher> { + public: + explicit UDPSocketEventDispatcher(Profile* profile); + virtual ~UDPSocketEventDispatcher(); + + // Socket is active, start receving from it. + void OnSocketBind(const std::string& extension_id, int socket_id); + + // ProfileKeyedAPI implementation. + static ProfileKeyedAPIFactory<UDPSocketEventDispatcher>* GetFactoryInstance(); + + // Convenience method to get the SocketEventDispatcher for a profile. + static UDPSocketEventDispatcher* Get(Profile* profile); + + private: + friend class ProfileKeyedAPIFactory<UDPSocketEventDispatcher>; + + ResumableUDPSocket* GetUdpSocket(const std::string& extension_id, + int socket_id); + + // Start a receive and register a callback. + void StartReceive(const std::string& extension_id, int socket_id); + + // Called when socket receive data. + void ReceiveCallback(const std::string& extension_id, + const int socket_id, + int bytes_read, + scoped_refptr<net::IOBuffer> io_buffer, + const std::string& address, + int port); + + // ProfileKeyedAPI implementation. + static const char* service_name() { + return "UDPSocketEventDispatcher"; + } + static const bool kServiceHasOwnInstanceInIncognito = true; + + // Usually IO thread (except for unit testing). + content::BrowserThread::ID thread_id_; + Profile* const profile_; +}; + +} // namespace api +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_ diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h index f6b688d..b6565db 100644 --- a/chrome/browser/extensions/extension_function_histogram_value.h +++ b/chrome/browser/extensions/extension_function_histogram_value.h @@ -596,6 +596,18 @@ enum HistogramValue { PROCESSES_GETPROCESSINFO, PROCESSES_GETPROCESSIDFORTAB, PROCESSES_TERMINATE, + SOCKETS_UDP_CREATE, + SOCKETS_UDP_UPDATE, + SOCKETS_UDP_BIND, + SOCKETS_UDP_SEND, + SOCKETS_UDP_CLOSE, + SOCKETS_UDP_GETINFO, + SOCKETS_UDP_GETSOCKETS, + SOCKETS_UDP_JOINGROUP, + SOCKETS_UDP_LEAVEGROUP, + SOCKETS_UDP_SETMULTICASTTIMETOLIVE, + SOCKETS_UDP_SETMULTICASTLOOPBACKMODE, + SOCKETS_UDP_GETJOINEDGROUPS, ENUM_BOUNDARY // Last entry: Add new entries above. }; diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc index af227d1..90115a6 100644 --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc @@ -46,6 +46,8 @@ #include "chrome/browser/extensions/api/serial/serial_connection.h" #include "chrome/browser/extensions/api/session_restore/session_restore_api.h" #include "chrome/browser/extensions/api/socket/socket.h" +#include "chrome/browser/extensions/api/socket/udp_socket.h" +#include "chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h" #include "chrome/browser/extensions/api/streams_private/streams_private_api.h" #include "chrome/browser/extensions/api/system_info/system_info_api.h" #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry_factory.h" @@ -197,11 +199,14 @@ EnsureBrowserContextKeyedServiceFactoriesBuilt() { extensions::ActivityLogFactory::GetInstance(); extensions::ActivityLogAPI::GetFactoryInstance(); extensions::AlarmManager::GetFactoryInstance(); + extensions::ApiResourceManager<extensions::ResumableUDPSocket>:: + GetFactoryInstance(); extensions::ApiResourceManager<extensions::SerialConnection>:: GetFactoryInstance(); extensions::ApiResourceManager<extensions::Socket>::GetFactoryInstance(); extensions::ApiResourceManager<extensions::UsbDeviceResource>:: GetFactoryInstance(); + extensions::api::UDPSocketEventDispatcher::GetFactoryInstance(); extensions::AudioAPI::GetFactoryInstance(); extensions::BookmarksAPI::GetFactoryInstance(); extensions::BluetoothAPIFactory::GetInstance(); diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index c477498..82fb593 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -425,6 +425,10 @@ 'browser/extensions/api/socket/tcp_socket.h', 'browser/extensions/api/socket/udp_socket.cc', 'browser/extensions/api/socket/udp_socket.h', + 'browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h', + 'browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc', + 'browser/extensions/api/sockets_udp/sockets_udp_api.cc', + 'browser/extensions/api/sockets_udp/sockets_udp_api.h', 'browser/extensions/api/spellcheck/spellcheck_api.cc', 'browser/extensions/api/spellcheck/spellcheck_api.h', 'browser/extensions/api/storage/leveldb_settings_storage_factory.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index cfb0361..a4cc711 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1334,6 +1334,7 @@ 'browser/extensions/api/serial/serial_apitest.cc', 'browser/extensions/api/session_restore/session_restore_apitest.cc', 'browser/extensions/api/socket/socket_apitest.cc', + 'browser/extensions/api/sockets_udp/sockets_udp_apitest.cc', 'browser/extensions/api/storage/settings_apitest.cc', 'browser/extensions/api/streams_private/streams_private_apitest.cc', 'browser/extensions/api/sync_file_system/sync_file_system_apitest.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 6d7f590..89acb7f 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -822,6 +822,7 @@ 'browser/extensions/api/socket/socket_api_unittest.cc', 'browser/extensions/api/socket/tcp_socket_unittest.cc', 'browser/extensions/api/socket/udp_socket_unittest.cc', + 'browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc', 'browser/extensions/api/storage/policy_value_store_unittest.cc', 'browser/extensions/api/storage/settings_frontend_unittest.cc', 'browser/extensions/api/storage/settings_quota_unittest.cc', diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index e471b94..5487034 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json @@ -470,6 +470,11 @@ "dependencies": ["permission:socket"], "contexts": ["blessed_extension"] }, + "sockets.udp": { + "dependencies": ["permission:sockets.udp"], + "channel": "dev", + "contexts": ["blessed_extension"] + }, "storage": { "dependencies": ["permission:storage"], "contexts": ["blessed_extension", "unblessed_extension", "content_script"] diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json index 1dbc60d..4b7a319 100644 --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json @@ -562,6 +562,10 @@ "kodldpbjkkmmnilagfdheibampofhaom" ] }], + "sockets.udp": { + "channel": "dev", + "extension_types": ["platform_app"] + }, "syncFileSystem": { "channel": "stable", "extension_types": ["platform_app"] diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp index 5c84ab1..ddb4dd1 100644 --- a/chrome/common/extensions/api/api.gyp +++ b/chrome/common/extensions/api/api.gyp @@ -89,6 +89,7 @@ 'serial.idl', 'session_restore.json', 'socket.idl', + 'sockets_udp.idl', 'storage.json', 'sync_file_system.idl', 'system_indicator.idl', diff --git a/chrome/common/extensions/api/sockets_udp.idl b/chrome/common/extensions/api/sockets_udp.idl new file mode 100644 index 0000000..bfc432e --- /dev/null +++ b/chrome/common/extensions/api/sockets_udp.idl @@ -0,0 +1,287 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Use the <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. Note that the socket ids created from +// this namespace are not compatible with ids created in other namespaces. +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. + 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 result; + + // The number of bytes sent (if result == 0) + long? bytesWritten; + }; + + // 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 (); + + // 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; + + // 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 result; + }; + + interface Functions { + // Creates a UDP 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 ID. + // |properties| : The properties to update. + // |callback| : Called when the properties are updated. + static void update(long socketId, + SocketProperties properties, + UpdateCallback callback); + + // Binds the local address for socket. When the <code>bind</code> operation + // completes successfully, <code>onReceive</code> events are raised + // when UDP packets arrive on the address/port specified. If a network + // error occurs while the runtime is receiving packets, an + // <code>onReceiveError</code> event is raised, at which point no more + // <code>onReceive</code> events will be raised for this socket. + // |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 UDP socket to the given address and port. + // |socketId| : The socket ID. + // |data| : The data to write. + // |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 + // 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 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, no + // more <code>onReceive</code> events will be raise for this socket. + // |info| : The event data. + static void onReceiveError(ReceiveErrorInfo info); + }; +}; diff --git a/chrome/common/extensions/permissions/api_permission.h b/chrome/common/extensions/permissions/api_permission.h index 964c872..e03474f 100644 --- a/chrome/common/extensions/permissions/api_permission.h +++ b/chrome/common/extensions/permissions/api_permission.h @@ -117,6 +117,7 @@ class APIPermission { kSerial, kSessionRestore, kSocket, + kSocketsUdp, kStorage, kStreamsPrivate, kSyncFileSystem, diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc index 491fa22..7cee99f 100644 --- a/chrome/common/extensions/permissions/chrome_api_permissions.cc +++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc @@ -253,6 +253,7 @@ std::vector<APIPermissionInfo*> ChromeAPIPermissions::GetAllPermissions() { APIPermission::kSocket, "socket", APIPermissionInfo::kFlagCannotBeOptional, 0, PermissionMessage::kNone, &CreateAPIPermission<SocketPermission> }, + { APIPermission::kSocketsUdp, "sockets.udp" }, { APIPermission::kAppCurrentWindowInternal, "app.currentWindowInternal" }, { APIPermission::kAppRuntime, "app.runtime" }, { APIPermission::kAppWindow, "app.window" }, diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc index 637fb54..224a487 100644 --- a/chrome/common/extensions/permissions/permission_set_unittest.cc +++ b/chrome/common/extensions/permissions/permission_set_unittest.cc @@ -734,6 +734,7 @@ TEST(PermissionsTest, PermissionMessages) { skip.insert(APIPermission::kFileSystem); skip.insert(APIPermission::kFileSystemRetainEntries); skip.insert(APIPermission::kSocket); + skip.insert(APIPermission::kSocketsUdp); skip.insert(APIPermission::kUsbDevice); PermissionsInfo* info = PermissionsInfo::GetInstance(); diff --git a/chrome/test/data/extensions/api_test/sockets_udp/api/background.js b/chrome/test/data/extensions/api_test/sockets_udp/api/background.js new file mode 100644 index 0000000..23dd357 --- /dev/null +++ b/chrome/test/data/extensions/api_test/sockets_udp/api/background.js @@ -0,0 +1,168 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// net/tools/testserver/testserver.py is picky about the format of what it +// calls its "echo" messages. One might go so far as to mutter to oneself that +// it isn't an echo server at all. +// +// The response is based on the request but obfuscated using a random key. +const request = "0100000005320000005hello"; +var expectedResponsePattern = /0100000005320000005.{11}/; + +const socket = chrome.sockets.udp; +var address; +var bytesWritten = 0; +var dataAsString; +var dataRead = []; +var port = -1; +var socketId = 0; +var succeeded = false; +var waitCount = 0; + +// Many thanks to Dennis for his StackOverflow answer: http://goo.gl/UDanx +// Since amended to handle BlobBuilder deprecation. +function string2ArrayBuffer(string, callback) { + var blob = new Blob([string]); + var f = new FileReader(); + f.onload = function(e) { + callback(e.target.result); + }; + f.readAsArrayBuffer(blob); +} + +function arrayBuffer2String(buf, callback) { + var blob = new Blob([new Uint8Array(buf)]); + var f = new FileReader(); + f.onload = function(e) { + callback(e.target.result); + }; + f.readAsText(blob); +} + +/////////////////////////////////////////////////////////////////////////////// +// Test socket creation +// + +var testSocketCreation = function() { + function onCreate(createInfo) { + function onGetInfo(info) { + if (info.localAddress || info.localPort) { + chrome.test.fail('Unconnected socket should not have local binding'); + } + + chrome.test.assertEq(createInfo.socketId, info.socketId); + chrome.test.assertEq(false, info.persistent); + + socket.close(createInfo.socketId, function() { + socket.getInfo(createInfo.socketId, function(info) { + chrome.test.assertEq(undefined, info); + chrome.test.succeed(); + }); + }); + } + + chrome.test.assertTrue(createInfo.socketId > 0); + + // Obtaining socket information before a connect() call should be safe, but + // return empty values. + socket.getInfo(createInfo.socketId, onGetInfo); + } + + socket.create({}, onCreate); +}; + +/////////////////////////////////////////////////////////////////////////////// +// Test socket send/receive +// + +function waitForBlockingOperation() { + if (++waitCount < 10) { + setTimeout(waitForBlockingOperation, 1000); + } else { + // We weren't able to succeed in the given time. + chrome.test.fail("Operations didn't complete after " + waitCount + " " + + "seconds. Response so far was <" + dataAsString + ">."); + } +} + +var testSending = function() { + dataRead = ""; + succeeded = false; + waitCount = 0; + + setTimeout(waitForBlockingOperation, 1000); + socket.create({}, function (socketInfo) { + console.log("socket created"); + socketId = socketInfo.socketId; + chrome.test.assertTrue(socketId > 0, "failed to create socket"); + socket.bind(socketId, "0.0.0.0", 0, function (result) { + console.log("socket bound to local host"); + chrome.test.assertEq(0, result, + "Connect or bind failed with error " + result); + if (result == 0) { + socket.getInfo(socketId, function (result) { + console.log("got socket info"); + chrome.test.assertTrue( + !!result.localAddress, + "Bound socket should always have local address"); + chrome.test.assertTrue( + !!result.localPort, + "Bound socket should always have local port"); + + string2ArrayBuffer(request, function(arrayBuffer) { + socket.onReceiveError.addListener(function (info) { + chrome.test.fail("Socket receive error:" + info.result); + }); + socket.onReceive.addListener(function (info) { + console.log( + "received bytes from echo server: " + info.data.byteLength); + if (socketId == info.socketId) { + arrayBuffer2String(info.data, function(s) { + dataAsString = s; // save this for error reporting + var match = !!s.match(expectedResponsePattern); + chrome.test.assertTrue( + match, "Received data does not match."); + succeeded = true; + chrome.test.succeed(); + }); + } + }); + console.log( + "sending bytes to echo server: " + arrayBuffer.byteLength); + socket.send(socketId, arrayBuffer, address, port, + function(writeInfo) { + chrome.test.assertEq(0, writeInfo.result); + chrome.test.assertEq( + writeInfo.bytesWritten, arrayBuffer.byteLength); + }); + }); + }); + } + }); + }); +}; + +/////////////////////////////////////////////////////////////////////////////// +// Test driver +// + +var onMessageReply = function(message) { + var parts = message.split(":"); + var test_type = parts[0]; + address = parts[1]; + port = parseInt(parts[2]); + console.log("Running tests, echo server " + + address + ":" + port); + if (test_type == 'multicast') { + console.log("Running multicast tests"); + chrome.test.runTests([ testMulticast ]); + } else { + console.log("Running udp tests"); + chrome.test.runTests([ testSocketCreation, testSending ]); + } +}; + +// Find out which protocol we're supposed to test, and which echo server we +// should be using, then kick off the tests. +chrome.test.sendMessage("info_please", onMessageReply); diff --git a/chrome/test/data/extensions/api_test/sockets_udp/api/manifest.json b/chrome/test/data/extensions/api_test/sockets_udp/api/manifest.json new file mode 100644 index 0000000..a8f9b17 --- /dev/null +++ b/chrome/test/data/extensions/api_test/sockets_udp/api/manifest.json @@ -0,0 +1,15 @@ +{ + "manifest_version": 2, + "name": "chrome.sockets.udp", + "version": "0.1", + "description": "end-to-end browser test for chrome.sockets.udp API", + "app": { + "background": { + "scripts": ["multicast.js", "background.js"] + } + }, + "permissions": [ + { "socket": ["udp-send-to", "udp-bind", "udp-multicast-membership"] }, + "sockets.udp" + ] +} diff --git a/chrome/test/data/extensions/api_test/sockets_udp/api/multicast.js b/chrome/test/data/extensions/api_test/sockets_udp/api/multicast.js new file mode 100644 index 0000000..f16b2fb3 --- /dev/null +++ b/chrome/test/data/extensions/api_test/sockets_udp/api/multicast.js @@ -0,0 +1,224 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Tests for multicast UDP socket. +function testMulticast() { + function randomHexString(count) { + var result = ''; + for (var i = 0; i < count; i++) { + result += (Math.random() * 16 >> 0).toString(16); + } + return result; + } + + var kMulticastAddress = "237.132.100.133"; + var kTestMessageLength = 128; + var kTestMessage = randomHexString(128); + var kPort = 11103; + + function arrayBufferToString(arrayBuffer) { + // UTF-16LE + return String.fromCharCode.apply(String, new Uint16Array(arrayBuffer)); + } + + function stringToArrayBuffer(string) { + // UTF-16LE + var buf = new ArrayBuffer(string.length * 2); + var bufView = new Uint16Array(buf); + for (var i = 0, strLen = string.length; i < strLen; i++) { + bufView[i] = string.charCodeAt(i); + } + return buf; + } + + // Registers a listener on receiving data on |socketId|. + // Calls |callback| with a |cancelled| argument of "false" when receiving data + // of excactly |kTestMessageLength| characters. + // Calls |callback| with a |cancelled| argument of "true" when the caller + // decides to invoke the returned function. + // Returns a function that can be invoked to "cancel" the operation (i.e. + // call |callback| with a |cancelled| argument of "true"). + function waitForMessage(socketId, callback) { + var cancelled = false; + var relayCanceller = null; + socket.onReceive.addListener(function(info) { + console.log("Data received: " + + "socketId=" + info.socketId + + ", bytes=" + info.data.byteLength + + ", address=" + info.remoteAddress + + ", port=" + info.remotePort); + if (socketId != info.socketId) + return; + + if (cancelled) + return; + + if (info.data.byteLength == kTestMessageLength * 2 && + kTestMessage === arrayBufferToString(info.data)) { + callback(false); + } else { + // Restart waiting. + relayCanceller = waitForMessage(socketId, callback); + } + }); + return function canceller() { + if (relayCanceller) { + relayCanceller(); + } else { + cancelled = true; // prevents callback from being called on receive. + callback(true); + } + }; + } + + function testMulticastSettings(nextTest) { + console.log("*************** testMulticastSettings"); + socket.create({}, function (socketInfo) { + var socketId; + if (socketInfo) { + socketId = socketInfo.socketId; + socket.setMulticastTimeToLive(socketId, 0, function (result) { + chrome.test.assertEq(0, result, + "Error setting multicast time to live."); + socket.setMulticastTimeToLive(socketId, -3, function (result) { + chrome.test.assertEq(-4, result, + "Error setting multicast time to live."); + socket.setMulticastLoopbackMode(socketId, false, + function (result) { + chrome.test.assertEq(0, result, + "Error setting multicast loop back mode."); + socket.setMulticastLoopbackMode(socketId, true, + function (result) { + chrome.test.assertEq(0, result, + "Error setting multicast loop back mode."); + socket.close(socketId, function() {}); + nextTest(); + }); + }); + }); + }); + } else { + chrome.test.fail("Cannot create server udp socket"); + } + }); + } + + function testSendMessage(message, address) { + // Send the UDP message to the address with multicast ttl = 0. + socket.create({}, function (socketInfo) { + var clientSocketId = socketInfo.socketId; + chrome.test.assertTrue(clientSocketId > 0, + "Cannot create client udp socket."); + socket.setMulticastTimeToLive(clientSocketId, 0, function (result) { + chrome.test.assertEq(0, result, + "Cannot create client udp socket."); + socket.bind(clientSocketId, "0.0.0.0", 0, function (result) { + chrome.test.assertEq(0, result, + "Cannot bind to localhost."); + socket.send(clientSocketId, stringToArrayBuffer(kTestMessage), + address, kPort, function (result) { + console.log("Sent bytes to socket:" + + " socketId=" + clientSocketId + + ", bytes=" + result.bytesWritten + + ", address=" + address + + ", port=" + kPort); + chrome.test.assertTrue(result.bytesWritten >= 0, + "Send to failed. " + JSON.stringify(result)); + socket.close(clientSocketId, function() {}); + }); + }); + }); + }); + } + + function testRecvBeforeAddMembership(serverSocketId, nextTest) { + console.log("*************** testRecvBeforeAddMembership"); + var recvTimeout; + var canceller = waitForMessage(serverSocketId, function (cancelled) { + clearTimeout(recvTimeout); + if (cancelled) { + nextTest(); + } else { + chrome.test.fail("Received message before joining the group"); + } + }); + testSendMessage(kTestMessage, kMulticastAddress); // Meant to be lost. + recvTimeout = setTimeout(function () { + // This is expected to execute. + canceller(); + }, 2000); + } + + function testRecvWithMembership(serverSocketId, nextTest) { + console.log("*************** testRecvWithMembership"); + socket.joinGroup(serverSocketId, kMulticastAddress, function (result) { + chrome.test.assertEq(0, result, "Join group failed."); + var recvTimeout; + var canceller = waitForMessage(serverSocketId, function (cancelled) { + clearTimeout(recvTimeout); + if (!cancelled) { + nextTest(); + } else { + chrome.test.fail("Faild to receive message after joining the group"); + } + }); + testSendMessage(kTestMessage, kMulticastAddress); + recvTimeout = setTimeout(function () { + canceller(); + chrome.test.fail("Cannot receive from multicast group."); + }, 2000); + }); + } + + function testRecvWithoutMembership(serverSocketId, nextTest) { + console.log("*************** testRecvWithoutMembership"); + socket.leaveGroup(serverSocketId, kMulticastAddress, function (result) { + chrome.test.assertEq(0, result, "leave group failed."); + var recvTimeout; + var canceller = waitForMessage(serverSocketId, function (cancelled) { + clearTimeout(recvTimeout); + if (cancelled) { + nextTest(); + } else { + chrome.test.fail("Received message after leaving the group"); + } + }); + testSendMessage(request, kMulticastAddress); + recvTimeout = setTimeout(function () { + // This is expected to execute. + canceller(); + }, 2000); + }); + } + + function testMulticastRecv() { + console.log("*************** testMulticastRecv"); + socket.create({}, function (socketInfo) { + var serverSocketId = socketInfo.socketId; + socket.bind(serverSocketId, "0.0.0.0", kPort, function (result) { + chrome.test.assertEq(0, result, "Bind failed."); + // Test 1 + testRecvBeforeAddMembership(serverSocketId, function() { + // Test 2 + testRecvWithMembership(serverSocketId, function() { + // Test 3 + testRecvWithoutMembership(serverSocketId, function() { + // Success! + socket.close(serverSocketId, function() { + console.log("*************** SUCCESS! "); + chrome.test.succeed(); + }); + }); + }); + }); + }); + }); + } + + setTimeout(function() { + testMulticastSettings(function() { + testMulticastRecv(); + }) + }, 100); +} |