import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,96 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <limits>
#include "mozilla/Hal.h"
#include "mozilla/dom/network/Connection.h"
#include "nsIDOMClassInfo.h"
#include "mozilla/Preferences.h"
#include "Constants.h"
/**
* We have to use macros here because our leak analysis tool things we are
* leaking strings when we have |static const nsString|. Sad :(
*/
#define CHANGE_EVENT_NAME NS_LITERAL_STRING("typechange")
namespace mozilla {
namespace dom {
namespace network {
NS_IMPL_QUERY_INTERFACE_INHERITED(Connection, DOMEventTargetHelper,
nsINetworkProperties)
// Don't use |Connection| alone, since that confuses nsTraceRefcnt since
// we're not the only class with that name.
NS_IMPL_ADDREF_INHERITED(dom::network::Connection, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(dom::network::Connection, DOMEventTargetHelper)
Connection::Connection(nsPIDOMWindowInner* aWindow)
: DOMEventTargetHelper(aWindow)
, mType(static_cast<ConnectionType>(kDefaultType))
, mIsWifi(kDefaultIsWifi)
, mDHCPGateway(kDefaultDHCPGateway)
{
hal::RegisterNetworkObserver(this);
hal::NetworkInformation networkInfo;
hal::GetCurrentNetworkInformation(&networkInfo);
UpdateFromNetworkInfo(networkInfo);
}
void
Connection::Shutdown()
{
hal::UnregisterNetworkObserver(this);
}
NS_IMETHODIMP
Connection::GetIsWifi(bool *aIsWifi)
{
*aIsWifi = mIsWifi;
return NS_OK;
}
NS_IMETHODIMP
Connection::GetDhcpGateway(uint32_t *aGW)
{
*aGW = mDHCPGateway;
return NS_OK;
}
void
Connection::UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo)
{
mType = static_cast<ConnectionType>(aNetworkInfo.type());
mIsWifi = aNetworkInfo.isWifi();
mDHCPGateway = aNetworkInfo.dhcpGateway();
}
void
Connection::Notify(const hal::NetworkInformation& aNetworkInfo)
{
ConnectionType previousType = mType;
UpdateFromNetworkInfo(aNetworkInfo);
if (previousType == mType) {
return;
}
DispatchTrustedEvent(CHANGE_EVENT_NAME);
}
JSObject*
Connection::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return NetworkInformationBinding::Wrap(aCx, this, aGivenProto);
}
} // namespace network
} // namespace dom
} // namespace mozilla

80
dom/network/Connection.h Normal file
View file

@ -0,0 +1,80 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_network_Connection_h
#define mozilla_dom_network_Connection_h
#include "Types.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/Observer.h"
#include "mozilla/dom/NetworkInformationBinding.h"
#include "nsCycleCollectionParticipant.h"
#include "nsINetworkProperties.h"
namespace mozilla {
namespace hal {
class NetworkInformation;
} // namespace hal
namespace dom {
namespace network {
class Connection final : public DOMEventTargetHelper
, public NetworkObserver
, public nsINetworkProperties
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSINETWORKPROPERTIES
NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
explicit Connection(nsPIDOMWindowInner* aWindow);
void Shutdown();
// For IObserver
void Notify(const hal::NetworkInformation& aNetworkInfo) override;
// WebIDL
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
ConnectionType Type() const { return mType; }
IMPL_EVENT_HANDLER(typechange)
private:
~Connection() {}
/**
* Update the connection information stored in the object using a
* NetworkInformation object.
*/
void UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo);
/**
* The type of current connection.
*/
ConnectionType mType;
/**
* If the connection is WIFI
*/
bool mIsWifi;
/**
* DHCP Gateway information for IPV4, in network byte order. 0 if unassigned.
*/
uint32_t mDHCPGateway;
};
} // namespace network
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_network_Connection_h

25
dom/network/Constants.h Normal file
View file

@ -0,0 +1,25 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_network_Constants_h__
#define mozilla_dom_network_Constants_h__
/**
* A set of constants to be used by network backends.
*/
namespace mozilla {
namespace dom {
namespace network {
static const uint32_t kDefaultType = 5; // ConnectionType::None
static const bool kDefaultIsWifi = false;
static const uint32_t kDefaultDHCPGateway = 0;
} // namespace network
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_network_Constants_h__

View file

@ -0,0 +1,655 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const TOPIC_INTERFACE_STATE_CHANGED = "network-interface-state-changed";
const ETHERNET_NETWORK_IFACE_PREFIX = "eth";
const DEFAULT_ETHERNET_NETWORK_IFACE = "eth0";
const INTERFACE_IPADDR_NULL = "0.0.0.0";
const INTERFACE_GATEWAY_NULL = "0.0.0.0";
const INTERFACE_PREFIX_NULL = 0;
const INTERFACE_MACADDR_NULL = "00:00:00:00:00:00";
const NETWORK_INTERFACE_UP = "up";
const NETWORK_INTERFACE_DOWN = "down";
const IP_MODE_DHCP = "dhcp";
const IP_MODE_STATIC = "static";
const PREF_NETWORK_DEBUG_ENABLED = "network.debugging.enabled";
XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
"@mozilla.org/network/manager;1",
"nsINetworkManager");
XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
"@mozilla.org/network/service;1",
"nsINetworkService");
let debug;
function updateDebug() {
let debugPref = false; // set default value here.
try {
debugPref = debugPref || Services.prefs.getBoolPref(PREF_NETWORK_DEBUG_ENABLED);
} catch (e) {}
if (debugPref) {
debug = function(s) {
dump("-*- EthernetManager: " + s + "\n");
};
} else {
debug = function(s) {};
}
}
updateDebug();
// nsINetworkInterface
function EthernetInterface(attr) {
this.info.state = attr.state;
this.info.type = attr.type;
this.info.name = attr.name;
this.info.ipMode = attr.ipMode;
this.info.ips = [attr.ip];
this.info.prefixLengths = [attr.prefixLength];
this.info.gateways = [attr.gateway];
this.info.dnses = attr.dnses;
this.httpProxyHost = "";
this.httpProxyPort = 0;
}
EthernetInterface.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
updateConfig: function(config) {
debug("Interface " + this.info.name + " updateConfig " + JSON.stringify(config));
this.info.state = (config.state != undefined) ?
config.state : this.info.state;
this.info.ips = (config.ip != undefined) ? [config.ip] : this.info.ips;
this.info.prefixLengths = (config.prefixLength != undefined) ?
[config.prefixLength] : this.info.prefixLengths;
this.info.gateways = (config.gateway != undefined) ?
[config.gateway] : this.info.gateways;
this.info.dnses = (config.dnses != undefined) ? config.dnses : this.info.dnses;
this.httpProxyHost = (config.httpProxyHost != undefined) ?
config.httpProxyHost : this.httpProxyHost;
this.httpProxyPort = (config.httpProxyPort != undefined) ?
config.httpProxyPort : this.httpProxyPort;
this.info.ipMode = (config.ipMode != undefined) ?
config.ipMode : this.info.ipMode;
},
info: {
getAddresses: function(ips, prefixLengths) {
ips.value = this.ips.slice();
prefixLengths.value = this.prefixLengths.slice();
return this.ips.length;
},
getGateways: function(count) {
if (count) {
count.value = this.gateways.length;
}
return this.gateways.slice();
},
getDnses: function(count) {
if (count) {
count.value = this.dnses.length;
}
return this.dnses.slice();
}
}
};
// nsIEthernetManager
/*
* Network state transition diagram
*
* ---------- enable --------- connect ----------- disconnect --------------
* | Disabled | -----> | Enabled | -------> | Connected | <----------> | Disconnected |
* ---------- --------- ----------- connect --------------
* ^ | | |
* | disable | | |
* -----------------------------------------------------------------------
*/
function EthernetManager() {
debug("EthernetManager start");
// Interface list.
this.ethernetInterfaces = {};
// Used to memorize last connection information.
this.lastStaticConfig = {};
Services.obs.addObserver(this, "xpcom-shutdown", false);
}
EthernetManager.prototype = {
classID: Components.ID("a96441dd-36b3-4f7f-963b-2c032e28a039"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIEthernetManager]),
ethernetInterfaces: null,
lastStaticConfig: null,
observer: function(subject, topic, data) {
switch (topic) {
case "xpcom-shutdown":
debug("xpcom-shutdown");
this._shutdown();
Services.obs.removeObserver(this, "xpcom-shutdown");
break;
}
},
_shutdown: function() {
debug("Shuting down");
(function onRemove(ifnameList) {
if (!ifnameList.length) {
return;
}
let ifname = ifnameList.shift();
this.removeInterface(ifname, { notify: onRemove.bind(this, ifnameList) });
}).call(this, Object.keys(this.ethernetInterfaces));
},
get interfaceList() {
return Object.keys(this.ethernetInterfaces);
},
scan: function(callback) {
debug("Scan");
gNetworkService.getInterfaces(function(success, list) {
let ethList = [];
if (!success) {
if (callback) {
callback.notify(ethList);
}
return;
}
for (let i = 0; i < list.length; i++) {
debug("Found interface " + list[i]);
if (!list[i].startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) {
continue;
}
ethList.push(list[i]);
}
if (callback) {
callback.notify(ethList);
}
});
},
addInterface: function(ifname, callback) {
debug("Add interface " + ifname);
if (!ifname || !ifname.startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) {
if (callback) {
callback.notify(false, "Invalid interface.");
}
return;
}
if (this.ethernetInterfaces[ifname]) {
if (callback) {
callback.notify(true, "Interface already exists.");
}
return;
}
gNetworkService.getInterfaceConfig(ifname, function(success, result) {
if (!success) {
if (callback) {
callback.notify(false, "Netd error.");
}
return;
}
// Since the operation may still succeed with an invalid interface name,
// check the mac address as well.
if (result.macAddr == INTERFACE_MACADDR_NULL) {
if (callback) {
callback.notify(false, "Interface not found.");
}
return;
}
this.ethernetInterfaces[ifname] = new EthernetInterface({
state: result.link == NETWORK_INTERFACE_UP ?
Ci.nsINetworkInfo.NETWORK_STATE_DISABLED :
Ci.nsINetworkInfo.NETWORK_STATE_ENABLED,
name: ifname,
type: Ci.nsINetworkInfo.NETWORK_TYPE_ETHERNET,
ip: result.ip,
prefixLength: result.prefix,
ipMode: IP_MODE_DHCP
});
// Register the interface to NetworkManager.
gNetworkManager.registerNetworkInterface(this.ethernetInterfaces[ifname]);
debug("Add interface " + ifname + " succeeded with " +
JSON.stringify(this.ethernetInterfaces[ifname]));
if (callback) {
callback.notify(true, "ok");
}
}.bind(this));
},
removeInterface: function(ifname, callback) {
debug("Remove interface " + ifname);
if (!ifname || !ifname.startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) {
if (callback) {
callback.notify(false, "Invalid interface.");
}
return;
}
if (!this.ethernetInterfaces[ifname]) {
if (callback) {
callback.notify(true, "Interface does not exist.");
}
return;
}
// Make sure interface is disable before removing.
this.disable(ifname, { notify: function(success, message) {
// Unregister the interface from NetworkManager and also remove it from
// the interface list.
gNetworkManager.unregisterNetworkInterface(this.ethernetInterfaces[ifname]);
delete this.ethernetInterfaces[ifname];
debug("Remove interface " + ifname + " succeeded.");
if (callback) {
callback.notify(true, "ok");
}
}.bind(this)});
},
updateInterfaceConfig: function(ifname, config, callback) {
debug("Update interface config with " + ifname);
this._ensureIfname(ifname, callback, function(iface) {
if (!config) {
if (callback) {
callback.notify(false, "No config to update.");
}
return;
}
// Network state can not be modified externally.
if (config.state) {
delete config.state;
}
let currentIpMode = iface.info.ipMode;
// Update config.
this.ethernetInterfaces[iface.info.name].updateConfig(config);
// Do not automatically re-connect if the interface is not in connected
// state.
if (iface.info.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
if (callback) {
callback.notify(true, "ok");
}
return;
}
let newIpMode = this.ethernetInterfaces[iface.info.name].info.ipMode;
if (newIpMode == IP_MODE_STATIC) {
this._setStaticIP(iface.info.name, callback);
return;
}
if ((currentIpMode == IP_MODE_STATIC) && (newIpMode == IP_MODE_DHCP)) {
gNetworkService.stopDhcp(iface.info.name, function(success) {
if (success) {
debug("DHCP for " + iface.info.name + " stopped.");
}
});
// Clear the current network settings before do dhcp request, otherwise
// dhcp settings could fail.
this.disconnect(iface.info.name, { notify: function(success, message) {
if (!success) {
if (callback) {
callback.notify("Disconnect failed.");
}
return;
}
this._runDhcp(iface.info.name, callback);
}.bind(this) });
return;
}
if (callback) {
callback.notify(true, "ok");
}
}.bind(this));
},
enable: function(ifname, callback) {
debug("Enable interface " + ifname);
this._ensureIfname(ifname, callback, function(iface) {
// Interface can be only enabled in the state of disabled.
if (iface.info.state != Ci.nsINetworkInfo.NETWORK_STATE_DISABLED) {
if (callback) {
callback.notify(true, "Interface already enabled.");
}
return;
}
let ips = {};
let prefixLengths = {};
iface.info.getAddresses(ips, prefixLengths);
let config = { ifname: iface.info.name,
ip: ips.value[0],
prefix: prefixLengths.value[0],
link: NETWORK_INTERFACE_UP };
gNetworkService.setInterfaceConfig(config, function(success) {
if (!success) {
if (callback) {
callback.notify(false, "Netd Error.");
}
return;
}
this.ethernetInterfaces[iface.info.name].updateConfig({
state: Ci.nsINetworkInfo.NETWORK_STATE_ENABLED
});
debug("Enable interface " + iface.info.name + " succeeded.");
if (callback) {
callback.notify(true, "ok");
}
}.bind(this));
}.bind(this));
},
disable: function(ifname, callback) {
debug("Disable interface " + ifname);
this._ensureIfname(ifname, callback, function(iface) {
if (iface.info.state == Ci.nsINetworkInfo.NETWORK_STATE_DISABLED) {
if (callback) {
callback.notify(true, "Interface already disabled.");
}
return;
}
if (iface.info.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
gNetworkService.stopDhcp(iface.info.name, function(success) {
if (success) {
debug("DHCP for " + iface.info.name + " stopped.");
}
});
}
let ips = {};
let prefixLengths = {};
iface.info.getAddresses(ips, prefixLengths);
let config = { ifname: iface.info.name,
ip: ips.value[0],
prefix: prefixLengths.value[0],
link: NETWORK_INTERFACE_DOWN };
gNetworkService.setInterfaceConfig(config, function(success) {
if (!success) {
if (callback) {
callback.notify(false, "Netd Error.");
}
return;
}
this.ethernetInterfaces[iface.info.name].updateConfig({
state: Ci.nsINetworkInfo.NETWORK_STATE_DISABLED
});
debug("Disable interface " + iface.info.name + " succeeded.");
if (callback) {
callback.notify(true, "ok");
}
}.bind(this));
}.bind(this));
},
connect: function(ifname, callback) {
debug("Connect interface " + ifname);
this._ensureIfname(ifname, callback, function(iface) {
// Interface can only be connected in the state of enabled or
// disconnected.
if (iface.info.state == Ci.nsINetworkInfo.NETWORK_STATE_DISABLED ||
iface.info.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
if (callback) {
callback.notify(true, "Interface " + ifname + " is not available or "
+ " already connected.");
}
return;
}
if (iface.info.ipMode == IP_MODE_DHCP) {
this._runDhcp(iface.info.name, callback);
return;
}
if (iface.info.ipMode == IP_MODE_STATIC) {
if (this._checkConfigNull(iface) && this.lastStaticConfig[iface.info.name]) {
debug("Connect with lastStaticConfig " +
JSON.stringify(this.lastStaticConfig[iface.info.name]));
this.ethernetInterfaces[iface.info.name].updateConfig(
this.lastStaticConfig[iface.info.name]);
}
this._setStaticIP(iface.info.name, callback);
return;
}
if (callback) {
callback.notify(false, "IP mode is wrong or not set.");
}
}.bind(this));
},
disconnect: function(ifname, callback) {
debug("Disconnect interface " + ifname);
this._ensureIfname(ifname, callback, function(iface) {
// Interface can be only disconnected in the state of connected.
if (iface.info.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
if (callback) {
callback.notify(true, "Interface is already disconnected");
}
return;
}
let config = { ifname: iface.info.name,
ip: INTERFACE_IPADDR_NULL,
prefix: INTERFACE_PREFIX_NULL,
link: NETWORK_INTERFACE_UP };
gNetworkService.setInterfaceConfig(config, function(success) {
if (!success) {
if (callback) {
callback.notify(false, "Netd error.");
}
return;
}
// Stop dhcp daemon.
gNetworkService.stopDhcp(iface.info.name, function(success) {
if (success) {
debug("DHCP for " + iface.info.name + " stopped.");
}
});
this.ethernetInterfaces[iface.info.name].updateConfig({
state: Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED,
ip: INTERFACE_IPADDR_NULL,
prefixLength: INTERFACE_PREFIX_NULL,
gateway: INTERFACE_GATEWAY_NULL
});
gNetworkManager.updateNetworkInterface(this.ethernetInterfaces[ifname]);
debug("Disconnect interface " + iface.info.name + " succeeded.");
if (callback) {
callback.notify(true, "ok");
}
}.bind(this));
}.bind(this));
},
_checkConfigNull: function(iface) {
let ips = {};
let prefixLengths = {};
let gateways = iface.info.getGateways();
iface.info.getAddresses(ips, prefixLengths);
if (ips.value[0] == INTERFACE_IPADDR_NULL &&
prefixLengths.value[0] == INTERFACE_PREFIX_NULL &&
gateways[0] == INTERFACE_GATEWAY_NULL) {
return true;
}
return false;
},
_ensureIfname: function(ifname, callback, func) {
// If no given ifname, use the default one.
if (!ifname) {
ifname = DEFAULT_ETHERNET_NETWORK_IFACE;
}
let iface = this.ethernetInterfaces[ifname];
if (!iface) {
if (callback) {
callback.notify(true, "Interface " + ifname + " is not available.");
}
return;
}
func.call(this, iface);
},
_runDhcp: function(ifname, callback) {
debug("runDhcp with " + ifname);
if (!this.ethernetInterfaces[ifname]) {
if (callback) {
callback.notify(false, "Invalid interface.");
}
return;
}
gNetworkService.dhcpRequest(ifname, function(success, result) {
if (!success) {
if (callback) {
callback.notify(false, "DHCP failed.");
}
return;
}
debug("DHCP succeeded with " + JSON.stringify(result));
// Clear last static network information when connecting with dhcp mode.
if (this.lastStaticConfig[ifname]) {
this.lastStaticConfig[ifname] = null;
}
this.ethernetInterfaces[ifname].updateConfig({
state: Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED,
ip: result.ipaddr_str,
gateway: result.gateway_str,
prefixLength: result.prefixLength,
dnses: [result.dns1_str, result.dns2_str]
});
gNetworkManager.updateNetworkInterface(this.ethernetInterfaces[ifname]);
debug("Connect interface " + ifname + " with DHCP succeeded.");
if (callback) {
callback.notify(true, "ok");
}
}.bind(this));
},
_setStaticIP: function(ifname, callback) {
let iface = this.ethernetInterfaces[ifname];
if (!iface) {
if (callback) {
callback.notify(false, "Invalid interface.");
}
return;
}
let ips = {};
let prefixLengths = {};
iface.info.getAddresses(ips, prefixLengths);
let config = { ifname: iface.info.name,
ip: ips.value[0],
prefix: prefixLengths.value[0],
link: NETWORK_INTERFACE_UP };
gNetworkService.setInterfaceConfig(config, function(success) {
if (!success) {
if (callback) {
callback.notify(false, "Netd Error.");
}
return;
}
// Keep the lastest static network information.
let ips = {};
let prefixLengths = {};
let gateways = iface.info.getGateways();
iface.info.getAddresses(ips, prefixLengths);
this.lastStaticConfig[iface.info.name] = {
ip: ips.value[0],
prefixLength: prefixLengths.value[0],
gateway: gateways[0]
};
this.ethernetInterfaces[ifname].updateConfig({
state: Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED,
});
gNetworkManager.updateNetworkInterface(this.ethernetInterfaces[ifname]);
debug("Connect interface " + ifname + " with static ip succeeded.");
if (callback) {
callback.notify(true, "ok");
}
}.bind(this));
},
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EthernetManager]);

View file

@ -0,0 +1,2 @@
component {a96441dd-36b3-4f7f-963b-2c032e28a039} EthernetManager.js
contract @mozilla.org/ethernetManager;1 {a96441dd-36b3-4f7f-963b-2c032e28a039}

200
dom/network/NetUtils.cpp Normal file
View file

@ -0,0 +1,200 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "NetUtils.h"
#include <dlfcn.h>
#include <errno.h>
#include "prinit.h"
#include "mozilla/Assertions.h"
#include "nsDebug.h"
#include "SystemProperty.h"
using mozilla::system::Property;
static void* sNetUtilsLib;
static PRCallOnceType sInitNetUtilsLib;
static PRStatus
InitNetUtilsLib()
{
sNetUtilsLib = dlopen("/system/lib/libnetutils.so", RTLD_LAZY);
// We might fail to open the hardware lib. That's OK.
return PR_SUCCESS;
}
static void*
GetNetUtilsLibHandle()
{
PR_CallOnce(&sInitNetUtilsLib, InitNetUtilsLib);
return sNetUtilsLib;
}
// static
void*
NetUtils::GetSharedLibrary()
{
void* netLib = GetNetUtilsLibHandle();
if (!netLib) {
NS_WARNING("No /system/lib/libnetutils.so");
}
return netLib;
}
// static
int32_t
NetUtils::SdkVersion()
{
char propVersion[Property::VALUE_MAX_LENGTH];
Property::Get("ro.build.version.sdk", propVersion, "0");
int32_t version = strtol(propVersion, nullptr, 10);
return version;
}
DEFINE_DLFUNC(ifc_enable, int32_t, const char*)
DEFINE_DLFUNC(ifc_disable, int32_t, const char*)
DEFINE_DLFUNC(ifc_configure, int32_t, const char*, in_addr_t, uint32_t,
in_addr_t, in_addr_t, in_addr_t)
DEFINE_DLFUNC(ifc_reset_connections, int32_t, const char*, const int32_t)
DEFINE_DLFUNC(ifc_set_default_route, int32_t, const char*, in_addr_t)
DEFINE_DLFUNC(ifc_add_route, int32_t, const char*, const char*, uint32_t, const char*)
DEFINE_DLFUNC(ifc_remove_route, int32_t, const char*, const char*, uint32_t, const char*)
DEFINE_DLFUNC(ifc_remove_host_routes, int32_t, const char*)
DEFINE_DLFUNC(ifc_remove_default_route, int32_t, const char*)
DEFINE_DLFUNC(dhcp_stop, int32_t, const char*)
NetUtils::NetUtils()
{
}
int32_t NetUtils::do_ifc_enable(const char *ifname)
{
USE_DLFUNC(ifc_enable)
return ifc_enable(ifname);
}
int32_t NetUtils::do_ifc_disable(const char *ifname)
{
USE_DLFUNC(ifc_disable)
return ifc_disable(ifname);
}
int32_t NetUtils::do_ifc_configure(const char *ifname,
in_addr_t address,
uint32_t prefixLength,
in_addr_t gateway,
in_addr_t dns1,
in_addr_t dns2)
{
USE_DLFUNC(ifc_configure)
int32_t ret = ifc_configure(ifname, address, prefixLength, gateway, dns1, dns2);
return ret;
}
int32_t NetUtils::do_ifc_reset_connections(const char *ifname,
const int32_t resetMask)
{
USE_DLFUNC(ifc_reset_connections)
return ifc_reset_connections(ifname, resetMask);
}
int32_t NetUtils::do_ifc_set_default_route(const char *ifname,
in_addr_t gateway)
{
USE_DLFUNC(ifc_set_default_route)
return ifc_set_default_route(ifname, gateway);
}
int32_t NetUtils::do_ifc_add_route(const char *ifname,
const char *dst,
uint32_t prefixLength,
const char *gateway)
{
USE_DLFUNC(ifc_add_route)
return ifc_add_route(ifname, dst, prefixLength, gateway);
}
int32_t NetUtils::do_ifc_remove_route(const char *ifname,
const char *dst,
uint32_t prefixLength,
const char *gateway)
{
USE_DLFUNC(ifc_remove_route)
return ifc_remove_route(ifname, dst, prefixLength, gateway);
}
int32_t NetUtils::do_ifc_remove_host_routes(const char *ifname)
{
USE_DLFUNC(ifc_remove_host_routes)
return ifc_remove_host_routes(ifname);
}
int32_t NetUtils::do_ifc_remove_default_route(const char *ifname)
{
USE_DLFUNC(ifc_remove_default_route)
return ifc_remove_default_route(ifname);
}
int32_t NetUtils::do_dhcp_stop(const char *ifname)
{
USE_DLFUNC(dhcp_stop)
return dhcp_stop(ifname);
}
int32_t NetUtils::do_dhcp_do_request(const char *ifname,
char *ipaddr,
char *gateway,
uint32_t *prefixLength,
char *dns1,
char *dns2,
char *server,
uint32_t *lease,
char* vendorinfo)
{
int32_t ret = -1;
uint32_t sdkVersion = SdkVersion();
if (sdkVersion == 15) {
// ICS
// http://androidxref.com/4.0.4/xref/system/core/libnetutils/dhcp_utils.c#149
DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*, uint32_t*, char*, char*, char*, uint32_t*)
USE_DLFUNC(dhcp_do_request)
vendorinfo[0] = '\0';
ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns1, dns2,
server, lease);
} else if (sdkVersion == 16 || sdkVersion == 17) {
// JB 4.1 and 4.2
// http://androidxref.com/4.1.2/xref/system/core/libnetutils/dhcp_utils.c#175
// http://androidxref.com/4.2.2_r1/xref/system/core/include/netutils/dhcp.h#26
DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*, uint32_t*, char*, char*, char*, uint32_t*, char*)
USE_DLFUNC(dhcp_do_request)
ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns1, dns2,
server, lease, vendorinfo);
} else if (sdkVersion == 18) {
// JB 4.3
// http://androidxref.com/4.3_r2.1/xref/system/core/libnetutils/dhcp_utils.c#181
DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*, uint32_t*, char**, char*, uint32_t*, char*, char*)
USE_DLFUNC(dhcp_do_request)
char *dns[3] = {dns1, dns2, nullptr};
char domains[Property::VALUE_MAX_LENGTH];
ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns,
server, lease, vendorinfo, domains);
} else if (sdkVersion >= 19) {
// KitKat 4.4.X
// http://androidxref.com/4.4_r1/xref/system/core/libnetutils/dhcp_utils.c#18
// Lollipop 5.0
//http://androidxref.com/5.0.0_r2/xref/system/core/libnetutils/dhcp_utils.c#186
DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*, uint32_t*, char**, char*, uint32_t*, char*, char*, char*)
USE_DLFUNC(dhcp_do_request)
char *dns[3] = {dns1, dns2, nullptr};
char domains[Property::VALUE_MAX_LENGTH];
char mtu[Property::VALUE_MAX_LENGTH];
ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns, server, lease, vendorinfo, domains, mtu);
} else {
NS_WARNING("Unable to perform do_dhcp_request: unsupported sdk version!");
}
return ret;
}

75
dom/network/NetUtils.h Normal file
View file

@ -0,0 +1,75 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Abstraction on top of the network support from libnetutils that we
* use to set up network connections.
*/
#ifndef NetUtils_h
#define NetUtils_h
#include "arpa/inet.h"
// Copied from ifc.h
#define RESET_IPV4_ADDRESSES 0x01
#define RESET_IPV6_ADDRESSES 0x02
#define RESET_ALL_ADDRESSES (RESET_IPV4_ADDRESSES | RESET_IPV6_ADDRESSES)
// Implements netutils functions. No need for an abstract class here since we
// only have a one sdk specific method (dhcp_do_request)
class NetUtils
{
public:
static void* GetSharedLibrary();
NetUtils();
int32_t do_ifc_enable(const char *ifname);
int32_t do_ifc_disable(const char *ifname);
int32_t do_ifc_configure(const char *ifname,
in_addr_t address,
uint32_t prefixLength,
in_addr_t gateway,
in_addr_t dns1,
in_addr_t dns2);
int32_t do_ifc_reset_connections(const char *ifname, const int32_t resetMask);
int32_t do_ifc_set_default_route(const char *ifname, in_addr_t gateway);
int32_t do_ifc_add_route(const char *ifname,
const char *dst,
uint32_t prefixLength,
const char *gateway);
int32_t do_ifc_remove_route(const char *ifname,
const char *dst,
uint32_t prefixLength,
const char *gateway);
int32_t do_ifc_remove_host_routes(const char *ifname);
int32_t do_ifc_remove_default_route(const char *ifname);
int32_t do_dhcp_stop(const char *ifname);
int32_t do_dhcp_do_request(const char *ifname,
char *ipaddr,
char *gateway,
uint32_t *prefixLength,
char *dns1,
char *dns2,
char *server,
uint32_t *lease,
char* vendorinfo);
static int32_t SdkVersion();
};
// Defines a function type with the right arguments and return type.
#define DEFINE_DLFUNC(name, ret, args...) typedef ret (*FUNC##name)(args);
// Set up a dlsymed function ready to use.
#define USE_DLFUNC(name) \
FUNC##name name = (FUNC##name) dlsym(GetSharedLibrary(), #name); \
if (!name) { \
MOZ_CRASH("Symbol not found in shared library : " #name); \
}
#endif // NetUtils_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,388 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const DEBUG = false;
function debug(s) { dump("-*- NetworkStatsManager: " + s + "\n"); }
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
// Ensure NetworkStatsService and NetworkStatsDB are loaded in the parent process
// to receive messages from the child processes.
var appInfo = Cc["@mozilla.org/xre/app-info;1"];
var isParentProcess = !appInfo || appInfo.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
if (isParentProcess) {
Cu.import("resource://gre/modules/NetworkStatsService.jsm");
}
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsISyncMessageSender");
// NetworkStatsData
const nsIClassInfo = Ci.nsIClassInfo;
const NETWORKSTATSDATA_CID = Components.ID("{3b16fe17-5583-483a-b486-b64a3243221c}");
function NetworkStatsData(aWindow, aData) {
this.rxBytes = aData.rxBytes;
this.txBytes = aData.txBytes;
this.date = new aWindow.Date(aData.date.getTime());
}
NetworkStatsData.prototype = {
classID : NETWORKSTATSDATA_CID,
QueryInterface : XPCOMUtils.generateQI([])
};
// NetworkStatsInterface
const NETWORKSTATSINTERFACE_CONTRACTID = "@mozilla.org/networkstatsinterface;1";
const NETWORKSTATSINTERFACE_CID = Components.ID("{f540615b-d803-43ff-8200-2a9d145a5645}");
function NetworkStatsInterface() {
if (DEBUG) {
debug("NetworkStatsInterface Constructor");
}
}
NetworkStatsInterface.prototype = {
__init: function(aNetwork) {
this.type = aNetwork.type;
this.id = aNetwork.id;
},
classID : NETWORKSTATSINTERFACE_CID,
contractID: NETWORKSTATSINTERFACE_CONTRACTID,
QueryInterface : XPCOMUtils.generateQI([])
}
// NetworkStats
const NETWORKSTATS_CID = Components.ID("{28904f59-8497-4ac0-904f-2af14b7fd3de}");
function NetworkStats(aWindow, aStats) {
if (DEBUG) {
debug("NetworkStats Constructor");
}
this.appManifestURL = aStats.appManifestURL || null;
this.browsingTrafficOnly = aStats.browsingTrafficOnly || false;
this.serviceType = aStats.serviceType || null;
this.network = new aWindow.MozNetworkStatsInterface(aStats.network);
this.start = aStats.start ? new aWindow.Date(aStats.start.getTime()) : null;
this.end = aStats.end ? new aWindow.Date(aStats.end.getTime()) : null;
let samples = this.data = new aWindow.Array();
for (let i = 0; i < aStats.data.length; i++) {
samples.push(aWindow.MozNetworkStatsData._create(
aWindow, new NetworkStatsData(aWindow, aStats.data[i])));
}
}
NetworkStats.prototype = {
classID : NETWORKSTATS_CID,
QueryInterface : XPCOMUtils.generateQI()
}
// NetworkStatsAlarm
const NETWORKSTATSALARM_CID = Components.ID("{a93ea13e-409c-4189-9b1e-95fff220be55}");
function NetworkStatsAlarm(aWindow, aAlarm) {
this.alarmId = aAlarm.id;
this.network = new aWindow.MozNetworkStatsInterface(aAlarm.network);
this.threshold = aAlarm.threshold;
this.data = aAlarm.data;
}
NetworkStatsAlarm.prototype = {
classID : NETWORKSTATSALARM_CID,
QueryInterface : XPCOMUtils.generateQI([])
};
// NetworkStatsManager
const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
const NETWORKSTATSMANAGER_CID = Components.ID("{ceb874cd-cc1a-4e65-b404-cc2d3e42425f}");
function NetworkStatsManager() {
if (DEBUG) {
debug("Constructor");
}
}
NetworkStatsManager.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
getSamples: function getSamples(aNetwork, aStart, aEnd, aOptions) {
if (aStart > aEnd) {
throw Components.results.NS_ERROR_INVALID_ARG;
}
// appManifestURL is used to query network statistics by app;
// serviceType is used to query network statistics by system service.
// It is illegal to specify both of them at the same time.
if (aOptions.appManifestURL && aOptions.serviceType) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
// browsingTrafficOnly is meaningful only when querying by app.
if (!aOptions.appManifestURL && aOptions.browsingTrafficOnly) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
let appManifestURL = aOptions.appManifestURL;
let serviceType = aOptions.serviceType;
let browsingTrafficOnly = aOptions.browsingTrafficOnly;
// TODO Bug 929410 Date object cannot correctly pass through cpmm/ppmm IPC
// This is just a work-around by passing timestamp numbers.
aStart = aStart.getTime();
aEnd = aEnd.getTime();
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:Get",
{ network: aNetwork.toJSON(),
start: aStart,
end: aEnd,
appManifestURL: appManifestURL,
browsingTrafficOnly: browsingTrafficOnly,
serviceType: serviceType,
id: this.getRequestId(request) });
return request;
},
clearStats: function clearStats(aNetwork) {
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:Clear",
{ network: aNetwork.toJSON(),
id: this.getRequestId(request) });
return request;
},
clearAllStats: function clearAllStats() {
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:ClearAll",
{id: this.getRequestId(request)});
return request;
},
addAlarm: function addAlarm(aNetwork, aThreshold, aOptions) {
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:SetAlarm",
{id: this.getRequestId(request),
data: {network: aNetwork.toJSON(),
threshold: aThreshold,
startTime: aOptions.startTime,
data: aOptions.data,
manifestURL: this.manifestURL,
pageURL: this.pageURL}});
return request;
},
getAllAlarms: function getAllAlarms(aNetwork) {
let network = null;
if (aNetwork) {
network = aNetwork.toJSON();
}
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:GetAlarms",
{id: this.getRequestId(request),
data: {network: network,
manifestURL: this.manifestURL}});
return request;
},
removeAlarms: function removeAlarms(aAlarmId) {
if (aAlarmId == 0) {
aAlarmId = -1;
}
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:RemoveAlarms",
{id: this.getRequestId(request),
data: {alarmId: aAlarmId,
manifestURL: this.manifestURL}});
return request;
},
getAvailableNetworks: function getAvailableNetworks() {
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:GetAvailableNetworks",
{ id: this.getRequestId(request) });
return request;
},
getAvailableServiceTypes: function getAvailableServiceTypes() {
let request = this.createRequest();
cpmm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes",
{ id: this.getRequestId(request) });
return request;
},
get sampleRate() {
return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0];
},
get maxStorageAge() {
return cpmm.sendSyncMessage("NetworkStats:MaxStorageAge")[0];
},
receiveMessage: function(aMessage) {
if (DEBUG) {
debug("NetworkStatsmanager::receiveMessage: " + aMessage.name);
}
let msg = aMessage.json;
let req = this.takeRequest(msg.id);
if (!req) {
if (DEBUG) {
debug("No request stored with id " + msg.id);
}
return;
}
switch (aMessage.name) {
case "NetworkStats:Get:Return":
if (msg.error) {
Services.DOMRequest.fireError(req, msg.error);
return;
}
let result = this._window.MozNetworkStats._create(
this._window, new NetworkStats(this._window, msg.result));
if (DEBUG) {
debug("result: " + JSON.stringify(result));
}
Services.DOMRequest.fireSuccess(req, result);
break;
case "NetworkStats:GetAvailableNetworks:Return":
if (msg.error) {
Services.DOMRequest.fireError(req, msg.error);
return;
}
let networks = new this._window.Array();
for (let i = 0; i < msg.result.length; i++) {
let network = new this._window.MozNetworkStatsInterface(msg.result[i]);
networks.push(network);
}
Services.DOMRequest.fireSuccess(req, networks);
break;
case "NetworkStats:GetAvailableServiceTypes:Return":
if (msg.error) {
Services.DOMRequest.fireError(req, msg.error);
return;
}
let serviceTypes = new this._window.Array();
for (let i = 0; i < msg.result.length; i++) {
serviceTypes.push(msg.result[i]);
}
Services.DOMRequest.fireSuccess(req, serviceTypes);
break;
case "NetworkStats:Clear:Return":
case "NetworkStats:ClearAll:Return":
if (msg.error) {
Services.DOMRequest.fireError(req, msg.error);
return;
}
Services.DOMRequest.fireSuccess(req, true);
break;
case "NetworkStats:SetAlarm:Return":
case "NetworkStats:RemoveAlarms:Return":
if (msg.error) {
Services.DOMRequest.fireError(req, msg.error);
return;
}
Services.DOMRequest.fireSuccess(req, msg.result);
break;
case "NetworkStats:GetAlarms:Return":
if (msg.error) {
Services.DOMRequest.fireError(req, msg.error);
return;
}
let alarms = new this._window.Array();
for (let i = 0; i < msg.result.length; i++) {
// The WebIDL type of data is any, so we should manually clone it
// into the content window.
if ("data" in msg.result[i]) {
msg.result[i].data = Cu.cloneInto(msg.result[i].data, this._window);
}
let alarm = new NetworkStatsAlarm(this._window, msg.result[i]);
alarms.push(this._window.MozNetworkStatsAlarm._create(this._window, alarm));
}
Services.DOMRequest.fireSuccess(req, alarms);
break;
default:
if (DEBUG) {
debug("Wrong message: " + aMessage.name);
}
}
},
init: function(aWindow) {
let principal = aWindow.document.nodePrincipal;
this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
"NetworkStats:GetAvailableNetworks:Return",
"NetworkStats:GetAvailableServiceTypes:Return",
"NetworkStats:Clear:Return",
"NetworkStats:ClearAll:Return",
"NetworkStats:SetAlarm:Return",
"NetworkStats:GetAlarms:Return",
"NetworkStats:RemoveAlarms:Return"]);
// Init app properties.
let appsService = Cc["@mozilla.org/AppsService;1"]
.getService(Ci.nsIAppsService);
this.manifestURL = appsService.getManifestURLByLocalId(principal.appId);
let isApp = !!this.manifestURL.length;
if (isApp) {
this.pageURL = principal.URI.spec;
}
this.window = aWindow;
},
// Called from DOMRequestIpcHelper
uninit: function uninit() {
if (DEBUG) {
debug("uninit call");
}
},
classID : NETWORKSTATSMANAGER_CID,
contractID : NETWORKSTATSMANAGER_CONTRACTID,
QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference,
Ci.nsIObserver]),
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsAlarm,
NetworkStatsData,
NetworkStatsInterface,
NetworkStats,
NetworkStatsManager]);

View file

@ -0,0 +1,14 @@
component {3b16fe17-5583-483a-b486-b64a3243221c} NetworkStatsManager.js
contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
component {28904f59-8497-4ac0-904f-2af14b7fd3de} NetworkStatsManager.js
contract @mozilla.org/networkStats;1 {28904f59-8497-4ac0-904f-2af14b7fd3de}
component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
component {a93ea13e-409c-4189-9b1e-95fff220be55} NetworkStatsManager.js
contract @mozilla.org/networkstatsalarm;1 {a93ea13e-409c-4189-9b1e-95fff220be55}
component {ceb874cd-cc1a-4e65-b404-cc2d3e42425f} NetworkStatsManager.js
contract @mozilla.org/networkStatsManager;1 {ceb874cd-cc1a-4e65-b404-cc2d3e42425f}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const DEBUG = false;
function debug(s) { dump("-*- NetworkStatsServiceProxy: " + s + "\n"); }
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
this.EXPORTED_SYMBOLS = ["NetworkStatsServiceProxy"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NetworkStatsService.jsm");
const NETWORKSTATSSERVICEPROXY_CONTRACTID = "@mozilla.org/networkstatsServiceProxy;1";
const NETWORKSTATSSERVICEPROXY_CID = Components.ID("98fd8f69-784e-4626-aa59-56d6436a3c24");
const nsINetworkStatsServiceProxy = Ci.nsINetworkStatsServiceProxy;
function NetworkStatsServiceProxy() {
if (DEBUG) {
debug("Proxy started");
}
}
NetworkStatsServiceProxy.prototype = {
/*
* Function called in the protocol layer (HTTP, FTP, WebSocket ...etc)
* to pass the per-app stats to NetworkStatsService.
*/
saveAppStats: function saveAppStats(aAppId, aIsInIsolatedMozBrowser, aNetworkInfo, aTimeStamp,
aRxBytes, aTxBytes, aIsAccumulative,
aCallback) {
if (!aNetworkInfo) {
if (DEBUG) {
debug("|aNetworkInfo| is not specified. Failed to save stats. Returning.");
}
return;
}
if (DEBUG) {
debug("saveAppStats: " + aAppId + " " + aIsInIsolatedMozBrowser + " " +
aNetworkInfo.type + " " + aTimeStamp + " " +
aRxBytes + " " + aTxBytes + " " + aIsAccumulative);
}
if (aCallback) {
aCallback = aCallback.notify;
}
NetworkStatsService.saveStats(aAppId, aIsInIsolatedMozBrowser, "", aNetworkInfo,
aTimeStamp, aRxBytes, aTxBytes,
aIsAccumulative, aCallback);
},
/*
* Function called in the points of different system services
* to pass the per-service stats to NetworkStatsService.
*/
saveServiceStats: function saveServiceStats(aServiceType, aNetworkInfo,
aTimeStamp, aRxBytes, aTxBytes,
aIsAccumulative, aCallback) {
if (!aNetworkInfo) {
if (DEBUG) {
debug("|aNetworkInfo| is not specified. Failed to save stats. Returning.");
}
return;
}
if (DEBUG) {
debug("saveServiceStats: " + aServiceType + " " + aNetworkInfo.type + " " +
aTimeStamp + " " + aRxBytes + " " + aTxBytes + " " +
aIsAccumulative);
}
if (aCallback) {
aCallback = aCallback.notify;
}
NetworkStatsService.saveStats(0, false, aServiceType , aNetworkInfo, aTimeStamp,
aRxBytes, aTxBytes, aIsAccumulative,
aCallback);
},
classID : NETWORKSTATSSERVICEPROXY_CID,
QueryInterface : XPCOMUtils.generateQI([nsINetworkStatsServiceProxy]),
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsServiceProxy]);

View file

@ -0,0 +1,2 @@
component {98fd8f69-784e-4626-aa59-56d6436a3c24} NetworkStatsServiceProxy.js
contract @mozilla.org/networkstatsServiceProxy;1 {98fd8f69-784e-4626-aa59-56d6436a3c24}

View file

@ -0,0 +1,32 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PNecko;
include protocol PTCPSocket;
include "mozilla/net/NeckoMessageUtils.h";
namespace mozilla {
namespace net {
//-------------------------------------------------------------------
protocol PTCPServerSocket
{
manager PNecko;
parent:
async Close();
async RequestDelete();
child:
async CallbackAccept(PTCPSocket socket);
async __delete__();
};
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,83 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PNecko;
include "mozilla/net/NeckoMessageUtils.h";
using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
struct TCPError {
nsString name;
nsString message;
};
union SendableData {
uint8_t[];
nsCString;
};
union CallbackData {
void_t;
SendableData;
TCPError;
};
namespace mozilla {
namespace net {
//-------------------------------------------------------------------
protocol PTCPSocket
{
manager PNecko;
parent:
// Forward calling to child's open() method to parent, expect TCPOptions
// is expanded to |useSSL| (from TCPOptions.useSecureTransport) and
// |binaryType| (from TCPOption.binaryType).
async Open(nsString host, uint16_t port, bool useSSL, bool useArrayBuffers);
// Ask parent to open a socket and bind the newly-opened socket to a local
// address specified in |localAddr| and |localPort|.
async OpenBind(nsCString host, uint16_t port,
nsCString localAddr, uint16_t localPort,
bool useSSL, bool aUseArrayBuffers, nsCString aFilter);
// When child's send() is called, this message requrests parent to send
// data and update it's trackingNumber.
async Data(SendableData data, uint32_t trackingNumber);
// Forward calling to child's upgradeToSecure() method to parent.
async StartTLS();
// Forward calling to child's send() method to parent.
async Suspend();
// Forward calling to child's resume() method to parent.
async Resume();
// Forward calling to child's close() method to parent.
async Close();
child:
// Forward events that are dispatched by parent.
async Callback(nsString type, CallbackData data, uint32_t readyState);
// Update child's bufferedAmount when parent's bufferedAmount is updated.
// trackingNumber is also passed back to child to ensure the bufferedAmount
// is corresponding the last call to send().
async UpdateBufferedAmount(uint32_t bufferedAmount, uint32_t trackingNumber);
both:
async RequestDelete();
async __delete__();
};
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,69 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PNecko;
include protocol PBackground;
include protocol PBlob;
include InputStreamParams;
include "mozilla/net/NeckoMessageUtils.h";
include "mozilla/net/DNS.h";
include "prio.h";
using mozilla::net::NetAddr from "mozilla/net/DNS.h";
using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
struct UDPAddressInfo {
nsCString addr;
uint16_t port;
};
union UDPSocketAddr {
UDPAddressInfo;
NetAddr;
};
union UDPData {
uint8_t[];
InputStreamParams;
};
namespace mozilla {
namespace net {
//-------------------------------------------------------------------
protocol PUDPSocket
{
manager PNecko or PBackground;
parent:
async Bind(UDPAddressInfo addressInfo, bool addressReuse, bool loopback,
uint32_t recvBufferSize, uint32_t sendBufferSize);
async Connect(UDPAddressInfo addressInfo);
async OutgoingData(UDPData data, UDPSocketAddr addr);
async JoinMulticast(nsCString multicastAddress, nsCString iface);
async LeaveMulticast(nsCString multicastAddress, nsCString iface);
async Close();
async RequestDelete();
child:
async CallbackOpened(UDPAddressInfo addressInfo);
async CallbackConnected(UDPAddressInfo addressInfo);
async CallbackClosed();
async CallbackReceivedData(UDPAddressInfo addressInfo, uint8_t[] data);
async CallbackError(nsCString message, nsCString filename, uint32_t lineNumber);
async __delete__();
};
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,192 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TCPServerSocketBinding.h"
#include "mozilla/dom/TCPServerSocketEvent.h"
#include "mozilla/dom/TCPSocketBinding.h"
#include "TCPServerSocketParent.h"
#include "TCPServerSocketChild.h"
#include "mozilla/dom/Event.h"
#include "mozilla/ErrorResult.h"
#include "TCPServerSocket.h"
#include "TCPSocket.h"
using namespace mozilla::dom;
NS_IMPL_CYCLE_COLLECTION_CLASS(TCPServerSocket)
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPServerSocket,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPServerSocket,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerSocket)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerBridgeChild)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerBridgeParent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPServerSocket,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerSocket)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerBridgeChild)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerBridgeParent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ADDREF_INHERITED(TCPServerSocket, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(TCPServerSocket, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TCPServerSocket)
NS_INTERFACE_MAP_ENTRY(nsIServerSocketListener)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
TCPServerSocket::TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort,
bool aUseArrayBuffers, uint16_t aBacklog)
: DOMEventTargetHelper(aGlobal)
, mPort(aPort)
, mBacklog(aBacklog)
, mUseArrayBuffers(aUseArrayBuffers)
{
}
TCPServerSocket::~TCPServerSocket()
{
}
nsresult
TCPServerSocket::Init()
{
if (mServerSocket || mServerBridgeChild) {
NS_WARNING("Child TCPServerSocket is already listening.");
return NS_ERROR_FAILURE;
}
if (XRE_GetProcessType() == GeckoProcessType_Content) {
mServerBridgeChild = new TCPServerSocketChild(this, mPort, mBacklog, mUseArrayBuffers);
return NS_OK;
}
nsresult rv;
mServerSocket = do_CreateInstance("@mozilla.org/network/server-socket;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mServerSocket->Init(mPort, false, mBacklog);
NS_ENSURE_SUCCESS(rv, rv);
rv = mServerSocket->GetPort(&mPort);
NS_ENSURE_SUCCESS(rv, rv);
rv = mServerSocket->AsyncListen(this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
already_AddRefed<TCPServerSocket>
TCPServerSocket::Constructor(const GlobalObject& aGlobal,
uint16_t aPort,
const ServerSocketOptions& aOptions,
uint16_t aBacklog,
mozilla::ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv = NS_ERROR_FAILURE;
return nullptr;
}
bool useArrayBuffers = aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer;
RefPtr<TCPServerSocket> socket = new TCPServerSocket(global, aPort, useArrayBuffers, aBacklog);
nsresult rv = socket->Init();
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv = NS_ERROR_FAILURE;
return nullptr;
}
return socket.forget();
}
uint16_t
TCPServerSocket::LocalPort()
{
return mPort;
}
void
TCPServerSocket::Close()
{
if (mServerBridgeChild) {
mServerBridgeChild->Close();
}
if (mServerSocket) {
mServerSocket->Close();
}
}
void
TCPServerSocket::FireEvent(const nsAString& aType, TCPSocket* aSocket)
{
TCPServerSocketEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mSocket = aSocket;
RefPtr<TCPServerSocketEvent> event =
TCPServerSocketEvent::Constructor(this, aType, init);
event->SetTrusted(true);
bool dummy;
DispatchEvent(event, &dummy);
if (mServerBridgeParent) {
mServerBridgeParent->OnConnect(event);
}
}
NS_IMETHODIMP
TCPServerSocket::OnSocketAccepted(nsIServerSocket* aServer, nsISocketTransport* aTransport)
{
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
RefPtr<TCPSocket> socket = TCPSocket::CreateAcceptedSocket(global, aTransport, mUseArrayBuffers);
if (mServerBridgeParent) {
socket->SetAppIdAndBrowser(mServerBridgeParent->GetAppId(),
mServerBridgeParent->GetInIsolatedMozBrowser());
}
FireEvent(NS_LITERAL_STRING("connect"), socket);
return NS_OK;
}
NS_IMETHODIMP
TCPServerSocket::OnStopListening(nsIServerSocket* aServer, nsresult aStatus)
{
if (aStatus != NS_BINDING_ABORTED) {
RefPtr<Event> event = new Event(GetOwner());
event->InitEvent(NS_LITERAL_STRING("error"), false, false);
event->SetTrusted(true);
bool dummy;
DispatchEvent(event, &dummy);
NS_WARNING("Server socket was closed by unexpected reason.");
return NS_ERROR_FAILURE;
}
mServerSocket = nullptr;
return NS_OK;
}
nsresult
TCPServerSocket::AcceptChildSocket(TCPSocketChild* aSocketChild)
{
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
RefPtr<TCPSocket> socket = TCPSocket::CreateAcceptedSocket(global, aSocketChild, mUseArrayBuffers);
NS_ENSURE_TRUE(socket, NS_ERROR_FAILURE);
FireEvent(NS_LITERAL_STRING("connect"), socket);
return NS_OK;
}
void
TCPServerSocket::SetServerBridgeParent(TCPServerSocketParent* aBridgeParent)
{
mServerBridgeParent = aBridgeParent;
}
JSObject*
TCPServerSocket::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return TCPServerSocketBinding::Wrap(aCx, this, aGivenProto);
}

View file

@ -0,0 +1,83 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TCPServerSocket_h
#define mozilla_dom_TCPServerSocket_h
#include "mozilla/DOMEventTargetHelper.h"
#include "nsIServerSocket.h"
namespace mozilla {
class ErrorResult;
namespace dom {
struct ServerSocketOptions;
class GlobalObject;
class TCPSocket;
class TCPSocketChild;
class TCPServerSocketChild;
class TCPServerSocketParent;
class TCPServerSocket final : public DOMEventTargetHelper
, public nsIServerSocketListener
{
public:
TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort, bool aUseArrayBuffers,
uint16_t aBacklog);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPServerSocket, DOMEventTargetHelper)
NS_DECL_NSISERVERSOCKETLISTENER
nsPIDOMWindowInner* GetParentObject() const
{
return GetOwner();
}
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
nsresult Init();
uint16_t LocalPort();
void Close();
static already_AddRefed<TCPServerSocket>
Constructor(const GlobalObject& aGlobal,
uint16_t aPort,
const ServerSocketOptions& aOptions,
uint16_t aBacklog,
mozilla::ErrorResult& aRv);
IMPL_EVENT_HANDLER(connect);
IMPL_EVENT_HANDLER(error);
// Relay an accepted socket notification from the parent process and
// initialize this object with an existing child actor for the new socket.
nsresult AcceptChildSocket(TCPSocketChild* aSocketChild);
// Associate this object with an IPC actor in the parent process to relay
// notifications to content processes.
void SetServerBridgeParent(TCPServerSocketParent* aBridgeParent);
private:
~TCPServerSocket();
// Dispatch a TCPServerSocketEvent event of a given type at this object.
void FireEvent(const nsAString& aType, TCPSocket* aSocket);
// The server socket associated with this object.
nsCOMPtr<nsIServerSocket> mServerSocket;
// The IPC actor in the content process.
RefPtr<TCPServerSocketChild> mServerBridgeChild;
// The IPC actor in the parent process.
RefPtr<TCPServerSocketParent> mServerBridgeParent;
int32_t mPort;
uint16_t mBacklog;
// True if any accepted sockets should use array buffers for received messages.
bool mUseArrayBuffers;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TCPServerSocket_h

View file

@ -0,0 +1,92 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TCPServerSocketChild.h"
#include "TCPSocketChild.h"
#include "TCPServerSocket.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/dom/PBrowserChild.h"
#include "mozilla/dom/TabChild.h"
#include "nsJSUtils.h"
#include "jsfriendapi.h"
using mozilla::net::gNeckoChild;
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION(TCPServerSocketChildBase, mServerSocket)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPServerSocketChildBase)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketChildBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketChildBase)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
TCPServerSocketChildBase::TCPServerSocketChildBase()
: mIPCOpen(false)
{
}
TCPServerSocketChildBase::~TCPServerSocketChildBase()
{
}
NS_IMETHODIMP_(MozExternalRefCountType) TCPServerSocketChild::Release(void)
{
nsrefcnt refcnt = TCPServerSocketChildBase::Release();
if (refcnt == 1 && mIPCOpen) {
PTCPServerSocketChild::SendRequestDelete();
return 1;
}
return refcnt;
}
TCPServerSocketChild::TCPServerSocketChild(TCPServerSocket* aServerSocket, uint16_t aLocalPort,
uint16_t aBacklog, bool aUseArrayBuffers)
{
mServerSocket = aServerSocket;
AddIPDLReference();
gNeckoChild->SendPTCPServerSocketConstructor(this, aLocalPort, aBacklog, aUseArrayBuffers);
}
void
TCPServerSocketChildBase::ReleaseIPDLReference()
{
MOZ_ASSERT(mIPCOpen);
mIPCOpen = false;
this->Release();
}
void
TCPServerSocketChildBase::AddIPDLReference()
{
MOZ_ASSERT(!mIPCOpen);
mIPCOpen = true;
this->AddRef();
}
TCPServerSocketChild::~TCPServerSocketChild()
{
}
bool
TCPServerSocketChild::RecvCallbackAccept(PTCPSocketChild *psocket)
{
RefPtr<TCPSocketChild> socket = static_cast<TCPSocketChild*>(psocket);
nsresult rv = mServerSocket->AcceptChildSocket(socket);
NS_ENSURE_SUCCESS(rv, true);
return true;
}
void
TCPServerSocketChild::Close()
{
SendClose();
}
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TCPServerSocketChild_h
#define mozilla_dom_TCPServerSocketChild_h
#include "mozilla/net/PTCPServerSocketChild.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
#define TCPSERVERSOCKETCHILD_CID \
{ 0x41a77ec8, 0xfd86, 0x409e, { 0xae, 0xa9, 0xaf, 0x2c, 0xa4, 0x07, 0xef, 0x8e } }
class nsITCPServerSocketInternal;
namespace mozilla {
namespace dom {
class TCPServerSocket;
class TCPServerSocketChildBase : public nsISupports {
public:
NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketChildBase)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
void AddIPDLReference();
void ReleaseIPDLReference();
protected:
TCPServerSocketChildBase();
virtual ~TCPServerSocketChildBase();
RefPtr<TCPServerSocket> mServerSocket;
bool mIPCOpen;
};
class TCPServerSocketChild : public mozilla::net::PTCPServerSocketChild
, public TCPServerSocketChildBase
{
public:
NS_IMETHOD_(MozExternalRefCountType) Release() override;
TCPServerSocketChild(TCPServerSocket* aServerSocket, uint16_t aLocalPort,
uint16_t aBacklog, bool aUseArrayBuffers);
~TCPServerSocketChild();
void Close();
virtual bool RecvCallbackAccept(PTCPSocketChild *socket) override;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TCPServerSocketChild_h

View file

@ -0,0 +1,164 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIScriptSecurityManager.h"
#include "TCPServerSocket.h"
#include "TCPServerSocketParent.h"
#include "nsJSUtils.h"
#include "TCPSocketParent.h"
#include "mozilla/Unused.h"
#include "mozilla/AppProcessChecker.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/TabParent.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION(TCPServerSocketParent, mServerSocket)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPServerSocketParent)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketParent)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketParent)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
void
TCPServerSocketParent::ReleaseIPDLReference()
{
MOZ_ASSERT(mIPCOpen);
mIPCOpen = false;
this->Release();
}
void
TCPServerSocketParent::AddIPDLReference()
{
MOZ_ASSERT(!mIPCOpen);
mIPCOpen = true;
this->AddRef();
}
TCPServerSocketParent::TCPServerSocketParent(PNeckoParent* neckoParent,
uint16_t aLocalPort,
uint16_t aBacklog,
bool aUseArrayBuffers)
: mNeckoParent(neckoParent)
, mIPCOpen(false)
{
mServerSocket = new TCPServerSocket(nullptr, aLocalPort, aUseArrayBuffers, aBacklog);
mServerSocket->SetServerBridgeParent(this);
}
TCPServerSocketParent::~TCPServerSocketParent()
{
}
void
TCPServerSocketParent::Init()
{
NS_ENSURE_SUCCESS_VOID(mServerSocket->Init());
}
uint32_t
TCPServerSocketParent::GetAppId()
{
const PContentParent *content = Manager()->Manager();
if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
TabParent *tab = TabParent::GetFrom(browser);
return tab->OwnAppId();
} else {
return nsIScriptSecurityManager::UNKNOWN_APP_ID;
}
}
bool
TCPServerSocketParent::GetInIsolatedMozBrowser()
{
const PContentParent *content = Manager()->Manager();
if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
TabParent *tab = TabParent::GetFrom(browser);
return tab->IsIsolatedMozBrowserElement();
} else {
return false;
}
}
nsresult
TCPServerSocketParent::SendCallbackAccept(TCPSocketParent *socket)
{
socket->AddIPDLReference();
nsresult rv;
nsString host;
rv = socket->GetHost(host);
if (NS_FAILED(rv)) {
NS_ERROR("Failed to get host from nsITCPSocketParent");
return NS_ERROR_FAILURE;
}
uint16_t port;
rv = socket->GetPort(&port);
if (NS_FAILED(rv)) {
NS_ERROR("Failed to get port from nsITCPSocketParent");
return NS_ERROR_FAILURE;
}
if (mNeckoParent) {
if (mNeckoParent->SendPTCPSocketConstructor(socket, host, port)) {
mozilla::Unused << PTCPServerSocketParent::SendCallbackAccept(socket);
}
else {
NS_ERROR("Sending data from PTCPSocketParent was failed.");
}
}
else {
NS_ERROR("The member value for NeckoParent is wrong.");
}
return NS_OK;
}
bool
TCPServerSocketParent::RecvClose()
{
NS_ENSURE_TRUE(mServerSocket, true);
mServerSocket->Close();
return true;
}
void
TCPServerSocketParent::ActorDestroy(ActorDestroyReason why)
{
if (mServerSocket) {
mServerSocket->Close();
mServerSocket = nullptr;
}
mNeckoParent = nullptr;
}
bool
TCPServerSocketParent::RecvRequestDelete()
{
mozilla::Unused << Send__delete__(this);
return true;
}
void
TCPServerSocketParent::OnConnect(TCPServerSocketEvent* event)
{
RefPtr<TCPSocket> socket = event->Socket();
socket->SetAppIdAndBrowser(GetAppId(), GetInIsolatedMozBrowser());
RefPtr<TCPSocketParent> socketParent = new TCPSocketParent();
socketParent->SetSocket(socket);
socket->SetSocketBridgeParent(socketParent);
SendCallbackAccept(socketParent);
}
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,60 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TCPServerSocketParent_h
#define mozilla_dom_TCPServerSocketParent_h
#include "mozilla/net/PNeckoParent.h"
#include "mozilla/net/PTCPServerSocketParent.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
namespace mozilla {
namespace dom {
class TCPServerSocket;
class TCPServerSocketEvent;
class TCPSocketParent;
class TCPServerSocketParent : public mozilla::net::PTCPServerSocketParent
, public nsISupports
{
public:
NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketParent)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
TCPServerSocketParent(PNeckoParent* neckoParent, uint16_t aLocalPort,
uint16_t aBacklog, bool aUseArrayBuffers);
void Init();
virtual bool RecvClose() override;
virtual bool RecvRequestDelete() override;
uint32_t GetAppId();
bool GetInIsolatedMozBrowser();
void AddIPDLReference();
void ReleaseIPDLReference();
void OnConnect(TCPServerSocketEvent* event);
private:
~TCPServerSocketParent();
nsresult SendCallbackAccept(TCPSocketParent *socket);
virtual void ActorDestroy(ActorDestroyReason why) override;
PNeckoParent* mNeckoParent;
RefPtr<TCPServerSocket> mServerSocket;
bool mIPCOpen;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TCPServerSocketParent_h

1252
dom/network/TCPSocket.cpp Normal file

File diff suppressed because it is too large Load diff

267
dom/network/TCPSocket.h Normal file
View file

@ -0,0 +1,267 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TCPSocket_h
#define mozilla_dom_TCPSocket_h
#include "mozilla/dom/TCPSocketBinding.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "nsITransport.h"
#include "nsIStreamListener.h"
#include "nsIAsyncInputStream.h"
#include "nsISupportsImpl.h"
#include "nsIObserver.h"
#include "nsWeakReference.h"
#include "nsITCPSocketCallback.h"
#include "js/RootingAPI.h"
class nsISocketTransport;
class nsIInputStreamPump;
class nsIScriptableInputStream;
class nsIBinaryInputStream;
class nsIMultiplexInputStream;
class nsIAsyncStreamCopier;
class nsIInputStream;
class nsINetworkInfo;
namespace mozilla {
class ErrorResult;
namespace dom {
class DOMError;
struct ServerSocketOptions;
class TCPServerSocket;
class TCPSocketChild;
class TCPSocketParent;
// This interface is only used for legacy navigator.mozTCPSocket API compatibility.
class LegacyMozTCPSocket : public nsISupports
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(LegacyMozTCPSocket)
explicit LegacyMozTCPSocket(nsPIDOMWindowInner* aWindow);
already_AddRefed<TCPServerSocket>
Listen(uint16_t aPort,
const ServerSocketOptions& aOptions,
uint16_t aBacklog,
ErrorResult& aRv);
already_AddRefed<TCPSocket>
Open(const nsAString& aHost,
uint16_t aPort,
const SocketOptions& aOptions,
ErrorResult& aRv);
bool WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aReflector);
private:
virtual ~LegacyMozTCPSocket();
nsCOMPtr<nsIGlobalObject> mGlobal;
};
class TCPSocket final : public DOMEventTargetHelper
, public nsIStreamListener
, public nsITransportEventSink
, public nsIInputStreamCallback
, public nsIObserver
, public nsSupportsWeakReference
, public nsITCPSocketCallback
{
public:
TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost, uint16_t aPort,
bool aSsl, bool aUseArrayBuffers);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPSocket, DOMEventTargetHelper)
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSITRANSPORTEVENTSINK
NS_DECL_NSIINPUTSTREAMCALLBACK
NS_DECL_NSIOBSERVER
NS_DECL_NSITCPSOCKETCALLBACK
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
static bool ShouldTCPSocketExist(JSContext* aCx, JSObject* aGlobal);
void GetHost(nsAString& aHost);
uint32_t Port();
bool Ssl();
uint64_t BufferedAmount();
void Suspend();
void Resume(ErrorResult& aRv);
void Close();
void CloseImmediately();
bool Send(JSContext* aCx, const nsACString& aData, ErrorResult& aRv);
bool Send(JSContext* aCx,
const ArrayBuffer& aData,
uint32_t aByteOffset,
const Optional<uint32_t>& aByteLength,
ErrorResult& aRv);
TCPReadyState ReadyState();
TCPSocketBinaryType BinaryType();
void UpgradeToSecure(ErrorResult& aRv);
static already_AddRefed<TCPSocket>
Constructor(const GlobalObject& aGlobal,
const nsAString& aHost,
uint16_t aPort,
const SocketOptions& aOptions,
ErrorResult& aRv);
// Perform a send operation that's asssociated with a sequence number. Used in
// IPC scenarios to track the number of bytes buffered at any given time.
void SendWithTrackingNumber(const nsACString& aData,
const uint32_t& aTrackingNumber,
ErrorResult& aRv);
void SendWithTrackingNumber(JSContext* aCx,
const ArrayBuffer& aData,
uint32_t aByteOffset,
const Optional<uint32_t>& aByteLength,
const uint32_t& aTrackingNumber,
ErrorResult& aRv);
// Create a TCPSocket object from an existing low-level socket connection.
// Used by the TCPServerSocket implementation when a new connection is accepted.
static already_AddRefed<TCPSocket>
CreateAcceptedSocket(nsIGlobalObject* aGlobal, nsISocketTransport* aTransport, bool aUseArrayBuffers);
// Create a TCPSocket object from an existing child-side IPC actor.
// Used by the TCPServerSocketChild implementation when a new connection is accepted.
static already_AddRefed<TCPSocket>
CreateAcceptedSocket(nsIGlobalObject* aGlobal, TCPSocketChild* aSocketBridge, bool aUseArrayBuffers);
// Initialize this socket's associated app and browser information.
void SetAppIdAndBrowser(uint32_t aAppId, bool aInBrowser);
// Initialize this socket's associated IPC actor in the parent process.
void SetSocketBridgeParent(TCPSocketParent* aBridgeParent);
static bool SocketEnabled();
IMPL_EVENT_HANDLER(open);
IMPL_EVENT_HANDLER(drain);
IMPL_EVENT_HANDLER(data);
IMPL_EVENT_HANDLER(error);
IMPL_EVENT_HANDLER(close);
nsresult Init();
// Inform this socket that a buffered send() has completed sending.
void NotifyCopyComplete(nsresult aStatus);
// Initialize this socket from a low-level connection that hasn't connected yet
// (called from RecvOpenBind() in TCPSocketParent).
nsresult InitWithUnconnectedTransport(nsISocketTransport* aTransport);
private:
~TCPSocket();
// Initialize this socket with an existing IPC actor.
void InitWithSocketChild(TCPSocketChild* aBridge);
// Initialize this socket from an existing low-level connection.
nsresult InitWithTransport(nsISocketTransport* aTransport);
// Initialize the input/output streams for this socket object.
nsresult CreateStream();
// Initialize the asynchronous read operation from this socket's input stream.
nsresult CreateInputStreamPump();
// Send the contents of the provided input stream, which is assumed to be the given length
// for reporting and buffering purposes.
bool Send(nsIInputStream* aStream, uint32_t aByteLength);
// Begin an asynchronous copy operation if one is not already in progress.
nsresult EnsureCopying();
// Enable TLS on this socket.
void ActivateTLS();
// Dispatch an error event if necessary, then dispatch a "close" event.
nsresult MaybeReportErrorAndCloseIfOpen(nsresult status);
#ifdef MOZ_WIDGET_GONK
// Store and reset any saved network stats for this socket.
void SaveNetworkStats(bool aEnforce);
#endif
// Helper for FireDataStringEvent/FireDataArrayEvent.
nsresult FireDataEvent(JSContext* aCx, const nsAString& aType,
JS::Handle<JS::Value> aData);
// Helper for Close/CloseImmediately
void CloseHelper(bool waitForUnsentData);
TCPReadyState mReadyState;
// Whether to use strings or array buffers for the "data" event.
bool mUseArrayBuffers;
nsString mHost;
uint16_t mPort;
// Whether this socket is using a secure transport.
bool mSsl;
// The associated IPC actor in a child process.
RefPtr<TCPSocketChild> mSocketBridgeChild;
// The associated IPC actor in a parent process.
RefPtr<TCPSocketParent> mSocketBridgeParent;
// Raw socket streams
nsCOMPtr<nsISocketTransport> mTransport;
nsCOMPtr<nsIInputStream> mSocketInputStream;
nsCOMPtr<nsIOutputStream> mSocketOutputStream;
// Input stream machinery
nsCOMPtr<nsIInputStreamPump> mInputStreamPump;
nsCOMPtr<nsIScriptableInputStream> mInputStreamScriptable;
nsCOMPtr<nsIBinaryInputStream> mInputStreamBinary;
// Output stream machinery
nsCOMPtr<nsIMultiplexInputStream> mMultiplexStream;
nsCOMPtr<nsIAsyncStreamCopier> mMultiplexStreamCopier;
// Is there an async copy operation in progress?
bool mAsyncCopierActive;
// True if the buffer is full and a "drain" event is expected by the client.
bool mWaitingForDrain;
// The id of the window that created this socket.
uint64_t mInnerWindowID;
// The current number of buffered bytes. Only used in content processes when IPC is enabled.
uint64_t mBufferedAmount;
// The number of times this socket has had `Suspend` called without a corresponding `Resume`.
uint32_t mSuspendCount;
// The current sequence number (ie. number of send operations) that have been processed.
// This is used in the IPC scenario by the child process to filter out outdated notifications
// about the amount of buffered data present in the parent process.
uint32_t mTrackingNumber;
// True if this socket has been upgraded to secure after the initial connection,
// but the actual upgrade is waiting for an in-progress copy operation to complete.
bool mWaitingForStartTLS;
// The buffered data awaiting the TLS upgrade to finish.
nsTArray<nsCOMPtr<nsIInputStream>> mPendingDataAfterStartTLS;
// The data to be sent while AsyncCopier is still active.
nsTArray<nsCOMPtr<nsIInputStream>> mPendingDataWhileCopierActive;
bool mObserversActive;
#ifdef MOZ_WIDGET_GONK
// Number of bytes sent.
uint32_t mTxBytes;
// Number of bytes received.
uint32_t mRxBytes;
// The app that owns this socket.
uint32_t mAppId;
// Was this socket created inside of an isolated browser frame?
bool mInIsolatedMozBrowser;
// The name of the active network used by this socket.
nsCOMPtr<nsINetworkInfo> mActiveNetworkInfo;
#endif
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TCPSocket_h

View file

@ -0,0 +1,254 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <algorithm>
#include "TCPSocketChild.h"
#include "mozilla/Unused.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/dom/PBrowserChild.h"
#include "mozilla/dom/TabChild.h"
#include "nsITCPSocketCallback.h"
#include "TCPSocket.h"
#include "nsContentUtils.h"
#include "jsapi.h"
#include "jsfriendapi.h"
using mozilla::net::gNeckoChild;
namespace IPC {
bool
DeserializeArrayBuffer(JSContext* cx,
const InfallibleTArray<uint8_t>& aBuffer,
JS::MutableHandle<JS::Value> aVal)
{
mozilla::UniquePtr<uint8_t[], JS::FreePolicy> data(js_pod_malloc<uint8_t>(aBuffer.Length()));
if (!data)
return false;
memcpy(data.get(), aBuffer.Elements(), aBuffer.Length());
JSObject* obj = JS_NewArrayBufferWithContents(cx, aBuffer.Length(), data.get());
if (!obj)
return false;
// If JS_NewArrayBufferWithContents returns non-null, the ownership of
// the data is transfered to obj, so we release the ownership here.
mozilla::Unused << data.release();
aVal.setObject(*obj);
return true;
}
} // namespace IPC
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocketChildBase)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TCPSocketChildBase)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocket)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TCPSocketChildBase)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocket)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(TCPSocketChildBase)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketChildBase)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketChildBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketChildBase)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
TCPSocketChildBase::TCPSocketChildBase()
: mIPCOpen(false)
{
mozilla::HoldJSObjects(this);
}
TCPSocketChildBase::~TCPSocketChildBase()
{
mozilla::DropJSObjects(this);
}
NS_IMETHODIMP_(MozExternalRefCountType) TCPSocketChild::Release(void)
{
nsrefcnt refcnt = TCPSocketChildBase::Release();
if (refcnt == 1 && mIPCOpen) {
PTCPSocketChild::SendRequestDelete();
return 1;
}
return refcnt;
}
TCPSocketChild::TCPSocketChild(const nsAString& aHost, const uint16_t& aPort)
: mHost(aHost)
, mPort(aPort)
{
}
void
TCPSocketChild::SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL, bool aUseArrayBuffers)
{
mSocket = aSocket;
AddIPDLReference();
gNeckoChild->SendPTCPSocketConstructor(this, mHost, mPort);
MOZ_ASSERT(mFilterName.IsEmpty()); // Currently nobody should use this
PTCPSocketChild::SendOpen(mHost, mPort, aUseSSL, aUseArrayBuffers);
}
void
TCPSocketChild::SendWindowlessOpenBind(nsITCPSocketCallback* aSocket,
const nsACString& aRemoteHost, uint16_t aRemotePort,
const nsACString& aLocalHost, uint16_t aLocalPort,
bool aUseSSL)
{
mSocket = aSocket;
AddIPDLReference();
gNeckoChild->SendPTCPSocketConstructor(this,
NS_ConvertUTF8toUTF16(aRemoteHost),
aRemotePort);
PTCPSocketChild::SendOpenBind(nsCString(aRemoteHost), aRemotePort,
nsCString(aLocalHost), aLocalPort,
aUseSSL, true, mFilterName);
}
void
TCPSocketChildBase::ReleaseIPDLReference()
{
MOZ_ASSERT(mIPCOpen);
mIPCOpen = false;
mSocket = nullptr;
this->Release();
}
void
TCPSocketChildBase::AddIPDLReference()
{
MOZ_ASSERT(!mIPCOpen);
mIPCOpen = true;
this->AddRef();
}
TCPSocketChild::~TCPSocketChild()
{
}
bool
TCPSocketChild::RecvUpdateBufferedAmount(const uint32_t& aBuffered,
const uint32_t& aTrackingNumber)
{
mSocket->UpdateBufferedAmount(aBuffered, aTrackingNumber);
return true;
}
bool
TCPSocketChild::RecvCallback(const nsString& aType,
const CallbackData& aData,
const uint32_t& aReadyState)
{
mSocket->UpdateReadyState(aReadyState);
if (aData.type() == CallbackData::Tvoid_t) {
mSocket->FireEvent(aType);
} else if (aData.type() == CallbackData::TTCPError) {
const TCPError& err(aData.get_TCPError());
mSocket->FireErrorEvent(err.name(), err.message());
} else if (aData.type() == CallbackData::TSendableData) {
const SendableData& data = aData.get_SendableData();
if (data.type() == SendableData::TArrayOfuint8_t) {
mSocket->FireDataArrayEvent(aType, data.get_ArrayOfuint8_t());
} else if (data.type() == SendableData::TnsCString) {
mSocket->FireDataStringEvent(aType, data.get_nsCString());
} else {
MOZ_CRASH("Invalid callback data type!");
}
} else {
MOZ_CRASH("Invalid callback type!");
}
return true;
}
void
TCPSocketChild::SendSend(const nsACString& aData, uint32_t aTrackingNumber)
{
SendData(nsCString(aData), aTrackingNumber);
}
nsresult
TCPSocketChild::SendSend(const ArrayBuffer& aData,
uint32_t aByteOffset,
uint32_t aByteLength,
uint32_t aTrackingNumber)
{
uint32_t buflen = aData.Length();
uint32_t offset = std::min(buflen, aByteOffset);
uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
FallibleTArray<uint8_t> fallibleArr;
if (!fallibleArr.InsertElementsAt(0, aData.Data() + offset, nbytes, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
InfallibleTArray<uint8_t> arr;
arr.SwapElements(fallibleArr);
SendData(arr, aTrackingNumber);
return NS_OK;
}
NS_IMETHODIMP
TCPSocketChild::SendSendArray(nsTArray<uint8_t>& aArray, uint32_t aTrackingNumber)
{
SendData(aArray, aTrackingNumber);
return NS_OK;
}
void
TCPSocketChild::SetSocket(TCPSocket* aSocket)
{
mSocket = aSocket;
}
void
TCPSocketChild::GetHost(nsAString& aHost)
{
aHost = mHost;
}
void
TCPSocketChild::GetPort(uint16_t* aPort)
{
*aPort = mPort;
}
nsresult
TCPSocketChild::SetFilterName(const nsACString& aFilterName)
{
if (!mFilterName.IsEmpty()) {
// filter name can only be set once.
return NS_ERROR_FAILURE;
}
mFilterName = aFilterName;
return NS_OK;
}
bool
TCPSocketChild::RecvRequestDelete()
{
mozilla::Unused << Send__delete__(this);
return true;
}
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,90 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TCPSocketChild_h
#define mozilla_dom_TCPSocketChild_h
#include "mozilla/dom/TypedArray.h"
#include "mozilla/net/PTCPSocketChild.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
#include "js/TypeDecls.h"
class nsITCPSocketCallback;
namespace IPC {
bool
DeserializeArrayBuffer(JSContext* cx,
const InfallibleTArray<uint8_t>& aBuffer,
JS::MutableHandle<JS::Value> aVal);
}
namespace mozilla {
namespace dom {
class TCPSocket;
class TCPSocketChildBase : public nsISupports {
public:
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TCPSocketChildBase)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
void AddIPDLReference();
void ReleaseIPDLReference();
protected:
TCPSocketChildBase();
virtual ~TCPSocketChildBase();
nsCOMPtr<nsITCPSocketCallback> mSocket;
bool mIPCOpen;
};
class TCPSocketChild : public mozilla::net::PTCPSocketChild
, public TCPSocketChildBase
{
public:
NS_IMETHOD_(MozExternalRefCountType) Release() override;
TCPSocketChild(const nsAString& aHost, const uint16_t& aPort);
~TCPSocketChild();
void SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL, bool aUseArrayBuffers);
void SendWindowlessOpenBind(nsITCPSocketCallback* aSocket,
const nsACString& aRemoteHost, uint16_t aRemotePort,
const nsACString& aLocalHost, uint16_t aLocalPort,
bool aUseSSL);
NS_IMETHOD SendSendArray(nsTArray<uint8_t>& aArray,
uint32_t aTrackingNumber);
void SendSend(const nsACString& aData, uint32_t aTrackingNumber);
nsresult SendSend(const ArrayBuffer& aData,
uint32_t aByteOffset,
uint32_t aByteLength,
uint32_t aTrackingNumber);
void SendSendArray(nsTArray<uint8_t>* arr,
uint32_t trackingNumber);
void SetSocket(TCPSocket* aSocket);
void GetHost(nsAString& aHost);
void GetPort(uint16_t* aPort);
virtual bool RecvCallback(const nsString& aType,
const CallbackData& aData,
const uint32_t& aReadyState) override;
virtual bool RecvRequestDelete() override;
virtual bool RecvUpdateBufferedAmount(const uint32_t& aBufferred,
const uint32_t& aTrackingNumber) override;
nsresult SetFilterName(const nsACString& aFilterName);
private:
nsString mHost;
uint16_t mPort;
nsCString mFilterName;
};
} // namespace dom
} // namespace mozilla
#endif

View file

@ -0,0 +1,427 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TCPSocketParent.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "nsJSUtils.h"
#include "mozilla/Unused.h"
#include "mozilla/AppProcessChecker.h"
#include "mozilla/net/NeckoCommon.h"
#include "mozilla/net/PNeckoParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/TabParent.h"
#include "mozilla/HoldDropJSObjects.h"
#include "nsISocketTransportService.h"
#include "nsISocketTransport.h"
#include "nsIScriptSecurityManager.h"
#include "nsNetUtil.h"
namespace IPC {
//Defined in TCPSocketChild.cpp
extern bool
DeserializeArrayBuffer(JSContext* aCx,
const InfallibleTArray<uint8_t>& aBuffer,
JS::MutableHandle<JS::Value> aVal);
} // namespace IPC
namespace mozilla {
namespace net {
//
// set MOZ_LOG=TCPSocket:5
//
extern LazyLogModule gTCPSocketLog;
#define TCPSOCKET_LOG(args) MOZ_LOG(gTCPSocketLog, LogLevel::Debug, args)
#define TCPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gTCPSocketLog, LogLevel::Debug)
} // namespace net
namespace dom {
static void
FireInteralError(mozilla::net::PTCPSocketParent* aActor, uint32_t aLineNo)
{
mozilla::Unused <<
aActor->SendCallback(NS_LITERAL_STRING("onerror"),
TCPError(NS_LITERAL_STRING("InvalidStateError"), NS_LITERAL_STRING("Internal error")),
static_cast<uint32_t>(TCPReadyState::Connecting));
}
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketParentBase)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION(TCPSocketParentBase, mSocket)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketParentBase)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketParentBase)
TCPSocketParentBase::TCPSocketParentBase()
: mIPCOpen(false)
{
}
TCPSocketParentBase::~TCPSocketParentBase()
{
}
uint32_t
TCPSocketParent::GetAppId()
{
const PContentParent *content = Manager()->Manager();
if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
TabParent *tab = TabParent::GetFrom(browser);
return tab->OwnAppId();
} else {
return nsIScriptSecurityManager::UNKNOWN_APP_ID;
}
};
bool
TCPSocketParent::GetInIsolatedMozBrowser()
{
const PContentParent *content = Manager()->Manager();
if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
TabParent *tab = TabParent::GetFrom(browser);
return tab->IsIsolatedMozBrowserElement();
} else {
return false;
}
}
void
TCPSocketParentBase::ReleaseIPDLReference()
{
MOZ_ASSERT(mIPCOpen);
mIPCOpen = false;
this->Release();
}
void
TCPSocketParentBase::AddIPDLReference()
{
MOZ_ASSERT(!mIPCOpen);
mIPCOpen = true;
this->AddRef();
}
NS_IMETHODIMP_(MozExternalRefCountType) TCPSocketParent::Release(void)
{
nsrefcnt refcnt = TCPSocketParentBase::Release();
if (refcnt == 1 && mIPCOpen) {
mozilla::Unused << PTCPSocketParent::SendRequestDelete();
return 1;
}
return refcnt;
}
bool
TCPSocketParent::RecvOpen(const nsString& aHost, const uint16_t& aPort, const bool& aUseSSL,
const bool& aUseArrayBuffers)
{
// We don't have browser actors in xpcshell, and hence can't run automated
// tests without this loophole.
if (net::UsingNeckoIPCSecurity() &&
!AssertAppProcessPermission(Manager()->Manager(), "tcp-socket")) {
FireInteralError(this, __LINE__);
return true;
}
// Obtain App ID
uint32_t appId = GetAppId();
bool inIsolatedMozBrowser = GetInIsolatedMozBrowser();
mSocket = new TCPSocket(nullptr, aHost, aPort, aUseSSL, aUseArrayBuffers);
mSocket->SetAppIdAndBrowser(appId, inIsolatedMozBrowser);
mSocket->SetSocketBridgeParent(this);
NS_ENSURE_SUCCESS(mSocket->Init(), true);
return true;
}
bool
TCPSocketParent::RecvOpenBind(const nsCString& aRemoteHost,
const uint16_t& aRemotePort,
const nsCString& aLocalAddr,
const uint16_t& aLocalPort,
const bool& aUseSSL,
const bool& aUseArrayBuffers,
const nsCString& aFilter)
{
if (net::UsingNeckoIPCSecurity() &&
!AssertAppProcessPermission(Manager()->Manager(), "tcp-socket")) {
FireInteralError(this, __LINE__);
return true;
}
nsresult rv;
nsCOMPtr<nsISocketTransportService> sts =
do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
if (NS_FAILED(rv)) {
FireInteralError(this, __LINE__);
return true;
}
nsCOMPtr<nsISocketTransport> socketTransport;
rv = sts->CreateTransport(nullptr, 0,
aRemoteHost, aRemotePort,
nullptr, getter_AddRefs(socketTransport));
if (NS_FAILED(rv)) {
FireInteralError(this, __LINE__);
return true;
}
PRNetAddr prAddr;
if (PR_SUCCESS != PR_InitializeNetAddr(PR_IpAddrAny, aLocalPort, &prAddr)) {
FireInteralError(this, __LINE__);
return true;
}
if (PR_SUCCESS != PR_StringToNetAddr(aLocalAddr.BeginReading(), &prAddr)) {
FireInteralError(this, __LINE__);
return true;
}
mozilla::net::NetAddr addr;
PRNetAddrToNetAddr(&prAddr, &addr);
rv = socketTransport->Bind(&addr);
if (NS_FAILED(rv)) {
FireInteralError(this, __LINE__);
return true;
}
if (!aFilter.IsEmpty()) {
nsAutoCString contractId(NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX);
contractId.Append(aFilter);
nsCOMPtr<nsISocketFilterHandler> filterHandler =
do_GetService(contractId.get());
if (!filterHandler) {
NS_ERROR("Content doesn't have a valid filter");
FireInteralError(this, __LINE__);
return true;
}
rv = filterHandler->NewFilter(getter_AddRefs(mFilter));
if (NS_FAILED(rv)) {
NS_ERROR("Cannot create filter that content specified");
FireInteralError(this, __LINE__);
return true;
}
}
// Obtain App ID
uint32_t appId = nsIScriptSecurityManager::NO_APP_ID;
bool inIsolatedMozBrowser = false;
const PContentParent *content = Manager()->Manager();
if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
// appId's are for B2G only currently, where managees.Count() == 1
// This is not guaranteed currently in Desktop, so skip this there.
TabParent *tab = TabParent::GetFrom(browser);
appId = tab->OwnAppId();
inIsolatedMozBrowser = tab->IsIsolatedMozBrowserElement();
}
mSocket = new TCPSocket(nullptr, NS_ConvertUTF8toUTF16(aRemoteHost), aRemotePort, aUseSSL, aUseArrayBuffers);
mSocket->SetAppIdAndBrowser(appId, inIsolatedMozBrowser);
mSocket->SetSocketBridgeParent(this);
rv = mSocket->InitWithUnconnectedTransport(socketTransport);
NS_ENSURE_SUCCESS(rv, true);
return true;
}
bool
TCPSocketParent::RecvStartTLS()
{
NS_ENSURE_TRUE(mSocket, true);
ErrorResult rv;
mSocket->UpgradeToSecure(rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
}
return true;
}
bool
TCPSocketParent::RecvSuspend()
{
NS_ENSURE_TRUE(mSocket, true);
mSocket->Suspend();
return true;
}
bool
TCPSocketParent::RecvResume()
{
NS_ENSURE_TRUE(mSocket, true);
ErrorResult rv;
mSocket->Resume(rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
}
return true;
}
bool
TCPSocketParent::RecvData(const SendableData& aData,
const uint32_t& aTrackingNumber)
{
ErrorResult rv;
if (mFilter) {
mozilla::net::NetAddr addr; // dummy value
bool allowed;
MOZ_ASSERT(aData.type() == SendableData::TArrayOfuint8_t,
"Unsupported data type for filtering");
const InfallibleTArray<uint8_t>& data(aData.get_ArrayOfuint8_t());
nsresult nsrv = mFilter->FilterPacket(&addr, data.Elements(),
data.Length(),
nsISocketFilter::SF_OUTGOING,
&allowed);
// Reject sending of unallowed data
if (NS_WARN_IF(NS_FAILED(nsrv)) || !allowed) {
TCPSOCKET_LOG(("%s: Dropping outgoing TCP packet", __FUNCTION__));
return false;
}
}
switch (aData.type()) {
case SendableData::TArrayOfuint8_t: {
AutoSafeJSContext autoCx;
JS::Rooted<JS::Value> val(autoCx);
const nsTArray<uint8_t>& buffer = aData.get_ArrayOfuint8_t();
bool ok = IPC::DeserializeArrayBuffer(autoCx, buffer, &val);
NS_ENSURE_TRUE(ok, true);
RootedTypedArray<ArrayBuffer> data(autoCx);
data.Init(&val.toObject());
Optional<uint32_t> byteLength(buffer.Length());
mSocket->SendWithTrackingNumber(autoCx, data, 0, byteLength, aTrackingNumber, rv);
break;
}
case SendableData::TnsCString: {
const nsCString& strData = aData.get_nsCString();
mSocket->SendWithTrackingNumber(strData, aTrackingNumber, rv);
break;
}
default:
MOZ_CRASH("unexpected SendableData type");
}
NS_ENSURE_SUCCESS(rv.StealNSResult(), true);
return true;
}
bool
TCPSocketParent::RecvClose()
{
NS_ENSURE_TRUE(mSocket, true);
mSocket->Close();
return true;
}
void
TCPSocketParent::FireErrorEvent(const nsAString& aName, const nsAString& aType, TCPReadyState aReadyState)
{
SendEvent(NS_LITERAL_STRING("error"), TCPError(nsString(aName), nsString(aType)), aReadyState);
}
void
TCPSocketParent::FireEvent(const nsAString& aType, TCPReadyState aReadyState)
{
return SendEvent(aType, mozilla::void_t(), aReadyState);
}
void
TCPSocketParent::FireArrayBufferDataEvent(nsTArray<uint8_t>& aBuffer, TCPReadyState aReadyState)
{
InfallibleTArray<uint8_t> arr;
arr.SwapElements(aBuffer);
if (mFilter) {
bool allowed;
mozilla::net::NetAddr addr;
nsresult nsrv = mFilter->FilterPacket(&addr, arr.Elements(), arr.Length(),
nsISocketFilter::SF_INCOMING,
&allowed);
// receiving unallowed data, drop it.
if (NS_WARN_IF(NS_FAILED(nsrv)) || !allowed) {
TCPSOCKET_LOG(("%s: Dropping incoming TCP packet", __FUNCTION__));
return;
}
}
SendableData data(arr);
SendEvent(NS_LITERAL_STRING("data"), data, aReadyState);
}
void
TCPSocketParent::FireStringDataEvent(const nsACString& aData, TCPReadyState aReadyState)
{
SendableData data((nsCString(aData)));
MOZ_ASSERT(!mFilter, "Socket filtering doesn't support nsCString");
SendEvent(NS_LITERAL_STRING("data"), data, aReadyState);
}
void
TCPSocketParent::SendEvent(const nsAString& aType, CallbackData aData, TCPReadyState aReadyState)
{
if (mIPCOpen) {
mozilla::Unused << PTCPSocketParent::SendCallback(nsString(aType),
aData,
static_cast<uint32_t>(aReadyState));
}
}
void
TCPSocketParent::SetSocket(TCPSocket *socket)
{
mSocket = socket;
}
nsresult
TCPSocketParent::GetHost(nsAString& aHost)
{
if (!mSocket) {
NS_ERROR("No internal socket instance mSocket!");
return NS_ERROR_FAILURE;
}
mSocket->GetHost(aHost);
return NS_OK;
}
nsresult
TCPSocketParent::GetPort(uint16_t* aPort)
{
if (!mSocket) {
NS_ERROR("No internal socket instance mSocket!");
return NS_ERROR_FAILURE;
}
*aPort = mSocket->Port();
return NS_OK;
}
void
TCPSocketParent::ActorDestroy(ActorDestroyReason why)
{
if (mSocket) {
mSocket->Close();
}
mSocket = nullptr;
}
bool
TCPSocketParent::RecvRequestDelete()
{
mozilla::Unused << Send__delete__(this);
return true;
}
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,91 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TCPSocketParent_h
#define mozilla_dom_TCPSocketParent_h
#include "mozilla/dom/TCPSocketBinding.h"
#include "mozilla/net/PTCPSocketParent.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
#include "nsISocketFilter.h"
#include "js/TypeDecls.h"
#define TCPSOCKETPARENT_CID \
{ 0x4e7246c6, 0xa8b3, 0x426d, { 0x9c, 0x17, 0x76, 0xda, 0xb1, 0xe1, 0xe1, 0x4a } }
namespace mozilla {
namespace dom {
class TCPSocket;
class TCPSocketParentBase : public nsISupports
{
public:
NS_DECL_CYCLE_COLLECTION_CLASS(TCPSocketParentBase)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
void AddIPDLReference();
void ReleaseIPDLReference();
protected:
TCPSocketParentBase();
virtual ~TCPSocketParentBase();
RefPtr<TCPSocket> mSocket;
bool mIPCOpen;
};
class TCPSocketParent : public mozilla::net::PTCPSocketParent
, public TCPSocketParentBase
{
public:
NS_IMETHOD_(MozExternalRefCountType) Release() override;
TCPSocketParent() {}
virtual bool RecvOpen(const nsString& aHost, const uint16_t& aPort,
const bool& useSSL, const bool& aUseArrayBuffers) override;
virtual bool RecvOpenBind(const nsCString& aRemoteHost,
const uint16_t& aRemotePort,
const nsCString& aLocalAddr,
const uint16_t& aLocalPort,
const bool& aUseSSL,
const bool& aUseArrayBuffers,
const nsCString& aFilter) override;
virtual bool RecvStartTLS() override;
virtual bool RecvSuspend() override;
virtual bool RecvResume() override;
virtual bool RecvClose() override;
virtual bool RecvData(const SendableData& aData,
const uint32_t& aTrackingNumber) override;
virtual bool RecvRequestDelete() override;
bool GetInIsolatedMozBrowser();
void FireErrorEvent(const nsAString& aName, const nsAString& aType, TCPReadyState aReadyState);
void FireEvent(const nsAString& aType, TCPReadyState aReadyState);
void FireArrayBufferDataEvent(nsTArray<uint8_t>& aBuffer, TCPReadyState aReadyState);
void FireStringDataEvent(const nsACString& aData, TCPReadyState aReadyState);
void SetSocket(TCPSocket *socket);
nsresult GetHost(nsAString& aHost);
nsresult GetPort(uint16_t* aPort);
private:
virtual uint32_t GetAppId();
virtual void ActorDestroy(ActorDestroyReason why) override;
void SendEvent(const nsAString& aType, CallbackData aData, TCPReadyState aReadyState);
nsresult SetFilter(const nsCString& aFilter);
nsCOMPtr<nsISocketFilter> mFilter;
};
} // namespace dom
} // namespace mozilla
#endif

22
dom/network/Types.h Normal file
View file

@ -0,0 +1,22 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_network_Types_h
#define mozilla_dom_network_Types_h
namespace mozilla {
namespace hal {
class NetworkInformation;
} // namespace hal
template <class T>
class Observer;
typedef Observer<hal::NetworkInformation> NetworkObserver;
} // namespace mozilla
#endif // mozilla_dom_network_Types_h

762
dom/network/UDPSocket.cpp Normal file
View file

@ -0,0 +1,762 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "UDPSocket.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/ErrorEvent.h"
#include "mozilla/dom/UDPMessageEvent.h"
#include "mozilla/dom/UDPSocketBinding.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/net/DNS.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsINetAddr.h"
#include "nsStringStream.h"
namespace mozilla {
namespace dom {
NS_IMPL_ISUPPORTS(UDPSocket::ListenerProxy,
nsIUDPSocketListener,
nsIUDPSocketInternal)
NS_IMPL_CYCLE_COLLECTION_CLASS(UDPSocket)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpened)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpened)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mClosed)
tmp->CloseWithReason(NS_OK);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ADDREF_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(UDPSocket)
NS_INTERFACE_MAP_ENTRY(nsIUDPSocketListener)
NS_INTERFACE_MAP_ENTRY(nsIUDPSocketInternal)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
/* static */ already_AddRefed<UDPSocket>
UDPSocket::Constructor(const GlobalObject& aGlobal,
const UDPOptions& aOptions,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
if (!ownerWindow) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
bool addressReuse = aOptions.mAddressReuse;
bool loopback = aOptions.mLoopback;
nsCString remoteAddress;
if (aOptions.mRemoteAddress.WasPassed()) {
remoteAddress = NS_ConvertUTF16toUTF8(aOptions.mRemoteAddress.Value());
} else {
remoteAddress.SetIsVoid(true);
}
Nullable<uint16_t> remotePort;
if (aOptions.mRemotePort.WasPassed()) {
remotePort.SetValue(aOptions.mRemotePort.Value());
if (remotePort.Value() == 0) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
}
}
nsString localAddress;
if (aOptions.mLocalAddress.WasPassed()) {
localAddress = aOptions.mLocalAddress.Value();
// check if localAddress is a valid IPv4/6 address
NS_ConvertUTF16toUTF8 address(localAddress);
PRNetAddr prAddr;
PRStatus status = PR_StringToNetAddr(address.BeginReading(), &prAddr);
if (status != PR_SUCCESS) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
}
} else {
SetDOMStringToNull(localAddress);
}
Nullable<uint16_t> localPort;
if (aOptions.mLocalPort.WasPassed()) {
localPort.SetValue(aOptions.mLocalPort.Value());
if (localPort.Value() == 0) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
}
}
RefPtr<UDPSocket> socket = new UDPSocket(ownerWindow, remoteAddress, remotePort);
aRv = socket->Init(localAddress, localPort, addressReuse, loopback);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return socket.forget();
}
UDPSocket::UDPSocket(nsPIDOMWindowInner* aOwner,
const nsCString& aRemoteAddress,
const Nullable<uint16_t>& aRemotePort)
: DOMEventTargetHelper(aOwner)
, mRemoteAddress(aRemoteAddress)
, mRemotePort(aRemotePort)
, mAddressReuse(false)
, mLoopback(false)
, mReadyState(SocketReadyState::Opening)
{
MOZ_ASSERT(aOwner);
MOZ_ASSERT(aOwner->IsInnerWindow());
nsIDocument* aDoc = aOwner->GetExtantDoc();
if (aDoc) {
aDoc->DisallowBFCaching();
}
}
UDPSocket::~UDPSocket()
{
CloseWithReason(NS_OK);
}
JSObject*
UDPSocket::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return UDPSocketBinding::Wrap(aCx, this, aGivenProto);
}
void
UDPSocket::DisconnectFromOwner()
{
DOMEventTargetHelper::DisconnectFromOwner();
CloseWithReason(NS_OK);
}
already_AddRefed<Promise>
UDPSocket::Close()
{
MOZ_ASSERT(mClosed);
RefPtr<Promise> promise = mClosed;
if (mReadyState == SocketReadyState::Closed) {
return promise.forget();
}
CloseWithReason(NS_OK);
return promise.forget();
}
void
UDPSocket::CloseWithReason(nsresult aReason)
{
if (mReadyState == SocketReadyState::Closed) {
return;
}
if (mOpened) {
if (mReadyState == SocketReadyState::Opening) {
// reject openedPromise with AbortError if socket is closed without error
nsresult openFailedReason = NS_FAILED(aReason) ? aReason : NS_ERROR_DOM_ABORT_ERR;
mOpened->MaybeReject(openFailedReason);
}
}
mReadyState = SocketReadyState::Closed;
if (mListenerProxy) {
mListenerProxy->Disconnect();
mListenerProxy = nullptr;
}
if (mSocket) {
mSocket->Close();
mSocket = nullptr;
}
if (mSocketChild) {
mSocketChild->Close();
mSocketChild = nullptr;
}
if (mClosed) {
if (NS_SUCCEEDED(aReason)) {
mClosed->MaybeResolveWithUndefined();
} else {
mClosed->MaybeReject(aReason);
}
}
mPendingMcastCommands.Clear();
}
void
UDPSocket::JoinMulticastGroup(const nsAString& aMulticastGroupAddress,
ErrorResult& aRv)
{
if (mReadyState == SocketReadyState::Closed) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mReadyState == SocketReadyState::Opening) {
MulticastCommand joinCommand(MulticastCommand::Join, aMulticastGroupAddress);
mPendingMcastCommands.AppendElement(joinCommand);
return;
}
MOZ_ASSERT(mSocket || mSocketChild);
NS_ConvertUTF16toUTF8 address(aMulticastGroupAddress);
if (mSocket) {
MOZ_ASSERT(!mSocketChild);
aRv = mSocket->JoinMulticast(address, EmptyCString());
NS_WARNING_ASSERTION(!aRv.Failed(), "JoinMulticast failed");
return;
}
MOZ_ASSERT(mSocketChild);
aRv = mSocketChild->JoinMulticast(address, EmptyCString());
NS_WARNING_ASSERTION(!aRv.Failed(), "JoinMulticast failed");
}
void
UDPSocket::LeaveMulticastGroup(const nsAString& aMulticastGroupAddress,
ErrorResult& aRv)
{
if (mReadyState == SocketReadyState::Closed) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mReadyState == SocketReadyState::Opening) {
MulticastCommand leaveCommand(MulticastCommand::Leave, aMulticastGroupAddress);
mPendingMcastCommands.AppendElement(leaveCommand);
return;
}
MOZ_ASSERT(mSocket || mSocketChild);
nsCString address = NS_ConvertUTF16toUTF8(aMulticastGroupAddress);
if (mSocket) {
MOZ_ASSERT(!mSocketChild);
aRv = mSocket->LeaveMulticast(address, EmptyCString());
NS_WARNING_ASSERTION(!aRv.Failed(), "mSocket->LeaveMulticast failed");
return;
}
MOZ_ASSERT(mSocketChild);
aRv = mSocketChild->LeaveMulticast(address, EmptyCString());
NS_WARNING_ASSERTION(!aRv.Failed(), "mSocketChild->LeaveMulticast failed");
}
nsresult
UDPSocket::DoPendingMcastCommand()
{
MOZ_ASSERT(mReadyState == SocketReadyState::Open, "Multicast command can only be executed after socket opened");
for (uint32_t i = 0; i < mPendingMcastCommands.Length(); ++i) {
MulticastCommand& command = mPendingMcastCommands[i];
ErrorResult rv;
switch (command.mCommand) {
case MulticastCommand::Join: {
JoinMulticastGroup(command.mAddress, rv);
break;
}
case MulticastCommand::Leave: {
LeaveMulticastGroup(command.mAddress, rv);
break;
}
}
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
}
mPendingMcastCommands.Clear();
return NS_OK;
}
bool
UDPSocket::Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData,
const Optional<nsAString>& aRemoteAddress,
const Optional<Nullable<uint16_t>>& aRemotePort,
ErrorResult& aRv)
{
if (mReadyState != SocketReadyState::Open) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return false;
}
MOZ_ASSERT(mSocket || mSocketChild);
// If the remote address and port were not specified in the constructor or as arguments,
// throw InvalidAccessError.
nsCString remoteAddress;
if (aRemoteAddress.WasPassed()) {
remoteAddress = NS_ConvertUTF16toUTF8(aRemoteAddress.Value());
UDPSOCKET_LOG(("%s: Send to %s", __FUNCTION__, remoteAddress.get()));
} else if (!mRemoteAddress.IsVoid()) {
remoteAddress = mRemoteAddress;
UDPSOCKET_LOG(("%s: Send to %s", __FUNCTION__, remoteAddress.get()));
} else {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return false;
}
uint16_t remotePort;
if (aRemotePort.WasPassed() && !aRemotePort.Value().IsNull()) {
remotePort = aRemotePort.Value().Value();
} else if (!mRemotePort.IsNull()) {
remotePort = mRemotePort.Value();
} else {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return false;
}
nsCOMPtr<nsIInputStream> stream;
if (aData.IsBlob()) {
Blob& blob = aData.GetAsBlob();
blob.GetInternalStream(getter_AddRefs(stream), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return false;
}
} else {
nsresult rv;
nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return false;
}
if (aData.IsString()) {
NS_ConvertUTF16toUTF8 data(aData.GetAsString());
aRv = strStream->SetData(data.BeginReading(), data.Length());
} else if (aData.IsArrayBuffer()) {
const ArrayBuffer& data = aData.GetAsArrayBuffer();
data.ComputeLengthAndData();
aRv = strStream->SetData(reinterpret_cast<const char*>(data.Data()), data.Length());
} else {
const ArrayBufferView& data = aData.GetAsArrayBufferView();
data.ComputeLengthAndData();
aRv = strStream->SetData(reinterpret_cast<const char*>(data.Data()), data.Length());
}
if (NS_WARN_IF(aRv.Failed())) {
return false;
}
stream = strStream;
}
if (mSocket) {
aRv = mSocket->SendBinaryStream(remoteAddress, remotePort, stream);
} else if (mSocketChild) {
aRv = mSocketChild->SendBinaryStream(remoteAddress, remotePort, stream);
}
if (NS_WARN_IF(aRv.Failed())) {
return false;
}
return true;
}
nsresult
UDPSocket::InitLocal(const nsAString& aLocalAddress,
const uint16_t& aLocalPort)
{
nsresult rv;
nsCOMPtr<nsIUDPSocket> sock =
do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner(), &rv);
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull();
if (!principal) {
return NS_ERROR_FAILURE;
}
if (aLocalAddress.IsEmpty()) {
rv = sock->Init(aLocalPort, /* loopback = */ false, principal,
mAddressReuse, /* optionalArgc = */ 1);
} else {
PRNetAddr prAddr;
PR_InitializeNetAddr(PR_IpAddrAny, aLocalPort, &prAddr);
PR_StringToNetAddr(NS_ConvertUTF16toUTF8(aLocalAddress).BeginReading(), &prAddr);
UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, NS_ConvertUTF16toUTF8(aLocalAddress).get(), aLocalPort));
mozilla::net::NetAddr addr;
PRNetAddrToNetAddr(&prAddr, &addr);
rv = sock->InitWithAddress(&addr, principal, mAddressReuse,
/* optionalArgc = */ 1);
}
if (NS_FAILED(rv)) {
return rv;
}
rv = sock->SetMulticastLoopback(mLoopback);
if (NS_FAILED(rv)) {
return rv;
}
mSocket = sock;
// Get real local address and local port
nsCOMPtr<nsINetAddr> localAddr;
rv = mSocket->GetLocalAddr(getter_AddRefs(localAddr));
if (NS_FAILED(rv)) {
return rv;
}
nsCString localAddress;
rv = localAddr->GetAddress(localAddress);
if (NS_FAILED(rv)) {
return rv;
}
mLocalAddress = NS_ConvertUTF8toUTF16(localAddress);
uint16_t localPort;
rv = localAddr->GetPort(&localPort);
if (NS_FAILED(rv)) {
return rv;
}
mLocalPort.SetValue(localPort);
mListenerProxy = new ListenerProxy(this);
rv = mSocket->AsyncListen(mListenerProxy);
if (NS_FAILED(rv)) {
return rv;
}
mReadyState = SocketReadyState::Open;
rv = DoPendingMcastCommand();
if (NS_FAILED(rv)) {
return rv;
}
mOpened->MaybeResolveWithUndefined();
return NS_OK;
}
nsresult
UDPSocket::InitRemote(const nsAString& aLocalAddress,
const uint16_t& aLocalPort)
{
nsresult rv;
nsCOMPtr<nsIUDPSocketChild> sock =
do_CreateInstance("@mozilla.org/udp-socket-child;1", &rv);
if (NS_FAILED(rv)) {
return rv;
}
mListenerProxy = new ListenerProxy(this);
nsCOMPtr<nsIGlobalObject> obj = do_QueryInterface(GetOwner(), &rv);
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsIPrincipal> principal = obj->PrincipalOrNull();
if (!principal) {
return NS_ERROR_FAILURE;
}
rv = sock->Bind(mListenerProxy,
principal,
NS_ConvertUTF16toUTF8(aLocalAddress),
aLocalPort,
mAddressReuse,
mLoopback,
0,
0);
if (NS_FAILED(rv)) {
return rv;
}
mSocketChild = sock;
return NS_OK;
}
nsresult
UDPSocket::Init(const nsString& aLocalAddress,
const Nullable<uint16_t>& aLocalPort,
const bool& aAddressReuse,
const bool& aLoopback)
{
MOZ_ASSERT(!mSocket && !mSocketChild);
mLocalAddress = aLocalAddress;
mLocalPort = aLocalPort;
mAddressReuse = aAddressReuse;
mLoopback = aLoopback;
ErrorResult rv;
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
mOpened = Promise::Create(global, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
mClosed = Promise::Create(global, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
class OpenSocketRunnable final : public Runnable
{
public:
explicit OpenSocketRunnable(UDPSocket* aSocket) : mSocket(aSocket)
{ }
NS_IMETHOD Run() override
{
MOZ_ASSERT(mSocket);
if (mSocket->mReadyState != SocketReadyState::Opening) {
return NS_OK;
}
uint16_t localPort = 0;
if (!mSocket->mLocalPort.IsNull()) {
localPort = mSocket->mLocalPort.Value();
}
nsresult rv;
if (!XRE_IsParentProcess()) {
rv = mSocket->InitRemote(mSocket->mLocalAddress, localPort);
} else {
rv = mSocket->InitLocal(mSocket->mLocalAddress, localPort);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
mSocket->CloseWithReason(NS_ERROR_DOM_NETWORK_ERR);
}
return NS_OK;
}
private:
RefPtr<UDPSocket> mSocket;
};
nsCOMPtr<nsIRunnable> runnable = new OpenSocketRunnable(this);
return NS_DispatchToMainThread(runnable);
}
void
UDPSocket::HandleReceivedData(const nsACString& aRemoteAddress,
const uint16_t& aRemotePort,
const uint8_t* aData,
const uint32_t& aDataLength)
{
if (mReadyState != SocketReadyState::Open) {
return;
}
if (NS_FAILED(CheckInnerWindowCorrectness())) {
return;
}
if (NS_FAILED(DispatchReceivedData(aRemoteAddress, aRemotePort, aData, aDataLength))) {
CloseWithReason(NS_ERROR_TYPE_ERR);
}
}
nsresult
UDPSocket::DispatchReceivedData(const nsACString& aRemoteAddress,
const uint16_t& aRemotePort,
const uint8_t* aData,
const uint32_t& aDataLength)
{
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
return NS_ERROR_FAILURE;
}
JSContext* cx = jsapi.cx();
// Copy packet data to ArrayBuffer
JS::Rooted<JSObject*> arrayBuf(cx, ArrayBuffer::Create(cx, aDataLength, aData));
if (NS_WARN_IF(!arrayBuf)) {
return NS_ERROR_FAILURE;
}
JS::Rooted<JS::Value> jsData(cx, JS::ObjectValue(*arrayBuf));
// Create DOM event
RootedDictionary<UDPMessageEventInit> init(cx);
init.mRemoteAddress = NS_ConvertUTF8toUTF16(aRemoteAddress);
init.mRemotePort = aRemotePort;
init.mData = jsData;
RefPtr<UDPMessageEvent> udpEvent =
UDPMessageEvent::Constructor(this, NS_LITERAL_STRING("message"), init);
if (NS_WARN_IF(!udpEvent)) {
return NS_ERROR_FAILURE;
}
udpEvent->SetTrusted(true);
RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(this, udpEvent);
return asyncDispatcher->PostDOMEvent();
}
// nsIUDPSocketListener
NS_IMETHODIMP
UDPSocket::OnPacketReceived(nsIUDPSocket* aSocket, nsIUDPMessage* aMessage)
{
// nsIUDPSocketListener callbacks should be invoked on main thread.
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
// Create appropriate JS object for message
FallibleTArray<uint8_t>& buffer = aMessage->GetDataAsTArray();
nsCOMPtr<nsINetAddr> addr;
if (NS_WARN_IF(NS_FAILED(aMessage->GetFromAddr(getter_AddRefs(addr))))) {
return NS_OK;
}
nsCString remoteAddress;
if (NS_WARN_IF(NS_FAILED(addr->GetAddress(remoteAddress)))) {
return NS_OK;
}
uint16_t remotePort;
if (NS_WARN_IF(NS_FAILED(addr->GetPort(&remotePort)))) {
return NS_OK;
}
HandleReceivedData(remoteAddress, remotePort, buffer.Elements(), buffer.Length());
return NS_OK;
}
NS_IMETHODIMP
UDPSocket::OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus)
{
// nsIUDPSocketListener callbacks should be invoked on main thread.
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
CloseWithReason(aStatus);
return NS_OK;
}
// nsIUDPSocketInternal
NS_IMETHODIMP
UDPSocket::CallListenerError(const nsACString& aMessage,
const nsACString& aFilename,
uint32_t aLineNumber)
{
CloseWithReason(NS_ERROR_DOM_NETWORK_ERR);
return NS_OK;
}
NS_IMETHODIMP
UDPSocket::CallListenerReceivedData(const nsACString& aRemoteAddress,
uint16_t aRemotePort,
const uint8_t* aData,
uint32_t aDataLength)
{
HandleReceivedData(aRemoteAddress, aRemotePort, aData, aDataLength);
return NS_OK;
}
NS_IMETHODIMP
UDPSocket::CallListenerOpened()
{
if (mReadyState != SocketReadyState::Opening) {
return NS_OK;
}
MOZ_ASSERT(mSocketChild);
// Get real local address and local port
nsCString localAddress;
mSocketChild->GetLocalAddress(localAddress);
mLocalAddress = NS_ConvertUTF8toUTF16(localAddress);
uint16_t localPort;
mSocketChild->GetLocalPort(&localPort);
mLocalPort.SetValue(localPort);
mReadyState = SocketReadyState::Open;
nsresult rv = DoPendingMcastCommand();
if (NS_WARN_IF(NS_FAILED(rv))) {
CloseWithReason(rv);
return NS_OK;
}
mOpened->MaybeResolveWithUndefined();
return NS_OK;
}
NS_IMETHODIMP
UDPSocket::CallListenerConnected()
{
// This shouldn't be called here.
MOZ_CRASH();
return NS_OK;
}
NS_IMETHODIMP
UDPSocket::CallListenerClosed()
{
CloseWithReason(NS_OK);
return NS_OK;
}
} // namespace dom
} // namespace mozilla

232
dom/network/UDPSocket.h Normal file
View file

@ -0,0 +1,232 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_UDPSocket_h__
#define mozilla_dom_UDPSocket_h__
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/SocketCommonBinding.h"
#include "nsIUDPSocket.h"
#include "nsIUDPSocketChild.h"
#include "nsTArray.h"
struct JSContext;
//
// set MOZ_LOG=UDPSocket:5
//
namespace mozilla {
namespace net {
extern LazyLogModule gUDPSocketLog;
#define UDPSOCKET_LOG(args) MOZ_LOG(gUDPSocketLog, LogLevel::Debug, args)
#define UDPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gUDPSocketLog, LogLevel::Debug)
} // namespace net
namespace dom {
struct UDPOptions;
class StringOrBlobOrArrayBufferOrArrayBufferView;
class UDPSocket final : public DOMEventTargetHelper
, public nsIUDPSocketListener
, public nsIUDPSocketInternal
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_DECL_NSIUDPSOCKETLISTENER
NS_DECL_NSIUDPSOCKETINTERNAL
NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
public:
nsPIDOMWindowInner*
GetParentObject() const
{
return GetOwner();
}
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
virtual void
DisconnectFromOwner() override;
static already_AddRefed<UDPSocket>
Constructor(const GlobalObject& aGlobal, const UDPOptions& aOptions, ErrorResult& aRv);
void
GetLocalAddress(nsString& aRetVal) const
{
aRetVal = mLocalAddress;
}
Nullable<uint16_t>
GetLocalPort() const
{
return mLocalPort;
}
void
GetRemoteAddress(nsString& aRetVal) const
{
if (mRemoteAddress.IsVoid()) {
SetDOMStringToNull(aRetVal);
return;
}
aRetVal = NS_ConvertUTF8toUTF16(mRemoteAddress);
}
Nullable<uint16_t>
GetRemotePort() const
{
return mRemotePort;
}
bool
AddressReuse() const
{
return mAddressReuse;
}
bool
Loopback() const
{
return mLoopback;
}
SocketReadyState
ReadyState() const
{
return mReadyState;
}
Promise*
Opened() const
{
return mOpened;
}
Promise*
Closed() const
{
return mClosed;
}
IMPL_EVENT_HANDLER(message)
already_AddRefed<Promise>
Close();
void
JoinMulticastGroup(const nsAString& aMulticastGroupAddress, ErrorResult& aRv);
void
LeaveMulticastGroup(const nsAString& aMulticastGroupAddress, ErrorResult& aRv);
bool
Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData,
const Optional<nsAString>& aRemoteAddress,
const Optional<Nullable<uint16_t>>& aRemotePort,
ErrorResult& aRv);
private:
class ListenerProxy : public nsIUDPSocketListener
, public nsIUDPSocketInternal
{
public:
NS_DECL_ISUPPORTS
NS_FORWARD_SAFE_NSIUDPSOCKETLISTENER(mSocket)
NS_FORWARD_SAFE_NSIUDPSOCKETINTERNAL(mSocket)
explicit ListenerProxy(UDPSocket* aSocket)
: mSocket(aSocket)
{
}
void Disconnect()
{
mSocket = nullptr;
}
private:
virtual ~ListenerProxy() {}
UDPSocket* mSocket;
};
UDPSocket(nsPIDOMWindowInner* aOwner,
const nsCString& aRemoteAddress,
const Nullable<uint16_t>& aRemotePort);
virtual ~UDPSocket();
nsresult
Init(const nsString& aLocalAddress,
const Nullable<uint16_t>& aLocalPort,
const bool& aAddressReuse,
const bool& aLoopback);
nsresult
InitLocal(const nsAString& aLocalAddress, const uint16_t& aLocalPort);
nsresult
InitRemote(const nsAString& aLocalAddress, const uint16_t& aLocalPort);
void
HandleReceivedData(const nsACString& aRemoteAddress,
const uint16_t& aRemotePort,
const uint8_t* aData,
const uint32_t& aDataLength);
nsresult
DispatchReceivedData(const nsACString& aRemoteAddress,
const uint16_t& aRemotePort,
const uint8_t* aData,
const uint32_t& aDataLength);
void
CloseWithReason(nsresult aReason);
nsresult
DoPendingMcastCommand();
nsString mLocalAddress;
Nullable<uint16_t> mLocalPort;
nsCString mRemoteAddress;
Nullable<uint16_t> mRemotePort;
bool mAddressReuse;
bool mLoopback;
SocketReadyState mReadyState;
RefPtr<Promise> mOpened;
RefPtr<Promise> mClosed;
nsCOMPtr<nsIUDPSocket> mSocket;
nsCOMPtr<nsIUDPSocketChild> mSocketChild;
RefPtr<ListenerProxy> mListenerProxy;
struct MulticastCommand {
enum CommandType { Join, Leave };
MulticastCommand(CommandType aCommand, const nsAString& aAddress)
: mCommand(aCommand), mAddress(aAddress)
{ }
CommandType mCommand;
nsString mAddress;
};
nsTArray<MulticastCommand> mPendingMcastCommands;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_UDPSocket_h__

View file

@ -0,0 +1,404 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "UDPSocketChild.h"
#include "mozilla/Unused.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsIIPCBackgroundChildCreateCallback.h"
using mozilla::net::gNeckoChild;
namespace mozilla {
namespace dom {
NS_IMPL_ISUPPORTS(UDPSocketChildBase, nsIUDPSocketChild)
UDPSocketChildBase::UDPSocketChildBase()
: mIPCOpen(false)
{
}
UDPSocketChildBase::~UDPSocketChildBase()
{
}
void
UDPSocketChildBase::ReleaseIPDLReference()
{
MOZ_ASSERT(mIPCOpen);
mIPCOpen = false;
mSocket = nullptr;
this->Release();
}
void
UDPSocketChildBase::AddIPDLReference()
{
MOZ_ASSERT(!mIPCOpen);
mIPCOpen = true;
this->AddRef();
}
NS_IMETHODIMP_(MozExternalRefCountType) UDPSocketChild::Release(void)
{
nsrefcnt refcnt = UDPSocketChildBase::Release();
if (refcnt == 1 && mIPCOpen) {
PUDPSocketChild::SendRequestDelete();
return 1;
}
return refcnt;
}
UDPSocketChild::UDPSocketChild()
:mBackgroundManager(nullptr)
,mLocalPort(0)
{
}
UDPSocketChild::~UDPSocketChild()
{
}
class UDPSocketBackgroundChildCallback final :
public nsIIPCBackgroundChildCreateCallback
{
bool* mDone;
public:
explicit UDPSocketBackgroundChildCallback(bool* aDone)
: mDone(aDone)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(mDone);
MOZ_ASSERT(!*mDone);
}
NS_DECL_ISUPPORTS
private:
~UDPSocketBackgroundChildCallback()
{ }
virtual void
ActorCreated(PBackgroundChild* aActor) override
{
*mDone = true;
}
virtual void
ActorFailed() override
{
*mDone = true;
}
};
NS_IMPL_ISUPPORTS(UDPSocketBackgroundChildCallback, nsIIPCBackgroundChildCreateCallback)
nsresult
UDPSocketChild::CreatePBackgroundSpinUntilDone()
{
using mozilla::ipc::BackgroundChild;
// Spinning the event loop in MainThread would be dangerous
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!BackgroundChild::GetForCurrentThread());
bool done = false;
nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback =
new UDPSocketBackgroundChildCallback(&done);
if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(callback))) {
return NS_ERROR_FAILURE;
}
nsIThread* thread = NS_GetCurrentThread();
while (!done) {
if (NS_WARN_IF(!NS_ProcessNextEvent(thread, true /* aMayWait */))) {
return NS_ERROR_FAILURE;
}
}
if (NS_WARN_IF(!BackgroundChild::GetForCurrentThread())) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
// nsIUDPSocketChild Methods
NS_IMETHODIMP
UDPSocketChild::SetBackgroundSpinsEvents()
{
using mozilla::ipc::BackgroundChild;
PBackgroundChild* existingBackgroundChild =
BackgroundChild::GetForCurrentThread();
// If it's not spun up yet, block until it is, and retry
if (!existingBackgroundChild) {
nsresult rv = CreatePBackgroundSpinUntilDone();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
existingBackgroundChild =
BackgroundChild::GetForCurrentThread();
MOZ_ASSERT(existingBackgroundChild);
}
// By now PBackground is guaranteed to be/have-been up
mBackgroundManager = existingBackgroundChild;
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::Bind(nsIUDPSocketInternal* aSocket,
nsIPrincipal* aPrincipal,
const nsACString& aHost,
uint16_t aPort,
bool aAddressReuse,
bool aLoopback,
uint32_t recvBufferSize,
uint32_t sendBufferSize)
{
UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
NS_ENSURE_ARG(aSocket);
mSocket = aSocket;
AddIPDLReference();
if (mBackgroundManager) {
// If we want to support a passed-in principal here we'd need to
// convert it to a PrincipalInfo
MOZ_ASSERT(!aPrincipal);
mBackgroundManager->SendPUDPSocketConstructor(this, void_t(), mFilterName);
} else {
gNeckoChild->SendPUDPSocketConstructor(this, IPC::Principal(aPrincipal),
mFilterName);
}
SendBind(UDPAddressInfo(nsCString(aHost), aPort), aAddressReuse, aLoopback,
recvBufferSize, sendBufferSize);
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::Connect(nsIUDPSocketInternal* aSocket, const nsACString & aHost, uint16_t aPort)
{
UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
mSocket = aSocket;
SendConnect(UDPAddressInfo(nsCString(aHost), aPort));
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::Close()
{
SendClose();
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::Send(const nsACString& aHost,
uint16_t aPort,
const uint8_t* aData,
uint32_t aByteLength)
{
NS_ENSURE_ARG(aData);
UDPSOCKET_LOG(("%s: %s:%u - %u bytes", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort, aByteLength));
return SendDataInternal(UDPSocketAddr(UDPAddressInfo(nsCString(aHost), aPort)),
aData, aByteLength);
}
NS_IMETHODIMP
UDPSocketChild::SendWithAddr(nsINetAddr* aAddr,
const uint8_t* aData,
uint32_t aByteLength)
{
NS_ENSURE_ARG(aAddr);
NS_ENSURE_ARG(aData);
NetAddr addr;
aAddr->GetNetAddr(&addr);
UDPSOCKET_LOG(("%s: %u bytes", __FUNCTION__, aByteLength));
return SendDataInternal(UDPSocketAddr(addr), aData, aByteLength);
}
NS_IMETHODIMP
UDPSocketChild::SendWithAddress(const NetAddr* aAddr,
const uint8_t* aData,
uint32_t aByteLength)
{
NS_ENSURE_ARG(aAddr);
NS_ENSURE_ARG(aData);
UDPSOCKET_LOG(("%s: %u bytes", __FUNCTION__, aByteLength));
return SendDataInternal(UDPSocketAddr(*aAddr), aData, aByteLength);
}
nsresult
UDPSocketChild::SendDataInternal(const UDPSocketAddr& aAddr,
const uint8_t* aData,
const uint32_t aByteLength)
{
NS_ENSURE_ARG(aData);
FallibleTArray<uint8_t> fallibleArray;
if (!fallibleArray.InsertElementsAt(0, aData, aByteLength, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
InfallibleTArray<uint8_t> array;
array.SwapElements(fallibleArray);
SendOutgoingData(array, aAddr);
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::SendBinaryStream(const nsACString& aHost,
uint16_t aPort,
nsIInputStream* aStream)
{
NS_ENSURE_ARG(aStream);
OptionalInputStreamParams stream;
nsTArray<mozilla::ipc::FileDescriptor> fds;
SerializeInputStream(aStream, stream, fds);
MOZ_ASSERT(fds.IsEmpty());
UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
SendOutgoingData(UDPData(stream), UDPSocketAddr(UDPAddressInfo(nsCString(aHost), aPort)));
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::JoinMulticast(const nsACString& aMulticastAddress,
const nsACString& aInterface)
{
SendJoinMulticast(nsCString(aMulticastAddress), nsCString(aInterface));
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::LeaveMulticast(const nsACString& aMulticastAddress,
const nsACString& aInterface)
{
SendLeaveMulticast(nsCString(aMulticastAddress), nsCString(aInterface));
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::GetLocalPort(uint16_t* aLocalPort)
{
NS_ENSURE_ARG_POINTER(aLocalPort);
*aLocalPort = mLocalPort;
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::GetLocalAddress(nsACString& aLocalAddress)
{
aLocalAddress = mLocalAddress;
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::SetFilterName(const nsACString& aFilterName)
{
if (!mFilterName.IsEmpty()) {
// filter name can only be set once.
return NS_ERROR_FAILURE;
}
mFilterName = aFilterName;
return NS_OK;
}
NS_IMETHODIMP
UDPSocketChild::GetFilterName(nsACString& aFilterName)
{
aFilterName = mFilterName;
return NS_OK;
}
// PUDPSocketChild Methods
bool
UDPSocketChild::RecvCallbackOpened(const UDPAddressInfo& aAddressInfo)
{
mLocalAddress = aAddressInfo.addr();
mLocalPort = aAddressInfo.port();
UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, mLocalAddress.get(), mLocalPort));
nsresult rv = mSocket->CallListenerOpened();
mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
return true;
}
// PUDPSocketChild Methods
bool
UDPSocketChild::RecvCallbackConnected(const UDPAddressInfo& aAddressInfo)
{
mLocalAddress = aAddressInfo.addr();
mLocalPort = aAddressInfo.port();
UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, mLocalAddress.get(), mLocalPort));
nsresult rv = mSocket->CallListenerConnected();
mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
return true;
}
bool
UDPSocketChild::RecvCallbackClosed()
{
nsresult rv = mSocket->CallListenerClosed();
mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
return true;
}
bool
UDPSocketChild::RecvCallbackReceivedData(const UDPAddressInfo& aAddressInfo,
InfallibleTArray<uint8_t>&& aData)
{
UDPSOCKET_LOG(("%s: %s:%u length %u", __FUNCTION__,
aAddressInfo.addr().get(), aAddressInfo.port(), aData.Length()));
nsresult rv = mSocket->CallListenerReceivedData(aAddressInfo.addr(), aAddressInfo.port(),
aData.Elements(), aData.Length());
mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
return true;
}
bool
UDPSocketChild::RecvCallbackError(const nsCString& aMessage,
const nsCString& aFilename,
const uint32_t& aLineNumber)
{
UDPSOCKET_LOG(("%s: %s:%s:%u", __FUNCTION__, aMessage.get(), aFilename.get(), aLineNumber));
nsresult rv = mSocket->CallListenerError(aMessage, aFilename, aLineNumber);
mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
return true;
}
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,70 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_UDPSocketChild_h__
#define mozilla_dom_UDPSocketChild_h__
#include "mozilla/net/PUDPSocketChild.h"
#include "nsIUDPSocketChild.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
#define UDPSOCKETCHILD_CID \
{0xb47e5a0f, 0xd384, 0x48ef, { 0x88, 0x85, 0x42, 0x59, 0x79, 0x3d, 0x9c, 0xf0 }}
namespace mozilla {
namespace dom {
class UDPSocketChildBase : public nsIUDPSocketChild {
public:
NS_DECL_ISUPPORTS
void AddIPDLReference();
void ReleaseIPDLReference();
protected:
UDPSocketChildBase();
virtual ~UDPSocketChildBase();
nsCOMPtr<nsIUDPSocketInternal> mSocket;
bool mIPCOpen;
};
class UDPSocketChild : public mozilla::net::PUDPSocketChild
, public UDPSocketChildBase
{
public:
NS_DECL_NSIUDPSOCKETCHILD
NS_IMETHOD_(MozExternalRefCountType) Release() override;
UDPSocketChild();
virtual ~UDPSocketChild();
nsresult CreatePBackgroundSpinUntilDone();
virtual bool RecvCallbackOpened(const UDPAddressInfo& aAddressInfo) override;
virtual bool RecvCallbackConnected(const UDPAddressInfo& aAddressInfo) override;
virtual bool RecvCallbackClosed() override;
virtual bool RecvCallbackReceivedData(const UDPAddressInfo& aAddressInfo,
InfallibleTArray<uint8_t>&& aData) override;
virtual bool RecvCallbackError(const nsCString& aMessage,
const nsCString& aFilename,
const uint32_t& aLineNumber) override;
private:
nsresult SendDataInternal(const UDPSocketAddr& aAddr,
const uint8_t* aData,
const uint32_t aByteLength);
mozilla::ipc::PBackgroundChild* mBackgroundManager;
uint16_t mLocalPort;
nsCString mLocalAddress;
nsCString mFilterName;
};
} // namespace dom
} // namespace mozilla
#endif // !defined(mozilla_dom_UDPSocketChild_h__)

View file

@ -0,0 +1,606 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIServiceManager.h"
#include "UDPSocketParent.h"
#include "nsComponentManagerUtils.h"
#include "nsIUDPSocket.h"
#include "nsINetAddr.h"
#include "mozilla/AppProcessChecker.h"
#include "mozilla/Unused.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/net/DNS.h"
#include "mozilla/net/NeckoCommon.h"
#include "mozilla/net/PNeckoParent.h"
#include "nsIPermissionManager.h"
#include "nsIScriptSecurityManager.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "mtransport/runnable_utils.h"
namespace mozilla {
namespace dom {
NS_IMPL_ISUPPORTS(UDPSocketParent, nsIUDPSocketListener)
UDPSocketParent::UDPSocketParent(PBackgroundParent* aManager)
: mBackgroundManager(aManager)
, mNeckoManager(nullptr)
, mIPCOpen(true)
{
}
UDPSocketParent::UDPSocketParent(PNeckoParent* aManager)
: mBackgroundManager(nullptr)
, mNeckoManager(aManager)
, mIPCOpen(true)
{
}
UDPSocketParent::~UDPSocketParent()
{
}
bool
UDPSocketParent::Init(const IPC::Principal& aPrincipal,
const nsACString& aFilter)
{
MOZ_ASSERT_IF(mBackgroundManager, !aPrincipal);
// will be used once we move all UDPSocket to PBackground, or
// if we add in Principal checking for mtransport
Unused << mBackgroundManager;
mPrincipal = aPrincipal;
if (net::UsingNeckoIPCSecurity() &&
mPrincipal &&
!ContentParent::IgnoreIPCPrincipal()) {
if (mNeckoManager) {
if (!AssertAppPrincipal(mNeckoManager->Manager(), mPrincipal)) {
return false;
}
} else {
// PBackground is (for now) using a STUN filter for verification
// it's not being used for DoS
}
nsCOMPtr<nsIPermissionManager> permMgr =
services::GetPermissionManager();
if (!permMgr) {
NS_WARNING("No PermissionManager available!");
return false;
}
uint32_t permission = nsIPermissionManager::DENY_ACTION;
permMgr->TestExactPermissionFromPrincipal(mPrincipal, "udp-socket",
&permission);
if (permission != nsIPermissionManager::ALLOW_ACTION) {
return false;
}
}
if (!aFilter.IsEmpty()) {
nsAutoCString contractId(NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX);
contractId.Append(aFilter);
nsCOMPtr<nsISocketFilterHandler> filterHandler =
do_GetService(contractId.get());
if (filterHandler) {
nsresult rv = filterHandler->NewFilter(getter_AddRefs(mFilter));
if (NS_FAILED(rv)) {
printf_stderr("Cannot create filter that content specified. "
"filter name: %s, error code: %u.", aFilter.BeginReading(), static_cast<uint32_t>(rv));
return false;
}
} else {
printf_stderr("Content doesn't have a valid filter. "
"filter name: %s.", aFilter.BeginReading());
return false;
}
}
// We don't have browser actors in xpcshell, and hence can't run automated
// tests without this loophole.
if (net::UsingNeckoIPCSecurity() && !mFilter &&
(!mPrincipal || ContentParent::IgnoreIPCPrincipal())) {
return false;
}
return true;
}
// PUDPSocketParent methods
bool
UDPSocketParent::RecvBind(const UDPAddressInfo& aAddressInfo,
const bool& aAddressReuse, const bool& aLoopback,
const uint32_t& recvBufferSize,
const uint32_t& sendBufferSize)
{
UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, aAddressInfo.addr().get(), aAddressInfo.port()));
if (NS_FAILED(BindInternal(aAddressInfo.addr(), aAddressInfo.port(),
aAddressReuse, aLoopback, recvBufferSize,
sendBufferSize))) {
FireInternalError(__LINE__);
return true;
}
nsCOMPtr<nsINetAddr> localAddr;
mSocket->GetLocalAddr(getter_AddRefs(localAddr));
nsCString addr;
if (NS_FAILED(localAddr->GetAddress(addr))) {
FireInternalError(__LINE__);
return true;
}
uint16_t port;
if (NS_FAILED(localAddr->GetPort(&port))) {
FireInternalError(__LINE__);
return true;
}
UDPSOCKET_LOG(("%s: SendCallbackOpened: %s:%u", __FUNCTION__, addr.get(), port));
mozilla::Unused << SendCallbackOpened(UDPAddressInfo(addr, port));
return true;
}
nsresult
UDPSocketParent::BindInternal(const nsCString& aHost, const uint16_t& aPort,
const bool& aAddressReuse, const bool& aLoopback,
const uint32_t& recvBufferSize,
const uint32_t& sendBufferSize)
{
nsresult rv;
UDPSOCKET_LOG(("%s: [this=%p] %s:%u addressReuse: %d loopback: %d recvBufferSize: %lu, sendBufferSize: %lu",
__FUNCTION__, this, nsCString(aHost).get(), aPort,
aAddressReuse, aLoopback, recvBufferSize, sendBufferSize));
nsCOMPtr<nsIUDPSocket> sock =
do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (aHost.IsEmpty()) {
rv = sock->Init(aPort, false, mPrincipal, aAddressReuse,
/* optional_argc = */ 1);
} else {
PRNetAddr prAddr;
PR_InitializeNetAddr(PR_IpAddrAny, aPort, &prAddr);
PRStatus status = PR_StringToNetAddr(aHost.BeginReading(), &prAddr);
if (status != PR_SUCCESS) {
return NS_ERROR_FAILURE;
}
mozilla::net::NetAddr addr;
PRNetAddrToNetAddr(&prAddr, &addr);
rv = sock->InitWithAddress(&addr, mPrincipal, aAddressReuse,
/* optional_argc = */ 1);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsINetAddr> laddr;
rv = sock->GetLocalAddr(getter_AddRefs(laddr));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
uint16_t family;
rv = laddr->GetFamily(&family);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (family == nsINetAddr::FAMILY_INET) {
rv = sock->SetMulticastLoopback(aLoopback);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
// TODO: once bug 1252759 is fixed query buffer first and only increase
if (recvBufferSize != 0) {
rv = sock->SetRecvBufferSize(recvBufferSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
UDPSOCKET_LOG(("%s: [this=%p] %s:%u failed to set recv buffer size to: %lu", __FUNCTION__, this, nsCString(aHost).get(), aPort, recvBufferSize));
}
}
if (sendBufferSize != 0) {
rv = sock->SetSendBufferSize(sendBufferSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
UDPSOCKET_LOG(("%s: [this=%p] %s:%u failed to set send buffer size to: %lu", __FUNCTION__, this, nsCString(aHost).get(), aPort, sendBufferSize));
}
}
// register listener
rv = sock->AsyncListen(this);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mSocket = sock;
return NS_OK;
}
static nsCOMPtr<nsIEventTarget> GetSTSThread()
{
nsresult rv;
nsCOMPtr<nsIEventTarget> sts_thread;
sts_thread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
MOZ_ASSERT(NS_SUCCEEDED(rv));
return sts_thread;
}
static void CheckSTSThread()
{
DebugOnly<nsCOMPtr<nsIEventTarget>> sts_thread = GetSTSThread();
ASSERT_ON_THREAD(sts_thread.value);
}
// Proxy the Connect() request to the STS thread, since it may block and
// should be done there.
bool
UDPSocketParent::RecvConnect(const UDPAddressInfo& aAddressInfo)
{
nsCOMPtr<nsIEventTarget> thread(NS_GetCurrentThread());
Unused <<
NS_WARN_IF(NS_FAILED(GetSTSThread()->Dispatch(WrapRunnable(
RefPtr<UDPSocketParent>(this),
&UDPSocketParent::DoConnect,
mSocket,
thread,
aAddressInfo),
NS_DISPATCH_NORMAL)));
return true;
}
void
UDPSocketParent::DoSendConnectResponse(const UDPAddressInfo& aAddressInfo)
{
// can't use directly with WrapRunnable due to warnings
mozilla::Unused << SendCallbackConnected(aAddressInfo);
}
void
UDPSocketParent::SendConnectResponse(nsIEventTarget *aThread,
const UDPAddressInfo& aAddressInfo)
{
Unused <<
NS_WARN_IF(NS_FAILED(aThread->Dispatch(WrapRunnable(
RefPtr<UDPSocketParent>(this),
&UDPSocketParent::DoSendConnectResponse,
aAddressInfo),
NS_DISPATCH_NORMAL)));
}
// Runs on STS thread
void
UDPSocketParent::DoConnect(nsCOMPtr<nsIUDPSocket>& aSocket,
nsCOMPtr<nsIEventTarget>& aReturnThread,
const UDPAddressInfo& aAddressInfo)
{
UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, aAddressInfo.addr().get(), aAddressInfo.port()));
if (NS_FAILED(ConnectInternal(aAddressInfo.addr(), aAddressInfo.port()))) {
SendInternalError(aReturnThread, __LINE__);
return;
}
CheckSTSThread();
nsCOMPtr<nsINetAddr> localAddr;
aSocket->GetLocalAddr(getter_AddRefs(localAddr));
nsCString addr;
if (NS_FAILED(localAddr->GetAddress(addr))) {
SendInternalError(aReturnThread, __LINE__);
return;
}
uint16_t port;
if (NS_FAILED(localAddr->GetPort(&port))) {
SendInternalError(aReturnThread, __LINE__);
return;
}
UDPSOCKET_LOG(("%s: SendConnectResponse: %s:%u", __FUNCTION__, addr.get(), port));
SendConnectResponse(aReturnThread, UDPAddressInfo(addr, port));
}
nsresult
UDPSocketParent::ConnectInternal(const nsCString& aHost, const uint16_t& aPort)
{
nsresult rv;
UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, nsCString(aHost).get(), aPort));
if (!mSocket) {
return NS_ERROR_NOT_AVAILABLE;
}
PRNetAddr prAddr;
PR_InitializeNetAddr(PR_IpAddrAny, aPort, &prAddr);
PRStatus status = PR_StringToNetAddr(aHost.BeginReading(), &prAddr);
if (status != PR_SUCCESS) {
return NS_ERROR_FAILURE;
}
mozilla::net::NetAddr addr;
PRNetAddrToNetAddr(&prAddr, &addr);
rv = mSocket->Connect(&addr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
bool
UDPSocketParent::RecvOutgoingData(const UDPData& aData,
const UDPSocketAddr& aAddr)
{
if (!mSocket) {
NS_WARNING("sending socket is closed");
FireInternalError(__LINE__);
return true;
}
nsresult rv;
if (mFilter) {
if (aAddr.type() != UDPSocketAddr::TNetAddr) {
return true;
}
// TODO, Packet filter doesn't support input stream yet.
if (aData.type() != UDPData::TArrayOfuint8_t) {
return true;
}
bool allowed;
const InfallibleTArray<uint8_t>& data(aData.get_ArrayOfuint8_t());
rv = mFilter->FilterPacket(&aAddr.get_NetAddr(), data.Elements(),
data.Length(), nsISocketFilter::SF_OUTGOING,
&allowed);
// Sending unallowed data, kill content.
if (NS_WARN_IF(NS_FAILED(rv)) || !allowed) {
return false;
}
}
switch(aData.type()) {
case UDPData::TArrayOfuint8_t:
Send(aData.get_ArrayOfuint8_t(), aAddr);
break;
case UDPData::TInputStreamParams:
Send(aData.get_InputStreamParams(), aAddr);
break;
default:
MOZ_ASSERT(false, "Invalid data type!");
return true;
}
return true;
}
void
UDPSocketParent::Send(const InfallibleTArray<uint8_t>& aData,
const UDPSocketAddr& aAddr)
{
nsresult rv;
uint32_t count;
switch(aAddr.type()) {
case UDPSocketAddr::TUDPAddressInfo: {
const UDPAddressInfo& addrInfo(aAddr.get_UDPAddressInfo());
rv = mSocket->Send(addrInfo.addr(), addrInfo.port(),
aData.Elements(), aData.Length(), &count);
break;
}
case UDPSocketAddr::TNetAddr: {
const NetAddr& addr(aAddr.get_NetAddr());
rv = mSocket->SendWithAddress(&addr, aData.Elements(),
aData.Length(), &count);
break;
}
default:
MOZ_ASSERT(false, "Invalid address type!");
return;
}
if (NS_WARN_IF(NS_FAILED(rv)) || count == 0) {
FireInternalError(__LINE__);
}
}
void
UDPSocketParent::Send(const InputStreamParams& aStream,
const UDPSocketAddr& aAddr)
{
nsTArray<mozilla::ipc::FileDescriptor> fds;
nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(aStream, fds);
if (NS_WARN_IF(!stream)) {
return;
}
nsresult rv;
switch(aAddr.type()) {
case UDPSocketAddr::TUDPAddressInfo: {
const UDPAddressInfo& addrInfo(aAddr.get_UDPAddressInfo());
rv = mSocket->SendBinaryStream(addrInfo.addr(), addrInfo.port(), stream);
break;
}
case UDPSocketAddr::TNetAddr: {
const NetAddr& addr(aAddr.get_NetAddr());
rv = mSocket->SendBinaryStreamWithAddress(&addr, stream);
break;
}
default:
MOZ_ASSERT(false, "Invalid address type!");
return;
}
if (NS_FAILED(rv)) {
FireInternalError(__LINE__);
}
}
bool
UDPSocketParent::RecvJoinMulticast(const nsCString& aMulticastAddress,
const nsCString& aInterface)
{
nsresult rv = mSocket->JoinMulticast(aMulticastAddress, aInterface);
if (NS_WARN_IF(NS_FAILED(rv))) {
FireInternalError(__LINE__);
}
return true;
}
bool
UDPSocketParent::RecvLeaveMulticast(const nsCString& aMulticastAddress,
const nsCString& aInterface)
{
nsresult rv = mSocket->LeaveMulticast(aMulticastAddress, aInterface);
if (NS_WARN_IF(NS_FAILED(rv))) {
FireInternalError(__LINE__);
}
return true;
}
bool
UDPSocketParent::RecvClose()
{
if (!mSocket) {
return true;
}
nsresult rv = mSocket->Close();
mSocket = nullptr;
mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
return true;
}
bool
UDPSocketParent::RecvRequestDelete()
{
mozilla::Unused << Send__delete__(this);
return true;
}
void
UDPSocketParent::ActorDestroy(ActorDestroyReason why)
{
MOZ_ASSERT(mIPCOpen);
mIPCOpen = false;
if (mSocket) {
mSocket->Close();
}
mSocket = nullptr;
}
// nsIUDPSocketListener
NS_IMETHODIMP
UDPSocketParent::OnPacketReceived(nsIUDPSocket* aSocket, nsIUDPMessage* aMessage)
{
// receiving packet from remote host, forward the message content to child process
if (!mIPCOpen) {
return NS_OK;
}
uint16_t port;
nsCString ip;
nsCOMPtr<nsINetAddr> fromAddr;
aMessage->GetFromAddr(getter_AddRefs(fromAddr));
fromAddr->GetPort(&port);
fromAddr->GetAddress(ip);
nsCString data;
aMessage->GetData(data);
const char* buffer = data.get();
uint32_t len = data.Length();
UDPSOCKET_LOG(("%s: %s:%u, length %u", __FUNCTION__, ip.get(), port, len));
if (mFilter) {
bool allowed;
mozilla::net::NetAddr addr;
fromAddr->GetNetAddr(&addr);
nsresult rv = mFilter->FilterPacket(&addr,
(const uint8_t*)buffer, len,
nsISocketFilter::SF_INCOMING,
&allowed);
// Receiving unallowed data, drop.
if (NS_WARN_IF(NS_FAILED(rv)) || !allowed) {
if (!allowed) {
UDPSOCKET_LOG(("%s: not allowed", __FUNCTION__));
}
return NS_OK;
}
}
FallibleTArray<uint8_t> fallibleArray;
if (!fallibleArray.InsertElementsAt(0, buffer, len, fallible)) {
FireInternalError(__LINE__);
return NS_ERROR_OUT_OF_MEMORY;
}
InfallibleTArray<uint8_t> infallibleArray;
infallibleArray.SwapElements(fallibleArray);
// compose callback
mozilla::Unused << SendCallbackReceivedData(UDPAddressInfo(ip, port), infallibleArray);
return NS_OK;
}
NS_IMETHODIMP
UDPSocketParent::OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus)
{
// underlying socket is dead, send state update to child process
if (mIPCOpen) {
mozilla::Unused << SendCallbackClosed();
}
return NS_OK;
}
void
UDPSocketParent::FireInternalError(uint32_t aLineNo)
{
if (!mIPCOpen) {
return;
}
mozilla::Unused << SendCallbackError(NS_LITERAL_CSTRING("Internal error"),
NS_LITERAL_CSTRING(__FILE__), aLineNo);
}
void
UDPSocketParent::SendInternalError(nsIEventTarget *aThread,
uint32_t aLineNo)
{
UDPSOCKET_LOG(("SendInternalError: %u", aLineNo));
Unused <<
NS_WARN_IF(NS_FAILED(aThread->Dispatch(WrapRunnable(
RefPtr<UDPSocketParent>(this),
&UDPSocketParent::FireInternalError,
aLineNo),
NS_DISPATCH_NORMAL)));
}
} // namespace dom
} // namespace mozilla

View file

@ -0,0 +1,84 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_UDPSocketParent_h__
#define mozilla_dom_UDPSocketParent_h__
#include "mozilla/net/PUDPSocketParent.h"
#include "nsCOMPtr.h"
#include "nsIUDPSocket.h"
#include "nsISocketFilter.h"
#include "mozilla/dom/PermissionMessageUtils.h"
namespace mozilla {
namespace net {
class PNeckoParent;
} // namespace net
namespace dom {
class UDPSocketParent : public mozilla::net::PUDPSocketParent
, public nsIUDPSocketListener
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIUDPSOCKETLISTENER
explicit UDPSocketParent(PBackgroundParent* aManager);
explicit UDPSocketParent(PNeckoParent* aManager);
bool Init(const IPC::Principal& aPrincipal, const nsACString& aFilter);
virtual bool RecvBind(const UDPAddressInfo& aAddressInfo,
const bool& aAddressReuse, const bool& aLoopback,
const uint32_t& recvBufferSize,
const uint32_t& sendBufferSize) override;
virtual bool RecvConnect(const UDPAddressInfo& aAddressInfo) override;
void DoSendConnectResponse(const UDPAddressInfo& aAddressInfo);
void SendConnectResponse(nsIEventTarget *aThread,
const UDPAddressInfo& aAddressInfo);
void DoConnect(nsCOMPtr<nsIUDPSocket>& aSocket,
nsCOMPtr<nsIEventTarget>& aReturnThread,
const UDPAddressInfo& aAddressInfo);
virtual bool RecvOutgoingData(const UDPData& aData, const UDPSocketAddr& aAddr) override;
virtual bool RecvClose() override;
virtual bool RecvRequestDelete() override;
virtual bool RecvJoinMulticast(const nsCString& aMulticastAddress,
const nsCString& aInterface) override;
virtual bool RecvLeaveMulticast(const nsCString& aMulticastAddress,
const nsCString& aInterface) override;
private:
virtual ~UDPSocketParent();
virtual void ActorDestroy(ActorDestroyReason why) override;
void Send(const InfallibleTArray<uint8_t>& aData, const UDPSocketAddr& aAddr);
void Send(const InputStreamParams& aStream, const UDPSocketAddr& aAddr);
nsresult BindInternal(const nsCString& aHost, const uint16_t& aPort,
const bool& aAddressReuse, const bool& aLoopback,
const uint32_t& recvBufferSize,
const uint32_t& sendBufferSize);
nsresult ConnectInternal(const nsCString& aHost, const uint16_t& aPort);
void FireInternalError(uint32_t aLineNo);
void SendInternalError(nsIEventTarget *aThread,
uint32_t aLineNo);
// One of these will be null and the other non-null.
PBackgroundParent* mBackgroundManager;
PNeckoParent* mNeckoManager;
bool mIPCOpen;
nsCOMPtr<nsIUDPSocket> mSocket;
nsCOMPtr<nsISocketFilter> mFilter;
nsCOMPtr<nsIPrincipal> mPrincipal;
};
} // namespace dom
} // namespace mozilla
#endif // !defined(mozilla_dom_UDPSocketParent_h__)

View file

@ -0,0 +1,19 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPIDL_SOURCES += [
'nsIMozNavigatorNetwork.idl',
'nsITCPSocketCallback.idl',
'nsIUDPSocketChild.idl',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
XPIDL_SOURCES += [
'nsIEthernetManager.idl',
'nsINetworkStatsServiceProxy.idl',
]
XPIDL_MODULE = 'dom_network'

View file

@ -0,0 +1,137 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
[scriptable, function, uuid(2a3ad56c-edc0-439f-8aae-900b331ddf49)]
interface nsIEthernetManagerCallback : nsISupports
{
/**
* Callback function used to report the success of different operations.
*
* @param success
* Boolean value indicates the success of an operation.
* @prarm message
* Message reported in the end of operation.
*/
void notify(in boolean success, in DOMString message);
};
[scriptable, function, uuid(1746e7dd-92d4-43fa-8ef4-bc13d0b60353)]
interface nsIEthernetManagerScanCallback : nsISupports
{
/**
* Callback function used to report the result of scan function.
*
* @param list
* List of available ethernet interfaces.
*/
void notify(in jsval list);
};
/**
* An internal idl provides control to ethernet interfaces.
*/
[scriptable, uuid(81750c87-bb3b-4724-b955-834eafa53fd1)]
interface nsIEthernetManager : nsISupports
{
/**
* List of exisiting interface name.
*/
readonly attribute jsval interfaceList;
/**
* Scan available ethernet interfaces on device.
*
* @param callback
* Callback function.
*/
void scan(in nsIEthernetManagerScanCallback callback);
/**
* Add a new interface to the interface list.
*
* @param ifname
* Interface name. Should be the form of "eth*".
* @param callback
* Callback function.
*/
void addInterface(in DOMString ifname,
in nsIEthernetManagerCallback callback);
/**
* Remove an existing interface from the interface list.
*
* @param ifname
* Interface name.
* @param Callback
* Callback function.
*/
void removeInterface(in DOMString ifname,
in nsIEthernetManagerCallback callback);
/**
* Update a conifg of an existing interface in the interface list.
*
* @param ifname
* Interface name.
* @param config
* .ip: IP address.
* .prefixLength: Mask length.
* .gateway: Gateway.
* .dnses: DNS addresses.
* .httpProxyHost: HTTP proxy host.
* .httpProxyPort: HTTP proxy port.
* .ipMode: IP mode, can be 'dhcp' or 'static'.
* @param callback
* Callback function.
*/
void updateInterfaceConfig(in DOMString ifname,
in jsval config,
in nsIEthernetManagerCallback callback);
/**
* Enable networking of an existing interface in the interface list.
*
* @param ifname
* Interface name.
* @param callback
* Callback function.
*/
void enable(in DOMString ifname,
in nsIEthernetManagerCallback callback);
/**
* Disable networking of an existing interface in the interface list.
*
* @param ifname
* Interface name.
* @param callback
* Callback function.
*/
void disable(in DOMString ifname,
in nsIEthernetManagerCallback callback);
/**
* Make an existing interface connect to network.
*
* @param ifname
* Interface name.
* @param callback
* Callback function.
*/
void connect(in DOMString ifname,
in nsIEthernetManagerCallback callback);
/**
* Disconnect a connected interface in the interface list.
*
* @param ifname
* Interface name.
* @param callback
* Callback function.
*/
void disconnect(in DOMString ifname,
in nsIEthernetManagerCallback callback);
};

View file

@ -0,0 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsINetworkProperties;
[uuid(7956523b-631e-4f80-94a5-3883bcfd6bf3)]
interface nsIMozNavigatorNetwork : nsISupports
{
readonly attribute nsINetworkProperties properties;
};

View file

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsINetworkInfo;
[scriptable, function, uuid(5f821529-1d80-4ab5-a933-4e1b3585b6bc)]
interface nsINetworkStatsServiceProxyCallback : nsISupports
{
/*
* @param aResult callback result with boolean value
* @param aMessage message
*/
void notify(in boolean aResult, in jsval aMessage);
};
[scriptable, uuid(f4f3e901-e102-499d-9d37-dc9951f52df7)]
interface nsINetworkStatsServiceProxy : nsISupports
{
/*
* An interface used to record per-app traffic data.
* @param aAppId app id
* @param aIsInIsolatedMozBrowser
* true if the frame is an isolated mozbrowser element. <iframe
* mozbrowser mozapp> and <xul:browser> are not considered to be
* mozbrowser elements. <iframe mozbrowser noisolation> does not count
* as isolated since isolation is disabled. Isolation can only be
* disabled if the containing document is chrome.
* @param aNetworkInterface network
* @param aTimeStamp time stamp
* @param aRxBytes received data amount
* @param aTxBytes transmitted data amount
* @param aIsAccumulative is stats accumulative
* @param aCallback an optional callback
*/
void saveAppStats(in unsigned long aAppId,
in boolean aIsInIsolatedMozBrowser,
in nsINetworkInfo aNetworkInfo,
in unsigned long long aTimeStamp,
in unsigned long long aRxBytes,
in unsigned long long aTxBytes,
in boolean aIsAccumulative,
[optional] in nsINetworkStatsServiceProxyCallback aCallback);
/*
* An interface used to record per-system service traffic data.
* @param aServiceType system service type
* @param aNetworkInterface network
* @param aTimeStamp time stamp
* @param aRxBytes received data amount
* @param aTxBytes transmitted data amount
* @param aIsAccumulative is stats accumulative
* @param aCallback an optional callback
*/
void saveServiceStats(in string aServiceType,
in nsINetworkInfo aNetworkInfo,
in unsigned long long aTimeStamp,
in unsigned long long aRxBytes,
in unsigned long long aTxBytes,
in boolean aIsAccumulative,
[optional] in nsINetworkStatsServiceProxyCallback aCallback);
};

View file

@ -0,0 +1,59 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* MozTCPSocket exposes a TCP client and server sockets
* to highly privileged apps. It provides a buffered, non-blocking
* interface for sending. For receiving, it uses an asynchronous,
* event handler based interface.
*/
#include "domstubs.idl"
%{C++
#include "nsTArrayForwardDeclare.h"
%}
[ref] native nsUint8TArrayRef(InfallibleTArray<uint8_t>);
[ptr] native JSContextPtr(JSContext);
/*
* This interface is implemented in TCPSocket.cpp as an internal interface
* for use in cross-process socket implementation.
* Needed to account for multiple possible types that can be provided to
* the socket callbacks as arguments.
*/
[scriptable, uuid(ac2c4b69-cb79-4767-b1ce-bcf62945cd39)]
interface nsITCPSocketCallback : nsISupports {
// Limitation of TCPSocket's buffer size.
const unsigned long BUFFER_SIZE = 65536;
// Dispatch an "error" event at this object with the given name and type.
void fireErrorEvent(in AString name, in AString type);
// Dispatch a "data" event at this object with a string
void fireDataStringEvent(in DOMString type, in ACString data);
// Dispatch a "data" event at this object with an Array
void fireDataArrayEvent(in DOMString type, [const] in nsUint8TArrayRef data);
// Dispatch an event of the given type at this object.
void fireEvent(in DOMString type);
// Update the DOM object's readyState.
// @param readyState
// new ready state
void updateReadyState(in unsigned long readystate);
// Update the DOM object's bufferedAmount value with a tracking number to
// to allow tracking of which writes are "in-flight"
// @param bufferedAmount
// TCPSocket parent's bufferedAmount.
// @param trackingNumber
// A number to ensure the bufferedAmount is updated after data
// from child are sent to parent.
void updateBufferedAmount(in uint32_t bufferedAmount,
in uint32_t trackingNumber);
};

View file

@ -0,0 +1,78 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "nsINetAddr.idl"
interface nsIUDPSocketInternal;
interface nsIInputStream;
interface nsIPrincipal;
%{ C++
namespace mozilla {
namespace net {
union NetAddr;
}
}
%}
native NetAddr(mozilla::net::NetAddr);
[ptr] native NetAddrPtr(mozilla::net::NetAddr);
[scriptable, uuid(1e6ad73b-6c05-4d78-9a88-2d357b88f58b)]
interface nsIUDPSocketChild : nsISupports
{
readonly attribute unsigned short localPort;
readonly attribute AUTF8String localAddress;
attribute AUTF8String filterName;
// Allow hosting this over PBackground instead of PNecko
[noscript] void setBackgroundSpinsEvents();
// Tell the chrome process to bind the UDP socket to a given local host and port
void bind(in nsIUDPSocketInternal socket, in nsIPrincipal principal,
in AUTF8String host, in unsigned short port,
in bool addressReuse, in bool loopback, in uint32_t recvBufferSize,
in uint32_t sendBufferSize);
// Tell the chrome process to connect the UDP socket to a given remote host and port
void connect(in nsIUDPSocketInternal socket, in AUTF8String host, in unsigned short port);
// Tell the chrome process to perform equivalent operations to all following methods
void send(in AUTF8String host, in unsigned short port,
[const, array, size_is(byteLength)] in uint8_t bytes,
in unsigned long byteLength);
// Send without DNS query
void sendWithAddr(in nsINetAddr addr,
[const, array, size_is(byteLength)] in uint8_t bytes,
in unsigned long byteLength);
[noscript] void sendWithAddress([const] in NetAddrPtr addr,
[const, array, size_is(byteLength)] in uint8_t bytes,
in unsigned long byteLength);
// Send input stream. This must be a buffered stream implementation.
void sendBinaryStream(in AUTF8String host, in unsigned short port, in nsIInputStream stream);
void close();
void joinMulticast(in AUTF8String multicastAddress, in AUTF8String iface);
void leaveMulticast(in AUTF8String multicastAddress, in AUTF8String iface);
};
/*
* Internal interface for callback from chrome process
*/
[scriptable, uuid(613dd3ad-598b-4da9-ad63-bbda50c20098)]
interface nsIUDPSocketInternal : nsISupports
{
// callback while socket is opened. localPort and localAddress is ready until this time.
void callListenerOpened();
// callback while socket is connected.
void callListenerConnected();
// callback while socket is closed.
void callListenerClosed();
// callback while incoming packet is received.
void callListenerReceivedData(in AUTF8String host, in unsigned short port,
[const, array, size_is(dataLength)] in uint8_t data,
in unsigned long dataLength);
// callback while any error happened.
void callListenerError(in AUTF8String message, in AUTF8String filename, in uint32_t lineNumber);
};

76
dom/network/moz.build Normal file
View file

@ -0,0 +1,76 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += ['interfaces']
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
EXPORTS.mozilla.dom += [
'TCPServerSocket.h',
'TCPSocket.h',
'UDPSocket.h',
]
EXPORTS.mozilla.dom.network += [
'Connection.h',
'Constants.h',
'TCPServerSocketChild.h',
'TCPServerSocketParent.h',
'TCPSocketChild.h',
'TCPSocketParent.h',
'Types.h',
'UDPSocketChild.h',
'UDPSocketParent.h',
]
UNIFIED_SOURCES += [
'Connection.cpp',
'TCPServerSocket.cpp',
'TCPServerSocketChild.cpp',
'TCPServerSocketParent.cpp',
'TCPSocket.cpp',
'TCPSocketChild.cpp',
'TCPSocketParent.cpp',
'UDPSocket.cpp',
'UDPSocketChild.cpp',
'UDPSocketParent.cpp',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
EXTRA_JS_MODULES += [
'NetworkStatsDB.jsm',
'NetworkStatsService.jsm',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
EXTRA_COMPONENTS += [
'EthernetManager.js',
'EthernetManager.manifest',
'NetworkStatsManager.js',
'NetworkStatsManager.manifest',
'NetworkStatsServiceProxy.js',
'NetworkStatsServiceProxy.manifest',
]
EXPORTS.mozilla.dom.network += [
'NetUtils.h',
]
UNIFIED_SOURCES += [
'NetUtils.cpp',
]
IPDL_SOURCES += [
'PTCPServerSocket.ipdl',
'PTCPSocket.ipdl',
'PUDPSocket.ipdl',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']

View file

@ -0,0 +1,83 @@
// Temporary implementation of add_task for mochitest-plain until bug 1078657 is
// implemented.
SimpleTest.waitForExplicitFinish();
(function(scope) {
var pendingTasks = [];
var pendingPromise = null;
// Strict spawn function that takes a known generatorFunc and assumes that
// every yielded value will be a Promise. If nesting is desired, then yield*
// should be used!
function spawn(generatorFunc) {
return new Promise(function(resolve, reject) {
try {
var iterator = generatorFunc();
}
catch (ex) {
ok(false, 'Problem invoking generator func: ' + ex + ': ' + ex.stack);
return;
}
var stepResolved = function(result) {
try {
var iterStep = iterator.next(result);
}
catch (ex) {
ok(false, 'Problem invoking iterator step: ' + ex + ': ' + ex.stack);
return;
}
if (iterStep.done) {
resolve(iterStep.value);
return;
}
if (!iterStep.value || !iterStep.value.then) {
ok(false, 'Iterator step returned non-Promise: ' + iterStep.value);
}
iterStep.value.then(stepResolved, generalErrback);
};
stepResolved();
});
}
function maybeSpawn(promiseOrGenerator) {
if (promiseOrGenerator.then) {
return promiseOrGenerator;
}
return spawn(promiseOrGenerator);
}
scope.add_task = function(thing) {
pendingTasks.push(thing);
};
function generalErrback(ex) {
ok(false,
'A rejection happened: ' +
(ex ? (ex + ': ' + ex.stack) : ''));
}
function runNextTask() {
if (pendingTasks.length) {
pendingPromise = maybeSpawn(pendingTasks.shift());
pendingPromise.then(runNextTask, generalErrback);
} else {
SimpleTest.finish();
}
}
// Trigger runNextTask after we think all JS files have been loaded.
// The primary goal is that we can call SimpleTest.finish() after all test
// code has been loaded and run. We gate this based on the document's
// readyState.
var running = false;
function maybeStartRunning() {
if (!running && document.readyState === 'complete') {
running = true;
document.removeEventListener('readystateChange', maybeStartRunning);
// Defer to a subsequent turn of the event loop to let micro-tasks and any
// other clever setTimeout(0) instances run first.
window.setTimeout(runNextTask, 0);
}
}
document.addEventListener('readystatechange', maybeStartRunning);
maybeStartRunning();
})(this);

View file

@ -0,0 +1,12 @@
[DEFAULT]
support-files =
tcpsocket_test.jsm
test_tcpsocket_client_and_server_basics.js
add_task.js
file_udpsocket_iframe.html
[test_tcpsocket_jsm.html]
[test_tcpsocket_client_and_server_basics.html]
[test_tcpsocket_enabled_with_perm.html]
[test_tcpsocket_legacy.html]
[test_udpsocket.html]

View file

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test UDPSocket BFCache</title>
</head>
<body>
<script type="application/javascript;version=1.8">
'use strict';
window.addEventListener('load', function onload() {
window.removeEventListener('load', onload);
let remotePort = parseInt(window.location.search.substring(1), 10);
let socket = new UDPSocket();
socket.addEventListener('message', function () {
socket.send('fail', '127.0.0.1', remotePort);
});
socket.opened.then(function() {
socket.send('ready', '127.0.0.1', remotePort);
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,552 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
const ETHERNET_MANAGER_CONTRACT_ID = "@mozilla.org/ethernetManager;1";
const INTERFACE_UP = "UP";
const INTERFACE_DOWN = "DOWN";
let gTestSuite = (function() {
let suite = {};
// Private member variables of the returned object |suite|.
let ethernetManager = SpecialPowers.Cc[ETHERNET_MANAGER_CONTRACT_ID]
.getService(SpecialPowers.Ci.nsIEthernetManager);
let pendingEmulatorShellCount = 0;
/**
* Send emulator shell command with safe guard.
*
* We should only call |finish()| after all emulator command transactions
* end, so here comes with the pending counter. Resolve when the emulator
* gives positive response, and reject otherwise.
*
* Fulfill params: an array of emulator response lines.
* Reject params: an array of emulator response lines.
*
* @param command
* A string command to be passed to emulator through its telnet console.
*
* @return A deferred promise.
*/
function runEmulatorShellSafe(command) {
let deferred = Promise.defer();
++pendingEmulatorShellCount;
runEmulatorShell(command, function(aResult) {
--pendingEmulatorShellCount;
ok(true, "Emulator shell response: " + JSON.stringify(aResult));
if (Array.isArray(aResult)) {
deferred.resolve(aResult);
} else {
deferred.reject(aResult);
}
});
return deferred.promise;
}
/**
* Get the system network conifg by the given interface name.
*
* Use shell command 'netcfg' to get the list of network cofig.
*
* Fulfill params: An object of { name, flag, ip }
*
* @parm ifname
* Interface name.
*
* @return A deferred promise.
*/
function getNetworkConfig(ifname) {
return runEmulatorShellSafe(['netcfg'])
.then(result => {
// Sample 'netcfg' output:
//
// lo UP 127.0.0.1/8 0x00000049 00:00:00:00:00:00
// eth0 UP 10.0.2.15/24 0x00001043 52:54:00:12:34:56
// eth1 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:57
// rmnet1 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:59
let config;
for (let i = 0; i < result.length; i++) {
let tokens = result[i].split(/\s+/);
let name = tokens[0];
let flag = tokens[1];
let ip = tokens[2].split(/\/+/)[0];
if (name == ifname) {
config = { name: name, flag: flag, ip: ip };
break;
}
}
return config;
});
}
/**
* Get the ip assigned by dhcp server of a given interface name.
*
* Get the ip from android property 'dhcp.[ifname].ipaddress'.
*
* Fulfill params: A string of ip address.
*
* @parm ifname
* Interface name.
*
* @return A deferred promise.
*/
function getDhcpIpAddr(ifname) {
return runEmulatorShellSafe(['getprop', 'dhcp.' + ifname + '.ipaddress'])
.then(function(ipAddr) {
return ipAddr[0];
});
}
/**
* Get the gateway assigned by dhcp server of a given interface name.
*
* Get the ip from android property 'dhcp.[ifname].gateway'.
*
* Fulfill params: A string of gateway.
*
* @parm ifname
* Interface name.
*
* @return A deferred promise.
*/
function getDhcpGateway(ifname) {
return runEmulatorShellSafe(['getprop', 'dhcp.' + ifname + '.gateway'])
.then(function(gateway) {
return gateway[0];
});
}
/**
* Get the default route.
*
* Use shell command 'ip route' to get the default of device.
*
* Fulfill params: An array of { name, gateway }
*
* @return A deferred promise.
*/
function getDefaultRoute() {
return runEmulatorShellSafe(['ip', 'route'])
.then(result => {
// Sample 'ip route' output:
//
// 10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15
// default via 10.0.2.2 dev eth0 metric 2
let routeInfo = [];
for (let i = 0; i < result.length; i++) {
if (!result[i].match('default')) {
continue;
}
let tokens = result[i].split(/\s+/);
let name = tokens[4];
let gateway = tokens[2];
routeInfo.push({ name: name, gateway: gateway });
}
return routeInfo;
});
}
/**
* Check a specific interface is enabled or not.
*
* @parm ifname
* Interface name.
* @parm enabled
* A boolean value used to check interface is disable or not.
*
* @return A deferred promise.
*/
function checkInterfaceIsEnabled(ifname, enabled) {
return getNetworkConfig(ifname)
.then(function(config) {
if (enabled) {
is(config.flag, INTERFACE_UP, "Interface is enabled as expected.");
} else {
is(config.flag, INTERFACE_DOWN, "Interface is disabled as expected.");
}
});
}
/**
* Check the ip of a specific interface is equal to given ip or not.
*
* @parm ifname
* Interface name.
* @parm ip
* Given ip address.
*
* @return A deferred promise.
*/
function checkInterfaceIpAddr(ifname, ip) {
return getNetworkConfig(ifname)
.then(function(config) {
is(config.ip, ip, "IP is right as expected.");
});
}
/**
* Check the default gateway of a specific interface is equal to given gateway
* or not.
*
* @parm ifname
* Interface name.
* @parm gateway
* Given gateway.
*
* @return A deferred promise.
*/
function checkDefaultRoute(ifname, gateway) {
return getDefaultRoute()
.then(function(routeInfo) {
for (let i = 0; i < routeInfo.length; i++) {
if (routeInfo[i].name == ifname) {
is(routeInfo[i].gateway, gateway,
"Default gateway is right as expected.");
return true;
}
}
if (!gateway) {
ok(true, "Default route is cleared.");
return true;
}
// TODO: should we ok(false, ......) here?
return false;
});
}
/**
* Check the length of interface list in EthernetManager is equal to given
* length or not.
*
* @parm length
* Given length.
*/
function checkInterfaceListLength(length) {
let list = ethernetManager.interfaceList;
is(length, list.length, "List length is equal as expected.");
}
/**
* Check the given interface exists on device or not.
*
* @parm ifname
* Interface name.
*
* @return A deferred promise.
*/
function checkInterfaceExist(ifname) {
return scanInterfaces()
.then(list => {
let index = list.indexOf(ifname);
if (index < 0) {
throw "Interface " + ifname + " not found.";
}
ok(true, ifname + " exists.");
});
}
/**
* Scan for available ethernet interfaces.
*
* Fulfill params: A list of available interfaces found in device.
*
* @return A deferred promise.
*/
function scanInterfaces() {
let deferred = Promise.defer();
ethernetManager.scan(function onScan(list) {
deferred.resolve(list);
});
return deferred.promise;
}
/**
* Add an interface into interface list.
*
* Fulfill params: A boolean value indicates success or not.
*
* @param ifname
* Interface name.
*
* @return A deferred promise.
*/
function addInterface(ifname) {
let deferred = Promise.defer();
ethernetManager.addInterface(ifname, function onAdd(success, message) {
ok(success, "Add interface " + ifname + " succeeded.");
is(message, "ok", "Message is as expected.");
deferred.resolve(success);
});
return deferred.promise;
}
/**
* Remove an interface form the interface list.
*
* Fulfill params: A boolean value indicates success or not.
*
* @param ifname
* Interface name.
*
* @return A deferred promise.
*/
function removeInterface(ifname) {
let deferred = Promise.defer();
ethernetManager.removeInterface(ifname, function onRemove(success, message) {
ok(success, "Remove interface " + ifname + " succeeded.");
is(message, "ok", "Message is as expected.");
deferred.resolve(success);
});
return deferred.promise;
}
/**
* Enable networking of an interface in the interface list.
*
* Fulfill params: A boolean value indicates success or not.
*
* @param ifname
* Interface name.
*
* @return A deferred promise.
*/
function enableInterface(ifname) {
let deferred = Promise.defer();
ethernetManager.enable(ifname, function onEnable(success, message) {
ok(success, "Enable interface " + ifname + " succeeded.");
is(message, "ok", "Message is as expected.");
deferred.resolve(success);
});
return deferred.promise;
}
/**
* Disable networking of an interface in the interface list.
*
* Fulfill params: A boolean value indicates success or not.
*
* @param ifname
* Interface name.
*
* @return A deferred promise.
*/
function disableInterface(ifname) {
let deferred = Promise.defer();
ethernetManager.disable(ifname, function onDisable(success, message) {
ok(success, "Disable interface " + ifname + " succeeded.");
is(message, "ok", "Message is as expected.");
deferred.resolve(success);
});
return deferred.promise;
}
/**
* Make an interface connect to network.
*
* Fulfill params: A boolean value indicates success or not.
*
* @param ifname
* Interface name.
*
* @return A deferred promise.
*/
function makeInterfaceConnect(ifname) {
let deferred = Promise.defer();
ethernetManager.connect(ifname, function onConnect(success, message) {
ok(success, "Interface " + ifname + " is connected successfully.");
is(message, "ok", "Message is as expected.");
deferred.resolve(success);
});
return deferred.promise;
}
/**
* Make an interface disconnect to network.
*
* Fulfill params: A boolean value indicates success or not.
*
* @param ifname
* Interface name.
*
* @return A deferred promise.
*/
function makeInterfaceDisconnect(ifname) {
let deferred = Promise.defer();
ethernetManager.disconnect(ifname, function onDisconnect(success, message) {
ok(success, "Interface " + ifname + " is disconnected successfully.");
is(message, "ok", "Message is as expected.");
deferred.resolve(success);
});
return deferred.promise;
}
/**
* Update the config the an interface in the interface list.
*
* @param ifname
* Interface name.
* @param config
* .ip: ip address.
* .prefixLength: mask length.
* .gateway: gateway.
* .dnses: dnses.
* .httpProxyHost: http proxy host.
* .httpProxyPort: http porxy port.
* .usingDhcp: an boolean value indicates using dhcp or not.
*
* @return A deferred promise.
*/
function updateInterfaceConfig(ifname, config) {
let deferred = Promise.defer();
ethernetManager.updateInterfaceConfig(ifname, config,
function onUpdated(success, message) {
ok(success, "Interface " + ifname + " config is updated successfully " +
"with " + JSON.stringify(config));
is(message, "ok", "Message is as expected.");
deferred.resolve(success);
});
return deferred.promise;
}
/**
* Wait for timeout.
*
* @param timeout
* Time in ms.
*
* @return A deferred promise.
*/
function waitForTimeout(timeout) {
let deferred = Promise.defer();
setTimeout(function() {
ok(true, "waitForTimeout " + timeout);
deferred.resolve();
}, timeout);
return deferred.promise;
}
/**
* Wait for default route of a specific interface being set and
* check.
*
* @param ifname
* Interface name.
* @param gateway
* Target gateway.
*
* @return A deferred promise.
*/
function waitForDefaultRouteSet(ifname, gateway) {
return gTestSuite.waitForTimeout(500)
.then(() => gTestSuite.checkDefaultRoute(ifname, gateway))
.then(success => {
if (success) {
ok(true, "Default route is set as expected." + gateway);
return;
}
ok(true, "Default route is not set yet, check again. " + success);
return waitForDefaultRouteSet(ifname, gateway);
});
}
//---------------------------------------------------
// Public test suite functions
//---------------------------------------------------
suite.scanInterfaces = scanInterfaces;
suite.addInterface = addInterface;
suite.removeInterface = removeInterface;
suite.enableInterface = enableInterface;
suite.disableInterface = disableInterface;
suite.makeInterfaceConnect = makeInterfaceConnect;
suite.makeInterfaceDisconnect = makeInterfaceDisconnect;
suite.updateInterfaceConfig = updateInterfaceConfig;
suite.getDhcpIpAddr = getDhcpIpAddr;
suite.getDhcpGateway = getDhcpGateway;
suite.checkInterfaceExist = checkInterfaceExist;
suite.checkInterfaceIsEnabled = checkInterfaceIsEnabled;
suite.checkInterfaceIpAddr = checkInterfaceIpAddr;
suite.checkDefaultRoute = checkDefaultRoute;
suite.checkInterfaceListLength = checkInterfaceListLength;
suite.waitForTimeout = waitForTimeout;
suite.waitForDefaultRouteSet = waitForDefaultRouteSet;
/**
* End up the test run.
*
* Wait until all pending emulator shell commands are done and then |finish|
* will be called in the end.
*/
function cleanUp() {
waitFor(finish, function() {
return pendingEmulatorShellCount === 0;
});
}
/**
* Common test routine.
*
* Start a test with the given test case chain. The test environment will be
* settled down before the test. After the test, all the affected things will
* be restored.
*
* @param aTestCaseChain
* The test case entry point, which can be a function or a promise.
*
* @return A deferred promise.
*/
suite.doTest = function(aTestCaseChain) {
return Promise.resolve()
.then(aTestCaseChain)
.then(function onresolve() {
cleanUp();
}, function onreject(aReason) {
ok(false, 'Promise rejects during test' + (aReason ? '(' + aReason + ')' : ''));
cleanUp();
});
};
return suite;
})();

View file

@ -0,0 +1,13 @@
[DEFAULT]
run-if = buildapp == 'b2g'
[test_ethernet_add_interface.js]
[test_ethernet_remove_interface.js]
[test_ethernet_enable.js]
[test_ethernet_disable.js]
[test_ethernet_connect_with_dhcp.js]
[test_ethernet_connect_with_static_ip.js]
[test_ethernet_reconnect_with_dhcp.js]
[test_ethernet_reconnect_with_static_ip.js]
[test_ethernet_ip_mode_change.js]
[test_ethernet_disconnect.js]

View file

@ -0,0 +1,16 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.checkInterfaceListLength(0))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.checkInterfaceListLength(1))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
});

View file

@ -0,0 +1,26 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
function checkDhcpResult(ifname) {
return gTestSuite.getDhcpIpAddr(ifname)
.then(ip => gTestSuite.checkInterfaceIpAddr(ifname, ip))
.then(() => gTestSuite.getDhcpGateway(ifname))
.then(gateway => gTestSuite.waitForDefaultRouteSet(ifname, gateway));
}
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
.then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
});

View file

@ -0,0 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
let staticConfig = {
ip: "1.2.3.4",
gateway: "1.2.3.5",
prefixLength: 24,
dnses: ["1.2.3.6"],
ipMode: "static"
};
function checkStaticResult(ifname) {
return gTestSuite.checkInterfaceIpAddr(ifname, staticConfig.ip)
.then(() => gTestSuite.checkDefaultRoute(ifname, staticConfig.gateway));
}
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, staticConfig))
.then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
.then(() => checkStaticResult(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
});

View file

@ -0,0 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.checkInterfaceIsEnabled(ETHERNET_INTERFACE_NAME, false))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
});

View file

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
const INTERFACE_IP_NONE = "0.0.0.0";
function checkIpAddrIsReset(ifname) {
return gTestSuite.checkInterfaceIpAddr(ifname, INTERFACE_IP_NONE)
.then(() => gTestSuite.checkDefaultRoute(ifname));
}
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
.then(() => checkIpAddrIsReset(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
});

View file

@ -0,0 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.checkInterfaceIsEnabled(ETHERNET_INTERFACE_NAME, true))
.then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
});

View file

@ -0,0 +1,43 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
let staticConfig = {
ip: "1.2.3.4",
gateway: "1.2.3.5",
prefixLength: 24,
dnses: ["1.2.3.6"],
ipMode: "static"
};
function checkStaticResult(ifname) {
return gTestSuite.checkInterfaceIpAddr(ifname, staticConfig.ip)
.then(() => gTestSuite.waitForDefaultRouteSet(ifname, staticConfig.gateway));
}
function checkDhcpResult(ifname) {
return gTestSuite.getDhcpIpAddr(ifname)
.then(ip => gTestSuite.checkInterfaceIpAddr(ifname, ip))
.then(() => gTestSuite.getDhcpGateway(ifname))
.then(gateway => gTestSuite.waitForDefaultRouteSet(ifname, gateway));
}
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
.then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, staticConfig))
.then(() => checkStaticResult(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, { ipMode: "dhcp"}))
.then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
});

View file

@ -0,0 +1,29 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
function checkDhcpResult(ifname) {
return gTestSuite.getDhcpIpAddr(ifname)
.then(ip => gTestSuite.checkInterfaceIpAddr(ifname, ip))
.then(() => gTestSuite.getDhcpGateway(ifname))
.then(gateway => gTestSuite.waitForDefaultRouteSet(ifname, gateway));
}
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
.then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
.then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
});

View file

@ -0,0 +1,36 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
let staticConfig = {
ip: "1.2.3.4",
gateway: "1.2.3.5",
prefixLength: 24,
dnses: ["1.2.3.6"],
ipMode: "static"
};
function checkStaticResult(ifname) {
return gTestSuite.checkInterfaceIpAddr(ifname, staticConfig.ip)
.then(() => gTestSuite.checkDefaultRoute(ifname, staticConfig.gateway));
}
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, staticConfig))
.then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
.then(() => checkStaticResult(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
.then(() => checkStaticResult(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
});

View file

@ -0,0 +1,16 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
const ETHERNET_INTERFACE_NAME = "eth1";
gTestSuite.doTest(function() {
return Promise.resolve()
.then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.checkInterfaceListLength(1))
.then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME))
.then(() => gTestSuite.checkInterfaceListLength(0));
});

View file

@ -0,0 +1,8 @@
[DEFAULT]
support-files =
add_task.js
[test_network_basics.html]
skip-if = toolkit == 'android'
[test_tcpsocket_default_permissions.html]
[test_tcpsocket_enabled_no_perm.html]

View file

@ -0,0 +1,20 @@
this.EXPORTED_SYMBOLS = [
'createSocket', 'createServer', 'enablePrefsAndPermissions',
'socketCompartmentInstanceOfArrayBuffer'];
this.createSocket = function(host, port, options) {
return new TCPSocket(host, port, options);
}
this.createServer = function(port, options, backlog) {
return new TCPServerSocket(port, options, backlog);
}
this.enablePrefsAndPermissions = function() {
return false;
}
// See test_tcpsocket_client_and_server_basics.html's version for rationale.
this.socketCompartmentInstanceOfArrayBuffer = function(obj) {
return obj instanceof ArrayBuffer;
}

View file

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Network API</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Network Information API **/
function test() {
ok('connection' in navigator, "navigator.connection should exist");
ok(navigator.connection, "navigator.connection returns an object");
ok(navigator.connection instanceof EventTarget,
"navigator.connection is a EventTarget object");
ok('type' in navigator.connection,
"type should be a Connection attribute");
is(navigator.connection.type, "none",
"By default connection.type equals to none");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({'set': [["dom.netinfo.enabled", true]]}, test);
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,51 @@
<!DOCTYPE HTML>
<html>
<!--
Core tests for TCPSocket and TCPServerSocket that replace their previous
separate xpcshell incarnations. This migration and cleanup occurred as part
of bug 1084245 in order to get coverage of the tests from content.
https://bugzilla.mozilla.org/show_bug.cgi?id=1084245
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1084245</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="add_task.js"></script>
<script type="application/javascript">
function createServer(port, options, backlog) {
return new TCPServerSocket(port, options, backlog);
}
function createSocket(host, port, options) {
return new TCPSocket(host, port, options);
}
function enablePrefsAndPermissions() {
return true;
}
// In the JSM case, ArrayBuffers will be created in the compartment of the
// JSM with different globals than the
// test_tcpsocket_client_and_server_basics.js test logic sees, so we (and
// tcpsocket_test.jsm) need to do something. To avoid complexity relating
// to wrappers and the varying nuances of the module scope and global scope
// in JSM's (they differ on B2G), we hardcode ArrayBuffer rather than taking
// a string that we look up, etc.
function socketCompartmentInstanceOfArrayBuffer(obj) {
return obj instanceof ArrayBuffer;
}
</script>
<script type="application/javascript;version=1.7" src="test_tcpsocket_client_and_server_basics.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1084245">Mozilla Bug 1084245</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View file

@ -0,0 +1,423 @@
'use strict';
const SERVER_BACKLOG = -1;
const SOCKET_EVENTS = ['open', 'data', 'drain', 'error', 'close'];
function concatUint8Arrays(a, b) {
let newArr = new Uint8Array(a.length + b.length);
newArr.set(a, 0);
newArr.set(b, a.length);
return newArr;
}
function assertUint8ArraysEqual(a, b, comparingWhat) {
if (a.length !== b.length) {
ok(false, comparingWhat + ' arrays do not have the same length; ' +
a.length + ' versus ' + b.length);
return;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
ok(false, comparingWhat + ' arrays differ at index ' + i +
a[i] + ' versus ' + b[i]);
return;
}
}
ok(true, comparingWhat + ' arrays were equivalent.');
}
/**
* Helper method to add event listeners to a socket and provide two Promise-returning
* helpers (see below for docs on them). This *must* be called during the turn of
* the event loop where TCPSocket's constructor is called or the onconnect method is being
* invoked.
*/
function listenForEventsOnSocket(socket, socketType) {
let wantDataLength = null;
let wantDataAndClose = false;
let pendingResolve = null;
let receivedEvents = [];
let receivedData = null;
let handleGenericEvent = function(event) {
dump('(' + socketType + ' event: ' + event.type + ')\n');
if (pendingResolve && wantDataLength === null) {
pendingResolve(event);
pendingResolve = null;
} else {
receivedEvents.push(event);
}
};
socket.onopen = handleGenericEvent;
socket.ondrain = handleGenericEvent;
socket.onerror = handleGenericEvent;
socket.onclose = function(event) {
if (!wantDataAndClose) {
handleGenericEvent(event);
} else if (pendingResolve) {
dump('(' + socketType + ' event: close)\n');
pendingResolve(receivedData);
pendingResolve = null;
wantDataAndClose = false;
}
}
socket.ondata = function(event) {
dump('(' + socketType + ' event: ' + event.type + ' length: ' +
event.data.byteLength + ')\n');
ok(socketCompartmentInstanceOfArrayBuffer(event.data),
'payload is ArrayBuffer');
var arr = new Uint8Array(event.data);
if (receivedData === null) {
receivedData = arr;
} else {
receivedData = concatUint8Arrays(receivedData, arr);
}
if (wantDataLength !== null &&
receivedData.length >= wantDataLength) {
pendingResolve(receivedData);
pendingResolve = null;
receivedData = null;
wantDataLength = null;
}
};
return {
/**
* Return a Promise that will be resolved with the next (non-data) event
* received by the socket. If there are queued events, the Promise will
* be immediately resolved (but you won't see that until a future turn of
* the event loop).
*/
waitForEvent: function() {
if (pendingResolve) {
throw new Error('only one wait allowed at a time.');
}
if (receivedEvents.length) {
return Promise.resolve(receivedEvents.shift());
}
dump('(' + socketType + ' waiting for event)\n');
return new Promise(function(resolve, reject) {
pendingResolve = resolve;
});
},
/**
* Return a Promise that will be resolved with a Uint8Array of at least the
* given length. We buffer / accumulate received data until we have enough
* data. Data is buffered even before you call this method, so be sure to
* explicitly wait for any and all data sent by the other side.
*/
waitForDataWithAtLeastLength: function(length) {
if (pendingResolve) {
throw new Error('only one wait allowed at a time.');
}
if (receivedData && receivedData.length >= length) {
let promise = Promise.resolve(receivedData);
receivedData = null;
return promise;
}
dump('(' + socketType + ' waiting for ' + length + ' bytes)\n');
return new Promise(function(resolve, reject) {
pendingResolve = resolve;
wantDataLength = length;
});
},
waitForAnyDataAndClose: function() {
if (pendingResolve) {
throw new Error('only one wait allowed at a time.');
}
return new Promise(function(resolve, reject) {
pendingResolve = resolve;
// we may receive no data before getting close, in which case we want to
// return an empty array
receivedData = new Uint8Array();
wantDataAndClose = true;
});
}
};
}
/**
* Return a promise that is resolved when the server receives a connection. The
* promise is resolved with { socket, queue } where `queue` is the result of
* calling listenForEventsOnSocket(socket). This must be done because we need
* to add the event listener during the connection.
*/
function waitForConnection(listeningServer) {
return new Promise(function(resolve, reject) {
// Because of the event model of sockets, we can't use the
// listenForEventsOnSocket mechanism; we need to hook up listeners during
// the connect event.
listeningServer.onconnect = function(event) {
// Clobber the listener to get upset if it receives any more connections
// after this.
listeningServer.onconnect = function() {
ok(false, 'Received a connection when not expecting one.');
};
ok(true, 'Listening server accepted socket');
resolve({
socket: event.socket,
queue: listenForEventsOnSocket(event.socket, 'server')
});
};
});
}
function defer() {
var deferred = {};
deferred.promise = new Promise(function(resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
}
function* test_basics() {
// See bug 903830; in e10s mode we never get to find out the localPort if we
// let it pick a free port by choosing 0. This is the same port the xpcshell
// test was using.
let serverPort = 8085;
// - Start up a listening socket.
let listeningServer = createServer(serverPort,
{ binaryType: 'arraybuffer' },
SERVER_BACKLOG);
let connectedPromise = waitForConnection(listeningServer);
// -- Open a connection to the server
let clientSocket = createSocket('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
let clientQueue = listenForEventsOnSocket(clientSocket, 'client');
// (the client connects)
is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
is(clientSocket.readyState, 'open', 'client readyState is open');
// (the server connected)
let { socket: serverSocket, queue: serverQueue } = yield connectedPromise;
is(serverSocket.readyState, 'open', 'server readyState is open');
// -- Simple send / receive
// - Send data from client to server
// (But not so much we cross the drain threshold.)
let smallUint8Array = new Uint8Array(256);
for (let i = 0; i < smallUint8Array.length; i++) {
smallUint8Array[i] = i;
}
is(clientSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length), true,
'Client sending less than 64k, buffer should not be full.');
let serverReceived = yield serverQueue.waitForDataWithAtLeastLength(256);
assertUint8ArraysEqual(serverReceived, smallUint8Array,
'Server received/client sent');
// - Send data from server to client
// (But not so much we cross the drain threshold.)
is(serverSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length), true,
'Server sending less than 64k, buffer should not be full.');
let clientReceived = yield clientQueue.waitForDataWithAtLeastLength(256);
assertUint8ArraysEqual(clientReceived, smallUint8Array,
'Client received/server sent');
// -- Perform sending multiple times with different buffer slices
// - Send data from client to server
// (But not so much we cross the drain threshold.)
is(clientSocket.send(smallUint8Array.buffer, 0, 7),
true, 'Client sending less than 64k, buffer should not be full.');
is(clientSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
true, 'Client sending less than 64k, buffer should not be full.');
serverReceived = yield serverQueue.waitForDataWithAtLeastLength(256);
assertUint8ArraysEqual(serverReceived, smallUint8Array,
'Server received/client sent');
// - Send data from server to client
// (But not so much we cross the drain threshold.)
is(serverSocket.send(smallUint8Array.buffer, 0, 7),
true, 'Server sending less than 64k, buffer should not be full.');
is(serverSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
true, 'Server sending less than 64k, buffer should not be full.');
clientReceived = yield clientQueue.waitForDataWithAtLeastLength(256);
assertUint8ArraysEqual(clientReceived, smallUint8Array,
'Client received/server sent');
// -- Send "big" data in both directions
// (Enough to cross the buffering/drain threshold; 64KiB)
let bigUint8Array = new Uint8Array(65536 + 3);
for (let i = 0; i < bigUint8Array.length; i++) {
bigUint8Array[i] = i % 256;
}
// Do this twice so we have confidence that the 'drain' event machinery
// doesn't break after the first use.
for (let iSend = 0; iSend < 2; iSend++) {
// - Send "big" data from the client to the server
is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
'Client sending more than 64k should result in the buffer being full.');
is((yield clientQueue.waitForEvent()).type, 'drain',
'The drain event should fire after a large send that indicated full.');
serverReceived = yield serverQueue.waitForDataWithAtLeastLength(
bigUint8Array.length);
assertUint8ArraysEqual(serverReceived, bigUint8Array,
'server received/client sent');
// - Send "big" data from the server to the client
is(serverSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
'Server sending more than 64k should result in the buffer being full.');
is((yield serverQueue.waitForEvent()).type, 'drain',
'The drain event should fire after a large send that indicated full.');
clientReceived = yield clientQueue.waitForDataWithAtLeastLength(
bigUint8Array.length);
assertUint8ArraysEqual(clientReceived, bigUint8Array,
'client received/server sent');
}
// -- Server closes the connection
serverSocket.close();
is(serverSocket.readyState, 'closing',
'readyState should be closing immediately after calling close');
is((yield clientQueue.waitForEvent()).type, 'close',
'The client should get a close event when the server closes.');
is(clientSocket.readyState, 'closed',
'client readyState should be closed after close event');
is((yield serverQueue.waitForEvent()).type, 'close',
'The server should get a close event when it closes itself.');
is(serverSocket.readyState, 'closed',
'server readyState should be closed after close event');
// -- Re-establish connection
connectedPromise = waitForConnection(listeningServer);
clientSocket = createSocket('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
clientQueue = listenForEventsOnSocket(clientSocket, 'client');
is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
let connectedResult = yield connectedPromise;
// destructuring assignment is not yet ES6 compliant, must manually unpack
serverSocket = connectedResult.socket;
serverQueue = connectedResult.queue;
// -- Client closes the connection
clientSocket.close();
is(clientSocket.readyState, 'closing',
'client readyState should be losing immediately after calling close');
is((yield clientQueue.waitForEvent()).type, 'close',
'The client should get a close event when it closes itself.');
is(clientSocket.readyState, 'closed',
'client readyState should be closed after the close event is received');
is((yield serverQueue.waitForEvent()).type, 'close',
'The server should get a close event when the client closes.');
is(serverSocket.readyState, 'closed',
'server readyState should be closed after the close event is received');
// -- Re-establish connection
connectedPromise = waitForConnection(listeningServer);
clientSocket = createSocket('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
clientQueue = listenForEventsOnSocket(clientSocket, 'client');
is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
connectedResult = yield connectedPromise;
// destructuring assignment is not yet ES6 compliant, must manually unpack
serverSocket = connectedResult.socket;
serverQueue = connectedResult.queue;
// -- Call close after enqueueing a lot of data, make sure it goes through.
// We'll have the client send and close.
is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
'Client sending more than 64k should result in the buffer being full.');
clientSocket.close();
// The drain will still fire
is((yield clientQueue.waitForEvent()).type, 'drain',
'The drain event should fire after a large send that returned true.');
// Then we'll get a close
is((yield clientQueue.waitForEvent()).type, 'close',
'The close event should fire after the drain event.');
// The server will get its data
serverReceived = yield serverQueue.waitForDataWithAtLeastLength(
bigUint8Array.length);
assertUint8ArraysEqual(serverReceived, bigUint8Array,
'server received/client sent');
// And a close.
is((yield serverQueue.waitForEvent()).type, 'close',
'The drain event should fire after a large send that returned true.');
// -- Re-establish connection
connectedPromise = waitForConnection(listeningServer);
clientSocket = createSocket('127.0.0.1', serverPort,
{ binaryType: 'string' });
clientQueue = listenForEventsOnSocket(clientSocket, 'client');
is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
connectedResult = yield connectedPromise;
// destructuring assignment is not yet ES6 compliant, must manually unpack
serverSocket = connectedResult.socket;
serverQueue = connectedResult.queue;
// -- Attempt to send non-string data.
// Restore the original behavior by replacing toString with
// Object.prototype.toString. (bug 1121938)
bigUint8Array.toString = Object.prototype.toString;
is(clientSocket.send(bigUint8Array), true,
'Client sending a large non-string should only send a small string.');
clientSocket.close();
// The server will get its data
serverReceived = yield serverQueue.waitForDataWithAtLeastLength(
bigUint8Array.toString().length);
// Then we'll get a close
is((yield clientQueue.waitForEvent()).type, 'close',
'The close event should fire after the drain event.');
// -- Re-establish connection (Test for Close Immediately)
connectedPromise = waitForConnection(listeningServer);
clientSocket = createSocket('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
clientQueue = listenForEventsOnSocket(clientSocket, 'client');
is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
connectedResult = yield connectedPromise;
// destructuring assignment is not yet ES6 compliant, must manually unpack
serverSocket = connectedResult.socket;
serverQueue = connectedResult.queue;
// -- Attempt to send two non-string data.
is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
'Server sending more than 64k should result in the buffer being full.');
is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
'Server sending more than 64k should result in the buffer being full.');
clientSocket.closeImmediately();
serverReceived = yield serverQueue.waitForAnyDataAndClose();
is(serverReceived.length < (2 * bigUint8Array.length), true, 'Received array length less than sent array length');
// -- Close the listening server (and try to connect)
// We want to verify that the server actually closes / stops listening when
// we tell it to.
listeningServer.close();
// - try and connect, get an error
clientSocket = createSocket('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
clientQueue = listenForEventsOnSocket(clientSocket, 'client');
is((yield clientQueue.waitForEvent()).type, 'error', 'fail to connect');
is(clientSocket.readyState, 'closed',
'client readyState should be closed after the failure to connect');
}
add_task(test_basics);

View file

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure TCPSocket permission is disabled by default</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test to ensure TCPSocket permission is disabled by default **/
var caught = false;
try {
new TCPSocket("localhost", 80, {})
} catch (e) {
caught = true;
}
ok(caught, "TCPSocket should not exist by default");
var caught = false;
try {
navigator.mozTCPSocket.open("localhost", 80, {})
} catch (e) {
caught = true;
}
ok(caught, "navigator.mozTCPSocket.open should not exist by default");
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure TCPSocket permission enabled and no tcp-socket perm does not allow open</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test to ensure TCPSocket preference being turned on does not enable
navigator.mozTCPSocket.
**/
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [['dom.mozTCPSocket.enabled', true]]}, runTest);
function runTest() {
is('TCPSocket' in this, false, "TCPSocket should not be accessible if dom.mozTCPSocket.enabled is true");
is('TCPServerSocket' in this, false, "TCPServerSocket should not be accessible if dom.mozTCPSocket.enabled is true");
is('mozTCPSocket' in navigator, false, "mozTCPSocket should not be accessible if dom.mozTCPSocket.enabled is true");
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure TCPSocket permission enabled and open works with tcp-socket perm</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test to ensure TCPSocket permission being turned on enables
navigator.mozTCPSocket, and mozTCPSocket.open works when
the tcp-socket permission has been granted.
**/
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [['dom.mozTCPSocket.enabled', true]]}, runTest);
function runTest() {
ok('TCPSocket' in this, "TCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
ok(new TCPSocket('localhost', 80), "TCPSocket constructor should work for content that has the tcp-socket permission");
ok(navigator.mozTCPSocket.open('localhost', 80), "navigator.mozTCPSocket.open should work for content that has the tcp-socket permission");
// This just helps the test harness clean up quickly
SpecialPowers.forceCC();
SpecialPowers.forceGC();
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,25 @@
<!DOCTYPE HTML>
<html>
<!--
-->
<head>
<meta charset="utf-8">
<title>Test for 1207090</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<script type="application/javascript">
Components.utils.import("chrome://mochitests/content/chrome/dom/network/tests/tcpsocket_test.jsm");
</script>
<script type="application/javascript" src="add_task.js"></script>
<script type="application/javascript;version=1.7" src="test_tcpsocket_client_and_server_basics.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1207090">Mozilla Bug 1207090</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<div id="container"></div>
</body>
</html>

View file

@ -0,0 +1,59 @@
<!DOCTYPE HTML>
<html>
<!--
Test of legacy navigator interface for opening TCPSocket/TCPServerSocket.
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 885982</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1084245">Mozilla Bug 1084245</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script>
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{ set: [ ['dom.mozTCPSocket.enabled', true] ] },
runTest);
function runTest() {
// See bug 903830; in e10s mode we never get to find out the localPort if we
// let it pick a free port by choosing 0. This is the same port the xpcshell
// test was using.
var serverPort = 8085;
var listeningServer = navigator.mozTCPSocket.listen(serverPort,
{ binaryType: 'arraybuffer' },
-1);
listeningServer.onconnect = function(ev) {
ok(true, "got server connect");
listeningServer.close();
listeningServer = null;
ev.socket.close()
}
var clientSocket = navigator.mozTCPSocket.open('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
clientSocket.onopen = function() { ok(true, "got client open"); }
clientSocket.onclose = function() {
ok(true, "got client close");
clientSocket.close();
clientSocket = null;
setTimeout(function() {
// This just helps the test harness clean up quickly
SpecialPowers.forceCC();
SpecialPowers.forceGC();
SimpleTest.finish();
}, 0);
}
}
</script>
</body>
</html>

View file

@ -0,0 +1,405 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test UDPSocket API</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe id="iframe"></iframe>
<pre id="test">
<script type="application/javascript;version=1.8">
'use strict';
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("untriaged");
const HELLO_WORLD = 'hlo wrld. ';
const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0];
const DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length);
const TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER);
const BIG_ARRAY = new Array(4096);
const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length);
const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER);
for (let i = 0; i < BIG_ARRAY.length; i++) {
BIG_ARRAY[i] = Math.floor(Math.random() * 256);
}
TYPED_DATA_ARRAY.set(DATA_ARRAY);
BIG_TYPED_ARRAY.set(BIG_ARRAY);
function is_same_buffer(recv_data, expect_data) {
let recv_dataview = new Uint8Array(recv_data);
let expected_dataview = new Uint8Array(expect_data);
if (recv_dataview.length !== expected_dataview.length) {
return false;
}
for (let i = 0; i < recv_dataview.length; i++) {
if (recv_dataview[i] != expected_dataview[i]) {
info('discover byte differenct at ' + i);
return false;
}
}
return true;
}
function testOpen() {
info('test for creating an UDP Socket');
let socket = new UDPSocket();
is(socket.localPort, null, 'expect no local port before socket opened');
is(socket.localAddress, null, 'expect no local address before socket opened');
is(socket.remotePort, null, 'expected no default remote port');
is(socket.remoteAddress, null, 'expected no default remote address');
is(socket.readyState, 'opening', 'expected ready state = opening');
is(socket.loopback, false, 'expected no loopback');
is(socket.addressReuse, true, 'expect to reuse address');
return socket.opened.then(function() {
ok(true, 'expect openedPromise to be resolved after successful socket binding');
ok(!(socket.localPort === 0), 'expect allocated a local port');
is(socket.localAddress, '0.0.0.0', 'expect assigned to default address');
is(socket.readyState, 'open', 'expected ready state = open');
return socket;
});
}
function testSendString(socket) {
info('test for sending string data');
socket.send(HELLO_WORLD, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
is(recvData, HELLO_WORLD, 'expected same string data');
resolve(socket);
});
});
}
function testSendArrayBuffer(socket) {
info('test for sending ArrayBuffer');
socket.send(DATA_ARRAY_BUFFER, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
ok(is_same_buffer(msg.data, DATA_ARRAY_BUFFER), 'expected same buffer data');
resolve(socket);
});
});
}
function testSendArrayBufferView(socket) {
info('test for sending ArrayBufferView');
socket.send(TYPED_DATA_ARRAY, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
ok(is_same_buffer(msg.data, TYPED_DATA_ARRAY), 'expected same buffer data');
resolve(socket);
});
});
}
function testSendBlob(socket) {
info('test for sending Blob');
let blob = new Blob([HELLO_WORLD], {type : 'text/plain'});
socket.send(blob, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
is(recvData, HELLO_WORLD, 'expected same string data');
resolve(socket);
});
});
}
function testSendBigArray(socket) {
info('test for sending Big ArrayBuffer');
socket.send(BIG_TYPED_ARRAY, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
let byteReceived = 0;
socket.addEventListener('message', function recv_callback(msg) {
let byteBegin = byteReceived;
byteReceived += msg.data.byteLength;
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']');
if (byteReceived >= BIG_TYPED_ARRAY.length) {
socket.removeEventListener('message', recv_callback);
resolve(socket);
}
});
});
}
function testSendBigBlob(socket) {
info('test for sending Big Blob');
let blob = new Blob([BIG_TYPED_ARRAY]);
socket.send(blob, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
let byteReceived = 0;
socket.addEventListener('message', function recv_callback(msg) {
let byteBegin = byteReceived;
byteReceived += msg.data.byteLength;
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']');
if (byteReceived >= BIG_TYPED_ARRAY.length) {
socket.removeEventListener('message', recv_callback);
resolve(socket);
}
});
});
}
function testUDPOptions(socket) {
info('test for UDP init options');
let remoteSocket = new UDPSocket({addressReuse: false,
loopback: true,
localAddress: '127.0.0.1',
remoteAddress: '127.0.0.1',
remotePort: socket.localPort});
is(remoteSocket.localAddress, '127.0.0.1', 'expected local address');
is(remoteSocket.remoteAddress, '127.0.0.1', 'expected remote address');
is(remoteSocket.remotePort, socket.localPort, 'expected remote port');
is(remoteSocket.addressReuse, false, 'expected address not reusable');
is(remoteSocket.loopback, true, 'expected loopback mode is on');
return remoteSocket.opened.then(function() {
remoteSocket.send(HELLO_WORLD);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
is(msg.remotePort, remoteSocket.localPort, 'expected packet from ' + remoteSocket.localPort);
is(recvData, HELLO_WORLD, 'expected same string data');
resolve(socket);
});
});
});
}
function testClose(socket) {
info('test for close');
socket.close();
is(socket.readyState, 'closed', 'expect ready state to be "closed"');
try {
socket.send(HELLO_WORLD, '127.0.0.1', socket.localPort);
ok(false, 'unexpect to send successfully');
} catch (e) {
ok(true, 'expected send fail after socket closed');
}
return socket.closed.then(function() {
ok(true, 'expected closedPromise is resolved after socket.close()');
});
}
function testMulticast() {
info('test for multicast');
let socket = new UDPSocket({loopback: true});
const MCAST_ADDRESS = '224.0.0.255';
socket.joinMulticastGroup(MCAST_ADDRESS);
return socket.opened.then(function() {
socket.send(HELLO_WORLD, MCAST_ADDRESS, socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
is(recvData, HELLO_WORLD, 'expected same string data');
socket.leaveMulticastGroup(MCAST_ADDRESS);
resolve();
});
});
});
}
function testInvalidUDPOptions() {
info('test for invalid UDPOptions');
try {
let socket = new UDPSocket({localAddress: 'not-a-valid-address'});
ok(false, 'should not create an UDPSocket with an invalid localAddress');
} catch (e) {
is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localAddress is not a valid IPv4/6 address');
}
try {
let socket = new UDPSocket({localPort: 0});
ok(false, 'should not create an UDPSocket with an invalid localPort');
} catch (e) {
is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localPort is not a valid port number');
}
try {
let socket = new UDPSocket({remotePort: 0});
ok(false, 'should not create an UDPSocket with an invalid remotePort');
} catch (e) {
is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localPort is not a valid port number');
}
}
function testOpenFailed() {
info('test for falied on open');
//according to RFC5737, address block 192.0.2.0/24 should not be used in both local and public contexts
let socket = new UDPSocket({localAddress: '192.0.2.0'});
return socket.opened.then(function() {
ok(false, 'should not resolve openedPromise while fail to bind socket');
socket.close();
}).catch(function(reason) {
is(reason.name, 'NetworkError', 'expected openedPromise to be rejected while fail to bind socket');
});
}
function testSendBeforeOpen() {
info('test for send before open');
let socket = new UDPSocket();
try {
socket.send(HELLO_WORLD, '127.0.0.1', 9);
ok(false, 'unexpect to send successfully');
} catch (e) {
ok(true, 'expected send fail before openedPromise is resolved');
}
return socket.opened.then(function() {
socket.close();
});
}
function testCloseBeforeOpened() {
info('test for close socket before opened');
let socket = new UDPSocket();
socket.opened.then(function() {
ok(false, 'should not resolve openedPromise if it has already been closed');
}).catch(function(reason) {
is(reason.name, 'AbortError', 'expected openedPromise to be rejected while socket is closed during opening');
});
return socket.close().then(function() {
ok(true, 'expected closedPromise to be resolved');
}).then(socket.opened);
}
function testOpenWithoutClose() {
info('test for open without close');
let closed = [];
for (let i = 0; i < 50; i++) {
let socket = new UDPSocket();
closed.push(socket.closed);
}
SpecialPowers.gc();
info('all unrefereced socket should be closed right after GC');
return Promise.all(closed);
}
function testBFCache() {
info('test for bfcache behavior');
let socket = new UDPSocket();
return socket.opened.then(function() {
let iframe = document.getElementById('iframe');
SpecialPowers.wrap(iframe).mozbrowser = true;
iframe.src = 'file_udpsocket_iframe.html?' + socket.localPort;
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
iframe.src = 'about:blank';
iframe.addEventListener('load', function onload() {
iframe.removeEventListener('load', onload);
socket.send(HELLO_WORLD, '127.0.0.1', msg.remotePort);
function recv_again_callback(msg) {
socket.removeEventListener('message', recv_again_callback);
ok(false, 'should not receive packet after page unload');
}
socket.addEventListener('message', recv_again_callback);
let timeout = setTimeout(function() {
socket.removeEventListener('message', recv_again_callback);
socket.close();
resolve();
}, 5000);
});
});
});
});
}
function runTest() {
testOpen()
.then(testSendString)
.then(testSendArrayBuffer)
.then(testSendArrayBufferView)
.then(testSendBlob)
.then(testSendBigArray)
.then(testSendBigBlob)
.then(testUDPOptions)
.then(testClose)
.then(testMulticast)
.then(testInvalidUDPOptions)
.then(testOpenFailed)
.then(testSendBeforeOpen)
.then(testCloseBeforeOpened)
.then(testOpenWithoutClose)
.then(testBFCache)
.then(function() {
info('test finished');
SimpleTest.finish();
})
.catch(function(err) {
ok(false, 'test failed due to: ' + err);
SimpleTest.finish();
});
}
window.addEventListener('load', function () {
SpecialPowers.pushPrefEnv({
'set': [
['dom.udpsocket.enabled', true],
['browser.sessionhistory.max_total_viewers', 10]
]
}, runTest);
});
</script>
</pre>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,290 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const NETWORK_STATUS_READY = 0;
const NETWORK_STATUS_STANDBY = 1;
const NETWORK_STATUS_AWAY = 2;
const QUEUE_TYPE_UPDATE_STATS = 0;
var wifiId = '00';
function getNetworks(callback) {
NetworkStatsService._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
callback(aError, aResult);
});
}
add_test(function test_clearDB() {
getNetworks(function onGetNetworks(error, result) {
do_check_eq(error, null);
var networks = result;
networks.forEach(function(network, index) {
networks[index] = {network: network, networkId: NetworkStatsService.getNetworkId(network.id, network.type)};
}, this);
NetworkStatsService._db.clearStats(networks, function onDBCleared(error, result) {
do_check_eq(error, null);
run_next_test();
});
});
});
function getNetworkId(callback) {
getNetworks(function onGetNetworks(error, result) {
do_check_eq(error, null);
var netId = NetworkStatsService.getNetworkId(result[0].id, result[0].type);
callback(null, netId);
});
}
add_test(function test_networkStatsAvailable_ok() {
getNetworkId(function onGetId(error, result) {
do_check_eq(error, null);
var netId = result;
NetworkStatsService.networkStatsAvailable(function (success, msg) {
do_check_eq(success, true);
run_next_test();
}, netId, true, 1234, 4321, Date.now());
});
});
add_test(function test_networkStatsAvailable_failure() {
getNetworkId(function onGetId(error, result) {
do_check_eq(error, null);
var netId = result;
NetworkStatsService.networkStatsAvailable(function (success, msg) {
do_check_eq(success, false);
run_next_test();
}, netId, false, 1234, 4321, Date.now());
});
});
add_test(function test_update_invalidNetwork() {
NetworkStatsService.update(-1, function (success, msg) {
do_check_eq(success, false);
do_check_eq(msg, "Invalid network -1");
run_next_test();
});
});
add_test(function test_update() {
getNetworkId(function onGetId(error, result) {
do_check_eq(error, null);
var netId = result;
NetworkStatsService.update(netId, function (success, msg) {
do_check_eq(success, true);
run_next_test();
});
});
});
add_test(function test_updateQueueIndex() {
NetworkStatsService.updateQueue = [{netId: 0, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS},
{netId: 1, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS},
{netId: 2, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS},
{netId: 3, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS},
{netId: 4, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS}];
var index = NetworkStatsService.updateQueueIndex(3);
do_check_eq(index, 3);
index = NetworkStatsService.updateQueueIndex(10);
do_check_eq(index, -1);
NetworkStatsService.updateQueue = [];
run_next_test();
});
add_test(function test_updateAllStats() {
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_READY;
NetworkStatsService.updateAllStats(function(success, msg) {
do_check_eq(success, true);
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_STANDBY;
NetworkStatsService.updateAllStats(function(success, msg) {
do_check_eq(success, true);
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_AWAY;
NetworkStatsService.updateAllStats(function(success, msg) {
do_check_eq(success, true);
run_next_test();
});
});
});
});
add_test(function test_updateStats_ok() {
getNetworkId(function onGetId(error, result) {
do_check_eq(error, null);
var netId = result;
NetworkStatsService.updateStats(netId, function(success, msg){
do_check_eq(success, true);
run_next_test();
});
});
});
add_test(function test_updateStats_failure() {
NetworkStatsService.updateStats(-1, function(success, msg){
do_check_eq(success, false);
run_next_test();
});
});
// Define Mockup function to simulate a request to netd
function MockNetdRequest(aCallback) {
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
var event = {
notify: function (timer) {
aCallback();
}
};
timer.initWithCallback(event, 100, Ci.nsITimer.TYPE_ONE_SHOT);
}
add_test(function test_queue() {
// Overwrite update function of NetworkStatsService to avoid netd errors due to use
// fake interfaces. First, original function is stored to restore it at the end of the
// test.
var updateFunctionBackup = NetworkStatsService.update;
NetworkStatsService.update = function update(aNetId, aCallback) {
MockNetdRequest(function () {
if (aCallback) {
aCallback(true, "ok");
}
});
};
// Fill networks with fake network interfaces to enable netd async requests.
var network = {id: "1234", type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE};
var netId1 = NetworkStatsService.getNetworkId(network.id, network.type);
NetworkStatsService._networks[netId1] = { network: network,
interfaceName: "net1" };
network = {id: "5678", type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE};
var netId2 = NetworkStatsService.getNetworkId(network.id, network.type);
NetworkStatsService._networks[netId2] = { network: network,
interfaceName: "net2" };
NetworkStatsService.updateStats(netId1);
NetworkStatsService.updateStats(netId2);
do_check_eq(NetworkStatsService.updateQueue.length, 2);
do_check_eq(NetworkStatsService.updateQueue[0].callbacks.length, 1);
var i = 0;
var updateCount = 0;
var callback = function(success, msg) {
i++;
if (i >= updateCount) {
NetworkStatsService.update = updateFunctionBackup;
run_next_test();
}
};
NetworkStatsService.updateStats(netId1, callback);
updateCount++;
NetworkStatsService.updateStats(netId2, callback);
updateCount++;
do_check_eq(NetworkStatsService.updateQueue.length, 2);
do_check_eq(NetworkStatsService.updateQueue[0].callbacks.length, 2);
do_check_eq(NetworkStatsService.updateQueue[0].callbacks[0], null);
do_check_neq(NetworkStatsService.updateQueue[0].callbacks[1], null);
});
add_test(function test_getAlarmQuota() {
let alarm = { networkId: wifiId, absoluteThreshold: 10000 };
NetworkStatsService._getAlarmQuota(alarm, function onSet(error, quota){
do_check_eq(error, null);
do_check_neq(quota, undefined);
do_check_eq(alarm.absoluteThreshold, alarm.relativeThreshold);
run_next_test();
});
});
var testPageURL = "http://test.com";
var testManifestURL = "http://test.com/manifest.webapp";
add_test(function test_setAlarm() {
let alarm = { id: null,
networkId: wifiId,
threshold: 10000,
absoluteThreshold: null,
alarmStart: null,
alarmEnd: null,
data: null,
pageURL: testPageURL,
manifestURL: testManifestURL };
NetworkStatsService._setAlarm(alarm, function onSet(error, result) {
do_check_eq(result, 1);
run_next_test();
});
});
add_test(function test_setAlarm_invalid_threshold() {
let alarm = { id: null,
networkId: wifiId,
threshold: -10000,
absoluteThreshold: null,
alarmStart: null,
alarmEnd: null,
data: null,
pageURL: testPageURL,
manifestURL: testManifestURL };
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_READY;
NetworkStatsService._setAlarm(alarm, function onSet(error, result) {
do_check_eq(error, "InvalidStateError");
run_next_test();
});
});
add_test(function test_fireAlarm() {
// Add a fake alarm into database.
let alarm = { id: null,
networkId: wifiId,
threshold: 10000,
absoluteThreshold: null,
alarmStart: null,
alarmEnd: null,
data: null,
pageURL: testPageURL,
manifestURL: testManifestURL };
// Set wifi status to standby to avoid connecting to netd when adding an alarm.
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_STANDBY;
NetworkStatsService._db.addAlarm(alarm, function addSuccessCb(error, newId) {
NetworkStatsService._db.getAlarms(Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
testManifestURL, function onGet(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 1);
// Result of getAlarms is based on expected child's data format, so
// some changes are needed to be able to use it.
result[0].networkId = wifiId;
result[0].pageURL = testPageURL;
result[0].manifestURL = testManifestURL;
NetworkStatsService._fireAlarm(result[0], false);
NetworkStatsService._db.getAlarms(Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
testManifestURL, function onGet(error, result) {
do_check_eq(error, undefined);
do_check_eq(result.length, 0);
run_next_test();
});
});
});
});
function run_test() {
do_get_profile();
Cu.import("resource://gre/modules/NetworkStatsService.jsm");
run_next_test();
}

View file

@ -0,0 +1,233 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "nssProxy",
"@mozilla.org/networkstatsServiceProxy;1",
"nsINetworkStatsServiceProxy");
function mokConvertNetworkInfo() {
NetworkStatsService.convertNetworkInfo = function(aNetworkInfo) {
if (aNetworkInfo.type != Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE &&
aNetworkInfo.type != Ci.nsINetworkInfo.NETWORK_TYPE_WIFI) {
return null;
}
let id = '0';
if (aNetworkInfo.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE) {
id = '1234'
}
let netId = this.getNetworkId(id, aNetworkInfo.type);
if (!this._networks[netId]) {
this._networks[netId] = Object.create(null);
this._networks[netId].network = { id: id,
type: aNetworkInfo.type };
}
return netId;
};
}
add_test(function test_saveAppStats() {
var cachedStats = NetworkStatsService.cachedStats;
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
// Create to fake nsINetworkInfos. As nsINetworkInfo can not be instantiated,
// these two vars will emulate it by filling the properties that will be used.
var wifi = {type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, id: "1234"};
// Insert fake mobile network info in NetworkStatsService
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
do_check_eq(Object.keys(cachedStats).length, 0);
nssProxy.saveAppStats(1, false, wifi, timestamp, 10, 20, false,
function (success, message) {
do_check_eq(success, true);
nssProxy.saveAppStats(1, false, mobile, timestamp, 10, 20, false,
function (success, message) {
var key1 = 1 + "" + false + "" + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
var key2 = 1 + "" + false + "" + mobileNetId + "";
do_check_eq(Object.keys(cachedStats).length, 2);
do_check_eq(cachedStats[key1].appId, 1);
do_check_eq(cachedStats[key1].isInBrowser, false);
do_check_eq(cachedStats[key1].serviceType.length, 0);
do_check_eq(cachedStats[key1].networkId, wifi.id);
do_check_eq(cachedStats[key1].networkType, wifi.type);
do_check_eq(cachedStats[key1].date.getTime(), timestamp);
do_check_eq(cachedStats[key1].rxBytes, 10);
do_check_eq(cachedStats[key1].txBytes, 20);
do_check_eq(cachedStats[key2].appId, 1);
do_check_eq(cachedStats[key1].serviceType.length, 0);
do_check_eq(cachedStats[key2].networkId, mobile.id);
do_check_eq(cachedStats[key2].networkType, mobile.type);
do_check_eq(cachedStats[key2].date.getTime(), timestamp);
do_check_eq(cachedStats[key2].rxBytes, 10);
do_check_eq(cachedStats[key2].txBytes, 20);
run_next_test();
});
});
});
add_test(function test_saveServiceStats() {
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
// Create to fake nsINetworkInfos. As nsINetworkInfo can not be instantiated,
// these two vars will emulate it by filling the properties that will be used.
var wifi = {type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, id: "1234"};
// Insert fake mobile network info in NetworkStatsService
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
NetworkStatsService.updateCachedStats(function (success, msg) {
do_check_eq(success, true);
var cachedStats = NetworkStatsService.cachedStats;
do_check_eq(Object.keys(cachedStats).length, 0);
var serviceType = 'FakeType';
nssProxy.saveServiceStats(serviceType, wifi, timestamp, 10, 20, false,
function (success, message) {
do_check_eq(success, true);
nssProxy.saveServiceStats(serviceType, mobile, timestamp, 10, 20, false,
function (success, message) {
do_check_eq(success, true);
var key1 = 0 + "" + false + "" + serviceType +
NetworkStatsService.getNetworkId(wifi.id, wifi.type);
var key2 = 0 + "" + false + "" + serviceType + mobileNetId + "";
do_check_eq(Object.keys(cachedStats).length, 2);
do_check_eq(cachedStats[key1].appId, 0);
do_check_eq(cachedStats[key1].isInBrowser, false);
do_check_eq(cachedStats[key1].serviceType, serviceType);
do_check_eq(cachedStats[key1].networkId, wifi.id);
do_check_eq(cachedStats[key1].networkType, wifi.type);
do_check_eq(cachedStats[key1].date.getTime(), timestamp);
do_check_eq(cachedStats[key1].rxBytes, 10);
do_check_eq(cachedStats[key1].txBytes, 20);
do_check_eq(cachedStats[key2].appId, 0);
do_check_eq(cachedStats[key1].serviceType, serviceType);
do_check_eq(cachedStats[key2].networkId, mobile.id);
do_check_eq(cachedStats[key2].networkType, mobile.type);
do_check_eq(cachedStats[key2].date.getTime(), timestamp);
do_check_eq(cachedStats[key2].rxBytes, 10);
do_check_eq(cachedStats[key2].txBytes, 20);
run_next_test();
});
});
});
});
add_test(function test_saveStatsWithDifferentDates() {
var today = NetworkStatsService.cachedStatsDate;
var tomorrow = new Date(today.getTime() + (24 * 60 * 60 * 1000));
var mobile = {type: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, id: "1234"};
NetworkStatsService.updateCachedStats(function (success, message) {
do_check_eq(success, true);
do_check_eq(Object.keys(NetworkStatsService.cachedStats).length, 0);
nssProxy.saveAppStats(1, false, mobile, today.getTime(), 10, 20, false,
function (success, message) {
do_check_eq(success, true);
nssProxy.saveAppStats(2, false, mobile, tomorrow.getTime(), 30, 40, false,
function (success, message) {
do_check_eq(success, true);
var cachedStats = NetworkStatsService.cachedStats;
var key = 2 + "" + false + "" +
NetworkStatsService.getNetworkId(mobile.id, mobile.type);
do_check_eq(Object.keys(cachedStats).length, 1);
do_check_eq(cachedStats[key].appId, 2);
do_check_eq(cachedStats[key].isInBrowser, false);
do_check_eq(cachedStats[key].networkId, mobile.id);
do_check_eq(cachedStats[key].networkType, mobile.type);
do_check_eq(cachedStats[key].date.getTime(), tomorrow.getTime());
do_check_eq(cachedStats[key].rxBytes, 30);
do_check_eq(cachedStats[key].txBytes, 40);
run_next_test();
});
});
});
});
add_test(function test_saveStatsWithMaxCachedTraffic() {
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
var maxtraffic = NetworkStatsService.maxCachedTraffic;
var wifi = {type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, id: "0"};
NetworkStatsService.updateCachedStats(function (success, message) {
do_check_eq(success, true);
var cachedStats = NetworkStatsService.cachedStats;
do_check_eq(Object.keys(cachedStats).length, 0);
nssProxy.saveAppStats(1, false, wifi, timestamp, 10, 20, false,
function (success, message) {
do_check_eq(success, true);
do_check_eq(Object.keys(cachedStats).length, 1);
nssProxy.saveAppStats(1, false, wifi, timestamp, maxtraffic, 20, false,
function (success, message) {
do_check_eq(success, true);
do_check_eq(Object.keys(cachedStats).length, 0);
run_next_test();
});
});
});
});
add_test(function test_saveAppStats() {
var cachedStats = NetworkStatsService.cachedStats;
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
// Create to fake nsINetworkInfo. As nsINetworkInfo can not
// be instantiated, these two vars will emulate it by filling the properties
// that will be used.
var wifi = {type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, id: "1234"};
// Insert fake mobile network interface in NetworkStatsService
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
do_check_eq(Object.keys(cachedStats).length, 0);
nssProxy.saveAppStats(1, false, wifi, timestamp, 10, 20, false, { notify:
function (success, message) {
do_check_eq(success, true);
var iterations = 10;
var counter = 0;
var callback = function (success, message) {
if (counter == iterations - 1)
run_next_test();
counter++;
};
for (var i = 0; i < iterations; i++) {
nssProxy.saveAppStats(1, false, mobile, timestamp, 10, 20, false, callback);
}
}});
});
function run_test() {
do_get_profile();
Cu.import("resource://gre/modules/NetworkStatsService.jsm");
// Function convertNetworkInfo of NetworkStatsService causes errors when dealing
// with RIL to get the iccid, so overwrite it.
mokConvertNetworkInfo();
run_next_test();
}

View file

@ -0,0 +1,7 @@
[DEFAULT]
head =
tail =
[test_networkstats_service.js]
[test_networkstats_service_proxy.js]
[test_networkstats_db.js]