summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrpaquay@chromium.org <rpaquay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-16 02:05:26 +0000
committerrpaquay@chromium.org <rpaquay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-16 02:05:26 +0000
commit64a3996dc779333627f34e4e46be2a23b9539551 (patch)
tree8e1e0829056207efdea2c70258f33d2d92bc0e2e
parent6a078da35f5fcf238013a837ab33e4a16e00d0a8 (diff)
downloadchromium_src-64a3996dc779333627f34e4e46be2a23b9539551.zip
chromium_src-64a3996dc779333627f34e4e46be2a23b9539551.tar.gz
chromium_src-64a3996dc779333627f34e4e46be2a23b9539551.tar.bz2
Implement v2 API of udp socket.
(Note this issue is a replacement for issue #19260002 that will not accept additional patch sets due to a corruption bug: https://code.google.com/p/chromium/issues/detail?id=107101). One notable change vs socket v1 is the use of event handlers for receiving data instead of using callbacks with the "recvFrom" function. This allow server apps to go to "suspend" mode when inactive (see https://docs.google.com/document/d/1qGytoYz6K0xYnOR6oM2tpxC0ET8bbb8XdTFMq4K9jTU/edit?usp=sharing), as well as improve performance (#packets/sec) (see https://docs.google.com/spreadsheet/ccc?key=0Ar6WDZ-sS7b5dEp1ckJGQjZEVGlFN3A1U1BVQUdQb2c&usp=sharing). Also implement the "close_on_suspend" behavior wrt to lifetime: By default, sockets are closed when the packaged app process dies (unload or suspend). When "close_on_suspend" is 'false', sockets survive "suspend" events, and thus can be re-used across process re-activation. This is useful for background server type apps. BUG=165273 BUG=173241 Review URL: https://chromiumcodereview.appspot.com/22650003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@217912 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/api/api_resource.cc4
-rw-r--r--chrome/browser/extensions/api/api_resource.h2
-rw-r--r--chrome/browser/extensions/api/api_resource_manager.h87
-rw-r--r--chrome/browser/extensions/api/socket/socket_api.cc29
-rw-r--r--chrome/browser/extensions/api/socket/socket_api.h68
-rw-r--r--chrome/browser/extensions/api/socket/udp_socket.cc41
-rw-r--r--chrome/browser/extensions/api/socket/udp_socket.h27
-rw-r--r--chrome/browser/extensions/api/sockets_udp/sockets_udp_api.cc471
-rw-r--r--chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h258
-rw-r--r--chrome/browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc79
-rw-r--r--chrome/browser/extensions/api/sockets_udp/sockets_udp_apitest.cc125
-rw-r--r--chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc125
-rw-r--r--chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h66
-rw-r--r--chrome/browser/extensions/extension_function_histogram_value.h12
-rw-r--r--chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc5
-rw-r--r--chrome/chrome_browser_extensions.gypi4
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/extensions/api/_api_features.json5
-rw-r--r--chrome/common/extensions/api/_permission_features.json4
-rw-r--r--chrome/common/extensions/api/api.gyp1
-rw-r--r--chrome/common/extensions/api/sockets_udp.idl287
-rw-r--r--chrome/common/extensions/permissions/api_permission.h1
-rw-r--r--chrome/common/extensions/permissions/chrome_api_permissions.cc1
-rw-r--r--chrome/common/extensions/permissions/permission_set_unittest.cc1
-rw-r--r--chrome/test/data/extensions/api_test/sockets_udp/api/background.js168
-rw-r--r--chrome/test/data/extensions/api_test/sockets_udp/api/manifest.json15
-rw-r--r--chrome/test/data/extensions/api_test/sockets_udp/api/multicast.js224
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, &params_.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,
+ &param)) {
+ 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,
+ &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_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, &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_ = 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,
+ &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_ = 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,
+ &param)) {
+ 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);
+}