pjs/dom/wifi/nsWifiWorker.js

1075 строки
33 KiB
JavaScript

/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Telephony.
*
* The Initial Developer of the Original Code is
* The Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Andreas Gal <gal@mozilla.com>
* Blake Kaplan <mrbkap@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const DEBUG = true; // set to false to suppress 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/network_worker.js";
// 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 controlWorker = new ChromeWorker(WIFIWORKER_WORKER);
var eventWorker = new ChromeWorker(WIFIWORKER_WORKER);
// Callbacks to invoke when a reply arrives from the controlWorker.
var controlCallbacks = Object.create(null);
var idgen = 0;
function controlMessage(obj, callback) {
var id = idgen++;
obj.id = id;
if (callback)
controlCallbacks[id] = callback;
controlWorker.postMessage(obj);
}
function onerror(e) {
// It is very important to call preventDefault on the event here.
// If an exception is thrown on the worker, it bubbles out to the
// component that created it. If that component doesn't have an
// onerror handler, the worker will try to call the error reporter
// on the context it was created on. However, That doesn't work
// for component contexts and can result in crashes. This onerror
// handler has to make sure that it calls preventDefault on the
// incoming event.
e.preventDefault();
var worker = (this === controlWorker) ? "control" : "event";
debug("Got an error from the " + worker + " worker: " + e.filename +
":" + e.lineno + ": " + e.message + "\n");
}
controlWorker.onerror = onerror;
eventWorker.onerror = onerror;
controlWorker.onmessage = function(e) {
var data = e.data;
var id = data.id;
var callback = controlCallbacks[id];
if (callback) {
callback(data);
delete controlCallbacks[id];
}
};
// Polling the status worker
var recvErrors = 0;
eventWorker.onmessage = function(e) {
// process the event and tell the event worker to listen for more events
if (handleEvent(e.data.event))
waitForEvent();
};
function waitForEvent() {
eventWorker.postMessage({ cmd: "wait_for_event" });
}
// Commands to the control worker
function voidControlMessage(cmd, callback) {
controlMessage({ cmd: cmd }, function (data) {
callback(data.status);
});
}
function loadDriver(callback) {
voidControlMessage("load_driver", callback);
}
function unloadDriver(callback) {
voidControlMessage("unload_driver", callback);
}
function startSupplicant(callback) {
voidControlMessage("start_supplicant", callback);
}
function stopSupplicant(callback) {
voidControlMessage("stop_supplicant", callback);
}
function connectToSupplicant(callback) {
voidControlMessage("connect_to_supplicant", callback);
}
function closeSupplicantConnection(callback) {
voidControlMessage("close_supplicant_connection", callback);
}
function doCommand(request, callback) {
controlMessage({ cmd: "command", request: request }, callback);
}
function doIntCommand(request, callback) {
doCommand(request, function(data) {
callback(data.status ? -1 : (data.reply|0));
});
}
function doBooleanCommand(request, expected, callback) {
doCommand(request, function(data) {
callback(data.status ? false : (data.reply == expected));
});
}
function doStringCommand(request, callback) {
doCommand(request, function(data) {
callback(data.status ? null : data.reply);
});
}
function listNetworksCommand(callback) {
doStringCommand("LIST_NETWORKS", callback);
}
function addNetworkCommand(callback) {
doIntCommand("ADD_NETWORK", callback);
}
function setNetworkVariableCommand(netId, name, value, callback) {
doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value, "OK", callback);
}
function getNetworkVariableCommand(netId, name, callback) {
doStringCommand("GET_NETWORK " + netId + " " + name, callback);
}
function removeNetworkCommand(netId, callback) {
doBooleanCommand("REMOVE_NETWORK " + netId, callback);
}
function enableNetworkCommand(netId, disableOthers, callback) {
doBooleanCommand((disableOthers ? "SELECT_NETWORK " : "ENABLE_NETWORK ") + netId, "OK", callback);
}
function disableNetworkCommand(netId, callback) {
doBooleanCommand("DISABLE_NETWORK " + netId, "OK", callback);
}
function statusCommand(callback) {
doStringCommand("STATUS", callback);
}
function pingCommand(callback) {
doBooleanCommand("PING", "PONG", callback);
}
function scanResultsCommand(callback) {
doStringCommand("SCAN_RESULTS", callback);
}
function disconnectCommand(callback) {
doBooleanCommand("DISCONNECT", "OK", callback);
}
function reconnectCommand(callback) {
doBooleanCommand("RECONNECT", "OK", callback);
}
function reassociateCommand(callback) {
doBooleanCommand("REASSOCIATE", "OK", callback);
}
var scanModeActive = false;
function doSetScanModeCommand(setActive, callback) {
doBooleanCommand(setActive ? "DRIVER SCAN-ACTIVE" : "DRIVER SCAN-PASSIVE", "OK", callback);
}
function scanCommand(forceActive, callback) {
if (forceActive && !scanModeActive) {
doSetScanModeCommand(true, function(ok) {
ok && doBooleanCommand("SCAN", "OK", function(ok) {
ok && doSetScanModeCommand(false, callback);
});
});
return;
}
doBooleanCommand("SCAN", "OK", callback);
}
function setScanModeCommand(setActive, callback) {
scanModeActive = setActive;
doSetScanModeCommand(setActive, callback);
}
function startDriverCommand(callback) {
doBooleanCommand("DRIVER START", "OK");
}
function stopDriverCommand(callback) {
doBooleanCommand("DRIVER STOP", "OK");
}
function startPacketFiltering(callback) {
doBooleanCommand("DRIVER RXFILTER-ADD 0", "OK", function(ok) {
ok && doBooleanCommand("DRIVER RXFILTER-ADD 1", "OK", function(ok) {
ok && doBooleanCommand("DRIVER RXFILTER-ADD 3", "OK", function(ok) {
ok && doBooleanCommand("DRIVER RXFILTER-START", "OK", callback)
});
});
});
}
function stopPacketFiltering(callback) {
doBooleanCommand("DRIVER RXFILTER-STOP", "OK", function(ok) {
ok && doBooleanCommand("DRIVER RXFILTER-REMOVE 3", "OK", function(ok) {
ok && doBooleanCommand("DRIVER RXFILTER-REMOVE 1", "OK", function(ok) {
ok && doBooleanCommand("DRIVER RXFILTER-REMOVE 0", "OK", callback)
});
});
});
}
function doGetRssiCommand(cmd, callback) {
doCommand(cmd, function(data) {
var rssi = -200;
if (!data.status) {
// If we are associating, the reply is "OK".
var reply = data.reply;
if (reply != "OK") {
// Format is: <SSID> rssi XX". SSID can contain spaces.
var offset = reply.lastIndexOf("rssi ");
if (offset != -1)
rssi = reply.substr(offset + 5) | 0;
}
}
callback(rssi);
});
}
function getRssiCommand(callback) {
doGetRssiCommand("DRIVER RSSI", callback);
}
function getRssiApproxCommand(callback) {
doGetRssiCommand("DRIVER RSSI-APPROX", callback);
}
function getLinkSpeedCommand(callback) {
doStringCommand("DRIVER LINKSPEED", function(reply) {
if (reply)
reply = reply.split()[1] | 0; // Format: LinkSpeed XX
callback(reply);
});
}
function getMacAddressCommand(callback) {
doStringCommand("DRIVER MACADDR", function(reply) {
if (reply)
reply = reply.split(" ")[2]; // Format: Macaddr = XX.XX.XX.XX.XX.XX
callback(reply);
});
}
function setPowerModeCommand(mode, callback) {
doBooleanCommand("DRIVER POWERMODE " + mode, "OK", callback);
}
function getPowerModeCommand(callback) {
doStringCommand("DRIVER GETPOWER", function(reply) {
if (reply)
reply = (reply.split()[2]|0); // Format: powermode = XX
callback(reply);
});
}
function setNumAllowedChannelsCommand(numChannels, callback) {
doBooleanCommand("DRIVER SCAN-CHANNELS " + numChannels, "OK", callback);
}
function getNumAllowedChannelsCommand(callback) {
doStringCommand("DRIVER SCAN-CHANNELS", function(reply) {
if (reply)
reply = (reply.split()[2]|0); // Format: Scan-Channels = X
callback(reply);
});
}
function setBluetoothCoexistenceModeCommand(mode, callback) {
doBooleanCommand("DRIVER BTCOEXMODE " + mode, "OK", callback);
}
function setBluetoothCoexistenceScanModeCommand(mode, callback) {
doBooleanCommand("DRIVER BTCOEXSCAN-" + (mode ? "START" : "STOP"), "OK", callback);
}
function saveConfigCommand(callback) {
// Make sure we never write out a value for AP_SCAN other than 1
doBooleanCommand("AP_SCAN 1", "OK", function(ok) {
doBooleanCommand("SAVE_CONFIG", "OK", callback);
});
}
function reloadConfigCommand(callback) {
doBooleanCommand("RECONFIGURE", "OK", callback);
}
function setScanResultHandlingCommand(mode, callback) {
doBooleanCommand("AP_SCAN " + mode, "OK", callback);
}
function addToBlacklistCommand(bssid, callback) {
doBooleanCommand("BLACKLIST " + bssid, "OK", callback);
}
function clearBlacklistCommand(callback) {
doBooleanCommand("BLACKLIST clear", "OK", callback);
}
function setSuspendOptimizationsCommand(enabled, callback) {
doBooleanCommand("DRIVER SETSUSPENDOPT " + (enabled ? 0 : 1), "OK", callback);
}
function getProperty(key, defaultValue, callback) {
controlMessage({ cmd: "property_get", key: key, defaultValue: defaultValue }, function(data) {
callback(data.status < 0 ? null : data.value);
});
}
function setProperty(key, value, callback) {
controlMessage({ cmd: "property_set", key: key, value: value }, function(data) {
callback(!data.status);
});
}
function enableInterface(ifname, callback) {
controlMessage({ cmd: "ifc_enable", ifname: ifname }, function(data) {
callback(!data.status);
});
}
function disableInterface(ifname, callback) {
controlMessage({ cmd: "ifc_disable", ifname: ifname }, function(data) {
callback(!data.status);
});
}
function addHostRoute(ifname, route, callback) {
controlMessage({ cmd: "ifc_add_host_route", ifname: ifname, route: route }, function(data) {
callback(!data.status);
});
}
function removeHostRoutes(ifname, callback) {
controlMessage({ cmd: "ifc_remove_host_routes", ifname: ifname }, function(data) {
callback(!data.status);
});
}
function setDefaultRoute(ifname, route, callback) {
controlMessage({ cmd: "ifc_set_default_route", ifname: ifname, route: route }, function(data) {
callback(!data.status);
});
}
function getDefaultRoute(ifname, callback) {
controlMessage({ cmd: "ifc_get_default_route", ifname: ifname }, function(data) {
callback(!data.route);
});
}
function removeDefaultRoute(ifname, callback) {
controlMessage({ cmd: "ifc_remove_default_route", ifname: ifname }, function(data) {
callback(!data.status);
});
}
function resetConnections(ifname, callback) {
controlMessage({ cmd: "ifc_reset_connections", ifname: ifname }, function(data) {
callback(!data.status);
});
}
var dhcpInfo = null;
function runDhcp(ifname, callback) {
controlMessage({ cmd: "dhcp_do_request", ifname: ifname }, function(data) {
if (!data.status)
dhcpInfo = data;
callback(data.status ? null : data);
});
}
function stopDhcp(ifname, callback) {
controlMessage({ cmd: "dhcp_stop", ifname: ifname }, function(data) {
if (!data.status)
dhcpInfo = null;
callback(!data.status);
});
}
function releaseDhcpLease(ifname, callback) {
controlMessage({ cmd: "dhcp_release_lease", ifname: ifname }, function(data) {
if (!data.status)
dhcpInfo = null;
callback(!data.status);
});
}
function getDhcpError(callback) {
controlMessage({ cmd: "dhcp_get_errmsg" }, function(data) {
callback(data.error);
});
}
function configureInterface(ifname, ipaddr, mask, gateway, dns1, dns2, callback) {
controlMessage({ cmd: "ifc_configure", ifname: ifname,
ipaddr: ipaddr, mask: mask, gateway: gateway,
dns1: dns1, dns2: dns2}, function(data) {
callback(!data.status);
});
}
function runDhcpRenew(ifname, callback) {
controlMessage({ cmd: "dhcp_do_request", ifname: ifname }, function(data) {
if (!data.status)
dhcpInfo = data;
callback(data.status ? null : data);
});
}
var manager = {};
function notify(eventName, eventObject) {
var handler = manager["on" + eventName];
if (handler) {
if (!eventObject)
eventObject = ({});
handler.call(eventObject);
}
}
function notifyStateChange(fields) {
fields.prevState = manager.state;
manager.state = fields.state;
// If we got disconnected, kill the DHCP client in preparation for
// reconnection.
if (fields.state === "DISCONNECTED" && dhcpInfo)
stopDhcp(manager.ifname, function() {});
notify("statechange", fields);
}
function parseStatus(status, reconnected) {
if (status === null) {
debug("Unable to get wpa supplicant's status");
return;
}
var lines = status.split("\n");
for (let i = 0; i < lines.length; ++i) {
let [key, value] = lines[i].split("=");
if (key === "wpa_state") {
notifyStateChange({ state: value });
if (value === "COMPLETED")
onconnected(reconnected);
}
}
}
// 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;
didConnectSupplicant(false, 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) {
connectToSupplicant(connectCallback);
}, 5000, Ci.nsITimer.TYPE_ONE_SHOT);
return;
}
retryTimer = null;
notify("supplicantlost");
}
manager.start = function() {
// If we reconnected to an already-running supplicant, then manager.state
// will have already been updated to the supplicant's state. Otherwise, we
// started the supplicant ourselves and need to connect.
if (manager.state === "UNINITIALIZED")
connectToSupplicant(connectCallback);
}
function dhcpAfterConnect() {
runDhcp(manager.ifname, function (data) {
if (!data) {
debug("DHCP failed to run");
return;
}
setProperty("net.dns1", ipToString(data.dns1), function(ok) {
if (!ok) {
debug("Unable to set net.dns1");
return;
}
setProperty("net.dns2", ipToString(data.dns2), function(ok) {
if (!ok) {
debug("Unable to set net.dns2");
return;
}
getProperty("net.dnschange", "0", function(value) {
if (value === null) {
debug("Unable to get net.dnschange");
return;
}
setProperty("net.dnschange", String(Number(value) + 1), function(ok) {
if (!ok)
debug("Unable to set net.dnschange");
});
});
});
});
});
}
function onconnected(reconnected) {
if (!reconnected) {
dhcpAfterConnect();
return;
}
// We're in the process of reconnecting to a pre-existing wpa_supplicant.
// Check to see if there was already a DHCP process:
getProperty("init.svc.dhcpcd_" + manager.ifname, "stopped", function(value) {
if (value === "running") {
return;
}
// Some phones use a different property name for the dhcpcd daemon.
getProperty("init.svc.dhcpcd", "stopped", function(value) {
if (value === "running") {
return;
}
dhcpAfterConnect();
});
});
}
var supplicantStatesMap = ["DISCONNECTED", "INACTIVE", "SCANNING", "ASSOCIATING",
"ASSOCIATED", "FOUR_WAY_HANDSHAKE", "GROUP_HANDSHAKE",
"COMPLETED", "DORMANT", "UNINITIALIZED"];
var driverEventMap = { STOPPED: "driverstopped", STARTED: "driverstarted", HANGED: "driverhung" };
// handle events sent to us by the event worker
function handleEvent(event) {
debug("Event coming in: " + event);
if (event.indexOf("CTRL-EVENT-") !== 0) {
debug("Got weird event, possibly not doing anything.");
if (event.indexOf("WPA:") == 0 &&
event.indexOf("pre-shared key may be incorrect") != -1) {
notify("passwordmaybeincorrect");
}
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];
notifyStateChange(fields);
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) {
// If the monitor socket is closed, we have already stopped the
// supplicant and we can stop waiting for more events and
// simply exit here (we don't have to notify).
if (eventData.indexOf("connection closed") !== -1)
return false;
// As long we haven't seen too many recv errors yet, we
// will keep going for a bit longer
if (eventData.indexOf("recv error") !== -1 && ++recvErrors < 10)
return true;
notify("supplicantlost");
return false;
}
if (eventData.indexOf("CTRL-EVENT-DISCONNECTED") === 0) {
notifyStateChange({ state: "DISCONNECTED" });
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 = eventData.split(" ")[4];
var id = eventData.substr(eventData.indexOf("id=")).split(" ")[0];
notifyStateChange({ state: "CONNECTED", BSSID: bssid, id: id });
onconnected(false);
return true;
}
if (eventData.indexOf("CTRL-EVENT-SCAN-RESULTS") === 0) {
debug("Notifying of scan results available");
notify("scanresultsavailable");
return true;
}
// unknown event
return true;
}
const SUPP_PROP = "init.svc.wpa_supplicant";
function killSupplicant(callback) {
// It is interesting to note that this function does exactly what
// wifi_stop_supplicant does. Unforunately, on the Galaxy S2, Samsung
// changed that function in a way that means that it doesn't recognize
// wpa_supplicant as already running. Therefore, we have to roll our own
// version here.
var count = 0;
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
function tick() {
getProperty(SUPP_PROP, "stopped", function (result) {
if (result === null) {
callback();
return;
}
if (result === "stopped" || ++count >= 5) {
// Either we succeeded or ran out of time.
timer = null;
callback();
return;
}
// Else it's still running, continue waiting.
timer.initWithCallback(tick, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
});
}
setProperty("ctl.stop", "wpa_supplicant", tick);
}
function didConnectSupplicant(reconnected, callback) {
waitForEvent();
notify("supplicantconnection");
// Load up the supplicant state.
statusCommand(function(status) {
parseStatus(status, reconnected);
callback();
});
}
function prepareForStartup(callback) {
// First, check to see if there's a wpa_supplicant running that we can
// connect to.
getProperty(SUPP_PROP, "stopped", function (value) {
debug(SUPP_PROP + " was " + value);
if (value !== "running") {
stopDhcp(manager.ifname, function() { callback(false) });
return;
}
debug(SUPP_PROP + " was running, trying to connect");
// It's running, try to reconnect to it.
connectToSupplicant(function (retval) {
if (retval === 0) {
// Successfully reconnected! Don't do anything else.
debug("Successfully connected!");
// It is important that we call parseStatus (in
// didConnectSupplicant) before calling the callback here.
// Otherwise, WifiManager.start will reconnect to it.
didConnectSupplicant(true, function() { callback(true) });
return;
}
debug("Didn't connect, trying other method.");
stopDhcp(manager.ifname, function() {
// Ignore any errors.
killSupplicant(function() { callback(false); });
});
});
});
}
// Initial state
var airplaneMode = false;
manager.state = "UNINITIALIZED";
// Public interface of the wifi service
manager.setWifiEnabled = function(enable, callback) {
var targetState = enable ? "ENABLED" : "DISABLED";
if (enable == targetState)
return true;
if (enable && airplaneMode)
return false;
if (enable) {
// Kill any existing connections if necessary.
getProperty("wifi.interface", "tiwlan0", function (ifname) {
if (!ifname) {
callback(-1);
return;
}
manager.ifname = ifname;
prepareForStartup(function(already_connected) {
if (already_connected) {
callback(0);
return;
}
loadDriver(function (status) {
if (status < 0) {
callback(status);
return;
}
startSupplicant(function (status) {
if (status < 0) {
callback(status);
return;
}
enableInterface(ifname, function (ok) {
callback(ok ? 0 : -1);
});
});
});
});
});
} else {
stopSupplicant(function (status) {
if (ok < 0) {
callback(-1);
return;
}
disableInterface(manager.ifname, function (ok) {
unloadDriver(callback);
});
});
}
}
manager.disconnect = disconnectCommand;
manager.reconnect = reconnectCommand;
manager.reassociate = reassociateCommand;
var networkConfigurationFields = ["ssid", "bssid", "psk", "wep_key0", "wep_key1", "wep_key2", "wep_key3",
"wep_tx_keyidx", "priority", "key_mgmt", "scan_ssid", "disabled"];
manager.getNetworkConfiguration = function(config, callback) {
var netId = config.netId;
var done = 0;
for (var n = 0; n < networkConfigurationFields.length; ++n) {
var fieldName = networkConfigurationFields[n];
getNetworkVariableCommand(netId, fieldName, function(value) {
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) {
var fieldName = networkConfigurationFields[n];
if (!(fieldName in config)) {
++done;
} else {
setNetworkVariableCommand(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) {
listNetworksCommand(function (reply) {
var networks = {};
var done = 0;
var errors = 0;
var lines = reply.split("\n");
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
removeNetworkCommand(netId, function() {
callback(null);
});
} else {
callback(networks);
}
}
});
}
});
}
manager.addNetwork = function(config, callback) {
addNetworkCommand(function (netId) {
config.netId = netId;
manager.setNetworkConfiguration(config, callback);
});
}
manager.updateNetwork = function(config, callback) {
manager.setNetworkConfiguration(config, callback);
}
manager.removeNetwork = function(netId, callback) {
removeNetworkCommand(netId, callback);
}
function ipToString(n) {
return String((n >> 0) & 0xFF) + "." +
((n >> 8) & 0xFF) + "." +
((n >> 16) & 0xFF) + "." +
((n >> 24) & 0xFF);
}
manager.enableNetwork = function(netId, disableOthers, callback) {
enableNetworkCommand(netId, disableOthers, callback);
}
manager.disableNetwork = function(netId, callback) {
disableNetworkCommand(netId, callback);
}
manager.getMacAddress = getMacAddressCommand;
manager.getScanResults = scanResultsCommand;
manager.setScanMode = function(mode, callback) {
setScanModeCommand(mode === "active", callback);
}
manager.scan = scanCommand;
return manager;
})();
function WifiNetwork(ssid, bssid, flags, signal) {
this.ssid = ssid;
this.bssid = bssid;
this.flags = flags;
this.signal = Number(signal);
}
WifiNetwork.prototype.QueryInterface = XPCOMUtils.generateQI([Ci.nsIWifiNetwork]);
function nsWifiWorker() {
WifiManager.onsupplicantconnection = function() {
debug("Connected to supplicant");
WifiManager.getMacAddress(function (mac) {
debug("Got mac: " + mac);
});
connectToMozilla();
}
WifiManager.onsupplicantlost = function() {
debug("Couldn't connect to supplicant");
}
var self = this;
this.networks = Object.create(null);
WifiManager.onstatechange = function() {
debug("State change: " + this.prevState + " -> " + this.state);
}
function connectToMozilla() {
// We're not trying to connect so try to find an open Mozilla network.
// TODO Don't do this for pre-existing networks.
// TODO Remove me in favor of UI and a way to select a network.
debug("Haven't connected to a network, trying a default (for now)");
var configs = [
{ "ssid": '"mozilla demo"', "key_mgmt": "NONE", "scan_ssid": 1, "disabled": 0 },
{ "ssid": '"Mozilla"', "key_mgmt": "NONE", "disabled": 0 },
{ "ssid": '"Mozilla Guest"', "key_mgmt": "NONE", "scan_ssid": 1, "disabled": 0 },
];
var i = 0;
function addThem() {
WifiManager.addNetwork(configs[i++], function(ok) {
if (!ok) {
debug("Unable to add the network!");
return;
}
if (i < configs.length) {
addThem();
return;
}
// Some drivers might not automatically start scanning. In that case,
// we need to give them a hint.
WifiManager.scan(false, function(){});
});
}
addThem();
}
WifiManager.onscanresultsavailable = function() {
debug("Scan results are available! Asking for them.");
WifiManager.getScanResults(function(r) {
// 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.
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]);
// TODO Choose bssid based on strength?
if (match && match[5])
self.networks[match[5]] = new WifiNetwork(match[5], match[1], match[4], match[3]);
else if (!match)
debug("Match didn't find anything for: " + lines[i]);
}
if (self.wantScanResults) {
self.wantScanResults();
}
});
}
WifiManager.setWifiEnabled(true, function (ok) {
if (ok === 0)
WifiManager.start();
else
debug("Couldn't start Wifi");
});
debug("Wifi starting");
}
nsWifiWorker.prototype = {
classID: WIFIWORKER_CID,
classInfo: XPCOMUtils.generateCI({classID: WIFIWORKER_CID,
contractID: WIFIWORKER_CONTRACTID,
classDescription: "WifiWorker",
interfaces: [Ci.nsIWorkerHolder,
Ci.nsIWifi]}),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
Ci.nsIWifi]),
// Internal methods.
waitForScan: function(callback) {
if (this.wantScanResults) {
var older = this.wantScanResults;
this.wantScanResults = function() { callback(); older(); };
} else {
this.wantScanResults = callback;
}
},
// nsIWifi
setWifiEnabled: function(enable) {
WifiManager.setWifiEnabled(enable, function (ok) {
debug(ok);
});
},
// 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() {
this.setWifiEnabled(false);
}
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([nsWifiWorker]);
let debug;
if (DEBUG) {
debug = function (s) {
dump("-*- nsWifiWorker component: " + s + "\n");
};
} else {
debug = function (s) {};
}