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