summaryrefslogtreecommitdiffstats
path: root/dom/wifi/WifiWorker.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/wifi/WifiWorker.js')
-rw-r--r--dom/wifi/WifiWorker.js3928
1 files changed, 3928 insertions, 0 deletions
diff --git a/dom/wifi/WifiWorker.js b/dom/wifi/WifiWorker.js
new file mode 100644
index 000000000..243ba8b97
--- /dev/null
+++ b/dom/wifi/WifiWorker.js
@@ -0,0 +1,3928 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/WifiCommand.jsm");
+Cu.import("resource://gre/modules/WifiNetUtil.jsm");
+Cu.import("resource://gre/modules/WifiP2pManager.jsm");
+Cu.import("resource://gre/modules/WifiP2pWorkerObserver.jsm");
+
+var DEBUG = false; // set to true to show debug messages.
+
+const WIFIWORKER_CONTRACTID = "@mozilla.org/wifi/worker;1";
+const WIFIWORKER_CID = Components.ID("{a14e8977-d259-433a-a88d-58dd44657e5b}");
+
+const WIFIWORKER_WORKER = "resource://gre/modules/wifi_worker.js";
+
+const kMozSettingsChangedObserverTopic = "mozsettings-changed";
+
+const MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
+const MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
+const MAX_RETRIES_ON_DHCP_FAILURE = 2;
+
+// Settings DB path for wifi
+const SETTINGS_WIFI_ENABLED = "wifi.enabled";
+const SETTINGS_WIFI_DEBUG_ENABLED = "wifi.debugging.enabled";
+// Settings DB path for Wifi tethering.
+const SETTINGS_WIFI_TETHERING_ENABLED = "tethering.wifi.enabled";
+const SETTINGS_WIFI_SSID = "tethering.wifi.ssid";
+const SETTINGS_WIFI_SECURITY_TYPE = "tethering.wifi.security.type";
+const SETTINGS_WIFI_SECURITY_PASSWORD = "tethering.wifi.security.password";
+const SETTINGS_WIFI_IP = "tethering.wifi.ip";
+const SETTINGS_WIFI_PREFIX = "tethering.wifi.prefix";
+const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
+const SETTINGS_WIFI_DHCPSERVER_ENDIP = "tethering.wifi.dhcpserver.endip";
+const SETTINGS_WIFI_DNS1 = "tethering.wifi.dns1";
+const SETTINGS_WIFI_DNS2 = "tethering.wifi.dns2";
+
+// Settings DB path for USB tethering.
+const SETTINGS_USB_DHCPSERVER_STARTIP = "tethering.usb.dhcpserver.startip";
+const SETTINGS_USB_DHCPSERVER_ENDIP = "tethering.usb.dhcpserver.endip";
+
+// Default value for WIFI tethering.
+const DEFAULT_WIFI_IP = "192.168.1.1";
+const DEFAULT_WIFI_PREFIX = "24";
+const DEFAULT_WIFI_DHCPSERVER_STARTIP = "192.168.1.10";
+const DEFAULT_WIFI_DHCPSERVER_ENDIP = "192.168.1.30";
+const DEFAULT_WIFI_SSID = "FirefoxHotspot";
+const DEFAULT_WIFI_SECURITY_TYPE = "open";
+const DEFAULT_WIFI_SECURITY_PASSWORD = "1234567890";
+const DEFAULT_DNS1 = "8.8.8.8";
+const DEFAULT_DNS2 = "8.8.4.4";
+
+// Default value for USB tethering.
+const DEFAULT_USB_DHCPSERVER_STARTIP = "192.168.0.10";
+const DEFAULT_USB_DHCPSERVER_ENDIP = "192.168.0.30";
+
+const WIFI_FIRMWARE_AP = "AP";
+const WIFI_FIRMWARE_STATION = "STA";
+const WIFI_SECURITY_TYPE_NONE = "open";
+const WIFI_SECURITY_TYPE_WPA_PSK = "wpa-psk";
+const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk";
+
+const NETWORK_INTERFACE_UP = "up";
+const NETWORK_INTERFACE_DOWN = "down";
+
+const DEFAULT_WLAN_INTERFACE = "wlan0";
+
+const DRIVER_READY_WAIT = 2000;
+
+const SUPP_PROP = "init.svc.wpa_supplicant";
+const WPA_SUPPLICANT = "wpa_supplicant";
+const DHCP_PROP = "init.svc.dhcpcd";
+const DHCP = "dhcpcd";
+
+const MODE_ESS = 0;
+const MODE_IBSS = 1;
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+ "@mozilla.org/network/manager;1",
+ "nsINetworkManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+ "@mozilla.org/network/service;1",
+ "nsINetworkService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gTetheringService",
+ "@mozilla.org/tethering/service;1",
+ "nsITetheringService");
+
+// A note about errors and error handling in this file:
+// The libraries that we use in this file are intended for C code. For
+// C code, it is natural to return -1 for errors and 0 for success.
+// Therefore, the code that interacts directly with the worker uses this
+// convention (note: command functions do get boolean results since the
+// command always succeeds and we do a string/boolean check for the
+// expected results).
+var WifiManager = (function() {
+ var manager = {};
+
+ function getStartupPrefs() {
+ return {
+ sdkVersion: parseInt(libcutils.property_get("ro.build.version.sdk"), 10),
+ unloadDriverEnabled: libcutils.property_get("ro.moz.wifi.unloaddriver") === "1",
+ schedScanRecovery: libcutils.property_get("ro.moz.wifi.sched_scan_recover") === "false" ? false : true,
+ driverDelay: libcutils.property_get("ro.moz.wifi.driverDelay"),
+ p2pSupported: libcutils.property_get("ro.moz.wifi.p2p_supported") === "1",
+ eapSimSupported: libcutils.property_get("ro.moz.wifi.eapsim_supported") === "1",
+ ibssSupported: libcutils.property_get("ro.moz.wifi.ibss_supported", "true") === "true",
+ ifname: libcutils.property_get("wifi.interface")
+ };
+ }
+
+ let {sdkVersion, unloadDriverEnabled, schedScanRecovery,
+ driverDelay, p2pSupported, eapSimSupported, ibssSupported, ifname} = getStartupPrefs();
+
+ let capabilities = {
+ security: ["OPEN", "WEP", "WPA-PSK", "WPA-EAP"],
+ eapMethod: ["PEAP", "TTLS", "TLS"],
+ eapPhase2: ["MSCHAPV2"],
+ certificate: ["SERVER"],
+ mode: [MODE_ESS]
+ };
+ if (eapSimSupported) {
+ capabilities.eapMethod.unshift("SIM");
+ }
+ if (ibssSupported) {
+ capabilities.mode.push(MODE_IBSS);
+ }
+
+ let wifiListener = {
+ onWaitEvent: function(event, iface) {
+ if (manager.ifname === iface && handleEvent(event)) {
+ waitForEvent(iface);
+ } else if (p2pSupported) {
+ // Please refer to
+ // http://androidxref.com/4.4.2_r1/xref/frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java#519
+ // for interface event mux/demux rules. In short words, both
+ // 'p2p0' and 'p2p-' should go to Wifi P2P state machine.
+ if (WifiP2pManager.INTERFACE_NAME === iface || -1 !== iface.indexOf('p2p-')) {
+ // If the connection is closed, wifi.c::wifi_wait_for_event()
+ // will still return 'CTRL-EVENT-TERMINATING - connection closed'
+ // rather than blocking. So when we see this special event string,
+ // just return immediately.
+ const TERMINATED_EVENT = 'CTRL-EVENT-TERMINATING - connection closed';
+ if (-1 !== event.indexOf(TERMINATED_EVENT)) {
+ return;
+ }
+ p2pManager.handleEvent(event);
+ waitForEvent(iface);
+ }
+ }
+ },
+
+ onCommand: function(event, iface) {
+ onmessageresult(event, iface);
+ }
+ }
+
+ manager.ifname = ifname;
+ manager.connectToSupplicant = false;
+ // Emulator build runs to here.
+ // The debug() should only be used after WifiManager.
+ if (!ifname) {
+ manager.ifname = DEFAULT_WLAN_INTERFACE;
+ }
+ manager.schedScanRecovery = schedScanRecovery;
+ manager.driverDelay = driverDelay ? parseInt(driverDelay, 10) : DRIVER_READY_WAIT;
+
+ // Regular Wifi stuff.
+ var netUtil = WifiNetUtil(controlMessage);
+ var wifiCommand = WifiCommand(controlMessage, manager.ifname, sdkVersion);
+
+ // Wifi P2P stuff
+ var p2pManager;
+ if (p2pSupported) {
+ let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME, sdkVersion);
+ p2pManager = WifiP2pManager(p2pCommand, netUtil);
+ }
+
+ let wifiService = Cc["@mozilla.org/wifi/service;1"];
+ if (wifiService) {
+ wifiService = wifiService.getService(Ci.nsIWifiProxyService);
+ let interfaces = [manager.ifname];
+ if (p2pSupported) {
+ interfaces.push(WifiP2pManager.INTERFACE_NAME);
+ }
+ wifiService.start(wifiListener, interfaces, interfaces.length);
+ } else {
+ debug("No wifi service component available!");
+ }
+
+ // Callbacks to invoke when a reply arrives from the wifi service.
+ var controlCallbacks = Object.create(null);
+ var idgen = 0;
+
+ function controlMessage(obj, callback) {
+ var id = idgen++;
+ obj.id = id;
+ if (callback) {
+ controlCallbacks[id] = callback;
+ }
+ wifiService.sendCommand(obj, obj.iface);
+ }
+
+ let onmessageresult = function(data, iface) {
+ var id = data.id;
+ var callback = controlCallbacks[id];
+ if (callback) {
+ callback(data);
+ delete controlCallbacks[id];
+ }
+ }
+
+ // Polling the status worker
+ var recvErrors = 0;
+
+ function waitForEvent(iface) {
+ wifiService.waitForEvent(iface);
+ }
+
+ // Commands to the control worker.
+
+ var driverLoaded = false;
+
+ function loadDriver(callback) {
+ if (driverLoaded) {
+ callback(0);
+ return;
+ }
+
+ wifiCommand.loadDriver(function (status) {
+ driverLoaded = (status >= 0);
+ callback(status)
+ });
+ }
+
+ function unloadDriver(type, callback) {
+ if (!unloadDriverEnabled) {
+ // Unloading drivers is generally unnecessary and
+ // can trigger bugs in some drivers.
+ // On properly written drivers, bringing the interface
+ // down powers down the interface.
+ if (type === WIFI_FIRMWARE_STATION) {
+ notify("supplicantlost", { success: true });
+ }
+ callback(0);
+ return;
+ }
+
+ wifiCommand.unloadDriver(function(status) {
+ driverLoaded = (status < 0);
+ if (type === WIFI_FIRMWARE_STATION) {
+ notify("supplicantlost", { success: true });
+ }
+ callback(status);
+ });
+ }
+
+ // A note about background scanning:
+ // Normally, background scanning shouldn't be necessary as wpa_supplicant
+ // has the capability to automatically schedule its own scans at appropriate
+ // intervals. However, with some drivers, this appears to get stuck after
+ // three scans, so we enable the driver's background scanning to work around
+ // that when we're not connected to any network. This ensures that we'll
+ // automatically reconnect to networks if one falls out of range.
+ var reEnableBackgroundScan = false;
+
+ // NB: This is part of the internal API.
+ manager.backgroundScanEnabled = false;
+ function setBackgroundScan(enable, callback) {
+ var doEnable = (enable === "ON");
+ if (doEnable === manager.backgroundScanEnabled) {
+ callback(false, true);
+ return;
+ }
+
+ manager.backgroundScanEnabled = doEnable;
+ wifiCommand.setBackgroundScan(manager.backgroundScanEnabled, callback);
+ }
+
+ var scanModeActive = false;
+
+ function scan(forceActive, callback) {
+ if (forceActive && !scanModeActive) {
+ // Note: we ignore errors from doSetScanMode.
+ wifiCommand.doSetScanMode(true, function(ignore) {
+ setBackgroundScan("OFF", function(turned, ignore) {
+ reEnableBackgroundScan = turned;
+ manager.handlePreWifiScan();
+ wifiCommand.scan(function(ok) {
+ wifiCommand.doSetScanMode(false, function(ignore) {
+ // The result of scanCommand is the result of the actual SCAN
+ // request.
+ callback(ok);
+ });
+ });
+ });
+ });
+ return;
+ }
+ manager.handlePreWifiScan();
+ wifiCommand.scan(callback);
+ }
+
+ var debugEnabled = false;
+
+ function syncDebug() {
+ if (debugEnabled !== DEBUG) {
+ let wanted = DEBUG;
+ wifiCommand.setLogLevel(wanted ? "DEBUG" : "INFO", function(ok) {
+ if (ok)
+ debugEnabled = wanted;
+ });
+ if (p2pSupported && p2pManager) {
+ p2pManager.setDebug(DEBUG);
+ }
+ }
+ }
+
+ function getDebugEnabled(callback) {
+ wifiCommand.getLogLevel(function(level) {
+ if (level === null) {
+ debug("Unable to get wpa_supplicant's log level");
+ callback(false);
+ return;
+ }
+
+ var lines = level.split("\n");
+ for (let i = 0; i < lines.length; ++i) {
+ let match = /Current level: (.*)/.exec(lines[i]);
+ if (match) {
+ debugEnabled = match[1].toLowerCase() === "debug";
+ callback(true);
+ return;
+ }
+ }
+
+ // If we're here, we didn't get the current level.
+ callback(false);
+ });
+ }
+
+ function setScanMode(setActive, callback) {
+ scanModeActive = setActive;
+ wifiCommand.doSetScanMode(setActive, callback);
+ }
+
+ var httpProxyConfig = Object.create(null);
+
+ /**
+ * Given a network, configure http proxy when using wifi.
+ * @param network A network object to update http proxy
+ * @param info Info should have following field:
+ * - httpProxyHost ip address of http proxy.
+ * - httpProxyPort port of http proxy, set 0 to use default port 8080.
+ * @param callback callback function.
+ */
+ function configureHttpProxy(network, info, callback) {
+ if (!network)
+ return;
+
+ let networkKey = getNetworkKey(network);
+
+ if (!info || info.httpProxyHost === "") {
+ delete httpProxyConfig[networkKey];
+ } else {
+ httpProxyConfig[networkKey] = network;
+ httpProxyConfig[networkKey].httpProxyHost = info.httpProxyHost;
+ httpProxyConfig[networkKey].httpProxyPort = info.httpProxyPort;
+ }
+
+ callback(true);
+ }
+
+ function getHttpProxyNetwork(network) {
+ if (!network)
+ return null;
+
+ let networkKey = getNetworkKey(network);
+ return httpProxyConfig[networkKey];
+ }
+
+ function setHttpProxy(network) {
+ if (!network)
+ return;
+
+ // If we got here, arg network must be the currentNetwork, so we just update
+ // WifiNetworkInterface correspondingly and notify NetworkManager.
+ WifiNetworkInterface.httpProxyHost = network.httpProxyHost;
+ WifiNetworkInterface.httpProxyPort = network.httpProxyPort;
+
+ if (WifiNetworkInterface.info.state ==
+ Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
+ }
+ }
+
+ var staticIpConfig = Object.create(null);
+ function setStaticIpMode(network, info, callback) {
+ let setNetworkKey = getNetworkKey(network);
+ let curNetworkKey = null;
+ let currentNetwork = Object.create(null);
+ currentNetwork.netId = manager.connectionInfo.id;
+
+ manager.getNetworkConfiguration(currentNetwork, function () {
+ curNetworkKey = getNetworkKey(currentNetwork);
+
+ // Add additional information to static ip configuration
+ // It is used to compatiable with information dhcp callback.
+ info.ipaddr = netHelpers.stringToIP(info.ipaddr_str);
+ info.gateway = netHelpers.stringToIP(info.gateway_str);
+ info.mask_str = netHelpers.ipToString(netHelpers.makeMask(info.maskLength));
+
+ // Optional
+ info.dns1 = netHelpers.stringToIP(info.dns1_str);
+ info.dns2 = netHelpers.stringToIP(info.dns2_str);
+ info.proxy = netHelpers.stringToIP(info.proxy_str);
+
+ staticIpConfig[setNetworkKey] = info;
+
+ // If the ssid of current connection is the same as configured ssid
+ // It means we need update current connection to use static IP address.
+ if (setNetworkKey == curNetworkKey) {
+ // Use configureInterface directly doesn't work, the network interface
+ // and routing table is changed but still cannot connect to network
+ // so the workaround here is disable interface the enable again to
+ // trigger network reconnect with static ip.
+ gNetworkService.disableInterface(manager.ifname, function (ok) {
+ gNetworkService.enableInterface(manager.ifname, function (ok) {
+ callback(ok);
+ });
+ });
+ return;
+ }
+
+ callback(true);
+ });
+ }
+
+ var dhcpInfo = null;
+
+ function runStaticIp(ifname, key) {
+ debug("Run static ip");
+
+ // Read static ip information from settings.
+ let staticIpInfo;
+
+ if (!(key in staticIpConfig))
+ return;
+
+ staticIpInfo = staticIpConfig[key];
+
+ // Stop dhcpd when use static IP
+ if (dhcpInfo != null) {
+ netUtil.stopDhcp(manager.ifname, function() {});
+ }
+
+ // Set ip, mask length, gateway, dns to network interface
+ gNetworkService.configureInterface( { ifname: ifname,
+ ipaddr: staticIpInfo.ipaddr,
+ mask: staticIpInfo.maskLength,
+ gateway: staticIpInfo.gateway,
+ dns1: staticIpInfo.dns1,
+ dns2: staticIpInfo.dns2 }, function (data) {
+ netUtil.runIpConfig(ifname, staticIpInfo, function(data) {
+ dhcpInfo = data.info;
+ notify("networkconnected", data);
+ });
+ });
+ }
+
+ var suppressEvents = false;
+ function notify(eventName, eventObject) {
+ if (suppressEvents)
+ return;
+ var handler = manager["on" + eventName];
+ if (handler) {
+ if (!eventObject)
+ eventObject = ({});
+ handler.call(eventObject);
+ }
+ }
+
+ function notifyStateChange(fields) {
+ // If we're already in the COMPLETED state, we might receive events from
+ // the supplicant that tell us that we're re-authenticating or reminding
+ // us that we're associated to a network. In those cases, we don't need to
+ // do anything, so just ignore them.
+ if (manager.state === "COMPLETED" &&
+ fields.state !== "DISCONNECTED" &&
+ fields.state !== "INTERFACE_DISABLED" &&
+ fields.state !== "INACTIVE" &&
+ fields.state !== "SCANNING") {
+ return false;
+ }
+
+ // Stop background scanning if we're trying to connect to a network.
+ if (manager.backgroundScanEnabled &&
+ (fields.state === "ASSOCIATING" ||
+ fields.state === "ASSOCIATED" ||
+ fields.state === "FOUR_WAY_HANDSHAKE" ||
+ fields.state === "GROUP_HANDSHAKE" ||
+ fields.state === "COMPLETED")) {
+ setBackgroundScan("OFF", function() {});
+ }
+
+ fields.prevState = manager.state;
+ // Detect wpa_supplicant's loop iterations.
+ manager.supplicantLoopDetection(fields.prevState, fields.state);
+ notify("statechange", fields);
+
+ // Don't update state when and after disabling.
+ if (manager.state === "DISABLING" ||
+ manager.state === "UNINITIALIZED") {
+ return false;
+ }
+
+ manager.state = fields.state;
+ return true;
+ }
+
+ function parseStatus(status) {
+ if (status === null) {
+ debug("Unable to get wpa supplicant's status");
+ return;
+ }
+
+ var ssid;
+ var bssid;
+ var state;
+ var ip_address;
+ var id;
+
+ var lines = status.split("\n");
+ for (let i = 0; i < lines.length; ++i) {
+ let [key, value] = lines[i].split("=");
+ switch (key) {
+ case "wpa_state":
+ state = value;
+ break;
+ case "ssid":
+ ssid = value;
+ break;
+ case "bssid":
+ bssid = value;
+ break;
+ case "ip_address":
+ ip_address = value;
+ break;
+ case "id":
+ id = value;
+ break;
+ }
+ }
+
+ if (bssid && ssid) {
+ manager.connectionInfo.bssid = bssid;
+ manager.connectionInfo.ssid = ssid;
+ manager.connectionInfo.id = id;
+ }
+
+ if (ip_address)
+ dhcpInfo = { ip_address: ip_address };
+
+ notifyStateChange({ state: state, fromStatus: true });
+
+ // If we parse the status and the supplicant has already entered the
+ // COMPLETED state, then we need to set up DHCP right away.
+ if (state === "COMPLETED")
+ onconnected();
+ }
+
+ // try to connect to the supplicant
+ var connectTries = 0;
+ var retryTimer = null;
+ function connectCallback(ok) {
+ if (ok === 0) {
+ // Tell the event worker to start waiting for events.
+ retryTimer = null;
+ connectTries = 0;
+ recvErrors = 0;
+ manager.connectToSupplicant = true;
+ didConnectSupplicant(function(){});
+ return;
+ }
+ if (connectTries++ < 5) {
+ // Try again in 1 seconds.
+ if (!retryTimer)
+ retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ retryTimer.initWithCallback(function(timer) {
+ wifiCommand.connectToSupplicant(connectCallback);
+ }, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
+ return;
+ }
+
+ retryTimer = null;
+ connectTries = 0;
+ notify("supplicantlost", { success: false });
+ }
+
+ manager.connectionDropped = function(callback) {
+ // Reset network interface when connection drop
+ gNetworkService.configureInterface( { ifname: manager.ifname,
+ ipaddr: 0,
+ mask: 0,
+ gateway: 0,
+ dns1: 0,
+ dns2: 0 }, function (data) {
+ });
+
+ // If we got disconnected, kill the DHCP client in preparation for
+ // reconnection.
+ gNetworkService.resetConnections(manager.ifname, function() {
+ netUtil.stopDhcp(manager.ifname, function() {
+ callback();
+ });
+ });
+ }
+
+ manager.start = function() {
+ debug("detected SDK version " + sdkVersion);
+ wifiCommand.connectToSupplicant(connectCallback);
+ }
+
+ let dhcpRequestGen = 0;
+
+ function onconnected() {
+ // For now we do our own DHCP. In the future, this should be handed
+ // off to the Network Manager.
+ let currentNetwork = Object.create(null);
+ currentNetwork.netId = manager.connectionInfo.id;
+
+ manager.getNetworkConfiguration(currentNetwork, function (){
+ let key = getNetworkKey(currentNetwork);
+ if (staticIpConfig &&
+ (key in staticIpConfig) &&
+ staticIpConfig[key].enabled) {
+ debug("Run static ip");
+ runStaticIp(manager.ifname, key);
+ return;
+ }
+ netUtil.runDhcp(manager.ifname, dhcpRequestGen++, function(data, gen) {
+ dhcpInfo = data.info;
+ debug('dhcpRequestGen: ' + dhcpRequestGen + ', gen: ' + gen);
+ if (!dhcpInfo) {
+ if (gen + 1 < dhcpRequestGen) {
+ debug('Do not bother younger DHCP request.');
+ return;
+ }
+ if (++manager.dhcpFailuresCount >= MAX_RETRIES_ON_DHCP_FAILURE) {
+ manager.dhcpFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ return;
+ }
+ // NB: We have to call disconnect first. Otherwise, we only reauth with
+ // the existing AP and don't retrigger DHCP.
+ manager.disconnect(function() {
+ manager.reassociate(function(){});
+ });
+ return;
+ }
+
+ manager.dhcpFailuresCount = 0;
+ notify("networkconnected", data);
+ });
+ });
+ }
+
+ var supplicantStatesMap = (sdkVersion >= 15) ?
+ ["DISCONNECTED", "INTERFACE_DISABLED", "INACTIVE", "SCANNING",
+ "AUTHENTICATING", "ASSOCIATING", "ASSOCIATED", "FOUR_WAY_HANDSHAKE",
+ "GROUP_HANDSHAKE", "COMPLETED"]
+ :
+ ["DISCONNECTED", "INACTIVE", "SCANNING", "ASSOCIATING",
+ "ASSOCIATED", "FOUR_WAY_HANDSHAKE", "GROUP_HANDSHAKE",
+ "COMPLETED", "DORMANT", "UNINITIALIZED"];
+
+ var driverEventMap = { STOPPED: "driverstopped", STARTED: "driverstarted", HANGED: "driverhung" };
+
+ manager.getNetworkId = function (ssid, callback) {
+ manager.getConfiguredNetworks(function(networks) {
+ if (!networks) {
+ debug("Unable to get configured networks");
+ return callback(null);
+ }
+ for (let net in networks) {
+ let network = networks[net];
+ // Trying to get netId from
+ // 1. network matching SSID if SSID is provided.
+ // 2. current network if no SSID is provided, it's not guaranteed that
+ // current network matches requested SSID.
+ if ((!ssid && network.status === "CURRENT") ||
+ (ssid && network.ssid && ssid === dequote(network.ssid))) {
+ return callback(net);
+ }
+ }
+ callback(null);
+ });
+ }
+
+ function handleWpaEapEvents(event) {
+ if (event.indexOf("CTRL-EVENT-EAP-FAILURE") !== -1) {
+ if (event.indexOf("EAP authentication failed") !== -1) {
+ notify("passwordmaybeincorrect");
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ }
+ return true;
+ }
+ if (event.indexOf("CTRL-EVENT-EAP-TLS-CERT-ERROR") !== -1) {
+ // Cert Error
+ notify("passwordmaybeincorrect");
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ return true;
+ }
+ if (event.indexOf("CTRL-EVENT-EAP-STARTED") !== -1) {
+ notifyStateChange({ state: "AUTHENTICATING" });
+ return true;
+ }
+ return true;
+ }
+
+ // Handle events sent to us by the event worker.
+ function handleEvent(event) {
+ debug("Event coming in: " + event);
+ if (event.indexOf("CTRL-EVENT-") !== 0 && event.indexOf("WPS") !== 0) {
+ // Handle connection fail exception on WEP-128, while password length
+ // is not 5 nor 13 bytes.
+ if (event.indexOf("Association request to the driver failed") !== -1) {
+ notify("passwordmaybeincorrect");
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ return true;
+ }
+
+ if (event.indexOf("WPA:") == 0 &&
+ event.indexOf("pre-shared key may be incorrect") != -1) {
+ notify("passwordmaybeincorrect");
+ }
+
+ // This is ugly, but we need to grab the SSID here. BSSID is not guaranteed
+ // to be provided, so don't grab BSSID here.
+ var match = /Trying to associate with.*SSID[ =]'(.*)'/.exec(event);
+ if (match) {
+ debug("Matched: " + match[1] + "\n");
+ manager.connectionInfo.ssid = match[1];
+ }
+ return true;
+ }
+
+ var space = event.indexOf(" ");
+ var eventData = event.substr(0, space + 1);
+ if (eventData.indexOf("CTRL-EVENT-STATE-CHANGE") === 0) {
+ // Parse the event data.
+ var fields = {};
+ var tokens = event.substr(space + 1).split(" ");
+ for (var n = 0; n < tokens.length; ++n) {
+ var kv = tokens[n].split("=");
+ if (kv.length === 2)
+ fields[kv[0]] = kv[1];
+ }
+ if (!("state" in fields))
+ return true;
+ fields.state = supplicantStatesMap[fields.state];
+
+ // The BSSID field is only valid in the ASSOCIATING and ASSOCIATED
+ // states, except when we "reauth", except this seems to depend on the
+ // driver, so simply check to make sure that we don't have a null BSSID.
+ if (fields.BSSID !== "00:00:00:00:00:00")
+ manager.connectionInfo.bssid = fields.BSSID;
+
+ if (notifyStateChange(fields) && fields.state === "COMPLETED") {
+ onconnected();
+ }
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-DRIVER-STATE") === 0) {
+ var handlerName = driverEventMap[eventData];
+ if (handlerName)
+ notify(handlerName);
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-TERMINATING") === 0) {
+ // As long the monitor socket is not closed and we haven't seen too many
+ // recv errors yet, we will keep going for a bit longer.
+ if (event.indexOf("connection closed") === -1 &&
+ event.indexOf("recv error") !== -1 && ++recvErrors < 10)
+ return true;
+
+ notifyStateChange({ state: "DISCONNECTED", BSSID: null, id: -1 });
+
+ // If the supplicant is terminated as commanded, the supplicant lost
+ // notification will be sent after driver unloaded. In such case, the
+ // manager state will be "DISABLING" or "UNINITIALIZED".
+ // So if supplicant terminated with incorrect manager state, implying
+ // unexpected condition, we should notify supplicant lost here.
+ if (manager.state !== "DISABLING" && manager.state !== "UNINITIALIZED") {
+ notify("supplicantlost", { success: true });
+ }
+
+ if (manager.stopSupplicantCallback) {
+ cancelWaitForTerminateEventTimer();
+ // It's possible that the terminating event triggered by timer comes
+ // earlier than the event from wpa_supplicant. Since
+ // stopSupplicantCallback contains async. callbacks, swap it to local
+ // to prevent calling the callback twice.
+ let stopSupplicantCallback = manager.stopSupplicantCallback.bind(manager);
+ manager.stopSupplicantCallback = null;
+ stopSupplicantCallback();
+ stopSupplicantCallback = null;
+ }
+ return false;
+ }
+ if (eventData.indexOf("CTRL-EVENT-DISCONNECTED") === 0) {
+ var token = event.split(" ")[1];
+ var bssid = token.split("=")[1];
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ manager.connectionInfo.bssid = null;
+ manager.connectionInfo.ssid = null;
+ manager.connectionInfo.id = -1;
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-CONNECTED") === 0) {
+ // Format: CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]
+ var bssid = event.split(" ")[4];
+
+ var keyword = "id=";
+ var id = event.substr(event.indexOf(keyword) + keyword.length).split(" ")[0];
+ // Read current BSSID here, it will always being provided.
+ manager.connectionInfo.id = id;
+ manager.connectionInfo.bssid = bssid;
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-SCAN-RESULTS") === 0) {
+ debug("Notifying of scan results available");
+ if (reEnableBackgroundScan) {
+ reEnableBackgroundScan = false;
+ setBackgroundScan("ON", function() {});
+ }
+ manager.handlePostWifiScan();
+ notify("scanresultsavailable");
+ return true;
+ }
+ if (eventData.indexOf("CTRL-EVENT-EAP") === 0) {
+ return handleWpaEapEvents(event);
+ }
+ if (eventData.indexOf("CTRL-EVENT-ASSOC-REJECT") === 0) {
+ debug("CTRL-EVENT-ASSOC-REJECT: network error");
+ notify("passwordmaybeincorrect");
+ if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ manager.authenticationFailuresCount = 0;
+ debug("CTRL-EVENT-ASSOC-REJECT: disconnect network");
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ }
+ return true;
+ }
+ if (eventData.indexOf("WPS-TIMEOUT") === 0) {
+ notifyStateChange({ state: "WPS_TIMEOUT", BSSID: null, id: -1 });
+ return true;
+ }
+ if (eventData.indexOf("WPS-FAIL") === 0) {
+ notifyStateChange({ state: "WPS_FAIL", BSSID: null, id: -1 });
+ return true;
+ }
+ if (eventData.indexOf("WPS-OVERLAP-DETECTED") === 0) {
+ notifyStateChange({ state: "WPS_OVERLAP_DETECTED", BSSID: null, id: -1 });
+ return true;
+ }
+ // Unknown event.
+ return true;
+ }
+
+ function setPowerSavingMode(enabled) {
+ let mode = enabled ? "AUTO" : "ACTIVE";
+ // Some wifi drivers may not implement this command. Set power mode
+ // even if suspend optimization command failed.
+ manager.setSuspendOptimizations(enabled, function(ok) {
+ manager.setPowerMode(mode, function() {});
+ });
+ }
+
+ function didConnectSupplicant(callback) {
+ waitForEvent(manager.ifname);
+
+ // Load up the supplicant state.
+ getDebugEnabled(function(ok) {
+ syncDebug();
+ });
+ wifiCommand.status(function(status) {
+ parseStatus(status);
+ notify("supplicantconnection");
+ callback();
+ });
+ // WPA supplicant already connected.
+ manager.setPowerSavingMode(true);
+ if (p2pSupported) {
+ manager.enableP2p(function(success) {});
+ }
+ }
+
+ function prepareForStartup(callback) {
+ let status = libcutils.property_get(DHCP_PROP + "_" + manager.ifname);
+ if (status !== "running") {
+ tryStopSupplicant();
+ return;
+ }
+ manager.connectionDropped(function() {
+ tryStopSupplicant();
+ });
+
+ // Ignore any errors and kill any currently-running supplicants. On some
+ // phones, stopSupplicant won't work for a supplicant that we didn't
+ // start, so we hand-roll it here.
+ function tryStopSupplicant () {
+ let status = libcutils.property_get(SUPP_PROP);
+ if (status !== "running") {
+ callback();
+ return;
+ }
+ suppressEvents = true;
+ wifiCommand.killSupplicant(function() {
+ gNetworkService.disableInterface(manager.ifname, function (ok) {
+ suppressEvents = false;
+ callback();
+ });
+ });
+ }
+ }
+
+ // Initial state.
+ manager.state = "UNINITIALIZED";
+ manager.tetheringState = "UNINITIALIZED";
+ manager.supplicantStarted = false;
+ manager.connectionInfo = { ssid: null, bssid: null, id: -1 };
+ manager.authenticationFailuresCount = 0;
+ manager.loopDetectionCount = 0;
+ manager.dhcpFailuresCount = 0;
+ manager.stopSupplicantCallback = null;
+
+ manager.__defineGetter__("enabled", function() {
+ switch (manager.state) {
+ case "UNINITIALIZED":
+ case "INITIALIZING":
+ case "DISABLING":
+ return false;
+ default:
+ return true;
+ }
+ });
+
+ var waitForTerminateEventTimer = null;
+ function cancelWaitForTerminateEventTimer() {
+ if (waitForTerminateEventTimer) {
+ waitForTerminateEventTimer.cancel();
+ waitForTerminateEventTimer = null;
+ }
+ };
+ function createWaitForTerminateEventTimer(onTimeout) {
+ if (waitForTerminateEventTimer) {
+ return;
+ }
+ waitForTerminateEventTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ waitForTerminateEventTimer.initWithCallback(onTimeout,
+ 4000,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ };
+
+ var waitForDriverReadyTimer = null;
+ function cancelWaitForDriverReadyTimer() {
+ if (waitForDriverReadyTimer) {
+ waitForDriverReadyTimer.cancel();
+ waitForDriverReadyTimer = null;
+ }
+ };
+ function createWaitForDriverReadyTimer(onTimeout) {
+ waitForDriverReadyTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ waitForDriverReadyTimer.initWithCallback(onTimeout,
+ manager.driverDelay,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ };
+
+ // Public interface of the wifi service.
+ manager.setWifiEnabled = function(enabled, callback) {
+ if (enabled === manager.isWifiEnabled(manager.state)) {
+ callback("no change");
+ return;
+ }
+
+ if (enabled) {
+ manager.state = "INITIALIZING";
+ // Register as network interface.
+ WifiNetworkInterface.info.name = manager.ifname;
+ if (!WifiNetworkInterface.registered) {
+ gNetworkManager.registerNetworkInterface(WifiNetworkInterface);
+ WifiNetworkInterface.registered = true;
+ }
+ WifiNetworkInterface.info.state =
+ Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
+ WifiNetworkInterface.info.ips = [];
+ WifiNetworkInterface.info.prefixLengths = [];
+ WifiNetworkInterface.info.gateways = [];
+ WifiNetworkInterface.info.dnses = [];
+ gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
+
+ prepareForStartup(function() {
+ loadDriver(function (status) {
+ if (status < 0) {
+ callback(status);
+ manager.state = "UNINITIALIZED";
+ return;
+ }
+ // This command is mandatory for Nexus 4. But some devices like
+ // Galaxy S2 don't support it. Continue to start wpa_supplicant
+ // even if we fail to set wifi operation mode to station.
+ gNetworkService.setWifiOperationMode(manager.ifname,
+ WIFI_FIRMWARE_STATION,
+ function (status) {
+
+ function startSupplicantInternal() {
+ wifiCommand.startSupplicant(function (status) {
+ if (status < 0) {
+ unloadDriver(WIFI_FIRMWARE_STATION, function() {
+ callback(status);
+ });
+ manager.state = "UNINITIALIZED";
+ return;
+ }
+
+ manager.supplicantStarted = true;
+ gNetworkService.enableInterface(manager.ifname, function (ok) {
+ callback(ok ? 0 : -1);
+ });
+ });
+ }
+
+ function doStartSupplicant() {
+ cancelWaitForDriverReadyTimer();
+
+ if (!manager.connectToSupplicant) {
+ startSupplicantInternal();
+ return;
+ }
+ wifiCommand.closeSupplicantConnection(function () {
+ manager.connectToSupplicant = false;
+ // closeSupplicantConnection() will trigger onsupplicantlost
+ // and set manager.state to "UNINITIALIZED", we have to
+ // restore it here.
+ manager.state = "INITIALIZING";
+ startSupplicantInternal();
+ });
+ }
+ // Driver startup on certain platforms takes longer than it takes for us
+ // to return from loadDriver, so wait 2 seconds before starting
+ // the supplicant to give it a chance to start.
+ if (manager.driverDelay > 0) {
+ createWaitForDriverReadyTimer(doStartSupplicant);
+ } else {
+ doStartSupplicant();
+ }
+ });
+ });
+ });
+ } else {
+ manager.state = "DISABLING";
+ // Note these following calls ignore errors. If we fail to kill the
+ // supplicant gracefully, then we need to continue telling it to die
+ // until it does.
+ let doDisableWifi = function() {
+ manager.stopSupplicantCallback = (function () {
+ wifiCommand.stopSupplicant(function (status) {
+ wifiCommand.closeSupplicantConnection(function() {
+ manager.connectToSupplicant = false;
+ manager.state = "UNINITIALIZED";
+ gNetworkService.disableInterface(manager.ifname, function (ok) {
+ unloadDriver(WIFI_FIRMWARE_STATION, callback);
+ });
+ });
+ });
+ }).bind(this);
+
+ let terminateEventCallback = (function() {
+ handleEvent("CTRL-EVENT-TERMINATING");
+ }).bind(this);
+ createWaitForTerminateEventTimer(terminateEventCallback);
+
+ // We are going to terminate the connection between wpa_supplicant.
+ // Stop the polling timer immediately to prevent connection info update
+ // command blocking in control thread until socket timeout.
+ notify("stopconnectioninfotimer");
+
+ wifiCommand.terminateSupplicant(function (ok) {
+ manager.connectionDropped(function () {
+ });
+ });
+ }
+
+ if (p2pSupported) {
+ p2pManager.setEnabled(false, { onDisabled: doDisableWifi });
+ } else {
+ doDisableWifi();
+ }
+ }
+ }
+
+ var wifiHotspotStatusTimer = null;
+ function cancelWifiHotspotStatusTimer() {
+ if (wifiHotspotStatusTimer) {
+ wifiHotspotStatusTimer.cancel();
+ wifiHotspotStatusTimer = null;
+ }
+ }
+
+ function createWifiHotspotStatusTimer(onTimeout) {
+ wifiHotspotStatusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ wifiHotspotStatusTimer.init(onTimeout, 5000, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ }
+
+ // Get wifi interface and load wifi driver when enable Ap mode.
+ manager.setWifiApEnabled = function(enabled, configuration, callback) {
+ if (enabled === manager.isWifiTetheringEnabled(manager.tetheringState)) {
+ callback("no change");
+ return;
+ }
+
+ if (enabled) {
+ manager.tetheringState = "INITIALIZING";
+ loadDriver(function (status) {
+ if (status < 0) {
+ callback();
+ manager.tetheringState = "UNINITIALIZED";
+ if (wifiHotspotStatusTimer) {
+ cancelWifiHotspotStatusTimer();
+ wifiCommand.closeHostapdConnection(function(result) {
+ });
+ }
+ return;
+ }
+
+ function getWifiHotspotStatus() {
+ wifiCommand.hostapdGetStations(function(result) {
+ notify("stationinfoupdate", {station: result});
+ });
+ }
+
+ function doStartWifiTethering() {
+ cancelWaitForDriverReadyTimer();
+ WifiNetworkInterface.info.name =
+ libcutils.property_get("wifi.tethering.interface", manager.ifname);
+ gTetheringService.setWifiTethering(enabled,
+ WifiNetworkInterface.info.name,
+ configuration, function(result) {
+ if (result) {
+ manager.tetheringState = "UNINITIALIZED";
+ } else {
+ manager.tetheringState = "COMPLETED";
+ wifiCommand.connectToHostapd(function(result) {
+ if (result) {
+ return;
+ }
+ // Create a timer to track the connection status.
+ createWifiHotspotStatusTimer(getWifiHotspotStatus);
+ });
+ }
+ // Pop out current request.
+ callback();
+ // Should we fire a dom event if we fail to set wifi tethering ?
+ debug("Enable Wifi tethering result: " + (result ? result : "successfully"));
+ });
+ }
+
+ // Driver startup on certain platforms takes longer than it takes
+ // for us to return from loadDriver, so wait 2 seconds before
+ // turning on Wifi tethering.
+ createWaitForDriverReadyTimer(doStartWifiTethering);
+ });
+ } else {
+ cancelWifiHotspotStatusTimer();
+ gTetheringService.setWifiTethering(enabled, WifiNetworkInterface,
+ configuration, function(result) {
+ // Should we fire a dom event if we fail to set wifi tethering ?
+ debug("Disable Wifi tethering result: " + (result ? result : "successfully"));
+ // Unload wifi driver even if we fail to control wifi tethering.
+ unloadDriver(WIFI_FIRMWARE_AP, function(status) {
+ if (status < 0) {
+ debug("Fail to unload wifi driver");
+ }
+ manager.tetheringState = "UNINITIALIZED";
+ callback();
+ });
+ });
+ }
+ }
+
+ manager.disconnect = wifiCommand.disconnect;
+ manager.reconnect = wifiCommand.reconnect;
+ manager.reassociate = wifiCommand.reassociate;
+
+ var networkConfigurationFields = [
+ {name: "ssid", type: "string"},
+ {name: "bssid", type: "string"},
+ {name: "psk", type: "string"},
+ {name: "wep_key0", type: "string"},
+ {name: "wep_key1", type: "string"},
+ {name: "wep_key2", type: "string"},
+ {name: "wep_key3", type: "string"},
+ {name: "wep_tx_keyidx", type: "integer"},
+ {name: "priority", type: "integer"},
+ {name: "key_mgmt", type: "string"},
+ {name: "scan_ssid", type: "string"},
+ {name: "disabled", type: "integer"},
+ {name: "identity", type: "string"},
+ {name: "password", type: "string"},
+ {name: "auth_alg", type: "string"},
+ {name: "phase1", type: "string"},
+ {name: "phase2", type: "string"},
+ {name: "eap", type: "string"},
+ {name: "pin", type: "string"},
+ {name: "pcsc", type: "string"},
+ {name: "ca_cert", type: "string"},
+ {name: "subject_match", type: "string"},
+ {name: "client_cert", type: "string"},
+ {name: "private_key", type: "stirng"},
+ {name: "engine", type: "integer"},
+ {name: "engine_id", type: "string"},
+ {name: "key_id", type: "string"},
+ {name: "frequency", type: "integer"},
+ {name: "mode", type: "integer"}
+ ];
+ // These fields are only handled in IBSS (aka ad-hoc) mode
+ var ibssNetworkConfigurationFields = [
+ "frequency", "mode"
+ ];
+
+ manager.getNetworkConfiguration = function(config, callback) {
+ var netId = config.netId;
+ var done = 0;
+ for (var n = 0; n < networkConfigurationFields.length; ++n) {
+ let fieldName = networkConfigurationFields[n].name;
+ let fieldType = networkConfigurationFields[n].type;
+ wifiCommand.getNetworkVariable(netId, fieldName, function(value) {
+ if (value !== null) {
+ if (fieldType === "integer") {
+ config[fieldName] = parseInt(value, 10);
+ } else if ( fieldName == "ssid" && value[0] != '"' ) {
+ // SET_NETWORK will set a quoted ssid to wpa_supplicant.
+ // But if ssid contains non-ascii char, it will be converted into utf-8.
+ // For example: "Testçš„wifi" --> 54657374e79a8477696669
+ // When GET_NETWORK receive a un-quoted utf-8 ssid, it must be decoded and quoted.
+ config[fieldName] = quote(decodeURIComponent(value.replace(/[0-9a-f]{2}/g, '%$&')));
+ } else {
+ // value is string type by default.
+ config[fieldName] = value;
+ }
+ }
+ if (++done == networkConfigurationFields.length)
+ callback(config);
+ });
+ }
+ }
+ manager.setNetworkConfiguration = function(config, callback) {
+ var netId = config.netId;
+ var done = 0;
+ var errors = 0;
+
+ function hasValidProperty(name) {
+ return ((name in config) &&
+ config[name] != null &&
+ (["password", "wep_key0", "psk"].indexOf(name) === -1 ||
+ config[name] !== '*'));
+ }
+
+ function getModeFromConfig() {
+ /* we use the mode from the config, or ESS as default */
+ return hasValidProperty("mode") ? config["mode"] : MODE_ESS;
+ }
+
+ var mode = getModeFromConfig();
+
+ function validForMode(name, mode) {
+ /* all fields are valid for IBSS */
+ return (mode == MODE_IBSS) ||
+ /* IBSS fields are not valid for ESS */
+ ((mode == MODE_ESS) && !(name in ibssNetworkConfigurationFields));
+ }
+
+ for (var n = 0; n < networkConfigurationFields.length; ++n) {
+ let fieldName = networkConfigurationFields[n].name;
+ if (!hasValidProperty(fieldName) || !validForMode(fieldName, mode)) {
+ ++done;
+ } else {
+ wifiCommand.setNetworkVariable(netId, fieldName, config[fieldName], function(ok) {
+ if (!ok)
+ ++errors;
+ if (++done == networkConfigurationFields.length)
+ callback(errors == 0);
+ });
+ }
+ }
+ // If config didn't contain any of the fields we want, don't lose the error callback.
+ if (done == networkConfigurationFields.length)
+ callback(false);
+ }
+ manager.getConfiguredNetworks = function(callback) {
+ wifiCommand.listNetworks(function (reply) {
+ var networks = Object.create(null);
+ var lines = reply ? reply.split("\n") : 0;
+ if (lines.length <= 1) {
+ // We need to make sure we call the callback even if there are no
+ // configured networks.
+ callback(networks);
+ return;
+ }
+
+ var done = 0;
+ var errors = 0;
+ for (var n = 1; n < lines.length; ++n) {
+ var result = lines[n].split("\t");
+ var netId = parseInt(result[0], 10);
+ var config = networks[netId] = { netId: netId };
+ switch (result[3]) {
+ case "[CURRENT]":
+ config.status = "CURRENT";
+ break;
+ case "[DISABLED]":
+ config.status = "DISABLED";
+ break;
+ default:
+ config.status = "ENABLED";
+ break;
+ }
+ manager.getNetworkConfiguration(config, function (ok) {
+ if (!ok)
+ ++errors;
+ if (++done == lines.length - 1) {
+ if (errors) {
+ // If an error occured, delete the new netId.
+ wifiCommand.removeNetwork(netId, function() {
+ callback(null);
+ });
+ } else {
+ callback(networks);
+ }
+ }
+ });
+ }
+ });
+ }
+ manager.addNetwork = function(config, callback) {
+ wifiCommand.addNetwork(function (netId) {
+ config.netId = netId;
+ manager.setNetworkConfiguration(config, function (ok) {
+ if (!ok) {
+ wifiCommand.removeNetwork(netId, function() { callback(false); });
+ return;
+ }
+
+ callback(ok);
+ });
+ });
+ }
+ manager.updateNetwork = function(config, callback) {
+ manager.setNetworkConfiguration(config, callback);
+ }
+ manager.removeNetwork = function(netId, callback) {
+ wifiCommand.removeNetwork(netId, callback);
+ }
+
+ manager.saveConfig = function(callback) {
+ wifiCommand.saveConfig(callback);
+ }
+ manager.enableNetwork = function(netId, disableOthers, callback) {
+ if (p2pSupported) {
+ // We have to stop wifi direct scan before associating to an AP.
+ // Otherwise we will get a "REJECT" wpa supplicant event.
+ p2pManager.setScanEnabled(false, function(success) {});
+ }
+ wifiCommand.enableNetwork(netId, disableOthers, callback);
+ }
+ manager.disableNetwork = function(netId, callback) {
+ wifiCommand.disableNetwork(netId, callback);
+ }
+ manager.getMacAddress = wifiCommand.getMacAddress;
+ manager.getScanResults = wifiCommand.scanResults;
+ manager.setScanMode = function(mode, callback) {
+ setScanMode(mode === "active", callback); // Use our own version.
+ }
+ manager.setBackgroundScan = setBackgroundScan; // Use our own version.
+ manager.scan = scan; // Use our own version.
+ manager.wpsPbc = wifiCommand.wpsPbc;
+ manager.wpsPin = wifiCommand.wpsPin;
+ manager.wpsCancel = wifiCommand.wpsCancel;
+ manager.setPowerMode = (sdkVersion >= 16)
+ ? wifiCommand.setPowerModeJB
+ : wifiCommand.setPowerModeICS;
+ manager.setPowerSavingMode = setPowerSavingMode;
+ manager.getHttpProxyNetwork = getHttpProxyNetwork;
+ manager.setHttpProxy = setHttpProxy;
+ manager.configureHttpProxy = configureHttpProxy;
+ manager.setSuspendOptimizations = (sdkVersion >= 16)
+ ? wifiCommand.setSuspendOptimizationsJB
+ : wifiCommand.setSuspendOptimizationsICS;
+ manager.setStaticIpMode = setStaticIpMode;
+ manager.getRssiApprox = wifiCommand.getRssiApprox;
+ manager.getLinkSpeed = wifiCommand.getLinkSpeed;
+ manager.getDhcpInfo = function() { return dhcpInfo; }
+ manager.getConnectionInfo = (sdkVersion >= 15)
+ ? wifiCommand.getConnectionInfoICS
+ : wifiCommand.getConnectionInfoGB;
+
+ manager.ensureSupplicantDetached = aCallback => {
+ if (!manager.enabled) {
+ aCallback();
+ return;
+ }
+ wifiCommand.closeSupplicantConnection(aCallback);
+ };
+
+ manager.isHandShakeState = function(state) {
+ switch (state) {
+ case "AUTHENTICATING":
+ case "ASSOCIATING":
+ case "ASSOCIATED":
+ case "FOUR_WAY_HANDSHAKE":
+ case "GROUP_HANDSHAKE":
+ return true;
+ case "DORMANT":
+ case "COMPLETED":
+ case "DISCONNECTED":
+ case "INTERFACE_DISABLED":
+ case "INACTIVE":
+ case "SCANNING":
+ case "UNINITIALIZED":
+ case "INVALID":
+ case "CONNECTED":
+ default:
+ return false;
+ }
+ }
+
+ manager.isWifiEnabled = function(state) {
+ switch (state) {
+ case "UNINITIALIZED":
+ case "DISABLING":
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ manager.isWifiTetheringEnabled = function(state) {
+ switch (state) {
+ case "UNINITIALIZED":
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ manager.syncDebug = syncDebug;
+ manager.stateOrdinal = function(state) {
+ return supplicantStatesMap.indexOf(state);
+ }
+ manager.supplicantLoopDetection = function(prevState, state) {
+ var isPrevStateInHandShake = manager.isHandShakeState(prevState);
+ var isStateInHandShake = manager.isHandShakeState(state);
+
+ if (isPrevStateInHandShake) {
+ if (isStateInHandShake) {
+ // Increase the count only if we are in the loop.
+ if (manager.stateOrdinal(state) > manager.stateOrdinal(prevState)) {
+ manager.loopDetectionCount++;
+ }
+ if (manager.loopDetectionCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
+ notify("disconnected", {connectionInfo: manager.connectionInfo});
+ manager.loopDetectionCount = 0;
+ }
+ }
+ } else {
+ // From others state to HandShake state. Reset the count.
+ if (isStateInHandShake) {
+ manager.loopDetectionCount = 0;
+ }
+ }
+ }
+
+ manager.handlePreWifiScan = function() {
+ if (p2pSupported) {
+ // Before doing regular wifi scan, we have to disable wifi direct
+ // scan first. Otherwise we will never get the scan result.
+ p2pManager.blockScan();
+ }
+ };
+
+ manager.handlePostWifiScan = function() {
+ if (p2pSupported) {
+ // After regular wifi scanning, we should restore the restricted
+ // wifi direct scan.
+ p2pManager.unblockScan();
+ }
+ };
+
+ //
+ // Public APIs for P2P.
+ //
+
+ manager.p2pSupported = function() {
+ return p2pSupported;
+ };
+
+ manager.getP2pManager = function() {
+ return p2pManager;
+ };
+
+ manager.enableP2p = function(callback) {
+ p2pManager.setEnabled(true, {
+ onSupplicantConnected: function() {
+ waitForEvent(WifiP2pManager.INTERFACE_NAME);
+ },
+
+ onEnabled: function(success) {
+ callback(success);
+ }
+ });
+ };
+
+ manager.getCapabilities = function() {
+ return capabilities;
+ }
+
+ // Cert Services
+ let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"];
+ if (wifiCertService) {
+ wifiCertService = wifiCertService.getService(Ci.nsIWifiCertService);
+ wifiCertService.start(wifiListener);
+ } else {
+ debug("No wifi CA service component available");
+ }
+
+ manager.importCert = function(caInfo, callback) {
+ var id = idgen++;
+ if (callback) {
+ controlCallbacks[id] = callback;
+ }
+
+ wifiCertService.importCert(id, caInfo.certBlob, caInfo.certPassword,
+ caInfo.certNickname);
+ }
+
+ manager.deleteCert = function(caInfo, callback) {
+ var id = idgen++;
+ if (callback) {
+ controlCallbacks[id] = callback;
+ }
+
+ wifiCertService.deleteCert(id, caInfo.certNickname);
+ }
+
+ manager.sdkVersion = function() {
+ return sdkVersion;
+ }
+
+ return manager;
+})();
+
+// Get unique key for a network, now the key is created by escape(SSID)+Security.
+// So networks of same SSID but different security mode can be identified.
+function getNetworkKey(network)
+{
+ var ssid = "",
+ encryption = "OPEN";
+
+ if ("security" in network) {
+ // manager network object, represents an AP
+ // object structure
+ // {
+ // .ssid : SSID of AP
+ // .security[] : "WPA-PSK" for WPA-PSK
+ // "WPA-EAP" for WPA-EAP
+ // "WEP" for WEP
+ // "" for OPEN
+ // other keys
+ // }
+
+ var security = network.security;
+ ssid = network.ssid;
+
+ for (let j = 0; j < security.length; j++) {
+ if (security[j] === "WPA-PSK") {
+ encryption = "WPA-PSK";
+ break;
+ } else if (security[j] === "WPA-EAP") {
+ encryption = "WPA-EAP";
+ break;
+ } else if (security[j] === "WEP") {
+ encryption = "WEP";
+ break;
+ }
+ }
+ } else if ("key_mgmt" in network) {
+ // configure network object, represents a network
+ // object structure
+ // {
+ // .ssid : SSID of network, quoted
+ // .key_mgmt : Encryption type
+ // "WPA-PSK" for WPA-PSK
+ // "WPA-EAP" for WPA-EAP
+ // "NONE" for WEP/OPEN
+ // .auth_alg : Encryption algorithm(WEP mode only)
+ // "OPEN_SHARED" for WEP
+ // other keys
+ // }
+ var key_mgmt = network.key_mgmt,
+ auth_alg = network.auth_alg;
+ ssid = dequote(network.ssid);
+
+ if (key_mgmt == "WPA-PSK") {
+ encryption = "WPA-PSK";
+ } else if (key_mgmt.indexOf("WPA-EAP") != -1) {
+ encryption = "WPA-EAP";
+ } else if (key_mgmt == "NONE" && auth_alg === "OPEN SHARED") {
+ encryption = "WEP";
+ }
+ }
+
+ // ssid here must be dequoted, and it's safer to esacpe it.
+ // encryption won't be empty and always be assigned one of the followings :
+ // "OPEN"/"WEP"/"WPA-PSK"/"WPA-EAP".
+ // So for a invalid network object, the returned key will be "OPEN".
+ return escape(ssid) + encryption;
+}
+
+function getMode(flags) {
+ if (/\[IBSS/.test(flags))
+ return MODE_IBSS;
+
+ return MODE_ESS;
+}
+
+function getKeyManagement(flags) {
+ var types = [];
+ if (!flags)
+ return types;
+
+ if (/\[WPA2?-PSK/.test(flags))
+ types.push("WPA-PSK");
+ if (/\[WPA2?-EAP/.test(flags))
+ types.push("WPA-EAP");
+ if (/\[WEP/.test(flags))
+ types.push("WEP");
+ return types;
+}
+
+function getCapabilities(flags) {
+ var types = [];
+ if (!flags)
+ return types;
+
+ if (/\[WPS/.test(flags))
+ types.push("WPS");
+ return types;
+}
+
+// These constants shamelessly ripped from WifiManager.java
+// strength is the value returned by scan_results. It is nominally in dB. We
+// transform it into a percentage for clients looking to simply show a
+// relative indication of the strength of a network.
+const MIN_RSSI = -100;
+const MAX_RSSI = -55;
+
+function calculateSignal(strength) {
+ // Some wifi drivers represent their signal strengths as 8-bit integers, so
+ // in order to avoid negative numbers, they add 256 to the actual values.
+ // While we don't *know* that this is the case here, we make an educated
+ // guess.
+ if (strength > 0)
+ strength -= 256;
+
+ if (strength <= MIN_RSSI)
+ return 0;
+ if (strength >= MAX_RSSI)
+ return 100;
+ return Math.floor(((strength - MIN_RSSI) / (MAX_RSSI - MIN_RSSI)) * 100);
+}
+
+function Network(ssid, mode, frequency, security, password, capabilities) {
+ this.ssid = ssid;
+ this.mode = mode;
+ this.frequency = frequency;
+ this.security = security;
+
+ if (typeof password !== "undefined")
+ this.password = password;
+ if (capabilities !== undefined)
+ this.capabilities = capabilities;
+ // TODO connected here as well?
+
+ this.__exposedProps__ = Network.api;
+}
+
+Network.api = {
+ ssid: "r",
+ mode: "r",
+ frequency: "r",
+ security: "r",
+ capabilities: "r",
+ known: "r",
+
+ password: "rw",
+ keyManagement: "rw",
+ psk: "rw",
+ identity: "rw",
+ wep: "rw",
+ hidden: "rw",
+ eap: "rw",
+ pin: "rw",
+ phase1: "rw",
+ phase2: "rw",
+ serverCertificate: "rw",
+ userCertificate: "rw"
+};
+
+// Note: We never use ScanResult.prototype, so the fact that it's unrelated to
+// Network.prototype is OK.
+function ScanResult(ssid, bssid, frequency, flags, signal) {
+ Network.call(this, ssid, getMode(flags), frequency,
+ getKeyManagement(flags), undefined, getCapabilities(flags));
+ this.bssid = bssid;
+ this.signalStrength = signal;
+ this.relSignalStrength = calculateSignal(Number(signal));
+
+ this.__exposedProps__ = ScanResult.api;
+}
+
+// XXX This should probably live in the DOM-facing side, but it's hard to do
+// there, so we stick this here.
+ScanResult.api = {
+ bssid: "r",
+ signalStrength: "r",
+ relSignalStrength: "r",
+ connected: "r"
+};
+
+for (let i in Network.api) {
+ ScanResult.api[i] = Network.api[i];
+}
+
+function quote(s) {
+ return '"' + s + '"';
+}
+
+function dequote(s) {
+ if (s[0] != '"' || s[s.length - 1] != '"')
+ throw "Invalid argument, not a quoted string: " + s;
+ return s.substr(1, s.length - 2);
+}
+
+function isWepHexKey(s) {
+ if (s.length != 10 && s.length != 26 && s.length != 58)
+ return false;
+ return !/[^a-fA-F0-9]/.test(s);
+}
+
+
+var WifiNetworkInterface = {
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
+
+ registered: false,
+
+ // nsINetworkInterface
+
+ NETWORK_STATE_UNKNOWN: Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN,
+ NETWORK_STATE_CONNECTING: Ci.nsINetworkInfo.CONNECTING,
+ NETWORK_STATE_CONNECTED: Ci.nsINetworkInfo.CONNECTED,
+ NETWORK_STATE_DISCONNECTING: Ci.nsINetworkInfo.DISCONNECTING,
+ NETWORK_STATE_DISCONNECTED: Ci.nsINetworkInfo.DISCONNECTED,
+
+ NETWORK_TYPE_WIFI: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
+ NETWORK_TYPE_MOBILE: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE,
+ NETWORK_TYPE_MOBILE_MMS: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS,
+ NETWORK_TYPE_MOBILE_SUPL: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL,
+
+ info: {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]),
+
+ state: Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN,
+
+ type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
+
+ name: null,
+
+ ips: [],
+
+ prefixLengths: [],
+
+ dnses: [],
+
+ gateways: [],
+
+ 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();
+ }
+ },
+
+ httpProxyHost: null,
+
+ httpProxyPort: null
+};
+
+function WifiScanResult() {}
+
+// TODO Make the difference between a DOM-based network object and our
+// networks objects much clearer.
+var netToDOM;
+var netFromDOM;
+
+function WifiWorker() {
+ var self = this;
+
+ this._mm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ const messages = ["WifiManager:getNetworks", "WifiManager:getKnownNetworks",
+ "WifiManager:associate", "WifiManager:forget",
+ "WifiManager:wps", "WifiManager:getState",
+ "WifiManager:setPowerSavingMode",
+ "WifiManager:setHttpProxy",
+ "WifiManager:setStaticIpMode",
+ "WifiManager:importCert",
+ "WifiManager:getImportedCerts",
+ "WifiManager:deleteCert",
+ "WifiManager:setWifiEnabled",
+ "WifiManager:setWifiTethering",
+ "child-process-shutdown"];
+
+ messages.forEach((function(msgName) {
+ this._mm.addMessageListener(msgName, this);
+ }).bind(this));
+
+ Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+
+ this.wantScanResults = [];
+
+ this._needToEnableNetworks = false;
+ this._highestPriority = -1;
+
+ // Networks is a map from SSID -> a scan result.
+ this.networks = Object.create(null);
+
+ // ConfiguredNetworks is a map from SSID -> our view of a network. It only
+ // lists networks known to the wpa_supplicant. The SSID field (and other
+ // fields) are quoted for ease of use with WifiManager commands.
+ // Note that we don't have to worry about escaping embedded quotes since in
+ // all cases, the supplicant will take the last quotation that we pass it as
+ // the end of the string.
+ this.configuredNetworks = Object.create(null);
+ this._addingNetworks = Object.create(null);
+
+ this.currentNetwork = null;
+ this.ipAddress = "";
+ this.macAddress = null;
+
+ this._lastConnectionInfo = null;
+ this._connectionInfoTimer = null;
+ this._reconnectOnDisconnect = false;
+
+ // Create p2pObserver and assign to p2pManager.
+ if (WifiManager.p2pSupported()) {
+ this._p2pObserver = WifiP2pWorkerObserver(WifiManager.getP2pManager());
+ WifiManager.getP2pManager().setObserver(this._p2pObserver);
+
+ // Add DOM message observerd by p2pObserver to the message listener as well.
+ this._p2pObserver.getObservedDOMMessages().forEach((function(msgName) {
+ this._mm.addMessageListener(msgName, this);
+ }).bind(this));
+ }
+
+ // Users of instances of nsITimer should keep a reference to the timer until
+ // it is no longer needed in order to assure the timer is fired.
+ this._callbackTimer = null;
+
+ // XXX On some phones (Otoro and Unagi) the wifi driver doesn't play nicely
+ // with the automatic scans that wpa_supplicant does (it appears that the
+ // driver forgets that it's returned scan results and then refuses to try to
+ // rescan. In order to detect this case we start a timer when we enter the
+ // SCANNING state and reset it whenever we either get scan results or leave
+ // the SCANNING state. If the timer fires, we assume that we are stuck and
+ // forceably try to unstick the supplican, also turning on background
+ // scanning to avoid having to constantly poke the supplicant.
+
+ // How long we wait is controlled by the SCAN_STUCK_WAIT constant.
+ const SCAN_STUCK_WAIT = 12000;
+ this._scanStuckTimer = null;
+ this._turnOnBackgroundScan = false;
+
+ function startScanStuckTimer() {
+ if (WifiManager.schedScanRecovery) {
+ self._scanStuckTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ }
+
+ function scanIsStuck() {
+ // Uh-oh, we've waited too long for scan results. Disconnect (which
+ // guarantees that we leave the SCANNING state and tells wpa_supplicant to
+ // wait for our next command) ensure that background scanning is on and
+ // then try again.
+ debug("Determined that scanning is stuck, turning on background scanning!");
+ WifiManager.handlePostWifiScan();
+ WifiManager.disconnect(function(ok) {});
+ self._turnOnBackgroundScan = true;
+ }
+
+ // A list of requests to turn wifi on or off.
+ this._stateRequests = [];
+
+ // Given a connection status network, takes a network from
+ // self.configuredNetworks and prepares it for the DOM.
+ netToDOM = function(net) {
+ if (!net) {
+ return null;
+ }
+ var ssid = dequote(net.ssid);
+ var mode = net.mode;
+ var frequency = net.frequency;
+ var security = (net.key_mgmt === "NONE" && net.wep_key0) ? ["WEP"] :
+ (net.key_mgmt && net.key_mgmt !== "NONE") ? [net.key_mgmt.split(" ")[0]] :
+ [];
+ var password;
+ if (("psk" in net && net.psk) ||
+ ("password" in net && net.password) ||
+ ("wep_key0" in net && net.wep_key0)) {
+ password = "*";
+ }
+
+ var pub = new Network(ssid, mode, frequency, security, password);
+ if (net.identity)
+ pub.identity = dequote(net.identity);
+ if ("netId" in net)
+ pub.known = true;
+ if (net.scan_ssid === 1)
+ pub.hidden = true;
+ if ("ca_cert" in net && net.ca_cert &&
+ net.ca_cert.indexOf("keystore://WIFI_SERVERCERT_" === 0)) {
+ pub.serverCertificate = net.ca_cert.substr(27);
+ }
+ if(net.subject_match) {
+ pub.subjectMatch = net.subject_match;
+ }
+ if ("client_cert" in net && net.client_cert &&
+ net.client_cert.indexOf("keystore://WIFI_USERCERT_" === 0)) {
+ pub.userCertificate = net.client_cert.substr(25);
+ }
+ return pub;
+ };
+
+ netFromDOM = function(net, configured) {
+ // Takes a network from the DOM and makes it suitable for insertion into
+ // self.configuredNetworks (that is calling addNetwork will do the right
+ // thing).
+ // NB: Modifies net in place: safe since we don't share objects between
+ // the dom and the chrome code.
+
+ // Things that are useful for the UI but not to us.
+ delete net.bssid;
+ delete net.signalStrength;
+ delete net.relSignalStrength;
+ delete net.security;
+ delete net.capabilities;
+
+ if (!configured)
+ configured = {};
+
+ net.ssid = quote(net.ssid);
+
+ let wep = false;
+ if ("keyManagement" in net) {
+ if (net.keyManagement === "WEP") {
+ wep = true;
+ net.keyManagement = "NONE";
+ } else if (net.keyManagement === "WPA-EAP") {
+ net.keyManagement += " IEEE8021X";
+ }
+
+ configured.key_mgmt = net.key_mgmt = net.keyManagement; // WPA2-PSK, WPA-PSK, etc.
+ delete net.keyManagement;
+ } else {
+ configured.key_mgmt = net.key_mgmt = "NONE";
+ }
+
+ if (net.hidden) {
+ configured.scan_ssid = net.scan_ssid = 1;
+ delete net.hidden;
+ }
+
+ function checkAssign(name, checkStar) {
+ if (name in net) {
+ let value = net[name];
+ if (!value || (checkStar && value === '*')) {
+ if (name in configured)
+ net[name] = configured[name];
+ else
+ delete net[name];
+ } else {
+ configured[name] = net[name] = quote(value);
+ }
+ }
+ }
+
+ checkAssign("psk", true);
+ checkAssign("identity", false);
+ checkAssign("password", true);
+ if (wep && net.wep && net.wep != '*') {
+ configured.wep_key0 = net.wep_key0 = isWepHexKey(net.wep) ? net.wep : quote(net.wep);
+ configured.auth_alg = net.auth_alg = "OPEN SHARED";
+ }
+
+ function hasValidProperty(name) {
+ return ((name in net) && net[name] != null);
+ }
+
+ if (hasValidProperty("eap")) {
+ if (hasValidProperty("pin")) {
+ net.pin = quote(net.pin);
+ }
+
+ if (hasValidProperty("phase1"))
+ net.phase1 = quote(net.phase1);
+
+ if (hasValidProperty("phase2")) {
+ if (net.phase2 === "TLS") {
+ net.phase2 = quote("autheap=" + net.phase2);
+ } else { // PAP, MSCHAPV2, etc.
+ net.phase2 = quote("auth=" + net.phase2);
+ }
+ }
+
+ if (hasValidProperty("serverCertificate"))
+ net.ca_cert = quote("keystore://WIFI_SERVERCERT_" + net.serverCertificate);
+
+ if (hasValidProperty("subjectMatch"))
+ net.subject_match = quote(net.subjectMatch);
+
+ if (hasValidProperty("userCertificate")) {
+ let userCertName = "WIFI_USERCERT_" + net.userCertificate;
+ net.client_cert = quote("keystore://" + userCertName);
+
+ let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"].
+ getService(Ci.nsIWifiCertService);
+ if (wifiCertService.hasPrivateKey(userCertName)) {
+ if (WifiManager.sdkVersion() >= 19) {
+ // Use openssol engine instead of keystore protocol after Kitkat.
+ net.engine = 1;
+ net.engine_id = quote("keystore");
+ net.key_id = quote("WIFI_USERKEY_" + net.userCertificate);
+ } else {
+ net.private_key = quote("keystore://WIFI_USERKEY_" + net.userCertificate);
+ }
+ }
+ }
+ }
+
+ return net;
+ };
+
+ WifiManager.onsupplicantconnection = function() {
+ debug("Connected to supplicant");
+ // Give it a state other than UNINITIALIZED, INITIALIZING or DISABLING
+ // defined in getter function of WifiManager.enabled. It implies that
+ // we are ready to accept dom request from applications.
+ WifiManager.state = "DISCONNECTED";
+ self._reloadConfiguredNetworks(function(ok) {
+ // Prime this.networks.
+ if (!ok)
+ return;
+
+ // The select network command we used in associate() disables others networks.
+ // Enable them here to make sure wpa_supplicant helps to connect to known
+ // network automatically.
+ self._enableAllNetworks();
+ WifiManager.saveConfig(function() {})
+ });
+
+ // Notify everybody, even if they didn't ask us to come up.
+ WifiManager.getMacAddress(function (mac) {
+ self.macAddress = mac;
+ debug("Got mac: " + mac);
+ self._fireEvent("wifiUp", { macAddress: mac });
+ self.requestDone();
+ });
+
+ if (WifiManager.state === "SCANNING")
+ startScanStuckTimer();
+ };
+
+ WifiManager.onsupplicantlost = function() {
+ WifiManager.supplicantStarted = false;
+ WifiManager.state = "UNINITIALIZED";
+ debug("Supplicant died!");
+
+ // Notify everybody, even if they didn't ask us to come up.
+ self._fireEvent("wifiDown", {});
+ self.requestDone();
+ };
+
+ WifiManager.onpasswordmaybeincorrect = function() {
+ WifiManager.authenticationFailuresCount++;
+ };
+
+ WifiManager.ondisconnected = function() {
+ // We may fail to establish the connection, re-enable the
+ // rest of our networks.
+ if (self._needToEnableNetworks) {
+ self._enableAllNetworks();
+ self._needToEnableNetworks = false;
+ }
+
+ let connectionInfo = this.connectionInfo;
+ WifiManager.getNetworkId(connectionInfo.ssid, function(netId) {
+ // Trying to get netId from current network.
+ if (!netId &&
+ self.currentNetwork && self.currentNetwork.ssid &&
+ dequote(self.currentNetwork.ssid) == connectionInfo.ssid &&
+ typeof self.currentNetwork.netId !== "undefined") {
+ netId = self.currentNetwork.netId;
+ }
+ if (netId) {
+ WifiManager.disableNetwork(netId, function() {});
+ }
+ });
+ self._fireEvent("onconnectingfailed", {network: netToDOM(self.currentNetwork)});
+ }
+
+ WifiManager.onstatechange = function() {
+ debug("State change: " + this.prevState + " -> " + this.state);
+
+ if (self._connectionInfoTimer &&
+ this.state !== "CONNECTED" &&
+ this.state !== "COMPLETED") {
+ self._stopConnectionInfoTimer();
+ }
+
+ if (this.state !== "SCANNING" &&
+ self._scanStuckTimer) {
+ self._scanStuckTimer.cancel();
+ self._scanStuckTimer = null;
+ }
+
+ switch (this.state) {
+ case "DORMANT":
+ // The dormant state is a bad state to be in since we won't
+ // automatically connect. Try to knock us out of it. We only
+ // hit this state when we've failed to run DHCP, so trying
+ // again isn't the worst thing we can do. Eventually, we'll
+ // need to detect if we're looping in this state and bail out.
+ WifiManager.reconnect(function(){});
+ break;
+ case "ASSOCIATING":
+ // id has not yet been filled in, so we can only report the ssid and
+ // bssid. mode and frequency are simply made up.
+ self.currentNetwork =
+ { bssid: WifiManager.connectionInfo.bssid,
+ ssid: quote(WifiManager.connectionInfo.ssid),
+ mode: MODE_ESS,
+ frequency: 0};
+ WifiManager.getNetworkConfiguration(self.currentNetwork, function (){
+ // Notify again because we get complete network information.
+ self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
+ });
+ break;
+ case "ASSOCIATED":
+ // set to full power mode when ready to do 4 way handsharke.
+ WifiManager.setPowerSavingMode(false);
+ if (!self.currentNetwork) {
+ self.currentNetwork =
+ { bssid: WifiManager.connectionInfo.bssid,
+ ssid: quote(WifiManager.connectionInfo.ssid) };
+ }
+ self.currentNetwork.netId = this.id;
+ WifiManager.getNetworkConfiguration(self.currentNetwork, function (){
+ // Notify again because we get complete network information.
+ self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
+ });
+ break;
+ case "COMPLETED":
+ // Now that we've successfully completed the connection, re-enable the
+ // rest of our networks.
+ // XXX Need to do this eventually if the user entered an incorrect
+ // password. For now, we require user interaction to break the loop and
+ // select a better network!
+ if (self._needToEnableNetworks) {
+ self._enableAllNetworks();
+ self._needToEnableNetworks = false;
+ }
+
+ var _oncompleted = function() {
+ // The full authentication process is completed, reset the count.
+ WifiManager.authenticationFailuresCount = 0;
+ WifiManager.loopDetectionCount = 0;
+ self._startConnectionInfoTimer();
+ self._fireEvent("onassociate", { network: netToDOM(self.currentNetwork) });
+ };
+
+ // We get the ASSOCIATED event when we've associated but not connected, so
+ // wait until the handshake is complete.
+ if (this.fromStatus || !self.currentNetwork) {
+ // In this case, we connected to an already-connected wpa_supplicant,
+ // because of that we need to gather information about the current
+ // network here.
+ self.currentNetwork = { bssid: WifiManager.connectionInfo.bssid,
+ ssid: quote(WifiManager.connectionInfo.ssid),
+ netId: WifiManager.connectionInfo.id };
+ WifiManager.getNetworkConfiguration(self.currentNetwork, _oncompleted);
+ } else {
+ _oncompleted();
+ }
+ break;
+ case "CONNECTED":
+ // wifi connection complete, turn on the power saving mode.
+ WifiManager.setPowerSavingMode(true);
+ // BSSID is read after connected, update it.
+ self.currentNetwork.bssid = WifiManager.connectionInfo.bssid;
+ break;
+ case "DISCONNECTED":
+ // wpa_supplicant may give us a "DISCONNECTED" event even if
+ // we are already in "DISCONNECTED" state.
+ if ((WifiNetworkInterface.info.state ===
+ Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED) &&
+ (this.prevState === "INITIALIZING" ||
+ this.prevState === "DISCONNECTED" ||
+ this.prevState === "INTERFACE_DISABLED" ||
+ this.prevState === "INACTIVE" ||
+ this.prevState === "UNINITIALIZED")) {
+ // When in disconnected mode, need to turn on wifi power saving mode.
+ WifiManager.setPowerSavingMode(true);
+ return;
+ }
+
+ self._fireEvent("ondisconnect", {network: netToDOM(self.currentNetwork)});
+
+ self.currentNetwork = null;
+ self.ipAddress = "";
+
+ if (self._turnOnBackgroundScan) {
+ self._turnOnBackgroundScan = false;
+ WifiManager.setBackgroundScan("ON", function(did_something, ok) {
+ WifiManager.reassociate(function() {});
+ });
+ }
+
+ WifiManager.connectionDropped(function() {
+ // We've disconnected from a network because of a call to forgetNetwork.
+ // Reconnect to the next available network (if any).
+ if (self._reconnectOnDisconnect) {
+ self._reconnectOnDisconnect = false;
+ WifiManager.reconnect(function(){});
+ }
+ });
+
+ WifiNetworkInterface.info.state =
+ Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
+
+ // Update network infterface first then clear properties.
+ gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
+ WifiNetworkInterface.info.ips = [];
+ WifiNetworkInterface.info.prefixLengths = [];
+ WifiNetworkInterface.info.gateways = [];
+ WifiNetworkInterface.info.dnses = [];
+
+
+ break;
+ case "WPS_TIMEOUT":
+ self._fireEvent("onwpstimeout", {});
+ break;
+ case "WPS_FAIL":
+ self._fireEvent("onwpsfail", {});
+ break;
+ case "WPS_OVERLAP_DETECTED":
+ self._fireEvent("onwpsoverlap", {});
+ break;
+ case "AUTHENTICATING":
+ self._fireEvent("onauthenticating", {network: netToDOM(self.currentNetwork)});
+ break;
+ case "SCANNING":
+ // If we're already scanning in the background, we don't need to worry
+ // about getting stuck while scanning.
+ if (!WifiManager.backgroundScanEnabled && WifiManager.enabled)
+ startScanStuckTimer();
+ break;
+ }
+ };
+
+ WifiManager.onnetworkconnected = function() {
+ if (!this.info || !this.info.ipaddr_str) {
+ debug("Network information is invalid.");
+ return;
+ }
+
+ let maskLength =
+ netHelpers.getMaskLength(netHelpers.stringToIP(this.info.mask_str));
+ if (!maskLength) {
+ maskLength = 32; // max prefix for IPv4.
+ }
+
+ let netConnect = WifiManager.getHttpProxyNetwork(self.currentNetwork);
+ if (netConnect) {
+ WifiNetworkInterface.httpProxyHost = netConnect.httpProxyHost;
+ WifiNetworkInterface.httpProxyPort = netConnect.httpProxyPort;
+ }
+
+ WifiNetworkInterface.info.state =
+ Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED;
+ WifiNetworkInterface.info.ips = [this.info.ipaddr_str];
+ WifiNetworkInterface.info.prefixLengths = [maskLength];
+ WifiNetworkInterface.info.gateways = [this.info.gateway_str];
+ if (typeof this.info.dns1_str == "string" &&
+ this.info.dns1_str.length) {
+ WifiNetworkInterface.info.dnses.push(this.info.dns1_str);
+ }
+ if (typeof this.info.dns2_str == "string" &&
+ this.info.dns2_str.length) {
+ WifiNetworkInterface.info.dnses.push(this.info.dns2_str);
+ }
+ gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
+
+ self.ipAddress = this.info.ipaddr_str;
+
+ // We start the connection information timer when we associate, but
+ // don't have our IP address until here. Make sure that we fire a new
+ // connectionInformation event with the IP address the next time the
+ // timer fires.
+ self._lastConnectionInfo = null;
+ self._fireEvent("onconnect", { network: netToDOM(self.currentNetwork) });
+ };
+
+ WifiManager.onscanresultsavailable = function() {
+ if (self._scanStuckTimer) {
+ // We got scan results! We must not be stuck for now, try again.
+ self._scanStuckTimer.cancel();
+ self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+
+ if (self.wantScanResults.length === 0) {
+ debug("Scan results available, but we don't need them");
+ return;
+ }
+
+ debug("Scan results are available! Asking for them.");
+ WifiManager.getScanResults(function(r) {
+ // Failure.
+ if (!r) {
+ self.wantScanResults.forEach(function(callback) { callback(null) });
+ self.wantScanResults = [];
+ return;
+ }
+
+ let capabilities = WifiManager.getCapabilities();
+
+ // Now that we have scan results, there's no more need to continue
+ // scanning. Ignore any errors from this command.
+ WifiManager.setScanMode("inactive", function() {});
+ let lines = r.split("\n");
+ // NB: Skip the header line.
+ self.networksArray = [];
+ for (let i = 1; i < lines.length; ++i) {
+ // bssid / frequency / signal level / flags / ssid
+ var match = /([\S]+)\s+([\S]+)\s+([\S]+)\s+(\[[\S]+\])?\s(.*)/.exec(lines[i]);
+
+ if (match && match[5]) {
+ let ssid = match[5],
+ bssid = match[1],
+ frequency = match[2],
+ signalLevel = match[3],
+ flags = match[4];
+
+ /* Skip networks with unknown or unsupported modes. */
+ if (capabilities.mode.indexOf(getMode(flags)) == -1)
+ continue;
+
+ // If this is the first time that we've seen this SSID in the scan
+ // results, add it to the list along with any other information.
+ // Also, we use the highest signal strength that we see.
+ let network = new ScanResult(ssid, bssid, frequency, flags, signalLevel);
+
+ let networkKey = getNetworkKey(network);
+ let eapIndex = -1;
+ if (networkKey in self.configuredNetworks) {
+ let known = self.configuredNetworks[networkKey];
+ network.known = true;
+
+ if ("identity" in known && known.identity)
+ network.identity = dequote(known.identity);
+
+ // Note: we don't hand out passwords here! The * marks that there
+ // is a password that we're hiding.
+ if (("psk" in known && known.psk) ||
+ ("password" in known && known.password) ||
+ ("wep_key0" in known && known.wep_key0)) {
+ network.password = "*";
+ }
+ }
+
+ self.networksArray.push(network);
+ if (network.bssid === WifiManager.connectionInfo.bssid)
+ network.connected = true;
+
+ let signal = calculateSignal(Number(match[3]));
+ if (signal > network.relSignalStrength)
+ network.relSignalStrength = signal;
+ } else if (!match) {
+ debug("Match didn't find anything for: " + lines[i]);
+ }
+ }
+
+ self.wantScanResults.forEach(function(callback) { callback(self.networksArray) });
+ self.wantScanResults = [];
+ });
+ };
+
+ WifiManager.onstationinfoupdate = function() {
+ self._fireEvent("stationinfoupdate", { station: this.station });
+ };
+
+ WifiManager.onstopconnectioninfotimer = function() {
+ self._stopConnectionInfoTimer();
+ };
+
+ // Read the 'wifi.enabled' setting in order to start with a known
+ // value at boot time. The handle() will be called after reading.
+ //
+ // nsISettingsServiceCallback implementation.
+ var initWifiEnabledCb = {
+ handle: function handle(aName, aResult) {
+ if (aName !== SETTINGS_WIFI_ENABLED)
+ return;
+ if (aResult === null)
+ aResult = true;
+ self.handleWifiEnabled(aResult);
+ },
+ handleError: function handleError(aErrorMessage) {
+ debug("Error reading the 'wifi.enabled' setting. Default to wifi on.");
+ self.handleWifiEnabled(true);
+ }
+ };
+
+ var initWifiDebuggingEnabledCb = {
+ handle: function handle(aName, aResult) {
+ if (aName !== SETTINGS_WIFI_DEBUG_ENABLED)
+ return;
+ if (aResult === null)
+ aResult = false;
+ DEBUG = aResult;
+ updateDebug();
+ },
+ handleError: function handleError(aErrorMessage) {
+ debug("Error reading the 'wifi.debugging.enabled' setting. Default to debugging off.");
+ DEBUG = false;
+ updateDebug();
+ }
+ };
+
+ this.initTetheringSettings();
+
+ let lock = gSettingsService.createLock();
+ lock.get(SETTINGS_WIFI_ENABLED, initWifiEnabledCb);
+ lock.get(SETTINGS_WIFI_DEBUG_ENABLED, initWifiDebuggingEnabledCb);
+
+ lock.get(SETTINGS_WIFI_SSID, this);
+ lock.get(SETTINGS_WIFI_SECURITY_TYPE, this);
+ lock.get(SETTINGS_WIFI_SECURITY_PASSWORD, this);
+ lock.get(SETTINGS_WIFI_IP, this);
+ lock.get(SETTINGS_WIFI_PREFIX, this);
+ lock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
+ lock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
+ lock.get(SETTINGS_WIFI_DNS1, this);
+ lock.get(SETTINGS_WIFI_DNS2, this);
+ lock.get(SETTINGS_WIFI_TETHERING_ENABLED, this);
+
+ lock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this);
+ lock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
+
+ this._wifiTetheringSettingsToRead = [SETTINGS_WIFI_SSID,
+ SETTINGS_WIFI_SECURITY_TYPE,
+ SETTINGS_WIFI_SECURITY_PASSWORD,
+ SETTINGS_WIFI_IP,
+ SETTINGS_WIFI_PREFIX,
+ SETTINGS_WIFI_DHCPSERVER_STARTIP,
+ SETTINGS_WIFI_DHCPSERVER_ENDIP,
+ SETTINGS_WIFI_DNS1,
+ SETTINGS_WIFI_DNS2,
+ SETTINGS_WIFI_TETHERING_ENABLED,
+ SETTINGS_USB_DHCPSERVER_STARTIP,
+ SETTINGS_USB_DHCPSERVER_ENDIP];
+}
+
+function translateState(state) {
+ switch (state) {
+ case "INTERFACE_DISABLED":
+ case "INACTIVE":
+ case "SCANNING":
+ case "DISCONNECTED":
+ default:
+ return "disconnected";
+
+ case "AUTHENTICATING":
+ case "ASSOCIATING":
+ case "ASSOCIATED":
+ case "FOUR_WAY_HANDSHAKE":
+ case "GROUP_HANDSHAKE":
+ return "connecting";
+
+ case "COMPLETED":
+ return WifiManager.getDhcpInfo() ? "connected" : "associated";
+ }
+}
+
+WifiWorker.prototype = {
+ classID: WIFIWORKER_CID,
+ classInfo: XPCOMUtils.generateCI({classID: WIFIWORKER_CID,
+ contractID: WIFIWORKER_CONTRACTID,
+ classDescription: "WifiWorker",
+ interfaces: [Ci.nsIWorkerHolder,
+ Ci.nsIWifi,
+ Ci.nsIObserver]}),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
+ Ci.nsIWifi,
+ Ci.nsIObserver,
+ Ci.nsISettingsServiceCallback]),
+
+ disconnectedByWifi: false,
+
+ disconnectedByWifiTethering: false,
+
+ _wifiTetheringSettingsToRead: [],
+
+ _oldWifiTetheringEnabledState: null,
+
+ tetheringSettings: {},
+
+ initTetheringSettings: function initTetheringSettings() {
+ this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = null;
+ this.tetheringSettings[SETTINGS_WIFI_SSID] = DEFAULT_WIFI_SSID;
+ this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE] = DEFAULT_WIFI_SECURITY_TYPE;
+ this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD] = DEFAULT_WIFI_SECURITY_PASSWORD;
+ this.tetheringSettings[SETTINGS_WIFI_IP] = DEFAULT_WIFI_IP;
+ this.tetheringSettings[SETTINGS_WIFI_PREFIX] = DEFAULT_WIFI_PREFIX;
+ this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
+ this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
+ this.tetheringSettings[SETTINGS_WIFI_DNS1] = DEFAULT_DNS1;
+ this.tetheringSettings[SETTINGS_WIFI_DNS2] = DEFAULT_DNS2;
+
+ this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
+ this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP;
+ },
+
+ // Internal methods.
+ waitForScan: function(callback) {
+ this.wantScanResults.push(callback);
+ },
+
+ // In order to select a specific network, we disable the rest of the
+ // networks known to us. However, in general, we want the supplicant to
+ // connect to which ever network it thinks is best, so when we select the
+ // proper network (or fail to), we need to re-enable the rest.
+ _enableAllNetworks: function() {
+ for (let key in this.configuredNetworks) {
+ let net = this.configuredNetworks[key];
+ WifiManager.enableNetwork(net.netId, false, function(ok) {
+ net.disabled = ok ? 1 : 0;
+ });
+ }
+ },
+
+ _startConnectionInfoTimer: function() {
+ if (this._connectionInfoTimer)
+ return;
+
+ var self = this;
+ function getConnectionInformation() {
+ WifiManager.getConnectionInfo(function(connInfo) {
+ // See comments in calculateSignal for information about this.
+ if (!connInfo) {
+ self._lastConnectionInfo = null;
+ return;
+ }
+
+ let { rssi, linkspeed } = connInfo;
+ if (rssi > 0)
+ rssi -= 256;
+ if (rssi <= MIN_RSSI)
+ rssi = MIN_RSSI;
+ else if (rssi >= MAX_RSSI)
+ rssi = MAX_RSSI;
+
+ let info = { signalStrength: rssi,
+ relSignalStrength: calculateSignal(rssi),
+ linkSpeed: linkspeed,
+ ipAddress: self.ipAddress };
+ let last = self._lastConnectionInfo;
+
+ // Only fire the event if the link speed changed or the signal
+ // strength changed by more than 10%.
+ function tensPlace(percent) {
+ return (percent / 10) | 0;
+ }
+
+ if (last && last.linkSpeed === info.linkSpeed &&
+ last.ipAddress === info.ipAddress &&
+ tensPlace(last.relSignalStrength) === tensPlace(info.relSignalStrength)) {
+ return;
+ }
+
+ self._lastConnectionInfo = info;
+ debug("Firing connectioninfoupdate: " + uneval(info));
+ self._fireEvent("connectioninfoupdate", info);
+ });
+ }
+
+ // Prime our _lastConnectionInfo immediately and fire the event at the
+ // same time.
+ getConnectionInformation();
+
+ // Now, set up the timer for regular updates.
+ this._connectionInfoTimer =
+ Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._connectionInfoTimer.init(getConnectionInformation, 5000,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+ },
+
+ _stopConnectionInfoTimer: function() {
+ if (!this._connectionInfoTimer)
+ return;
+
+ this._connectionInfoTimer.cancel();
+ this._connectionInfoTimer = null;
+ this._lastConnectionInfo = null;
+ },
+
+ _reloadConfiguredNetworks: function(callback) {
+ WifiManager.getConfiguredNetworks((function(networks) {
+ if (!networks) {
+ debug("Unable to get configured networks");
+ callback(false);
+ return;
+ }
+
+ this._highestPriority = -1;
+
+ // Convert between netId-based and ssid-based indexing.
+ for (let net in networks) {
+ let network = networks[net];
+ delete networks[net];
+
+ if (!network.ssid) {
+ WifiManager.removeNetwork(network.netId, function() {});
+ continue;
+ }
+
+ if (network.hasOwnProperty("priority") &&
+ network.priority > this._highestPriority) {
+ this._highestPriority = network.priority;
+ }
+
+ let networkKey = getNetworkKey(network);
+ // Accept latest config of same network(same SSID and same security).
+ if (networks[networkKey]) {
+ WifiManager.removeNetwork(networks[networkKey].netId, function() {});
+ }
+ networks[networkKey] = network;
+ }
+
+ this.configuredNetworks = networks;
+ callback(true);
+ }).bind(this));
+ },
+
+ // Important side effect: calls WifiManager.saveConfig.
+ _reprioritizeNetworks: function(callback) {
+ // First, sort the networks in orer of their priority.
+ var ordered = Object.getOwnPropertyNames(this.configuredNetworks);
+ let self = this;
+ ordered.sort(function(a, b) {
+ var neta = self.configuredNetworks[a],
+ netb = self.configuredNetworks[b];
+
+ // Sort unsorted networks to the end of the list.
+ if (isNaN(neta.priority))
+ return isNaN(netb.priority) ? 0 : 1;
+ if (isNaN(netb.priority))
+ return -1;
+ return netb.priority - neta.priority;
+ });
+
+ // Skip unsorted networks.
+ let newPriority = 0, i;
+ for (i = ordered.length - 1; i >= 0; --i) {
+ if (!isNaN(this.configuredNetworks[ordered[i]].priority))
+ break;
+ }
+
+ // No networks we care about?
+ if (i < 0) {
+ WifiManager.saveConfig(callback);
+ return;
+ }
+
+ // Now assign priorities from 0 to length, starting with the smallest
+ // priority and heading towards the highest (note the dependency between
+ // total and i here).
+ let done = 0, errors = 0, total = i + 1;
+ for (; i >= 0; --i) {
+ let network = this.configuredNetworks[ordered[i]];
+ network.priority = newPriority++;
+
+ // Note: networkUpdated declared below since it happens logically after
+ // this loop.
+ WifiManager.updateNetwork(network, networkUpdated);
+ }
+
+ function networkUpdated(ok) {
+ if (!ok)
+ ++errors;
+ if (++done === total) {
+ if (errors > 0) {
+ callback(false);
+ return;
+ }
+
+ WifiManager.saveConfig(function(ok) {
+ if (!ok) {
+ callback(false);
+ return;
+ }
+
+ self._reloadConfiguredNetworks(function(ok) {
+ callback(ok);
+ });
+ });
+ }
+ }
+ },
+
+ // nsIWifi
+
+ _domManagers: [],
+ _fireEvent: function(message, data) {
+ this._domManagers.forEach(function(manager) {
+ // Note: We should never have a dead message manager here because we
+ // observe our child message managers shutting down, below.
+ manager.sendAsyncMessage("WifiManager:" + message, data);
+ });
+ },
+
+ _sendMessage: function(message, success, data, msg) {
+ try {
+ msg.manager.sendAsyncMessage(message + (success ? ":OK" : ":NO"),
+ { data: data, rid: msg.rid, mid: msg.mid });
+ } catch (e) {
+ debug("sendAsyncMessage error : " + e);
+ }
+ this._splicePendingRequest(msg);
+ },
+
+ _domRequest: [],
+
+ _splicePendingRequest: function(msg) {
+ for (let i = 0; i < this._domRequest.length; i++) {
+ if (this._domRequest[i].msg === msg) {
+ this._domRequest.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ _clearPendingRequest: function() {
+ if (this._domRequest.length === 0) return;
+ this._domRequest.forEach((function(req) {
+ this._sendMessage(req.name + ":Return", false, "Wifi is disabled", req.msg);
+ }).bind(this));
+ },
+
+ receiveMessage: function MessageManager_receiveMessage(aMessage) {
+ let msg = aMessage.data || {};
+ msg.manager = aMessage.target;
+
+ if (WifiManager.p2pSupported()) {
+ // If p2pObserver returns something truthy, return it!
+ // Otherwise, continue to do the rest of tasks.
+ var p2pRet = this._p2pObserver.onDOMMessage(aMessage);
+ if (p2pRet) {
+ return p2pRet;
+ }
+ }
+
+ // Note: By the time we receive child-process-shutdown, the child process
+ // has already forgotten its permissions so we do this before the
+ // permissions check.
+ if (aMessage.name === "child-process-shutdown") {
+ let i;
+ if ((i = this._domManagers.indexOf(msg.manager)) != -1) {
+ this._domManagers.splice(i, 1);
+ }
+ for (i = this._domRequest.length - 1; i >= 0; i--) {
+ if (this._domRequest[i].msg.manager === msg.manager) {
+ this._domRequest.splice(i, 1);
+ }
+ }
+ return;
+ }
+
+ if (!aMessage.target.assertPermission("wifi-manage")) {
+ return;
+ }
+
+ // We are interested in DOMRequests only.
+ if (aMessage.name != "WifiManager:getState") {
+ this._domRequest.push({name: aMessage.name, msg:msg});
+ }
+
+ switch (aMessage.name) {
+ case "WifiManager:setWifiEnabled":
+ this.setWifiEnabled(msg);
+ break;
+ case "WifiManager:getNetworks":
+ this.getNetworks(msg);
+ break;
+ case "WifiManager:getKnownNetworks":
+ this.getKnownNetworks(msg);
+ break;
+ case "WifiManager:associate":
+ this.associate(msg);
+ break;
+ case "WifiManager:forget":
+ this.forget(msg);
+ break;
+ case "WifiManager:wps":
+ this.wps(msg);
+ break;
+ case "WifiManager:setPowerSavingMode":
+ this.setPowerSavingMode(msg);
+ break;
+ case "WifiManager:setHttpProxy":
+ this.setHttpProxy(msg);
+ break;
+ case "WifiManager:setStaticIpMode":
+ this.setStaticIpMode(msg);
+ break;
+ case "WifiManager:importCert":
+ this.importCert(msg);
+ break;
+ case "WifiManager:getImportedCerts":
+ this.getImportedCerts(msg);
+ break;
+ case "WifiManager:deleteCert":
+ this.deleteCert(msg);
+ break;
+ case "WifiManager:setWifiTethering":
+ this.setWifiTethering(msg);
+ break;
+ case "WifiManager:getState": {
+ let i;
+ if ((i = this._domManagers.indexOf(msg.manager)) === -1) {
+ this._domManagers.push(msg.manager);
+ }
+
+ let net = this.currentNetwork ? netToDOM(this.currentNetwork) : null;
+ return { network: net,
+ connectionInfo: this._lastConnectionInfo,
+ enabled: WifiManager.enabled,
+ status: translateState(WifiManager.state),
+ macAddress: this.macAddress,
+ capabilities: WifiManager.getCapabilities()};
+ }
+ }
+ },
+
+ getNetworks: function(msg) {
+ const message = "WifiManager:getNetworks:Return";
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ let sent = false;
+ let callback = (function (networks) {
+ if (sent)
+ return;
+ sent = true;
+ this._sendMessage(message, networks !== null, networks, msg);
+ }).bind(this);
+ this.waitForScan(callback);
+
+ WifiManager.scan(true, (function(ok) {
+ // If the scan command succeeded, we're done.
+ if (ok)
+ return;
+
+ // Avoid sending multiple responses.
+ if (sent)
+ return;
+
+ // Otherwise, let the client know that it failed, it's responsible for
+ // trying again in a few seconds.
+ sent = true;
+ this._sendMessage(message, false, "ScanFailed", msg);
+ }).bind(this));
+ },
+
+ getWifiScanResults: function(callback) {
+ var count = 0;
+ var timer = null;
+ var self = this;
+
+ if (!WifiManager.enabled) {
+ callback.onfailure();
+ return;
+ }
+
+ self.waitForScan(waitForScanCallback);
+ doScan();
+ function doScan() {
+ WifiManager.scan(true, (function (ok) {
+ if (!ok) {
+ if (!timer) {
+ count = 0;
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ }
+
+ if (count++ >= 3) {
+ timer = null;
+ self.wantScanResults.splice(self.wantScanResults.indexOf(waitForScanCallback), 1);
+ callback.onfailure();
+ return;
+ }
+
+ // Else it's still running, continue waiting.
+ timer.initWithCallback(doScan, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
+ return;
+ }
+ }).bind(this));
+ }
+
+ function waitForScanCallback(networks) {
+ if (networks === null) {
+ callback.onfailure();
+ return;
+ }
+
+ var wifiScanResults = new Array();
+ var net;
+ for (let net in networks) {
+ let value = networks[net];
+ wifiScanResults.push(transformResult(value));
+ }
+ callback.onready(wifiScanResults.length, wifiScanResults);
+ }
+
+ function transformResult(element) {
+ var result = new WifiScanResult();
+ result.connected = false;
+ for (let id in element) {
+ if (id === "__exposedProps__") {
+ continue;
+ }
+ if (id === "security") {
+ result[id] = 0;
+ var security = element[id];
+ for (let j = 0; j < security.length; j++) {
+ if (security[j] === "WPA-PSK") {
+ result[id] |= Ci.nsIWifiScanResult.WPA_PSK;
+ } else if (security[j] === "WPA-EAP") {
+ result[id] |= Ci.nsIWifiScanResult.WPA_EAP;
+ } else if (security[j] === "WEP") {
+ result[id] |= Ci.nsIWifiScanResult.WEP;
+ } else {
+ result[id] = 0;
+ }
+ }
+ } else {
+ result[id] = element[id];
+ }
+ }
+ return result;
+ }
+ },
+
+ getKnownNetworks: function(msg) {
+ const message = "WifiManager:getKnownNetworks:Return";
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ this._reloadConfiguredNetworks((function(ok) {
+ if (!ok) {
+ this._sendMessage(message, false, "Failed", msg);
+ return;
+ }
+
+ var networks = [];
+ for (let networkKey in this.configuredNetworks) {
+ networks.push(netToDOM(this.configuredNetworks[networkKey]));
+ }
+
+ this._sendMessage(message, true, networks, msg);
+ }).bind(this));
+ },
+
+ _setWifiEnabledCallback: function(status) {
+ if (status !== 0) {
+ this.requestDone();
+ return;
+ }
+
+ // If we're enabling ourselves, then wait until we've connected to the
+ // supplicant to notify. If we're disabling, we take care of this in
+ // supplicantlost.
+ if (WifiManager.supplicantStarted)
+ WifiManager.start();
+ },
+
+ /**
+ * Compatibility flags for detecting if Gaia is controlling wifi by settings
+ * or API, once API is called, gecko will no longer accept wifi enable
+ * control from settings.
+ * This is used to deal with compatibility issue while Gaia adopted to use
+ * API but gecko doesn't remove the settings code in time.
+ * TODO: Remove this flag in Bug 1050147
+ */
+ ignoreWifiEnabledFromSettings: false,
+ setWifiEnabled: function(msg) {
+ const message = "WifiManager:setWifiEnabled:Return";
+ let self = this;
+ let enabled = msg.data;
+
+ self.ignoreWifiEnabledFromSettings = true;
+ // No change.
+ if (enabled === WifiManager.enabled) {
+ this._sendMessage(message, true, true, msg);
+ return;
+ }
+
+ // Can't enable wifi while hotspot mode is enabled.
+ if (enabled && (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
+ WifiManager.isWifiTetheringEnabled(WifiManager.tetheringState))) {
+ self._sendMessage(message, false, "Can't enable Wifi while hotspot mode is enabled", msg);
+ return;
+ }
+
+ WifiManager.setWifiEnabled(enabled, function(ok) {
+ if (ok === 0 || ok === "no change") {
+ self._sendMessage(message, true, true, msg);
+
+ // Reply error to pending requests.
+ if (!enabled) {
+ self._clearPendingRequest();
+ } else {
+ WifiManager.start();
+ }
+ } else {
+ self._sendMessage(message, false, "Set wifi enabled failed", msg);
+ }
+ });
+ },
+
+ _setWifiEnabled: function(enabled, callback) {
+ // Reply error to pending requests.
+ if (!enabled) {
+ this._clearPendingRequest();
+ }
+
+ WifiManager.setWifiEnabled(enabled, callback);
+ },
+
+ // requestDone() must be called to before callback complete(or error)
+ // so next queue in the request quene can be executed.
+ // TODO: Remove command queue in Bug 1050147
+ queueRequest: function(data, callback) {
+ if (!callback) {
+ throw "Try to enqueue a request without callback";
+ }
+
+ let optimizeCommandList = ["setWifiEnabled", "setWifiApEnabled"];
+ if (optimizeCommandList.indexOf(data.command) != -1) {
+ this._stateRequests = this._stateRequests.filter(function(element) {
+ return element.data.command !== data.command;
+ });
+ }
+
+ this._stateRequests.push({
+ data: data,
+ callback: callback
+ });
+
+ this.nextRequest();
+ },
+
+ getWifiTetheringParameters: function getWifiTetheringParameters(enable) {
+ if (this.useTetheringAPI) {
+ return this.getWifiTetheringConfiguration(enable);
+ } else {
+ return this.getWifiTetheringParametersBySetting(enable);
+ }
+ },
+
+ getWifiTetheringConfiguration: function getWifiTetheringConfiguration(enable) {
+ let config = {};
+ let params = this.tetheringConfig;
+
+ let check = function(field, _default) {
+ config[field] = field in params ? params[field] : _default;
+ };
+
+ check("ssid", DEFAULT_WIFI_SSID);
+ check("security", DEFAULT_WIFI_SECURITY_TYPE);
+ check("key", DEFAULT_WIFI_SECURITY_PASSWORD);
+ check("ip", DEFAULT_WIFI_IP);
+ check("prefix", DEFAULT_WIFI_PREFIX);
+ check("wifiStartIp", DEFAULT_WIFI_DHCPSERVER_STARTIP);
+ check("wifiEndIp", DEFAULT_WIFI_DHCPSERVER_ENDIP);
+ check("usbStartIp", DEFAULT_USB_DHCPSERVER_STARTIP);
+ check("usbEndIp", DEFAULT_USB_DHCPSERVER_ENDIP);
+ check("dns1", DEFAULT_DNS1);
+ check("dns2", DEFAULT_DNS2);
+
+ config.enable = enable;
+ config.mode = enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION;
+ config.link = enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN;
+
+ // Check the format to prevent netd from crash.
+ if (enable && (!config.ssid || config.ssid == "")) {
+ debug("Invalid SSID value.");
+ return null;
+ }
+
+ if (enable && (config.security != WIFI_SECURITY_TYPE_NONE && !config.key)) {
+ debug("Invalid security password.");
+ return null;
+ }
+
+ // Using the default values here until application supports these settings.
+ if (config.ip == "" || config.prefix == "" ||
+ config.wifiStartIp == "" || config.wifiEndIp == "" ||
+ config.usbStartIp == "" || config.usbEndIp == "") {
+ debug("Invalid subnet information.");
+ return null;
+ }
+
+ return config;
+ },
+
+ getWifiTetheringParametersBySetting: function getWifiTetheringParametersBySetting(enable) {
+ let ssid;
+ let securityType;
+ let securityId;
+ let interfaceIp;
+ let prefix;
+ let wifiDhcpStartIp;
+ let wifiDhcpEndIp;
+ let usbDhcpStartIp;
+ let usbDhcpEndIp;
+ let dns1;
+ let dns2;
+
+ ssid = this.tetheringSettings[SETTINGS_WIFI_SSID];
+ securityType = this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE];
+ securityId = this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD];
+ interfaceIp = this.tetheringSettings[SETTINGS_WIFI_IP];
+ prefix = this.tetheringSettings[SETTINGS_WIFI_PREFIX];
+ wifiDhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
+ wifiDhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
+ usbDhcpStartIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP];
+ usbDhcpEndIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP];
+ dns1 = this.tetheringSettings[SETTINGS_WIFI_DNS1];
+ dns2 = this.tetheringSettings[SETTINGS_WIFI_DNS2];
+
+ // Check the format to prevent netd from crash.
+ if (!ssid || ssid == "") {
+ debug("Invalid SSID value.");
+ return null;
+ }
+ // Truncate ssid if its length of encoded to utf8 is longer than 32.
+ while (unescape(encodeURIComponent(ssid)).length > 32)
+ {
+ ssid = ssid.substring(0, ssid.length-1);
+ }
+
+ if (securityType != WIFI_SECURITY_TYPE_NONE &&
+ securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
+ securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
+
+ debug("Invalid security type.");
+ return null;
+ }
+ if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
+ debug("Invalid security password.");
+ return null;
+ }
+ // Using the default values here until application supports these settings.
+ if (interfaceIp == "" || prefix == "" ||
+ wifiDhcpStartIp == "" || wifiDhcpEndIp == "" ||
+ usbDhcpStartIp == "" || usbDhcpEndIp == "") {
+ debug("Invalid subnet information.");
+ return null;
+ }
+
+ return {
+ ssid: ssid,
+ security: securityType,
+ key: securityId,
+ ip: interfaceIp,
+ prefix: prefix,
+ wifiStartIp: wifiDhcpStartIp,
+ wifiEndIp: wifiDhcpEndIp,
+ usbStartIp: usbDhcpStartIp,
+ usbEndIp: usbDhcpEndIp,
+ dns1: dns1,
+ dns2: dns2,
+ enable: enable,
+ mode: enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION,
+ link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
+ };
+ },
+
+ setWifiApEnabled: function(enabled, callback) {
+ let configuration = this.getWifiTetheringParameters(enabled);
+
+ if (!configuration) {
+ this.requestDone();
+ debug("Invalid Wifi Tethering configuration.");
+ return;
+ }
+
+ WifiManager.setWifiApEnabled(enabled, configuration, callback);
+ },
+
+ associate: function(msg) {
+ const MAX_PRIORITY = 9999;
+ const message = "WifiManager:associate:Return";
+ let network = msg.data;
+
+ let privnet = network;
+ let dontConnect = privnet.dontConnect;
+ delete privnet.dontConnect;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ let self = this;
+ function networkReady() {
+ // saveConfig now before we disable most of the other networks.
+ function selectAndConnect() {
+ WifiManager.enableNetwork(privnet.netId, true, function (ok) {
+ if (ok)
+ self._needToEnableNetworks = true;
+ if (WifiManager.state === "DISCONNECTED" ||
+ WifiManager.state === "SCANNING") {
+ WifiManager.reconnect(function (ok) {
+ self._sendMessage(message, ok, ok, msg);
+ });
+ } else {
+ self._sendMessage(message, ok, ok, msg);
+ }
+ });
+ }
+
+ var selectAndConnectOrReturn = dontConnect ?
+ function() {
+ self._sendMessage(message, true, "Wifi has been recorded", msg);
+ } : selectAndConnect;
+ if (self._highestPriority >= MAX_PRIORITY) {
+ self._reprioritizeNetworks(selectAndConnectOrReturn);
+ } else {
+ WifiManager.saveConfig(selectAndConnectOrReturn);
+ }
+ }
+
+ function connectToNetwork() {
+ WifiManager.updateNetwork(privnet, (function(ok) {
+ if (!ok) {
+ self._sendMessage(message, false, "Network is misconfigured", msg);
+ return;
+ }
+
+ networkReady();
+ }));
+ }
+
+ let ssid = privnet.ssid;
+ let networkKey = getNetworkKey(privnet);
+ let configured;
+
+ if (networkKey in this._addingNetworks) {
+ this._sendMessage(message, false, "Racing associates");
+ return;
+ }
+
+ if (networkKey in this.configuredNetworks)
+ configured = this.configuredNetworks[networkKey];
+
+ netFromDOM(privnet, configured);
+
+ privnet.priority = ++this._highestPriority;
+ if (configured) {
+ privnet.netId = configured.netId;
+ // Sync priority back to configured so if priority reaches MAX_PRIORITY,
+ // it can be sorted correctly in _reprioritizeNetworks() because the
+ // function sort network based on priority in configure list.
+ configured.priority = privnet.priority;
+
+ // When investigating Bug 1123680, we observed that gaia may unexpectedly
+ // request to associate with incorrect password before successfully
+ // forgetting the network. It would cause the network unable to connect
+ // subsequently. Aside from preventing the racing forget/associate, we
+ // also try to disable network prior to updating the network.
+ WifiManager.getNetworkId(dequote(configured.ssid), function(netId) {
+ if (netId) {
+ WifiManager.disableNetwork(netId, function() {
+ connectToNetwork();
+ });
+ }
+ else {
+ connectToNetwork();
+ }
+ });
+ } else {
+ // networkReady, above, calls saveConfig. We want to remember the new
+ // network as being enabled, which isn't the default, so we explicitly
+ // set it to being "enabled" before we add it and save the
+ // configuration.
+ privnet.disabled = 0;
+ this._addingNetworks[networkKey] = privnet;
+ WifiManager.addNetwork(privnet, (function(ok) {
+ delete this._addingNetworks[networkKey];
+
+ if (!ok) {
+ this._sendMessage(message, false, "Network is misconfigured", msg);
+ return;
+ }
+
+ this.configuredNetworks[networkKey] = privnet;
+ networkReady();
+ }).bind(this));
+ }
+ },
+
+ forget: function(msg) {
+ const message = "WifiManager:forget:Return";
+ let network = msg.data;
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ this._reloadConfiguredNetworks((function(ok) {
+ // Give it a chance to remove the network even if reload is failed.
+ if (!ok) {
+ debug("Warning !!! Failed to reload the configured networks");
+ }
+
+ let ssid = network.ssid;
+ let networkKey = getNetworkKey(network);
+ if (!(networkKey in this.configuredNetworks)) {
+ this._sendMessage(message, false, "Trying to forget an unknown network", msg);
+ return;
+ }
+
+ let self = this;
+ let configured = this.configuredNetworks[networkKey];
+ this._reconnectOnDisconnect = (this.currentNetwork &&
+ (this.currentNetwork.ssid === ssid));
+ WifiManager.removeNetwork(configured.netId, function(ok) {
+ if (self._needToEnableNetworks) {
+ self._enableAllNetworks();
+ self._needToEnableNetworks = false;
+ }
+
+ if (!ok) {
+ self._sendMessage(message, false, "Unable to remove the network", msg);
+ self._reconnectOnDisconnect = false;
+ return;
+ }
+
+ WifiManager.saveConfig(function() {
+ self._reloadConfiguredNetworks(function() {
+ self._sendMessage(message, true, true, msg);
+ });
+ });
+ });
+ }).bind(this));
+ },
+
+ wps: function(msg) {
+ const message = "WifiManager:wps:Return";
+ let self = this;
+ let detail = msg.data;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ if (detail.method === "pbc") {
+ WifiManager.wpsPbc(function(ok) {
+ if (ok)
+ self._sendMessage(message, true, true, msg);
+ else
+ self._sendMessage(message, false, "WPS PBC failed", msg);
+ });
+ } else if (detail.method === "pin") {
+ WifiManager.wpsPin(detail, function(pin) {
+ if (pin)
+ self._sendMessage(message, true, pin, msg);
+ else
+ self._sendMessage(message, false, "WPS PIN failed", msg);
+ });
+ } else if (detail.method === "cancel") {
+ WifiManager.wpsCancel(function(ok) {
+ if (ok)
+ self._sendMessage(message, true, true, msg);
+ else
+ self._sendMessage(message, false, "WPS Cancel failed", msg);
+ });
+ } else {
+ self._sendMessage(message, false, "Invalid WPS method=" + detail.method,
+ msg);
+ }
+ },
+
+ setPowerSavingMode: function(msg) {
+ const message = "WifiManager:setPowerSavingMode:Return";
+ let self = this;
+ let enabled = msg.data;
+ let mode = enabled ? "AUTO" : "ACTIVE";
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ // Some wifi drivers may not implement this command. Set power mode
+ // even if suspend optimization command failed.
+ WifiManager.setSuspendOptimizations(enabled, function(ok) {
+ WifiManager.setPowerMode(mode, function(ok) {
+ if (ok) {
+ self._sendMessage(message, true, true, msg);
+ } else {
+ self._sendMessage(message, false, "Set power saving mode failed", msg);
+ }
+ });
+ });
+ },
+
+ setHttpProxy: function(msg) {
+ const message = "WifiManager:setHttpProxy:Return";
+ let self = this;
+ let network = msg.data.network;
+ let info = msg.data.info;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ WifiManager.configureHttpProxy(network, info, function(ok) {
+ if (ok) {
+ // If configured network is current connected network
+ // need update http proxy immediately.
+ let setNetworkKey = getNetworkKey(network);
+ let curNetworkKey = self.currentNetwork ? getNetworkKey(self.currentNetwork) : null;
+ if (setNetworkKey === curNetworkKey)
+ WifiManager.setHttpProxy(network);
+
+ self._sendMessage(message, true, true, msg);
+ } else {
+ self._sendMessage(message, false, "Set http proxy failed", msg);
+ }
+ });
+ },
+
+ setStaticIpMode: function(msg) {
+ const message = "WifiManager:setStaticIpMode:Return";
+ let self = this;
+ let network = msg.data.network;
+ let info = msg.data.info;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ // To compatiable with DHCP returned info structure, do translation here
+ info.ipaddr_str = info.ipaddr;
+ info.proxy_str = info.proxy;
+ info.gateway_str = info.gateway;
+ info.dns1_str = info.dns1;
+ info.dns2_str = info.dns2;
+
+ WifiManager.setStaticIpMode(network, info, function(ok) {
+ if (ok) {
+ self._sendMessage(message, true, true, msg);
+ } else {
+ self._sendMessage(message, false, "Set static ip mode failed", msg);
+ }
+ });
+ },
+
+ importCert: function importCert(msg) {
+ const message = "WifiManager:importCert:Return";
+ let self = this;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ WifiManager.importCert(msg.data, function(data) {
+ if (data.status === 0) {
+ let usageString = ["ServerCert", "UserCert"];
+ let usageArray = [];
+ for (let i = 0; i < usageString.length; i++) {
+ if (data.usageFlag & (0x01 << i)) {
+ usageArray.push(usageString[i]);
+ }
+ }
+
+ self._sendMessage(message, true, {
+ nickname: data.nickname,
+ usage: usageArray
+ }, msg);
+ } else {
+ self._sendMessage(message, false, "Import Cert failed", msg);
+ }
+ });
+ },
+
+ getImportedCerts: function getImportedCerts(msg) {
+ const message = "WifiManager:getImportedCerts:Return";
+ let self = this;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ let certDB = Cc["@mozilla.org/security/x509certdb;1"]
+ .getService(Ci.nsIX509CertDB);
+ if (!certDB) {
+ self._sendMessage(message, false, "Failed to query NSS DB service", msg);
+ }
+
+ let certList = certDB.getCerts();
+ if (!certList) {
+ self._sendMessage(message, false, "Failed to get certificate List", msg);
+ }
+
+ let certListEnum = certList.getEnumerator();
+ if (!certListEnum) {
+ self._sendMessage(message, false, "Failed to get certificate List enumerator", msg);
+ }
+ let importedCerts = {
+ ServerCert: [],
+ UserCert: [],
+ };
+ let UsageMapping = {
+ SERVERCERT: "ServerCert",
+ USERCERT: "UserCert",
+ };
+
+ while (certListEnum.hasMoreElements()) {
+ let certInfo = certListEnum.getNext().QueryInterface(Ci.nsIX509Cert);
+ let certNicknameInfo = /WIFI\_([A-Z]*)\_(.*)/.exec(certInfo.nickname);
+ if (!certNicknameInfo) {
+ continue;
+ }
+ importedCerts[UsageMapping[certNicknameInfo[1]]].push(certNicknameInfo[2]);
+ }
+
+ self._sendMessage(message, true, importedCerts, msg);
+ },
+
+ deleteCert: function deleteCert(msg) {
+ const message = "WifiManager:deleteCert:Return";
+ let self = this;
+
+ if (!WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is disabled", msg);
+ return;
+ }
+
+ WifiManager.deleteCert(msg.data, function(data) {
+ self._sendMessage(message, data.status === 0, "Delete Cert failed", msg);
+ });
+ },
+
+ // TODO : These two variables should be removed once GAIA uses tethering API.
+ useTetheringAPI : false,
+ tetheringConfig : {},
+
+ setWifiTethering: function setWifiTethering(msg) {
+ const message = "WifiManager:setWifiTethering:Return";
+ let self = this;
+ let enabled = msg.data.enabled;
+
+ this.useTetheringAPI = true;
+ this.tetheringConfig = msg.data.config;
+
+ if (WifiManager.enabled) {
+ this._sendMessage(message, false, "Wifi is enabled", msg);
+ return;
+ }
+
+ this.setWifiApEnabled(enabled, function() {
+ if ((enabled && WifiManager.tetheringState == "COMPLETED") ||
+ (!enabled && WifiManager.tetheringState == "UNINITIALIZED")) {
+ self._sendMessage(message, true, msg.data, msg);
+ } else {
+ msg.data.reason = enabled ?
+ "Enable WIFI tethering faild" : "Disable WIFI tethering faild";
+ self._sendMessage(message, false, msg.data, msg);
+ }
+ });
+ },
+
+ // This is a bit ugly, but works. In particular, this depends on the fact
+ // that RadioManager never actually tries to get the worker from us.
+ get worker() { throw "Not implemented"; },
+
+ shutdown: function() {
+ debug("shutting down ...");
+ this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
+ this._setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
+ }.bind(this));
+ },
+
+ // TODO: Remove command queue in Bug 1050147.
+ requestProcessing: false, // Hold while dequeue and execution a request.
+ // Released upon the request is fully executed,
+ // i.e, mostly after callback is done.
+ requestDone: function requestDone() {
+ this.requestProcessing = false;
+ this.nextRequest();
+ },
+
+ nextRequest: function nextRequest() {
+ // No request to process
+ if (this._stateRequests.length === 0) {
+ return;
+ }
+
+ // Handling request, wait for it.
+ if (this.requestProcessing) {
+ return;
+ }
+
+ // Hold processing lock
+ this.requestProcessing = true;
+
+ // Find next valid request
+ let request = this._stateRequests.shift();
+
+ request.callback(request.data);
+ },
+
+ notifyTetheringOn: function notifyTetheringOn() {
+ // It's really sad that we don't have an API to notify the wifi
+ // hotspot status. Toggle settings to let gaia know that wifi hotspot
+ // is enabled.
+ let self = this;
+ this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = true;
+ this._oldWifiTetheringEnabledState = true;
+ gSettingsService.createLock().set(
+ SETTINGS_WIFI_TETHERING_ENABLED,
+ true,
+ {
+ handle: function(aName, aResult) {
+ self.requestDone();
+ },
+ handleError: function(aErrorMessage) {
+ self.requestDone();
+ }
+ });
+ },
+
+ notifyTetheringOff: function notifyTetheringOff() {
+ // It's really sad that we don't have an API to notify the wifi
+ // hotspot status. Toggle settings to let gaia know that wifi hotspot
+ // is disabled.
+ let self = this;
+ this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
+ this._oldWifiTetheringEnabledState = false;
+ gSettingsService.createLock().set(
+ SETTINGS_WIFI_TETHERING_ENABLED,
+ false,
+ {
+ handle: function(aName, aResult) {
+ self.requestDone();
+ },
+ handleError: function(aErrorMessage) {
+ self.requestDone();
+ }
+ });
+ },
+
+ handleWifiEnabled: function(enabled) {
+ if (this.ignoreWifiEnabledFromSettings) {
+ return;
+ }
+
+ // Make sure Wifi hotspot is idle before switching to Wifi mode.
+ if (enabled) {
+ this.queueRequest({command: "setWifiApEnabled", value: false}, function(data) {
+ if (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
+ WifiManager.isWifiTetheringEnabled(WifiManager.tetheringState)) {
+ this.disconnectedByWifi = true;
+ this.setWifiApEnabled(false, this.notifyTetheringOff.bind(this));
+ } else {
+ this.requestDone();
+ }
+ }.bind(this));
+ }
+
+ this.queueRequest({command: "setWifiEnabled", value: enabled}, function(data) {
+ this._setWifiEnabled(enabled, this._setWifiEnabledCallback.bind(this));
+ }.bind(this));
+
+ if (!enabled) {
+ this.queueRequest({command: "setWifiApEnabled", value: true}, function(data) {
+ if (this.disconnectedByWifi) {
+ this.setWifiApEnabled(true, this.notifyTetheringOn.bind(this));
+ } else {
+ this.requestDone();
+ }
+ this.disconnectedByWifi = false;
+ }.bind(this));
+ }
+ },
+
+ handleWifiTetheringEnabled: function(enabled) {
+ // Make sure Wifi is idle before switching to Wifi hotspot mode.
+ if (enabled) {
+ this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
+ if (WifiManager.isWifiEnabled(WifiManager.state)) {
+ this.disconnectedByWifiTethering = true;
+ this._setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
+ } else {
+ this.requestDone();
+ }
+ }.bind(this));
+ }
+
+ this.queueRequest({command: "setWifiApEnabled", value: enabled}, function(data) {
+ this.setWifiApEnabled(enabled, this.requestDone.bind(this));
+ }.bind(this));
+
+ if (!enabled) {
+ this.queueRequest({command: "setWifiEnabled", value: true}, function(data) {
+ if (this.disconnectedByWifiTethering) {
+ this._setWifiEnabled(true, this._setWifiEnabledCallback.bind(this));
+ } else {
+ this.requestDone();
+ }
+ this.disconnectedByWifiTethering = false;
+ }.bind(this));
+ }
+ },
+
+ // nsIObserver implementation
+ observe: function observe(subject, topic, data) {
+ switch (topic) {
+ case kMozSettingsChangedObserverTopic:
+ // To avoid WifiWorker setting the wifi again, don't need to deal with
+ // the "mozsettings-changed" event fired from internal setting.
+ if ("wrappedJSObject" in subject) {
+ subject = subject.wrappedJSObject;
+ }
+ if (subject.isInternalChange) {
+ return;
+ }
+
+ this.handle(subject.key, subject.value);
+ break;
+
+ case "xpcom-shutdown":
+ // Ensure the supplicant is detached from B2G to avoid XPCOM shutdown
+ // blocks forever.
+ WifiManager.ensureSupplicantDetached(() => {
+ let wifiService = Cc["@mozilla.org/wifi/service;1"].getService(Ci.nsIWifiProxyService);
+ wifiService.shutdown();
+ let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"].getService(Ci.nsIWifiCertService);
+ wifiCertService.shutdown();
+ });
+ break;
+ }
+ },
+
+ handle: function handle(aName, aResult) {
+ switch(aName) {
+ // TODO: Remove function call in Bug 1050147.
+ case SETTINGS_WIFI_ENABLED:
+ this.handleWifiEnabled(aResult)
+ break;
+ case SETTINGS_WIFI_DEBUG_ENABLED:
+ if (aResult === null)
+ aResult = false;
+ DEBUG = aResult;
+ updateDebug();
+ break;
+ case SETTINGS_WIFI_TETHERING_ENABLED:
+ this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
+ // Fall through!
+ case SETTINGS_WIFI_SSID:
+ case SETTINGS_WIFI_SECURITY_TYPE:
+ case SETTINGS_WIFI_SECURITY_PASSWORD:
+ case SETTINGS_WIFI_IP:
+ case SETTINGS_WIFI_PREFIX:
+ case SETTINGS_WIFI_DHCPSERVER_STARTIP:
+ case SETTINGS_WIFI_DHCPSERVER_ENDIP:
+ case SETTINGS_WIFI_DNS1:
+ case SETTINGS_WIFI_DNS2:
+ case SETTINGS_USB_DHCPSERVER_STARTIP:
+ case SETTINGS_USB_DHCPSERVER_ENDIP:
+ // TODO: code related to wifi-tethering setting should be removed after GAIA
+ // use tethering API
+ if (this.useTetheringAPI) {
+ break;
+ }
+
+ if (aResult !== null) {
+ this.tetheringSettings[aName] = aResult;
+ }
+ debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
+ let index = this._wifiTetheringSettingsToRead.indexOf(aName);
+
+ if (index != -1) {
+ this._wifiTetheringSettingsToRead.splice(index, 1);
+ }
+
+ if (this._wifiTetheringSettingsToRead.length) {
+ debug("We haven't read completely the wifi Tethering data from settings db.");
+ break;
+ }
+
+ if (this._oldWifiTetheringEnabledState === this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
+ debug("No changes for SETTINGS_WIFI_TETHERING_ENABLED flag. Nothing to do.");
+ break;
+ }
+
+ if (this._oldWifiTetheringEnabledState === null &&
+ !this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
+ debug("Do nothing when initial settings for SETTINGS_WIFI_TETHERING_ENABLED flag is false.");
+ break;
+ }
+
+ this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
+ this.handleWifiTetheringEnabled(aResult)
+ break;
+ };
+ },
+
+ handleError: function handleError(aErrorMessage) {
+ debug("There was an error while reading Tethering settings.");
+ this.tetheringSettings = {};
+ this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiWorker]);
+
+var debug;
+function updateDebug() {
+ if (DEBUG) {
+ debug = function (s) {
+ dump("-*- WifiWorker component: " + s + "\n");
+ };
+ } else {
+ debug = function (s) {};
+ }
+ WifiManager.syncDebug();
+}
+updateDebug();