1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/wifi/WifiCommand.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,524 @@ 1.4 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +"use strict"; 1.11 + 1.12 +this.EXPORTED_SYMBOLS = ["WifiCommand"]; 1.13 + 1.14 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 1.15 + 1.16 +Cu.import("resource://gre/modules/systemlibs.js"); 1.17 + 1.18 +const SUPP_PROP = "init.svc.wpa_supplicant"; 1.19 +const WPA_SUPPLICANT = "wpa_supplicant"; 1.20 +const DEBUG = false; 1.21 + 1.22 +this.WifiCommand = function(aControlMessage, aInterface) { 1.23 + function debug(msg) { 1.24 + if (DEBUG) { 1.25 + dump('-------------- WifiCommand: ' + msg); 1.26 + } 1.27 + } 1.28 + 1.29 + var command = {}; 1.30 + 1.31 + //------------------------------------------------- 1.32 + // General commands. 1.33 + //------------------------------------------------- 1.34 + 1.35 + command.loadDriver = function (callback) { 1.36 + voidControlMessage("load_driver", function(status) { 1.37 + callback(status); 1.38 + }); 1.39 + }; 1.40 + 1.41 + command.unloadDriver = function (callback) { 1.42 + voidControlMessage("unload_driver", function(status) { 1.43 + callback(status); 1.44 + }); 1.45 + }; 1.46 + 1.47 + command.startSupplicant = function (callback) { 1.48 + voidControlMessage("start_supplicant", callback); 1.49 + }; 1.50 + 1.51 + command.killSupplicant = function (callback) { 1.52 + // It is interesting to note that this function does exactly what 1.53 + // wifi_stop_supplicant does. Unforunately, on the Galaxy S2, Samsung 1.54 + // changed that function in a way that means that it doesn't recognize 1.55 + // wpa_supplicant as already running. Therefore, we have to roll our own 1.56 + // version here. 1.57 + stopProcess(SUPP_PROP, WPA_SUPPLICANT, callback); 1.58 + }; 1.59 + 1.60 + command.terminateSupplicant = function (callback) { 1.61 + doBooleanCommand("TERMINATE", "OK", callback); 1.62 + }; 1.63 + 1.64 + command.stopSupplicant = function (callback) { 1.65 + voidControlMessage("stop_supplicant", callback); 1.66 + }; 1.67 + 1.68 + command.listNetworks = function (callback) { 1.69 + doStringCommand("LIST_NETWORKS", callback); 1.70 + }; 1.71 + 1.72 + command.addNetwork = function (callback) { 1.73 + doIntCommand("ADD_NETWORK", callback); 1.74 + }; 1.75 + 1.76 + command.setNetworkVariable = function (netId, name, value, callback) { 1.77 + doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + 1.78 + value, "OK", callback); 1.79 + }; 1.80 + 1.81 + command.getNetworkVariable = function (netId, name, callback) { 1.82 + doStringCommand("GET_NETWORK " + netId + " " + name, callback); 1.83 + }; 1.84 + 1.85 + command.removeNetwork = function (netId, callback) { 1.86 + doBooleanCommand("REMOVE_NETWORK " + netId, "OK", callback); 1.87 + }; 1.88 + 1.89 + command.enableNetwork = function (netId, disableOthers, callback) { 1.90 + doBooleanCommand((disableOthers ? "SELECT_NETWORK " : "ENABLE_NETWORK ") + 1.91 + netId, "OK", callback); 1.92 + }; 1.93 + 1.94 + command.disableNetwork = function (netId, callback) { 1.95 + doBooleanCommand("DISABLE_NETWORK " + netId, "OK", callback); 1.96 + }; 1.97 + 1.98 + command.status = function (callback) { 1.99 + doStringCommand("STATUS", callback); 1.100 + }; 1.101 + 1.102 + command.ping = function (callback) { 1.103 + doBooleanCommand("PING", "PONG", callback); 1.104 + }; 1.105 + 1.106 + command.scanResults = function (callback) { 1.107 + doStringCommand("SCAN_RESULTS", callback); 1.108 + }; 1.109 + 1.110 + command.disconnect = function (callback) { 1.111 + doBooleanCommand("DISCONNECT", "OK", callback); 1.112 + }; 1.113 + 1.114 + command.reconnect = function (callback) { 1.115 + doBooleanCommand("RECONNECT", "OK", callback); 1.116 + }; 1.117 + 1.118 + command.reassociate = function (callback) { 1.119 + doBooleanCommand("REASSOCIATE", "OK", callback); 1.120 + }; 1.121 + 1.122 + command.setBackgroundScan = function (enable, callback) { 1.123 + doBooleanCommand("SET pno " + (enable ? "1" : "0"), 1.124 + "OK", 1.125 + function(ok) { 1.126 + callback(true, ok); 1.127 + }); 1.128 + }; 1.129 + 1.130 + command.doSetScanMode = function (setActive, callback) { 1.131 + doBooleanCommand(setActive ? 1.132 + "DRIVER SCAN-ACTIVE" : 1.133 + "DRIVER SCAN-PASSIVE", "OK", callback); 1.134 + }; 1.135 + 1.136 + command.scan = function (callback) { 1.137 + doBooleanCommand("SCAN", "OK", callback); 1.138 + }; 1.139 + 1.140 + command.setLogLevel = function (level, callback) { 1.141 + doBooleanCommand("LOG_LEVEL " + level, "OK", callback); 1.142 + }; 1.143 + 1.144 + command.getLogLevel = function (callback) { 1.145 + doStringCommand("LOG_LEVEL", callback); 1.146 + }; 1.147 + 1.148 + command.wpsPbc = function (iface, callback) { 1.149 + doBooleanCommand("WPS_PBC" + (iface ? (" interface=" + iface) : ""), 1.150 + "OK", callback); 1.151 + }; 1.152 + 1.153 + command.wpsPin = function (detail, callback) { 1.154 + doStringCommand("WPS_PIN " + 1.155 + (detail.bssid === undefined ? "any" : detail.bssid) + 1.156 + (detail.pin === undefined ? "" : (" " + detail.pin)) + 1.157 + (detail.iface ? (" interface=" + detail.iface) : ""), 1.158 + callback); 1.159 + }; 1.160 + 1.161 + command.wpsCancel = function (callback) { 1.162 + doBooleanCommand("WPS_CANCEL", "OK", callback); 1.163 + }; 1.164 + 1.165 + command.startDriver = function (callback) { 1.166 + doBooleanCommand("DRIVER START", "OK"); 1.167 + }; 1.168 + 1.169 + command.stopDriver = function (callback) { 1.170 + doBooleanCommand("DRIVER STOP", "OK"); 1.171 + }; 1.172 + 1.173 + command.startPacketFiltering = function (callback) { 1.174 + var commandChain = ["DRIVER RXFILTER-ADD 0", 1.175 + "DRIVER RXFILTER-ADD 1", 1.176 + "DRIVER RXFILTER-ADD 3", 1.177 + "DRIVER RXFILTER-START"]; 1.178 + 1.179 + doBooleanCommandChain(commandChain, callback); 1.180 + }; 1.181 + 1.182 + command.stopPacketFiltering = function (callback) { 1.183 + var commandChain = ["DRIVER RXFILTER-STOP", 1.184 + "DRIVER RXFILTER-REMOVE 3", 1.185 + "DRIVER RXFILTER-REMOVE 1", 1.186 + "DRIVER RXFILTER-REMOVE 0"]; 1.187 + 1.188 + doBooleanCommandChain(commandChain, callback); 1.189 + }; 1.190 + 1.191 + command.doGetRssi = function (cmd, callback) { 1.192 + doCommand(cmd, function(data) { 1.193 + var rssi = -200; 1.194 + 1.195 + if (!data.status) { 1.196 + // If we are associating, the reply is "OK". 1.197 + var reply = data.reply; 1.198 + if (reply !== "OK") { 1.199 + // Format is: <SSID> rssi XX". SSID can contain spaces. 1.200 + var offset = reply.lastIndexOf("rssi "); 1.201 + if (offset !== -1) { 1.202 + rssi = reply.substr(offset + 5) | 0; 1.203 + } 1.204 + } 1.205 + } 1.206 + callback(rssi); 1.207 + }); 1.208 + }; 1.209 + 1.210 + command.getRssi = function (callback) { 1.211 + command.doGetRssi("DRIVER RSSI", callback); 1.212 + }; 1.213 + 1.214 + command.getRssiApprox = function (callback) { 1.215 + command.doGetRssi("DRIVER RSSI-APPROX", callback); 1.216 + }; 1.217 + 1.218 + command.getLinkSpeed = function (callback) { 1.219 + doStringCommand("DRIVER LINKSPEED", function(reply) { 1.220 + if (reply) { 1.221 + reply = reply.split(" ")[1] | 0; // Format: LinkSpeed XX 1.222 + } 1.223 + callback(reply); 1.224 + }); 1.225 + }; 1.226 + 1.227 + command.getConnectionInfoICS = function (callback) { 1.228 + doStringCommand("SIGNAL_POLL", function(reply) { 1.229 + if (!reply) { 1.230 + callback(null); 1.231 + return; 1.232 + } 1.233 + 1.234 + let rval = {}; 1.235 + var lines = reply.split("\n"); 1.236 + for (let i = 0; i < lines.length; ++i) { 1.237 + let [key, value] = lines[i].split("="); 1.238 + switch (key.toUpperCase()) { 1.239 + case "RSSI": 1.240 + rval.rssi = value | 0; 1.241 + break; 1.242 + case "LINKSPEED": 1.243 + rval.linkspeed = value | 0; 1.244 + break; 1.245 + default: 1.246 + // Ignore. 1.247 + } 1.248 + } 1.249 + 1.250 + callback(rval); 1.251 + }); 1.252 + }; 1.253 + 1.254 + command.getMacAddress = function (callback) { 1.255 + doStringCommand("DRIVER MACADDR", function(reply) { 1.256 + if (reply) { 1.257 + reply = reply.split(" ")[2]; // Format: Macaddr = XX.XX.XX.XX.XX.XX 1.258 + } 1.259 + callback(reply); 1.260 + }); 1.261 + }; 1.262 + 1.263 + command.setPowerModeICS = function (mode, callback) { 1.264 + doBooleanCommand("DRIVER POWERMODE " + (mode === "AUTO" ? 0 : 1), "OK", callback); 1.265 + }; 1.266 + 1.267 + command.setPowerModeJB = function (mode, callback) { 1.268 + doBooleanCommand("SET ps " + (mode === "AUTO" ? 1 : 0), "OK", callback); 1.269 + }; 1.270 + 1.271 + command.getPowerMode = function (callback) { 1.272 + doStringCommand("DRIVER GETPOWER", function(reply) { 1.273 + if (reply) { 1.274 + reply = (reply.split()[2]|0); // Format: powermode = XX 1.275 + } 1.276 + callback(reply); 1.277 + }); 1.278 + }; 1.279 + 1.280 + command.setNumAllowedChannels = function (numChannels, callback) { 1.281 + doBooleanCommand("DRIVER SCAN-CHANNELS " + numChannels, "OK", callback); 1.282 + }; 1.283 + 1.284 + command.getNumAllowedChannels = function (callback) { 1.285 + doStringCommand("DRIVER SCAN-CHANNELS", function(reply) { 1.286 + if (reply) { 1.287 + reply = (reply.split()[2]|0); // Format: Scan-Channels = X 1.288 + } 1.289 + callback(reply); 1.290 + }); 1.291 + }; 1.292 + 1.293 + command.setBluetoothCoexistenceMode = function (mode, callback) { 1.294 + doBooleanCommand("DRIVER BTCOEXMODE " + mode, "OK", callback); 1.295 + }; 1.296 + 1.297 + command.setBluetoothCoexistenceScanMode = function (mode, callback) { 1.298 + doBooleanCommand("DRIVER BTCOEXSCAN-" + (mode ? "START" : "STOP"), 1.299 + "OK", callback); 1.300 + }; 1.301 + 1.302 + command.saveConfig = function (callback) { 1.303 + // Make sure we never write out a value for AP_SCAN other than 1. 1.304 + doBooleanCommand("AP_SCAN 1", "OK", function(ok) { 1.305 + doBooleanCommand("SAVE_CONFIG", "OK", callback); 1.306 + }); 1.307 + }; 1.308 + 1.309 + command.reloadConfig = function (callback) { 1.310 + doBooleanCommand("RECONFIGURE", "OK", callback); 1.311 + }; 1.312 + 1.313 + command.setScanResultHandling = function (mode, callback) { 1.314 + doBooleanCommand("AP_SCAN " + mode, "OK", callback); 1.315 + }; 1.316 + 1.317 + command.addToBlacklist = function (bssid, callback) { 1.318 + doBooleanCommand("BLACKLIST " + bssid, "OK", callback); 1.319 + }; 1.320 + 1.321 + command.clearBlacklist = function (callback) { 1.322 + doBooleanCommand("BLACKLIST clear", "OK", callback); 1.323 + }; 1.324 + 1.325 + command.setSuspendOptimizationsICS = function (enabled, callback) { 1.326 + doBooleanCommand("DRIVER SETSUSPENDOPT " + (enabled ? 0 : 1), 1.327 + "OK", callback); 1.328 + }; 1.329 + 1.330 + command.setSuspendOptimizationsJB = function (enabled, callback) { 1.331 + doBooleanCommand("DRIVER SETSUSPENDMODE " + (enabled ? 1 : 0), 1.332 + "OK", callback); 1.333 + }; 1.334 + 1.335 + command.connectToSupplicant = function(callback) { 1.336 + voidControlMessage("connect_to_supplicant", callback); 1.337 + }; 1.338 + 1.339 + command.closeSupplicantConnection = function(callback) { 1.340 + voidControlMessage("close_supplicant_connection", callback); 1.341 + }; 1.342 + 1.343 + command.getMacAddress = function(callback) { 1.344 + doStringCommand("DRIVER MACADDR", function(reply) { 1.345 + if (reply) { 1.346 + reply = reply.split(" ")[2]; // Format: Macaddr = XX.XX.XX.XX.XX.XX 1.347 + } 1.348 + callback(reply); 1.349 + }); 1.350 + }; 1.351 + 1.352 + command.setDeviceName = function(deviceName, callback) { 1.353 + doBooleanCommand("SET device_name " + deviceName, "OK", callback); 1.354 + }; 1.355 + 1.356 + //------------------------------------------------- 1.357 + // P2P commands. 1.358 + //------------------------------------------------- 1.359 + 1.360 + command.p2pProvDiscovery = function(address, wpsMethod, callback) { 1.361 + var command = "P2P_PROV_DISC " + address + " " + wpsMethod; 1.362 + doBooleanCommand(command, "OK", callback); 1.363 + }; 1.364 + 1.365 + command.p2pConnect = function(config, callback) { 1.366 + var command = "P2P_CONNECT " + config.address + " " + config.wpsMethodWithPin + " "; 1.367 + if (config.joinExistingGroup) { 1.368 + command += "join"; 1.369 + } else { 1.370 + command += "go_intent=" + config.goIntent; 1.371 + } 1.372 + 1.373 + debug('P2P connect command: ' + command); 1.374 + doBooleanCommand(command, "OK", callback); 1.375 + }; 1.376 + 1.377 + command.p2pGroupRemove = function(iface, callback) { 1.378 + debug("groupRemove()"); 1.379 + doBooleanCommand("P2P_GROUP_REMOVE " + iface, "OK", callback); 1.380 + }; 1.381 + 1.382 + command.p2pEnable = function(detail, callback) { 1.383 + var commandChain = ["SET device_name " + detail.deviceName, 1.384 + "SET device_type " + detail.deviceType, 1.385 + "SET config_methods " + detail.wpsMethods, 1.386 + "P2P_SET conc_pref sta", 1.387 + "P2P_FLUSH"]; 1.388 + 1.389 + doBooleanCommandChain(commandChain, callback); 1.390 + }; 1.391 + 1.392 + command.p2pDisable = function(callback) { 1.393 + doBooleanCommand("P2P_SET disabled 1", "OK", callback); 1.394 + }; 1.395 + 1.396 + command.p2pEnableScan = function(timeout, callback) { 1.397 + doBooleanCommand("P2P_FIND " + timeout, "OK", callback); 1.398 + }; 1.399 + 1.400 + command.p2pDisableScan = function(callback) { 1.401 + doBooleanCommand("P2P_STOP_FIND", "OK", callback); 1.402 + }; 1.403 + 1.404 + command.p2pGetGroupCapab = function(address, callback) { 1.405 + command.p2pPeer(address, function(reply) { 1.406 + debug('p2p_peer reply: ' + reply); 1.407 + if (!reply) { 1.408 + callback(0); 1.409 + return; 1.410 + } 1.411 + var capab = /group_capab=0x([0-9a-fA-F]+)/.exec(reply)[1]; 1.412 + if (!capab) { 1.413 + callback(0); 1.414 + } else { 1.415 + callback(parseInt(capab, 16)); 1.416 + } 1.417 + }); 1.418 + }; 1.419 + 1.420 + command.p2pPeer = function(address, callback) { 1.421 + doStringCommand("P2P_PEER " + address, callback); 1.422 + }; 1.423 + 1.424 + command.p2pGroupAdd = function(netId, callback) { 1.425 + doBooleanCommand("P2P_GROUP_ADD persistent=" + netId, callback); 1.426 + }; 1.427 + 1.428 + command.p2pReinvoke = function(netId, address, callback) { 1.429 + doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + address, "OK", callback); 1.430 + }; 1.431 + 1.432 + //---------------------------------------------------------- 1.433 + // Private stuff. 1.434 + //---------------------------------------------------------- 1.435 + 1.436 + function voidControlMessage(cmd, callback) { 1.437 + aControlMessage({ cmd: cmd, iface: aInterface }, function (data) { 1.438 + callback(data.status); 1.439 + }); 1.440 + } 1.441 + 1.442 + function doCommand(request, callback) { 1.443 + var msg = { cmd: "command", 1.444 + request: request, 1.445 + iface: aInterface }; 1.446 + 1.447 + aControlMessage(msg, callback); 1.448 + } 1.449 + 1.450 + function doIntCommand(request, callback) { 1.451 + doCommand(request, function(data) { 1.452 + callback(data.status ? -1 : (data.reply|0)); 1.453 + }); 1.454 + } 1.455 + 1.456 + function doBooleanCommand(request, expected, callback) { 1.457 + doCommand(request, function(data) { 1.458 + callback(data.status ? false : (data.reply === expected)); 1.459 + }); 1.460 + } 1.461 + 1.462 + function doStringCommand(request, callback) { 1.463 + doCommand(request, function(data) { 1.464 + callback(data.status ? null : data.reply); 1.465 + }); 1.466 + } 1.467 + 1.468 + function doBooleanCommandChain(commandChain, callback, i) { 1.469 + if (undefined === i) { 1.470 + i = 0; 1.471 + } 1.472 + 1.473 + doBooleanCommand(commandChain[i], "OK", function(ok) { 1.474 + if (!ok) { 1.475 + return callback(false); 1.476 + } 1.477 + i++; 1.478 + if (i === commandChain.length || !commandChain[i]) { 1.479 + // Reach the end or empty command. 1.480 + return callback(true); 1.481 + } 1.482 + doBooleanCommandChain(commandChain, callback, i); 1.483 + }); 1.484 + } 1.485 + 1.486 + //-------------------------------------------------- 1.487 + // Helper functions. 1.488 + //-------------------------------------------------- 1.489 + 1.490 + function stopProcess(service, process, callback) { 1.491 + var count = 0; 1.492 + var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.493 + function tick() { 1.494 + let result = libcutils.property_get(service); 1.495 + if (result === null) { 1.496 + callback(); 1.497 + return; 1.498 + } 1.499 + if (result === "stopped" || ++count >= 5) { 1.500 + // Either we succeeded or ran out of time. 1.501 + timer = null; 1.502 + callback(); 1.503 + return; 1.504 + } 1.505 + 1.506 + // Else it's still running, continue waiting. 1.507 + timer.initWithCallback(tick, 1000, Ci.nsITimer.TYPE_ONE_SHOT); 1.508 + } 1.509 + 1.510 + setProperty("ctl.stop", process, tick); 1.511 + } 1.512 + 1.513 + // Wrapper around libcutils.property_set that returns true if setting the 1.514 + // value was successful. 1.515 + // Note that the callback is not called asynchronously. 1.516 + function setProperty(key, value, callback) { 1.517 + let ok = true; 1.518 + try { 1.519 + libcutils.property_set(key, value); 1.520 + } catch(e) { 1.521 + ok = false; 1.522 + } 1.523 + callback(ok); 1.524 + } 1.525 + 1.526 + return command; 1.527 +};