зеркало из https://github.com/mozilla/pjs.git
1075 строки
33 KiB
JavaScript
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) {};
|
|
}
|