diff options
Diffstat (limited to 'dom/wifi/WifiWorker.js')
-rw-r--r-- | dom/wifi/WifiWorker.js | 3928 |
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(); |