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