gecko-dev/dom/wifi/WifiWorker.js

3206 lines
104 KiB
JavaScript

/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/systemlibs.js");
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 kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
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";
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");
// 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",
ifname: libcutils.property_get("wifi.interface")
};
}
let {sdkVersion, unloadDriverEnabled, schedScanRecovery, driverDelay, p2pSupported, ifname} = getStartupPrefs();
let wifiListener = {
onWaitEvent: function(event, iface) {
if (manager.ifname === iface && handleEvent(event)) {
waitForEvent(iface);
} else if (p2pSupported) {
if (WifiP2pManager.INTERFACE_NAME === iface) {
// 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;
// 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);
// Wifi P2P stuff
var p2pManager;
if (p2pSupported) {
let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME);
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 ((networkKey in httpProxyConfig) ? httpProxyConfig : null);
}
function setHttpProxy(network) {
if (!network)
return;
gNetworkService.setNetworkProxy(network);
}
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 = stringToIp(info.ipaddr_str);
info.gateway = stringToIp(info.gateway_str);
info.mask_str = makeMask(info.maskLength);
// Optional
info.dns1 = stringToIp("dns1_str" in info ? info.dns1_str : "");
info.dns2 = stringToIp("dns2_str" in info ? info.dns2_str : "");
info.proxy = stringToIp("proxy_str" in info ? 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 iterface
// 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.
netUtil.disableInterface(manager.ifname, function (ok) {
netUtil.enableInterface(manager.ifname, function (ok) {
});
});
}
});
}
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
netUtil.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;
didConnectSupplicant(function(){});
return;
}
if (connectTries++ < 3) {
// Try again in 5 seconds.
if (!retryTimer)
retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
retryTimer.initWithCallback(function(timer) {
wifiCommand.connectToSupplicant(connectCallback);
}, 5000, Ci.nsITimer.TYPE_ONE_SHOT);
return;
}
retryTimer = null;
connectTries = 0;
notify("supplicantlost", { success: false });
}
manager.connectionDropped = function(callback) {
// Reset network interface when connection drop
netUtil.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.
netUtil.resetConnections(manager.ifname, function() {
netUtil.stopDhcp(manager.ifname, function() {
callback();
});
});
}
manager.start = function() {
debug("detected SDK version " + sdkVersion);
wifiCommand.connectToSupplicant(connectCallback);
}
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, function(data) {
dhcpInfo = data.info;
if (!dhcpInfo) {
if (++manager.dhcpFailuresCount >= MAX_RETRIES_ON_DHCP_FAILURE) {
manager.dhcpFailuresCount = 0;
notify("disconnected", {ssid: manager.connectionInfo.ssid});
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.getCurrentNetworkId = 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. CURRENT network.
// 2. Trying to associate with SSID 'ssid' event
if (network.status === "CURRENT" ||
(ssid && ssid === dequote(network.ssid))) {
return callback(net);
}
}
callback(null);
});
}
// 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", {ssid: manager.connectionInfo.ssid});
}
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 (eventData.indexOf("connection closed") === -1 &&
eventData.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 });
}
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", {ssid: manager.connectionInfo.ssid});
}
manager.connectionInfo.bssid = null;
manager.connectionInfo.ssid = null;
manager.connectionInfo.id = -1;
return true;
}
// Association reject is triggered mostly on incorrect WEP key.
if (eventData.indexOf("CTRL-EVENT-ASSOC-REJECT") === 0) {
notify("passwordmaybeincorrect");
if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
manager.authenticationFailuresCount = 0;
notify("disconnected", {ssid: manager.connectionInfo.ssid});
}
return true;
}
if (eventData.indexOf("CTRL-EVENT-EAP-FAILURE") === 0) {
if (event.indexOf("EAP authentication failed") !== -1) {
notify("passwordmaybeincorrect");
}
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("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 didConnectSupplicant(callback) {
waitForEvent(manager.ifname);
// Load up the supplicant state.
getDebugEnabled(function(ok) {
syncDebug();
});
wifiCommand.status(function(status) {
parseStatus(status);
notify("supplicantconnection");
callback();
});
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() {
netUtil.disableInterface(manager.ifname, function (ok) {
suppressEvents = false;
callback();
});
});
}
}
// Initial state.
manager.state = "UNINITIALIZED";
manager.tetheringState = "UNINITIALIZED";
manager.enabled = false;
manager.supplicantStarted = false;
manager.connectionInfo = { ssid: null, bssid: null, id: -1 };
manager.authenticationFailuresCount = 0;
manager.loopDetectionCount = 0;
manager.dhcpFailuresCount = 0;
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(enable, callback) {
if (enable === manager.enabled) {
callback("no change");
return;
}
if (enable) {
manager.state = "INITIALIZING";
// Register as network interface.
WifiNetworkInterface.name = manager.ifname;
if (!WifiNetworkInterface.registered) {
gNetworkManager.registerNetworkInterface(WifiNetworkInterface);
WifiNetworkInterface.registered = true;
}
WifiNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
WifiNetworkInterface.ip = null;
WifiNetworkInterface.netmask = null;
WifiNetworkInterface.broadcast = null;
WifiNetworkInterface.gateway = null;
WifiNetworkInterface.dns1 = null;
WifiNetworkInterface.dns2 = null;
Services.obs.notifyObservers(WifiNetworkInterface,
kNetworkInterfaceStateChangedTopic,
null);
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 doStartSupplicant() {
cancelWaitForDriverReadyTimer();
wifiCommand.startSupplicant(function (status) {
if (status < 0) {
unloadDriver(WIFI_FIRMWARE_STATION, function() {
callback(status);
});
manager.state = "UNINITIALIZED";
return;
}
manager.supplicantStarted = true;
netUtil.enableInterface(manager.ifname, function (ok) {
callback(ok ? 0 : -1);
});
});
}
// 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 {
// 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.state = "DISABLING";
wifiCommand.terminateSupplicant(function (ok) {
manager.connectionDropped(function () {
wifiCommand.stopSupplicant(function (status) {
wifiCommand.closeSupplicantConnection(function () {
manager.state = "UNINITIALIZED";
netUtil.disableInterface(manager.ifname, function (ok) {
unloadDriver(WIFI_FIRMWARE_STATION, callback);
});
});
});
});
});
}
if (p2pSupported) {
p2pManager.setEnabled(false, { onDisabled: doDisableWifi });
} else {
doDisableWifi();
}
}
}
// Get wifi interface and load wifi driver when enable Ap mode.
manager.setWifiApEnabled = function(enabled, configuration, callback) {
if (enabled) {
manager.tetheringState = "INITIALIZING";
loadDriver(function (status) {
if (status < 0) {
callback();
manager.tetheringState = "UNINITIALIZED";
return;
}
function doStartWifiTethering() {
cancelWaitForDriverReadyTimer();
WifiNetworkInterface.name = manager.ifname;
gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
configuration, function(result) {
if (result) {
manager.tetheringState = "UNINITIALIZED";
} else {
manager.tetheringState = "COMPLETED";
}
// 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 {
gNetworkManager.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 = [
"ssid", "bssid", "psk", "wep_key0", "wep_key1", "wep_key2", "wep_key3",
"wep_tx_keyidx", "priority", "key_mgmt", "scan_ssid", "disabled",
"identity", "password", "auth_alg", "phase1", "phase2", "eap", "pin",
"pcsc"
];
manager.getNetworkConfiguration = function(config, callback) {
var netId = config.netId;
var done = 0;
for (var n = 0; n < networkConfigurationFields.length; ++n) {
let fieldName = networkConfigurationFields[n];
wifiCommand.getNetworkVariable(netId, fieldName, function(value) {
if (value !== null)
config[fieldName] = value;
if (++done == networkConfigurationFields.length)
callback(config);
});
}
}
manager.setNetworkConfiguration = function(config, callback) {
var netId = config.netId;
var done = 0;
var errors = 0;
for (var n = 0; n < networkConfigurationFields.length; ++n) {
let fieldName = networkConfigurationFields[n];
if (!(fieldName in config) ||
// These fields are special: We can't retrieve them from the
// supplicant, and often we have a star in our config. In that case,
// we need to avoid overwriting the correct password with a *.
(fieldName === "password" ||
fieldName === "wep_key0" ||
fieldName === "psk") &&
config[fieldName] === '*') {
++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 = result[0];
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);
}
function stringToIp(string) {
let ip = 0;
let start, end = -1;
for (let i = 0; i < 4; i++) {
start = end + 1;
end = string.indexOf(".", start);
if (end == -1) {
end = string.length;
}
let num = parseInt(string.slice(start, end), 10);
if (isNaN(num)) {
return 0;
}
ip |= num << (i * 8);
}
return ip;
}
function swap32(n) {
return (((n >> 24) & 0xFF) << 0) |
(((n >> 16) & 0xFF) << 8) |
(((n >> 8) & 0xFF) << 16) |
(((n >> 0) & 0xFF) << 24);
}
function ntohl(n) {
return swap32(n);
}
function makeMask(len) {
let mask = 0;
for (let i = 0; i < len; ++i) {
mask |= (0x80000000 >> i);
}
return ntohl(mask);
}
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.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.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.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", {ssid: manager.connectionInfo.ssid});
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() {
wifiService.waitForEvent(WifiP2pManager.INTERFACE_NAME);
},
onEnabled: function(success) {
callback(success);
}
});
};
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 == "WPA-EAP") {
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 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, security, password, capabilities) {
this.ssid = ssid;
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",
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"
};
// Note: We never use ScanResult.prototype, so the fact that it's unrelated to
// Network.prototype is OK.
function ScanResult(ssid, bssid, flags, signal) {
Network.call(this, ssid, 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);
}
let WifiNetworkInterface = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
registered: false,
// nsINetworkInterface
NETWORK_STATE_UNKNOWN: Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN,
NETWORK_STATE_CONNECTING: Ci.nsINetworkInterface.CONNECTING,
NETWORK_STATE_CONNECTED: Ci.nsINetworkInterface.CONNECTED,
NETWORK_STATE_DISCONNECTING: Ci.nsINetworkInterface.DISCONNECTING,
NETWORK_STATE_DISCONNECTED: Ci.nsINetworkInterface.DISCONNECTED,
state: Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN,
NETWORK_TYPE_WIFI: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
NETWORK_TYPE_MOBILE: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
NETWORK_TYPE_MOBILE_MMS: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS,
NETWORK_TYPE_MOBILE_SUPL: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL,
type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
name: null,
ip: null,
prefixLength: 0,
broadcast: null,
dns1: null,
dns2: null,
httpProxyHost: null,
httpProxyPort: null,
};
function WifiScanResult() {}
// TODO Make the difference between a DOM-based network object and our
// networks objects much clearer.
let netToDOM;
let 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",
"child-process-shutdown"];
messages.forEach((function(msgName) {
this._mm.addMessageListener(msgName, this);
}).bind(this));
Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
this.wantScanResults = [];
this._allowWpaEap = false;
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) {
var ssid = dequote(net.ssid);
var security = (net.key_mgmt === "NONE" && net.wep_key0) ? ["WEP"] :
(net.key_mgmt && net.key_mgmt !== "NONE") ? [net.key_mgmt] :
[];
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, security, password);
if (net.identity)
pub.identity = dequote(net.identity);
if (net.netId)
pub.known = true;
if (net.scan_ssid === 1)
pub.hidden = true;
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";
}
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";
}
if ("pin" in net) {
net.pin = quote(net.pin);
}
if ("phase1" in net)
net.phase1 = quote(net.phase1);
if ("phase2" in net)
net.phase2 = quote(net.phase2);
return net;
};
WifiManager.onsupplicantconnection = function() {
debug("Connected to supplicant");
WifiManager.enabled = true;
self._reloadConfiguredNetworks(function(ok) {
// Prime this.networks.
if (!ok)
return;
self.waitForScan(function firstScan() {});
// 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() {})
});
try {
self._allowWpaEap = Services.prefs.getBoolPref("b2g.wifi.allow_unsafe_wpa_eap");
} catch (e) {
self._allowWpaEap = false;
}
// 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 });
});
if (WifiManager.state === "SCANNING")
startScanStuckTimer();
};
WifiManager.onsupplicantlost = function() {
WifiManager.enabled = WifiManager.supplicantStarted = false;
WifiManager.state = "UNINITIALIZED";
debug("Supplicant died!");
// Notify everybody, even if they didn't ask us to come up.
self._fireEvent("wifiDown", {});
};
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;
}
WifiManager.getCurrentNetworkId(this.ssid, function(netId) {
// Trying to get netId from current network.
if (!netId &&
self.currentNetwork &&
typeof self.currentNetwork.netId !== "undefined") {
netId = self.currentNetwork.netId;
}
if (netId) {
WifiManager.disableNetwork(netId, function() {});
}
});
self._fireEvent("onconnectingfailed", {network: 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.
self.currentNetwork =
{ bssid: WifiManager.connectionInfo.bssid,
ssid: quote(WifiManager.connectionInfo.ssid) };
self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
break;
case "ASSOCIATED":
if (!self.currentNetwork) {
self.currentNetwork =
{ bssid: WifiManager.connectionInfo.bssid,
ssid: quote(WifiManager.connectionInfo.ssid) };
}
self.currentNetwork.netId = this.id;
WifiManager.getNetworkConfiguration(self.currentNetwork, function (){});
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;
}
// 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 = { ssid: quote(WifiManager.connectionInfo.ssid),
netId: WifiManager.connectionInfo.id };
WifiManager.getNetworkConfiguration(self.currentNetwork, function(){});
}
// Update http proxy when connected to network.
let netConnect = WifiManager.getHttpProxyNetwork(self.currentNetwork);
if (netConnect)
WifiManager.setHttpProxy(netConnect);
// The full authentication process is completed, reset the count.
WifiManager.authenticationFailuresCount = 0;
WifiManager.loopDetectionCount = 0;
self._startConnectionInfoTimer();
self._fireEvent("onassociate", { network: netToDOM(self.currentNetwork) });
break;
case "CONNECTED":
// 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 (this.prevState === "INITIALIZING" ||
this.prevState === "DISCONNECTED" ||
this.prevState === "INTERFACE_DISABLED" ||
this.prevState === "INACTIVE" ||
this.prevState === "UNINITIALIZED") {
return;
}
self._fireEvent("ondisconnect", {});
// When disconnected, clear the http proxy setting if it exists.
// Temporarily set http proxy to empty and restore user setting after setHttpProxy.
let netDisconnect = WifiManager.getHttpProxyNetwork(self.currentNetwork);
if (netDisconnect) {
let prehttpProxyHostSetting = netDisconnect.httpProxyHost;
let prehttpProxyPortSetting = netDisconnect.httpProxyPort;
netDisconnect.httpProxyHost = "";
netDisconnect.httpProxyPort = 0;
WifiManager.setHttpProxy(netDisconnect);
netDisconnect.httpProxyHost = prehttpProxyHostSetting;
netDisconnect.httpProxyPort = prehttpProxyPortSetting;
}
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.state =
Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
WifiNetworkInterface.ip = null;
WifiNetworkInterface.prefixLength = 0;
WifiNetworkInterface.broadcast = null;
WifiNetworkInterface.gateway = null;
WifiNetworkInterface.dns1 = null;
WifiNetworkInterface.dns2 = null;
Services.obs.notifyObservers(WifiNetworkInterface,
kNetworkInterfaceStateChangedTopic,
null);
break;
case "WPS_TIMEOUT":
self._fireEvent("onwpstimeout", {});
break;
case "WPS_FAIL":
self._fireEvent("onwpsfail", {});
break;
case "WPS_OVERLAP_DETECTED":
self._fireEvent("onwpsoverlap", {});
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) {
debug("Network information is invalid.");
return;
}
let maskLength =
netHelpers.getMaskLength(netHelpers.stringToIP(this.info.mask_str));
WifiNetworkInterface.state =
Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
WifiNetworkInterface.ip = this.info.ipaddr_str;
WifiNetworkInterface.prefixLength = maskLength;
WifiNetworkInterface.broadcast = this.info.broadcast_str;
WifiNetworkInterface.gateway = this.info.gateway_str;
WifiNetworkInterface.dns1 = this.info.dns1_str;
WifiNetworkInterface.dns2 = this.info.dns2_str;
Services.obs.notifyObservers(WifiNetworkInterface,
kNetworkInterfaceStateChangedTopic,
null);
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;
}
// 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],
signalLevel = match[3],
flags = match[4];
// Skip ad-hoc networks which aren't supported (bug 811635).
if (flags.indexOf("[IBSS]") >= 0)
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, 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 = "*";
}
} else if (!self._allowWpaEap &&
(eapIndex = network.security.indexOf("WPA-EAP")) >= 0) {
// Don't offer to connect to WPA-EAP networks unless one has been
// configured through other means (e.g. it was added directly to
// wpa_supplicant.conf). Here, we have an unknown WPA-EAP network,
// so we ignore it entirely if it only supports WPA-EAP, otherwise
// we take EAP out of the list and offer the rest of the
// security.
if (network.security.length === 1)
continue;
network.security.splice(eapIndex, 1);
}
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 = [];
});
};
// 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.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 each (let net in this.configuredNetworks) {
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) ((percent / 10) | 0)
if (last && last.linkSpeed === info.linkSpeed &&
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];
if (!network.ssid) {
delete networks[net]; // TODO support these?
continue;
}
if (network.priority && network.priority > this._highestPriority)
this._highestPriority = network.priority;
let networkKey = getNetworkKey(network);
networks[networkKey] = network;
delete networks[net];
}
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) {
msg.manager.sendAsyncMessage(message + (success ? ":OK" : ":NO"),
{ data: data, rid: msg.rid, mid: msg.mid });
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);
});
},
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);
}
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: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: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 };
}
}
},
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;
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;
this.wantScanResults.splice(this.wantScanResults.indexOf(waitForScanCallback), 1);
callback.onfailure();
return;
}
// Else it's still running, continue waiting.
timer.initWithCallback(doScan, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
return;
}
});
}
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 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();
this.requestDone();
},
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.
queueRequest: function(enabled, callback) {
if (!callback) {
throw "Try to enqueue a request without callback";
}
this._stateRequests.push({
enabled: enabled,
callback: callback
});
this.nextRequest();
},
getWifiTetheringParameters: function getWifiTetheringParameters(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;
}
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);
}
}
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;
WifiManager.updateNetwork(privnet, (function(ok) {
if (!ok) {
this._sendMessage(message, false, "Network is misconfigured", msg);
return;
}
networkReady();
}).bind(this));
} 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 (!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 (detail.method === "pbc") {
WifiManager.wpsPbc(WifiManager.ifname, 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";
// 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;
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:setStaticMode:Return";
let self = this;
let network = msg.data.network;
let info = msg.data.info;
// 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);
}
});
},
// 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(false, function(data) {
this.setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
}.bind(this));
},
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.enabled);
},
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();
}
},
"fromInternalSetting");
},
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();
}
},
"fromInternalSetting");
},
handleWifiEnabled: function(enabled) {
if (WifiManager.enabled === enabled) {
return;
}
// Make sure Wifi hotspot is idle before switching to Wifi mode.
if (enabled) {
this.queueRequest(false, function(data) {
if (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
WifiManager.tetheringState != "UNINITIALIZED") {
this.disconnectedByWifi = true;
this.setWifiApEnabled(false, this.notifyTetheringOff.bind(this));
} else {
this.requestDone();
}
}.bind(this));
}
this.queueRequest(enabled, function(data) {
this.setWifiEnabled(enabled, this._setWifiEnabledCallback.bind(this));
}.bind(this));
if (!enabled) {
this.queueRequest(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(false, function(data) {
if (WifiManager.enabled || WifiManager.state != "UNINITIALIZED") {
this.disconnectedByWifiTethering = true;
this.setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
} else {
this.requestDone();
}
}.bind(this));
}
this.queueRequest(enabled, function(data) {
this.setWifiApEnabled(data, this.requestDone.bind(this));
}.bind(this));
if (!enabled) {
this.queueRequest(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) {
// Note that this function gets called for any and all settings changes,
// so we need to carefully check if we have the one we're interested in.
// The string we're interested in will be a JSON string that looks like:
// {"key":"wifi.enabled","value":"true"}.
if (topic !== kMozSettingsChangedObserverTopic) {
return;
}
let setting = JSON.parse(data);
// To avoid WifiWorker setting the wifi again, don't need to deal with
// the "mozsettings-changed" event fired from internal setting.
if (setting.message && setting.message === "fromInternalSetting") {
return;
}
this.handle(setting.key, setting.value);
},
handle: function handle(aName, aResult) {
switch(aName) {
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:
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]);
let debug;
function updateDebug() {
if (DEBUG) {
debug = function (s) {
dump("-*- WifiWorker component: " + s + "\n");
};
} else {
debug = function (s) {};
}
WifiManager.syncDebug();
}
updateDebug();